diff --git a/mapmaker/__main__.py b/mapmaker/__main__.py index cee64534..d5a9ad4f 100644 --- a/mapmaker/__main__.py +++ b/mapmaker/__main__.py @@ -91,6 +91,10 @@ def arg_parser(): help='Maximum zoom level (defaults to 10)') zoom_options.add_argument('--max-raster-zoom', dest='maxRasterZoom', metavar='N', type=int, help='Maximum zoom level of rasterised tiles (defaults to maximum zoom level)') + zoom_options.add_argument('--path-min-coverage', dest='pathMinCoverage', metavar='R', type=float, default=0.7, + help='Lower coverage factor used to derive path minzoom (defaults to 0.7)') + zoom_options.add_argument('--path-max-coverage', dest='pathMaxCoverage', metavar='R', type=float, default=5.0, + help='Upper coverage factor used to derive path maxzoom (defaults to 5.0)') misc_options = parser.add_argument_group('Miscellaneous') misc_options.add_argument('--commit', metavar='GIT_COMMIT', diff --git a/mapmaker/maker.py b/mapmaker/maker.py index b115b60c..63e8539d 100644 --- a/mapmaker/maker.py +++ b/mapmaker/maker.py @@ -121,6 +121,14 @@ def __init__(self, options: dict[str, Any], logger_port: Optional[int]=None, raise ValueError(f'Max raster zoom cannot be greater than max zoom ({max_zoom})') if initial_zoom < min_zoom or initial_zoom > max_zoom: raise ValueError(f'Initial zoom cannot be greater than max zoom ({max_zoom})') + + path_min_coverage = options.get('pathMinCoverage', 0.7) + path_max_coverage = options.get('pathMaxCoverage', 5.0) + if path_min_coverage <= 0 or path_max_coverage <= 0: + raise ValueError('Path coverage values must be greater than 0') + if path_max_coverage < path_min_coverage: + raise ValueError('Path max coverage cannot be less than path min coverage') + self.__zoom = (min_zoom, max_zoom, initial_zoom) if options.get('publish'): diff --git a/mapmaker/output/geojson.py b/mapmaker/output/geojson.py index 1d408156..71531893 100644 --- a/mapmaker/output/geojson.py +++ b/mapmaker/output/geojson.py @@ -39,6 +39,11 @@ #=============================================================================== +# Earth circumference (WGS84 equatorial radius for Web Mercator EPSG:3857) +EARTH_CIRCUMFERENCE = 2 * math.pi * 6378137.0 # meters + +#=============================================================================== + class GeoJSONOutput(object): def __init__(self, flatmap: FlatMap, layer: MapLayer, output_dir: str): #====================================================================== @@ -120,6 +125,10 @@ def __save_features(self, features): if scale > 6 and 'group' not in properties and 'minzoom' not in properties: geojson['tippecanoe']['minzoom'] = 4 else: + if (nodes:=self.__flatmap.connectivity()['paths'].get(feature.models)): + zoom_range = self.__get_path_zoom_range(nodes) + geojson['properties']['minzoom'] = zoom_range[0] + geojson['properties']['maxzoom'] = zoom_range[1] geojson['properties']['scale'] = 10 geojson['properties'].update(properties) @@ -158,3 +167,43 @@ def __save_features(self, features): progress_bar.update(1) progress_bar.close() + + def __get_path_zoom_range(self, nodes): + path_min_coverage = settings.get('pathMinCoverage', 0.7) + path_max_coverage = settings.get('pathMaxCoverage', 5.0) + + points = [geom.centroid + for node in nodes.get('nodes', []) + if (f := self.__flatmap.get_feature_by_geojson_id(node)) is not None + and (geom := f.geometry) is not None] + + if len(points) > 1: + # extent + xs = [p.x for p in points] + ys = [p.y for p in points] + # World-space extents (meters, EPSG:3857) + x_extent = max(xs) - min(xs) + y_extent = max(ys) - min(ys) + max_dist = 0.0 + for i, p1 in enumerate(points): + for p2 in points[i+1:]: + dist = math.hypot(p2.x - p1.x, p2.y - p1.y) + if dist > max_dist: + max_dist = dist + extent = max(x_extent, y_extent, max_dist) + if not math.isfinite(extent) or extent <= 0: + return self.__flatmap.min_zoom, self.__flatmap.max_zoom + + # minzoom and maxzoom + z_min = math.ceil(math.log2((path_min_coverage * EARTH_CIRCUMFERENCE) / extent)) + z_max = math.floor(math.log2((path_max_coverage * EARTH_CIRCUMFERENCE) / extent)) + if z_max < z_min: + z_max = z_min + z_min = max(self.__flatmap.min_zoom, z_min) + z_max = min(self.__flatmap.max_zoom, z_max) + if z_min >= self.__flatmap.max_zoom: + z_min = self.__flatmap.max_zoom - 1 + z_max = self.__flatmap.max_zoom + return z_min, z_max + + return self.__flatmap.min_zoom, self.__flatmap.max_zoom