Skip to content

Instantly share code, notes, and snippets.

@ryanbaumann
Last active July 24, 2017 01:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanbaumann/d286190943d6b4eb70e65a9f76eab5a5 to your computer and use it in GitHub Desktop.
Save ryanbaumann/d286190943d6b4eb70e65a9f76eab5a5 to your computer and use it in GitHub Desktop.
Mapbox GL JS - editable circles

Editable Circles in Mapbox GL JS

This example shows how to create a native geojson circle in Mapbox GL JS.

  • circle.js contains all of the code for managing the geojson circle object.
  • index.js contains all of the code to render, edit, update, and interact with the circle.

Extend these two piceces of code to get started building with GL circles in your application today.

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var turf_circle = require('@turf/circle');
var turf_linedistance = require('@turf/line-distance');
var turf_bbox = require('@turf/bbox');
var turf_bbox_poly = require('@turf/bbox-polygon');
var turf_truncate = require('@turf/truncate');
var turf_destination = require('@turf/destination');
var turf_helpers = require('@turf/helpers');
function Circle(center, radius, options) {
this.center = center; //Point geojson feature or array of [long,lat]
this.radius = radius; //Radius of circle
// miles, kilometers, degrees, or radians
this.units = options.units ? options.units : 'kilometers';
//Current zoom level detail of circle
this.zoom = options.zoom ? options.zoom : 8;
// JSON Object - property metadata for circle
this.properties = options.properties ? options.properties : {};
this.steps = 100; // Default steps
this.circle_gj = turf_circle(this.center, this.radius, this.steps, this.units, this.properties);
this.controlPoints = [turf_destination(this.center, this.radius, 0, this.units), turf_destination(this.center, this.radius, 90, this.units), turf_destination(this.center, this.radius, 180, this.units), turf_destination(this.center, this.radius, -90, this.units)];
this._updateCircle = function () {
this.steps = this._calcSteps(this.zoom);
this.circle_gj = turf_circle(this.center, this.radius, this.steps, this.units, this.properties);
this.controlPoints = [turf_destination(this.center, this.radius, 0, this.units), turf_destination(this.center, this.radius, 90, this.units), turf_destination(this.center, this.radius, 180, this.units), turf_destination(this.center, this.radius, -90, this.units)];
};
this._calcSteps = function (zoom) {
if (zoom <= 0.1) {
zoom = 0.1;
}
var radius_km = turf_helpers.convertDistance(this.radius, this.units, 'kilometers');
this.steps = Math.sqrt(radius_km * 250) * zoom ^ 2;
};
this._calcSteps(this.zoom);
this.asGeojson = function () {
var feats = this.controlPoints;
feats.push(this.circle_gj);
feats.push(turf_helpers.point(this.center, { "type": "center" }));
return turf_helpers.featureCollection(feats);
};
this.updateCenter = function (newCenter) {
this.center = newCenter;
this._updateCircle();
};
this.updateRadius = function (newRadius) {
this.radius = newRadius;
this._updateCircle();
};
this.updateZoom = function (newZoom) {
this.zoom = this._calcSteps(newZoom);
this._updateCircle();
};
this.updateSteps = function (newSteps) {
this.steps = newSteps;
this._updateCircle();
};
this.updateUnits = function (newUnits) {
this.units = newUnits;
this._updateCircle();
};
this.getBounds = function () {
var bbox_poly = turf_truncate(turf_bbox_poly(turf_bbox(this.circle_gj)), 6);
var bounds = [bbox_poly.geometry.coordinates[0][0][0], bbox_poly.geometry.coordinates[0][0][1], bbox_poly.geometry.coordinates[0][2][0], bbox_poly.geometry.coordinates[0][2][1]];
return bounds;
};
this.getBboxPoly = function () {
return bbox_poly = turf_truncate(turf_bbox_poly(turf_bbox(this.circle_gj)), 6);
};
this.getCenter = function () {
return this.center;
};
this.getRadius = function () {
return this.radius;
};
this.getControlPoints = function () {
return turf_helpers.featureCollection(this.controlPoints);
};
}
module.exports = exports = Circle;
},{"@turf/bbox":4,"@turf/bbox-polygon":3,"@turf/circle":5,"@turf/destination":6,"@turf/helpers":9,"@turf/line-distance":12,"@turf/truncate":14}],2:[function(require,module,exports){
'use strict';
var Circle = require('./Circle.js');
var turf_inside = require('@turf/inside');
var turf_helpers = require('@turf/helpers');
var turf_truncate = require('@turf/truncate');
var turf_distance = require('@turf/distance');
mapboxgl.accessToken = 'pk.eyJ1IjoicnNiYXVtYW5uIiwiYSI6IjdiOWEzZGIyMGNkOGY3NWQ4ZTBhN2Y5ZGU2Mzg2NDY2In0.jycgv7qwF8MMIWt4cT0RaQ';
var mapzoom = 12;
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [-75.343, 39.984],
zoom: mapzoom
});
// Circle Setup
var center = [-75.343, 39.984];
var radius = 3;
var units = 'kilometers';
var properties = { foo: 'bar' };
var myCircle = new Circle(center, radius, {
units: units,
zoom: mapzoom,
properties: properties
});
// DOM elements
var bounds_el = document.getElementById('circleBounds');
var radius_el = document.getElementById('selectRadius');
var drag_el = document.getElementById('selectRadius');
var center_el = document.getElementById('circleCenter');
var radiusLabel_el = document.getElementById('circleRadiusLabel');
bounds_el.innerHTML = 'Bounds: ' + myCircle.getBounds();
center_el.innerHTML = 'Center: ' + myCircle.getCenter();
radiusLabel_el.innerHTML = 'Radius: ' + myCircle.getRadius() + ' ' + units;
// Helper functions
var animateCircle = function animateCircle() {
//map.on('sourcedata', onSourceData)
map.getSource('circle-1').setData(myCircle.asGeojson());
bounds_el.innerHTML = 'Bounds: ' + myCircle.getBounds();
center_el.innerHTML = 'Center: ' + myCircle.getCenter();
};
var adjustCirclePrecision = function adjustCirclePrecision() {
var cur_zoom = map.getZoom();
myCircle.updateZoom(cur_zoom);
animateCircle();
};
var onMoveCircle = function onMoveCircle(e) {
var mousePoint = turf_truncate(turf_helpers.point(map.unproject(e.point).toArray()), 6);
myCircle.updateCenter(mousePoint.geometry.coordinates);
animateCircle();
};
var mouseUpCircle = function mouseUpCircle() {
map.setPaintProperty('circle-center-point', 'circle-color', '#fb6a4a');
map.dragPan.enable();
map.off('mousemove', onMoveCircle);
};
var mouseDownCircle = function mouseDownCircle(e) {
map.dragPan.disable();
map.setPaintProperty('circle-center-point', 'circle-color', '#a50f15');
map.on('mousemove', onMoveCircle);
map.once('mouseup', mouseUpCircle);
};
var onMovePoint = function onMovePoint(e) {
var clickPoint = map.unproject(e.point).toArray();
myCircle.updateRadius(turf_distance(myCircle.getCenter(), clickPoint, units));
radiusLabel_el.innerHTML = 'Radius: ' + Math.trunc(myCircle.getRadius()) + ' ' + units;
animateCircle();
};
var mouseUpPoint = function mouseUpPoint() {
map.setPaintProperty('circle-control-points', 'circle-color', 'white');
map.dragPan.enable();
map.off('mousemove', onMovePoint);
};
var mouseDownPoint = function mouseDownPoint(e) {
map.dragPan.disable();
map.setPaintProperty('circle-control-points', 'circle-color', '#a50f15');
map.on('mousemove', onMovePoint);
map.once('mouseup', mouseUpPoint);
};
var onMousemove = function onMousemove(e) {
map.off('mousedown', mouseDownCircle);
map.off('mousedown', mouseDownPoint);
var pointFeatures = map.queryRenderedFeatures(e.point, {
layers: ['circle-control-points']
});
var circleFeatures = map.queryRenderedFeatures(e.point, {
layers: ['circle-center-point']
});
if (!pointFeatures.length && !circleFeatures.length) {
map.getCanvas().style.cursor = '';
return;
}
if (pointFeatures.length) {
map.getCanvas().style.cursor = 'pointer';
map.once('mousedown', mouseDownPoint);
} else if (circleFeatures.length) {
map.getCanvas().style.cursor = 'pointer';
map.once('mousedown', mouseDownCircle);
}
};
map.on('load', function () {
map.addSource('circle-1', {
type: "geojson",
data: myCircle.asGeojson(),
buffer: 1
});
map.addLayer({
id: "circle-line",
type: "line",
source: "circle-1",
paint: {
"line-color": "#fb6a4a",
"line-width": {
stops: [[0, 0.1], [16, 5]]
}
},
filter: ["==", "$type", "Polygon"]
}, 'waterway-label');
map.addLayer({
id: "circle-fill",
type: "fill",
source: "circle-1",
paint: {
"fill-color": "#fb6a4a",
"fill-opacity": 0.5
},
filter: ["==", "$type", "Polygon"]
}, 'waterway-label');
map.addLayer({
id: "circle-control-points",
type: "circle",
source: "circle-1",
paint: {
"circle-color": "white",
"circle-radius": {
stops: [[0, 6], [4, 10], [18, 12]]
},
"circle-stroke-color": "black",
"circle-stroke-width": {
stops: [[0, 0.1], [8, 1], [16, 4]]
}
},
filter: ["all", ["==", "$type", "Point"], ["!=", "type", "center"]]
});
map.addLayer({
id: "circle-center-point",
type: "circle",
source: "circle-1",
paint: {
"circle-color": "#fb6a4a",
"circle-radius": {
stops: [[0, 6], [4, 10], [18, 12]]
},
"circle-stroke-color": "black",
"circle-stroke-width": {
stops: [[0, 0.1], [8, 1], [16, 4]]
}
},
filter: ["all", ["==", "$type", "Point"], ["==", "type", "center"]]
});
// Add map event listeners
map.on('zoomend', adjustCirclePrecision);
map.on('mousemove', _.debounce(onMousemove, 16));
});
},{"./Circle.js":1,"@turf/distance":7,"@turf/helpers":9,"@turf/inside":10,"@turf/truncate":14}],3:[function(require,module,exports){
var polygon = require('@turf/helpers').polygon;
/**
* Takes a bbox and returns an equivalent {@link Polygon|polygon}.
*
* @name bboxPolygon
* @param {Array<number>} bbox extent in [minX, minY, maxX, maxY] order
* @return {Feature<Polygon>} a Polygon representation of the bounding box
* @addToMap poly
* @example
* var bbox = [0, 0, 10, 10];
*
* var poly = turf.bboxPolygon(bbox);
*
* //addToMap
* var addToMap = [poly]
*/
module.exports = function (bbox) {
var lowLeft = [bbox[0], bbox[1]];
var topLeft = [bbox[0], bbox[3]];
var topRight = [bbox[2], bbox[3]];
var lowRight = [bbox[2], bbox[1]];
return polygon([[
lowLeft,
lowRight,
topRight,
topLeft,
lowLeft
]]);
};
},{"@turf/helpers":9}],4:[function(require,module,exports){
var coordEach = require('@turf/meta').coordEach;
/**
* Takes a set of features, calculates the bbox of all input features, and returns a bounding box.
*
* @name bbox
* @param {FeatureCollection|Feature<any>} geojson input features
* @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order
* @example
* var line = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "LineString",
* "coordinates": [[-74, 40], [-78, 42], [-82, 35]]
* }
* }
* var bbox = turf.bbox(line);
*
* //addToMap
* var bboxPolygon = turf.bboxPolygon(bbox);
* var addToMap = [line, bboxPolygon]
*/
module.exports = function (geojson) {
var bbox = [Infinity, Infinity, -Infinity, -Infinity];
coordEach(geojson, function (coord) {
if (bbox[0] > coord[0]) bbox[0] = coord[0];
if (bbox[1] > coord[1]) bbox[1] = coord[1];
if (bbox[2] < coord[0]) bbox[2] = coord[0];
if (bbox[3] < coord[1]) bbox[3] = coord[1];
});
return bbox;
};
},{"@turf/meta":13}],5:[function(require,module,exports){
var destination = require('@turf/destination');
var polygon = require('@turf/helpers').polygon;
/**
* Takes a {@link Point} and calculates the circle polygon given a radius in degrees, radians, miles, or kilometers; and steps for precision.
*
* @name circle
* @param {Feature<Point>|number[]} center center point
* @param {number} radius radius of the circle
* @param {number} [steps=64] number of steps
* @param {string} [units=kilometers] miles, kilometers, degrees, or radians
* @param {Object} [properties={}] properties
* @returns {Feature<Polygon>} circle polygon
* @example
* var center = [-75.343, 39.984];
* var radius = 5;
* var steps = 10;
* var units = 'kilometers';
* var properties = {foo: 'bar'};
*
* var circle = turf.circle(center, radius, steps, units, properties);
*
* //addToMap
* var addToMap = [turf.point(center), circle]
*/
module.exports = function (center, radius, steps, units, properties) {
// validation
if (!center) throw new Error('center is required');
if (!radius) throw new Error('radius is required');
// default params
steps = steps || 64;
properties = properties || center.properties || {};
var coordinates = [];
for (var i = 0; i < steps; i++) {
coordinates.push(destination(center, radius, i * 360 / steps, units).geometry.coordinates);
}
coordinates.push(coordinates[0]);
return polygon([coordinates], properties);
};
},{"@turf/destination":6,"@turf/helpers":9}],6:[function(require,module,exports){
//http://en.wikipedia.org/wiki/Haversine_formula
//http://www.movable-type.co.uk/scripts/latlong.html
var getCoord = require('@turf/invariant').getCoord;
var helpers = require('@turf/helpers');
var point = helpers.point;
var distanceToRadians = helpers.distanceToRadians;
/**
* Takes a {@link Point} and calculates the location of a destination point given a distance in degrees, radians, miles, or kilometers; and bearing in degrees. This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature.
*
* @name destination
* @param {Geometry|Feature<Point>|Array<number>} origin starting point
* @param {number} distance distance from the origin point
* @param {number} bearing ranging from -180 to 180
* @param {string} [units=kilometers] miles, kilometers, degrees, or radians
* @returns {Feature<Point>} destination point
* @example
* var point = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [-75.343, 39.984]
* }
* };
* var distance = 50;
* var bearing = 90;
* var units = 'miles';
*
* var destination = turf.destination(point, distance, bearing, units);
*
* //addToMap
* destination.properties['marker-color'] = '#f00';
* point.properties['marker-color'] = '#0f0';
* var addToMap = [point, destination]
*/
module.exports = function (origin, distance, bearing, units) {
var degrees2radians = Math.PI / 180;
var radians2degrees = 180 / Math.PI;
var coordinates1 = getCoord(origin);
var longitude1 = degrees2radians * coordinates1[0];
var latitude1 = degrees2radians * coordinates1[1];
var bearing_rad = degrees2radians * bearing;
var radians = distanceToRadians(distance, units);
var latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) +
Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearing_rad));
var longitude2 = longitude1 + Math.atan2(Math.sin(bearing_rad) *
Math.sin(radians) * Math.cos(latitude1),
Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2));
return point([radians2degrees * longitude2, radians2degrees * latitude2]);
};
},{"@turf/helpers":9,"@turf/invariant":11}],7:[function(require,module,exports){
var getCoord = require('@turf/invariant').getCoord;
var radiansToDistance = require('@turf/helpers').radiansToDistance;
//http://en.wikipedia.org/wiki/Haversine_formula
//http://www.movable-type.co.uk/scripts/latlong.html
/**
* Calculates the distance between two {@link Point|points} in degrees, radians,
* miles, or kilometers. This uses the
* [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula)
* to account for global curvature.
*
* @name distance
* @param {Geometry|Feature<Point>|Array<number>} from origin point
* @param {Geometry|Feature<Point>|Array<number>} to destination point
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers
* @returns {number} distance between the two points
* @example
* var from = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [-75.343, 39.984]
* }
* };
* var to = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [-75.534, 39.123]
* }
* };
*
* var distance = turf.distance(from, to, "miles");
*
* //addToMap
* from.properties.distance = distance;
* to.properties.distance = distance;
* var addToMap = [from, to];
*/
module.exports = function (from, to, units) {
var degrees2radians = Math.PI / 180;
var coordinates1 = getCoord(from);
var coordinates2 = getCoord(to);
var dLat = degrees2radians * (coordinates2[1] - coordinates1[1]);
var dLon = degrees2radians * (coordinates2[0] - coordinates1[0]);
var lat1 = degrees2radians * coordinates1[1];
var lat2 = degrees2radians * coordinates2[1];
var a = Math.pow(Math.sin(dLat / 2), 2) +
Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
return radiansToDistance(2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)), units);
};
},{"@turf/helpers":9,"@turf/invariant":11}],8:[function(require,module,exports){
var flattenEach = require('@turf/meta').flattenEach;
var featureCollection = require('@turf/helpers').featureCollection;
/**
* Flattens any {@link GeoJSON} to a {@link FeatureCollection} inspired by [geojson-flatten](https://github.com/tmcw/geojson-flatten).
*
* @name flatten
* @param {FeatureCollection|Geometry|Feature<any>} geojson any valid GeoJSON Object
* @returns {FeatureCollection<any>} all Multi-Geometries are flattened into single Features
* @example
* var multiGeometry = {
* "type": "MultiPolygon",
* "coordinates": [
* [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],
* [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
* [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]
* ]
* };
*
* var flatten = turf.flatten(multiGeometry);
*
* //addToMap
* var addToMap = [flatten]
*/
function flatten(geojson) {
if (!geojson) throw new Error('geojson is required');
var results = [];
flattenEach(geojson, function (feature) {
results.push(feature);
});
return featureCollection(results);
}
module.exports = flatten;
},{"@turf/helpers":9,"@turf/meta":13}],9:[function(require,module,exports){
/**
* Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}.
*
* @name feature
* @param {Geometry} geometry input geometry
* @param {Object} properties properties
* @returns {Feature} a GeoJSON Feature
* @example
* var geometry = {
* "type": "Point",
* "coordinates": [110, 50]
* };
*
* var feature = turf.feature(geometry);
*
* //=feature
*/
function feature(geometry, properties) {
if (!geometry) throw new Error('No geometry passed');
return {
type: 'Feature',
properties: properties || {},
geometry: geometry
};
}
/**
* Takes coordinates and properties (optional) and returns a new {@link Point} feature.
*
* @name point
* @param {Array<number>} coordinates longitude, latitude position (each in decimal degrees)
* @param {Object=} properties an Object that is used as the {@link Feature}'s
* properties
* @returns {Feature<Point>} a Point feature
* @example
* var point = turf.point([-75.343, 39.984]);
*
* //=point
*/
function point(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
if (coordinates.length === undefined) throw new Error('Coordinates must be an array');
if (coordinates.length < 2) throw new Error('Coordinates must be at least 2 numbers long');
if (typeof coordinates[0] !== 'number' || typeof coordinates[1] !== 'number') throw new Error('Coordinates must contain numbers');
return feature({
type: 'Point',
coordinates: coordinates
}, properties);
}
/**
* Takes an array of LinearRings and optionally an {@link Object} with properties and returns a {@link Polygon} feature.
*
* @name polygon
* @param {Array<Array<Array<number>>>} coordinates an array of LinearRings
* @param {Object=} properties a properties object
* @returns {Feature<Polygon>} a Polygon feature
* @throws {Error} throw an error if a LinearRing of the polygon has too few positions
* or if a LinearRing of the Polygon does not have matching Positions at the beginning & end.
* @example
* var polygon = turf.polygon([[
* [-2.275543, 53.464547],
* [-2.275543, 53.489271],
* [-2.215118, 53.489271],
* [-2.215118, 53.464547],
* [-2.275543, 53.464547]
* ]], { name: 'poly1', population: 400});
*
* //=polygon
*/
function polygon(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
for (var i = 0; i < coordinates.length; i++) {
var ring = coordinates[i];
if (ring.length < 4) {
throw new Error('Each LinearRing of a Polygon must have 4 or more Positions.');
}
for (var j = 0; j < ring[ring.length - 1].length; j++) {
if (ring[ring.length - 1][j] !== ring[0][j]) {
throw new Error('First and last Position are not equivalent.');
}
}
}
return feature({
type: 'Polygon',
coordinates: coordinates
}, properties);
}
/**
* Creates a {@link LineString} based on a
* coordinate array. Properties can be added optionally.
*
* @name lineString
* @param {Array<Array<number>>} coordinates an array of Positions
* @param {Object=} properties an Object of key-value pairs to add as properties
* @returns {Feature<LineString>} a LineString feature
* @throws {Error} if no coordinates are passed
* @example
* var linestring1 = turf.lineString([
* [-21.964416, 64.148203],
* [-21.956176, 64.141316],
* [-21.93901, 64.135924],
* [-21.927337, 64.136673]
* ]);
* var linestring2 = turf.lineString([
* [-21.929054, 64.127985],
* [-21.912918, 64.134726],
* [-21.916007, 64.141016],
* [-21.930084, 64.14446]
* ], {name: 'line 1', distance: 145});
*
* //=linestring1
*
* //=linestring2
*/
function lineString(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
if (coordinates.length < 2) throw new Error('Coordinates must be an array of two or more positions');
return feature({
type: 'LineString',
coordinates: coordinates
}, properties);
}
/**
* Takes one or more {@link Feature|Features} and creates a {@link FeatureCollection}.
*
* @name featureCollection
* @param {Feature[]} features input features
* @returns {FeatureCollection} a FeatureCollection of input features
* @example
* var features = [
* turf.point([-75.343, 39.984], {name: 'Location A'}),
* turf.point([-75.833, 39.284], {name: 'Location B'}),
* turf.point([-75.534, 39.123], {name: 'Location C'})
* ];
*
* var collection = turf.featureCollection(features);
*
* //=collection
*/
function featureCollection(features) {
if (!features) throw new Error('No features passed');
if (!Array.isArray(features)) throw new Error('features must be an Array');
return {
type: 'FeatureCollection',
features: features
};
}
/**
* Creates a {@link Feature<MultiLineString>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiLineString
* @param {Array<Array<Array<number>>>} coordinates an array of LineStrings
* @param {Object=} properties an Object of key-value pairs to add as properties
* @returns {Feature<MultiLineString>} a MultiLineString feature
* @throws {Error} if no coordinates are passed
* @example
* var multiLine = turf.multiLineString([[[0,0],[10,10]]]);
*
* //=multiLine
*/
function multiLineString(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
return feature({
type: 'MultiLineString',
coordinates: coordinates
}, properties);
}
/**
* Creates a {@link Feature<MultiPoint>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPoint
* @param {Array<Array<number>>} coordinates an array of Positions
* @param {Object=} properties an Object of key-value pairs to add as properties
* @returns {Feature<MultiPoint>} a MultiPoint feature
* @throws {Error} if no coordinates are passed
* @example
* var multiPt = turf.multiPoint([[0,0],[10,10]]);
*
* //=multiPt
*/
function multiPoint(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
return feature({
type: 'MultiPoint',
coordinates: coordinates
}, properties);
}
/**
* Creates a {@link Feature<MultiPolygon>} based on a
* coordinate array. Properties can be added optionally.
*
* @name multiPolygon
* @param {Array<Array<Array<Array<number>>>>} coordinates an array of Polygons
* @param {Object=} properties an Object of key-value pairs to add as properties
* @returns {Feature<MultiPolygon>} a multipolygon feature
* @throws {Error} if no coordinates are passed
* @example
* var multiPoly = turf.multiPolygon([[[[0,0],[0,10],[10,10],[10,0],[0,0]]]]);
*
* //=multiPoly
*
*/
function multiPolygon(coordinates, properties) {
if (!coordinates) throw new Error('No coordinates passed');
return feature({
type: 'MultiPolygon',
coordinates: coordinates
}, properties);
}
/**
* Creates a {@link Feature<GeometryCollection>} based on a
* coordinate array. Properties can be added optionally.
*
* @name geometryCollection
* @param {Array<{Geometry}>} geometries an array of GeoJSON Geometries
* @param {Object=} properties an Object of key-value pairs to add as properties
* @returns {Feature<GeometryCollection>} a GeoJSON GeometryCollection Feature
* @example
* var pt = {
* "type": "Point",
* "coordinates": [100, 0]
* };
* var line = {
* "type": "LineString",
* "coordinates": [ [101, 0], [102, 1] ]
* };
* var collection = turf.geometryCollection([pt, line]);
*
* //=collection
*/
function geometryCollection(geometries, properties) {
if (!geometries) throw new Error('geometries is required');
return feature({
type: 'GeometryCollection',
geometries: geometries
}, properties);
}
// https://en.wikipedia.org/wiki/Great-circle_distance#Radius_for_spherical_Earth
var factors = {
miles: 3960,
nauticalmiles: 3441.145,
degrees: 57.2957795,
radians: 1,
inches: 250905600,
yards: 6969600,
meters: 6373000,
metres: 6373000,
centimeters: 6.373e+8,
centimetres: 6.373e+8,
kilometers: 6373,
kilometres: 6373,
feet: 20908792.65
};
var areaFactors = {
kilometers: 0.000001,
kilometres: 0.000001,
meters: 1,
metres: 1,
centimetres: 10000,
millimeter: 1000000,
acres: 0.000247105,
miles: 3.86e-7,
yards: 1.195990046,
feet: 10.763910417,
inches: 1550.003100006
};
/**
* Round number to precision
*
* @param {number} num Number
* @param {number} [precision=0] Precision
* @returns {number} rounded number
* @example
* round(120.4321)
* //=120
*
* round(120.4321, 2)
* //=120.43
*/
function round(num, precision) {
if (num === undefined || num === null || isNaN(num)) throw new Error('num is required');
if (precision && !(precision >= 0)) throw new Error('precision must be a positive number');
var multiplier = Math.pow(10, precision || 0);
return Math.round(num * multiplier) / multiplier;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from radians to a more friendly unit.
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @name radiansToDistance
* @param {number} radians in radians across the sphere
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} distance
*/
function radiansToDistance(radians, units) {
if (radians === undefined || radians === null) throw new Error('radians is required');
var factor = factors[units || 'kilometers'];
if (!factor) throw new Error('units is invalid');
return radians * factor;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from a real-world unit into radians
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @name distanceToRadians
* @param {number} distance in real units
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} radians
*/
function distanceToRadians(distance, units) {
if (distance === undefined || distance === null) throw new Error('distance is required');
var factor = factors[units || 'kilometers'];
if (!factor) throw new Error('units is invalid');
return distance / factor;
}
/**
* Convert a distance measurement (assuming a spherical Earth) from a real-world unit into degrees
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, centimeters, kilometres, feet
*
* @name distanceToDegrees
* @param {number} distance in real units
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
* @returns {number} degrees
*/
function distanceToDegrees(distance, units) {
return radians2degrees(distanceToRadians(distance, units));
}
/**
* Converts any bearing angle from the north line direction (positive clockwise)
* and returns an angle between 0-360 degrees (positive clockwise), 0 being the north line
*
* @name bearingToAngle
* @param {number} bearing angle, between -180 and +180 degrees
* @returns {number} angle between 0 and 360 degrees
*/
function bearingToAngle(bearing) {
if (bearing === null || bearing === undefined) throw new Error('bearing is required');
var angle = bearing % 360;
if (angle < 0) angle += 360;
return angle;
}
/**
* Converts an angle in radians to degrees
*
* @name radians2degrees
* @param {number} radians angle in radians
* @returns {number} degrees between 0 and 360 degrees
*/
function radians2degrees(radians) {
if (radians === null || radians === undefined) throw new Error('radians is required');
var degrees = radians % (2 * Math.PI);
return degrees * 180 / Math.PI;
}
/**
* Converts an angle in degrees to radians
*
* @name degrees2radians
* @param {number} degrees angle between 0 and 360 degrees
* @returns {number} angle in radians
*/
function degrees2radians(degrees) {
if (degrees === null || degrees === undefined) throw new Error('degrees is required');
var radians = degrees % 360;
return radians * Math.PI / 180;
}
/**
* Converts a distance to the requested unit.
* Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
*
* @param {number} distance to be converted
* @param {string} originalUnit of the distance
* @param {string} [finalUnit=kilometers] returned unit
* @returns {number} the converted distance
*/
function convertDistance(distance, originalUnit, finalUnit) {
if (distance === null || distance === undefined) throw new Error('distance is required');
if (!(distance >= 0)) throw new Error('distance must be a positive number');
var convertedDistance = radiansToDistance(distanceToRadians(distance, originalUnit), finalUnit || 'kilometers');
return convertedDistance;
}
/**
* Converts a area to the requested unit.
* Valid units: kilometers, kilometres, meters, metres, centimetres, millimeter, acre, mile, yard, foot, inch
* @param {number} area to be converted
* @param {string} [originalUnit=meters] of the distance
* @param {string} [finalUnit=kilometers] returned unit
* @returns {number} the converted distance
*/
function convertArea(area, originalUnit, finalUnit) {
if (area === null || area === undefined) throw new Error('area is required');
if (!(area >= 0)) throw new Error('area must be a positive number');
var startFactor = areaFactors[originalUnit || 'meters'];
if (!startFactor) throw new Error('invalid original units');
var finalFactor = areaFactors[finalUnit || 'kilometers'];
if (!finalFactor) throw new Error('invalid final units');
return (area / startFactor) * finalFactor;
}
module.exports = {
feature: feature,
featureCollection: featureCollection,
geometryCollection: geometryCollection,
point: point,
multiPoint: multiPoint,
lineString: lineString,
multiLineString: multiLineString,
polygon: polygon,
multiPolygon: multiPolygon,
radiansToDistance: radiansToDistance,
distanceToRadians: distanceToRadians,
distanceToDegrees: distanceToDegrees,
radians2degrees: radians2degrees,
degrees2radians: degrees2radians,
bearingToAngle: bearingToAngle,
convertDistance: convertDistance,
convertArea: convertArea,
round: round
};
},{}],10:[function(require,module,exports){
var invariant = require('@turf/invariant');
var getCoord = invariant.getCoord;
var getCoords = invariant.getCoords;
// http://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
// modified from: https://github.com/substack/point-in-polygon/blob/master/index.js
// which was modified from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
/**
* Takes a {@link Point} and a {@link Polygon} or {@link MultiPolygon} and determines if the point resides inside the polygon. The polygon can
* be convex or concave. The function accounts for holes.
*
* @name inside
* @param {Feature<Point>} point input point
* @param {Feature<Polygon|MultiPolygon>} polygon input polygon or multipolygon
* @param {boolean} [ignoreBoundary=false] True if polygon boundary should be ignored when determining if the point is inside the polygon otherwise false.
* @returns {boolean} `true` if the Point is inside the Polygon; `false` if the Point is not inside the Polygon
* @example
* var pt = turf.point([-77, 44]);
* var poly = turf.polygon([[
* [-81, 41],
* [-81, 47],
* [-72, 47],
* [-72, 41],
* [-81, 41]
* ]]);
*
* var isInside = turf.inside(pt, poly);
*
* //addToMap
* pt.properties.isInside = isInside
* var addToMap = [pt, poly]
*/
module.exports = function (point, polygon, ignoreBoundary) {
// validation
if (!point) throw new Error('point is required');
if (!polygon) throw new Error('polygon is required');
var pt = getCoord(point);
var polys = getCoords(polygon);
var type = (polygon.geometry) ? polygon.geometry.type : polygon.type;
var bbox = polygon.bbox;
// Quick elimination if point is not inside bbox
if (bbox && inBBox(pt, bbox) === false) return false;
// normalize to multipolygon
if (type === 'Polygon') polys = [polys];
for (var i = 0, insidePoly = false; i < polys.length && !insidePoly; i++) {
// check if it is in the outer ring first
if (inRing(pt, polys[i][0], ignoreBoundary)) {
var inHole = false;
var k = 1;
// check for the point in any of the holes
while (k < polys[i].length && !inHole) {
if (inRing(pt, polys[i][k], !ignoreBoundary)) {
inHole = true;
}
k++;
}
if (!inHole) insidePoly = true;
}
}
return insidePoly;
};
/**
* inRing
*
* @private
* @param {[number, number]} pt [x,y]
* @param {Array<[number, number]>} ring [[x,y], [x,y],..]
* @param {boolean} ignoreBoundary ignoreBoundary
* @returns {boolean} inRing
*/
function inRing(pt, ring, ignoreBoundary) {
var isInside = false;
if (ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1]) ring = ring.slice(0, ring.length - 1);
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var xi = ring[i][0], yi = ring[i][1];
var xj = ring[j][0], yj = ring[j][1];
var onBoundary = (pt[1] * (xi - xj) + yi * (xj - pt[0]) + yj * (pt[0] - xi) === 0) &&
((xi - pt[0]) * (xj - pt[0]) <= 0) && ((yi - pt[1]) * (yj - pt[1]) <= 0);
if (onBoundary) return !ignoreBoundary;
var intersect = ((yi > pt[1]) !== (yj > pt[1])) &&
(pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi);
if (intersect) isInside = !isInside;
}
return isInside;
}
/**
* inBBox
*
* @private
* @param {[number, number]} pt point [x,y]
* @param {[number, number, number, number]} bbox BBox [west, south, east, north]
* @returns {boolean} true/false if point is inside BBox
*/
function inBBox(pt, bbox) {
return bbox[0] <= pt[0] &&
bbox[1] <= pt[1] &&
bbox[2] >= pt[0] &&
bbox[3] >= pt[1];
}
},{"@turf/invariant":11}],11:[function(require,module,exports){
/**
* Unwrap a coordinate from a Point Feature, Geometry or a single coordinate.
*
* @name getCoord
* @param {Array<any>|Geometry|Feature<Point>} obj any value
* @returns {Array<number>} coordinates
*/
function getCoord(obj) {
if (!obj) throw new Error('No obj passed');
var coordinates = getCoords(obj);
// getCoord() must contain at least two numbers (Point)
if (coordinates.length > 1 &&
typeof coordinates[0] === 'number' &&
typeof coordinates[1] === 'number') {
return coordinates;
} else {
throw new Error('Coordinate is not a valid Point');
}
}
/**
* Unwrap coordinates from a Feature, Geometry Object or an Array of numbers
*
* @name getCoords
* @param {Array<any>|Geometry|Feature<any>} obj any value
* @returns {Array<any>} coordinates
*/
function getCoords(obj) {
if (!obj) throw new Error('No obj passed');
var coordinates;
// Array of numbers
if (obj.length) {
coordinates = obj;
// Geometry Object
} else if (obj.coordinates) {
coordinates = obj.coordinates;
// Feature
} else if (obj.geometry && obj.geometry.coordinates) {
coordinates = obj.geometry.coordinates;
}
// Checks if coordinates contains a number
if (coordinates) {
containsNumber(coordinates);
return coordinates;
}
throw new Error('No valid coordinates');
}
/**
* Checks if coordinates contains a number
*
* @name containsNumber
* @param {Array<any>} coordinates GeoJSON Coordinates
* @returns {boolean} true if Array contains a number
*/
function containsNumber(coordinates) {
if (coordinates.length > 1 &&
typeof coordinates[0] === 'number' &&
typeof coordinates[1] === 'number') {
return true;
}
if (Array.isArray(coordinates[0]) && coordinates[0].length) {
return containsNumber(coordinates[0]);
}
throw new Error('coordinates must only contain numbers');
}
/**
* Enforce expectations about types of GeoJSON objects for Turf.
*
* @name geojsonType
* @param {GeoJSON} value any GeoJSON object
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} if value is not the expected type.
*/
function geojsonType(value, type, name) {
if (!type || !name) throw new Error('type and name required');
if (!value || value.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + value.type);
}
}
/**
* Enforce expectations about types of {@link Feature} inputs for Turf.
* Internally this uses {@link geojsonType} to judge geometry types.
*
* @name featureOf
* @param {Feature} feature a feature with an expected geometry type
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} error if value is not the expected type.
*/
function featureOf(feature, type, name) {
if (!feature) throw new Error('No feature passed');
if (!name) throw new Error('.featureOf() requires a name');
if (!feature || feature.type !== 'Feature' || !feature.geometry) {
throw new Error('Invalid input to ' + name + ', Feature with geometry required');
}
if (!feature.geometry || feature.geometry.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + feature.geometry.type);
}
}
/**
* Enforce expectations about types of {@link FeatureCollection} inputs for Turf.
* Internally this uses {@link geojsonType} to judge geometry types.
*
* @name collectionOf
* @param {FeatureCollection} featureCollection a FeatureCollection for which features will be judged
* @param {string} type expected GeoJSON type
* @param {string} name name of calling function
* @throws {Error} if value is not the expected type.
*/
function collectionOf(featureCollection, type, name) {
if (!featureCollection) throw new Error('No featureCollection passed');
if (!name) throw new Error('.collectionOf() requires a name');
if (!featureCollection || featureCollection.type !== 'FeatureCollection') {
throw new Error('Invalid input to ' + name + ', FeatureCollection required');
}
for (var i = 0; i < featureCollection.features.length; i++) {
var feature = featureCollection.features[i];
if (!feature || feature.type !== 'Feature' || !feature.geometry) {
throw new Error('Invalid input to ' + name + ', Feature with geometry required');
}
if (!feature.geometry || feature.geometry.type !== type) {
throw new Error('Invalid input to ' + name + ': must be a ' + type + ', given ' + feature.geometry.type);
}
}
}
/**
* Get Geometry from Feature or Geometry Object
*
* @param {Feature<any>|Geometry<any>} geojson GeoJSON Feature or Geometry Object
* @returns {Geometry<any>} GeoJSON Geometry Object
* @throws {Error} if geojson is not a Feature or Geometry Object
* @example
* var point = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [110, 40]
* }
* }
* var geom = invariant.getGeom(point)
* //={"type": "Point", "coordinates": [110, 40]}
*/
function getGeom(geojson) {
if (!geojson) throw new Error('<geojson> is required');
if (geojson.geometry) return geojson.geometry;
if (geojson.coordinates || geojson.geometries) return geojson;
throw new Error('<geojson> must be a Feature or Geometry Object');
}
/**
* Get Geometry Type from Feature or Geometry Object
*
* @param {Feature<any>|Geometry<any>} geojson GeoJSON Feature or Geometry Object
* @returns {string} GeoJSON Geometry Type
* @throws {Error} if geojson is not a Feature or Geometry Object
* @example
* var point = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [110, 40]
* }
* }
* var geom = invariant.getGeom(point)
* //="Point"
*/
function getGeomType(geojson) {
return getGeom(geojson).type;
}
module.exports = {
geojsonType: geojsonType,
collectionOf: collectionOf,
featureOf: featureOf,
getCoord: getCoord,
getCoords: getCoords,
containsNumber: containsNumber,
getGeom: getGeom,
getGeomType: getGeomType
};
},{}],12:[function(require,module,exports){
var flatten = require('@turf/flatten');
var distance = require('@turf/distance');
var meta = require('@turf/meta');
var geomEach = meta.geomEach;
var featureEach = meta.featureEach;
var coordReduce = meta.coordReduce;
var helpers = require('@turf/helpers');
var point = helpers.point;
var lineString = helpers.lineString;
/**
* Takes a {@link LineString} or {@link Polygon} and measures its length in the specified units.
*
* @name lineDistance
* @param {Feature<(LineString|Polygon)>|FeatureCollection<(LineString|Polygon)>} geojson feature to measure
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers
* @returns {number} length feature
* @example
* var line = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "LineString",
* "coordinates": [
* [-77.031669, 38.878605],
* [-77.029609, 38.881946],
* [-77.020339, 38.884084],
* [-77.025661, 38.885821],
* [-77.021884, 38.889563],
* [-77.019824, 38.892368]
* ]
* }
* };
*
* var length = turf.lineDistance(line, 'miles');
*
* //addToMap
* line.properties.distance = length;
* var addToMap = [line];
*/
module.exports = function lineDistance(geojson, units) {
// Input Validation
if (!geojson) throw new Error('geojson is required');
geomEach(geojson, function (geometry) {
if (geometry.type === 'Point') throw new Error('geojson cannot be a Point');
if (geometry.type === 'MultiPoint') throw new Error('geojson cannot be a MultiPoint');
});
// Calculate distance from 2-vertex line segements
return segmentReduce(geojson, function (previousValue, segment) {
var coords = segment.geometry.coordinates;
var start = point(coords[0]);
var end = point(coords[1]);
return previousValue + distance(start, end, units);
}, 0);
};
/**
* Iterate over 2-vertex line segment in any GeoJSON object, similar to Array.forEach()
*
* @private
* @param {FeatureCollection|Feature<any>} geojson any GeoJSON
* @param {Function} callback a method that takes (currentSegment, currentIndex)
* @returns {void}
* @example
* var polygon = {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Polygon",
* "coordinates": [[[-50, 5], [-40, -10], [-50, -10], [-40, 5], [-50, 5]]]
* }
* }
* turf.segmentEach(polygon, function (segment) {
* //= segment
* });
*/
function segmentEach(geojson, callback) {
var count = 0;
featureEach(geojson, function (multiFeature) {
featureEach(flatten(multiFeature), function (feature) {
coordReduce(feature, function (previousCoords, currentCoords) {
var line = lineString([previousCoords, currentCoords], feature.properties);
callback(line, count);
count++;
return currentCoords;
});
});
});
}
/**
* Reduce 2-vertex line segment in any GeoJSON object, similar to Array.reduce()
*
* @private
* @param {FeatureCollection|Feature<any>} geojson any GeoJSON
* @param {Function} callback a method that takes (previousValue, currentSegment, currentIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {void}
*/
function segmentReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
segmentEach(geojson, function (currentSegment, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentSegment;
} else {
previousValue = callback(previousValue, currentSegment, currentIndex);
}
});
return previousValue;
}
},{"@turf/distance":7,"@turf/flatten":8,"@turf/helpers":9,"@turf/meta":13}],13:[function(require,module,exports){
/**
* Callback for coordEach
*
* @private
* @callback coordEachCallback
* @param {Array<number>} currentCoords The current coordinates being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Iterate over coordinates in any GeoJSON object, similar to Array.forEach()
*
* @name coordEach
* @param {FeatureCollection|Geometry|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentCoords, currentIndex)
* @param {boolean} [excludeWrapCoord=false] whether or not to include the final coordinate of LinearRings that wraps the ring in its iteration.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.coordEach(features, function (currentCoords, currentIndex) {
* //=currentCoords
* //=currentIndex
* });
*/
function coordEach(geojson, callback, excludeWrapCoord) {
var i, j, k, g, l, geometry, stopG, coords,
geometryMaybeCollection,
wrapShrink = 0,
currentIndex = 0,
isGeometryCollection,
isFeatureCollection = geojson.type === 'FeatureCollection',
isFeature = geojson.type === 'Feature',
stop = isFeatureCollection ? geojson.features.length : 1;
// This logic may look a little weird. The reason why it is that way
// is because it's trying to be fast. GeoJSON supports multiple kinds
// of objects at its root: FeatureCollection, Features, Geometries.
// This function has the responsibility of handling all of them, and that
// means that some of the `for` loops you see below actually just don't apply
// to certain inputs. For instance, if you give this just a
// Point geometry, then both loops are short-circuited and all we do
// is gradually rename the input until it's called 'geometry'.
//
// This also aims to allocate as few resources as possible: just a
// few numbers and booleans, rather than any temporary arrays as would
// be required with the normalization approach.
for (i = 0; i < stop; i++) {
geometryMaybeCollection = (isFeatureCollection ? geojson.features[i].geometry :
(isFeature ? geojson.geometry : geojson));
isGeometryCollection = geometryMaybeCollection.type === 'GeometryCollection';
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (g = 0; g < stopG; g++) {
geometry = isGeometryCollection ?
geometryMaybeCollection.geometries[g] : geometryMaybeCollection;
coords = geometry.coordinates;
wrapShrink = (excludeWrapCoord &&
(geometry.type === 'Polygon' || geometry.type === 'MultiPolygon')) ?
1 : 0;
if (geometry.type === 'Point') {
callback(coords, currentIndex);
currentIndex++;
} else if (geometry.type === 'LineString' || geometry.type === 'MultiPoint') {
for (j = 0; j < coords.length; j++) {
callback(coords[j], currentIndex);
currentIndex++;
}
} else if (geometry.type === 'Polygon' || geometry.type === 'MultiLineString') {
for (j = 0; j < coords.length; j++)
for (k = 0; k < coords[j].length - wrapShrink; k++) {
callback(coords[j][k], currentIndex);
currentIndex++;
}
} else if (geometry.type === 'MultiPolygon') {
for (j = 0; j < coords.length; j++)
for (k = 0; k < coords[j].length; k++)
for (l = 0; l < coords[j][k].length - wrapShrink; l++) {
callback(coords[j][k][l], currentIndex);
currentIndex++;
}
} else if (geometry.type === 'GeometryCollection') {
for (j = 0; j < geometry.geometries.length; j++)
coordEach(geometry.geometries[j], callback, excludeWrapCoord);
} else {
throw new Error('Unknown Geometry Type');
}
}
}
}
/**
* Callback for coordReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @private
* @callback coordReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {[number, number]} currentCoords The current coordinate being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Reduce coordinates in any GeoJSON object, similar to Array.reduce()
*
* @name coordReduce
* @param {FeatureCollection|Geometry|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentCoords, currentIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @param {boolean} [excludeWrapCoord=false] whether or not to include
* the final coordinate of LinearRings that wraps the ring in its iteration.
* @returns {*} The value that results from the reduction.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.coordReduce(features, function (previousValue, currentCoords, currentIndex) {
* //=previousValue
* //=currentCoords
* //=currentIndex
* return currentCoords;
* });
*/
function coordReduce(geojson, callback, initialValue, excludeWrapCoord) {
var previousValue = initialValue;
coordEach(geojson, function (currentCoords, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentCoords;
} else {
previousValue = callback(previousValue, currentCoords, currentIndex);
}
}, excludeWrapCoord);
return previousValue;
}
/**
* Callback for propEach
*
* @private
* @callback propEachCallback
* @param {*} currentProperties The current properties being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Iterate over properties in any GeoJSON object, similar to Array.forEach()
*
* @name propEach
* @param {FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentProperties, currentIndex)
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {"foo": "bar"},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {"hello": "world"},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.propEach(features, function (currentProperties, currentIndex) {
* //=currentProperties
* //=currentIndex
* });
*/
function propEach(geojson, callback) {
var i;
switch (geojson.type) {
case 'FeatureCollection':
for (i = 0; i < geojson.features.length; i++) {
callback(geojson.features[i].properties, i);
}
break;
case 'Feature':
callback(geojson.properties, 0);
break;
}
}
/**
* Callback for propReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @private
* @callback propReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {*} currentProperties The current properties being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Reduce properties in any GeoJSON object into a single value,
* similar to how Array.reduce works. However, in this case we lazily run
* the reduction, so an array of all properties is unnecessary.
*
* @name propReduce
* @param {FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentProperties, currentIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {"foo": "bar"},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {"hello": "world"},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.propReduce(features, function (previousValue, currentProperties, currentIndex) {
* //=previousValue
* //=currentProperties
* //=currentIndex
* return currentProperties
* });
*/
function propReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
propEach(geojson, function (currentProperties, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentProperties;
} else {
previousValue = callback(previousValue, currentProperties, currentIndex);
}
});
return previousValue;
}
/**
* Callback for featureEach
*
* @private
* @callback featureEachCallback
* @param {Feature<any>} currentFeature The current feature being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Iterate over features in any GeoJSON object, similar to
* Array.forEach.
*
* @name featureEach
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, currentIndex)
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.featureEach(features, function (currentFeature, currentIndex) {
* //=currentFeature
* //=currentIndex
* });
*/
function featureEach(geojson, callback) {
if (geojson.type === 'Feature') {
callback(geojson, 0);
} else if (geojson.type === 'FeatureCollection') {
for (var i = 0; i < geojson.features.length; i++) {
callback(geojson.features[i], i);
}
}
}
/**
* Callback for featureReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @private
* @callback featureReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature<any>} currentFeature The current Feature being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
*/
/**
* Reduce features in any GeoJSON object, similar to Array.reduce().
*
* @name featureReduce
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentFeature, currentIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {"foo": "bar"},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {"hello": "world"},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.featureReduce(features, function (previousValue, currentFeature, currentIndex) {
* //=previousValue
* //=currentFeature
* //=currentIndex
* return currentFeature
* });
*/
function featureReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
featureEach(geojson, function (currentFeature, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentFeature;
} else {
previousValue = callback(previousValue, currentFeature, currentIndex);
}
});
return previousValue;
}
/**
* Get all coordinates from any GeoJSON object.
*
* @name coordAll
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @returns {Array<Array<number>>} coordinate position array
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* var coords = turf.coordAll(features);
* //=coords
*/
function coordAll(geojson) {
var coords = [];
coordEach(geojson, function (coord) {
coords.push(coord);
});
return coords;
}
/**
* Iterate over each geometry in any GeoJSON object, similar to Array.forEach()
*
* @name geomEach
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentGeometry, currentIndex, currentProperties)
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.geomEach(features, function (currentGeometry, currentIndex, currentProperties) {
* //=currentGeometry
* //=currentIndex
* //=currentProperties
* });
*/
function geomEach(geojson, callback) {
var i, j, g, geometry, stopG,
geometryMaybeCollection,
isGeometryCollection,
geometryProperties,
currentIndex = 0,
isFeatureCollection = geojson.type === 'FeatureCollection',
isFeature = geojson.type === 'Feature',
stop = isFeatureCollection ? geojson.features.length : 1;
// This logic may look a little weird. The reason why it is that way
// is because it's trying to be fast. GeoJSON supports multiple kinds
// of objects at its root: FeatureCollection, Features, Geometries.
// This function has the responsibility of handling all of them, and that
// means that some of the `for` loops you see below actually just don't apply
// to certain inputs. For instance, if you give this just a
// Point geometry, then both loops are short-circuited and all we do
// is gradually rename the input until it's called 'geometry'.
//
// This also aims to allocate as few resources as possible: just a
// few numbers and booleans, rather than any temporary arrays as would
// be required with the normalization approach.
for (i = 0; i < stop; i++) {
geometryMaybeCollection = (isFeatureCollection ? geojson.features[i].geometry :
(isFeature ? geojson.geometry : geojson));
geometryProperties = (isFeatureCollection ? geojson.features[i].properties :
(isFeature ? geojson.properties : {}));
isGeometryCollection = geometryMaybeCollection.type === 'GeometryCollection';
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (g = 0; g < stopG; g++) {
geometry = isGeometryCollection ?
geometryMaybeCollection.geometries[g] : geometryMaybeCollection;
if (geometry.type === 'Point' ||
geometry.type === 'LineString' ||
geometry.type === 'MultiPoint' ||
geometry.type === 'Polygon' ||
geometry.type === 'MultiLineString' ||
geometry.type === 'MultiPolygon') {
callback(geometry, currentIndex, geometryProperties);
currentIndex++;
} else if (geometry.type === 'GeometryCollection') {
for (j = 0; j < geometry.geometries.length; j++) {
callback(geometry.geometries[j], currentIndex, geometryProperties);
currentIndex++;
}
} else {
throw new Error('Unknown Geometry Type');
}
}
}
}
/**
* Callback for geomReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @private
* @callback geomReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {*} currentGeometry The current Feature being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
* @param {object} currentProperties The current feature properties being processed.
*/
/**
* Reduce geometry in any GeoJSON object, similar to Array.reduce().
*
* @name geomReduce
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentGeometry, currentIndex, currentProperties)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {"foo": "bar"},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {"hello": "world"},
* "geometry": {
* "type": "Point",
* "coordinates": [36, 53]
* }
* }
* ]
* };
* turf.geomReduce(features, function (previousValue, currentGeometry, currentIndex) {
* //=previousValue
* //=currentGeometry
* //=currentIndex
* return currentGeometry
* });
*/
function geomReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
geomEach(geojson, function (currentGeometry, currentIndex, currentProperties) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentGeometry;
} else {
previousValue = callback(previousValue, currentGeometry, currentIndex, currentProperties);
}
});
return previousValue;
}
/**
* Callback for flattenEach
*
* @private
* @callback flattenEachCallback
* @param {Feature<any>} currentFeature The current flattened feature being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array. Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
* @param {number} currentSubIndex The subindex of the current element being processed in the
* array. Starts at index 0 and increases if the flattened feature was a multi-geometry.
*/
/**
* Iterate over flattened features in any GeoJSON object, similar to
* Array.forEach.
*
* @name flattenEach
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, currentIndex, currentSubIndex)
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {},
* "geometry": {
* "type": "MultiPoint",
* "coordinates": [ [36, 53], [46, 69] ]
* }
* }
* ]
* };
* turf.flattenEach(features, function (currentFeature, currentIndex, currentSubIndex) {
* //=currentFeature
* //=currentIndex
* //=currentSubIndex
* });
*/
function flattenEach(geojson, callback) {
geomEach(geojson, function (geometry, index, properties) {
// Callback for single geometry
switch (geometry.type) {
case 'Point':
case 'LineString':
case 'Polygon':
callback(feature(geometry, properties), index, 0);
return;
}
var geomType;
// Callback for multi-geometry
switch (geometry.type) {
case 'MultiPoint':
geomType = 'Point';
break;
case 'MultiLineString':
geomType = 'LineString';
break;
case 'MultiPolygon':
geomType = 'Polygon';
break;
}
geometry.coordinates.forEach(function (coordinate, subindex) {
var geom = {
type: geomType,
coordinates: coordinate
};
callback(feature(geom, properties), index, subindex);
});
});
}
/**
* Callback for flattenReduce
*
* The first time the callback function is called, the values provided as arguments depend
* on whether the reduce method has an initialValue argument.
*
* If an initialValue is provided to the reduce method:
* - The previousValue argument is initialValue.
* - The currentValue argument is the value of the first element present in the array.
*
* If an initialValue is not provided:
* - The previousValue argument is the value of the first element present in the array.
* - The currentValue argument is the value of the second element present in the array.
*
* @private
* @callback flattenReduceCallback
* @param {*} previousValue The accumulated value previously returned in the last invocation
* of the callback, or initialValue, if supplied.
* @param {Feature<any>} currentFeature The current Feature being processed.
* @param {number} currentIndex The index of the current element being processed in the
* array.Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
* @param {number} currentSubIndex The subindex of the current element being processed in the
* array. Starts at index 0 and increases if the flattened feature was a multi-geometry.
*/
/**
* Reduce flattened features in any GeoJSON object, similar to Array.reduce().
*
* @name flattenReduce
* @param {Geometry|FeatureCollection|Feature<any>} geojson any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentFeature, currentIndex, currentSubIndex)
* @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
* @returns {*} The value that results from the reduction.
* @example
* var features = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "properties": {"foo": "bar"},
* "geometry": {
* "type": "Point",
* "coordinates": [26, 37]
* }
* },
* {
* "type": "Feature",
* "properties": {"hello": "world"},
* "geometry": {
* "type": "MultiPoint",
* "coordinates": [ [36, 53], [46, 69] ]
* }
* }
* ]
* };
* turf.flattenReduce(features, function (previousValue, currentFeature, currentIndex, currentSubIndex) {
* //=previousValue
* //=currentFeature
* //=currentIndex
* //=currentSubIndex
* return currentFeature
* });
*/
function flattenReduce(geojson, callback, initialValue) {
var previousValue = initialValue;
flattenEach(geojson, function (currentFeature, currentIndex, currentSubIndex) {
if (currentIndex === 0 && currentSubIndex === 0 && initialValue === undefined) {
previousValue = currentFeature;
} else {
previousValue = callback(previousValue, currentFeature, currentIndex, currentSubIndex);
}
});
return previousValue;
}
/**
* Create Feature
*
* @private
* @param {Geometry} geometry GeoJSON Geometry
* @param {Object} properties Properties
* @returns {Feature} GeoJSON Feature
*/
function feature(geometry, properties) {
if (!geometry) throw new Error('No geometry passed');
return {
type: 'Feature',
properties: properties || {},
geometry: geometry
};
}
module.exports = {
coordEach: coordEach,
coordReduce: coordReduce,
propEach: propEach,
propReduce: propReduce,
featureEach: featureEach,
featureReduce: featureReduce,
coordAll: coordAll,
geomEach: geomEach,
geomReduce: geomReduce,
flattenEach: flattenEach,
flattenReduce: flattenReduce
};
},{}],14:[function(require,module,exports){
var coordEach = require('@turf/meta').coordEach;
/**
* Takes a GeoJSON Feature or FeatureCollection and truncates the precision of the geometry.
*
* @name truncate
* @param {FeatureCollection|Feature<any>} geojson any GeoJSON Feature, FeatureCollection, Geometry or GeometryCollection.
* @param {number} [precision=6] coordinate decimal precision
* @param {number} [coordinates=3] maximum number of coordinates (primarly used to remove z coordinates)
* @param {boolean} [mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {FeatureCollection|Feature<any>} layer with truncated geometry
* @example
* var point = {
* "type": "Feature",
* "properties": {}
* "geometry": {
* "type": "Point",
* "coordinates": [
* 70.46923055566859,
* 58.11088890802906,
* 1508
* ]
* }
* };
* var truncated = turf.truncate(point);
*
* //addToMap
* var addToMap = [truncated];
*/
module.exports = function (geojson, precision, coordinates, mutate) {
// default params
precision = (precision === undefined || precision === null || isNaN(precision)) ? 6 : precision;
coordinates = (coordinates === undefined || coordinates === null || isNaN(coordinates)) ? 3 : coordinates;
// validation
if (!geojson) throw new Error('<geojson> is required');
if (typeof precision !== 'number') throw new Error('<precision> must be a number');
if (typeof coordinates !== 'number') throw new Error('<coordinates> must be a number');
// prevent input mutation
if (mutate === false || mutate === undefined) geojson = JSON.parse(JSON.stringify(geojson));
var factor = Math.pow(10, precision);
// Truncate Coordinates
coordEach(geojson, function (coords) {
truncate(coords, factor, coordinates);
});
return geojson;
};
/**
* Truncate Coordinates - Mutates coordinates in place
*
* @private
* @param {Array<any>} coords Geometry Coordinates
* @param {number} factor rounding factor for coordinate decimal precision
* @param {number} coordinates maximum number of coordinates (primarly used to remove z coordinates)
* @returns {Array<any>} mutated coordinates
*/
function truncate(coords, factor, coordinates) {
// Remove extra coordinates (usually elevation coordinates and more)
if (coords.length > coordinates) coords.splice(coordinates, coords.length);
// Truncate coordinate decimals
for (var i = 0; i < coords.length; i++) {
coords[i] = Math.round(coords[i] * factor) / factor;
}
return coords;
}
},{"@turf/meta":13}]},{},[2]);
'use strict'
const turf_circle = require('@turf/circle');
const turf_linedistance = require('@turf/line-distance');
const turf_bbox = require('@turf/bbox');
const turf_bbox_poly = require('@turf/bbox-polygon');
const turf_truncate = require('@turf/truncate');
const turf_destination = require('@turf/destination');
const turf_helpers = require('@turf/helpers');
function Circle(center, radius, options) {
this.center = center; //Point geojson feature or array of [long,lat]
this.radius = radius; //Radius of circle
// miles, kilometers, degrees, or radians
this.units = options.units ? options.units : 'kilometers'
//Current zoom level detail of circle
this.zoom = options.zoom ? options.zoom : 8
// JSON Object - property metadata for circle
this.properties = options.properties ? options.properties : {}
this.steps = 100 // Default steps
this.circle_gj = turf_circle(
this.center,
this.radius,
this.steps,
this.units,
this.properties
)
this.controlPoints = [
turf_destination(this.center, this.radius, 0, this.units),
turf_destination(this.center, this.radius, 90, this.units),
turf_destination(this.center, this.radius, 180, this.units),
turf_destination(this.center, this.radius, -90, this.units)
]
this._updateCircle = function() {
this.steps = this._calcSteps(this.zoom)
this.circle_gj = turf_circle(
this.center,
this.radius,
this.steps,
this.units,
this.properties
)
this.controlPoints = [
turf_destination(this.center, this.radius, 0, this.units),
turf_destination(this.center, this.radius, 90, this.units),
turf_destination(this.center, this.radius, 180, this.units),
turf_destination(this.center, this.radius, -90, this.units)
]
}
this._calcSteps = function(zoom) {
if (zoom <= 0.1) { zoom = 0.1 }
var radius_km = turf_helpers.convertDistance(this.radius, this.units, 'kilometers')
this.steps = (Math.sqrt(radius_km * 250) * zoom ^ 2);
}
this._calcSteps(this.zoom)
this.asGeojson = function() {
var feats = this.controlPoints
feats.push(this.circle_gj)
feats.push(turf_helpers.point(this.center, {"type": "center"}))
return turf_helpers.featureCollection(feats)
}
this.updateCenter = function(newCenter) {
this.center = newCenter;
this._updateCircle();
}
this.updateRadius = function(newRadius) {
this.radius = newRadius;
this._updateCircle();
}
this.updateZoom = function(newZoom) {
this.zoom = this._calcSteps(newZoom)
this._updateCircle();
}
this.updateSteps = function(newSteps) {
this.steps = newSteps;
this._updateCircle();
}
this.updateUnits = function(newUnits) {
this.units = newUnits;
this._updateCircle();
}
this.getBounds = function() {
var bbox_poly = turf_truncate(turf_bbox_poly(turf_bbox(this.circle_gj)), 6)
var bounds = [
bbox_poly.geometry.coordinates[0][0][0],
bbox_poly.geometry.coordinates[0][0][1],
bbox_poly.geometry.coordinates[0][2][0],
bbox_poly.geometry.coordinates[0][2][1],
]
return bounds
}
this.getBboxPoly = function() {
return bbox_poly = turf_truncate(turf_bbox_poly(turf_bbox(this.circle_gj)), 6)
}
this.getCenter = function() {
return this.center
}
this.getRadius = function() {
return this.radius
}
this.getControlPoints = function() {
return turf_helpers.featureCollection(this.controlPoints)
}
}
module.exports = exports = Circle;
<!DOCTYPE html>
<html lang='en'>
<head>
<title>Circles in GL JS</title>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='shortcut icon' href='Your favicon path goes here' type='image/x-icon'>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<!-- Mapbox GL-JS CSS -->
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.39.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.39.0/mapbox-gl.css' rel='stylesheet' />
<!-- Mapbox Assembly -->
<link href='https://api.mapbox.com/mapbox-assembly/v0.15.0/assembly.min.css' rel='stylesheet'>
<script async defer src='https://api.mapbox.com/mapbox-assembly/v0.15.0/assembly.js'></script>
<!-- Custom styles -->
<link href='main.css' rel='stylesheet' />
</head>
<body>
<div class='flex-parent viewport-full relative clip'>
<div class='flex-child w-full w300-ml absolute static-ml left bottom'>
<div class='flex-parent flex-parent--column viewport-third h-full-ml hmax-full bg-white scroll-auto'>
<div class='flex-child flex-child--grow p12 scroll-auto'>
<h3 class='txt-m txt-bold mb6'>Mapbox GL Circles</h3>
<p class="py6">Create and edit circle objects in GL JS.</p>
<p class="py6">Edit the circle radius or center by click+hold+drag on a control point.</p>
<div id='circleBounds' class='block mb6 py6 txt-m'></div>
<div id='circleCenter' class='block mb6 py6 txt-m'></div>
<div id='circleRadiusLabel' class='block mb6 py6 txt-m'></div>
</div>
</div>
</div>
<div class='flex-child flex-child--grow viewport-twothirds viewport-full-ml relative'>
<div id='map' class='absolute top bottom right left w-full'></div>
</div>
</div>
<script src='bundle.js'></script>
</body>
</html>
'use strict';
const Circle = require('./Circle.js')
const turf_inside = require('@turf/inside');
const turf_helpers = require('@turf/helpers');
const turf_truncate = require('@turf/truncate');
const turf_distance = require('@turf/distance');
mapboxgl.accessToken = 'your-token';
var mapzoom = 12;
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [-75.343, 39.984],
zoom: mapzoom
});
// Circle Setup
var center = [-75.343, 39.984];
var radius = 3;
var units = 'kilometers';
var properties = { foo: 'bar' };
var myCircle = new Circle(center, radius, {
units: units,
zoom: mapzoom,
properties: properties
});
// DOM elements
var bounds_el = document.getElementById('circleBounds');
var radius_el = document.getElementById('selectRadius');
var drag_el = document.getElementById('selectRadius');
var center_el = document.getElementById('circleCenter');
var radiusLabel_el = document.getElementById('circleRadiusLabel');
bounds_el.innerHTML = 'Bounds: ' + myCircle.getBounds();
center_el.innerHTML = 'Center: ' + myCircle.getCenter();
radiusLabel_el.innerHTML = 'Radius: ' + myCircle.getRadius() + ' ' + units;
// Helper functions
var animateCircle = function() {
//map.on('sourcedata', onSourceData)
map.getSource('circle-1').setData(myCircle.asGeojson());
bounds_el.innerHTML = 'Bounds: ' + myCircle.getBounds();
center_el.innerHTML = 'Center: ' + myCircle.getCenter();
}
var adjustCirclePrecision = function() {
let cur_zoom = map.getZoom();
myCircle.updateZoom(cur_zoom);
animateCircle();
}
var onMoveCircle = function(e) {
let mousePoint = turf_truncate(turf_helpers.point(map.unproject(e.point).toArray()), 6);
myCircle.updateCenter(mousePoint.geometry.coordinates);
animateCircle();
}
var mouseUpCircle = function() {
map.setPaintProperty('circle-center-point', 'circle-color', '#fb6a4a');
map.dragPan.enable();
map.off('mousemove', onMoveCircle);
}
var mouseDownCircle = function(e) {
map.dragPan.disable();
map.setPaintProperty('circle-center-point', 'circle-color', '#a50f15');
map.on('mousemove', onMoveCircle);
map.once('mouseup', mouseUpCircle);
};
var onMovePoint = function(e) {
let clickPoint = map.unproject(e.point).toArray();
myCircle.updateRadius(turf_distance(myCircle.getCenter(), clickPoint, units));
radiusLabel_el.innerHTML = 'Radius: ' + Math.trunc(myCircle.getRadius()) + ' ' + units;
animateCircle();
}
var mouseUpPoint = function() {
map.setPaintProperty('circle-control-points', 'circle-color', 'white');
map.dragPan.enable();
map.off('mousemove', onMovePoint);
}
var mouseDownPoint = function(e) {
map.dragPan.disable();
map.setPaintProperty('circle-control-points', 'circle-color', '#a50f15');
map.on('mousemove', onMovePoint);
map.once('mouseup', mouseUpPoint);
};
var onMousemove = function(e) {
map.off('mousedown', mouseDownCircle);
map.off('mousedown', mouseDownPoint);
let pointFeatures = map.queryRenderedFeatures(e.point, {
layers: ['circle-control-points']
});
let circleFeatures = map.queryRenderedFeatures(e.point, {
layers: ['circle-center-point']
});
if ((!pointFeatures.length) && (!circleFeatures.length)) {
map.getCanvas().style.cursor = '';
return
}
if (pointFeatures.length) {
map.getCanvas().style.cursor = 'pointer';
map.once('mousedown', mouseDownPoint);
} else if (circleFeatures.length) {
map.getCanvas().style.cursor = 'pointer';
map.once('mousedown', mouseDownCircle);
}
}
map.on('load', () => {
map.addSource('circle-1', {
type: "geojson",
data: myCircle.asGeojson(),
buffer: 1
});
map.addLayer({
id: "circle-line",
type: "line",
source: "circle-1",
paint: {
"line-color": "#fb6a4a",
"line-width": {
stops: [
[0, 0.1],
[16, 5]
]
}
},
filter: ["==", "$type", "Polygon"]
}, 'waterway-label')
map.addLayer({
id: "circle-fill",
type: "fill",
source: "circle-1",
paint: {
"fill-color": "#fb6a4a",
"fill-opacity": 0.5
},
filter: ["==", "$type", "Polygon"]
}, 'waterway-label');
map.addLayer({
id: "circle-control-points",
type: "circle",
source: "circle-1",
paint: {
"circle-color": "white",
"circle-radius": {
stops: [
[0, 6],
[4, 10],
[18, 12]
]
},
"circle-stroke-color": "black",
"circle-stroke-width": {
stops: [
[0, 0.1],
[8,1],
[16,4]
]
}
},
filter: ["all", ["==", "$type", "Point"],
["!=", "type", "center"]
]
});
map.addLayer({
id: "circle-center-point",
type: "circle",
source: "circle-1",
paint: {
"circle-color": "#fb6a4a",
"circle-radius": {
stops: [
[0, 6],
[4, 10],
[18, 12]
]
},
"circle-stroke-color": "black",
"circle-stroke-width": {
stops: [
[0, 0.1],
[8,1],
[16,4]
]
}
},
filter: ["all", ["==", "$type", "Point"],
["==", "type", "center"]
]
});
// Add map event listeners
map.on('zoomend', adjustCirclePrecision);
map.on('mousemove', _.debounce(onMousemove, 16))
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment