From c39100e5d04778273e91fa114bd939d255e5ff64 Mon Sep 17 00:00:00 2001 From: Trevor Gerhardt Date: Thu, 26 Oct 2023 17:44:15 +0800 Subject: [PATCH] Create opportunity datasets from GeoJSON Closely mirrors the way Shapefiles are converted into grids. With GeoTools it is quite simple. It's possible to abstract common creation code between the two types but may not be necessary unless more types are added. --- .../OpportunityDatasetController.java | 3 + .../java/com/conveyal/r5/analyst/Grid.java | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java b/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java index b1afcfc05..4700caafa 100644 --- a/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java +++ b/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java @@ -358,6 +358,9 @@ private OpportunityDatasetUploadStatus createOpportunityDataset(Request req, Res } else if (uploadFormat == FileStorageFormat.SHP) { LOG.info("Detected opportunity dataset stored as ESRI shapefile."); pointsets.addAll(createGridsFromShapefile(fileItems, zoom, status)); + } else if (uploadFormat == FileStorageFormat.GEOJSON) { + LOG.info("Detected opportunity dataset stored as GeoJSON."); + pointsets.addAll(Grid.fromGeoJson(fileItems.get(0).getInputStream(), zoom, status)); } else if (uploadFormat == FileStorageFormat.CSV) { LOG.info("Detected opportunity dataset stored as CSV"); // Create a grid even when user has requested a freeform pointset so we have something to visualize. diff --git a/src/main/java/com/conveyal/r5/analyst/Grid.java b/src/main/java/com/conveyal/r5/analyst/Grid.java index ad9faf1ed..4469c5a37 100644 --- a/src/main/java/com/conveyal/r5/analyst/Grid.java +++ b/src/main/java/com/conveyal/r5/analyst/Grid.java @@ -16,6 +16,9 @@ import org.geotools.data.FileDataStore; import org.geotools.data.FileDataStoreFinder; import org.geotools.data.Transaction; +import org.geotools.data.geojson.GeoJSONReader; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.gce.geotiff.GeoTiffFormat; import org.geotools.gce.geotiff.GeoTiffWriteParams; @@ -753,6 +756,70 @@ public static List fromShapefile (File shapefile, int zoom, ProgressListen return new ArrayList<>(grids.values()); } + /** + * Take an `InputStream` containing GeoJson Features and turn it into an opportunity grid. + */ + public static List fromGeoJson (InputStream geoJsonInputStream, int zoom, ProgressListener progressListener) + throws IOException { + GeoJSONReader reader = new GeoJSONReader(geoJsonInputStream); + SimpleFeatureCollection features = reader.getFeatures(); + Envelope envelope = features.getBounds(); + + checkWgsEnvelopeSize(envelope, "Shapefile"); + WebMercatorExtents extents = WebMercatorExtents.forWgsEnvelope(envelope, zoom); + + int total = features.size(); + if (progressListener != null) { + progressListener.setTotalItems(total); + } + + AtomicInteger count = new AtomicInteger(0); + HashMap grids = new HashMap<>(); + + SimpleFeatureIterator featureIterator = features.features(); + while (featureIterator.hasNext()) { + SimpleFeature feature = featureIterator.next(); + Geometry geom = (Geometry) feature.getDefaultGeometry(); + + for (var p : feature.getProperties()) { + var val = p.getValue(); + + if (!(val instanceof Number)) continue; + double numericVal = ((Number) val).doubleValue(); + if (numericVal == 0) continue; + + String attributeName = p.getName().getLocalPart(); + + Grid grid = grids.get(attributeName); + if (grid == null) { + grid = new Grid(extents); + grid.name = attributeName; + grids.put(attributeName, grid); + } + + if (geom instanceof Point) { + Point point = (Point) geom; + // already in WGS 84 + grid.incrementPoint(point.getY(), point.getX(), numericVal); + } else if (geom instanceof Polygon || geom instanceof MultiPolygon) { + grid.rasterize(geom, numericVal); + } else { + throw new IllegalArgumentException("Unsupported geometry type: " + geom); + } + } + + int currentCount = count.incrementAndGet(); + if (progressListener != null) { + progressListener.setCompletedItems(currentCount); + } + if (currentCount % 10000 == 0) { + LOG.info("{} / {} features read", human(currentCount), human(total)); + } + } + reader.close(); + return new ArrayList<>(grids.values()); + } + @Override public double sumTotalOpportunities() { double totalOpportunities = 0;