From f71b880a28c0afa837081d33f7a2b1781daed409 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 | 68 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java b/src/main/java/com/conveyal/analysis/controllers/OpportunityDatasetController.java index cd2932c03..299eebe40 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 60bb526c6..375268f67 100644 --- a/src/main/java/com/conveyal/r5/analyst/Grid.java +++ b/src/main/java/com/conveyal/r5/analyst/Grid.java @@ -17,6 +17,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; @@ -64,7 +67,6 @@ import static com.conveyal.gtfs.util.Util.human; import static com.conveyal.r5.common.GeometryUtils.checkWgsEnvelopeSize; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.lang.Double.parseDouble; import static org.apache.commons.math3.util.FastMath.atan; @@ -734,6 +736,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;