Доброго времени суток дорогие читатели хабра, 12 августа 2020 года были опубликованы этапы переезда по программе реновации (ознакомиться можно здесь) и мне стало интересно, а как это будет выглядеть, если эти этапы визуализировать. Тут нужно уточнить, что я никак ни связан с правительством Москвы, но являюсь счастливым обладателем квартиры в доме под реновацию, поэтому мне было интересно посмотреть, может даже с некоторой точностью предположить, куда возможно будет двигаться волна реновации в моём случае (а может быть и в вашем, если вас дорогой читатель это заинтересует). Конечно точного прогноза не получится, но хотя-бы можно будет увидеть картину под новым углом. Сразу нужно отметить, что у меня получилось не всё, что я хотел сделать, но если кому-то интересно почитать о том, как собирались данные и какие интересные моменты с этим были прошу под кат. Забегая вперёд, скажу, что пометить дома согласно волнам реновации получилось, однако не удалось отобразить на карте стартовые площадки, хотя их можно добавить вручную, ниже покажу как.
1. Введение
Программа реновации была запущена Правительством Москвы в 2017 году. Благодаря ей 350 тысяч московских семей, то есть более миллиона человек, переедут в новые квартиры с отделкой комфорткласса.
Какие дома войдут в программу, решали сами жители. По итогам голосования в программу было включено 5174 дома.
Участники получат равнозначное жилье в своем районе… (далее можно прочитать здесь)
На основании приказа правительства Москвы от 12 августа 2020 г. № 45/182/ПР-335/20 (прочитать можно здесь) вся программа переселения рассчитана до 2032 года и должна будет пройти в три этапа (три волны):
- первый этап 2020 — 2024гг., в него вошло 930 домов, страницы 3-29 в приказе
- второй этап 2025 — 2028гг., в него вошло 1636 домов, страницы 30-76 в приказе
- третий этап 2029 — 2032гг., в него вошло 1809 домов, страницы 77-128 в приказе
- без определённого этапа (этапы должны будут определиться до конца 1 квартала 2021г.) — 688 домов, страницы 129-148 в приказе
2. Парсинг данных
Данные я взял из этого приказа, т.к. приказ — это pdf файл с таблицами, то я использовал библиотеку tabula для парсинга pdf файлов.
import pandas as pd import numpy as np import requests from tabula import read_pdf import json import os
Первым делом я спарсил одну страницу из этих таблиц, чтобы посмотреть, как дальше чистить данные.
test = read_pdf('prikaz_grafikpereseleniya.pdf', pages='3', pandas_options={'header':None})
test.head()
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | No п/п | АО | Район | NaN | Адрес дома | unom |
1 | 1 | ЦАО | Басманный | Бакунинская ул., д.49 c.4 | NaN | 1316 |
2 | 2 | ЦАО | Басманный | Бакунинская ул., д.77 c.3 | NaN | 1327 |
3 | 3 | ЦАО | Басманный | Балакиревский пер., д.2/26 | NaN | 19328 |
4 | 4 | ЦАО | Басманный | Госпитальный Вал ул., д.3 | NaN | 31354 |
Как видно из того, что получилось спарсить, чтобы очистить данные необходимо удалить лишние колонки и строчки, что и делает функция parse_pdf_table
.
def parse_pdf_table(pages, pdf_file='prikaz_grafikpereseleniya.pdf'): df = read_pdf(pdf_file, pages=pages, pandas_options={'header':None}) # удаляем не нужные строки df = df[~(df.iloc[:,0] == 'No п/п')] # оставляем только нужные колонки df = df.iloc[:,1:4] df.columns = ['AO', 'district', 'address'] return df
Каждая волна находится в своём диапазоне страниц, парсим их и проверяем по документу, т.е. количество строк должно совпадать с тем, что есть в pdf файле. (Также сразу добавляем к данным номер волны, т.к. это пригодится в будущем)
wave_1 = parse_pdf_table('3-29') # 2020 - 2024 wave_1['wave'] = 1
wave_1.shape
(930, 4)
wave_2 = parse_pdf_table('30-76') # 2025 - 2028 wave_2['wave'] = 2
wave_2.shape
(1636, 4)
wave_3 = parse_pdf_table('77-128') # 2029 - 2032 wave_3['wave'] = 3
wave_3.shape
(1809, 4)
unknown = parse_pdf_table('129-148') unknown['wave'] = 0
unknown.shape
(688, 4)
3. Обработка данных
Обрабатывать данные будем на пандасе (pandas), для этого соберём все волны в один датафрейм df
.
df = pd.concat([wave_1, wave_2, wave_3, unknown], ignore_index=True)
Выделим своим цветом метки каждой волны.
df['marker-color'] = df['wave'].map({1:'#0ACF00', # зеленый 2:'#1142AA', # синий 3:'#FFFD00', # жёлтый 0:'#FD0006'}) # красный
Также подпишем каждую метку в зависимости от волны.
df['iconContent'] = df['wave'].map({1:'1', 2:'2', 3:'3', 0:''})
В описание метки добавим адрес.
df['description'] = df['address']
Если не уточнить город — Москва, то по данным, полученным из геокодера получится, что реновация началась по всей стране, да что там, во всём мире. (Даёшь реновацию во всём мире! 🙂
def add_city(x): if x['AO'] == 'ЗелАО': return 'Зеленоград, ' + x['address'] return 'Москва, ' + x['address']
df['address'] = df[['AO', 'address']].apply(add_city, axis=1)
Для определения координат каждого дома по адресу я использовал геокодер яндекса, что очень удобно, т.к. он бесплатный и на него есть очень хорошая документация. Если нужно будет пересчитать координаты, то не забудьте ввести свой ключ.
def geocoder(addr, key='введи свой ключ'): url = 'https://geocode-maps.yandex.ru/1.x' params = {'format':'json', 'apikey': key, 'geocode': addr} response = requests.get(url, params=params) try: coordinates = response.json()["response"]["GeoObjectCollection"]["featureMember"][0]["GeoObject"]["Point"]["pos"] lon, lat = coordinates.split(' ') except: lon, lat = 0, 0 return lon, lat
%%time df['longitude'], df['latitude'] = zip(*df['address'].apply(geocoder))
CPU times: user 2min 11s, sys: 4.31 s, total: 2min 15s Wall time: 15min 14s
Все координаты определились удачно (именно удачно, т.к. нет гарантий, что геокодер спарсил адрес так как нам нужно), другими словами он хотя-бы что-то вернул.
len(df[df['longitude'] == 0])
0
Сохраним полученные данные.
df.to_csv('waves.csv')
#df = pd.read_csv('waves.csv')
4. Формирование карты волн реновации
Для отображения полученных данных на карте я использовал формат GeoJSON.
def df_to_geojson(df, properties, lat='latitude', lon='longitude'): geojson = {'type':'FeatureCollection', 'features':[]} for _, row in df.iterrows(): feature = {'type':'Feature', 'properties':{}, 'geometry':{'type':'Point', 'coordinates':[]}} feature['geometry']['coordinates'] = [row[lon],row[lat]] for prop in properties: feature['properties'][prop] = row[prop] geojson['features'].append(feature) return geojson
Т.к. меток получилось очень много, то полная карта может медленно работать на слабом ПК, поэтому я разделил данные по округам Москвы для удобства.
properties = ['marker-color', 'iconContent', 'description'] if not os.path.exists('data'): os.makedirs('data') for ao, data in df.groupby('AO'): geojson = df_to_geojson(data, properties) with open('data/' + ao + '.geojson', 'w') as f: json.dump(geojson, f, indent=2)
Полученные данные в формате .geojson
я сохранил в папку data
. В файле ВСЕ_ОКРУГА.geojson
записаны данные по всем округам вместе.
geojson = df_to_geojson(df, properties) with open('data/ВСЕ_ОКРУГА.geojson', 'w') as f: json.dump(geojson, f, indent=2)
ссылка на полную карту (может работать медленно) здесь.
В целом получилось не плохо, все метки внутри границ Москвы, однако, есть и несколько ошибок, как например недалеко от Сергиева Посада — Пролетарий СНТ территория (п.Вороновское), д.1 или в окрестностях Орехово-Зуево — Гаражный пер. (пос.ДСК Мичуринец, п.Внуковское), д.8/КБ/Н. (Честно говоря я бы и сам не сразу понял, где это находится)
5. Что хотелось сделать, но не получилось 🙁
Официальный список стартовых площадок находится здесь.
Также на карту волн реновации я хотел добавить стартовые площадки, однако это не получилось сделать. Проблема даже не в том, что нормально спарсить список не удалось, это можно было бы решить, проблема в том, что геокодер не может точно определить координаты по владению, например, Шмитовский проезд, вл. 39, Мукомольный проезд, вл. 6, или где находится этот адрес — район Южное Медведково, мкр. 1, 2, 3, корп. 38.
Таким образом единственный источник данных это официальная карта реновации (находится здесь), а как получить из неё координаты я не знаю, если кто знает, как получить координаты стартовых площадок, напишите пожалуйста в комментах.
Однако не всё так плохо и выход всё же есть — можно добавить эти метки вручную!
Видео-инструкция о том, как это сделать есть в исходном коде проекта, а также её можно посмотреть/скачать здесь.
Выводы
В целом можно сказать, что затея удалась, однако ещё раз повторю, выводы, которые вы можете получить исходя из этих данных носят лишь примерный характер, даже в самом приказе написано, что сроки указанные в нём являются ориентировочными и могут быть скорректированы, к тому же значительное число домов пока ещё даже не распределено.
Исходный код залит на github и скачать его можно здесь.
Спасибо за внимание.
ссылка на оригинал статьи https://habr.com/ru/post/516790/
Добавить комментарий