Продолжаем изучать географию столицы и как она влияет на комфорт жилья. В этой публикации подключим маршрутизацию и расчитаем пешеходные расстояния от входа в метрополитен до жилых зданий. В прошлый раз я анализировал жилье в городе на удаленность от негативных факторов и поделился инструкцией "Где в Москве жить «неплохо»". Теперь же перейдем на позитивные факторы выбора места квартиры и найдем в Москве жилые дома в шаговой доступности от метро.
Сделаем это c помощью Graphhopper, OpenStreetMap H3 и PostGIS. Если вы далеки от PostgreSQL/Java, то сразу пролистывайте в конец статьи.
Подготовка данных
Загружал геоданные Москвы в Ubuntu, где установлены git, wget, docker.io:
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
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 .
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
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'
После успешного подключения к базе данных командой:
psql -h 127.0.0.1 -p 5432 -U postgres -d osmworld
В первую очередь нужно найти входы в метро и МЦК по тегу railway=subway_entrance:
create temporary table subway_entrance as
select id, centre, tags
from geometry_global_view e
where tags@>'railway=>subway_entrance';
Вспоминая школьные задачки про пешехода, идущего со скоростью 5км/ч, за 15 минут пешком обычно проходят расстояние 1250м. Поэтому для каждого входа в подземку найдем жилые здания в заданном радиусе, по прямой.
CREATE TYPE building_info AS (id bigint, type table_reference, x float8, y float8);
create table living_building_near as
select m.id subway_entrance_id,st_x(m.centre) subway_entrance_x,st_y(m.centre) subway_entrance_y,
array_agg((b.id,b.type,st_x(b.centre),st_y(b.centre))::building_info) buildings
from subway_entrance m inner join geometry_global_view b on
b.tags?'building' and
not(b.tags?'amenity') and
not(b.tags?'shop') 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','ice_rink','entrance','construction','transformer') and
ST_DWithin(b.geom::geography,m.centre::geography,1250)
group by 1,2,3; --select m.id subway_entrance_id,st_x(m.centre) subway_entrance_x,st_y(m.centre) subway_entrance_y,array_agg((b.id,b.type,st_x(b.centre),st_y(b.centre))::building_info) buildings from subway_entrance m inner join geometry_global_view b on b.tags?'_lb' and ST_DWithin(b.geom::geography,m.centre::geography,1250) group by group by 1,2,3;
В результате выполения запроса в базе создастся таблица, где для каждого входа в метро будет список зданий в радиусе 1250м по прямой с координатами их центров и ID:
osmworld=# \d living_building_near
Table "public.living_building_near"
Column | Type | Collation | Nullable | Default
--------------------+------------------+-----------+----------+---------
subway_entrance_id | bigint | | |
subway_entrance_x | double precision | | |
subway_entrance_y | double precision | | |
buildings | building_info[] | | |
Создадим таблицу, куда сохраним рассчитанные программой расстояний:
create table subway_distance(
entrance_id bigint,
building_id bigint,
building_type table_reference,
distance smallint,
primary key(entrance_id,building_id,building_type)
);
Расчет пешеходных расстояний
Выбрали дома в окрестностях, но расстояние по прямой обычно короче чем реальный путь. А чтобы его расчитать нужен либо сервис расчета маршрутов за деньги и с ограничением количества запросов, либо локльно запущенный роутер энджин. Для OpenStreetMap есть несколько Open Source движков маршрутизации. Но мне привычнее и удобнее использовать Graphhopper на Java. В чем основное преимущество перед API любого поставщика для прокладки маршрутов - отсутствие лимитов, сетевых задержек и платы за сервис. Например, для этой публикации программа расчитала 450032 расстояний "без регистраций и СМС". Также при желании Graphhopper позволяет настраивать веса и можно делать роутинг по правилам предпочитая например близость деревьев к дорожке и избегая мест где нет асфальта.
Основная проблема с расчетом расстояний - это время вычислений, в случае если сервис взаимодействует с программой как REST через TCP, поэтому я буду использовать Graphhopper 8.0 как библиотеку с in-process взаимодействием без вызовов по HTTP. Для этого добавлю в скрипт сборки программы зависимости graphhopper-core и postgresql jdbc драйвер:
Maven pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.igor-suhorukov</groupId>
<artifactId>routing-example</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.graphhopper</groupId>
<artifactId>graphhopper-core</artifactId>
<version>8.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
</dependencies>
</project>
Моя Java программа извлекает из таблицы living_building_near идентификаторы входа в метро и здания в окрестностях а также их координаты, расчитывает пешеходную дистанцию и сохраняет ее в таблицу subway_distance. В процессе работы пропуская строения, пешеходное расстояния до которых больше чем 1250м.
package com.github.isuhorukov.routing;
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopper;
import com.graphhopper.ResponsePath;
import com.graphhopper.config.CHProfile;
import com.graphhopper.config.Profile;
import org.postgresql.jdbc.PgArray;
import org.postgresql.util.PGobject;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Routing {
public static void main(String[] args) throws Exception {
GraphHopper hopper = getGraphHopper();
try (Connection connection = DriverManager.getConnection(
System.getenv("jdbc_url"), System.getenv("user"), System.getenv("password"))) {
connection.setAutoCommit(false);
try {
var distances = calculateDistances(connection, hopper);
saveDistance(connection, distances);
} catch (SQLException e) {
connection.rollback();
throw e;
}
}
}
private static GraphHopper getGraphHopper() {
var hopper = new GraphHopper();
hopper.setOSMFile(System.getenv("osm_file"));
hopper.setGraphHopperLocation("route");
hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("foot"));
hopper.setProfiles(new Profile("foot").setVehicle("foot"));
hopper.importOrLoad();
return hopper;
}
private static List<Distance> calculateDistances(Connection connection, GraphHopper hopper) throws SQLException {
List<Distance> distances = new ArrayList<>();
try (Statement subwayQuery = connection.createStatement()){
int step=0;
try (ResultSet livingBuilding = subwayQuery.executeQuery("select * from living_building_near")){
while (livingBuilding.next()){
long subwayEntranceId = livingBuilding.getLong("subway_entrance_id");
double subwayEntranceX = livingBuilding.getDouble("subway_entrance_x");
double subwayEntranceY = livingBuilding.getDouble("subway_entrance_y");
PgArray buildingsArray = (PgArray) livingBuilding.getArray("buildings");
List<Building> buildings = mapBuildings(buildingsArray);
System.out.println(step++);
distances.addAll(buildings.stream().map(building -> {
short distance = calculateDistances(hopper, building, subwayEntranceY, subwayEntranceX);
return new Distance(subwayEntranceId,building.buildingId, building.buildingType, distance);
}).collect(Collectors.toList()));
}
}
}
return distances;
}
private static void saveDistance(Connection connection, List<Distance> distances) throws SQLException {
try (PreparedStatement insert = connection.prepareStatement(
"insert into subway_distance(entrance_id,building_id,building_type,distance) " +
"values (?,?,?::table_reference,?)")){
distances.forEach(distance -> {
if(distance.distance>1250) return;
try {
insert.setLong(1, distance.subwayEntranceId);
insert.setLong(2, distance.buildingId);
insert.setString(3, distance.buildingType);
insert.setShort(4, distance.distance);
insert.addBatch();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
insert.executeBatch();
connection.commit();
}
}
private static List<Building> mapBuildings(PgArray buildingsArray) throws SQLException {
return getPgObjects(buildingsArray).stream().map(pGobject -> {
String value = pGobject.getValue();
if(value==null || value.length()<=2){
throw new IllegalArgumentException();
}
String[] parts = value.substring(1, value.length() - 1).split(",");
long buildingId = Long.parseLong(parts[0]);
String buildingType = parts[1];
double buildingX = Double.parseDouble(parts[2]);
double buildingY = Double.parseDouble(parts[3]);
return new Building(buildingId, buildingType, buildingX, buildingY);
}).collect(Collectors.toList());
}
private static List<PGobject> getPgObjects(PgArray buildingsArray) throws SQLException {
Object[] array = (Object[]) buildingsArray.getArray();
buildingsArray.free();
return Arrays.stream(array).map(object -> (PGobject) object).collect(Collectors.toList());
}
private static short calculateDistances(GraphHopper hopper,
Building building, double subwayEntranceY, double subwayEntranceX) {
GHRequest request = new GHRequest(subwayEntranceY, subwayEntranceX, building.buildingY, building.buildingX);
request.setProfile("foot");
GHResponse route = hopper.route(request);
ResponsePath bestRoute = route.getBest();
return (short) Math.round(bestRoute.getDistance());
}
private static class Building{
long buildingId;
String buildingType;
double buildingX;
double buildingY;
public Building(long buildingId, String buildingType, double buildingX, double buildingY) {
this.buildingId = buildingId;
this.buildingType = buildingType;
this.buildingX = buildingX;
this.buildingY = buildingY;
}
}
private static class Distance{
long subwayEntranceId;
long buildingId;
String buildingType;
short distance;
public Distance(long subwayEntranceId, long buildingId, String buildingType, short distance) {
this.subwayEntranceId = subwayEntranceId;
this.buildingId = buildingId;
this.buildingType = buildingType;
this.distance = distance;
}
}
}
Параметры программе передаются через переменные окружения jdbc_url, osm_file, password, user.
Центры зданий выгрузил в GeoJSON файл:
\copy (select json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type', 'Feature','geometry', st_AsGeoJSON(centre)::json))) from (select distinct centre from subway_distance s inner join geometry_global_view b on b.id=s.building_id and b.type=s.building_type) buildings ) to '~/moscow_metro_footpath.json';
Когда хочется найти панельный дом в OpenStreetMap
Захотелось мне в параметрах домов добавить типовой проект дома, чтобы отличать панельки от современных зданий и тут я обнаружил кое-что интересное с неполнотой данных.
--все жилые дома
select count(tags->'design:ref')*100.0/count(*) cnt, count(*) from geometry_global_view where tags?'_lb';
cnt | count
---------------------+--------
18.7563917273088398 | 118317
--в 15минутах от метро
select count(tags->'design:ref')*100.0/count(*) cnt, count(*) from (select distinct building_id, building_type, b.tags from subway_distance s inner join geometry_global_view b on b.id=s.building_id and b.type=s.building_type) b;
cnt | count
---------------------+-------
38.1045937491921516 | 38683
Значения серий домов, указанные для Москвы в OpenStreetMap
select regexp_split_to_table(tags->'design:ref',';') design_ref, count(*)
from geometry_global_view
where tags?'_lb'
group by 1
order by 2 desc;
design_ref | count
---------------------------------+-------
II-18-01/12 | 1100
П44-1/17 тип 1 | 636
II-18-01/МИ вариант «Б» | 635
1-511-4/М | 627
П44Т-1/17 тип 1 | 534
П3-2/16 | 514
И-209А | 496
1-511-3/М | 464
1-515-4/М | 371
II-68-01/16Ю-2/78 | 361
II-49-04/Ю вариант «Д» | 331
II-14 | 309
КОПЭ-85 | 295
1-410 | 267
1-510-4/М23 | 265
СМ-6 | 252
1-515-04/9ЮЛ | 246
1-515-4/М37 | 243
П44-1/16 тип 1 | 242
П43/16 | 233
II-14-33 | 231
II-68-01/16Ю | 231
Башня Вулыха | 227
КТЖС-2 | 223
П47/12 | 222
1-410-9 | 219
1-515-3/Ю37 | 218
II-49-06/Ю вариант «Д» | 216
КТЖС-1 | 206
КТЖС-5 | 191
И-209 | 184
1-515-5/М | 181
II-01 | 180
П46-2/12В | 180
1-510-3/М23 | 171
1-515-3/Ю | 163
П44Т-4/17 тип 2 | 162
1-515-5/М37 | 157
1-515-04/9М | 155
II-18-11/МИ вариант «Б» | 151
1605АМ/12 | 151
П44-4/17 тип 2 | 147
II-49-08/Ю вариант «Д» | 145
1-511-4/М37 | 144
1-511-3/Ю | 141
II-14-31 | 140
II-29-41/37 | 135
II-18-21/12 | 134
П30-4/12 | 133
1-515-06/9ЮЛ | 131
1-510-4/М | 130
1605АМ-04/12Ю | 129
П55-4/12 | 124
II-68-03/12Ю | 122
П-44 | 118
1-511-3/М37 | 117
1-511-4/Ю | 114
II-57-03/12ЮА | 112
1-511-2/Ю | 108
1-511 | 106
1-510-4/Ю23 | 104
1-510-3/М | 103
П3-1/16 | 98
П30-3/12 | 97
II-29/37 | 94
П46М-2/14 | 94
II-18-31/12 | 94
1-515-06/9М | 92
Мг-1 | 90
КТЖС-9 | 89
II-18-01/МИ вариант «К» | 89
П44Т-4/17 тип 3 | 89
II-49-06/Ю вариант «П» | 89
II-49-04/Ю вариант «П» | 88
П44-4/17 тип 3 | 88
II-28-3 | 88
П46М | 87
П46-3/12 | 86
1-410-10 | 86
1-447С-5 | 85
II-49-02/Ю вариант «Д» | 84
II-18-31/12А | 83
II-14-32 | 81
II-49-06/М вариант «Д» | 81
П44Т-4/17 тип 5 | 81
1-447С-7 | 79
П30-1/12 | 79
П44-4/17 тип 4 | 79
II-28-4 | 78
П44Т-4/17 тип 4 | 75
II-29-03/Ю37 | 74
КОПЭ-2000 | 73
1-511-2/М | 72
П46/12 | 71
II-18-02/12 | 70
II-49-04/М вариант «Д» | 67
1-410 (общежития) | 67
Унифицированный каркас | 65
П44-4/16 тип 2 | 65
II-29-03/М37 | 63
ГМС-2001 | 61
II-29-04/М37 | 61
П30-2/12 | 61
1-511-3/Ю37 | 60
1-510-4/М37 | 59
4572А | 59
П44-4/17 тип 5 | 57
П46М-4/14 | 57
II-49/Ю вариант «Д» | 57
II-29-04/Ю37 | 57
II-68-02/16М | 55
1-510-3/Ю23 | 55
П44Т | 54
II-68-02/12К | 54
1-410-12 | 54
П3М-1/17 | 54
II-29 | 53
II-05-02А | 52
ИП46С | 52
П55-26/12 | 51
П3М | 51
И-522А | 50
П46М-4/9 | 48
II-18-01 вариант «Б» | 48
1-511-4/Ю37 | 47
И-155Б | 47
П55-25/12 | 46
КОПЭ-80 | 46
II-29-05/Ю37 | 45
П44 | 45
П3М-1/16 | 45
1-447С-8 | 42
П55-21/12 | 42
II-67 Смирновская | 42
П44К-1/17 | 42
II-29-14/Ю37 | 41
П30-6/12 | 41
II-57 | 40
П30/12 | 40
111М | 40
II-29-03/М | 40
1-511-14/М37 | 40
1-447С-3 | 40
II-04 | 39
II-49-08/Ю вариант «П» | 39
КОПЭ-М «Парус» | 39
1-515-178/9М | 39
И-III-3 | 38
СМ-3 | 38
II-29-04/М | 38
1605АМ/9 | 38
II-49/М вариант «Д» | 37
П55-23/12 | 37
БС 05-17М | 37
1-410-13 | 36
1-410-11 | 36
1Мг-601/Д | 36
II-49-08/М вариант «Д» | 35
1-510-4/Ю37 | 35
II-68 | 35
П3М-3/17 | 35
II-68-01/22-83 | 34
II-29-05/М | 33
П3/16 | 33
1Мг-601 | 32
П46М-22пр/14 | 32
4570 | 32
II-57-05/Ю | 31
1-515/9 | 31
II-29-15/М37 | 31
1-447С-1 | 31
1605АМ-04/9Ю | 30
1-510-3/Ю | 30
П30-5/12 | 30
П3М-4/17 | 29
1-510-3/М37 | 29
1-515-3/М | 29
Тип I | 29
П3М-2/16 | 29
1-511-2/Ю37 | 29
П46М-2/9 | 29
КТЖС 1п-25 | 29
II-05-01 | 28
1Мг-601/Ж | 28
II-29-14/М37 | 28
Тип IV | 28
II-29-05/М37 | 28
II-67 Москворецкая | 28
II-28-2 | 27
II-49-04/М вариант «П» | 27
П44-4/16 тип 5 | 27
П42/16 | 26
1-300-3 | 26
П3М-2/17 | 26
II-57-05/12ЮА | 26
1-410-5 | 26
1-335-1 | 26
П46М-22пр/9 | 26
II-57-07/Ю | 25
1-447С-2 | 25
П3МК | 25
П44-4/16 тип 3 | 24
П-111М | 24
II-29-04/Ю | 24
II-49/12 вариант «П» | 24
П31/12 | 24
II-49/Ю вариант «П» | 24
1-511-130/37 | 23
II-57-03/12МА | 23
ПИК-1 | 22
1-515-24/9М | 22
I-511(I-511/25БИ) | 22
1-510/23 | 22
И-155НБ | 22
СМ-3-3 | 22
1-447С-6 | 21
П44-4/16 тип 4 | 21
I-515(I-515/37) | 21
II-29-03/Ю | 21
II-18-01 вариант «К» | 21
II-20 | 21
II-29-15/Ю37 | 21
КТЖС-1ук | 20
1-335-2 | 20
П30 | 20
1605АМ-06/12Ю | 20
И-155Мк | 19
1-247С-1 | 19
П55-24/12 | 19
П3М-3/16 | 18
1605АМ/5 | 18
КТЖС 2п-25 | 18
1-447С-38 | 18
И-491А | 17
И-155-16К1 | 17
П46М-21/9 | 17
1-410-8 | 17
1-447С-40 | 17
П46М-21/14 | 17
II-07-19 | 17
II-49-02/М вариант «Д» | 17
И-1782/1 | 17
1605АМ-06/9Ю | 17
II-57-03/ЮА | 16
П3М-4/16 | 16
II-68-01/12-83 | 16
1605АМ-08/9М | 16
II-57-05/ЮА | 16
1-511/37 | 16
Пд4-1/12Н1 | 15
II-67-386 | 15
1-801 | 15
П3М-7/23 | 15
1-513 | 15
П46М-22л/9 | 15
П55М-4/14 | 15
1-515-26/9М | 15
1-515-3/М37 | 15
222-15/17 | 14
П44Т-1/14М тип 1 | 14
II-67 Тишинская | 14
II-29-160 | 14
1-510-3/Ю37 | 14
И-700 | 14
СМ-3-5 | 14
ПИК-2 | 14
ЛСР | 14
1-515/37 | 13
I-511(I-511/37) | 13
1-464А-15 | 13
II-49-06/М вариант «П» | 13
КОПЭ | 13
II-14-34 | 13
II-14-35 | 13
1-244-1 | 13
БС 05-16М | 13
II-57-07/ЮА | 13
II-57-05/12МА | 12
1МГ-601-Ж | 12
КОПЭ-87 | 12
П-3/17 | 12
П55/12 | 12
П3М-6/17 | 12
И-155Мм | 12
II-34 | 12
II-68-03/12Ю-2/76 | 12
1-467Д | 12
I-515(I-515/5) | 12
ШКД | 12
Мм1-3 | 12
II-18/12 | 12
1-467А-17 | 12
1-447С-4 | 11
1-233-2 | 11
И-1491-17 | 11
I-515-3/Ю37 | 11
П44К | 11
I-410 | 11
1-510-2/23 | 11
II-18-11/МИ вариант «К» | 11
1-510-4/Ю | 11
1-464А-17 | 11
1-204-113 | 11
222-02/17 | 11
1-447С-37 | 11
II-29-05/Ю | 11
Мг-2 | 11
1Р-447С-25 | 11
1Р-447С-26 | 11
П46М-22л/9И | 11
II-68-01/16-83 | 10
П22/25 | 10
П46 | 10
1Мг-600-1 | 10
1-515-4/Ю | 10
II-68-01/14-83 | 10
1-510/37 | 10
П44Т-1/25 | 10
1-204-5 | 10
4570-63 | 10
1-515/9М | 10
Пд4-1/16Н1 | 10
БС 08-17К | 10
П55М-2/14 | 10
И-155-16К2 | 10
I-410 (САКБ) | 9
П-46М | 9
П44М-1/17 | 9
Мм1-4 | 9
КТЖС-14 | 9
П55-22/12 | 9
П30(П30/12) | 9
Пд4-5/12Н1 | 9
И-700Н | 9
II-57-05/М | 9
I-515-4/М37 | 9
П30М | 9
1-225-109 | 9
И-155Б2 | 9
ЭК-1 | 8
КОПЭ-Башня(КОПЭ-Башня М-2) | 8
Пд4-5/16Н1 | 8
2-4-1 | 8
Пд4 | 8
Бекерон | 8
И-782 | 8
II-68-04/12М1 | 8
111 | 8
Пд4-1/10Н1 | 8
И-155-16М4 | 8
И-155 | 8
1-447С-39 | 8
1-511-2/М37 | 8
БС 07-17М | 8
И-1820 | 8
1Мг-601-441 | 8
И-155-22М3 | 8
II-66 | 8
И-155С | 7
II-49 | 7
1-801-1 | 7
И-155-9М2 | 7
П4/22 | 7
И-155-16К3 | 7
И-155-9М3 | 7
1605АМ-06/9М | 7
Пп83(83-305/28-503) | 7
II-08 | 7
индивидуальный проект | 7
И-1158 | 7
Пд4-1/12 | 7
П46М-21/9И | 7
II-29-208 | 7
Град-2М | 7
П55-2/12А | 7
И-155-16К8 | 7
СЭМ-2 | 7
II-07-04 | 7
1605АМ-04/9М | 7
1-467А-15 | 7
1-467А-12 | 7
ПД-4 | 7
П22К | 7
1605АМ/Э | 7
II-49-08/М вариант «П» | 7
1-515-142/9М | 7
I-410(I-410 (САКБ)) | 7
1605АМ-08/9Ю | 7
II-18/12(II-18-01/12) | 7
I-510/23БИ | 6
И-286 | 6
Пд4-5/10Н1 | 6
1-211-2 | 6
I-511 | 6
П3М-3/9 | 6
БАШНЯ М-1 | 6
Тип II | 6
П-44Т | 6
И-155-22М4 | 6
И-273 | 6
П55-2/12 | 6
И-155-9М4 | 6
СМ-3-4 | 6
БС 09-17К | 6
П3М-6 | 6
БОД-1 | 6
1605-АМ(1605АМ/12Ю) | 6
1-211-3 | 6
II-57-07/МА | 6
БС 08-17М | 6
222-12/25 | 6
П-44м | 6
П3-3/16 | 6
Пд4-2/16Н1 | 6
1Мг-600 | 5
220-М17-1122-РТ | 5
И-155-22М2 | 5
Пд4-1/8 | 5
I-515/9М | 5
СЭМ2 | 5
1-253А-7 | 5
1-235-1 | 5
1МГ-601(1МГ-601-Ж) | 5
ПБ-02 | 5
И-155Н | 5
1-515-5/Ю | 5
И-155-16М2 | 5
И-1751 | 5
1-515 | 5
П46М-10л/17 | 5
220-М25-1222-РТ | 5
ТМ-25 | 5
II-28 | 5
Призма (И-1630) | 5
1-234-1КБ | 5
I-447С-26 | 5
И-760А | 5
1МГ-601 | 5
I-515/9ЮЛ | 5
Пд3-02/22 | 5
КПД-4570-73/75 | 5
1-510 | 5
КОПЭ(КОПЭ-85) | 5
П3-4/16 | 5
1-801-3 | 5
II-18-32/12А | 5
П46М-2/14И | 5
I-511/25БИ | 5
СМ-3-2 | 5
П44Т-4/14М тип 4 | 5
Пд3-03/22 | 5
I-510(I-510/23БИ) | 5
МПСМ | 5
И-155-16М5 | 5
ПБ-01 | 4
П44ТМ | 4
1-253А-2 | 4
II-57-07/12ЮА | 4
II-18/12(II-18-21/12А) | 4
П44ТМ/25 | 4
222-11/17 | 4
II-49(II-49/Ю вариант Д) | 4
XI-36-01 | 4
1Мг-601/Е | 4
К7/16 | 4
БС 08-16К | 4
С-222-01/17 | 4
1605АМ/л | 4
1-446С-3 | 4
1-447С | 4
И-155-22М1 | 4
И-521А | 4
И-155-Б | 4
1-428-3 | 4
Мм1-2 | 4
I-515/37 | 4
И-1630(Призма) | 4
1605АМ-03/12Ю | 4
1-510-2/37 | 4
БС 12-17М | 4
БС 06-17М | 4
1-447С-34 | 4
П3М-1/9 | 4
И-1723 (ШКД) | 4
МЭС-84 | 4
Пд4-4/16Н1 | 4
Колос | 4
1-440С-1 | 4
Д-25 | 4
П22-1/16 | 4
1-447 | 4
1-260-3А | 4
220-М25-1223-УТ | 4
М-10 | 4
1-515-4/Ю37 | 4
И-155-9М5 | 3
П23/16 | 3
1-515-5/Ю37 | 3
П3 | 3
114-86 | 3
II-57-07/М | 3
Пд4-1/16 | 3
система КУБ | 3
ЕвроПа | 3
БС 09-17М | 3
1-440С-3 | 3
222-01/17 | 3
II-18-01/08 | 3
МЮ | 3
II-57-А/12 | 3
П44Т-4/14М тип 3 | 3
1-260-1 | 3
VII-40 | 3
КТЖС-13 | 3
И-1168 | 3
И-1194 | 3
1-253-3 | 3
БС 09-16К | 3
К4/16 | 3
П46ММ, И-1824 | 3
II-18-01/09 МИБ | 3
Пд4-2/16 | 3
КТЖС-16 | 3
И-155-16М1 | 3
И-1822/1 | 3
КОПЭ-М-Парус | 3
1-439А-5 | 3
П55М-30 | 3
1-228 | 3
1-467А-18 | 3
П30(85-07) | 3
И-155-9К1 | 3
222-08/17 | 3
И-155-22М7 | 3
I-447С | 3
VI-12А | 3
I-515-178/9М | 3
222-09/17 | 3
II-68-04/12М2 | 3
И-155-16М6 | 3
П44Т-4/14М тип 5 | 3
И-1414 | 3
II-49/М вариант Д | 3
Пд4-5/8 | 3
И-155-16М3 | 3
VII-42 | 3
И-1824 | 3
И-701 | 3
II-18-22/МИ вариант «Б» | 3
И-155-16К4 | 3
И-155-9К5 | 3
1-515-5 | 3
II-49(II-49/М вариант П) | 3
П46М-22пр/9И | 3
П46М(ИП46М) | 3
П-46 | 3
1-428-1 | 2
164-80-3 | 2
1-228-9 | 2
I-511-4/М | 2
II-18-01/09 МИ вариант К | 2
П-3М | 2
1-447С-47 | 2
П46М-22л/14ПР | 2
П46М-322/14 | 2
П46М-23пр/14 | 2
П46ММ | 2
220-М17-1234-РТ | 2
1-801-13 | 2
П-30 | 2
КТЖС 3п-25 | 2
1-447С-10 | 2
П55М | 2
VI-44 | 2
1-228-7 | 2
И-155-9К3 | 2
II-49(II-49/М вариант Д) | 2
1-447С-17 | 2
П3(П3/16) | 2
I-511-2/Ю | 2
I-515-06/9ЮЛ | 2
КТЖС-3 | 2
1-801-4 | 2
И-155-16К5 | 2
И-155-16К7 | 2
II-18 | 2
1-460-7 | 2
1-466К-4 | 2
И-155-22М6 | 2
1-260-2 | 2
1-277-1 | 2
П44К(П44Т,П44К) | 2
I-515-06/9М | 2
1-510-2 | 2
1МГ-601/Д | 2
II-05 | 2
1-467А-20 | 2
1-234-1 | 2
1-225-110 | 2
Пд4-2/12 | 2
П46М-23л/14 | 2
С-222-15/17 | 2
И-155-16М7 | 2
1-222-126 | 2
1-447С-46 | 2
1-300 | 2
ИП46М | 2
И-155-9М1 | 2
1-446С-1 | 2
1-467Д-20 | 2
1-447С-35 | 2
Пд3-01/22 | 2
П46М-302/14 | 2
II-68-04/12М | 2
1-447С-53 | 2
I-511/37 | 2
К-8-49Б | 2
II-57-02/12ЮА | 2
1-428-2 | 2
Пп70-07/17 | 2
1Р-447С-25М | 2
И-1782 | 2
XI-36-02 | 2
1-206-104 | 2
П44Т-4/14М тип 2 | 2
II-57-07/12МА | 2
И-155-9К2 | 2
1-204-112 | 2
II-57/17 | 2
II-18-01/09 | 2
II-18(II-18-01/09 МИБ) | 2
И-209A | 2
1-253А-6 | 2
I-511-3/М | 2
И-155-22К1 | 2
I-515-24/9М | 2
II-29-Б | 2
СУ-155 | 2
1-301-1 | 2
1-464А-1 | 2
1-102-12 | 2
I-510-4/М23 | 2
1-225-111 | 2
VI-52 вариант 1 | 1
1Р-303-2 | 1
II-49/М вариант «П» | 1
П-3/16 | 1
1-439А-7 | 1
П3М-2/9 | 1
Пд4-5/12 | 1
БАШНЯ М-2 | 1
1-447С-13 | 1
II-18/12(II-18-31/12А) | 1
VII-70 | 1
1С-01-1 | 1
Пд4-2/12Н1 | 1
П30(П31/12) | 1
1-464Д-105 | 1
1-410-4 | 1
II-07 | 1
1-255-5 | 1
1-515-5М | 1
Лебедь (унифицированный каркас) | 1
II-32-04 | 1
II-66/БС-36 | 1
КПД-4570(КПД-4570-I) | 1
1-460-10 | 1
I-515-5/М37 | 1
П46М-22пр/14ПР | 1
I-447С-1 | 1
К-7-3-5 | 1
C-222-01/17 | 1
II-32-05 | 1
П44Т(П44Т, П44К) | 1
II-57-А/09 | 1
ЦИТП № 51 | 1
1Р-303-17 | 1
1-439А-4 | 1
1-300-2 | 1
1-251-7 | 1
1-203-15 | 1
1-447С-12 | 1
1-301-2 | 1
И-155-22М5 | 1
I-335-5 | 1
П46М, И-1824 | 1
brown | 1
1-275-4 | 1
П46М-321/14 | 1
С-222-02/17 | 1
БС 10-17М | 1
II-57-05/МА | 1
II-49/Ю вариант Д | 1
Пд4-4/12Н1 | 1
II-38-03/1A | 1
К-7 (К7-3) | 1
I-447 | 1
II-49(II-49П/12 (И-294А)) | 1
1-418 | 1
II-66/БС-34 | 1
161-101-21 | 1
1-251А-17 | 1
1-460-9 | 1
I-467А-15 | 1
П30(П30/12*) | 1
I-447С-25 | 1
И-1849 | 1
БС 01-17К | 1
1-251-15 | 1
1-801-16 | 1
II-32-130 | 1
БС 07-16М | 1
1-447С-12А | 1
124-124-4 | 1
Пд4-5/14Н1 | 1
1-335-4 | 1
1-253-5 | 1
КОПЭ(монолит-кирпич) | 1
II-29-3(II-29-3(9)) | 1
II-49/Ю | 1
V-79 | 1
12 (Союзтранспроект) | 1
1-204-6 | 1
1-204-114 | 1
Т-3 | 1
II-57-02/12МА | 1
1-251 | 1
КПД-4572А | 1
1-242-103 | 1
I-335-4 | 1
Призма(И-1630) | 1
I-447С-38 | 1
Пд4-1/14Н1 | 1
П3М-4/9 | 1
I-515(сталинский) | 1
VI-13А | 1
КОПЭ(КОПЭ-2000)) | 1
И-1747 | 1
1-275-1 | 1
1-466А-2 | 1
222-10/17 | 1
БС 10-16М | 1
БС 06-16М | 1
I-510(I-510/37) | 1
II-51 | 1
П46М-302/14И | 1
П47(П47/12*) | 1
K-7(индивидуальный проект) | 1
И-1746 | 1
65-426/I | 1
VI-23 | 1
П-49 Д | 1
I-515/9 | 1
I-515-04/9ЮЛ | 1
П47(П47/12) | 1
1-253А-4 | 1
1-251-11 | 1
1-294-3 | 1
1-447С-54 | 1
VI-52/2 | 1
1-467Д-19 | 1
I-467А-4 | 1
2-5-6 | 1
1МГ-601-441 | 1
1-277-2 | 1
1-251-2 | 1
КПД-4570-I | 1
1-447С-44 | 1
П46М-23пр/9 | 1
I-511-130/37 | 1
II-18-21/12А | 1
С222 | 1
П-111М(П-111) | 1
1-464Д-102 | 1
1-447С-36 | 1
II-66/БС-35 | 1
ИП-46С | 1
С-222(222) | 1
white | 1
II-65 | 1
1-201-12 | 1
ПЗ | 1
П30/12* | 1
1-252-8 | 1
ДОМРИК тип 1 | 1
1-439А-3 | 1
1-447С-11А | 1
17 (Союзтранспроект) | 1
П46М-23л/9 | 1
И-155-22К5 | 1
1-447С-33 | 1
П-3м | 1
1-255-4 | 1
П46М-322/14И | 1
I-410 (тип I, IV) | 1
1-251-13 | 1
1-447С-9 | 1
I-447С-8 | 1
1-262 | 1
1-280 | 1
1-241-115 | 1
КОПЭ(КОПЭ-80) | 1
II-68-01 | 1
II-18-03/МИ вариант «Б» | 1
1-255-6 | 1
II-49(II-49/12 вариант П) | 1
П46М-23пр/14И | 1
П46М-22л/14 | 1
II-29-3(9) | 1
K-7 | 1
1-253-6 | 1
И-1483 | 1
П3М-2/19 | 1
(813 rows)
Всего-то вручную надо проверить 100 000 жилых зданий в Москве и указать design:ref где не указан... Бродить по джунглям бетонных коробок с OSMTracker, наслаждаясь атмосферой мелонхолии и флешбеками детства и студенчества. В этом есть что-то родное и одновременно грустное!
Для домов у метро процент design:ref = 35%, значит осталось проверить 23983 не размеченных этим тегом домов! Эх, если бы каждый Хабровчанин окинул взором свои окрестности и указал design:ref и год постройки для пары домов. Но пока я решил не включать этот параметр в аналитику.
Результаты
По данным OpenStreetMap 32% зданий в Москве расположены в 15 минутах пешком от метро. Рассмотреть расположение этих домов у метро можно по ссылке GitHub Gist, где карту можно масштабировать (там еще случайно оказалась пасхалочка в центре с домом).
Расчеты основаны на открытых геоданных используя только Open Source компоненты. Если вы нашли ошибку, в первую очередь проверьте на OpenStreetMap обозначена ли там пешеходная дорога от вашего дома к метро. Во вторую очередь попробуйте вручную построить маршрут на сайте и смотрите как прокладывается маршрут и какое расстояние. И если вдруг у вашего дома с моей карты "пропало метро", то пишите в коментариях - будем разбираться!
В следующих публикациях рассчитаем пешеходное расстояние от домов до школ, детсадов, поликлиник. Удачного вам поиска комфортного жилья!
Комментарии (36)
aborouhin
26.10.2023 18:26+2Вот если бы спарсить гугл/яндекс-панорамы, "прогулять" по ним нейросеть, обучив на тех данных, которые уже размечены на OSM, и на основании полученной модели разметить всё остальное... Дата-сайентисты, ау! :)
sshikov
26.10.2023 18:26+1А не проще пойти от года строительства, фактически большинство домов района построены одновременно, и относятся к примерно одной серии.
igor_suhorukov Автор
26.10.2023 18:26Кстати, да! Плюс можно попробовать автоматическую кластеризацию по форме линий, как подсказку для человека что дома скорее всего относятся к одному проекту.
sshikov
26.10.2023 18:26Впрочем, я подумал, наверное все-таки без изображений и их распознавания не обойтись. Ну вот представим, что я хочу помочь разметке. И что я знаю даже про свой дом? Примерно год постройки (+- пару лет), примерно число этажей (кроме шуток, потому что зачем мне точно знать, 9 их или 10), ничего про серию дома я не знаю вообще. Но если его сфоткать в нужном ракурсе, вполне вероятно распознавание.
Ну т.е. человеку чтобы размечать даже вручную, не помешал бы помощник.
DevsStorm
26.10.2023 18:26Есть Яндекс Народная карта)
В ней Яндекс как раз таким и занимается.
Только за нейронками люди потом проверяют.
igor_suhorukov Автор
26.10.2023 18:26+2Все что люди размечают в "Яндекс Народная карта" пренадлежит Яндексу, а не тем кто создавал эти данные. Так что толку что там эти данные есть, если эти данные закрытые.
igor_suhorukov Автор
26.10.2023 18:26Отличная идея! И для Yandex/Google/Bing и Mapillary это уже реальность для распознавания с панорам. Для OpenStreetMap все компьютерное зрение пока относится к распознаванию дорог и домов по спутниковым снимкам в rapideditor.org
С юридической точки зрения нельзя использовать гугл/яндекс-панорамы для OSM, но панорамы с Mapilio вроде можно.
sshikov
26.10.2023 18:26+1Для Москвы тут не хватает МЦК, которая по сути тоже метро. Ну т.е. по сути, какие-то еще виды транспорта не учтены (скорее всего МЦК и ее станции на карте OSM есть).
igor_suhorukov Автор
26.10.2023 18:26@sshikovзамечание в точку! Они уже автомагически включены:
osmworld=# select tags->'network',count(*) from subway_entrance group by 1; ?column? | count -------------------------+------- | 273 МЦК | 56 МЦК(14) | 1 Московский метрополитен | 853 (4 rows)
Если какие станции пропустил, надо загребаться в разметку
sshikov
26.10.2023 18:26Ну я случайно ткнул в район станции Зорге, и мне кажется, там куча домов близко к ней. При этом остальные станции далеко - и дома эти не помечены.
igor_suhorukov Автор
26.10.2023 18:26Красные точки - входы в МЦК Зорге, синие - центры зданий считающимися жилыми, расположенные на расстоянии до 1250м от входов
PastorGL
26.10.2023 18:26Неплохое упражнение в аналитике small data :)
Для больших данных ST_DWithin (и вообще георасширения любой СУБД) не подошло бы, да и вообще, слишком уж это медленно и неэффективно — сравнивать в лоб все сочетания пар POI из двух наборов. Но для ~300 станций и ~100к домов (≅ 30М сочетаний) потянет.
igor_suhorukov Автор
26.10.2023 18:26@PastorGLперечитал все твои статьи на Хабре и OneRing/datacooker-etl в моих закладках. Смотрю как ты героически допиливаешь spark инструментарий чтобы решать проблемы которые в олдскульных решениях для работы с данными из коробки. Твой проект с SQL ETL для геоданных вызывает мое уважение, как технаря! С другой стороны глядя на как архитектор и менеджер хочется в моих проектах взять готовые вещи и не разрабатывать что-то новое и потом не поддерживать это.
Неплохое упражнение в аналитике small data :)
Мерси! Считаю если что-то можно решить без hadoop/spark это и нужно решать подходящими простыми инструментами, не пытаясь сразу заложиться на петабайты данных. Моя задача пока не тянет на бигдату и divide and conquer позволяет решать задачи с помощью UberH3. Когда от геофункции ST_DWithin перехожу к работе с геохешами h3_grid_disk.
Проблема PostgreSQL в этих задачах - отсутствие SIMD для геофункций и заточеность его query executor и планировщика на row based структурах, так как эта база из 90х годов и до сих пор основной сценарий её использования OLTP. Поэтому не спасает
Для больших данных ST_DWithin (и вообще георасширения любой СУБД) не
подошло бы, да и вообще, слишком уж это медленно и неэффективно —
сравнивать в лоб все сочетания пар POI из двух наборов. Но для ~300
станций и ~100к домов (≅ 30М сочетаний) потянет.В прошлых статьях я создавал GIST геоиндексы, поэтому
план запроса использует GIST индекс для ST_DWithin
QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate (cost=4181841.45..4184368.59 rows=1183 width=56) Group Key: m.id, (st_x(m.centre)), (st_y(m.centre)) -> Sort (cost=4181841.45..4182199.51 rows=143225 width=68) Sort Key: m.id, (st_x(m.centre)), (st_y(m.centre)) -> Nested Loop (cost=0.54..4166133.76 rows=143225 width=68) -> Seq Scan on subway_entrance m (cost=0.00..52.83 rows=1183 width=40) -> Append (cost=0.54..3520.85 rows=17 width=191) -> Index Scan using nodes_000_geom_idx1 on nodes_000 nodes (cost=0.54..25.78 rows=1 width=76) Index Cond: ((geom)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) Filter: ((tags ? 'building'::text) AND (NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((geom)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Bitmap Heap Scan on nodes_001 nodes_1 (cost=183.03..757.78 rows=1 width=76) Recheck Cond: (tags ? 'building'::text) Filter: ((NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((geom)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Bitmap Index Scan on nodes_001_tags_idx (cost=0.00..182.76 rows=22 width=0) Index Cond: (tags ? 'building'::text) -> Index Scan using ways_000_linestring_idx1 on ways_000 ways (cost=0.67..155.15 rows=1 width=197) Index Cond: ((linestring)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) Filter: ((tags ? 'building'::text) AND (NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((linestring)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Index Scan using ways_001_linestring_idx1 on ways_001 ways_1 (cost=0.68..2385.05 rows=10 width=177) Index Cond: ((linestring)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) Filter: ((tags ? 'building'::text) AND (NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((linestring)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Index Scan using ways_32767_linestring_idx1 on ways_32767 ways_2 (cost=0.53..25.65 rows=1 width=843) Index Cond: ((linestring)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) Filter: ((tags ? 'building'::text) AND (NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((linestring)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Index Scan using multipolygon_000_polygon_idx2 on multipolygon_000 multipolygon (cost=0.53..25.67 rows=1 width=807) Index Cond: ((polygon)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) Filter: ((tags ? 'building'::text) AND (NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((polygon)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Bitmap Heap Scan on multipolygon_001 multipolygon_1 (cost=90.96..117.09 rows=1 width=471) Recheck Cond: (tags ? 'building'::text) Filter: ((NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((polygon)::geography, (m.centre)::geography, '1250'::double precision, true)) -> BitmapAnd (cost=90.70..90.70 rows=1 width=0) -> Bitmap Index Scan on multipolygon_001_polygon_idx2 (cost=0.00..1.00 rows=5 width=0) Index Cond: ((polygon)::geography && _st_expand((m.centre)::geography, '1250'::double precision)) -> Bitmap Index Scan on multipolygon_001_tags_idx (cost=0.00..88.32 rows=10749 width=0) Index Cond: (tags ? 'building'::text) -> Bitmap Heap Scan on multipolygon_32767 multipolygon_2 (cost=2.46..28.59 rows=1 width=5966) Recheck Cond: (tags ? 'building'::text) Filter: ((NOT (tags ? 'amenity'::text)) AND (NOT (tags ? 'shop'::text)) 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,ice_rink,entrance,construction,transformer}'::text[])) AND st_dwithin((polygon)::geography, (m.centre)::geography, '1250'::double precision, true)) -> Bitmap Index Scan on multipolygon_32767_tags_idx (cost=0.00..2.20 rows=1 width=0) Index Cond: (tags ? 'building'::text) JIT: Functions: 55 Options: Inlining true, Optimization true, Expressions true, Deforming true (43 rows)
PastorGL
26.10.2023 18:26Ну, GIST эт такое себе индексирование, для геометрий его натянули примерно как сову понятно на что. Плавали, знаем как оно внутри устроено.
BTW, мы тоже ведь начинали 6 лет назад именно с PostGIS, и для считанных сотен тыщ точек использовать тамошние ST_ функции было ещё нормально. Проблемы начались, когда точек стали миллионы, и постгря перестала справляться с такими запросами. Вот тогда и мигрировали на Spark, поначалу с разбиениями по полигонам и/или по Вороному. А к решениям, позволяющим работать с миллиардами точек, и требующим нормальное геометрическое индексирование типа того же H3, мы пришли за несколько лет.
igor_suhorukov Автор
26.10.2023 18:26Ну, GIST эт такое себе индексирование, для геометрий его натянули примерно как сову понятно на что.
Неэффективно по сравнению с чем? Расскажи пожалуйста какие самые частые операции в ваших запросах, какие геофункции используете, какие индексы, библиотеки для Spark?
да и вообще, слишком уж это медленно и неэффективно — сравнивать в лоб
все сочетания пар POI из двух наборов. Но для ~300 станций и ~100к домов
(≅ 30М сочетаний) потянет.Вот я показываю выше план запроса, в котором видно что используется
Index Scan, Index Cond: ((geom)::geography && _st_expand((m.centre)::geography, '1250'::double precision))
а не перебор "в лоб".Вот тогда и мигрировали на Spark, поначалу с разбиениями по полигонам
и/или по Вороному. А к решениям, позволяющим работать с миллиардами
точек, и требующим нормальное геометрическое индексирование типа того же
H3, мы пришли за несколько лет.Сам по себе Spark удобный фреймворк для распределенных вычислений над данными, но в плане эффективности использования аппаратного обеспечения ему еще далеко до многих колоночных/MPP баз, многое пытаются вынести из jvm байткода в нативный чтобы повысить его производительность/уменьшить накладные расходы связанные с GC. Мне интересно какие у вас расчеты в запросах, почему не проще было взять тот же omniscidb(heavydb) на GPGPU?
Vsevo10d
26.10.2023 18:26Угу, выбираешь так квартиру недалеко от входа на станцию МЦД, потом от этого "входа" кандехаешь 250 метров по крытым надземным кишкам и павильонам с шаурмячными до самих поездов.
igor_suhorukov Автор
26.10.2023 18:26А кто-то стоит под дождем и снегом на автобусной остановке и не помещается в маршрутку к метро и ждет следующую в Москву через 40мин. Опыт из моей жизни в феврале этого года, когда гостил в подмосковье.
Vsevo10d
26.10.2023 18:26-1А это тут ни при чем. Я говорю о том, что выбранная автором метрика сомнительна, вход входу рознь в плане удобства посадки и дальнейшего маршрута.
Я бы вообще жил подальше (в разумных пределах) от входов в метро, это тот еще рассадник всякого быдла.
igor_suhorukov Автор
26.10.2023 18:26Я говорю о том, что выбранная автором метрика сомнительна
Я и есть автор) Жизненный опыт показывает что когда в моем присутствии говорят обо мне в третьем лице, то мотивация человека не добрая и не направлена на конструктивную критику.
Расскажите как вы предлагаете изменить учет расстояния? Погрешность указанных вами 250м на максимальном расстоянии 1250м - это 20%. Есть документированнный алгоритм как расчитывают метрику расстояния до метро другие сервисы?
Я бы вообще жил подальше
На каком расстоянии?
от входов в метро, это тот еще рассадник всякого быдла
Вы ставите себя выше других людей, пользующихся метрополитеном?
Vsevo10d
26.10.2023 18:26-2Я и есть автор) Жизненный опыт показывает что когда в моем присутствии говорят обо мне в третьем лице, то мотивация человека не добрая и не направлена на конструктивную критику.
Я не всегда смотрю на цвет плашек в ответах (иногда сам Хабр их не подсвечивает зеленым).
Расскажите как вы предлагаете изменить учет расстояния? Погрешность указанных вами 250м на максимальном расстоянии 1250м - это 20%. Есть документированнный алгоритм как расчитывают метрику расстояния до метро другие сервисы?
Я не могу точно ответить на это и тем более дать алгоритм, могу только указать юзеркейс. Я бы вообще не рассматривал МЦД, они говно как транспорт и связан чисто условными "переходами" с метро, на которые нужно потратить 10-15 минут по улице. Входы в метро – более честное понятие, даже если идти от лестницы в подземный переход и по эскалаторам на платформу, то это не будет дольше 5 минут ни на одной станции.
Вы ставите себя выше других людей, пользующихся метрополитеном?
Это вы, похоже, ставите, раз приравниваете к быдлу всех пассажиров метро.
На каком расстоянии?
Повторюсь: на достаточно близком, чтобы ходить до дома пешком (<700 м); на достаточно далеком, чтобы быдло (всякие обитающие у входов рыгающие панки и стреляющие мелочь бомжи) были не у меня под окнами (>50-100 м).
igor_suhorukov Автор
26.10.2023 18:26Я бы вообще не рассматривал МЦД, они говно как транспорт и связан чисто
условными "переходами" с метро, на которые нужно потратить 10-15 минут
по улице.А где в статье указано что я его рассматриваю? Все что я учитываю в этой аналитики -
railway=>subway_entrance
Это вы, похоже, ставите, раз приравниваете к быдлу всех пассажиров метро.
Вы только что сами придумали и приписали мне неуважительное высказывание по отношению к пассажирам Московского метрополитена. Чистой воды демагогия! Еще и выбрали из викисловаря определение удобное вам
всякие обитающие у входов рыгающие панки и стреляющие мелочь бомжи)
У меня в целом положительные впечатления от пользования метрополитеном. Не знаю где вы находите маргинальные элементы.
igor_suhorukov Автор
26.10.2023 18:26были не у меня под окнами (>50-100 м
Достаточно ли этой дистанции? В прошлых статьях некоторые коментаторы возмущались что 150м от негативных факторов им мало.
fekrado
26.10.2023 18:26Ну тут напрашивается привязка к циану и тд. И аналитика на подбор кв в доступности от метро и по ₽
CBET_TbMbI
26.10.2023 18:26Только на практике 5 км/ч это максимальная скорость. Так ходят единицы и то не всегда. Средная скорость пешеходов, скорее, 3 км/ч.
igor_suhorukov Автор
26.10.2023 18:26+1Да ладно) В Москве почти все бегают!
sshikov
26.10.2023 18:26+1Не просто бегают, а на электросамокате :). Кстати, я бы попробовал учесть и его, потому что на сегодня прокат самокатов и великов сильно меняет ландшафт. В том числе даже зимой. В тоже время, маршруты, доступные самокату, велосипеду и пешеходу, далеко не всегда совпадают.
Ну т.е. когда я ездил на работу самокатом, мне было удобно доехать 20 минут до МЦК, 20 минут на нем, и 20 минут до офиса. При этом метро от меня в 5 минутах, а МЦК где-то 3.5-4 км. Но - тут уже вступают в игру погодные условия. Сегодня вот снег выпал - какие нафиг самокаты (хотя прокатчики их еще не убирали)?
igor_suhorukov Автор
26.10.2023 18:26Похоже на новую фичу для Graphhopper. Он до сих пор не поддерживает "из коробки" комбинированные маршруты (велосипед+пешком+транспорт итп) При этом поддержка для общественного транспорта по протоколу gtfs в коде есть, но кто же в РФ будет публиковать такие данные для общественного транспорта, хотя эти данные есть у Яндекса и Мосгортранса? Так что я бы пока так не усложнял задачу на старте.
sshikov
26.10.2023 18:26+1Ну, такая комбинированная навигация - она сложная логически. Я бы сказал что Яндекс, который выкатил навигацию для самокатов, до сих пор строит такие маршруты, что только ой. Скажем, у меня поблизости два пешеходных перехода через магистраль, оба с лифтами, но меня с самокатом почему-то гонят за пару км, чтобы переехать магистраль поверху. Считается, что я на самокате не могу войти в лифт, или закатить его по лестнице с пандусом?
igor_suhorukov Автор
26.10.2023 18:26Ко-фаундер Graphhopper мне честно отвечал почему они удалили нужный код для сложной навигации:
Считается, что я на самокате не могу войти в лифт, или закатить его по лестнице с пандусом?
Надо разработчиков маршрутизации в Яндексе спрашивать. Отправь к ним на митап или конференцию своих коллег этим с вопросом)
sshikov
26.10.2023 18:26Ну вот откуда им знать, что оно не используется? Потому и не используется, что нет такой функции. А по сути, чем самокат плюс метро отличается от метро плюс общественный транспорт? Та же фигня на первый взгляд.
Mitya78
А из кадастровой карты что-то можно извлечь? Уже и не помню.
sshikov
Вроде можно. Но когда я пытался это сделать, кадастровая карта Росреестра работала ужасно медленно. И потом, очень желателен доступ к API (там вроде был ArcGIS), но его не факт что дадут.