make-geo-json
Version:
Converts polylines, wkts, wkbs, polygons, circles, rectangles, as well as standard geoJSON into geoJSON MultiPolygon Feature Objects. Converts wkb_list and FeatureCollection to arrays.
408 lines (375 loc) • 12.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeGeoJSON = exports.getArea = exports.polyToMulti = exports.ensureGeometryIsValid = exports.ensureShapeIsClosed = exports.convertPoly = exports.translateGooglePoly = undefined;
var _wkx = require('wkx');
var _wkx2 = _interopRequireDefault(_wkx);
var _map = require('lodash/map');
var _map2 = _interopRequireDefault(_map);
var _flatten = require('lodash/flatten');
var _flatten2 = _interopRequireDefault(_flatten);
var _isEqual = require('lodash/isEqual');
var _isEqual2 = _interopRequireDefault(_isEqual);
var _isEmpty = require('lodash/isEmpty');
var _isEmpty2 = _interopRequireDefault(_isEmpty);
var _cloneDeep = require('lodash/cloneDeep');
var _cloneDeep2 = _interopRequireDefault(_cloneDeep);
var _polyline = require('polyline');
var _polyline2 = _interopRequireDefault(_polyline);
var _geojsonArea = require('@mapbox/geojson-area');
var _geojsonArea2 = _interopRequireDefault(_geojsonArea);
var _geojsonRewind = require('geojson-rewind');
var _geojsonRewind2 = _interopRequireDefault(_geojsonRewind);
var _makeCircle = require('./makeCircle');
var _makeCircle2 = _interopRequireDefault(_makeCircle);
var _rectFrom2Points = require('./rectFrom2Points');
var _rectFrom2Points2 = _interopRequireDefault(_rectFrom2Points);
var _kml = require('./kml');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// receives Google Maps Poly and translates to useable format for validation
// convertpoly function takes polyline, geoJSON, wkt, wkb, circle paths,
// and rectangle paths
// and returns geoJSON
// in the form of 'Feature' objects
// see https://tools.ietf.org/html/rfc7946 for geoJSONFORMAT
const translateGooglePoly = exports.translateGooglePoly = gPoly => {
const gPolyClone = (0, _cloneDeep2.default)(gPoly);
const coords = (0, _map2.default)(gPolyClone.path, coord => [coord.lng, coord.lat]);
const feature = {
type: 'Feature',
properties: {},
geometry: {
type: 'MultiPolygon',
coordinates: [[coords]]
}
};
return feature;
};
// given different poly types: polyline, geoJSON (both poly and multipoly),
// wkt, wkb, circle (center/radius), rectangle (bounds/path)
// Returns as geoJSON Feature Objects
const convertPoly = exports.convertPoly = (type, poly) => {
const polyClone = (0, _cloneDeep2.default)(poly);
switch (type) {
case 'googlePoly':
{
const googPoly = translateGooglePoly(polyClone);
return convertPoly('geoJSON', googPoly);
}
case 'polyline':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: 'Feature',
properties,
geometry: _polyline2.default.toGeoJSON(polyClone)
};
return feature;
}
case 'wkt':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: 'Feature',
properties,
geometry: _wkx2.default.Geometry.parse(polyClone).toGeoJSON()
};
return feature;
}
case 'wkb':
{
const properties = polyClone.properties ? polyClone.properties : {};
const buf = Buffer.from(polyClone, 'base64');
const feature = {
type: 'Feature',
properties,
geometry: _wkx2.default.Geometry.parse(buf).toGeoJSON(polyClone)
};
return feature;
}
case 'polygons':
{
const properties = polyClone.properties ? polyClone.properties : {};
const pathCoords = (0, _map2.default)(polyClone.path, x => [x.lng, x.lat]);
const feature = {
type: 'Feature',
properties,
geometry: {
type: 'Polygon',
coordinates: [pathCoords]
}
};
return feature;
}
case 'circles':
{
const circleCoords = (0, _map2.default)(polyClone.path, x => [x.lng, x.lat]);
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: 'Feature',
properties,
geometry: {
type: 'Polygon',
coordinates: [circleCoords]
}
};
return feature;
}
case 'rectangles':
{
const properties = polyClone.properties ? polyClone.properties : {};
const { bounds } = polyClone;
const rectCoords = [[bounds.west, bounds.north], [bounds.west, bounds.south], [bounds.east, bounds.south], [bounds.east, bounds.north]];
return {
type: 'Feature',
properties,
geometry: {
type: 'Polygon',
coordinates: [rectCoords]
}
};
}
case 'Feature':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: polyClone.type,
properties,
geometry: polyClone.geometry
};
return feature;
}
case 'kml':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: polyClone.type,
properties,
geometry: polyClone.geometry
};
return feature;
}
case 'geoJSON':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: polyClone.type,
properties,
geometry: polyClone.geometry
};
return feature;
}
case 'geo_json':
{
const properties = polyClone.properties ? polyClone.properties : {};
const feature = {
type: polyClone.type,
properties,
geometry: polyClone.geometry
};
return feature;
}
default:
console.log('INVALID POLY TYPE', JSON.stringify(poly, null, 2));
throw Error(`Ensure Geometry - invalid poly type: ${polyClone.type}`);
}
};
// given set of coordinates, checks to see if last set is equal to first set
// (read: makes sure it's closed)
// if not, closes shape by adding first set to end of coordinate array
const ensureShapeIsClosed = exports.ensureShapeIsClosed = shape => {
const shapeClone = (0, _cloneDeep2.default)(shape);
if (shapeClone.length >= 3) {
const last = shapeClone.length - 1;
if (!(0, _isEqual2.default)(shapeClone[0], shapeClone[last])) shapeClone.push(shapeClone[0]);
return shapeClone;
}
if (shapeClone.length === 1) {
const radius = 75;
const center = shapeClone[0];
const sides = 24;
const coordinates = (0, _makeCircle2.default)(radius, center, sides);
const last = coordinates.length - 1;
if (!(0, _isEqual2.default)(coordinates[0], coordinates[last])) coordinates.push(coordinates[0]);
return coordinates;
}
if (shapeClone.length === 2) {
const p1 = shapeClone[0];
const p2 = shapeClone[1];
return (0, _rectFrom2Points2.default)(p1, p2);
}
if (shapeClone.length === 0) {
return shapeClone;
}
return shapeClone;
};
// given geoJSON FEATURE OBJ, calls ensureShapeIsClosed() to check coord
// validity
const ensureGeometryIsValid = exports.ensureGeometryIsValid = featObj => {
const featObjClone = (0, _cloneDeep2.default)(featObj);
switch (featObjClone.geometry.type) {
case 'Point':
{
featObjClone.geometry.type = 'Polygon';
const coords = [featObjClone.geometry.coordinates];
featObjClone.geometry.coordinates = [ensureShapeIsClosed(coords)];
return featObjClone;
}
case 'MultiPoint':
{
featObjClone.geometry.type = 'Polygon';
featObjClone.geometry.coordinates = [ensureShapeIsClosed(featObjClone.geometry.coordinates)];
return featObjClone;
}
case 'LineString':
{
featObjClone.geometry.type = 'Polygon';
featObjClone.geometry.coordinates = [ensureShapeIsClosed(featObjClone.geometry.coordinates)];
return featObjClone;
}
case 'MultiLineString':
{
const coordsArray = (0, _flatten2.default)(featObjClone.geometry.coordinates);
featObjClone.geometry.coordinates = [ensureShapeIsClosed(coordsArray)];
return featObjClone;
}
case 'Polygon':
{
const poly = featObjClone.geometry.coordinates;
const data = (0, _map2.default)(poly, shape => ensureShapeIsClosed(shape));
featObjClone.geometry.coordinates = data;
return featObjClone;
}
case 'MultiPolygon':
{
const data = featObjClone.geometry.coordinates;
const carl = (0, _map2.default)(data, poly => (0, _map2.default)(poly, shape => ensureShapeIsClosed(shape)));
featObjClone.geometry.coordinates = carl;
return featObjClone;
}
default:
throw Error(`Ensure Geometry - invalid geometry type: ${featObjClone.geometry.type}`);
}
};
// Given geoJSON Feature
// Returns Polygons as MultiPolys,
// Multipolys pass through
// Everything else is error
const polyToMulti = exports.polyToMulti = geoJSONObj => {
const gJClone = (0, _cloneDeep2.default)(geoJSONObj);
const { properties } = gJClone;
const { type, coordinates } = gJClone.geometry;
try {
if ((0, _isEmpty2.default)(gJClone.geometry.coordinates)) {
const errorFeature = {
type: 'Feature',
properties,
geometry: {
type: 'ERROR',
coordinates
}
};
return errorFeature;
}
switch (type) {
case 'Polygon':
{
const coordArray = gJClone.geometry.coordinates;
if (coordArray[0][0].length > 1) {
const newFeature = {
type: 'Feature',
properties,
geometry: {
type: 'MultiPolygon',
coordinates: [coordinates]
}
};
return newFeature;
}if (coordArray[0][0] <= 1) {
const newFeature = {
type: 'Feature',
properties,
geometry: {
type: 'MultiPolygon',
coordinates
}
};
return newFeature;
}
break;
}
case 'MultiLineString':
{
const newFeature = {
type: 'Feature',
properties,
geometry: {
type: 'MultiPolygon',
coordinates: [coordinates]
}
};
return newFeature;
}
case 'MultiPolygon':
{
return gJClone;
}
default:
throw Error(`Ensure Geometry - invalid poly type: ${type}`);
}
} catch (error) {
console.error(error);
throw Error(`Ensure Geometry - invalid poly type: ${type}`);
}
throw Error(`Unknown Geometry Error - invalid poly type: ${type}`);
};
// Given a GeoJSON Feature Object, returns the area inside the properties obj
const getArea = exports.getArea = featObj => {
const featObjClone = (0, _cloneDeep2.default)(featObj);
const { geometry } = featObjClone;
const areaInMeters = _geojsonArea2.default.geometry(geometry);
const area = areaInMeters * 0.0000003861;
featObjClone.properties.area = area;
return featObjClone;
};
// Given one of our specified poly types, runs it through the series of
// functions above
// Returns valid geoJSON Feature
// Or array of features if given FeatureCollection, KML
const makeGeoJSON = exports.makeGeoJSON = (type, poly) => {
const polyClone = (0, _cloneDeep2.default)(poly);
if (type !== 'kml' && type !== 'FeatureCollection' && type !== 'wkb_list') {
const featObj = convertPoly(type, polyClone);
const validatedObj = ensureGeometryIsValid(featObj);
const resizedArray = polyToMulti(validatedObj);
const woundCoords = (0, _geojsonRewind2.default)(resizedArray);
const resizedWithArea = getArea(woundCoords);
return resizedWithArea;
}
if (type === 'kml') {
const preppedKML = (0, _kml.prepKml)(polyClone);
const resultsArray = [];
for (let i = 0; i < preppedKML.length; i += 1) {
resultsArray.push(makeGeoJSON(preppedKML[i].type, preppedKML[i]));
}
return resultsArray;
}
if (type === 'FeatureCollection') {
const resultsArray = [];
for (let i = 0; i < polyClone.length; i += 1) {
resultsArray.push(makeGeoJSON(polyClone[i].type, polyClone[i]));
}
return resultsArray;
}
if (type === 'wkb_list') {
const resultsArray = [];
for (let i = 0; i < polyClone.length; i += 1) {
resultsArray.push(makeGeoJSON('wkb', polyClone[i]));
}
return resultsArray;
}
throw Error(`${type} not currently supported.`);
};
/* if type is array, iterate return array
* if type is object, return object */