kopia lustrzana https://github.com/onthegomap/planetiler
convex centroid
rodzic
74610f47d1
commit
68842f5b4b
|
@ -83,12 +83,12 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
|
|||
}
|
||||
}
|
||||
|
||||
public Feature validatedPolygon(String layer) {
|
||||
public Feature centroidIfConvex(String layer) {
|
||||
try {
|
||||
return geometry(layer, source.validatedPolygon());
|
||||
return geometry(layer, source.centroidIfConvex());
|
||||
} catch (GeometryException e) {
|
||||
stats.dataError("feature_validated_polygon_" + e.stat());
|
||||
LOGGER.warn("Error constructing validated polygon for " + source + ": " + e.getMessage());
|
||||
stats.dataError("feature_centroid_if_convex_" + e.stat());
|
||||
LOGGER.warn("Error constructing centroid if convex for " + source + ": " + e.getMessage());
|
||||
return new Feature(layer, EMPTY_GEOM, source.id());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.Lineal;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
|
||||
public abstract class SourceFeature {
|
||||
|
||||
|
@ -56,6 +57,24 @@ public abstract class SourceFeature {
|
|||
worldGeometry().getInteriorPoint());
|
||||
}
|
||||
|
||||
private Geometry centroidIfConvex = null;
|
||||
|
||||
private Geometry computeCentroidIfConvex() throws GeometryException {
|
||||
if (!canBePolygon()) {
|
||||
return centroid();
|
||||
} else if (polygon() instanceof Polygon poly &&
|
||||
poly.getNumInteriorRing() == 0 &&
|
||||
GeoUtils.isConvex(poly.getExteriorRing())) {
|
||||
return centroid();
|
||||
} else {
|
||||
return pointOnSurface();
|
||||
}
|
||||
}
|
||||
|
||||
public Geometry centroidIfConvex() throws GeometryException {
|
||||
return centroidIfConvex != null ? centroidIfConvex : (centroidIfConvex = computeCentroidIfConvex());
|
||||
}
|
||||
|
||||
private Geometry linearGeometry = null;
|
||||
|
||||
protected Geometry computeLine() throws GeometryException {
|
||||
|
|
|
@ -298,6 +298,58 @@ public class GeoUtils {
|
|||
return JTS_FACTORY.createPolygon(exteriorRing, rings.toArray(LinearRing[]::new));
|
||||
}
|
||||
|
||||
public static boolean isConvex(LinearRing r) {
|
||||
CoordinateSequence seq = r.getCoordinateSequence();
|
||||
if (seq.size() <= 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double c0x = seq.getX(0);
|
||||
double c0y = seq.getY(0);
|
||||
double c1x = Double.NaN, c1y = Double.NaN;
|
||||
int i;
|
||||
for (i = 1; i < seq.size(); i++) {
|
||||
c1x = seq.getX(i);
|
||||
c1y = seq.getY(i);
|
||||
if (c1x != c0x || c1y != c0y) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double dx1 = c1x - c0x;
|
||||
double dy1 = c1y - c0y;
|
||||
|
||||
int sign = 0;
|
||||
|
||||
for (; i < seq.size(); i++) {
|
||||
double c2x = seq.getX(i);
|
||||
double c2y = seq.getY(i);
|
||||
|
||||
double dx2 = c2x - c1x;
|
||||
double dy2 = c2y - c1y;
|
||||
double z = dx1 * dy2 - dy1 * dx2;
|
||||
|
||||
// if z == 0 (with small delta to account for rounding errors) then keep skipping
|
||||
// points to ignore identical or colinear points
|
||||
if (Math.abs(z) < 1e-10) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int s = z >= 0d ? 1 : -1;
|
||||
if (sign == 0) {
|
||||
sign = s;
|
||||
} else if (sign != s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
c1x = c2x;
|
||||
c1y = c2y;
|
||||
dx1 = dx2;
|
||||
dy1 = dy2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static record PolyAndArea(Polygon poly, double area) implements Comparable<PolyAndArea> {
|
||||
|
||||
PolyAndArea(Polygon poly) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.onthegomap.flatmap;
|
||||
|
||||
import static com.onthegomap.flatmap.geo.GeoUtils.JTS_FACTORY;
|
||||
import static com.onthegomap.flatmap.geo.GeoUtils.coordinateSequence;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -483,4 +485,8 @@ public class TestUtils {
|
|||
actual.stream().sorted(comparator).toList()
|
||||
);
|
||||
}
|
||||
|
||||
public static LinearRing newLinearRing(double... coords) {
|
||||
return JTS_FACTORY.createLinearRing(coordinateSequence(coords));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.util.AffineTransformation;
|
||||
|
||||
public class GeoUtilsTest {
|
||||
|
||||
|
@ -112,4 +114,190 @@ public class GeoUtilsTest {
|
|||
public void testMetersPerPixel(int zoom, double meters) {
|
||||
assertEquals(meters, metersPerPixelAtEquator(zoom), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsConvexTriangle() {
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsConvexRectangle() {
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBarelyConvexRectangle() {
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.5, 0.5,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.4, 0.4,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.7, 0.7,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcaveRectangleDoublePoints() {
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBarelyConcaveRectangle() {
|
||||
assertConvex(false, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.51, 0.5,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test5PointsConcave() {
|
||||
assertConvex(false, newLinearRing(
|
||||
0, 0,
|
||||
0.5, 0.1,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(false, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
0.9, 0.5,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(false, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.5, 0.9,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(false, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0.1, 0.5,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test5PointsColinear() {
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
0.5, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 0.5,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0.5, 1,
|
||||
0, 1,
|
||||
0, 0
|
||||
));
|
||||
assertConvex(true, newLinearRing(
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1,
|
||||
0, 0.5,
|
||||
0, 0
|
||||
));
|
||||
}
|
||||
|
||||
private static void assertConvex(boolean isConvex, LinearRing ring) {
|
||||
for (double rotation : new double[]{0, 90, 180, 270}) {
|
||||
LinearRing rotated = (LinearRing) AffineTransformation.rotationInstance(Math.toRadians(rotation)).transform(ring);
|
||||
for (boolean flip : new boolean[]{false, true}) {
|
||||
LinearRing flipped = flip ? (LinearRing) AffineTransformation.scaleInstance(-1, 1).transform(rotated) : rotated;
|
||||
for (boolean reverse : new boolean[]{false, true}) {
|
||||
LinearRing reversed = reverse ? flipped.reverse() : flipped;
|
||||
assertEquals(isConvex, isConvex(reversed), "rotation=" + rotation + " flip=" + flip + " reverse=" + reverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@ public class Housenumber implements OpenMapTilesSchema.Housenumber, Tables.OsmHo
|
|||
|
||||
@Override
|
||||
public void process(Tables.OsmHousenumberPoint element, FeatureCollector features) {
|
||||
// TODO if not convex, use pointOnSurface
|
||||
features.centroid(LAYER_NAME)
|
||||
features.centroidIfConvex(LAYER_NAME)
|
||||
.setBufferPixels(BUFFER_SIZE)
|
||||
.setAttr(Fields.HOUSENUMBER, element.housenumber())
|
||||
.setZoomRange(14, 14);
|
||||
|
|
|
@ -87,8 +87,7 @@ public class Poi implements OpenMapTilesSchema.Poi,
|
|||
|
||||
@Override
|
||||
public void process(Tables.OsmPoiPolygon element, FeatureCollector features) {
|
||||
// TODO pointOnSurface if not convex
|
||||
setupPoiFeature(element, features.centroid(LAYER_NAME));
|
||||
setupPoiFeature(element, features.centroidIfConvex(LAYER_NAME));
|
||||
}
|
||||
|
||||
private <T extends Tables.WithSubclass & Tables.WithStation & Tables.WithFunicular & Tables.WithSport & Tables.WithInformation & Tables.WithReligion & Tables.WithMappingKey & Tables.WithName & Tables.WithIndoor & Tables.WithLayer & Tables.WithSource>
|
||||
|
|
Ładowanie…
Reference in New Issue