GeoJSON

This is an optional advanced topic on the GeoJSON data format, which those of you who are ahead might want to look at.

GeoJSON - introduction

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 JSON.parse() in standards-compliant browsers. We can then access the GeoJSON collection using JavaScript syntax. For example, in an AJAX callback:

function responseReceived(e)
{
    var geojsonData=JSON.parse(e.target.responseText);
    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]); 
}

GeoJSON from Leaflet

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. With a GeoJSON layer, different types of GeoJSON feature will be automatically added as a corresponding Leaflet layer type (marker, polyline or polygon). So GeoJSON points will be added as Leaflet markers, and GeoJSON polylines and polygons as Leaflet polylines and polygons. Here is how to do this

First you 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):

var geojsonLayer = L.geoJSON();

The marker (or polyline or polygon, depending on the type of GeoJSON feature) is passed in to this anonymous function via the layer parameter. The marker, polyline or polygon has a feature property representing the GeoJSON feature. Using the feature we can then access its geometry or properties from JavaScript, so that, for example, feature.properties.name represents the name property of the current feature.

The above code defines the GeoJSON layer and defines how popups will appear, based on the feature's properties. However, we also need to actually add the features to the GeoJSON layer. This is typically done in an AJAX callback function, from an AJAX call to a web service which provides data as GeoJSON.

You loop through your GeoJSON features, and add them to your layer. To do this you use the geojsonLayer.addData() method, for example:

function resultsReturned (e)
{
    var geojson = JSON.parse(e.target.responseText);
    for(var i=0; i<geojson.features.length; i++)
    {
         geojsonLayer.addData(geojson.features[i]);
    }
}

GeoJSON exercise

On Edward2, at https://edward2.solent.ac.uk/wad/restaurants.php is a web service which generates GeoJSON of all restaurants within a given area stored in the restaurants table in the dftitutorials database. (This data is taken from OpenStreetMap, data copyright OSM contributors, licenced under Open Database Licence).

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 restaurants 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:

https://edward2.solent.ac.uk/wad/restaurants.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. The following code retrieves these values into four variables: west, south, east and north:
    var west= map.getBounds().getSouthWest().lng;
    var south= map.getBounds().getSouthWest().lat;
    var east = map.getBounds().getNorthEast().lng;
    var 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 restaurants 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.

You specify an onEachFeature function when you create your L.GeoJSON object. The idea is that when a GeoJSON feature is added to the GeoJSON layer, the function specified in the onEachFeature property is called. Here is how this could be done: (the popup will remain hidden until the user clicks the feature)

var geojsonLayer = new L.GeoJSON(null,
                {onEachFeature: feature,layer =>
                    {
                        layer.bindPopup("Name=" + feature.properties.name);
                    }
                }
            );
Note that this is an arrow function, which you first encountered in the AJAX topic. The function is automatically supplied with two parameters, feature, the GeoJSON feature we are adding, and layer, the marker we are adding the feature too. So feature.properties will contain the properties of the feature. So, the effect of this code is to attach a popup to the layer (the marker) 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.addData(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();

Exercise 3: creating your own GeoJSON feed

On Edward2, under the dftitutorials database, there is a table called restaurants representing restaurants. It contains lat, lon, and name 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)