Skip to content

Commit

Permalink
Add support for geojson (#1147)
Browse files Browse the repository at this point in the history
  • Loading branch information
HarelM authored Jan 17, 2025
1 parent 59c3abd commit 5588fca
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 2 deletions.
10 changes: 10 additions & 0 deletions planetiler-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson-store</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.onthegomap.planetiler.collection.LongLongMultimap;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.reader.GeoJsonReader;
import com.onthegomap.planetiler.reader.GeoPackageReader;
import com.onthegomap.planetiler.reader.NaturalEarthReader;
import com.onthegomap.planetiler.reader.ShapefileReader;
Expand Down Expand Up @@ -435,6 +436,30 @@ public Planetiler addGeoPackageSource(String name, Path defaultPath, String defa
return addGeoPackageSource(null, name, defaultPath, defaultUrl);
}

/**
* Adds a new GeoJSON source that will be processed when {@link #run()} is called.
* <p>
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
* {@code defaultUrl}.
* <p>
* To override the location of the {@code geojson} file, set {@code name_path=newpath.geojson} in the arguments and to
* override the download URL set {@code name_url=http://url/of/file.geojson}.
*
* @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code
* name_url} argument is not set
* @return this runner instance for chaining
* @see GeoJsonReader
* @see Downloader
*/
public Planetiler addGeoJsonSource(String name, Path defaultPath, String defaultUrl) {
Path path = getPath(name, "geojson", defaultPath, defaultUrl);
return addStage(name, "Process features in " + path,
ifSourceUsed(name,
() -> GeoJsonReader.process(name, List.of(path), featureGroup, config, profile, stats)));
}

/**
* Adds a new Natural Earth sqlite file source that will be processed when {@link #run()} is called.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.onthegomap.planetiler.reader;

import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.collection.FeatureGroup;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.stats.Stats;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import org.geotools.data.geojson.store.GeoJSONDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.locationtech.jts.geom.Geometry;

/**
* Utility that reads {@link SourceFeature SourceFeatures} from the vector geometries contained in a GeoJSON file.
*/
public class GeoJsonReader extends SimpleReader<SimpleFeature> {

private final GeoJSONDataStore store;
private final String layer;

GeoJsonReader(String sourceName, Path input) {
super(sourceName);
store = new GeoJSONDataStore(input.toFile());
layer = input.getFileName().toString().replaceFirst("\\.[^.]+$", ""); // remove file extention.
}

/**
* Renders map features for all elements from an GeoJSON on the mapping logic defined in {@code
* profile}.
*
* @param sourceName string ID for this reader to use in logs and stats
* @param sourcePaths paths to the {@code .geojson} files on disk
* @param writer consumer for rendered features
* @param config user-defined parameters controlling number of threads and log interval
* @param profile logic that defines what map features to emit for each source feature
* @param stats to keep track of counters and timings
* @throws IllegalArgumentException if a problem occurs reading the input file
*/
public static void process(String sourceName, List<Path> sourcePaths, FeatureGroup writer, PlanetilerConfig config,
Profile profile, Stats stats) {
SourceFeatureProcessor.processFiles(
sourceName,
sourcePaths,
path -> new GeoJsonReader(sourceName, path),
writer, config, profile, stats
);
}

@Override
public void close() throws IOException {
store.dispose();
}

@Override
public long getFeatureCount() {
String typeName;
try {
typeName = store.getTypeNames()[0];
SimpleFeatureCollection features = store.getFeatureSource(typeName).getFeatures();
return Long.valueOf(features.size());
} catch (IOException e) {
return 0;
}
}

@Override
public void readFeatures(Consumer<SimpleFeature> next) throws Exception {
long id = 0;
String typeName = store.getTypeNames()[0];
SimpleFeatureCollection features = store.getFeatureSource(typeName).getFeatures();

try (var iter = features.features()) {
while (iter.hasNext()) {
var feature = iter.next();
var properties = feature.getProperties();
SimpleFeature simpleFeature = SimpleFeature.create((Geometry) feature.getDefaultGeometry(), HashMap.newHashMap(properties.size()),
sourceName, layer, id++);
properties.forEach(property -> simpleFeature.setTag(property.getName().toString(), property.getValue()));
next.accept(simpleFeature);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.onthegomap.planetiler.TestUtils.OsmXml;
import com.onthegomap.planetiler.archive.ReadableTileArchive;
import com.onthegomap.planetiler.archive.TileArchiveConfig;
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
Expand Down Expand Up @@ -2194,6 +2195,7 @@ public void processFeature(SourceFeature source, FeatureCollector features) {
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
.addGeoJsonSource("geojson", TestUtils.pathToResource("geojson.geojson"), null)
.setOutput(outputUri)
.run();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.onthegomap.planetiler.reader;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.onthegomap.planetiler.TestUtils;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.stats.Stats;
import com.onthegomap.planetiler.worker.WorkerPipeline;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Geometry;

public class GeoJsonReaderTest {
@Test
void testReadGeoJson() throws IOException {
Path path = TestUtils.pathToResource("geojson.geojson");
try (var reader = new GeoJsonReader("test", path)) {
assertEquals(3, reader.getFeatureCount());
List<Geometry> points = new CopyOnWriteArrayList<>();
List<String> names = new CopyOnWriteArrayList<>();
WorkerPipeline.start("test", Stats.inMemory())
.fromGenerator("source", reader::readFeatures)
.addBuffer("reader_queue", 100, 1)
.sinkToConsumer("counter", 1, elem -> {
assertTrue(elem.getTag("name") instanceof String);
assertEquals("test", elem.getSource());
assertEquals("geojson", elem.getSourceLayer());
points.add(elem.latLonGeometry());
names.add(elem.getTag("name").toString());
}).await();
assertEquals(3, points.size());
assertTrue(names.contains("line"));
assertTrue(names.contains("point"));
assertTrue(names.contains("polygon"));
var gc = GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0]));
var centroid = gc.getCentroid();
assertEquals(100.5, centroid.getX(), 1e-5);
assertEquals(0.5, centroid.getY(), 1e-5);
}
}
}

37 changes: 37 additions & 0 deletions planetiler-core/src/test/resources/geojson.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": {
"name": "point"
}
}, {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]
},
"properties": {
"name": "line",
"prop1": 0.0
}
}, {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
[100.0, 1.0], [100.0, 0.0]
]
]
},
"properties": {
"name": "polygon"
}
}]
}
3 changes: 2 additions & 1 deletion planetiler-custommap/planetiler.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"enum": [
"osm",
"shapefile",
"geopackage"
"geopackage",
"geojson"
]
},
"url": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ private static void configureSource(Planetiler planetiler, Path sourcesDir, Sour
case OSM -> planetiler.addOsmSource(source.id(), localPath, source.url());
case SHAPEFILE -> planetiler.addShapefileSource(projection, source.id(), localPath, source.url());
case GEOPACKAGE -> planetiler.addGeoPackageSource(projection, source.id(), localPath, source.url());
case GEOJSON -> planetiler.addGeoJsonSource(source.id(), localPath, source.url());
default -> throw new IllegalArgumentException("Unhandled source type for " + source.id() + ": " + sourceType);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public enum DataSourceType {
@JsonProperty("shapefile")
SHAPEFILE,
@JsonProperty("geopackage")
GEOPACKAGE
GEOPACKAGE,
@JsonProperty("geojson")
GEOJSON
}

0 comments on commit 5588fca

Please sign in to comment.