slifty
slifty

Reputation: 13791

Converting Polylines to GeoJSON

I have a series of (encoded, or decoded) polylines captured from a service like Google Maps / Open Street Maps.

For example:

var polylines = ["kclaG|i_qLe@i@{AgBu@_AsCyCiBmBMMW[eCiC}A_BEEeBiB{@}@MW]a@a@e@]a@IGGG_AgAm@i@MOYYMEQSCCi@k@KKu@y@{A}Ay@cA{@{@eBiBOMe@k@_@a@e@e@QQY[cAkAUWMOUWu@w@AACCYY?AMKUUSSSQ]]GGECCCECA?AAA?A?C?G?WB"
       ,"yfnaGld}pL?e@?]AuDAgA?KAkBGqG?{C?_B?S?aC?[?]A}A?wAASAQ"
       ,"qmnaGlxxpLn@tEL~@"]

I want to store these as GeoJSON. I've found plenty of packages which render GeoJSON through tools like Leaflet, but I'm having trouble finding packages that could export polylines to GeoJSON.

Do any solutions like this exist or is there a particular subset of the GeoJSON standard that I should be looking into so I can write the tool myset?

Side note: I ultimately want a KML or SHP file. I'm planning to use http://ogre.adc4gis.com/ to convert from GeoJSON to SHP.

Upvotes: 11

Views: 8167

Answers (1)

08Dc91wk
08Dc91wk

Reputation: 4318

You're looking for the GeoJSON LineString geometry type. Although the algorithm is a little complicated, there is a a project on Github that provides the functionality in JavaScript. You should easily be able to port this to the language of your choosing. I have also wrapped the functionality into a JSFiddle so you can use this as an online tool.

You'll find the tool at https://jsfiddle.net/ivansams/tw7qLvh4/2/ - you can paste an encoded polyline into "Encoded Polyline" and click "Decode", and this will return a GeoJSON LineString. The code is reproduced below because of StackOverflow's rules around requiring JSFiddle to be accompanied by code, but get the up to date code at the Github project site.

function decode() {
  var encodedPolyline = document.getElementById("encoded-polyline").value;
  document.getElementById("geojson").value = encodedPolyline;
  var coords = polyline.toGeoJSON(encodedPolyline);
  document.getElementById("geojson").value = JSON.stringify(coords);
}

var polyline = {};

function encode(current, previous, factor) {
  current = Math.round(current * factor);
  previous = Math.round(previous * factor);
  var coordinate = current - previous;
  coordinate <<= 1;
  if (current - previous < 0) {
    coordinate = ~coordinate;
  }
  var output = '';
  while (coordinate >= 0x20) {
    output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
    coordinate >>= 5;
  }
  output += String.fromCharCode(coordinate + 63);
  return output;
}

/**
 * Decodes to a [latitude, longitude] coordinates array.
 *
 * This is adapted from the implementation in Project-OSRM.
 *
 * @param {String} str
 * @param {Number} precision
 * @returns {Array}
 *
 * @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
 */
polyline.decode = function(str, precision) {
  var index = 0,
    lat = 0,
    lng = 0,
    coordinates = [],
    shift = 0,
    result = 0,
    byte = null,
    latitude_change,
    longitude_change,
    factor = Math.pow(10, precision || 5);

  // Coordinates have variable length when encoded, so just keep
  // track of whether we've hit the end of the string. In each
  // loop iteration, a single coordinate is decoded.
  while (index < str.length) {

    // Reset shift, result, and byte
    byte = null;
    shift = 0;
    result = 0;

    do {
      byte = str.charCodeAt(index++) - 63;
      result |= (byte & 0x1f) << shift;
      shift += 5;
    } while (byte >= 0x20);

    latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));

    shift = result = 0;

    do {
      byte = str.charCodeAt(index++) - 63;
      result |= (byte & 0x1f) << shift;
      shift += 5;
    } while (byte >= 0x20);

    longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));

    lat += latitude_change;
    lng += longitude_change;

    coordinates.push([lat / factor, lng / factor]);
  }

  return coordinates;
};

/**
 * Encodes the given [latitude, longitude] coordinates array.
 *
 * @param {Array.<Array.<Number>>} coordinates
 * @param {Number} precision
 * @returns {String}
 */
polyline.encode = function(coordinates, precision) {
  if (!coordinates.length) {
    return '';
  }

  var factor = Math.pow(10, precision || 5),
    output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);

  for (var i = 1; i < coordinates.length; i++) {
    var a = coordinates[i],
      b = coordinates[i - 1];
    output += encode(a[0], b[0], factor);
    output += encode(a[1], b[1], factor);
  }

  return output;
};

function flipped(coords) {
  var flipped = [];
  for (var i = 0; i < coords.length; i++) {
    flipped.push(coords[i].slice().reverse());
  }
  return flipped;
}

/**
 * Encodes a GeoJSON LineString feature/geometry.
 *
 * @param {Object} geojson
 * @param {Number} precision
 * @returns {String}
 */
polyline.fromGeoJSON = function(geojson, precision) {
  if (geojson && geojson.type === 'Feature') {
    geojson = geojson.geometry;
  }
  if (!geojson || geojson.type !== 'LineString') {
    throw new Error('Input must be a GeoJSON LineString');
  }
  return polyline.encode(flipped(geojson.coordinates), precision);
};

/**
 * Decodes to a GeoJSON LineString geometry.
 *
 * @param {String} str
 * @param {Number} precision
 * @returns {Object}
 */
polyline.toGeoJSON = function(str, precision) {
  var coords = polyline.decode(str, precision);
  return {
    type: 'LineString',
    coordinates: flipped(coords)
  };
};

if (typeof module === 'object' && module.exports) {
  module.exports = polyline;
}

Upvotes: 8

Related Questions