kopia lustrzana https://github.com/onthegomap/planetiler
boundaries
rodzic
aa0b622474
commit
eea9d4ce86
|
@ -35,7 +35,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
}
|
||||
|
||||
public Feature geometry(String layer, Geometry geometry) {
|
||||
Feature feature = new Feature(layer, geometry);
|
||||
Feature feature = new Feature(layer, geometry, source.id());
|
||||
output.add(feature);
|
||||
return feature;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_point_" + e.stat());
|
||||
LOGGER.warn("Error getting point geometry for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_centroid_" + e.stat());
|
||||
LOGGER.warn("Error getting centroid for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_line_" + e.stat());
|
||||
LOGGER.warn("Error constructing line for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_polygon_" + e.stat());
|
||||
LOGGER.warn("Error constructing polygon for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_validated_polygon_" + e.stat());
|
||||
LOGGER.warn("Error constructing validated polygon for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_point_on_surface_" + e.stat());
|
||||
LOGGER.warn("Error constructing point on surface for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM);
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,6 +118,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
private final Geometry geom;
|
||||
private final Map<String, Object> attrs = new TreeMap<>();
|
||||
private final GeometryType geometryType;
|
||||
private final long sourceId;
|
||||
private int zOrder;
|
||||
private int minzoom = config.minzoom();
|
||||
private int maxzoom = config.maxzoom();
|
||||
|
@ -134,11 +135,16 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
private double pixelToleranceAtMaxZoom = 256d / 4096;
|
||||
private ZoomFunction<Double> pixelTolerance = null;
|
||||
|
||||
private Feature(String layer, Geometry geom) {
|
||||
private Feature(String layer, Geometry geom, long sourceId) {
|
||||
this.layer = layer;
|
||||
this.geom = geom;
|
||||
this.zOrder = 0;
|
||||
this.geometryType = GeometryType.valueOf(geom);
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public long sourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public int getZorder() {
|
||||
|
|
|
@ -109,4 +109,18 @@ public class Parse {
|
|||
(Parse.boolInt(tags.get("bridge")) * 10L);
|
||||
return Math.abs(z) < 10_000 ? (int) z : 0;
|
||||
}
|
||||
|
||||
public static Double parseDoubleOrNull(Object value) {
|
||||
if (value instanceof Number num) {
|
||||
return num.doubleValue();
|
||||
}
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.graphhopper.reader.ReaderRelation;
|
|||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.read.OpenStreetMapReader;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface Profile {
|
||||
|
||||
|
@ -44,6 +45,10 @@ public interface Profile {
|
|||
return true;
|
||||
}
|
||||
|
||||
default void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next) {
|
||||
}
|
||||
|
||||
class NullProfile implements Profile {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,11 +14,11 @@ public abstract class SourceFeature {
|
|||
private final Map<String, Object> properties;
|
||||
private final String source;
|
||||
private final String sourceLayer;
|
||||
private final List<OpenStreetMapReader.RelationMember<?>> relationInfos;
|
||||
private final List<OpenStreetMapReader.RelationMember<OpenStreetMapReader.RelationInfo>> relationInfos;
|
||||
private final long id;
|
||||
|
||||
protected SourceFeature(Map<String, Object> properties, String source, String sourceLayer,
|
||||
List<OpenStreetMapReader.RelationMember<?>> relationInfos, long id) {
|
||||
List<OpenStreetMapReader.RelationMember<OpenStreetMapReader.RelationInfo>> relationInfos, long id) {
|
||||
this.properties = properties;
|
||||
this.source = source;
|
||||
this.sourceLayer = sourceLayer;
|
||||
|
|
|
@ -134,7 +134,7 @@ public class GeoUtils {
|
|||
}
|
||||
|
||||
public static double decodeWorldX(long encoded) {
|
||||
return ((double) (encoded >> 32)) / QUANTIZED_WORLD_SIZE;
|
||||
return ((double) (encoded >>> 32)) / QUANTIZED_WORLD_SIZE;
|
||||
}
|
||||
|
||||
public static double getZoomFromLonLatBounds(Envelope envelope) {
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateList;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
|
@ -169,12 +170,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
ReaderElement readerElement;
|
||||
var featureCollectors = new FeatureCollector.Factory(config, stats);
|
||||
NodeLocationProvider nodeCache = newNodeGeometryCache();
|
||||
var encoder = writer.newRenderedFeatureEncoder();
|
||||
FeatureRenderer renderer = new FeatureRenderer(
|
||||
config,
|
||||
rendered -> next.accept(encoder.apply(rendered)),
|
||||
stats
|
||||
);
|
||||
FeatureRenderer renderer = getFeatureRenderer(writer, config, next);
|
||||
while ((readerElement = prev.get()) != null) {
|
||||
SourceFeature feature = null;
|
||||
if (readerElement instanceof ReaderNode node) {
|
||||
|
@ -220,9 +216,23 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
.addTopologyStats(topology);
|
||||
|
||||
topology.awaitAndLog(logger, config.logInterval());
|
||||
|
||||
profile.finish(name,
|
||||
new FeatureCollector.Factory(config, stats),
|
||||
getFeatureRenderer(writer, config, writer));
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
private FeatureRenderer getFeatureRenderer(FeatureGroup writer, CommonParams config,
|
||||
Consumer<FeatureSort.Entry> next) {
|
||||
var encoder = writer.newRenderedFeatureEncoder();
|
||||
return new FeatureRenderer(
|
||||
config,
|
||||
rendered -> next.accept(encoder.apply(rendered)),
|
||||
stats
|
||||
);
|
||||
}
|
||||
|
||||
SourceFeature processRelationPass2(ReaderRelation rel, NodeLocationProvider nodeCache) {
|
||||
return rel.hasTag("type", "multipolygon") ? new MultipolygonSourceFeature(rel, nodeCache) : null;
|
||||
}
|
||||
|
@ -237,7 +247,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
boolean closed = nodes.size() > 1 && nodes.get(0) == nodes.get(nodes.size() - 1);
|
||||
String area = way.getTag("area");
|
||||
LongArrayList relationIds = wayToRelations.get(way.getId());
|
||||
List<RelationMember<?>> rels = null;
|
||||
List<RelationMember<RelationInfo>> rels = null;
|
||||
if (!relationIds.isEmpty()) {
|
||||
rels = new ArrayList<>(relationIds.size());
|
||||
for (int r = 0; r < relationIds.size(); r++) {
|
||||
|
@ -332,7 +342,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
final boolean point;
|
||||
|
||||
public ProxyFeature(ReaderElement elem, boolean point, boolean line, boolean polygon,
|
||||
List<RelationMember<?>> relationInfo) {
|
||||
List<RelationMember<RelationInfo>> relationInfo) {
|
||||
super(ReaderElementUtils.getProperties(elem), name, null, relationInfo, elem.getId());
|
||||
this.point = point;
|
||||
this.line = line;
|
||||
|
@ -407,7 +417,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
private final LongArrayList nodeIds;
|
||||
|
||||
public WaySourceFeature(ReaderWay way, boolean closed, String area, NodeLocationProvider nodeCache,
|
||||
List<RelationMember<?>> relationInfo) {
|
||||
List<RelationMember<RelationInfo>> relationInfo) {
|
||||
super(way, false,
|
||||
(!closed || !"yes".equals(area)) && way.getNodes().size() >= 2,
|
||||
(closed && !"no".equals(area)) && way.getNodes().size() >= 4,
|
||||
|
|
|
@ -15,8 +15,10 @@ package com.onthegomap.flatmap.read;
|
|||
|
||||
import com.carrotsearch.hppc.LongArrayList;
|
||||
import com.carrotsearch.hppc.LongObjectMap;
|
||||
import com.carrotsearch.hppc.ObjectIntMap;
|
||||
import com.carrotsearch.hppc.cursors.LongObjectCursor;
|
||||
import com.graphhopper.coll.GHLongObjectHashMap;
|
||||
import com.graphhopper.coll.GHObjectIntHashMap;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -35,7 +37,7 @@ import org.locationtech.jts.geom.prep.PreparedPolygon;
|
|||
* This class is ported to Java from https://github.com/omniscale/imposm3/blob/master/geom/multipolygon.go and
|
||||
* https://github.com/omniscale/imposm3/blob/master/geom/ring.go
|
||||
*/
|
||||
class OsmMultipolygon {
|
||||
public class OsmMultipolygon {
|
||||
|
||||
private static final double MIN_CLOSE_RING_GAP = 0.1 / GeoUtils.WORLD_CIRCUMFERENCE_METERS;
|
||||
private static final Comparator<Ring> BY_AREA_DESCENDING = Comparator.comparingDouble(ring -> -ring.area);
|
||||
|
@ -68,10 +70,39 @@ class OsmMultipolygon {
|
|||
}
|
||||
}
|
||||
|
||||
public static Geometry build(List<CoordinateSequence> rings) throws GeometryException {
|
||||
ObjectIntMap<Coordinate> coordToId = new GHObjectIntHashMap<>();
|
||||
List<Coordinate> idToCoord = new ArrayList<>();
|
||||
int id = 0;
|
||||
List<LongArrayList> idRings = new ArrayList<>(rings.size());
|
||||
for (CoordinateSequence coords : rings) {
|
||||
LongArrayList idRing = new LongArrayList(coords.size());
|
||||
idRings.add(idRing);
|
||||
for (Coordinate coord : coords.toCoordinateArray()) {
|
||||
if (!coordToId.containsKey(coord)) {
|
||||
coordToId.put(coord, id);
|
||||
idToCoord.add(coord);
|
||||
id++;
|
||||
}
|
||||
idRing.add(coordToId.get(coord));
|
||||
}
|
||||
}
|
||||
return build(idRings, lookupId -> idToCoord.get((int) lookupId), 0, MIN_CLOSE_RING_GAP);
|
||||
}
|
||||
|
||||
public static Geometry build(
|
||||
List<LongArrayList> rings,
|
||||
OpenStreetMapReader.NodeLocationProvider nodeCache,
|
||||
long osmId
|
||||
) throws GeometryException {
|
||||
return build(rings, nodeCache, osmId, MIN_CLOSE_RING_GAP);
|
||||
}
|
||||
|
||||
public static Geometry build(
|
||||
List<LongArrayList> rings,
|
||||
OpenStreetMapReader.NodeLocationProvider nodeCache,
|
||||
long osmId,
|
||||
double minGap
|
||||
) throws GeometryException {
|
||||
try {
|
||||
if (rings.size() == 0) {
|
||||
|
@ -83,7 +114,7 @@ class OsmMultipolygon {
|
|||
for (LongArrayList segment : idSegments) {
|
||||
int size = segment.size();
|
||||
long firstId = segment.get(0), lastId = segment.get(size - 1);
|
||||
if (firstId == lastId || tryClose(segment, nodeCache)) {
|
||||
if (firstId == lastId || tryClose(segment, nodeCache, minGap)) {
|
||||
CoordinateSequence coordinates = nodeCache.getWayGeometry(segment);
|
||||
Polygon poly = GeoUtils.JTS_FACTORY.createPolygon(coordinates);
|
||||
polygons.add(new Ring(poly));
|
||||
|
@ -142,12 +173,13 @@ class OsmMultipolygon {
|
|||
return shells;
|
||||
}
|
||||
|
||||
private static boolean tryClose(LongArrayList segment, OpenStreetMapReader.NodeLocationProvider nodeCache) {
|
||||
private static boolean tryClose(LongArrayList segment, OpenStreetMapReader.NodeLocationProvider nodeCache,
|
||||
double minGap) {
|
||||
int size = segment.size();
|
||||
long firstId = segment.get(0);
|
||||
Coordinate firstCoord = nodeCache.getCoordinate(firstId);
|
||||
Coordinate lastCoord = nodeCache.getCoordinate(segment.get(size - 1));
|
||||
if (firstCoord.distance(lastCoord) <= MIN_CLOSE_RING_GAP) {
|
||||
if (firstCoord.distance(lastCoord) <= minGap) {
|
||||
segment.set(size - 1, firstId);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import com.onthegomap.flatmap.render.FeatureRenderer;
|
|||
import com.onthegomap.flatmap.worker.Topology;
|
||||
import java.io.Closeable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.locationtech.jts.geom.Envelope;
|
||||
|
||||
public abstract class Reader implements Closeable {
|
||||
|
@ -40,12 +42,7 @@ public abstract class Reader implements Closeable {
|
|||
.<FeatureSort.Entry>addWorker("process", threads, (prev, next) -> {
|
||||
SourceFeature sourceFeature;
|
||||
var featureCollectors = new FeatureCollector.Factory(config, stats);
|
||||
var encoder = writer.newRenderedFeatureEncoder();
|
||||
FeatureRenderer renderer = new FeatureRenderer(
|
||||
config,
|
||||
rendered -> next.accept(encoder.apply(rendered)),
|
||||
stats
|
||||
);
|
||||
FeatureRenderer renderer = getFeatureRenderer(writer, config, next);
|
||||
while ((sourceFeature = prev.get()) != null) {
|
||||
featuresRead.incrementAndGet();
|
||||
FeatureCollector features = featureCollectors.get(sourceFeature);
|
||||
|
@ -71,9 +68,25 @@ public abstract class Reader implements Closeable {
|
|||
.addTopologyStats(topology);
|
||||
|
||||
topology.awaitAndLog(loggers, config.logInterval());
|
||||
|
||||
profile.finish(sourceName,
|
||||
new FeatureCollector.Factory(config, stats),
|
||||
getFeatureRenderer(writer, config, writer)
|
||||
);
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private FeatureRenderer getFeatureRenderer(FeatureGroup writer, CommonParams config,
|
||||
Consumer<FeatureSort.Entry> next) {
|
||||
var encoder = writer.newRenderedFeatureEncoder();
|
||||
return new FeatureRenderer(
|
||||
config,
|
||||
rendered -> next.accept(encoder.apply(rendered)),
|
||||
stats
|
||||
);
|
||||
}
|
||||
|
||||
public abstract long getCount();
|
||||
|
||||
public abstract Topology.SourceStep<? extends SourceFeature> read();
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.onthegomap.flatmap.read;
|
|||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
|
@ -21,15 +22,20 @@ public class ReaderFeature extends SourceFeature {
|
|||
|
||||
public ReaderFeature(Geometry latLonGeometry, Map<String, Object> properties, String source, String sourceLayer,
|
||||
long id) {
|
||||
super(properties, source, sourceLayer, null, id);
|
||||
this.latLonGeometry = latLonGeometry;
|
||||
this.properties = properties;
|
||||
this(latLonGeometry, properties, source, sourceLayer, id, null);
|
||||
}
|
||||
|
||||
public ReaderFeature(Geometry latLonGeometry, int numProperties, String source, String sourceLayer, long id) {
|
||||
this(latLonGeometry, new HashMap<>(numProperties), source, sourceLayer, id);
|
||||
}
|
||||
|
||||
public ReaderFeature(Geometry latLonGeometry, Map<String, Object> properties, String source, String sourceLayer,
|
||||
long id, List<OpenStreetMapReader.RelationMember<OpenStreetMapReader.RelationInfo>> relations) {
|
||||
super(properties, source, sourceLayer, relations, id);
|
||||
this.latLonGeometry = latLonGeometry;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Geometry latLonGeometry() {
|
||||
return latLonGeometry;
|
||||
|
|
|
@ -88,7 +88,7 @@ public class FeatureRenderer implements Consumer<FeatureCollector.Feature> {
|
|||
double buffer = feature.getBufferPixelsAtZoom(zoom) / 256;
|
||||
int tilesAtZoom = 1 << zoom;
|
||||
TileExtents.ForZoom extents = config.extents().getForZoom(zoom);
|
||||
TiledGeometry tiled = TiledGeometry.slicePointsIntoTiles(extents, buffer, zoom, coords);
|
||||
TiledGeometry tiled = TiledGeometry.slicePointsIntoTiles(extents, buffer, zoom, coords, feature.sourceId());
|
||||
|
||||
RenderedFeature.Group groupInfo = null;
|
||||
if (hasLabelGrid && coords.length == 1) {
|
||||
|
@ -163,7 +163,7 @@ public class FeatureRenderer implements Consumer<FeatureCollector.Feature> {
|
|||
List<List<CoordinateSequence>> groups = CoordinateSequenceExtractor.extractGroups(geom, minSize);
|
||||
double buffer = feature.getBufferPixelsAtZoom(z) / 256;
|
||||
TileExtents.ForZoom extents = config.extents().getForZoom(z);
|
||||
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
|
||||
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents, feature.sourceId());
|
||||
writeTileFeatures(z, id, feature, sliced);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ class TiledGeometry {
|
|||
private static final double NEIGHBOR_BUFFER_EPS = 0.1d / 4096;
|
||||
|
||||
private final Map<TileCoord, List<List<CoordinateSequence>>> tileContents = new HashMap<>();
|
||||
private final long id;
|
||||
private Map<Integer, IntRange> filledRanges = null;
|
||||
private final TileExtents.ForZoom extents;
|
||||
private final double buffer;
|
||||
|
@ -54,7 +55,8 @@ class TiledGeometry {
|
|||
private final boolean area;
|
||||
private final int max;
|
||||
|
||||
private TiledGeometry(TileExtents.ForZoom extents, double buffer, int z, boolean area) {
|
||||
private TiledGeometry(TileExtents.ForZoom extents, double buffer, int z, boolean area, long id) {
|
||||
this.id = id;
|
||||
this.extents = extents;
|
||||
this.buffer = buffer;
|
||||
// make sure we inspect neighboring tiles when a line runs along an edge
|
||||
|
@ -65,8 +67,8 @@ class TiledGeometry {
|
|||
}
|
||||
|
||||
public static TiledGeometry slicePointsIntoTiles(TileExtents.ForZoom extents, double buffer, int z,
|
||||
Coordinate[] coords) {
|
||||
TiledGeometry result = new TiledGeometry(extents, buffer, z, false);
|
||||
Coordinate[] coords, long id) {
|
||||
TiledGeometry result = new TiledGeometry(extents, buffer, z, false, id);
|
||||
for (Coordinate coord : coords) {
|
||||
result.slicePoint(coord);
|
||||
}
|
||||
|
@ -107,9 +109,9 @@ class TiledGeometry {
|
|||
}
|
||||
|
||||
public static TiledGeometry sliceIntoTiles(List<List<CoordinateSequence>> groups, double buffer, boolean area, int z,
|
||||
TileExtents.ForZoom extents) {
|
||||
TileExtents.ForZoom extents, long id) {
|
||||
int worldExtent = 1 << z;
|
||||
TiledGeometry result = new TiledGeometry(extents, buffer, z, area);
|
||||
TiledGeometry result = new TiledGeometry(extents, buffer, z, area, id);
|
||||
EnumSet<Direction> wrapResult = result.sliceWorldCopy(groups, 0);
|
||||
if (wrapResult.contains(Direction.RIGHT)) {
|
||||
result.sliceWorldCopy(groups, -worldExtent);
|
||||
|
@ -176,7 +178,7 @@ class TiledGeometry {
|
|||
boolean outer = i == 0;
|
||||
IntObjectMap<List<MutableCoordinateSequence>> xSlices = sliceX(segment);
|
||||
if (z >= 6 && xSlices.size() >= Math.pow(2, z) - 1) {
|
||||
LOGGER.warn("Feature crosses world at z" + z + ": " + xSlices.size());
|
||||
LOGGER.warn("Feature " + id + " crosses world at z" + z + ": " + xSlices.size());
|
||||
}
|
||||
for (IntObjectCursor<List<MutableCoordinateSequence>> xCursor : xSlices) {
|
||||
int x = xCursor.key + xOffset;
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.io.IOException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -151,6 +152,18 @@ public class FlatMapTest {
|
|||
);
|
||||
}
|
||||
|
||||
private FlatMapResults runWithReaderFeaturesProfile(
|
||||
Map<String, String> args,
|
||||
List<ReaderFeature> features,
|
||||
Profile profileToUse
|
||||
) throws Exception {
|
||||
return run(
|
||||
args,
|
||||
(featureGroup, profile, config) -> processReaderFeatures(featureGroup, profile, config, features),
|
||||
profileToUse
|
||||
);
|
||||
}
|
||||
|
||||
private FlatMapResults runWithOsmElements(
|
||||
Map<String, String> args,
|
||||
List<ReaderElement> features,
|
||||
|
@ -163,6 +176,18 @@ public class FlatMapTest {
|
|||
);
|
||||
}
|
||||
|
||||
private FlatMapResults runWithOsmElements(
|
||||
Map<String, String> args,
|
||||
List<ReaderElement> features,
|
||||
Profile profileToUse
|
||||
) throws Exception {
|
||||
return run(
|
||||
args,
|
||||
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
||||
profileToUse
|
||||
);
|
||||
}
|
||||
|
||||
private FlatMapResults runWithOsmElements(
|
||||
Map<String, String> args,
|
||||
List<ReaderElement> features,
|
||||
|
@ -1042,6 +1067,114 @@ public class FlatMapTest {
|
|||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderProfileFinish() throws Exception {
|
||||
double y = 0.5 + Z14_WIDTH / 2;
|
||||
double lat = GeoUtils.getWorldLat(y);
|
||||
|
||||
double x1 = 0.5 + Z14_WIDTH / 4;
|
||||
double lng1 = GeoUtils.getWorldLon(x1);
|
||||
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
||||
|
||||
var results = runWithReaderFeaturesProfile(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
newReaderFeature(newPoint(lng1, lat), Map.of("a", 1, "b", 2)),
|
||||
newReaderFeature(newPoint(lng2, lat), Map.of("a", 3, "b", 4))
|
||||
),
|
||||
new Profile.NullProfile() {
|
||||
private final List<SourceFeature> featureList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Override
|
||||
public void processFeature(SourceFeature in, FeatureCollector features) {
|
||||
featureList.add(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(String name, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next) {
|
||||
if ("test".equals(name)) {
|
||||
for (SourceFeature in : featureList) {
|
||||
var features = featureCollectors.get(in);
|
||||
features.point("layer")
|
||||
.setZoomRange(13, 14)
|
||||
.inheritFromSource("a");
|
||||
for (var feature : features) {
|
||||
next.accept(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assertSubmap(sortListValues(Map.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
feature(newPoint(64, 128), Map.of("a", 1L)),
|
||||
feature(newPoint(74, 128), Map.of("a", 3L))
|
||||
),
|
||||
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
||||
// merge 32->37 and 37->42 since they have same attrs
|
||||
feature(newPoint(32, 64), Map.of("a", 1L)),
|
||||
feature(newPoint(37, 64), Map.of("a", 3L))
|
||||
)
|
||||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmProfileFinish() throws Exception {
|
||||
double y = 0.5 + Z14_WIDTH / 2;
|
||||
double lat = GeoUtils.getWorldLat(y);
|
||||
|
||||
double x1 = 0.5 + Z14_WIDTH / 4;
|
||||
double lng1 = GeoUtils.getWorldLon(x1);
|
||||
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
||||
|
||||
var results = runWithOsmElements(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
with(new ReaderNode(1, lat, lng1), t -> t.setTag("a", 1)),
|
||||
with(new ReaderNode(2, lat, lng2), t -> t.setTag("a", 3))
|
||||
),
|
||||
new Profile.NullProfile() {
|
||||
private final List<SourceFeature> featureList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Override
|
||||
public void processFeature(SourceFeature in, FeatureCollector features) {
|
||||
featureList.add(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(String name, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next) {
|
||||
if ("osm".equals(name)) {
|
||||
for (SourceFeature in : featureList) {
|
||||
var features = featureCollectors.get(in);
|
||||
features.point("layer")
|
||||
.setZoomRange(13, 14)
|
||||
.inheritFromSource("a");
|
||||
for (var feature : features) {
|
||||
next.accept(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assertSubmap(sortListValues(Map.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
feature(newPoint(64, 128), Map.of("a", 1L)),
|
||||
feature(newPoint(74, 128), Map.of("a", 3L))
|
||||
),
|
||||
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
||||
// merge 32->37 and 37->42 since they have same attrs
|
||||
feature(newPoint(32, 64), Map.of("a", 1L)),
|
||||
feature(newPoint(37, 64), Map.of("a", 3L))
|
||||
)
|
||||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
||||
private <K extends Comparable<? super K>, V extends List<?>> Map<K, ?> sortListValues(Map<K, V> input) {
|
||||
Map<K, List<?>> result = new TreeMap<>();
|
||||
for (var entry : input.entrySet()) {
|
||||
|
|
|
@ -17,6 +17,7 @@ public class GeoUtilsTest {
|
|||
@CsvSource({
|
||||
"0,0, 0.5,0.5",
|
||||
"0, -180, 0, 0.5",
|
||||
"0, 180, 1, 0.5",
|
||||
"0, " + (180 - 1e-7) + ", 1, 0.5",
|
||||
"45, 0, 0.5, 0.359725",
|
||||
"-45, 0, 0.5, " + (1 - 0.359725)
|
||||
|
|
|
@ -164,6 +164,15 @@ public class OsmMultipolygonTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildMultipolygonFromGeometries() throws GeometryException {
|
||||
Geometry actual = OsmMultipolygon.build(List.of(
|
||||
newLineString(0.2, 0.2, 0.4, 0.2, 0.4, 0.4).getCoordinateSequence(),
|
||||
newLineString(0.4, 0.4, 0.2, 0.4, 0.2, 0.2).getCoordinateSequence()
|
||||
));
|
||||
assertSameNormalizedFeature(rectangle(0.2, 0.4), actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowWhenNoClosed() {
|
||||
var node1 = node(0.5, 0.5);
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
<version>0.17.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
|
|
|
@ -26,6 +26,9 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
|
@ -343,6 +346,7 @@ public class Generate {
|
|||
import com.onthegomap.flatmap.Translations;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenMapTilesSchema {
|
||||
public static final String NAME = %s;
|
||||
|
@ -415,6 +419,10 @@ public class Generate {
|
|||
.formatted(name.toUpperCase(Locale.ROOT) + "_" + v.toUpperCase(Locale.ROOT).replace('-', '_'), quote(v)))
|
||||
.collect(joining("\n")).indent(2).strip()
|
||||
.indent(4));
|
||||
fieldValues.append("public static final Set<String> %s = Set.of(%s);".formatted(
|
||||
name.toUpperCase(Locale.ROOT) + "_VALUES",
|
||||
values.stream().map(Generate::quote).collect(joining(", "))
|
||||
).indent(4));
|
||||
}
|
||||
|
||||
if (valuesNode != null && valuesNode.isObject()) {
|
||||
|
@ -524,8 +532,12 @@ public class Generate {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static final Parser parser = Parser.builder().build();
|
||||
private static final HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
|
||||
private static String escapeJavadoc(String description) {
|
||||
return description.replaceAll("[\n\r*\\s]+", " ");
|
||||
Node document = parser.parse(description);
|
||||
return renderer.render(document).replaceAll("[\n\r*\\s]+", " ");
|
||||
}
|
||||
|
||||
private static String getFieldDescription(JsonNode value) {
|
||||
|
|
|
@ -27,8 +27,8 @@ public class OpenMapTilesMain {
|
|||
.setProfile(createProfileWithWikidataTranslations(runner))
|
||||
.addShapefileSource("EPSG:3857", OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE,
|
||||
sourcesDir.resolve("lake_centerline.shp.zip"))
|
||||
.addShapefileSource(OpenMapTilesProfile.WATER_POLYGON_SOURCE,
|
||||
sourcesDir.resolve("water-polygons-split-3857.zip"))
|
||||
// .addShapefileSource(OpenMapTilesProfile.WATER_POLYGON_SOURCE,
|
||||
// sourcesDir.resolve("water-polygons-split-3857.zip"))
|
||||
.addNaturalEarthSource(OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
|
||||
sourcesDir.resolve("natural_earth_vector.sqlite.zip"))
|
||||
.addOsmSource(OpenMapTilesProfile.OSM_SOURCE, sourcesDir.resolve(fallbackOsmFile))
|
||||
|
|
|
@ -23,9 +23,14 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OpenMapTilesProfile implements Profile {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OpenMapTilesProfile.class);
|
||||
|
||||
public static final String LAKE_CENTERLINE_SOURCE = "lake_centerlines";
|
||||
public static final String WATER_POLYGON_SOURCE = "water_polygons";
|
||||
public static final String NATURAL_EARTH_SOURCE = "natural_earth";
|
||||
|
@ -41,6 +46,8 @@ public class OpenMapTilesProfile implements Profile {
|
|||
private final List<OsmWaterPolygonProcessor> osmWaterProcessors;
|
||||
private final List<LakeCenterlineProcessor> lakeCenterlineProcessors;
|
||||
private final List<OsmAllProcessor> osmAllProcessors;
|
||||
private final List<OsmRelationPreprocessor> osmRelationPreprocessors;
|
||||
private final List<FinishHandler> finishHandlers;
|
||||
|
||||
private MultiExpression.MultiExpressionIndex<Tables.Constructor> indexForType(String type) {
|
||||
return Tables.MAPPINGS
|
||||
|
@ -73,6 +80,8 @@ public class OpenMapTilesProfile implements Profile {
|
|||
lakeCenterlineProcessors = new ArrayList<>();
|
||||
naturalEarthProcessors = new ArrayList<>();
|
||||
osmWaterProcessors = new ArrayList<>();
|
||||
osmRelationPreprocessors = new ArrayList<>();
|
||||
finishHandlers = new ArrayList<>();
|
||||
for (Layer layer : layers) {
|
||||
if (layer instanceof FeaturePostProcessor postProcessor) {
|
||||
postProcessors.put(layer.name(), postProcessor);
|
||||
|
@ -89,6 +98,12 @@ public class OpenMapTilesProfile implements Profile {
|
|||
if (layer instanceof NaturalEarthProcessor processor) {
|
||||
naturalEarthProcessors.add(processor);
|
||||
}
|
||||
if (layer instanceof OsmRelationPreprocessor processor) {
|
||||
osmRelationPreprocessors.add(processor);
|
||||
}
|
||||
if (layer instanceof FinishHandler processor) {
|
||||
finishHandlers.add(processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +125,19 @@ public class OpenMapTilesProfile implements Profile {
|
|||
|
||||
@Override
|
||||
public List<OpenStreetMapReader.RelationInfo> preprocessOsmRelation(ReaderRelation relation) {
|
||||
return null;
|
||||
List<OpenStreetMapReader.RelationInfo> result = null;
|
||||
for (int i = 0; i < osmRelationPreprocessors.size(); i++) {
|
||||
List<OpenStreetMapReader.RelationInfo> thisResult = osmRelationPreprocessors.get(i)
|
||||
.preprocessOsmRelation(relation);
|
||||
if (thisResult != null) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>(thisResult);
|
||||
} else {
|
||||
result.addAll(thisResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,6 +193,14 @@ public class OpenMapTilesProfile implements Profile {
|
|||
return result == null ? List.of() : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next) {
|
||||
for (var handler : finishHandlers) {
|
||||
handler.finish(sourceName, featureCollectors, next);
|
||||
}
|
||||
}
|
||||
|
||||
public interface NaturalEarthProcessor {
|
||||
|
||||
void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features);
|
||||
|
@ -186,6 +221,17 @@ public class OpenMapTilesProfile implements Profile {
|
|||
void processAllOsm(SourceFeature feature, FeatureCollector features);
|
||||
}
|
||||
|
||||
public interface FinishHandler {
|
||||
|
||||
void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next);
|
||||
}
|
||||
|
||||
public interface OsmRelationPreprocessor {
|
||||
|
||||
List<OpenStreetMapReader.RelationInfo> preprocessOsmRelation(ReaderRelation relation);
|
||||
}
|
||||
|
||||
public interface FeaturePostProcessor {
|
||||
|
||||
List<VectorTileEncoder.Feature> postProcess(int zoom, List<VectorTileEncoder.Feature> items)
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,14 +1,344 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.OpenMapTilesSchema;
|
||||
import static com.onthegomap.flatmap.geo.GeoUtils.JTS_FACTORY;
|
||||
|
||||
public class Boundary implements OpenMapTilesSchema.Boundary {
|
||||
import com.carrotsearch.hppc.LongObjectMap;
|
||||
import com.graphhopper.coll.GHLongObjectHashMap;
|
||||
import com.graphhopper.reader.ReaderElementUtils;
|
||||
import com.graphhopper.reader.ReaderRelation;
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.FeatureMerge;
|
||||
import com.onthegomap.flatmap.MemoryEstimator;
|
||||
import com.onthegomap.flatmap.Parse;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.OpenMapTilesSchema;
|
||||
import com.onthegomap.flatmap.read.OpenStreetMapReader;
|
||||
import com.onthegomap.flatmap.read.OsmMultipolygon;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryComponentFilter;
|
||||
import org.locationtech.jts.geom.LineSegment;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.prep.PreparedGeometry;
|
||||
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
|
||||
import org.locationtech.jts.operation.linemerge.LineMerger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Boundary implements
|
||||
OpenMapTilesSchema.Boundary,
|
||||
OpenMapTilesProfile.NaturalEarthProcessor,
|
||||
OpenMapTilesProfile.OsmRelationPreprocessor,
|
||||
OpenMapTilesProfile.OsmAllProcessor,
|
||||
OpenMapTilesProfile.FeaturePostProcessor,
|
||||
OpenMapTilesProfile.FinishHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Boundary.class);
|
||||
private static final double COUNTRY_TEST_OFFSET = GeoUtils.metersToPixelAtEquator(0, 100) / 256d;
|
||||
private final Map<Long, String> regionNames = new HashMap<>();
|
||||
private final Map<Long, List<Geometry>> regionGeometries = new HashMap<>();
|
||||
private final Map<CountryBoundaryComponent, List<Geometry>> boundariesToMerge = new HashMap<>();
|
||||
private final Stats stats;
|
||||
|
||||
public Boundary(Translations translations, Arguments args, Stats stats) {
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
private static boolean isDisputed(Map<String, Object> tags) {
|
||||
return Parse.bool(tags.get("disputed")) ||
|
||||
Parse.bool(tags.get("dispute")) ||
|
||||
"dispute".equals(tags.get("border_status")) ||
|
||||
tags.containsKey("disputed_by") ||
|
||||
tags.containsKey("claimed_by");
|
||||
}
|
||||
|
||||
private static String editName(String name) {
|
||||
return name == null ? null : name.replace(" at ", "")
|
||||
.replaceAll("\\s+", "")
|
||||
.replace("Extentof", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
regionGeometries.clear();
|
||||
boundariesToMerge.clear();
|
||||
regionNames.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) {
|
||||
boolean disputed = feature.getString("featurecla", "").startsWith("Disputed");
|
||||
record BoundaryInfo(int adminLevel, int minzoom, int maxzoom) {}
|
||||
BoundaryInfo info = switch (table) {
|
||||
case "ne_110m_admin_0_boundary_lines_land" -> new BoundaryInfo(2, 0, 0);
|
||||
case "ne_50m_admin_0_boundary_lines_land" -> new BoundaryInfo(2, 1, 3);
|
||||
case "ne_10m_admin_0_boundary_lines_land" -> feature.hasTag("featurecla", "Lease Limit") ? null
|
||||
: new BoundaryInfo(2, 4, 4);
|
||||
case "ne_10m_admin_1_states_provinces_lines" -> {
|
||||
Double minZoom = Parse.parseDoubleOrNull(feature.getTag("min_zoom"));
|
||||
yield minZoom != null && minZoom <= 7 ? new BoundaryInfo(4, 1, 4) : null;
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
if (info != null) {
|
||||
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setZoomRange(info.minzoom, info.maxzoom)
|
||||
.setMinPixelSizeAtAllZooms(0)
|
||||
.setAttr(Fields.ADMIN_LEVEL, info.adminLevel)
|
||||
.setAttr(Fields.MARITIME, 0)
|
||||
.setAttr(Fields.DISPUTED, disputed ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VectorTileEncoder.Feature> postProcess(int zoom, List<VectorTileEncoder.Feature> items)
|
||||
throws GeometryException {
|
||||
double tolerance = zoom >= 14 ? 256d / 4096d : 0.1;
|
||||
return FeatureMerge.mergeLineStrings(items, 1, tolerance, BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
||||
Consumer<FeatureCollector.Feature> next) {
|
||||
if (OpenMapTilesProfile.OSM_SOURCE.equals(sourceName)) {
|
||||
var timer = stats.startTimer("boundaries");
|
||||
LOGGER.info("[boundaries] Creating polygons for " + regionGeometries.size() + " boundaries");
|
||||
LongObjectMap<PreparedGeometry> countryBoundaries = new GHLongObjectHashMap<>();
|
||||
for (var entry : regionGeometries.entrySet()) {
|
||||
Long countryCode = entry.getKey();
|
||||
List<CoordinateSequence> seqs = new ArrayList<>();
|
||||
for (Geometry geometry : entry.getValue()) {
|
||||
geometry.apply((GeometryComponentFilter) geom -> {
|
||||
if (geom instanceof LineString lineString) {
|
||||
seqs.add(lineString.getCoordinateSequence());
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
countryBoundaries.put(countryCode, PreparedGeometryFactory.prepare(
|
||||
GeoUtils.fixPolygon(
|
||||
OsmMultipolygon.build(seqs)
|
||||
)
|
||||
));
|
||||
} catch (GeometryException e) {
|
||||
LOGGER.warn("[boundaries] Unable to build boundary polygon for " + countryCode + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
LOGGER.info("[boundaries] Finished creating polygons");
|
||||
|
||||
long number = 0;
|
||||
for (var entry : boundariesToMerge.entrySet()) {
|
||||
number++;
|
||||
CountryBoundaryComponent key = entry.getKey();
|
||||
LineMerger merger = new LineMerger();
|
||||
for (Geometry geom : entry.getValue()) {
|
||||
merger.add(geom);
|
||||
}
|
||||
entry.getValue().clear();
|
||||
for (Object merged : merger.getMergedLineStrings()) {
|
||||
if (merged instanceof LineString lineString) {
|
||||
Long rightCountry = null, leftCountry = null;
|
||||
int numPoints = lineString.getNumPoints();
|
||||
int middle = Math.max(0, Math.min(numPoints - 2, numPoints / 2));
|
||||
Coordinate a = lineString.getCoordinateN(middle);
|
||||
Coordinate b = lineString.getCoordinateN(middle + 1);
|
||||
LineSegment segment = new LineSegment(a, b);
|
||||
Point right = JTS_FACTORY.createPoint(segment.pointAlongOffset(0.5, COUNTRY_TEST_OFFSET));
|
||||
Point left = JTS_FACTORY.createPoint(segment.pointAlongOffset(0.5, -COUNTRY_TEST_OFFSET));
|
||||
for (Long regionId : key.regions) {
|
||||
PreparedGeometry geom = countryBoundaries.get(regionId);
|
||||
if (geom != null) {
|
||||
if (geom.contains(right)) {
|
||||
rightCountry = regionId;
|
||||
} else if (geom.contains(left)) {
|
||||
leftCountry = regionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftCountry == null && rightCountry == null) {
|
||||
LOGGER.warn("[boundaries] no left or right country for " + key);
|
||||
}
|
||||
|
||||
var features = featureCollectors.get(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(lineString),
|
||||
Map.of(),
|
||||
number
|
||||
));
|
||||
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.ADMIN_LEVEL, key.adminLevel)
|
||||
.setAttr(Fields.DISPUTED, key.disputed ? 1 : 0)
|
||||
.setAttr(Fields.MARITIME, key.maritime ? 1 : 0)
|
||||
.setAttr(Fields.CLAIMED_BY, key.claimedBy)
|
||||
.setAttr(Fields.DISPUTED_NAME, key.disputed ? editName(key.name) : null)
|
||||
.setAttr(Fields.ADM0_L, regionNames.get(leftCountry))
|
||||
.setAttr(Fields.ADM0_R, regionNames.get(rightCountry))
|
||||
.setMinPixelSizeAtAllZooms(0)
|
||||
.setZoomRange(key.minzoom, 14);
|
||||
for (var feature : features) {
|
||||
next.accept(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OpenStreetMapReader.RelationInfo> preprocessOsmRelation(ReaderRelation relation) {
|
||||
String typeTag = relation.getTag("type");
|
||||
if ("boundary".equals(typeTag) && relation.hasTag("admin_level") && relation.hasTag("boundary", "administrative")) {
|
||||
Integer adminLevelValue = Parse.parseIntSubstring(relation.getTag("admin_level"));
|
||||
String code = relation.getTag("ISO3166-1:alpha3");
|
||||
if (adminLevelValue != null && adminLevelValue >= 2 && adminLevelValue <= 2) {
|
||||
boolean disputed = isDisputed(ReaderElementUtils.getProperties(relation));
|
||||
if (code != null) {
|
||||
synchronized (regionNames) {
|
||||
regionNames.put(relation.getId(), code);
|
||||
}
|
||||
}
|
||||
return List.of(new BoundaryRelation(
|
||||
relation.getId(),
|
||||
adminLevelValue,
|
||||
disputed,
|
||||
relation.getTag("name"),
|
||||
disputed ? relation.getTag("claimed_by") : null,
|
||||
code
|
||||
));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAllOsm(SourceFeature feature, FeatureCollector features) {
|
||||
if (!feature.canBeLine()) {
|
||||
return;
|
||||
}
|
||||
var relationInfos = feature.relationInfo(BoundaryRelation.class);
|
||||
if (!relationInfos.isEmpty()) {
|
||||
int minAdminLevel = Integer.MAX_VALUE;
|
||||
String disputedName = null, claimedBy = null;
|
||||
Set<Long> regionIds = new HashSet<>();
|
||||
boolean disputed = false;
|
||||
for (var info : relationInfos) {
|
||||
BoundaryRelation rel = info.relation();
|
||||
disputed |= rel.disputed;
|
||||
if (rel.adminLevel < minAdminLevel) {
|
||||
minAdminLevel = rel.adminLevel;
|
||||
}
|
||||
if (rel.disputed) {
|
||||
disputedName = disputedName == null ? rel.name : disputedName;
|
||||
claimedBy = claimedBy == null ? rel.claimedBy : claimedBy;
|
||||
}
|
||||
if (minAdminLevel == 2 && regionNames.containsKey(info.relation().id)) {
|
||||
regionIds.add(info.relation().id);
|
||||
}
|
||||
}
|
||||
|
||||
if (minAdminLevel <= 10) {
|
||||
boolean wayIsDisputed = isDisputed(feature.properties());
|
||||
disputed |= wayIsDisputed;
|
||||
if (wayIsDisputed) {
|
||||
disputedName = disputedName == null ? feature.getString("name") : disputedName;
|
||||
claimedBy = claimedBy == null ? feature.getString("claimed_by") : claimedBy;
|
||||
}
|
||||
boolean maritime = feature.getBoolean("maritime") ||
|
||||
feature.hasTag("natural", "coastline") ||
|
||||
feature.hasTag("boundary_type", "maritime");
|
||||
int minzoom =
|
||||
(maritime && minAdminLevel == 2) ? 4 :
|
||||
minAdminLevel <= 4 ? 5 :
|
||||
minAdminLevel <= 6 ? 9 :
|
||||
minAdminLevel <= 8 ? 11 : 12;
|
||||
if (!regionIds.isEmpty()) {
|
||||
// save for later
|
||||
try {
|
||||
CountryBoundaryComponent component = new CountryBoundaryComponent(
|
||||
minAdminLevel,
|
||||
disputed,
|
||||
maritime,
|
||||
minzoom,
|
||||
feature.line(),
|
||||
regionIds,
|
||||
claimedBy,
|
||||
disputedName
|
||||
);
|
||||
synchronized (regionGeometries) {
|
||||
boundariesToMerge.computeIfAbsent(component.groupingKey(), key -> new ArrayList<>()).add(component.line);
|
||||
for (var info : relationInfos) {
|
||||
var rel = info.relation();
|
||||
if (rel.adminLevel <= 2) {
|
||||
regionGeometries.computeIfAbsent(rel.id, id -> new ArrayList<>()).add(component.line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (GeometryException e) {
|
||||
LOGGER.warn("Cannot extract boundary line from " + feature);
|
||||
}
|
||||
} else {
|
||||
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.ADMIN_LEVEL, minAdminLevel)
|
||||
.setAttr(Fields.DISPUTED, disputed ? 1 : 0)
|
||||
.setAttr(Fields.MARITIME, maritime ? 1 : 0)
|
||||
.setMinPixelSizeAtAllZooms(0)
|
||||
.setZoomRange(minzoom, 14)
|
||||
.setAttr(Fields.CLAIMED_BY, claimedBy)
|
||||
.setAttr(Fields.DISPUTED_NAME, editName(disputedName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static record BoundaryRelation(
|
||||
long id,
|
||||
int adminLevel,
|
||||
boolean disputed,
|
||||
String name,
|
||||
String claimedBy,
|
||||
String iso3166alpha3
|
||||
) implements OpenStreetMapReader.RelationInfo {
|
||||
|
||||
@Override
|
||||
public long estimateMemoryUsageBytes() {
|
||||
return 29 + 8 + MemoryEstimator.size(name)
|
||||
+ 8 + MemoryEstimator.size(claimedBy)
|
||||
+ 8 + MemoryEstimator.size(iso3166alpha3);
|
||||
}
|
||||
}
|
||||
|
||||
private static record CountryBoundaryComponent(
|
||||
int adminLevel,
|
||||
boolean disputed,
|
||||
boolean maritime,
|
||||
int minzoom,
|
||||
Geometry line,
|
||||
Set<Long> regions,
|
||||
String claimedBy,
|
||||
String name
|
||||
) {
|
||||
|
||||
CountryBoundaryComponent groupingKey() {
|
||||
return new CountryBoundaryComponent(adminLevel, disputed, maritime, minzoom, null, regions, claimedBy, name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,859 +1,23 @@
|
|||
package com.onthegomap.flatmap.openmaptiles;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.TestUtils.newPoint;
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.OSM_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.WATER_POLYGON_SOURCE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import com.graphhopper.reader.ReaderNode;
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.TestUtils;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.Wikidata;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.layers.MountainPeak;
|
||||
import com.onthegomap.flatmap.openmaptiles.layers.Waterway;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
public class OpenMaptilesProfileTest {
|
||||
|
||||
private final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations();
|
||||
private final Translations translations = Translations.defaultProvider(List.of("en", "es", "de"))
|
||||
.addTranslationProvider(wikidataTranslations);
|
||||
|
||||
private final CommonParams params = CommonParams.defaults();
|
||||
private final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, Arguments.of(),
|
||||
new Stats.InMemory());
|
||||
private final Stats stats = new Stats.InMemory();
|
||||
private final FeatureCollector.Factory featureCollectorFactory = new FeatureCollector.Factory(params, stats);
|
||||
|
||||
private static void assertFeatures(int zoom, List<Map<String, Object>> expected, FeatureCollector actual) {
|
||||
List<FeatureCollector.Feature> actualList = StreamSupport.stream(actual.spliterator(), false).toList();
|
||||
assertEquals(expected.size(), actualList.size(), "size");
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
assertSubmap(expected.get(i), TestUtils.toMap(actualList.get(i), zoom));
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
public List<DynamicTest> mountainPeakProcessing() {
|
||||
wikidataTranslations.put(123, "es", "es wd name");
|
||||
return List.of(
|
||||
dynamicTest("happy path", () -> {
|
||||
var peak = process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "test",
|
||||
"ele", "100",
|
||||
"wikidata", "Q123"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "peak",
|
||||
"ele", 100,
|
||||
"ele_ft", 328,
|
||||
|
||||
"_layer", "mountain_peak",
|
||||
"_type", "point",
|
||||
"_minzoom", 7,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 64d
|
||||
)), peak);
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"name:latin", "test",
|
||||
"name", "test",
|
||||
"name:es", "es wd name"
|
||||
)), peak);
|
||||
}),
|
||||
|
||||
dynamicTest("labelgrid", () -> {
|
||||
var peak = process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"ele", "100"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_labelgrid_limit", 0
|
||||
)), peak);
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_labelgrid_limit", 5,
|
||||
"_labelgrid_size", 100d
|
||||
)), peak);
|
||||
}),
|
||||
|
||||
dynamicTest("volcano", () ->
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "volcano"
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "volcano",
|
||||
"ele", "100"
|
||||
))))),
|
||||
|
||||
dynamicTest("no elevation", () ->
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"natural", "volcano"
|
||||
))))),
|
||||
|
||||
dynamicTest("bogus elevation", () ->
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"natural", "volcano",
|
||||
"ele", "11000"
|
||||
))))),
|
||||
|
||||
dynamicTest("ignore lines", () ->
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"ele", "100"
|
||||
))))),
|
||||
|
||||
dynamicTest("zorder", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"ele", "100"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 10100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"ele", "100"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 20100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"wikipedia", "wikilink",
|
||||
"ele", "100"
|
||||
))));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMountainPeakPostProcessing() throws GeometryException {
|
||||
assertEquals(List.of(), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of()));
|
||||
|
||||
assertEquals(List.of(pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1),
|
||||
1
|
||||
)), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of(pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of(),
|
||||
1
|
||||
))));
|
||||
|
||||
assertEquals(List.of(
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 2, "name", "a"),
|
||||
1
|
||||
), pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1, "name", "b"),
|
||||
1
|
||||
), pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1, "name", "c"),
|
||||
2
|
||||
)
|
||||
), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of(
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "a"),
|
||||
1
|
||||
),
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "b"),
|
||||
1
|
||||
),
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "c"),
|
||||
2
|
||||
)
|
||||
)));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
public List<DynamicTest> aerodromeLabel() {
|
||||
wikidataTranslations.put(123, "es", "es wd name");
|
||||
return List.of(
|
||||
dynamicTest("happy path point", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "international",
|
||||
"ele", 100,
|
||||
"ele_ft", 328,
|
||||
"name", "osm name",
|
||||
"name:es", "es wd name",
|
||||
|
||||
"_layer", "aerodrome_label",
|
||||
"_type", "point",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 64d
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"name", "osm name",
|
||||
"wikidata", "Q123",
|
||||
"ele", "100",
|
||||
"aerodrome", "international",
|
||||
"iata", "123",
|
||||
"icao", "1234"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("international", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "international",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "international"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("public", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "public",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "public airport"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "public",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "civil"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("military", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "military",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "military airport"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "military",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"military", "airfield"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("private", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "private",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "private"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "private",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome", "private"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("other", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "other",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("ignore non-points", () -> {
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "aerodrome"
|
||||
))));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aerowayGate() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "gate",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "gate",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "gate"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"aeroway", "gate"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aerowayLine() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "line",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(lineFeature(Map.of(
|
||||
"aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"aeroway", "runway"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aerowayPolygon() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon"
|
||||
)), process(polygonFeature(Map.of(
|
||||
"area:aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "heliport",
|
||||
"ref", "123",
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon"
|
||||
)), process(polygonFeature(Map.of(
|
||||
"aeroway", "heliport",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "heliport"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"aeroway", "heliport"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwayImportantRiverProcess() {
|
||||
var charlesRiver = process(lineFeature(Map.of(
|
||||
"waterway", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name",
|
||||
"intermittent", 0,
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), charlesRiver);
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name",
|
||||
"intermittent", "<null>",
|
||||
"_buffer", 13.082664546679323
|
||||
)), charlesRiver);
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"class", "river",
|
||||
"_buffer", 26.165329093358647
|
||||
)), charlesRiver);
|
||||
assertFeatures(9, List.of(Map.of(
|
||||
"class", "river",
|
||||
"_buffer", 26.165329093358647
|
||||
)), charlesRiver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwayImportantRiverPostProcess() throws GeometryException {
|
||||
var line1 = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(0, 0, 10, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
var line2 = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(10, 0, 20, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
var connected = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(00, 0, 20, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
List.of(),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of())
|
||||
);
|
||||
assertEquals(
|
||||
List.of(line1, line2),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 12, List.of(line1, line2))
|
||||
);
|
||||
assertEquals(
|
||||
List.of(connected),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of(line1, line2))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwaySmaller() {
|
||||
// river with no name is not important
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"brunnel", "bridge",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 12
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "river",
|
||||
"bridge", "1"
|
||||
))));
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "canal",
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 12
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "canal",
|
||||
"name", "name"
|
||||
))));
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "stream",
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 13
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "stream",
|
||||
"name", "name"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwayNaturalEarth() {
|
||||
assertFeatures(3, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "<null>",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 3,
|
||||
"_maxzoom", 3
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "river",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 4,
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "river",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 8
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterNaturalEarth() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 0
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_lakes",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 0
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_ocean",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_lakes",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_ocean",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterOsmWaterPolygon() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
WATER_POLYGON_SOURCE,
|
||||
null,
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWater() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"natural", "water",
|
||||
"water", "reservoir"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "lake",
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"leisure", "swimming_pool"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"natural", "bay"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of()), process(polygonFeature(Map.of(
|
||||
"natural", "water"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"natural", "water",
|
||||
"covered", "yes"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"brunnel", "bridge",
|
||||
"intermittent", 1,
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"waterway", "stream",
|
||||
"bridge", "1",
|
||||
"intermittent", "1"
|
||||
))));
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"brunnel", "<null>",
|
||||
"intermittent", 0,
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", 2d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"landuse", "salt_pond",
|
||||
"bridge", "1"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterNamePoint() {
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"class", "lake",
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"intermittent", 1,
|
||||
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(1, Map.of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"natural", "water",
|
||||
"water", "pond",
|
||||
"intermittent", "1"
|
||||
))));
|
||||
double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11);
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 11,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(z11area, Map.of(
|
||||
"name", "waterway",
|
||||
"natural", "water",
|
||||
"water", "pond"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterNameLakeline() {
|
||||
assertFeatures(11, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"OSM_ID", -10
|
||||
)),
|
||||
LAKE_CENTERLINE_SOURCE,
|
||||
null,
|
||||
0
|
||||
)));
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
|
||||
"_layer", "water_name",
|
||||
"_type", "line",
|
||||
"_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))),
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", "waterway".length() * 6d
|
||||
)), process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"natural", "water",
|
||||
"water", "pond"
|
||||
)),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
10
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarinePoint() {
|
||||
assertFeatures(11, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"scalerank", 10,
|
||||
"name", "pacific ocean"
|
||||
)),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_geography_marine_polys",
|
||||
0
|
||||
)));
|
||||
|
||||
// name match - use scale rank from NE
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"name", "Pacific",
|
||||
"name:es", "Pacific es",
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Pacific",
|
||||
"name:es", "Pacific es",
|
||||
"place", "sea"
|
||||
))));
|
||||
|
||||
// name match but ocean - use min zoom=0
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 0,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Pacific",
|
||||
"place", "ocean"
|
||||
))));
|
||||
|
||||
// no name match - use OSM rank
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Atlantic",
|
||||
"place", "sea"
|
||||
))));
|
||||
|
||||
// no rank at all, default to 8
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 8,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"name", "Atlantic",
|
||||
"place", "sea"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHousenumber() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "housenumber",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 8d
|
||||
)), process(pointFeature(Map.of(
|
||||
"addr:housenumber", "10"
|
||||
))));
|
||||
assertFeatures(15, List.of(Map.of(
|
||||
"_layer", "housenumber",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 8d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"addr:housenumber", "10"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaresAboutWikidata() {
|
||||
|
@ -864,54 +28,4 @@ public class OpenMaptilesProfileTest {
|
|||
node.setTag("aeroway", "other");
|
||||
assertFalse(profile.caresAboutWikidataTranslation(node));
|
||||
}
|
||||
|
||||
private VectorTileEncoder.Feature pointFeature(String layer, Map<String, Object> map, int group) {
|
||||
return new VectorTileEncoder.Feature(
|
||||
layer,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newPoint(0, 0)),
|
||||
new HashMap<>(map),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
private FeatureCollector process(SourceFeature feature) {
|
||||
var collector = featureCollectorFactory.get(feature);
|
||||
profile.processFeature(feature, collector);
|
||||
return collector;
|
||||
}
|
||||
|
||||
private SourceFeature pointFeature(Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
newPoint(0, 0),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private SourceFeature lineFeature(Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private SourceFeature polygonFeatureWithArea(double area, Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(area))),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private SourceFeature polygonFeature(Map<String, Object> props) {
|
||||
return polygonFeatureWithArea(1, props);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
public class AerodromeLabelTest extends BaseLayerTest {
|
||||
|
||||
|
||||
@TestFactory
|
||||
public List<DynamicTest> aerodromeLabel() {
|
||||
wikidataTranslations.put(123, "es", "es wd name");
|
||||
return List.of(
|
||||
dynamicTest("happy path point", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "international",
|
||||
"ele", 100,
|
||||
"ele_ft", 328,
|
||||
"name", "osm name",
|
||||
"name:es", "es wd name",
|
||||
|
||||
"_layer", "aerodrome_label",
|
||||
"_type", "point",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 64d
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"name", "osm name",
|
||||
"wikidata", "Q123",
|
||||
"ele", "100",
|
||||
"aerodrome", "international",
|
||||
"iata", "123",
|
||||
"icao", "1234"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("international", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "international",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "international"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("public", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "public",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "public airport"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "public",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "civil"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("military", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "military",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "military airport"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "military",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"military", "airfield"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("private", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "private",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome_type", "private"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "private",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome",
|
||||
"aerodrome", "private"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("other", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "other",
|
||||
"_layer", "aerodrome_label"
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "aerodrome"
|
||||
))));
|
||||
}),
|
||||
|
||||
dynamicTest("ignore non-points", () -> {
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "aerodrome"
|
||||
))));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class AerowayTest extends BaseLayerTest {
|
||||
|
||||
@Test
|
||||
public void aerowayGate() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "gate",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(pointFeature(Map.of(
|
||||
"aeroway", "gate",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "gate"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"aeroway", "gate"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aerowayLine() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "line",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(lineFeature(Map.of(
|
||||
"aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"aeroway", "runway"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aerowayPolygon() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "runway",
|
||||
"ref", "123",
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon"
|
||||
)), process(polygonFeature(Map.of(
|
||||
"area:aeroway", "runway",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "heliport",
|
||||
"ref", "123",
|
||||
"_layer", "aeroway",
|
||||
"_type", "polygon"
|
||||
)), process(polygonFeature(Map.of(
|
||||
"aeroway", "heliport",
|
||||
"ref", "123"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"aeroway", "heliport"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"aeroway", "heliport"
|
||||
))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.TestUtils.newPoint;
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.OSM_SOURCE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.TestUtils;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.Wikidata;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public abstract class BaseLayerTest {
|
||||
|
||||
final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations();
|
||||
final Translations translations = Translations.defaultProvider(List.of("en", "es", "de"))
|
||||
.addTranslationProvider(wikidataTranslations);
|
||||
|
||||
final CommonParams params = CommonParams.defaults();
|
||||
final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, Arguments.of(),
|
||||
new Stats.InMemory());
|
||||
final Stats stats = new Stats.InMemory();
|
||||
final FeatureCollector.Factory featureCollectorFactory = new FeatureCollector.Factory(params, stats);
|
||||
|
||||
static void assertFeatures(int zoom, List<Map<String, Object>> expected, Iterable<FeatureCollector.Feature> actual) {
|
||||
List<FeatureCollector.Feature> actualList = StreamSupport.stream(actual.spliterator(), false).toList();
|
||||
assertEquals(expected.size(), actualList.size(), "size");
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
assertSubmap(expected.get(i), TestUtils.toMap(actualList.get(i), zoom));
|
||||
}
|
||||
}
|
||||
|
||||
VectorTileEncoder.Feature pointFeature(String layer, Map<String, Object> map, int group) {
|
||||
return new VectorTileEncoder.Feature(
|
||||
layer,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newPoint(0, 0)),
|
||||
new HashMap<>(map),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
FeatureCollector process(SourceFeature feature) {
|
||||
var collector = featureCollectorFactory.get(feature);
|
||||
profile.processFeature(feature, collector);
|
||||
return collector;
|
||||
}
|
||||
|
||||
void assertCoversZoomRange(int minzoom, int maxzoom, String layer, FeatureCollector... featureCollectors) {
|
||||
Map<?, ?>[] zooms = new Map[Math.max(15, maxzoom + 1)];
|
||||
for (var features : featureCollectors) {
|
||||
for (var feature : features) {
|
||||
if (feature.getLayer().equals(layer)) {
|
||||
for (int zoom = feature.getMinZoom(); zoom <= feature.getMaxZoom(); zoom++) {
|
||||
Map<String, Object> map = TestUtils.toMap(feature, zoom);
|
||||
if (zooms[zoom] != null) {
|
||||
fail("Multiple features at z" + zoom + ":\n" + zooms[zoom] + "\n" + map);
|
||||
}
|
||||
zooms[zoom] = map;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int zoom = 0; zoom <= 14; zoom++) {
|
||||
if (zoom < minzoom || zoom > maxzoom) {
|
||||
if (zooms[zoom] != null) {
|
||||
fail("Expected nothing at z" + zoom + " but found: " + zooms[zoom]);
|
||||
}
|
||||
} else {
|
||||
if (zooms[zoom] == null) {
|
||||
fail("No feature at z" + zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SourceFeature pointFeature(Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
newPoint(0, 0),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
SourceFeature lineFeature(Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
SourceFeature polygonFeatureWithArea(double area, Map<String, Object> props) {
|
||||
return new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(area))),
|
||||
new HashMap<>(props),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
SourceFeature polygonFeature(Map<String, Object> props) {
|
||||
return polygonFeatureWithArea(1, props);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,603 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.OSM_SOURCE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import com.graphhopper.reader.ReaderRelation;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.read.OpenStreetMapReader;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class BoundaryTest extends BaseLayerTest {
|
||||
|
||||
@Test
|
||||
public void testNaturalEarthCountryBoundaries() {
|
||||
assertCoversZoomRange(
|
||||
0, 4, "boundary",
|
||||
process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_admin_0_boundary_lines_land",
|
||||
1
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_0_boundary_lines_land",
|
||||
2
|
||||
))
|
||||
);
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed", 0,
|
||||
"maritime", 0,
|
||||
"admin_level", 2,
|
||||
|
||||
"_minzoom", 0,
|
||||
"_buffer", 4d
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "International boundary (verify)"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed", 1,
|
||||
"maritime", 0,
|
||||
"admin_level", 2,
|
||||
"_buffer", 4d
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "Disputed (please verify)"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"admin_level", 2
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "International boundary (verify)"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"admin_level", 2
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "International boundary (verify)"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "Lease Limit"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_0_boundary_lines_land",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNaturalEarthStateBoundaries() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed", 0,
|
||||
"maritime", 0,
|
||||
"admin_level", 4,
|
||||
|
||||
"_minzoom", 1,
|
||||
"_maxzoom", 4,
|
||||
"_buffer", 4d
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"min_zoom", 7d
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_1_states_provinces_lines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"min_zoom", 7.1d
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_1_states_provinces_lines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_admin_1_states_provinces_lines",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergesDisconnectedLineFeatures() throws GeometryException {
|
||||
var line1 = new VectorTileEncoder.Feature(
|
||||
Boundary.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(0, 0, 10, 0)),
|
||||
Map.of("admin_level", 2),
|
||||
0
|
||||
);
|
||||
var line2 = new VectorTileEncoder.Feature(
|
||||
Boundary.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(10, 0, 20, 0)),
|
||||
Map.of("admin_level", 2),
|
||||
0
|
||||
);
|
||||
var connected = new VectorTileEncoder.Feature(
|
||||
Boundary.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(00, 0, 20, 0)),
|
||||
Map.of("admin_level", 2),
|
||||
0
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
List.of(connected),
|
||||
profile.postProcessLayerFeatures(Boundary.LAYER_NAME, 14, List.of(line1, line2))
|
||||
);
|
||||
assertEquals(
|
||||
List.of(connected),
|
||||
profile.postProcessLayerFeatures(Boundary.LAYER_NAME, 13, List.of(line1, line2))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmTownBoundary() {
|
||||
var relation = new ReaderRelation(1);
|
||||
relation.setTag("type", "boundary");
|
||||
relation.setTag("admin_level", "10");
|
||||
relation.setTag("boundary", "administrative");
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed", 0,
|
||||
"maritime", 0,
|
||||
"admin_level", 10,
|
||||
|
||||
"_minzoom", 12,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d,
|
||||
"_minpixelsize", 0d
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation),
|
||||
Map.of())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmBoundaryTakesMinAdminLevel() {
|
||||
var relation1 = new ReaderRelation(1);
|
||||
relation1.setTag("type", "boundary");
|
||||
relation1.setTag("admin_level", "10");
|
||||
relation1.setTag("name", "Town");
|
||||
relation1.setTag("boundary", "administrative");
|
||||
var relation2 = new ReaderRelation(2);
|
||||
relation2.setTag("type", "boundary");
|
||||
relation2.setTag("admin_level", "4");
|
||||
relation2.setTag("name", "State");
|
||||
relation2.setTag("boundary", "administrative");
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed", 0,
|
||||
"maritime", 0,
|
||||
"admin_level", 4
|
||||
)), process(lineFeatureWithRelation(
|
||||
Stream.concat(
|
||||
profile.preprocessOsmRelation(relation2).stream(),
|
||||
profile.preprocessOsmRelation(relation1).stream()
|
||||
).toList(),
|
||||
Map.of())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmBoundarySetsMaritimeFromWay() {
|
||||
var relation1 = new ReaderRelation(1);
|
||||
relation1.setTag("type", "boundary");
|
||||
relation1.setTag("admin_level", "10");
|
||||
relation1.setTag("boundary", "administrative");
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"maritime", 1
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation1),
|
||||
Map.of(
|
||||
"maritime", "yes"
|
||||
))
|
||||
));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"maritime", 1
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation1),
|
||||
Map.of(
|
||||
"natural", "coastline"
|
||||
))
|
||||
));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"maritime", 1
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation1),
|
||||
Map.of(
|
||||
"boundary_type", "maritime"
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoresProtectedAreas() {
|
||||
var relation1 = new ReaderRelation(1);
|
||||
relation1.setTag("type", "boundary");
|
||||
relation1.setTag("admin_level", "10");
|
||||
relation1.setTag("boundary", "protected_area");
|
||||
|
||||
assertNull(profile.preprocessOsmRelation(relation1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoresProtectedAdminLevelOver10() {
|
||||
var relation1 = new ReaderRelation(1);
|
||||
relation1.setTag("type", "boundary");
|
||||
relation1.setTag("admin_level", "11");
|
||||
relation1.setTag("boundary", "administrative");
|
||||
|
||||
assertNull(profile.preprocessOsmRelation(relation1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmBoundaryDisputed() {
|
||||
var relation = new ReaderRelation(1);
|
||||
relation.setTag("type", "boundary");
|
||||
relation.setTag("admin_level", "5");
|
||||
relation.setTag("boundary", "administrative");
|
||||
relation.setTag("disputed", "yes");
|
||||
relation.setTag("name", "Border A - B");
|
||||
relation.setTag("claimed_by", "A");
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
"disputed_name", "BorderA-B",
|
||||
"claimed_by", "A",
|
||||
|
||||
"disputed", 1,
|
||||
"maritime", 0,
|
||||
"admin_level", 5
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation),
|
||||
Map.of())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmBoundaryDisputedFromWay() {
|
||||
var relation = new ReaderRelation(1);
|
||||
relation.setTag("type", "boundary");
|
||||
relation.setTag("admin_level", "5");
|
||||
relation.setTag("boundary", "administrative");
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
|
||||
"disputed", 1,
|
||||
"maritime", 0,
|
||||
"admin_level", 5
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation),
|
||||
Map.of(
|
||||
"disputed", "yes"
|
||||
))
|
||||
));
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
|
||||
"disputed", 1,
|
||||
"maritime", 0,
|
||||
"admin_level", 5,
|
||||
"claimed_by", "A",
|
||||
"disputed_name", "AB"
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation),
|
||||
Map.of(
|
||||
"disputed", "yes",
|
||||
"claimed_by", "A",
|
||||
"name", "AB"
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountryBoundaryEmittedIfNoName() {
|
||||
var relation = new ReaderRelation(1);
|
||||
relation.setTag("type", "boundary");
|
||||
relation.setTag("admin_level", "2");
|
||||
relation.setTag("boundary", "administrative");
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "boundary",
|
||||
"_type", "line",
|
||||
|
||||
"disputed", 0,
|
||||
"maritime", 0,
|
||||
"admin_level", 2
|
||||
)), process(lineFeatureWithRelation(
|
||||
profile.preprocessOsmRelation(relation),
|
||||
Map.of())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountryLeftRightName() {
|
||||
var country1 = new ReaderRelation(1);
|
||||
country1.setTag("type", "boundary");
|
||||
country1.setTag("admin_level", "2");
|
||||
country1.setTag("boundary", "administrative");
|
||||
country1.setTag("ISO3166-1:alpha3", "C1");
|
||||
var country2 = new ReaderRelation(2);
|
||||
country2.setTag("type", "boundary");
|
||||
country2.setTag("admin_level", "2");
|
||||
country2.setTag("boundary", "administrative");
|
||||
country2.setTag("ISO3166-1:alpha3", "C2");
|
||||
|
||||
// shared edge
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 0, 10),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
3,
|
||||
Stream.concat(
|
||||
profile.preprocessOsmRelation(country1).stream(),
|
||||
profile.preprocessOsmRelation(country2).stream()
|
||||
).map(r -> new OpenStreetMapReader.RelationMember<>("", r)).toList()
|
||||
)
|
||||
));
|
||||
|
||||
// other 2 edges of country 1
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 5, 10),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
4,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)
|
||||
));
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 10, 5, 10),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
4,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)
|
||||
));
|
||||
|
||||
// other 2 edges of country 2
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, -5, 10),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
4,
|
||||
profile.preprocessOsmRelation(country2).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)
|
||||
));
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 10, -5, 10),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
4,
|
||||
profile.preprocessOsmRelation(country2).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)
|
||||
));
|
||||
|
||||
List<FeatureCollector.Feature> features = new ArrayList<>();
|
||||
profile.finish(OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
|
||||
assertEquals(3, features.size());
|
||||
|
||||
// ensure shared edge has country labels on right sides
|
||||
var sharedEdge = features.stream()
|
||||
.filter(c -> c.getAttrsAtZoom(0).containsKey("adm0_l") && c.getAttrsAtZoom(0).containsKey("adm0_r")).findFirst()
|
||||
.get();
|
||||
if (sharedEdge.getGeometry().getCoordinate().y == 0.5) { // going up
|
||||
assertEquals("C1", sharedEdge.getAttrsAtZoom(0).get("adm0_r"));
|
||||
assertEquals("C2", sharedEdge.getAttrsAtZoom(0).get("adm0_l"));
|
||||
} else { // going down
|
||||
assertEquals("C2", sharedEdge.getAttrsAtZoom(0).get("adm0_r"));
|
||||
assertEquals("C1", sharedEdge.getAttrsAtZoom(0).get("adm0_l"));
|
||||
}
|
||||
var c1 = features.stream()
|
||||
.filter(c -> c.getGeometry().getEnvelopeInternal().getMaxX() > 0.5).findFirst()
|
||||
.get();
|
||||
if (c1.getGeometry().getCoordinate().y == 0.5) { // going up
|
||||
assertEquals("C1", c1.getAttrsAtZoom(0).get("adm0_l"));
|
||||
} else { // going down
|
||||
assertEquals("C1", c1.getAttrsAtZoom(0).get("adm0_r"));
|
||||
}
|
||||
var c2 = features.stream()
|
||||
.filter(c -> c.getGeometry().getEnvelopeInternal().getMinX() < 0.5).findFirst()
|
||||
.get();
|
||||
if (c2.getGeometry().getCoordinate().y == 0.5) { // going up
|
||||
assertEquals("C2", c2.getAttrsAtZoom(0).get("adm0_r"));
|
||||
} else { // going down
|
||||
assertEquals("C2", c2.getAttrsAtZoom(0).get("adm0_l"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountryBoundaryNotClosed() {
|
||||
var country1 = new ReaderRelation(1);
|
||||
country1.setTag("type", "boundary");
|
||||
country1.setTag("admin_level", "2");
|
||||
country1.setTag("boundary", "administrative");
|
||||
country1.setTag("ISO3166-1:alpha3", "C1");
|
||||
|
||||
// shared edge
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 0, 10, 5, 5),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
3,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)));
|
||||
|
||||
List<FeatureCollector.Feature> features = new ArrayList<>();
|
||||
profile.finish(OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"adm0_r", "<null>",
|
||||
"adm0_l", "<null>",
|
||||
"maritime", 0,
|
||||
"disputed", 0,
|
||||
"admin_level", 2,
|
||||
|
||||
"_layer", "boundary"
|
||||
)), features);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCountry() throws GeometryException {
|
||||
var country1 = new ReaderRelation(1);
|
||||
country1.setTag("type", "boundary");
|
||||
country1.setTag("admin_level", "2");
|
||||
country1.setTag("boundary", "administrative");
|
||||
country1.setTag("ISO3166-1:alpha3", "C1");
|
||||
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
GeoUtils.polygonToLineString(rectangle(0, 10)),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
3,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)));
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
GeoUtils.polygonToLineString(rectangle(1, 9)),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
3,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)));
|
||||
|
||||
List<FeatureCollector.Feature> features = new ArrayList<>();
|
||||
profile.finish(OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"adm0_l", "C1",
|
||||
"adm0_r", "<null>"
|
||||
), Map.of(
|
||||
"adm0_r", "C1",
|
||||
"adm0_l", "<null>"
|
||||
)), features);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontLabelBadPolygon() throws GeometryException {
|
||||
var country1 = new ReaderRelation(1);
|
||||
country1.setTag("type", "boundary");
|
||||
country1.setTag("admin_level", "2");
|
||||
country1.setTag("boundary", "administrative");
|
||||
country1.setTag("ISO3166-1:alpha3", "C1");
|
||||
|
||||
assertFeatures(14, List.of(), process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(newLineString(0, 0, 10, 0, 10, 10, 2, 10, 2, -2)),
|
||||
Map.of(),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
3,
|
||||
profile.preprocessOsmRelation(country1).stream().map(r -> new OpenStreetMapReader.RelationMember<>("", r))
|
||||
.toList()
|
||||
)));
|
||||
|
||||
List<FeatureCollector.Feature> features = new ArrayList<>();
|
||||
profile.finish(OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"adm0_l", "<null>",
|
||||
"adm0_r", "<null>"
|
||||
)), features);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ReaderFeature lineFeatureWithRelation(List<OpenStreetMapReader.RelationInfo> relationInfos,
|
||||
Map<String, Object> map) {
|
||||
return new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
map,
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0,
|
||||
(relationInfos == null ? List.<OpenStreetMapReader.RelationInfo>of() : relationInfos).stream()
|
||||
.map(r -> new OpenStreetMapReader.RelationMember<>("", r)).toList()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class HousenumberTest extends BaseLayerTest {
|
||||
|
||||
@Test
|
||||
public void testHousenumber() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "housenumber",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 8d
|
||||
)), process(pointFeature(Map.of(
|
||||
"addr:housenumber", "10"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_layer", "housenumber",
|
||||
"_type", "point",
|
||||
"_minzoom", 14,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 8d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"addr:housenumber", "10"
|
||||
))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
public class MountainPeakTest extends BaseLayerTest {
|
||||
|
||||
@TestFactory
|
||||
public List<DynamicTest> mountainPeakProcessing() {
|
||||
wikidataTranslations.put(123, "es", "es wd name");
|
||||
return List.of(
|
||||
dynamicTest("happy path", () -> {
|
||||
var peak = process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "test",
|
||||
"ele", "100",
|
||||
"wikidata", "Q123"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "peak",
|
||||
"ele", 100,
|
||||
"ele_ft", 328,
|
||||
|
||||
"_layer", "mountain_peak",
|
||||
"_type", "point",
|
||||
"_minzoom", 7,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 64d
|
||||
)), peak);
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"name:latin", "test",
|
||||
"name", "test",
|
||||
"name:es", "es wd name"
|
||||
)), peak);
|
||||
}),
|
||||
|
||||
dynamicTest("labelgrid", () -> {
|
||||
var peak = process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"ele", "100"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_labelgrid_limit", 0
|
||||
)), peak);
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_labelgrid_limit", 5,
|
||||
"_labelgrid_size", 100d
|
||||
)), peak);
|
||||
}),
|
||||
|
||||
dynamicTest("volcano", () ->
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "volcano"
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "volcano",
|
||||
"ele", "100"
|
||||
))))),
|
||||
|
||||
dynamicTest("no elevation", () ->
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"natural", "volcano"
|
||||
))))),
|
||||
|
||||
dynamicTest("bogus elevation", () ->
|
||||
assertFeatures(14, List.of(), process(pointFeature(Map.of(
|
||||
"natural", "volcano",
|
||||
"ele", "11000"
|
||||
))))),
|
||||
|
||||
dynamicTest("ignore lines", () ->
|
||||
assertFeatures(14, List.of(), process(lineFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"ele", "100"
|
||||
))))),
|
||||
|
||||
dynamicTest("zorder", () -> {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"ele", "100"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 10100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"ele", "100"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"_zorder", 20100
|
||||
)), process(pointFeature(Map.of(
|
||||
"natural", "peak",
|
||||
"name", "name",
|
||||
"wikipedia", "wikilink",
|
||||
"ele", "100"
|
||||
))));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMountainPeakPostProcessing() throws GeometryException {
|
||||
assertEquals(List.of(), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of()));
|
||||
|
||||
assertEquals(List.of(pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1),
|
||||
1
|
||||
)), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of(pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of(),
|
||||
1
|
||||
))));
|
||||
|
||||
assertEquals(List.of(
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 2, "name", "a"),
|
||||
1
|
||||
), pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1, "name", "b"),
|
||||
1
|
||||
), pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("rank", 1, "name", "c"),
|
||||
2
|
||||
)
|
||||
), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of(
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "a"),
|
||||
1
|
||||
),
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "b"),
|
||||
1
|
||||
),
|
||||
pointFeature(
|
||||
MountainPeak.LAYER_NAME,
|
||||
Map.of("name", "c"),
|
||||
2
|
||||
)
|
||||
)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.OSM_SOURCE;
|
||||
|
||||
import com.onthegomap.flatmap.TestUtils;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WaterNameTest extends BaseLayerTest {
|
||||
|
||||
@Test
|
||||
public void testWaterNamePoint() {
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"class", "lake",
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"intermittent", 1,
|
||||
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(1, Map.of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"natural", "water",
|
||||
"water", "pond",
|
||||
"intermittent", "1"
|
||||
))));
|
||||
double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11);
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 11,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(z11area, Map.of(
|
||||
"name", "waterway",
|
||||
"natural", "water",
|
||||
"water", "pond"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterNameLakeline() {
|
||||
assertFeatures(11, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"OSM_ID", -10
|
||||
)),
|
||||
LAKE_CENTERLINE_SOURCE,
|
||||
null,
|
||||
0
|
||||
)));
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water"
|
||||
), Map.of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
|
||||
"_layer", "water_name",
|
||||
"_type", "line",
|
||||
"_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))),
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", "waterway".length() * 6d
|
||||
)), process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"name", "waterway",
|
||||
"name:es", "waterway es",
|
||||
"natural", "water",
|
||||
"water", "pond"
|
||||
)),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
10
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarinePoint() {
|
||||
assertFeatures(11, List.of(), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
new HashMap<>(Map.<String, Object>of(
|
||||
"scalerank", 10,
|
||||
"name", "pacific ocean"
|
||||
)),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_geography_marine_polys",
|
||||
0
|
||||
)));
|
||||
|
||||
// name match - use scale rank from NE
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"name", "Pacific",
|
||||
"name:es", "Pacific es",
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 10,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Pacific",
|
||||
"name:es", "Pacific es",
|
||||
"place", "sea"
|
||||
))));
|
||||
|
||||
// name match but ocean - use min zoom=0
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 0,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Pacific",
|
||||
"place", "ocean"
|
||||
))));
|
||||
|
||||
// no name match - use OSM rank
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"rank", 9,
|
||||
"name", "Atlantic",
|
||||
"place", "sea"
|
||||
))));
|
||||
|
||||
// no rank at all, default to 8
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "water_name",
|
||||
"_type", "point",
|
||||
"_minzoom", 8,
|
||||
"_maxzoom", 14
|
||||
)), process(pointFeature(Map.of(
|
||||
"name", "Atlantic",
|
||||
"place", "sea"
|
||||
))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.OSM_SOURCE;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.WATER_POLYGON_SOURCE;
|
||||
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WaterTest extends BaseLayerTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testWaterNaturalEarth() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 0
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_lakes",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 0
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_ocean",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_lakes",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_ocean",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterOsmWaterPolygon() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"class", "ocean",
|
||||
"intermittent", "<null>",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
WATER_POLYGON_SOURCE,
|
||||
null,
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWater() {
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"natural", "water",
|
||||
"water", "reservoir"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "lake",
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"leisure", "swimming_pool"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"natural", "bay"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of()), process(polygonFeature(Map.of(
|
||||
"natural", "water"
|
||||
))));
|
||||
assertFeatures(14, List.of(), process(polygonFeature(Map.of(
|
||||
"natural", "water",
|
||||
"covered", "yes"
|
||||
))));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"brunnel", "bridge",
|
||||
"intermittent", 1,
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"waterway", "stream",
|
||||
"bridge", "1",
|
||||
"intermittent", "1"
|
||||
))));
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"class", "lake",
|
||||
"brunnel", "<null>",
|
||||
"intermittent", 0,
|
||||
|
||||
"_layer", "water",
|
||||
"_type", "polygon",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", 2d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"landuse", "salt_pond",
|
||||
"bridge", "1"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOceanZoomLevels() {
|
||||
assertCoversZoomRange(0, 14, "water",
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_ocean",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_ocean",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_ocean",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
WATER_POLYGON_SOURCE,
|
||||
null,
|
||||
0
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLakeZoomLevels() {
|
||||
assertCoversZoomRange(0, 14, "water",
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_lakes",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_lakes",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_lakes",
|
||||
0
|
||||
)),
|
||||
process(new ReaderFeature(
|
||||
rectangle(0, 10),
|
||||
Map.of(
|
||||
"natural", "water",
|
||||
"water", "reservoir"
|
||||
),
|
||||
OSM_SOURCE,
|
||||
null,
|
||||
0
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WaterwayTest extends BaseLayerTest {
|
||||
|
||||
@Test
|
||||
public void testWaterwayImportantRiverProcess() {
|
||||
var charlesRiver = process(lineFeature(Map.of(
|
||||
"waterway", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name"
|
||||
)));
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name",
|
||||
"intermittent", 0,
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14,
|
||||
"_buffer", 4d
|
||||
)), charlesRiver);
|
||||
assertFeatures(11, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "charles river",
|
||||
"name:es", "es name",
|
||||
"intermittent", "<null>",
|
||||
"_buffer", 13.082664546679323
|
||||
)), charlesRiver);
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"class", "river",
|
||||
"_buffer", 26.165329093358647
|
||||
)), charlesRiver);
|
||||
assertFeatures(9, List.of(Map.of(
|
||||
"class", "river",
|
||||
"_buffer", 26.165329093358647
|
||||
)), charlesRiver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwayImportantRiverPostProcess() throws GeometryException {
|
||||
var line1 = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(0, 0, 10, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
var line2 = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(10, 0, 20, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
var connected = new VectorTileEncoder.Feature(
|
||||
Waterway.LAYER_NAME,
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(newLineString(00, 0, 20, 0)),
|
||||
Map.of("name", "river"),
|
||||
0
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
List.of(),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of())
|
||||
);
|
||||
assertEquals(
|
||||
List.of(line1, line2),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 12, List.of(line1, line2))
|
||||
);
|
||||
assertEquals(
|
||||
List.of(connected),
|
||||
profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of(line1, line2))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwaySmaller() {
|
||||
// river with no name is not important
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "river",
|
||||
"brunnel", "bridge",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 12
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "river",
|
||||
"bridge", "1"
|
||||
))));
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "canal",
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 12
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "canal",
|
||||
"name", "name"
|
||||
))));
|
||||
|
||||
assertFeatures(14, List.of(Map.of(
|
||||
"class", "stream",
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 13
|
||||
)), process(lineFeature(Map.of(
|
||||
"waterway", "stream",
|
||||
"name", "name"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaterwayNaturalEarth() {
|
||||
assertFeatures(3, List.of(Map.of(
|
||||
"class", "river",
|
||||
"name", "<null>",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 3,
|
||||
"_maxzoom", 3
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "river",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 4,
|
||||
"_maxzoom", 5
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"class", "river",
|
||||
"intermittent", "<null>",
|
||||
|
||||
"_layer", "waterway",
|
||||
"_type", "line",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 8
|
||||
)), process(new ReaderFeature(
|
||||
newLineString(0, 0, 1, 1),
|
||||
Map.of(
|
||||
"featurecla", "River",
|
||||
"name", "name"
|
||||
),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_rivers_lake_centerlines",
|
||||
0
|
||||
)));
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue