Further Web Mapping

Introduction

Today we will look at some additional web mapping topics, specifically:

GeoJSON

GeoJSON is a relatively new format for representing geographical data. As the name implies, it is based on JSON. It is typically supplied to clients by a web service; for instance, we might have a web service which supplies the location of points of interest close to a given latitude and longitude in GeoJSON format. Because GeoJSON is JSON based, it is easily parsed by JavaScript-based clients.

The GeoJSON format

GeoJSON consists of a series of objects. These are:

Here is an example of some GeoJSON.

{
    type: "FeatureCollection":
    features:
    [
        {
            type: "Feature",
            geometry:
            {
                type: "Point",
                coordinates: [-1, 51]    
            },
            properties:
            {
                featureClass: "pub",
                name: "The Red Lion"
            }    
        },
        
        {
            type: "Feature",
            geometry:
            {
                type: "Point",
                coordinates: [-0.9, 51.1]
            },
            properties:
            {
                featureClass: "restaurant",
                name: "Sams Burger Joint"
            }
        },

        {
            type: "Feature",
            geometry:
            {
                type: "LineString",
                coordinates: [
                    [-1, 51],
                    [-1.01, 50.99],
                    [-1.01, 50.98],
                    [-1.02, 50.97],
                    [-1.04, 50.96]
                        ]
            },
            properties:
            {
                featureClass: "main road",
                number: "A987",
                name: "High Street"
            }
        }
    ]
}
Note how this GeoJSON consists of a FeatureCollection. The FeatureCollection in turn contains an array of Feature objects, each of which contains three fields:

Interpreting GeoJSON

Interpreting GeoJSON is the same as interpreting any other type of JSON. A client would typically send an AJAX request to a web service supplying GeoJSON, and then evaluate the GeoJSON returned to load it into a JavaScript variable, e.g. with evalJSON() within Prototype. We can then access the GeoJSON collection using JavaScript syntax. For example, in an AJAX callback:

function responseReceived(xmlHTTP)
{
    var geojsonData=xmlHTTP.responseText.evalJSON();
    alert(geojsonData.features[0].properties.name); // name of 1st feature

    //type of geometry of 1st feature
    alert(geojsonData.features[0].geometry.type);

    //longitude of 1st feature, assuming it's a point 
    alert(geojsonData.features[0].geometry.coordinates[0]); 

    //latitude of 1st feature, assuming it's a point 
    alert(geojsonData.features[0].geometry.coordinates[1]); 

    // longitude and latitude of 1st point of 3rd feature, assuming it's
    // a LineString
    alert(geojsonData.features[2].geometry.coordinates[0][0]); 
    alert(geojsonData.features[2].geometry.coordinates[0][1]); 
}

If we are using Leaflet, however, it's easier than that. Leaflet comes with GeoJSON parsing built-in. Within Leaflet, you can create a GeoJSON Layer. Here is an example of how you would handle GeoJSON from Leaflet. First you would add a GeoJSON layer in your init() function (note that geojsonLayer would need to be a global variable, declared outside of any function, so that the AJAX parsing function can access it):

geojsonLayer = new L.GeoJSON();
map.addLayer(geojsonLayer);
Then, in your AJAX callback function, you simply do, for example:
function resultsReturned (xmlHTTP)
{
    var geojson = xmlHTTP.responseText.evalJSON();
    for(var i=0; i<geojson.features.length; i++)
    {
        geojsonLayer.addGeoJSON(geojson.features[i]);
    }
}

Exercise 1

On Edward, at http://edward/ewt/poi.php is a web service which generates GeoJSON of all features within a given area stored in the poi table in the dftitutorials database. (This data is taken from OpenStreetMap, data copyright OSM contributors, licenced under Creative Commons Attribution-ShareAlike Licence 2.0).

This takes one query string parameter, bbox (a bounding box). This consists of four values separated by commas: the western, southern, eastern and northern bounds of the area to query respectively. So if bbox is -1,51,0,52 for instance, only points of interest between longitude -1 and longitude 0 (1 West and 0) and latitude 51 North and 52 North will be returned. To call the script and instruct it to generate GeoJSON, use:

http://edward/ewt/poi.php?bbox=west,south,east,north&format=geojson

  1. Connect your Leaflet map from last time to this web service. In your init() function, make an AJAX request to the web service; always supply the bounding box -1.5, 50.8, -1.3, 50.9 (this is the local area). In your AJAX callback, add each feature to the GeoJSON layer as shown above.
  2. In Leaflet, you can get the current bounds of the visible area with the getBounds() method of L.Map. This gives you an L.LatLngBounds object which you can then query with the getSouthWest() and getNorthEast() methods to obtain the SW and NE corners of the bounds. Each of these is an L.LatLng object from which you can obtain the latitude and longitude. For example:
    alert('Bounds: west=' + map.getBounds().getSouthWest().lng + 
            ' south=' + map.getBounds().getSouthWest().lat + 
            ' east=' + map.getBounds().getNorthEast().lng + 
            ' north=' + map.getBounds().getNorthEast().lat );
    
    You can also detect when a user stops dragging the map by reacting to the dragend event, eg:
    map.on("dragend", onDragEnd); 
    
    where onDragEnd would be a function to handle a drag end event.
    Use this to load all points of interest within the currently-displayed area of the map when the user finishes dragging it.

Adding popups to GeoJSON-derived features

What would be useful is to display some information about the feature when the user clicks the marker. This can be approached via the featureparse event. The idea is that when a GeoJSON feature is added to the GeoJSON layer, a featureparse event is generated. We can write code to react to the featureparse event, so that, for example, we can attach a popup containing information about the feature. (The popup will remain hidden until the user clicks the feature). Here is how this could be done:

geojsonLayer.on("featureparse", function(e)
            {
                e.layer.bindPopup("Name=" + e.properties.name);
            }
    );
Note that this is an anonymous function, which you first encountered in the jQuery topic. The variable e, automatically supplied to the function, contains the GeoJSON feature being added, which we can query to obtain the properties, as shown here. What the function is therefore doing is attaching a popup to the layer associated with the feature, containing the feature's name.

Exercise 2

  1. Add popups to your features from the previous exercise, containing the feature's name.
  2. Advanced, for students comfortable with programming only: One problem with the approach so far is that markers are added every time the user drags to a new area, even if the marker has already been added when a user previously visited that area. You can get round this by creating an array of references to each feature, using the feature's ID as the array index (in JavaScript you can use non-consecutive array indices), and only adding the marker if there is not already a marker with that index in the array. The ID of each feature is contained within the GeoJSON. You would use code of this form; here, the variable indexedFeatures references each feature by ID.
    for(var i=0; i<geojson.features.length; i++)
    {
        var id=geojson.features[i].properties.id;
        if (!indexedFeatures[id])
        {
            indexedFeatures[id] = geojson.features[i]; 
            geojsonLayer.addGeoJSON(geojson.features[i]);
        }
    }
    
    Implement this. The GeoJSON contains a record of each feature's ID. You will need to make the array global, so that any function can access it, and create memory for the array in your init() function with:
    indexedFeatures=new Array();

Advanced exercise: creating your own GeoJSON feed

On Edward, under the dftitutorials database, there is a table called poi representing points of interest. It contains lat, lon, name and type columns.

  1. Write a script which generates a GeoJSON feed from this table. It should take one query string parameter, bbox, containing the bounds of the area to generate the feed from, as a comma separated list : west,south, east, north. You can extract the individual variables from this parameter using the following PHP statement:
    list($w,$s,$e,$n) = explode(",", $_GET["bbox"]);
    This "explodes" the bbox variable into an array (the comma is the separator character), and list() then extracts the four elements of the array into the variables $w, $s, $e and $n.
  2. Connect to this feed from your Leaflet map (this should simply be a case of switching the URL)