diff --git a/places_insights/places-insights-demo/help.js b/places_insights/places-insights-demo/help.js
index ad4762c..a94caea 100644
--- a/places_insights/places-insights-demo/help.js
+++ b/places_insights/places-insights-demo/help.js
@@ -94,13 +94,13 @@ function generateGuideHtml() {
C. Region Search
- This powerful mode allows you to search by administrative names like cities, states, or postal codes instead of drawing on the map.
+ This powerful mode allows you to search by administrative regions like cities, states, or postal codes using Place Autocomplete.
Select Region Search from the "Demo Type" dropdown.
- Choose a Region Type from the dropdown. This list is dynamically populated based on the selected ${locationTypeLower}.
- Enter a name in the Region Name(s) input box (e.g., "London").
- (Optional) Add Multiple Regions: To search across several regions at once, click the + button after typing each name.
+ Start typing a region name in the Region input box (e.g., "Manhattan").
+ Select the correct region from the dropdown suggestions. This guarantees accurate results by using the exact Place ID.
+ (Optional) Add Multiple Regions: To search across several regions at once, continue searching and adding more places to the list.
D. Route Search
@@ -134,7 +134,7 @@ function generateGuideHtml() {
Business Status: Filter places by their operational status (Operational, Closed Temporarily, Closed Permanently, or Any). Default is Operational .
Attribute Filters: Filter by Price Level, set min/max ratings, or select checkboxes for amenities (e.g., "Offers Delivery").
Opening Hours: Select a Day of Week and time window (Not available in H3 Function mode).
- Brand Filters (US Only): Filter by Brand Category or Brand Name (Not available in H3 Function mode).
+ Brand Filters (US Only): Filter by Brand Name (Not available in H3 Function mode).
5. Choosing Your Visualization
diff --git a/places_insights/places-insights-demo/index.html b/places_insights/places-insights-demo/index.html
index f9d426a..a7e0daf 100644
--- a/places_insights/places-insights-demo/index.html
+++ b/places_insights/places-insights-demo/index.html
@@ -92,19 +92,10 @@ Places Insights
-
Select a region type and enter name(s).
+
Search for a region (e.g., city, state, zip code).
- Region Type
-
- -- Select location first --
-
-
-
-
Region Name(s)
-
-
- +
-
+
Region
+
diff --git a/places_insights/places-insights-demo/main.js b/places_insights/places-insights-demo/main.js
index cb2f396..16cb5cf 100644
--- a/places_insights/places-insights-demo/main.js
+++ b/places_insights/places-insights-demo/main.js
@@ -72,9 +72,12 @@ function handleStartDemo() {
const brandFilters = document.getElementById('brand-filters');
// Logic for Brand Filters visibility
let isUS = false;
+ let countryCode;
if (DATASET === 'SAMPLE') {
- isUS = SAMPLE_LOCATIONS[selectedCountryName] === 'us';
+ countryCode = SAMPLE_LOCATIONS[selectedCountryName];
+ isUS = countryCode === 'us';
} else {
+ countryCode = COUNTRY_CODES[selectedCountryName];
isUS = selectedCountryName === 'United States';
}
@@ -84,7 +87,11 @@ function handleStartDemo() {
brandFilters.classList.add('hidden');
}
- populateRegionTypes(selectedCountryName);
+ // Restrict Region Autocomplete to the active country
+ if (window.regionAutocomplete) {
+ window.regionAutocomplete.includedRegionCodes = [countryCode];
+ }
+
startDemo(selectedCountryName);
} else {
alert("Please select a location to begin.");
@@ -102,22 +109,6 @@ function handleChangeCountryClick() {
invalidateQueryState();
}
-/**
- * Handles clicks on the "+" button to add a region to the list.
- */
-function handleAddRegionClick() {
- const regionInput = document.getElementById('region-name-input');
- const regionList = document.getElementById('selected-regions-list');
- const regionName = regionInput.value.trim();
-
- if (regionName) {
- addTag(toTitleCase(regionName), regionList); // Apply Title Case here
- regionInput.value = '';
- regionInput.focus();
- invalidateQueryState();
- }
-}
-
/**
* Handles clicks on the "+" button to add a brand to the list.
*/
@@ -192,41 +183,49 @@ function handleCopyQueryClick() {
/**
- * Populates the Region Type dropdown based on the selected country's configuration.
+ * Initializes the Place Autocomplete (New) web components for Region search.
*/
-function populateRegionTypes(locationName) {
- const select = document.getElementById('region-type-select');
- select.innerHTML = '';
-
- let countryCode;
- if (DATASET === 'SAMPLE') {
- countryCode = SAMPLE_LOCATIONS[locationName];
- } else {
- countryCode = COUNTRY_CODES[locationName];
- }
+async function initializeRegionSearch() {
+ const { PlaceAutocompleteElement } = await google.maps.importLibrary("places");
+ const container = document.getElementById('region-autocomplete-container');
- const regionFields = REGION_FIELD_CONFIG[countryCode];
+ const autocomplete = new PlaceAutocompleteElement();
+ container.appendChild(autocomplete);
- if (regionFields && regionFields.length > 0) {
- const defaultOption = document.createElement('option');
- defaultOption.value = '';
- defaultOption.textContent = '-- Select a region type --';
- defaultOption.disabled = true;
- defaultOption.selected = true;
- select.appendChild(defaultOption);
+ autocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
+ if (!placePrediction) return;
+ invalidateQueryState();
- regionFields.forEach(field => {
- const option = document.createElement('option');
- option.value = `${field.field}|${field.type}`;
- option.textContent = field.label;
- select.appendChild(option);
- });
- } else {
- const disabledOption = document.createElement('option');
- disabledOption.textContent = 'Region search not available';
- disabledOption.disabled = true;
- select.appendChild(disabledOption);
- }
+ const place = placePrediction.toPlace();
+ // Request the viewport field to properly frame the region on the map
+ await place.fetchFields({ fields: ['id', 'displayName', 'types', 'location', 'viewport'] });
+
+ let targetColumn = null;
+ if (place.types) {
+ for (const type of place.types) {
+ if (REGION_TYPE_TO_BQ_COLUMN[type]) {
+ targetColumn = REGION_TYPE_TO_BQ_COLUMN[type];
+ break;
+ }
+ }
+ }
+
+ if (!targetColumn) {
+ alert(`The selected place type is not supported as a search region. Please select a valid city, state, or neighborhood.`);
+ autocomplete.inputValue = '';
+ return;
+ }
+
+ if (place.location || place.viewport) {
+ addRegionTag(place.displayName, place.id, targetColumn, place.location, place.viewport);
+ } else {
+ alert("Could not retrieve location for this place.");
+ }
+
+ autocomplete.inputValue = '';
+ });
+
+ window.regionAutocomplete = autocomplete;
}
/**
@@ -296,7 +295,6 @@ window.onload = () => {
demoTypeSelect: document.getElementById('demo-type-select'),
startButton: document.getElementById("start-demo-btn"),
changeCountryBtn: document.getElementById('change-country-btn'),
- addRegionBtn: document.getElementById('add-region-btn'),
addBrandBtn: document.getElementById('add-brand-btn'),
showHelpBtn: document.getElementById('show-help-btn'),
guideModal: document.getElementById('guide-modal'),
@@ -325,7 +323,6 @@ window.onload = () => {
elements.copyQueryBtn.addEventListener('click', handleCopyQueryClick);
elements.startButton.addEventListener("click", handleStartDemo);
elements.changeCountryBtn.addEventListener('click', handleChangeCountryClick);
- elements.addRegionBtn.addEventListener('click', handleAddRegionClick);
elements.addBrandBtn.addEventListener('click', handleAddBrandClick);
elements.showHelpBtn.addEventListener('click', showHelpModal);
elements.closeHelpBtn.addEventListener('click', () => hideModal('guide-modal'));
@@ -362,6 +359,7 @@ window.onload = () => {
initializeIdentityServices();
initializeAutocomplete(document.getElementById('place-type-input'));
+ initializeRegionSearch();
initializeRouteSearch();
initializeAccordion();
diff --git a/places_insights/places-insights-demo/query.js b/places_insights/places-insights-demo/query.js
index 3104d91..ee5e7cb 100644
--- a/places_insights/places-insights-demo/query.js
+++ b/places_insights/places-insights-demo/query.js
@@ -38,36 +38,55 @@ function getPolygonSearchParams() {
}
async function getRegionSearchParams() {
- const regionInputValue = document.getElementById('region-type-select').value;
- const regionNameInput = toTitleCase(document.getElementById('region-name-input').value.trim());
- const regionTags = [...document.querySelectorAll('#selected-regions-list span')].map(s => s.textContent);
+ const regionTags = [...document.querySelectorAll('#selected-regions-list .selected-region-tag')];
- if (!regionInputValue || (!regionNameInput && regionTags.length === 0)) {
- return { success: false, message: "Select a Region Type and enter at least one Region Name." };
+ if (regionTags.length === 0) {
+ return { success: false, message: "Search for and select at least one Region." };
}
- const uniqueRegionNames = [...new Set([...regionTags, regionNameInput].filter(Boolean))];
- const [field, dataType] = regionInputValue.split('|');
- const filter = buildRegionFilter(field, dataType, uniqueRegionNames);
+ // Group tags by their target column and type to build the filter
+ const columnsToIds = {};
+ regionTags.forEach(tag => {
+ const col = tag.dataset.column;
+ const colType = tag.dataset.colType;
+ if (!columnsToIds[col]) columnsToIds[col] = { type: colType, ids: [] };
+ columnsToIds[col].ids.push(tag.dataset.id);
+ });
+
+ // Build exact match filters using the Place IDs based on their column data type
+ const filterParts = [];
+ for (const [col, data] of Object.entries(columnsToIds)) {
+ const idList = data.ids.map(id => `'${id}'`).join(', ');
+ if (data.type === 'STRING') {
+ filterParts.push(`places.${col} IN (${idList})`);
+ } else {
+ filterParts.push(`EXISTS (SELECT 1 FROM UNNEST(places.${col}) AS id WHERE id IN (${idList}))`);
+ }
+ }
+ const filter = `(${filterParts.join(' OR ')})`;
- updateStatus('Geocoding regions...');
- const { Geocoder } = await google.maps.importLibrary("geocoding");
+ // Calculate map bounds from tags, prioritizing the viewport for accurate framing
const bounds = new google.maps.LatLngBounds();
- const geocodePromises = uniqueRegionNames.map(name => new Geocoder().geocode({ address: `${name}, ${selectedCountryName}` }));
-
- const resultsArray = await Promise.all(geocodePromises);
- resultsArray.forEach(({ results }) => {
- if (results.length > 0) bounds.union(results[0].geometry.viewport);
+ let hasBounds = false;
+
+ regionTags.forEach(tag => {
+ if (tag.dataset.north) {
+ const sw = { lat: parseFloat(tag.dataset.south), lng: parseFloat(tag.dataset.west) };
+ const ne = { lat: parseFloat(tag.dataset.north), lng: parseFloat(tag.dataset.east) };
+ bounds.union(new google.maps.LatLngBounds(sw, ne));
+ hasBounds = true;
+ } else if (tag.dataset.lat) {
+ bounds.extend({ lat: parseFloat(tag.dataset.lat), lng: parseFloat(tag.dataset.lng) });
+ hasBounds = true;
+ }
});
-
- if (bounds.isEmpty()) {
- throw new Error(`Could not geocode any of the specified regions.`);
+
+ if (hasBounds) {
+ map.fitBounds(bounds);
}
- map.fitBounds(bounds);
return {
- success: true, filter, center: bounds.getCenter(), searchAreaVar: '',
- isMultiRegion: uniqueRegionNames.length > 1, regionType: field, regionDataType: dataType
+ success: true, filter, center: bounds.getCenter(), searchAreaVar: ''
};
}
@@ -199,10 +218,12 @@ async function runQuery() {
// Brand Filters (only applicable here, not in H3 Function)
const brandNames = [...document.querySelectorAll('#selected-brands-list span')].map(s => s.textContent);
+ const pendingBrandInput = document.getElementById('brand-name-input').value.trim();
+ if (pendingBrandInput && !brandNames.includes(pendingBrandInput)) {
+ brandNames.push(pendingBrandInput);
+ }
if (brandNames.length > 0) allFilters.push(buildBrandFilter(brandNames));
- // Brand Category is removed as we no longer have brands.json data
-
const openingDay = document.getElementById('day-of-week-select').value;
const hoursFilter = buildOpeningHoursFilter(openingDay, document.getElementById('start-time-input').value, document.getElementById('end-time-input').value);
if (hoursFilter.whereClause) allFilters.push(hoursFilter.whereClause);
@@ -241,7 +262,7 @@ async function runQuery() {
const h3Res = parseInt(document.getElementById('h3-resolution-slider').value, 10);
sqlQuery = buildH3DensityQuery(searchParams.searchAreaVar, fromClause, whereClause, h3Res);
} else {
- sqlQuery = buildAggregateQuery(searchParams.searchAreaVar, fromClause, whereClause, placeTypes, isBrandQuery, searchParams.isMultiRegion, searchParams.regionType, searchParams.regionDataType);
+ sqlQuery = buildAggregateQuery(searchParams.searchAreaVar, fromClause, whereClause, placeTypes, isBrandQuery);
}
lastExecutedQuery = sqlQuery;
@@ -292,8 +313,6 @@ function buildBrandFilter(brandNames) {
return `brands.name IN (${sanitizedNames})`;
}
-// Brand Category Filter removed
-
function buildOpeningHoursFilter(day, startTime, endTime) {
if (!day || (!startTime && !endTime)) return { unnestClause: '', whereClause: '' };
const unnestClause = `, UNNEST(places.regular_opening_hours.${day}) AS opening_period`;
@@ -317,21 +336,9 @@ function buildRatingFilter(min, max) {
return '';
}
-function buildRegionFilter(regionType, regionDataType, regionNames) {
- if (!regionType || !regionNames || regionNames.length === 0) return '';
- const sanitizedNames = regionNames.map(name => `'${name.replace(/'/g, "\\'")}'`).join(', ');
- if (regionDataType === 'STRING') {
- return `places.${regionType} IN (${sanitizedNames})`;
- }
- return `EXISTS (SELECT 1 FROM UNNEST(places.${regionType}) AS name WHERE name IN (${sanitizedNames}))`;
-}
-
// --- UNIFIED QUERY BUILDERS ---
-function buildAggregateQuery(searchAreaVar, fromClause, whereClause, types, isBrandQuery, isMultiRegion, regionType, regionDataType) {
- if (isMultiRegion && regionDataType === 'STRING') {
- return `${searchAreaVar} SELECT WITH AGGREGATION_THRESHOLD places.${regionType} AS region_name, COUNT(*) AS count ${fromClause} ${whereClause} GROUP BY region_name ORDER BY count DESC`;
- }
+function buildAggregateQuery(searchAreaVar, fromClause, whereClause, types, isBrandQuery) {
if (isBrandQuery) {
return `${searchAreaVar} SELECT WITH AGGREGATION_THRESHOLD brands.name, COUNT(places.id) AS count ${fromClause} ${whereClause} GROUP BY brands.name ORDER BY count DESC`;
}
diff --git a/places_insights/places-insights-demo/state.js b/places_insights/places-insights-demo/state.js
index 0b52597..1323b25 100644
--- a/places_insights/places-insights-demo/state.js
+++ b/places_insights/places-insights-demo/state.js
@@ -52,101 +52,27 @@ const SAMPLE_LOCATIONS = {
// Holds the data from brands.json, loaded at startup.
let BRANDS_DATA = [];
-// Configuration for country-specific region search fields, now with explicit types.
-const REGION_FIELD_CONFIG = {
- 'au': [
- { label: 'State / Territory', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'br': [
- { label: 'State', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'City / Municipality', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' },
- { label: 'Neighborhood', field: 'sublocality_level_1_names', type: 'ARRAY' }
- ],
- 'ca': [
- { label: 'Province / Territory', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Neighborhood', field: 'neighborhood_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'de': [
- { label: 'State', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'District', field: 'administrative_area_level_3_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' },
- { label: 'Sublocality / Borough', field: 'sublocality_level_1_names', type: 'ARRAY' }
- ],
- 'es': [
- { label: 'Autonomous Community', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'Province', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Neighborhood', field: 'neighborhood_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'fr': [
- { label: 'Region', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'Department', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' },
- { label: 'Sublocality', field: 'sublocality_level_1_names', type: 'ARRAY' }
- ],
- 'gb': [
- { label: 'Country', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Town', field: 'postal_town_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'in': [
- { label: 'State', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'District', field: 'administrative_area_level_3_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code (PIN)', field: 'postal_code_names', type: 'ARRAY' },
- { label: 'Sublocality', field: 'sublocality_level_1_names', type: 'ARRAY' }
- ],
- 'id': [
- { label: 'Province', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'Regency / City', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'District', field: 'administrative_area_level_3_name', type: 'STRING' },
- { label: 'Village / Kelurahan', field: 'administrative_area_level_4_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'it': [
- { label: 'Region', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'Province', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'Municipality (Comune)', field: 'administrative_area_level_3_name', type: 'STRING' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'jp': [
- { label: 'Prefecture', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' },
- { label: 'Sublocality', field: 'sublocality_level_1_names', type: 'ARRAY' }
- ],
- 'mx': [
- { label: 'State', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'Municipality', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'ch': [
- { label: 'Canton', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'District', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'Municipality / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ],
- 'us': [
- { label: 'State', field: 'administrative_area_level_1_name', type: 'STRING' },
- { label: 'County', field: 'administrative_area_level_2_name', type: 'STRING' },
- { label: 'City / Locality', field: 'locality_names', type: 'ARRAY' },
- { label: 'Neighborhood', field: 'neighborhood_names', type: 'ARRAY' },
- { label: 'Postal Code', field: 'postal_code_names', type: 'ARRAY' }
- ]
-};
+// Maps Places API Types to their exact BigQuery Columns and Data Types
+const REGION_TYPE_TO_BQ_COLUMN = {
+ 'administrative_area_level_1': { column: 'administrative_area_level_1_id', type: 'STRING' },
+ 'administrative_area_level_2': { column: 'administrative_area_level_2_id', type: 'STRING' },
+ 'administrative_area_level_3': { column: 'administrative_area_level_3_id', type: 'STRING' },
+ 'administrative_area_level_4': { column: 'administrative_area_level_4_id', type: 'STRING' },
+ 'administrative_area_level_5': { column: 'administrative_area_level_5_id', type: 'STRING' },
+ 'administrative_area_level_6': { column: 'administrative_area_level_6_id', type: 'STRING' },
+ 'administrative_area_level_7': { column: 'administrative_area_level_7_id', type: 'STRING' },
+ 'locality': { column: 'locality_ids', type: 'ARRAY' },
+ 'sublocality': { column: 'sublocality_level_1_ids', type: 'ARRAY' },
+ 'sublocality_level_1': { column: 'sublocality_level_1_ids', type: 'ARRAY' },
+ 'sublocality_level_2': { column: 'sublocality_level_2_ids', type: 'ARRAY' },
+ 'sublocality_level_3': { column: 'sublocality_level_3_ids', type: 'ARRAY' },
+ 'sublocality_level_4': { column: 'sublocality_level_4_ids', type: 'ARRAY' },
+ 'sublocality_level_5': { column: 'sublocality_level_5_ids', type: 'ARRAY' },
+ 'neighborhood': { column: 'neighborhood_ids', type: 'ARRAY' },
+ 'postal_code': { column: 'postal_code_ids', type: 'ARRAY' },
+ 'postal_town': { column: 'postal_town_ids', type: 'ARRAY' }
+};
// --- MAP & OVERLAY STATE ---
// References to Google Maps and deck.gl objects.
diff --git a/places_insights/places-insights-demo/ui.js b/places_insights/places-insights-demo/ui.js
index 6f84ed7..f69aa7d 100644
--- a/places_insights/places-insights-demo/ui.js
+++ b/places_insights/places-insights-demo/ui.js
@@ -69,8 +69,13 @@ function resetSidebarUI(targetMode = 'circle-search') {
document.getElementById('wkt-input').value = '';
document.getElementById('wkt-input').classList.remove('invalid');
- document.getElementById('region-name-input').value = '';
+
+ // Reset Region Search
document.getElementById('selected-regions-list').innerHTML = '';
+ if (window.regionAutocomplete) {
+ window.regionAutocomplete.inputValue = '';
+ }
+
document.getElementById('route-radius-input').value = '100';
// Reset Filters
@@ -139,6 +144,50 @@ function addTag(text, listElement) {
listElement.appendChild(tag);
}
+/**
+ * Adds a region tag that explicitly stores its Place ID, column, data type, and viewport.
+ */
+function addRegionTag(name, id, columnObj, location, viewport) {
+ const listElement = document.getElementById('selected-regions-list');
+ if ([...listElement.querySelectorAll('.selected-region-tag')].some(el => el.dataset.id === id)) return;
+
+ const tag = document.createElement('li');
+ tag.className = 'selected-type-tag selected-region-tag';
+ tag.dataset.id = id;
+ tag.dataset.column = columnObj.column;
+ tag.dataset.colType = columnObj.type;
+
+ // Store Viewport for accurate bounding box
+ if (viewport) {
+ const vp = viewport.toJSON();
+ tag.dataset.north = vp.north;
+ tag.dataset.south = vp.south;
+ tag.dataset.east = vp.east;
+ tag.dataset.west = vp.west;
+ }
+
+ // Always store location as a fallback
+ if (location) {
+ tag.dataset.lat = location.lat();
+ tag.dataset.lng = location.lng();
+ }
+
+ const textSpan = document.createElement('span');
+ textSpan.textContent = name;
+
+ const removeBtn = document.createElement('button');
+ removeBtn.className = 'remove-tag-btn';
+ removeBtn.innerHTML = '×';
+ removeBtn.onclick = () => {
+ tag.remove();
+ invalidateQueryState();
+ };
+
+ tag.appendChild(textSpan);
+ tag.appendChild(removeBtn);
+ listElement.appendChild(tag);
+}
+
/**
* Initializes the place type autocomplete functionality.
*/