package com.onthegomap.planetiler.reader; import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.reader.osm.OsmReader; import com.onthegomap.planetiler.reader.osm.OsmRelationInfo; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Lineal; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; /** * Base class for input features read from a data source. *
* Provides cached convenience methods with lazy initialization for geometric attributes derived from * {@link #latLonGeometry()} and {@link #worldGeometry()} to avoid computing them if not needed, and recomputing them if * needed by multiple features. *
* All geometries except for {@link #latLonGeometry()} return elements in world web mercator coordinates where (0,0) is
* the northwest corner and (1,1) is the southeast corner of the planet.
*/
public abstract class SourceFeature implements WithTags, WithGeometryType {
private final Map
* If the geometry is convex, uses the faster {@link Geometry#getCentroid()} but otherwise falls back to the slower
* {@link Geometry#getInteriorPoint()}.
*/
public final Geometry centroidIfConvex() throws GeometryException {
return centroidIfConvex != null ? centroidIfConvex : (centroidIfConvex = computeCentroidIfConvex());
}
/**
* Computes this feature as a {@link LineString} or {@link MultiLineString} in world web mercator coordinates.
*
* @return the linestring in web mercator coordinates
* @throws GeometryException if an unexpected but recoverable error occurs creating this geometry that should
* be logged for debugging
* @throws GeometryException.Verbose if an expected error occurs creating this geometry that will be logged at a lower
* log level
*/
protected Geometry computeLine() throws GeometryException {
Geometry world = worldGeometry();
return world instanceof Lineal ? world : GeoUtils.polygonToLineString(world);
}
/**
* Returns this feature as a {@link LineString} or {@link MultiLineString} in world web mercator coordinates.
*
* @throws GeometryException if an error occurs constructing the geometry, or of this feature should not be
* interpreted as a line
*/
public final Geometry line() throws GeometryException {
if (!canBeLine()) {
throw new GeometryException("feature_not_line", "cannot be line", true);
}
if (linearGeometry == null) {
linearGeometry = computeLine();
}
return linearGeometry;
}
/**
* Computes this feature as a {@link Polygon} or {@link MultiPolygon} in world web mercator coordinates.
*
* @return the polygon in web mercator coordinates
* @throws GeometryException if an unexpected but recoverable error occurs creating this geometry that should
* be logged for debugging
* @throws GeometryException.Verbose if an expected error occurs creating this geometry that will be logged at a lower
* log level
*/
protected Geometry computePolygon() throws GeometryException {
return worldGeometry();
}
/**
* Returns this feature as a {@link Polygon} or {@link MultiPolygon} in world web mercator coordinates.
*
* @throws GeometryException if an error occurs constructing the geometry, or of this feature should not be
* interpreted as a line
*/
public final Geometry polygon() throws GeometryException {
if (!canBePolygon()) {
throw new GeometryException("feature_not_polygon", "cannot be polygon", true);
}
return polygonGeometry != null ? polygonGeometry : (polygonGeometry = computePolygon());
}
private Geometry computeValidPolygon() throws GeometryException {
Geometry polygon = polygon();
if (!polygon.isValid()) {
polygon = GeoUtils.fixPolygon(polygon);
}
return polygon;
}
/**
* Returns this feature as a valid {@link Polygon} or {@link MultiPolygon} in world web mercator coordinates.
*
* Validating and fixing invalid polygons can be expensive, so use only if necessary. Invalid polygons will also be
* fixed at render-time.
*
* @throws GeometryException if an error occurs constructing the geometry, or of this feature should not be
* interpreted as a line
*/
public final Geometry validatedPolygon() throws GeometryException {
if (!canBePolygon()) {
throw new GeometryException("feature_not_polygon", "cannot be polygon", true);
}
return validPolygon != null ? validPolygon : (validPolygon = computeValidPolygon());
}
/**
* Returns and caches the result of {@link Geometry#getArea()} of this feature in world web mercator coordinates where
* {@code 1} means the area of the entire planet.
*/
public double area() throws GeometryException {
return Double.isNaN(area) ? (area = canBePolygon() ? polygon().getArea() : 0) : area;
}
/**
* Returns and caches the result of {@link Geometry#getLength()} of this feature in world web mercator coordinates
* where {@code 1} means the circumference of the entire planet or the distance from 85 degrees north to 85 degrees
* south.
*/
public double length() throws GeometryException {
return Double.isNaN(length) ? (length =
(isPoint() || canBePolygon() || canBeLine()) ? worldGeometry().getLength() : 0) : length;
}
/**
* Returns and caches sqrt of {@link #area()} if polygon or {@link #length()} if a line string.
*/
public double size() throws GeometryException {
return Double.isNaN(size) ? (size = canBePolygon() ? Math.sqrt(Math.abs(area())) : canBeLine() ? length() : 0) :
size;
}
/** Returns the ID of the source that this feature came from. */
public String getSource() {
return source;
}
/** Returns the layer ID within a source that this feature comes from. */
public String getSourceLayer() {
return sourceLayer;
}
/**
* Returns a list of OSM relations that this element belongs to.
*
* @param relationInfoClass class of the processed relation data
* @param