Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
183 lines (143 sloc) 8.71 KB

Turf.js has become an indispensible part of my Node.js workflow over the last couple years. Turf brings an unparalleled level of sophistication to geospatial data, I haven't seen any library in any other language that makes working with GeoJSON quite as easy. Turf and Node.js also work nicely with MongoDB, so you can leverage MongoDB to handle GeoJSON data that's too big to fit in memory and then do additional processing in Node.js with Turf. Much like how numpy and scipy made Python a go-to language for number crunching, I think Turf makes Node.js the go-to for geospatial.

Geospatial is hard, and even massive companies whose businesses are built on geospatial data don't always do it well, like Uber. Below is Uber's polygon describing what parts of the San Francisco Bay Area are not eligible for flat fare rides. Clearly a candidate for a MultiPolygon but odds are some part of Uber's stack doesn't support it.

Which Points are Within a Polygon?

GeoJSON has several fundamental data structures, but this article will only consider features, points, lines, and polygons. A point is a single latitude and longitude coordinate pair that represents a single point on the Earth. An example GeoJSON point is shown below.

{ type: 'Point', coordinates: [ -122.95, 50.12 ] } }

A polygon is a list of points defining a shape on a map. Below is a sample GeoJSON polygon that approximately represents the US state of Colorado.

{ type: 'Polygon',
  coordinates:
   [ [ [ -109, 41 ],
       [ -102, 41 ],
       [ -102, 37 ],
       [ -109, 37 ],
       [ -109, 41 ] ] ] }

A line is a list of coordinates representing a line on the map. For example, below is a GeoJSON line that represents the boundary of the Colorado polygon above.

{ type: 'LineString',
  coordinates:
   [ [ -109, 41 ],
     [ -102, 41 ],
     [ -102, 37 ],
     [ -109, 37 ],
     [ -109, 41 ] ] }

A feature is a wrapper object that contains a point, line or polygon in its geometry property. Below is a feature that wraps the above polygon.

{ type: 'Feature',
  properties: {},
  geometry:
   { type: 'Polygon',
     coordinates:
      [ [ [ -109, 41 ],
          [ -102, 41 ],
          [ -102, 37 ],
          [ -109, 37 ],
          [ -109, 41 ] ] ] } }

Generally, Turf prefers to use features rather than points or polygons. The @turf/helpers npm package contains helpers for creating point and polygon features. Below is an example of creating a polygon feature representing the state of Colorado and 4 point features representing North American ski destinations.

const { point, polygon } = require('@turf/helpers');

// Rough approximation of the state of Colorado, see:
// http://i.imgur.com/59OCqJR.jpg
// Note the triple-nested array.
const colorado = polygon([[
  [-109, 41],
  [-102, 41],
  [-102, 37],
  [-109, 37],
  [-109, 41]
]]);

// Approximate coordinates of 4 ski destinations in North America.
// Only Aspen is in the state of Colorado
const squaw = point([-120.24, 39.21]);
const mammoth = point([-118.9, 37.61]);
const aspen = point([-106.82, 39.18]);
const whistler = point([-122.95, 50.12]);

Here's how these features look on a map. Click on the map for an interactive view.

How do you determine which of these points is within the polygon? You can use the @turf/inside npm package, which returns true if the given point is within the polygon, and filter() over an array of the 4 points.

const inside = require('@turf/inside');

const inColorado = [squaw, mammoth, aspen, whistler].filter(point => inside(point, colorado));
// `[ [-106.82, 39.18] ]`, corresponds to Aspen
console.log(inColorado.map(point => point.geometry.coordinates));

Determining whether a point is within a polygon is one of the most common geospatial computing tasks. For example, the gross Uber polygon in the introduction is used to determine whether a point (your current position) is within an area that isn't eligible for flat fares (the polygon that contains the major Bay Area airports).

Sort Points By Distance from A Point

Another common geospatial computing task is determining the distance between two points, or, more generally, ordering points based on their distance from a point. The @turf/distance npm package computes the distance between two points. Here's how you use Turf's distance() function to sort the 4 ski destinations based on their distance from San Francisco.

const { point, polygon } = require('@turf/helpers');
const distance = require('@turf/distance');

// Approximate coordinates of 4 ski destinations in North America.
const squaw = point([-120.24, 39.21]);
const mammoth = point([-118.9, 37.61]);
const aspen = point([-106.82, 39.18]);
const whistler = point([-122.95, 50.12]);

// Approximate coordinates of San Francisco
const sf = point([-122.5, 37.7]);

const points = [whistler, aspen, mammoth, squaw];
// Units don't matter for this case, but turf uses kilometers by default
points.sort((p1, p2) => distance(p1, sf) - distance(p2, sf));

// Prints the 4 points in the order they were declared, because that's
// the correct sort order:
// [ [ -120.24, 39.21 ],
//   [ -118.9, 37.61 ],
//   [ -106.82, 39.18 ],
//   [ -122.95, 50.12 ] ]
console.log(points.map(point => point.geometry.coordinates));

Distance from Point to Polygon

Checking if a point is in a polygon and finding the distance between two points are both fairly simple. Let's do something more sophisticated: what's the distance between a polygon and a point outside the polygon? For example, with the above data set, how far is Mammoth Lakes from the border of Colorado?

Since Mammoth Lakes is not in Colorado, the distance between Mammoth Lakes and Colorado is the same thing as the distance between Mammoth Lakes and the border of Colorado. Turf has a handy polygonToLine() function that converts a polygon to a line, so you can convert the Colorado polygon to a line representing the border of Colorado. Then, you can use the pointToLineDistance() function that computes the distance from a point to a line. Here's how you combine these two functions to compute the distance from Mammoth Lakes to Colorado:

const { point, polygon } = require('@turf/helpers');
const pointToLineDistance = require('@turf/point-to-line-distance');
const polygonToLine = require('@turf/polygon-to-line');

// Rough approximation of the state of Colorado, see:
// http://i.imgur.com/59OCqJR.jpg
const colorado = polygon([[
  [-109, 41],
  [-102, 41],
  [-102, 37],
  [-109, 37],
  [-109, 41]
]]);

const mammoth = point([-118.9, 37.61]);

// Mammoth Lakes is ~545 miles from the border of Colorado
console.log(pointToLineDistance(mammoth, polygonToLine(colorado), { units: 'miles' }));

Moving On

Turf makes Node.js a natural choice for working with GeoJSON. The above use cases are just the tip of the iceberg of what Turf offers. Writing your own logic to calculate the distance between two points isn't too hard, but Turf can do some very complex calculations for you, like convex hulls, line intersection, Voronoi diagrams, and areas. The Turf team is adding new functionality regularly: I previously implemented DBSCAN clustering on top of Turf for an internal project, and they've since added their own implementation. Check out Turf if you find yourself dealing with geospatial data, it will enable you to do some incredible things with geospatial data.

Like this article? Subscribe on Patreon to get access to exclusive in-depth articles on Node.js