reader geometry coercion

pull/1/head
Mike Barry 2021-05-25 06:05:41 -04:00
rodzic 20506de8cc
commit 2d3dfa9577
12 zmienionych plików z 685 dodań i 41 usunięć

Wyświetl plik

@ -1,15 +1,22 @@
package com.onthegomap.flatmap;
import com.onthegomap.flatmap.collections.CacheByZoom;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.locationtech.jts.geom.Geometry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
private static final Geometry EMPTY_GEOM = GeoUtils.JTS_FACTORY.createGeometryCollection();
private static final Logger LOGGER = LoggerFactory.getLogger(FeatureCollector.class);
private final SourceFeature source;
private final List<Feature> output = new ArrayList<>();
private final CommonParams config;
@ -24,22 +31,58 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
return output.iterator();
}
public Feature geometry(String layer, Geometry geometry) {
Feature feature = new Feature(layer, geometry);
output.add(feature);
return feature;
}
public Feature point(String layer) {
var feature = new Feature(layer, source.isPoint() ? source.worldGeometry() : source.centroid(), false);
output.add(feature);
return feature;
try {
if (!source.isPoint()) {
throw new GeometryException("not a point");
}
return geometry(layer, source.worldGeometry());
} catch (GeometryException e) {
LOGGER.warn("Error getting point geometry for " + source + ": " + e);
return new Feature(layer, EMPTY_GEOM);
}
}
public Feature line(String layername) {
var feature = new Feature(layername, source.line(), false);
output.add(feature);
return feature;
public Feature centroid(String layer) {
try {
return geometry(layer, source.centroid());
} catch (GeometryException e) {
LOGGER.warn("Error getting centroid for " + source + ": " + e);
return new Feature(layer, EMPTY_GEOM);
}
}
public Feature polygon(String layername) {
var feature = new Feature(layername, source.polygon(), true);
output.add(feature);
return feature;
public Feature line(String layer) {
try {
return geometry(layer, source.line());
} catch (GeometryException e) {
LOGGER.warn("Error constructing line for " + source + ": " + e);
return new Feature(layer, EMPTY_GEOM);
}
}
public Feature polygon(String layer) {
try {
return geometry(layer, source.polygon());
} catch (GeometryException e) {
LOGGER.warn("Error constructing polygon for " + source + ": " + e);
return new Feature(layer, EMPTY_GEOM);
}
}
public Feature pointOnSurface(String layer) {
try {
return geometry(layer, source.pointOnSurface());
} catch (GeometryException e) {
LOGGER.warn("Error constructing point on surface for " + source + ": " + e);
return new Feature(layer, EMPTY_GEOM);
}
}
public static record Factory(CommonParams config) {
@ -51,12 +94,12 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
public final class Feature {
private final boolean area;
private static final double DEFAULT_LABEL_GRID_SIZE = 0;
private static final int DEFAULT_LABEL_GRID_LIMIT = 0;
private final String layer;
private final Geometry geom;
private final Map<String, Object> attrs = new TreeMap<>();
private final GeometryType geometryType;
private int zOrder;
private int minzoom = config.minzoom();
private int maxzoom = config.maxzoom();
@ -73,11 +116,11 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
private double pixelToleranceAtMaxZoom = 256d / 4096;
private ZoomFunction<Double> pixelTolerance = null;
private Feature(String layer, Geometry geom, boolean area) {
private Feature(String layer, Geometry geom) {
this.layer = layer;
this.geom = geom;
this.zOrder = 0;
this.area = area;
this.geometryType = GeometryType.valueOf(geom);
}
public int getZorder() {
@ -254,8 +297,12 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
return setAttr(key, ZoomFunction.minZoom(minzoom, value));
}
public GeometryType getGeometryType() {
return geometryType;
}
public boolean area() {
return area;
return geometryType == GeometryType.POLYGON;
}
@Override

Wyświetl plik

@ -0,0 +1,48 @@
package com.onthegomap.flatmap;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Lineal;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.Puntal;
import vector_tile.VectorTile;
public enum GeometryType {
UNKNOWN(VectorTile.Tile.GeomType.UNKNOWN),
POINT(VectorTile.Tile.GeomType.POINT),
LINE(VectorTile.Tile.GeomType.LINESTRING),
POLYGON(VectorTile.Tile.GeomType.POLYGON);
private final VectorTile.Tile.GeomType protobufType;
GeometryType(VectorTile.Tile.GeomType protobufType) {
this.protobufType = protobufType;
}
public static GeometryType valueOf(Geometry geom) {
return geom instanceof Puntal ? POINT
: geom instanceof Lineal ? LINE
: geom instanceof Polygonal ? POLYGON
: UNKNOWN;
}
public static GeometryType valueOf(VectorTile.Tile.GeomType geomType) {
return switch (geomType) {
case POINT -> POINT;
case LINESTRING -> LINE;
case POLYGON -> POLYGON;
default -> UNKNOWN;
};
}
public static GeometryType valueOf(byte val) {
return valueOf(VectorTile.Tile.GeomType.forNumber(val));
}
public byte asByte() {
return (byte) protobufType.getNumber();
}
public VectorTile.Tile.GeomType asProtobufType() {
return protobufType;
}
}

Wyświetl plik

@ -1,19 +1,24 @@
package com.onthegomap.flatmap;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
import java.util.Map;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Lineal;
public abstract class SourceFeature {
private final Map<String, Object> properties;
private String source;
private String sourceLayer;
protected SourceFeature(Map<String, Object> properties) {
this.properties = properties;
}
public abstract Geometry latLonGeometry();
public abstract Geometry latLonGeometry() throws GeometryException;
public abstract Geometry worldGeometry();
public abstract Geometry worldGeometry() throws GeometryException;
public void setTag(String key, Object value) {
properties.put(key, value);
@ -25,32 +30,54 @@ public abstract class SourceFeature {
private Geometry centroid = null;
public Geometry centroid() {
return centroid != null ? centroid : (centroid = worldGeometry().getCentroid());
public Geometry centroid() throws GeometryException {
return centroid != null ? centroid : (centroid =
canBePolygon() ? polygon().getCentroid() :
canBeLine() ? line().getCentroid() :
worldGeometry().getCentroid());
}
private Geometry pointOnSurface = null;
public Geometry pointOnSurface() throws GeometryException {
return pointOnSurface != null ? pointOnSurface : (pointOnSurface =
canBePolygon() ? polygon().getInteriorPoint() :
canBeLine() ? line().getInteriorPoint() :
worldGeometry().getInteriorPoint());
}
private Geometry linearGeometry = null;
public Geometry line() {
return linearGeometry != null ? linearGeometry : (linearGeometry = worldGeometry());
public Geometry line() throws GeometryException {
if (!canBeLine()) {
throw new GeometryException("cannot be line");
}
if (linearGeometry == null) {
Geometry world = worldGeometry();
linearGeometry = world instanceof Lineal ? world : GeoUtils.polygonToLineString(world);
}
return linearGeometry;
}
private Geometry polygonGeometry = null;
public Geometry polygon() {
public Geometry polygon() throws GeometryException {
if (!canBePolygon()) {
throw new GeometryException("cannot be polygon");
}
return polygonGeometry != null ? polygonGeometry : (polygonGeometry = worldGeometry());
}
private double area = Double.NaN;
public double area() {
return Double.isNaN(area) ? (area = polygon().getArea()) : area;
public double area() throws GeometryException {
return Double.isNaN(area) ? (area = canBePolygon() ? polygon().getArea() : 0) : area;
}
private double length = Double.NaN;
public double length() {
return Double.isNaN(length) ? (length = line().getLength()) : length;
public double length() throws GeometryException {
return Double.isNaN(length) ? (length = worldGeometry().getLength()) : length;
}
public Object getTag(String key) {
@ -79,4 +106,24 @@ public abstract class SourceFeature {
}
public abstract boolean isPoint();
public abstract boolean canBePolygon();
public abstract boolean canBeLine();
public String getSource() {
return source;
}
public String getSourceLayer() {
return sourceLayer;
}
public void setSource(String source) {
this.source = source;
}
public void setSourceLayer(String sourceLayer) {
this.sourceLayer = sourceLayer;
}
}

Wyświetl plik

@ -1,5 +1,6 @@
package com.onthegomap.flatmap.geo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.locationtech.jts.geom.Coordinate;
@ -7,10 +8,13 @@ import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
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.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.locationtech.jts.geom.util.GeometryTransformer;
@ -240,4 +244,35 @@ public class GeoUtils {
public static Geometry createMultiPoint(List<Point> points) {
return JTS_FACTORY.createMultiPoint(points.toArray(EMPTY_POINT_ARRAY));
}
public static Geometry polygonToLineString(Geometry world) throws GeometryException {
List<LineString> lineStrings = new ArrayList<>();
getLineStrings(world, lineStrings);
if (lineStrings.size() == 0) {
throw new GeometryException("No line strings");
} else if (lineStrings.size() == 1) {
return lineStrings.get(0);
} else {
return createMultiLineString(lineStrings);
}
}
private static void getLineStrings(Geometry input, List<LineString> output) throws GeometryException {
if (input instanceof LinearRing linearRing) {
output.add(JTS_FACTORY.createLineString(linearRing.getCoordinateSequence()));
} else if (input instanceof LineString lineString) {
output.add(lineString);
} else if (input instanceof Polygon polygon) {
getLineStrings(polygon.getExteriorRing(), output);
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
getLineStrings(polygon.getInteriorRingN(i), output);
}
} else if (input instanceof GeometryCollection gc) {
for (int i = 0; i < gc.getNumGeometries(); i++) {
getLineStrings(gc.getGeometryN(i), output);
}
} else {
throw new GeometryException("unrecognized geometry type: " + input.getGeometryType());
}
}
}

Wyświetl plik

@ -3,7 +3,6 @@ package com.onthegomap.flatmap.read;
import com.onthegomap.flatmap.CommonParams;
import com.onthegomap.flatmap.FileUtils;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.SourceFeature;
import com.onthegomap.flatmap.collections.FeatureGroup;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.monitoring.Stats;
@ -104,7 +103,7 @@ public class NaturalEarthReader extends Reader {
}
@Override
public Topology.SourceStep<SourceFeature> read() {
public Topology.SourceStep<ReaderFeature> read() {
return next -> {
var tables = tableNames();
for (int i = 0; i < tables.size(); i++) {
@ -129,7 +128,8 @@ public class NaturalEarthReader extends Reader {
continue;
}
Geometry latLonGeometry = GeoUtils.wkbReader.read(geometry);
SourceFeature readerGeometry = new ReaderFeature(latLonGeometry, column.length - 1);
ReaderFeature readerGeometry = new ReaderFeature(latLonGeometry, column.length - 1);
readerGeometry.setSourceLayer(table);
for (int c = 0; c < column.length; c++) {
if (c != geometryColumn) {
Object value = rs.getObject(c + 1);

Wyświetl plik

@ -137,7 +137,10 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
feature = new NodeSourceFeature(node);
} else if (readerElement instanceof ReaderWay way) {
waysProcessed.incrementAndGet();
feature = new WaySourceFeature(way, nodeCache);
LongArrayList nodes = way.getNodes();
boolean closed = nodes.size() > 1 && nodes.get(0) == nodes.get(nodes.size() - 1);
String area = way.getTag("area");
feature = new WaySourceFeature(way, closed, area, nodeCache);
} else if (readerElement instanceof ReaderRelation rel) {
// ensure all ways finished processing before we start relations
if (waysDone.getCount() > 0) {
@ -210,8 +213,15 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
private static abstract class ProxyFeature extends SourceFeature {
public ProxyFeature(ReaderElement elem) {
private final boolean polygon;
private final boolean line;
private final boolean point;
public ProxyFeature(ReaderElement elem, boolean point, boolean line, boolean polygon) {
super(ReaderElementUtils.getProperties(elem));
this.point = point;
this.line = line;
this.polygon = polygon;
}
private Geometry latLonGeom;
@ -229,6 +239,21 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
}
protected abstract Geometry computeWorldGeometry();
@Override
public boolean isPoint() {
return point;
}
@Override
public boolean canBeLine() {
return line;
}
@Override
public boolean canBePolygon() {
return polygon;
}
}
private static class NodeSourceFeature extends ProxyFeature {
@ -237,7 +262,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
private final double lat;
NodeSourceFeature(ReaderNode node) {
super(node);
super(node, true, false, false);
this.lon = node.getLon();
this.lat = node.getLat();
}
@ -261,8 +286,8 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
private final NodeGeometryCache nodeCache;
private final LongArrayList nodeIds;
public WaySourceFeature(ReaderWay way, NodeGeometryCache nodeCache) {
super(way);
public WaySourceFeature(ReaderWay way, boolean closed, String area, NodeGeometryCache nodeCache) {
super(way, false, !closed || !"yes".equals(area), closed && !"no".equals(area));
this.nodeIds = way.getNodes();
this.nodeCache = nodeCache;
}
@ -281,7 +306,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
private static class MultipolygonSourceFeature extends ProxyFeature {
public MultipolygonSourceFeature(ReaderRelation relation) {
super(relation);
super(relation, false, false, true);
}
@Override

Wyświetl plik

@ -47,6 +47,7 @@ public abstract class Reader implements Closeable {
);
while ((sourceFeature = prev.get()) != null) {
featuresRead.incrementAndGet();
sourceFeature.setSource(name);
FeatureCollector features = featureCollectors.get(sourceFeature);
if (sourceFeature.latLonGeometry().getEnvelopeInternal().intersects(latLonBounds)) {
profile.processFeature(sourceFeature, features);
@ -74,7 +75,7 @@ public abstract class Reader implements Closeable {
public abstract long getCount();
public abstract Topology.SourceStep<SourceFeature> read();
public abstract Topology.SourceStep<? extends SourceFeature> read();
@Override
public abstract void close();

Wyświetl plik

@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.Puntal;
public class ReaderFeature extends SourceFeature {
@ -44,6 +45,16 @@ public class ReaderFeature extends SourceFeature {
return latLonGeometry instanceof Puntal;
}
@Override
public boolean canBePolygon() {
return latLonGeometry instanceof Polygonal;
}
@Override
public boolean canBeLine() {
return !isPoint();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {

Wyświetl plik

@ -3,7 +3,6 @@ package com.onthegomap.flatmap.read;
import com.onthegomap.flatmap.CommonParams;
import com.onthegomap.flatmap.FileUtils;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.SourceFeature;
import com.onthegomap.flatmap.collections.FeatureGroup;
import com.onthegomap.flatmap.monitoring.Stats;
import com.onthegomap.flatmap.worker.Topology;
@ -103,7 +102,7 @@ public class ShapefileReader extends Reader implements Closeable {
}
@Override
public Topology.SourceStep<SourceFeature> read() {
public Topology.SourceStep<ReaderFeature> read() {
return next -> {
try (var iter = inputSource.features()) {
while (iter.hasNext()) {
@ -114,7 +113,7 @@ public class ShapefileReader extends Reader implements Closeable {
latLonGeometry = JTS.transform(source, transformToLatLon);
}
if (latLonGeometry != null) {
SourceFeature geom = new ReaderFeature(latLonGeometry, attributeNames.length);
ReaderFeature geom = new ReaderFeature(latLonGeometry, attributeNames.length);
for (int i = 1; i < attributeNames.length; i++) {
geom.setTag(attributeNames[i], feature.getAttribute(i));
}

Wyświetl plik

@ -1,17 +1,29 @@
package com.onthegomap.flatmap;
import static com.onthegomap.flatmap.TestUtils.assertSubmap;
import static com.onthegomap.flatmap.TestUtils.newCoordinateList;
import static com.onthegomap.flatmap.TestUtils.newLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiPolygon;
import static com.onthegomap.flatmap.TestUtils.newPoint;
import static com.onthegomap.flatmap.TestUtils.newPolygon;
import static com.onthegomap.flatmap.TestUtils.rectangle;
import static com.onthegomap.flatmap.TestUtils.rectangleCoordList;
import static com.onthegomap.flatmap.TestUtils.round;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.onthegomap.flatmap.geo.GeoUtils;
import com.onthegomap.flatmap.geo.GeometryException;
import com.onthegomap.flatmap.read.ReaderFeature;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.StreamSupport;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class FeatureCollectorTest {
@ -247,5 +259,349 @@ public class FeatureCollectorTest {
assertEquals(2d, poly.getPixelTolerance(14));
}
// TODO test shape coercion
/*
* SHAPE COERCION TESTS
*/
@Test
public void testPointReaderFeatureCoercion() throws GeometryException {
var pointSourceFeature = new ReaderFeature(newPoint(0, 0), Map.of());
assertEquals(0, pointSourceFeature.area());
assertEquals(0, pointSourceFeature.length());
var fc = factory.get(pointSourceFeature);
fc.line("layer").setZoomRange(0, 10);
fc.polygon("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to line/polygon");
fc.point("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
for (int i = 0; i < 3; i++) {
assertTrue(iter.hasNext(), "item " + i);
var item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(newPoint(0.5, 0.5), item.getGeometry());
}
assertFalse(iter.hasNext());
}
@ParameterizedTest
@ValueSource(ints = {2, 3, 4})
public void testLineWithSamePointsReaderFeatureCoercion(int nPoints) throws GeometryException {
double[] coords = new double[nPoints * 2];
Arrays.fill(coords, 0d);
double[] worldCoords = new double[nPoints * 2];
Arrays.fill(worldCoords, 0.5d);
var sourceLine = new ReaderFeature(newLineString(coords), Map.of());
assertEquals(0, sourceLine.length());
assertEquals(0, sourceLine.area());
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
fc.polygon("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point/polygon");
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(newLineString(worldCoords), item.getGeometry());
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(newPoint(0.5, 0.5), item.getGeometry());
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(newPoint(0.5, 0.5), item.getGeometry());
assertFalse(iter.hasNext());
}
private static double[] worldToLatLon(double... coords) {
double[] result = new double[coords.length];
for (int i = 0; i < coords.length; i += 2) {
result[i] = GeoUtils.getWorldLon(coords[i]);
result[i + 1] = GeoUtils.getWorldLat(coords[i + 1]);
}
return result;
}
@Test
public void testNonZeroLineStringReaderFeatureCoercion() throws GeometryException {
var sourceLine = new ReaderFeature(newLineString(worldToLatLon(
0.2, 0.2,
0.75, 0.75,
0.25, 0.75,
0.2, 0.2
)), Map.of());
assertEquals(0, sourceLine.area());
assertEquals(1.83008, sourceLine.length(), 1e-5);
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
fc.polygon("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point/polygon");
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(round(newLineString(
0.2, 0.2,
0.75, 0.75,
0.25, 0.75,
0.2, 0.2
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.40639, 0.55013)), round(item.getGeometry()), "centroid");
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(newPoint(0.25, 0.75), item.getGeometry(), "point on surface");
assertFalse(iter.hasNext());
}
@Test
public void testPolygonReaderFeatureCoercion() throws GeometryException {
var sourceLine = new ReaderFeature(newPolygon(worldToLatLon(
0.25, 0.25,
0.75, 0.75,
0.25, 0.75,
0.25, 0.25
)), Map.of());
assertEquals(0.125, sourceLine.area());
assertEquals(1.7071067811865475, sourceLine.length(), 1e-5);
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point");
fc.polygon("layer").setZoomRange(0, 10);
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.POLYGON, item.getGeometryType());
assertEquals(round(newPolygon(
0.25, 0.25,
0.75, 0.75,
0.25, 0.75,
0.25, 0.25
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(round(newLineString(
0.25, 0.25,
0.75, 0.75,
0.25, 0.75,
0.25, 0.25
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.4166667, 0.5833333)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.375, 0.5)), round(item.getGeometry()));
assertFalse(iter.hasNext());
}
@Test
public void testPolygonWithHoleCoercion() throws GeometryException {
var sourceLine = new ReaderFeature(newPolygon(newCoordinateList(worldToLatLon(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
)), List.of(newCoordinateList(worldToLatLon(
0.25, 0.25,
0.75, 0.25,
0.75, 0.75,
0.25, 0.75,
0.25, 0.25
)))), Map.of());
assertEquals(0.75, sourceLine.area(), 1e-5);
assertEquals(6, sourceLine.length(), 1e-5);
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point");
fc.polygon("layer").setZoomRange(0, 10);
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.POLYGON, item.getGeometryType());
assertEquals(round(newPolygon(
rectangleCoordList(0, 1),
List.of(rectangleCoordList(0.25, 0.75))
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(round(newMultiLineString(
newLineString(0, 0, 1, 0, 1, 1, 0, 1, 0, 0),
newLineString(0.25, 0.25, 0.75, 0.25, 0.75, 0.75, 0.25, 0.75, 0.25, 0.25)
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.5, 0.5)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.125, 0.5)), round(item.getGeometry()));
assertFalse(iter.hasNext());
}
@Test
public void testPointOnSurface() {
var sourceLine = new ReaderFeature(newPolygon(worldToLatLon(
0, 0,
1, 0,
1, 0.25,
0.25, 0.25,
0.25, 0.75,
1, 0.75,
1, 1,
0, 1,
0, 0
)), Map.of());
var fc = factory.get(sourceLine);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.425, 0.5)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.125, 0.5)), round(item.getGeometry()));
assertFalse(iter.hasNext());
}
@Test
public void testMultiPolygonCoercion() throws GeometryException {
var sourceLine = new ReaderFeature(newMultiPolygon(
newPolygon(worldToLatLon(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
)), newPolygon(worldToLatLon(
2, 0,
3, 0,
3, 1,
2, 1,
2, 0
))), Map.of());
assertEquals(2, sourceLine.area(), 1e-5);
assertEquals(8, sourceLine.length(), 1e-5);
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point");
fc.polygon("layer").setZoomRange(0, 10);
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.POLYGON, item.getGeometryType());
assertEquals(round(newMultiPolygon(
rectangle(0, 1),
rectangle(2, 0, 3, 1)
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(round(newMultiLineString(
newLineString(rectangleCoordList(0, 1)),
newLineString(rectangleCoordList(2, 0, 3, 1))
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(1.5, 0.5)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(0.5, 0.5)), round(item.getGeometry()));
assertFalse(iter.hasNext());
}
@Test
public void testMultiLineStringCoercion() throws GeometryException {
var sourceLine = new ReaderFeature(newMultiLineString(
newLineString(worldToLatLon(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
)), newLineString(worldToLatLon(
2, 0,
3, 0,
3, 1,
2, 1,
2, 0
))), Map.of());
assertEquals(0, sourceLine.area(), 1e-5);
assertEquals(8, sourceLine.length(), 1e-5);
var fc = factory.get(sourceLine);
fc.point("layer").setZoomRange(0, 10);
fc.polygon("layer").setZoomRange(0, 10);
assertFalse(fc.iterator().hasNext(), "silently fail coercing to point/polygon");
fc.line("layer").setZoomRange(0, 10);
fc.centroid("layer").setZoomRange(0, 10);
fc.pointOnSurface("layer").setZoomRange(0, 10);
var iter = fc.iterator();
var item = iter.next();
assertEquals(GeometryType.LINE, item.getGeometryType());
assertEquals(round(newMultiLineString(
newLineString(rectangleCoordList(0, 1)),
newLineString(rectangleCoordList(2, 0, 3, 1))
)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(1.5, 0.5)), round(item.getGeometry()));
item = iter.next();
assertEquals(GeometryType.POINT, item.getGeometryType());
assertEquals(round(newPoint(1, 0)), round(item.getGeometry()));
assertFalse(iter.hasNext());
}
}

Wyświetl plik

@ -129,7 +129,11 @@ public class TestUtils {
}
public static LineString newLineString(double... coords) {
return GeoUtils.JTS_FACTORY.createLineString(newCoordinateList(coords).toArray(new Coordinate[0]));
return newLineString(newCoordinateList(coords));
}
public static LineString newLineString(List<Coordinate> coords) {
return GeoUtils.JTS_FACTORY.createLineString(coords.toArray(new Coordinate[0]));
}
public static MultiLineString newMultiLineString(LineString... lineStrings) {

Wyświetl plik

@ -1,6 +1,12 @@
package com.onthegomap.flatmap.geo;
import static com.onthegomap.flatmap.TestUtils.newLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiLineString;
import static com.onthegomap.flatmap.TestUtils.newMultiPolygon;
import static com.onthegomap.flatmap.TestUtils.newPoint;
import static com.onthegomap.flatmap.TestUtils.newPolygon;
import static com.onthegomap.flatmap.TestUtils.rectangle;
import static com.onthegomap.flatmap.TestUtils.rectangleCoordList;
import static com.onthegomap.flatmap.TestUtils.round;
import static com.onthegomap.flatmap.geo.GeoUtils.ProjectWorldCoords;
import static com.onthegomap.flatmap.geo.GeoUtils.decodeWorldX;
@ -10,6 +16,8 @@ import static com.onthegomap.flatmap.geo.GeoUtils.getWorldX;
import static com.onthegomap.flatmap.geo.GeoUtils.getWorldY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
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;
@ -37,4 +45,67 @@ public class GeoUtilsTest {
Geometry actual = ProjectWorldCoords.transform(input);
assertEquals(round(expected), round(actual));
}
@Test
public void testPolygonToLineString() throws GeometryException {
assertEquals(newLineString(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
), GeoUtils.polygonToLineString(rectangle(
0, 1
)));
}
@Test
public void testMultiPolygonToLineString() throws GeometryException {
assertEquals(newLineString(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
), GeoUtils.polygonToLineString(newMultiPolygon(rectangle(
0, 1
))));
}
@Test
public void testLineRingToLineString() throws GeometryException {
assertEquals(newLineString(
0, 0,
1, 0,
1, 1,
0, 1,
0, 0
), GeoUtils.polygonToLineString(rectangle(
0, 1
).getExteriorRing()));
}
@Test
public void testComplexPolygonToLineString() throws GeometryException {
assertEquals(newMultiLineString(
newLineString(
0, 0,
3, 0,
3, 3,
0, 3,
0, 0
), newLineString(
1, 1,
2, 1,
2, 2,
1, 2,
1, 1
)
), GeoUtils.polygonToLineString(newPolygon(
rectangleCoordList(
0, 3
), List.of(rectangleCoordList(
1, 2
)))));
}
}