kopia lustrzana https://github.com/onthegomap/planetiler
reader geometry coercion
rodzic
20506de8cc
commit
2d3dfa9577
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)))));
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue