В статье рассказано о процессе создания тепловой карты цен по продаже недвижимости для Москвы и Санкт-Петербурга.


Тепловая карта цен Москвы


Меня зовут Дмитрий, я программист из Санкт-Петербурга и у меня есть хобби — это портал по недвижимости которым я занимаюсь в свободное от работы время вот уже почти 5 лет. Сайт авторский, и это дает достаточный уровень свободы для экспериментирования и реализации любых идей на нем. И одной из давних идей было создание тепловой карты цен.


Если лень читать статью, то потрогать готовый результат можно здесь.


При обилии сайтов посвященных недвижимости, в рунете нет нормальной карты цен. Есть какие-то не очень внятные карты где районы покрашены в разный цвет, но это все не то. Средняя цена по району мало о чем говорит, есть районы в которых цены различаются на порядок, а то и больше. Идея сделать тепловую карту посещала меня давно, но предвидя сложности браться за это не хотелось — не хватало вдохновения что-ли.


Как водится, я случайно наткнулся на статью про статистику цен на недвижимость Саратова. Автор описывает именно то что хотел сделать я: карту какой я ее себе представлял. Собственно это меня и вдохновило.


Инструменты


В статье есть ссылка на исходники, но я не понимаю в Пайтоне (или в питоне), и цели изучать новый язык у меня не было, так что решил искать если не готовый компонент, то хотя бы что-то что я смогу самостоятельно переписать под .Net.


В качестве первого компонента для генерации изображения я попробовал то что предлагает Гугл.


Это оказалось совсем не то что нужно. Тут скорее карты интенсивности — они бы подошли для визуализации плотности объектов на карте, но не для отображения цен. Кроме того, при масштабировании карты точки сливаются становясь более интенсивными — это уж совсем никуда не годится.



Есть один белорусский сайт где с помощью данного способа реализована карта цен. Можно посмотреть здесь.


Карта цен resta.by


Глядя на эту карту кто может сказать, где дороже, а где дешевле? Я не могу. В общем такое… три из десяти.


Поиски продолжились, и я нагуглил в итоге на стэке вот что: человек задает именно тот вопрос который интересовал меня, а именно, как сделать тепловую карту а не карту интенсивности. И в ответах есть ссылка на JS-библиотеку которая делает то что надо. Для расчетов используется Inverse Distance Weighting. JS — это конечно не шарп, но уже ближе, так что я был очень рад. Особенно после того как “пощупал” все это на jsfiddle и убедился в годности результата. Через несколько часов у меня уже был работающий код на C# (который впоследствии был сильно доработан). Вот ссылка на Гитхаб, если кому надо.


Данные


За время работы портала у меня накопилось более 20 миллионов объектов по всей России (архивные объекты сохраняются навсегда).


Как обработать сырые данные — вопрос не очевидный. Для начала фильтры: объекты по продаже, новостройки и вторичка, причем только квартиры и дома, потому что по ним можно точно рассчитать стоимость квадратного метра, а я собирался именно стоимость метра показывать на карте, как наиболее объективный показатель. Я не люблю аренду, потому что там много мусора. Цены сильно искажены, множество фиктивных объявлений, и т.д. В продаже тоже это все есть, но в меньших масштабах. Кроме того, цены на аренду напрямую зависят от стоимости продажи квартир, так что итоговая карта вполне подходит для визуализации общей картины, если не привязываться к значениям в легенде.


Всякую коммерцию, участки и паркинги пришлось исключить, но по каждой из этих категорий тоже было бы интересно посмотреть результат, но это как-нибудь потом. Плюс ко всему, наверное не имеет смысла включать туда старые объекты, цены-то меняются. Было решено что за полгода и объем будет нормальный и цены актуальны. Получилось примерно 40 000 объектов для Москвы и 30 000 для Питера.


Я так и не смог определиться с оптимальным шагом для компоновки объектов в исходную точку на карте. Пробовал разные варианты от 100 метров до 5 км. Решил оставить на усмотрение пользователей три наиболее интересных варианта: 250, 500 и 1000 метров.


Точки генерируются следующим образом: область рекурсивно делится на 4 прямоугольные секции до тех пор пока размер секции не будет совпадать или чуть-чуть превышать минимальный, либо до тех пор пока в области не останется объектов меньше чем минимально-допустимое количество (например 3). Для секций в которых меньше трех объектов итоговая точка не создается — они искажают общую картину и создают излишнюю “дырявость”, так как часто такие одинокие объекты отличаются по цене от окружающих.


Heat Map points


Внутри каждой получившейся секции считается средняя цена по объектам и устанавливается итоговая точка. Координаты устанавливаются не на центр секции а высчитывается среднее значение по координатам всех объектов.


Для каждого из шагов (250, 500, 1000) генерируется свой набор точек. Для каждой точки запоминается список использованных объектов для отображения по клику на карте.


Координаты точек в БД хранятся в виде географических данных, поэтому прежде чем передать их в работу, координаты надо привести к мировым, а потом к пиксельным на итоговом битмапе. Что такое мировые координаты можно почитать здесь. Если в двух словах, то географические координаты подразумевают размещение на сфере, и чтобы их отобразить на плоскости их нужно сконвертировать определенным образом. Вот отсюда я взял код для получения мировых и пиксельных коордиат.


Я решил что ограничу зум на карте от 8 до 14, потому как учитывая минимальный шаг сетки со значениями в 250 метров, ближе нет смысла рассматривать.


Тайлы


Я поначалу думал что лучше сделать один большой битмап, а потом разбить его на маленькие фрагменты. Но, в итоге, сделал наоборот — генерируются маленькие фрагменты — тайлы (tile), после чего компонуются для каждого из зумов.


Теперь чтобы отобразить все на карте надо привязать их к соответствующим координатам. Первое что я нашел в поиске — Ground Overlays.


После нескольких часов работы я получил вполне себе наглядный результат, но с одной проблемой — жуткие тормоза при навигации по карте. Очевидно работа с большим количеством фрагментов — это не то для чего нужен данный механизм.


Стал гуглить дальше и нашел Tile Overlays — это оказалось в итоге то что нужно. Суть такова: для каждого из уровней приближения карты (zoom index) итоговое изображение компонуется из плиток 256 на 256 пикселей, для каждой из которых можно наложить поверх свое изображение. При навигации по карте подгружаются только те тайлы которые попадают в видимую область и соответствуют значению zoom index.


Границы регионов


Сами координаты границ регионов у меня всегда были, так что это немного облегчило работу. При генерации каждого из тайлов проверяется не нужно ли его обрезать, и если нужно — то обрезаю, делая непопадающую область прозрачной.


Увидев результат я подумал что пользователей мало интересуют официальные границы, и, возможно им было бы полезнее видеть и близлежащие области тоже. Пришлось нарисовать свои границы для карт захватывающие как непосредственно города (Москву и Питер) так и ближайшие области. Количество объектов выросло в несколько раз. Теперь их стало около 140 и 50 тысяч для Москвы и СПб соответственно.


Москва:


Moscow Area


Санкт-Петербург:


SPB Area


Для рисования границ и получения их координат я использовал чей-то готовый код в codepen.io с небольшими изменениями. Вот ссылка для Москвы и Питера. После изменения какой-нибудь из точек на карте в окошко снизу вставляется список географических координат в виде удобном для вставки в БД.


Позже обнаружилась такая проблема: бывают ситуации когда области с разными ценовыми категориями расположены настолько близко что попадают в один сегмент и для них считается средняя цена. Например в Питере есть Каменный и Крестовский острова, где продается только элитная недвижимость, а через реку шириной в 300 метров — обычный район с хрущевками. Разница в цене — более чем на порядок (98 т.р. против 1200 т.р.).


Kamenny island


На рисунке Каменный остров, и красными точками обозначены объекты с двух сторон от него попавшими в итоговую секцию при шаге в 1000 метров. Это сильно влияет на среднюю цену в позиции и искажает общую картину.


Решение было такое: выделить некоторые секции и при компоновке объектов, попавшие в секцию объекты не должны смешиваться с объектами непопавшими, либо попавшими в другую секцию. Для Питера я выделил также острова.


SPB separated areas


Точность тут не важна. Главное — чтобы не было пересечений между областями.


Выбор цветов


Я сделал настраиваемым процесс генерации тепловой карты так чтобы можно было выбирать цвета, настраивать количество уровней, и т.д.
Например для области в 500 на 500 пикселей с установленными 6 точками со значениями от -100 до 100 можно получить такие варианты.


Тестовые точки со значениями:


TestPoints


Те же данные на карте с уровнями:


MapWithTestPoints


Без ограничения по цветам и с разбивкой по уровням:



С ограничением по цветами и без уровней:



С заданными вручную цветами:



После долгих и мучительных экспериментов предпочтение было отдано собственному набору цветов (позаимствовал здесь) которые захардкодил в класс как дефолтный набор.


Результат при шаге в 500 метров выглядит так:


500m Map


Производительность


Чтобы сгенерировать карту только для Москвы при параллельных 6 потоках (на восьмиядерном сервере 3,2 GHz) требовалось более суток. Это совсем неприемлемо, потому что в перспективе регионов будет больше и запуск должен происходить по расписанию, как минимум раз в неделю.


Узкое место в алгоритме — высчитывание цвета для каждого пикселя в тайле. Нужно отсортировать все точки по расстоянию от данного пикселя. То есть массив из 6000 точек приходилось сортировать 256х256 раз. Бессмысленная трата ресурсов. Очевидно, что все точки не нужны, и можно ограничиться ближайшими. Самое простое решение взять, например топ 100 точек отсортированных по расстоянию от центра тайла. Но тут может быть ситуация когда ближайшие 100 точек находятся в группе, например с одной только стороны. Т.е. нужны не просто 100 ближайших, а так чтобы они еще и были расположены вокруг. Вот что я сделал: из середины тайла во все стороны с шагом в 10 градусов распространяются лучи каждый длиной в треть всей карты. Каждый луч растет до тех пор пока в нем не будет как минимум 5 точек, либо он не достигнет предела по длине. Таким образом, гарантированно в итоговом списке будет примерно 150 точек со всех сторон.


Выглядит это так (зеленые точки — которые попали в выборку, красные — все остальные. Красный квадратик в середине — это непосредственно тайл):


Bicycle


Красиво, интересно и залипательно, но абсолютно бесполезно. Велосипед, в классическом его проявлении. Я потратил целый выходной день экспериментируя с параметрами: количеством лучей, их длиной, количеством точек в каждом луче, и т.д. И всегда я получал ошибки на карте.


Выглядят они так:



Это угловатости на областях границы которых должны быть всегда округлыми. Ошибки эти появляются всегда в местах с низкой концентрацией данных.


В итоге, весь этот механизм пришлось выкинуть. Лучше всего работает самый простой и очевидный способ — 100 ближайших точек без учета тех которые в самом тайле. Хоть ошибки и остались на карте, но они в местах которые, я надеюсь, мало интересны людям, ибо там почти ничего не продается.


Скорость работы выросла в разы. На Москву уходит около 3 часов, из них около часа только на обработку данных, остальное непосредственно на рисование.


Просмотр объектов


При клике по карте выбирается ближайшая к месту клика точка, и для нее отображается сводная информация: средняя цена за метр и список объектов использованных для расчета. Также, красными точками на карте отображены координаты этих объектов. По ссылке можно зайти в карточку каждого для более подробной информации. Многие объекты являются архивными, так что для них могут не отображаться фотографии и контакты продавца, а в остальном — вся информация соответствует изначальной.


Display points


Заключение


В ближайшее время я планирую увеличить количество объектов на карте, потому что большая часть их в БД не имеет географических координат. Для этого надо сделать модуль геолокации который будет ежедневно проходить по таким объектам получая для них координаты по адресу через сервисы Гугл или Яндекс.


Также я планирую дополнить карту некоторой статистической информацией в виде табличных данных. Разбивка по ценовым категориям, средние цены и т.д.


Ссылки


На всякий случай дублирую ссылки здесь в том же порядке в каком они указаны в статье.



PS


При навигации по карте сейчас примерно раз в 10-15 секунд происходит небольшое зависание, это не у меня на сайте баг, так ведет себя новый Вебвизор Метрики. В Яндекс я уже написал — они сказали что им нужно время чтобы разобраться. Так что, в скором времени, надеюсь починят.

Комментарии (78)


  1. AlexLeonov
    14.08.2017 20:35
    +1

    Спасибо за интересную статью, но, если честно, в нынешнем виде карта «не работает».

    Откройте, для примера, Крылатское. Соседние, совершенно одинаковые дома почему-то попадают в категорию 369 Круб или 234 Круб. Причем центрами более дорогих «пятен» в обоих случаях служат лесные массивы, где жилья вообще нет…

    При этом реально дорогой ельцинский дом на Осенней почему-то никак не повлиял на тепловую карту.


    1. d-sky Автор
      14.08.2017 21:56
      +2

      Центры пятен бывают не на своем месте — это одна из причин почему я оставил на усмотрение пользователя выбор масштаба. В разных режимах рисунки совершенно непохожи.

      Объекты используются далеко не все которые есть в Москве, так что эта карта скорее для того чтобы посмотреть «свысока» на ситуацию.

      А что касается одинаковых домов, можно скриншот или еще как-нибудь? А то я не смог найти. Там есть прокрутка в всплывающем окошке, если что, но она не во всех браузерах видна.


  1. sshikov
    14.08.2017 21:03
    +1

    Подход выглядит странным. Начиная с leaflet (т.е. с GIS) в качестве исходной точки, тепловые карты делаются намного быстрее. А так… делать самому tile server — разве что ради интереса?


    1. d-sky Автор
      14.08.2017 22:27
      +1

      Я видел leaflet когда искал решение. Видел еще много таких же инструментов — там получается то же самое что в Google HeatMap layer.
      Вот их официальный пример использования. Попробуйте позумить карту — точки сливаются.

      Tile server — это громко сказано. За это больше отвечает Google Maps. Мое дело отдать ему тайлы для координат которые он «попросил». Кэширование картинок было на сайте и без того, так что тут не пришлось изобретать ничего.

      Ну и конечно же, да, это все ради интереса.


  1. bopoh13
    14.08.2017 21:40
    +3

    Карта интересная, цвета подобраны хорошо. Задумка визуализации тоже отличная. Скинул ссыль знакомому риэлтору: выборка очень усреднённая (нужно считать хотя бы не среднее арифметическое, а медианное).

    На примере п.Архангельское (4 км на запад от МКАД). Там многоквартирные дома перемешаны с частными домами и особняками. Разбрас цен от 78 т.руб. за м2 до 338 т.руб. за м2. Т.е. если искать жильё только по тепловой карте, то эту область можно пропустить, хотя там оч много квартир стоимостью 130-140 т.руб. за м2. Медианная цена получается гораздо ниже средней.
    По-хорошему нужно делать выборку по большим критериям: с учетом типа дома (пятиэтажек панельных и девятиэтажек панельных отдельно), количества комнат, наличие инфраструктуры ближайшей (300-500м).

    На более человеческом примере скажу так: это как вычислять среднюю стоимость еды, среди которой есть кукурузные хлопья, хлеб, колбаса, свинина и черная икра. Мы увидим что средняя стоимость еды составит 3000 рублей за килограмм.
    Как более явный пример, — с.Татариново (44 км от МКАД на юг по М-4). Так неправильно :)


    1. d-sky Автор
      14.08.2017 22:50
      +1

      Спасибо!

      В Татариново — это кто-то ошибся с количеством нулей просто. Такое, кстати, часто бывает. Я удалил этот объект с карты, так что как только она обновится там будет все в порядке.

      Думаю, что для поиска жилья эта карта совсем не подходит. Там большинство объектов — архивные. Честно говоря, я даже думаю что она вовсе бесполезна — так, один раз посмотрел и понял, что и так все знал, где дороже, где дешевле… Просто красиво это нарисовано.

      Я сделаю на днях карту с медианным значением цены. Посмотрим что получится. Отпишусь здесь.


      1. vics001
        14.08.2017 23:46

        Лучше бы с фильтрами сделать по площади к примеру и по типу жилья.


        1. d-sky Автор
          15.08.2017 10:28

          Для этого придется сгенерировать по целой отдельной карте для каждого из сочетаний нескольких фильтров. К сожалению, при текущих ресурсах для меня это практически невозможно.


          1. Odrin
            15.08.2017 10:34

            Почему бы не генерить тайлы налету и кэшировать их?


            1. d-sky Автор
              15.08.2017 11:13

              Это слишком долго.
              Чтобы покрыть видимую область экрана (примерно 1900х800) тайлами нужно что-то около 30 секунд только на рисование при максимальном зуме (14). Для каждого из меньших зумов время соответственно увеличивается в несколько раз. Это без учета подгрузки и обработки данных.


      1. Sekira
        15.08.2017 01:27
        +3

        На будущее, можно делать среднее усеченное, чтобы некорректные цены не влияли на среднюю цену.


        1. d-sky Автор
          15.08.2017 10:29

          Пожалуй, это хорошая идея. Сделаю.


          1. OlegMax
            15.08.2017 10:44

            Обязательно надо. Тогда, например, объявления типа «Петроградский р-н., Архангельск, Попова ул, 25» не будут попадать в статистику Петербурга.


            1. d-sky Автор
              15.08.2017 10:57

              Это не спасет от таких случаев. Криворукие есть как среди программистов, так и среди агентов.


              1. OlegMax
                15.08.2017 11:00

                От таких — спасёт конечно. Если среди цен вокруг 100 попадается пара вариантов ~25, то их можно смело выбрасывать.


  1. mickvav
    14.08.2017 23:51
    +1

    А сделать две цветовые координаты — цена-тон, частота объявлений — насыщенность — не пробовали?


    1. d-sky Автор
      15.08.2017 10:32

      Не пробовал, но попробую.

      Я еще пробовал с прозрачностью: где дешевле там маска прозрачнее вплоть до совсем невидимой, получилось не очень.


  1. superyateam
    15.08.2017 00:22

    Хорошо выглядит!
    Но немного критики не помешает :)

    А почему не использовали кадастровые кварталы (они довольно маленькие)? Насколько я знаю, границы кварталов (GIS-данные) можно найти в инете. Для каждого такого квартала задать стоимость за м2 и отрисовать. Мне кажется так было бы быстрее — сейчас карта притормаживает.

    Я, кстати, не совсем понял — вы тайлы генерируете на стороне клиента что-ли? Мне кажется опять же не оптимально — зависеть от мощности компьютера (или планшета пользователя сайта). Наверно, лучше было бы отдельный сервер поднять, который бы для заданного прямоугольника возвращал картинки с тайлами, которые вы бы просто накладывали поверх карты.


    1. d-sky Автор
      15.08.2017 10:38

      Такие карты, где раскрашены микрорайоны или районы уже есть.

      Карта притормаживает не из-за генерации тайлов а из-за метрики. Я написал об этом в самом конце статьи.

      Тайлы генерируются на стороне сервера и это занимает много времени, так что генерировать их по запросу, к сожалению, не получится, хотя, это было бы лучше.


  1. Jef239
    15.08.2017 01:24
    -1

    Всякую коммерцию, участки и паркинги пришлось исключить

    Беда в том, что на карте (как минимум Питера) закрашиваются и промзоны и парки и пустыри… Если район знаешь — то смешно, если не знаешь — то странно.

    Очень странное пятно низкой цены вблизи будущего метро «проспект Славы». Нельзя ли настраивать, за сколько месяцев берутся данные? Метро открывается в декабре, вряд ли это пятно будет по текущим данным БН.


    1. d-sky Автор
      15.08.2017 10:44

      Да, я планировал сделать фильтр по периоду дат. Но там будет небольшой выбор. Что-то вроде, 6, 12, 24 месяца.


  1. Inomaratadeath
    15.08.2017 10:17

    только питер и москва?


    1. d-sky Автор
      15.08.2017 10:17

      Есть еще Краснодар.


  1. asdasda11
    15.08.2017 10:18
    +1

    Очень классно, прям залип. Но карту бы побольше, что-то типа на весь экран.


    1. d-sky Автор
      15.08.2017 10:18

      Спасибо!
      Я бы сделал карту на всю Россию, но пока не хватает для этого ресурсов.


  1. multed
    15.08.2017 10:18

    на хабре уже была тепловая «карта цен» недвижки. и там и тут карта предложения. итоговая цена сделки может отличаться. и иногда сильно.


    1. d-sky Автор
      15.08.2017 10:22

      Может отличаться, особенно для элитки, но реальную цену сделки Вы никогда не узнаете. Ее знают только продавец и покупатель.
      И в пересчете на квадратный метр, в большинстве случаев, цена будет отличаться не сильно.


  1. oleg_gf
    15.08.2017 10:24

    Постоянно натыкаюсь на «Данное объявление было снято с публикации собственником. „
    Может, надо такие отмечать красным в списках объектов на карте?


    1. d-sky Автор
      15.08.2017 10:48
      +1

      Большая часть использованных объектов — архивные. Да, думаю, надо их помечать как-то.
      Но в любом случае, эта карта не подходит для поиска предложений. Только чтобы посмотреть где дороже, где дешевле.


  1. SamDark
    15.08.2017 10:27
    +1

    Тепловые карты гугла более-менее, кстати. Мы их использовали именно для этих целей на хакатоне в прошлом году: https://www.youtube.com/watch?v=7GCJ_2v4gxg


    1. d-sky Автор
      15.08.2017 10:49

      Круто! Что за мероприятие такое?
      Но все же мне не нравятся эти красные ореолы вокруг точек. И что делать со сливанием точек при зуме?


  1. oleg_gf
    15.08.2017 10:30

    Ещё интересно, почему в районе Видного (где синева ближе всего ко мкаду) такие низкие цены?


    1. d-sky Автор
      15.08.2017 10:55

      Я не знаю почему такие цены, я вообще из Питера :) Как минимум один из объектов там не архивный — можно позвонить и спросить.


      1. oleg_gf
        15.08.2017 11:30
        +2

        Ну продавец же не скажет «Дёшево продаю, потому что тут по ночам упыри из болот вылезают». ))


  1. OlegMax
    15.08.2017 10:51
    +1

    Интересный проект.
    Видны пятна «дешевого цвета» вокруг новостроек на ранней стадии строительства.


  1. Rozmysel
    15.08.2017 11:05

    Однако, прочитав заголовок я сначала подумал что речь пойдет о корреляции между тепловым излучением территории и ценами на недвижимость.


    1. d-sky Автор
      15.08.2017 11:19

      Это было бы интересно.


      1. oleg_gf
        15.08.2017 11:25

        Тогда уж посмотреть, насколько цены на недвижимость коррелируют с уровнем энергопотребления. Или с количеством новостроек.


  1. Green2
    15.08.2017 12:12

    Я думаю, для увеличения скорости расчетов можно использовать видеокарты. они как раз позволяют рассчитывать картинки и обладают внушительной вычислительной мощностью.

    Еще хочется проанализировать другие города)))