kopia lustrzana https://github.com/onthegomap/planetiler
Add support for geojson (#1147)
rodzic
59c3abdd4f
commit
5588fca3b2
|
@ -65,6 +65,16 @@
|
||||||
<artifactId>gt-epsg-hsql</artifactId>
|
<artifactId>gt-epsg-hsql</artifactId>
|
||||||
<version>${geotools.version}</version>
|
<version>${geotools.version}</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.xerial</groupId>
|
<groupId>org.xerial</groupId>
|
||||||
<artifactId>sqlite-jdbc</artifactId>
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.onthegomap.planetiler.collection.LongLongMap;
|
||||||
import com.onthegomap.planetiler.collection.LongLongMultimap;
|
import com.onthegomap.planetiler.collection.LongLongMultimap;
|
||||||
import com.onthegomap.planetiler.config.Arguments;
|
import com.onthegomap.planetiler.config.Arguments;
|
||||||
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
||||||
|
import com.onthegomap.planetiler.reader.GeoJsonReader;
|
||||||
import com.onthegomap.planetiler.reader.GeoPackageReader;
|
import com.onthegomap.planetiler.reader.GeoPackageReader;
|
||||||
import com.onthegomap.planetiler.reader.NaturalEarthReader;
|
import com.onthegomap.planetiler.reader.NaturalEarthReader;
|
||||||
import com.onthegomap.planetiler.reader.ShapefileReader;
|
import com.onthegomap.planetiler.reader.ShapefileReader;
|
||||||
|
@ -435,6 +436,30 @@ public class Planetiler {
|
||||||
return addGeoPackageSource(null, name, defaultPath, defaultUrl);
|
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.
|
* Adds a new Natural Earth sqlite file source that will be processed when {@link #run()} is called.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
|
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
|
||||||
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
|
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
|
||||||
|
import com.onthegomap.planetiler.TestUtils.OsmXml;
|
||||||
import com.onthegomap.planetiler.archive.ReadableTileArchive;
|
import com.onthegomap.planetiler.archive.ReadableTileArchive;
|
||||||
import com.onthegomap.planetiler.archive.TileArchiveConfig;
|
import com.onthegomap.planetiler.archive.TileArchiveConfig;
|
||||||
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
|
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
|
||||||
|
@ -2194,6 +2195,7 @@ class PlanetilerTests {
|
||||||
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
|
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
|
||||||
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
|
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
|
||||||
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
|
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
|
||||||
|
.addGeoJsonSource("geojson", TestUtils.pathToResource("geojson.geojson"), null)
|
||||||
.setOutput(outputUri)
|
.setOutput(outputUri)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -41,7 +41,8 @@
|
||||||
"enum": [
|
"enum": [
|
||||||
"osm",
|
"osm",
|
||||||
"shapefile",
|
"shapefile",
|
||||||
"geopackage"
|
"geopackage",
|
||||||
|
"geojson"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
|
|
|
@ -74,6 +74,7 @@ public class ConfiguredMapMain {
|
||||||
case OSM -> planetiler.addOsmSource(source.id(), localPath, source.url());
|
case OSM -> planetiler.addOsmSource(source.id(), localPath, source.url());
|
||||||
case SHAPEFILE -> planetiler.addShapefileSource(projection, 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 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);
|
default -> throw new IllegalArgumentException("Unhandled source type for " + source.id() + ": " + sourceType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,7 @@ public enum DataSourceType {
|
||||||
@JsonProperty("shapefile")
|
@JsonProperty("shapefile")
|
||||||
SHAPEFILE,
|
SHAPEFILE,
|
||||||
@JsonProperty("geopackage")
|
@JsonProperty("geopackage")
|
||||||
GEOPACKAGE
|
GEOPACKAGE,
|
||||||
|
@JsonProperty("geojson")
|
||||||
|
GEOJSON
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue