Embedding Interactive Maps into an IPython Notebook

Using HTML widgets to create interactive maps on an IPython Notebook

Posted by Agustín Bartó 3 years ago Comments

About the authors

ttwick and Machinalis are cooperating on fintech projects for Wall Street

IPython notebook

This post is also available as an IPython notebook on GitHub.

Introduction

There are many sources of geolocated data for interesting events available on the Internet. The data ranges from catastrophic events as the latest earthquakes, to the most mundane of tweets regarding Kim Kardashian. Putting aside the actual value of the data, IPyhton Notebooks are a great tool to play, analyze and publish the results of of our work.

The purpose of this post (and the associated Notebook) is to show you how easy it is to fetch geolocated data from public sources, create Pandas’ DataFrames from it, and plotting the results using interactive Matplotlib, Google Maps and OpenLayers.

Requirements

In order to properly run this Notebook, you need to install the following Python modules:

  • matplotlib
  • Basemap (you might need to install it with pip install basemap –allow-external basemap –allow-unverified basemap)
  • dateutil
  • pandas

The rest of the modules we use are dependencies for IPython Notebook, pandas or Basemap.

About Pandas

Although Pandas is not strictly necessary to accomplish what we do in the examples, it is such a popular data analysis tool that we wanted to use it anyway. We recommend that you read the 10 Minutes to Pandas tutorial to get and idea of what it can do or buy Python for Data Analysis for an in depth guide of data analysis using Python, Pandas and NumPy.

Custom date picker widget

Before we can show you how to embed interactive maps we need to define a custom IPython Notebook widget to pick dates. The code is pretty much the same as the one presented in this example save for a few bugfixes.

# Taken from http://nbviewer.ipython.org/github/ipython/ipython/blob/master/examples/Interactive%20Widgets/Custom%20Widgets.ipynb

class DateWidget(widgets.DOMWidget):
    _view_name = Unicode('DatePickerView', sync=True)
    value = Unicode(sync=True)
    description = Unicode(sync=True)

    # This function automatically gets called by the traitlet machinery when
    # value is modified because of this function's name.
    def _value_changed(self, name, old_value, new_value):
        # Parse the date time value.
        try:
            parsed_date = parser.parse(new_value)
            parsed_date_string = parsed_date.strftime("%Y-%m-%d")
        except:
            parsed_date_string = ''

        # Set the parsed date string if the current date string is different.
        if self.value != parsed_date_string:
            self.value = parsed_date_string
%%javascript

// Taken from http://nbviewer.ipython.org/github/ipython/ipython/blob/master/examples/Interactive%20Widgets/Custom%20Widgets.ipynb

require(["widgets/js/widget"], function(WidgetManager){

    // Define the DatePickerView
    var DatePickerView = IPython.DOMWidgetView.extend({
        render: function(){
            this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make
                                                        it fit with the other built in widgets.*/
            // Create a label.
            this.$label = $('<div />')
                .addClass('widget-hlabel')
                .appendTo(this.$el)
                .hide(); // Hide the label by default.

            // Create the date picker control.
            this.$date = $('<input />')
                .attr('type', 'date')
                .appendTo(this.$el);

            // FIXED: This line wasn't on the example, but the widget is not properly initialized if we
            // don't call update() on render()
            this.update();
        },

        update: function() {
            console.log('value', this.model.get('value'))

            // Set the value of the date control and then call base.
            this.$date.val(this.model.get('value')); // ISO format "YYYY-MM-DDTHH:mm:ss.sssZ" is required

            // Hide or show the label depending on the existance of a description.
            var description = this.model.get('description');
            if (description == undefined || description == '') {
                this.$label.hide();
            } else {
                this.$label.show();
                this.$label.text(description);
            }

            return DatePickerView.__super__.update.apply(this);
        },

        // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)
        events: {"change": "handle_date_change"},

        // Callback for when the date is changed.
        handle_date_change: function(event) {
            this.model.set('value', this.$date.val());
            this.touch();
        },
    });

    // Register the DatePickerView with the widget manager.
    WidgetManager.register_widget_view('DatePickerView', DatePickerView);
});

Plotting historic earthquake data for Mexico City with Matplotlib

The United States Geological Survey is a fantastic source of information. They provide quality data from all over the planet in a variety of formats for all kinds of events (not just geological ones), so it’s the go to source for examples related to geolocated data.

For this example we’re going to use Earthquake Search and Web Service API that allows you to search for geological events using several criteria and filters.

We’re going to plot (initially) all the earthquakes of magnitude 4.0 or higher in an 800km circle around Mexico City from 1/1/1984 to 1/1/2014.

In order to make the map interactive, we’ll use IPython’s interact() function that takes a callback function and an arbitrary list of parameters that can either be fixed values or widgets. Whenever a widget is updated, the callback function is called.

Our callback function display_map, makes a request to the USGS API, creates a Pandas DataFrame from the CSV response, creates a Basemap map, and plots the geological events onto the map. The latitude, longitude, radius, start date, end date and minimum magnitude parameters are presented as widgets. The start date and end date use the custom date selection widget that was defined above.

def display_map(latitude, longitude, starttime, endtime, maxradiuskm, minmagnitude):
    # Make the request to the Earthquake Search and Web Service API
    response = requests.get('http://comcat.cr.usgs.gov/fdsnws/event/1/query', params={
        'starttime': parser.parse(starttime).isoformat(),
        'endtime': parser.parse(endtime).isoformat(),
        'latitude': latitude,
        'longitude': longitude,
        'maxradiuskm': maxradiuskm,
        'minmagnitude': minmagnitude,
        'magtype': 'md',
        'format': 'csv' # We ask for a CSV formatted response
    })

    # Create a DataFrame from the CSV response. Notice that we force Pandas to
    # interpret the latitude and longitude columns as float and that we use the
    # time column as index.
    earthquakes_df = pd.read_csv(
        StringIO.StringIO(response.text),
        dtype={'latitude':'float', 'longitude':'float'},
        index_col='time',
        parse_dates=True
    )

    figure, axes = plt.subplots(nrows=1, ncols=1, figsize=(18, 18))

    # Create a new map with cylindrical projection centered around the supplied
    # coordinates
    m = Basemap(
                ax=axes,
                projection='cyl',
                lon_0=longitude, lat_0=latitude,
                llcrnrlat=latitude - 5.0, urcrnrlat=latitude + 5.0,
                llcrnrlon=longitude - 5.0, urcrnrlon=longitude + 5.0,
                resolution='i'
                )

    m.drawmapboundary(fill_color='aqua')
    m.fillcontinents(color='coral',lake_color='aqua')

    m.drawcoastlines()
    m.drawstates()
    m.drawcountries()
    m.drawstates()

    # Plot the geolocical events as blue dots
    x, y = m(earthquakes_df.longitude, earthquakes_df.latitude)
    m.plot(x, y, 'bo', alpha=0.5)

    x, y = m([longitude], [latitude])
    m.plot(x, y, 'rx', markersize=15.0)

    plt.title('Earthquakes around ({longitude}, {latitude}) from {starttime} to {endtime}'.format(longitude=longitude, latitude=latitude, starttime=starttime, endtime=endtime))
    plt.show()
i = interact(
    display_map,
    latitude=widgets.FloatTextWidget(description='Latitude', value=19.4284700, min=-90.0, max=90.0),
    longitude=widgets.FloatTextWidget(description='Longitude', value=-99.1276600, min=-180.0, max=180.0),
    starttime=DateWidget(description='Start Date', value='1984-01-01'),
    endtime=DateWidget(description='End Date', value='2014-01-01'),
    maxradiuskm=widgets.FloatTextWidget(description="Radius (Km)", value=800, min=0.0),
    minmagnitude=widgets.FloatTextWidget(description="Minimum Magnitude", value=4.0, min=0.0)
)
/static/media/uploads/uploads/basemap_controls.png /static/media/uploads/uploads/basemap.png

Plotting recent earthquakes with Google Maps

Although Basemap is quite capable it lacks the interactivity that most users are accostumed to. Luckily, it is pretty simple to embed arbitrary HTML and Javascript code into an IPython Notebook, and we’re going to use this feature to integrate Google Maps (using Google Maps Javascript API) in our example.

We’ll use of one USGS earthquake feeds to plot the latest events. We’re going to use the interact function() again, but we’ll give the user the ability to choose which feed to use and the type of map to render. Notice that there’s an additional parameter called “div” that takes and HTMLWidget as parameter. We need this widget rendered before any Javascript code is executed so the Google Maps API can paint the map onto it.

The callback function makes the request to the web service, and generates a Pandas DataFrame from the JSON response. Afterwards it displays Javascript code from a Jinja2 template that creates the map, and then iterates over the events to create a marker for each of them.

def display_earthquake_markers_map(latitude, longitude, feed, map_type, div):
    # Make a request to the supplied feed and parse the response as JSON
    response = requests.get(feed)
    response_json = response.json()

    # Create a DataFrame from a list comprehension built from the "features"
    # field of the JSON response.
    latest_earthquakes = [(
        datetime.fromtimestamp(float(feature['properties']['time']) / 1000.0),
        feature['geometry']['coordinates'][1],
        feature['geometry']['coordinates'][0],
        feature['properties']['mag'],
        feature['properties']['title'],
    ) for feature in response_json['features']]

    latest_earthquakes_df = pd.DataFrame(latest_earthquakes, columns=['time', 'latitude', 'longitude', 'mag', 'title'])
    latest_earthquakes_df.set_index('time', inplace=True)

    # Define a Jinja2 template that creates the map, and iterates of the
    # features to create a marker for each of them.
    latest_earthquakes_javascript = jinja2.Template(
    """
 require(['async!http://maps.googleapis.com/maps/api/js?sensor=false&libraries=visualization&callback=console.log'], function() {
 var mapOptions = {
 zoom: 2.0,
 center: new google.maps.LatLng({{ latitude }}, {{ longitude }}),
 mapTypeId: {{ map_type }}
 };

 var map = new google.maps.Map(document.getElementById('latest_earthquakes_map_markers'), mapOptions);
 var markers = Array();

 var machinalis_marker = new google.maps.Marker({
 position: new google.maps.LatLng(-31.4122599,-64.2010031),
 icon: {
 size: new google.maps.Size(30, 30),
 url: 'data:image/png;base64,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'
 },
 title: 'http://www.machinalis.com',
 map: map
 });

 {% for earthquake in latest_earthquakes %}
 markers.push(new google.maps.Marker({
 position: new google.maps.LatLng({{ earthquake[1] }}, {{ earthquake[2] }}),
 title: "{{ earthquake[4] }}",
 map: map
 }));
 {% endfor %}
 });
 """
    )

    # Render the template and display it as Javascript
    display(Javascript(latest_earthquakes_javascript.render(
        latest_earthquakes=latest_earthquakes_df.itertuples(),
        map_type=map_type, latitude=latitude, longitude=longitude
    )))
i = interact(
    display_earthquake_markers_map,
    latitude=widgets.FloatTextWidget(description='Latitude', value=-31.4122599, min=-90.0, max=90.0),
    longitude=widgets.FloatTextWidget(description='Longitude', value=-64.2010031, min=-180.0, max=180.0),
    map_type=widgets.DropdownWidget(description="Map Type", values={
        'Road Map': 'google.maps.MapTypeId.ROADMAP',
        'Satellite': 'google.maps.MapTypeId.SATELLITE',
        'Hybrid': 'google.maps.MapTypeId.HYBRID',
        'Terrain': 'google.maps.MapTypeId.TERRAIN',
    }, value='google.maps.MapTypeId.SATELLITE'),
    feed=widgets.DropdownWidget(description='Feed', values={
        'Significant Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_week.geojson',
        'M4.5+ Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson',
        'M2.5+ Earthquakes': 'hytp://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojson',
        'M1.0+ Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_week.geojson',
        'All Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson'
    }, value='http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson'),
    div=widgets.HTMLWidget(value='<div id="latest_earthquakes_map_markers" style="width: 800px; height: 600px"></div>')
)
/static/media/uploads/uploads/google_maps_markers_controls.png /static/media/uploads/uploads/google_maps_markers.png

It might be useful to represent the geological events as circles with radius proportional to its magnitude. For that we define a getCircle Javascript function that’s going to define the icon for each marker.

def display_earthquake_circles_map(latitude, longitude, feed, map_type, div):
    response = requests.get(feed)
    response_json = response.json()

    latest_earthquakes = [(
        datetime.fromtimestamp(float(feature['properties']['time']) / 1000.0),
        feature['geometry']['coordinates'][1],
        feature['geometry']['coordinates'][0],
        feature['properties']['mag'],
        feature['properties']['title'],
    ) for feature in response_json['features']]

    latest_earthquakes_df = pd.DataFrame(latest_earthquakes, columns=['time', 'latitude', 'longitude', 'mag', 'title'])
    latest_earthquakes_df.set_index('time', inplace=True)

    latest_earthquakes_javascript = jinja2.Template(
    """
 require(['async!http://maps.googleapis.com/maps/api/js?sensor=false&libraries=visualization&callback=console.log'], function() {
 function getCircle(magnitude) {
 return {
 path: google.maps.SymbolPath.CIRCLE,
 fillColor: 'red',
 fillOpacity: .2,
 scale: Math.pow(2, magnitude) / Math.PI,
 strokeColor: 'white',
 strokeWeight: .5
 };
 }

 var mapOptions = {
 zoom: 2.0,
 center: new google.maps.LatLng({{ latitude }}, {{ longitude }}),
 mapTypeId: {{ map_type }}
 };

 var map = new google.maps.Map(document.getElementById('latest_earthquakes_map_circles'), mapOptions);

 var machinalis_marker = new google.maps.Marker({
 position: new google.maps.LatLng(-31.4122599,-64.2010031),
 icon: {
 size: new google.maps.Size(30, 30),
 url: 'data:image/png;base64,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'
 },
 title: 'http://www.machinalis.com',
 map: map
 });

 var markers = Array();

 {% for earthquake in latest_earthquakes %}
 markers.push(new google.maps.Marker({
 position: new google.maps.LatLng({{ earthquake[1] }}, {{ earthquake[2] }}),
 title: "{{ earthquake[4] }}",
 icon: getCircle({{ earthquake[3] }}),
 map: map
 }));
 {% endfor %}
 });
 """
    )
    display(Javascript(latest_earthquakes_javascript.render(
        latest_earthquakes=latest_earthquakes_df.itertuples(),
        map_type=map_type, latitude=latitude, longitude=longitude
    )))
i = interact(
    display_earthquake_circles_map,
    latitude=widgets.FloatTextWidget(description='Latitude', value=-31.4122599, min=-90.0, max=90.0),
    longitude=widgets.FloatTextWidget(description='Longitude', value=-64.2010031, min=-180.0, max=180.0),
    map_type = widgets.DropdownWidget(description="Map Type", values={
        'Road Map': 'google.maps.MapTypeId.ROADMAP',
        'Satellite': 'google.maps.MapTypeId.SATELLITE',
        'Hybrid': 'google.maps.MapTypeId.HYBRID',
        'Terrain': 'google.maps.MapTypeId.TERRAIN',
    }, value='google.maps.MapTypeId.SATELLITE'),
    feed = widgets.DropdownWidget(description='Feed', values={
        'Significant Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_week.geojson',
        'M4.5+ Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson',
        'M2.5+ Earthquakes': 'hytp://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojson',
        'M1.0+ Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_week.geojson',
        'All Earthquakes': 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson'
    }, value='http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson'),
    div=widgets.HTMLWidget(value='<div id="latest_earthquakes_map_circles" style="width: 800px; height: 600px"></div>')
)
/static/media/uploads/uploads/google_maps_markers_controls.png /static/media/uploads/uploads/google_maps_circles.png

Plotting recent heat points in Argentina with Google Maps

We mentioned at the beginning of the post that there are many sources of geolocated data available on the Internet. Of one such sources is provided by CONAE (The Argentine Space Agency) and it gives us a list of heat points as a GEORSS feed (A specialized Atom feed).

We can use this feed to build a new DataFrame and from it, a map that shows those heat points with a custom icon (an Base64 encoded PNG file). As before, we’re going to use requests to fetch the data, but this time we’ll use ElementTree and XPath to parse and preparte the date before we build the DataFrame. All this functionality is contained within a new callback function.

def display_heat_points_map(latitude, longitude, map_type, div):
    # Make the request and parse the response as XML using ElementTree
    response = requests.get('http://catalogos.conae.gov.ar/focos/focosdecalor.xml')
    response_atom = ElementTree.parse(BytesIO(response.content))

    # Build a DataFrame extrating the data for each entry using XPath
    entries_id = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}id')]
    entries_title = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}title')]
    entries_updated = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}updated')]
    entries_summary = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}summary')]
    entries_point_lat = [float(e.text.split(" ")[0]) for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.georss.org/georss}point')]
    entries_point_lon = [float(e.text.split(" ")[1]) for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.georss.org/georss}point')]

    entries_df = pd.DataFrame({
        'id': entries_id,
        'title': entries_title,
        'summary': entries_summary,
        'lat': entries_point_lat,
        'lon': entries_point_lon
    }, index=entries_updated)

    # Define a Jinja2 template that creates the map, and iterates of the
    # entries to create a marker with a custom icon for each of them.
    heat_points_javascript = jinja2.Template(
    """
 require(['async!http://maps.googleapis.com/maps/api/js?sensor=false&libraries=visualization&callback=console.log'], function() {
 var mapOptions = {
 zoom: 4,
 center: new google.maps.LatLng({{ latitude }}, {{ longitude }}),
 mapTypeId: {{ map_type }}
 };

 var map = new google.maps.Map(document.getElementById('heat_points'), mapOptions);

 var machinalis_marker = new google.maps.Marker({
 position: new google.maps.LatLng(-31.4122599,-64.2010031),
 icon: {
 size: new google.maps.Size(30, 30),
 url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUdESIC2is5hgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAF9klEQVRIx72WaVBTVxTH/29LYgCxskQkLlSgSFSMYqmgVRSjIoxUpVrHqrWiWMcFl7rUDrU6Mu1UaV1ah6mOSGHccBxrnSq4VS1QQcRQjIIgiAUEFRASs7x3+yESQxIRmE7vpzfv3nt+95zzP+deoBujMHbDyjyXcJLLhRF1WFxVUKCC6aoNujtg44073xG9AaAptObf6bdBOlj5v4BB220j/zn4aMTCThkqW7TN8p2lWvLG9eybFszOPoSLk+PneurI96VyacAHGclPHa3zPfglyhZtO1L74KHrmPMpUzvt8TpXhd3keP8hLjcDPix2unYnvSWn2IPTGvp0ZExbVj2BuaKeku8dpS349KulnQJv3vJFv0ynkHX3P/t2FACkKqcHJlOBzfqyagUx8QBNARTVYS4pmjZSNA1TQ2MPY+r5/fkRy1YDwG9LtsjOeIQvvTAsdjIAJPmHvwq1Zmfqr95GLqj+wGkURq46T0qrw/VV/4Biuqc/MDRMl24lF83ZFG04lR/ONxkpmmpGkn84t+neJRMNAJkTFkYxrYYgUOY9+qx8laGylrMYEQjY3j1hchY3dsTiPHrVEkGwUhADXeaVCXxjCwUKEJ49h8pzkNoSapahtbSIA9o2mcNqgdKyt1pefDJpcEzazprXQZtX7UHA8aSRxM/7D7vSo8wFR7EMOIm4wQKennXwYtFQd4VkuH+Tnex794Ru4UTlmB0Jmo687fnDCgBAqObEOOfRQ0vsIu/qBFHs+MRh2fvGthPX4ssZJS5BfhG2GwR/73XjtieUdTa1pQu24oVvn2lEaK/DZi/XA8rD27522ECqLuQEt1Mpx0IX+s4vu5VRndaUX2oign9OfCCdFFwIq3w7CdT8dpG8MWNtnFBcsRsA6GYdx1sJShoSWBXyzfo6O+vWAnp5Rtsf1yrvJo2SiI8JBqN5QUUtdyNglk4QBFByz2ssybqZQvQGAAAPysoEgUjued1hvfaXPUJV/UBQAOvmihYJ1WCXUxfpaUpCgJdgYuLBl9dIAIBUPo5ghRfmW8ZxfbB25VM9dyvkGYk+l2clLAdPJOWc4eySYz8+sFN5D1ZvZ7etUggBK1X612mLSmWOuPyz58Nt/8kzEs3t9ETyvg5V3qwfBiPv+ILw6GVi97o09JuslI8w8bxxQCvicL8mvu102rySYHRzKOQDlvHqAouXEp++KB/QU0GaWpy1Ura2XSzygj9WEXXFuVe3LAGJUE4cfXbvxc4CT0XFg14zQySP2681PKxj2sCtPp45EzUnQx2WE9PXbYptTqTPdOld8TTmzH64f354gaH6sQUKAJ4eHrLEoRNk7cApo2d6F/jOqDedy0+wNaTLv9MnJ2jOGQC4t3j7a4Gno8wZuh67fjZXXpdiO9+So347qoarLfloc5IFLGOlK0xVde7WIbbutfTdR9PU4cuuNfagvayN7QpUWb41q6ewuaMXxosu3DrCt+pga4tiaPDNLTDcrlhvpW/gr76RrcKTZikAcHIPo+FhPUfZVgPLQqwKzr6iKdrxgqOvbrh9zrQrUDUoOEAR51JcvUZfWcO1bzQEIm8P3lDTwJjhDO4rvebPvX4k7RVYtXy+UNNwsMmg31gTM+KnIXnVCaar6m1gGbuuRTv1AC0RARQFYuTBP281e2Z9UkJg7OdedjXSb+TMevHwJ+p76bSztOi9vNSojLA5cNg5dgZEYK0mGwWRK4uNWfkKB6/KjgcBmF7OpHJeaB9xYXl99KVD5I09tm2cmbt2oOfZWxWCTt/dUoYwZMDs0IL0Y1163nrerVtkgRICTtYbpsD+JxgfL1A2yRfLZTD6epVQwwf9bi1MsdZ4tMvv6kadVsO4OJlvIoaBJDp0/pjCjNhRmuMU3dfNaA24O8Tdf+zfxxUhealTaTfXVggCKJoGLxXf7DJYVXIyI7e/yJULDngkCVVkK/ZtTHulm/YpM9U+dbb06JixCU4jAwzVo/qvebcgbWSX87NnRPRr5wrGL/0zVzKW5IrCSMHAGENKWKzc0brM9+e91sa/SYtOrUfMruYAAAAASUVORK5CYII='
 },
 title: 'http://www.machinalis.com',
 map: map
 });

 // Taken from http://hubpic.com/flame-icon-vector-material-04.html
 var marker_icon = {
 size: new google.maps.Size(21, 32),
 url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAgCAYAAAD9oDOIAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH3gUcDQEVStH2UQAAArJJREFUSMet1UtoXGUUB/Dfdyc0j0JREmybSBaKL6ILxTRYXUQQFAQXlUIXpQWzUFS6Koi7SnFpNz6ouml9RKpI6SYUurGCzcKtglVQWm2h1Edt7CN1JsfF/XKbiZnMJOmB4c537rl//uf1/1ihxc1nEYwHA4tjipWCphKwH3/jUuL3sEYL9gUR7HcLwLqCqQwYwV2xBrD53+kgoihBbwXLL4OIFyqWPy8XX3TQ5f3Y5gAuVa/n1sKwN6jHdhHfiHikYlpfFdNsJwyqeT1TvwlVCwZiFemP4HFv4Gp29jaFjKVVMN3lXjy0wDPS9P7d5RZE0JO4vojpFUf0uXNB1/7Ec03fv5R4vxXT2eCzoJYB73aHPkOaJ3Ijnm/6fh//H9oi0w1cwLns32Jwibz+xYvorjybgo/TMjX9ERuDNzFguEXB1uMoeirPzmC0Fej9+fkaety2zFZswCdN3qNNoAvqsXV+BrGhGqNWNohD1WkoZ1iCZn3sxsO2VUGXnWkDOof7cKDyvBp0LUz/U+sVdlcBF5zvYI8bGMM4uSgHIUWZyDm78Qqexl+mccYxOwxoL3QNPFnJTH+RjzyaR+Y98BhO2ysn1MbW4eXqNF5gO7g9MxrGBNjjJx+ant+7Nky3VqcdKTiPzb7A5uzuw6RyAWfMOqZbfxvgelXb31JwDT0+z2OymMHX+BW72oB24wnManRlmRg0s0RgDU/l9Osd1LZs1MUicymXtGhRr04AL+dGM13gMLnrfWu4Hc9W/yYLnMB3ZvJ2pFUA9uIIyv5MFWikUt/P+iiL30qBv8VxgZHE1cUqNWUnfuhw6FOu495y6BO/xLygZLueeNYNEyZ87x1czKNSW0KF1+EUnnHKP+5JueGpzZ0/HHwQo/6It9yIk+px0lx8pRFvm40HHA8eXOo6+Q9fYtvU17HpOgAAAABJRU5ErkJggg=='
 }

 var markers = Array();
 var marker_info_windows = Array();

 {% for heat_point in heat_points %}
 marker_info_windows[{{ loop.index0 }}] = new google.maps.InfoWindow({
 content: '{{ heat_point[4] }}'
 });

 markers[{{ loop.index0 }}] = new google.maps.Marker({
 position: new google.maps.LatLng({{ heat_point[2] }}, {{ heat_point[3] }}),
 title: '{{ heat_point[5] }}',
 icon: marker_icon,
 map: map
 });

 google.maps.event.addListener(markers[{{ loop.index0 }}], 'click', function() {
 var _map = marker_info_windows[{{ loop.index0 }}].getMap();

 if (_map !== null && typeof _map !== "undefined") {
 marker_info_windows[{{ loop.index0 }}].close();
 } else {
 marker_info_windows[{{ loop.index0 }}].open(map, markers[{{ loop.index0 }}]);
 }
 });
 {% endfor %}
 })
 """
    )
    # Render the template and display it as Javascript
    display(Javascript(heat_points_javascript.render(
        heat_points=entries_df.itertuples(),
        map_type=map_type, latitude=latitude, longitude=longitude
    )))
i = interact(
    display_heat_points_map,
    latitude=widgets.FloatTextWidget(description='Latitude', value=-31.4122599, min=-90.0, max=90.0),
    longitude=widgets.FloatTextWidget(description='Longitude', value=-64.2010031, min=-180.0, max=180.0),
    map_type = widgets.DropdownWidget(description="Map Type", values={
        'Road Map': 'google.maps.MapTypeId.ROADMAP',
        'Satellite': 'google.maps.MapTypeId.SATELLITE',
        'Hybrid': 'google.maps.MapTypeId.HYBRID',
        'Terrain': 'google.maps.MapTypeId.TERRAIN',
    }, value='google.maps.MapTypeId.SATELLITE'),
    div=widgets.HTMLWidget(value='<div id="heat_points" style="width: 800px; height: 600px"></div>'),
)
/static/media/uploads/uploads/google_maps_fire_controls.png /static/media/uploads/uploads/google_maps_fire.png

Instead of using one of Google Map’s predefined marker icons, we’ve embedded our own encoding the image using Base64. Each data entry contains an HTML snippet that we’re going to show when the user clicks the marker’s icon. The following code contains a Jinja2 template that defines the required Javascript objects and iterates over the data entries to create the markers. Later we embed the rendered template so the map is displayed on the div above.

def display_heat_points_map_ol(latitude, longitude, zoom, div):
    # Make the request and parse the response as XML using ElementTree
    response = requests.get('http://catalogos.conae.gov.ar/focos/focosdecalor.xml')
    response_atom = ElementTree.parse(BytesIO(response.content))

    # Build a DataFrame extrating the data for each entry using XPath
    entries_id = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}id')]
    entries_title = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}title')]
    entries_updated = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}updated')]
    entries_summary = [e.text for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}summary')]
    entries_point_lat = [float(e.text.split(" ")[0]) for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.georss.org/georss}point')]
    entries_point_lon = [float(e.text.split(" ")[1]) for e in response_atom.findall('.//{http://www.w3.org/2005/Atom}entry/{http://www.georss.org/georss}point')]

    entries_df = pd.DataFrame({
        'id': entries_id,
        'title': entries_title,
        'summary': entries_summary,
        'lat': entries_point_lat,
        'lon': entries_point_lon
    }, index=entries_updated)

    # Define a Jinja2 template that creates the map, and iterates of the
    # entries to create a marker with a custom icon for each of them.
    heat_points_javascript = jinja2.Template(
    """
 require(["open_layers"], function() {
 var map = new OpenLayers.Map('heat_points_ol', {});
 // var layer = new OpenLayers.Layer.WMS('OpenLayers WMS', 'http://vmap0.tiles.osgeo.org/wms/vmap0', {layers: 'basic'});

 var layer = new OpenLayers.Layer.WMS(
 'Global Imagery',
 'http://maps.opengeo.org/geowebcache/service/wms',
 {layers: 'bluemarble'},
 {isBaseLayer: true}
 );

 map.addLayer(layer);
 map.setCenter(
 new OpenLayers.LonLat({{ longitude }}, {{ latitude }}),
 {{ zoom }}
 );

 var heat_points_layer = new OpenLayers.Layer.Markers("Heat Points Layer");

 var machinalis_logo_icon_url = 'data:image/png;base64,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';
 var heat_point_icon_url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAgCAYAAAD9oDOIAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH3gUcDQEVStH2UQAAArJJREFUSMet1UtoXGUUB/Dfdyc0j0JREmybSBaKL6ILxTRYXUQQFAQXlUIXpQWzUFS6Koi7SnFpNz6ouml9RKpI6SYUurGCzcKtglVQWm2h1Edt7CN1JsfF/XKbiZnMJOmB4c537rl//uf1/1ihxc1nEYwHA4tjipWCphKwH3/jUuL3sEYL9gUR7HcLwLqCqQwYwV2xBrD53+kgoihBbwXLL4OIFyqWPy8XX3TQ5f3Y5gAuVa/n1sKwN6jHdhHfiHikYlpfFdNsJwyqeT1TvwlVCwZiFemP4HFv4Gp29jaFjKVVMN3lXjy0wDPS9P7d5RZE0JO4vojpFUf0uXNB1/7Ec03fv5R4vxXT2eCzoJYB73aHPkOaJ3Ijnm/6fh//H9oi0w1cwLns32Jwibz+xYvorjybgo/TMjX9ERuDNzFguEXB1uMoeirPzmC0Fej9+fkaety2zFZswCdN3qNNoAvqsXV+BrGhGqNWNohD1WkoZ1iCZn3sxsO2VUGXnWkDOof7cKDyvBp0LUz/U+sVdlcBF5zvYI8bGMM4uSgHIUWZyDm78Qqexl+mccYxOwxoL3QNPFnJTH+RjzyaR+Y98BhO2ysn1MbW4eXqNF5gO7g9MxrGBNjjJx+ant+7Nky3VqcdKTiPzb7A5uzuw6RyAWfMOqZbfxvgelXb31JwDT0+z2OymMHX+BW72oB24wnManRlmRg0s0RgDU/l9Osd1LZs1MUicymXtGhRr04AL+dGM13gMLnrfWu4Hc9W/yYLnMB3ZvJ2pFUA9uIIyv5MFWikUt/P+iiL30qBv8VxgZHE1cUqNWUnfuhw6FOu495y6BO/xLygZLueeNYNEyZ87x1czKNSW0KF1+EUnnHKP+5JueGpzZ0/HHwQo/6It9yIk+px0lx8pRFvm40HHA8eXOo6+Q9fYtvU17HpOgAAAABJRU5ErkJggg==';

 var machinalis_logo_icon = new OpenLayers.Icon(machinalis_logo_icon_url, new OpenLayers.Size(30, 30));
 heat_points_layer.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(-64.2010031, -31.4122599), machinalis_logo_icon));

 var heat_point_icon = new OpenLayers.Icon(heat_point_icon_url, new OpenLayers.Size(21, 32));

 {% for heat_point in heat_points %}
 heat_points_layer.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat({{ heat_point[3] }}, {{ heat_point[2] }}), heat_point_icon.clone()));
 {% endfor %}

 map.addLayer(heat_points_layer);
 })
 """
    )
    # Render the template and display it as Javascript
    display(Javascript(heat_points_javascript.render(
        heat_points=entries_df.itertuples(),
        zoom=zoom, latitude=latitude, longitude=longitude
    )))
i = interact(
    display_heat_points_map_ol,
    latitude=widgets.FloatTextWidget(description='Latitude', value=-31.4122599, min=-90.0, max=90.0),
    longitude=widgets.FloatTextWidget(description='Longitude', value=-64.2010031, min=-180.0, max=180.0),
    zoom=widgets.IntSliderWidget(description='zoom', value=4, min=0, max=100),
    div=widgets.HTMLWidget(value='<div id="heat_points_ol" style="width: 800px; height: 600px"></div>'),
)
/static/media/uploads/uploads/openlayers_controls.png /static/media/uploads/uploads/openlayers.png

Conclusions

You’ll find a lot of interesting examples of using geolocated data on an IPython Notebook, but we wanted to show you how easy it is to turn them into an interactive experience using IPython Notebook. Both Basemap and Google Maps are great tools, but you can easily apply what we just showed you to integrate other javascript plotting libraries with geographical support like d3js.


Previous / Next posts


Comments