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 where 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 website, 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 backward 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 bearing of the line between the two points that span the geofence boundary.

Happy Geofencing.

 

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 in 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


			

How to Display Feature Collection GeoJson with Leaflet’s Marker Clusters

I enjoyed playing with Leaflet’s marker cluster plugin this weekend while writing a new demo, Demo 5, that shows how to load feature collection GeoJson into a Leaflet map layer.

Leaflet Marker ClusterIt was surprisingly easy to setup. First, I updated my GeoJson utility project to create feature collection GeoJson loaded with 768 of my runs from Garmin Connect. The features in the feature collection are summaries. They contain the same properties as the activity detail GeoJson from Demo 4, except that they use starting latitude/longitude instead of a polyline.   Next, I generated individual feature GeoJson files for each run with the full polyline. Finally, I added the new GeoJson files to my website at http://www.exploringspatial.com/activity/.

A Backbone Collection, Activities.js, uses AJAX to fetch the activity summaries. The collection is passed into the ActivitiesMapLayerView.js. The code in its render function is simple:

render: function() {
    var _self = this;
    geoJsonLayer = L.geoJson(this.collection.toJSON(),{
        onEachFeature: _self.onEachFeature
    });
    this.map.fitBounds(geojson.getBounds());
    this.activitiesLayer = L.markerClusterGroup();
    this.activitiesLayer.addLayer(geoJsonLayer);
    this.map.addLayer(this.activitiesLayer);
    this.map.on('popupopen', function(event) {_self.onPopupOpen(event);});
    $('.returnToSearch').on('click', '.returnTrigger', function(event){
        _self.onReturnToSearch(event)
      });
}
  1. L.geoJson – Create a map layer by passing in the activity collection JSON along with a onEachFeature function (see below).
  2. L.markerClusterGroup – Create a marker cluster map layer.
  3. addLayer – Add the GeoJson layer to the marker cluster group.
  4. addLayer – Add the marker cluster group to the map.

The last two lines of code bind a listener to the popupopen event, and add a listener for the activity detail page event that returns to the main page.

The onEachFeature function is used to bind a popup to each feature marker on the map. It does some date and number formatting and then passes HTML into layer.bindPopup.

onEachFeature: function(feature, layer) {
    var date = new Date(feature.properties.startTime);
    var triggerId = feature.properties.activityId;
    var msg = [];
    msg.push(feature.properties.name);
    msg.push('Start: ' + date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + '');
    var dist = Math.round((feature.properties.totalMeters * 0.000621371)*100)/100;
    msg.push('Dist: ' + dist + ' mi');
    msg.push('Go to Activity');
    layer.bindPopup(msg.join(''), {maxWidth: 200});
},

The interesting bit happens in the onOpenActivity and renderActivity functions when a user clicks the “Go to Activity” anchor in the popup. The onOpenActivity function instantiates an Activity Backbone model and invokes its fetch function to go get the polyline for the selected activity id. The renderActivity function is passed in the success property of the AJAX call.

onOpenActivity: function(event, popup) {
    var location = popup._latlng;
    this.map.closePopup(popup);
    // Capture the current center and zoom to restore map later
    this.originalCenter = this.map.getCenter();
    this.originalZoom = this.map.getZoom();
    this.activity = new Activity({activityId: event.target.id});
    var _this = this;
    this.activity.fetch({
        success: function () {
        _this.renderActivity();
    }
});
        },

When the polyline is successfully returned from the server the renderActivity function creates a new activity map layer to swap with the marker cluster map layer. A feature GeoJson map layer is added to the map, along with the start and end markers.

renderActivity: function() {
    $('.returnToSearch').show();
    if (this.map.hasLayer(this.activitiesLayer)) {
        this.map.removeLayer(this.activitiesLayer);
    }
    var props = this.activity.get('properties');
    this.map.fitBounds([
        [props.get('minLat'), props.get('minLon')],
        [props.get('maxLat'), props.get('maxLon')]
    ]);
    var style = {
        color: '#FF0000',
        weight: 3,
        opacity: 0.6
    };
    this.activityLayer = L.geoJson(this.activity.toJSON(), 
        {style: style}).addTo(this.map);
    var polyline = this.activity.get('geometry').get('coordinates');
    var startPoint = polyline[0];
    var endPoint = polyline[polyline.length - 1];
    this.activityStart = L.marker([startPoint[1], startPoint[0]], 
        {icon: this.startIcon}).addTo(this.map);
    this.activityEnd = L.marker([endPoint[1], endPoint[0]], 
        {icon: this.endIcon}).addTo(this.map);
},

Once the user clicks Back to Search, this is the block of code that restores the marker cluster layer. The activity feature layer, start, and end markers are removed (not shown), and then the marker cluster layer is added back to the map and the original center and zoom are restored.

 this.map.addLayer(this.activitiesLayer);
 if (this.originalCenter != null && this.originalZoom != null) {
    this.map.setView(this.originalCenter, this.originalZoom, {animate: true});
    this.originalCenter = null;
    this.originalZoom = null;
 }

That’s it! Be sure to go to the demo, http://www.exploringspatial.com/#demo/5, and click around.

Help Wanted: QGIS Mentor

quantum

HELP WANTED: QGIS Mentor

My goal was simple, generate a heat map in QGIS that looked similar to the heat map that I generated using Google Fusion Tables with just a couple of clicks, or similar to what was eventually implemented on Garmin Connect.

This is what my test data set of 350,000 GPS points looks like in QGIS 1.9.0 on a PostGIS layer:

PostGIS Layer in Quantum GIS 1.9.0

PostGIS Layer in Quantum GIS 1.9.0

Several attempts resulted in either no visible heat map (above) or a large blobbish, weather map looking effect, not what I wanted.

I talked to the cartographer who took my Google Fusion Table prototype from concept to production. His process bears little resemblances to my prototype. Instead of a loose collection of GPS points, he extracted running and cycling activity polylines. Instead of using a heat map plugin he wrote custom C++ code to convert the vector data into raster data.

So, I’ll switch from loose points to polylines too. Oracle Spatial and Graph has a SDO_UTIL.FROM_GML311GEOMETRY function to export the polylines in GML format. Likewise, PostGIS has a ST_GeomFromGML to import GML data into a geometry. I’ll work on creating a data set of 20,000 – 30,000 polylines.

I sure could use a QGIS mentor to answer my basic questions and to help me avoid making wrong turns. It might be time to reach out some cartographers in other departments at work.  I also plan on reading “Learning QGIS 2.0,” by Anita Grase, when that comes out later this month. I guess Anita can be my virtual mentor.

Exploring Leaflet Raster Classes

leaflet

Below is the class diagram for the Leaflet raster classes. As I prepared this diagram I realized it did not go far enough for me since I don’t have experience working the raster images. I’m going to put my exploration of Leaflet classes on hold and take a detour into the world of raster graphics.

Leaflet Raster Classes

Leaflet Raster Classes

I found several posts on Google map tilesBing map tiles, and OSM map tiles. The article on OSM map tiles referenced the Slippy Map Tile Application, and Mapnik, an open source tool for rendering maps.

If I start with Mapnik I need some raster tiles to render, don’t I? This would be the perfect time to continue my post from June about working with satellite images with TileMill. In that post I installed the following tools on my computer:

  1. GDAL, a low-level GIS toolkit
  2. libgeotiff, to work with geotags
  3. ImageMagick, an image processing package
  4. TileMill, an open-source mapmaking app from Mapbox

For my next post, I’ll pick up where I left off and follow the article Processing Landsat 8 Using Open-Source Tools by Charlie Loyd. Then perhaps I’ll look at GMap Creator and MapTiler. I’m open to suggestions for good raster learning resources.

When I’m done I’ll revisit this Leaflet raster class diagram again.

Penn State Geospatial Revolution MOOC on Coursera

coursea

This week I completed the 5-week MOOC, Maps and the Geospatial Revolution, taught by Dr. Anthony Robinson of Penn State on the site Coursera. Over 47,000 students enrolled and over 35,000 students participated, making it the largest GIS course ever taught. I found the course to be a good overall survey of the world of GIS (geographic information systems). I recommend it to anybody wanting to learn about GIS or who wants to see what it’s like to take a MOOC (massive open online course). Below is my brief overview which only skims the surface of what we covered.

In the first week, Dr. Robinson illustrated how geospatial technology has revolutionized the way we navigate, make decisions using geography, and share stories using geographic references, like geotagging and geocoding.

The second week covered reference maps and thematic maps. We saw thematic maps showcasing geographic observations, including choropleth maps, dot mapping, proportional symbol mapping, and isoline maps. The concept of spatial autocorrelation  was introduced (the measure of similarity of observations that are close to one another), as well as Tobler’s first law of geography:

Everything is related to everything else, but near things are more related than distant things.

A cool project highlighted in week two was Ushahidi, a non-profit organization that built a platform for volunteered geographic information in disaster areas. Ushahidi was instrumental in relief efforts in Haiti where online maps were very poor at the time of the earthquake.

Week three introduced GNSS (global navigation satellite systems) ranging from America’s GPS, GLONASS in Russia, to the E.U. Galileo system. We also covered the vector and raster spatial data types, as well as remote sensing, LIDAR, spatial metadata, and ArcGIS Online.

Week four discussed map overlays, first introduced in Ian McHarg’s 1969 book, Design with Nature.  A video explained how Dr. John Snow’s map of the cholera outbreak in 1854, Soho, London, was used to trace the cholera outbreak to a bad well. That was the first major use of cluster detection. We learned that spatial correlation is not causation, and we learned about the MAUP (modifiable areal unit problem), where scale effects how geospatial observations are visualized. We covered how important normalization is to avoid skewing maps by population density. To normalize data you calculate the rates of occurrence as a proportion of the overall population.

Finally, week five covered the art of map making, which starts by clearly understanding the audience, target format, and specific purpose of the map. It’s difficult to give a concise summary, so I’ll just say the lesson taught that it is important to use the right thematic map for the type of data (nominal, ordinal, or interval/ratio data). On the topic of color, we covered sequential colors, diverging colors, and qualitative colors. We then explored data classifications: equal interval classification, quantile classification, and natural break classification.

The final assignment was a map of your choosing. I chose to use to running and cycling data that I extracted to create a surface analysis map. The data has some serious flaws (a triathlon at Clinton Lake skewed my data), but it was adequate for my proposes.

lesson5

I’m already looking for other MOOCs of interest to me that are offered by Corsera.

Installing MapBox TileMill on Ubuntu

tilemill_logo

Preparing to Work with Satellite Imagery

Time to get out of my comfort zone. I’m a Java web developer, not a GIS imagery specialist, but when I saw the post Processing Landsat 8 Using Open-Source Tools by Charlie Loyd, I just had to give it a try. Charlie’s post is a step by step tutorial on how to manipulate Landsat 8 satellite images.

There are four prerequisites:

  1. GDAL, a low-level GIS toolkit
  2. libgeotiff, to work with geotags
  3. ImageMagick, an image processing package
  4. TileMill, an open-source mapmaking app from Mapbox

This post walks through the installation of these four tools.

1. Install GDAL

I followed this install-gdal script, except that I executed the commands individually so I knew what the script was doing.

$ sudo apt-get -y install g++
$ svn checkout https://svn.osgeo.org/gdal/trunk/gdal gdal
$ cd gdal
$ ./configure
$ sudo make install
...long build process...
libtool: install: /home/smitchell/gdal/install-sh -c .libs/gdalbuildvrt /usr/local/bin/gdalbuildvrt
/bin/bash /home/smitchell/gdal/libtool --mode=install  /home/smitchell/gdal/install-sh -c gdal-config-inst /usr/local/bin/gdal-config
libtool: install: /home/smitchell/gdal/install-sh -c gdal-config-inst /usr/local/bin/gdal-config
make[1]: Leaving directory `/home/smitchell/gdal/apps'
for f in LICENSE.TXT data/*.* ; do /home/smitchell/gdal/install-sh -c -m 0644 $f /usr/local/share/gdal ; done
/bin/bash /home/smitchell/gdal/libtool --mode=finish --silent /usr/local/lib

After GDAL is installed you need to change your LD_LIBRARY_PATH. Edit $HOME/.bashrc and add the following line:

LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH; export LD_LIBRARY_PATH

2. Install libgeotiff

Libgeotiff was the only tool that I found in the Ubuntu Software Center, although I wasn’t sure which one to install since I’m running 64-bit Ubuntu. I went with libgeotiff2.

libgeotiff

3. Install Imagemagick

Install Imagemagick and imagick:

$ sudo apt-get install imagemagick php5-imagick

4. Install TileMill.

Finally, we are ready to install TileMill.

sudo add-apt-repository ppa:developmentseed/mapbox
sudo apt-get update
sudo apt-get install tilemill libmapnik nodejs

Once installed TileMill can be started from the launcher.

linux-install-4

That’s it, you’re done. TileMill should now start as shown below:

tilemill

For my next post, I will grab images of interest to me and follow along with Charlie’s tutorial.