2021-12-23 10:42:24 +00:00
package com.onthegomap.planetiler ;
import com.onthegomap.planetiler.collection.FeatureGroup ;
import com.onthegomap.planetiler.config.PlanetilerConfig ;
import com.onthegomap.planetiler.geo.GeoUtils ;
import com.onthegomap.planetiler.geo.GeometryException ;
import com.onthegomap.planetiler.geo.GeometryType ;
import com.onthegomap.planetiler.reader.SourceFeature ;
import com.onthegomap.planetiler.render.FeatureRenderer ;
import com.onthegomap.planetiler.stats.Stats ;
import com.onthegomap.planetiler.util.CacheByZoom ;
import com.onthegomap.planetiler.util.ZoomFunction ;
2021-05-08 10:53:37 +00:00
import java.util.ArrayList ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Map ;
2021-05-13 10:25:06 +00:00
import java.util.TreeMap ;
2023-11-14 12:44:47 +00:00
import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle ;
2021-05-08 10:53:37 +00:00
import org.locationtech.jts.geom.Geometry ;
2021-09-10 00:46:20 +00:00
/ * *
* Utility that { @link Profile } implementations use to build map features that should be emitted for an input source
* feature .
* < p >
* For example to add a polygon feature for a lake and a center label point with its name :
2023-10-28 00:29:26 +00:00
* { @snippet :
2021-09-10 00:46:20 +00:00
* featureCollector . polygon ( "water" )
* . setAttr ( "class" , "lake" ) ;
* featureCollector . centroid ( "water_name" )
* . setAttr ( "class" , "lake" )
* . setAttr ( "name" , element . getString ( "name" ) ) ;
2022-03-09 02:08:03 +00:00
* }
2021-09-10 00:46:20 +00:00
* /
2021-05-18 10:53:12 +00:00
public class FeatureCollector implements Iterable < FeatureCollector . Feature > {
2021-05-08 10:53:37 +00:00
2021-05-25 10:05:41 +00:00
private static final Geometry EMPTY_GEOM = GeoUtils . JTS_FACTORY . createGeometryCollection ( ) ;
2021-05-08 10:53:37 +00:00
private final SourceFeature source ;
2021-05-18 10:53:12 +00:00
private final List < Feature > output = new ArrayList < > ( ) ;
2021-12-23 10:42:24 +00:00
private final PlanetilerConfig config ;
2021-06-06 12:00:04 +00:00
private final Stats stats ;
2021-05-08 10:53:37 +00:00
2021-12-23 10:42:24 +00:00
private FeatureCollector ( SourceFeature source , PlanetilerConfig config , Stats stats ) {
2021-05-08 10:53:37 +00:00
this . source = source ;
this . config = config ;
2021-06-06 12:00:04 +00:00
this . stats = stats ;
2021-05-08 10:53:37 +00:00
}
@Override
2021-05-18 10:53:12 +00:00
public Iterator < Feature > iterator ( ) {
2021-05-08 10:53:37 +00:00
return output . iterator ( ) ;
}
2021-09-10 00:46:20 +00:00
/ * *
* 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 .
* /
2021-05-25 10:05:41 +00:00
public Feature geometry ( String layer , Geometry geometry ) {
2021-06-23 01:46:42 +00:00
Feature feature = new Feature ( layer , geometry , source . id ( ) ) ;
2021-05-13 10:25:06 +00:00
output . add ( feature ) ;
return feature ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Starts building a new point map feature that expects the source feature to be a point .
* < p >
* 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 .
* /
2021-05-25 10:05:41 +00:00
public Feature point ( String layer ) {
try {
if ( ! source . isPoint ( ) ) {
2023-03-20 20:41:18 +00:00
throw new GeometryException ( "feature_not_point" , "not a point" , true ) ;
2021-05-25 10:05:41 +00:00
}
return geometry ( layer , source . worldGeometry ( ) ) ;
} catch ( GeometryException e ) {
2021-07-18 12:17:58 +00:00
e . log ( stats , "feature_point" , "Error getting point geometry for " + source . id ( ) ) ;
2021-06-23 01:46:42 +00:00
return new Feature ( layer , EMPTY_GEOM , source . id ( ) ) ;
2021-05-25 10:05:41 +00:00
}
2021-05-13 10:25:06 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Starts building a new line map feature that expects the source feature to be a line .
* < p >
* 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 .
* < p >
* 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 .
* /
2021-05-25 10:05:41 +00:00
public Feature line ( String layer ) {
try {
return geometry ( layer , source . line ( ) ) ;
} catch ( GeometryException e ) {
2021-07-18 12:17:58 +00:00
e . log ( stats , "feature_line" , "Error constructing line for " + source . id ( ) ) ;
2021-06-23 01:46:42 +00:00
return new Feature ( layer , EMPTY_GEOM , source . id ( ) ) ;
2021-05-25 10:05:41 +00:00
}
}
2021-09-10 00:46:20 +00:00
/ * *
* Starts building a new polygon map feature that expects the source feature to be a polygon .
* < p >
* 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 .
* < p >
* 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 .
* /
2021-05-25 10:05:41 +00:00
public Feature polygon ( String layer ) {
try {
return geometry ( layer , source . polygon ( ) ) ;
} catch ( GeometryException e ) {
2021-07-18 12:17:58 +00:00
e . log ( stats , "feature_polygon" , "Error constructing polygon for " + source . id ( ) ) ;
2021-06-23 01:46:42 +00:00
return new Feature ( layer , EMPTY_GEOM , source . id ( ) ) ;
2021-05-30 11:42:06 +00:00
}
}
2021-09-10 00:46:20 +00:00
/ * *
* 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 . id ( ) ) ;
}
}
/ * *
* 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 .
* /
2021-07-17 00:48:02 +00:00
public Feature centroidIfConvex ( String layer ) {
2021-05-30 11:42:06 +00:00
try {
2021-07-17 00:48:02 +00:00
return geometry ( layer , source . centroidIfConvex ( ) ) ;
2021-05-30 11:42:06 +00:00
} catch ( GeometryException e ) {
2021-07-18 12:17:58 +00:00
e . log ( stats , "feature_centroid_if_convex" , "Error constructing centroid if convex for " + source . id ( ) ) ;
2021-06-23 01:46:42 +00:00
return new Feature ( layer , EMPTY_GEOM , source . id ( ) ) ;
2021-05-25 10:05:41 +00:00
}
}
2021-09-10 00:46:20 +00:00
/ * *
* 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 .
* /
2021-05-25 10:05:41 +00:00
public Feature pointOnSurface ( String layer ) {
try {
return geometry ( layer , source . pointOnSurface ( ) ) ;
} catch ( GeometryException e ) {
2021-07-18 12:17:58 +00:00
e . log ( stats , "feature_point_on_surface" , "Error constructing point on surface for " + source . id ( ) ) ;
2021-06-23 01:46:42 +00:00
return new Feature ( layer , EMPTY_GEOM , source . id ( ) ) ;
2021-05-25 10:05:41 +00:00
}
2021-05-08 10:53:37 +00:00
}
2023-11-14 12:44:47 +00:00
/ * *
* 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 .
* < p >
* 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 . id ( ) ) ;
}
}
/** Alias for {@link #innermostPoint(String, double)} with a default tolerance of 10%. */
public Feature innermostPoint ( String layer ) {
return innermostPoint ( layer , 0.1 ) ;
}
2023-11-20 11:15:52 +00:00
/** 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 ;
}
}
2021-09-10 00:46:20 +00:00
/ * *
* Creates new feature collector instances for each source feature that we encounter .
* /
2022-02-24 01:45:56 +00:00
public record Factory ( PlanetilerConfig config , Stats stats ) {
2021-05-18 10:53:12 +00:00
public FeatureCollector get ( SourceFeature source ) {
2021-06-06 12:00:04 +00:00
return new FeatureCollector ( source , config , stats ) ;
2021-05-18 10:53:12 +00:00
}
}
2021-09-10 00:46:20 +00:00
/ * *
* 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 .
* < p >
2021-09-18 00:18:06 +00:00
* Some feature attributes are set globally ( like sort key ) , and some allow the value to change by zoom - level ( like
2021-09-10 00:46:20 +00:00
* tags ) .
* /
2021-05-18 10:53:12 +00:00
public final class Feature {
2021-05-08 10:53:37 +00:00
private static final double DEFAULT_LABEL_GRID_SIZE = 0 ;
private static final int DEFAULT_LABEL_GRID_LIMIT = 0 ;
2021-09-10 00:46:20 +00:00
2021-05-18 10:53:12 +00:00
private final String layer ;
private final Geometry geom ;
private final Map < String , Object > attrs = new TreeMap < > ( ) ;
2021-05-25 10:05:41 +00:00
private final GeometryType geometryType ;
2023-03-15 14:28:16 +00:00
private long id ;
2021-09-10 00:46:20 +00:00
2021-09-18 00:18:06 +00:00
private int sortKey = 0 ;
2021-09-10 00:46:20 +00:00
2021-05-18 10:53:12 +00:00
private int minzoom = config . minzoom ( ) ;
private int maxzoom = config . maxzoom ( ) ;
2021-09-10 00:46:20 +00:00
2021-05-13 10:25:06 +00:00
private ZoomFunction < Number > labelGridPixelSize = null ;
private ZoomFunction < Number > labelGridLimit = null ;
2021-09-10 00:46:20 +00:00
2021-05-19 10:44:28 +00:00
private boolean attrsChangeByZoom = false ;
private CacheByZoom < Map < String , Object > > attrCache = null ;
2021-09-10 00:46:20 +00:00
private double defaultBufferPixels = 4 ;
private ZoomFunction < Number > bufferPixelOverrides ;
// TODO better API for default value, value at max zoom, and zoom-specific overrides for tolerance and min size?
private double defaultMinPixelSize = config . minFeatureSizeBelowMaxZoom ( ) ;
private double minPixelSizeAtMaxZoom = config . minFeatureSizeAtMaxZoom ( ) ;
private ZoomFunction < Number > minPixelSize = null ;
private double defaultPixelTolerance = config . simplifyToleranceBelowMaxZoom ( ) ;
private double pixelToleranceAtMaxZoom = config . simplifyToleranceAtMaxZoom ( ) ;
private ZoomFunction < Number > pixelTolerance = null ;
2021-06-25 11:06:55 +00:00
private String numPointsAttr = null ;
2021-05-08 10:53:37 +00:00
2023-03-15 14:28:16 +00:00
private Feature ( String layer , Geometry geom , long id ) {
2021-05-18 10:53:12 +00:00
this . layer = layer ;
this . geom = geom ;
2022-09-23 10:49:09 +00:00
this . geometryType = GeometryType . typeOf ( geom ) ;
2023-03-15 14:28:16 +00:00
this . id = id ;
2023-11-13 12:16:55 +00:00
if ( geometryType = = GeometryType . POINT ) {
minPixelSizeAtMaxZoom = 0 ;
defaultMinPixelSize = 0 ;
}
2021-06-23 01:46:42 +00:00
}
2021-09-10 00:46:20 +00:00
/** Returns the original ID of the source feature that this feature came from (i.e. OSM node/way ID). */
2023-03-15 14:28:16 +00:00
public long getId ( ) {
return id ;
}
public Feature setId ( long id ) {
this . id = id ;
return this ;
2021-05-13 10:25:06 +00:00
}
2021-09-10 00:46:20 +00:00
GeometryType getGeometryType ( ) {
return geometryType ;
}
public boolean isPolygon ( ) {
return geometryType = = GeometryType . POLYGON ;
}
/ * *
2021-09-18 00:18:06 +00:00
* Returns the value by which features are sorted within a layer in the output vector tile .
2021-09-10 00:46:20 +00:00
* /
2021-09-18 00:18:06 +00:00
public int getSortKey ( ) {
return sortKey ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2021-09-18 00:18:06 +00:00
* Sets the value by which features are sorted within a layer in the output vector tile . Sort key gets packed into
2023-11-14 12:44:47 +00:00
* { @link FeatureGroup # SORT_KEY_BITS } bits so the range of this is limited to { @code - ( 2 ^ ( bits - 1 ) ) } to
* { @code ( 2 ^ ( bits - 1 ) ) - 1 } .
2021-09-18 00:18:06 +00:00
* < p >
* Circles , lines , and polygons are rendered in the order they appear in each layer , so features that appear later
* ( higher sort key ) show up on top of features with a lower sort key .
* < p >
* For symbols ( text / icons ) where clients try to avoid label collisions , features are placed in the order they
* appear in each layer , so features that appear earlier ( lower sort key ) will show up at lower zoom levels than
* feature that appear later ( higher sort key ) in a layer .
2021-09-10 00:46:20 +00:00
* /
2021-09-18 00:18:06 +00:00
public Feature setSortKey ( int sortKey ) {
2022-03-09 02:08:03 +00:00
assert sortKey > = FeatureGroup . SORT_KEY_MIN & & sortKey < = FeatureGroup . SORT_KEY_MAX : "Sort key " + sortKey +
" outside of allowed range [" + FeatureGroup . SORT_KEY_MIN + ", " + FeatureGroup . SORT_KEY_MAX + "]" ;
2021-09-18 00:18:06 +00:00
this . sortKey = sortKey ;
2021-05-08 10:53:37 +00:00
return this ;
}
2021-05-16 10:42:57 +00:00
2021-09-18 00:18:06 +00:00
/** Sets the value by which features are sorted from high to low within a layer in the output vector tile. */
public Feature setSortKeyDescending ( int sortKey ) {
return setSortKey ( FeatureGroup . SORT_KEY_MAX + FeatureGroup . SORT_KEY_MIN - sortKey ) ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the zoom range ( inclusive ) that this feature appears in .
* < p >
* If not called , then defaults to all zoom levels .
* /
2021-05-18 10:53:12 +00:00
public Feature setZoomRange ( int min , int max ) {
2021-09-10 00:46:20 +00:00
assert min < = max ;
2021-05-18 10:53:12 +00:00
return setMinZoom ( min ) . setMaxZoom ( max ) ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/** Returns the minimum zoom level (inclusive) that this feature appears in. */
2021-05-18 10:53:12 +00:00
public int getMinZoom ( ) {
return minzoom ;
2021-05-13 10:25:06 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the minimum zoom level ( inclusive ) that this feature appears in .
* < p >
* If not called , defaults to minimum zoom - level of the map .
* /
2021-05-18 10:53:12 +00:00
public Feature setMinZoom ( int min ) {
minzoom = Math . max ( min , config . minzoom ( ) ) ;
2021-05-13 10:25:06 +00:00
return this ;
}
2021-09-10 00:46:20 +00:00
/** Returns the maximum zoom level (inclusive) that this feature appears in. */
2021-05-18 10:53:12 +00:00
public int getMaxZoom ( ) {
return maxzoom ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the maximum zoom level ( inclusive ) that this feature appears in .
* < p >
* If not called , defaults to maximum zoom - level of the map .
* /
2021-05-18 10:53:12 +00:00
public Feature setMaxZoom ( int max ) {
maxzoom = Math . min ( max , config . maxzoom ( ) ) ;
2021-05-08 10:53:37 +00:00
return this ;
}
2021-09-10 00:46:20 +00:00
/** Returns the output vector tile layer that this feature will appear in. */
2021-05-18 10:53:12 +00:00
public String getLayer ( ) {
return layer ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Returns the JTS geometry ( in world web mercator coordinates ) of this feature .
* < p >
* Subsequent postprocessing in { @link FeatureRenderer } will slice this into tile geometries .
* /
2021-05-18 10:53:12 +00:00
public Geometry getGeometry ( ) {
return geom ;
2021-05-13 10:25:06 +00:00
}
2021-09-10 00:46:20 +00:00
/** Returns the number of pixels of detail to render outside the visible tile boundary at {@code zoom}. */
2021-05-18 10:53:12 +00:00
public double getBufferPixelsAtZoom ( int zoom ) {
return ZoomFunction . applyAsDoubleOrElse ( bufferPixelOverrides , zoom , defaultBufferPixels ) ;
2021-05-13 10:25:06 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the default number of pixels of detail to render outside the visible tile boundary when no zoom - specific
* override is set in { @link # setBufferPixelOverrides ( ZoomFunction ) } .
* /
2021-05-18 10:53:12 +00:00
public Feature setBufferPixels ( double buffer ) {
defaultBufferPixels = buffer ;
2021-05-13 10:25:06 +00:00
return this ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets zoom - specific overrides to the number of pixels of detail to render outside the visible tile boundary .
* < p >
2022-03-09 02:08:03 +00:00
* If { @code buffer } is { @code null } or returns { @code null } , the buffer pixels will default to
* { @link # setBufferPixels ( double ) } .
2021-09-10 00:46:20 +00:00
* /
2021-05-18 10:53:12 +00:00
public Feature setBufferPixelOverrides ( ZoomFunction < Number > buffer ) {
bufferPixelOverrides = buffer ;
2021-05-08 10:53:37 +00:00
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Returns the minimum resolution in tile pixels of features to emit at { @code zoom } .
* < p >
* For line features , this is length , and for polygon features this is the square root of the minimum area of
* features to emit .
* /
public double getMinPixelSizeAtZoom ( int zoom ) {
2022-06-20 10:31:50 +00:00
return zoom = = config . maxzoomForRendering ( ) ? minPixelSizeAtMaxZoom :
2022-03-09 02:08:03 +00:00
ZoomFunction . applyAsDoubleOrElse ( minPixelSize , zoom , defaultMinPixelSize ) ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the minimum length of line features or square root of the minimum area of polygon features to emit below the
* maximum zoom - level of the map .
* < p >
* At the maximum zoom level of the map , clients can "overzoom" in on features , so this leaves the minimum size at
2021-12-23 10:42:24 +00:00
* the max zoom level at { @link PlanetilerConfig # minFeatureSizeAtMaxZoom ( ) } unless you explicitly override it with
2021-09-10 00:46:20 +00:00
* { @link # setMinPixelSizeAtMaxZoom ( double ) } or { @link # setMinPixelSizeAtAllZooms ( int ) } .
* /
2021-05-18 10:53:12 +00:00
public Feature setMinPixelSize ( double minPixelSize ) {
this . defaultMinPixelSize = minPixelSize ;
return this ;
2021-06-19 09:35:57 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets zoom - specific overrides to the minimum length of line features or square root of the minimum area of polygon
* features to emit below the maximum zoom - level of the map .
* < p >
* At the maximum zoom level of the map , clients can "overzoom" in on features , so this leaves the minimum size at
2021-12-23 10:42:24 +00:00
* the max zoom level at { @link PlanetilerConfig # minFeatureSizeAtMaxZoom ( ) } unless you explicitly override it with
2021-09-10 00:46:20 +00:00
* { @link # setMinPixelSizeAtMaxZoom ( double ) } or { @link # setMinPixelSizeAtAllZooms ( int ) } .
* < p >
* If { @code levels } is { @code null } or returns { @code null } , the min pixel size will default to the default value .
* /
public Feature setMinPixelSizeOverrides ( ZoomFunction < Number > levels ) {
2021-06-19 09:35:57 +00:00
this . minPixelSize = levels ;
return this ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Overrides the default minimum pixel size at and below { @code zoom } with { @code minPixelSize } .
* < p >
2022-03-09 02:08:03 +00:00
* This replaces all previous zoom overrides that were set . To use multiple zoom - level thresholds , create a
* { @link ZoomFunction } explicitly and pass it to { @link # setMinPixelSizeOverrides ( ZoomFunction ) } .
2021-09-10 00:46:20 +00:00
* /
2021-05-18 10:53:12 +00:00
public Feature setMinPixelSizeBelowZoom ( int zoom , double minPixelSize ) {
2022-06-20 10:31:50 +00:00
if ( zoom > = config . maxzoomForRendering ( ) ) {
2021-09-10 00:46:20 +00:00
minPixelSizeAtMaxZoom = minPixelSize ;
}
2021-05-18 10:53:12 +00:00
this . minPixelSize = ZoomFunction . maxZoom ( zoom , minPixelSize ) ;
return this ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the minimum length of line features or square root of the minimum area of polygon features to emit at the
* maximum zoom - level of the map .
* < p >
* Since clients can "overzoom" in on features past the maximum zoom level , this is typically much smaller than min
* pixel size at lower zoom levels .
* < p >
* This overrides , but does not replace the default min pixel size or overrides set through other methods .
* /
2021-05-23 16:42:27 +00:00
public Feature setMinPixelSizeAtMaxZoom ( double minPixelSize ) {
this . minPixelSizeAtMaxZoom = minPixelSize ;
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the minimum length of line features or square root of the minimum area of polygon features to emit at all
* zoom levels , including the maximum zoom - level of the map .
* < p >
2022-03-09 02:08:03 +00:00
* This replaces previous default values , but not overrides set with
* { @link # setMinPixelSizeOverrides ( ZoomFunction ) } .
2021-09-10 00:46:20 +00:00
* /
2021-05-23 16:42:27 +00:00
public Feature setMinPixelSizeAtAllZooms ( int minPixelSize ) {
2021-09-10 00:46:20 +00:00
this . minPixelSizeAtMaxZoom = minPixelSize ;
return this . setMinPixelSize ( minPixelSize ) ;
2021-05-23 16:42:27 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Returns the simplification tolerance for lines and polygons in tile pixels at { @code zoom } .
* /
public double getPixelToleranceAtZoom ( int zoom ) {
2022-06-20 10:31:50 +00:00
return zoom = = config . maxzoomForRendering ( ) ? pixelToleranceAtMaxZoom :
2022-03-09 02:08:03 +00:00
ZoomFunction . applyAsDoubleOrElse ( pixelTolerance , zoom , defaultPixelTolerance ) ;
2021-09-10 00:46:20 +00:00
}
2021-05-23 16:42:27 +00:00
2021-09-10 00:46:20 +00:00
/ * *
* Sets the simplification tolerance for lines and polygons in tile pixels below the maximum zoom - level of the map .
* < p >
* Since clients can "overzoom" past the max zoom of the map , this is typically smaller than the default tolerance
* to provide more detail as you zoom in .
* < p >
* This does not replace any overrides that were set with { @link # setPixelToleranceOverrides ( ZoomFunction ) } .
* /
2021-05-23 16:42:27 +00:00
public Feature setPixelTolerance ( double tolerance ) {
this . defaultPixelTolerance = tolerance ;
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the simplification tolerance for lines and polygons in tile pixels at the maximum zoom - level of the map .
* < p >
* This does not replace the default value at other zoom levels set through { @link # setPixelTolerance ( double ) } any
* zoom - specific overrides that were set with { @link # setPixelToleranceOverrides ( ZoomFunction ) } .
* /
2021-05-23 16:42:27 +00:00
public Feature setPixelToleranceAtMaxZoom ( double tolerance ) {
this . pixelToleranceAtMaxZoom = tolerance ;
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets the simplification tolerance for lines and polygons in tile pixels including at the maximum zoom - level of
* the map .
* < p >
* This does not replace the default value at other zoom levels set through { @link # setPixelTolerance ( double ) } .
* /
2021-05-23 16:42:27 +00:00
public Feature setPixelToleranceAtAllZooms ( double tolerance ) {
return setPixelToleranceAtMaxZoom ( tolerance ) . setPixelTolerance ( tolerance ) ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets zoom - specific overrides to the simplification tolerance for lines and polygons in tile pixels below the
* maximum zoom - level of the map .
* < p >
* At the maximum zoom level of the map , clients can "overzoom" in on features , so this leaves the tolerance at the
2021-12-23 10:42:24 +00:00
* max zoom level set to { @link PlanetilerConfig # simplifyToleranceAtMaxZoom ( ) } unless you explicitly override it
* with { @link # setMinPixelSizeAtAllZooms ( int ) } or { @link # setMinPixelSizeAtMaxZoom ( double ) } .
2021-09-10 00:46:20 +00:00
* < p >
* If { @code levels } is { @code null } or returns { @code null } , the min pixel size will default to the default value .
* /
public Feature setPixelToleranceOverrides ( ZoomFunction < Number > overrides ) {
this . pixelTolerance = overrides ;
2021-05-23 16:42:27 +00:00
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Overrides the default simplification tolerance for lines and polygons in tile pixels at and below { @code zoom }
* with { @code minPixelSize } .
* < p >
2022-03-09 02:08:03 +00:00
* This replaces all previous zoom overrides that were set . To use multiple zoom - level thresholds , create a
* { @link ZoomFunction } explicitly and pass it to { @link # setPixelToleranceOverrides ( ZoomFunction ) }
2021-09-10 00:46:20 +00:00
* /
public Feature setPixelToleranceBelowZoom ( int zoom , double tolerance ) {
2022-06-20 10:31:50 +00:00
if ( zoom = = config . maxzoomForRendering ( ) ) {
2021-09-10 00:46:20 +00:00
pixelToleranceAtMaxZoom = tolerance ;
}
return setPixelToleranceOverrides ( ZoomFunction . maxZoom ( zoom , tolerance ) ) ;
2021-05-23 16:42:27 +00:00
}
2021-09-10 00:46:20 +00:00
public boolean hasLabelGrid ( ) {
return labelGridPixelSize ! = null | | labelGridLimit ! = null ;
}
/ * *
2022-01-10 11:41:15 +00:00
* Returns the size in pixels of the grid used to group or limit output points .
2021-09-10 00:46:20 +00:00
*
* @throws AssertionError when assertions are enabled and the returned value is smaller than the buffer pixel size
* /
2022-01-10 11:41:15 +00:00
public double getPointLabelGridPixelSizeAtZoom ( int zoom ) {
2021-09-10 00:46:20 +00:00
double result = ZoomFunction . applyAsDoubleOrElse ( labelGridPixelSize , zoom , DEFAULT_LABEL_GRID_SIZE ) ;
// TODO is this enough? what about a grid square that ends just before the start of the tile
2022-03-09 02:08:03 +00:00
assert result < = getBufferPixelsAtZoom (
zoom ) : "to avoid inconsistent rendering of the same point between adjacent tiles, buffer pixel size should be >= label grid size but in '%s' buffer pixel size=%f was greater than label grid size=%f"
. formatted (
getLayer ( ) , getBufferPixelsAtZoom ( zoom ) , result ) ;
2021-09-10 00:46:20 +00:00
return result ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2022-01-10 11:41:15 +00:00
* Returns the maximum number of lowest - sort - key points to include in the output vector tile in each square of a
* grid with size { @link # getPointLabelGridPixelSizeAtZoom ( int ) } .
2021-09-10 00:46:20 +00:00
* /
2022-01-10 11:41:15 +00:00
public int getPointLabelGridLimitAtZoom ( int zoom ) {
2021-05-18 10:53:12 +00:00
return ZoomFunction . applyAsIntOrElse ( labelGridLimit , zoom , DEFAULT_LABEL_GRID_LIMIT ) ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2022-01-10 11:41:15 +00:00
* Sets the size of a grid in tile pixels that will be used to compute a "location hash" of points rendered in each
* zoom - level for limiting the density of features in the output tile , or computing a "rank" key that clients can
* use to control label density .
2021-09-10 00:46:20 +00:00
* < p >
* If limit is set , features will be dropped automatically before encoding the vector tile , but "rank" must be added
* explicitly in { @link Profile # postProcessLayerFeatures ( String , int , List ) } .
* < p >
* Replaces any previous values set for label grid pixel size .
* < p >
* NOTE : the label grid is computed within each tile independently of its neighbors , so to ensure consistent limits
* and ranking of a point rendered in adjacent tiles , be sure to set the buffer pixel size to at least be larger
* than the label grid pixel size at each zoom level .
*
* @param labelGridSize a function that returns the size of the label grid to use at each zoom level . If function is
* or returns null for a zoom - level , no label grid will be computed .
* @see < a href = "https://github.com/mapbox/postgis-vt-util/blob/master/src/LabelGrid.sql" > LabelGrid postgis
2022-03-09 02:08:03 +00:00
* function < / a >
2021-09-10 00:46:20 +00:00
* /
2022-01-10 11:41:15 +00:00
public Feature setPointLabelGridPixelSize ( ZoomFunction < Number > labelGridSize ) {
2021-05-18 10:53:12 +00:00
this . labelGridPixelSize = labelGridSize ;
return this ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2022-01-10 11:41:15 +00:00
* Sets the size of a grid in tile pixels that will be used to compute a "location hash" of points rendered in each
* zoom - level at and below { @code maxzoom } .
2021-09-10 00:46:20 +00:00
* < p >
2022-01-10 11:41:15 +00:00
* This is a thin wrapper around { @link # setPointLabelGridPixelSize ( ZoomFunction ) } . It replaces any previous value
* set for label grid size . To set multiple thresholds , use the other method directly .
2021-09-10 00:46:20 +00:00
*
* @see < a href = "https://github.com/mapbox/postgis-vt-util/blob/master/src/LabelGrid.sql" > LabelGrid postgis
2022-03-09 02:08:03 +00:00
* function < / a >
2021-09-10 00:46:20 +00:00
* /
2022-01-10 11:41:15 +00:00
public Feature setPointLabelGridPixelSize ( int maxzoom , double size ) {
return setPointLabelGridPixelSize ( ZoomFunction . maxZoom ( maxzoom , size ) ) ;
2021-09-10 00:46:20 +00:00
}
/ * *
2022-01-10 11:41:15 +00:00
* Sets the maximum number of points with the lowest sort - key to include with the same label grid hash in a tile .
2021-09-10 00:46:20 +00:00
* < p >
* Replaces any previous values set for label grid limit .
*
* @param labelGridLimit a function that returns the size of the label grid to use at each zoom level . If function
* is or returns null for a zoom - level , no label grid will be computed .
* @see < a href = "https://github.com/mapbox/postgis-vt-util/blob/master/src/LabelGrid.sql" > LabelGrid postgis
2022-03-09 02:08:03 +00:00
* function < / a >
2021-09-10 00:46:20 +00:00
* /
2022-01-10 11:41:15 +00:00
public Feature setPointLabelGridLimit ( ZoomFunction < Number > labelGridLimit ) {
2021-05-18 10:53:12 +00:00
this . labelGridLimit = labelGridLimit ;
return this ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2022-01-10 11:41:15 +00:00
* Limits the density of points on an output tile at and below { @code maxzoom } by only emitting the { @code limit }
2021-09-18 00:18:06 +00:00
* features with lowest sort - key in each square of a { @code size x size } pixel grid .
2021-09-10 00:46:20 +00:00
* < p >
2022-03-09 02:08:03 +00:00
* This is a thin wrapper around { @link # setPointLabelGridPixelSize ( ZoomFunction ) } and
* { @link # setPointLabelGridLimit ( ZoomFunction ) } . It replaces any previous value set for label grid size or limit .
* To set multiple thresholds , use the other methods directly .
2021-09-10 00:46:20 +00:00
* < p >
* NOTE : the label grid is computed within each tile independently of its neighbors , so to ensure consistent limits
* and ranking of a point rendered in adjacent tiles , be sure to set the buffer pixel size to at least be larger
* than the label grid pixel size at each zoom level .
*
* @param maxzoom the zoom - level at and below which we should limit point density
* @param size the label grid size to use when computing hashes
2021-09-18 00:18:06 +00:00
* @param limit the number of lowest - sort - key points to include in each square of the grid
2021-09-10 00:46:20 +00:00
* @see < a href = "https://github.com/mapbox/postgis-vt-util/blob/master/src/LabelGrid.sql" > LabelGrid postgis
2022-03-09 02:08:03 +00:00
* function < / a >
2021-09-10 00:46:20 +00:00
* /
2022-01-10 11:41:15 +00:00
public Feature setPointLabelGridSizeAndLimit ( int maxzoom , double size , int limit ) {
return setPointLabelGridPixelSize ( ZoomFunction . maxZoom ( maxzoom , size ) )
. setPointLabelGridLimit ( ZoomFunction . maxZoom ( maxzoom , limit ) ) ;
2021-09-10 00:46:20 +00:00
}
// could be expensive, so cache results
2021-05-19 10:44:28 +00:00
private Map < String , Object > computeAttrsAtZoom ( int zoom ) {
2021-05-13 10:25:06 +00:00
Map < String , Object > result = new TreeMap < > ( ) ;
for ( var entry : attrs . entrySet ( ) ) {
Object value = entry . getValue ( ) ;
if ( value instanceof ZoomFunction < ? > fn ) {
value = fn . apply ( zoom ) ;
}
if ( value ! = null & & ! "" . equals ( value ) ) {
result . put ( entry . getKey ( ) , value ) ;
}
}
return result ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/** Returns the attribute to put on all output vector tile features at a zoom level. */
2021-05-19 10:44:28 +00:00
public Map < String , Object > getAttrsAtZoom ( int zoom ) {
if ( ! attrsChangeByZoom ) {
return attrs ;
}
if ( attrCache = = null ) {
2022-09-23 10:49:09 +00:00
attrCache = CacheByZoom . create ( this : : computeAttrsAtZoom ) ;
2021-05-19 10:44:28 +00:00
}
return attrCache . get ( zoom ) ;
}
2021-09-10 00:46:20 +00:00
/** Copies the value for {@code key} attribute from source feature to the output feature. */
public Feature inheritAttrFromSource ( String key ) {
return setAttr ( key , source . getTag ( key ) ) ;
2021-05-08 10:53:37 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
* Sets an attribute on the output feature to either a string , number , boolean , or instance of { @link ZoomFunction }
* to change the value for { @code key } by zoom - level .
* /
2021-05-18 10:53:12 +00:00
public Feature setAttr ( String key , Object value ) {
2021-05-19 10:44:28 +00:00
if ( value instanceof ZoomFunction ) {
attrsChangeByZoom = true ;
2021-06-17 01:05:42 +00:00
}
if ( value ! = null ) {
2021-06-14 11:04:03 +00:00
attrs . put ( key , value ) ;
2021-05-19 10:44:28 +00:00
}
2021-05-18 10:53:12 +00:00
return this ;
2021-05-08 10:53:37 +00:00
}
2021-05-13 10:25:06 +00:00
2021-09-10 00:46:20 +00:00
/ * *
2022-03-09 02:08:03 +00:00
* Sets the value for { @code key } attribute at or above { @code minzoom } . Below { @code minzoom } it will be ignored .
2021-09-10 00:46:20 +00:00
* < p >
2022-03-09 02:08:03 +00:00
* Replaces all previous value that has been for { @code key } at any zoom level . To have a value that changes at
* multiple zoom level thresholds , call { @link # setAttr ( String , Object ) } with a manually - constructed
* { @link ZoomFunction } value .
2021-09-10 00:46:20 +00:00
* /
2021-05-18 10:53:12 +00:00
public Feature setAttrWithMinzoom ( String key , Object value , int minzoom ) {
2021-05-19 10:44:28 +00:00
return setAttr ( key , ZoomFunction . minZoom ( minzoom , value ) ) ;
}
2023-11-20 11:15:52 +00:00
/ * *
* Sets the value for { @code key } only at zoom levels where the feature is at least { @code minPixelSize } pixels in
* size .
* /
public Feature setAttrWithMinSize ( String key , Object value , double minPixelSize ) {
return setAttrWithMinzoom ( key , value , getMinZoomForPixelSize ( minPixelSize ) ) ;
}
/ * *
* Sets the value for { @code key } so that it always shows when { @code zoom_level > = minZoomToShowAlways } but only
* shows when { @code minZoomIfBigEnough < = zoom_level < minZoomToShowAlways } when it is at least
* { @code minPixelSize } pixels in size .
* < p >
* If you need more flexibility , use { @link # getMinZoomForPixelSize ( double ) } directly , or create a
* { @link ZoomFunction } that calculates { @link # getPixelSizeAtZoom ( int ) } and applies a custom threshold based on the
* zoom level .
* /
public Feature setAttrWithMinSize ( String key , Object value , double minPixelSize , int minZoomIfBigEnough ,
int minZoomToShowAlways ) {
return setAttrWithMinzoom ( key , value ,
Math . clamp ( getMinZoomForPixelSize ( minPixelSize ) , minZoomIfBigEnough , minZoomToShowAlways ) ) ;
}
2022-01-19 10:36:44 +00:00
/ * *
* Inserts all key / value pairs in { @code attrs } into the set of attribute to emit on the output feature at or above
* { @code minzoom } .
* < p >
2022-03-27 09:49:58 +00:00
* Replace values that have already been set .
2022-01-19 10:36:44 +00:00
* /
public Feature putAttrsWithMinzoom ( Map < String , Object > attrs , int minzoom ) {
for ( var entry : attrs . entrySet ( ) ) {
setAttrWithMinzoom ( entry . getKey ( ) , entry . getValue ( ) , minzoom ) ;
}
return this ;
}
2021-09-10 00:46:20 +00:00
/ * *
* Inserts all key / value pairs in { @code attrs } into the set of attribute to emit on the output feature .
* < p >
* Does not touch attributes that have already been set .
* < p >
* Values in { @code attrs } can either be the raw value to set , or an instance of { @link ZoomFunction } to change the
* value for that attribute by zoom level .
* /
public Feature putAttrs ( Map < String , Object > attrs ) {
for ( Object value : attrs . values ( ) ) {
if ( value instanceof ZoomFunction ) {
attrsChangeByZoom = true ;
break ;
}
}
this . attrs . putAll ( attrs ) ;
return this ;
2021-05-25 10:05:41 +00:00
}
2021-09-10 00:46:20 +00:00
/ * *
2023-11-20 11:15:52 +00:00
* Returns the attribute key that the renderer should use to store the number of points in the simplified geometry
2021-09-10 00:46:20 +00:00
* before slicing it into tiles .
* /
2023-11-20 11:15:52 +00:00
public String getNumPointsAttr ( ) {
return numPointsAttr ;
2021-09-10 00:46:20 +00:00
}
/ * *
2023-11-20 11:15:52 +00:00
* Sets a special attribute key that the renderer will use to store the number of points in the simplified geometry
2021-09-10 00:46:20 +00:00
* before slicing it into tiles .
* /
2023-11-20 11:15:52 +00:00
public Feature setNumPointsAttr ( String numPointsAttr ) {
this . numPointsAttr = numPointsAttr ;
return this ;
2021-05-13 10:25:06 +00:00
}
2021-05-16 10:42:57 +00:00
@Override
public String toString ( ) {
return "Feature{" +
"layer='" + layer + '\'' +
", geom=" + geom . getGeometryType ( ) +
", attrs=" + attrs +
'}' ;
}
2023-11-13 12:16:55 +00:00
/** Returns the actual pixel size of the source feature at {@code zoom} (length if line, sqrt(area) if polygon). */
public double getSourceFeaturePixelSizeAtZoom ( int zoom ) {
2023-11-20 11:15:52 +00:00
return getPixelSizeAtZoom ( zoom ) ;
2023-11-13 12:16:55 +00:00
}
2021-05-08 10:53:37 +00:00
}
}