Upgrading to Leaflet 1.0 Beta 2

This post is just a migration log. I’m very exciting to be learning about Leaflet Hotline, but it requires Leaflet 1.0 Beta 2. The problem is that several of the other plugins that I use on http://exploringspatial.com are not compatibleLeaflet 1.0. Here is the run down of what I ran into.

Why are there problems?

I will try to keep this post up-to-date with issues as I find them:

  • Multpolygon class is gone – The Mulitpolygon class is being replaced by enhancements to the Polygon class (see thread). If your plugin used Multipolygon, then you’ll need a new version of the plugin for Leaflet 1.0.
  • GeoJSONLayer.resetStyle(layer) is gone. I switched to layer.setStyle().
  • L.FeatureGroup.EVENTS no longer exists.
  • “Event.layer” is now called “Event.target” in mouse events.

Bing, Google and  Yandex Plugins

Pavel Shramov and Bruno Bergot provide Leaftet plugins for Bing, Google and Yandex maps. When you switch to Leaflet 1.0 the Google Tiles layer will break (I’ve just dumped Bing for the time being since it has not been addressed). When you add a Google layer it to your Leaflet 1.0 map you will see an error in Leaflet-src.js  at line 2382 because callback is undefined.

    whenReady: function (callback, context) {
        if (this._loaded) {
            callback.call(context || this, {target: this});
        } else {
            this.on('load', callback, context);
        }
        return this;
    },

Fortunately Bruno Bergot is already on top of this and you can pull his patch down for the time being: https://github.com/shramov/leaflet-plugins/pull/175.

Leaflet Point-in-Polygon

Leaflet-pip depends on the Mutlipolygon class, so it broke http://exploringspatial.com/#demo/8 at line 15: “Uncaught TypeError: Expecting a function in instanceof check, but got undefined.” Specifically, it breaks at this line of code: “if ((l instanceof L.MultiPolygon…”

var leafletPip = {
    bassackwards: false,
    pointInLayer: function(p, layer, first) {
        'use strict';
        if (p instanceof L.LatLng) p = [p.lng, p.lat];
        else if (leafletPip.bassackwards) p = p.concat().reverse();

        var results = [];

        layer.eachLayer(function(l) {
            if (first && results.length) return;
            if ((l instanceof L.MultiPolygon ||
                 l instanceof L.Polygon) &&
                gju.pointInPolygon({
                    type: 'Point',
                    coordinates: p
                }, l.toGeoJSON().geometry)) {
                results.push(l);
            }
        });
        return results;
    }
};

There is not a patch yet, but I found the solution contained in this thread: https://github.com/mapbox/leaflet-pip/issues/8.  There is a post from user “nikolauskrismer” that replaces the entire code block starting at line 6: “var leafletPip = …” That got me past the error above.

Leaflet MarkerCluster

In http://exploringspatial.com/#demo/5 I used Leaflet.MarkerCluster, but in http://exploringspatial.com/#demo/6 I switched to Leaflet.PruneCluster for performance reasons. Upgrading to Leaflet 1.0 Beta 2 broke Demo 5. Leaflet.MarkerCluster caused the error “Uncaught TypeError: Cannot read property ‘trim’ of undefined” in leaflet-src.js at line 137. The cause was line 51 from leaflet.markercluster-src.js, “this._featureGroup.on( L.FeatureGroup.EVENTS, this._propagateEvent, this).” The problem is that L.FeatureGroup.EVENTS no longer exists in Leaflet 1.0. Since I was already using PruneCluster successfully with Leaftet 1.0 beta 2, and I didn’t see any patches, I dumped Leaflet.MarkerCluster and switched to Demo 5 to Leaflet.PruneCluster.

Mouse Events

Another issue that I ran into on http://exploringspatial.com/#demo/7 was that my mouseover and mouseout events were expecting “event.layer“, but with Leaflet 1.0 that has changed to “event.target” That was easy for me to switch. I was just calling event.target.style to highlight polygons on mouseover.

Reset Style

The final issue I hit was on http://exploringspatial.com/#demo/8. In my states map view I called GeoJSONLayer.resetStyle(layer) on all the state polygons each time a new race distance was selected. That no longer worked in Leaflet 1.0. I had to switch to layer.setStyle() to get around that.

That is all that I’ve found so far. All my changes are now live at http://exploringspatial.com. Let me know if you spot any problems that I missed.

 

Color Gradient Lines: Speed Bump

I was all set to start using Leaflet Hotline when I hit a speed bump. It does not appear to work with Leaflet 0.7.7. Unfortunately, some other plugins on my site do not work with Leaflet 1.0 Beta 2.

This is where having a demo “site” with a MVC framework, as opposed to simple standalone demo pages or in JSFiddle with straight html/javascript, becomes a problem. I’ve brought Leaflet and all of my plugins up to the latest versions, but I cannot run Leaflet 1.0 Beta 2 without causing problems for Leaflet PruneCluster and the my Google/Bing plugins.

I’ll have to either, a) figure out whether RequireJS will allow me to essential overwrite “L” with a different version of Leaflet for Demo 10, or b) build a standalone page for Demo 10.

Grrrr!

Color Gradient Lines: Begin with the End in Mind

I’ve been looking into what it would take to draw map lines with color gradients, like the red/blue line to the left. My assumption was that this would require generating raster map tiles (something I know little about), but this week Tom MacWrite pointed out the demo that he wrote for MapBox. That, in turn, led me to look for Leaflet plugins that support gradient colors.

The first one I found is Leaflet.MultiOptionsPolyline.  The screen shot below is from its demo page. It looks interesting, but it’s not quite the smooth gradient effect I was after.

A more promising Leaflet plugin I found is Leaflet.hotline, by iosSphere of Cologne, Germany. Below is a screen shot from its demo page — much closer to the effect that I was looking for. Leaflet Hotline was inspired by the Leaflet.heat plugin. That makes sense to me now since heat maps also have smooth color gradients.

Clearly, a polyline drawn with a color gradient is possible with Leaflet, so now let’s step back and begin with end in mind.

Objective

My objective is to convey information, like heart rate or pace, on top of the race path recorded by a GPS device. I’ll use pace for my demo. The map should show the runner where her pace was faster, and where it was slower.

The race path on the map should support interaction with other components on the page. For instance, it may not be enough to see speed alone. It would be nice to hover over the line on the map and see the corresponding point on an elevation chart. A slowdown may be the result of a steep incline.

Colors

The gradient colors need to convey additional information about the runner’s race without being distracting. The colors should not make the map harder to read, for example, blending into the background.  The Leaflet Hotline example used black outlines to solve that problem. Also, the colors should take color blindness into consideration.

Below is an example of line colors Google chose for the Bart Transit map. How do those work for red/green, or blue/yellow color blindness? Perhaps those shades are okay?

Awhile back I found an article, How The Rainbow Color Map Misleads, that explains why not to use rainbow colors. For more information see Rainbow Color Map (Still) Considered Harmful (Full discloser, I didn’t pay to download this report). There is a handy tool to help find alternative colors for your map called ColorBrewer. Another tool for picking gradient colors generally is uiGradients.

I stumbled upon this set of color-blind safe colors on Paul Tol’s web site. The shades of blue and green from Paul Tol’s site are are somewhat similar to the colors from Google’s Bart transit map above. I’ll try to incorporate some of these colors into my soon-to-come demo.

colourpalette_small

Color-blind Safe Palette from Paul Tol

Stay tuned for a demo at http://exploringspatial.com.

Privacy Geofencing

A question was posted yesterday on the Leaflet forum about doing geofencing with LeafletJS, so I posted this demo in response: http://exploringspatial.com/#demo/9.

Screen shot of privacy geofence demo

Privacy Geofence Demo

My demo scenario is that there is a user who likes to share her runs with friends on social media, but doesn’t want everybody to see exactly were she lives, so she creates a privacy geofence around her house. The starting and ending sections of her run polyline are hidden once they enter the geofenced area. If the middle section of the run passes through the geofenced area it remains visible.

Here is a shout out to Roadrunners of Kansas City. The geofence boundary in this demo is around Sport+Spine, home-base to many of the Roadrunner runs. Thanks, Coach Amy!

For my example I turned once again to the Leaflet-pip library, as I did for Demo7 and Demo 8. I use it a couple of different ways in this demo. First, I use it to locate runs for this demo whose start point falls within the demo’s privacy geofenced area. Second, when the user displays a run (by clicking the numbered buttons on the bottom of the page) I use Leaflet-pip again to test which points at the beginning and end of the run fall within the privacy geofence.

In the real world you would always do the privacy filtering server-side, using something like GeoTools or spatial database queries, so that only the public sections of the polyline are exposed in the GeoJSON. I did this demo on the browser-side because my site is an Amazon S3-hosted static web site, and also because the forum question was about doing geofencing with Leaflet.

The code for this demo can all be found in RightSideView.js. The render method does the following things:

  1. Creates the L.featureGroup, geoFence, and adds a L.polygon defining the geofence boundaries.
  2. Displays the geofence on the map.
  3. Loads the running log GeoJSON file from Demo 5.
  4. Uses leafletPip.pointInLayer to find runs that start in the geofenced area, and then adds a paging div to the bottom of the page to open each matching run.
  5. Loads the first run.

The interesting part of the code is in the onActivityFetched function. The “hidden” parts of the polyline are shown in gray on the map, so I needed to define three polylines: the hidden start, the visible middle, and the hidden end.

I loop through the polyline coordinates and add the points to the “fencedStart” array until I hit a point falling outside the geofenced area. After that, all points are added to a “middle” array.

Screen shot of the code to find the hidden start of the run.

Find the Hidden Start of the Run and the Middle

Next, I loop backwards over the points collected in the middle array looking for the points at the end of the run to be hidden.

Screen shot of the code to find the ending section of the run to be hidden.

Find the End Section of the Run to Hide

Finally, now that I have the points to hide at the start and the end of the run, I just need to find the points in between to draw on the map in red.

Screen shot of code to find the middle of the run to add to map.

Find the Visible Section of the Run to Display on Map

Go to the demo and click on a few of the runs to see how it works. Doing this demo I learned that “Leaflet.PIP” (point-in-polygon), only works on polygons (go figure). I started out trying to use a L.circle for the geofenced area. That didn’t work.

The other shortcoming of this demo is that sometimes the first point outside the privacy geofence is some distance away, causing the visible line to stop short. That could easily be fixed with point projection along the baring of the line between the two points that span the geofence boundary.

Happy Geofencing.

 

Browser-side Geospatial Maps

If you know many marathon runners, then you probably know somebody in the Fifty States Marathon Club, like my friend Jennifer. Here is a post from Jennifer on Facebook:

Map of states run

Jennifer’s 50-States Progress Report

What if users could simply use their electronic running log to generate progress maps on-the-fly? And, what if that map could be generated without increasing the load on expensive geospatial database servers?

That is exactly what I’m demonstrating today with Tom McWright‘s Leaflet-pip library. See it live here: http://exploringspatial.com/#demo/8. This demo reuses my personal running log GeoJSON file from demo 5. It combines my race data with a GeoJSON file containing shapes for the 50 states that I found at Frictionless Data.

The map flow is illustrated below. Keep in mind that I’m doing all of this with static files on my Amazon S3-hosted web site.

states_map_flow

First, RightSideView.js downloads the running log GeoJSON file and the States GeoJSON file. It passes the states GeoJSON feature collection to StatesMapLayerView.js which loads it into three maps: the mainland, Alaska, and Hawaii.

Next, RacesMapLayerView.js loads the running log GeoJSON feature collection into a Leaflet GeoJson Layer filtering by eventType = “Race” and totalMeters equals the selected race distance. As each point is added to the map, MapEventDispatcher.js is used to publish a RACE_ADDED event.

The RACE_ADDED event triggers the State Map Layer View to call the Leaflet-pip library to find the state polygon containing the race point and change that state’s background color from blue to red.

Image of code used to find the race start point.

Find State Containing Race Start Point

Above the map is the race selector. It uses the Map Event Dispatcher to fire the RACE_SELECTED event. The RACE_SELECTED event triggers the State Map Layer View to remove all state highlighting. It also triggers the Race Map Layer View to re-filter the running log GeoJSON file using the updated race distance. That, in turn, fires RACE_ADDED again, which re-triggers state highlighting.

That’s it. Pretty simple. The geospatial matching to produce this map, that might have been done, say, in Oracle Spatial, is successfully offloaded to the customer’s web browser instead. This leaves expensive servers to do the large-scale geospatial operations they do best.

 

Breaking Dependence on Vendor Map APIs with Leaflet

A colleague had a question about porting a map written using Bing Maps AJAX 6.2 to Bing Maps AJAX V7. The existing map shows coverage areas that, when highlighted, highlight a corresponding item in a price list. The problem was that the mouse events didn’t work the same in the new version. I’ve become a bit of a Leaflet evangelist these days, so I showed them how to do it using Leaflet and the Leaflet-pip library instead. You can see the demo, pictured below, at this URL: http://exploringspatial.com/#demo/7. It’s a fun little interactive map.

Screenshot of Shape Event Demo

Screenshot of Shape Event Demo

I grabbed a GeoJSON file of Kansas counties from CivicDashboards for the shapes to be highlighted. There was also a requirement that hovering the mouse above overlapping shapes highlight all of the overlapping shapes, not just the top shape, so I added several large circles to my map representing Kansas universities. The list of county names to the left of the map is meant to represent the price list on my colleague’s map.

Hovering the mouse over a name in the list of counties highlights the corresponding county shape on the map, and vice versa. Hovering over the university circles not only highlights the circle , but also the county shape underneath that circle.

The demo came together quickly since I was using Leaflet’s L.geoJson() layer for the county shapes, as I have done in several previous demos. There were just a couple of things that slowed me down: scrolling the list of county names to the highlighted name when hovering over a county shape on the map, and propagating the mouse events to all overlapping shapes.

Scrolling Long Lists

Automatically scrolling to a highlighted name should have been easy, but it took me a bit to get it right. I worked up a proof-of-concept in JSFiddle first. You can find the POC code here: http://jsfiddle.net/stevecmitchell/5y1c3xsh/.  The code in my map demo is even shorter than my proof-of-concept on JSFiddle.

Propagating Mouse Events

I used mouseover and mouseout event handlers on the county shapes and college circles to handle highlighting. The problem was that when the circle received the mouse event the county underneath the circle did not receive an event, so it never got highlighted.

I posted a question to the Leaflet forum and was sent a solution in JSFiddle by user “ghybs”, https://github.com/ghybs, that used the leaflet-pip plugin. The leaflet-pip plugin was written by Tom McWright of Mapbox. It, in turn, credits an algorithm documented at Rensselaer Polytechnic Institute of Troy, NY.

Long story short, I added a mousemove listener to the college circles on the map. When the mouse moves over a circle the mousemove event handler uses leaflet-pip to find the county shape containing the point of the mouse event and then explicitly fires the mouseover event of the matching county shape.

Using leaflet-pip.pointInLayer

Using leaflet-pip.pointInLayer

I did not take the time to implement the event propagation the other direction. When you hover over a county name in the list, which highlights a county shape on the map, the college circle above it is not highlighted. This could be done… but I’ve invested enough time in this demo, so I didn’t do that.

I love this plugin. It is useful and easy to use. This gave me an idea for my next demo which will use leaflet-pip to demonstrate a “50-State Marathon Club” style map. You’ve seen them before. People who are trying to run a half or full marathon in all 50-states, or all seven continents, use them to track their progress.

The bulk of the code for this demo can be found here:

https://github.com/smitchell/exploringspatial/blob/master/js/views/maps/CountiesMapLayerView.js


			

Web Hosting with Amazon S3 and Route 53

S3 logoDemo 6 went on the back burner while I re-hosted my site with S3, Route 53, and CloudFront. I found that adding 99,000 GeoJSON files into a child folder of an Intellij project is problematic. Indexing all those folders and files caused Intellij to become unresponsive, so I wanted to move my content elsewhere.

I decided to move my data files to Amazon S3 into a bucket named data.exploringspatial.com. That worked great. Publishing that many files to S3 was time consuming, but since they don’t change often I decided that was okay. I updated all the web site URLs and I was off to the races.

Next, since my web site is static, I decided to move it from Amazon EC2 on to S3 too. Moving the site to S3 frees up my EC2 instance for hosting a other interesting software, like ElasticSearch. There is no sense paying for more than one EC2 instance for a demo site.

The process was pretty painless. First, I created buckets for my data and my web sites:

S3
Next, I created a hosted zone using Route 53, mapping my URLs to my buckets.

Route53

I transferred my domain names from GoDaddy and Networks Solutions to Amazon Route 53.

domain

Finally, I switched my CDN from CloudFlare to AWS CloudFront.

cloudfront

That’s all it took. You’ll find plenty of instructions the Amazon AWS web site.

Now… back to my demos.