Mapping with Vector Tiles

Query Features Outside the Viewport

The goal of my original post on client-side web mapping for large datasets was to create an inexpensive, robust county-wide parcel viewer web application. The base functionality would include the ability to both identify and search the parcel data. The result of this effort was the Open Parcel Viewer. That project consists of a Leaflet map with Leaflet.VectorGrid cutting GeoJSON into vector-tiles on-the-fly. The app is performant, with the drawback being a potentially large file loaded directly into the browser. This method works well for small datasets, but breaks down when the size and complexity of the data exceeds device or network capabilities. For example, load time for a typical 70k polygon county parcel map on mobile might exceed 30 seconds.

To get around this dilemma, one option is to create the vector tiles first, then serve these tiles out via static pbf or mvt files (see vtile), via a simple mbtiles server, or even host the data directly on Mapbox. These tiles can then be viewed with virtually any web mapping library such as Mapbox GL JS. However, creating this type of vector tile cache solves one problem but creates another: since only the data within the current viewport is loaded, data outside the viewport is unknown to the client and therefore not searchable. So how do we go about searching for data that exists outside the viewport, especially if we are unwilling to host a dedicated server-side geocoding application for our data?

Data outside the viewport is unknown to the client and therefore not searchable, which is one of the two key components needed for this application.

The answer to this is a simple Node script that minimizes the GeoJSON data into a JSON file with only those fields we need to search. This is accomplished using the .map() function on the GeoJSON features object. Utilizing the turf.center() function we can also create a centroid that is used to pan the map to the search result. The original data would ideally contain a unique ID field in order for map.queryRenderedFeatures() in Mapbox GL JS to work properly. If the dataset does not have a unique ID field, this can easily be created in the Node script before cutting out data into tiles, making sure to return this ID to our searchable JSON object.

var geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id":"0ABCDE",
"name": "point",
"label": "nowhere",
"type":"nothing"
},
"geometry": {
"type": "Point",
"coordinates": [
105.77636718749999,
69.76375692223178
]
}
}
]
}

var json = (geojson.features).map(function(feature) {
var keys = ["id","label","name"];
var obj = {};
for (prop in feature.properties) {
if (keys.includes(prop)) {
obj[prop] = feature.properties[prop]
}
}
return obj
});

There are a wide variety of JSON search plugins, and since I use Jekyll for my website and since I am familiar with Jekyll I decided to use the simple-jekyll-search plugin for this project. This plugin has the advantage of having a built-in search results preview. This is shown in the image below.

One final problem exists before we can call this project a success, and that is knowing when the Mapbox GL map has all loaded and rendered all the tiles after zooming to the search result. This is necessary in order to highlight the feature and access any additional attributes stored in the vector tile. One method to check for loaded tiles is to set an interval function on the sourcedataloading event, then check to see if this event still fired x seconds later. Once this event is no longer fired, then all the tiles have loaded. Another, more simpler method is to simply query the features and check if anything is returned, and if not query them again in 500ms or so, and do this for maybe five seconds. If nothing is returned simply send an alert to the client.

As long as the searchable JSON file is small enough, this method allows for easily searching data beyond the viewport in Mapbox GL JS. Once the size of the data grows, this client-side operation will have to be moved server-side and replaced with some type of geocoder instance.

Newer Post
Intro to Turf JS
Older Post
Data-Driven Styling, 3D Polygons & Vector Tiles with Mapbox GL JS

Related Posts