pull/1/head
Mike Barry 2021-05-22 07:07:34 -04:00
rodzic fcaeb88c40
commit c219c93f93
11 zmienionych plików z 784 dodań i 103 usunięć

Wyświetl plik

@ -54,11 +54,11 @@ public class TileExtents implements Predicate<TileCoord> {
}
public boolean testX(int x) {
return x >= minX && x <= maxX;
return x >= minX && x < maxX;
}
public boolean testY(int y) {
return y >= minY && y <= maxY;
return y >= minY && y < maxY;
}
}
}

Wyświetl plik

@ -196,6 +196,7 @@ public class VectorTileEncoder {
if (first) {
first = false;
outerCCW = ccw;
assert outerCCW;
}
if (ccw == outerCCW) {
ringsForCurrentPolygon = new ArrayList<>();

Wyświetl plik

@ -51,7 +51,10 @@ public class MutableCoordinateSequence extends PackedCoordinateSequence {
@Override
public Envelope expandEnvelope(Envelope env) {
return null;
for (int i = 0; i < points.size(); i += dimension) {
env.expandToInclude(points.get(i), points.get(i + 1));
}
return env;
}
public void addPoint(double x, double y) {

Wyświetl plik

@ -23,6 +23,7 @@ public class GeoUtils {
private static final LineString[] EMPTY_LINE_STRING_ARRAY = new LineString[0];
private static final Coordinate[] EMPTY_COORD_ARRAY = new Coordinate[0];
private static final Point[] EMPTY_POINT_ARRAY = new Point[0];
private static final double WORLD_RADIUS_METERS = 6_378_137;
private static final double WORLD_CIRCUMFERENCE_METERS = Math.PI * 2 * WORLD_RADIUS_METERS;
@ -191,11 +192,6 @@ public class GeoUtils {
return JTS_FACTORY.createMultiPointFromCoords(coords.toArray(EMPTY_COORD_ARRAY));
}
public static Geometry multiPoint(double... coords) {
assert coords.length % 2 == 0;
return JTS_FACTORY.createMultiPoint(new PackedCoordinateSequence.Double(coords, 2, 0));
}
public static Geometry createMultiLineString(List<LineString> lineStrings) {
return JTS_FACTORY.createMultiLineString(lineStrings.toArray(EMPTY_LINE_STRING_ARRAY));
}
@ -230,4 +226,12 @@ public class GeoUtils {
(int) Math.floor((coord.getY() * tilesAtZoom) / labelGridTileSize)
);
}
public static CoordinateSequence coordinateSequence(double... coords) {
return new PackedCoordinateSequence.Double(coords, 2, 0);
}
public static Geometry createMultiPoint(List<Point> points) {
return JTS_FACTORY.createMultiPoint(points.toArray(EMPTY_POINT_ARRAY));
}
}

Wyświetl plik

@ -1,8 +1,6 @@
package com.onthegomap.flatmap.render;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.TileCoord;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@ -13,6 +11,7 @@ import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -21,39 +20,6 @@ class CoordinateSequenceExtractor {
private static final Logger LOGGER = LoggerFactory.getLogger(CoordinateSequenceExtractor.class);
static Geometry reassembleLineString(List<List<CoordinateSequence>> geoms) {
Geometry geom;
List<LineString> lineStrings = new ArrayList<>();
for (List<CoordinateSequence> inner : geoms) {
for (CoordinateSequence coordinateSequence : inner) {
lineStrings.add(GeoUtils.JTS_FACTORY.createLineString(coordinateSequence));
}
}
geom = GeoUtils.createMultiLineString(lineStrings);
return geom;
}
@NotNull
static Geometry reassemblePolygon(FeatureCollector.Feature feature, TileCoord tile,
List<List<CoordinateSequence>> geoms) {
Geometry geom;
int numGeoms = geoms.size();
Polygon[] polygons = new Polygon[numGeoms];
for (int i = 0; i < numGeoms; i++) {
List<CoordinateSequence> group = geoms.get(i);
LinearRing first = GeoUtils.JTS_FACTORY.createLinearRing(group.get(0));
LinearRing[] rest = new LinearRing[group.size() - 1];
for (int j = 1; j < group.size(); j++) {
CoordinateSequence seq = group.get(j);
CoordinateSequences.reverse(seq);
rest[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(seq);
}
polygons[i] = GeoUtils.JTS_FACTORY.createPolygon(first, rest);
}
geom = GeoUtils.JTS_FACTORY.createMultiPolygon(polygons);
return geom;
}
static List<List<CoordinateSequence>> extractGroups(Geometry geom, double minSize) {
List<List<CoordinateSequence>> result = new ArrayList<>();
extractGroups(geom, result, minSize);
@ -102,4 +68,51 @@ class CoordinateSequenceExtractor {
}
}
}
static Geometry reassembleLineStrings(List<List<CoordinateSequence>> geoms) {
List<LineString> lineStrings = new ArrayList<>();
for (List<CoordinateSequence> inner : geoms) {
for (CoordinateSequence coordinateSequence : inner) {
lineStrings.add(GeoUtils.JTS_FACTORY.createLineString(coordinateSequence));
}
}
return lineStrings.size() == 1 ? lineStrings.get(0) : GeoUtils.createMultiLineString(lineStrings);
}
@NotNull
static Geometry reassemblePolygons(List<List<CoordinateSequence>> groups) {
int numGeoms = groups.size();
if (numGeoms == 1) {
return reassemblePolygon(groups.get(0));
} else {
Polygon[] polygons = new Polygon[numGeoms];
for (int i = 0; i < numGeoms; i++) {
polygons[i] = reassemblePolygon(groups.get(i));
}
return GeoUtils.JTS_FACTORY.createMultiPolygon(polygons);
}
}
private static Polygon reassemblePolygon(List<CoordinateSequence> group) {
LinearRing first = GeoUtils.JTS_FACTORY.createLinearRing(group.get(0));
LinearRing[] rest = new LinearRing[group.size() - 1];
for (int j = 1; j < group.size(); j++) {
CoordinateSequence seq = group.get(j);
CoordinateSequences.reverse(seq);
rest[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(seq);
}
return GeoUtils.JTS_FACTORY.createPolygon(first, rest);
}
static Geometry reassemblePoints(List<List<CoordinateSequence>> result) {
List<Point> points = new ArrayList<>();
for (List<CoordinateSequence> inner : result) {
for (CoordinateSequence coordinateSequence : inner) {
if (coordinateSequence.size() == 1) {
points.add(GeoUtils.JTS_FACTORY.createPoint(coordinateSequence));
}
}
}
return points.size() == 1 ? points.get(0) : GeoUtils.createMultiPoint(points);
}
}

Wyświetl plik

@ -8,7 +8,6 @@ import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
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;
@ -17,7 +16,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
@ -57,14 +55,6 @@ public class FeatureRenderer {
this.consumer = consumer;
}
private static int wrapInt(int value, int max) {
value %= max;
if (value < 0) {
value += max;
}
return value;
}
public void renderFeature(FeatureCollector.Feature feature) {
renderGeometry(feature.getGeometry(), feature);
}
@ -90,27 +80,6 @@ public class FeatureRenderer {
}
}
private void slicePoint(Map<TileCoord, Set<Coordinate>> output, int zoom, double buffer, Coordinate coord) {
// TODO put this into TiledGeometry
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);
tilePrecision.makePrecise(outCoordinate);
output.computeIfAbsent(tile, t -> new HashSet<>()).add(outCoordinate);
}
}
}
private void addPointFeature(FeatureCollector.Feature feature, Coordinate... coords) {
long id = idGen.incrementAndGet();
boolean hasLabelGrid = feature.hasLabelGrid();
@ -119,10 +88,8 @@ public class FeatureRenderer {
Map<String, Object> attrs = feature.getAttrsAtZoom(zoom);
double buffer = feature.getBufferPixelsAtZoom(zoom) / 256;
int tilesAtZoom = 1 << zoom;
for (Coordinate coord : coords) {
// TODO TiledGeometry.sliceIntoTiles(...)
slicePoint(sliced, zoom, buffer, coord);
}
TileExtents.ForZoom extents = config.extents().getForZoom(zoom);
TiledGeometry tiled = TiledGeometry.slicePointsIntoTiles(extents, buffer, zoom, coords);
RenderedFeature.Group groupInfo = null;
if (hasLabelGrid && coords.length == 1) {
@ -133,10 +100,10 @@ public class FeatureRenderer {
);
}
for (var entry : sliced.entrySet()) {
for (var entry : tiled.getTileData()) {
TileCoord tile = entry.getKey();
Set<Coordinate> value = entry.getValue();
Geometry geom = value.size() == 1 ? GeoUtils.point(value.iterator().next()) : GeoUtils.multiPoint(value);
List<List<CoordinateSequence>> result = entry.getValue();
Geometry geom = CoordinateSequenceExtractor.reassemblePoints(result);
// TODO stats
// TODO writeTileFeatures
emitFeature(feature, id, attrs, tile, geom, groupInfo);
@ -197,7 +164,7 @@ public class FeatureRenderer {
geom = simplifier.getResultGeometry();
List<List<CoordinateSequence>> groups = CoordinateSequenceExtractor.extractGroups(geom, minSize);
double buffer = feature.getBufferPixelsAtZoom(z);
double buffer = feature.getBufferPixelsAtZoom(z) / 256;
TileExtents.ForZoom extents = config.extents().getForZoom(z);
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
writeTileFeatures(id, feature, sliced);
@ -213,10 +180,10 @@ public class FeatureRenderer {
Geometry geom;
if (feature.area()) {
geom = CoordinateSequenceExtractor.reassemblePolygon(feature, tile, geoms);
geom = CoordinateSequenceExtractor.reassemblePolygons(geoms);
geom = GeoUtils.fixPolygon(geom, 2);
} else {
geom = CoordinateSequenceExtractor.reassembleLineString(geoms);
geom = CoordinateSequenceExtractor.reassembleLineStrings(geoms);
}
try {

Wyświetl plik

@ -21,13 +21,16 @@ import com.graphhopper.coll.GHIntObjectHashMap;
import com.onthegomap.flatmap.TileExtents;
import com.onthegomap.flatmap.collections.IntRange;
import com.onthegomap.flatmap.collections.MutableCoordinateSequence;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.TileCoord;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.slf4j.Logger;
@ -40,11 +43,13 @@ import org.slf4j.LoggerFactory;
class TiledGeometry {
private static final Logger LOGGER = LoggerFactory.getLogger(TiledGeometry.class);
private static final double NEIGHBOR_BUFFER_EPS = 0.1d / 4096;
private final Map<TileCoord, List<List<CoordinateSequence>>> tileContents = new HashMap<>();
private final Map<Integer, IntRange> filledRanges = new HashMap<>();
private Map<Integer, IntRange> filledRanges = null;
private final TileExtents.ForZoom extents;
private final double buffer;
private final double neighborBuffer;
private final int z;
private final boolean area;
private final int max;
@ -52,11 +57,51 @@ class TiledGeometry {
private TiledGeometry(TileExtents.ForZoom extents, double buffer, int z, boolean area) {
this.extents = extents;
this.buffer = buffer;
// make sure we inspect neighboring tiles when a line runs along an edge
this.neighborBuffer = buffer + NEIGHBOR_BUFFER_EPS;
this.z = z;
this.area = area;
this.max = 1 << z;
}
public static TiledGeometry slicePointsIntoTiles(TileExtents.ForZoom extents, double buffer, int z,
Coordinate[] coords) {
TiledGeometry result = new TiledGeometry(extents, buffer, z, false);
for (Coordinate coord : coords) {
result.slicePoint(coord);
}
return result;
}
private static int wrapInt(int value, int max) {
value %= max;
if (value < 0) {
value += max;
}
return value;
}
private void slicePoint(Coordinate coord) {
double worldX = coord.getX() * max;
double worldY = coord.getY() * max;
int minX = (int) Math.floor(worldX - neighborBuffer);
int maxX = (int) Math.floor(worldX + neighborBuffer);
int minY = Math.max(extents.minY(), (int) Math.floor(worldY - neighborBuffer));
int maxY = Math.min(extents.maxY() - 1, (int) Math.floor(worldY + neighborBuffer));
for (int x = minX; x <= maxX; x++) {
double tileX = worldX - x;
int wrappedX = wrapInt(x, max);
if (extents.testX(wrappedX)) {
for (int y = minY; y <= maxY; y++) {
TileCoord tile = TileCoord.ofXYZ(wrappedX, y, z);
double tileY = worldY - y;
List<CoordinateSequence> points = tileContents.computeIfAbsent(tile, t -> List.of(new ArrayList<>())).get(0);
points.add(GeoUtils.coordinateSequence(tileX * 256, tileY * 256));
}
}
}
}
public int zoomLevel() {
return z;
}
@ -76,7 +121,7 @@ class TiledGeometry {
}
public Iterable<TileCoord> getFilledTiles() {
return () -> filledRanges.entrySet().stream()
return filledRanges == null ? Collections.emptyList() : () -> filledRanges.entrySet().stream()
.<TileCoord>mapMulti((entry, next) -> {
int x = entry.getKey();
for (int y : entry.getValue()) {
@ -194,8 +239,8 @@ class TiledGeometry {
double minX = Math.min(_ax, _bx);
double maxX = Math.max(_ax, _bx);
int startX = (int) Math.floor(minX - buffer);
int endX = (int) Math.floor(maxX + buffer);
int startX = (int) Math.floor(minX - neighborBuffer);
int endX = (int) Math.floor(maxX + neighborBuffer);
for (int x = startX; x <= endX; x++) {
double ax = _ax - x;
@ -244,8 +289,8 @@ class TiledGeometry {
// add the last point
double _ax = segment.getX(segment.size() - 1);
double ay = segment.getY(segment.size() - 1);
int startX = (int) Math.floor(_ax - buffer);
int endX = (int) Math.floor(_ax + buffer);
int startX = (int) Math.floor(_ax - neighborBuffer);
int endX = (int) Math.floor(_ax + neighborBuffer);
for (int x = startX - 1; x <= endX + 1; x++) {
double ax = _ax - x;
@ -297,10 +342,10 @@ class TiledGeometry {
int extentMinY = extents.minY();
int extentMaxY = extents.maxY();
int startY = Math.max(extentMinY, (int) Math.floor(minY - buffer));
int endStartY = Math.max(extentMinY, (int) Math.floor(minY + buffer));
int startEndY = Math.min(extentMaxY - 1, (int) Math.floor(maxY - buffer));
int endY = Math.min(extentMaxY - 1, (int) Math.floor(maxY + buffer));
int startY = Math.max(extentMinY, (int) Math.floor(minY - neighborBuffer));
int endStartY = Math.max(extentMinY, (int) Math.floor(minY + neighborBuffer));
int startEndY = Math.min(extentMaxY - 1, (int) Math.floor(maxY - neighborBuffer));
int endY = Math.min(extentMaxY - 1, (int) Math.floor(maxY + neighborBuffer));
boolean onRightEdge = area && ax == bx && ax == rightEdge && by > ay;
boolean onLeftEdge = area && ax == bx && ax == leftEdge && by < ay;
@ -409,8 +454,8 @@ class TiledGeometry {
int last = stripeSegment.size() - 1;
double ax = stripeSegment.getX(last);
double ay = stripeSegment.getY(last);
int startY = (int) (ay - buffer);
int endY = (int) (ay + buffer);
int startY = (int) Math.floor(ay - neighborBuffer);
int endY = (int) Math.floor(ay + neighborBuffer);
for (int y = startY - 1; y <= endY + 1; y++) {
MutableCoordinateSequence slice = ySlices.get(y);
@ -433,6 +478,9 @@ class TiledGeometry {
if (yRange == null) {
return;
}
if (filledRanges == null) {
filledRanges = new HashMap<>();
}
IntRange existing = filledRanges.get(x);
if (existing == null) {
filledRanges.put(x, yRange);
@ -445,6 +493,9 @@ class TiledGeometry {
if (yRange == null) {
return;
}
if (filledRanges == null) {
filledRanges = new HashMap<>();
}
IntRange existing = filledRanges.get(x);
if (existing != null) {
existing.removeAll(yRange);

Wyświetl plik

@ -3,9 +3,23 @@ package com.onthegomap.flatmap;
import static com.onthegomap.flatmap.TestUtils.assertSameJson;
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
import static com.onthegomap.flatmap.TestUtils.feature;
import static com.onthegomap.flatmap.TestUtils.newCoordinateList;
import static com.onthegomap.flatmap.TestUtils.newLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
import static com.onthegomap.flatmap.TestUtils.newPoint;
import static com.onthegomap.flatmap.TestUtils.newPolygon;
import static com.onthegomap.flatmap.TestUtils.rectangle;
import static com.onthegomap.flatmap.TestUtils.rectangleCoordList;
import static com.onthegomap.flatmap.TestUtils.tileBottom;
import static com.onthegomap.flatmap.TestUtils.tileBottomLeft;
import static com.onthegomap.flatmap.TestUtils.tileBottomRight;
import static com.onthegomap.flatmap.TestUtils.tileFill;
import static com.onthegomap.flatmap.TestUtils.tileLeft;
import static com.onthegomap.flatmap.TestUtils.tileRight;
import static com.onthegomap.flatmap.TestUtils.tileTop;
import static com.onthegomap.flatmap.TestUtils.tileTopLeft;
import static com.onthegomap.flatmap.TestUtils.tileTopRight;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.graphhopper.reader.ReaderRelation;
@ -29,6 +43,7 @@ import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Coordinate;
/**
* In-memory tests with fake data and profiles to ensure all features work end-to-end.
@ -43,6 +58,8 @@ public class FlatMapTest {
private static final double Z14_WIDTH = 1d / Z14_TILES;
private static final int Z13_TILES = 1 << 13;
private static final double Z13_WIDTH = 1d / Z13_TILES;
private static final int Z12_TILES = 1 << 12;
private static final double Z12_WIDTH = 1d / Z12_TILES;
private final Stats stats = new Stats.InMemory();
private void processReaderFeatures(FeatureGroup featureGroup, Profile profile, CommonParams config,
@ -83,7 +100,11 @@ public class FlatMapTest {
featureGroup.sorter().sort();
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
MbtilesWriter.writeOutput(featureGroup, db, () -> 0L, profile, config, stats);
return new FlatMapResults(TestUtils.getTileMap(db), db.metadata().getAll());
var tileMap = TestUtils.getTileMap(db);
tileMap.values().forEach(fs -> {
fs.forEach(f -> f.geometry().validate());
});
return new FlatMapResults(tileMap, db.metadata().getAll());
}
}
@ -296,6 +317,179 @@ public class FlatMapTest {
), results.tiles);
}
@Test
public void testMultiLineString() throws IOException, SQLException {
double x1 = 0.5 + Z14_WIDTH / 2;
double y1 = 0.5 + Z14_WIDTH / 2;
double x2 = x1 + Z14_WIDTH;
double y2 = y1 + Z14_WIDTH;
double lat1 = GeoUtils.getWorldLat(y1);
double lng1 = GeoUtils.getWorldLon(x1);
double lat2 = GeoUtils.getWorldLat(y2);
double lng2 = GeoUtils.getWorldLon(x2);
var results = runWithReaderFeatures(
Map.of("threads", "1"),
List.of(
new ReaderFeature(newMultiLineString(
newLineString(lng1, lat1, lng2, lat2),
newLineString(lng2, lat2, lng1, lat1)
), Map.of(
"attr", "value"
))
),
(in, features) -> {
features.line("layer")
.setZoomRange(13, 14)
.setBufferPixels(4);
}
);
assertSubmap(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
feature(newMultiLineString(
newLineString(128, 128, 260, 260),
newLineString(260, 260, 128, 128)
), Map.of())
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of(
feature(newMultiLineString(
newLineString(-4, -4, 128, 128),
newLineString(128, 128, -4, -4)
), Map.of())
),
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
feature(newMultiLineString(
newLineString(64, 64, 192, 192),
newLineString(192, 192, 64, 64)
), Map.of())
)
), results.tiles);
}
private List<Coordinate> z14CoordinateList(double... coords) {
List<Coordinate> points = newCoordinateList(coords);
points.forEach(c -> {
c.x = 0.5 + c.x * Z14_WIDTH;
c.y = 0.5 + c.y * Z14_WIDTH;
});
return points;
}
@Test
public void testPolygon() throws IOException, SQLException {
List<Coordinate> outerPoints = z14CoordinateList(
0.5, 0.5,
3.5, 0.5,
3.5, 2.5,
0.5, 2.5,
0.5, 0.5
);
List<Coordinate> innerPoints = z14CoordinateList(
1.25, 1.25,
1.75, 1.25,
1.75, 1.75,
1.25, 1.75,
1.25, 1.25
);
var results = runWithReaderFeatures(
Map.of("threads", "1"),
List.of(
new ReaderFeature(newPolygon(
outerPoints,
List.of(innerPoints)
), Map.of())
),
(in, features) -> {
features.line("layer")
.setZoomRange(12, 14)
.setBufferPixels(4);
}
);
assertSubmap(Map.ofEntries(
// Z14 - row 1
newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of(
feature(tileBottomRight(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2, 14, List.of(
feature(tileBottom(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2, 14, List.of(
feature(tileBottom(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2, 14, List.of(
feature(tileBottomLeft(4), Map.of())
)),
// Z14 - row 2
newTileEntry(Z14_TILES / 2, Z14_TILES / 2 + 1, 14, List.of(
feature(tileRight(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14, List.of(
feature(newPolygon(
tileFill(5),
List.of(newCoordinateList(
64, 64,
192, 64,
192, 192,
64, 192,
64, 64
))
), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2 + 1, 14, List.of(
feature(newPolygon(tileFill(5), List.of()), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2 + 1, 14, List.of(
feature(tileLeft(4), Map.of())
)),
// Z14 - row 3
newTileEntry(Z14_TILES / 2, Z14_TILES / 2 + 2, 14, List.of(
feature(tileTopRight(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 2, 14, List.of(
feature(tileTop(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2 + 2, 14, List.of(
feature(tileTop(4), Map.of())
)),
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2 + 2, 14, List.of(
feature(tileTopLeft(4), Map.of())
)),
// Z13
newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of(
feature(newPolygon(
rectangleCoordList(64, 256 + 4),
List.of(rectangleCoordList(128 + 64, 256 - 64)) // hole
), Map.of())
)),
newTileEntry(Z13_TILES / 2 + 1, Z13_TILES / 2, 13, List.of(
feature(rectangle(-4, 64, 256 - 64, 256 + 4), Map.of())
)),
newTileEntry(Z13_TILES / 2, Z13_TILES / 2 + 1, 13, List.of(
feature(rectangle(64, -4, 256 + 4, 64), Map.of())
)),
newTileEntry(Z13_TILES / 2 + 1, Z13_TILES / 2 + 1, 13, List.of(
feature(rectangle(-4, -4, 256 - 64, 64), Map.of())
)),
// Z12
newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of(
feature(newPolygon(
rectangleCoordList(32, 32, 256 - 32, 128 + 32),
List.of(
rectangleCoordList(64 + 32, 128 - 32) // hole
)
), Map.of())
))
), results.tiles);
}
private Map.Entry<TileCoord, List<TestUtils.ComparableFeature>> newTileEntry(int x, int y, int z,
List<TestUtils.ComparableFeature> features) {
return Map.entry(TileCoord.ofXYZ(x, y, z), features);
}
private interface LayerPostprocessFunction {
List<VectorTileEncoder.Feature> process(String layer, int zoom, List<VectorTileEncoder.Feature> items);

Wyświetl plik

@ -1,6 +1,9 @@
package com.onthegomap.flatmap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -23,6 +26,7 @@ import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
@ -30,6 +34,8 @@ import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Lineal;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
@ -55,10 +61,80 @@ public class TestUtils {
return GeoUtils.JTS_FACTORY.createPolygon(newCoordinateList(coords).toArray(new Coordinate[0]));
}
public static Polygon tileBottomRight(double buffer) {
return rectangle(128, 128, 256 + buffer, 256 + buffer);
}
public static Polygon tileBottom(double buffer) {
return rectangle(-buffer, 128, 256 + buffer, 256 + buffer);
}
public static Polygon tileBottomLeft(double buffer) {
return rectangle(-buffer, 128, 128, 256 + buffer);
}
public static Polygon tileLeft(double buffer) {
return rectangle(-buffer, -buffer, 128, 256 + buffer);
}
public static Polygon tileTopLeft(double buffer) {
return rectangle(-buffer, -buffer, 128, 128);
}
public static Polygon tileTop(double buffer) {
return rectangle(-buffer, -buffer, 256 + buffer, 128);
}
public static Polygon tileTopRight(double buffer) {
return rectangle(128, -buffer, 256 + buffer, 128);
}
public static Polygon tileRight(double buffer) {
return rectangle(128, -buffer, 256 + buffer, 256 + buffer);
}
public static List<Coordinate> tileFill(double buffer) {
return rectangleCoordList(-buffer, 256 + buffer);
}
public static List<Coordinate> rectangleCoordList(double minX, double minY, double maxX, double maxY) {
return newCoordinateList(
minX, minY,
maxX, minY,
maxX, maxY,
minX, maxY,
minX, minY
);
}
public static List<Coordinate> rectangleCoordList(double min, double max) {
return rectangleCoordList(min, min, max, max);
}
public static Polygon rectangle(double minX, double minY, double maxX, double maxY) {
return newPolygon(rectangleCoordList(minX, minY, maxX, maxY), List.of());
}
public static Polygon rectangle(double min, double max) {
return rectangle(min, min, max, max);
}
public static Polygon newPolygon(List<Coordinate> outer, List<List<Coordinate>> inner) {
return GeoUtils.JTS_FACTORY.createPolygon(
GeoUtils.JTS_FACTORY.createLinearRing(outer.toArray(new Coordinate[0])),
inner.stream().map(i -> GeoUtils.JTS_FACTORY.createLinearRing(i.toArray(new Coordinate[0])))
.toArray(LinearRing[]::new)
);
}
public static LineString newLineString(double... coords) {
return GeoUtils.JTS_FACTORY.createLineString(newCoordinateList(coords).toArray(new Coordinate[0]));
}
public static MultiLineString newMultiLineString(LineString... lineStrings) {
return GeoUtils.JTS_FACTORY.createMultiLineString(lineStrings);
}
public static Point newPoint(double x, double y) {
return GeoUtils.JTS_FACTORY.createPoint(new CoordinateXY(x, y));
}
@ -158,9 +234,39 @@ public class TestUtils {
return GeoUtils.JTS_FACTORY.createGeometryCollection();
}
public static void validateGeometry(Geometry g) {
if (g instanceof GeometryCollection gs) {
for (int i = 0; i < gs.getNumGeometries(); i++) {
validateGeometry(gs.getGeometryN(i));
}
} else if (g instanceof Point point) {
assertFalse(point.isEmpty(), "empty: " + point);
} else if (g instanceof LineString line) {
assertTrue(line.getNumPoints() >= 2, "too few points: " + line);
} else if (g instanceof Polygon poly) {
var outer = poly.getExteriorRing();
assertTrue(Orientation.isCCW(outer.getCoordinateSequence()), "outer not CCW: " + poly);
assertTrue(outer.getNumPoints() >= 4, "outer too few points: " + poly);
assertTrue(outer.isClosed(), "outer not closed: " + poly);
for (int i = 0; i < poly.getNumInteriorRing(); i++) {
var inner = poly.getInteriorRingN(i);
assertFalse(Orientation.isCCW(inner.getCoordinateSequence()),
"inner " + i + " not CW: " + poly);
assertTrue(outer.getNumPoints() >= 4, "inner " + i + " too few points: " + poly);
assertTrue(inner.isClosed(), "inner " + i + " not closed: " + poly);
}
} else {
fail("Unrecognized geometry: " + g);
}
}
public interface GeometryComparision {
Geometry geom();
default void validate() {
validateGeometry(geom());
}
}
private static record NormGeometry(Geometry geom) implements GeometryComparision {

Wyświetl plik

@ -47,7 +47,7 @@ public class TileExtentsTest {
}
@Test
public void bottomLeft() {
public void testBottomLeft() {
TileExtents extents = TileExtents
.computeFromWorldBounds(14, new Envelope(0, eps, 1 - eps, 1));
for (int z = 0; z <= 14; z++) {

Wyświetl plik

@ -1,15 +1,21 @@
package com.onthegomap.flatmap.render;
import static com.onthegomap.flatmap.TestUtils.assertExactSameFeatures;
import static com.onthegomap.flatmap.TestUtils.assertSameNormalizedFeatures;
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
import static com.onthegomap.flatmap.TestUtils.newLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
import static com.onthegomap.flatmap.TestUtils.newPoint;
import static com.onthegomap.flatmap.TestUtils.newPolygon;
import static com.onthegomap.flatmap.TestUtils.rectangle;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import com.onthegomap.flatmap.Arguments;
import com.onthegomap.flatmap.CommonParams;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.TestUtils;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.TileCoord;
import com.onthegomap.flatmap.read.ReaderFeature;
@ -23,11 +29,13 @@ import java.util.TreeMap;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.locationtech.jts.geom.Geometry;
public class FeatureRendererTest {
private final CommonParams config = CommonParams.defaults();
private CommonParams config = CommonParams.defaults();
private FeatureCollector collector(Geometry worldGeom) {
var latLonGeom = GeoUtils.worldToLatLonCoords(worldGeom);
@ -38,6 +46,7 @@ public class FeatureRendererTest {
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);
result.values().forEach(gs -> gs.forEach(TestUtils::validateGeometry));
return result;
}
@ -45,11 +54,16 @@ public class FeatureRendererTest {
Map<TileCoord, Collection<RenderedFeature>> result = new TreeMap<>();
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
.add(rendered)).renderFeature(feature);
result.values()
.forEach(gs -> gs.forEach(f -> TestUtils.validateGeometry(f.vectorTileFeature().geometry().decode())));
return result;
}
private static final int Z14_TILES = 1 << 14;
private static final double Z14_WIDTH = 1d / Z14_TILES;
private static final double Z14_PX = Z14_WIDTH / 256;
private static final int Z13_TILES = 1 << 13;
private static final double Z13_WIDTH = 1d / Z13_TILES;
@Test
public void testEmptyGeometry() {
@ -57,6 +71,10 @@ public class FeatureRendererTest {
assertSameNormalizedFeatures(Map.of(), renderGeometry(feature));
}
/*
* POINT TESTS
*/
@Test
public void testSinglePoint() {
var feature = pointFeature(newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2))
@ -82,6 +100,33 @@ public class FeatureRendererTest {
), renderGeometry(feature));
}
@Test
public void testRepeatSinglePointNeighboringTilesBuffer0() {
var feature = pointFeature(newPoint(0.5, 0.5))
.setZoomRange(1, 1)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(0, 0, 1), List.of(newPoint(256, 256)),
TileCoord.ofXYZ(1, 0, 1), List.of(newPoint(0, 256)),
TileCoord.ofXYZ(0, 1, 1), List.of(newPoint(256, 0)),
TileCoord.ofXYZ(1, 1, 1), List.of(newPoint(0, 0))
), renderGeometry(feature));
}
@Test
public void testEmitPointsRespectExtents() {
config = CommonParams.from(Arguments.of(
"bounds", "0,-80,180,0"
));
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(1, 1, 1), List.of(newPoint(1, 1))
), renderGeometry(feature));
}
@TestFactory
public List<DynamicTest> testProcessPointsNearInternationalDateLineAndPoles() {
double d = 1d / 512;
@ -221,6 +266,10 @@ public class FeatureRendererTest {
return collector(geom).point("layer");
}
/*
* LINE TESTS
*/
private FeatureCollector.Feature lineFeature(Geometry geom) {
return collector(geom).line("layer");
}
@ -234,17 +283,310 @@ public class FeatureRendererTest {
))
.setZoomRange(14, 14)
.setBufferPixels(8);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newLineString(64, 64, 192, 192)
)
), renderGeometry(feature));
}
@Test
public void testSimplifyLine() {
double z14hypot = Math.sqrt(Z14_WIDTH * Z14_WIDTH);
var feature = lineFeature(newLineString(
0.5 + z14hypot / 4, 0.5 + z14hypot / 4,
0.5 + z14hypot / 2, 0.5 + z14hypot / 2,
0.5 + z14hypot * 3 / 4, 0.5 + z14hypot * 3 / 4
))
.setZoomRange(14, 14)
.setBufferPixels(8);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newLineString(64, 64, 192, 192)
)
), renderGeometry(feature));
}
@Test
public void testSplitLineFeatureTouchingNeighboringTile() {
double z14hypot = Math.sqrt(Z14_WIDTH * Z14_WIDTH);
var feature = lineFeature(newLineString(
0.5 + z14hypot / 4, 0.5 + z14hypot / 4,
0.5 + Z14_WIDTH * (256 - 8) / 256d, 0.5 + Z14_WIDTH * (256 - 8) / 256d
))
.setZoomRange(14, 14)
.setBufferPixels(8);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newLineString(64, 64, 256 - 8, 256 - 8)
)
// only a single point in neighboring tile, exclude
), renderGeometry(feature));
}
@Test
public void testSplitLineFeatureEnteringNeighboringTileBoudary() {
double z14hypot = Math.sqrt(Z14_WIDTH * Z14_WIDTH);
var feature = lineFeature(newLineString(
0.5 + z14hypot / 4, 0.5 + z14hypot / 4,
0.5 + Z14_WIDTH * (256 - 7) / 256d, 0.5 + Z14_WIDTH * (256 - 7) / 256d
))
.setZoomRange(14, 14)
.setBufferPixels(8);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newLineString(64, 64, 256 - 7, 256 - 7)
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2, 14), List.of(
newLineString(-8, 248, -7, 249)
),
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2 + 1, 14), List.of(
newLineString(248, -8, 249, -7)
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of(
newLineString(-8, -8, -7, -7)
)
), renderGeometry(feature));
}
@Test
public void test3PointLine() {
var feature = lineFeature(newLineString(
0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2,
0.5 + 3 * Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2,
0.5 + 3 * Z14_WIDTH / 2, 0.5 + 3 * Z14_WIDTH / 2
))
.setZoomRange(14, 14)
.setBufferPixels(8);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newLineString(128, 128, 256 + 8, 128)
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2, 14), List.of(
newLineString(-8, 128, 128, 128, 128, 256 + 8)
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of(
newLineString(128, -8, 128, 128)
)
), renderGeometry(feature));
}
@Test
public void testLimitSingleLineStringLength() {
var eps = Z13_WIDTH / 4096;
var pixel = Z13_WIDTH / 256;
var featureBelow = lineFeature(newMultiLineString(
// below limit - ignore
newLineString(0.5, 0.5 + pixel, 0.5 + pixel - eps, 0.5 + pixel)
))
.setMinPixelSize(1)
.setZoomRange(13, 13)
.setBufferPixels(0);
var featureAbove = lineFeature(newMultiLineString(
// above limit - allow
newLineString(0.5, 0.5 + pixel, 0.5 + pixel + eps, 0.5 + pixel)
))
.setMinPixelSize(1)
.setZoomRange(13, 13)
.setBufferPixels(0);
assertExactSameFeatures(Map.of(), renderGeometry(featureBelow));
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
newLineString(0, 1, 1 + 256d / 4096, 1)
)
), renderGeometry(featureAbove));
}
@Test
public void testLimitMultiLineStringLength() {
var eps = Z13_WIDTH / 4096;
var pixel = Z13_WIDTH / 256;
var feature = lineFeature(newMultiLineString(
// below limit - ignore
newLineString(0.5, 0.5 + pixel, 0.5 + pixel - eps, 0.5 + pixel),
// above limit - allow
newLineString(0.5, 0.5 + pixel, 0.5 + pixel + eps, 0.5 + pixel)
))
.setMinPixelSize(1)
.setZoomRange(13, 13)
.setBufferPixels(0);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
newLineString(0, 1, 1 + 256d / 4096, 1)
)
), renderGeometry(feature));
}
/*
* POLYGON TESTS
*/
private FeatureCollector.Feature polygonFeature(Geometry geom) {
return collector(geom).polygon("layer");
}
@Test
public void testSimpleTriangleCCW() {
var feature = polygonFeature(
newPolygon(
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 20, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 20,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newPoint(64, 64),
newPoint(192, 192)
newPolygon(
10, 10,
20, 10,
10, 20,
10, 10
)
)
), renderGeometry(feature));
}
@Test
public void testSimpleTriangleCW() {
var feature = polygonFeature(
newPolygon(
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 20,
0.5 + Z14_PX * 20, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newPolygon(
10, 10,
10, 20,
20, 10,
10, 10
)
)
), renderGeometry(feature));
}
@Test
public void testTriangleTouchingNeighboringTileDoesNotEmit() {
var feature = polygonFeature(
newPolygon(
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 256, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 20,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newPolygon(
10, 10,
256, 10,
10, 20,
10, 10
)
)
), renderGeometry(feature));
}
@Test
public void testTriangleTouchingNeighboringTileBelowDoesNotEmit() {
var feature = polygonFeature(
newPolygon(
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 20, 0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 256,
0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
newPolygon(
10, 10,
20, 10,
10, 256,
10, 10
)
)
), renderGeometry(feature));
}
@ParameterizedTest
@CsvSource({
"0,256, 0,256", // all
"0,10, 0,10", // top-left
"5,10, 0,10", // top partial
"250,256, 0,10", // top all
"250,256, 0,10", // top-right
"250,256, 0,256", // right all
"250,256, 10,250", // right partial
"250,256, 250,256", // right bottom
"0,256, 250,256", // bottom all
"240,250, 250,256", // bottom partial
"0,10, 250,256", // bottom left
"0,10, 0,256", // left all
"0,10, 240,250", // left partial
})
public void testRectangleTouchingNeighboringTilesDoesNotEmit(int x1, int x2, int y1, int y2) {
var feature = polygonFeature(
rectangle(
0.5 + Z14_PX * x1,
0.5 + Z14_PX * y1,
0.5 + Z14_PX * x2,
0.5 + Z14_PX * y2
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(0);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
rectangle(x1, y1, x2, y2)
)
), renderGeometry(feature));
}
@Test
public void testOverlapTileHorizontal() {
var feature = polygonFeature(
rectangle(
0.5 + Z14_PX * 10,
0.5 + Z14_PX * 10,
0.5 + Z14_PX * 258,
0.5 + Z14_PX * 20
)
)
.setMinPixelSize(1)
.setZoomRange(14, 14)
.setBufferPixels(1);
assertSameNormalizedFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
rectangle(10, 10, 257, 20)
),
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2, 14), List.of(
rectangle(-1, 10, 2, 20)
)
), renderGeometry(feature));
}
// TODO: centroid
// TODO: line
// TODO: multilinestring
// TODO: poly
// TODO: multipolygon
// TODO: geometry collection