kopia lustrzana https://github.com/onthegomap/planetiler
better invalid geometry handling
rodzic
d990784606
commit
2800c339f8
|
@ -23,6 +23,8 @@ import com.carrotsearch.hppc.IntArrayList;
|
|||
import com.google.common.primitives.Ints;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -98,137 +100,145 @@ public class VectorTileEncoder {
|
|||
return ((n >> 1) ^ (-(n & 1)));
|
||||
}
|
||||
|
||||
private static Geometry decodeCommands(byte geomTypeByte, int[] commands) {
|
||||
VectorTile.Tile.GeomType geomType = Objects.requireNonNull(VectorTile.Tile.GeomType.forNumber(geomTypeByte));
|
||||
GeometryFactory gf = GeoUtils.JTS_FACTORY;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
private static Geometry decodeCommands(byte geomTypeByte, int[] commands) throws GeometryException {
|
||||
try {
|
||||
VectorTile.Tile.GeomType geomType = Objects.requireNonNull(VectorTile.Tile.GeomType.forNumber(geomTypeByte));
|
||||
GeometryFactory gf = GeoUtils.JTS_FACTORY;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
List<DoubleArrayList> coordsList = new ArrayList<>();
|
||||
DoubleArrayList coords = null;
|
||||
List<DoubleArrayList> coordsList = new ArrayList<>();
|
||||
DoubleArrayList coords = null;
|
||||
|
||||
int geometryCount = commands.length;
|
||||
int length = 0;
|
||||
int command = 0;
|
||||
int i = 0;
|
||||
while (i < geometryCount) {
|
||||
int geometryCount = commands.length;
|
||||
int length = 0;
|
||||
int command = 0;
|
||||
int i = 0;
|
||||
while (i < geometryCount) {
|
||||
|
||||
if (length <= 0) {
|
||||
length = commands[i++];
|
||||
command = length & ((1 << 3) - 1);
|
||||
length = length >> 3;
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
|
||||
if (command == Command.MOVE_TO.value) {
|
||||
coords = new DoubleArrayList();
|
||||
coordsList.add(coords);
|
||||
} else {
|
||||
Objects.requireNonNull(coords);
|
||||
if (length <= 0) {
|
||||
length = commands[i++];
|
||||
command = length & ((1 << 3) - 1);
|
||||
length = length >> 3;
|
||||
}
|
||||
|
||||
if (command == Command.CLOSE_PATH.value) {
|
||||
if (geomType != VectorTile.Tile.GeomType.POINT && !coords.isEmpty()) {
|
||||
coords.add(coords.get(0), coords.get(1));
|
||||
if (length > 0) {
|
||||
|
||||
if (command == Command.MOVE_TO.value) {
|
||||
coords = new DoubleArrayList();
|
||||
coordsList.add(coords);
|
||||
} else {
|
||||
Objects.requireNonNull(coords);
|
||||
}
|
||||
|
||||
if (command == Command.CLOSE_PATH.value) {
|
||||
if (geomType != VectorTile.Tile.GeomType.POINT && !coords.isEmpty()) {
|
||||
coords.add(coords.get(0), coords.get(1));
|
||||
}
|
||||
length--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int dx = commands[i++];
|
||||
int dy = commands[i++];
|
||||
|
||||
length--;
|
||||
continue;
|
||||
|
||||
dx = zigZagDecode(dx);
|
||||
dy = zigZagDecode(dy);
|
||||
|
||||
x = x + dx;
|
||||
y = y + dy;
|
||||
|
||||
coords.add(x / SCALE, y / SCALE);
|
||||
}
|
||||
|
||||
int dx = commands[i++];
|
||||
int dy = commands[i++];
|
||||
|
||||
length--;
|
||||
|
||||
dx = zigZagDecode(dx);
|
||||
dy = zigZagDecode(dy);
|
||||
|
||||
x = x + dx;
|
||||
y = y + dy;
|
||||
|
||||
coords.add(x / SCALE, y / SCALE);
|
||||
}
|
||||
|
||||
Geometry geometry = null;
|
||||
boolean outerCCW = false;
|
||||
|
||||
switch (geomType) {
|
||||
case LINESTRING:
|
||||
List<LineString> lineStrings = new ArrayList<>(coordsList.size());
|
||||
for (DoubleArrayList cs : coordsList) {
|
||||
if (cs.size() <= 2) {
|
||||
continue;
|
||||
}
|
||||
lineStrings.add(gf.createLineString(toCs(cs)));
|
||||
}
|
||||
if (lineStrings.size() == 1) {
|
||||
geometry = lineStrings.get(0);
|
||||
} else if (lineStrings.size() > 1) {
|
||||
geometry = gf.createMultiLineString(lineStrings.toArray(new LineString[0]));
|
||||
}
|
||||
break;
|
||||
case POINT:
|
||||
CoordinateSequence cs = new PackedCoordinateSequence.Double(coordsList.size(), 2, 0);
|
||||
for (int j = 0; j < coordsList.size(); j++) {
|
||||
cs.setOrdinate(j, 0, coordsList.get(j).get(0));
|
||||
cs.setOrdinate(j, 1, coordsList.get(j).get(1));
|
||||
}
|
||||
if (cs.size() == 1) {
|
||||
geometry = gf.createPoint(cs);
|
||||
} else if (cs.size() > 1) {
|
||||
geometry = gf.createMultiPoint(cs);
|
||||
}
|
||||
break;
|
||||
case POLYGON:
|
||||
List<List<LinearRing>> polygonRings = new ArrayList<>();
|
||||
List<LinearRing> ringsForCurrentPolygon = new ArrayList<>();
|
||||
boolean first = true;
|
||||
for (DoubleArrayList clist : coordsList) {
|
||||
// skip hole with too few coordinates
|
||||
if (ringsForCurrentPolygon.size() > 0 && clist.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
LinearRing ring = gf.createLinearRing(toCs(clist));
|
||||
boolean ccw = Orientation.isCCW(ring.getCoordinates());
|
||||
if (first) {
|
||||
first = false;
|
||||
outerCCW = ccw;
|
||||
assert outerCCW;
|
||||
}
|
||||
if (ccw == outerCCW) {
|
||||
ringsForCurrentPolygon = new ArrayList<>();
|
||||
polygonRings.add(ringsForCurrentPolygon);
|
||||
}
|
||||
ringsForCurrentPolygon.add(ring);
|
||||
}
|
||||
List<Polygon> polygons = new ArrayList<>();
|
||||
for (List<LinearRing> rings : polygonRings) {
|
||||
LinearRing shell = rings.get(0);
|
||||
LinearRing[] holes = rings.subList(1, rings.size()).toArray(new LinearRing[rings.size() - 1]);
|
||||
polygons.add(gf.createPolygon(shell, holes));
|
||||
}
|
||||
if (polygons.size() == 1) {
|
||||
geometry = polygons.get(0);
|
||||
}
|
||||
if (polygons.size() > 1) {
|
||||
geometry = gf.createMultiPolygon(GeometryFactory.toPolygonArray(polygons));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (geometry == null) {
|
||||
geometry = gf.createGeometryCollection(new Geometry[0]);
|
||||
}
|
||||
|
||||
return geometry;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new GeometryException("Unable to decode geometry", e);
|
||||
}
|
||||
|
||||
Geometry geometry = null;
|
||||
boolean outerCCW = false;
|
||||
|
||||
switch (geomType) {
|
||||
case LINESTRING:
|
||||
List<LineString> lineStrings = new ArrayList<>(coordsList.size());
|
||||
for (DoubleArrayList cs : coordsList) {
|
||||
if (cs.size() <= 2) {
|
||||
continue;
|
||||
}
|
||||
lineStrings.add(gf.createLineString(toCs(cs)));
|
||||
}
|
||||
if (lineStrings.size() == 1) {
|
||||
geometry = lineStrings.get(0);
|
||||
} else if (lineStrings.size() > 1) {
|
||||
geometry = gf.createMultiLineString(lineStrings.toArray(new LineString[0]));
|
||||
}
|
||||
break;
|
||||
case POINT:
|
||||
CoordinateSequence cs = new PackedCoordinateSequence.Double(coordsList.size(), 2, 0);
|
||||
for (int j = 0; j < coordsList.size(); j++) {
|
||||
cs.setOrdinate(j, 0, coordsList.get(j).get(0));
|
||||
cs.setOrdinate(j, 1, coordsList.get(j).get(1));
|
||||
}
|
||||
if (cs.size() == 1) {
|
||||
geometry = gf.createPoint(cs);
|
||||
} else if (cs.size() > 1) {
|
||||
geometry = gf.createMultiPoint(cs);
|
||||
}
|
||||
break;
|
||||
case POLYGON:
|
||||
List<List<LinearRing>> polygonRings = new ArrayList<>();
|
||||
List<LinearRing> ringsForCurrentPolygon = new ArrayList<>();
|
||||
boolean first = true;
|
||||
for (DoubleArrayList clist : coordsList) {
|
||||
// skip hole with too few coordinates
|
||||
if (ringsForCurrentPolygon.size() > 0 && clist.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
LinearRing ring = gf.createLinearRing(toCs(clist));
|
||||
boolean ccw = Orientation.isCCW(ring.getCoordinates());
|
||||
if (first) {
|
||||
first = false;
|
||||
outerCCW = ccw;
|
||||
assert outerCCW;
|
||||
}
|
||||
if (ccw == outerCCW) {
|
||||
ringsForCurrentPolygon = new ArrayList<>();
|
||||
polygonRings.add(ringsForCurrentPolygon);
|
||||
}
|
||||
ringsForCurrentPolygon.add(ring);
|
||||
}
|
||||
List<Polygon> polygons = new ArrayList<>();
|
||||
for (List<LinearRing> rings : polygonRings) {
|
||||
LinearRing shell = rings.get(0);
|
||||
LinearRing[] holes = rings.subList(1, rings.size()).toArray(new LinearRing[rings.size() - 1]);
|
||||
polygons.add(gf.createPolygon(shell, holes));
|
||||
}
|
||||
if (polygons.size() == 1) {
|
||||
geometry = polygons.get(0);
|
||||
}
|
||||
if (polygons.size() > 1) {
|
||||
geometry = gf.createMultiPolygon(GeometryFactory.toPolygonArray(polygons));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (geometry == null) {
|
||||
geometry = gf.createGeometryCollection(new Geometry[0]);
|
||||
}
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
public static List<Feature> decode(byte[] encoded) {
|
||||
return decode(TileCoord.ofXYZ(0, 0, 0), encoded);
|
||||
}
|
||||
|
||||
public static List<Feature> decode(TileCoord tileID, byte[] encoded) {
|
||||
try {
|
||||
VectorTile.Tile tile = VectorTile.Tile.parseFrom(encoded);
|
||||
List<Feature> features = new ArrayList<>();
|
||||
|
@ -267,13 +277,17 @@ public class VectorTileEncoder {
|
|||
Object value = values.get(feature.getTags(tagIdx++));
|
||||
attrs.put(key, value);
|
||||
}
|
||||
Geometry geometry = decodeCommands(feature.getType(), feature.getGeometryList());
|
||||
features.add(new Feature(
|
||||
layerName,
|
||||
feature.getId(),
|
||||
encodeGeometry(geometry),
|
||||
attrs
|
||||
));
|
||||
try {
|
||||
Geometry geometry = decodeCommands(feature.getType(), feature.getGeometryList());
|
||||
features.add(new Feature(
|
||||
layerName,
|
||||
feature.getId(),
|
||||
encodeGeometry(geometry),
|
||||
attrs
|
||||
));
|
||||
} catch (GeometryException e) {
|
||||
LOGGER.warn("Error decoding " + tileID + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return features;
|
||||
|
@ -282,7 +296,7 @@ public class VectorTileEncoder {
|
|||
}
|
||||
}
|
||||
|
||||
private static Geometry decodeCommands(GeomType type, List<Integer> geometryList) {
|
||||
private static Geometry decodeCommands(GeomType type, List<Integer> geometryList) throws GeometryException {
|
||||
return decodeCommands((byte) type.getNumber(), geometryList.stream().mapToInt(i -> i).toArray());
|
||||
}
|
||||
|
||||
|
@ -387,7 +401,7 @@ public class VectorTileEncoder {
|
|||
|
||||
public static record VectorGeometry(int[] commands, byte geomType) {
|
||||
|
||||
public Geometry decode() {
|
||||
public Geometry decode() throws GeometryException {
|
||||
return decodeCommands(geomType, commands);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -73,14 +74,16 @@ class CoordinateSequenceExtractor {
|
|||
List<LineString> lineStrings = new ArrayList<>();
|
||||
for (List<CoordinateSequence> inner : geoms) {
|
||||
for (CoordinateSequence coordinateSequence : inner) {
|
||||
lineStrings.add(GeoUtils.JTS_FACTORY.createLineString(coordinateSequence));
|
||||
if (coordinateSequence.size() > 1) {
|
||||
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) {
|
||||
static Geometry reassemblePolygons(List<List<CoordinateSequence>> groups) throws GeometryException {
|
||||
int numGeoms = groups.size();
|
||||
if (numGeoms == 1) {
|
||||
return reassemblePolygon(groups.get(0));
|
||||
|
@ -93,15 +96,19 @@ class CoordinateSequenceExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
private static Polygon reassemblePolygon(List<CoordinateSequence> group) throws GeometryException {
|
||||
try {
|
||||
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);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new GeometryException("Could not build polygon", e);
|
||||
}
|
||||
return GeoUtils.JTS_FACTORY.createPolygon(first, rest);
|
||||
}
|
||||
|
||||
static Geometry reassemblePoints(List<List<CoordinateSequence>> result) {
|
||||
|
|
|
@ -172,15 +172,13 @@ public class FeatureRenderer {
|
|||
if (feature.area()) {
|
||||
geom = CoordinateSequenceExtractor.reassemblePolygons(geoms);
|
||||
geom = GeoUtils.snapAndFixPolygon(geom, tilePrecision);
|
||||
// JTS utilities "fix" the geometry to be clockwise outer/CCW inner
|
||||
geom = geom.reverse();
|
||||
} else {
|
||||
geom = CoordinateSequenceExtractor.reassembleLineStrings(geoms);
|
||||
}
|
||||
|
||||
if (!geom.isEmpty()) {
|
||||
// JTS utilities "fix" the geometry to be clockwise outer/CCW inner
|
||||
if (feature.area()) {
|
||||
geom = geom.reverse();
|
||||
}
|
||||
emitFeature(feature, id, attrs, tile, geom, null);
|
||||
}
|
||||
} catch (GeometryException e) {
|
||||
|
|
|
@ -231,20 +231,20 @@ class TiledGeometry {
|
|||
IntObjectMap<MutableCoordinateSequence> xSlices = new GHIntObjectHashMap<>();
|
||||
int end = segment.size() - 1;
|
||||
for (int i = 0; i < end; i++) {
|
||||
double _ax = segment.getX(i);
|
||||
double ax = segment.getX(i);
|
||||
double ay = segment.getY(i);
|
||||
double _bx = segment.getX(i + 1);
|
||||
double bx = segment.getX(i + 1);
|
||||
double by = segment.getY(i + 1);
|
||||
|
||||
double minX = Math.min(_ax, _bx);
|
||||
double maxX = Math.max(_ax, _bx);
|
||||
double minX = Math.min(ax, bx);
|
||||
double maxX = Math.max(ax, bx);
|
||||
|
||||
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;
|
||||
double bx = _bx - x;
|
||||
double axTile = ax - x;
|
||||
double bxTile = bx - x;
|
||||
MutableCoordinateSequence slice = xSlices.get(x);
|
||||
if (slice == null) {
|
||||
xSlices.put(x, slice = new MutableCoordinateSequence());
|
||||
|
@ -257,27 +257,27 @@ class TiledGeometry {
|
|||
|
||||
boolean exited = false;
|
||||
|
||||
if (ax < k1) {
|
||||
if (axTile < k1) {
|
||||
// ---|--> | (line enters the clip region from the left)
|
||||
if (bx > k1) {
|
||||
intersectX(slice, ax, ay, bx, by, k1);
|
||||
if (bxTile > k1) {
|
||||
intersectX(slice, axTile, ay, bxTile, by, k1);
|
||||
}
|
||||
} else if (ax > k2) {
|
||||
} else if (axTile > k2) {
|
||||
// | <--|--- (line enters the clip region from the right)
|
||||
if (bx < k2) {
|
||||
intersectX(slice, ax, ay, bx, by, k2);
|
||||
if (bxTile < k2) {
|
||||
intersectX(slice, axTile, ay, bxTile, by, k2);
|
||||
}
|
||||
} else {
|
||||
slice.addPoint(ax, ay);
|
||||
slice.addPoint(axTile, ay);
|
||||
}
|
||||
if (bx < k1 && ax >= k1) {
|
||||
if (bxTile < k1 && axTile >= k1) {
|
||||
// <--|--- | or <--|-----|--- (line exits the clip region on the left)
|
||||
intersectX(slice, ax, ay, bx, by, k1);
|
||||
intersectX(slice, axTile, ay, bxTile, by, k1);
|
||||
exited = true;
|
||||
}
|
||||
if (bx > k2 && ax <= k2) {
|
||||
if (bxTile > k2 && axTile <= k2) {
|
||||
// | ---|--> or ---|-----|--> (line exits the clip region on the right)
|
||||
intersectX(slice, ax, ay, bx, by, k2);
|
||||
intersectX(slice, axTile, ay, bxTile, by, k2);
|
||||
exited = true;
|
||||
}
|
||||
|
||||
|
@ -287,16 +287,16 @@ class TiledGeometry {
|
|||
}
|
||||
}
|
||||
// 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);
|
||||
int startX = (int) Math.floor(_ax - neighborBuffer);
|
||||
int endX = (int) Math.floor(_ax + neighborBuffer);
|
||||
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;
|
||||
double axTile = ax - x;
|
||||
MutableCoordinateSequence slice = xSlices.get(x);
|
||||
if (slice != null && ax >= k1 && ax <= k2) {
|
||||
slice.addPoint(ax, ay);
|
||||
if (slice != null && axTile >= k1 && axTile <= k2) {
|
||||
slice.addPoint(axTile, ay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.write.Mbtiles;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -176,13 +177,21 @@ public class TestUtils {
|
|||
Map<TileCoord, List<ComparableFeature>> tiles = new TreeMap<>();
|
||||
for (var tile : getAllTiles(db)) {
|
||||
var bytes = gunzip(tile.bytes());
|
||||
var decoded = VectorTileEncoder.decode(bytes).stream()
|
||||
.map(feature -> feature(feature.geometry().decode(), feature.attrs())).toList();
|
||||
var decoded = VectorTileEncoder.decode(tile.tile(), bytes).stream()
|
||||
.map(feature -> feature(decodeSilently(feature.geometry()), feature.attrs())).toList();
|
||||
tiles.put(tile.tile(), decoded);
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
public static Geometry decodeSilently(VectorTileEncoder.VectorGeometry geom) {
|
||||
try {
|
||||
return geom.decode();
|
||||
} catch (GeometryException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Mbtiles.TileEntry> getAllTiles(Mbtiles db) throws SQLException {
|
||||
Set<Mbtiles.TileEntry> result = new HashSet<>();
|
||||
try (Statement statement = db.connection().createStatement()) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package com.onthegomap.flatmap;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.TRANSFORM_TO_TILE;
|
||||
import static com.onthegomap.flatmap.TestUtils.decodeSilently;
|
||||
import static com.onthegomap.flatmap.TestUtils.newGeometryCollection;
|
||||
import static com.onthegomap.flatmap.TestUtils.newMultiPoint;
|
||||
import static com.onthegomap.flatmap.TestUtils.newMultiPolygon;
|
||||
|
@ -30,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -118,7 +120,7 @@ public class VectorTileEncoderTest {
|
|||
byte[] encoded = vtm.encode();
|
||||
assertNotSame(0, encoded.length);
|
||||
|
||||
var decoded = VectorTileEncoder.decode(encoded);
|
||||
var decoded = VectorTileEncoder.decode(TileCoord.ofXYZ(0, 0, 0), encoded);
|
||||
assertEquals(List
|
||||
.of(new VectorTileEncoder.Feature("DEPCNT", 1, VectorTileEncoder.encodeGeometry(newPoint(3, 6)), Map.of(
|
||||
"key1", "value1",
|
||||
|
@ -209,7 +211,7 @@ public class VectorTileEncoderTest {
|
|||
|
||||
var features = VectorTileEncoder.decode(encoded);
|
||||
assertEquals(1, features.size());
|
||||
MultiPolygon mp2 = (MultiPolygon) features.get(0).geometry().decode();
|
||||
MultiPolygon mp2 = (MultiPolygon) decodeSilently(features.get(0).geometry());
|
||||
assertEquals(mp.getNumGeometries(), mp2.getNumGeometries());
|
||||
}
|
||||
|
||||
|
@ -339,7 +341,7 @@ public class VectorTileEncoderTest {
|
|||
|
||||
private void testRoundTrip(Geometry input, String layer, Map<String, Object> attrs, long id) {
|
||||
VectorTileEncoder.VectorGeometry encodedGeom = VectorTileEncoder.encodeGeometry(input);
|
||||
Geometry output = encodedGeom.decode();
|
||||
Geometry output = decodeSilently(encodedGeom);
|
||||
assertTrue(input.equalsExact(output), "\n" + input + "\n!=\n" + output);
|
||||
|
||||
byte[] encoded = new VectorTileEncoder().addLayerFeatures(layer, List.of(
|
||||
|
@ -354,6 +356,6 @@ public class VectorTileEncoderTest {
|
|||
}
|
||||
|
||||
private void assertSameGeometries(List<Geometry> expected, List<VectorTileEncoder.Feature> actual) {
|
||||
assertEquals(expected, actual.stream().map(d -> d.geometry().decode()).toList());
|
||||
assertEquals(expected, actual.stream().map(d -> decodeSilently(d.geometry())).toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.onthegomap.flatmap.collections;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.decodeSilently;
|
||||
import static com.onthegomap.flatmap.TestUtils.newPoint;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -69,10 +70,10 @@ public class FeatureGroupTest {
|
|||
private Map<Integer, Map<String, List<Feature>>> getFeatures() {
|
||||
Map<Integer, Map<String, List<Feature>>> map = new TreeMap<>();
|
||||
for (FeatureGroup.TileFeatures tile : features) {
|
||||
for (var feature : VectorTileEncoder.decode(tile.getTile().encode())) {
|
||||
for (var feature : VectorTileEncoder.decode(tile.coord(), tile.getTile().encode())) {
|
||||
map.computeIfAbsent(tile.coord().encoded(), (i) -> new TreeMap<>())
|
||||
.computeIfAbsent(feature.layer(), l -> new ArrayList<>())
|
||||
.add(new Feature(feature.attrs(), feature.geometry().decode()));
|
||||
.add(new Feature(feature.attrs(), decodeSilently(feature.geometry())));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
|
|
|
@ -2,6 +2,7 @@ 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.decodeSilently;
|
||||
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
|
||||
import static com.onthegomap.flatmap.TestUtils.newLineString;
|
||||
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
|
||||
|
@ -60,7 +61,7 @@ public class FeatureRendererTest {
|
|||
private Map<TileCoord, Collection<Geometry>> renderGeometry(FeatureCollector.Feature feature) {
|
||||
Map<TileCoord, Collection<Geometry>> result = new TreeMap<>();
|
||||
new FeatureRenderer(config, rendered -> result.computeIfAbsent(rendered.tile(), tile -> new HashSet<>())
|
||||
.add(rendered.vectorTileFeature().geometry().decode())).renderFeature(feature);
|
||||
.add(decodeSilently(rendered.vectorTileFeature().geometry()))).renderFeature(feature);
|
||||
result.values().forEach(gs -> gs.forEach(TestUtils::validateGeometry));
|
||||
return result;
|
||||
}
|
||||
|
@ -70,7 +71,7 @@ public class FeatureRendererTest {
|
|||
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())));
|
||||
.forEach(gs -> gs.forEach(f -> TestUtils.validateGeometry(decodeSilently(f.vectorTileFeature().geometry()))));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -456,6 +457,21 @@ public class FeatureRendererTest {
|
|||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLineStringCollapsesToPointWithRounding() {
|
||||
var eps = Z14_WIDTH / 4096;
|
||||
var pixel = Z14_WIDTH / 256;
|
||||
var feature = lineFeature(newLineString(
|
||||
0.5 + pixel * 10, 0.5 + pixel * 10,
|
||||
0.5 + pixel * 10 + eps / 3, 0.5 + pixel * 10
|
||||
))
|
||||
.setMinPixelSize(1)
|
||||
.setZoomRange(14, 14)
|
||||
.setBufferPixels(0)
|
||||
.setPixelToleranceAtAllZooms(0);
|
||||
assertExactSameFeatures(Map.of(), renderGeometry(feature));
|
||||
}
|
||||
|
||||
/*
|
||||
* POLYGON TESTS
|
||||
*/
|
||||
|
|
Ładowanie…
Reference in New Issue