map-zoomtospan
v1.0.7
Published
A cross-platform utility designed to calculate the optimal map zoom level and/or center point for a given map viewport, ensuring a balanced view of map features.
Downloads
16
Maintainers
Readme
map-zoomtospan
map-zoomtospan is a general-purpose "best view" calculation tool, independent of any specific mapping library. While we currently provide a MapLibre demo to demonstrate its functionality, more demos for additional mapping libraries are coming soon. Adapters for various mapping libraries will also be available in the near future.
Installation
npm install map-zoomtospan
What is "Best View"?
A "best view" refers to the zoom level that allows all features/layers to fit within the viewport, with the map centered on these features. For a clearer understanding, see the example below:
Many mapping libraries, like MapLibre, offer fitBounds/setFitView
methods for similar purposes. However, these methods focus only on the spatial bounds of features and ignore the size of markers, or the width of polylines and polygons—leading to imperfect results. Here’s an example of this limitation:
While adjusting the padding of the fitBounds
method can somewhat improve the fit, it often requires case-by-case tweaking rather than offering a consistent solution.
Our approach solves this problem by providing a general "best view" calculation that accommodates not only markers, polylines, and polygons, but also custom overlays.
The key requirement for custom overlays is that their size remains consistent, regardless of zoom level. We use a binary search algorithm to find the optimal view. If the boundingRect
changes with the zoom level, the binary search may not work correctly. If this is a common scenario for you, please let us know, and we can provide an alternative strategy.
How to Calculate the Best View
import { mapZoomToSpan } from "map-zoomtospan";
const mapResult = mapZoomToSpan({
viewport: {
width: 800, // width of the map element
height: 600, // height of the map element
inset: { top: 10, right: 40, bottom: 60, left: 10 }, // padding around the map element
},
overlays: [
{ // a marker
position: { lat: 10, lng: 10 },
boundingRect: { width: 10, height: 10 },
anchor: { x: 0.5, y: 0.5 },
},
{ // a polyline
points: [
{ lat: 10, lng: 10 },
{ lat: 20, lng: 20 },
{ lat: 20, lng: 10 },
],
width: 5, // line width
},
{ // a polygon
points: [
{ lat: 10, lng: 10 },
{ lat: 20, lng: 20 },
{ lat: 20, lng: 10 },
{ lat: 10, lng: 10 }
],
width: 8, // line width
},
{ // a circle
center: { lat: 10, lng: 10 },
radius: 5,
}
],
zoomRange: [4, 18], // zoom level range
worldSize: 256, // size of the world map
precision: 0.001, // zoom level precision
});
if (mapResult.ok) {
mapSetCenterAndZoom(mapResult.result.center, mapResult.result.zoom);
} else {
console.error(mapResult.error);
}
Key Concepts
Map Viewport
The map viewport represents the pixel dimensions (width and height) of the map element. The viewport inset refers to the padding (top, right, bottom, left) where other UI components or controls may cover part of the map.
Overlays
Overlays represent the features or layers you want to display on the map, such as markers, polylines, polygons, or even custom elements.
We define overlays using the IStandardOverlay
structure, which includes the position, bounding rectangle, and anchor of the overlay.
- The position is an
ILatLng
coordinate on the map. - The anchor (represented as percentage values) determines how the bounding rectangle is attached to the position.
This structure works well for markers but can be adapted to describe most types of overlays. Below, we show how to calculate an IStandardOverlay
from various overlay types. Additionally, we support simplified structures for common overlays.
Example: A marker with a size of 10px * 20px
attached to the position (10, 80)
is described as:
{
position: {
lat: 10,
lng: 80,
},
anchor: {
x: 0.5,
y: 1,
},
boundingRect: {
width: 10,
height: 20,
},
}
Example: A polyline with a width of 10px
, and bounds defined by four points (10, 10), (10, 20), (20, 10), (20, 20)
, can be described in two ways:
- Using two
IStandardOverlay
objects based on its bounds:
[{
position: {
lat: 10,
lng: 10,
},
anchor: {
x: 0.5,
y: 0.5,
},
boundingRect: {
width: 10,
height: 10,
},
}, {
position: {
lat: 20,
lng: 20,
},
anchor: {
x: 0.5,
y: 0.5,
},
boundingRect: {
width: 10,
height: 10,
},
}]
- Using a
IPolylineOverlay
:
{
polyline: {
points: [
{ lat: 10, lng: 10 },
{ lat: 10, lng: 20 },
{ lat: 20, lng: 10 },
{ lat: 20, lng: 20 },
],
width: 10,
}
}
Polygons follow a similar structure to polylines.
Example: A circle’s bounds form a rectangle, allowing it to be described either as an ICircleOverlay
or IStandardOverlay
:
{
circle: {
center: { lat: 10, lng: 10 },
radius: 10,
}
}
or
{
position: { lat: 10, lng: 10 },
anchor: { x: 0.5, y: 0.5 },
boundingRect: { width: 20, height: 20 },
}
For custom overlays, determine the position
, anchor
, and boundingRect
based on your specific requirements.
Projection and World Size
Since the Earth is spherical and maps are flat, projections are needed. Web maps commonly use the WebMercator projection, which is built into map-zoomtospan
with a default worldSize
of 512
.
The WebMercator projection maps the globe to a 0-1
square. The worldSize
determines the pixel size of this world at zoom level 0
. Each zoom level doubles the required pixel size to display the world. For instance, Leaflet’s default worldSize
is 256
.
If you are using a custom projection or world size, you can adjust the worldSize
or implement your own IProjection
.
Precision
Some map libraries support fractional zoom levels. If you want more precise zoom calculations, set precision
to a decimal value for better accuracy.
However, if your map library doesn't support fractional zoom levels, it's important to set precision
to 1
to ensure correct behavior.
Zoom Range
To limit the zoom level, use the zoomRange
parameter to specify a minimum and maximum zoom.
Fixed Map Center
If you need to keep the map center fixed, set the center
to a specific ILatLng
point.
License
This project is licensed under the MIT License. See the LICENSE file for more details.