Введение
В этом документе описывается, как создать решение для выбора места расположения путем объединения набора данных Places Insights , общедоступных геопространственных данных в BigQuery и API Place Details .
Он основан на демонстрации, представленной на конференции Google Cloud Next 2025, которую можно посмотреть на YouTube .
Бизнес-вызов
Представьте, что вы владеете успешной сетью кофеен и хотите расширить своё присутствие в новом штате, например, в Неваде, где у вас ещё нет представительства. Открытие нового заведения — это серьёзная инвестиция, и принятие решения на основе данных критически важно для успеха. С чего же начать?
Это руководство поможет вам провести многоуровневый анализ, чтобы точно определить оптимальное место для новой кофейни. Мы начнём с анализа по всему штату, постепенно сузим поиск до конкретного округа и коммерческой зоны, а затем проведём гиперлокальный анализ, чтобы оценить отдельные районы и выявить рыночные ниши, изучив конкурентов.
Рабочий процесс решения
Этот процесс следует логической воронке, начиная с общего и постепенно становясь более детальным, чтобы уточнить область поиска и повысить уверенность в окончательном выборе сайта.
Предпосылки и настройка среды
Прежде чем приступить к анализу, вам понадобится среда с несколькими ключевыми возможностями. В этом руководстве мы рассмотрим реализацию с использованием SQL и Python, но общие принципы можно применить и к другим технологическим стекам.
В качестве предварительного условия убедитесь, что ваша среда может:
- Выполнение запросов в BigQuery.
- Доступ к Places Insights. Дополнительную информацию см. в разделе «Настройка Places Insights».
- Подпишитесь на общедоступные наборы данных
bigquery-public-data
и данные Бюро переписи населения США о численности населения округов.
Вам также необходимо визуализировать геопространственные данные на карте, что критически важно для интерпретации результатов каждого этапа анализа. Существует множество способов добиться этого. Вы можете использовать инструменты бизнес-аналитики, такие как Looker Studio , которые напрямую подключаются к BigQuery, или языки программирования для анализа данных, например, Python.
Анализ на уровне штата: найдите лучший округ
Наш первый шаг — это широкий анализ для определения наиболее перспективного округа Невады. Перспективность определяется сочетанием высокой численности населения и высокой плотности существующих ресторанов, что свидетельствует о сильной культуре питания и напитков.
Наш запрос BigQuery достигает этого, используя встроенные компоненты адресов, доступные в наборе данных Places Insights. Запрос подсчитывает количество ресторанов, сначала фильтруя данные, чтобы включить только места в штате Невада, используя поле administrative_area_level_1_name
. Затем он дополнительно уточняет этот набор, включая только места, где массив типов содержит « restaurant
». Наконец, он группирует эти результаты по названию округа ( administrative_area_level_2_name
), чтобы получить количество для каждого округа. Этот подход использует встроенную, предварительно проиндексированную структуру адресов набора данных.
В этом отрывке показано, как мы объединяем геометрию округов с Places Insights и фильтруем по определенному типу места — restaurant
:
SELECT WITH AGGREGATION_THRESHOLD
administrative_area_level_2_name,
COUNT(*) AS restaurant_count
FROM
`places_insights___us.places`
WHERE
-- Filter for the state of Nevada
administrative_area_level_1_name = 'Nevada'
-- Filter for places that are restaurants
AND 'restaurant' IN UNNEST(types)
-- Filter for operational places only
AND business_status = 'OPERATIONAL'
-- Exclude rows where the county name is null
AND administrative_area_level_2_name IS NOT NULL
GROUP BY
administrative_area_level_2_name
ORDER BY
restaurant_count DESC
Простого подсчёта количества ресторанов недостаточно; нам необходимо сопоставить его с данными о населении, чтобы получить истинное представление о насыщенности рынка и имеющихся возможностях. Мы будем использовать данные о населении из Бюро переписи населения США, посвящённые общей численности населения округов .
Чтобы сравнить эти две совершенно разные метрики (количество мест и численность населения), мы используем нормализацию по минимуму и максимуму. Этот метод масштабирует обе метрики до общего диапазона (от 0 до 1). Затем мы объединяем их в один normalized_score
, присваивая каждой метрике 50%-ный вес для сбалансированного сравнения.
В этом фрагменте показана основная логика расчёта рейтинга. Она учитывает нормализованное население и количество ресторанов:
(
-- Normalize restaurant count (scales to a 0-1 value) and apply 50% weight
SAFE_DIVIDE(restaurant_count - min_restaurants, max_restaurants - min_restaurants) * 0.5
+
-- Normalize population (scales to a 0-1 value) and apply 50% weight
SAFE_DIVIDE(population_2023 - min_pop, max_pop - min_pop) * 0.5
) AS normalized_score
После выполнения полного запроса возвращается список округов, количество ресторанов, численность населения и нормализованный балл. Сортировка по normalized_score DESC
показывает, что округ Кларк является явным победителем для дальнейшего исследования и главным претендентом.
На этом снимке экрана показаны 4 округа с наивысшим показателем по нормализованному показателю. Исходная численность населения в этом примере намеренно не указана.
Анализ на уровне округа: найдите самые загруженные коммерческие зоны
Теперь, когда мы определили округ Кларк, следующим шагом будет увеличение масштаба, чтобы найти почтовые индексы с самой высокой коммерческой активностью. Основываясь на данных по нашим действующим кофейням, мы знаем, что эффективность выше, если они расположены рядом с высокой плотностью магазинов известных брендов, поэтому мы будем использовать это в качестве показателя высокой посещаемости.
Этот запрос использует таблицу brands
в Places Insights, содержащую информацию о конкретных брендах. К этой таблице можно обратиться, чтобы получить список поддерживаемых брендов. Сначала мы определяем список наших целевых брендов, а затем объединяем его с основным набором данных Places Insights, чтобы подсчитать, сколько таких магазинов приходится на каждый почтовый индекс в округе Кларк.
Наиболее эффективный способ достижения этой цели — двухэтапный подход:
- Сначала мы выполним быстрое негеопространственное агрегирование для подсчета брендов в пределах каждого почтового индекса.
- Во-вторых, мы присоединим эти результаты к общедоступному набору данных , чтобы получить границы карты для визуализации.
Подсчитайте бренды, используя поле postal_code_names
Этот первый запрос реализует основную логику подсчёта. Он фильтрует населённые пункты округа Кларк, а затем извлекает массив postal_code_names для группировки количества брендов по почтовому индексу.
WITH brand_names AS (
-- First, select the chains we are interested in by name
SELECT
id,
name
FROM
`places_insights___us.brands`
WHERE
name IN ('7-Eleven', 'CVS', 'Walgreens', 'Subway Restaurants', "McDonald's")
)
SELECT WITH AGGREGATION_THRESHOLD
postal_code,
COUNT(*) AS total_brand_count
FROM
`places_insights___us.places` AS places_table,
-- Unnest the built-in postal code and brand ID arrays
UNNEST(places_table.postal_code_names) AS postal_code,
UNNEST(places_table.brand_ids) AS brand_id
JOIN
brand_names
ON brand_names.id = brand_id
WHERE
-- Filter directly on the administrative area fields in the places table
places_table.administrative_area_level_2_name = 'Clark County'
AND places_table.administrative_area_level_1_name = 'Nevada'
GROUP BY
postal_code
ORDER BY
total_brand_count DESC
На выходе получается таблица почтовых индексов и соответствующего им количества брендов.
Прикрепите геометрию почтового индекса для отображения на карте
Теперь, когда у нас есть данные о количестве, мы можем получить формы многоугольников, необходимые для визуализации. Этот второй запрос берёт наш первый запрос, оборачивает его в общее табличное выражение (CTE) с именем brand_counts_by_zip
и объединяет его результаты с общедоступной geo_us_boundaries.zip_codes table
. Это эффективно привязывает геометрию к нашим предварительно рассчитанным данным.
WITH brand_counts_by_zip AS (
-- This will be the entire query from the previous step, without the final ORDER BY (excluded for brevity).
. . .
)
-- Now, join the aggregated results to the boundaries table
SELECT
counts.postal_code,
counts.total_brand_count,
-- Simplify the geometry for faster rendering in maps
ST_SIMPLIFY(zip_boundaries.zip_code_geom, 100) AS geography
FROM
brand_counts_by_zip AS counts
JOIN
`bigquery-public-data.geo_us_boundaries.zip_codes` AS zip_boundaries
ON counts.postal_code = zip_boundaries.zip_code
ORDER BY
counts.total_brand_count DESC
На выходе получается таблица почтовых индексов, соответствующих им количеств брендов и геометрии почтового индекса.
Мы можем визуализировать эти данные в виде тепловой карты. Более тёмные красные области указывают на более высокую концентрацию наших целевых брендов, указывая нам на наиболее коммерчески насыщенные зоны Лас-Вегаса.
Гиперлокальный анализ: оценка отдельных областей сетки
Определив общую территорию Лас-Вегаса, пришло время для детального анализа. Здесь мы используем наши специфические знания о бизнесе. Мы знаем, что отличная кофейня процветает рядом с другими заведениями, которые работают в часы пик, например, поздним утром и в обеденное время.
Наш следующий запрос становится по-настоящему конкретным. Он начинается с создания мелкозернистой гексагональной сетки для агломерации Лас-Вегаса с использованием стандартного геопространственного индекса H3 (с разрешением 8) для анализа территории на микроуровне. Сначала запрос определяет все сопутствующие компании, работающие в пиковое время (понедельник, с 10:00 до 14:00).
Затем мы применяем взвешенный балл к каждому типу места. Ближайший ресторан для нас ценнее магазина шаговой доступности, поэтому он получает более высокий множитель. Это даёт нам индивидуальный показатель suitability_score
для каждой небольшой зоны.
В этом отрывке описывается логика взвешенной оценки, которая ссылается на предварительно рассчитанный флаг ( is_open_monday_window
) для проверки часов работы:
. . .
(
COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) * 8 +
COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) * 3 +
COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) * 7 +
COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) * 6 +
COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) * 7
) AS suitability_score
. . .
Развернуть для полного запроса
-- This query calculates a custom 'suitability score' for different areas in the Las Vegas -- metropolitan area to identify prime commercial zones. It uses a weighted model based -- on the density of specific business types that are open during a target time window. -- Step 1: Pre-filter the dataset to only include relevant places. -- This CTE finds all places in our target localities (Las Vegas, Spring Valley, etc.) and -- adds a boolean flag 'is_open_monday_window' for those open during the target time. WITH PlacesInTargetAreaWithOpenFlag AS ( SELECT point, types, EXISTS( SELECT 1 FROM UNNEST(regular_opening_hours.monday) AS monday_hours WHERE monday_hours.start_time <= TIME '10:00:00' AND monday_hours.end_time >= TIME '14:00:00' ) AS is_open_monday_window FROM `places_insights___us.places` WHERE EXISTS ( SELECT 1 FROM UNNEST(locality_names) AS locality WHERE locality IN ('Las Vegas', 'Spring Valley', 'Paradise', 'North Las Vegas', 'Winchester') ) AND administrative_area_level_1_name = 'Nevada' ), -- Step 2: Aggregate the filtered places into H3 cells and calculate the suitability score. -- Each place's location is converted to an H3 index (at resolution 8). The query then -- calculates a weighted 'suitability_score' and individual counts for each business type -- within that cell. TileScores AS ( SELECT WITH AGGREGATION_THRESHOLD -- Convert each place's geographic point into an H3 cell index. `carto-os.carto.H3_FROMGEOGPOINT`(point, 8) AS h3_index, -- Calculate the weighted score based on the count of places of each type -- that are open during the target window. ( COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) * 8 + COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) * 3 + COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) * 7 + COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) * 6 + COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) * 7 ) AS suitability_score, -- Also return the individual counts for each category for detailed analysis. COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) AS restaurant_count, COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) AS convenience_store_count, COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) AS bar_count, COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) AS tourist_attraction_count, COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) AS casino_count FROM -- CHANGED: This now references the CTE with the expanded area. PlacesInTargetAreaWithOpenFlag -- Group by the H3 index to ensure all calculations are per-cell. GROUP BY h3_index ), -- Step 3: Find the maximum suitability score across all cells. -- This value is used in the next step to normalize the scores to a consistent scale (e.g., 0-10). MaxScore AS ( SELECT MAX(suitability_score) AS max_score FROM TileScores ) -- Step 4: Assemble the final results. -- This joins the scored tiles with the max score, calculates the normalized score, -- generates the H3 cell's polygon geometry for mapping, and orders the results. SELECT ts.h3_index, -- Generate the hexagonal polygon for the H3 cell for visualization. `carto-os.carto.H3_BOUNDARY`(ts.h3_index) AS h3_geography, ts.restaurant_count, ts.convenience_store_count, ts.bar_count, ts.tourist_attraction_count, ts.casino_count, ts.suitability_score, -- Normalize the score to a 0-10 scale for easier interpretation. ROUND( CASE WHEN ms.max_score = 0 THEN 0 ELSE (ts.suitability_score / ms.max_score) * 10 END, 2 ) AS normalized_suitability_score FROM -- A cross join is efficient here as MaxScore contains only one row. TileScores ts, MaxScore ms -- Display the highest-scoring locations first. ORDER BY normalized_suitability_score DESC;
Визуализация этих результатов на карте показывает очевидные выигрышные локации. Самые тёмно-фиолетовые участки, в основном рядом с Лас-Вегас-Стрип и центром города, — это районы с наибольшим потенциалом для строительства нашей новой кофейни.
Анализ конкурентов: определение существующих кофеен
Наша модель оценки пригодности успешно выявила наиболее перспективные зоны, но один лишь высокий балл не гарантирует успеха. Теперь нам необходимо сопоставить эти данные с данными конкурентов. Идеальное местоположение — это район с высоким потенциалом и низкой плотностью существующих кофеен, поскольку мы ищем очевидную нишу на рынке.
Для этого мы используем функцию PLACES_COUNT_PER_H3
. Эта функция предназначена для эффективного подсчёта количества мест в указанной географической области по ячейке H3.
Сначала мы динамически определяем географию всей агломерации Лас-Вегаса. Вместо того, чтобы полагаться на отдельный населённый пункт, мы запрашиваем общедоступный набор данных Overture Maps, чтобы получить границы Лас-Вегаса и его основных прилегающих населённых пунктов, объединяя их в один полигон с помощью ST_UNION_AGG
. Затем мы передаём эту область в функцию, прося её подсчитать все действующие кофейни.
Этот запрос определяет агломерацию и вызывает функцию для получения количества кофеен в ячейках H3:
-- Define a variable to hold the combined geography for the Las Vegas metro area.
DECLARE las_vegas_metro_area GEOGRAPHY;
-- Set the variable by fetching the shapes for the five localities from Overture Maps
-- and merging them into a single polygon using ST_UNION_AGG.
SET las_vegas_metro_area = (
SELECT
ST_UNION_AGG(geometry)
FROM
`bigquery-public-data.overture_maps.division_area`
WHERE
country = 'US'
AND region = 'US-NV'
AND names.primary IN ('Las Vegas', 'Spring Valley', 'Paradise', 'North Las Vegas', 'Winchester')
);
-- Call the PLACES_COUNT_PER_H3 function with our defined area and parameters.
SELECT
*
FROM
`places_insights___us.PLACES_COUNT_PER_H3`(
JSON_OBJECT(
-- Use the metro area geography we just created.
'geography', las_vegas_metro_area,
-- Specify 'coffee_shop' as the place type to count.
'types', ["coffee_shop"],
-- Best practice: Only count places that are currently operational.
'business_status', ['OPERATIONAL'],
-- Set the H3 grid resolution to 8.
'h3_resolution', 8
)
);
Функция возвращает таблицу, которая включает индекс ячейки H3, ее геометрию, общее количество кофеен и выборку их идентификаторов мест:
Хотя агрегированные данные полезны, важно видеть реальных конкурентов. Именно здесь мы переходим от набора данных Places Insights к API Places . Извлекая идентификаторы sample_place_ids
из ячеек с наивысшим нормализованным рейтингом пригодности, мы можем вызвать API Place Details для получения подробной информации о каждом конкуренте, такой как его имя, адрес, рейтинг и местоположение.
Для этого необходимо сравнить результаты предыдущего запроса, в котором был сгенерирован показатель пригодности, и запроса PLACES_COUNT_PER_H3
. Индекс ячеек H3 можно использовать для получения количества кофеен и их идентификаторов из ячеек с наивысшим нормализованным показателем пригодности.
Этот код Python демонстрирует, как можно выполнить такое сравнение.
# Isolate the Top 5 Most Suitable H3 Cells
top_suitability_cells = gdf_suitability.head(5)
# Extract the 'h3_index' values from these top 5 cells into a list.
top_h3_indexes = top_suitability_cells['h3_index'].tolist()
print(f"The top 5 H3 indexes are: {top_h3_indexes}")
# Now, we find the rows in our DataFrame where the
# 'h3_cell_index' matches one of the indexes from our top 5 list.
coffee_counts_in_top_zones = gdf_coffee_shops[
gdf_coffee_shops['h3_cell_index'].isin(top_h3_indexes)
]
Теперь у нас есть список идентификаторов мест для кофеен, которые уже существуют в ячейках H3 с наивысшей оценкой пригодности, можно запросить дополнительную информацию о каждом месте.
Это можно сделать, отправив запрос к API Place Details напрямую для каждого идентификатора места или используя клиентскую библиотеку . Не забудьте установить параметр FieldMask
, чтобы запрашивать только необходимые данные.
Наконец, мы объединяем всё в единую, мощную визуализацию. Мы используем нашу фиолетовую карту пригодности в качестве базового слоя, а затем добавляем метки для каждой отдельной кофейни, полученные из API Places. Эта итоговая карта представляет собой наглядное представление, синтезирующее весь наш анализ: тёмно-фиолетовые области отображают потенциал, а зелёные метки — текущее состояние рынка.
Выявляя темно-фиолетовые клетки с небольшим количеством или без булавок, мы можем с уверенностью определить точные области, представляющие наилучшие возможности для нашего нового местоположения.
Две указанные выше ячейки имеют высокий балл пригодности, но имеются некоторые явные пробелы, которые могли бы стать потенциальными местами для нашей новой кофейни.
Заключение
В этом документе мы перешли от вопроса масштаба штата « куда расширяться?» к локальному ответу, подкрепленному данными. Наложение различных наборов данных и применение настраиваемой бизнес-логики позволяет систематически снижать риски, связанные с принятием важных бизнес-решений. Этот рабочий процесс, сочетающий в себе масштаб BigQuery, богатый функционал Places Insights и детализацию данных API Places в режиме реального времени, представляет собой мощный шаблон для любой организации, стремящейся использовать данные о местоположении для стратегического роста.
Следующие шаги
- Адаптируйте этот рабочий процесс с учетом вашей собственной бизнес-логики, целевых географических регионов и собственных наборов данных.
- Изучите другие поля данных в наборе данных Places Insights, такие как количество отзывов, уровни цен и оценки пользователей, чтобы еще больше обогатить свою модель.
- Автоматизируйте этот процесс, чтобы создать внутреннюю панель выбора площадок, которую можно использовать для динамической оценки новых рынков.
Подробнее в документации:
Авторы
Хенрик Валв | Инженер DevX