{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#!pip3 install geopandas \n", "#!pip3 install geopy\n", "#!pip3 install folium\n", "#!pip3 install branca" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Часть 1. Прямой геокодинг адресов из базы, подготовка датасета для дальнейшей визуализации на карте" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 1.1 geopy - основная билбиотека, которая будет использоваться в проекте для прямого геокодирования с помощью API Yandex" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import time\n", "from pprint import pprint\n", "\n", "from geopy.geocoders import ArcGIS,Yandex\n", "from geopy.extra.rate_limiter import RateLimiter\n", "\n", "# instantiate a new Nominatim client\n", "app = ArcGIS(user_agent=\"tutorial\")\n", "\n", "import warnings\n", "warnings.filterwarnings('ignore')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 1.2 Так как прямое геокодирование большого количества адресов достаточно долгая процедура, в качестве теста, добавим оповещение в Telegramm (это позволит в цикле каждые, например, 10 000 адресов, отправлять сообщение с статусом работы)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import requests\n", "\n", "# telegram url\n", "bot_token = \"TelegramBotApiToken\"\n", "chat_id = \"TelegramChatID\"\n", "\n", "def send_mess(text):\n", " params = {'chat_id':chat_id, 'text': text}\n", " response = requests.post('https://api.telegram.org/bot'+ bot_token + '/sendMessage', data=params)\n", " return response\n", "send_mess(\"Hello world!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 1.3 Загрузка первичной базы с адресами 500 строк (Публичный источник: Адресный справочник Челябинска - https://t.domspravka.com/, дополнительно поля name и birth изменены)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "data = pd.read_csv(\"D:\\\\data_test.csv\", sep=\";\",encoding=\"windows-1251\",index_col=False)#cp866" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebirthadresskvexample_feature
0ЮЛИЯ10.10.1983454080 ПР-КТ. ЛЕНИНА, д. 83-example_email1983@gmail.com
1СЕРГЕЙ06.09.1986454080 ПР-КТ. ЛЕНИНА, д. 83-example_email1986@gmail.com
2ЕЛЕНА06.03.1977454080 ПР-КТ. ЛЕНИНА, д. 83/А1example_email1977@gmail.com
\n", "
" ], "text/plain": [ " name birth adress kv \\\n", "0 ЮЛИЯ 10.10.1983 454080 ПР-КТ. ЛЕНИНА, д. 83 - \n", "1 СЕРГЕЙ 06.09.1986 454080 ПР-КТ. ЛЕНИНА, д. 83 - \n", "2 ЕЛЕНА 06.03.1977 454080 ПР-КТ. ЛЕНИНА, д. 83/А 1 \n", "\n", " example_feature \n", "0 example_email1983@gmail.com \n", "1 example_email1986@gmail.com \n", "2 example_email1977@gmail.com " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.head(3)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "RangeIndex: 499 entries, 0 to 498\n", "Data columns (total 5 columns):\n", " # Column Non-Null Count Dtype \n", "--- ------ -------------- ----- \n", " 0 name 499 non-null object\n", " 1 birth 499 non-null object\n", " 2 adress 499 non-null object\n", " 3 kv 499 non-null object\n", " 4 example_feature 251 non-null object\n", "dtypes: object(5)\n", "memory usage: 19.6+ KB\n" ] } ], "source": [ "data.info()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "#Очистка от аномалий и форматирование полей в датафрейме\n", "data['name'] = data['name'].astype(str)\n", "data['name'] = data['name'].str.replace('/n','')\n", "data['adress'] = data['adress'].str.replace('/n','')\n", "#data = data.infer_objects()\n", "\n", "#Для более точного прямого геокодирования добавим значения страны, области и города в строку адреса\n", "data['adress']= \"РОССИЯ, Челябинская область, г. Челябинск\"+ \", \" +data['adress']" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'РОССИЯ, Челябинская область, г. Челябинск, 454080 ПР-КТ. ЛЕНИНА, д. 83'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Пример дополненного адреса\n", "data['adress'][1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 1.4 Подключение Геокодер API Яндекс.Карт и проверка работы прямого геокодирования Яндекс API (https://yandex.ru/dev/maps/geocoder/)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Location(проспект Ленина, 83, Челябинск, Россия, (55.158637, 61.371382, 0.0))\n" ] } ], "source": [ "#https://pikabu.ru/story/yandeks_baunti_ili_klyuch_za_million_besplatno_7737687 может помочь\n", "\n", "app = Yandex(user_agent=\"tutorial\",api_key=\"ApiKey\")\n", "\n", "location = app.geocode(\"454080 ПР-КТ. ЛЕНИНА, д. 83\") #Тест работы прямого геокодинга\n", "pprint(location)\n", "\n", "# min_delay_seconds - Задержка на вызов (таким образом исключаем ошибки при слишком большой скорости запросов к геокодеру)\n", "geocode = RateLimiter(app.geocode, min_delay_seconds=0.3)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "#Создаем новый столбец для записи совокупных данных геокодера\n", "data['location'] = 0\n", "data['location'] = data['location'].astype(str)\n", "\n", "#Создаем новые столбцы для конкретных числовых значений определенных координат\n", "data['latitude'] = 0\n", "data['longitude'] = 0\n", "data['altitude'] = 0\n", "data['point'] = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 1.5 Применение геокодера Яндекс для получения координат адресов из загруженного списка" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Прямой геокодинг\n", "for i in range(1,len(data)):\n", " if (data['location'][i-1]==data['location'][i] and data['location'][i-1]!='0'):#Проверяем не совпадает ли следующая строка с предыдущей, если одинаковые, то просто приравниваем их друг к другу\n", " data['location'][i-1]=data['location'][i]\n", " else:\n", " data['location'][i:(i+1)] = data['adress'][i:(i+1)].apply(geocode) #Цикличное геокодирование \n", " data['point'][i:(i+1)] = data['location'][i:(i+1)].apply(lambda loc: tuple(loc.point) if loc else (0.0,0.0,0.0)) #None)\n", " #if (i % 10==0): \n", " # print (data[['location']][(i-10):i])\n", "\n", " if (i % 100==0):#Актуально для 10 000 и более, 100 взято в качестве теста\n", " send_mess(\"(\"+ str(i) + \") строк обработано\") #Сообщение с статусом в Telegram чат" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> # 1.6 Заполнение полей конкретными числовыми значениями координат (разделение столбца 'point' на столбцы 'latitude', 'longitude','altitude')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "scrolled": true }, "outputs": [], "source": [ "data['latitude']=data['latitude'].astype(float)\n", "data['longitude']=data['longitude'].astype(float)\n", "data['altitude']=data['altitude'].astype(float)\n", "\n", "a = data['point'].tolist()# Формирование из столбца датафрейма 'point' списка с значениями\n", "b = []\n", "z = 0\n", "\n", "for n in range(1,len(data)):\n", " b.append(str(a[n]).split(','))# Дробим список с значениями 'point' по запятым и записываем части в отдельные столбцы\n", "\n", " data['latitude'][n]=float(b[n-1][0][1:50])\n", " data['longitude'][n]=float(b[n-1][1][1:50])\n", " data['altitude'][n]=float(b[n-1][2][1:3])\n", " \n", " #print(n,data['latitude'][n],data['longitude'][n])" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebirthadresskvexample_featurelocationlatitudelongitudealtitudepoint
0ЮЛИЯ10.10.1983РОССИЯ, Челябинская область, г. Челябинск, 454...-example_email1983@gmail.com00.0000000.0000000.00
1СЕРГЕЙ06.09.1986РОССИЯ, Челябинская область, г. Челябинск, 454...-example_email1986@gmail.com(проспект Ленина, 83, Челябинск, Россия, (55.1...55.15863761.3713820.0(55.158637, 61.371382, 0.0)
2ЕЛЕНА06.03.1977РОССИЯ, Челябинская область, г. Челябинск, 454...1example_email1977@gmail.com(проспект Ленина, 83А, Челябинск, Россия, (55....55.15861661.3722350.0(55.158616, 61.372235, 0.0)
3САША08.04.1994РОССИЯ, Челябинская область, г. Челябинск, 454...2example_email1994@gmail.com(проспект Ленина, 83А, Челябинск, Россия, (55....55.15861661.3722350.0(55.158616, 61.372235, 0.0)
4ГАЛИНА15.01.1947РОССИЯ, Челябинская область, г. Челябинск, 454...2NaN(проспект Ленина, 83А, Челябинск, Россия, (55....55.15861661.3722350.0(55.158616, 61.372235, 0.0)
\n", "
" ], "text/plain": [ " name birth adress kv \\\n", "0 ЮЛИЯ 10.10.1983 РОССИЯ, Челябинская область, г. Челябинск, 454... - \n", "1 СЕРГЕЙ 06.09.1986 РОССИЯ, Челябинская область, г. Челябинск, 454... - \n", "2 ЕЛЕНА 06.03.1977 РОССИЯ, Челябинская область, г. Челябинск, 454... 1 \n", "3 САША 08.04.1994 РОССИЯ, Челябинская область, г. Челябинск, 454... 2 \n", "4 ГАЛИНА 15.01.1947 РОССИЯ, Челябинская область, г. Челябинск, 454... 2 \n", "\n", " example_feature \\\n", "0 example_email1983@gmail.com \n", "1 example_email1986@gmail.com \n", "2 example_email1977@gmail.com \n", "3 example_email1994@gmail.com \n", "4 NaN \n", "\n", " location latitude longitude \\\n", "0 0 0.000000 0.000000 \n", "1 (проспект Ленина, 83, Челябинск, Россия, (55.1... 55.158637 61.371382 \n", "2 (проспект Ленина, 83А, Челябинск, Россия, (55.... 55.158616 61.372235 \n", "3 (проспект Ленина, 83А, Челябинск, Россия, (55.... 55.158616 61.372235 \n", "4 (проспект Ленина, 83А, Челябинск, Россия, (55.... 55.158616 61.372235 \n", "\n", " altitude point \n", "0 0.0 0 \n", "1 0.0 (55.158637, 61.371382, 0.0) \n", "2 0.0 (55.158616, 61.372235, 0.0) \n", "3 0.0 (55.158616, 61.372235, 0.0) \n", "4 0.0 (55.158616, 61.372235, 0.0) " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.head()# Итоговый датафрейм с данными" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Часть 2. Отрисовка полученных координат на карте с группами маркеров" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.1 folium - основная билбиотека, которая будет использоваться в проекте для создания интерактивных карт" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "import folium\n", "from folium.plugins import FastMarkerCluster\n", "from folium.plugins import MarkerCluster\n", "from folium.plugins import Search\n", "from folium import FeatureGroup\n", "\n", "import branca\n", "\n", "from datetime import timedelta, datetime" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# 1 - Добавляем столбец с именем и номером квартиры (для дальнейшего облегчения поиска по фильтру на карте)\n", "# 2 - Сортируем датафрейм по № квартиры и адресу (для дальнейшего удобного отображения марекров в составе кластера маркеров)\n", "data.sort_values(by=['kv','adress'])\n", "data['name_kv'] = data['name']+\" //\"+data['kv']\n", "data['emailkv'] = data['example_feature']+\" //\"+data['kv']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.2 Создание функции для формирования HTML таблиц с значениями из датафрейма (при клике по значку на карте откроется данная таблица с детальной информацией. Формат приведен для примера, реально применение всей палитры возможностей языка HTML)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def fancy_html(row):\n", " i = row\n", " \n", " FIO = data['name'].iloc[i] \n", " age = data['birth'].iloc[i]\n", " adress = data['adress'].iloc[i] \n", " adress_geo = data['location'].iloc[i]\n", " kv = data['kv'].iloc[i]\n", " email = data['example_feature'].iloc[i]\n", " \n", " left_col_colour = \"#2A799C\"\n", " right_col_colour = \"#C5DCE7\"\n", " \n", " # Простая HTML таблица с данными из датафрейма\n", " html = \"\"\"\n", "\n", "\n", " \n", "\n", "\n", "\n", "\n", "\"\"\".format(FIO) + \"\"\"\n", "\n", "\n", "\n", "\n", "\"\"\".format(age) + \"\"\"\n", "\n", "\n", "\n", "\n", "\"\"\".format(kv) + \"\"\"\n", "\n", "\n", "\n", "\n", "\"\"\".format(email) + \"\"\"\n", "\n", "\n", "\n", "\n", "\"\"\".format(adress) + \"\"\"\n", "\n", "\n", "\n", "\n", "\"\"\".format(adress_geo) + \"\"\"\n", "\n", "\n", "\n", "\n", "
ФИО жильца{}
Год рождения{}
Номер помещения{}
E-mail{}
Адрес(полный){}
Адрес по геодекодингу{}
\n", "\n", "\"\"\"\n", " return html" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.3 Создание функций для динмаической окраски значков на карте по заданным условиям (дата рождения и есть ли e-mail)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "def color_change(age):\n", " if(datetime(2000, 1, 1) < age):\n", " return('darkgreen')\n", " elif(datetime(1980, 1, 1) <= age < datetime(2000, 1, 1)):\n", " return('green')\n", " elif(datetime(1960, 1, 1) <= age < datetime(1980, 1, 1)):\n", " return('orange')\n", " elif(datetime(1930, 1, 1) <= age < datetime(1960, 1, 1)):\n", " return('red')\n", " elif(age > datetime(1930, 1, 1)):\n", " return('darkred')\n", " else:\n", " return('gray')\n", "\n", "def back_color_change(email_flag):\n", " if(email_flag == False):\n", " return('#FFD700')\n", " else:\n", " return('#FFFFE0')\n", " #7FFFD4 - циан\n", " #FFD700 - золото" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "lat = data['latitude']\n", "lon = data['longitude']\n", "location1 = data['adress']\n", "location2 = data['location']\n", "age = pd.to_datetime(data['birth']) # Преобразование столбца с датой рождения в формат datetime (для сравнения с другими датами)\n", "#data['birth'] = data['birth'].dt.strftime('%Y-%m-%d') # Преобразование столбца с датой рождения в формат str (для отрисовки на карте)\n", "\n", "email=data['example_feature'].copy()\n", "email=email.isnull()\n", "\n", "if (z==0): #Конструкция нужна, чтобы функционировала предыдущая проверка isnull, т.к. после замены nan на \"\" значение перестанет быть nan\n", " data['example_feature'] = data['example_feature'].fillna('') # Убираем nan из отображения на карте\n", " z=1\n", "else:\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.4 Преобразование данных датафрейма в geojson для последующего использования в качестве входных данных построения карты" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "import requests, json\n", "\n", "def df_to_geojson(df, properties, lat='latitude', lon='longitude'):\n", " # create a new python dict to contain our geojson data, using geojson format\n", " geojson = {'type':'FeatureCollection', 'features':[]}\n", "\n", " # loop through each row in the dataframe and convert each row to geojson format\n", " for _, row in df.iterrows():\n", " # create a feature template to fill in\n", " feature = {'type':'Feature',\n", " 'properties':{},\n", " 'geometry':{'type':'Point',\n", " 'coordinates':[]}}\n", "\n", " # fill in the coordinates\n", " feature['geometry']['coordinates'] = [row[lon],row[lat]]\n", "\n", " # for each column, get the value and add it as a new feature property\n", " for prop in properties:\n", " feature['properties'][prop] = row[prop]\n", " \n", " # add this feature (aka, converted dataframe row) to the list of features inside our dict\n", " geojson['features'].append(feature)\n", " \n", " return geojson\n", "\n", "geolist=data.columns.tolist()\n", "geolist.remove('location')\n", "geolist.remove('latitude')\n", "geolist.remove('longitude')\n", "\n", "cols=geolist\n", "geojson = df_to_geojson(data, cols)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.5 Создание самого объекта map библиотеки folium, инициализация начальных парметров (приближение/стиль карт) и фильтров для поиска по ключевым значениям" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map1 = folium.Map(\n", " location=[55.159563, 61.375695], # Начальная позиция карты\n", " tiles='OpenStreetMap',#\"Mapbox bright\" - Стиль карты\n", " zoom_start=16, # Начальное приближение карты\n", " control_scale=False,\n", " prefer_canvas=True\n", ")\n", "\n", "marker_cluster = MarkerCluster(name=\"Markers_GEO\",overlay=True,show=False).add_to(map1)\n", "marker_cluster2 = MarkerCluster(name=\"Markers\",overlay=True).add_to(map1)\n", "\n", "geo = folium.GeoJson(\n", " geojson,\n", " name=\"adress\",\n", " show=True,\n", " tooltip=folium.GeoJsonTooltip(\n", " fields=[\"adress\", \"name_kv\",\"emailkv\"], \n", " aliases=[\"adress\", \"name_kv\",\"emailkv\"], localize=True\n", " ),\n", ").add_to(marker_cluster)\n", "\n", "#Добавление поискового фильтра по адресу\n", "adresssearch = Search(\n", " layer=marker_cluster,\n", " geom_type=\"Point\",\n", " placeholder=\"Поиск по адресу\",\n", " collapsed=True,\n", " search_label=\"adress\",\n", " search_zoom=15\n", ").add_to(map1)\n", "\n", "#Добавление поискового фильтра по ФИО\n", "FIOsearch = Search(\n", " layer=marker_cluster,\n", " geom_type=\"Point\",\n", " placeholder=\"Поиск по ФИО\",\n", " collapsed=True,\n", " search_label=\"name_kv\",\n", " search_zoom=15\n", ").add_to(map1)\n", "\n", "#Добавление поискового фильтра по e-mail\n", "Email_search = Search(\n", " layer=marker_cluster,\n", " geom_type=\"Point\",\n", " placeholder=\"Поиск по E-mail\",\n", " collapsed=True,\n", " search_label=\"emailkv\",\n", " search_zoom=15\n", ").add_to(map1)\n", "\n", "folium.LayerControl().add_to(map1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.6 Отрисовываем на карте все элементы из geojson, в соответствии с заданными параметрами (используя функции color_change и back_color_change для цветовой индикации)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "100\n", "200\n", "300\n", "400\n" ] } ], "source": [ "for p in range(0,len(data)):\n", " html = fancy_html(p) #Получаем данные из HTML для попапов\n", " \n", " iframe = branca.element.IFrame(html=html,width=330,height=270) #Размер объекта попапа\n", " popup = folium.Popup(iframe,parse_html=True) \n", " \n", " folium.map.Marker(location=[lat.iloc[p], lon.iloc[p]],\n", " icon=folium.plugins.BeautifyIcon(border_color = color_change(age.iloc[p]),# Цвет границы (в соответствии с датой рождения)\n", " border_width = 1, #Ширина границы \n", " background_color = back_color_change(email.iloc[p]), #Цвет подложки значка (если есть email, то золотой) \n", " text_color = 'dark', #Цвет текста внутри значка\n", " number = data[\"kv\"].iloc[p], # Текст внутри значка\n", " icon_shape = 'marker'), # Формат значка\n", " popup=popup, # Всплывающий элемент при нажатии на значок\n", " tooltip = '{0}
{1}'.format(data[\"name\"].iloc[p], data[\"example_feature\"].iloc[p])# Всплывающий элемент при наведении мышкой на значок\n", " ).add_to(marker_cluster2)\n", " if (p % 100==0): \n", " print(p) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ## 2.7 Финальная визуализация карты" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map1" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "map1.save(\"Yours_path_here.html\")" ] } ], "metadata": { "celltoolbar": "Необработанный формат ячейки", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }