diff --git a/places_insights/notebooks/spot_check_results/places_insights_spot_check_results_using_functions.ipynb b/places_insights/notebooks/spot_check_results/places_insights_spot_check_results_using_functions.ipynb
index ab68f96..8dd3475 100644
--- a/places_insights/notebooks/spot_check_results/places_insights_spot_check_results_using_functions.ipynb
+++ b/places_insights/notebooks/spot_check_results/places_insights_spot_check_results_using_functions.ipynb
@@ -3,7 +3,8 @@
"nbformat_minor": 0,
"metadata": {
"colab": {
- "provenance": []
+ "provenance": [],
+ "private_outputs": true
},
"kernelspec": {
"name": "python3",
@@ -14,11 +15,64 @@
}
},
"cells": [
+ {
+ "cell_type": "code",
+ "source": [
+ "# Copyright 2026 Google LLC\n",
+ "#\n",
+ "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ],
+ "metadata": {
+ "id": "8gEou7lgxicD"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
{
"cell_type": "markdown",
"source": [
"# Spot-Checking Places Insights Data with Functions and Sample Place IDs\n",
"\n",
+ "
\n",
+ " \n",
+ " \n",
+ "  Open in Colab\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  Open in Colab Enterprise\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  Open in BigQuery Studio\n",
+ " \n",
+ " | \n",
+ " \n",
+ " \n",
+ "  View on GitHub\n",
+ " \n",
+ " | \n",
+ "
"
+ ],
+ "metadata": {
+ "id": "pd8oXCgWx_o9"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
"### Overall Goal\n",
"\n",
"This notebook demonstrates a workflow for spot-checking Places Insights data. It starts with a high-level statistical query to find restaurant density and then **directly visualizes both the high-level density and ground-truth sample locations from the city's busiest areas on a single, combined map.**\n",
@@ -27,7 +81,7 @@
"\n",
"* **[Places Insights](https://developers.google.com/maps/documentation/placesinsights):** To provide the Places Data and Place Count Function.\n",
"* **[BigQuery](https://cloud.google.com/bigquery):** To run the `PLACES_COUNT_PER_H3` function, which provides aggregated place counts and `sample_place_ids`.\n",
- "* **[Google Maps Place Details API](https://developers.google.com/maps/documentation/places/web-service/place-details):** To fetch rich, detailed information (name, address, rating, and a Google Maps link) for the specific `sample_place_ids`.\n",
+ "* **[Google Maps Place Details API](https://developers.google.com/maps/documentation/places/web-service/place-details):** To fetch rich, detailed information (name, address, and a Google Maps link) for the specific `sample_place_ids`.\n",
"* **[Google Maps 2D Tiles](https://developers.google.com/maps/documentation/tile/2d-tiles-overview):** To use Google Maps as the basemap.\n",
"* **Python Libraries:**\n",
" * **[GeoPandas](https://geopandas.org/en/stable/)** for spatial data manipulation.\n",
@@ -37,17 +91,17 @@
"\n",
"### The Step-by-Step Workflow\n",
"\n",
- "1. **Query Aggregated Data:** We begin by querying BigQuery to count all highly-rated, operational restaurants across London, grouping them into H3 hexagonal cells. This query provides the statistical foundation for our analysis and, crucially, a list of `sample_place_ids` for each cell.\n",
+ "1. **Query Aggregated Data:** We begin by querying BigQuery to count all highly-rated, operational restaurants within a **5km radius of Central London**. We group them into H3 hexagonal cells at **resolution 9**. This query provides the statistical foundation for our analysis and, crucially, a list of `sample_place_ids` for each cell.\n",
"\n",
"2. **Identify Hotspots & Fetch Details:** The notebook then **automatically** identifies the 20 busiest H3 cells. It consolidates the `sample_place_ids` from all of these top hotspots into a single master list and uses the Places API to fetch detailed information for each one.\n",
"\n",
"3. **Create a Combined Visualization:** In the final step, we generate a single, layered map.\n",
- " * The **base layer** is a choropleth \"heatmap\" showing restaurant density across the entire city.\n",
+ " * The **base layer** is a choropleth \"heatmap\" showing restaurant density across the search area.\n",
" * The **top layer** displays individual pins for all the sample restaurants from the top 20 hotspots, providing a direct, ground-level view of the locations that make up the aggregated counts. Each pin's popup includes a link to open the location directly in Google Maps.\n",
"\n",
"### **How to Use This Notebook**\n",
"\n",
- "1. ** Set Up Secrets:** Before you begin, you must configure two secrets in the Colab \"Secrets\" tab (the **π key icon** on the left menu):\n",
+ "1. **Set Up Secrets:** Before you begin, you must configure two secrets in the Colab \"Secrets\" tab (the **key icon** on the left menu):\n",
" * `GCP_PROJECT`: Your Google Cloud Project ID with access to Places Insights.\n",
" * `GMP_API_KEY`: Your Google Maps Platform API key. Ensure the **Maps Tile API** is enabled for this key in your GCP console.\n",
"\n",
@@ -59,17 +113,15 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "source": [
+ "# Install the Google Maps Places Client Library\n",
+ "!pip install --quiet google-maps-places"
+ ],
"metadata": {
- "collapsed": true,
- "id": "T7xSzI46psaW"
+ "id": "W8j_299tirSY"
},
- "outputs": [],
- "source": [
- "# Install necessary libraries\n",
- "# We use folium and its ecosystem for mapping.\n",
- "!pip install google-cloud-bigquery geopandas shapely folium mapclassify xyzservices google-maps-places googlemaps"
- ]
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "code",
@@ -85,6 +137,7 @@
"\n",
"import geopandas as gpd\n",
"import shapely\n",
+ "from shapely import wkt\n",
"import sys\n",
"\n",
"import pandas as pd\n",
@@ -103,8 +156,8 @@
{
"cell_type": "code",
"source": [
- "# Configure GCP Authentication\n",
- "# This part securely gets your GCP Project ID.\n",
+ "# @title Configure GCP Authentication\n",
+ "# @markdown This part securely gets your GCP Project ID and initializes a BigQuery Client.\n",
"GCP_PROJECT_SECRET_KEY_NAME = \"GCP_PROJECT\" #@param {type:\"string\"}\n",
"GCP_PROJECT_ID = None\n",
"\n",
@@ -119,7 +172,9 @@
" \"Please make sure the secret is set in your Colab environment.\")\n",
" except userdata.SecretNotFoundError:\n",
" raise ValueError(f\"Secret named '{GCP_PROJECT_SECRET_KEY_NAME}' not found. \"\n",
- " \"Please create it in the 'Secrets' tab (key icon) in Colab.\")"
+ " \"Please create it in the 'Secrets' tab (key icon) in Colab.\")\n",
+ "\n",
+ "client = bigquery.Client(project=GCP_PROJECT_ID)"
],
"metadata": {
"id": "zZ6eEv6cp1pR"
@@ -130,6 +185,8 @@
{
"cell_type": "code",
"source": [
+ "# @title Get GMP API Key\n",
+ "\n",
"API_KEY_SECRET_NAME = \"GMP_API_KEY\" #@param {type:\"string\"}\n",
"\n",
"# Initialize a variable to hold our key.\n",
@@ -153,12 +210,85 @@
{
"cell_type": "code",
"source": [
- "# Enable interactive tables for pandas DataFrames\n",
- "data_table.enable_dataframe_formatter()\n",
- "client = bigquery.Client(project=GCP_PROJECT_ID)"
+ "# @title Maps Backend Initialization: Session, Copyright & Assets\n",
+ "# @markdown This cell manages the Maps Tiles API handshake. It performs the following steps:\n",
+ "# @markdown 1. **Session Creation:** Authenticates and requests a \"Roadmap\" session for the target region.\n",
+ "# @markdown 2. **Attribution Fetching:** Queries the API for the specific copyright text required for the configured viewport.\n",
+ "# @markdown 3. **Asset Preparation:** Generates the compliant HTML for the Google Maps logo overlay.\n",
+ "\n",
+ "# --- Configuration ---\n",
+ "# We use the center point from the SQL query (Charing Cross) to make sure\n",
+ "# the attribution we fetch matches the area we are mapping.\n",
+ "CENTER_LAT = 51.5074\n",
+ "CENTER_LNG = -0.1276\n",
+ "ZOOM_LEVEL = 12\n",
+ "\n",
+ "# --- 1. Create Google Maps Session ---\n",
+ "print(\"πΊοΈ Initializing Google Maps Session...\")\n",
+ "session_url = f\"https://tile.googleapis.com/v1/createSession?key={gmp_api_key}\"\n",
+ "headers = {\"Content-Type\": \"application/json\"}\n",
+ "payload = {\n",
+ " \"mapType\": \"roadmap\",\n",
+ " \"language\": \"en-GB\",\n",
+ " \"region\": \"GB\"\n",
+ "}\n",
+ "\n",
+ "try:\n",
+ " response = requests.post(session_url, json=payload, headers=headers)\n",
+ " response.raise_for_status()\n",
+ " session_token = response.json().get(\"session\")\n",
+ " print(f\"β
Session Token acquired: {session_token[:10]}...\")\n",
+ "except Exception as e:\n",
+ " raise RuntimeError(f\"Failed to initialize Google Maps session: {e}\")\n",
+ "\n",
+ "# --- 2. Fetch Dynamic Attribution ---\n",
+ "# We calculate a small dynamic bounding box around our center point\n",
+ "# to fetch the correct copyright for London.\n",
+ "delta = 0.05 # Roughly 5km buffer\n",
+ "north, south = CENTER_LAT + delta, CENTER_LAT - delta\n",
+ "east, west = CENTER_LNG + delta, CENTER_LNG - delta\n",
+ "\n",
+ "viewport_url = (\n",
+ " f\"https://tile.googleapis.com/tile/v1/viewport?key={gmp_api_key}\"\n",
+ " f\"&session={session_token}\"\n",
+ " f\"&zoom={ZOOM_LEVEL}\"\n",
+ " f\"&north={north}&south={south}\"\n",
+ " f\"&west={west}&east={east}\"\n",
+ ")\n",
+ "\n",
+ "try:\n",
+ " vp_response = requests.get(viewport_url)\n",
+ " vp_response.raise_for_status()\n",
+ " google_attribution = vp_response.json().get('copyright', 'Map data Β© Google')\n",
+ " print(\"β
Attribution fetched.\")\n",
+ "except Exception as e:\n",
+ " print(f\"β οΈ Warning: Could not fetch attribution ({e}). Defaulting.\")\n",
+ " google_attribution = \"Map data Β© Google\"\n",
+ "\n",
+ "# --- 3. Construct Tile URL ---\n",
+ "# This variable will be passed to 'tiles=' in folium\n",
+ "google_tiles_url = f\"https://tile.googleapis.com/v1/2dtiles/{{z}}/{{x}}/{{y}}?session={session_token}&key={gmp_api_key}\"\n",
+ "\n",
+ "# --- 4. Construct Logo HTML ---\n",
+ "# This HTML block will be injected into the map root\n",
+ "logo_url = \"https://maps.gstatic.com/mapfiles/api-3/images/google_white3.png\"\n",
+ "google_logo_html = f\"\"\"\n",
+ " \n",
+ "

\n",
+ "
\n",
+ "\"\"\"\n",
+ "print(\"β
Logo HTML prepared.\")"
],
"metadata": {
- "id": "b5-gJNwcp9fK"
+ "cellView": "form",
+ "id": "mrJQEhrKttTq"
},
"execution_count": null,
"outputs": []
@@ -166,38 +296,29 @@
{
"cell_type": "code",
"source": [
- "restaurants_in_london_sql = \"\"\"\n",
- "-- Declare a variable to hold the GEOGRAPHY for London.\n",
- "DECLARE london_boundary GEOGRAPHY;\n",
- "-- Set the variable by dynamically loading the boundary\n",
- "-- from the Overture Maps public dataset.\n",
- "SET london_boundary = (\n",
- " SELECT geometry\n",
- " FROM `bigquery-public-data.overture_maps.division_area`\n",
- " WHERE names.primary = 'London' AND country = 'GB' LIMIT 1\n",
- ");\n",
- "-- Call the function with all parameters in a single JSON_OBJECT.\n",
+ "# @title Query Aggregated Restaurant Data\n",
+ "# @markdown This query selects operational restaurants with a rating > 3.5 within a 5km radius of Central London.\n",
+ "# @markdown The results are aggregated into H3 cells (Resolution 9) and saved to the dataframe `df_restaurants_in_london`.\n",
+ "%%bigquery df_restaurants_in_london --project $GCP_PROJECT_ID\n",
+ "\n",
+ "DECLARE geo GEOGRAPHY;\n",
+ "\n",
+ "-- Create a polygon: A 5km radius around Central London (Charing Cross)\n",
+ "SET geo = ST_BUFFER(ST_GEOGPOINT(-0.1276, 51.5074), 5000);\n",
+ "\n",
"SELECT *\n",
- "FROM\n",
- " `places_insights___gb.PLACES_COUNT_PER_H3`(\n",
+ "FROM `places_insights___gb.PLACES_COUNT_PER_H3`(\n",
" JSON_OBJECT(\n",
- " -- Define the search area\n",
- " 'geography', london_boundary,\n",
- " -- Set the aggregation grid size and other filters\n",
- " 'h3_resolution', 8,\n",
- " 'types', ['restaurant'],\n",
- " 'business_status', ['OPERATIONAL'],\n",
- " 'min_rating', 3.5,\n",
- " -- NEW FILTER: Only include places with 100 or more user ratings.\n",
- " 'min_user_rating_count', 100\n",
+ " 'geography', geo,\n",
+ " 'h3_resolution', 9,\n",
+ " 'types', ['restaurant'],\n",
+ " 'business_status', ['OPERATIONAL'],\n",
+ " 'min_rating', 3.5\n",
" )\n",
- " )\n",
- "ORDER BY\n",
- " count DESC;\n",
- "\"\"\""
+ ");"
],
"metadata": {
- "id": "K-GzCtZIqERI"
+ "id": "bihXeGeyrA18"
},
"execution_count": null,
"outputs": []
@@ -205,16 +326,23 @@
{
"cell_type": "code",
"source": [
- "# Step 1.2: Execute Query and Create GeoDataFrame\n",
- "print(\"Running london query...\")\n",
- "df_restaurants_in_london = client.query(restaurants_in_london_sql).to_dataframe()\n",
+ "# 1. Convert the WKT string column into geometry objects\n",
+ "# We use apply(wkt.loads) to parse the string representation of the polygons\n",
+ "df_restaurants_in_london['geography'] = df_restaurants_in_london['geography'].apply(wkt.loads) # type: ignore\n",
+ "\n",
+ "# 2. Convert the standard DataFrame to a GeoDataFrame\n",
+ "gdf_restaurants_in_london = gpd.GeoDataFrame(\n",
+ " df_restaurants_in_london, # type: ignore\n",
+ " geometry='geography',\n",
+ " crs='EPSG:4326'\n",
+ ")\n",
"\n",
- "df_restaurants_in_london['geography'] = df_restaurants_in_london['geography'].dropna().apply(shapely.from_wkt)\n",
- "gdf_restaurants_in_london = gpd.GeoDataFrame(df_restaurants_in_london, geometry='geography', crs='EPSG:4326')\n",
- "print(f\"Successfully processed {len(gdf_restaurants_in_london)} cells.\")"
+ "print(f\"Successfully processed {len(gdf_restaurants_in_london)} cells.\")\n",
+ "print(\"Displaying top 5 rows:\")\n",
+ "display(gdf_restaurants_in_london.head())"
],
"metadata": {
- "id": "XqtsN40tqRNl"
+ "id": "pWoAjJSmrCzl"
},
"execution_count": null,
"outputs": []
@@ -241,69 +369,36 @@
{
"cell_type": "code",
"source": [
- "# Define the columns from your GeoDataFrame that you want to see in the tooltip\n",
- "restaurant_tooltip_cols = [\n",
- " 'h3_cell_index',\n",
- " 'count'\n",
- "]\n",
- "\n",
- "# Verify the GMP API key exists.\n",
- "if 'gmp_api_key' not in locals() or gmp_api_key is None:\n",
- " raise NameError(\"The 'gmp_api_key' variable is not defined. Please run the API key cell first.\")\n",
- "\n",
- "\n",
- "# Get Session Token\n",
- "session_url = f\"https://tile.googleapis.com/v1/createSession?key={gmp_api_key}\"\n",
- "payload = {\"mapType\": \"roadmap\", \"language\": \"en-US\", \"region\": \"US\"}\n",
- "headers = {\"Content-Type\": \"application/json\"}\n",
- "\n",
- "response_session = requests.post(session_url, json=payload, headers=headers)\n",
- "response_session.raise_for_status()\n",
- "session_data = response_session.json()\n",
- "session_token = session_data['session']\n",
+ "# Check if dataframe exists\n",
+ "if 'gdf_restaurants_in_london' in locals() and not gdf_restaurants_in_london.empty:\n",
"\n",
+ " # Define tooltips\n",
+ " restaurant_tooltip_cols = ['h3_cell_index', 'count']\n",
"\n",
- "# Get Dynamic Attribution from Viewport API\n",
- "# We need to define a bounding box for the viewport request.\n",
- "# We'll use the total bounds of our GeoDataFrame.\n",
- "bounds = gdf_restaurants_in_london.total_bounds\n",
- "viewport_url = (\n",
- " f\"https://tile.googleapis.com/tile/v1/viewport?key={gmp_api_key}\"\n",
- " f\"&session={session_token}\"\n",
- " f\"&zoom=10\"\n",
- " f\"&north={bounds[3]}&south={bounds[1]}\"\n",
- " f\"&west={bounds[0]}&east={bounds[2]}\"\n",
- ")\n",
+ " # 1. Create the map using .explore()\n",
+ " # Note how clean this is now: we just pass the pre-calculated URL and Attribution\n",
+ " london_restaurants_map = gdf_restaurants_in_london.explore(\n",
+ " column=\"count\",\n",
+ " cmap=\"YlOrRd\",\n",
+ " scheme=\"NaturalBreaks\",\n",
+ " tooltip=restaurant_tooltip_cols,\n",
+ " popup=True,\n",
+ " tiles=google_tiles_url, # <--- Injected from Setup Cell\n",
+ " attr=google_attribution, # <--- Injected from Setup Cell\n",
+ " style_kwds={\"stroke\": True, \"color\": \"black\", \"weight\": 0.2, \"fillOpacity\": 0.7}\n",
+ " )\n",
"\n",
- "response_viewport = requests.get(viewport_url)\n",
- "response_viewport.raise_for_status()\n",
- "viewport_data = response_viewport.json()\n",
- "\n",
- "# Extract the mandatory copyright/attribution string.\n",
- "google_attribution = viewport_data.get('copyright', 'Google') # Fallback to 'Google'\n",
- "\n",
- "# Construct Tile URL and Display Map\n",
- "google_tiles = f\"https://tile.googleapis.com/v1/2dtiles/{{z}}/{{x}}/{{y}}?session={session_token}&key={gmp_api_key}\"\n",
- "\n",
- "# Create the map using the .explore() function on your GeoDataFrame\n",
- "# This will create a choropleth map where the color of each H3 cell\n",
- "# is based on the number of restaurants it contains.\n",
- "london_restaurants_map = gdf_restaurants_in_london.explore(\n",
- " column=\"count\", # The column to color the map by\n",
- " cmap=\"YlOrRd\", # A color map that's great for density (Yellow-Orange-Red)\n",
- " scheme=\"NaturalBreaks\", # A smart way to group data into color buckets\n",
- " tooltip=restaurant_tooltip_cols, # The columns to show when you hover\n",
- " popup=True, # Show a popup with all data on click\n",
- " tiles=google_tiles,\n",
- " attr=google_attribution,\n",
- " style_kwds={\"stroke\": True, \"color\": \"black\", \"weight\": 0.2, \"fillOpacity\": 0.7} # Styling for the hexagons\n",
- ")\n",
+ " # 2. Inject the Google Logo\n",
+ " # This adds the HTML div on top of the map container\n",
+ " london_restaurants_map.get_root().html.add_child(folium.Element(google_logo_html))\n",
"\n",
- "# Display the map\n",
- "display(london_restaurants_map)"
+ " # 3. Display\n",
+ " display(london_restaurants_map)\n",
+ "else:\n",
+ " print(\"DataFrame 'gdf_restaurants_in_london' not found. Please run the BigQuery cell first.\")"
],
"metadata": {
- "id": "XRWrxqu6rQGn"
+ "id": "ip7EqZQLuAtf"
},
"execution_count": null,
"outputs": []
@@ -324,9 +419,6 @@
{
"cell_type": "code",
"source": [
- "# Ensure Colab's interactive data table formatter is enabled.\n",
- "data_table.enable_dataframe_formatter()\n",
- "\n",
"# Isolate the top 20 H3 cells with the highest restaurant counts.\n",
"print(f\"Identifying the top 20 busiest H3 cells from the {len(gdf_restaurants_in_london)} total cells...\")\n",
"top_20_cells_df = gdf_restaurants_in_london.sort_values(by='count', ascending=False).head(20).reset_index(drop=True)\n",
@@ -384,9 +476,6 @@
{
"cell_type": "code",
"source": [
- "# Ensure Colab's interactive data table formatter is enabled.\n",
- "data_table.enable_dataframe_formatter()\n",
- "\n",
"# Check if the list of Place IDs from Phase 1 exists.\n",
"if 'place_ids_to_process' in locals() and place_ids_to_process:\n",
"\n",
@@ -394,8 +483,7 @@
" if places_client:\n",
" # Loop through the list of Place IDs and fetch details.\n",
" place_details_list = []\n",
- " # Add 'googleMapsUri' to the list of fields we are requesting.\n",
- " fields_to_request = \"displayName,formattedAddress,rating,userRatingCount,location,googleMapsUri\"\n",
+ " fields_to_request = \"formattedAddress,location,displayName,googleMapsUri\"\n",
"\n",
" total_ids = len(place_ids_to_process)\n",
" print(f\"\\nFetching details for {total_ids} unique Place IDs...\")\n",
@@ -414,12 +502,9 @@
" place_details_list.append({\n",
" \"Name\": response.display_name.text,\n",
" \"Address\": response.formatted_address,\n",
- " \"Rating\": response.rating,\n",
- " \"Total Ratings\": response.user_rating_count,\n",
" \"Place ID\": place_id,\n",
" \"Latitude\": response.location.latitude,\n",
" \"Longitude\": response.location.longitude,\n",
- " # Add the new URI field to our collected data.\n",
" \"Google Maps URI\": response.google_maps_uri\n",
" })\n",
" except exceptions.GoogleAPICallError as e:\n",
@@ -431,7 +516,7 @@
" details_df = pd.DataFrame(place_details_list)\n",
"\n",
" # Define which columns we want to show in the summary table.\n",
- " columns_to_display = [\"Name\", \"Address\", \"Rating\", \"Total Ratings\", \"Place ID\"]\n",
+ " columns_to_display = [\"Name\", \"Address\", \"Place ID\"]\n",
"\n",
" print(\"Here is a sample of the retrieved data (Google Maps URI is hidden):\")\n",
" # Display only the selected columns from the head of the DataFrame.\n",
@@ -470,76 +555,61 @@
{
"cell_type": "code",
"source": [
- "# Check if the required DataFrames from previous steps exist.\n",
+ "# Check if required DataFrames exist\n",
"if 'gdf_restaurants_in_london' in locals() and not gdf_restaurants_in_london.empty and 'details_df' in locals() and not details_df.empty:\n",
"\n",
" restaurant_tooltip_cols = ['h3_cell_index', 'count']\n",
"\n",
- " # Create the base choropleth map from the H3 cell data using GeoPandas .explore()\n",
- " print(\"Generating base choroplep map of restaurant density...\")\n",
+ " # 1. Generate base choropleth map\n",
+ " print(\"Generating base choropleth map...\")\n",
" combined_map = gdf_restaurants_in_london.explore(\n",
" column=\"count\",\n",
" cmap=\"YlOrRd\",\n",
" scheme=\"NaturalBreaks\",\n",
" tooltip=restaurant_tooltip_cols,\n",
" popup=True,\n",
- " tiles=google_tiles,\n",
- " attr=google_attribution,\n",
+ " tiles=google_tiles_url, # <--- Injected from Setup Cell\n",
+ " attr=google_attribution, # <--- Injected from Setup Cell\n",
" style_kwds={\"stroke\": True, \"color\": \"black\", \"weight\": 0.2, \"fillOpacity\": 0.7}\n",
" )\n",
"\n",
- " # Iterate through the detailed restaurant data to add a marker for each one.\n",
- " print(f\"Adding {len(details_df)} individual restaurant markers with Google Maps links to the map...\")\n",
- " skipped_count = 0\n",
- "\n",
+ " # 2. Iterate and add markers (Logic unchanged)\n",
+ " print(f\"Adding {len(details_df)} individual restaurant markers...\")\n",
" for index, row in details_df.iterrows():\n",
- " # Defensively validate that coordinates exist for the record.\n",
- " lat = row['Latitude']\n",
- " lon = row['Longitude']\n",
- " if pd.isna(lat) or pd.isna(lon):\n",
- " skipped_count += 1\n",
- " continue # Skip this record if coordinates are missing.\n",
- "\n",
- " # Clean and sanitize all data that will be used in the popup.\n",
- " name = str(row['Name']) if pd.notna(row['Name']) else \"Unnamed Place\"\n",
- " rating = row['Rating'] if pd.notna(row['Rating']) else \"N/A\"\n",
- " total_ratings = int(row['Total Ratings']) if pd.notna(row['Total Ratings']) else 0\n",
- " address = str(row['Address']) if pd.notna(row['Address']) else \"No Address Provided\"\n",
+ " lat, lon = row['Latitude'], row['Longitude']\n",
+ "\n",
+ " if pd.isna(lat) or pd.isna(lon): continue\n",
+ "\n",
+ " name = str(row['Name']).replace('`', \"'\") if pd.notna(row['Name']) else \"Unnamed Place\"\n",
+ " address = str(row['Address']) if pd.notna(row['Address']) else \"\"\n",
" uri = str(row['Google Maps URI']) if pd.notna(row['Google Maps URI']) else \"#\"\n",
- " name = name.replace('`', \"'\")\n",
"\n",
- " # Create the full HTML content for the marker's popup.\n",
" popup_html = f\"\"\"\n",
" {name}
\n",
- " Rating: {rating} ({total_ratings} reviews)
\n",
"
\n",
" {address}
\n",
" View on Google Maps\n",
" \"\"\"\n",
"\n",
- " popup = folium.Popup(popup_html, max_width=300)\n",
- "\n",
- " # Create the marker and add it to the existing map object.\n",
" folium.Marker(\n",
" location=[lat, lon],\n",
" tooltip=name,\n",
- " popup=popup,\n",
+ " popup=folium.Popup(popup_html, max_width=300),\n",
" icon=folium.Icon(color='blue', icon='utensils', prefix='fa')\n",
" ).add_to(combined_map)\n",
"\n",
- " # Provide a summary message if any records were skipped.\n",
- " if skipped_count > 0:\n",
- " print(f\"\\nWarning: Skipped {skipped_count} marker(s) due to missing coordinates.\")\n",
+ " # 3. Inject the Google Logo\n",
+ " combined_map.get_root().html.add_child(folium.Element(google_logo_html))\n",
"\n",
- " print(\"Map layers combined successfully. Displaying below.\")\n",
+ " # 4. Display\n",
+ " print(\"Displaying combined map.\")\n",
" display(combined_map)\n",
"\n",
"else:\n",
- " print(\"One or more required DataFrames ('gdf_restaurants_in_london', 'details_df') do not exist or are empty.\")\n",
- " print(\"Please ensure you have run the previous cells successfully before this one.\")"
+ " print(\"Required DataFrames not found. Ensure previous cells ran successfully.\")"
],
"metadata": {
- "id": "qCM--0WL8dAj"
+ "id": "TfH22zQ1uJkt"
},
"execution_count": null,
"outputs": []