Leaflet part 4 - NieneB/webmapping_for_developers GitHub Wiki

GeoJSON

GeoJSON is the standard data type to create web maps with. You can add this data as another map layer. The geodata that we want to add will be a GeoJSON file. JSON stands for JavaScript Object Notation. GeoJSON is a format for encoding a variety of geographic data structures. It’s also fairly human readable if you open it in a text editor.

This is what a point feature looks like:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Dinagat Islands"
  }
}

:information_source: The GeoJSON file starts with defining its type and features. The type is a FeatureCollection, meaning it can contain multiple geometries of type, point, line and polygons combined.

:information_source: All the geometries are in the features array. The array starts with a square bracket [ and closes with ]. Here we only have one polygon in our features array.

:information_source: Per feature, there is a type, properties and the geometry. In the properties you are free to define your own attributes like a name, type, function ar any kind of data you have about the feature.

GeoJSON supports the following geometry types: Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon. Geometric objects with additional properties are Feature objects. Sets of features are contained by FeatureCollection objects.

It uses the World Geodetic System 1984, and units of decimal degrees.

The GeoJSON format specification can be found here and here.

Adding a simple GeoJSON to the map

Let's add this simple feature to our map. We can just put it in our code.

geojson.io is a nice website where you can draw and validate a GeoJSON object.

:arrow_forward: Go to geojson.io and draw a simple(!) polygon. Make it simple because every click is a set of extra coordinates and we don't want our data to become too big!

:arrow_forward: Copy your GeoJSON object in your code, above the map definition:

let myGeojson = {
 "type": "FeatureCollection",
  "features": [
    // ... 
  ]
}

:arrow_forward: Add the GeoJSON layer to your map. Place this after the map definition:

// ADD the geoJSON Layer
L.geoJSON(myGeojson).addTo(map);

Leaflet will give it a standard style.

Adding a WFS as GeoJSON

Let's add the neighborhoods of the municipality of Amsterdam to the map. The neighborhood, district and municipality statistics are available as a service at PDOK. The same as we added before as a WMS but now we will use the WFS! See https://www.pdok.nl/geo-services/-/article/cbs-wijken-en-buurten for all the services available.

:arrow_forward: Open the getCapabilities file of the WFS.

https://geodata.nationaalgeoregister.nl/wijkenbuurten2018/wfs?&request=GetCapabilities&service=WFS

:arrow_forward: Look at the Neighborhoods of 2018 data set. This is the FeatureType wijkenbuurten2018:cbs_buurten_2018

I always use Ctrl+F to search the XML file..

There are 3 FeatureTypes:

  • gemeenten2018
  • cbs_wijken_2018
  • cbs_buurten_2018

We will use the WFS to request a GeoJSON file.

:arrow_forward: See the outputFormat parameter for the possible output types.

Before requesting the GeoJSON we will first add an empty GeoJSON to our map.

:arrow_forward: Create a style and a GeoJSON:

 let myStyle = {
    "color": "#ff7800",
    "weight": 5,
    "opacity": 0.65
};

let serviceGeoJSON = L.geoJson(null, {
    style: myStyle
}).addTo(map);

:arrow_forward: Use the DescribeFeatureType to see all the attributes available to the feature service:

https://geodata.nationaalgeoregister.nl/wijkenbuurten2018/wfs?service=WFS&version=2.0.0&request=DescribeFeatureType&TypeName=wijkenbuurten2018:cbs_buurten_2018

:arrow_forward: Define the WFS request.

In order to keep the service quick we will make our request as small as possible. Never request more then you need! This will only make your map slow and you will loose the users interest..

let url = "https://geodata.nationaalgeoregister.nl/wijkenbuurten2018/wfs?service=WFS&version=2.0.0&"
+ "request=GetFeature&"
+ "outputFormat=application/json&"
+ "srsName=EPSG:4326&"
+ "geometryLengh=5&"
+ "TypeName=wijkenbuurten2018:cbs_buurten_2018&"
+ "propertyName=buurtnaam,aantal_inwoners,geom&"
+ "cql_filter=(water='NEE' AND gemeentenaam='Amsterdam')" 

We will use a xhr request. But feel free to use your own.

:arrow_forward: Parse the request response as a JSON object. And add it to our previously generated GeoJSON with addData

let xhr = new XMLHttpRequest()
xhr.open('GET', encodeURI(url))
xhr.onload = function () {
    if (xhr.status === 200) {
        console.log(xhr.responseText)
        serviceGeoJSON.addData(JSON.parse(xhr.responseText))
        
    } else {
        alert('Request failed.  Returned status of ' + xhr.status)
    }
}
xhr.send()

:arrow_forward: Have a look at the file in your console log. Do you see how it is build up? Did we receive the right properties?

:bangbang: slow?!

Yes, this kind of WFS request is rather slow.. Making your request as small as possible is therefore the best we can do. Even better would be if we could reduce the coordinate precision, to make our file smaller. But this is not supported by the WFS standard. An option is to host the GeoJSON file yourselves and edit it beforehand in a GIS software program to make it smaller.

:bangbang: Good news! Development WFS 3.0 or OGC API Features

Good news, the WFS 3.0 standard is on the way. This defines a standard for a Rest API feature service. Bridging the gap between the cumbersome gis services and the web. It will enable web developers to quickly request geo features as GeoJSON. Have a look at the https://github.com/opengeospatial/ogcapi-features for more information.

Data driven styling

In order to make our style dependent on the data we have to create a style function instead of an object.

:arrow_forward: Create a color scale function:

function getColor(d) {
 return d > 6000 ? '#800026' :
   d > 5000 ? '#BD0026' :
   d > 4000 ? '#E31A1C' :
   d > 3000 ? '#FC4E2A' :
   d > 2000 ? '#FD8D3C' :
   d > 1000 ? '#FEB24C' :
   d > 500 ? '#FED976' :
   '#FFEDA0';
}

:arrow_forward: Create a style function:

function style(feature) {
  return {
      fillColor: getColor(feature.properties.aantal_inwoners),
      weight: 2,
      opacity: 1,
      color: 'white',
      dashArray: '3',
      fillOpacity: 0.7
  };
}

:arrow_forward: Use the style function instead of the simple style object:

let serviceGeoJSON = L.geoJson(null, {
    style: style
}).addTo(map);

Popup

let serviceGeoJSON = L.geoJson(null, {
    style: style,
    onEachFeature: onEachFeature
}).bindPopup(function (layer) {
    return layer.feature.properties.buurtnaam
})
    .addTo(map);

Highlight Feature

:arrow_forward: Create functions for mouseover actions:

// Mouse interaction
function highlightFeature(e) {
    var layer = e.target;
    layer.setStyle({
        weight: 3,
        color: '#eee000',
    });
    if (!L.Browser.ie && !L.Browser.opera) {
        layer.bringToFront();
    }
}
// Function for reseting highlight
function resetHighlight(e) {
    geojson.resetStyle(e.target);
}
// Function to zoom to feature
function zoomToFeature(e) {
    map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
    layer.on({
        mouseover: highlightFeature,
        mouseout: resetHighlight,
        click: zoomToFeature
    });
}

:arrow_forward: Add the onEachFeature functions:

// WFS
var geojson = L.geoJson(null,
    {
        style: getStyle,
        onEachFeature: onEachFeature
    }
).bindPopup(function (layer) {
        return layer.feature.properties.buurtnaam
}).addTo(map);

:arrow_right: To MapboxGL part 1.