Search code examples
python-3.xbing-mapsgeocoder

Open a Bing Maps map of the world and put a pushpin on a location in Python 3


Given a Bing Maps API key, Python 3 and the location on a Bing Map of a city, as given by for example

import geocoder # pip install geocoder
g = geocoder.bing('Mountain View, CA', key=BING_KEY)
print(g.json['bbox'])

How do I, in Python 3 and using geocoder with or without another package,

  • Open up Bing Maps to a map of the world
  • Set a pushpin on Mountain View, as specified by the BBOX or any other element of the JSON from Bing obtained as above using geocoder?

Microsoft shows JavaScript examples, but it doesn't seem to have a native Python API, and I couldn't find a Bing Maps API for pushpins on GitHub, only a few APIs that extract information, but not any that decorates a map.


Solution

  • You can see in the Microsoft documentation that you cannot add pushpins to a Bing Maps URL. If you really want pushpins, you'll need to create your own web page that embeds a Bing Map using their JavaScript API (see below).

    However, instead of pushpins you can use shared places, which gives a similar effect.

    If that is all right with you, you can do the following:

    import geocoder # pip install geocoder
    import webbrowser
    from urllib.parse import quote
    
    BING_KEY = "..."
    searchTerm = "Mountain View, CA"
    g = geocoder.bing(searchTerm, key=BING_KEY)
    lat = g.json['lat']
    long = g.json['lng']
    zoomLevel = 1 # Show entire world
    url = f"https://www.bing.com/maps?cp={lat}~{long}&lvl={zoomLevel}&sp=point.{lat}_{long}_{quote(searchTerm)}"
    webbrowser.open_new(url)
    

    Using the JavaScript API

    If you do want to create your own page rather than use Bing.com, the easiest way is probably to create a separate HTML template and pull the parameters you need from a query string. This template can be opened as a static file, served locally from a server on your computer, or hosted on your own website.

    File bing.html, in the same directory as your Python script:

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <meta charset="utf-8" />
        <script type='text/javascript'>
        function GetMap() {
            // Note Internet Explorer does not support URLSearchParams. It would need to parse manually.
            var urlParams = new URLSearchParams(window.location.search);
    
            var map = new Microsoft.Maps.Map('#myMap', {
                credentials: urlParams.get("key"),
                center: new Microsoft.Maps.Location(+urlParams.get("lat"), +urlParams.get("long")),
                zoom: +urlParams.get("zoom")
            });
    
            var center = map.getCenter();
    
            // Create custom Pushpin
            var pin = new Microsoft.Maps.Pushpin(center, {
                title: urlParams.get("title"),
                subTitle: urlParams.get("subTitle"),
                text: urlParams.get("label")
            });
    
            // Add the pushpin to the map
            map.entities.push(pin);
        }
        </script>
        <script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap' async defer></script>
    </head>
    <body>
        <div id="myMap" style="position:relative;width:600px;height:400px;"></div>
    </body>
    </html>
    

    Then your Python script will be only slightly different from the other option above:

    import geocoder
    import webbrowser
    import pathlib
    from urllib.parse import quote, urlencode
    
    BING_KEY = "..."
    searchTerm = "Mountain View, CA"
    g = geocoder.bing(searchTerm, key=BING_KEY)
    lat = g.json['lat']
    long = g.json['lng']
    zoomLevel = 1
    
    queryString = urlencode({
        "key": BING_KEY,
        "lat": lat,
        "long": long,
        "title": searchTerm,
        "subTitle": "",
        "label": "1",
        "zoom": str(zoomLevel)
    })
    templateFile = "bing.html"
    templatePath = pathlib.Path(__file__).parent.absolute().joinpath(templateFile)
    url = f"file:///{templatePath}?{queryString}"
    
    # If serving locally with `http.server` module:
    # url = f"http://localhost:8000/{templateFile}?{queryString}"
    
    browser = "chrome" # 'firefox' and 'safari' are also options here
    
    # If opening as a static file, it MUST specify `browser`. The default will not work with a query string.
    webbrowser.get(browser).open_new(url)