shoreline integration test

pull/1/head
Mike Barry 2021-05-23 07:34:47 -04:00
rodzic 5bec63fb66
commit 0908cf2bdd
8 zmienionych plików z 127 dodań i 54 usunięć

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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

Wyświetl plik

@ -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");

Wyświetl plik

@ -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.