# Creating viewsheds with Mapbox GL and Turf.js

I was super impressed when I saw cartographer John Nelson's tweet about an ESRI project called the Campus Blue Lights App. The ArcGIS Online application enables a user to add, remove and position Blue Lights (emergency telephones) in the context of a campus and evaluate the visibility of each beacon. The detailed write-up inspired me to try and recreate the same functionality using Mapbox GL and TurfJS. I have attempted to explain the process below, but the entire code is also available here.

The first step in creating the viewsheds was to figure out a way to create an array of points that completed a full ring around a centre point. The first attempt looked something like this:

for (i = 0; i < 360; i++) { var x = centerX + radius * Math.cos(2 * Math.PI * i / 360); var y = centerY + radius * Math.sin(2 * Math.PI * i / 360); }

This successfully created 360 points, but as you moved away from the equator the resulting buffer became more skewed due to the Web Mercator projection. To account for this, I needed to create geodesic lines. Luckily, I found the perfect solution on Turf's Github page by Mathieu Albrespy. His code accounts for the curvature of the Earth.

I then used the following Turf functions to convert the 360 points into LinesStrings and detect whether any of them intersected any of the building polygons.

var matchLine = turf.lineString([[centerX, centerY], [x, y]]); for (n = 0; n < buildings.features.length; n++) { var polyInfo = [buildings.features[n].geometry.coordinates[0]]; var matchPoly = turf.polygon(polyInfo); intersection = turf.lineIntersect(matchLine, matchPoly); }

Turf's 'intersection' function returns a list of all intersecting points, which in this case, provided all of the points where one of the LineStrings crossed a building polygon. If an intersection was detected I then pushed the returned coordinate data to an array.

intersectionArray.push(intersection.features);

If there were multiple intersections I then used Turf's 'nearest' function to determine which intersection point was closest to the centre (targetPoint).

var targetPoint = turf.point([centerX, centerY]); var points = turf.featureCollection(pointsArray); nearest = turf.nearestPoint(targetPoint, points);

The nearest coordinate information was then pushed to the geojson containing the data for the viewshed polygon. If there was no intersection, the original x and y coordinates were pushed.

if (conflictCheck == true) { var targetPoint = turf.point([centerX, centerY]); var points = turf.featureCollection(pointsArray); nearest = turf.nearestPoint(targetPoint, points); geojsonPolyArray.push([intersectCon[0], intersectCon[1]]); } else { geojsonPolyArray.push([x, y]); }

The resulting geojsonPolyArray was then placed inside of the data source of the viewshed layer. Once the geojsonPolyArray was built I updated the source of the viewshed layer.

function updatePoly() { geojsonPolyArray = []; geojsonPolyCoords = []; geojsonPoly = { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": geojsonPolyCoords } }; evalPoly(); //creates linestrings, detects intersections, and creates geojsonPolyArray map.getSource('polygon').setData(geojsonPoly); }

Below is the finished product. I added a bunch of cosmetic things, but I hope the code can help someone else create their own viewsheds. Eventually I'm hoping to integrate a z factor into this using Mapbox's fill-extrusion capabilities. If anyone has questions or wants to help develop a better version of this hit me up on twitter.

Drag and drop the marker