pull/1/head
Mike Barry 2021-05-16 06:42:57 -04:00
rodzic 57dba3bb14
commit 1e36e9e940
10 zmienionych plików z 496 dodań i 80 usunięć

Wyświetl plik

@ -5,14 +5,12 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Lineal;
import org.locationtech.jts.geom.Puntal;
public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
private static final AtomicLong idGen = new AtomicLong(0);
private final SourceFeature source;
private final List<Feature<?>> output = new ArrayList<>();
private final CommonParams config;
@ -36,7 +34,7 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
}
public PointFeature point(String layer) {
var feature = new PointFeature(layer, source.centroid());
var feature = new PointFeature(layer, source.isPoint() ? source.worldGeometry() : source.centroid());
output.add(feature);
return feature;
}
@ -97,6 +95,10 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
protected PointFeature self() {
return this;
}
public boolean hasLabelGrid() {
return labelGridPixelSize != null || labelGridLimit != null;
}
}
public class LineFeature extends Feature<LineFeature> {
@ -162,18 +164,16 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
private final String layer;
private final Geometry geom;
private final long id;
private int zOrder;
private int minzoom = config.minzoom();
private int maxzoom = config.maxzoom();
private double defaultBuffer;
private ZoomFunction<Number> bufferOverrides;
private double defaultBufferPixels = 4;
private ZoomFunction<Number> bufferPixelOverrides;
private final Map<String, Object> attrs = new TreeMap<>();
private Feature(String layer, Geometry geom) {
this.layer = layer;
this.geom = geom;
this.id = idGen.incrementAndGet();
this.zOrder = 0;
}
@ -210,10 +210,6 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
return maxzoom;
}
public long getId() {
return id;
}
public String getLayer() {
return layer;
}
@ -222,17 +218,17 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
return geom;
}
public double getBufferAtZoom(int zoom) {
return ZoomFunction.applyAsDoubleOrElse(bufferOverrides, zoom, defaultBuffer);
public double getBufferPixelsAtZoom(int zoom) {
return ZoomFunction.applyAsDoubleOrElse(bufferPixelOverrides, zoom, defaultBufferPixels);
}
public T setBuffer(double buffer) {
defaultBuffer = buffer;
public T setBufferPixels(double buffer) {
defaultBufferPixels = buffer;
return self();
}
public T setBufferOverrides(ZoomFunction<Number> buffer) {
bufferOverrides = buffer;
public T setBufferPixelOverrides(ZoomFunction<Number> buffer) {
bufferPixelOverrides = buffer;
return self();
}
@ -264,5 +260,13 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature<?>> {
return self();
}
@Override
public String toString() {
return "Feature{" +
"layer='" + layer + '\'' +
", geom=" + geom.getGeometryType() +
", attrs=" + attrs +
'}';
}
}
}

Wyświetl plik

@ -3,10 +3,15 @@ package com.onthegomap.flatmap;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.TileCoord;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
@ -20,6 +25,8 @@ import org.slf4j.LoggerFactory;
public class FeatureRenderer {
private static final AtomicLong idGen = new AtomicLong(0);
private static final Logger LOGGER = LoggerFactory.getLogger(FeatureRenderer.class);
private final CommonParams config;
private final Consumer<RenderedFeature> consumer;
@ -33,12 +40,14 @@ public class FeatureRenderer {
renderGeometry(feature.getGeometry(), feature);
}
public void renderGeometry(Geometry geom, FeatureCollector.Feature<?> feature) {
private void renderGeometry(Geometry geom, FeatureCollector.Feature<?> feature) {
// TODO what about converting between area and line?
// TODO generate ID in here?
if (feature instanceof FeatureCollector.PointFeature pointFeature) {
if (geom.isEmpty()) {
LOGGER.warn("Empty geometry " + feature);
} else if (feature instanceof FeatureCollector.PointFeature pointFeature) {
if (geom instanceof Point point) {
addPointFeature(pointFeature, point);
addPointFeature(pointFeature, point.getCoordinates());
} else if (geom instanceof MultiPoint points) {
addPointFeature(pointFeature, points);
} else {
@ -82,7 +91,7 @@ public class FeatureRenderer {
// }
}
private static double clip(double value, double max) {
private static int wrapInt(int value, int max) {
value %= max;
if (value < 0) {
value += max;
@ -90,55 +99,80 @@ public class FeatureRenderer {
return value;
}
private void addPointFeature(FeatureCollector.PointFeature feature, Point point) {
private static double wrapDouble(double value, double max) {
value %= max;
if (value < 0) {
value += max;
}
return value;
}
private void slicePoint(Map<TileCoord, Set<Coordinate>> output, int zoom, double buffer, Coordinate coord) {
int tilesAtZoom = 1 << zoom;
double worldX = coord.getX() * tilesAtZoom;
double worldY = coord.getY() * tilesAtZoom;
int minX = (int) Math.floor(worldX - buffer);
int maxX = (int) Math.floor(worldX + buffer);
int minY = Math.max(0, (int) Math.floor(worldY - buffer));
int maxY = Math.min(tilesAtZoom - 1, (int) Math.floor(worldY + buffer));
for (int x = minX; x <= maxX; x++) {
double tileX = worldX - x;
for (int y = minY; y <= maxY; y++) {
TileCoord tile = TileCoord.ofXYZ(wrapInt(x, tilesAtZoom), y, zoom);
double tileY = worldY - y;
Coordinate outCoordinate = new CoordinateXY(tileX * 256, tileY * 256);
output.computeIfAbsent(tile, t -> new HashSet<>()).add(outCoordinate);
}
}
}
private void addPointFeature(FeatureCollector.PointFeature feature, Coordinate... coords) {
long id = idGen.incrementAndGet();
for (int zoom = feature.getMaxZoom(); zoom >= feature.getMinZoom(); zoom--) {
Map<TileCoord, Set<Coordinate>> sliced = new HashMap<>();
Map<String, Object> attrs = feature.getAttrsAtZoom(zoom);
double buffer = feature.getBufferAtZoom(zoom);
double tilesAtZoom = 1 << zoom;
double worldX = point.getX() * tilesAtZoom;
double worldY = point.getY() * tilesAtZoom;
double buffer = feature.getBufferPixelsAtZoom(zoom) / 256;
int tilesAtZoom = 1 << zoom;
for (Coordinate coord : coords) {
slicePoint(sliced, zoom, buffer, coord);
}
double labelGridTileSize = feature.getLabelGridPixelSizeAtZoom(zoom) / 256d;
Optional<RenderedFeature.Group> groupInfo = labelGridTileSize >= 1d / 4096d ?
Optional.of(new RenderedFeature.Group(GeoUtils.longPair(
(int) Math.floor(clip(worldX, tilesAtZoom) / labelGridTileSize),
(int) Math.floor(worldY / labelGridTileSize)
), feature.getLabelGridLimitAtZoom(zoom))) : Optional.empty();
Optional<RenderedFeature.Group> groupInfo = Optional.empty();
if (feature.hasLabelGrid() && coords.length == 1) {
double labelGridTileSize = feature.getLabelGridPixelSizeAtZoom(zoom) / 256d;
groupInfo = labelGridTileSize >= 1d / 4096d ?
Optional.of(new RenderedFeature.Group(GeoUtils.longPair(
(int) Math.floor(wrapDouble(coords[0].getX() * tilesAtZoom, tilesAtZoom) / labelGridTileSize),
(int) Math.floor((coords[0].getY() * tilesAtZoom) / labelGridTileSize)
), feature.getLabelGridLimitAtZoom(zoom))) : Optional.empty();
}
int minX = (int) (worldX - buffer);
int maxX = (int) (worldX + buffer);
int minY = (int) (worldY - buffer);
int maxY = (int) (worldY + buffer);
// TODO test (real, feature, unit)
// TODO wrap x at z0
// TODO multipoints?
// TODO factor-out rendered feature creation
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
TileCoord tile = TileCoord.ofXYZ(x, y, zoom);
double tileX = worldX - x;
double tileY = worldY - y;
consumer.accept(new RenderedFeature(
tile,
new VectorTileEncoder.Feature(
feature.getLayer(),
feature.getId(),
VectorTileEncoder.encodeGeometry(GeoUtils.point(tileX * 256, tileY * 256)),
attrs
),
feature.getZorder(),
groupInfo
));
}
for (var entry : sliced.entrySet()) {
Set<Coordinate> value = entry.getValue();
Geometry geom = value.size() == 1 ? GeoUtils.point(value.iterator().next()) : GeoUtils.multiPoint(value);
consumer.accept(new RenderedFeature(
entry.getKey(),
new VectorTileEncoder.Feature(
feature.getLayer(),
id,
VectorTileEncoder.encodeGeometry(geom),
attrs
),
feature.getZorder(),
groupInfo
));
}
}
}
private void addPointFeature(FeatureCollector.PointFeature feature, MultiPoint points) {
Map<TileCoord, List<Point>> tilePoints = new HashMap<>();
// TODO render features into tile
if (feature.hasLabelGrid()) {
for (Coordinate coord : points.getCoordinates()) {
addPointFeature(feature, coord);
}
} else {
addPointFeature(feature, points.getCoordinates());
}
}
private void addLinearFeature(FeatureCollector.Feature<?> feature, Geometry geom) {

Wyświetl plik

@ -1,10 +1,14 @@
package com.onthegomap.flatmap.geo;
import java.util.Collection;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.locationtech.jts.geom.util.GeometryTransformer;
import org.locationtech.jts.io.WKBReader;
@ -169,7 +173,20 @@ public class GeoUtils {
return (pair << 16) >> 16;
}
public static Geometry point(double x, double y) {
public static Point point(double x, double y) {
return gf.createPoint(new CoordinateXY(x, y));
}
public static Point point(Coordinate coord) {
return gf.createPoint(coord);
}
public static MultiPoint multiPoint(Collection<Coordinate> coords) {
return gf.createMultiPointFromCoords(coords.toArray(new Coordinate[0]));
}
public static Geometry multiPoint(double... coords) {
assert coords.length % 2 == 0;
return gf.createMultiPoint(new PackedCoordinateSequence.Double(coords, 2, 0));
}
}

Wyświetl plik

@ -67,10 +67,7 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
@Override
public String toString() {
return "[" +
z + "/" + x + "/" + y +
" (" + encoded +
")]";
return "{x=" + x + " y=" + y + " z=" + z + '}';
}
@Override

Wyświetl plik

@ -30,7 +30,6 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
@ -245,10 +244,10 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
@Override
protected Geometry computeWorldGeometry() {
return GeoUtils.gf.createPoint(new CoordinateXY(
return GeoUtils.point(
GeoUtils.getWorldX(lon),
GeoUtils.getWorldY(lat)
));
);
}
@Override

Wyświetl plik

@ -42,8 +42,8 @@ public class FeatureCollectorTest {
.setZoomRange(12, 14)
.setZorder(3)
.setAttr("attr1", 2)
.setBuffer(10d)
.setBufferOverrides(ZoomFunction.maxZoom(12, 11d))
.setBufferPixels(10d)
.setBufferPixelOverrides(ZoomFunction.maxZoom(12, 11d))
.setLabelGridSizeAndLimit(12, 100, 10);
assertFeatures(14, List.of(
Map.of(

Wyświetl plik

@ -0,0 +1,239 @@
package com.onthegomap.flatmap;
import static com.onthegomap.flatmap.TestUtils.assertSameNormalizedFeatures;
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
import static com.onthegomap.flatmap.TestUtils.newPoint;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.TileCoord;
import com.onthegomap.flatmap.read.ReaderFeature;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.locationtech.jts.geom.Geometry;
public class FeatureRendererTest {
private final CommonParams config = CommonParams.defaults();
private FeatureCollector collector(Geometry worldGeom) {
var latLonGeom = GeoUtils.worldToLatLonCoords(worldGeom);
return new FeatureCollector.Factory(config).get(new ReaderFeature(latLonGeom, 0));
}
private Map<TileCoord, Collection<Geometry>> renderGeometry(FeatureCollector.Feature<?> feature) {
Map<TileCoord, Collection<Geometry>> result = new TreeMap<>();
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
.add(rendered.vectorTileFeature().geometry().decode())).renderFeature(feature);
return result;
}
private Map<TileCoord, Collection<RenderedFeature>> renderFeatures(FeatureCollector.Feature<?> feature) {
Map<TileCoord, Collection<RenderedFeature>> result = new TreeMap<>();
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
.add(rendered)).renderFeature(feature);
return result;
}
private static final int Z14_TILES = 1 << 14;
private static final double Z14_WIDTH = 1d / Z14_TILES;
@Test
public void testEmptyGeometry() {
var feature = collector(emptyGeometry()).point("layer");
assertSameNormalizedFeatures(Map.of(), renderGeometry(feature));
}
@Test
public void testSinglePoint() {
var feature = pointFeature(newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2))
.setZoomRange(14, 14);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newPoint(128, 128)
)
), renderGeometry(feature));
}
@Test
public void testRepeatSinglePointNeighboringTiles() {
var feature = pointFeature(newPoint(0.5 + 1d / 512, 0.5 + 1d / 512))
.setZoomRange(0, 1)
.setBufferPixels(2);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(newPoint(128.5, 128.5)),
TileCoord.ofXYZ(0, 0, 1), List.of(newPoint(257, 257)),
TileCoord.ofXYZ(1, 0, 1), List.of(newPoint(1, 257)),
TileCoord.ofXYZ(0, 1, 1), List.of(newPoint(257, 1)),
TileCoord.ofXYZ(1, 1, 1), List.of(newPoint(1, 1))
), renderGeometry(feature));
}
@TestFactory
public List<DynamicTest> testProcessPointsNearInternationalDateLineAndPoles() {
double d = 1d / 512;
record X(double x, double wrapped, double z1x0, double z1x1) {
}
record Y(double y, int z1ty, double tyoff) {
}
var xs = List.of(
new X(-d, 1 - d, -1, 255),
new X(d, 1 + d, 1, 257),
new X(1 - d, -d, -1, 255),
new X(1 + d, d, 1, 257)
);
var ys = List.of(
new Y(0.25, 0, 128),
new Y(-d, 0, -1),
new Y(d, 0, 1),
new Y(1 - d, 1, 255),
new Y(1 + d, 1, 257)
);
List<DynamicTest> tests = new ArrayList<>();
for (X x : xs) {
for (Y y : ys) {
tests.add(dynamicTest((x.x * 256) + ", " + (y.y * 256), () -> {
var feature = pointFeature(newPoint(x.x, y.y))
.setZoomRange(0, 1)
.setBufferPixels(2);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(newMultiPoint(
newPoint(x.x * 256, y.y * 256),
newPoint(x.wrapped * 256, y.y * 256)
)),
TileCoord.ofXYZ(0, y.z1ty, 1), List.of(newPoint(x.z1x0, y.tyoff)),
TileCoord.ofXYZ(1, y.z1ty, 1), List.of(newPoint(x.z1x1, y.tyoff))
), renderGeometry(feature));
}));
}
}
return tests;
}
@Test
public void testZ0FullTileBuffer() {
var feature = pointFeature(newPoint(0.25, 0.25))
.setZoomRange(0, 1)
.setBufferPixels(256);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(newMultiPoint(
newPoint(-192, 64),
newPoint(64, 64),
newPoint(320, 64)
)),
TileCoord.ofXYZ(0, 0, 1), List.of(newPoint(128, 128)),
TileCoord.ofXYZ(1, 0, 1), List.of(newMultiPoint(
newPoint(-128, 128),
newPoint(256 + 128, 128)
)),
TileCoord.ofXYZ(0, 1, 1), List.of(newPoint(128, -128)),
TileCoord.ofXYZ(1, 1, 1), List.of(newMultiPoint(
newPoint(-128, -128),
newPoint(256 + 128, -128)
))
), renderGeometry(feature));
}
@Test
public void testMultipointNoLabelGrid() {
var feature = pointFeature(newMultiPoint(
newPoint(0.25, 0.25),
newPoint(0.25 + 1d / 256, 0.25 + 1d / 256)
))
.setZoomRange(0, 1)
.setBufferPixels(4);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(newMultiPoint(
newPoint(64, 64),
newPoint(65, 65)
)),
TileCoord.ofXYZ(0, 0, 1), List.of(newMultiPoint(
newPoint(128, 128),
newPoint(130, 130)
))
), renderGeometry(feature));
}
@Test
public void testMultipointWithLabelGridSplits() {
var feature = pointFeature(newMultiPoint(
newPoint(0.25, 0.25),
newPoint(0.25 + 1d / 256, 0.25 + 1d / 256)
))
.setLabelGridPixelSize(10, 256)
.setZoomRange(0, 1)
.setBufferPixels(4);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(
newPoint(64, 64),
newPoint(65, 65)
),
TileCoord.ofXYZ(0, 0, 1), List.of(
newPoint(128, 128),
newPoint(130, 130)
)
), renderGeometry(feature));
}
@Test
public void testLabelGrid() {
var feature = pointFeature(newPoint(0.75, 0.75))
.setLabelGridSizeAndLimit(10, 256, 2)
.setZoomRange(0, 1)
.setBufferPixels(4);
var rendered = renderFeatures(feature);
var z0Feature = rendered.get(TileCoord.ofXYZ(0, 0, 0)).iterator().next();
var z1Feature = rendered.get(TileCoord.ofXYZ(1, 1, 1)).iterator().next();
assertEquals(Optional.of(new RenderedFeature.Group(0, 2)), z0Feature.group());
assertEquals(Optional.of(new RenderedFeature.Group((1L << 32) + 1, 2)), z1Feature.group());
}
@Test
public void testWrapLabelGrid() {
var feature = pointFeature(newPoint(1.1, -0.1))
.setLabelGridSizeAndLimit(10, 256, 2)
.setZoomRange(0, 1)
.setBufferPixels(64);
var rendered = renderFeatures(feature);
System.err.println(rendered);
var z0Feature = rendered.get(TileCoord.ofXYZ(0, 0, 0)).iterator().next();
var z1Feature = rendered.get(TileCoord.ofXYZ(0, 0, 1)).iterator().next();
assertEquals(Optional.of(new RenderedFeature.Group((1L << 32) - 1, 2)), z0Feature.group());
assertEquals(Optional.of(new RenderedFeature.Group((1L << 32) - 1, 2)), z1Feature.group());
}
private FeatureCollector.PointFeature pointFeature(Geometry geom) {
return collector(geom).point("layer");
}
// happy tests:
// TODO: multipoint together?
// TODO: multipoint separate (if has group)? separate IDs?
// TODO: world wrap
// TODO: line
// TODO: multilinestring
// TODO: poly
// TODO: multipolygon
// TODO: geometry collection
// sad tests:
// TODO: invalid line
// TODO: invalid poly
// TODO: coerce poly -> line
// TODO: coerce line -> poly
// TODO: wrong types: point/line/poly -> point/line/poly
}

Wyświetl plik

@ -28,7 +28,10 @@ import java.util.function.BiConsumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
public class FeatureTest {
/**
* In-memory tests with fake data and profiles to ensure all features work end-to-end.
*/
public class FlatMapTest {
private static final String TEST_PROFILE_NAME = "test name";
private static final String TEST_PROFILE_DESCRIPTION = "test description";
@ -162,6 +165,46 @@ public class FeatureTest {
);
}
@Test
public void testLabelGridLimit() throws IOException, SQLException {
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);
double lng3 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 20d / 256);
var results = runWithReaderFeatures(
Map.of("threads", "1"),
List.of(
new ReaderFeature(newPoint(lng1, lat), Map.of("rank", "1")),
new ReaderFeature(newPoint(lng2, lat), Map.of("rank", "2")),
new ReaderFeature(newPoint(lng3, lat), Map.of("rank", "3"))
),
(in, features) -> {
features.point("layer")
.setZoomRange(13, 14)
.inheritFromSource("rank")
.setZorder(Integer.parseInt(in.getTag("rank").toString()))
.setLabelGridSizeAndLimit(13, 128, 2);
}
);
assertSubmap(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
feature(newPoint(64, 128), Map.of("rank", "1")),
feature(newPoint(74, 128), Map.of("rank", "2")),
feature(newPoint(84, 128), Map.of("rank", "3"))
),
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
// omit rank=1 due to label grid size
feature(newPoint(37, 64), Map.of("rank", "2")),
feature(newPoint(42, 64), Map.of("rank", "3"))
)
), results.tiles);
}
private interface LayerPostprocessFunction {
List<VectorTileEncoder.Feature> process(String layer, int zoom, List<VectorTileEncoder.Feature> items);
@ -218,6 +261,4 @@ public class FeatureTest {
return postprocessLayerFeatures.process(layer, zoom, items);
}
}
// TODO: refactor into parameterized test?
}

Wyświetl plik

@ -86,7 +86,6 @@ public class LayerStatsTest {
), layerStats.getTileStats());
}
@Test
public void testMergeFromMultipleThreads() throws InterruptedException {
Thread t1 = new Thread(() -> {
@ -103,7 +102,6 @@ public class LayerStatsTest {
));
});
t1.start();
t1.join();
Thread t2 = new Thread(() -> {
layerStats.accept(new RenderedFeature(
TileCoord.ofXYZ(1, 2, 4),
@ -118,6 +116,7 @@ public class LayerStatsTest {
));
});
t2.start();
t1.join();
t2.join();
assertEquals(new Mbtiles.MetadataJson(
new Mbtiles.MetadataJson.VectorLayer("layer1", Map.of(

Wyświetl plik

@ -13,11 +13,15 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
@ -148,25 +152,67 @@ public class TestUtils {
assertEquals(new TreeMap<>(expectedSubmap), actualFiltered, message + " others: " + others);
}
public static Geometry emptyGeometry() {
return GeoUtils.gf.createGeometryCollection();
}
public interface GeometryComparision {
Geometry geom();
}
public static record ExactGeometry(Geometry geom) implements GeometryComparision {
private static record NormGeometry(Geometry geom) implements GeometryComparision {
@Override
public boolean equals(Object o) {
return o instanceof GeometryComparision that && geom.equalsNorm(that.geom());
}
@Override
public String toString() {
return "Norm{" + geom + '}';
}
@Override
public int hashCode() {
return 0;
}
}
private static record ExactGeometry(Geometry geom) implements GeometryComparision {
@Override
public boolean equals(Object o) {
return o instanceof GeometryComparision that && geom.equalsExact(that.geom());
}
@Override
public String toString() {
return "Exact{" + geom + '}';
}
@Override
public int hashCode() {
return 0;
}
}
public static record TopoGeometry(Geometry geom) implements GeometryComparision {
private static record TopoGeometry(Geometry geom) implements GeometryComparision {
@Override
public boolean equals(Object o) {
return o instanceof GeometryComparision that && geom.equalsTopo(that.geom());
}
@Override
public String toString() {
return "Topo{" + geom + '}';
}
@Override
public int hashCode() {
return 0;
}
}
public static record ComparableFeature(
@ -184,8 +230,7 @@ public class TestUtils {
TreeMap<String, Object> result = new TreeMap<>(feature.getAttrsAtZoom(zoom));
result.put("_minzoom", feature.getMinZoom());
result.put("_maxzoom", feature.getMaxZoom());
result.put("_buffer", feature.getBufferAtZoom(zoom));
result.put("_id", feature.getId());
result.put("_buffer", feature.getBufferPixelsAtZoom(zoom));
result.put("_layer", feature.getLayer());
result.put("_zorder", feature.getZorder());
result.put("_geom", new TopoGeometry(feature.getGeometry()));
@ -211,4 +256,45 @@ public class TestUtils {
objectMapper.readTree(actual)
);
}
public static <T> Map<TileCoord, Collection<T>> mapTileFeatures(Map<TileCoord, Collection<Geometry>> in,
Function<Geometry, T> fn) {
TreeMap<TileCoord, Collection<T>> out = new TreeMap<>();
for (var entry : in.entrySet()) {
out.put(entry.getKey(), entry.getValue().stream().map(fn)
.sorted(Comparator.comparing(Object::toString))
.collect(Collectors.toList()));
}
return out;
}
public static void assertExactSameFeatures(
Map<TileCoord, Collection<Geometry>> expected,
Map<TileCoord, Collection<Geometry>> actual
) {
assertEquals(
mapTileFeatures(expected, ExactGeometry::new),
mapTileFeatures(actual, ExactGeometry::new)
);
}
public static void assertTopologicallyEquivalentFeatures(
Map<TileCoord, Collection<Geometry>> expected,
Map<TileCoord, Collection<Geometry>> actual
) {
assertEquals(
mapTileFeatures(expected, TopoGeometry::new),
mapTileFeatures(actual, TopoGeometry::new)
);
}
public static void assertSameNormalizedFeatures(
Map<TileCoord, Collection<Geometry>> expected,
Map<TileCoord, Collection<Geometry>> actual
) {
assertEquals(
mapTileFeatures(expected, NormGeometry::new),
mapTileFeatures(actual, NormGeometry::new)
);
}
}