From be7f0b1495a1e24fc92139203e4fa18ed8c962b3 Mon Sep 17 00:00:00 2001 From: Naoya Moritani Date: Wed, 11 Mar 2026 16:21:03 +0800 Subject: [PATCH] feat(pydeck): add mapId support for Google Maps --- bindings/pydeck/pydeck/bindings/deck.py | 15 +++++++++++++-- bindings/pydeck/pydeck/bindings/json_tools.py | 1 + bindings/pydeck/pydeck/io/html.py | 4 ++++ bindings/pydeck/pydeck/io/templates/index.j2 | 3 +++ bindings/pydeck/pydeck/widget/widget.py | 3 +++ .../src/lib/jupyter-transport-model.js | 2 ++ .../jupyter-widget/src/playground/create-deck.js | 10 +++++++--- .../jupyter-widget/src/playground/playground.js | 16 ++++++++++++++-- .../src/playground/utils/google-maps-utils.js | 4 ++++ 9 files changed, 51 insertions(+), 7 deletions(-) diff --git a/bindings/pydeck/pydeck/bindings/deck.py b/bindings/pydeck/pydeck/bindings/deck.py index 511e3193fc3..c750d671f3b 100644 --- a/bindings/pydeck/pydeck/bindings/deck.py +++ b/bindings/pydeck/pydeck/bindings/deck.py @@ -62,6 +62,8 @@ def __init__( Dictionary of geospatial API service providers, where the keys are ``mapbox``, ``google_maps``, or ``carto`` and the values are the API key. Defaults to None if not set. Any of the environment variables ``MAPBOX_API_KEY``, ``GOOGLE_MAPS_API_KEY``, and ``CARTO_API_KEY`` can be set instead of hardcoding the key here. + If using Google Maps, you can also provide a ``google_maps_map_id`` in this dictionary to enable + Vector Map features (like 3D tilt and rotation), or set the ``GOOGLE_MAPS_MAP_ID`` environment variable. map_provider : str, default 'carto' If multiple API keys are set (e.g., both Mapbox and Google Maps), inform pydeck which basemap provider to prefer. Values can be ``carto``, ``mapbox``, ``google_maps``, or ``maplibre``. @@ -151,16 +153,24 @@ def selected_data(self): def _set_api_keys(self, api_keys: dict = None): """Sets API key for base map provider for both HTML embedding and the Jupyter widget""" + valid_providers = [p.value for p in BaseMapProvider] for k in api_keys: - k and BaseMapProvider(k) + if k in valid_providers: + BaseMapProvider(k) for provider in BaseMapProvider: attr_name = f"{provider.value}_key" provider_env_var = f"{provider.name}_API_KEY" - attr_value = api_keys.get(provider.value) or os.getenv(provider_env_var) + attr_value = api_keys.get(provider.value) or api_keys.get(attr_name) or os.getenv(provider_env_var) setattr(self, attr_name, attr_value) if has_jupyter_extra(): setattr(self.deck_widget, attr_name, attr_value) + # Handle google_maps_map_id specifically + gm_map_id = api_keys.get("google_maps_map_id") or os.getenv("GOOGLE_MAPS_MAP_ID") + self.google_maps_map_id = gm_map_id + if has_jupyter_extra(): + self.deck_widget.google_maps_map_id = gm_map_id + def show(self): """Display current Deck object for a Jupyter notebook""" # TODO: Jupyter-specific features not currently supported in pydeck v0.9. @@ -238,6 +248,7 @@ def to_html( deck_json, mapbox_key=self.mapbox_key, google_maps_key=self.google_maps_key, + google_maps_map_id=self.google_maps_map_id, filename=filename, open_browser=open_browser, notebook_display=notebook_display, diff --git a/bindings/pydeck/pydeck/bindings/json_tools.py b/bindings/pydeck/pydeck/bindings/json_tools.py index 5414bb171ae..43b986f0bf1 100644 --- a/bindings/pydeck/pydeck/bindings/json_tools.py +++ b/bindings/pydeck/pydeck/bindings/json_tools.py @@ -9,6 +9,7 @@ IGNORE_KEYS = [ "mapbox_key", "google_maps_key", + "google_maps_map_id", "deck_widget", "binary_data_sets", "_binary_data", diff --git a/bindings/pydeck/pydeck/io/html.py b/bindings/pydeck/pydeck/io/html.py index 082fc564ce2..a94cb83bf1d 100644 --- a/bindings/pydeck/pydeck/io/html.py +++ b/bindings/pydeck/pydeck/io/html.py @@ -59,6 +59,7 @@ def render_json_to_html( json_input, mapbox_key=None, google_maps_key=None, + google_maps_map_id=None, tooltip=True, css_background_color=None, custom_libraries=None, @@ -72,6 +73,7 @@ def render_json_to_html( html_str = js.render( mapbox_key=mapbox_key, google_maps_key=google_maps_key, + google_maps_map_id=google_maps_map_id, json_input=json_input, deckgl_jupyter_widget_bundle=cdn_picker(offline=offline), deckgl_widget_css_url=CDN_CSS_URL, @@ -126,6 +128,7 @@ def deck_to_html( deck_json, mapbox_key=None, google_maps_key=None, + google_maps_map_id=None, filename=None, open_browser=False, notebook_display=None, @@ -144,6 +147,7 @@ def deck_to_html( deck_json, mapbox_key=mapbox_key, google_maps_key=google_maps_key, + google_maps_map_id=google_maps_map_id, tooltip=tooltip, css_background_color=css_background_color, custom_libraries=custom_libraries, diff --git a/bindings/pydeck/pydeck/io/templates/index.j2 b/bindings/pydeck/pydeck/io/templates/index.j2 index fdd74bd01e0..8f1130800b4 100644 --- a/bindings/pydeck/pydeck/io/templates/index.j2 +++ b/bindings/pydeck/pydeck/io/templates/index.j2 @@ -35,6 +35,9 @@ {% if google_maps_key %} googleMapsKey: '{{google_maps_key}}', {% endif %} + {% if google_maps_map_id %} + googleMapsMapId: '{{google_maps_map_id}}', + {% endif %} container, jsonInput, tooltip, diff --git a/bindings/pydeck/pydeck/widget/widget.py b/bindings/pydeck/pydeck/widget/widget.py index 60450ab6154..59faec8dac6 100644 --- a/bindings/pydeck/pydeck/widget/widget.py +++ b/bindings/pydeck/pydeck/widget/widget.py @@ -43,6 +43,8 @@ class DeckGLWidget(DOMWidget): See the ``Deck`` constructor. google_maps_key : str, default '' API key for Google Maps + google_maps_map_id : str, default '' + Optional ID for Google Maps Vector Maps selected_data : list of dict, default [] Data selected on click, if the pydeck Jupyter widget is enabled for server use """ @@ -57,6 +59,7 @@ class DeckGLWidget(DOMWidget): carto_key = Unicode("", allow_none=True).tag(sync=True) mapbox_key = Unicode("", allow_none=True).tag(sync=True) google_maps_key = Unicode("", allow_none=True).tag(sync=True) + google_maps_map_id = Unicode("", allow_none=True).tag(sync=True) json_input = Unicode("").tag(sync=True) data_buffer = Any(default_value=None, allow_none=True).tag(sync=True, **data_buffer_serialization) diff --git a/modules/jupyter-widget/src/lib/jupyter-transport-model.js b/modules/jupyter-widget/src/lib/jupyter-transport-model.js index 9d60e51b13c..c2cb8c14892 100644 --- a/modules/jupyter-widget/src/lib/jupyter-transport-model.js +++ b/modules/jupyter-widget/src/lib/jupyter-transport-model.js @@ -27,6 +27,8 @@ if (DOMWidgetModel) { custom_libraries: [], json_input: null, mapbox_key: null, + google_maps_key: null, + google_maps_map_id: null, selected_data: [], data_buffer: null, tooltip: null, diff --git a/modules/jupyter-widget/src/playground/create-deck.js b/modules/jupyter-widget/src/playground/create-deck.js index 8cd7c283a95..b602dcb7cc1 100644 --- a/modules/jupyter-widget/src/playground/create-deck.js +++ b/modules/jupyter-widget/src/playground/create-deck.js @@ -131,6 +131,7 @@ function createStandaloneFromProvider({ props, mapboxApiKey, googleMapsKey, + googleMapsMapId, handleEvent, getTooltip, container, @@ -183,7 +184,8 @@ function createStandaloneFromProvider({ return createGoogleMapsDeckOverlay({ ...sharedProps, ...props, - googleMapsKey + googleMapsKey, + googleMapsMapId }); case 'maplibre': log.info('Using MapLibre')(); @@ -202,9 +204,10 @@ function createStandaloneFromProvider({ } } -function createDeck({ +export function createDeck({ mapboxApiKey, googleMapsKey, + googleMapsMapId, container, jsonInput, tooltip, @@ -251,6 +254,7 @@ function createDeck({ props, mapboxApiKey, googleMapsKey, + googleMapsMapId, handleEvent, getTooltip, container, @@ -284,4 +288,4 @@ function createDeck({ return deckgl; } -export {createDeck, updateDeck, jsonConverter}; +export {updateDeck, jsonConverter}; diff --git a/modules/jupyter-widget/src/playground/playground.js b/modules/jupyter-widget/src/playground/playground.js index 601516f6bb7..13b0fc300da 100644 --- a/modules/jupyter-widget/src/playground/playground.js +++ b/modules/jupyter-widget/src/playground/playground.js @@ -16,8 +16,16 @@ export function initPlayground() { Transport.setCallbacks({ onInitialize({transport}) { // Extract "deck.gl playground" props - const {width, height, customLibraries, mapboxApiKey, jsonInput, tooltip} = - getPlaygroundProps(transport); + const { + width, + height, + customLibraries, + mapboxApiKey, + googleMapsKey, + googleMapsMapId, + jsonInput, + tooltip + } = getPlaygroundProps(transport); // Load mapbox CSS loadMapboxCSS(); @@ -31,6 +39,8 @@ export function initPlayground() { const deck = createDeck({ mapboxApiKey, + googleMapsKey, + googleMapsMapId, container: deckContainer, jsonInput: jsonProps, tooltip, @@ -120,6 +130,8 @@ function getPlaygroundProps(transport) { height: jupyterModel.get('height'), customLibraries: jupyterModel.get('custom_libraries'), mapboxApiKey: jupyterModel.get('mapbox_key'), + googleMapsKey: jupyterModel.get('google_maps_key'), + googleMapsMapId: jupyterModel.get('google_maps_map_id'), jsonInput: jupyterModel.get('json_input'), tooltip: jupyterModel.get('tooltip') }; diff --git a/modules/jupyter-widget/src/playground/utils/google-maps-utils.js b/modules/jupyter-widget/src/playground/utils/google-maps-utils.js index dfe8ef5a65d..494aec0e45d 100644 --- a/modules/jupyter-widget/src/playground/utils/google-maps-utils.js +++ b/modules/jupyter-widget/src/playground/utils/google-maps-utils.js @@ -13,6 +13,7 @@ export function createGoogleMapsDeckOverlay({ onComplete, getTooltip, googleMapsKey, + googleMapsMapId, layers, mapStyle = 'satellite', initialViewState = {latitude: 0, longitude: 0, zoom: 1} @@ -27,6 +28,9 @@ export function createGoogleMapsDeckOverlay({ mapTypeId: mapStyle, zoom: initialViewState.zoom }; + if (googleMapsMapId) { + view.mapId = googleMapsMapId; + } const map = new window.google.maps.Map(container, view); deckOverlay.setMap(map);