В комментариях в одной из прошлых статей и на гитхаб писали что не обязательно жить близко к магазину, когда есть службы доставки. Вот с этим я могу поспорить - большая часть людей в России покупает продукты и товары в магазинах. В 90е были крайне популярны вещевые рынки и часто покупали одежду там потому что было дешевле. У многих студентов и пенсионеров их финансовая ситуация не располагает не то что платить за доставку курьером, но даже возможность купить хорошие продукты и одежду после квартплаты - это арифметика жизни. Им неосознанно приходится решать задачу коммивояжера в голове чтобы обойти несколько торговых точек и выгодно купить по заранее составленному списку покупок. Шаговая доступность магазинов все еще важна для жителей городов.

Как выбрать правильное место для открытия магазина и что нужно учесть
  • Его локация должна быть в удобном месте для потенциальных клиентов. Рядом с офисными зданиями, в туристическом или в жилом районе. Учитываются поток пешеходов, так и доступность общественного транспорта и парковок. И конечно учитывают целевую аудиторию: для магазинов детских товаров, лучше расположить в том районе где много семей с детьми и инфраструктуры для них.

  • Торговля требует выполнения стандартов безопасности и санитарии. Нужно быть уверенным что здание соответствует требованиям пожарной безопасности, санитарии и перепланировки торгового помещения узаконены. Помещение должно иметь достаточно места для товара и посетителей магазина. Хорошо бы найти профессионального юриста / агента по коммерческой недвижимости для помощи при заключения договора и решении юридических вопросов связанных с торговым помещением. Рекомендации + опыт эксперта поможет избежать лишних трат и распространенных ошибок при выборе здания.

  • Помимо арендной платы или стоимости покупки помещения, нужно заложить дополнительные расходы на ремонт, обслуживание помещений, зарплаты персонала, закупку товара и уплату налогов.

  • Как много в этом районе конкурентов? Кто целевая аудитория, где проживает или как часто посещает этот район?

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

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

Специалисты, кто занимаются геоаналитикой профессионально для открытия новых магазинов, имеют доступ к большому объему часто платной информации: локации абонентов от операторов связи, платежных операторов и банков, доступ к подготовленным данным из ГИС ЖКХ/Реформа ЖКХ, и много еще к чему. Но они, как почтальон Печкин, вам их не дадут. И не дадут результат расчета на данных: дешево, быстро, качественно - выбирай любые два варианта.

Не претендую на академичность и точность моего субъективного исследования, лишь покажу вам как cамостоятельно рассчитать достижимость магазинов клиентами на основе свободных данных из OpenStreetMap. К счастью, для Москвы полнота и качество данных для многоэтажных жилых домов одно из самых лучших по РФ.

Данные и расчеты

Как подготовть данные для анализа я уже рассказывал в "Где 15 минут пешком от дома до метро в Москве…" и в "Где бы вы точно не жили и не остановились даже на время, если бы знали и выбирали на основе фактов". Пример расчета расстояний для магазинов с программой также публиковал в статье "Жилье в 500м от сетевых продуктовых магазинов в Москве". Данные из OpenStreetMap PBF файла загружаются в PostgreSQL базу с установленным расширением PostGIS и H3.

Кроме пешеходных дистанций, расчитаных с помощью GraphHopper, понадобится еще число квартир в каждом доме. Его можно найти в значениях OSM тега building:flats.

Для жилых домов, у которых не указан этот тег проставляем число квартир по правилам:

  • Если для здания указан диапазон квартир на подъездах (addr:flats) то выбираем максимальное значение для дома. Если это значение отличается не более чем на 50% от вычисленного по среднему, то для здания выбираем его, иначе значение из следующего пункта

  • можно проставить среднее значение квартир и умножить на число этажей в доме и площадь здания:

CREATE TABLE flats_per_building AS WITH flats_precalc AS
  (SELECT g.id,
          g.type,
          (CASE
               WHEN g.tags->'building:levels' ~ '^\d+(\.\d+)?$' THEN g.tags->'building:levels'
               ELSE NULL
           END)::real levels,
          (CASE
               WHEN g.tags->'building:flats' ~ '^[0-9]+$' THEN g.tags->'building:flats'
               ELSE NULL
           END)::smallint flats,
          (CASE
               WHEN g.type='ways'
                    AND ST_IsClosed(g.geom) THEN st_area(ST_MakePolygon(g.geom)::geography)
               WHEN g.type='multipolygon' THEN st_area(g.geom::geography)
               ELSE 0
           END) area
   FROM geometry_global_view g
   WHERE type<>'nodes'
     AND (CASE
              WHEN g.tags->'building:levels' ~ '^\d+(\.\d+)?$' THEN g.tags->'building:levels'
              ELSE NULL
          END)::real>2 --and **проверка что здание жилое - портянка OSM тегов**;
     ),

building_max_flat AS
  (SELECT g.id,
          type,
          max(max_flat)::smallint max_flat
   FROM geometry_global_view g
   INNER JOIN
     (SELECT id,(regexp_split_to_array(tags->'addr:flats', '-'))[2] max_flat,
                                                                    geom
      FROM nodes
      WHERE tags?'addr:flats'
        AND tags?'entrance'
        AND tags->'addr:flats' ~ '^\d+\-\d+?$') e ON st_contains(CASE
                                                                     WHEN TYPE='ways'
                                                                          AND ST_IsClosed(g.geom) THEN ST_MakePolygon(g.geom)
                                                                     ELSE g.geom
                                                                 END, e.geom)
   WHERE type<>'nodes'
     AND ((CASE
               WHEN g.tags->'building:levels' ~ '^\d+(\.\d+)?$' THEN g.tags->'building:levels'
               ELSE NULL
           END)::real>2)
     AND not(g.tags?'building:flats')
   GROUP BY 1,2),

flats_aprox AS
  (SELECT id,
          type,
          flats,
          (levels*area*
             (SELECT avg((flats/levels)/area)
              FROM flats_precalc
              WHERE area>0
                AND flats>0))::smallint aproximated_flats
   FROM flats_precalc)
SELECT b.id,
       b.type,
       coalesce(flats, CASE
                           WHEN max_flat IS NOT NULL
                                AND aproximated_flats>0
                                AND abs(max_flat-aproximated_flats)/(aproximated_flats::real)<0.5 THEN -1*max_flat
                           ELSE aproximated_flats
                       END) flats
FROM flats_aprox b LEFT JOIN building_max_flat USING(id,type);

ALTER TABLE flats_per_building ADD PRIMARY KEY(id,type);

Спасибо @roman_deev за подсказку в комментарии к статье, чтобы получить более точное решение по числу квартир из данных уже доступных в OSM!

Простой вариант того же запроса, где отсутствующие flats заполняются только средним
create table flats_per_building as 
WITH flats_precalc as (select g.id,g.type, 
  (case when g.tags->'building:levels' ~ '^\d+(\.\d+)?$' then g.tags->'building:levels' else NULL end)::real levels,
  (case when g.tags->'building:flats' ~ '^[0-9]+$' then g.tags->'building:flats' else NULL end)::smallint flats,
  (CASE WHEN g.type='ways' and ST_IsClosed(g.geom) THEN st_area(ST_MakePolygon(g.geom)::geography) WHEN g.type='multipolygon' THEN st_area(g.geom::geography) ELSE 0 END) area
 from geometry_global_view g 
 where  type<>'nodes' and (case when g.tags->'building:levels' ~ '^\d+(\.\d+)?$' then g.tags->'building:levels' else NULL end)::real>2)--and **проверка что здание жилое - портянка OSM тегов**;
   select id,type,
       coalesce(flats, (levels*area*
                        (select avg((flats/levels)/area) 
                         from flats_precalc 
                         where area>0 and flats>0))::smallint) flats 
   from flats_precalc ; 

alter table flats_per_building add primary key(id,type);

Это простое решение чтобы заполнить пропуски в данных. Правильнее и точнее - это обойти дома где не указано число квартир и по почтовым ящикам( табличкам с номером подъезда) посмотреть номера квартир и указать точное число квартир в доме в исходных данных. Когда анализ выполняется для больших территорий, то можно найти данные в ГИС ЖКХ, но выгрузок для этого сайта в открытом доступе нет.

Зеленые точки - building:flats - указан в OSM, серые - апроксимация значений. Видно что точность будет выше не в центре города и не за МКАД (хотя район Митино хорошее исключение).
Зеленые точки - building:flats - указан в OSM, серые - апроксимация значений. Видно что точность будет выше не в центре города и не за МКАД (хотя район Митино хорошее исключение).

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

osmworld=# \d flats_per_building

 Column |      Type       |  
--------+-----------------|
 id     | bigint          | 
 type   | table_reference | 
 flats  | smallint        |
Indexes:
    "flats_per_building_pkey" PRIMARY KEY, btree (id, type)

osmworld=# \d distance

    Column     |      Type       |  
---------------+-----------------|
 building_id   | bigint          | 
 building_type | table_reference | 
 poi_id        | bigint          | 
 poi_type      | table_reference | 
 distance      | smallint        | 

osmworld=# select count(*) from distance;
  count   
----------
 60816611
(1 row)
Time: 1722,807 ms (00:01,723)

Теперь объединим эти данные - сложив число квартир каждого дома в пешеходной доступности 1000м от точек интереса (POI) - магазинов:

create table flats_per_poi as 
  select poi_id, poi_type, sum(flats) total_flats 
  from distance inner join flats_per_building 
    on building_id=id and building_type=type and distance<=1000 
  group by poi_id,poi_type 
  order by total_flats desc;

SELECT 129128
Time: 3108,646 ms (00:03,109)

Обогатим эти данные районом и в каждом районе отсортируем данные по убыванию числа квартир в пешей доступности:

CREATE TABLE moscow_district AS select tags->'name' name,polygon from multipolygon where tags->'admin_level'='8' and tags->'boundary'='administrative';

CREATE INDEX idx_moscow_district_geometry ON moscow_district USING gist (polygon);

create table best_retail_location_per_district as 
  select name, 
         row_number() over (partition by district order by total_flats desc) max_flats_rating, district,
         total_flats, 
         centre
   from (select total_flats,
                coalesce(r.tags->'name',r.tags->'name:ru',r.tags->'name:en') name, (select name from moscow_district where st_contains(polygon, centre) limit 1) district, centre
         from flats_per_poi inner join poi r 
            on poi_id=id and poi_type=type 
               and 'shop' = any(categories) 
               and coalesce(r.tags->'name',r.tags->'name:ru',r.tags->'name:en') is not null 
          order by total_flats desc) shops;

Если нужен анализ конкурентов при открытии продуктового магазина или места для торговли товаром который не представлен у сетевых магазинов Пятёрочка, Перекрёсток, Дикси, Магнит, Магнолия, ВкусВилл, Лента, Азбука Вкуса, Ашан, Атак, но попадает в ту же целевую аудиторию, можно переиспользовать аналитику которую уже для себя выполнили торговые сети и открыли в нужном месте магазин. Для этого в фильтры можно добавить:

(tags@>'shop=>supermarket' or tags@>'shop=>convenience') and 
    		(tags->'brand' in ('Billa',
                               'Eurospar','EuroSPAR','EUROSPAR',
                               '"METRO Cash & Carry"',
                               'Spar','SPAR',
                               '"АВ Daily"','Авокадо','Авоська','"Азбука Вкуса"',
                               'Атак','Ашан','"Ашан Сити"',
                               'Верный','Виктория',
                               'ВкусВилл','Дикси','Лента','Магнит',
                               'Магнолия','"Мини Лента"','МясновЪ',
                               'О’КЕЙ',
                               'Перекрёсток','"Перекресток Экспресс"',
                               'Пятёрочка','Светофор',
                               '"Супер Лента"','СуперЛента',
                               '"У Палыча"','Фасоль','Чижик','Ярче!')

Статистика по брендам продуктовых в Москве:

select tags->'brand' brand,count(*) from geometry_global_view where (tags@>'shop=>supermarket' or tags@>'shop=>convenience') and tags?'brand' group by 1 having count(*)>1 order by 2 desc;

       brand          | count 
----------------------+-------
 Пятёрочка            |  1142
 ВкусВилл             |   594
 Дикси                |   434
 Магнит               |   388
 Перекрёсток          |   295
 Магнолия             |   161
 Лента                |    84
 У Палыча             |    76
 Верный               |    69
 Азбука Вкуса         |    68
 МясновЪ              |    56
 Суши Wok             |    40
 Ашан                 |    27
 АВ Daily             |    27
 Виктория             |    24
 Eurospar             |    23
 Ярче!                |    22
 Атак                 |    15
 Супер Лента          |    14
 Светофор             |    11
 Чижик                |     9
 О’КЕЙ                |     9
 СуперЛента           |     9
 Spar                 |     7
 Авокадо              |     6
 Перекресток Экспресс |     6
 SPAR                 |     4
 Авоська              |     3
 METRO Cash & Carry   |     3
 EuroSPAR             |     3
 Ашан Сити            |     3
 Фасоль               |     3
 Мини Лента           |     3
 Billa                |     2
 Нефтьмагистраль      |     2
 Продуктовий магазин  |     2
 EUROSPAR             |     2
(37 rows)

Отобразим в каждом районе лучшие места для торговых точек по числу жителей:

select * from best_retail_location_per_district where max_flats_rating<=10
select * from best_retail_location_per_district where max_flats_rating<=10

Список существующих торговых мест по одному в каждом из районов Москвы с наибольшим количеством квартир в пешеходных окрестностях 1км от торгового помещения:

select district, total_flats, st_x(centre), st_y(centre) 
  from best_retail_location_per_district 
  where max_flats_rating=1 
  order by total_flats desc;

            district             | total_flats |        st_x        |        st_y        
---------------------------------+-------------+--------------------+--------------------
 район Митино                    |       44554 | 37.355865200000004 |         55.8478696
 район Измайлово                 |       39255 |          37.800719 | 55.793489400000006
 район Восточное Измайлово       |       39254 | 37.799821800000004 |  55.79304380000001
 район Гольяново                 |       32907 |        37.80093225 |        55.81096895
 район Раменки                   |       31709 |         37.5055284 |  55.70210530000001
 район Северное Измайлово        |       30541 |         37.8124447 |         55.8050585
 Ломоносовский район             |       29928 |         37.5473658 |         55.6803493
 район Южное Бутово              |       29658 |         37.5593346 |         55.5486773
 Таганский район                 |       29457 |         37.6752278 | 55.739144700000004
 район Черёмушки                 |       28843 |         37.5469279 |         55.6735065
 Пресненский район               |       28788 | 37.518790700000004 |         55.7570794
 Гагаринский район               |       28615 |         37.5473967 |         55.6811439
 район Хорошёво-Мнёвники         |       28588 |         37.4602547 |         55.7818655
 район Щукино                    |       28155 |         37.4625799 |         55.8089335
 район Ховрино                   |       27974 |         37.5031309 |         55.8609803
 Обручевский район               |       27952 |          37.517463 |         55.6643959
 район Аэропорт                  |       27551 |         37.5356024 | 55.803232200000004
 район Северное Бутово           |       27199 | 37.577473600000005 |          55.568533
 район Некрасовка                |       27009 | 37.927194300000004 |         55.7028795
 Бескудниковский район           |       26881 | 37.550535800000006 | 55.875935000000005
 район Кунцево                   |       26791 | 37.410576407649934 |  55.73863922462011
 район Богородское               |       26623 | 37.719145000000005 |         55.8078323
 район Зюзино                    |       26066 | 37.599057900000005 | 55.653555100000005
 район Левобережный              |       25818 | 37.475883100000004 | 55.864906600000005
 район Северное Тушино           |       25815 |         37.4244894 |         55.8499326
 район Солнцево                  |       25813 |         37.4061808 |         55.6502106
 район Коньково                  |       25808 | 37.524680000000004 |         55.6426711
 район Восточное Дегунино        |       25701 |         37.5481326 | 55.878368900000005
 район Люблино                   |       25541 |         37.7584202 |  55.67367470000001
 район Северное Медведково       |       25425 | 37.662568300000004 | 55.888120150000006
 Академический район             |       25403 |         37.5539208 |         55.6836137
 район Преображенское            |       25278 | 37.717225500000005 | 55.804943400000006
 район Выхино-Жулебино           |       24985 |         37.8533144 | 55.684696200000005
 район Марьино                   |       24825 |          37.765138 |          55.668098
 Дмитровский район               |       24682 |         37.5332311 |         55.8812342
 район Арбат                     |       24663 |         37.5830846 |         55.7471182
 район Южное Тушино              |       24653 | 37.425816600000005 | 55.848617700000005
 район Отрадное                  |       24578 |        37.60205305 | 55.863150100000006
 район Новокосино                |       24372 | 37.858880500000005 | 55.740321800000004
 Нагорный район                  |       24369 |         37.6060573 |         55.6516913
 Ново-Переделкино                |       24264 | 37.349189800000005 |         55.6410172
 район Текстильщики              |       24168 |         37.7484052 |         55.7007545
 район Нагатинский Затон         |       24130 |         37.6775296 |  55.68217430000001
 район Свиблово                  |       24093 |         37.6462869 |         55.8517595
 район Хамовники                 |       24042 |         37.5789264 |         55.7254597
 Тверской район                  |       24037 |         37.5973848 |         55.7709714
 Алексеевский район              |       23962 | 37.645125400000005 |  55.81044300000001
 Орехово-Борисово Южное          |       23737 |         37.7338043 |         55.6073353
 район Новогиреево               |       23715 |         37.8027832 | 55.753313600000006
 Головинский район               |       23674 |         37.5111866 |  55.85889400000001
 район Чертаново Центральное     |       23578 | 37.593337250000005 |        55.60662975
 район Кузьминки                 |       23536 |          37.763281 | 55.705186100000006
 район Перово                    |       23473 |         37.7999486 | 55.754013400000005
 Лосиноостровский район          |       23342 |         37.6858603 |          55.876531
 район Бибирево                  |       23312 | 37.596387500000006 |         55.8903642
 район Южное Медведково          |       23006 |         37.6367825 |  55.87363860000001
 Рязанский район                 |       22981 | 37.778901677736364 |  55.71208193761292
 район Строгино                  |       22929 |         37.4063571 | 55.807376600000005
 Тимирязевский район             |       22852 |         37.5732797 | 55.812274200000004
 район Якиманка                  |       22658 |         37.6136508 |         55.7213738
 район Чертаново Южное           |       22591 | 37.605664700000005 | 55.594416100000004
 район Крылатское                |       22568 |         37.4084348 | 55.760584400000006
 район Проспект Вернадского      |       22325 | 37.512944600000004 | 55.666986800000004
 поселение Сосенское             |       22225 | 37.477374700000006 |  55.57048090000001
 Бабушкинский район              |       21958 |         37.6771351 | 55.867305300000005
 поселение Внуковское            |       21831 |         37.3237201 |         55.6370195
 район Зябликово                 |       21643 |         37.7416136 |         55.6214627
 район Тёплый Стан               |       21573 |         37.5181272 | 55.632703400000004
 Даниловский район               |       21467 |         37.6175062 | 55.719938000000006
 район Ясенево                   |       21410 |         37.5439495 | 55.607119700000005
 район Ивановское                |       21390 | 37.826334100000004 | 55.753645600000006
 район Коптево                   |       21371 |          37.520052 | 55.823584700000005
 район Марьина Роща              |       21313 |         37.6127702 |         55.7926579
 район Лианозово                 |       21263 |         37.5785372 |  55.89771390000001
 район Сокол                     |       21262 |         37.5147357 | 55.800178700000004
 Останкинский район              |       21086 | 37.637360900000004 |         55.8134049
 Южнопортовый район              |       21057 | 37.664825900000004 | 55.731036100000004
 Савёловский район               |       21011 |         37.5738591 |         55.7945739
 район Замоскворечье             |       20951 |          37.617964 |         55.7204885
 Басманный район                 |       20784 |         37.6759708 |  55.77245370000001
 район Орехово-Борисово Северное |       20782 | 37.708416400000004 | 55.609964500000004
 район Западное Дегунино         |       20547 |         37.5228794 |         55.8780158
 район Чертаново Северное        |       20459 |         37.6001775 |         55.6291803
 Бутырский район                 |       20390 | 37.581405100000005 |         55.8199976
 район Сокольники                |       20328 | 37.676637400000004 |         55.7855112
 Войковский район                |       20192 |         37.5187881 | 55.821412200000005
 Мещанский район                 |       20033 | 37.621846000000005 |         55.7732935
 Можайский район                 |       19652 | 37.423553500000004 | 55.725824700000004
 Донской район                   |       19509 | 37.607535000000006 |         55.7157616
 район Очаково-Матвеевское       |       19501 |         37.4757927 |         55.7104287
 Тропарёво-Никулино              |       19432 |         37.4681865 | 55.664694000000004
 Красносельский район            |       18394 | 37.636166200000005 |         55.7719856
 Алтуфьевский район              |       18364 | 37.597489200000005 | 55.885716900000006
 район Лефортово                 |       18326 |         37.7081619 |         55.7505072
 район Братеево                  |       18287 |         37.7634054 |  55.63621010000001
 район Соколиная Гора            |       18285 |         37.7326258 | 55.779113100000004
 район Царицыно                  |       18200 |         37.6565437 |         55.6260485
 Хорошёвский район               |       17739 | 37.533148600000004 | 55.776608200000005
 район Вешняки                   |       17409 | 37.823054400000004 | 55.721068200000005
 район Котловка                  |       17388 |         37.5936012 |         55.6813876
 район Косино-Ухтомский          |       17371 |  37.88647929577547 |   55.7146019953649
 район Дорогомилово              |       17313 |         37.5489696 | 55.743085300000004
 район Филёвский Парк            |       17221 | 37.489751000000005 |         55.7397427
 район Нагатино-Садовники        |       17176 |         37.6629725 | 55.679679900000004
 район Печатники                 |       17069 |         37.7247369 | 55.682193700000006
 район Фили-Давыдково            |       16830 | 37.451718400000004 | 55.723709500000005
 район Беговой                   |       16642 |         37.5755901 |         55.7896471
 район Ростокино                 |       15438 |         37.6557199 |         55.8335372
 район Марфино                   |       14216 | 37.589894400000006 |         55.8285594
 район Метрогородок              |       13866 |         37.7586345 | 55.825178550000004
 район Бирюлёво Восточное        |       13122 |        37.66389045 | 55.601072300000006
 район Москворечье-Сабурово      |       12305 | 37.663324800000005 |         55.6403018
 район Покровское-Стрешнево      |       12231 |         37.4536358 | 55.830881500000004
 Ярославский район               |       10980 |          37.687691 | 55.862006400000006
 поселение Московский            |       10884 |  37.41915496649685 | 55.660058285851896
 район Северный                  |       10731 | 37.544192200000005 | 55.928780100000004
 Нижегородский район             |       10346 | 37.681484632205915 |  55.72787293563479
 поселение Воскресенское         |        9373 |          37.514949 |         55.5256966
 район Капотня                   |        9128 | 37.796075200000004 |         55.6350781
 район Куркино                   |        7364 | 37.428532700000005 |  55.87720755000001
 район Внуково                   |        5940 |         37.2621203 | 55.614216500000005
 поселение «Мосрентген»          |        4296 |         37.4733691 | 55.620890900000006
 район Бирюлёво Западное         |        1817 |         37.6464437 |         55.5913467

Где total_flats - число квартир в радиусе 1км от торговой точки района district и её координаты. Конечно эти цифры включают расчет только по пешеходной доступности и только тех многоэтажных домов, что не дальше 2км от входа в метро.

Более насыщенный цвет - больше квартир в окрестности торговой точки в районе
Более насыщенный цвет - больше квартир в окрестности торговой точки в районе

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

Если углубляться в эту тему, то нужно больше данных и смотреть на применимость модели Хаффа( Huff model), Multinomial Logit Model, Multiplicative Interactive Choice. Данные о расположении торговых точек магазинов по сегментам бизнеса доступны как в данных OSM, так и в объектах Places от Overture Maps Foundation. Надеюсь, что расчеты и геоаналитика помогут найти лучшее расположение для магазина шаговой доступности в месте, популярном у жителей района Москвы.

Уважаемая бульварная пресса! При публикации этой новости, ссылка на оригинал на сайте Хабр обязательна! Искажение информации при цитировании вами моих статей с Хабра говорит о заказном характере вашей работы и крайне низком профессионализме.

Вывод

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

Использовать open source технологии и открытые данные - лучше чем гадать на кофейной гуще и верить на слово. Конечно это лишь один из факторов выбора места для торговли, где реальный успех малого бизнеса включает множество других факторов.

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


  1. igor_suhorukov Автор
    07.12.2023 15:29

    @sshikov смотри что получается если перевернуть агрегацию данных наоборот.


  1. sshikov
    07.12.2023 15:29

    Вот с этим я могу поспорить - большая часть людей в России покупает продукты и товары в магазинах. 

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


    1. igor_suhorukov Автор
      07.12.2023 15:29

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


      1. sshikov
        07.12.2023 15:29

        Ну так я и говорю, что скорее всего тут разные есть истории. По-хорошему, надо исследовать.


        1. igor_suhorukov Автор
          07.12.2023 15:29

          Ты прав! Кажется, что проще всего это исследовать банкам и ФНС - все платежи картой и оплата через POS терминалы доступны для аналитики, а кеш флоу из магазинов есть в налоговой. Но обычному человеку это не перепроверить.


          1. sshikov
            07.12.2023 15:29
            +1

            Ну мы кстати пытались. Вот представь, что у тебя есть под рукой биллинг (по картам твоего банка). И в этом биллинге написано, что человек заплатил какому-то ООО Вася Пупкин (название латиницей, транслитерация) столько-то денег непонятно за что. Биллинг этот присылает тебе Visa, ну или в общем, некая карточная система, и составлен он по ее стандартам. И адрес там тоже написан так, что геокодировать его удается скажем в 80% случаев. Да, еще биллинг это терабайты в день, скажем, и работать с ним не так и просто чисто технически.

            Короче говоря, если это POS терминал не принадлежит своему банку, то исследовать платежи даже в такой ситуации далеко не тривиально. Вплоть до того, что мы начали применять всякие эвристики типа того, что если платежи А и Б были произведены в течение 5 минут - значит POS Б (это например банк конкурент) находится где-то близко от POS А (который нашего банка, и мы даже знаем координаты), и мы можем примерно прикинуть, где этот POS стоит, даже не имея правильного адреса, ну или уточнить результат геокодирования. С кучей допущений, что человек скажем шел пешком, и за 5 минут удалился не более чем на ... (а если он на самокате?).

            Даже будучи банком, это не так просто, не все удается определить, что может быть интересно. Удобнее всего когда у человека в кармане смартфон, и там стоит твое платежное приложение, и каждый платеж сопровождается координатами, помимо всего остального. Да и то остаются вопросы при платежах скажем за билет в трамвае ;) Ну или нужно сочетать данные банка, и скажем сотового провайдера - а это уже сложная задача, причем не технически.

            А уж про обычного человека я и не говорю.


            1. igor_suhorukov Автор
              07.12.2023 15:29

              Счастливчик, нетривиальная задача попадалась!

              Я про такой мэтчинг только слышал. В том числе от компании когда получал оффер, которая не банк, а просто продает банкам систему для оповещения по операциям. Там вроде ElasticSearch для фаззи матчинга пытались использовать - дорого на таком траффике.

              даже не имея правильного адреса, ну или уточнить результат
              геокодирования. С кучей допущений, что человек скажем шел пешком, и за 5 минут удалился не более чем на ... (а если он на самокате?).

              Поэтому банкам выгодно "впаривать" клиентам свою виртуальную сим карту.


              1. sshikov
                07.12.2023 15:29

                Поэтому банкам выгодно "впаривать" клиентам свою виртуальную сим карту.

                Конечно. И иметь данные с сот.


  1. miksoft
    07.12.2023 15:29

    сколько человек сможет дойти до конкретного магазина

    В качестве идеи для продолжения:

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

    И следующий шаг:

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


    1. igor_suhorukov Автор
      07.12.2023 15:29

      Спасибо вам за совет!

      нужно больше данных и смотреть на применимость модели Хаффа( Huff
      model), Multinomial Logit Model, Multiplicative Interactive Choice.

      Модель Хаффа как раз учитывает факторы, нужны данные.


  1. sshmakov
    07.12.2023 15:29

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

    Как-то в одном инвестбанке рассказали отличный контрпример - ТРЦ Метрополис. Отличное расположение на маршруте, между Балтийской и Войковской, каждый день огромные толпы народа перемещаются прямо через ТРЦ. Казалось бы, все условия созданы, что здесь может пойти не так?

    И полный пролет - вся эта толпа чуть менее чем полностью игнорирует магазины ТРЦ на своем пути.

    Видимо, близость к дому важнее расположения на маршруте.


    1. igor_suhorukov Автор
      07.12.2023 15:29
      +1

      Отличный пример! Сам много раз через него пробегал ничего не покупая внутри. Возможно это ошибка зонирования ТЦ стоимости аренды а следовательно и магазинов на пути потока и уровня цен. Есть другой пример ТЦ Праздник - постоянно людей внутри полно и торговля там успешная - как раз если бы не поток людей в метро и из метро в этом торговом центре не было бы такого ажиотажа, как и в супермаркете напротив.


  1. BobArctor
    07.12.2023 15:29

    1 километр это просто радиус или реальный путь с учетом всех промзон, рельс и заборов во дворах?


    1. igor_suhorukov Автор
      07.12.2023 15:29

      1 километр это пешеходная дистанция с учетом всех ограничений, а не расстояние по прямой. Для расчетов использовались данные полноценной маршрутизации.


  1. roman_deev
    07.12.2023 15:29
    +1

    Правильнее и точнее - это обойти дома где не указано число квартир и по почтовым ящикам( табличкам с номером подъезда)

    Или достать все подъезды дома и посмотреть на максимальный addr:flats


    1. igor_suhorukov Автор
      07.12.2023 15:29

      Спасибо!

      Дельный совет, но это не так просто. Требуется очистка данных и тестировать парсер...

      osmworld=# select tags->'addr:flats',count(*) from geometry_global_view where tags?'addr:flats' and tags?'entrance' group by 1 order by length(tags->'addr:flats')  desc limit 20;
                                                  ?column?                                            | count 
      ------------------------------------------------------------------------------------------------+-------
       61-69, 90-109, 130-149, 170-189, 210-229, 250-269, 290-309, 330-349, 370-389, 410-429, 450-460 |     1
       70-89, 110-129, 150-169, 190-209, 230-249, 270-289, 310-329, 350-369, 390-409, 430-449         |     1
       1-9; 10-19; 20-29; 30-39; 40-49; 50-59; 119-120; 131-140; 151-160; 171-178                     |     1
       3-29;31-32;3А-4А;6А-8А;10А-12А;14А-16А;18А-20А;22А-24А;26А-28А                                 |     1
       34-59;34А;36А-38А;40А-42А;44А-46А;48А-50А;52А-54А;56А-57А;59А                                  |     1
       60-60А;69-72;81-88;97-104;113-120;130-136;146;188-195                                          |     1
       8-20;21а-23а;22а;26а-28а;30а;31а;34а-36а;37;39;40                                              |     1
       112-124;286;286а;287-288;294;294а;295;295а                                                     |     1
       95-108;289;289а;290;290а;296;296а;297;297а                                                     |     1
       1-59, 119-120, 131-140, 151-160, 171-178                                                       |     1
       60-68; 69-118; 121-130; 141-150; 161-170                                                       |     1
       80А;82А;84А;86А;161-163;91А;158-160                                                            |     1
       61,62,64,65,67,68,70,71,73,74,76,77                                                            |     1
       11-14; 24-29; 39-44; 54-59; 69-70                                                              |     1
       60-118, 121-130, 141-150, 161-170                                                              |     1
       52-58;64;40а;42а;44а;46а;48а;50а                                                               |     1
       517-526;518А;520А;522А;524А;526А                                                               |     1
       41-45; 46а; 48a; 50a; 52a; 54а                                                                 |     1
       1-10;1А;2А;3А;4А;5А;6А;7А;8А                                                                   |     1
       7-11;20-23;33;34;36;37;46-50                                                                   |     1
      (20 rows)
      

      C другой стороны:

      osmworld=# select tags->'addr:flats',count(*) from geometry_global_view where tags?'addr:flats' and tags?'entrance' group by 1 order by length(tags->'addr:flats') limit 50;
       ?column? | count 
      ----------+-------
       8        |    34
       2        |   102
       3        |   101
       9        |    18
       1        |   104
       7        |    33
       5        |    76
       6        |    79
       4        |    98
       16       |     3
       1А       |     1
       19       |     3
       17       |     2
       11       |     8
       58       |     1
       12       |     7
       21       |     2
       13       |     3
       6А       |     1
       15       |     4
       20       |     1
       2-       |     1
       31       |     1
       38       |     1
       14       |     4
       37       |     1
       10       |    17
       18       |     2
       85-      |     1
       6-9      |     2
       280      |     1
       1-3      |     3
       251      |     1
       37-      |     1
       46-      |     1
       3-5      |     1
       2-7      |     2
       3-4      |     1
       6-?      |     1
       301      |     1
       4-7      |     1
       3-9      |     1
       1-5      |    11
       4-8      |     2
       1-2      |     2
       160      |     1
       1-9      |    66
       2-8      |     3
       1-8      |    90
       5-6      |     1
      (50 rows)
      


    1. igor_suhorukov Автор
      07.12.2023 15:29

      Решил быстро проверить как много домов в Москве можно заполнить максимальным номером квартиры с подъездов дома( при этом отбросив все addr:flats которые не соответствуют шаблону ^\d+-\d+?$) - вышло 1186 шт. Жаль этот подход покрывает малую часть домов:

      with entrances as (
      select id,(regexp_split_to_array(tags->'addr:flats','-'))[2] max_flat,geom from nodes where tags?'addr:flats' and tags?'entrance' and tags- >'addr:flats' ~ '^\d+\-\d+?$')
      select g.id,type,max(max_flat) max_flat from geometry_global_view g inner join entrances e on st_contains(CASE WHEN type='ways' and ST_IsClosed(g.geom) THEN ST_MakePolygon(g.geom) ELSE g.geom END,e.geom)  where type<>'nodes' and ((case when g.tags->'building:levels' ~ '^\d+(\.\d+)?$' then g.tags->'building:levels' else NULL end)::real>2) and not(g.tags?'building:flats') --проверка по OSM тегам что дом жилой 
      group by 1,2;

      IMHO не особо лучше среднего - нужно валидировать, есть результаты где более 1000 квартир в доме. Ну и в OSM разметке видел что иногда отмечают не все подъезды. Идея-то логичная и отличная!


      1. roman_deev
        07.12.2023 15:29

        Потыкался в данные Москвы и внезапно там много building:flats и мало addr:flats. Например, в Питере обратная ситуация (но ещё и свои приколы в виде пропущенных квартир и склеенных зданий в центре)

        Навскидку, в Москве не более чем у 25% многоэтажек по addr:flats получится узнать число квартир (поделил 8464 addr:flats начинающиеся с 1- на 31925 building=apartments)
        https://overpass-turbo.eu/s/1ExL

        есть результаты где более 1000 квартир в доме.

        А в чём проблема? Вполне реальные дома


        1. igor_suhorukov Автор
          07.12.2023 15:29

          Потыкался в данные Москвы и внезапно там много building:flats и мало addr:flats. Например, в Питере обратная ситуация

          О, да! Каждый город - свои особенности в разметке и свой фокус на тегах!

          Навскидку, в Москве не более чем у 25% многоэтажек по addr:flats

          Я скорее всего плохо описал свой прошлый запрос для проверки. 1186 это число домов в котором по addr:flats удалось добавить значение по максимальной квартире на подъезде. Это число в добавок к домам где указан building:flats. К сожалению, building=apartments не всегда расставлено на многоэтажных жилых домах. Поэтому там в прошлых публикаций тянется портянка тегов в where запроса.

          Спасибо тебе за совет!!! Я обновил запрос в статье и это заменило около 1% в данных

          count: 445 (1 row) значений квартир в домах из count:43612 (1 row)