iterator() {
return output.iterator();
}
/**
* Starts building a new map feature with an explicit JTS {@code geometry} that overrides the source geometry.
*
* @param layer the output vector tile layer this feature will be written to
* @param geometry the explicit geometry to use instead of what is present in source data
* @return a feature that can be configured further.
*/
public Feature geometry(String layer, Geometry geometry) {
Feature feature = new Feature(layer, geometry, source);
output.add(feature);
return feature;
}
/**
* Starts building a new point map feature that expects the source feature to be a point.
*
* If the source feature is not a point, logs an error and returns a feature that can be configured, but won't
* actually emit anything to the map.
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature point(String layer) {
try {
if (!source.isPoint()) {
throw new GeometryException("feature_not_point", "not a point", true);
}
return geometry(layer, source.worldGeometry());
} catch (GeometryException e) {
e.log(stats, "feature_point", "Error getting point geometry for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new line map feature that expects the source feature to be a line.
*
* If the source feature cannot be a line, logs an error and returns a feature that can be configured, but won't
* actually emit anything to the map.
*
* Some OSM closed OSM ways can be both a polygon and a line
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature line(String layer) {
try {
return geometry(layer, source.line());
} catch (GeometryException e) {
e.log(stats, "feature_line", "Error constructing line for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new polygon map feature that expects the source feature to be a polygon.
*
* If the source feature cannot be a polygon, logs an error and returns a feature that can be configured, but won't
* actually emit anything to the map.
*
* Some OSM closed OSM ways can be both a polygon and a line
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature polygon(String layer) {
try {
return geometry(layer, source.polygon());
} catch (GeometryException e) {
e.log(stats, "feature_polygon", "Error constructing polygon for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new point map feature with geometry from {@link Geometry#getCentroid()} of the source feature.
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature centroid(String layer) {
try {
return geometry(layer, source.centroid());
} catch (GeometryException e) {
e.log(stats, "feature_centroid", "Error getting centroid for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new point map feature with geometry from {@link Geometry#getCentroid()} if the source feature is
* a point, line, or simple convex polygon, or {@link Geometry#getInteriorPoint()} if it is a multipolygon, polygon
* with holes, or concave simple polygon.
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature centroidIfConvex(String layer) {
try {
return geometry(layer, source.centroidIfConvex());
} catch (GeometryException e) {
e.log(stats, "feature_centroid_if_convex", "Error constructing centroid if convex for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new point map feature with geometry from {@link Geometry#getInteriorPoint()} of the source
* feature.
*
* @param layer the output vector tile layer this feature will be written to
* @return a feature that can be configured further.
*/
public Feature pointOnSurface(String layer) {
try {
return geometry(layer, source.pointOnSurface());
} catch (GeometryException e) {
e.log(stats, "feature_point_on_surface", "Error constructing point on surface for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/**
* Starts building a new point map feature at the furthest interior point of a polygon from its edge using
* {@link MaximumInscribedCircle} (aka "pole of inaccessibility") of the source feature.
*
* NOTE: This is substantially more expensive to compute than {@link #centroid(String)} or
* {@link #pointOnSurface(String)}, especially for small {@code tolerance} values.
*
* @param layer the output vector tile layer this feature will be written to
* @param tolerance precision for calculating maximum inscribed circle. 0.01 means 1% of the square root of the area.
* Smaller values for a more precise tolerance become very expensive to compute. Values between 5%
* and 10% are a good compromise of performance vs. precision.
* @return a feature that can be configured further.
*/
public Feature innermostPoint(String layer, double tolerance) {
try {
return geometry(layer, source.innermostPoint(tolerance));
} catch (GeometryException e) {
e.log(stats, "feature_innermost_point", "Error constructing innermost point for " + source.id());
return new Feature(layer, EMPTY_GEOM, source);
}
}
/** Alias for {@link #innermostPoint(String, double)} with a default tolerance of 10%. */
public Feature innermostPoint(String layer) {
return innermostPoint(layer, 0.1);
}
/** Returns the minimum zoom level at which this feature is at least {@code pixelSize} pixels large. */
public int getMinZoomForPixelSize(double pixelSize) {
try {
return GeoUtils.minZoomForPixelSize(source.size(), pixelSize);
} catch (GeometryException e) {
e.log(stats, "min_zoom_for_size_failure", "Error getting min zoom for size from geometry " + source.id());
return config.maxzoom();
}
}
/** Returns the actual pixel size of the source feature at {@code zoom} (length if line, sqrt(area) if polygon). */
public double getPixelSizeAtZoom(int zoom) {
try {
return source.size() * (256 << zoom);
} catch (GeometryException e) {
e.log(stats, "source_feature_pixel_size_at_zoom_failure",
"Error getting source feature pixel size at zoom from geometry " + source.id());
return 0;
}
}
/**
* Creates new feature collector instances for each source feature that we encounter.
*/
public record Factory(PlanetilerConfig config, Stats stats) {
public FeatureCollector get(SourceFeature source) {
return new FeatureCollector(source, config, stats);
}
}
/**
* A builder for an output map feature that contains all the information that will be needed to render vector tile
* features from the input element.
*
* Some feature attributes are set globally (like sort key), and some allow the value to change by zoom-level (like
* tags).
*/
public final class Feature {
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 attrs = new TreeMap<>();
private final GeometryType geometryType;
private long id;
private int sortKey = 0;
private int minzoom = config.minzoom();
private int maxzoom = config.maxzoom();
private ZoomFunction labelGridPixelSize = null;
private ZoomFunction labelGridLimit = null;
private boolean attrsChangeByZoom = false;
private CacheByZoom