#!/usr/bin/env python # coding: utf-8 # In[1]: #!pip3 install geopandas #!pip3 install geopy #!pip3 install folium #!pip3 install branca # # Часть 1. Прямой геокодинг адресов из базы, подготовка датасета для дальнейшей визуализации на карте # > ## 1.1 geopy - основная билбиотека, которая будет использоваться в проекте для прямого геокодирования с помощью API Yandex # In[2]: import numpy as np import pandas as pd import time from pprint import pprint from geopy.geocoders import ArcGIS,Yandex from geopy.extra.rate_limiter import RateLimiter # instantiate a new Nominatim client app = ArcGIS(user_agent="tutorial") import warnings warnings.filterwarnings('ignore') # > ## 1.2 Так как прямое геокодирование большого количества адресов достаточно долгая процедура, в качестве теста, добавим оповещение в Telegramm (это позволит в цикле каждые, например, 10 000 адресов, отправлять сообщение с статусом работы) # In[3]: import requests # telegram url bot_token = "TelegramBotApiToken" chat_id = "TelegramChatID" def send_mess(text): params = {'chat_id':chat_id, 'text': text} response = requests.post('https://api.telegram.org/bot'+ bot_token + '/sendMessage', data=params) return response send_mess("Hello world!") # > ## 1.3 Загрузка первичной базы с адресами 500 строк (Публичный источник: Адресный справочник Челябинска - https://t.domspravka.com/, дополнительно поля name и birth изменены) # In[4]: data = pd.read_csv("D:\\data_test.csv", sep=";",encoding="windows-1251",index_col=False)#cp866 # In[5]: data.head(3) # In[6]: data.info() # In[7]: #Очистка от аномалий и форматирование полей в датафрейме data['name'] = data['name'].astype(str) data['name'] = data['name'].str.replace('/n','') data['adress'] = data['adress'].str.replace('/n','') #data = data.infer_objects() #Для более точного прямого геокодирования добавим значения страны, области и города в строку адреса data['adress']= "РОССИЯ, Челябинская область, г. Челябинск"+ ", " +data['adress'] # In[8]: #Пример дополненного адреса data['adress'][1] # > ## 1.4 Подключение Геокодер API Яндекс.Карт и проверка работы прямого геокодирования Яндекс API (https://yandex.ru/dev/maps/geocoder/) # In[9]: #https://pikabu.ru/story/yandeks_baunti_ili_klyuch_za_million_besplatno_7737687 может помочь app = Yandex(user_agent="tutorial",api_key="ApiKey") location = app.geocode("454080 ПР-КТ. ЛЕНИНА, д. 83") #Тест работы прямого геокодинга pprint(location) # min_delay_seconds - Задержка на вызов (таким образом исключаем ошибки при слишком большой скорости запросов к геокодеру) geocode = RateLimiter(app.geocode, min_delay_seconds=0.3) # In[10]: #Создаем новый столбец для записи совокупных данных геокодера data['location'] = 0 data['location'] = data['location'].astype(str) #Создаем новые столбцы для конкретных числовых значений определенных координат data['latitude'] = 0 data['longitude'] = 0 data['altitude'] = 0 data['point'] = 0 # > ## 1.5 Применение геокодера Яндекс для получения координат адресов из загруженного списка # In[11]: # Прямой геокодинг for i in range(1,len(data)): if (data['location'][i-1]==data['location'][i] and data['location'][i-1]!='0'):#Проверяем не совпадает ли следующая строка с предыдущей, если одинаковые, то просто приравниваем их друг к другу data['location'][i-1]=data['location'][i] else: data['location'][i:(i+1)] = data['adress'][i:(i+1)].apply(geocode) #Цикличное геокодирование 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) #if (i % 10==0): # print (data[['location']][(i-10):i]) if (i % 100==0):#Актуально для 10 000 и более, 100 взято в качестве теста send_mess("("+ str(i) + ") строк обработано") #Сообщение с статусом в Telegram чат # > # 1.6 Заполнение полей конкретными числовыми значениями координат (разделение столбца 'point' на столбцы 'latitude', 'longitude','altitude') # In[12]: data['latitude']=data['latitude'].astype(float) data['longitude']=data['longitude'].astype(float) data['altitude']=data['altitude'].astype(float) a = data['point'].tolist()# Формирование из столбца датафрейма 'point' списка с значениями b = [] z = 0 for n in range(1,len(data)): b.append(str(a[n]).split(','))# Дробим список с значениями 'point' по запятым и записываем части в отдельные столбцы data['latitude'][n]=float(b[n-1][0][1:50]) data['longitude'][n]=float(b[n-1][1][1:50]) data['altitude'][n]=float(b[n-1][2][1:3]) #print(n,data['latitude'][n],data['longitude'][n]) # In[13]: data.head()# Итоговый датафрейм с данными # # Часть 2. Отрисовка полученных координат на карте с группами маркеров # > ## 2.1 folium - основная билбиотека, которая будет использоваться в проекте для создания интерактивных карт # In[14]: import folium from folium.plugins import FastMarkerCluster from folium.plugins import MarkerCluster from folium.plugins import Search from folium import FeatureGroup import branca from datetime import timedelta, datetime # In[15]: # 1 - Добавляем столбец с именем и номером квартиры (для дальнейшего облегчения поиска по фильтру на карте) # 2 - Сортируем датафрейм по № квартиры и адресу (для дальнейшего удобного отображения марекров в составе кластера маркеров) data.sort_values(by=['kv','adress']) data['name_kv'] = data['name']+" //"+data['kv'] data['emailkv'] = data['example_feature']+" //"+data['kv'] # > ## 2.2 Создание функции для формирования HTML таблиц с значениями из датафрейма (при клике по значку на карте откроется данная таблица с детальной информацией. Формат приведен для примера, реально применение всей палитры возможностей языка HTML) # In[16]: def fancy_html(row): i = row FIO = data['name'].iloc[i] age = data['birth'].iloc[i] adress = data['adress'].iloc[i] adress_geo = data['location'].iloc[i] kv = data['kv'].iloc[i] email = data['example_feature'].iloc[i] left_col_colour = "#2A799C" right_col_colour = "#C5DCE7" # Простая HTML таблица с данными из датафрейма html = """ """.format(FIO) + """ """.format(age) + """ """.format(kv) + """ """.format(email) + """ """.format(adress) + """ """.format(adress_geo) + """
ФИО жильца {}
Год рождения {}
Номер помещения {}
E-mail {}
Адрес(полный) {}
Адрес по геодекодингу {}
""" return html # > ## 2.3 Создание функций для динмаической окраски значков на карте по заданным условиям (дата рождения и есть ли e-mail) # In[17]: def color_change(age): if(datetime(2000, 1, 1) < age): return('darkgreen') elif(datetime(1980, 1, 1) <= age < datetime(2000, 1, 1)): return('green') elif(datetime(1960, 1, 1) <= age < datetime(1980, 1, 1)): return('orange') elif(datetime(1930, 1, 1) <= age < datetime(1960, 1, 1)): return('red') elif(age > datetime(1930, 1, 1)): return('darkred') else: return('gray') def back_color_change(email_flag): if(email_flag == False): return('#FFD700') else: return('#FFFFE0') #7FFFD4 - циан #FFD700 - золото # In[18]: lat = data['latitude'] lon = data['longitude'] location1 = data['adress'] location2 = data['location'] age = pd.to_datetime(data['birth']) # Преобразование столбца с датой рождения в формат datetime (для сравнения с другими датами) #data['birth'] = data['birth'].dt.strftime('%Y-%m-%d') # Преобразование столбца с датой рождения в формат str (для отрисовки на карте) email=data['example_feature'].copy() email=email.isnull() if (z==0): #Конструкция нужна, чтобы функционировала предыдущая проверка isnull, т.к. после замены nan на "" значение перестанет быть nan data['example_feature'] = data['example_feature'].fillna('') # Убираем nan из отображения на карте z=1 else: pass # > ## 2.4 Преобразование данных датафрейма в geojson для последующего использования в качестве входных данных построения карты # In[19]: import requests, json def df_to_geojson(df, properties, lat='latitude', lon='longitude'): # create a new python dict to contain our geojson data, using geojson format geojson = {'type':'FeatureCollection', 'features':[]} # loop through each row in the dataframe and convert each row to geojson format for _, row in df.iterrows(): # create a feature template to fill in feature = {'type':'Feature', 'properties':{}, 'geometry':{'type':'Point', 'coordinates':[]}} # fill in the coordinates feature['geometry']['coordinates'] = [row[lon],row[lat]] # for each column, get the value and add it as a new feature property for prop in properties: feature['properties'][prop] = row[prop] # add this feature (aka, converted dataframe row) to the list of features inside our dict geojson['features'].append(feature) return geojson geolist=data.columns.tolist() geolist.remove('location') geolist.remove('latitude') geolist.remove('longitude') cols=geolist geojson = df_to_geojson(data, cols) # > ## 2.5 Создание самого объекта map библиотеки folium, инициализация начальных парметров (приближение/стиль карт) и фильтров для поиска по ключевым значениям # In[20]: map1 = folium.Map( location=[55.159563, 61.375695], # Начальная позиция карты tiles='OpenStreetMap',#"Mapbox bright" - Стиль карты zoom_start=16, # Начальное приближение карты control_scale=False, prefer_canvas=True ) marker_cluster = MarkerCluster(name="Markers_GEO",overlay=True,show=False).add_to(map1) marker_cluster2 = MarkerCluster(name="Markers",overlay=True).add_to(map1) geo = folium.GeoJson( geojson, name="adress", show=True, tooltip=folium.GeoJsonTooltip( fields=["adress", "name_kv","emailkv"], aliases=["adress", "name_kv","emailkv"], localize=True ), ).add_to(marker_cluster) #Добавление поискового фильтра по адресу adresssearch = Search( layer=marker_cluster, geom_type="Point", placeholder="Поиск по адресу", collapsed=True, search_label="adress", search_zoom=15 ).add_to(map1) #Добавление поискового фильтра по ФИО FIOsearch = Search( layer=marker_cluster, geom_type="Point", placeholder="Поиск по ФИО", collapsed=True, search_label="name_kv", search_zoom=15 ).add_to(map1) #Добавление поискового фильтра по e-mail Email_search = Search( layer=marker_cluster, geom_type="Point", placeholder="Поиск по E-mail", collapsed=True, search_label="emailkv", search_zoom=15 ).add_to(map1) folium.LayerControl().add_to(map1) # > ## 2.6 Отрисовываем на карте все элементы из geojson, в соответствии с заданными параметрами (используя функции color_change и back_color_change для цветовой индикации) # In[21]: for p in range(0,len(data)): html = fancy_html(p) #Получаем данные из HTML для попапов iframe = branca.element.IFrame(html=html,width=330,height=270) #Размер объекта попапа popup = folium.Popup(iframe,parse_html=True) folium.map.Marker(location=[lat.iloc[p], lon.iloc[p]], icon=folium.plugins.BeautifyIcon(border_color = color_change(age.iloc[p]),# Цвет границы (в соответствии с датой рождения) border_width = 1, #Ширина границы background_color = back_color_change(email.iloc[p]), #Цвет подложки значка (если есть email, то золотой) text_color = 'dark', #Цвет текста внутри значка number = data["kv"].iloc[p], # Текст внутри значка icon_shape = 'marker'), # Формат значка popup=popup, # Всплывающий элемент при нажатии на значок tooltip = '{0}
{1}'.format(data["name"].iloc[p], data["example_feature"].iloc[p])# Всплывающий элемент при наведении мышкой на значок ).add_to(marker_cluster2) if (p % 100==0): print(p) # > ## 2.7 Финальная визуализация карты # In[22]: map1 # In[23]: map1.save("Yours_path_here.html")