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;