kopia lustrzana https://github.com/onthegomap/planetiler
polygon tests
rodzic
fcaeb88c40
commit
c219c93f93
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ public class VectorTileEncoder {
|
|||
if (first) {
|
||||
first = false;
|
||||
outerCCW = ccw;
|
||||
assert outerCCW;
|
||||
}
|
||||
if (ccw == outerCCW) {
|
||||
ringsForCurrentPolygon = new ArrayList<>();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue