kopia lustrzana https://github.com/onthegomap/planetiler
shoreline integration test
rodzic
5bec63fb66
commit
0908cf2bdd
|
@ -315,6 +315,9 @@ class TiledGeometry {
|
|||
|
||||
private IntRange sliceY(CoordinateSequence stripeSegment, int x, boolean outer,
|
||||
Map<TileCoord, List<CoordinateSequence>> inProgressShapes) {
|
||||
if (stripeSegment.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
double leftEdge = -buffer;
|
||||
double rightEdge = 1 + buffer;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ 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.assertNotNull;
|
||||
|
||||
import com.graphhopper.reader.ReaderRelation;
|
||||
import com.onthegomap.flatmap.collections.FeatureGroup;
|
||||
|
@ -37,13 +38,21 @@ import com.onthegomap.flatmap.worker.Topology;
|
|||
import com.onthegomap.flatmap.write.Mbtiles;
|
||||
import com.onthegomap.flatmap.write.MbtilesWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.io.InputStreamInStream;
|
||||
import org.locationtech.jts.io.ParseException;
|
||||
import org.locationtech.jts.io.WKBReader;
|
||||
|
||||
/**
|
||||
* In-memory tests with fake data and profiles to ensure all features work end-to-end.
|
||||
|
@ -60,6 +69,7 @@ public class FlatMapTest {
|
|||
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 static final int Z4_TILES = 1 << 4;
|
||||
private final Stats stats = new Stats.InMemory();
|
||||
|
||||
private void processReaderFeatures(FeatureGroup featureGroup, Profile profile, CommonParams config,
|
||||
|
@ -367,17 +377,26 @@ public class FlatMapTest {
|
|||
), results.tiles);
|
||||
}
|
||||
|
||||
private List<Coordinate> worldCoordinateList(double... coords) {
|
||||
List<Coordinate> points = newCoordinateList(coords);
|
||||
points.forEach(c -> {
|
||||
c.x = GeoUtils.getWorldLon(c.x);
|
||||
c.y = GeoUtils.getWorldLat(c.y);
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
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;
|
||||
c.x = GeoUtils.getWorldLon(0.5 + c.x * Z14_WIDTH);
|
||||
c.y = GeoUtils.getWorldLat(0.5 + c.y * Z14_WIDTH);
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPolygon() throws IOException, SQLException {
|
||||
public void testPolygonWithHoleSpanningMultipleTiles() throws IOException, SQLException {
|
||||
List<Coordinate> outerPoints = z14CoordinateList(
|
||||
0.5, 0.5,
|
||||
3.5, 0.5,
|
||||
|
@ -402,13 +421,40 @@ public class FlatMapTest {
|
|||
), Map.of())
|
||||
),
|
||||
(in, features) -> {
|
||||
features.line("layer")
|
||||
features.polygon("layer")
|
||||
.setZoomRange(12, 14)
|
||||
.setBufferPixels(4);
|
||||
}
|
||||
);
|
||||
|
||||
assertSubmap(Map.ofEntries(
|
||||
assertEquals(Map.ofEntries(
|
||||
// Z12
|
||||
newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of(
|
||||
feature(newPolygon(
|
||||
rectangleCoordList(32, 32, 256 - 32, 128 + 32),
|
||||
List.of(
|
||||
rectangleCoordList(64 + 16, 128 - 16) // hole
|
||||
)
|
||||
), Map.of())
|
||||
)),
|
||||
|
||||
// Z13
|
||||
newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of(
|
||||
feature(newPolygon(
|
||||
rectangleCoordList(64, 256 + 4),
|
||||
List.of(rectangleCoordList(128 + 32, 256 - 32)) // 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())
|
||||
)),
|
||||
|
||||
// Z14 - row 1
|
||||
newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of(
|
||||
feature(tileBottomRight(4), Map.of())
|
||||
|
@ -428,7 +474,7 @@ public class FlatMapTest {
|
|||
)),
|
||||
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14, List.of(
|
||||
feature(newPolygon(
|
||||
tileFill(5),
|
||||
tileFill(4 + 256d / 4096),
|
||||
List.of(newCoordinateList(
|
||||
64, 64,
|
||||
192, 64,
|
||||
|
@ -456,35 +502,70 @@ public class FlatMapTest {
|
|||
)),
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOceanPolygon() throws IOException, SQLException {
|
||||
List<Coordinate> outerPoints = worldCoordinateList(
|
||||
Z14_WIDTH / 2, Z14_WIDTH / 2,
|
||||
1 - Z14_WIDTH / 2, Z14_WIDTH / 2,
|
||||
1 - Z14_WIDTH / 2, 1 - Z14_WIDTH / 2,
|
||||
Z14_WIDTH / 2, 1 - Z14_WIDTH / 2,
|
||||
Z14_WIDTH / 2, Z14_WIDTH / 2
|
||||
);
|
||||
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
new ReaderFeature(newPolygon(
|
||||
outerPoints,
|
||||
List.of()
|
||||
), Map.of())
|
||||
),
|
||||
(in, features) -> {
|
||||
features.polygon("layer")
|
||||
.setZoomRange(0, 6)
|
||||
.setBufferPixels(4);
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(5461, results.tiles.size());
|
||||
// spot-check one filled tile
|
||||
assertEquals(List.of(rectangle(-5, 256 + 5).norm()), results.tiles.get(TileCoord.ofXYZ(
|
||||
Z4_TILES / 2, Z4_TILES / 2, 4
|
||||
)).stream().map(d -> d.geometry().geom().norm()).toList());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"chesapeake.wkb, 4077",
|
||||
"mdshore.wkb, 19904",
|
||||
"njshore.wkb, 10571"
|
||||
})
|
||||
public void testComplexShorelinePolygons__TAKES_A_MINUTE_OR_TWO(String fileName, int expected)
|
||||
throws IOException, SQLException, ParseException {
|
||||
MultiPolygon geometry = (MultiPolygon) new WKBReader()
|
||||
.read(new InputStreamInStream(Files.newInputStream(Path.of("src", "test", "resources", fileName))));
|
||||
assertNotNull(geometry);
|
||||
|
||||
// automatically checks for self-intersections
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
new ReaderFeature(geometry, Map.of())
|
||||
),
|
||||
(in, features) -> {
|
||||
features.polygon("layer")
|
||||
.setZoomRange(0, 14)
|
||||
.setBufferPixels(4);
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(expected, results.tiles.size());
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -235,26 +235,27 @@ public class TestUtils {
|
|||
}
|
||||
|
||||
public static void validateGeometry(Geometry g) {
|
||||
assertTrue(g.isValid(), "JTS isValid()");
|
||||
assertTrue(g.isSimple(), "JTS isValid()");
|
||||
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);
|
||||
assertFalse(point.isEmpty(), () -> "empty: " + point);
|
||||
} else if (g instanceof LineString line) {
|
||||
assertTrue(line.getNumPoints() >= 2, "too few points: " + 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);
|
||||
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++) {
|
||||
int _i = 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);
|
||||
() -> "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);
|
||||
|
@ -279,7 +280,7 @@ public class TestUtils {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Norm{" + geom + '}';
|
||||
return "Norm{" + geom.norm() + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -196,7 +196,7 @@ public class VectorTileEncoderTest {
|
|||
MultiPolygon mp = newMultiPolygon(
|
||||
(Polygon) newPoint(13, 16).buffer(3),
|
||||
(Polygon) newPoint(24, 25).buffer(5)
|
||||
);
|
||||
).reverse(); // ensure outer CCW, inner CW winding
|
||||
assertTrue(mp.isValid());
|
||||
|
||||
Map<String, Object> attrs = Map.of("key1", "value1");
|
||||
|
|
|
@ -1093,16 +1093,4 @@ public class FeatureRendererTest {
|
|||
var rendered = renderGeometry(feature);
|
||||
assertFalse(rendered.containsKey(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14)));
|
||||
}
|
||||
|
||||
// TODO: centroid
|
||||
// TODO: poly
|
||||
// TODO: multipolygon
|
||||
// TODO: geometry collection
|
||||
|
||||
// sad tests:
|
||||
// TODO: invalid line
|
||||
// TODO: invalid poly
|
||||
// TODO: coerce poly -> line
|
||||
// TODO: coerce line -> poly
|
||||
// TODO: wrong types: point/line/poly -> point/line/poly
|
||||
}
|
||||
|
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Ładowanie…
Reference in New Issue