Working with GeoJSON in Node.js: a practical introduction

GeoJSON is a standardized format for representing geographic data structures based on JSON. There are many great tools for visualizing GeoJSON data. Moreover, this format is good not only in the storage of coordinates of certain points. It, in addition to points, allows you to describe other objects: lines, polygons, collections of objects.



Points - Point objects


GeoJSON point looks like this:

{
  "type": "Point",
  "coordinates": [-80.1347334, 25.7663562]
}

This point represents a park in Miami Beach, Florida, USA. You can easily visualize this point on the map using the geojson.io project .


Point on the map

It is important to note that the coordinate in the property iscoordinateswritten in the format[lng, lat]. Longitude in GeoJSON comes before latitude . This is because longitude represents the east-west direction (axisxon a typical map), and latitude represents the north-south direction (axisyon a typical map). GeoJSON authors sought to maintain coordinate orderx, y.

A typical example of using GeoJSON points is geocoding - address translation like "429 Lenox Ave, Miami Beach, FL" to coordinates expressed in longitude and latitude. For example, we use the APIGeocoding Mapbox. To access this API, an HTTP request must be made to the following endpoint:

https://api.mapbox.com/geocoding/v5/mapbox.places/429%20lenox%20ave%20miami.json?access_token=pk.eyJ1IjoibWF0dGZpY2tlIiwiYSI6ImNqNnM2YmFoNzAwcTMzM214NTB1NHdwbnoifQ.Or19S7KmYPHW8YjRz82v6g&cachebuster=1581993735895&autocomplete=true

In response, the following code will come:

{"type":"FeatureCollection","query":["429","lenox","ave","miami"],"features":[{"id":"address.8052276751051244","type":"Feature","place_type":["address"],"relevance":1,"properties":{"accuracy":"rooftop"},"text":"Lenox Avenue","place_name":"429 Lenox Avenue, Miami Beach, Florida 33139, United States","center":[-80.139145,25.77409],"geometry":{"type":"Point","coordinates":[-80.139145,25.77409]}, ...}

If you look closely at the answer, it turns out that features[0].geometryin the JSON code it is a GeoJSON point:

{"type":"Point","coordinates":[-80.139145,25.77409]}


Visualization of the coordinates of the

API static maps Mapbox is a great tool for displaying points on maps. Below is a script that decodes the string passed to it and returns the URL to the image that shows the first search result.

const axios = require('axios');

async function search(str) {
  const geocoderUrl = 'https://api.mapbox.com/geocoding/v5/mapbox.places/' +
    encodeURIComponent(str) +
    '.json?access_token=' +
    'pk.eyJ1IjoibWF0dGZpY2tlIiwiYSI6ImNqNnM2YmFoNzAwcTMzM214NTB1NHdwbnoifQ.Or19S7KmYPHW8YjRz82v6g';

  const res = await axios.get(geocoderUrl).then(res => res.data);
  const point = res.features[0].geometry;

  return 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/' +
    'pin-l-1+333(' + point.coordinates[0] + ',' + point.coordinates[1] + ')/' +
    point.coordinates[0] + ',' + point.coordinates[1] +
    ',14.25,0,0/600x600/' +
    '?access_token=pk.eyJ1IjoibWF0dGZpY2tlIiwiYSI6ImNqNnM2YmFoNzAwcTMzM214NTB1NHdwbnoifQ.Or19S7KmYPHW8YjRz82v6g';
}

search('429 Lenox Ave, Miami Beach').then(res => console.log(res));


An example of visualizing a point on a map

Lines - LineString objects


In GeoJSON lines, objects LineStringrepresent arrays of coordinates that describe a line on a map. The following is a GeoJSON object LineStringrepresenting the approximate border between the states of California and Oregon in the USA:

{
  "type": "LineString",
  "coordinates": [[-124.2, 42], [-120, 42]]
}


Rendering a LineString on a Map

Lines, using a navigation API like Mapbox , are used to render a step-by-step path between two points. One way to represent a road from a point[-80.139145,25.77409](WeWork Miami Beach office) to a point[-80.2752743,25.7938434](Miami International Airport) is to use a GeoJSON objectLineString:

{
  "type": "LineString",
  "coordinates": [
    [-80.139153, 25.774281],
    [-80.13829, 25.774307],
    [-80.142029, 25.774479],
    [-80.148438, 25.772148],
    [-80.151237, 25.772232],
    [-80.172043, 25.78116],
    [-80.177322, 25.787195],
    [-80.185326, 25.787212],
    [-80.189804, 25.785891],
    [-80.19268, 25.785954],
    [-80.202301, 25.789175],
    [-80.207954, 25.788721],
    [-80.223, 25.782646],
    [-80.231026, 25.78261],
    [-80.238007, 25.784889],
    [-80.246025, 25.784403],
    [-80.249611, 25.785175],
    [-80.253166, 25.786049],
    [-80.259262, 25.786324],
    [-80.264038, 25.786186],
    [-80.264221, 25.787256],
    [-80.264214, 25.791618],
    [-80.264221, 25.792633],
    [-80.264069, 25.795443],
    [-80.263397, 25.795652],
    [-80.263786, 25.794928],
    [-80.267723, 25.794926],
    [-80.271141, 25.794859],
    [-80.273163, 25.795704],
    [-80.275009, 25.796482],
    [-80.277481, 25.796461],
    [-80.278435, 25.795622],
    [-80.278061, 25.794088],
    [-80.275276, 25.793804]
  ]
}

Objects LineStringthat are some routes can be very complex. The above object, for example, describes a short 15-minute ride. This is how it all looks on the map.


Path from one point to another

Here is a simple script that returns aLineStringrepresentation of the path between 2 points using thedirectionsMapboxAPI.

const axios = require('axios');

async function directions(fromPt, toPt) {
  const fromCoords = fromPt.coordinates.join(',');
  const toCoords = toPt.coordinates.join(',');
  const directionsUrl = 'https://api.mapbox.com/directions/v5/mapbox/driving/' +
    fromCoords + ';' + toCoords + '?' +
    'geometries=geojson&' +
    'access_token=pk.eyJ1IjoibWF0dGZpY2tlIiwiYSI6ImNqNnM2YmFoNzAwcTMzM214NTB1NHdwbnoifQ.Or19S7KmYPHW8YjRz82v6g';

  const res = await axios.get(directionsUrl).then(res => res.data);
  return res.routes[0].geometry;
}

const wework = { type: 'Point', coordinates: [-80.139145,25.77409] };
const airport = { type: 'Point', coordinates: [-80.2752743,25.7938434] };

directions(wework, airport).then(res => {
  console.log(res);
});

Polygons - Polygon Objects


GeoJSON polygons, objects Polygon, are used to describe closed areas on maps. These can be areas in the shape of a triangle, square, dodecagon , or any other shape with a fixed number of sides. For example, the following GeoJSON object roughly describes the borders of the state of Colorado in the USA:

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


Visualization of a polygon on a map

GeoJSON polygons can be used to describe very complex shapes. For example, for some time Uber used the only GeoJSON-training ground, which includes all 3 major airports in the San Francisco Bay area.


Complex GeoJSON polygon

True, it should be noted that GeoJSON polygons cannot represent circles and ellipses.

What are polygons used for? Usually - to describe geofences . For example, imagine that you work in Uber or in Lyft. You need to show users who book trips from the airport a special screen. In order to do this, you will need to find out if the point from which the trip is ordered is located within the polygon describing the airport (or several airports as in the previous figure).

One way to verify that a GeoJSON point is within the polygon is to use the Turpm npm module. The module @turf/boolean-point-in-polygon allows you to find out if a point is within the polygon.

const pointInPolygon = require('@turf/boolean-point-in-polygon').default;

const colorado = {
  "type": "Polygon",
  "coordinates": [[
    [-109, 41],
    [-102, 41],
    [-102, 37],
    [-109, 37],
    [-109, 41]
  ]]
};

const denver = {
  "type": "Point",
  "coordinates": [-104.9951943, 39.7645187]
};

const sanFrancisco = {
  "type": "Point",
  "coordinates": [-122.4726194, 37.7577627]
};

// true
console.log(pointInPolygon(denver, colorado));

// false
console.log(pointInPolygon(sanFrancisco, colorado));

The Turf package allows you to find out whether a point is within the polygon using the Node.js. But what if we are interested in obtaining the same information by executing queries to the database? In this case, you should be aware that the built-in MongoDB statement$geoIntersects supports GeoJSON. Therefore, for example, you can write a query that allows you to find out which US state corresponds to a certain point on the map:

const mongoose = require('mongoose');

run().catch(err => console.log(err));

async function run() {
  await mongoose.connect('mongodb://localhost:27017/geotest', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });
  await mongoose.connection.dropDatabase();

  const State = mongoose.model('State', mongoose.Schema({
    name: String,
    location: mongoose.Schema({
      type: String,
      coordinates: [[[Number]]]
    })
  }));

  const colorado = await State.create({
    name: 'Colorado',
    location: {
      "type": "Polygon",
      "coordinates": [[
        [-109, 41],
        [-102, 41],
        [-102, 37],
        [-109, 37],
        [-109, 41]
      ]]
    }
  });

    const denver = {
    "type": "Point",
    "coordinates": [-104.9951943, 39.7645187]
  };

  const sanFrancisco = {
    "type": "Point",
    "coordinates": [-122.4726194, 37.7577627]
  };

  //     ?
  let res = await State.findOne({
    location: {
      $geoIntersects: { $geometry: denver }
    }
  });
  res.name; // 

  //     -?
  res = await State.findOne({
    location: {
      $geoIntersects: { $geometry: sanFrancisco }
    }
  });
  res; // null
}

Summary


GeoJSON is not only storage of coordinates of points. You can store paths in this format. Using GeoJSON data, you can find out when a user entered the geofence. And if necessary, then GeoJSON even allows you to create isochrones . Around the GeoJSON format, a set of great tools has formed. So, the resource geojson.io allows you to perform simple visualization of coordinates on the map. The Mapbox project provides access to advanced geographic APIs. The Turf package allows you to perform geospatial computing in browsers and in Node.js.

MongoDB supports queries related to geographic data. And if you store the geographical coordinates of points in the form of pairs of values, without using the GeoJSON format, this means that you miss the opportunity to use some wonderful development tools.

Dear readers! Do you use the GeoJSON format?


All Articles