Plotly Automatic Zooming For "mapbox Maps"
In the plotly website Map Configuration and Styling in Python is described how to automatically zoom a 'Geo map': import plotly.express as px fig = px.line_geo(lat=[0,15,20,35], l
Solution 1:
I wrote my own function along with other geojson
compatible functions in rv_geojson.py
It takes a list of locations and finds the geometric height and width of the rectangular binding box, good for using with mercator projection. It returns zoom and center.
defzoom_center(lons: tuple=None, lats: tuple=None, lonlats: tuple=None,
format: str='lonlat', projection: str='mercator',
width_to_height: float=2.0) -> (float, dict):
"""Finds optimal zoom and centering for a plotly mapbox.
Must be passed (lons & lats) or lonlats.
Temporary solution awaiting official implementation, see:
https://github.com/plotly/plotly.js/issues/3434
Parameters
--------
lons: tuple, optional, longitude component of each location
lats: tuple, optional, latitude component of each location
lonlats: tuple, optional, gps locations
format: str, specifying the order of longitud and latitude dimensions,
expected values: 'lonlat' or 'latlon', only used if passed lonlats
projection: str, only accepting 'mercator' at the moment,
raises `NotImplementedError` if other is passed
width_to_height: float, expected ratio of final graph's with to height,
used to select the constrained axis.
Returns
--------
zoom: float, from 1 to 20
center: dict, gps position with 'lon' and 'lat' keys
>>> print(zoom_center((-109.031387, -103.385460),
... (25.587101, 31.784620)))
(5.75, {'lon': -106.208423, 'lat': 28.685861})
"""if lons isNoneand lats isNone:
ifisinstance(lonlats, tuple):
lons, lats = zip(*lonlats)
else:
raise ValueError(
'Must pass lons & lats or lonlats'
)
maxlon, minlon = max(lons), min(lons)
maxlat, minlat = max(lats), min(lats)
center = {
'lon': round((maxlon + minlon) / 2, 6),
'lat': round((maxlat + minlat) / 2, 6)
}
# longitudinal range by zoom level (20 to 1)# in degrees, if centered at equator
lon_zoom_range = np.array([
0.0007, 0.0014, 0.003, 0.006, 0.012, 0.024, 0.048, 0.096,
0.192, 0.3712, 0.768, 1.536, 3.072, 6.144, 11.8784, 23.7568,
47.5136, 98.304, 190.0544, 360.0
])
if projection == 'mercator':
margin = 1.2
height = (maxlat - minlat) * margin * width_to_height
width = (maxlon - minlon) * margin
lon_zoom = np.interp(width , lon_zoom_range, range(20, 0, -1))
lat_zoom = np.interp(height, lon_zoom_range, range(20, 0, -1))
zoom = round(min(lon_zoom, lat_zoom), 2)
else:
raise NotImplementedError(
f'{projection} projection is not implemented'
)
return zoom, center
Use it as
zoom, center = zoom_center(
lons=[5, 10, 25, 30],
lats=[0, 15, 20, 35]
)
fig = px.scatter_mapbox(
filtered_df, lat="latitude", lon="longitude", color="ID",
zoom=zoom, center=center
) # Creates a "Mapbox map" figure
Solution 2:
The Mapbox API documentation shows that zooms are essentially on a log scale. So after some trial and error the following function worked for me:
max_bound = max(abs(x1-x2), abs(y1-y2)) * 111zoom = 11.5 - np.log(max_bound)
Notes:
- In this example, the xy (lon/lat) coordinates are in decimal degrees
- The 111 is a constant to convert decimal degrees to kilometers
- The value of 11.5 worked for my desired level of zoom/cropping, but I first experimented with values between 10-12
Solution 3:
Based on this question on plotly.com with the first version of the function below I came up with the following final solution:
defget_plotting_zoom_level_and_center_coordinates_from_lonlat_tuples(
longitudes=None, latitudes=None, lonlat_pairs=None):
"""Function documentation:\n
Basic framework adopted from Krichardson under the following thread:
https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/6
# NOTE:
# THIS IS A TEMPORARY SOLUTION UNTIL THE DASH TEAM IMPLEMENTS DYNAMIC ZOOM
# in their plotly-functions associated with mapbox, such as go.Densitymapbox() etc.
Returns the appropriate zoom-level for these plotly-mapbox-graphics along with
the center coordinate tuple of all provided coordinate tuples.
"""# Check whether the list hasn't already be prepared outside this functionif lonlat_pairs isNone:
# Check whether both latitudes and longitudes have been passed,# or if the list lenghts don't matchif ((latitudes isNoneor longitudes isNone)
or (len(latitudes) != len(longitudes))):
# Otherwise, return the default values of 0 zoom and the coordinate origin as center pointreturn0, (0, 0)
# Instantiate collator list for all coordinate-tuples
lonlat_pairs = [(longitudes[i], latitudes[i]) for i inrange(len(longitudes))]
# Get the boundary-box via the planar-module
b_box = planar.BoundingBox(lonlat_pairs)
# In case the resulting b_box is empty, return the default 0-values as wellif b_box.is_empty:
return0, (0, 0)
# Otherwise, get the area of the bounding box in order to calculate a zoom-level
area = b_box.height * b_box.width
# * 1D-linear interpolation with numpy:# - Pass the area as the only x-value and not as a list, in order to return a scalar as well# - The x-points "xp" should be in parts in comparable order of magnitude of the given area# - The zoom-levels are adapted to the areas, i.e. start with the smallest area possible of 0# which leads to the highest possible zoom value 20, and so forth decreasing with increasing areas# as these variables are antiproportional
zoom = np.interp(x=area,
xp=[0, 5**-10, 4**-10, 3**-10, 2**-10, 1**-10, 1**-5],
fp=[20, 17, 16, 15, 14, 7, 5])
# Finally, return the zoom level and the associated boundary-box center coordinatesreturn zoom, b_box.center
Post a Comment for "Plotly Automatic Zooming For "mapbox Maps""