VII. Веб-карта с Folium (II)#
и данные портала открытых данных Правительства Москвы …#
На этой неделе мы посмотрим, как выгружать геоданные с Портала Открытых данных Правительства Москвы, а также визуализировать на интерактивной карте точки с фотографиями
Импортируем библиотеки#
import geopandas as gpd
import requests
import pandas as pd
import folium
import base64
import os
1. DataMos API: никогда не было проще скачивать данные с data_mos…#
1.1 Определяем параметры для запроса#
# datamos_api = 'yourAPIcode'
data_set = 62883
url_data = f'https://apidata.mos.ru/v1/datasets/{data_set}/features?api_key={datamos_api}'
1.2 Отправляем запрос#
data_mos = requests.get(url = url_data )
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[4], line 1
----> 1 data_mos = requests.get(url = url_data )
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/requests/api.py:73, in get(url, params, **kwargs)
62 def get(url, params=None, **kwargs):
63 r"""Sends a GET request.
64
65 :param url: URL for the new :class:`Request` object.
(...)
70 :rtype: requests.Response
71 """
---> 73 return request("get", url, params=params, **kwargs)
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/requests/api.py:59, in request(method, url, **kwargs)
55 # By using the 'with' statement we are sure the session is closed, thus we
56 # avoid leaving sockets open which can trigger a ResourceWarning in some
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/requests/sessions.py:587, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
582 send_kwargs = {
583 "timeout": timeout,
584 "allow_redirects": allow_redirects,
585 }
586 send_kwargs.update(settings)
--> 587 resp = self.send(prep, **send_kwargs)
589 return resp
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/requests/sessions.py:701, in Session.send(self, request, **kwargs)
698 start = preferred_clock()
700 # Send the request
--> 701 r = adapter.send(request, **kwargs)
703 # Total elapsed time of the request (approximately)
704 elapsed = preferred_clock() - start
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/requests/adapters.py:489, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
487 try:
488 if not chunked:
--> 489 resp = conn.urlopen(
490 method=request.method,
491 url=url,
492 body=request.body,
493 headers=request.headers,
494 redirect=False,
495 assert_same_host=False,
496 preload_content=False,
497 decode_content=False,
498 retries=self.max_retries,
499 timeout=timeout,
500 )
502 # Send the request.
503 else:
504 if hasattr(conn, "proxy_pool"):
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/connectionpool.py:670, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
667 self._prepare_proxy(conn)
669 # Make the request on the httplib connection object.
--> 670 httplib_response = self._make_request(
671 conn,
672 method,
673 url,
674 timeout=timeout_obj,
675 body=body,
676 headers=headers,
677 chunked=chunked,
678 )
680 # If we're going to release the connection in ``finally:``, then
681 # the response doesn't need to know about the connection. Otherwise
682 # it will also try to release it and we'll have a double-release
683 # mess.
684 response_conn = conn if not release_conn else None
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/connectionpool.py:381, in HTTPConnectionPool._make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
379 # Trigger any extra validation we need to do.
380 try:
--> 381 self._validate_conn(conn)
382 except (SocketTimeout, BaseSSLError) as e:
383 # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.
384 self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/connectionpool.py:978, in HTTPSConnectionPool._validate_conn(self, conn)
976 # Force connect early to allow us to validate the connection.
977 if not getattr(conn, "sock", None): # AppEngine might not have `.sock`
--> 978 conn.connect()
980 if not conn.is_verified:
981 warnings.warn(
982 (
983 "Unverified HTTPS request is being made to host '%s'. "
(...)
988 InsecureRequestWarning,
989 )
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/connection.py:309, in HTTPSConnection.connect(self)
307 def connect(self):
308 # Add certificate verification
--> 309 conn = self._new_conn()
310 hostname = self.host
312 # Google App Engine's httplib does not define _tunnel_host
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/connection.py:159, in HTTPConnection._new_conn(self)
156 extra_kw["socket_options"] = self.socket_options
158 try:
--> 159 conn = connection.create_connection(
160 (self._dns_host, self.port), self.timeout, **extra_kw
161 )
163 except SocketTimeout:
164 raise ConnectTimeoutError(
165 self,
166 "Connection to %s timed out. (connect timeout=%s)"
167 % (self.host, self.timeout),
168 )
File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/urllib3/util/connection.py:74, in create_connection(address, timeout, source_address, socket_options)
72 if source_address:
73 sock.bind(source_address)
---> 74 sock.connect(sa)
75 return sock
77 except socket.error as e:
KeyboardInterrupt:
смотрим на статус запроса
data_mos.status_code
200
смотрим на ответ
data_mos.json()
{'features': [{'geometry': {'coordinates': [37.900526, 55.414307],
'type': 'Point'},
'properties': {'datasetId': 62883,
'rowId': None,
'attributes': {'is_deleted': 0,
'ID': 1,
'Name': 'Домодедово',
'Aeroexpress': 'есть',
'AeroexpressStation': {'global_id': 1508979300,
'value': 'Аэропорт Домодедово'},
'AeroexpressTerminal': 'есть',
'AeroexpressTicketWindow': 'есть',
'AeroexpressTicketAutomat': 'есть',
'AeroexpressWorkingHours': '05:30 - 23:30 (с Павелецкого вокзала), 06:00 - 23:30 (из аэропорта)',
'MCDStation': [],
'RailwayStation': [],
'Latitude_WGS84': '55.414307',
'Longitude_WGS84': '37.900526',
'global_id': 1272818040},
'releaseNumber': 41,
'versionNumber': 1},
'type': 'Feature'},
{'geometry': {'coordinates': [37.415713, 55.966771], 'type': 'Point'},
'properties': {'datasetId': 62883,
'rowId': None,
'attributes': {'is_deleted': 0,
'ID': 2,
'Name': 'Шереметьево',
'Aeroexpress': 'есть',
'AeroexpressStation': {'global_id': 1508983189,
'value': 'Аэропорт Шереметьево'},
'AeroexpressTerminal': 'есть',
'AeroexpressTicketWindow': 'есть',
'AeroexpressTicketAutomat': 'есть',
'AeroexpressWorkingHours': '5:15 - 00:31 (с Белорусского вокзала), 05:14 - 00:06 (из аэропорта)',
'MCDStation': [],
'RailwayStation': [],
'Latitude_WGS84': '55.966771',
'Longitude_WGS84': '37.415713',
'global_id': 1272818309},
'releaseNumber': 41,
'versionNumber': 1},
'type': 'Feature'},
{'geometry': {'coordinates': [37.286287, 55.605059], 'type': 'Point'},
'properties': {'datasetId': 62883,
'rowId': None,
'attributes': {'is_deleted': 0,
'ID': 3,
'Name': 'Внуково',
'Aeroexpress': 'нет',
'AeroexpressStation': [],
'AeroexpressTerminal': 'нет',
'AeroexpressTicketWindow': 'нет',
'AeroexpressTicketAutomat': 'нет',
'AeroexpressWorkingHours': '',
'MCDStation': [],
'RailwayStation': [],
'Latitude_WGS84': '55.605059',
'Longitude_WGS84': '37.286287',
'global_id': 1272818481},
'releaseNumber': 41,
'versionNumber': 1},
'type': 'Feature'},
{'geometry': {'coordinates': [37.510824, 55.502859], 'type': 'Point'},
'properties': {'datasetId': 62883,
'rowId': None,
'attributes': {'is_deleted': 0,
'ID': 4,
'Name': 'Остафьево',
'Aeroexpress': 'нет',
'AeroexpressStation': [],
'AeroexpressTerminal': 'нет',
'AeroexpressTicketWindow': 'нет',
'AeroexpressTicketAutomat': 'нет',
'AeroexpressWorkingHours': '',
'MCDStation': [],
'RailwayStation': [],
'Latitude_WGS84': '55.502859',
'Longitude_WGS84': '37.510824',
'global_id': 1272818826},
'releaseNumber': 41,
'versionNumber': 1},
'type': 'Feature'},
{'geometry': {'coordinates': [38.117812, 55.56155], 'type': 'Point'},
'properties': {'datasetId': 62883,
'rowId': None,
'attributes': {'is_deleted': 0,
'ID': 5,
'Name': 'Жуковский',
'Aeroexpress': 'нет',
'AeroexpressStation': [],
'AeroexpressTerminal': 'нет',
'AeroexpressTicketWindow': 'нет',
'AeroexpressTicketAutomat': 'нет',
'AeroexpressWorkingHours': '',
'MCDStation': [],
'RailwayStation': [],
'Latitude_WGS84': '55.561550',
'Longitude_WGS84': '38.117812',
'global_id': 1272818941},
'releaseNumber': 41,
'versionNumber': 1},
'type': 'Feature'}],
'type': 'FeatureCollection'}
1.3. Создаем GeoDataFrame#
dataGDF = gpd.GeoDataFrame.from_features(data_mos.json()["features"], crs="EPSG:4326")
dataGDF.head()
Loading ITables v2.4.2 from the init_notebook_mode cell...
(need help?) |
1.4 Разбиваем атрибуты на разные поля#
dataGDF_attributes = pd.DataFrame(dataGDF['attributes'].values.tolist(), index=dataGDF.index).applymap(str)
dataGDF_final = pd.concat([dataGDF, dataGDF_attributes], axis = 1).drop('attributes', axis = 1)
dataGDF_final.head()
/var/folders/ry/9bb7wrz54vq_kn2ytlj6ynzm0000gn/T/ipykernel_39923/1609880950.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
Loading ITables v2.4.2 from the init_notebook_mode cell...
(need help?) |
Посмотрим на объекты на карте
dataGDF_final.explore(tiles="cartodb positron")
Make this Notebook Trusted to load map: File -> Trust Notebook
1.5 Сохраняем данные#
#dataGDF_final.to_file('data_gdf.shp')
/var/folders/ry/9bb7wrz54vq_kn2ytlj6ynzm0000gn/T/ipykernel_39923/2206928343.py:1: UserWarning:
Column names longer than 10 characters will be truncated when saved to ESRI Shapefile.
2. Фотографии для объектов на интерактивной карте#
df = dataGDF_final
# Папка с фото
photo_folder = "photos"
# Создаём карту
m = folium.Map(location=[55.75, 37.6], zoom_start=9, tiles="cartodb positron")
# Обход строк GeoDataFrame
for _, row in df.iterrows():
lat = row.geometry.y
lon = row.geometry.x
name = row["Name"]
photo_filename = f"{name}.jpeg"
photo_path = os.path.join(photo_folder, photo_filename)
if os.path.exists(photo_path):
with open(photo_path, "rb") as f:
img_base64 = base64.b64encode(f.read()).decode("utf-8")
html = f'<h4>{name}</h4><img src="data:image/jpeg;base64,{img_base64}" width="300">'
else:
html = f"<h4>{name}</h4><p><i>Фото не найдено</i></p>"
iframe = folium.IFrame(html=html, width=310, height=250)
popup = folium.Popup(iframe, max_width=310)
folium.Marker(
location=[lat, lon],
popup=popup,
tooltip=name,
icon=folium.Icon(icon="plane", prefix="fa", color="blue")
).add_to(m)
m
Make this Notebook Trusted to load map: File -> Trust Notebook
3. Интерактивная таблица#
Создадим интерактинвную таблицу c помощью ITables на основе атрибутивной таблицы слоя
Импорт нужных библиотек / установка параметров
from itables import init_notebook_mode
init_notebook_mode(all_interactive=True)
from itables import show
Визуализация
df = dataGDF_final.drop(columns='geometry')
show(df, buttons=["copyHtml5", "csvHtml5", "excelHtml5"])
Loading ITables v2.4.2 from the init_notebook_mode cell...
(need help?) |