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", + " \n", + " \n", + "
\n", + " \n", + " \"Google
Open in Colab\n", + "
\n", + "
\n", + " \n", + " \"Google
Open in Colab Enterprise\n", + "
\n", + "
\n", + " \n", + " \"BigQuery
Open in BigQuery Studio\n", + "
\n", + "
\n", + " \n", + " \"GitHub
View on GitHub\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", + " \"Google\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": []