kopia lustrzana https://github.com/onthegomap/planetiler
add test coverage
rodzic
0b00a82ab4
commit
27d3aa9d75
|
@ -80,12 +80,15 @@ public class OpenMapTilesMain {
|
|||
|
||||
stats.time("lake_centerlines", () ->
|
||||
ShapefileReader
|
||||
.process("EPSG:3857", "lake_centerlines", centerlines, featureMap, config, profile, stats));
|
||||
.process("EPSG:3857", OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, centerlines, featureMap, config, profile,
|
||||
stats));
|
||||
stats.time("water_polygons", () ->
|
||||
ShapefileReader.process("water_polygons", waterPolygons, featureMap, config, profile, stats));
|
||||
ShapefileReader
|
||||
.process(OpenMapTilesProfile.WATER_POLYGON_SOURCE, waterPolygons, featureMap, config, profile, stats));
|
||||
stats.time("natural_earth", () ->
|
||||
NaturalEarthReader
|
||||
.process("natural_earth", naturalEarth, tmpDir.resolve("natearth.sqlite"), featureMap, config,
|
||||
.process(OpenMapTilesProfile.NATURAL_EARTH_SOURCE, naturalEarth, tmpDir.resolve("natearth.sqlite"), featureMap,
|
||||
config,
|
||||
profile, stats)
|
||||
);
|
||||
|
||||
|
|
|
@ -45,12 +45,6 @@ public class GeoUtils {
|
|||
public static final GeometryTransformer UNPROJECT_WORLD_COORDS = new GeometryTransformer() {
|
||||
@Override
|
||||
protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent) {
|
||||
if (coords.getDimension() != 2) {
|
||||
throw new IllegalArgumentException("Dimension must be 2, was: " + coords.getDimension());
|
||||
}
|
||||
if (coords.getMeasures() != 0) {
|
||||
throw new IllegalArgumentException("Measures must be 0, was: " + coords.getMeasures());
|
||||
}
|
||||
CoordinateSequence copy = new PackedCoordinateSequence.Double(coords.size(), 2, 0);
|
||||
for (int i = 0; i < coords.size(); i++) {
|
||||
copy.setOrdinate(i, 0, getWorldLon(coords.getX(i)));
|
||||
|
@ -62,12 +56,6 @@ public class GeoUtils {
|
|||
public static final GeometryTransformer PROJECT_WORLD_COORDS = new GeometryTransformer() {
|
||||
@Override
|
||||
protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent) {
|
||||
if (coords.getDimension() != 2) {
|
||||
throw new IllegalArgumentException("Dimension must be 2, was: " + coords.getDimension());
|
||||
}
|
||||
if (coords.getMeasures() != 0) {
|
||||
throw new IllegalArgumentException("Measures must be 0, was: " + coords.getMeasures());
|
||||
}
|
||||
CoordinateSequence copy = new PackedCoordinateSequence.Double(coords.size(), 2, 0);
|
||||
for (int i = 0; i < coords.size(); i++) {
|
||||
copy.setOrdinate(i, 0, getWorldX(coords.getX(i)));
|
||||
|
|
|
@ -12,6 +12,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
public class OpenMapTilesProfile implements Profile {
|
||||
|
||||
public static final String LAKE_CENTERLINE_SOURCE = "lake_centerlines";
|
||||
public static final String WATER_POLYGON_SOURCE = "water_polygons";
|
||||
public static final String NATURAL_EARTH_SOURCE = "natural_earth";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OpenMapTilesProfile.class);
|
||||
|
||||
@Override
|
||||
|
@ -52,12 +55,30 @@ public class OpenMapTilesProfile implements Profile {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void processFeature(SourceFeature sourceFeature,
|
||||
FeatureCollector features) {
|
||||
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
|
||||
if (sourceFeature.isPoint()) {
|
||||
if (sourceFeature.hasTag("natural", "peak", "volcano")) {
|
||||
features.point("mountain_peak")
|
||||
.setAttr("name", sourceFeature.getTag("name"));
|
||||
.setAttr("name", sourceFeature.getTag("name"))
|
||||
.setLabelGridSizeAndLimit(13, 100, 5);
|
||||
}
|
||||
}
|
||||
|
||||
if (WATER_POLYGON_SOURCE.equals(sourceFeature.getSource())) {
|
||||
features.polygon("water").setZoomRange(6, 14).setAttr("class", "ocean");
|
||||
} else if (NATURAL_EARTH_SOURCE.equals(sourceFeature.getSource())) {
|
||||
String sourceLayer = sourceFeature.getSourceLayer();
|
||||
boolean lake = sourceLayer.endsWith("_lakes");
|
||||
switch (sourceLayer) {
|
||||
case "ne_10m_lakes", "ne_10m_ocean" -> features.polygon("water")
|
||||
.setZoomRange(4, 5)
|
||||
.setAttr("class", lake ? "lake" : "ocean");
|
||||
case "ne_50m_lakes", "ne_50m_ocean" -> features.polygon("water")
|
||||
.setZoomRange(2, 3)
|
||||
.setAttr("class", lake ? "lake" : "ocean");
|
||||
case "ne_110m_lakes", "ne_110m_ocean" -> features.polygon("water")
|
||||
.setZoomRange(0, 1)
|
||||
.setAttr("class", lake ? "lake" : "ocean");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class ShapefileReader extends Reader implements Closeable {
|
|||
public static void process(String sourceProjection, String sourceName, Path input, FeatureGroup writer,
|
||||
CommonParams config,
|
||||
Profile profile, Stats stats) {
|
||||
try (var reader = new ShapefileReader(sourceName, sourceProjection, input, profile, stats)) {
|
||||
try (var reader = new ShapefileReader(sourceProjection, sourceName, input, profile, stats)) {
|
||||
reader.process(writer, config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,16 +57,15 @@ public class FeatureRenderer {
|
|||
}
|
||||
|
||||
private void renderGeometry(Geometry geom, FeatureCollector.Feature feature) {
|
||||
// TODO what about converting between area and line?
|
||||
if (geom.isEmpty()) {
|
||||
LOGGER.warn("Empty geometry " + feature);
|
||||
} else if (geom instanceof Point point) {
|
||||
addPointFeature(feature, point.getCoordinates());
|
||||
renderPoint(feature, point.getCoordinates());
|
||||
} else if (geom instanceof MultiPoint points) {
|
||||
addPointFeature(feature, points);
|
||||
renderPoint(feature, points);
|
||||
} else if (geom instanceof Polygon || geom instanceof MultiPolygon || geom instanceof LineString
|
||||
|| geom instanceof MultiLineString) {
|
||||
addLinearFeature(feature, geom);
|
||||
renderLineOrPolygon(feature, geom);
|
||||
} else if (geom instanceof GeometryCollection collection) {
|
||||
for (int i = 0; i < collection.getNumGeometries(); i++) {
|
||||
renderGeometry(collection.getGeometryN(i), feature);
|
||||
|
@ -77,7 +76,7 @@ public class FeatureRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private void addPointFeature(FeatureCollector.Feature feature, Coordinate... coords) {
|
||||
private void renderPoint(FeatureCollector.Feature feature, Coordinate... coords) {
|
||||
long id = idGen.incrementAndGet();
|
||||
boolean hasLabelGrid = feature.hasLabelGrid();
|
||||
for (int zoom = feature.getMaxZoom(); zoom >= feature.getMinZoom(); zoom--) {
|
||||
|
@ -122,17 +121,17 @@ public class FeatureRenderer {
|
|||
));
|
||||
}
|
||||
|
||||
private void addPointFeature(FeatureCollector.Feature feature, MultiPoint points) {
|
||||
private void renderPoint(FeatureCollector.Feature feature, MultiPoint points) {
|
||||
if (feature.hasLabelGrid()) {
|
||||
for (Coordinate coord : points.getCoordinates()) {
|
||||
addPointFeature(feature, coord);
|
||||
renderPoint(feature, coord);
|
||||
}
|
||||
} else {
|
||||
addPointFeature(feature, points.getCoordinates());
|
||||
renderPoint(feature, points.getCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
private void addLinearFeature(FeatureCollector.Feature feature, Geometry input) {
|
||||
private void renderLineOrPolygon(FeatureCollector.Feature feature, Geometry input) {
|
||||
long id = idGen.incrementAndGet();
|
||||
boolean area = input instanceof Polygonal;
|
||||
double worldLength = (area || input.getNumGeometries() > 1) ? 0 : input.getLength();
|
||||
|
|
|
@ -156,7 +156,6 @@ class TiledGeometry {
|
|||
}
|
||||
|
||||
private static CoordinateSequence fill(double buffer) {
|
||||
buffer += 1d / 4096;
|
||||
double min = -256d * buffer;
|
||||
double max = 256d - min;
|
||||
return new PackedCoordinateSequence.Double(new double[]{
|
||||
|
|
|
@ -446,7 +446,7 @@ public class FlatMapTest {
|
|||
)),
|
||||
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14, List.of(
|
||||
feature(newPolygon(
|
||||
tileFill(4 + 256d / 4096),
|
||||
tileFill(4),
|
||||
List.of(newCoordinateList(
|
||||
64, 64,
|
||||
192, 64,
|
||||
|
|
|
@ -157,14 +157,14 @@ public class TestUtils {
|
|||
return GeoUtils.JTS_FACTORY.createGeometryCollection(geoms);
|
||||
}
|
||||
|
||||
public static Geometry round(Geometry input) {
|
||||
public static Geometry round(Geometry input, double delta) {
|
||||
return new GeometryTransformer() {
|
||||
@Override
|
||||
protected CoordinateSequence transformCoordinates(
|
||||
CoordinateSequence coords, Geometry parent) {
|
||||
for (int i = 0; i < coords.size(); i++) {
|
||||
for (int j = 0; j < coords.getDimension(); j++) {
|
||||
coords.setOrdinate(i, j, Math.round(coords.getOrdinate(i, j) * 1e5) / 1e5);
|
||||
coords.setOrdinate(i, j, Math.round(coords.getOrdinate(i, j) * delta) / delta);
|
||||
}
|
||||
}
|
||||
return coords;
|
||||
|
@ -172,6 +172,10 @@ public class TestUtils {
|
|||
}.transform(input.copy());
|
||||
}
|
||||
|
||||
public static Geometry round(Geometry input) {
|
||||
return round(input, 1e5);
|
||||
}
|
||||
|
||||
private static byte[] gunzip(byte[] zipped) throws IOException {
|
||||
try (var is = new GZIPInputStream(new ByteArrayInputStream(zipped))) {
|
||||
return is.readAllBytes();
|
||||
|
@ -400,16 +404,6 @@ public class TestUtils {
|
|||
);
|
||||
}
|
||||
|
||||
public static void assertTopologicallyEquivalentFeatures(
|
||||
Map<TileCoord, Collection<Geometry>> expected,
|
||||
Map<TileCoord, Collection<Geometry>> actual
|
||||
) {
|
||||
assertEquals(
|
||||
mapTileFeatures(expected, TopoGeometry::new),
|
||||
mapTileFeatures(actual, TopoGeometry::new)
|
||||
);
|
||||
}
|
||||
|
||||
public static void assertSameNormalizedFeatures(
|
||||
Map<TileCoord, Collection<Geometry>> expected,
|
||||
Map<TileCoord, Collection<Geometry>> actual
|
||||
|
@ -424,6 +418,20 @@ public class TestUtils {
|
|||
assertEquals(new NormGeometry(expected), new NormGeometry(actual));
|
||||
}
|
||||
|
||||
public static void assertTopologicallyEquivalentFeatures(
|
||||
Map<TileCoord, Collection<Geometry>> expected,
|
||||
Map<TileCoord, Collection<Geometry>> actual
|
||||
) {
|
||||
assertEquals(
|
||||
mapTileFeatures(expected, TopoGeometry::new),
|
||||
mapTileFeatures(actual, TopoGeometry::new)
|
||||
);
|
||||
}
|
||||
|
||||
public static void assertTopologicallyEquivalentFeature(Geometry expected, Geometry actual) {
|
||||
assertEquals(new TopoGeometry(expected), new TopoGeometry(actual));
|
||||
}
|
||||
|
||||
public static void assertNormalizedSubmap(
|
||||
Map<TileCoord, Collection<Geometry>> expectedSubmap,
|
||||
Map<TileCoord, Collection<Geometry>> actual
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.onthegomap.flatmap.geo.TileCoord;
|
|||
import com.onthegomap.flatmap.read.ReaderFeature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -27,7 +28,13 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateXY;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.PrecisionModel;
|
||||
import org.locationtech.jts.geom.util.AffineTransformation;
|
||||
import org.locationtech.jts.precision.GeometryPrecisionReducer;
|
||||
|
||||
public class FeatureRendererTest {
|
||||
|
||||
|
@ -470,6 +477,61 @@ public class FeatureRendererTest {
|
|||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLineWrap() {
|
||||
var feature = lineFeature(newLineString(
|
||||
-1d / 256, -1d / 256,
|
||||
257d / 256, 257d / 256
|
||||
))
|
||||
.setMinPixelSize(1)
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(0, 1);
|
||||
assertSameNormalizedFeatures(Map.of(
|
||||
TileCoord.ofXYZ(0, 0, 0), List.of(newMultiLineString(
|
||||
newLineString(
|
||||
-1, -1,
|
||||
257, 257
|
||||
),
|
||||
newLineString(
|
||||
-4, 252,
|
||||
1, 257
|
||||
),
|
||||
newLineString(
|
||||
255, -1,
|
||||
260, 4
|
||||
)
|
||||
)),
|
||||
TileCoord.ofXYZ(0, 0, 1), List.of(newLineString(
|
||||
-2, -2,
|
||||
260, 260
|
||||
)),
|
||||
TileCoord.ofXYZ(1, 0, 1), List.of(newMultiLineString(
|
||||
newLineString(
|
||||
-4, 252,
|
||||
4, 260
|
||||
),
|
||||
newLineString(
|
||||
254, -2,
|
||||
260, 4
|
||||
)
|
||||
)),
|
||||
TileCoord.ofXYZ(0, 1, 1), List.of(newMultiLineString(
|
||||
newLineString(
|
||||
252, -4,
|
||||
260, 4
|
||||
),
|
||||
newLineString(
|
||||
-4, 252,
|
||||
2, 258
|
||||
)
|
||||
)),
|
||||
TileCoord.ofXYZ(1, 1, 1), List.of(newLineString(
|
||||
-4, -4,
|
||||
258, 258
|
||||
))
|
||||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
/*
|
||||
* POLYGON TESTS
|
||||
*/
|
||||
|
@ -798,7 +860,7 @@ public class FeatureRendererTest {
|
|||
),
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
// the filled tile with a hole!
|
||||
newPolygon(tileFill(1 + 256d / 4096), List.of(rectangleCoordList(10, 250)))
|
||||
newPolygon(tileFill(1), List.of(rectangleCoordList(10, 250)))
|
||||
),
|
||||
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2, 14), List.of(
|
||||
tileLeft(1)
|
||||
|
@ -1098,7 +1160,7 @@ public class FeatureRendererTest {
|
|||
var innerTile = rendered.get(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14));
|
||||
assertEquals(1, innerTile.size());
|
||||
assertEquals(new TestUtils.NormGeometry(newPolygon(
|
||||
rectangleCoordList(-1 - TILE_RESOLUTION_PX, 256 + 1 + TILE_RESOLUTION_PX),
|
||||
rectangleCoordList(-1, 256 + 1),
|
||||
List.of(rectangleCoordList(10, 246))
|
||||
)),
|
||||
new TestUtils.NormGeometry(innerTile.iterator().next()));
|
||||
|
@ -1130,4 +1192,196 @@ public class FeatureRendererTest {
|
|||
var rendered = renderGeometry(feature);
|
||||
assertFalse(rendered.containsKey(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlappingMultipolygon() {
|
||||
var feature = polygonFeature(newMultiPolygon(
|
||||
rectangle(10d / 256, 10d / 256, 30d / 256, 30d / 256),
|
||||
rectangle(20d / 256, 20d / 256, 40d / 256, 40d / 256)
|
||||
))
|
||||
.setMinPixelSize(1)
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(0, 0);
|
||||
assertSameNormalizedFeatures(Map.of(
|
||||
TileCoord.ofXYZ(0, 0, 0), List.of(newPolygon(
|
||||
10, 10,
|
||||
30, 10,
|
||||
30, 20,
|
||||
40, 20,
|
||||
40, 40,
|
||||
20, 40,
|
||||
20, 30,
|
||||
10, 30,
|
||||
10, 10
|
||||
))
|
||||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverlappingMultipolygonSideBySide() {
|
||||
var feature = polygonFeature(newMultiPolygon(
|
||||
rectangle(10d / 256, 10d / 256, 20d / 256, 20d / 256),
|
||||
rectangle(15d / 256, 10d / 256, 25d / 256, 20d / 256)
|
||||
))
|
||||
.setMinPixelSize(1)
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(0, 0);
|
||||
assertTopologicallyEquivalentFeatures(Map.of(
|
||||
TileCoord.ofXYZ(0, 0, 0), List.of(rectangle(
|
||||
10, 10,
|
||||
25, 20
|
||||
))
|
||||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPolygonWrap() {
|
||||
var feature = polygonFeature(rectangle(
|
||||
-1d / 256, -1d / 256, 257d / 256, 1d / 256
|
||||
))
|
||||
.setMinPixelSize(1)
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(0, 1);
|
||||
assertTopologicallyEquivalentFeatures(Map.of(
|
||||
TileCoord.ofXYZ(0, 0, 0), List.of(
|
||||
rectangle(-4, -1, 260, 1)
|
||||
),
|
||||
TileCoord.ofXYZ(0, 0, 1), List.of(
|
||||
rectangle(-4, -2, 260, 2)
|
||||
),
|
||||
TileCoord.ofXYZ(1, 0, 1), List.of(
|
||||
rectangle(-4, -2, 260, 2)
|
||||
)
|
||||
), renderGeometry(feature));
|
||||
}
|
||||
|
||||
private static Geometry rotateWorld(Geometry geom, double degrees) {
|
||||
return AffineTransformation.rotationInstance(-Math.PI * degrees / 180, 0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2)
|
||||
.transform(geom);
|
||||
}
|
||||
|
||||
private static Geometry rotateTile(Geometry geom, double degrees) {
|
||||
return AffineTransformation.rotationInstance(-Math.PI * degrees / 180, 128, 128)
|
||||
.transform(geom);
|
||||
}
|
||||
|
||||
private void testClipWithRotation(double rotation, Geometry inputTile) {
|
||||
Geometry input = new AffineTransformation()
|
||||
.scale(1d / 256 / Z14_TILES, 1d / 256 / Z14_TILES)
|
||||
.translate(0.5, 0.5)
|
||||
.transform(inputTile);
|
||||
Geometry expectedOutput = inputTile.intersection(rectangle(-4, 260));
|
||||
|
||||
var feature = polygonFeature(rotateWorld(input, rotation))
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(14, 14);
|
||||
var geom = renderGeometry(feature)
|
||||
.get(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14))
|
||||
.iterator().next();
|
||||
|
||||
assertTopologicallyEquivalentFeature(
|
||||
round(rotateTile(expectedOutput, rotation)),
|
||||
round(geom)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 90, 180, -90})
|
||||
public void testBackAndForthsOutsideTile(int rotation) {
|
||||
testClipWithRotation(rotation, newPolygon(
|
||||
300, -10,
|
||||
310, 300,
|
||||
320, -10,
|
||||
330, 300,
|
||||
340, 400,
|
||||
128, 400,
|
||||
128, 128,
|
||||
128, -10,
|
||||
300, -10
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 90, 180, -90})
|
||||
public void testReplayEdgesOuterPoly(int rotation) {
|
||||
testClipWithRotation(rotation, newPolygon(
|
||||
130, -10,
|
||||
270, -10,
|
||||
270, 270,
|
||||
-10, 270,
|
||||
-10, -10,
|
||||
120, -10,
|
||||
120, 10,
|
||||
130, 10,
|
||||
130, -10
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 90, 180, -90})
|
||||
public void testReplayEdgesInnerPoly(int rotation) {
|
||||
var innerShape = newCoordinateList(
|
||||
130, -10,
|
||||
270, -10,
|
||||
270, 270,
|
||||
-10, 270,
|
||||
-10, -10,
|
||||
120, -10,
|
||||
120, 10,
|
||||
130, 10,
|
||||
130, -10
|
||||
);
|
||||
Collections.reverse(innerShape);
|
||||
testClipWithRotation(rotation, newPolygon(
|
||||
rectangleCoordList(-20, 300),
|
||||
List.of(innerShape)
|
||||
));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"0, 0",
|
||||
"0.5, 0",
|
||||
"0.5, 0.5",
|
||||
"0, 0.5",
|
||||
"-0.5, 0.5",
|
||||
"-0.5, 0",
|
||||
"-0.5, -0.5",
|
||||
"0, -0.5",
|
||||
"0.5, -0.5"
|
||||
})
|
||||
public void testSpiral(double dx, double dy) {
|
||||
// generate spirals at different offsets and make sure that tile clipping
|
||||
// returns the same result as JTS intersection with the tile's boundary
|
||||
List<Coordinate> coords = new ArrayList<>();
|
||||
int outerRadius = 300;
|
||||
int iters = 25;
|
||||
for (int i = 0; i < iters; i++) {
|
||||
int radius = outerRadius - i * 10;
|
||||
coords.add(new CoordinateXY(-radius, 0));
|
||||
coords.add(new CoordinateXY(0, -radius));
|
||||
coords.add(new CoordinateXY(radius, 0));
|
||||
coords.add(new CoordinateXY(0, radius));
|
||||
}
|
||||
Geometry poly = newLineString(coords).buffer(1, 1);
|
||||
poly = AffineTransformation.translationInstance(128 + dx * 256, 128 + dy * 256).transform(poly);
|
||||
|
||||
Geometry input = new AffineTransformation()
|
||||
.scale(1d / 256d / Z14_TILES, 1d / 256d / Z14_TILES)
|
||||
.translate(0.5, 0.5)
|
||||
.transform(poly);
|
||||
Geometry expectedOutput = poly.intersection(rectangle(-4, 260));
|
||||
|
||||
var feature = polygonFeature(input)
|
||||
.setBufferPixels(4)
|
||||
.setZoomRange(14, 14);
|
||||
var actual = renderGeometry(feature)
|
||||
.get(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14))
|
||||
.iterator().next();
|
||||
|
||||
assertTopologicallyEquivalentFeature(
|
||||
GeometryPrecisionReducer.reduce(expectedOutput, new PrecisionModel(4096d / 256d)),
|
||||
actual
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue