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) {
|
public boolean testX(int x) {
|
||||||
return x >= minX && x <= maxX;
|
return x >= minX && x < maxX;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean testY(int y) {
|
public boolean testY(int y) {
|
||||||
return y >= minY && y <= maxY;
|
return y >= minY && y < maxY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ public class VectorTileEncoder {
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
outerCCW = ccw;
|
outerCCW = ccw;
|
||||||
|
assert outerCCW;
|
||||||
}
|
}
|
||||||
if (ccw == outerCCW) {
|
if (ccw == outerCCW) {
|
||||||
ringsForCurrentPolygon = new ArrayList<>();
|
ringsForCurrentPolygon = new ArrayList<>();
|
||||||
|
|
|
@ -51,7 +51,10 @@ public class MutableCoordinateSequence extends PackedCoordinateSequence {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Envelope expandEnvelope(Envelope env) {
|
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) {
|
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 LineString[] EMPTY_LINE_STRING_ARRAY = new LineString[0];
|
||||||
private static final Coordinate[] EMPTY_COORD_ARRAY = new Coordinate[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_RADIUS_METERS = 6_378_137;
|
||||||
private static final double WORLD_CIRCUMFERENCE_METERS = Math.PI * 2 * WORLD_RADIUS_METERS;
|
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));
|
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) {
|
public static Geometry createMultiLineString(List<LineString> lineStrings) {
|
||||||
return JTS_FACTORY.createMultiLineString(lineStrings.toArray(EMPTY_LINE_STRING_ARRAY));
|
return JTS_FACTORY.createMultiLineString(lineStrings.toArray(EMPTY_LINE_STRING_ARRAY));
|
||||||
}
|
}
|
||||||
|
@ -230,4 +226,12 @@ public class GeoUtils {
|
||||||
(int) Math.floor((coord.getY() * tilesAtZoom) / labelGridTileSize)
|
(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;
|
package com.onthegomap.flatmap.render;
|
||||||
|
|
||||||
import com.onthegomap.flatmap.FeatureCollector;
|
|
||||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||||
import com.onthegomap.flatmap.geo.TileCoord;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.jetbrains.annotations.NotNull;
|
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.GeometryCollection;
|
||||||
import org.locationtech.jts.geom.LineString;
|
import org.locationtech.jts.geom.LineString;
|
||||||
import org.locationtech.jts.geom.LinearRing;
|
import org.locationtech.jts.geom.LinearRing;
|
||||||
|
import org.locationtech.jts.geom.Point;
|
||||||
import org.locationtech.jts.geom.Polygon;
|
import org.locationtech.jts.geom.Polygon;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -21,39 +20,6 @@ class CoordinateSequenceExtractor {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(CoordinateSequenceExtractor.class);
|
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) {
|
static List<List<CoordinateSequence>> extractGroups(Geometry geom, double minSize) {
|
||||||
List<List<CoordinateSequence>> result = new ArrayList<>();
|
List<List<CoordinateSequence>> result = new ArrayList<>();
|
||||||
extractGroups(geom, result, minSize);
|
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.GeometryException;
|
||||||
import com.onthegomap.flatmap.geo.TileCoord;
|
import com.onthegomap.flatmap.geo.TileCoord;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -17,7 +16,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
import org.locationtech.jts.geom.CoordinateSequence;
|
import org.locationtech.jts.geom.CoordinateSequence;
|
||||||
import org.locationtech.jts.geom.CoordinateXY;
|
|
||||||
import org.locationtech.jts.geom.Geometry;
|
import org.locationtech.jts.geom.Geometry;
|
||||||
import org.locationtech.jts.geom.GeometryCollection;
|
import org.locationtech.jts.geom.GeometryCollection;
|
||||||
import org.locationtech.jts.geom.LineString;
|
import org.locationtech.jts.geom.LineString;
|
||||||
|
@ -57,14 +55,6 @@ public class FeatureRenderer {
|
||||||
this.consumer = consumer;
|
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) {
|
public void renderFeature(FeatureCollector.Feature feature) {
|
||||||
renderGeometry(feature.getGeometry(), 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) {
|
private void addPointFeature(FeatureCollector.Feature feature, Coordinate... coords) {
|
||||||
long id = idGen.incrementAndGet();
|
long id = idGen.incrementAndGet();
|
||||||
boolean hasLabelGrid = feature.hasLabelGrid();
|
boolean hasLabelGrid = feature.hasLabelGrid();
|
||||||
|
@ -119,10 +88,8 @@ public class FeatureRenderer {
|
||||||
Map<String, Object> attrs = feature.getAttrsAtZoom(zoom);
|
Map<String, Object> attrs = feature.getAttrsAtZoom(zoom);
|
||||||
double buffer = feature.getBufferPixelsAtZoom(zoom) / 256;
|
double buffer = feature.getBufferPixelsAtZoom(zoom) / 256;
|
||||||
int tilesAtZoom = 1 << zoom;
|
int tilesAtZoom = 1 << zoom;
|
||||||
for (Coordinate coord : coords) {
|
TileExtents.ForZoom extents = config.extents().getForZoom(zoom);
|
||||||
// TODO TiledGeometry.sliceIntoTiles(...)
|
TiledGeometry tiled = TiledGeometry.slicePointsIntoTiles(extents, buffer, zoom, coords);
|
||||||
slicePoint(sliced, zoom, buffer, coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderedFeature.Group groupInfo = null;
|
RenderedFeature.Group groupInfo = null;
|
||||||
if (hasLabelGrid && coords.length == 1) {
|
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();
|
TileCoord tile = entry.getKey();
|
||||||
Set<Coordinate> value = entry.getValue();
|
List<List<CoordinateSequence>> result = entry.getValue();
|
||||||
Geometry geom = value.size() == 1 ? GeoUtils.point(value.iterator().next()) : GeoUtils.multiPoint(value);
|
Geometry geom = CoordinateSequenceExtractor.reassemblePoints(result);
|
||||||
// TODO stats
|
// TODO stats
|
||||||
// TODO writeTileFeatures
|
// TODO writeTileFeatures
|
||||||
emitFeature(feature, id, attrs, tile, geom, groupInfo);
|
emitFeature(feature, id, attrs, tile, geom, groupInfo);
|
||||||
|
@ -197,7 +164,7 @@ public class FeatureRenderer {
|
||||||
geom = simplifier.getResultGeometry();
|
geom = simplifier.getResultGeometry();
|
||||||
|
|
||||||
List<List<CoordinateSequence>> groups = CoordinateSequenceExtractor.extractGroups(geom, minSize);
|
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);
|
TileExtents.ForZoom extents = config.extents().getForZoom(z);
|
||||||
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
|
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
|
||||||
writeTileFeatures(id, feature, sliced);
|
writeTileFeatures(id, feature, sliced);
|
||||||
|
@ -213,10 +180,10 @@ public class FeatureRenderer {
|
||||||
|
|
||||||
Geometry geom;
|
Geometry geom;
|
||||||
if (feature.area()) {
|
if (feature.area()) {
|
||||||
geom = CoordinateSequenceExtractor.reassemblePolygon(feature, tile, geoms);
|
geom = CoordinateSequenceExtractor.reassemblePolygons(geoms);
|
||||||
geom = GeoUtils.fixPolygon(geom, 2);
|
geom = GeoUtils.fixPolygon(geom, 2);
|
||||||
} else {
|
} else {
|
||||||
geom = CoordinateSequenceExtractor.reassembleLineString(geoms);
|
geom = CoordinateSequenceExtractor.reassembleLineStrings(geoms);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -21,13 +21,16 @@ import com.graphhopper.coll.GHIntObjectHashMap;
|
||||||
import com.onthegomap.flatmap.TileExtents;
|
import com.onthegomap.flatmap.TileExtents;
|
||||||
import com.onthegomap.flatmap.collections.IntRange;
|
import com.onthegomap.flatmap.collections.IntRange;
|
||||||
import com.onthegomap.flatmap.collections.MutableCoordinateSequence;
|
import com.onthegomap.flatmap.collections.MutableCoordinateSequence;
|
||||||
|
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||||
import com.onthegomap.flatmap.geo.TileCoord;
|
import com.onthegomap.flatmap.geo.TileCoord;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
import org.locationtech.jts.geom.CoordinateSequence;
|
import org.locationtech.jts.geom.CoordinateSequence;
|
||||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
|
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -40,11 +43,13 @@ import org.slf4j.LoggerFactory;
|
||||||
class TiledGeometry {
|
class TiledGeometry {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(TiledGeometry.class);
|
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<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 TileExtents.ForZoom extents;
|
||||||
private final double buffer;
|
private final double buffer;
|
||||||
|
private final double neighborBuffer;
|
||||||
private final int z;
|
private final int z;
|
||||||
private final boolean area;
|
private final boolean area;
|
||||||
private final int max;
|
private final int max;
|
||||||
|
@ -52,11 +57,51 @@ class TiledGeometry {
|
||||||
private TiledGeometry(TileExtents.ForZoom extents, double buffer, int z, boolean area) {
|
private TiledGeometry(TileExtents.ForZoom extents, double buffer, int z, boolean area) {
|
||||||
this.extents = extents;
|
this.extents = extents;
|
||||||
this.buffer = buffer;
|
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.z = z;
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.max = 1 << z;
|
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() {
|
public int zoomLevel() {
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +121,7 @@ class TiledGeometry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<TileCoord> getFilledTiles() {
|
public Iterable<TileCoord> getFilledTiles() {
|
||||||
return () -> filledRanges.entrySet().stream()
|
return filledRanges == null ? Collections.emptyList() : () -> filledRanges.entrySet().stream()
|
||||||
.<TileCoord>mapMulti((entry, next) -> {
|
.<TileCoord>mapMulti((entry, next) -> {
|
||||||
int x = entry.getKey();
|
int x = entry.getKey();
|
||||||
for (int y : entry.getValue()) {
|
for (int y : entry.getValue()) {
|
||||||
|
@ -194,8 +239,8 @@ class TiledGeometry {
|
||||||
double minX = Math.min(_ax, _bx);
|
double minX = Math.min(_ax, _bx);
|
||||||
double maxX = Math.max(_ax, _bx);
|
double maxX = Math.max(_ax, _bx);
|
||||||
|
|
||||||
int startX = (int) Math.floor(minX - buffer);
|
int startX = (int) Math.floor(minX - neighborBuffer);
|
||||||
int endX = (int) Math.floor(maxX + buffer);
|
int endX = (int) Math.floor(maxX + neighborBuffer);
|
||||||
|
|
||||||
for (int x = startX; x <= endX; x++) {
|
for (int x = startX; x <= endX; x++) {
|
||||||
double ax = _ax - x;
|
double ax = _ax - x;
|
||||||
|
@ -244,8 +289,8 @@ class TiledGeometry {
|
||||||
// add the last point
|
// add the last point
|
||||||
double _ax = segment.getX(segment.size() - 1);
|
double _ax = segment.getX(segment.size() - 1);
|
||||||
double ay = segment.getY(segment.size() - 1);
|
double ay = segment.getY(segment.size() - 1);
|
||||||
int startX = (int) Math.floor(_ax - buffer);
|
int startX = (int) Math.floor(_ax - neighborBuffer);
|
||||||
int endX = (int) Math.floor(_ax + buffer);
|
int endX = (int) Math.floor(_ax + neighborBuffer);
|
||||||
|
|
||||||
for (int x = startX - 1; x <= endX + 1; x++) {
|
for (int x = startX - 1; x <= endX + 1; x++) {
|
||||||
double ax = _ax - x;
|
double ax = _ax - x;
|
||||||
|
@ -297,10 +342,10 @@ class TiledGeometry {
|
||||||
|
|
||||||
int extentMinY = extents.minY();
|
int extentMinY = extents.minY();
|
||||||
int extentMaxY = extents.maxY();
|
int extentMaxY = extents.maxY();
|
||||||
int startY = Math.max(extentMinY, (int) Math.floor(minY - buffer));
|
int startY = Math.max(extentMinY, (int) Math.floor(minY - neighborBuffer));
|
||||||
int endStartY = Math.max(extentMinY, (int) Math.floor(minY + buffer));
|
int endStartY = Math.max(extentMinY, (int) Math.floor(minY + neighborBuffer));
|
||||||
int startEndY = Math.min(extentMaxY - 1, (int) Math.floor(maxY - buffer));
|
int startEndY = Math.min(extentMaxY - 1, (int) Math.floor(maxY - neighborBuffer));
|
||||||
int endY = Math.min(extentMaxY - 1, (int) Math.floor(maxY + buffer));
|
int endY = Math.min(extentMaxY - 1, (int) Math.floor(maxY + neighborBuffer));
|
||||||
|
|
||||||
boolean onRightEdge = area && ax == bx && ax == rightEdge && by > ay;
|
boolean onRightEdge = area && ax == bx && ax == rightEdge && by > ay;
|
||||||
boolean onLeftEdge = area && ax == bx && ax == leftEdge && by < ay;
|
boolean onLeftEdge = area && ax == bx && ax == leftEdge && by < ay;
|
||||||
|
@ -409,8 +454,8 @@ class TiledGeometry {
|
||||||
int last = stripeSegment.size() - 1;
|
int last = stripeSegment.size() - 1;
|
||||||
double ax = stripeSegment.getX(last);
|
double ax = stripeSegment.getX(last);
|
||||||
double ay = stripeSegment.getY(last);
|
double ay = stripeSegment.getY(last);
|
||||||
int startY = (int) (ay - buffer);
|
int startY = (int) Math.floor(ay - neighborBuffer);
|
||||||
int endY = (int) (ay + buffer);
|
int endY = (int) Math.floor(ay + neighborBuffer);
|
||||||
|
|
||||||
for (int y = startY - 1; y <= endY + 1; y++) {
|
for (int y = startY - 1; y <= endY + 1; y++) {
|
||||||
MutableCoordinateSequence slice = ySlices.get(y);
|
MutableCoordinateSequence slice = ySlices.get(y);
|
||||||
|
@ -433,6 +478,9 @@ class TiledGeometry {
|
||||||
if (yRange == null) {
|
if (yRange == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (filledRanges == null) {
|
||||||
|
filledRanges = new HashMap<>();
|
||||||
|
}
|
||||||
IntRange existing = filledRanges.get(x);
|
IntRange existing = filledRanges.get(x);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
filledRanges.put(x, yRange);
|
filledRanges.put(x, yRange);
|
||||||
|
@ -445,6 +493,9 @@ class TiledGeometry {
|
||||||
if (yRange == null) {
|
if (yRange == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (filledRanges == null) {
|
||||||
|
filledRanges = new HashMap<>();
|
||||||
|
}
|
||||||
IntRange existing = filledRanges.get(x);
|
IntRange existing = filledRanges.get(x);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.removeAll(yRange);
|
existing.removeAll(yRange);
|
||||||
|
|
|
@ -3,9 +3,23 @@ package com.onthegomap.flatmap;
|
||||||
import static com.onthegomap.flatmap.TestUtils.assertSameJson;
|
import static com.onthegomap.flatmap.TestUtils.assertSameJson;
|
||||||
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
|
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
|
||||||
import static com.onthegomap.flatmap.TestUtils.feature;
|
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.newLineString;
|
||||||
|
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
|
||||||
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
|
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
|
||||||
import static com.onthegomap.flatmap.TestUtils.newPoint;
|
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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import com.graphhopper.reader.ReaderRelation;
|
import com.graphhopper.reader.ReaderRelation;
|
||||||
|
@ -29,6 +43,7 @@ import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.junit.jupiter.api.Test;
|
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.
|
* 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 double Z14_WIDTH = 1d / Z14_TILES;
|
||||||
private static final int Z13_TILES = 1 << 13;
|
private static final int Z13_TILES = 1 << 13;
|
||||||
private static final double Z13_WIDTH = 1d / Z13_TILES;
|
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 final Stats stats = new Stats.InMemory();
|
||||||
|
|
||||||
private void processReaderFeatures(FeatureGroup featureGroup, Profile profile, CommonParams config,
|
private void processReaderFeatures(FeatureGroup featureGroup, Profile profile, CommonParams config,
|
||||||
|
@ -83,7 +100,11 @@ public class FlatMapTest {
|
||||||
featureGroup.sorter().sort();
|
featureGroup.sorter().sort();
|
||||||
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
||||||
MbtilesWriter.writeOutput(featureGroup, db, () -> 0L, profile, config, stats);
|
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);
|
), 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 {
|
private interface LayerPostprocessFunction {
|
||||||
|
|
||||||
List<VectorTileEncoder.Feature> process(String layer, int zoom, List<VectorTileEncoder.Feature> items);
|
List<VectorTileEncoder.Feature> process(String layer, int zoom, List<VectorTileEncoder.Feature> items);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package com.onthegomap.flatmap;
|
package com.onthegomap.flatmap;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
@ -23,6 +26,7 @@ import java.util.TreeMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
import org.locationtech.jts.algorithm.Orientation;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
import org.locationtech.jts.geom.CoordinateSequence;
|
import org.locationtech.jts.geom.CoordinateSequence;
|
||||||
import org.locationtech.jts.geom.CoordinateXY;
|
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.GeometryCollection;
|
||||||
import org.locationtech.jts.geom.LineString;
|
import org.locationtech.jts.geom.LineString;
|
||||||
import org.locationtech.jts.geom.Lineal;
|
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.MultiPoint;
|
||||||
import org.locationtech.jts.geom.MultiPolygon;
|
import org.locationtech.jts.geom.MultiPolygon;
|
||||||
import org.locationtech.jts.geom.Point;
|
import org.locationtech.jts.geom.Point;
|
||||||
|
@ -55,10 +61,80 @@ public class TestUtils {
|
||||||
return GeoUtils.JTS_FACTORY.createPolygon(newCoordinateList(coords).toArray(new Coordinate[0]));
|
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) {
|
public static LineString newLineString(double... coords) {
|
||||||
return GeoUtils.JTS_FACTORY.createLineString(newCoordinateList(coords).toArray(new Coordinate[0]));
|
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) {
|
public static Point newPoint(double x, double y) {
|
||||||
return GeoUtils.JTS_FACTORY.createPoint(new CoordinateXY(x, y));
|
return GeoUtils.JTS_FACTORY.createPoint(new CoordinateXY(x, y));
|
||||||
}
|
}
|
||||||
|
@ -158,9 +234,39 @@ public class TestUtils {
|
||||||
return GeoUtils.JTS_FACTORY.createGeometryCollection();
|
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 {
|
public interface GeometryComparision {
|
||||||
|
|
||||||
Geometry geom();
|
Geometry geom();
|
||||||
|
|
||||||
|
default void validate() {
|
||||||
|
validateGeometry(geom());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static record NormGeometry(Geometry geom) implements GeometryComparision {
|
private static record NormGeometry(Geometry geom) implements GeometryComparision {
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class TileExtentsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bottomLeft() {
|
public void testBottomLeft() {
|
||||||
TileExtents extents = TileExtents
|
TileExtents extents = TileExtents
|
||||||
.computeFromWorldBounds(14, new Envelope(0, eps, 1 - eps, 1));
|
.computeFromWorldBounds(14, new Envelope(0, eps, 1 - eps, 1));
|
||||||
for (int z = 0; z <= 14; z++) {
|
for (int z = 0; z <= 14; z++) {
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
package com.onthegomap.flatmap.render;
|
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.assertSameNormalizedFeatures;
|
||||||
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
|
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
|
||||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
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.newMultiPoint;
|
||||||
import static com.onthegomap.flatmap.TestUtils.newPoint;
|
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.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||||
|
|
||||||
|
import com.onthegomap.flatmap.Arguments;
|
||||||
import com.onthegomap.flatmap.CommonParams;
|
import com.onthegomap.flatmap.CommonParams;
|
||||||
import com.onthegomap.flatmap.FeatureCollector;
|
import com.onthegomap.flatmap.FeatureCollector;
|
||||||
|
import com.onthegomap.flatmap.TestUtils;
|
||||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||||
import com.onthegomap.flatmap.geo.TileCoord;
|
import com.onthegomap.flatmap.geo.TileCoord;
|
||||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
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.DynamicTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestFactory;
|
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;
|
import org.locationtech.jts.geom.Geometry;
|
||||||
|
|
||||||
public class FeatureRendererTest {
|
public class FeatureRendererTest {
|
||||||
|
|
||||||
private final CommonParams config = CommonParams.defaults();
|
private CommonParams config = CommonParams.defaults();
|
||||||
|
|
||||||
private FeatureCollector collector(Geometry worldGeom) {
|
private FeatureCollector collector(Geometry worldGeom) {
|
||||||
var latLonGeom = GeoUtils.worldToLatLonCoords(worldGeom);
|
var latLonGeom = GeoUtils.worldToLatLonCoords(worldGeom);
|
||||||
|
@ -38,6 +46,7 @@ public class FeatureRendererTest {
|
||||||
Map<TileCoord, Collection<Geometry>> result = new TreeMap<>();
|
Map<TileCoord, Collection<Geometry>> result = new TreeMap<>();
|
||||||
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
|
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
|
||||||
.add(rendered.vectorTileFeature().geometry().decode())).renderFeature(feature);
|
.add(rendered.vectorTileFeature().geometry().decode())).renderFeature(feature);
|
||||||
|
result.values().forEach(gs -> gs.forEach(TestUtils::validateGeometry));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +54,16 @@ public class FeatureRendererTest {
|
||||||
Map<TileCoord, Collection<RenderedFeature>> result = new TreeMap<>();
|
Map<TileCoord, Collection<RenderedFeature>> result = new TreeMap<>();
|
||||||
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
|
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
|
||||||
.add(rendered)).renderFeature(feature);
|
.add(rendered)).renderFeature(feature);
|
||||||
|
result.values()
|
||||||
|
.forEach(gs -> gs.forEach(f -> TestUtils.validateGeometry(f.vectorTileFeature().geometry().decode())));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int Z14_TILES = 1 << 14;
|
private static final int Z14_TILES = 1 << 14;
|
||||||
private static final double Z14_WIDTH = 1d / Z14_TILES;
|
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
|
@Test
|
||||||
public void testEmptyGeometry() {
|
public void testEmptyGeometry() {
|
||||||
|
@ -57,6 +71,10 @@ public class FeatureRendererTest {
|
||||||
assertSameNormalizedFeatures(Map.of(), renderGeometry(feature));
|
assertSameNormalizedFeatures(Map.of(), renderGeometry(feature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* POINT TESTS
|
||||||
|
*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSinglePoint() {
|
public void testSinglePoint() {
|
||||||
var feature = pointFeature(newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2))
|
var feature = pointFeature(newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2))
|
||||||
|
@ -82,6 +100,33 @@ public class FeatureRendererTest {
|
||||||
), renderGeometry(feature));
|
), 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
|
@TestFactory
|
||||||
public List<DynamicTest> testProcessPointsNearInternationalDateLineAndPoles() {
|
public List<DynamicTest> testProcessPointsNearInternationalDateLineAndPoles() {
|
||||||
double d = 1d / 512;
|
double d = 1d / 512;
|
||||||
|
@ -221,6 +266,10 @@ public class FeatureRendererTest {
|
||||||
return collector(geom).point("layer");
|
return collector(geom).point("layer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LINE TESTS
|
||||||
|
*/
|
||||||
|
|
||||||
private FeatureCollector.Feature lineFeature(Geometry geom) {
|
private FeatureCollector.Feature lineFeature(Geometry geom) {
|
||||||
return collector(geom).line("layer");
|
return collector(geom).line("layer");
|
||||||
}
|
}
|
||||||
|
@ -234,17 +283,310 @@ public class FeatureRendererTest {
|
||||||
))
|
))
|
||||||
.setZoomRange(14, 14)
|
.setZoomRange(14, 14)
|
||||||
.setBufferPixels(8);
|
.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(
|
assertSameNormalizedFeatures(Map.of(
|
||||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||||
newPoint(64, 64),
|
newPolygon(
|
||||||
newPoint(192, 192)
|
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));
|
), renderGeometry(feature));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: centroid
|
// TODO: centroid
|
||||||
// TODO: line
|
|
||||||
// TODO: multilinestring
|
|
||||||
// TODO: poly
|
// TODO: poly
|
||||||
// TODO: multipolygon
|
// TODO: multipolygon
|
||||||
// TODO: geometry collection
|
// TODO: geometry collection
|
||||||
|
|
Ładowanie…
Reference in New Issue