example and integration tests

pull/1/head
Mike Barry 2021-08-16 21:51:49 -04:00
rodzic 6c6b9c976a
commit 9c05837fcf
26 zmienionych plików z 689 dodań i 26 usunięć

Wyświetl plik

@ -126,7 +126,7 @@
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
<filtering>false</filtering>
</testResource>
</testResources>
<pluginManagement>

Wyświetl plik

@ -55,7 +55,11 @@ public class FlatMapRunner {
}
public static FlatMapRunner create() {
return new FlatMapRunner(Arguments.fromJvmProperties());
return createWithArguments(Arguments.fromJvmProperties());
}
public static FlatMapRunner createWithArguments(Arguments arguments) {
return new FlatMapRunner(arguments);
}
private LongLongMap getLongLongMap() {

Wyświetl plik

@ -24,6 +24,13 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
return new TileCoord(encoded, x, y, z);
}
public static TileCoord aroundLngLat(double lng, double lat, int zoom) {
double factor = 1 << zoom;
double x = GeoUtils.getWorldX(lng) * factor;
double y = GeoUtils.getWorldY(lat) * factor;
return TileCoord.ofXYZ((int) Math.floor(x), (int) Math.floor(y), zoom);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -98,4 +105,11 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
Coordinate coord = getLatLon();
return "https://www.openstreetmap.org/#map=" + z + "/" + format.format(coord.y) + "/" + format.format(coord.x);
}
public Coordinate lngLatToTileCoords(double lng, double lat) {
double factor = 1 << z;
double x = GeoUtils.getWorldX(lng) * factor;
double y = GeoUtils.getWorldY(lat) * factor;
return new CoordinateXY((x - Math.floor(x)) * 256, (y - Math.floor(y)) * 256);
}
}

Wyświetl plik

@ -186,6 +186,10 @@ public final class Mbtiles implements Closeable {
return getTileStatement;
}
public byte[] getTile(TileCoord coord) {
return getTile(coord.x(), coord.y(), coord.z());
}
public byte[] getTile(int x, int y, int z) {
try {
PreparedStatement stmt = getTileStatement();

Wyświetl plik

@ -62,6 +62,7 @@ public class NaturalEarthReader extends Reader {
extracted = toOpen;
try (var zipFs = FileSystems.newFileSystem(path)) {
var zipEntry = FileUtils.walkFileSystem(zipFs)
.filter(Files::isRegularFile)
.filter(entry -> FileUtils.hasExtension(entry, "sqlite"))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No .sqlite file found inside " + path));

Wyświetl plik

@ -20,6 +20,11 @@ public class ReaderFeature extends SourceFeature {
this(latLonGeometry, tags, null, null, id);
}
public ReaderFeature(Geometry latLonGeometry, Map<String, Object> tags, long id,
List<OsmReader.RelationMember<OsmReader.RelationInfo>> relations) {
this(latLonGeometry, tags, null, null, id, relations);
}
public ReaderFeature(Geometry latLonGeometry, Map<String, Object> tags, String source, String sourceLayer,
long id) {
this(latLonGeometry, tags, source, sourceLayer, id, null);

Wyświetl plik

@ -9,7 +9,6 @@ import com.graphhopper.coll.GHIntObjectHashMap;
import com.onthegomap.flatmap.geo.GeometryException;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -571,7 +570,7 @@ public class FeatureMergeTest {
})
public void testMergeManyPolygons(String file, int x, int y, int z, int expected)
throws IOException, GeometryException {
try (var db = Mbtiles.newReadOnlyDatabase(Path.of("src", "test", "resources", file))) {
try (var db = Mbtiles.newReadOnlyDatabase(TestUtils.pathToResource(file))) {
byte[] tileData = db.getTile(x, y, z);
byte[] gunzipped = TestUtils.gunzip(tileData);
List<VectorTileEncoder.Feature> features = VectorTileEncoder.decode(gunzipped);

Wyświetl plik

@ -43,6 +43,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.DoubleStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.locationtech.jts.geom.Coordinate;
@ -673,7 +674,7 @@ public class FlatMapTest {
public void testComplexShorelinePolygons__TAKES_A_MINUTE_OR_TWO(String fileName, int expected)
throws Exception {
MultiPolygon geometry = (MultiPolygon) new WKBReader()
.read(new InputStreamInStream(Files.newInputStream(Path.of("src", "test", "resources", fileName))));
.read(new InputStreamInStream(Files.newInputStream(TestUtils.pathToResource(fileName))));
assertNotNull(geometry);
// automatically checks for self-intersections
@ -1410,4 +1411,37 @@ public class FlatMapTest {
assertEquals(11, results.tiles.size());
}
@Test
public void testFlatMapRunner(@TempDir Path tempDir) throws Exception {
Path mbtiles = tempDir.resolve("output.mbtiles");
FlatMapRunner.createWithArguments(Arguments.of("tmpdir", tempDir))
.setProfile(new Profile.NullProfile() {
@Override
public void processFeature(SourceFeature source, FeatureCollector features) {
if (source.canBePolygon() && source.hasTag("building", "yes")) {
features.polygon("building").setZoomRange(0, 14).setMinPixelSize(1);
}
}
})
.addOsmSource("osm", TestUtils.pathToResource("monaco-latest.osm.pbf"))
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
.setOutput("mbtiles", mbtiles)
.run();
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) {
int features = 0;
var tileMap = TestUtils.getTileMap(db);
for (var tile : tileMap.values()) {
for (var feature : tile) {
feature.geometry().validate();
features++;
}
}
assertEquals(11, tileMap.size(), "num tiles");
assertEquals(2146, features, "num buildings");
}
}
}

Wyświetl plik

@ -14,12 +14,17 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.onthegomap.flatmap.config.CommonParams;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
import com.onthegomap.flatmap.geo.TileCoord;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import com.onthegomap.flatmap.reader.SourceFeature;
import com.onthegomap.flatmap.stats.Stats;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.ResultSet;
@ -31,8 +36,10 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
@ -40,6 +47,7 @@ import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
@ -299,6 +307,15 @@ public class TestUtils {
validateGeometryRecursive(g);
}
public static Path pathToResource(String resource) {
URL url = Objects.requireNonNull(TestUtils.class.getResource("/" + resource));
try {
return Path.of(url.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public interface GeometryComparision {
Geometry geom();
@ -560,7 +577,122 @@ public class TestUtils {
}
public static OsmXml readOsmXml(String s) throws IOException {
Path path = Path.of("src", "test", "resources", s);
Path path = pathToResource(s);
return xmlMapper.readValue(Files.newInputStream(path), OsmXml.class);
}
public static FeatureCollector newFeatureCollectorFor(SourceFeature feature) {
var featureCollectorFactory = new FeatureCollector.Factory(
CommonParams.defaults(),
Stats.inMemory()
);
return featureCollectorFactory.get(feature);
}
public static List<FeatureCollector.Feature> processSourceFeature(SourceFeature sourceFeature, Profile profile) {
FeatureCollector collector = newFeatureCollectorFor(sourceFeature);
profile.processFeature(sourceFeature, collector);
List<FeatureCollector.Feature> result = new ArrayList<>();
collector.forEach(result::add);
return result;
}
public static void assertContains(String substring, String string) {
if (!string.contains(substring)) {
fail("'%s' did not contain '%s'".formatted(string, substring));
}
}
public static void assertNumFeatures(Mbtiles db, String layer, int zoom, Map<String, Object> attrs,
Envelope envelope,
int expected, Class<? extends Geometry> clazz) {
try {
int num = 0;
for (var tileCoord : db.getAllTileCoords()) {
Envelope tileEnv = new Envelope();
tileEnv.expandToInclude(tileCoord.lngLatToTileCoords(envelope.getMinX(), envelope.getMinY()));
tileEnv.expandToInclude(tileCoord.lngLatToTileCoords(envelope.getMaxX(), envelope.getMaxY()));
if (tileCoord.z() == zoom) {
byte[] data = db.getTile(tileCoord);
for (var feature : VectorTileEncoder.decode(gunzip(data))) {
if (layer.equals(feature.layer()) && feature.attrs().entrySet().containsAll(attrs.entrySet())) {
Geometry geometry = feature.geometry().decode();
num += getGeometryCounts(geometry, clazz);
}
}
}
}
assertEquals(expected, num, "z%d features in %s".formatted(zoom, layer));
} catch (IOException | GeometryException e) {
fail(e);
}
}
private static int getGeometryCounts(Geometry geom, Class<? extends Geometry> clazz) {
int count = 0;
if (geom instanceof GeometryCollection geometryCollection) {
for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
count += getGeometryCounts(geometryCollection.getGeometryN(i), clazz);
}
} else if (clazz.isInstance(geom)) {
count = 1;
}
return count;
}
public static void assertFeatureNear(Mbtiles db, String layer, Map<String, Object> attrs, double lng, double lat,
int minzoom, int maxzoom) {
try {
List<String> failures = new ArrayList<>();
outer:
for (int zoom = 0; zoom <= 14; zoom++) {
boolean shouldFind = zoom >= minzoom && zoom <= maxzoom;
var coord = TileCoord.aroundLngLat(lng, lat, zoom);
Geometry tilePoint = GeoUtils.point(coord.lngLatToTileCoords(lng, lat));
byte[] tile = db.getTile(coord);
List<VectorTileEncoder.Feature> features = tile == null ? List.of() : VectorTileEncoder.decode(gunzip(tile));
Set<String> containedInLayers = new TreeSet<>();
Set<String> containedInLayerFeatures = new TreeSet<>();
for (var feature : features) {
if (feature.geometry().decode().isWithinDistance(tilePoint, 2)) {
containedInLayers.add(feature.layer());
if (layer.equals(feature.layer())) {
Map<String, Object> tags = feature.attrs();
containedInLayerFeatures.add(tags.toString());
if (tags.entrySet().containsAll(attrs.entrySet())) {
// found a match
if (!shouldFind) {
failures.add("z%d found feature but should not have".formatted(zoom));
}
continue outer;
}
}
}
}
// not found
if (shouldFind) {
if (containedInLayers.isEmpty()) {
failures.add("z%d no features were found in any layer".formatted(zoom));
} else if (!containedInLayers.contains(layer)) {
failures.add("z%d features found in %s but not %s".formatted(
zoom, containedInLayers, layer
));
} else {
failures.add("z%d features found in %s but had wrong tags: %s".formatted(
zoom, layer, containedInLayerFeatures.stream().collect(Collectors.joining("\n", "\n", "")))
);
}
}
}
if (!failures.isEmpty()) {
fail(String.join("\n", failures));
}
} catch (GeometryException | IOException e) {
fail(e);
}
}
}

Wyświetl plik

@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.stats.Stats;
import com.onthegomap.flatmap.worker.WorkerPipeline;
@ -24,10 +25,10 @@ public class NaturalEarthReaderTest {
@ValueSource(strings = {"natural_earth_vector.sqlite", "natural_earth_vector.sqlite.zip"})
@Timeout(30)
public void testReadNaturalEarth(String filename, @TempDir Path tempDir) {
var path = Path.of("src", "test", "resources", filename);
var path = TestUtils.pathToResource(filename);
try (var reader = new NaturalEarthReader("test", path, tempDir, new Profile.NullProfile(), Stats.inMemory())) {
for (int i = 1; i <= 2; i++) {
assertEquals(19, reader.getCount(), "iter " + i);
assertEquals(7_679, reader.getCount(), "iter " + i);
List<Geometry> points = new ArrayList<>();
WorkerPipeline.start("test", Stats.inMemory())
@ -35,10 +36,11 @@ public class NaturalEarthReaderTest {
.addBuffer("reader_queue", 100, 1)
.sinkToConsumer("counter", 1, elem -> {
Object elevation = elem.getTag("elevation");
assertTrue(elevation instanceof Double, Objects.toString(elevation));
assertEquals("test", elem.getSource());
assertEquals("ne_110m_geography_regions_elevation_points", elem.getSourceLayer());
points.add(elem.latLonGeometry());
if ("ne_110m_geography_regions_elevation_points".equals(elem.getSourceLayer())) {
assertTrue(elevation instanceof Double, Objects.toString(elevation));
assertEquals("test", elem.getSource());
points.add(elem.latLonGeometry());
}
}).await();
assertEquals(19, points.size());
var gc = GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0]));

Wyświetl plik

@ -5,10 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.stats.Stats;
import com.onthegomap.flatmap.worker.WorkerPipeline;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
@ -20,7 +20,7 @@ public class ShapefileReaderTest {
private ShapefileReader reader = new ShapefileReader(
"test",
Path.of("src", "test", "resources", "shapefile.zip"),
TestUtils.pathToResource("shapefile.zip"),
new Profile.NullProfile(),
Stats.inMemory()
);

Wyświetl plik

@ -3,9 +3,9 @@ package com.onthegomap.flatmap.reader.osm;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.graphhopper.reader.ReaderElement;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.stats.Stats;
import com.onthegomap.flatmap.worker.WorkerPipeline;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@ -13,7 +13,7 @@ import org.locationtech.jts.geom.Envelope;
public class OsmInputFileTest {
private OsmInputFile file = new OsmInputFile(Path.of("src", "test", "resources", "monaco-latest.osm.pbf"));
private OsmInputFile file = new OsmInputFile(TestUtils.pathToResource("monaco-latest.osm.pbf"));
@Test
public void testGetBounds() {

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -18,6 +18,15 @@
<artifactId>flatmap-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- To use test utilities: -->
<dependency>
<groupId>com.onthegomap</groupId>
<artifactId>flatmap-core</artifactId>
<version>${project.parent.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>

Wyświetl plik

@ -5,6 +5,7 @@ import com.onthegomap.flatmap.FeatureMerge;
import com.onthegomap.flatmap.FlatMapRunner;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.VectorTileEncoder;
import com.onthegomap.flatmap.config.Arguments;
import com.onthegomap.flatmap.geo.GeometryException;
import com.onthegomap.flatmap.reader.SourceFeature;
import com.onthegomap.flatmap.reader.osm.OsmElement;
@ -161,9 +162,13 @@ public class BikeRouteOverlay implements Profile {
* Main entrypoint for this example program
*/
public static void main(String[] args) throws Exception {
run(Arguments.fromJvmProperties());
}
static void run(Arguments args) throws Exception {
// FlatMapRunner is a convenience wrapper around the lower-level API for the most common use-cases.
// See ToiletsOverlayLowLevelApi for an example using the lower-level API
FlatMapRunner.create()
FlatMapRunner.createWithArguments(args)
.setProfile(new BikeRouteOverlay())
// override this default with -Dosm="path/to/data.osm.pbf"
.addOsmSource("osm", Path.of("data", "sources", "north-america_us_massachusetts.pbf"))

Wyświetl plik

@ -3,6 +3,7 @@ package com.onthegomap.flatmap.examples;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.FlatMapRunner;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.config.Arguments;
import com.onthegomap.flatmap.reader.SourceFeature;
import com.onthegomap.flatmap.util.ZoomFunction;
import java.nio.file.Path;
@ -89,9 +90,13 @@ public class ToiletsOverlay implements Profile {
* Main entrypoint for the example program
*/
public static void main(String[] args) throws Exception {
run(Arguments.fromJvmProperties());
}
static void run(Arguments args) throws Exception {
// FlatMapRunner is a convenience wrapper around the lower-level API for the most common use-cases.
// See ToiletsOverlayLowLevelApi for an example using this same profile but the lower-level API
FlatMapRunner.create()
FlatMapRunner.createWithArguments(args)
.setProfile(new ToiletsOverlay())
// override this default with -Dosm="path/to/data.osm.pbf"
.addOsmSource("osm", Path.of("data", "sources", "north-america_us_massachusetts.pbf"))

Wyświetl plik

@ -12,6 +12,7 @@ import com.onthegomap.flatmap.reader.osm.OsmInputFile;
import com.onthegomap.flatmap.reader.osm.OsmReader;
import com.onthegomap.flatmap.stats.Stats;
import com.onthegomap.flatmap.util.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.slf4j.Logger;
@ -35,15 +36,19 @@ public class ToiletsOverlayLowLevelApi {
private static final Logger LOGGER = LoggerFactory.getLogger(ToiletsOverlayLowLevelApi.class);
public static void main(String[] args) throws Exception {
run(
Path.of("data", "sources", "north-america_us_massachusetts.pbf"),
Path.of("data", "tmp"),
Path.of("data", "toilets.mbtiles")
);
}
static void run(Path input, Path tmpDir, Path output) throws IOException {
// Collect runtime statistics in memory. Alternatively you can push them to
// prometheus using a push gateway (see https://github.com/prometheus/pushgateway)
Stats stats = Stats.inMemory();
Profile profile = new ToiletsOverlay();
Path input = Path.of("data", "sources", "north-america_us_massachusetts.pbf");
Path tmpDir = Path.of("data", "tmp");
Path output = Path.of("data", "toilets.mbtiles");
// use default settings, but allow overrides from -Dkey=value jvm arguments
CommonParams config = CommonParams.from(Arguments.fromJvmProperties());

Wyświetl plik

@ -0,0 +1,120 @@
package com.onthegomap.flatmap.examples;
import static com.onthegomap.flatmap.TestUtils.assertContains;
import static com.onthegomap.flatmap.TestUtils.newLineString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.VectorTileEncoder;
import com.onthegomap.flatmap.config.Arguments;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import com.onthegomap.flatmap.reader.ReaderFeature;
import com.onthegomap.flatmap.reader.osm.OsmElement;
import com.onthegomap.flatmap.reader.osm.OsmReader;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.locationtech.jts.geom.LineString;
public class BikeRouteOverlayTest {
private final BikeRouteOverlay profile = new BikeRouteOverlay();
@Test
public void testSourceFeatureProcessing() {
// step 1) preprocess an example OSM relation
var relationResult = profile.preprocessOsmRelation(new OsmElement.Relation(1, Map.of(
"type", "route",
"route", "bicycle",
"name", "rail trail",
"network", "lcn",
"ref", "1"
), List.of(
new OsmElement.Relation.Member(OsmElement.Type.WAY, 2, "role")
)));
// step 2) process a way contained in that relation
var way = new ReaderFeature(
TestUtils.newLineString(
10, 20, // point 1: 10 east 20 north
30, 40 // point 2: 30 east 40 north
),
Map.of(), // way tags don't matter
2,
relationResult.stream().map(info -> new OsmReader.RelationMember<>("role", info)).toList()
);
List<FeatureCollector.Feature> mapFeatures = TestUtils.processSourceFeature(way, profile);
// verify output geometry
assertEquals(1, mapFeatures.size());
var feature = mapFeatures.get(0);
assertEquals("bicycle-route-local", feature.getLayer());
assertEquals(Map.of(
"name", "rail trail",
"ref", "1"
), feature.getAttrsAtZoom(14));
// output geometry is in world coordinates where 0,0 is top left and 1,1 is bottom right
assertEquals(0.085, feature.getGeometry().getLength(), 1e-2);
assertEquals(0, feature.getMinZoom());
assertEquals(14, feature.getMaxZoom());
}
@Test
public void testTilePostProcessingMergesConnectedLines() throws GeometryException {
String layer = "bicycle-route-local";
Map<String, Object> attrs = Map.of(
"name", "rail trail",
"ref", "1"
);
// segment 1: (0, 0) to (10, 0)
var line1 = new VectorTileEncoder.Feature(layer, 1, // id
VectorTileEncoder.encodeGeometry(newLineString(0, 0, 10, 0)),
attrs
);
// segment 2: (10, 0) to (20, 0)
var line2 = new VectorTileEncoder.Feature(layer, 2, // id
VectorTileEncoder.encodeGeometry(newLineString(10, 0, 20, 0)),
attrs
);
// merged: (0, 0) to (20, 0)
var connected = new VectorTileEncoder.Feature(layer, 1, // id
VectorTileEncoder.encodeGeometry(newLineString(0, 0, 20, 0)),
attrs
);
// ensure that 2 touching linestrings with same tags are merged
assertEquals(
List.of(connected),
profile.postProcessLayerFeatures(layer, 14, List.of(line1, line2))
);
}
@Test
public void integrationTest(@TempDir Path tmpDir) throws Exception {
Path dbPath = tmpDir.resolve("output.mbtiles");
BikeRouteOverlay.run(Arguments.of(
// Override input source locations
"osm", TestUtils.pathToResource("monaco-latest.osm.pbf"),
// Override temp dir location
"tmp", tmpDir.toString(),
// Override output location
"mbtiles", dbPath.toString()
));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("Bike Paths Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution"));
TestUtils
.assertNumFeatures(mbtiles, "bicycle-route-international", 14, Map.of(
"name", "EuroVelo 8 - Mediterranean Route - part Monaco",
"ref", "EV8"
), GeoUtils.WORLD_LAT_LON_BOUNDS, 25, LineString.class);
}
}
}

Wyświetl plik

@ -0,0 +1,38 @@
package com.onthegomap.flatmap.examples;
import static com.onthegomap.flatmap.TestUtils.assertContains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.locationtech.jts.geom.Point;
public class ToiletsOverlayLowLevelApiTest {
@Test
public void integrationTest(@TempDir Path tmpDir) throws IOException {
Path dbPath = tmpDir.resolve("output.mbtiles");
ToiletsOverlayLowLevelApi.run(
// Override input source locations
TestUtils.pathToResource("monaco-latest.osm.pbf"),
// Override temp dir location
tmpDir,
// Override output location
dbPath
);
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("Toilets Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution"));
TestUtils.assertNumFeatures(mbtiles, "toilets", 14, Map.of(), GeoUtils.WORLD_LAT_LON_BOUNDS,
34, Point.class);
}
}
}

Wyświetl plik

@ -0,0 +1,73 @@
package com.onthegomap.flatmap.examples;
import static com.onthegomap.flatmap.TestUtils.assertContains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.config.Arguments;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import com.onthegomap.flatmap.reader.ReaderFeature;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.locationtech.jts.geom.Point;
public class ToiletsProfileTest {
private final ToiletsOverlay profile = new ToiletsOverlay();
@Test
public void testSourceFeatureProcessing() {
var node = new ReaderFeature(
TestUtils.newPoint(1, 2),
Map.of("amenity", "toilets"),
1 // node ID
);
List<FeatureCollector.Feature> mapFeatures = TestUtils.processSourceFeature(node, profile);
// verify output feature
assertEquals(1, mapFeatures.size());
var feature = mapFeatures.get(0);
assertEquals("toilets", feature.getLayer());
// no attributes
assertEquals(Map.of(), feature.getAttrsAtZoom(14));
assertEquals(0, feature.getMinZoom());
assertEquals(14, feature.getMaxZoom());
assertEquals(1, feature.getZorder());
// at z12 - use label grid to limit output
assertEquals(4, feature.getLabelGridLimitAtZoom(12));
assertEquals(32, feature.getLabelGridPixelSizeAtZoom(12));
assertEquals(32, feature.getBufferPixelsAtZoom(12));
// at z13 - no label grid (0 disables filtering)
assertEquals(0, feature.getLabelGridLimitAtZoom(13));
assertEquals(0, feature.getLabelGridPixelSizeAtZoom(13));
assertEquals(4, feature.getBufferPixelsAtZoom(13));
}
@Test
public void integrationTest(@TempDir Path tmpDir) throws Exception {
Path dbPath = tmpDir.resolve("output.mbtiles");
ToiletsOverlay.run(Arguments.of(
// Override input source locations
"osm", TestUtils.pathToResource("monaco-latest.osm.pbf"),
// Override temp dir location
"tmp", tmpDir.toString(),
// Override output location
"mbtiles", dbPath.toString()
));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("Toilets Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution"));
TestUtils.assertNumFeatures(mbtiles, "toilets", 14, Map.of(), GeoUtils.WORLD_LAT_LON_BOUNDS,
36, Point.class);
}
}
}

Wyświetl plik

@ -20,9 +20,11 @@ public class OpenMapTilesMain {
private static final Path sourcesDir = Path.of("data", "sources");
public static void main(String[] args) throws Exception {
run(Arguments.fromJvmProperties());
}
FlatMapRunner runner = FlatMapRunner.create();
static void run(Arguments arguments) throws Exception {
FlatMapRunner runner = FlatMapRunner.createWithArguments(arguments);
runner
.setProfile(createProfileWithWikidataTranslations(runner))
.addShapefileSource("EPSG:3857", OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE,

Wyświetl plik

@ -103,7 +103,7 @@ public class WaterName implements OpenMapTilesSchema.WaterName,
public void processLakeCenterline(SourceFeature feature, FeatureCollector features) {
long osmId = Math.abs(feature.getLong("OSM_ID"));
if (osmId == 0L) {
LOGGER.warn("Bad lake centerline: " + feature);
LOGGER.warn("Bad lake centerline. Tags: " + feature.tags());
} else {
try {
// multiple threads call this concurrently

Wyświetl plik

@ -11,7 +11,7 @@ import com.onthegomap.flatmap.stats.Stats;
import java.util.List;
import org.junit.jupiter.api.Test;
public class OpenMaptilesProfileTest {
public class OpenMapTilesProfileTest {
private final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations();
private final Translations translations = Translations.defaultProvider(List.of("en", "es", "de"))

Wyświetl plik

@ -0,0 +1,211 @@
package com.onthegomap.flatmap.openmaptiles;
import static com.onthegomap.flatmap.TestUtils.assertContains;
import static com.onthegomap.flatmap.TestUtils.assertFeatureNear;
import static com.onthegomap.flatmap.TestUtils.gunzip;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.VectorTileEncoder;
import com.onthegomap.flatmap.config.Arguments;
import com.onthegomap.flatmap.mbiles.Mbtiles;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
/**
* End-to-end tests for OpenMapTiles map generation.
*/
public class OpenMapTilesTest {
@TempDir
static Path tmpDir;
private static Mbtiles mbtiles;
private static final Envelope monacoBounds = new Envelope(7.40921, 7.44864, 43.72335, 43.75169);
@BeforeAll
public static void runFlatmap() throws Exception {
Path dbPath = tmpDir.resolve("output.mbtiles");
OpenMapTilesMain.run(Arguments.of(
// Override input source locations
"osm", TestUtils.pathToResource("monaco-latest.osm.pbf"),
"natural_earth", TestUtils.pathToResource("natural_earth_vector.sqlite"),
"lake_centerlines", TestUtils.pathToResource("natural_earth_vector.sqlite"),
"water_polygons", TestUtils.pathToResource("water-polygons-split-3857.zip"),
// no centerlines in monaco - so fake it out with an empty source
"lake_centerlines", TestUtils.pathToResource("water-polygons-split-3857.zip"),
// Override temp dir location
"tmp", tmpDir.toString(),
// Override output location
"mbtiles", dbPath.toString()
));
mbtiles = Mbtiles.newReadOnlyDatabase(dbPath);
}
@AfterAll
public static void close() throws IOException {
mbtiles.close();
}
@Test
public void testMetadata() {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("OpenMapTiles", metadata.get("name"));
assertEquals("0", metadata.get("minzoom"));
assertEquals("14", metadata.get("maxzoom"));
assertEquals("baselayer", metadata.get("type"));
assertEquals("pbf", metadata.get("format"));
assertEquals("7.40921,43.72335,7.44864,43.75169", metadata.get("bounds"));
assertEquals("7.42892,43.73752,14", metadata.get("center"));
assertContains("openmaptiles.org", metadata.get("description"));
assertContains("openmaptiles.org", metadata.get("attribution"));
assertContains("www.openstreetmap.org/copyright", metadata.get("attribution"));
}
@Test
public void ensureValidGeometries() throws Exception {
Set<Mbtiles.TileEntry> parsedTiles = TestUtils.getAllTiles(mbtiles);
for (var tileEntry : parsedTiles) {
var decoded = VectorTileEncoder.decode(gunzip(tileEntry.bytes()));
for (VectorTileEncoder.Feature feature : decoded) {
TestUtils.validateGeometry(feature.geometry().decode());
}
}
}
@Test
public void testContainsOceanPolyons() {
assertFeatureNear(mbtiles, "water", Map.of(
"class", "ocean"
), 7.4484, 43.70783, 0, 14);
}
@Test
public void testContainsCountryName() {
assertFeatureNear(mbtiles, "place", Map.of(
"class", "country",
"iso_a2", "MC",
"name", "Monaco"
), 7.42769, 43.73235, 2, 14);
}
@Test
public void testContainsSuburb() {
assertFeatureNear(mbtiles, "place", Map.of(
"name", "Les Moneghetti",
"class", "suburb"
), 7.41746, 43.73638, 11, 14);
}
@Test
public void testContainsBuildings() {
assertFeatureNear(mbtiles, "building", Map.of(), 7.41919, 43.73401, 13, 14);
assertNumFeatures("building", Map.of(), 14, 1316, Polygon.class);
assertNumFeatures("building", Map.of(), 13, 196, Polygon.class);
}
@Test
public void testContainsHousenumber() {
assertFeatureNear(mbtiles, "housenumber", Map.of(
"housenumber", "27"
), 7.42117, 43.73652, 14, 14);
assertNumFeatures("housenumber", Map.of(), 14, 274, Point.class);
}
@Test
public void testBoundary() {
assertFeatureNear(mbtiles, "boundary", Map.of(
"admin_level", 2L,
"maritime", 1L,
"disputed", 0L
), 7.41884, 43.72396, 4, 14);
}
@Test
public void testAeroway() {
assertNumFeatures("aeroway", Map.of(
"class", "heliport"
), 14, 1, Polygon.class);
assertNumFeatures("aeroway", Map.of(
"class", "helipad"
), 14, 11, Polygon.class);
}
@Test
public void testLandcover() {
assertNumFeatures("landcover", Map.of(
"class", "grass",
"subclass", "park"
), 14, 20, Polygon.class);
assertNumFeatures("landcover", Map.of(
"class", "grass",
"subclass", "garden"
), 14, 33, Polygon.class);
}
@Test
public void testPoi() {
assertNumFeatures("poi", Map.of(
"class", "restaurant",
"subclass", "restaurant"
), 14, 217, Point.class);
assertNumFeatures("poi", Map.of(
"class", "art_gallery",
"subclass", "artwork"
), 14, 132, Point.class);
}
@Test
public void testLanduse() {
assertNumFeatures("landuse", Map.of(
"class", "residential"
), 14, 8, Polygon.class);
assertNumFeatures("landuse", Map.of(
"class", "hospital"
), 14, 4, Polygon.class);
}
@Test
public void testTransportation() {
assertNumFeatures("transportation", Map.of(
"class", "path",
"subclass", "footway"
), 14, 909, LineString.class);
assertNumFeatures("transportation", Map.of(
"class", "primary"
), 14, 170, LineString.class);
}
@Test
public void testTransportationName() {
assertNumFeatures("transportation_name", Map.of(
"name", "Boulevard du Larvotto",
"class", "primary"
), 14, 22, LineString.class);
}
@Test
public void testWaterway() {
assertNumFeatures("waterway", Map.of(
"class", "stream"
), 14, 6, LineString.class);
}
private static void assertNumFeatures(String layer, Map<String, Object> attrs, int zoom,
int expected, Class<? extends Geometry> clazz) {
TestUtils.assertNumFeatures(mbtiles, layer, zoom, attrs, monacoBounds, expected, clazz);
}
}