team-10/venv/Lib/site-packages/streamlit/elements/map.py

209 lines
6 KiB
Python
Raw Permalink Normal View History

2025-08-02 02:00:33 +02:00
# Copyright 2018-2022 Streamlit Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A wrapper for simple PyDeck scatter charts."""
import copy
import json
from typing import Any, Dict, cast
import pandas as pd
import streamlit
import streamlit.elements.deck_gl_json_chart as deck_gl_json_chart
from streamlit.errors import StreamlitAPIException
from streamlit.proto.DeckGlJsonChart_pb2 import DeckGlJsonChart as DeckGlJsonChartProto
class MapMixin:
def map(self, data=None, zoom=None, use_container_width=True):
"""Display a map with points on it.
This is a wrapper around st.pydeck_chart to quickly create scatterplot
charts on top of a map, with auto-centering and auto-zoom.
When using this command, we advise all users to use a personal Mapbox
token. This ensures the map tiles used in this chart are more
robust. You can do this with the mapbox.token config option.
To get a token for yourself, create an account at
https://mapbox.com. It's free! (for moderate usage levels). For more
info on how to set config options, see
https://docs.streamlit.io/library/advanced-features/configuration#set-configuration-options
Parameters
----------
data : pandas.DataFrame, pandas.Styler, numpy.ndarray, Iterable, dict,
or None
The data to be plotted. Must have columns called 'lat', 'lon',
'latitude', or 'longitude'.
zoom : int
Zoom level as specified in
https://wiki.openstreetmap.org/wiki/Zoom_levels
Example
-------
>>> import streamlit as st
>>> import pandas as pd
>>> import numpy as np
>>>
>>> df = pd.DataFrame(
... np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
... columns=['lat', 'lon'])
>>>
>>> st.map(df)
.. output::
https://doc-map.streamlitapp.com/
height: 650px
"""
map_proto = DeckGlJsonChartProto()
map_proto.json = to_deckgl_json(data, zoom)
map_proto.use_container_width = use_container_width
return self.dg._enqueue("deck_gl_json_chart", map_proto)
@property
def dg(self) -> "streamlit.delta_generator.DeltaGenerator":
"""Get our DeltaGenerator."""
return cast("streamlit.delta_generator.DeltaGenerator", self)
# Map used as the basis for st.map.
_DEFAULT_MAP = dict(deck_gl_json_chart.EMPTY_MAP) # type: Dict[str, Any]
# Other default parameters for st.map.
_DEFAULT_COLOR = [200, 30, 0, 160]
_DEFAULT_ZOOM_LEVEL = 12
_ZOOM_LEVELS = [
360,
180,
90,
45,
22.5,
11.25,
5.625,
2.813,
1.406,
0.703,
0.352,
0.176,
0.088,
0.044,
0.022,
0.011,
0.005,
0.003,
0.001,
0.0005,
0.00025,
]
def _get_zoom_level(distance):
"""Get the zoom level for a given distance in degrees.
See https://wiki.openstreetmap.org/wiki/Zoom_levels for reference.
Parameters
----------
distance : float
How many degrees of longitude should fit in the map.
Returns
-------
int
The zoom level, from 0 to 20.
"""
# For small number of points the default zoom level will be used.
if distance < _ZOOM_LEVELS[-1]:
return _DEFAULT_ZOOM_LEVEL
for i in range(len(_ZOOM_LEVELS) - 1):
if _ZOOM_LEVELS[i + 1] < distance <= _ZOOM_LEVELS[i]:
return i
def to_deckgl_json(data, zoom):
if data is None or data.empty:
return json.dumps(_DEFAULT_MAP)
if "lat" in data:
lat = "lat"
elif "latitude" in data:
lat = "latitude"
else:
raise StreamlitAPIException(
'Map data must contain a column named "latitude" or "lat".'
)
if "lon" in data:
lon = "lon"
elif "longitude" in data:
lon = "longitude"
else:
raise StreamlitAPIException(
'Map data must contain a column called "longitude" or "lon".'
)
if data[lon].isnull().values.any() or data[lat].isnull().values.any():
raise StreamlitAPIException("Latitude and longitude data must be numeric.")
data = pd.DataFrame(data)
min_lat = data[lat].min()
max_lat = data[lat].max()
min_lon = data[lon].min()
max_lon = data[lon].max()
center_lat = (max_lat + min_lat) / 2.0
center_lon = (max_lon + min_lon) / 2.0
range_lon = abs(max_lon - min_lon)
range_lat = abs(max_lat - min_lat)
if zoom == None:
if range_lon > range_lat:
longitude_distance = range_lon
else:
longitude_distance = range_lat
zoom = _get_zoom_level(longitude_distance)
# "+1" because itertuples includes the row index.
lon_col_index = data.columns.get_loc(lon) + 1
lat_col_index = data.columns.get_loc(lat) + 1
final_data = []
for row in data.itertuples():
final_data.append(
{"lon": float(row[lon_col_index]), "lat": float(row[lat_col_index])}
)
default = copy.deepcopy(_DEFAULT_MAP)
default["initialViewState"]["latitude"] = center_lat
default["initialViewState"]["longitude"] = center_lon
default["initialViewState"]["zoom"] = zoom
default["layers"] = [
{
"@@type": "ScatterplotLayer",
"getPosition": "@@=[lon, lat]",
"getRadius": 10,
"radiusScale": 10,
"radiusMinPixels": 3,
"getFillColor": _DEFAULT_COLOR,
"data": final_data,
}
]
return json.dumps(default)