Removing intersection of GeoJSON Multipolygons with Mapbox and Turf.js
Turf.js provides a lot of geospatial functions that use GeoJSON (a standard format for geographical data in JavaScript Object Notation) that allow you to easily manipulate GIS data. In this post, I’ll show you how to use Turf.js functions to remove the overlap of multiple multipolygons so that you’re left with one multipolygon that doesn’t overlap any others.
I begin by assuming you’ve got a Mapbox MultiPolygon that you’re interested in, and an array of other MultiPolygons you want to “subtract”.
We’re going to use turf.erase (now seems to be turf.difference) to subtract individual polygons. To do this, we need find all the polygons in each multipolygon (of our list of multipolygons we want to subtract) that touch any polygon in our main multipolygon. We can use turf.intersect for this, and check that the function returns something non-null.
Let’s assume that 'featureGroup' is your Mapbox FeatureGroup object (a FeatureGroup containing only MultiPolygons in this case), and 'target' is the MultiPolygon you want to “shrink”.
We want to extract an array of all the polygons inside 'featureGroup' that aren’t a part of 'target'. The following code snippet generates this array, labelled 'everyOtherPolygon'.
var everyOtherPolygon = [];
featureGroup.eachLayer(function(l) {
if (l._leaflet_id !== target._leaflet_id) {
var geojson = l.toGeoJSON();
// For each polygon in this multipolygon layer
for (var i=0; i<geojson.geometry.coordinates.length; i++) {
var feat={'type':'Polygon','coordinates':geojson.geometry.coordinates[i]};
everyOtherPolygon.push(feat);
}
}
});
Now, we need an array of all the polygons inside 'target' that we want to change. The following code snippet creates this array, labelled 'thisPolygons'.
var thisPolygons = [];
var geojson = target.toGeoJSON();
for (var i=0; i<geojson.geometry.coordinates.length; i++) {
var feat={'type':'Polygon','coordinates':geojson.geometry.coordinates[i]};
thisPolygons.push(feat);
}
The main part of this algorithm is contained in the next JavaScript snippet. First, we check that we need to execute this section of the algorithm (i.e. that both 'everyOtherPolygon' and 'thisPolygons' are both non-empty). Next, we iterate through every element of 'thisPolygons' for each element of 'everyOtherPolygon'. If there is a polygon in 'thisPolygons' that intersects our current polygon, then we need to use 'turf.erase' to calculate what needs to be removed. We then combine all the polygons in 'target' back together, and save it back to our Mapbox 'target' object.
var changedAnything = false;
if (everyOtherPolygon.length > 0 && thisPolygons.length > 0) {
for (var i=0; i<everyOtherPolygon.length; i++) {
var currentPolygon = everyOtherPolygon[i];
for (var j=0; j<thisPolygons.length; j++) {
if (turf.intersect(currentPolygon, thisPolygons[j])) {
var eraseResult = turf.erase(thisPolygons[j], currentPolygon);
thisPolygons[j] = eraseResult;
changedAnything = true;
}
}
}
if (changedAnything) {
var fc = turf.featurecollection(thisPolygons);
var combined = turf.combine(fc);
target.setLatLngs(flipMultiPolyLatLng(combined.geometry.coordinates));
}
}
You may notice I used a function called 'flipMultiPolyLatLng'. This is because turf seemed to return geometry coordinates in '[Longitude, Latitude]' form (rather than the other way around). This functions just iterates through all the coordinate pairs and flips them - returning a new array.
Edit: I've found that turf has this function built in called turf.flip. Thus you can use that in place of 'flipMultiPolyLatLng'.
I’ve found this code to work in a non-performance-critical environment, so if you’re dealing with huge GeoJSON features, you may need to make certain optimizations for performant code.
If you enjoyed this post, please check out my other blog posts.