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

Как подготовить для этого данные я детально описывал в "Где 15 минут пешком от дома до метро в Москве" "Где в Москве жить «неплохо»". А в публикации "Жилье в 500м от сетевых продуктовых магазинов в Москве." я столкнулся со специфичным трафиком GitHub Gist с желтушных публикаций. Сообщество OSMеров предложило мне отличный вариант, когда визуализация не требует чтения исходной статьи.

С моей точки зрения у этого варианта был один главный недостаток какой тайл сервер использовать чтобы это было законно. Яндекс карты не раздают свои тайлы для бесплатной аналитики в MapLibre. Завязываться на SDK от Яндекс я не хочу. Это фрагментация - когда в картах по миру используешь один виджет, а для родины другой.

Карты акварелью от Stamen watercolor смотрятся отлично, не отвлекая буквами и резкими линиями. Но у этого провайдера главная загвоздка - работает на localhost, а как размещаешь на сайте - то надо платить за карту. Не мой вариант для хобби, поэтому я продолжил поиски и нашел вектрные CARTO - positron-gl-style, которые отлично подходят для отображения данных.

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

Метро и магазинов шаговой доступности недостаточно

У меня завершился расчет пешеходных расстояний в Москве для 82768 объектов представляющих интерес (point of interest) от 47324 жилых зданий, расположенных в радиусе 2 км от входов в метро и МЦК. Расчеты делал без Apache Spark, пока не замахнулся на более масштабный анализ по всему миру. Расчеты учитывают подъезды и ближайшие входы (например в парк), если же информации для здания и POI недостаточно, то используется центры объектов на карте. В итоге выгрузил 35 984 392 пешеходных дистанций в parquet файлы общим объемом 198Мб. И все эти маршруты доступны вам для запросов.

Парки, кинотеатры, магазины, школы, театры, обзорные площадки итп. Это достаточно большой объем данных для отображения на карте, тем более для статического сайта без доступа к базе данных. Если за хостинг базы на проприетарных данных нужно платить деньги, то в случае с OSM можно перенести данные в статические выгрузки на github pages, а базу внедрить в сам браузер.

Встречайте DuckDB Wasm

Это встраиваемая колоночная база данных, которая умеет многое из того что делает ClickHouse и PostgreSQL. Этот же проект сделал кросс компиляцию базы данных на WebAssembly в JavaScript. Сайт проекта поможет вам разобраться что же это такое.

Для меня самое главное что эта база данных полнофункциональная, поддерживает чтение сжатых parquet файлов и распространяется по MIT лицензии. Это позволило мне начать разрабатывать логику фильтрации даннных карты прямо в браузере на JavaScript. И не платить за хостинг! Цена этого в том, что производительность произвольных запросов с карт зависит от времени доступа к данным по сети. Хоть база и использует статистику с паркетов и запросы с ranges чтобы не скачивать лишние данные. Вот это настоящий Serverless, а не разные лямбды в облаке.

DuckDB Wasm - новая технология, по сравнению с SQLite это более приспособленная для аналитических запросов база данных, при этом обладающая почти всеми плюсами встриваемой базы данных.

Например так я извлекаю данные о негативных факторов для отображения как GeoJSON на карте:

const stmt = await conn.prepare(
`select distinct type||id id from 'https://igor-suhorukov.github.io/data/direct_distance.parquet' where (category='_air_quality' and distance<=?) or (category='_noisy_place' and distance<=?) or (category='_industrial' and distance<=?) or (category='_dangerous' and distance<=?) or (category='_mosquitoes' and distance<=?) or (category='_sad_place' and distance<=?)`
);

    const ecologyMetrics = await stmt.query(
        document.querySelector('#_air_quality').value,
        document.querySelector('#_noisy_place').value,
        document.querySelector('#_industrial').value,
        document.querySelector('#_dangerous').value,
        document.querySelector('#_mosquitoes').value,
        document.querySelector('#_sad_place').value
      );

    var newGeo ={"type": "FeatureCollection", "features": []};
    for(const id of ecologyMetrics.toArray().map((row) => row.toJSON().id)){
     const building= buildings[id];
     if(building){
         building.properties.fill='red';
         newGeo.features.push(building);
     }
    }

Как будем отображать информацию о детсадах, школах и поликлиниках?

Все POI разделены на категории. В выгрузках содержатся пути к школам, коледжам/техникумам и институтам, разным курсам и тренингам. Так же как и все что связано с детьми - детские сады, игровые площадки, панда парки, зоопарки есть в детальной информации.

Может кто нибудь из вас сталкивался с Open Source проектами на JS/HTML, отображающими карточки с детальной информцией из OSM для POI? В сообществе порекомендовали openpoimap.org но лицензия на его код не понятна, как и не все что нужно мне отображать там есть.

Поскольку целевая аудитория моих публикаций сейчас - это программисты и OSMры, то начнем с простого интерфейса, в виде запросов в поле "Что ищем:" amenity='school' and distance < 1500 выводит жилые дома ближе 1.5км пешком от территории школ. Жилые дома amenity='kindergarten' and distance < 500 доступные пешком в 500м от детсадов. Для поиска жилья в радиусе 500м от сетевых продуктовых магазинов: distance <= 500 and (shop='supermarket' or shop='convenience') and brand is not null Когда жилой дом расположен на расстоянии от 300 до 1500м от входа в метро (network='Московский метрополитен' or network='МЦК') and 'transport'=any(categories) and distance between 300 and 1500

Схема данных сейчас такая:

select * from parquet_scan('building_distance/data*.parquet') limit 0;
┌───────┬───────┬─────────────┬───────────────┬──────────┬────────────┬─────────┬─────────┬─────────┬───┬──────────┬───────────┬──────────┬───────────┬──────────┬─────────┬─────────┬─────────┬──────────┐
│ part  │ h3_8  │ building_id │ building_type │ distance │ categories │  name   │ amenity │ leisure │ … │ historic │   sport   │ memorial │ education │ religion │ office  │  brand  │ network │ operator │
│ int32 │ int32 │    int64    │    varchar    │  int16   │ varchar[]  │ varchar │ varchar │ varchar │   │ varchar  │ varchar[] │ varchar  │  varchar  │ varchar  │ varchar │ varchar │ varchar │ varchar  │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                                 0 rows                                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Run Time (s): real 0.002 user 0.002213 sys 0.000000

D .mode line
D select * from parquet_scan('building_distance/data*.parquet') limit 1;
         part = 0
         h3_8 = 296373465
  building_id = 24241948
building_type = ways
     distance = 110
   categories = [education]
         name = Школа № 1507. Корпус школьного образования
      amenity = school
      leisure = 
         shop = 
   healthcare = 
      tourism = 
     historic = 
        sport = 
     memorial = 
    education = 
     religion = 
       office = 
        brand = 
      network = 
     operator = 
Run Time (s): real 0.020 user 0.062543 sys 0.027150
Дома в 500м до сетевых продуктовых магазинов обозначены оранжевым
Дома в 500м до сетевых продуктовых магазинов обозначены оранжевым

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

Итог

Благодаря OpenStreetMap + PostgreSQL живем во время, когда можно самостоятельно рассчитать и перепроверить геоданные на ноутбуке. Раньше это требовало бигдаты и бюджетов на аналитику и доступа к API расчета маршрутов за деньги... Теперь хватает open data + open source. Уверен, что через пару лет ChatGPT сможет выдать ответ на любой вопрос пользователя по геоаналитике, сформировав и отправив SQL запрос к гео данным. Встраиваемая база данных DuckDB очень быстро развивается, сообщество оптимизирует ее производительность и теперь доступна и в браузере через WebAssembly и позволяет сайту быть Serverless и работать с данными на клиенте.

Интерфейс "Москва, где мне комфортно" позволяет регулировать дистанцию и отображать данные как по негативным факторам, так и по позитивным. Карта доступна по адресу https://igor-suhorukov.github.io и является моим субъективным методом поиска жилья в мегаполисе на основе открытых геоданных. Работает карта на смартфоне и в браузере на компьютере.

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


  1. freeExec
    17.11.2023 10:39

    Так как тут всё статично и нету каких-то пользовательских данных не совсем понятно зачем нужна была именно база как parquet?

    Атрибуты можно было положить в сам дом вместе с геометрией и упаковать всё в векторные тайлы, если уж считать geojson на 200Мб излишеством. А управлять цветом чисто за счёт стиля.

    Но сама идея базы данных на клиенте мне понравилась.


    1. igor_suhorukov Автор
      17.11.2023 10:39

      Атрибуты можно было положить в сам дом вместе с геометрией и упаковать
      всё в векторные тайлы, если уж считать geojson на 200Мб излишеством.

      Кто сказал что там 200Мб GeoJSON? Там пару гигабайт декомпрессированного бинарного паркета)

      зачем нужна была именно база как parquet?

      База данных нужна тогда, когда данные не помещаются в память и писать кастомный код для обработки долго/дорого или нужна гибкость в разработке - резко поменять паттерны доступа, когда в процессе выясняются новые подробности.

      Но сама идея базы данных на клиенте мне понравилась.

      Мы общаемся онлайн пару недель, в т.ч. через matrix gateway. Хочешь обижайся, хочешь нет - я тебе скажу свое впечатление. Как-то пренебрежительно-учительски со мной общаешься, хотя я к вам на курсы не записывался и не в подчинении по работе.

      Жизненный опыт показывает что в таких фразах по шаблону "ну ты конечно фигню сделал, но #ABC# мне понравилось" - дальше следует копирование этого самого #ABC#, заработок на этом и поливание грязью меня. Надеюсь все же, что в этом случае так не будет! Или да?