55# Modifications by PolusAI, 2024
66
77import logging
8+ import math
89from .convert import convert
910from .clip import clip
1011from .transform import transform_tile
1112from .tile import create_tile
1213from .simplify import simplify
1314
1415
16+ def default_tolerance_func (z , options ):
17+ """Calculates the default simplification tolerance based on zoom level."""
18+ # Ensure options exist and have defaults if necessary
19+ tolerance_val = options .get ('tolerance' , 50 ) # Use default if not present
20+ extent_val = options .get ('extent' , 4096 ) # Use default if not present
21+ denominator = (1 << z ) * extent_val
22+ if denominator == 0 :
23+ # Avoid division by zero, return a very small tolerance
24+ # Consider if raising an error might be better depending on context
25+ return 1e-12
26+ return (tolerance_val / denominator ) ** 2
27+
28+
29+ # --- Alternative Tolerance Functions ---
30+
31+ def linear_tolerance_func (z , options ):
32+ """Linear scaling: tolerance decreases linearly with map scale."""
33+ tolerance_val = options .get ('tolerance' , 50 )
34+ extent_val = options .get ('extent' , 4096 )
35+ denominator = (1 << z ) * extent_val
36+ if denominator == 0 :
37+ return 1e-12 # Avoid division by zero
38+ # Note: No square here compared to default
39+ return tolerance_val / denominator
40+
41+ def constant_tolerance_func (z , options ):
42+ """Constant tolerance relative to extent (same simplification regardless of zoom)."""
43+ tolerance_val = options .get ('tolerance' , 50 )
44+ extent_val = options .get ('extent' , 4096 )
45+ if extent_val == 0 :
46+ return 1e-12 # Avoid division by zero
47+ # Apply the base tolerance scaled by extent, squared like the default, but without zoom factor
48+ return (tolerance_val / extent_val ) ** 2
49+ # Alternative: return a fixed value if extent scaling is not desired e.g. options.get('tolerance', 50)
50+
51+ def slow_exponential_tolerance_func (z , options , exponent = 1.5 ):
52+ """Slower exponential decay (exponent < 2). Tune exponent as needed."""
53+ tolerance_val = options .get ('tolerance' , 50 )
54+ extent_val = options .get ('extent' , 4096 )
55+ denominator = (1 << z ) * extent_val
56+ if denominator == 0 :
57+ return 1e-12
58+ return (tolerance_val / denominator ) ** exponent
59+
60+ def logarithmic_tolerance_func (z , options ):
61+ """Logarithmic scaling: tolerance decreases slowly, especially at high zooms."""
62+ tolerance_val = options .get ('tolerance' , 50 )
63+ extent_val = options .get ('extent' , 4096 )
64+ # Use log(z + 2) to avoid log(0) or log(1) issues at low zooms
65+ log_factor = math .log (z + 2 )
66+ if extent_val == 0 or log_factor == 0 :
67+ return 1e-12 # Avoid division by zero
68+ # Example scaling - adjust as needed
69+ return tolerance_val / (log_factor * extent_val )
70+
71+ def step_tolerance_func (z , options ):
72+ """Step function: different tolerance levels for different zoom ranges."""
73+ base_tolerance = options .get ('tolerance' , 50 )
74+ extent = options .get ('extent' , 4096 )
75+ index_max_zoom = options .get ('indexMaxZoom' , 5 )
76+ max_zoom = options .get ('maxZoom' , 8 )
77+
78+ # Define zoom thresholds and corresponding multipliers
79+ if z < index_max_zoom - 1 : # Low zooms (e.g., < 4 if indexMaxZoom is 5)
80+ effective_tolerance = base_tolerance * 4
81+ elif z < max_zoom - 1 : # Mid zooms (e.g., 4-6 if maxZoom is 8)
82+ effective_tolerance = base_tolerance * 1.5
83+ else : # High zooms (e.g., >= 7 if maxZoom is 8)
84+ effective_tolerance = base_tolerance * 0.5
85+
86+ # Apply scaling based on extent and zoom, similar to default
87+ denominator = (1 << z ) * extent
88+ if denominator == 0 :
89+ return 1e-12
90+ return (effective_tolerance / denominator ) ** 2
91+ # Alternative: Return a tolerance based only on the step, scaled by extent
92+ # if extent == 0: return 1e-12
93+ # return (effective_tolerance / extent) ** 2
94+
95+
96+ # --- End Alternative Tolerance Functions ---
97+
98+ AVAILABLE_TOLERANCE_FUNCTIONS = {
99+ "default" : default_tolerance_func ,
100+ "linear" : linear_tolerance_func ,
101+ "constant" : constant_tolerance_func ,
102+ "slow_exponential" : slow_exponential_tolerance_func ,
103+ "logarithmic" : logarithmic_tolerance_func ,
104+ "step" : step_tolerance_func ,
105+ }
106+
15107def get_default_options ():
16108 return {
17109 "maxZoom" : 8 , # max zoom to preserve detail on
@@ -24,7 +116,8 @@ def get_default_options():
24116 "promoteId" : None , # name of a feature property to be promoted
25117 "generateId" : False , # whether to generate feature ids.
26118 "projector" : None , # which projection to use
27- "bounds" : None # [west, south, east, north]
119+ "bounds" : None , # [west, south, east, north]
120+ "tolerance_function" : default_tolerance_func # function to calculate tolerance per zoom
28121 }
29122
30123
@@ -46,6 +139,22 @@ def __init__(self, data, options, log_level=logging.INFO):
46139 level = log_level , format = '%(asctime)s %(levelname)s %(message)s' )
47140 options = self .options = extend (get_default_options (), options )
48141
142+ # Validate and resolve tolerance_function
143+ tolerance_setting = options .get ('tolerance_function' )
144+ if isinstance (tolerance_setting , str ):
145+ if tolerance_setting in AVAILABLE_TOLERANCE_FUNCTIONS :
146+ options ['tolerance_function' ] = AVAILABLE_TOLERANCE_FUNCTIONS [tolerance_setting ]
147+ else :
148+ raise ValueError (
149+ f"Invalid tolerance function key: '{ tolerance_setting } '. "
150+ f"Available keys: { list (AVAILABLE_TOLERANCE_FUNCTIONS .keys ())} "
151+ )
152+ elif not callable (tolerance_setting ):
153+ raise TypeError (
154+ "Option 'tolerance_function' must be a callable function or a valid string key."
155+ )
156+ # If it's already callable, we use it directly.
157+
49158 logging .debug ('preprocess data start' )
50159
51160 if options .get ('maxZoom' ) < 0 or options .get ('maxZoom' ) > 24 :
@@ -66,10 +175,12 @@ def __init__(self, data, options, log_level=logging.INFO):
66175 for feature in features :
67176 feature [f'geometry_z{ z } ' ] = feature ['geometry' ].copy ()
68177
178+ tolerance_func = options ['tolerance_function' ] # Resolved above
179+
69180 # Simplify features for each zoom level
70181 for z in range (options .get ('maxZoom' ) + 1 ):
71- tolerance = ( options . get ( ' tolerance' ) / (( 1 << z ) * options . get (
72- 'extent' ))) ** 2
182+ # Calculate tolerance using the provided or default function
183+ tolerance = tolerance_func ( z , options )
73184 for feature in features :
74185 geometry_key = f'geometry_z{ z } '
75186 # check feature type only simplify Polygon
0 commit comments