сюжетное автоматическое масштабирование для карт Mapbox

На веб-сайте plotly Настройка и стиль карты в Python описано, как автоматически масштабировать географическую карту:

import plotly.express as px

fig = px.line_geo(lat=[0,15,20,35], lon=[5,10,25,30])            # Creates a "Geo map" figure
fig.update_geos(fitbounds="locations")                           # Automatic Zooming !!!!
fig.show()

и это работает, более того, если я попытаюсь сделать то же самое на карте Mapbox, он не применит автоматическое масштабирование:

fig = px.scatter_mapbox(filtered_df, lat="latitude", lon="longitude", color="ID")  # Creates a "Mapbox map" figure
fig.update_layout(mapbox_style="open-street-map")
fig.update_geos(fitbounds="locations")                                             # Automatic Zooming not working!!!

Информации о том, как это сделать, нет в слоях карты Mapbox в Python.


person Antonio Ramírez    schedule 08.09.2020    source источник
comment
Можете ли вы включить filtered_df?   -  person Derek O    schedule 08.09.2020


Ответы (3)


Я написал свою собственную функцию вместе с другими geojson совместимыми функциями в rv_geojson.py < / а>

Он берет список местоположений и находит геометрическую высоту и ширину прямоугольной рамки для переплета, что удобно для использования с проекцией Меркатора. Возвращает масштаб и центр.

def zoom_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 is None and lats is None:
        if isinstance(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

Используйте это как

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

person RichieV    schedule 01.10.2020

В документации API Mapbox показано что масштабирование по сути в логарифмическом масштабе. Итак, после некоторых проб и ошибок у меня сработала следующая функция:

max_bound = max(abs(x1-x2), abs(y1-y2)) * 111
zoom = 11.5 - np.log(max_bound)

Примечания:

  • В этом примере координаты xy (долгота / широта) указаны в десятичных градусах.
  • 111 - это константа для преобразования десятичных градусов в километры.
  • Значение 11,5 работало для моего желаемого уровня масштабирования / кадрирования, но сначала я экспериментировал со значениями между 10-12.
person Carl Cervone    schedule 27.11.2020

На основании этого вопроса на сайте plotly.com с первая версия функции ниже Я придумал следующее окончательное решение:

def get_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 function
    if lonlat_pairs is None:
        # Check whether both latitudes and longitudes have been passed,
        # or if the list lenghts don't match
        if ((latitudes is None or longitudes is None)
                or (len(latitudes) != len(longitudes))):
            # Otherwise, return the default values of 0 zoom and the coordinate origin as center point
            return 0, (0, 0)

        # Instantiate collator list for all coordinate-tuples
        lonlat_pairs = [(longitudes[i], latitudes[i]) for i in range(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 well
    if b_box.is_empty:
        return 0, (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 coordinates
    return zoom, b_box.center
person Andreas L.    schedule 18.12.2020