Google Maps in ERPNext?

I’m trying to build a map using the new Frappe Custom HTML Block. I have the following html/css/javascript, but the map doesn’t load, but it also doesn’t give any console errors.

Has anybody had any success using Google Maps in things like custom dialogs or elsewhere in ERPNext?

#map-canvas {
    height: 300px;
    width: 600px;
    background-color: #CCC;
}
<div id="map-canvas"></div>
function initialize() {
  window.addEventListener('load', function(event) {
    
       //Here is my logic now

    var myLatLng = new google.maps.LatLng(45.4375, 12.3358),
        myOptions = {
            zoom: 5,
            center: myLatLng,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        },
        map = new google.maps.Map(document.getElementById('map-canvas'), myOptions),
        marker = new google.maps.Marker({
            position: myLatLng,
            map: map
        });

    marker.setMap(map);
    moveMarker(map, marker);
   });
}

function moveMarker(map, marker) {

    //delayed so you can see it move
    setTimeout(function () {

        marker.setPosition(new google.maps.LatLng(45.4375, 12.3358));
        map.panTo(new google.maps.LatLng(45.4375, 12.3358));

    }, 1500);

};

initialize();

This is an old repo link where we had used leaflet maps instead of Google to show on the Address doctype. You may find it helpful.

Thanks for the input, but I’d much rather use Google Maps.

Hi @oguruma:

Should import google library somewhere …

Or use just an iframe

<div class="map">
    <iframe
        width="600"
        height="450"
        frameborder="0"
        style="border:0"
         src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2824.7550855442924!2d-93.47895428450719!3d44.862149979098314!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x87f6217d2bc3e9c3%3A0xe9a98dcb1a4689e7!2sNewOrleans!5e0!3m2!1sen!2sus!4v1639448793852!5m2!1sen!2sus"
        allowfullscreen
    ></iframe>
</div>

Hope this helps

Even with the Google library imported, the map still won’t load.

It works fine with a plain html file, so it must be something in ERPNext. Is there something in ERPNext that’s designed to prevent Google Maps from loading or something?

I got it working by setting dimensions in the div.

HTML:

<div id="map" style="height:400px;width:500px"></div>

JS:

 (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
    key: "INSERT_KEY",
    v: "weekly",
    // Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
    // Add other bootstrap parameters as needed, using camel case.
  });

let map;

async function initMap() {
  const { Map } = await google.maps.importLibrary("maps");
    let map_element = root_element.querySelector('#map');
  map = new Map(map_element, {
    center: { lat: -34.397, lng: 150.644 },
    zoom: 8,
  });
}

initMap();
1 Like

Integrating Google Maps into Frappe Framework: A Step-by-Step Guide

0fdaefe7-4a38-4677-b8e1-028f892bd12a

This documentation provides a clean and structured explanation of how to replace OpenStreetMap with Google Maps in a Frappe-based application. The example demonstrates integrating Google Maps for geolocation visualization and updating the map dynamically based on latitude and longitude values.


1. Overview

The goal is to replace OpenStreetMap with Google Maps for better usability and functionality. This involves:

  • Fetching the Google Maps API key securely.
  • Initializing the Google Maps JavaScript API.
  • Dynamically updating the map based on latitude and longitude values stored in the document.
  • Ensuring proper error handling and debouncing for performance optimization.

2. Implementation

2.1 Backend Configuration

a. Fetching the Google Maps API Key

To ensure security, the Google Maps API key is stored encrypted in the iScore Settings document. A custom method retrieves the decrypted key.

import frappe
from frappe.utils.password import get_decrypted_password
@frappe.whitelist()
def get_google_map_api_key():
    """
    Retrieves the decrypted Google Maps API key from the system settings.
    """
    return get_decrypted_password("System Settings", "System Settings", "google_map_api_key")

2.2 Frontend Integration

a. Loading the Google Maps JavaScript API

The Google Maps JavaScript API is loaded dynamically using the importLibrary method. This ensures modularity and avoids loading unnecessary libraries.

refresh: function(frm) {
    frappe.call({
        method: 'your_app.your_module.get_google_map_api_key',
        callback: function(response) {
            (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn():d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
                key: response.message,
                v: "weekly",
            });
        // Initialize the map after loading the API
        initializeMap(frm);
        }
    });
}

b. Initializing the Map

Once the API is loaded, the map is initialized in the form’s UI. The map replaces the existing OpenStreetMap element and is dynamically updated based on the document’s latitude and longitude.

function initializeMap(frm) {
    const originalElement = frm.$wrapper.find('.frappe-control[data-fieldtype="Geolocation"][data-fieldname="map"] .control-input-wrapper');
    if (originalElement.length) {
        originalElement.hide(); // Hide the original OpenStreetMap element
        if (!document.getElementById('google_map')) {
            // Create a new div for Google Maps
            $('<div id="google_map" style="height:500px;width:100%"></div>').insertAfter(originalElement);
        }
    }
    updateMap(parseFloat(frm.doc.latitude), parseFloat(frm.doc.longitude));
}

c. Updating the Map Dynamically

The map and marker are updated whenever the latitude and longitude values change. If the map does not exist, it is initialized; otherwise, the existing map and marker are updated.

let map;
let marker;
async function updateMap(lat,lon) {
    const position = { lat: lat, lng: lon };
    if (!map) {
        // First time initialization
        const { Map } = await google.maps.importLibrary("maps");
        const { Marker } = await google.maps.importLibrary("marker");
        map = new Map(document.getElementById('google_map'), {
            center: position,
            zoom: 17
        });
        marker = new Marker({
            position,
            map,
            title: "Selected Location"
        });
    } else {
        // Update existing map and marker
        map.setCenter(position);
        marker.setPosition(position);
    }


3. Key Benefits

  1. Improved Usability : Google Maps provides a familiar interface with advanced features like street view and satellite imagery.
  2. Dynamic Updates : The map updates automatically based on latitude and longitude values, ensuring real-time visualization.
  3. Security : The Google Maps API key is stored securely and fetched dynamically.

4. Prerequisites

  1. Google Maps API Key : Obtain a valid API key from the Google Cloud Console .
  2. Frappe Framework : Ensure you are using a compatible version of Frappe.
  3. Encrypted Storage : Use the get_decrypted_password utility to securely store and retrieve the API key.
4 Likes