kopia lustrzana https://github.com/onthegomap/planetiler
landcover, landuse, park
rodzic
753a4aee03
commit
7e9e342867
|
@ -106,7 +106,8 @@ public class Arguments {
|
|||
|
||||
public List<String> get(String arg, String description, String[] defaultValue) {
|
||||
String value = getArg(arg, String.join(",", defaultValue));
|
||||
List<String> results = List.of(value.split("[\\s,]+"));
|
||||
List<String> results = Stream.of(value.split("[\\s,]+"))
|
||||
.filter(c -> !c.isBlank()).toList();
|
||||
LOGGER.debug(description + ": " + value);
|
||||
return results;
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
private double defaultPixelTolerance = 0.1d;
|
||||
private double pixelToleranceAtMaxZoom = 256d / 4096;
|
||||
private ZoomFunction<Double> pixelTolerance = null;
|
||||
private String numPointsAttr = null;
|
||||
|
||||
private Feature(String layer, Geometry geom, long sourceId) {
|
||||
this.layer = layer;
|
||||
|
@ -349,5 +350,14 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
attrs.putAll(names);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Feature setNumPointsAttr(String numPointsAttr) {
|
||||
this.numPointsAttr = numPointsAttr;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNumPointsAttr() {
|
||||
return numPointsAttr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,17 @@ public class FeatureMerge {
|
|||
|
||||
public static List<VectorTileEncoder.Feature> mergePolygons(List<VectorTileEncoder.Feature> features, double minArea,
|
||||
double minDist, double buffer) throws GeometryException {
|
||||
return mergePolygons(
|
||||
features,
|
||||
minArea,
|
||||
0,
|
||||
minDist,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
public static List<VectorTileEncoder.Feature> mergePolygons(List<VectorTileEncoder.Feature> features, double minArea,
|
||||
double minHoleArea, double minDist, double buffer) throws GeometryException {
|
||||
List<VectorTileEncoder.Feature> result = new ArrayList<>(features.size());
|
||||
Collection<List<VectorTileEncoder.Feature>> groupedByAttrs = groupByAttrs(features, result, GeometryType.POLYGON);
|
||||
for (List<VectorTileEncoder.Feature> groupedFeatures : groupedByAttrs) {
|
||||
|
@ -167,7 +178,7 @@ public class FeatureMerge {
|
|||
}
|
||||
}
|
||||
// TODO VW simplify?
|
||||
extractPolygons(merged, outPolygons, minArea);
|
||||
extractPolygons(merged, outPolygons, minArea, minHoleArea);
|
||||
}
|
||||
if (!outPolygons.isEmpty()) {
|
||||
Geometry combined = GeoUtils.combinePolygons(outPolygons);
|
||||
|
@ -177,11 +188,11 @@ public class FeatureMerge {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static void extractPolygons(Geometry geom, List<Polygon> result, double minArea) {
|
||||
private static void extractPolygons(Geometry geom, List<Polygon> result, double minArea, double minHoleArea) {
|
||||
if (geom instanceof Polygon poly) {
|
||||
if (Area.ofRing(poly.getExteriorRing().getCoordinateSequence()) > minArea) {
|
||||
int innerRings = poly.getNumInteriorRing();
|
||||
if (innerRings > 0) {
|
||||
if (minHoleArea > 0 && innerRings > 0) {
|
||||
List<LinearRing> rings = new ArrayList<>(innerRings);
|
||||
for (int i = 0; i < innerRings; i++) {
|
||||
LinearRing innerRing = poly.getInteriorRingN(i);
|
||||
|
@ -197,7 +208,7 @@ public class FeatureMerge {
|
|||
}
|
||||
} else if (geom instanceof GeometryCollection) {
|
||||
for (int i = 0; i < geom.getNumGeometries(); i++) {
|
||||
extractPolygons(geom.getGeometryN(i), result, minArea);
|
||||
extractPolygons(geom.getGeometryN(i), result, minArea, minHoleArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -446,6 +446,10 @@ public class VectorTileEncoder {
|
|||
|
||||
public static final long NO_GROUP = Long.MIN_VALUE;
|
||||
|
||||
public boolean hasGroup() {
|
||||
return group != NO_GROUP;
|
||||
}
|
||||
|
||||
public Feature copyWithNewGeometry(Geometry newGeometry) {
|
||||
return new Feature(
|
||||
layer,
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.onthegomap.flatmap.geo.GeoUtils;
|
|||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
@ -143,6 +144,7 @@ public class FeatureRenderer implements Consumer<FeatureCollector.Feature> {
|
|||
long id = idGen.incrementAndGet();
|
||||
boolean area = input instanceof Polygonal;
|
||||
double worldLength = (area || input.getNumGeometries() > 1) ? 0 : input.getLength();
|
||||
String numPointsAttr = feature.getNumPointsAttr();
|
||||
for (int z = feature.getMaxZoom(); z >= feature.getMinZoom(); z--) {
|
||||
double scale = 1 << z;
|
||||
double tolerance = feature.getPixelTolerance(z) / 256d;
|
||||
|
@ -164,14 +166,19 @@ public class FeatureRenderer implements Consumer<FeatureCollector.Feature> {
|
|||
double buffer = feature.getBufferPixelsAtZoom(z) / 256;
|
||||
TileExtents.ForZoom extents = config.extents().getForZoom(z);
|
||||
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents, feature.sourceId());
|
||||
writeTileFeatures(z, id, feature, sliced);
|
||||
Map<String, Object> attrs = feature.getAttrsAtZoom(sliced.zoomLevel());
|
||||
if (numPointsAttr != null) {
|
||||
attrs = new HashMap<>(attrs);
|
||||
attrs.put(numPointsAttr, geom.getNumPoints());
|
||||
}
|
||||
writeTileFeatures(z, id, feature, sliced, attrs);
|
||||
}
|
||||
|
||||
stats.processedElement(area ? "polygon" : "line", feature.getLayer());
|
||||
}
|
||||
|
||||
private void writeTileFeatures(int zoom, long id, FeatureCollector.Feature feature, TiledGeometry sliced) {
|
||||
Map<String, Object> attrs = feature.getAttrsAtZoom(sliced.zoomLevel());
|
||||
private void writeTileFeatures(int zoom, long id, FeatureCollector.Feature feature, TiledGeometry sliced,
|
||||
Map<String, Object> attrs) {
|
||||
int emitted = 0;
|
||||
for (var entry : sliced.getTileData()) {
|
||||
TileCoord tile = entry.getKey();
|
||||
|
|
|
@ -621,6 +621,7 @@ public class FeatureMergeTest {
|
|||
feature(1, rectangle(10, 20, 22, 22), Map.of("a", 1))
|
||||
),
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0
|
||||
)
|
||||
|
|
|
@ -416,6 +416,45 @@ public class FlatMapTest {
|
|||
), results.tiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumPointsAttr() throws Exception {
|
||||
double x1 = 0.5 + Z14_WIDTH / 2;
|
||||
double y1 = 0.5 + Z14_WIDTH / 2 - Z14_WIDTH / 2;
|
||||
double x2 = x1 + Z14_WIDTH;
|
||||
double y2 = y1 + Z14_WIDTH + Z14_WIDTH / 2;
|
||||
double x3 = x2 + Z14_WIDTH;
|
||||
double y3 = y2 + Z14_WIDTH;
|
||||
double lat1 = GeoUtils.getWorldLat(y1);
|
||||
double lng1 = GeoUtils.getWorldLon(x1);
|
||||
double lat2 = GeoUtils.getWorldLat(y2);
|
||||
double lng2 = GeoUtils.getWorldLon(x2);
|
||||
double lat3 = GeoUtils.getWorldLat(y3);
|
||||
double lng3 = GeoUtils.getWorldLon(x3);
|
||||
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
newReaderFeature(newLineString(lng1, lat1, lng2, lat2, lng3, lat3), Map.of(
|
||||
"attr", "value"
|
||||
))
|
||||
),
|
||||
(in, features) -> {
|
||||
features.line("layer")
|
||||
.setZoomRange(13, 14)
|
||||
.setBufferPixels(4)
|
||||
.setNumPointsAttr("_numpoints");
|
||||
}
|
||||
);
|
||||
|
||||
assertSubmap(Map.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2 + 2, Z14_TILES / 2 + 2, 14), List.of(
|
||||
feature(newLineString(-4, -4, 128, 128), Map.of(
|
||||
"_numpoints", 3L
|
||||
))
|
||||
)
|
||||
), results.tiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiLineString() throws Exception {
|
||||
double x1 = 0.5 + Z14_WIDTH / 2;
|
||||
|
|
|
@ -375,6 +375,7 @@ public class TestUtils {
|
|||
result.put("_labelgrid_size", feature.getLabelGridPixelSizeAtZoom(zoom));
|
||||
result.put("_minpixelsize", feature.getMinPixelSize(zoom));
|
||||
result.put("_type", geom instanceof Puntal ? "point" : geom instanceof Lineal ? "line" : "polygon");
|
||||
result.put("_numpointsattr", feature.getNumPointsAttr());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,12 @@ public class OpenMapTilesProfile implements Profile {
|
|||
}
|
||||
|
||||
public OpenMapTilesProfile(Translations translations, Arguments arguments, Stats stats) {
|
||||
this.layers = OpenMapTilesSchema.createInstances(translations, arguments, stats);
|
||||
List<String> onlyLayers = arguments.get("only_layers", "Include only certain layers", new String[]{});
|
||||
List<String> excludeLayers = arguments.get("exclude_layers", "Exclude certain layers", new String[]{});
|
||||
this.layers = OpenMapTilesSchema.createInstances(translations, arguments, stats)
|
||||
.stream()
|
||||
.filter(l -> (onlyLayers.isEmpty() || onlyLayers.contains(l.name())) && !excludeLayers.contains(l.name()))
|
||||
.toList();
|
||||
osmDispatchMap = new HashMap<>();
|
||||
Tables.generateDispatchMap(layers).forEach((clazz, handlers) -> {
|
||||
osmDispatchMap.put(clazz, handlers.stream().map(handler -> {
|
||||
|
|
|
@ -19,6 +19,14 @@ public class Utils {
|
|||
return a != null ? a : b != null ? b : c != null ? c : d;
|
||||
}
|
||||
|
||||
public static <T> T coalesce(T a, T b, T c, T d, T e) {
|
||||
return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e;
|
||||
}
|
||||
|
||||
public static <T> T coalesce(T a, T b, T c, T d, T e, T f) {
|
||||
return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e != null ? e : f;
|
||||
}
|
||||
|
||||
public static <T> T coalesceLazy(T a, Supplier<T> b) {
|
||||
return a != null ? a : b.get();
|
||||
}
|
||||
|
|
|
@ -126,6 +126,6 @@ public class Building implements OpenMapTilesSchema.Building,
|
|||
@Override
|
||||
public List<VectorTileEncoder.Feature> postProcess(int zoom,
|
||||
List<VectorTileEncoder.Feature> items) throws GeometryException {
|
||||
return (mergeZ13Buildings && zoom == 13) ? FeatureMerge.mergePolygons(items, 4, 0.5, 0.5) : items;
|
||||
return (mergeZ13Buildings && zoom == 13) ? FeatureMerge.mergePolygons(items, 4, 4, 0.5, 0.5) : items;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,131 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.FeatureMerge;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.ZoomFunction;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.MultiExpression;
|
||||
import com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.OpenMapTilesSchema;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.Tables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Landcover implements OpenMapTilesSchema.Landcover {
|
||||
public class Landcover implements
|
||||
OpenMapTilesSchema.Landcover,
|
||||
OpenMapTilesProfile.NaturalEarthProcessor,
|
||||
Tables.OsmLandcoverPolygon.Handler,
|
||||
OpenMapTilesProfile.FeaturePostProcessor {
|
||||
|
||||
public static final ZoomFunction<Number> MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of(
|
||||
13, 8,
|
||||
10, 4,
|
||||
9, 2
|
||||
));
|
||||
private static final String NUM_POINTS_ATTR = "_numpoints";
|
||||
private static final Set<String> WOOD_OR_FOREST = Set.of(
|
||||
FieldValues.SUBCLASS_WOOD,
|
||||
FieldValues.SUBCLASS_FOREST
|
||||
);
|
||||
private final MultiExpression.MultiExpressionIndex<String> classMapping;
|
||||
|
||||
public Landcover(Translations translations, Arguments args, Stats stats) {
|
||||
this.classMapping = FieldMappings.Class.index();
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
private String getClassFromSubclass(String subclass) {
|
||||
return subclass == null ? null : classMapping.getOrElse(Map.of(Fields.SUBCLASS, subclass), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processNaturalEarth(String table, SourceFeature feature,
|
||||
FeatureCollector features) {
|
||||
record LandcoverInfo(String subclass, int minzoom, int maxzoom) {}
|
||||
LandcoverInfo info = switch (table) {
|
||||
case "ne_110m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 0, 1);
|
||||
case "ne_50m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 2, 4);
|
||||
case "ne_10m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 5, 6);
|
||||
case "ne_50m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 2, 4);
|
||||
case "ne_10m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 5, 6);
|
||||
default -> null;
|
||||
};
|
||||
if (info != null) {
|
||||
String clazz = getClassFromSubclass(info.subclass);
|
||||
if (clazz != null) {
|
||||
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.CLASS, clazz)
|
||||
.setAttr(Fields.SUBCLASS, info.subclass)
|
||||
.setZoomRange(info.minzoom, info.maxzoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Tables.OsmLandcoverPolygon element, FeatureCollector features) {
|
||||
String subclass = element.subclass();
|
||||
String clazz = getClassFromSubclass(subclass);
|
||||
if (clazz != null) {
|
||||
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setMinPixelSizeThresholds(MIN_PIXEL_SIZE_THRESHOLDS)
|
||||
.setAttr(Fields.CLASS, clazz)
|
||||
.setAttr(Fields.SUBCLASS, subclass)
|
||||
.setNumPointsAttr(NUM_POINTS_ATTR)
|
||||
.setZoomRange(WOOD_OR_FOREST.contains(subclass) ? 9 : 7, 14);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VectorTileEncoder.Feature> postProcess(int zoom, List<VectorTileEncoder.Feature> items)
|
||||
throws GeometryException {
|
||||
if (zoom < 7 || zoom > 13) {
|
||||
for (var item : items) {
|
||||
item.attrs().remove(NUM_POINTS_ATTR);
|
||||
}
|
||||
return items;
|
||||
} else { // z7-13
|
||||
String groupKey = "_group";
|
||||
List<VectorTileEncoder.Feature> result = new ArrayList<>();
|
||||
List<VectorTileEncoder.Feature> toMerge = new ArrayList<>();
|
||||
for (var item : items) {
|
||||
Map<String, Object> attrs = item.attrs();
|
||||
Object numPointsObj = attrs.remove(NUM_POINTS_ATTR);
|
||||
Object subclassObj = attrs.get(Fields.SUBCLASS);
|
||||
if (numPointsObj instanceof Number num && subclassObj instanceof String subclass) {
|
||||
long numPoints = num.longValue();
|
||||
if (zoom >= 10) {
|
||||
if (WOOD_OR_FOREST.contains(subclass) && numPoints < 300) {
|
||||
attrs.put(groupKey, numPoints < 50 ? "<50" : "<300");
|
||||
toMerge.add(item);
|
||||
} else { // don't merge
|
||||
result.add(item);
|
||||
}
|
||||
} else if (zoom == 9) {
|
||||
if (WOOD_OR_FOREST.contains(subclass)) {
|
||||
attrs.put(groupKey, numPoints < 50 ? "<50" : numPoints < 300 ? "<300" : ">300");
|
||||
toMerge.add(item);
|
||||
} else { // don't merge
|
||||
result.add(item);
|
||||
}
|
||||
} else { // zoom between 7 and 8
|
||||
toMerge.add(item);
|
||||
}
|
||||
} else {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
var merged = FeatureMerge.mergePolygons(toMerge, 4, 0, 0);
|
||||
for (var item : merged) {
|
||||
item.attrs().remove(groupKey);
|
||||
}
|
||||
result.addAll(merged);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,69 @@
|
|||
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.openmaptiles.Utils.coalesce;
|
||||
import static com.onthegomap.flatmap.openmaptiles.Utils.nullIfEmpty;
|
||||
|
||||
public class Landuse implements OpenMapTilesSchema.Landuse {
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.Parse;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.Translations;
|
||||
import com.onthegomap.flatmap.ZoomFunction;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.OpenMapTilesSchema;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.Tables;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Landuse implements
|
||||
OpenMapTilesSchema.Landuse,
|
||||
OpenMapTilesProfile.NaturalEarthProcessor,
|
||||
Tables.OsmLandusePolygon.Handler {
|
||||
|
||||
public Landuse(Translations translations, Arguments args, Stats stats) {
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
@Override
|
||||
public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) {
|
||||
if ("ne_50m_urban_areas".equals(table)) {
|
||||
Double scalerank = Parse.parseDoubleOrNull(feature.getTag("scalerank"));
|
||||
if (scalerank != null && scalerank <= 2) {
|
||||
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.CLASS, FieldValues.CLASS_RESIDENTIAL)
|
||||
.setZoomRange(4, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final ZoomFunction<Number> MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of(
|
||||
13, 4,
|
||||
7, 2,
|
||||
6, 1
|
||||
));
|
||||
|
||||
private static final Set<String> Z6_CLASSES = Set.of(
|
||||
FieldValues.CLASS_RESIDENTIAL,
|
||||
FieldValues.CLASS_SUBURB,
|
||||
FieldValues.CLASS_QUARTER,
|
||||
FieldValues.CLASS_NEIGHBOURHOOD
|
||||
);
|
||||
|
||||
@Override
|
||||
public void process(Tables.OsmLandusePolygon element, FeatureCollector features) {
|
||||
String clazz = coalesce(
|
||||
nullIfEmpty(element.landuse()),
|
||||
nullIfEmpty(element.amenity()),
|
||||
nullIfEmpty(element.leisure()),
|
||||
nullIfEmpty(element.tourism()),
|
||||
nullIfEmpty(element.place()),
|
||||
nullIfEmpty(element.waterway())
|
||||
);
|
||||
if (clazz != null) {
|
||||
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.CLASS, clazz)
|
||||
.setMinPixelSizeThresholds(MIN_PIXEL_SIZE_THRESHOLDS)
|
||||
.setZoomRange(Z6_CLASSES.contains(clazz) ? 6 : 9, 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,100 @@
|
|||
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.collections.FeatureGroup.Z_ORDER_BITS;
|
||||
import static com.onthegomap.flatmap.collections.FeatureGroup.Z_ORDER_MIN;
|
||||
import static com.onthegomap.flatmap.openmaptiles.Utils.coalesce;
|
||||
import static com.onthegomap.flatmap.openmaptiles.Utils.nullIfEmpty;
|
||||
|
||||
public class Park implements OpenMapTilesSchema.Park {
|
||||
import com.carrotsearch.hppc.LongIntHashMap;
|
||||
import com.carrotsearch.hppc.LongIntMap;
|
||||
import com.onthegomap.flatmap.Arguments;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.GeometryType;
|
||||
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.LanguageUtils;
|
||||
import com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.OpenMapTilesSchema;
|
||||
import com.onthegomap.flatmap.openmaptiles.generated.Tables;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Park implements
|
||||
OpenMapTilesSchema.Park,
|
||||
Tables.OsmParkPolygon.Handler,
|
||||
OpenMapTilesProfile.FeaturePostProcessor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Park.class);
|
||||
private final Translations translations;
|
||||
|
||||
public Park(Translations translations, Arguments args, Stats stats) {
|
||||
this.translations = translations;
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
private static final int PARK_NATIONAL_PARK_BOOST = 1 << (Z_ORDER_BITS - 1);
|
||||
private static final int PARK_WIKIPEDIA_BOOST = 1 << (Z_ORDER_BITS - 2);
|
||||
private static final double WORLD_AREA_FOR_70K_SQUARE_METERS =
|
||||
Math.pow(GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d, 2);
|
||||
private static final double LOG2 = Math.log(2);
|
||||
private static final int PARK_AREA_RANGE = 1 << (Z_ORDER_BITS - 3);
|
||||
private static final double PARK_LOG_RANGE = Math.log(Math.pow(4, 26)); // 2^14 tiles, 2^12 pixels per tile
|
||||
private static final double LOG4 = Math.log(4);
|
||||
|
||||
@Override
|
||||
public void process(Tables.OsmParkPolygon element, FeatureCollector features) {
|
||||
String protectionTitle = element.protectionTitle();
|
||||
if (protectionTitle != null) {
|
||||
protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT);
|
||||
}
|
||||
String clazz = coalesce(
|
||||
nullIfEmpty(protectionTitle),
|
||||
nullIfEmpty(element.boundary()),
|
||||
nullIfEmpty(element.leisure())
|
||||
);
|
||||
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.CLASS, clazz)
|
||||
.setMinPixelSize(2)
|
||||
.setZoomRange(6, 14);
|
||||
|
||||
if (element.name() != null) {
|
||||
try {
|
||||
double area = element.source().area();
|
||||
int minzoom = (int) Math.floor(20 - Math.log(area / WORLD_AREA_FOR_70K_SQUARE_METERS) / LOG2);
|
||||
double logWorldArea = Math.min(1d, Math.max(0d, (Math.log(area) + PARK_LOG_RANGE) / PARK_LOG_RANGE));
|
||||
int areaBoost = (int) (logWorldArea * PARK_AREA_RANGE);
|
||||
minzoom = Math.min(14, Math.max(6, minzoom));
|
||||
|
||||
features.centroid(LAYER_NAME).setBufferPixels(256)
|
||||
.setAttr(Fields.CLASS, clazz)
|
||||
.setAttrs(LanguageUtils.getNames(element.source().properties(), translations))
|
||||
.setLabelGridPixelSize(14, 100)
|
||||
.setZorder(Z_ORDER_MIN +
|
||||
("national_park".equals(clazz) ? PARK_NATIONAL_PARK_BOOST : 0) +
|
||||
((element.source().hasTag("wikipedia") || element.source().hasTag("wikidata")) ? PARK_WIKIPEDIA_BOOST : 0) +
|
||||
areaBoost
|
||||
).setZoomRange(minzoom, 14);
|
||||
} catch (GeometryException e) {
|
||||
LOGGER.warn("Unable to get park area for " + element.source().id() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VectorTileEncoder.Feature> postProcess(int zoom, List<VectorTileEncoder.Feature> items) {
|
||||
LongIntMap counts = new LongIntHashMap();
|
||||
for (int i = items.size() - 1; i >= 0; i--) {
|
||||
var feature = items.get(i);
|
||||
if (feature.geometry().geomType() == GeometryType.POINT && feature.hasGroup()) {
|
||||
int count = counts.getOrDefault(feature.group(), 0) + 1;
|
||||
feature.attrs().put("rank", count);
|
||||
counts.put(feature.group(), count);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public abstract class AbstractLayerTest {
|
|||
|
||||
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");
|
||||
assertEquals(expected.size(), actualList.size(), () -> "size: " + actualList);
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
assertSubmap(expected.get(i), TestUtils.toMap(actualList.get(i), zoom));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
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 org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LandcoverTest extends AbstractLayerTest {
|
||||
|
||||
@Test
|
||||
public void testNaturalEarthGlaciers() {
|
||||
var glacier1 = process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_110m_glaciated_areas",
|
||||
0
|
||||
));
|
||||
var glacier2 = process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_glaciated_areas",
|
||||
0
|
||||
));
|
||||
var glacier3 = process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_glaciated_areas",
|
||||
0
|
||||
));
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "glacier",
|
||||
"class", "ice",
|
||||
"_buffer", 4d
|
||||
)), glacier1);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "glacier",
|
||||
"class", "ice",
|
||||
"_buffer", 4d
|
||||
)), glacier2);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "glacier",
|
||||
"class", "ice",
|
||||
"_buffer", 4d
|
||||
)), glacier3);
|
||||
assertCoversZoomRange(0, 6, "landcover",
|
||||
glacier1,
|
||||
glacier2,
|
||||
glacier3
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNaturalEarthAntarcticIceShelves() {
|
||||
var ice1 = process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_antarctic_ice_shelves_polys",
|
||||
0
|
||||
));
|
||||
var ice2 = process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of(),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_10m_antarctic_ice_shelves_polys",
|
||||
0
|
||||
));
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "ice_shelf",
|
||||
"class", "ice",
|
||||
"_buffer", 4d
|
||||
)), ice1);
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "ice_shelf",
|
||||
"class", "ice",
|
||||
"_buffer", 4d
|
||||
)), ice2);
|
||||
assertCoversZoomRange(2, 6, "landcover",
|
||||
ice1,
|
||||
ice2
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmLandcover() {
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "wood",
|
||||
"class", "wood",
|
||||
"_minpixelsize", 8d,
|
||||
"_numpointsattr", "_numpoints",
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"natural", "wood"
|
||||
))));
|
||||
assertFeatures(12, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "forest",
|
||||
"class", "wood",
|
||||
"_minpixelsize", 8d,
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"landuse", "forest"
|
||||
))));
|
||||
assertFeatures(10, List.of(Map.of(
|
||||
"_layer", "landcover",
|
||||
"subclass", "dune",
|
||||
"class", "sand",
|
||||
"_minpixelsize", 4d,
|
||||
"_minzoom", 7,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"natural", "dune"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeForestsBuNumPointsZ9to13() throws GeometryException {
|
||||
Map<String, Object> map = Map.of("subclass", "wood");
|
||||
|
||||
assertMerges(List.of(map, map, map, map, map, map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")),
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood"))
|
||||
), 14);
|
||||
assertMerges(List.of(map, map, map, map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")),
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood"))
|
||||
), 13);
|
||||
assertMerges(List.of(map, map, map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")),
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood"))
|
||||
), 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeNonForestsBelowZ9() throws GeometryException {
|
||||
Map<String, Object> map = Map.of("subclass", "dune");
|
||||
|
||||
assertMerges(List.of(map, map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune"))
|
||||
), 9);
|
||||
assertMerges(List.of(map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune"))
|
||||
), 8);
|
||||
assertMerges(List.of(map, map), List.of(
|
||||
feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")),
|
||||
feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune"))
|
||||
), 6);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private VectorTileEncoder.Feature feature(org.locationtech.jts.geom.Polygon geom, Map<String, Object> m) {
|
||||
return new VectorTileEncoder.Feature(
|
||||
"landcover",
|
||||
1,
|
||||
VectorTileEncoder.encodeGeometry(geom),
|
||||
new HashMap<>(m),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private void assertMerges(List<Map<String, Object>> expected, List<VectorTileEncoder.Feature> in, int zoom)
|
||||
throws GeometryException {
|
||||
assertEquals(expected,
|
||||
profile.postProcessLayerFeatures("landcover", zoom, in).stream().map(
|
||||
VectorTileEncoder.Feature::attrs)
|
||||
.toList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.rectangle;
|
||||
import static com.onthegomap.flatmap.openmaptiles.OpenMapTilesProfile.NATURAL_EARTH_SOURCE;
|
||||
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LanduseTest extends AbstractLayerTest {
|
||||
|
||||
@Test
|
||||
public void testNaturalEarthUrbanAreas() {
|
||||
assertFeatures(0, List.of(Map.of(
|
||||
"_layer", "landuse",
|
||||
"class", "residential",
|
||||
"_buffer", 4d
|
||||
)), process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of("scalerank", 1.9),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_urban_areas",
|
||||
0
|
||||
)));
|
||||
assertFeatures(0, List.of(), process(new ReaderFeature(
|
||||
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
|
||||
Map.of("scalerank", 2.1),
|
||||
NATURAL_EARTH_SOURCE,
|
||||
"ne_50m_urban_areas",
|
||||
0
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmLanduse() {
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "landuse",
|
||||
"class", "railway",
|
||||
"_minpixelsize", 4d,
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"landuse", "railway",
|
||||
"amenity", "school"
|
||||
))));
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "landuse",
|
||||
"class", "school",
|
||||
"_minpixelsize", 4d,
|
||||
"_minzoom", 9,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"amenity", "school"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmLanduseLowerZoom() {
|
||||
assertFeatures(6, List.of(Map.of(
|
||||
"_layer", "landuse",
|
||||
"class", "suburb",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", 1d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"place", "suburb"
|
||||
))));
|
||||
assertFeatures(7, List.of(Map.of(
|
||||
"_layer", "landuse",
|
||||
"class", "residential",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14,
|
||||
"_minpixelsize", 2d
|
||||
)), process(polygonFeature(Map.of(
|
||||
"landuse", "residential"
|
||||
))));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package com.onthegomap.flatmap.openmaptiles.layers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ParkTest extends AbstractLayerTest {
|
||||
|
||||
@Test
|
||||
public void testNationalPark() {
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "polygon",
|
||||
"class", "national_park",
|
||||
"name", "<null>",
|
||||
"_minpixelsize", 2d,
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
), Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "point",
|
||||
"class", "national_park",
|
||||
"name", "Grand Canyon National Park",
|
||||
"name_int", "Grand Canyon National Park",
|
||||
"name:latin", "Grand Canyon National Park",
|
||||
"name:es", "es name",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeature(Map.of(
|
||||
"boundary", "national_park",
|
||||
"name", "Grand Canyon National Park",
|
||||
"name:es", "es name",
|
||||
"protection_title", "National Park",
|
||||
"wikipedia", "en:Grand Canyon National Park"
|
||||
))));
|
||||
|
||||
// needs a name
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "polygon"
|
||||
)), process(polygonFeature(Map.of(
|
||||
"boundary", "national_park",
|
||||
"protection_title", "National Park"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallerPark() {
|
||||
double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11);
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "polygon",
|
||||
"class", "protected_area",
|
||||
"name", "<null>",
|
||||
"_minpixelsize", 2d,
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
), Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "point",
|
||||
"class", "protected_area",
|
||||
"name", "Small park",
|
||||
"name_int", "Small park",
|
||||
"_minzoom", 11,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(z11area, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "Small park",
|
||||
"wikipedia", "en:Small park"
|
||||
))));
|
||||
assertFeatures(13, List.of(Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "polygon"
|
||||
), Map.of(
|
||||
"_layer", "park",
|
||||
"_type", "point",
|
||||
"_minzoom", 6,
|
||||
"_maxzoom", 14
|
||||
)), process(polygonFeatureWithArea(1, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "Small park",
|
||||
"wikidata", "Q123"
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZorders() {
|
||||
assertDescending(
|
||||
getLabelZorder(1, Map.of(
|
||||
"boundary", "national_park",
|
||||
"name", "a",
|
||||
"wikipedia", "en:park"
|
||||
)),
|
||||
getLabelZorder(1e-10, Map.of(
|
||||
"boundary", "national_park",
|
||||
"name", "a",
|
||||
"wikipedia", "en:Park"
|
||||
)),
|
||||
getLabelZorder(1, Map.of(
|
||||
"boundary", "national_park",
|
||||
"name", "a"
|
||||
)),
|
||||
getLabelZorder(1e-10, Map.of(
|
||||
"boundary", "national_park",
|
||||
"name", "a"
|
||||
)),
|
||||
|
||||
getLabelZorder(1, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "a",
|
||||
"wikipedia", "en:park"
|
||||
)),
|
||||
getLabelZorder(1e-10, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "a",
|
||||
"wikipedia", "en:Park"
|
||||
)),
|
||||
getLabelZorder(1, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "a"
|
||||
)),
|
||||
getLabelZorder(1e-10, Map.of(
|
||||
"boundary", "protected_area",
|
||||
"name", "a"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
private void assertDescending(int... vals) {
|
||||
for (int i = 1; i < vals.length; i++) {
|
||||
if (vals[i - 1] < vals[i]) {
|
||||
fail("element at " + (i - 1) + " is less than element at " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getLabelZorder(double area, Map<String, Object> tags) {
|
||||
var iter = process(polygonFeatureWithArea(area, tags)).iterator();
|
||||
iter.next();
|
||||
return iter.next().getZorder();
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue