landcover, landuse, park

pull/1/head
Mike Barry 2021-06-25 07:06:55 -04:00
rodzic 753a4aee03
commit 7e9e342867
18 zmienionych plików z 797 dodań i 25 usunięć

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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);
}
}
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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();

Wyświetl plik

@ -621,6 +621,7 @@ public class FeatureMergeTest {
feature(1, rectangle(10, 20, 22, 22), Map.of("a", 1))
),
1,
1,
0,
0
)

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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 -> {

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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);
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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));
}

Wyświetl plik

@ -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());
}
}

Wyświetl plik

@ -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"
))));
}
}

Wyświetl plik

@ -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();
}
}