pull/1/head
Mike Barry 2021-05-25 20:53:04 -04:00
rodzic 9c9bae7d2e
commit 0b00a82ab4
7 zmienionych plików z 176 dodań i 42 usunięć

Wyświetl plik

@ -3,6 +3,9 @@ package com.onthegomap.flatmap.geo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.locationtech.jts.algorithm.Area;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
@ -13,6 +16,7 @@ import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
@ -122,24 +126,6 @@ public class GeoUtils {
return 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
}
public static final GeometryTransformer ProjectWorldCoords = 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)));
copy.setOrdinate(i, 1, getWorldY(coords.getY(i)));
}
return copy;
}
};
private static final double QUANTIZED_WORLD_SIZE = Math.pow(2, 31);
private static final long LOWER_32_BIT_MASK = (1L << 32) - 1L;
@ -177,14 +163,6 @@ public class GeoUtils {
return (((long) a) << 32L) | (((long) b) & LOWER_32_BIT_MASK);
}
public static int first(int pair) {
return pair >> 16;
}
public static int second(int pair) {
return (pair << 16) >> 16;
}
public static Point point(double x, double y) {
return JTS_FACTORY.createPoint(new CoordinateXY(x, y));
}
@ -275,4 +253,30 @@ public class GeoUtils {
throw new GeometryException("unrecognized geometry type: " + input.getGeometryType());
}
}
private static record PolyAndArea(Polygon poly, double area) implements Comparable<PolyAndArea> {
PolyAndArea(Polygon poly) {
this(poly, Area.ofRing(poly.getExteriorRing().getCoordinateSequence()));
}
@Override
public int compareTo(@NotNull PolyAndArea o) {
return -Double.compare(area, o.area);
}
}
public static Geometry ensureDescendingPolygonsSizes(Geometry geometry) {
if (geometry instanceof MultiPolygon multiPolygon) {
PolyAndArea[] areas = new PolyAndArea[multiPolygon.getNumGeometries()];
for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
areas[i] = new PolyAndArea((Polygon) multiPolygon.getGeometryN(i));
}
return JTS_FACTORY.createMultiPolygon(
Stream.of(areas).sorted().map(d -> d.poly).toArray(Polygon[]::new)
);
} else {
return geometry;
}
}
}

Wyświetl plik

@ -37,7 +37,8 @@ public class ReaderFeature extends SourceFeature {
@Override
public Geometry worldGeometry() {
return worldGeometry != null ? worldGeometry : (worldGeometry = GeoUtils.latLonToWorldCoords(latLonGeometry));
return worldGeometry != null ? worldGeometry
: (worldGeometry = GeoUtils.ensureDescendingPolygonsSizes(GeoUtils.latLonToWorldCoords(latLonGeometry)));
}
public Map<String, Object> properties() {

Wyświetl plik

@ -3,6 +3,7 @@ package com.onthegomap.flatmap;
import static com.onthegomap.flatmap.TestUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.graphhopper.reader.ReaderRelation;
import com.onthegomap.flatmap.collections.FeatureGroup;
@ -30,6 +31,7 @@ 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.Geometry;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.io.InputStreamInStream;
import org.locationtech.jts.io.ParseException;
@ -356,16 +358,7 @@ 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) {
public List<Coordinate> z14CoordinateList(double... coords) {
List<Coordinate> points = newCoordinateList(coords);
points.forEach(c -> {
c.x = GeoUtils.getWorldLon(0.5 + c.x * Z14_WIDTH);
@ -486,7 +479,7 @@ public class FlatMapTest {
}
@Test
public void testOceanPolygon() throws IOException, SQLException {
public void testFullWorldPolygon() throws IOException, SQLException {
List<Coordinate> outerPoints = worldCoordinateList(
Z14_WIDTH / 2, Z14_WIDTH / 2,
1 - Z14_WIDTH / 2, Z14_WIDTH / 2,
@ -545,6 +538,44 @@ public class FlatMapTest {
assertEquals(expected, results.tiles.size());
}
@Test
public void testReorderNestedMultipolygons() throws IOException, SQLException {
List<Coordinate> outerPoints1 = worldRectangle(10d / 256, 240d / 256);
List<Coordinate> innerPoints1 = worldRectangle(20d / 256, 230d / 256);
List<Coordinate> outerPoints2 = worldRectangle(30d / 256, 220d / 256);
List<Coordinate> innerPoints2 = worldRectangle(40d / 256, 210d / 256);
var results = runWithReaderFeatures(
Map.of("threads", "1"),
List.of(
new ReaderFeature(newMultiPolygon(
newPolygon(outerPoints2, List.of(innerPoints2)),
newPolygon(outerPoints1, List.of(innerPoints1))
), Map.of())
),
(in, features) -> {
features.polygon("layer")
.setZoomRange(0, 0)
.setBufferPixels(0);
}
);
var tileContents = results.tiles.get(TileCoord.ofXYZ(0, 0, 0));
assertEquals(1, tileContents.size());
Geometry geom = tileContents.get(0).geometry().geom();
assertTrue(geom instanceof MultiPolygon, geom.toString());
MultiPolygon multiPolygon = (MultiPolygon) geom;
assertSameNormalizedFeature(newPolygon(
rectangleCoordList(10, 240),
List.of(rectangleCoordList(20, 230))
), multiPolygon.getGeometryN(0));
assertSameNormalizedFeature(newPolygon(
rectangleCoordList(30, 220),
List.of(rectangleCoordList(40, 210))
), multiPolygon.getGeometryN(1));
assertEquals(2, multiPolygon.getNumGeometries());
}
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

@ -41,6 +41,7 @@ import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.Puntal;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.geom.util.GeometryTransformer;
@ -168,7 +169,7 @@ public class TestUtils {
}
return coords;
}
}.transform(input);
}.transform(input.copy());
}
private static byte[] gunzip(byte[] zipped) throws IOException {
@ -247,8 +248,7 @@ public class TestUtils {
return GeoUtils.JTS_FACTORY.createGeometryCollection();
}
public static void validateGeometry(Geometry g) {
assertTrue(g.isSimple(), "JTS isValid()");
private static void validateGeometryRecursive(Geometry g) {
if (g instanceof GeometryCollection gs) {
for (int i = 0; i < gs.getNumGeometries(); i++) {
validateGeometry(gs.getGeometryN(i));
@ -275,6 +275,13 @@ public class TestUtils {
}
}
public static void validateGeometry(Geometry g) {
if (g instanceof Polygonal) {
assertTrue(g.isSimple(), "JTS isValid()");
}
validateGeometryRecursive(g);
}
public interface GeometryComparision {
Geometry geom();
@ -426,4 +433,24 @@ public class TestUtils {
mapTileFeatures(actual, NormGeometry::new)
);
}
public static 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;
}
public static List<Coordinate> worldRectangle(double min, double max) {
return worldCoordinateList(
min, min,
max, min,
max, max,
min, max,
min, min
);
}
}

Wyświetl plik

@ -30,8 +30,11 @@ public class GeoUtilsTest {
Point input = newPoint(lon, lat);
Point expected = newPoint(worldX, worldY);
Geometry actual = ProjectWorldCoords.transform(input);
Geometry actual = latLonToWorldCoords(input);
assertEquals(round(expected), round(actual));
Geometry roundTripped = worldToLatLonCoords(actual);
assertEquals(round(input), round(roundTripped));
}
@Test
@ -96,4 +99,14 @@ public class GeoUtilsTest {
1, 2
)))));
}
@ParameterizedTest
@CsvSource({
"0, 156543",
"8, 611",
"14, 9",
})
public void testMetersPerPixel(int zoom, double meters) {
assertEquals(meters, metersPerPixelAtEquator(zoom), 1);
}
}

Wyświetl plik

@ -0,0 +1,36 @@
package com.onthegomap.flatmap.read;
import static com.onthegomap.flatmap.TestUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.MultiPolygon;
public class ReaderFeatureTest {
@Test
public void testFixesMultipolygonOrdering() {
List<Coordinate> outerPoints1 = worldRectangle(0.1, 0.9);
List<Coordinate> innerPoints1 = worldRectangle(0.2, 0.8);
List<Coordinate> outerPoints2 = worldRectangle(0.3, 0.7);
List<Coordinate> innerPoints2 = worldRectangle(0.4, 0.6);
MultiPolygon multiPolygon = (MultiPolygon) new ReaderFeature(newMultiPolygon(
newPolygon(outerPoints2, List.of(innerPoints2)),
newPolygon(outerPoints1, List.of(innerPoints1))
), Map.of()).worldGeometry();
assertEquals(2, multiPolygon.getNumGeometries());
assertSameNormalizedFeature(round(newPolygon(
rectangleCoordList(0.1, 0.9),
List.of(rectangleCoordList(0.2, 0.8))
)), round(multiPolygon.getGeometryN(0)));
assertSameNormalizedFeature(round(newPolygon(
rectangleCoordList(0.3, 0.7),
List.of(rectangleCoordList(0.4, 0.6))
)), round(multiPolygon.getGeometryN(1)));
}
}

Wyświetl plik

@ -448,6 +448,28 @@ public class FeatureRendererTest {
assertExactSameFeatures(Map.of(), renderGeometry(feature));
}
@Test
public void testSelfIntersectingLineStringOK() {
var feature = lineFeature(newLineString(z14WorldCoords(
10, 10,
20, 20,
10, 20,
20, 10,
10, 10
)))
.setMinPixelSize(1)
.setZoomRange(14, 14);
assertExactSameFeatures(Map.of(
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(newLineString(
10, 10,
20, 20,
10, 20,
20, 10,
10, 10
))
), renderGeometry(feature));
}
/*
* POLYGON TESTS
*/