Pull to refresh

Где бы вы точно не жили и не остановились даже на время, если бы знали и выбирали на основе фактов

Level of difficultyMedium
Reading time31 min
Views20K

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

Визуальные фантазии нейросети для следующего абзаца...
Визуальные фантазии нейросети для следующего абзаца...

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

Что общего во всех этих факторах, кроме того что я сгустил краски StableDiffusion? В описаном примере для многих людей есть не только экологическая, но психологически дискомфортная составляющая. В студенчестве в части диплома, связанной с экологией, были расчеты уровня шума в жилых помещениях от трассы/индустриальных объектов. Главный параметр был - расстояние по прямой от жилья.

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

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

Давайте напишем прототип запроса, по которому сможете понять где вероятнее всего вы бы не захотели жить. Хоть он основан на факторах, выбранных по моему личному мнению и они могут не совпадать с вашими предпочтениями. Можете изменять SQL запрос так, чтобы геоаналитика была больше похожа на ваш кошмар! И конечно же применим его к доступным для столицы геоданным и визуализируем их на карте.

Перед тем как начнем

Если не планируете воспроизводить процесс подготовки данных самостоятельно, то смело пропускайте следующий текст и пролистывайте до заголовка "Анализ данных для Москвы".

Подготавливаем данные для анализа

Этот раздел предполагает что вы знаете как работать с PostgreSQL и Linux. Я выполнял следующие шаги на Ubuntu, где установлены git, wget, docker.io

Скачиваем OSM геоданные сайта geofabric и извлекаем данные Москвы по полигону Russia_Moscow.poly.

mkdir moscow && cd moscow 
wget https://download.geofabrik.de/russia/central-fed-district-latest.osm.pbf
wget https://raw.githubusercontent.com/mapsme/omim/master/data/borders/Russia_Moscow.poly
docker run -rm -it -w /wkd -v $(pwd):/wkd mschilde/osmium-tool osmium extract --polygon Russia_Moscow.poly central-fed-district-latest.osm.pbf -o moscow.osm.pbf
Готовых выгрузок, к сожалению, не нашел

Данные с сайта https://download.openstreetmap.fr/extracts/russia/central_federal_district/ для столицы оказались не полные, что приводило к ошибке location for one or more nodes not found in node location index.

Загружаем утилиту openstreetmap_h3 подготовки данных OpenStreetMap для аналитики в PostGIS. Про нее я рассказывал в серии публикаций на Хабре.

Все как и раньше, с небольшими дополнениями

Во время подготовки этой статьи, я разобрался как запускать Docker in Docker для случая с openstreetmap_h3 и теперь не надо устанавливать JDK, maven. Все зависимости и сборка упакованы в докер образ. Не самое красивое решение конечно, но поскольку osmium используется сейчас как утилита командной строки в виде Docker образа, а не библиотека, то оставлю как есть.

git clone https://github.com/igor-suhorukov/openstreetmap_h3.git
cd openstreetmap_h3 && docker build -t openstreetmap_h3 .
cd postgis_docker-master && docker build -t postgres15_postgis .

Заходим в директорию, где подготовили moscow.osm.pbf и запускаем:

cd moscow
docker run -it --rm -w $(pwd) -v $(pwd):/$(pwd) -v /var/run/docker.sock:/var/run/docker.sock openstreetmap_h3:latest -source_pbf $(pwd)/moscow.osm.pbf -result_in_tsv true
Данные из moscow.osm.pbf будут готовы для загрузки в PostGIS в директории "moscow_loc_ways"
docker run -it --rm -w $(pwd) -v $(pwd):/$(pwd) -v /var/run/docker.sock:/var/run/docker.sock openstreetmap_h3:latest -source_pbf $(pwd)/moscow.osm.pbf -result_in_tsv true

[ 0:00] Started osmium add-locations-to-ways
[ 0:00]   osmium version 1.14.0
[ 0:00]   libosmium version 2.18.0
[ 0:00] Command line options and default settings:
[ 0:00]   input options:
[ 0:00]     file names: 
[ 0:00]       moscow.osm.pbf
[ 0:00]     file format: 
[ 0:00]   output options:
[ 0:00]     file name: moscow_loc_ways.pbf
[ 0:00]     file format: pbf,pbf_compression=none
[ 0:00]     generator: osmium/1.14.0
[ 0:00]     overwrite: no
[ 0:00]     fsync: no
[ 0:00]   other options:
[ 0:00]     index type (for positive ids): sparse_mem_array
[ 0:00]     index type (for negative ids): flex_mem
[ 0:00]     keep untagged nodes: no
[ 0:00]     keep nodes that are relation members: yes
[ 0:00] 
[ 0:00] Getting all nodes referenced from relations...
[ 0:00] Found 40919 nodes referenced from relations.
[ 0:00] Copying input file 'moscow.osm.pbf'...
[ 0:02] About 78 MBytes used for node location index (in main memory or on disk).
[ 0:02] Peak memory used: 809 MBytes
[ 0:02] Done.

0
188  time 14295
diff between total and processing 12765
total thread time 57059
total processing time 44294
total save time 4074
total waiting for save time 4120
thread max time 1024
processing max time 767
nodes 419908
ways 991744
relations 85883
relation members 875942
multipolygon count 54309
0	[-32768,4520)	10044611	37288
1	[4520,32695)	151011380	28175
2 10 80.5279955

[ 0:00] Started osmium export
[ 0:00]   osmium version 1.14.0
[ 0:00]   libosmium version 2.18.0
[ 0:00] Command line options and default settings:
[ 0:00]   input options:
[ 0:00]     file name: moscow.osm.pbf
[ 0:00]     file format: 
[ 0:00]   output options:
[ 0:00]     file name: moscow_loc_ways/multipolygon/source.tsv
[ 0:00]     file format: pg
[ 0:00]     overwrite: no
[ 0:00]     fsync: yes
[ 0:00]   attributes:
[ 0:00]     type:      @type
[ 0:00]     id:        @id
[ 0:00]     version:   (omitted)
[ 0:00]     changeset: (omitted)
[ 0:00]     timestamp: (omitted)
[ 0:00]     uid:       (omitted)
[ 0:00]     user:      (omitted)
[ 0:00]     way_nodes: (omitted)
[ 0:00]   output format options:
[ 0:00]     tags_type = hstore
[ 0:00]   linear tags: none
[ 0:00]   area tags:   one of the following:
[ 0:00]     @type=relation
[ 0:00]   other options:
[ 0:00]     index type: sparse_mem_array
[ 0:00]     add unique IDs: no
[ 0:00]     keep untagged features: no
[ 0:00] 
[ 0:00] Create table with something like this:
[ 0:00] CREATE EXTENSION IF NOT EXISTS hstore;
[ 0:00] CREATE TABLE osmdata (
[ 0:00]     geom      GEOMETRY, -- or GEOGRAPHY
[ 0:00]     osm_type  TEXT,
[ 0:00]     osm_id    BIGINT,
[ 0:00]     tags      hstore
[ 0:00] );
[ 0:00] Then load data with something like this:
[ 0:00] \copy osmdata FROM 'moscow_loc_ways/multipolygon/source.tsv'
[ 0:00] 
[ 0:00] First pass (of two) through input file (reading relations)...
[ 0:00] First pass done.
[ 0:00] Second pass (of two) through input file...
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
Geometry error: Could not build area geometry
[ 0:03] About 78 MBytes used for node location index (in main memory or on disk).
[ 0:03] Second pass done.
[ 0:03] Wrote 55341 features.
[ 0:03] Encountered 39 errors.
[ 0:03] Peak memory used: 828 MBytes
[ 0:03] Done.

cat /home/user/dev/map/moscow/moscow_loc_ways/multipolygon/source.tsv | grep $'\trelation\t' | split -l 13578 - /home/user/dev/map/moscow/moscow_loc_ways/multipolygon/multipolygon_

Осталось загрузить эти данные в PostGIS. Я это cделаю в той же директории с помощью Docker, а параметры контейнера будут зависеть от конфигурации вашей системы:

docker run --name postgis15-moscow --memory=12g --memory-swap=12g --memory-swappiness 0 --shm-size=1g -v $(pwd)/database:/var/lib/postgresql/data -v $(pwd)/moscow_loc_ways:/input -e POSTGRES_PASSWORD=osmworld -d -p 5432:5432 postgres15_postgis:latest -c checkpoint_timeout='15 min' -c checkpoint_completion_target=0.9 -c shared_buffers='4096 MB' -c wal_buffers=-1 -c bgwriter_delay=200ms -c bgwriter_lru_maxpages=100 -c bgwriter_lru_multiplier=2.0 -c bgwriter_flush_after=0 -c max_wal_size='32768 MB' -c min_wal_size='16384 MB'
Теперь можно подключаться любым клиентом, пароль у вас указан в POSTGRES_PASSWORD команды создания. Посмотреть статистику по таблицам, например:
PSQL_PAGER="pspg" psql -h 127.0.0.1 -p 5432 -U postgres -d osmworld
Password for user postgres: 
🧐 🐘 postgres_dba 6.0 installed. Use ":dba" to see menu
Timing is on.
psql (14.8 (Ubuntu 14.8-1.pgdg20.04+1), server 15.2 (Debian 15.2-1.pgdg110+1))
WARNING: psql major version 14, server major version 15.
         Some psql features might not work.
Type "help" for help.

osmworld=# :dba
Time: 1,892 ms
Time: 0,458 ms
Time: 0,413 ms
Time: 0,538 ms
Menu:
   0 – Node & current DB information: master/replica, lag, DB size, tmp files, etc.
   1 – Databases: size, stats
   2 – Tables: table/index/TOAST size, number of rows
   3 – Load profile
  a1 – Current activity: count of current connections grouped by database, user name, state
  b1 – Table bloat (estimated)
  b2 – B-tree index bloat (estimated)
  b3 – Table bloat (requires pgstattuple; expensive)
  b4 – B-tree indexes bloat (requires pgstattuple; expensive)
  b5 – Tables and columns without stats (so bloat cannot be estimated)
  e1 – Extensions installed in current DB
  i1 – Unused and rarely used indexes
  i2 – Redundant indexes
  i3 – FKs with Missing/Bad Indexes
  i4 – Invalid indexes
  i5 – Cleanup unused and redundant indexes – DO & UNDO migration DDL
  l1 – Lock trees (leightweight)
  l2 – Lock trees, detailed (based on pg_blocking_pids())
  p1 – [EXP] Alignment padding: how many bytes can be saved if columns are reordered?
  s1 – Slowest queries, by total time (requires pg_stat_statements)
  s2 – Slowest queries report (requires pg_stat_statements)
  t1 – Postgres parameters tuning
  v1 – Vacuum: current activity
  v2 – Vacuum: VACUUM progress and autovacuum queue
   q – Quit

Type your choice and press <Enter>:
2
Time: 0,990 ms
         Table          | Rows  |     Total Size     |     Table Size     |  Index(es) Size  |     TOAST Size     
------------------------+-------+--------------------+--------------------+------------------+--------------------
 *** TOTAL ***          | ~2M   | 722 MB (100.00%)   | 682 MB (100.00%)   | 35 MB (100.00%)  | 4712 kB (100.00%)
                        |       |                    |                    |                  | 
 ways_001               | ~932k | 461 MB (63.93%)    | 460 MB (67.44%)    | 0 bytes (0.00%)  | 1280 kB (27.35%)
 relation_members       | ~876k | 74 MB (10.30%)     | 48 MB (7.03%)      | 26 MB (75.64%)   | 8192 bytes (0.17%)
 nodes_001              | ~412k | 72 MB (9.92%)      | 72 MB (10.49%)     | 0 bytes (0.00%)  | 32 kB (0.68%)
 multipolygon_001       | ~53k  | 43 MB (6.01%)      | 36 MB (5.29%)      | 4992 kB (13.98%) | 2448 kB (52.31%)
 ways_000               | ~59k  | 30 MB (4.20%)      | 30 MB (4.41%)      | 0 bytes (0.00%)  | 248 kB (5.30%)
 h3_3_bounds_complex    | ~41k  | 15 MB (2.13%)      | 12 MB (1.77%)      | 3344 kB (9.37%)  | 8192 bytes (0.17%)
 relations              | ~86k  | 15 MB (2.07%)      | 15 MB (2.18%)      | 0 bytes (0.00%)  | 80 kB (1.71%)
 spatial_ref_sys        | ~9k   | 7144 kB (0.97%)    | 6928 kB (0.99%)    | 208 kB (0.58%)   | 8192 bytes (0.17%)
 nodes_000              | ~8k   | 1152 kB (0.16%)    | 1144 kB (0.16%)    | 0 bytes (0.00%)  | 8192 bytes (0.17%)
 multipolygon_000       | ~745  | 960 kB (0.13%)     | 752 kB (0.11%)     | 112 kB (0.31%)   | 96 kB (2.05%)
 ways_32767             | ~518  | 816 kB (0.11%)     | 616 kB (0.09%)     | 0 bytes (0.00%)  | 200 kB (4.27%)
 multipolygon_32767     | ~50   | 440 kB (0.06%)     | 152 kB (0.02%)     | 40 kB (0.11%)    | 248 kB (5.30%)
 osm_file_block_content | ~598  | 80 kB (0.01%)      | 80 kB (0.01%)      | 0 bytes (0.00%)  | 
 osm_file_block         | ~188  | 48 kB (0.01%)      | 48 kB (0.01%)      | 0 bytes (0.00%)  | 
 osm_stat_nodes_3_3     | ~6    | 16 kB (0.00%)      | 8192 bytes (0.00%) | 0 bytes (0.00%)  | 8192 bytes (0.17%)
 osm_stat_ways_3_3      | ~4    | 16 kB (0.00%)      | 8192 bytes (0.00%) | 0 bytes (0.00%)  | 8192 bytes (0.17%)
 osm_file_statistics    | ~1    | 8192 bytes (0.00%) | 8192 bytes (0.00%) | 0 bytes (0.00%)  | 
(19 rows)

Time: 12,725 ms
Press <Enter> to continue…

Создадим индексы для колонок типа географии, которые ускорят наши запросы:

CREATE INDEX idx_nodes_geography ON nodes USING gist (cast (geom as geography));
CREATE INDEX idx_ways_geography ON ways USING gist (cast (linestring as geography));
CREATE INDEX idx_multipolygon_geography ON multipolygon USING gist (cast (polygon as geography));

Тип geography, вместо geometry в запросе приходится использовать, так как измерять расстояния будем в метрах, а не долях градусов.

Проверим план нашего запроса расчета растояний от зданий до факторов дискомфорта:
osmworld=# explain create table distance as
                    select h3_lat_lng_to_cell(b.centre,10) h3_10, h3_lat_lng_to_cell(b.centre,8) h3_8,
                    round(ST_Distance(b.geom::geography,g.geom::geography))::integer distance,
                    b.type from_type, g.type to_type, b.id from_id, g.id to_id
                        from geometry_global_view b inner join geometry_global_view g
                            on ST_DWithin(b.geom::geography,g.geom::geography,200) and
    b.tags->'building' is not null and
    b.tags->'building' not in --в перечисленных ниже зданиях обычные люди не живут на постоянной основе
        ('service','garages','industrial','retail','office','roof','commercial','garage','kiosk','warehouse','church',
        'parking','public','shed','hangar','train_station','guardhouse','transportation','terrace','greenhouse','bridge',
        'government','chapel','gazebo','civic','ruins','supermarket','sports_centre','semidetached_house','toilets',
        'sports_hall','clinic','farm_auxiliary','stable','grandstand','bunker','gatehouse','store','temple','ventilation_kiosk',
        'carport','cowshed','barracks','shop','cabin','barn','cathedral','wall','townhouse','manufacture','shelter',
        'fire_station','stadium','stands','sport_hall','theatre','storage_tank','checkpoint','houseboat','abandoned','dovecote',
        'mosque','museum','military','container','observatory','lift','tent','factory','sport','mall','riding_hall','depot',
        'prison','gate','triumphal_arch','water_works','public_building','pavilion','bank','institute','works','collapsed',
        'car_repair','crossing_box','fuel','tree_house','presbytery','yesq','farm','outbuilding','police','porch','sauna',
        'monastery','cinema','tower','boathouse','library','transformer_tower','heat_exchange_station')
    and
(
    g.tags->'amenity'='waste_transfer_station' or --_качество воздуха_
    g.tags->'landuse'='landfill' or --_качество воздуха_
    g.tags->'man_made'='spoil_heap' or --_качество воздуха_
    g.tags->'man_made'='wastewater_plant' or --_качество воздуха_
    g.tags->'amenity'='crematorium' or --_качество воздуха_
    (g.tags->'natural'='wetland' and g.tags->'wetland' is distinct from 'mangrove') or --_комары_
    g.tags->'power'='substation' or --_промышленность_
    g.tags->'power'='generator' or --_промышленность_
    g.tags->'power'='plant' or --_промышленность_
    g.tags->'building'='industrial' or --_промышленность_
    g.tags->'landuse'='industrial' or --_промышленность_
    g.tags->'landuse'='quarry' or --промышленность_
    g.tags->'aeroway'='runway' or --_шумное место_
    g.tags->'railway'='rail' or --_шумное место_
    g.tags->'highway'='primary' or --_шумное место_
    g.tags->'highway'='trunk' or --_шумное место_
    g.tags->'leisure'='stadium' or --_шумное место_
    g.tags->'landuse'='construction' --_шумное место_
);
Time: 41,440 ms
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1000.53..698216225.15 rows=91264670 width=44)
   Workers Planned: 4
   ->  Nested Loop  (cost=0.53..689088758.15 rows=22816168 width=44)
         ->  Parallel Append  (cost=0.00..124924.54 rows=31608 width=162)
               ->  Parallel Seq Scan on ways_001 ways_4  (cost=0.00..83311.46 rows=20092 width=178)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on nodes_001 nodes_3  (cost=0.00..27175.52 rows=14806 width=76)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on multipolygon_001 multipolygon_4  (cost=0.00..6941.94 rows=1913 width=467)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on ways_000 ways_3  (cost=0.00..6444.37 rows=2136 width=198)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on nodes_000 nodes_2  (cost=0.00..628.11 rows=402 width=76)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on multipolygon_000 multipolygon_3  (cost=0.00..137.01 rows=38 width=807)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on ways_32767 ways_5  (cost=0.00..108.99 rows=26 width=843)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
               ->  Parallel Seq Scan on multipolygon_32767 multipolygon_5  (cost=0.00..19.09 rows=9 width=5966)
                     Filter: (((tags -> 'amenity'::text) = 'waste_transfer_station'::text) OR ((tags -> 'landuse'::text) = 'landfill'::text) OR ((tags -> 'man_made'::text) = 'spoil_heap'::text) OR ((tags -> 'man_made'::text) = 'wastewater_plant'::text) OR ((tags -> 'amenity'::text) = 'crematorium'::text) OR (((tags -> 'natural'::text) = 'wetland'::text) AND ((tags -> 'wetland'::text) IS DISTINCT FROM 'mangrove'::text)) OR ((tags -> 'power'::text) = 'substation'::text) OR ((tags -> 'power'::text) = 'generator'::text) OR ((tags -> 'power'::text) = 'plant'::text) OR ((tags -> 'building'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'industrial'::text) OR ((tags -> 'landuse'::text) = 'quarry'::text) OR ((tags -> 'aeroway'::text) = 'runway'::text) OR ((tags -> 'railway'::text) = 'rail'::text) OR ((tags -> 'highway'::text) = 'primary'::text) OR ((tags -> 'highway'::text) = 'trunk'::text) OR ((tags -> 'leisure'::text) = 'stadium'::text) OR ((tags -> 'landuse'::text) = 'construction'::text))
         ->  Append  (cost=0.53..3735.74 rows=76 width=129)
               ->  Index Scan using nodes_000_geom_idx on nodes_000 nodes  (cost=0.53..25.56 rows=1 width=44)
                     Index Cond: ((geom)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((geom)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using nodes_001_geom_idx on nodes_001 nodes_1  (cost=0.66..1027.11 rows=20 width=44)
                     Index Cond: ((geom)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((geom)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using ways_000_linestring_idx on ways_000 ways  (cost=0.66..150.89 rows=3 width=166)
                     Index Cond: ((linestring)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((linestring)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using ways_001_linestring_idx on ways_001 ways_1  (cost=0.67..2329.28 rows=46 width=146)
                     Index Cond: ((linestring)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((linestring)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using ways_32767_linestring_idx on ways_32767 ways_2  (cost=0.52..25.56 rows=1 width=811)
                     Index Cond: ((linestring)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((linestring)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using multipolygon_000_polygon_idx1 on multipolygon_000 multipolygon  (cost=0.52..25.56 rows=1 width=775)
                     Index Cond: ((polygon)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((polygon)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using multipolygon_001_polygon_idx1 on multipolygon_001 multipolygon_1  (cost=0.66..125.86 rows=3 width=435)
                     Index Cond: ((polygon)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((polygon)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
               ->  Index Scan using multipolygon_32767_polygon_idx1 on multipolygon_32767 multipolygon_2  (cost=0.52..25.55 rows=1 width=5934)
                     Index Cond: ((polygon)::geography && _st_expand((ways_4.linestring)::geography, '200'::double precision))
                     Filter: (((tags -> 'building'::text) IS NOT NULL) AND ((tags -> 'building'::text) <> ALL ('{service,garages,industrial,retail,office,roof,commercial,garage,kiosk,warehouse,church,parking,public,shed,hangar,train_station,guardhouse,transportation,terrace,greenhouse,bridge,government,chapel,gazebo,civic,ruins,supermarket,sports_centre,semidetached_house,toilets,sports_hall,clinic,farm_auxiliary,stable,grandstand,bunker,gatehouse,store,temple,ventilation_kiosk,carport,cowshed,barracks,shop,cabin,barn,cathedral,wall,townhouse,manufacture,shelter,fire_station,stadium,stands,sport_hall,theatre,storage_tank,checkpoint,houseboat,abandoned,dovecote,mosque,museum,military,container,observatory,lift,tent,factory,sport,mall,riding_hall,depot,prison,gate,triumphal_arch,water_works,public_building,pavilion,bank,institute,works,collapsed,car_repair,crossing_box,fuel,tree_house,presbytery,yesq,farm,outbuilding,police,porch,sauna,monastery,cinema,tower,boathouse,library,transformer_tower,heat_exchange_station}'::text[])) AND st_dwithin((polygon)::geography, (ways_4.linestring)::geography, '200'::double precision, true))
 JIT:
   Functions: 81
   Options: Inlining true, Optimization true, Expressions true, Deforming true
(48 rows)

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

osmworld=# create table distance as
                    select h3_lat_lng_to_cell(b.centre,10) h3_10, h3_lat_lng_to_cell(b.centre,8) h3_8,
                    round(ST_Distance(b.geom::geography,g.geom::geography))::integer distance,
                    b.type from_type, g.type to_type, b.id from_id, g.id to_id,
                    CASE
                        WHEN g.tags->'amenity'='waste_transfer_station' or
                             g.tags->'landuse'='landfill' or
                             g.tags->'man_made'='spoil_heap' or
                             g.tags->'man_made'='wastewater_plant' or
                             g.tags->'amenity'='crematorium' THEN 'air_quality'
                        WHEN g.tags->'power'='substation' or
                             g.tags->'power'='generator' or
                             g.tags->'power'='plant' or
                             g.tags->'building'='industrial' or
                             g.tags->'landuse'='industrial' or
                             g.tags->'landuse'='quarry' THEN 'industrial'
                        WHEN g.tags->'natural'='wetland' and g.tags->'wetland' is distinct from 'mangrove' THEN 'mosquitoes'
                        WHEN g.tags->'aeroway'='runway' or
                             g.tags->'railway'='rail' or
                             g.tags->'highway'='primary' or
                             g.tags->'highway'='trunk' or
                             g.tags->'leisure'='stadium' or
                             g.tags->'landuse'='construction' THEN 'noisy_place'
                    END reason
                        from geometry_global_view b inner join geometry_global_view g
                            on ST_DWithin(b.geom::geography,g.geom::geography,200) and
    b.tags->'building' is not null and
    b.tags->'building' not in --в перечисленных ниже зданиях не живут на постоянной основе
        ('service','garages','industrial','retail','office','roof','commercial','garage','kiosk','warehouse','church',
        'parking','public','shed','hangar','train_station','guardhouse','transportation','terrace','greenhouse','bridge',
        'government','chapel','gazebo','civic','ruins','supermarket','sports_centre','semidetached_house','toilets',
        'sports_hall','clinic','farm_auxiliary','stable','grandstand','bunker','gatehouse','store','temple','ventilation_kiosk',
        'carport','cowshed','barracks','shop','cabin','barn','cathedral','wall','townhouse','manufacture','shelter',
        'fire_station','stadium','stands','sport_hall','theatre','storage_tank','checkpoint','houseboat','abandoned','dovecote',
        'mosque','museum','military','container','observatory','lift','tent','factory','sport','mall','riding_hall','depot',
        'prison','gate','triumphal_arch','water_works','public_building','pavilion','bank','institute','works','collapsed',
        'car_repair','crossing_box','fuel','tree_house','presbytery','yesq','farm','outbuilding','police','porch','sauna',
        'monastery','cinema','tower','boathouse','library','transformer_tower','heat_exchange_station')
    and
(
    g.tags->'amenity'='waste_transfer_station' or --_качество воздуха_
    g.tags->'landuse'='landfill' or --_качество воздуха_
    g.tags->'man_made'='spoil_heap' or --_качество воздуха_
    g.tags->'man_made'='wastewater_plant' or --_качество воздуха_
    g.tags->'amenity'='crematorium' or --_качество воздуха_
    (g.tags->'natural'='wetland' and g.tags->'wetland' is distinct from 'mangrove') or --_комары_
    g.tags->'power'='substation' or --_промышленность_
    g.tags->'power'='generator' or --_промышленность_
    g.tags->'power'='plant' or --_промышленность_
    g.tags->'building'='industrial' or --_промышленность_
    g.tags->'landuse'='industrial' or --_промышленность_
    g.tags->'landuse'='quarry' or --промышленность_
    g.tags->'aeroway'='runway' or --_шумное место_
    g.tags->'railway'='rail' or --_шумное место_
    g.tags->'highway'='primary' or --_шумное место_
    g.tags->'highway'='trunk' or --_шумное место_
    g.tags->'leisure'='stadium' or --_шумное место_
    g.tags->'landuse'='construction' --_шумное место_
);
SELECT 808496
Time: 50868,122 ms (00:50,868)

osmworld=# ALTER TABLE distance ADD primary key(from_id,from_type,to_id,to_type);
ALTER TABLE
Time: 678,886 ms

osmworld=# \d distance
                   Table "public.distance"
  Column   |      Type       | Collation | Nullable | Default 
-----------+-----------------+-----------+----------+---------
 h3_10     | h3index         |           |          | 
 h3_8      | h3index         |           |          | 
 distance  | integer         |           |          | 
 from_type | table_reference |           | not null | 
 to_type   | table_reference |           | not null | 
 from_id   | bigint          |           | not null | 
 to_id     | bigint          |           | not null | 
 reason    | text            |           |          | 
Indexes:
    "distance_pkey" PRIMARY KEY, btree (from_id, from_type, to_id, to_type)

geometry_global_view - это представление, которое объединяет таблицы nodes, ways, multipolygon из OSM PgSnapshot схемы базы и сильно упрощает написание запросов, когда вам надо найти геометрию по заданным тегам, но ее тип заранее не знаете.

Для агрегации данных нам понадобится расширение H3:

CREATE EXTENSION h3_postgis CASCADE;

Сохраним предагрегированные данные в таблицы h3_10_stat для агрегации с H3 ячейкой 10 масштаба и h3_8_stat соответственно:

create table h3_8_stat as select h3_cell_to_boundary_geography(h3) cell ,count, reason from (select h3_8 h3, count(distinct (to_id,to_type)),array_agg(distinct reason) reason from distance group by 1 order by 1) geo;
SELECT 2047
Time: 1257,269 ms (00:01,257)

osmworld=# create table h3_10_stat as select h3_cell_to_boundary_geography(h3) cell ,count, reason from (select h3_10 h3, count(distinct (to_id,to_type)),array_agg(distinct reason) reason from distance group by 1 order by 1) geo;
SELECT 15660
Time: 1107,552 ms (00:01,108)

Предрасчет сделал, чтобы QGIS не "подвисала" во время отображения из PostGIS. Для таблицы выгружу данные в TSV файл, вы можете посмотреть что же это за места "портят" комфорт жизни в районах Москвы. Районы определяются разбивкой гексагональной сеткой с ребром около 75м для уровня=10:

osmworld=# \copy (select distinct h3_10, reason, CASE WHEN to_type='nodes' THEN 'https://www.openstreetmap.org/node/'||to_id WHEN to_type='ways' THEN 'https://www.openstreetmap.org/way/'||to_id WHEN to_type='multipolygon' THEN 'https://www.openstreetmap.org/relation/'||to_id END OSM_URL from distance order by 1) to 'uncomfortable_h3-10_region.tsv'
COPY 27354
Time: 289,032 ms

Анализ данных для Москвы

Почему я решил начать с Москвы - во первых цена ошибки здесь слишком большая, что с арендой, что с покупкой недвижимости. К тому же я достаточно долго прожил в Москве и могу сказать что для меня этот город достаточно комфортный. В плане удобства метрополитена и развитости/цены на общественный транспорт в Москве достаточно хорошо. Есть парки, места где погулять пешком поближе к природе. И все неплохо, пока живешь в хорошем районе, не дальше 10минут ходьбы от метро. Да все отлично пока живешь и работаешь в приличном месте, где не нужно быть на работе к времени попадая в час пик!

Итак, я считаю что рядом с квартирой, ближе чем в 200м не хотел бы видеть, нюхать и слышать то что ухудшает:

Конечно модель - это всего лишь грубое приближение реальности, тем более что данные о материалах дома и реальное качество строительства не учитываются. Как и года постройки здания, название типового проекта. Не учитывается слышимость из соседних квартир и с улицы. Так же как не расчитывается затухание шума на деревьях/ на шумозащитном экране у трассы( детальная информация о высоте деревьев и плотности их размещения на OpenStreetMap отсутствует), какие соседи из каких квартир уходят в запой и поколачивают свою семью, где есть агрессивные к другим людям индивиды во дворах и парках, качество водоснабжения и можно ли безопасно пить воду из-под крана. Живут ли тараканы, у соседки-пенсионерки с 20 котами. Как часто ломает лифт "упоротый" сосед осознанно перегружая его до потолка стройматериалами. Есть ли хулиганы жгущие пластиковые кнопки в лифте, изрисовывают ли стены дома. И многое другое важное для комфорта остается вне модели.

Пока также не учитываю социальный фактор который легко извлечь из картографии окрестностей - тюрьмы/изоляторы, психиатрические, наркологические и инфекционные( они редко правильно протегированны) больницы, морги, кладбища. Эти бонусы влияют на привлекательность жилья на пешем расстоянии, а не на расстоянии по прямой. Такие расстояния я умею считать, но интеграция c openstreetmap_h3 пока с помощью синей изоленты)

Напишите в коментариях какие еще места могут вас отпугнуть от места где собираетесь жить и почему?

Загружаем данные в QGIS
Загружаем данные в QGIS
Настраиваем цвет ячейки в зависимости от числа факторов в ней
Настраиваем цвет ячейки в зависимости от числа факторов в ней
Скрываем те ячейки где фактор "дискомфорта" минимален и любуемся на места где бы я не хотел жить
Скрываем те ячейки где фактор "дискомфорта" минимален и любуемся на места где бы я не хотел жить

Из QGIS я экспортировал данные слоя в GeoJSON и поделюсь им с вами. Вы можете загрузить его либо удобную вам для просмотра программу или воспользоваться онлайн инструментом https://geojson.io

Не самое удачное место для жилья, если верить статистике OSM - 220 проблемных мест на 7км2
Не самое удачное место для жилья, если верить статистике OSM - 220 проблемных мест на 7км2

А для примера, можем выгрузить GeoJSON для H3 ячеек уровня 10 с помощью SQL:

osmworld=# \copy (select json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type', 'Feature','geometry', st_AsGeoJSON(cell)::json, 'properties', json_build_object('count', count, 'reason', reason)))) FROM h3_10_stat) to 'uncomfortable_h3-10_region.json.geojson';
COPY 1
Time: 87,625 ms

Результаты доступны здесь.

Просмотр uncomfortable_h3-10_region.json.geojson в gist.github.com
Просмотр uncomfortable_h3-10_region.json.geojson в gist.github.com
Просмотр uncomfortable_h3-10_region.json.geojson в geojson.io
Просмотр uncomfortable_h3-10_region.json.geojson в geojson.io

Забыл упомянуть что расстояния считал только до жилых зданий. А это все что не:

building = ...

'service','garages','industrial','retail','office','roof','commercial','garage','kiosk','warehouse','church','parking','public','shed','hangar','train_station','guardhouse','transportation','terrace','greenhouse','bridge','government','chapel','gazebo','civic','ruins','supermarket','sports_centre','semidetached_house','toilets','sports_hall','clinic','farm_auxiliary','stable','grandstand','bunker','gatehouse','store','temple','ventilation_kiosk','carport','cowshed','barracks','shop','cabin','barn','cathedral','wall','townhouse','manufacture','shelter','fire_station','stadium','stands','sport_hall','theatre','storage_tank','checkpoint','houseboat','abandoned','dovecote','mosque','museum','military','container','observatory','lift','tent','factory','sport','mall','riding_hall','depot','prison','gate','triumphal_arch','water_works','public_building','pavilion','bank','institute','works','collapsed','car_repair','crossing_box','fuel','tree_house','presbytery','yesq','farm','outbuilding','police','porch','sauna','monastery','cinema','tower','boathouse','library','transformer_tower','heat_exchange_station'

Считая, что остальные здания являются жилыми. Исходные данные с OpenStreetMap не могут быть идеальными и всегда правильно размеченными, как и запросы к данным могут содержать ошибки. Но лично я лучше перестрахуюсь, и обойду здание - "жемчужину" индустриального района стороной.

Смотря на данные по Москве, я заметил что идеальная транспортная доступность играет в минус жилью, когда трасса ближе 200м к зданию - из-за шума автотранспорта и загазованности воздуха.

StableDiffusion: Best place in the city to live
StableDiffusion: Best place in the city to live

Ищите ваш дом и исследуйте окрестности, пишите что бы вы добавили к запросу, какие объекты! Загружайте данные - выгрузки по ссылкам в статье или исходные OSM файлы.

Результат

Модель вышла слишком общей для разных людей. В реальности же предпочтения могут различаться, и некоторые люди могут жить в таких "некомфортных" местах из-за других факторов, таких как доступность жилья, близость к работе или просто нравится готичность и индустриальность места. Открытые данные OSM и инструмент для работы с ними доступны и я выше как их импортировать, в чем визуализировать.

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

Свет провели в прошлом году! Три локтя по карте от центра мегаполиса... Попросил еще белку на лужайке)
Свет провели в прошлом году! Три локтя по карте от центра мегаполиса... Попросил еще белку на лужайке)

Если тема вас заинтересует, то могу провести анализ для других городов: как мегаполисов, так и более индустриальных Красноярска, Омска или курортных городов на побережье. Можем проверить запросы на фавелах в других странах, островах, континентах. Жду ваши идеи и предложения в коментариях.

Tags:
Hubs:
Total votes 32: ↑32 and ↓0+32
Comments73

Articles