@visactor/vdataset
Version:
data processing tool
12,917 lines • 430 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VDataset = {}));
})(this, (function (exports) { 'use strict';
var thirdPi = Math.PI / 3,
angles = [0, thirdPi, 2 * thirdPi, 3 * thirdPi, 4 * thirdPi, 5 * thirdPi];
function pointX(d) {
return d[0];
}
function pointY(d) {
return d[1];
}
function hexbin () {
var x0 = 0,
y0 = 0,
x1 = 1,
y1 = 1,
x = pointX,
y = pointY,
r,
dx,
dy;
function hexbin(points) {
var binsById = {},
bins = [],
i,
n = points.length;
for (i = 0; i < n; ++i) {
if (isNaN(px = +x.call(null, point = points[i], i, points)) || isNaN(py = +y.call(null, point, i, points))) continue;
var point,
px,
py,
pj = Math.round(py = py / dy),
pi = Math.round(px = px / dx - (pj & 1) / 2),
py1 = py - pj;
if (Math.abs(py1) * 3 > 1) {
var px1 = px - pi,
pi2 = pi + (px < pi ? -1 : 1) / 2,
pj2 = pj + (py < pj ? -1 : 1),
px2 = px - pi2,
py2 = py - pj2;
if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) pi = pi2 + (pj & 1 ? 1 : -1) / 2, pj = pj2;
}
var id = pi + "-" + pj,
bin = binsById[id];
if (bin) bin.push(point);else {
bins.push(bin = binsById[id] = [point]);
bin.x = (pi + (pj & 1) / 2) * dx;
bin.y = pj * dy;
}
}
return bins;
}
function hexagon(radius) {
var x0 = 0,
y0 = 0;
return angles.map(function (angle) {
var x1 = Math.sin(angle) * radius,
y1 = -Math.cos(angle) * radius,
dx = x1 - x0,
dy = y1 - y0;
x0 = x1, y0 = y1;
return [dx, dy];
});
}
hexbin.hexagon = function (radius) {
return "m" + hexagon(radius == null ? r : +radius).join("l") + "z";
};
hexbin.centers = function () {
var centers = [],
j = Math.round(y0 / dy),
i = Math.round(x0 / dx);
for (var y = j * dy; y < y1 + r; y += dy, ++j) {
for (var x = i * dx + (j & 1) * dx / 2; x < x1 + dx / 2; x += dx) {
centers.push([x, y]);
}
}
return centers;
};
hexbin.mesh = function () {
var fragment = hexagon(r).slice(0, 4).join("l");
return hexbin.centers().map(function (p) {
return "M" + p + "m" + fragment;
}).join("");
};
hexbin.x = function (_) {
return arguments.length ? (x = _, hexbin) : x;
};
hexbin.y = function (_) {
return arguments.length ? (y = _, hexbin) : y;
};
hexbin.radius = function (_) {
return arguments.length ? (r = +_, dx = r * 2 * Math.sin(thirdPi), dy = r * 1.5, hexbin) : r;
};
hexbin.size = function (_) {
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], hexbin) : [x1 - x0, y1 - y0];
};
hexbin.extent = function (_) {
return arguments.length ? (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1], hexbin) : [[x0, y0], [x1, y1]];
};
return hexbin.radius(1);
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function getAugmentedNamespace(n) {
if (n.__esModule) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a () {
if (this instanceof a) {
var args = [null];
args.push.apply(args, arguments);
var Ctor = Function.bind.apply(f, args);
return new Ctor();
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
var eventemitter3 = {exports: {}};
(function (module) {
var has = Object.prototype.hasOwnProperty,
prefix = '~';
/**
* Constructor to create a storage for our `EE` objects.
* An `Events` instance is a plain object whose properties are event names.
*
* @constructor
* @private
*/
function Events() {}
//
// We try to not inherit from `Object.prototype`. In some engines creating an
// instance in this way is faster than calling `Object.create(null)` directly.
// If `Object.create(null)` is not supported we prefix the event names with a
// character to make sure that the built-in object properties are not
// overridden or used as an attack vector.
//
if (Object.create) {
Events.prototype = Object.create(null);
//
// This hack is needed because the `__proto__` property is still inherited in
// some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
//
if (!new Events().__proto__) prefix = false;
}
/**
* Representation of a single event listener.
*
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} [once=false] Specify if the listener is a one-time listener.
* @constructor
* @private
*/
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
/**
* Add a listener for a given event.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} once Specify if the listener is a one-time listener.
* @returns {EventEmitter}
* @private
*/
function addListener(emitter, event, fn, context, once) {
if (typeof fn !== 'function') {
throw new TypeError('The listener must be a function');
}
var listener = new EE(fn, context || emitter, once),
evt = prefix ? prefix + event : event;
if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);else emitter._events[evt] = [emitter._events[evt], listener];
return emitter;
}
/**
* Clear event by name.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} evt The Event name.
* @private
*/
function clearEvent(emitter, evt) {
if (--emitter._eventsCount === 0) emitter._events = new Events();else delete emitter._events[evt];
}
/**
* Minimal `EventEmitter` interface that is molded against the Node.js
* `EventEmitter` interface.
*
* @constructor
* @public
*/
function EventEmitter() {
this._events = new Events();
this._eventsCount = 0;
}
/**
* Return an array listing the events for which the emitter has registered
* listeners.
*
* @returns {Array}
* @public
*/
EventEmitter.prototype.eventNames = function eventNames() {
var names = [],
events,
name;
if (this._eventsCount === 0) return names;
for (name in events = this._events) {
if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
}
if (Object.getOwnPropertySymbols) {
return names.concat(Object.getOwnPropertySymbols(events));
}
return names;
};
/**
* Return the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Array} The registered listeners.
* @public
*/
EventEmitter.prototype.listeners = function listeners(event) {
var evt = prefix ? prefix + event : event,
handlers = this._events[evt];
if (!handlers) return [];
if (handlers.fn) return [handlers.fn];
for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
ee[i] = handlers[i].fn;
}
return ee;
};
/**
* Return the number of listeners listening to a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Number} The number of listeners.
* @public
*/
EventEmitter.prototype.listenerCount = function listenerCount(event) {
var evt = prefix ? prefix + event : event,
listeners = this._events[evt];
if (!listeners) return 0;
if (listeners.fn) return 1;
return listeners.length;
};
/**
* Calls each of the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Boolean} `true` if the event had listeners, else `false`.
* @public
*/
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return false;
var listeners = this._events[evt],
len = arguments.length,
args,
i;
if (listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
switch (len) {
case 1:
return listeners.fn.call(listeners.context), true;
case 2:
return listeners.fn.call(listeners.context, a1), true;
case 3:
return listeners.fn.call(listeners.context, a1, a2), true;
case 4:
return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5:
return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6:
return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len - 1); i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
var length = listeners.length,
j;
for (i = 0; i < length; i++) {
if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
switch (len) {
case 1:
listeners[i].fn.call(listeners[i].context);
break;
case 2:
listeners[i].fn.call(listeners[i].context, a1);
break;
case 3:
listeners[i].fn.call(listeners[i].context, a1, a2);
break;
case 4:
listeners[i].fn.call(listeners[i].context, a1, a2, a3);
break;
default:
if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
/**
* Add a listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.on = function on(event, fn, context) {
return addListener(this, event, fn, context, false);
};
/**
* Add a one-time listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.once = function once(event, fn, context) {
return addListener(this, event, fn, context, true);
};
/**
* Remove the listeners of a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn Only remove the listeners that match this function.
* @param {*} context Only remove the listeners that have this context.
* @param {Boolean} once Only remove one-time listeners.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return this;
if (!fn) {
clearEvent(this, evt);
return this;
}
var listeners = this._events[evt];
if (listeners.fn) {
if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
clearEvent(this, evt);
}
} else {
for (var i = 0, events = [], length = listeners.length; i < length; i++) {
if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
events.push(listeners[i]);
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;else clearEvent(this, evt);
}
return this;
};
/**
* Remove all listeners, or those of the specified event.
*
* @param {(String|Symbol)} [event] The event name.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
var evt;
if (event) {
evt = prefix ? prefix + event : event;
if (this._events[evt]) clearEvent(this, evt);
} else {
this._events = new Events();
this._eventsCount = 0;
}
return this;
};
//
// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
//
// Expose the prefix.
//
EventEmitter.prefixed = prefix;
//
// Allow `EventEmitter` to be imported as module namespace.
//
EventEmitter.EventEmitter = EventEmitter;
//
// Expose the module.
//
{
module.exports = EventEmitter;
}
})(eventemitter3);
var eventemitter3Exports = eventemitter3.exports;
var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
const isType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
var isType$1 = isType;
const isBoolean = function (value) {
let fuzzy = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : !1;
return fuzzy ? "boolean" == typeof value : !0 === value || !1 === value || isType$1(value, "Boolean");
};
var isBoolean$1 = isBoolean;
const isFunction = value => "function" == typeof value;
var isFunction$1 = isFunction;
const isNil = value => null == value;
var isNil$1 = isNil;
const isValid = value => null != value;
var isValid$1 = isValid;
const isObject$1 = value => {
const type = typeof value;
return null !== value && "object" === type || "function" === type;
};
var isObject$2 = isObject$1;
const isObjectLike = value => "object" == typeof value && null !== value;
var isObjectLike$1 = isObjectLike;
const isPlainObject = function (value) {
if (!isObjectLike$1(value) || !isType$1(value, "Object")) return !1;
if (null === Object.getPrototypeOf(value)) return !0;
let proto = value;
for (; null !== Object.getPrototypeOf(proto);) proto = Object.getPrototypeOf(proto);
return Object.getPrototypeOf(value) === proto;
};
var isPlainObject$1 = isPlainObject;
const isString = function (value) {
let fuzzy = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : !1;
const type = typeof value;
return fuzzy ? "string" === type : "string" === type || isType$1(value, "String");
};
var isString$1 = isString;
const isArray = value => Array.isArray ? Array.isArray(value) : isType$1(value, "Array");
var isArray$1 = isArray;
const isArrayLike = function (value) {
return null !== value && "function" != typeof value && Number.isFinite(value.length);
};
var isArrayLike$1 = isArrayLike;
const isDate = value => isType$1(value, "Date");
var isDate$1 = isDate;
const isNumber = function (value) {
let fuzzy = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : !1;
const type = typeof value;
return fuzzy ? "number" === type : "number" === type || isType$1(value, "Number");
};
var isNumber$1 = isNumber;
const isValidNumber = value => isNumber$1(value) && Number.isFinite(value);
var isValidNumber$1 = isValidNumber;
function cloneDeep(value, ignoreWhen, excludeKeys) {
let result;
if (!isValid$1(value) || "object" != typeof value || ignoreWhen && ignoreWhen(value)) return value;
const isArr = isArray$1(value),
length = value.length;
result = isArr ? new Array(length) : "object" == typeof value ? {} : isBoolean$1(value) || isNumber$1(value) || isString$1(value) ? value : isDate$1(value) ? new Date(+value) : void 0;
const props = isArr ? void 0 : Object.keys(Object(value));
let index = -1;
if (result) for (; ++index < (props || value).length;) {
const key = props ? props[index] : index,
subValue = value[key];
excludeKeys && excludeKeys.includes(key.toString()) ? result[key] = subValue : result[key] = cloneDeep(subValue, ignoreWhen, excludeKeys);
}
return result;
}
function baseMerge(target, source) {
let shallowArray = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : !1;
let skipTargetArray = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : !1;
if (source) {
if (target === source) return;
if (isValid$1(source) && "object" == typeof source) {
const iterable = Object(source),
props = [];
for (const key in iterable) props.push(key);
let {
length: length
} = props,
propIndex = -1;
for (; length--;) {
const key = props[++propIndex];
!isValid$1(iterable[key]) || "object" != typeof iterable[key] || skipTargetArray && isArray$1(target[key]) ? assignMergeValue(target, key, iterable[key]) : baseMergeDeep(target, source, key, shallowArray, skipTargetArray);
}
}
}
}
function baseMergeDeep(target, source, key) {
let shallowArray = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : !1;
let skipTargetArray = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : !1;
const objValue = target[key],
srcValue = source[key];
let newValue = source[key],
isCommon = !0;
if (isArray$1(srcValue)) {
if (shallowArray) newValue = [];else if (isArray$1(objValue)) newValue = objValue;else if (isArrayLike$1(objValue)) {
newValue = new Array(objValue.length);
let index = -1;
const length = objValue.length;
for (; ++index < length;) newValue[index] = objValue[index];
}
} else isPlainObject$1(srcValue) ? (newValue = null != objValue ? objValue : {}, "function" != typeof objValue && "object" == typeof objValue || (newValue = {})) : isCommon = !1;
isCommon && baseMerge(newValue, srcValue, shallowArray, skipTargetArray), assignMergeValue(target, key, newValue);
}
function assignMergeValue(target, key, value) {
(void 0 !== value && !eq(target[key], value) || void 0 === value && !(key in target)) && (target[key] = value);
}
function eq(value, other) {
return value === other || Number.isNaN(value) && Number.isNaN(other);
}
function merge$2(target) {
let sourceIndex = -1;
const length = arguments.length <= 1 ? 0 : arguments.length - 1;
for (; ++sourceIndex < length;) {
baseMerge(target, sourceIndex + 1 < 1 || arguments.length <= sourceIndex + 1 ? undefined : arguments[sourceIndex + 1], !0);
}
return target;
}
function array(arr) {
return isValid$1(arr) ? isArray$1(arr) ? arr : [arr] : [];
}
function uniqArray(arr) {
return arr && isArray$1(arr) ? Array.from(new Set(array(arr))) : arr;
}
function flattenArray(arr) {
if (!isArray$1(arr)) return [arr];
const result = [];
for (const value of arr) result.push(...flattenArray(value));
return result;
}
const hasConsole = "undefined" != typeof console;
function log$1(method, level, input) {
const args = [level].concat([].slice.call(input));
hasConsole && console[method].apply(console, args);
}
var LoggerLevel;
!function (LoggerLevel) {
LoggerLevel[LoggerLevel.None = 0] = "None", LoggerLevel[LoggerLevel.Error = 1] = "Error", LoggerLevel[LoggerLevel.Warn = 2] = "Warn", LoggerLevel[LoggerLevel.Info = 3] = "Info", LoggerLevel[LoggerLevel.Debug = 4] = "Debug";
}(LoggerLevel || (LoggerLevel = {}));
class Logger {
static getInstance(level, method) {
return Logger._instance && isNumber$1(level) ? Logger._instance.level(level) : Logger._instance || (Logger._instance = new Logger(level, method)), Logger._instance;
}
static setInstance(logger) {
return Logger._instance = logger;
}
static setInstanceLevel(level) {
Logger._instance ? Logger._instance.level(level) : Logger._instance = new Logger(level);
}
static clearInstance() {
Logger._instance = null;
}
constructor() {
let level = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : LoggerLevel.None;
let method = arguments.length > 1 ? arguments[1] : undefined;
this._onErrorHandler = [], this._level = level, this._method = method;
}
addErrorHandler(handler) {
this._onErrorHandler.find(h => h === handler) || this._onErrorHandler.push(handler);
}
removeErrorHandler(handler) {
const index = this._onErrorHandler.findIndex(h => h === handler);
index < 0 || this._onErrorHandler.splice(index, 1);
}
callErrorHandler() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
this._onErrorHandler.forEach(h => h(...args));
}
canLogInfo() {
return this._level >= LoggerLevel.Info;
}
canLogDebug() {
return this._level >= LoggerLevel.Debug;
}
canLogError() {
return this._level >= LoggerLevel.Error;
}
canLogWarn() {
return this._level >= LoggerLevel.Warn;
}
level(levelValue) {
return arguments.length ? (this._level = +levelValue, this) : this._level;
}
error() {
var _a;
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return this._level >= LoggerLevel.Error && (this._onErrorHandler.length ? this.callErrorHandler(...args) : log$1(null !== (_a = this._method) && void 0 !== _a ? _a : "error", "ERROR", args)), this;
}
warn() {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return this._level >= LoggerLevel.Warn && log$1(this._method || "warn", "WARN", args), this;
}
info() {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return this._level >= LoggerLevel.Info && log$1(this._method || "log", "INFO", args), this;
}
debug() {
for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
return this._level >= LoggerLevel.Debug && log$1(this._method || "log", "DEBUG", args), this;
}
}
Logger._instance = null;
const clamp = function (input, min, max) {
return input < min ? min : input > max ? max : input;
};
var clamp$1 = clamp;
function radianToDegree(radian) {
return 180 * radian / Math.PI;
}
class Matrix {
constructor() {
let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
let c = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
let d = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1;
let e = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
let f = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
this.a = a, this.b = b, this.c = c, this.d = d, this.e = e, this.f = f;
}
equalToMatrix(m2) {
return !(this.e !== m2.e || this.f !== m2.f || this.a !== m2.a || this.d !== m2.d || this.b !== m2.b || this.c !== m2.c);
}
equalTo(a, b, c, d, e, f) {
return !(this.e !== e || this.f !== f || this.a !== a || this.d !== d || this.b !== b || this.c !== c);
}
setValue(a, b, c, d, e, f) {
return this.a = a, this.b = b, this.c = c, this.d = d, this.e = e, this.f = f, this;
}
reset() {
return this.a = 1, this.b = 0, this.c = 0, this.d = 1, this.e = 0, this.f = 0, this;
}
getInverse() {
const a = this.a,
b = this.b,
c = this.c,
d = this.d,
e = this.e,
f = this.f,
m = new Matrix(),
dt = a * d - b * c;
return m.a = d / dt, m.b = -b / dt, m.c = -c / dt, m.d = a / dt, m.e = (c * f - d * e) / dt, m.f = -(a * f - b * e) / dt, m;
}
rotate(rad) {
const c = Math.cos(rad),
s = Math.sin(rad),
m11 = this.a * c + this.c * s,
m12 = this.b * c + this.d * s,
m21 = this.a * -s + this.c * c,
m22 = this.b * -s + this.d * c;
return this.a = m11, this.b = m12, this.c = m21, this.d = m22, this;
}
rotateByCenter(rad, cx, cy) {
const cos = Math.cos(rad),
sin = Math.sin(rad),
rotateM13 = (1 - cos) * cx + sin * cy,
rotateM23 = (1 - cos) * cy - sin * cx,
m11 = cos * this.a - sin * this.b,
m21 = sin * this.a + cos * this.b,
m12 = cos * this.c - sin * this.d,
m22 = sin * this.c + cos * this.d,
m13 = cos * this.e - sin * this.f + rotateM13,
m23 = sin * this.e + cos * this.f + rotateM23;
return this.a = m11, this.b = m21, this.c = m12, this.d = m22, this.e = m13, this.f = m23, this;
}
scale(sx, sy) {
return this.a *= sx, this.b *= sx, this.c *= sy, this.d *= sy, this;
}
setScale(sx, sy) {
return this.b = this.b / this.a * sx, this.c = this.c / this.d * sy, this.a = sx, this.d = sy, this;
}
transform(a, b, c, d, e, f) {
return this.multiply(a, b, c, d, e, f), this;
}
translate(x, y) {
return this.e += this.a * x + this.c * y, this.f += this.b * x + this.d * y, this;
}
transpose() {
const {
a: a,
b: b,
c: c,
d: d,
e: e,
f: f
} = this;
return this.a = b, this.b = a, this.c = d, this.d = c, this.e = f, this.f = e, this;
}
multiply(a2, b2, c2, d2, e2, f2) {
const a1 = this.a,
b1 = this.b,
c1 = this.c,
d1 = this.d,
m11 = a1 * a2 + c1 * b2,
m12 = b1 * a2 + d1 * b2,
m21 = a1 * c2 + c1 * d2,
m22 = b1 * c2 + d1 * d2,
dx = a1 * e2 + c1 * f2 + this.e,
dy = b1 * e2 + d1 * f2 + this.f;
return this.a = m11, this.b = m12, this.c = m21, this.d = m22, this.e = dx, this.f = dy, this;
}
interpolate(m2, t) {
const m = new Matrix();
return m.a = this.a + (m2.a - this.a) * t, m.b = this.b + (m2.b - this.b) * t, m.c = this.c + (m2.c - this.c) * t, m.d = this.d + (m2.d - this.d) * t, m.e = this.e + (m2.e - this.e) * t, m.f = this.f + (m2.f - this.f) * t, m;
}
transformPoint(source, target) {
const {
a: a,
b: b,
c: c,
d: d,
e: e,
f: f
} = this,
dt = a * d - b * c,
nextA = d / dt,
nextB = -b / dt,
nextC = -c / dt,
nextD = a / dt,
nextE = (c * f - d * e) / dt,
nextF = -(a * f - b * e) / dt,
{
x: x,
y: y
} = source;
target.x = x * nextA + y * nextC + nextE, target.y = x * nextB + y * nextD + nextF;
}
onlyTranslate() {
let scale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return this.a === scale && 0 === this.b && 0 === this.c && this.d === scale;
}
clone() {
return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
}
toTransformAttrs() {
const a = this.a,
b = this.b,
c = this.c,
d = this.d,
delta = a * d - b * c,
result = {
x: this.e,
y: this.f,
rotateDeg: 0,
scaleX: 0,
scaleY: 0,
skewX: 0,
skewY: 0
};
if (0 !== a || 0 !== b) {
const r = Math.sqrt(a * a + b * b);
result.rotateDeg = b > 0 ? Math.acos(a / r) : -Math.acos(a / r), result.scaleX = r, result.scaleY = delta / r, result.skewX = (a * c + b * d) / delta, result.skewY = 0;
} else if (0 !== c || 0 !== d) {
const s = Math.sqrt(c * c + d * d);
result.rotateDeg = Math.PI / 2 - (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s)), result.scaleX = delta / s, result.scaleY = s, result.skewX = 0, result.skewY = (a * c + b * d) / delta;
}
return result.rotateDeg = radianToDegree(result.rotateDeg), result;
}
}
function hslToRgb(h, s, l) {
s /= 100, l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs(h / 60 % 2 - 1)),
m = l - c / 2;
let r = 0,
g = 0,
b = 0;
return 0 <= h && h < 60 ? (r = c, g = x, b = 0) : 60 <= h && h < 120 ? (r = x, g = c, b = 0) : 120 <= h && h < 180 ? (r = 0, g = c, b = x) : 180 <= h && h < 240 ? (r = 0, g = x, b = c) : 240 <= h && h < 300 ? (r = x, g = 0, b = c) : 300 <= h && h < 360 && (r = c, g = 0, b = x), r = Math.round(255 * (r + m)), g = Math.round(255 * (g + m)), b = Math.round(255 * (b + m)), {
r: r,
g: g,
b: b
};
}
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
const cMin = Math.min(r, g, b),
cMax = Math.max(r, g, b),
delta = cMax - cMin;
let h = 0,
s = 0,
l = 0;
return h = 0 === delta ? 0 : cMax === r ? (g - b) / delta % 6 : cMax === g ? (b - r) / delta + 2 : (r - g) / delta + 4, h = Math.round(60 * h), h < 0 && (h += 360), l = (cMax + cMin) / 2, s = 0 === delta ? 0 : delta / (1 - Math.abs(2 * l - 1)), s = +(100 * s).toFixed(1), l = +(100 * l).toFixed(1), {
h: h,
s: s,
l: l
};
}
const REG_HEX = /^#([0-9a-f]{3,8})$/,
DEFAULT_COLORS_OPACITY = {
transparent: 4294967040
};
const DEFAULT_COLORS = {
aliceblue: 15792383,
antiquewhite: 16444375,
aqua: 65535,
aquamarine: 8388564,
azure: 15794175,
beige: 16119260,
bisque: 16770244,
black: 0,
blanchedalmond: 16772045,
blue: 255,
blueviolet: 9055202,
brown: 10824234,
burlywood: 14596231,
cadetblue: 6266528,
chartreuse: 8388352,
chocolate: 13789470,
coral: 16744272,
cornflowerblue: 6591981,
cornsilk: 16775388,
crimson: 14423100,
cyan: 65535,
darkblue: 139,
darkcyan: 35723,
darkgoldenrod: 12092939,
darkgray: 11119017,
darkgreen: 25600,
darkgrey: 11119017,
darkkhaki: 12433259,
darkmagenta: 9109643,
darkolivegreen: 5597999,
darkorange: 16747520,
darkorchid: 10040012,
darkred: 9109504,
darksalmon: 15308410,
darkseagreen: 9419919,
darkslateblue: 4734347,
darkslategray: 3100495,
darkslategrey: 3100495,
darkturquoise: 52945,
darkviolet: 9699539,
deeppink: 16716947,
deepskyblue: 49151,
dimgray: 6908265,
dimgrey: 6908265,
dodgerblue: 2003199,
firebrick: 11674146,
floralwhite: 16775920,
forestgreen: 2263842,
fuchsia: 16711935,
gainsboro: 14474460,
ghostwhite: 16316671,
gold: 16766720,
goldenrod: 14329120,
gray: 8421504,
green: 32768,
greenyellow: 11403055,
grey: 8421504,
honeydew: 15794160,
hotpink: 16738740,
indianred: 13458524,
indigo: 4915330,
ivory: 16777200,
khaki: 15787660,
lavender: 15132410,
lavenderblush: 16773365,
lawngreen: 8190976,
lemonchiffon: 16775885,
lightblue: 11393254,
lightcoral: 15761536,
lightcyan: 14745599,
lightgoldenrodyellow: 16448210,
lightgray: 13882323,
lightgreen: 9498256,
lightgrey: 13882323,
lightpink: 16758465,
lightsalmon: 16752762,
lightseagreen: 2142890,
lightskyblue: 8900346,
lightslategray: 7833753,
lightslategrey: 7833753,
lightsteelblue: 11584734,
lightyellow: 16777184,
lime: 65280,
limegreen: 3329330,
linen: 16445670,
magenta: 16711935,
maroon: 8388608,
mediumaquamarine: 6737322,
mediumblue: 205,
mediumorchid: 12211667,
mediumpurple: 9662683,
mediumseagreen: 3978097,
mediumslateblue: 8087790,
mediumspringgreen: 64154,
mediumturquoise: 4772300,
mediumvioletred: 13047173,
midnightblue: 1644912,
mintcream: 16121850,
mistyrose: 16770273,
moccasin: 16770229,
navajowhite: 16768685,
navy: 128,
oldlace: 16643558,
olive: 8421376,
olivedrab: 7048739,
orange: 16753920,
orangered: 16729344,
orchid: 14315734,
palegoldenrod: 15657130,
palegreen: 10025880,
paleturquoise: 11529966,
palevioletred: 14381203,
papayawhip: 16773077,
peachpuff: 16767673,
peru: 13468991,
pink: 16761035,
plum: 14524637,
powderblue: 11591910,
purple: 8388736,
rebeccapurple: 6697881,
red: 16711680,
rosybrown: 12357519,
royalblue: 4286945,
saddlebrown: 9127187,
salmon: 16416882,
sandybrown: 16032864,
seagreen: 3050327,
seashell: 16774638,
sienna: 10506797,
silver: 12632256,
skyblue: 8900331,
slateblue: 6970061,
slategray: 7372944,
slategrey: 7372944,
snow: 16775930,
springgreen: 65407,
steelblue: 4620980,
tan: 13808780,
teal: 32896,
thistle: 14204888,
tomato: 16737095,
turquoise: 4251856,
violet: 15631086,
wheat: 16113331,
white: 16777215,
whitesmoke: 16119285,
yellow: 16776960,
yellowgreen: 10145074
};
function hex(value) {
return ((value = Math.max(0, Math.min(255, Math.round(value) || 0))) < 16 ? "0" : "") + value.toString(16);
}
function rgb(value) {
return isNumber$1(value) ? new RGB(value >> 16, value >> 8 & 255, 255 & value, 1) : isArray$1(value) ? new RGB(value[0], value[1], value[2]) : new RGB(255, 255, 255);
}
function rgba(value) {
return isNumber$1(value) ? new RGB(value >>> 24, value >>> 16 & 255, value >>> 8 & 255, 255 & value) : isArray$1(value) ? new RGB(value[0], value[1], value[2], value[3]) : new RGB(255, 255, 255, 1);
}
function SRGBToLinear(c) {
return c < .04045 ? .0773993808 * c : Math.pow(.9478672986 * c + .0521327014, 2.4);
}
function LinearToSRGB(c) {
return c < .0031308 ? 12.92 * c : 1.055 * Math.pow(c, .41666) - .055;
}
const setHex = (formatValue, forceHex) => {
const isHex = REG_HEX.exec(formatValue);
if (forceHex || isHex) {
const hex = parseInt(isHex[1], 16),
hexLength = isHex[1].length;
return 3 === hexLength ? new RGB((hex >> 8 & 15) + ((hex >> 8 & 15) << 4), (hex >> 4 & 15) + ((hex >> 4 & 15) << 4), (15 & hex) + ((15 & hex) << 4), 1) : 6 === hexLength ? rgb(hex) : 8 === hexLength ? new RGB(hex >> 24 & 255, hex >> 16 & 255, hex >> 8 & 255, (255 & hex) / 255) : null;
}
};
class Color {
static Brighter(source) {
let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return 1 === b ? source : new Color(source).brighter(b).toRGBA();
}
static SetOpacity(source) {
let o = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return 1 === o ? source : new Color(source).setOpacity(o).toRGBA();
}
static getColorBrightness(source) {
let model = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "hsl";
const color = source instanceof Color ? source : new Color(source);
switch (model) {
case "hsv":
default:
return color.getHSVBrightness();
case "hsl":
return color.getHSLBrightness();
case "lum":
return color.getLuminance();
case "lum2":
return color.getLuminance2();
case "lum3":
return color.getLuminance3();
case "wcag":
return color.getLuminanceWCAG();
}
}
static parseColorString(value) {
if (isValid$1(DEFAULT_COLORS_OPACITY[value])) return rgba(DEFAULT_COLORS_OPACITY[value]);
if (isValid$1(DEFAULT_COLORS[value])) return rgb(DEFAULT_COLORS[value]);
const formatValue = `${value}`.trim().toLowerCase(),
hexRes = setHex(formatValue);
if (void 0 !== hexRes) return hexRes;
if (/^(rgb|RGB|rgba|RGBA)/.test(formatValue)) {
const aColor = formatValue.replace(/(?:\(|\)|rgba|RGBA|rgb|RGB)*/g, "").split(",");
return new RGB(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10), parseFloat(aColor[3]));
}
if (/^(hsl|HSL|hsla|HSLA)/.test(formatValue)) {
const aColor = formatValue.replace(/(?:\(|\)|hsla|HSLA|hsl|HSL)*/g, "").split(","),
rgb = hslToRgb(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10));
return new RGB(rgb.r, rgb.g, rgb.b, parseFloat(aColor[3]));
}
}
constructor(value) {
const color = Color.parseColorString(value);
color ? this.color = color : (console.warn(`Warn: 传入${value}无法解析为Color`), this.color = new RGB(255, 255, 255));
}
toRGBA() {
return this.color.formatRgb();
}
toString() {
return this.color.formatRgb();
}
toHex() {
return this.color.formatHex();
}
toHsl() {
return this.color.formatHsl();
}
brighter(k) {
const {
r: r,
g: g,
b: b
} = this.color;
return this.color.r = Math.max(0, Math.min(255, Math.floor(r * k))), this.color.g = Math.max(0, Math.min(255, Math.floor(g * k))), this.color.b = Math.max(0, Math.min(255, Math.floor(b * k))), this;
}
add(color) {
const {
r: r,
g: g,
b: b
} = this.color;
return this.color.r += Math.min(255, r + color.color.r), this.color.g += Math.min(255, g + color.color.g), this.color.b += Math.min(255, b + color.color.b), this;
}
sub(color) {
return this.color.r = Math.max(0, this.color.r - color.color.r), this.color.g = Math.max(0, this.color.g - color.color.g), this.color.b = Math.max(0, this.color.b - color.color.b), this;
}
multiply(color) {
const {
r: r,
g: g,
b: b
} = this.color;
return this.color.r = Math.max(0, Math.min(255, Math.floor(r * color.color.r))), this.color.g = Math.max(0, Math.min(255, Math.floor(g * color.color.g))), this.color.b = Math.max(0, Math.min(255, Math.floor(b * color.color.b))), this;
}
getHSVBrightness() {
return Math.max(this.color.r, this.color.g, this.color.b) / 255;
}
getHSLBrightness() {
return .5 * (Math.max(this.color.r, this.color.g, this.color.b) / 255 + Math.min(this.color.r, this.color.g, this.color.b) / 255);
}
setHsl(h, s, l) {
const opacity = this.color.opacity,
hsl = rgbToHsl(this.color.r, this.color.g, this.color.b),
rgb = hslToRgb(isNil$1(h) ? hsl.h : clamp$1(h, 0, 360), isNil$1(s) ? hsl.s : s >= 0 && s <= 1 ? 100 * s : s, isNil$1(l) ? hsl.l : l <= 1 && l >= 0 ? 100 * l : l);
return this.color = new RGB(rgb.r, rgb.g, rgb.b, opacity), this;
}
setRGB(r, g, b) {
return !isNil$1(r) && (this.color.r = r), !isNil$1(g) && (this.color.g = g), !isNil$1(b) && (this.color.b = b), this;
}
setHex(value) {
const formatValue = `${value}`.trim().toLowerCase(),
res = setHex(formatValue, !0);
return null != res ? res : this;
}
setColorName(name) {
const hex = DEFAULT_COLORS[name.toLowerCase()];
return void 0 !== hex ? this.setHex(hex) : console.warn("THREE.Color: Unknown color " + name), this;
}
setScalar(scalar) {
return this.color.r = scalar, this.color.g = scalar, this.color.b = scalar, this;
}
setOpacity() {
let o = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return this.color.opacity = o, this;
}
getLuminance() {
return (.2126 * this.color.r + .7152 * this.color.g + .0722 * this.color.b) / 255;
}
getLuminance2() {
return (.2627 * this.color.r + .678 * this.color.g + .0593 * this.color.b) / 255;
}
getLuminance3() {
return (.299 * this.color.r + .587 * this.color.g + .114 * this.color.b) / 255;
}
getLuminanceWCAG() {
const RsRGB = this.color.r / 255,
GsRGB = this.color.g / 255,
BsRGB = this.color.b / 255;
let R, G, B;
R = RsRGB <= .03928 ? RsRGB / 12.92 : Math.pow((RsRGB + .055) / 1.055, 2.4), G = GsRGB <= .03928 ? GsRGB / 12.92 : Math.pow((GsRGB + .055) / 1.055, 2.4), B = BsRGB <= .03928 ? BsRGB / 12.92 : Math.pow((BsRGB + .055) / 1.055, 2.4);
return .2126 * R + .7152 * G + .0722 * B;
}
clone() {
return new Color(this.color.toString());
}
copyGammaToLinear(color) {
let gammaFactor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
return this.color.r = Math.pow(color.color.r, gammaFactor), this.color.g = Math.pow(color.color.g, gammaFactor), this.color.b = Math.pow(color.color.b, gammaFactor), this;
}
copyLinearToGamma(color) {
let gammaFactor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
const safeInverse = gammaFactor > 0 ? 1 / gammaFactor : 1;
return this.color.r = Math.pow(color.color.r, safeInverse), this.color.g = Math.pow(color.color.g, safeInverse), this.color.b = Math.pow(color.color.b, safeInverse), this;
}
convertGammaToLinear(gammaFactor) {
return this.copyGammaToLinear(this, gammaFactor), this;
}
convertLinearToGamma(gammaFactor) {
return this.copyLinearToGamma(this, gammaFactor), this;
}
copySRGBToLinear(color) {
return this.color.r = SRGBToLinear(color.color.r), this.color.g = SRGBToLinear(color.color.g), this.color.b = SRGBToLinear(color.color.b), this;
}
copyLinearToSRGB(color) {
return this.color.r = LinearToSRGB(color.color.r), this.color.g = LinearToSRGB(color.color.g), this.color.b = LinearToSRGB(color.color.b), this;
}
convertSRGBToLinear() {
return this.copySRGBToLinear(this), this;
}
convertLinearToSRGB() {
return this.copyLinearToSRGB(this), this;
}
}
class RGB {
constructor(r, g, b, opacity) {
this.r = isNaN(+r) ? 255 : Math.max(0, Math.min(255, +r)), this.g = isNaN(+g) ? 255 : Math.max(0, Math.min(255, +g)), this.b = isNaN(+b) ? 255 : Math.max(0, Math.min(255, +b)), isValid$1(opacity) ? this.opacity = isNaN(+opacity) ? 1 : Math.max(0, Math.min(1, +opacity)) : this.opacity = 1;
}
formatHex() {
return `#${hex(this.r) + hex(this.g) + hex(this.b) + (1 === this.opacity ? "" : hex(255 * this.opacity))}`;
}
formatRgb() {
const opacity = this.opacity;
return `${1 === opacity ? "rgb(" : "rgba("}${this.r},${this.g},${this.b}${1 === opacity ? ")" : `,${opacity})`}`;
}
formatHsl() {
const opacity = this.opacity,
{
h: h,
s: s,
l: l
} = rgbToHsl(this.r, this.g, this.b);
return `${1 === opacity ? "hsl(" : "hsla("}${h},${s}%,${l}%${1 === opacity ? ")" : `,${opacity})`}`;
}
toString() {
return this.formatHex();
}
}
function toCamelCase(str) {
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* @module helpers
*/
/**
* Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}.
*
* @name feature
* @param {Geometry} geometry input geometry
* @param {Object} [properties={}] an Object of key-value pairs to add as properties
* @param {Object} [options={}] Optional Parameters
* @param {Array<number>} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {Feature} a GeoJSON Feature
* @example
* var geometry = {
* "type": "Point",
* "coordinates": [110, 50]
* };
*
* var feature = turf.feature(geometry);
*
* //=feature
*/
function feature$2(geom, properties, options) {
if (options === void 0) {
options = {};
}
var feat = {
type: "Feature"
};
if (options.id === 0 || options.id) {
feat.id = options.id;
}
if (options.bbox) {
feat.bbox = options.bbox;
}
feat.properties = properties || {};
feat.geometry = geom;
return feat;
}
/**
* Takes one or more {@link Feature|Features} and creates a {@link FeatureCollection}.
*
* @name featureCollection
* @param {Feature[]} features input features
* @param {Object} [options={}] Optional Parameters
* @param {Array<number>} [options.bbox] Bounding Box Array [west, south, east, north] associated with the Feature
* @param {string|number} [options.id] Identifier associated with the Feature
* @returns {FeatureCollection} FeatureCollection of Features
* @example
* var locationA = turf.point([-75.343, 39.984], {name: 'Location A'});
* var locationB = turf.point([-75.833, 39.284], {name: 'Location B'});
* var locationC = turf.point([-75.534, 39.123], {name: 'Location C'});
*
* var collection = turf.featureCollection([
* locationA,
* locationB,
* locationC
* ]);
*
* //=collection
*/
function featureCollection(features, options) {
if (options === void 0) {
options = {};
}
var fc = {
type: "FeatureCollection"
};
if (options.id) {
fc.id = options.id;
}
if (options.bbox) {
fc.bbox = options.bbox;
}
fc.features = features;
return fc;
}
/**
* isObject
*
* @param {*} input variable to validate
* @returns {boolean} true/false
* @example
* turf.isObject({elevation: 10})
* //=true
* turf.isObject('foo')
* //=false
*/
function isObject(input) {
return !!input && input.constructor === Object;
}
function colorLinearGenerator(startColor, endColor, data, field) {
if (!startColor) {
console.warn(`Warn: 颜色 range 未传入 startColor`);
return;
}
if (!endColor) {
const colorObj = ColorObjGenerator(startColor);
data.forEach((item, index) => {
item.colorObj = colorObj;
});
return;
}
const { color: startColorObj, opacity: startOpacity } = ColorObjGenerator(startColor);
const { r: startR, g: startG, b: startB } = startColorObj.color;
const { color: endColorObj, opacity: endOpacity } = ColorObjGenerator(endColor);
const { r: endR, g: endG, b: endB } = endColorObj.color;
const dR = endR - startR;
const dG = endG - startG;
const dB = endB - startB;
const dA = endOpacity - startOpacity;
const total = data.length;
if (total === 0) {
return;
}
if (total === 1) {
data[0].colorObj = {
color: new Color(new RGB(startR, startG, startB).toString()),
transparent: true,
opacity: startOpacity
};
return;
}
else if (total === 2) {
data[0].colorObj = {
color: new Color(new RGB(startR, startG, startB).toString()),
transparent: true,
opacity: startOpacity
};
data[1].colorObj = {
color: new Color(new RGB(endR, endG, endB).toString()),
transparent: true,
opacity: endOpacity
};
return;
}
const dValue = data[total - 1][field] - data[0][field];
data.forEach((item, index) => {
const step = dValue === 0 ? 0 : (item[field] - data[0][field]) / dValue;
const color = `rgba(${Math.floor((startR + dR * step) * 255)},${Math.floor((startG + dG * step) * 255)},${Math.floor((startB + dB * step) * 255)}, ${startOpacity + dA * step})`;
const colorObj = ColorObjGenerator(color);
item.colorObj = colorObj;
});
return;
}
function ColorObjGenerator(color) {
const reg = /^(rgba|RGBA)/;
let rgbaColor;
if (reg.test(color)) {
rgbaColor = rgbaStr2RgbaObj(color);
}
return {
color: new Color(color),
transparent: !!rgbaColor,
opacity: rgbaColor ? rgbaColor.a : 1
};
}
function rgbaStr2RgbaObj(color) {
const colorArr = color.replace(/(?:\(|\)|rgba|RGBA)*/g, '').split(',');
return {
r: Number(colorArr[0]),
g: Number(colorArr[1]),
b: Number(colorArr[2]),
a: Number(colorArr[3])
};
}
const point_hex_corner = (centerPoints, size, z, angle = 0) => {
const position = [];
const colors = [];
let init_indexes = [0, 1, 2, 2, 3, 4, 4, 5, 0, 0, 2, 4];
const last_indexes = [];
const offset = [];
centerPoints.forEach((center, index) => {
const offetX = center.hexCenterCoord[0] - centerPoints[0].hexCenterCoord[0];
const offetY = center.hexCenterCoord[1] - centerPoints[0].hexCenterCoord[1];
offset.push(offetX, offetY, z);
for (let i = 0; i < 6; i++) {
const angle_deg = 60 * i - 30 + angle;
const angle_rad = (Math.PI / 180) * angle_deg;
position.push(center.hexCenterCoord[0] + size * Math.cos(angle_rad), center.hexCenterCoord[1] + size * Math.sin(angle_rad), z);
}
if (index === 0) {
last_indexes.push(...init_indexes);
}
else {
init_indexes = init_indexes.map(item => item + 6);
last_indexes.push(...init_indexes);
}
const { color, opacity } = center.colorObj;
const arrColor = [color.r, color.g, color.b, opacity];
colors.push(...arrColor);
});
return {
position,
indexes: last_indexes,
centerPoints: [...centerPoints[0].hexCenterCoord, z],
colors,
offset
};
};
const pointToHexbin = (data, options) => {
const { size = 10, angle = 0, calcMethod = 'sum', padding = 0, field = 'value', colorConfig } = options;
if (data.length === 0) {
return null;
}
const { type, field: colorField, range } = colorConfig;
const transformHexbin = hexbin()
.radius(size)
.x(c => c.coordinates[0])
.y(c => c.coordinates[1]);
const centerPoints = transformHexbin(data).map((hex, index) => {
const calcFields = hex.map((item) => item[field]);
const calcValue = calcFields.reduce((acc, curr) => acc + curr);
return {
_id: index,
hexCenterCoord: [hex.x, hex.y],
rawData: hex,
count: hex.length,
[field]: calcValue
};
});
centerPoints.sort((a, b) => {
return a[field] - b[field];
});
colorLinearGenerator(range[0], range[1], centerPoints, field);
const hex = point_hex_corner(centerPoints, size - padding, 0, angle);
return hex;
};
const filter = (data, options) => {
const { callback } = options;
if (callback) {
data = data.filter(callback);
}
return data;
};
// Adds floating point numbers with twice the normal precision.
// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and
// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3)
// 305–363 (1997).
// Code adapted from GeographicLib by Charles F. F. Karney,
// http://geographiclib.sourceforge.net/
function adder () {
return new Adder();
}
function Adder() {
this.reset();
}
Adder.prototype = {
constructor: Adder,
reset: function () {
this.s =
// rounded value
this.t = 0; // exact error
},
add: function (y) {
add(temp, y, this.t);
add(this, temp.s, this.s);
if (this.s) this.t += temp.t;else this.s = temp.t;
},
valueOf: function () {
return this.s;
}
};
var temp = new Adder();
function add(adder, a, b) {
var x = adder.s = a + b,
bv = x - a,
av = x - bv;
adder.t = a - av + (b - bv);
}
var epsilon$1 = 1e-6;
var pi = Math.PI;
var halfPi = pi / 2;
var quarterPi = pi / 4;
var tau = pi * 2;
var degrees = 180 / pi;
var radians = pi / 180;
var abs = Math.abs;
var atan = Math.atan;
var atan2 = Math.atan2;
var cos = Math.cos;
var exp = Math.exp;
var log = Math.log;
var sin = Math.sin;
var sign$1 = Math.sign || function (x) {
return x > 0 ? 1 : x < 0 ? -1 : 0;
};
var sqrt = Math.sqrt;
var tan = Math.tan;
function acos(x) {
return x > 1 ? 0 : x < -1 ? pi : Math.acos(x);
}
function asin(x) {
return x > 1 ? halfPi : x < -1 ? -halfPi : Math.asin(x);
}
function noop() {}
function streamGeometry(geometry, stream) {
if (geometry && streamGeometryType.hasOwnProperty(geometry.type)) {
streamGeometryType[geometry.type](geometry, stream);
}
}
var streamObjectType = {
Feature: function (object, stream) {
streamGeometry(object.geometry, stream);
},
FeatureCollection: function (object, stream) {
var features = object.features,
i = -1,
n = features.length;
while (++i < n) streamGeometry(features[i].geometry, stream);
}
};
var streamGeometryType = {
Sphere: function (object, stream) {
stream.sphere();
},
Point: function (object, stream) {
object = object.coordinates;
stream.point(object[0], object[1], object[2]);
},
MultiPoint: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) object = coordinates[i], stream.point(object[0], object[1], object[2]);
},
LineString: function (object, stream) {
streamLine(object.coordinates, stream, 0);
},
MultiLineString: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) streamLine(coordinates[i], stream, 0);
},
Polygon: function (object, stream) {
streamPolygon(object.coordinates, stream);
},
MultiPolygon: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) streamPolygon(coordinates[i], stream);
},
GeometryCollection: function (object, stream) {
var geometries = object.geometries,
i = -1,
n = geometries.length;
while (++i < n) streamGeometry(geometries[i], stream);
}
};
function streamLine(coordinates, stream, closed) {
var i = -1,
n = coordinates.length - closed,
coordinate;
stream.lineStart();
while (++i < n) coordinate = coordinates[i], stream.point(coordinate[0], coordinate[1], coordinate[2]);
stream.lineEnd();
}
function streamPolygon(coordinates, stream) {
var i = -1,
n = coordinates.length;
stream.polygonStart();
while (++i < n) streamLine(coordinates[i], stream, 1);
stream.polygonEnd();
}
function geoStream (object, stream) {
if (object && streamObjectType.hasOwnProperty(object.type)) {
streamObjectType[object.type](object, stream);
} else {
streamGeometry(object, stream);
}
}
function spherical(cartesian) {
return [atan2(cartesian[1], cartesian[0]), asin(cartesian[2])];
}
function cartesian(spherical) {
var lambda = spherical[0],
phi = spherical[1],
cosPhi = cos(phi);
return [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)];
}
function cartesianDot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
function cartesianCross(a, b) {
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
}
// TODO return a
function cartesianAddInPlace(a, b) {
a[0] += b[0], a[1] += b[1], a[2] += b[2];
}
function cartesianScale(vector, k) {
return [vector[0] * k, vector[1] * k, vector[2] * k];
}
// TODO return d
function cartesianNormalizeInPlace(d) {
var l = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
d[0] /= l, d[1] /= l, d[2] /= l;
}
function compose (a, b) {
function compose(x, y) {
return x = a(x, y), b(x[0], x[1]);
}
if (a.invert && b.invert) compose.invert = function (x, y) {
return x = b.invert(x, y), x && a.invert(x[0], x[1]);
};
return compose;
}
function rotationIdentity(lambda, phi) {
return [abs(lambda) > pi ? lambda + Math.round(-lambda / tau) * tau : lambda, phi];
}
rotationIdentity.invert = rotationIdentity;
function rotateRadians(deltaLambda, deltaPhi, deltaGamma) {
return (deltaLambda %= tau) ? deltaPhi || deltaGamma ? compose(rotationLambda(deltaLambda), rotationPhiGamma(deltaPhi, deltaGamma)) : rotationLambda(deltaLambda) : deltaPhi || deltaGamma ? rotationPhiGamma(deltaPhi, deltaGamma) : rotationIdentity;
}
function forwardRotationLambda(deltaLambda) {
return function (lambda, phi) {
return lambda += deltaLambda, [lambda > pi ? lambda - tau : lambda < -pi ? lambda + tau : lambda, phi];
};
}
function rotationLambda(deltaLambda) {
var rotation = forwardRotationLambda(deltaLambda);
rotation.invert = forwardRotationLambda(-deltaLambda);
return rotation;
}
function rotationPhiGamma(deltaPhi, deltaGamma) {
var cosDeltaPhi = cos(deltaPhi),
sinDeltaPhi = sin(deltaPhi),
cosDeltaGamma = cos(deltaGamma),
sinDeltaGamma = sin(deltaGamma);
function rotation(lambda, phi) {
var cosPhi = cos(phi),
x = cos(lambda) * cosPhi,
y = sin(lambda) * cosPhi,
z = sin(phi),
k = z * cosDeltaPhi + x * sinDeltaPhi;
return [atan2(y * cosDeltaGamma - k * sinDeltaGamma, x * cosDeltaPhi - z * sinDeltaPhi), asin(k * cosDeltaGamma + y * sinDeltaGamma)];
}
rotation.invert = function (lambda, phi) {
var cosPhi = cos(phi),
x = cos(lambda) * cosPhi,
y = sin(lambda) * cosPhi,
z = sin(phi),
k = z * cosDeltaGamma - y * sinDeltaGamma;
return [atan2(y * cosDeltaGamma + z * sinDeltaGamma, x * cosDeltaPhi + k * sinDeltaPhi), asin(k * cosDeltaPhi - x * sinDeltaPhi)];
};
return rotation;
}
function rotation (rotate) {
rotate = rotateRadians(rotate[0] * radians, rotate[1] * radians, rotate.length > 2 ? rotate[2] * radians : 0);
function forward(coordinates) {
coordinates = rotate(coordinates[0] * radians, coordinates[1] * radians);
return coordinates[0] *= degrees, coordinates[1] *= degrees, coordinates;
}
forward.invert = function (coordinates) {
coordinates = rotate.invert(coordinates[0] * radians, coordinates[1] * radians);
return coordinates[0] *= degrees, coordinates[1] *= degrees, coordinates;
};
return forward;
}
// Generates a circle centered at [0°, 0°], with a given radius and precision.
function circleStream(stream, radius, delta, direction, t0, t1) {
if (!delta) return;
var cosRadius = cos(radius),
sinRadius = sin(radius),
step = direction * delta;
if (t0 == null) {
t0 = radius + direction * tau;
t1 = radius - step / 2;
} else {
t0 = circleRadius(cosRadius, t0);
t1 = circleRadius(cosRadius, t1);
if (direction > 0 ? t0 < t1 : t0 > t1) t0 += direction * tau;
}
for (var point, t = t0; direction > 0 ? t > t1 : t < t1; t -= step) {
point = spherical([cosRadius, -sinRadius * cos(t), -sinRadius * sin(t)]);
stream.point(point[0], point[1]);
}
}
// Returns the signed angle of a cartesian point relative to [cosRadius, 0, 0].
function circleRadius(cosRadius, point) {
point = cartesian(point), point[0] -= cosRadius;
cartesianNormalizeInPlace(point);
var radius = acos(-point[1]);
return ((-point[2] < 0 ? -radius : radius) + tau - epsilon$1) % tau;
}
function clipBuffer () {
var lines = [],
line;
return {
point: function (x, y, m) {
line.push([x, y, m]);
},
lineStart: function () {
lines.push(line = []);
},
lineEnd: noop,
rejoin: function () {
if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
},
result: function () {
var result = lines;
lines = [];
line = null;
return result;
}
};
}
function pointEqual (a, b) {
return abs(a[0] - b[0]) < epsilon$1 && abs(a[1] - b[1]) < epsilon$1;
}
function Intersection(point, points, other, entry) {
this.x = point;
this.z = points;
this.o = other; // another intersection
this.e = entry; // is an entry?
this.v = false; // visited
this.n = this.p = null; // next & previous
}
// A generalized polygon clipping algorithm: given a polygon that has been cut
// into its visible line segments, and rejoins the segments by interpolating
// along the clip edge.
function clipRejoin (segments, compareIntersection, startInside, interpolate, stream) {
var subject = [],
clip = [],
i,
n;
segments.forEach(function (segment) {
if ((n = segment.length - 1) <= 0) return;
var n,
p0 = segment[0],
p1 = segment[n],
x;
if (pointEqual(p0, p1)) {
if (!p0[2] && !p1[2]) {
stream.lineStart();
for (i = 0; i < n; ++i) stream.point((p0 = segment[i])[0], p0[1]);
stream.lineEnd();
return;
}
// handle degenerate cases by moving the point
p1[0] += 2 * epsilon$1;
}
subject.push(x = new Intersection(p0, segment, null, true));
clip.push(x.o = new Intersection(p0, null, x, false));
subject.push(x = new Intersection(p1, segment, null, false));
clip.push(x.o = new Intersection(p1, null, x, true));
});
if (!subject.length) return;
clip.sort(compareIntersection);
link(subject);
link(clip);
for (i = 0, n = clip.length; i < n; ++i) {
clip[i].e = startInside = !startInside;
}
var start = subject[0],
points,
point;
while (1) {
// Find first unvisited intersection.
var current = start,
isSubject = true;
while (current.v) if ((current = current.n) === start) return;
points = current.z;
stream.lineStart();
do {
current.v = current.o.v = true;
if (current.e) {
if (isSubject) {
for (i = 0, n = points.length; i < n; ++i) stream.point((point = points[i])[0], point[1]);
} else {
interpolate(current.x, current.n.x, 1, stream);
}
current = current.n;
} else {
if (isSubject) {
points = current.p.z;
for (i = points.length - 1; i >= 0; --i) stream.point((point = points[i])[0], point[1]);
} else {
interpolate(current.x, current.p.x, -1, stream);
}
current = current.p;
}
current = current.o;
points = current.z;
isSubject = !isSubject;
} while (!current.v);
stream.lineEnd();
}
}
function link(array) {
if (!(n = array.length)) return;
var n,
i = 0,
a = array[0],
b;
while (++i < n) {
a.n = b = array[i];
b.p = a;
a = b;
}
a.n = b = array[0];
b.p = a;
}
var sum$1 = adder();
function longitude(point) {
if (abs(point[0]) <= pi) return point[0];else return sign$1(point[0]) * ((abs(point[0]) + pi) % tau - pi);
}
function polygonContains (polygon, point) {
var lambda = longitude(point),
phi = point[1],
sinPhi = sin(phi),
normal = [sin(lambda), -cos(lambda), 0],
angle = 0,
winding = 0;
sum$1.reset();
if (sinPhi === 1) phi = halfPi + epsilon$1;else if (sinPhi === -1) phi = -halfPi - epsilon$1;
for (var i = 0, n = polygon.length; i < n; ++i) {
if (!(m = (ring = polygon[i]).length)) continue;
var ring,
m,
point0 = ring[m - 1],
lambda0 = longitude(point0),
phi0 = point0[1] / 2 + quarterPi,
sinPhi0 = sin(phi0),
cosPhi0 = cos(phi0);
for (var j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) {
var point1 = ring[j],
lambda1 = longitude(point1),
phi1 = point1[1] / 2 + quarterPi,
sinPhi1 = sin(phi1),
cosPhi1 = cos(phi1),
delta = lambda1 - lambda0,
sign = delta >= 0 ? 1 : -1,
absDelta = sign * delta,
antimeridian = absDelta > pi,
k = sinPhi0 * sinPhi1;
sum$1.add(atan2(k * sign * sin(absDelta), cosPhi0 * cosPhi1 + k * cos(absDelta)));
angle += antimeridian ? delta + sign * tau : delta;
// Are the longitudes either side of the point’s meridian (lambda),
// and are the latitudes smaller than the parallel (phi)?
if (antimeridian ^ lambda0 >= lambda ^ lambda1 >= lambda) {
var arc = cartesianCross(cartesian(point0), cartesian(point1));
cartesianNormalizeInPlace(arc);
var intersection = cartesianCross(normal, arc);
cartesianNormalizeInPlace(intersection);
var phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin(intersection[2]);
if (phi > phiArc || phi === phiArc && (arc[0] || arc[1])) {
winding += antimeridian ^ delta >= 0 ? 1 : -1;
}
}
}
}
// First, determine whether the South pole is inside or outside:
//
// It is inside if:
// * the polygon winds around it in a clockwise direction.
// * the polygon does not (cumulatively) wind around it, but has a negative
// (counter-clockwise) area.
//
// Second, count the (signed) number of times a segment crosses a lambda
// from the point to the South pole. If it is zero, then the point is the
// same side as the South pole.
return (angle < -epsilon$1 || angle < epsilon$1 && sum$1 < -epsilon$1) ^ winding & 1;
}
function ascending (a, b) {
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
function bisector (compare) {
if (compare.length === 1) compare = ascendingComparator(compare);
return {
left: function (a, x, lo, hi) {
if (lo == null) lo = 0;
if (hi == null) hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid;
}
return lo;
},
right: function (a, x, lo, hi) {
if (lo == null) lo = 0;
if (hi == null) hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1;
}
return lo;
}
};
}
function ascendingComparator(f) {
return function (d, x) {
return ascending(f(d), x);
};
}
bisector(ascending);
function merge$1 (arrays) {
var n = arrays.length,
m,
i = -1,
j = 0,
merged,
array;
while (++i < n) j += arrays[i].length;
merged = new Array(j);
while (--n >= 0) {
array = arrays[n];
m = array.length;
while (--m >= 0) {
merged[--j] = array[m];
}
}
return merged;
}
function clip (pointVisible, clipLine, interpolate, start) {
return function (sink) {
var line = clipLine(sink),
ringBuffer = clipBuffer(),
ringSink = clipLine(ringBuffer),
polygonStarted = false,
polygon,
segments,
ring;
var clip = {
point: point,
lineStart: lineStart,
lineEnd: lineEnd,
polygonStart: function () {
clip.point = pointRing;
clip.lineStart = ringStart;
clip.lineEnd = ringEnd;
segments = [];
polygon = [];
},
polygonEnd: function () {
clip.point = point;
clip.lineStart = lineStart;
clip.lineEnd = lineEnd;
segments = merge$1(segments);
var startInside = polygonContains(polygon, start);
if (segments.length) {
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
clipRejoin(segments, compareIntersection, startInside, interpolate, sink);
} else if (startInside) {
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
sink.lineStart();
interpolate(null, null, 1, sink);
sink.lineEnd();
}
if (polygonStarted) sink.polygonEnd(), polygonStarted = false;
segments = polygon = null;
},
sphere: function () {
sink.polygonStart();
sink.lineStart();
interpolate(null, null, 1, sink);
sink.lineEnd();
sink.polygonEnd();
}
};
function point(lambda, phi) {
if (pointVisible(lambda, phi)) sink.point(lambda, phi);
}
function pointLine(lambda, phi) {
line.point(lambda, phi);
}
function lineStart() {
clip.point = pointLine;
line.lineStart();
}
function lineEnd() {
clip.point = point;
line.lineEnd();
}
function pointRing(lambda, phi) {
ring.push([lambda, phi]);
ringSink.point(lambda, phi);
}
function ringStart() {
ringSink.lineStart();
ring = [];
}
function ringEnd() {
pointRing(ring[0][0], ring[0][1]);
ringSink.lineEnd();
var clean = ringSink.clean(),
ringSegments = ringBuffer.result(),
i,
n = ringSegments.length,
m,
segment,
point;
ring.pop();
polygon.push(ring);
ring = null;
if (!n) return;
// No intersections.
if (clean & 1) {
segment = ringSegments[0];
if ((m = segment.length - 1) > 0) {
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
sink.lineStart();
for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]);
sink.lineEnd();
}
return;
}
// Rejoin connected segments.
// TODO reuse ringBuffer.rejoin()?
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
segments.push(ringSegments.filter(validSegment));
}
return clip;
};
}
function validSegment(segment) {
return segment.length > 1;
}
// Intersections are sorted along the clip edge. For both antimeridian cutting
// and circle clipping, the same comparison is used.
function compareIntersection(a, b) {
return ((a = a.x)[0] < 0 ? a[1] - halfPi - epsilon$1 : halfPi - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfPi - epsilon$1 : halfPi - b[1]);
}
var clipAntimeridian = clip(function () {
return true;
}, clipAntimeridianLine, clipAntimeridianInterpolate, [-pi, -halfPi]);
// Takes a line and cuts into visible segments. Return values: 0 - there were
// intersections or the line was empty; 1 - no intersections; 2 - there were
// intersections, and the first and last segments should be rejoined.
function clipAntimeridianLine(stream) {
var lambda0 = NaN,
phi0 = NaN,
sign0 = NaN,
clean; // no intersections
return {
lineStart: function () {
stream.lineStart();
clean = 1;
},
point: function (lambda1, phi1) {
var sign1 = lambda1 > 0 ? pi : -pi,
delta = abs(lambda1 - lambda0);
if (abs(delta - pi) < epsilon$1) {
// line crosses a pole
stream.point(lambda0, phi0 = (phi0 + phi1) / 2 > 0 ? halfPi : -halfPi);
stream.point(sign0, phi0);
stream.lineEnd();
stream.lineStart();
stream.point(sign1, phi0);
stream.point(lambda1, phi0);
clean = 0;
} else if (sign0 !== sign1 && delta >= pi) {
// line crosses antimeridian
if (abs(lambda0 - sign0) < epsilon$1) lambda0 -= sign0 * epsilon$1; // handle degeneracies
if (abs(lambda1 - sign1) < epsilon$1) lambda1 -= sign1 * epsilon$1;
phi0 = clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1);
stream.point(sign0, phi0);
stream.lineEnd();
stream.lineStart();
stream.point(sign1, phi0);
clean = 0;
}
stream.point(lambda0 = lambda1, phi0 = phi1);
sign0 = sign1;
},
lineEnd: function () {
stream.lineEnd();
lambda0 = phi0 = NaN;
},
clean: function () {
return 2 - clean; // if intersections, rejoin first and last segments
}
};
}
function clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1) {
var cosPhi0,
cosPhi1,
sinLambda0Lambda1 = sin(lambda0 - lambda1);
return abs(sinLambda0Lambda1) > epsilon$1 ? atan((sin(phi0) * (cosPhi1 = cos(phi1)) * sin(lambda1) - sin(phi1) * (cosPhi0 = cos(phi0)) * sin(lambda0)) / (cosPhi0 * cosPhi1 * sinLambda0Lambda1)) : (phi0 + phi1) / 2;
}
function clipAntimeridianInterpolate(from, to, direction, stream) {
var phi;
if (from == null) {
phi = direction * halfPi;
stream.point(-pi, phi);
stream.point(0, phi);
stream.point(pi, phi);
stream.point(pi, 0);
stream.point(pi, -phi);
stream.point(0, -phi);
stream.point(-pi, -phi);
stream.point(-pi, 0);
stream.point(-pi, phi);
} else if (abs(from[0] - to[0]) > epsilon$1) {
var lambda = from[0] < to[0] ? pi : -pi;
phi = direction * lambda / 2;
stream.point(-lambda, phi);
stream.point(0, phi);
stream.point(lambda, phi);
} else {
stream.point(to[0], to[1]);
}
}
function clipCircle (radius) {
var cr = cos(radius),
delta = 6 * radians,
smallRadius = cr > 0,
notHemisphere = abs(cr) > epsilon$1; // TODO optimise for this common case
function interpolate(from, to, direction, stream) {
circleStream(stream, radius, delta, direction, from, to);
}
function visible(lambda, phi) {
return cos(lambda) * cos(phi) > cr;
}
// Takes a line and cuts into visible segments. Return values used for polygon
// clipping: 0 - there were intersections or the line was empty; 1 - no
// intersections 2 - there were intersections, and the first and last segments
// should be rejoined.
function clipLine(stream) {
var point0,
// previous point
c0,
// code for previous point
v0,
// visibility of previous point
v00,
// visibility of first point
clean; // no intersections
return {
lineStart: function () {
v00 = v0 = false;
clean = 1;
},
point: function (lambda, phi) {
var point1 = [lambda, phi],
point2,
v = visible(lambda, phi),
c = smallRadius ? v ? 0 : code(lambda, phi) : v ? code(lambda + (lambda < 0 ? pi : -pi), phi) : 0;
if (!point0 && (v00 = v0 = v)) stream.lineStart();
if (v !== v0) {
point2 = intersect(point0, point1);
if (!point2 || pointEqual(point0, point2) || pointEqual(point1, point2)) point1[2] = 1;
}
if (v !== v0) {
clean = 0;
if (v) {
// outside going in
stream.lineStart();
point2 = intersect(point1, point0);
stream.point(point2[0], point2[1]);
} else {
// inside going out
point2 = intersect(point0, point1);
stream.point(point2[0], point2[1], 2);
stream.lineEnd();
}
point0 = point2;
} else if (notHemisphere && point0 && smallRadius ^ v) {
var t;
// If the codes for two points are different, or are both zero,
// and there this segment intersects with the small circle.
if (!(c & c0) && (t = intersect(point1, point0, true))) {
clean = 0;
if (smallRadius) {
stream.lineStart();
stream.point(t[0][0], t[0][1]);
stream.point(t[1][0], t[1][1]);
stream.lineEnd();
} else {
stream.point(t[1][0], t[1][1]);
stream.lineEnd();
stream.lineStart();
stream.point(t[0][0], t[0][1], 3);
}
}
}
if (v && (!point0 || !pointEqual(point0, point1))) {
stream.point(point1[0], point1[1]);
}
point0 = point1, v0 = v, c0 = c;
},
lineEnd: function () {
if (v0) stream.lineEnd();
point0 = null;
},
// Rejoin first and last segments if there were intersections and the first
// and last points were visible.
clean: function () {
return clean | (v00 && v0) << 1;
}
};
}
// Intersects the great circle between a and b with the clip circle.
function intersect(a, b, two) {
var pa = cartesian(a),
pb = cartesian(b);
// We have two planes, n1.p = d1 and n2.p = d2.
// Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
var n1 = [1, 0, 0],
// normal
n2 = cartesianCross(pa, pb),
n2n2 = cartesianDot(n2, n2),
n1n2 = n2[0],
// cartesianDot(n1, n2),
determinant = n2n2 - n1n2 * n1n2;
// Two polar points.
if (!determinant) return !two && a;
var c1 = cr * n2n2 / determinant,
c2 = -cr * n1n2 / determinant,
n1xn2 = cartesianCross(n1, n2),
A = cartesianScale(n1, c1),
B = cartesianScale(n2, c2);
cartesianAddInPlace(A, B);
// Solve |p(t)|^2 = 1.
var u = n1xn2,
w = cartesianDot(A, u),
uu = cartesianDot(u, u),
t2 = w * w - uu * (cartesianDot(A, A) - 1);
if (t2 < 0) return;
var t = sqrt(t2),
q = cartesianScale(u, (-w - t) / uu);
cartesianAddInPlace(q, A);
q = spherical(q);
if (!two) return q;
// Two intersection points.
var lambda0 = a[0],
lambda1 = b[0],
phi0 = a[1],
phi1 = b[1],
z;
if (lambda1 < lambda0) z = lambda0, lambda0 = lambda1, lambda1 = z;
var delta = lambda1 - lambda0,
polar = abs(delta - pi) < epsilon$1,
meridian = polar || delta < epsilon$1;
if (!polar && phi1 < phi0) z = phi0, phi0 = phi1, phi1 = z;
// Check that the first point is between a and b.
if (meridian ? polar ? phi0 + phi1 > 0 ^ q[1] < (abs(q[0] - lambda0) < epsilon$1 ? phi0 : phi1) : phi0 <= q[1] && q[1] <= phi1 : delta > pi ^ (lambda0 <= q[0] && q[0] <= lambda1)) {
var q1 = cartesianScale(u, (-w + t) / uu);
cartesianAddInPlace(q1, A);
return [q, spherical(q1)];
}
}
// Generates a 4-bit vector representing the location of a point relative to
// the small circle's bounding box.
function code(lambda, phi) {
var r = smallRadius ? radius : pi - radius,
code = 0;
if (lambda < -r) code |= 1; // left
else if (lambda > r) code |= 2; // right
if (phi < -r) code |= 4; // below
else if (phi > r) code |= 8; // above
return code;
}
return clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-pi, radius - pi]);
}
function clipLine (a, b, x0, y0, x1, y1) {
var ax = a[0],
ay = a[1],
bx = b[0],
by = b[1],
t0 = 0,
t1 = 1,
dx = bx - ax,
dy = by - ay,
r;
r = x0 - ax;
if (!dx && r > 0) return;
r /= dx;
if (dx < 0) {
if (r < t0) return;
if (r < t1) t1 = r;
} else if (dx > 0) {
if (r > t1) return;
if (r > t0) t0 = r;
}
r = x1 - ax;
if (!dx && r < 0) return;
r /= dx;
if (dx < 0) {
if (r > t1) return;
if (r > t0) t0 = r;
} else if (dx > 0) {
if (r < t0) return;
if (r < t1) t1 = r;
}
r = y0 - ay;
if (!dy && r > 0) return;
r /= dy;
if (dy < 0) {
if (r < t0) return;
if (r < t1) t1 = r;
} else if (dy > 0) {
if (r > t1) return;
if (r > t0) t0 = r;
}
r = y1 - ay;
if (!dy && r < 0) return;
r /= dy;
if (dy < 0) {
if (r > t1) return;
if (r > t0) t0 = r;
} else if (dy > 0) {
if (r < t0) return;
if (r < t1) t1 = r;
}
if (t0 > 0) a[0] = ax + t0 * dx, a[1] = ay + t0 * dy;
if (t1 < 1) b[0] = ax + t1 * dx, b[1] = ay + t1 * dy;
return true;
}
var clipMax = 1e9,
clipMin = -clipMax;
// TODO Use d3-polygon’s polygonContains here for the ring check?
// TODO Eliminate duplicate buffering in clipBuffer and polygon.push?
function clipRectangle(x0, y0, x1, y1) {
function visible(x, y) {
return x0 <= x && x <= x1 && y0 <= y && y <= y1;
}
function interpolate(from, to, direction, stream) {
var a = 0,
a1 = 0;
if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoint(from, to) < 0 ^ direction > 0) {
do stream.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); while ((a = (a + direction + 4) % 4) !== a1);
} else {
stream.point(to[0], to[1]);
}
}
function corner(p, direction) {
return abs(p[0] - x0) < epsilon$1 ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < epsilon$1 ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < epsilon$1 ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; // abs(p[1] - y1) < epsilon
}
function compareIntersection(a, b) {
return comparePoint(a.x, b.x);
}
function comparePoint(a, b) {
var ca = corner(a, 1),
cb = corner(b, 1);
return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
}
return function (stream) {
var activeStream = stream,
bufferStream = clipBuffer(),
segments,
polygon,
ring,
x__,
y__,
v__,
// first point
x_,
y_,
v_,
// previous point
first,
clean;
var clipStream = {
point: point,
lineStart: lineStart,
lineEnd: lineEnd,
polygonStart: polygonStart,
polygonEnd: polygonEnd
};
function point(x, y) {
if (visible(x, y)) activeStream.point(x, y);
}
function polygonInside() {
var winding = 0;
for (var i = 0, n = polygon.length; i < n; ++i) {
for (var ring = polygon[i], j = 1, m = ring.length, point = ring[0], a0, a1, b0 = point[0], b1 = point[1]; j < m; ++j) {
a0 = b0, a1 = b1, point = ring[j], b0 = point[0], b1 = point[1];
if (a1 <= y1) {
if (b1 > y1 && (b0 - a0) * (y1 - a1) > (b1 - a1) * (x0 - a0)) ++winding;
} else {
if (b1 <= y1 && (b0 - a0) * (y1 - a1) < (b1 - a1) * (x0 - a0)) --winding;
}
}
}
return winding;
}
// Buffer geometry within a polygon and then clip it en masse.
function polygonStart() {
activeStream = bufferStream, segments = [], polygon = [], clean = true;
}
function polygonEnd() {
var startInside = polygonInside(),
cleanInside = clean && startInside,
visible = (segments = merge$1(segments)).length;
if (cleanInside || visible) {
stream.polygonStart();
if (cleanInside) {
stream.lineStart();
interpolate(null, null, 1, stream);
stream.lineEnd();
}
if (visible) {
clipRejoin(segments, compareIntersection, startInside, interpolate, stream);
}
stream.polygonEnd();
}
activeStream = stream, segments = polygon = ring = null;
}
function lineStart() {
clipStream.point = linePoint;
if (polygon) polygon.push(ring = []);
first = true;
v_ = false;
x_ = y_ = NaN;
}
// TODO rather than special-case polygons, simply handle them separately.
// Ideally, coincident intersection points should be jittered to avoid
// clipping issues.
function lineEnd() {
if (segments) {
linePoint(x__, y__);
if (v__ && v_) bufferStream.rejoin();
segments.push(bufferStream.result());
}
clipStream.point = point;
if (v_) activeStream.lineEnd();
}
function linePoint(x, y) {
var v = visible(x, y);
if (polygon) ring.push([x, y]);
if (first) {
x__ = x, y__ = y, v__ = v;
first = false;
if (v) {
activeStream.lineStart();
activeStream.point(x, y);
}
} else {
if (v && v_) activeStream.point(x, y);else {
var a = [x_ = Math.max(clipMin, Math.min(clipMax, x_)), y_ = Math.max(clipMin, Math.min(clipMax, y_))],
b = [x = Math.max(clipMin, Math.min(clipMax, x)), y = Math.max(clipMin, Math.min(clipMax, y))];
if (clipLine(a, b, x0, y0, x1, y1)) {
if (!v_) {
activeStream.lineStart();
activeStream.point(a[0], a[1]);
}
activeStream.point(b[0], b[1]);
if (!v) activeStream.lineEnd();
clean = false;
} else if (v) {
activeStream.lineStart();
activeStream.point(x, y);
clean = false;
}
}
}
x_ = x, y_ = y, v_ = v;
}
return clipStream;
};
}
function identity$1 (x) {
return x;
}
var areaSum = adder(),
areaRingSum = adder(),
x00$2,
y00$2,
x0$3,
y0$3;
var areaStream = {
point: noop,
lineStart: noop,
lineEnd: noop,
polygonStart: function () {
areaStream.lineStart = areaRingStart;
areaStream.lineEnd = areaRingEnd;
},
polygonEnd: function () {
areaStream.lineStart = areaStream.lineEnd = areaStream.point = noop;
areaSum.add(abs(areaRingSum));
areaRingSum.reset();
},
result: function () {
var area = areaSum / 2;
areaSum.reset();
return area;
}
};
function areaRingStart() {
areaStream.point = areaPointFirst;
}
function areaPointFirst(x, y) {
areaStream.point = areaPoint;
x00$2 = x0$3 = x, y00$2 = y0$3 = y;
}
function areaPoint(x, y) {
areaRingSum.add(y0$3 * x - x0$3 * y);
x0$3 = x, y0$3 = y;
}
function areaRingEnd() {
areaPoint(x00$2, y00$2);
}
var pathArea = areaStream;
var x0$2 = Infinity,
y0$2 = x0$2,
x1 = -x0$2,
y1 = x1;
var boundsStream = {
point: boundsPoint,
lineStart: noop,
lineEnd: noop,
polygonStart: noop,
polygonEnd: noop,
result: function () {
var bounds = [[x0$2, y0$2], [x1, y1]];
x1 = y1 = -(y0$2 = x0$2 = Infinity);
return bounds;
}
};
function boundsPoint(x, y) {
if (x < x0$2) x0$2 = x;
if (x > x1) x1 = x;
if (y < y0$2) y0$2 = y;
if (y > y1) y1 = y;
}
var boundsStream$1 = boundsStream;
// TODO Enforce positive area for exterior, negative area for interior?
var X0 = 0,
Y0 = 0,
Z0 = 0,
X1 = 0,
Y1 = 0,
Z1 = 0,
X2 = 0,
Y2 = 0,
Z2 = 0,
x00$1,
y00$1,
x0$1,
y0$1;
var centroidStream = {
point: centroidPoint,
lineStart: centroidLineStart,
lineEnd: centroidLineEnd,
polygonStart: function () {
centroidStream.lineStart = centroidRingStart;
centroidStream.lineEnd = centroidRingEnd;
},
polygonEnd: function () {
centroidStream.point = centroidPoint;
centroidStream.lineStart = centroidLineStart;
centroidStream.lineEnd = centroidLineEnd;
},
result: function () {
var centroid = Z2 ? [X2 / Z2, Y2 / Z2] : Z1 ? [X1 / Z1, Y1 / Z1] : Z0 ? [X0 / Z0, Y0 / Z0] : [NaN, NaN];
X0 = Y0 = Z0 = X1 = Y1 = Z1 = X2 = Y2 = Z2 = 0;
return centroid;
}
};
function centroidPoint(x, y) {
X0 += x;
Y0 += y;
++Z0;
}
function centroidLineStart() {
centroidStream.point = centroidPointFirstLine;
}
function centroidPointFirstLine(x, y) {
centroidStream.point = centroidPointLine;
centroidPoint(x0$1 = x, y0$1 = y);
}
function centroidPointLine(x, y) {
var dx = x - x0$1,
dy = y - y0$1,
z = sqrt(dx * dx + dy * dy);
X1 += z * (x0$1 + x) / 2;
Y1 += z * (y0$1 + y) / 2;
Z1 += z;
centroidPoint(x0$1 = x, y0$1 = y);
}
function centroidLineEnd() {
centroidStream.point = centroidPoint;
}
function centroidRingStart() {
centroidStream.point = centroidPointFirstRing;
}
function centroidRingEnd() {
centroidPointRing(x00$1, y00$1);
}
function centroidPointFirstRing(x, y) {
centroidStream.point = centroidPointRing;
centroidPoint(x00$1 = x0$1 = x, y00$1 = y0$1 = y);
}
function centroidPointRing(x, y) {
var dx = x - x0$1,
dy = y - y0$1,
z = sqrt(dx * dx + dy * dy);
X1 += z * (x0$1 + x) / 2;
Y1 += z * (y0$1 + y) / 2;
Z1 += z;
z = y0$1 * x - x0$1 * y;
X2 += z * (x0$1 + x);
Y2 += z * (y0$1 + y);
Z2 += z * 3;
centroidPoint(x0$1 = x, y0$1 = y);
}
var pathCentroid = centroidStream;
function PathContext(context) {
this._context = context;
}
PathContext.prototype = {
_radius: 4.5,
pointRadius: function (_) {
return this._radius = _, this;
},
polygonStart: function () {
this._line = 0;
},
polygonEnd: function () {
this._line = NaN;
},
lineStart: function () {
this._point = 0;
},
lineEnd: function () {
if (this._line === 0) this._context.closePath();
this._point = NaN;
},
point: function (x, y) {
switch (this._point) {
case 0:
{
this._context.moveTo(x, y);
this._point = 1;
break;
}
case 1:
{
this._context.lineTo(x, y);
break;
}
default:
{
this._context.moveTo(x + this._radius, y);
this._context.arc(x, y, this._radius, 0, tau);
break;
}
}
},
result: noop
};
var lengthSum = adder(),
lengthRing,
x00,
y00,
x0,
y0;
var lengthStream = {
point: noop,
lineStart: function () {
lengthStream.point = lengthPointFirst;
},
lineEnd: function () {
if (lengthRing) lengthPoint(x00, y00);
lengthStream.point = noop;
},
polygonStart: function () {
lengthRing = true;
},
polygonEnd: function () {
lengthRing = null;
},
result: function () {
var length = +lengthSum;
lengthSum.reset();
return length;
}
};
function lengthPointFirst(x, y) {
lengthStream.point = lengthPoint;
x00 = x0 = x, y00 = y0 = y;
}
function lengthPoint(x, y) {
x0 -= x, y0 -= y;
lengthSum.add(sqrt(x0 * x0 + y0 * y0));
x0 = x, y0 = y;
}
var pathMeasure = lengthStream;
function PathString() {
this._string = [];
}
PathString.prototype = {
_radius: 4.5,
_circle: circle(4.5),
pointRadius: function (_) {
if ((_ = +_) !== this._radius) this._radius = _, this._circle = null;
return this;
},
polygonStart: function () {
this._line = 0;
},
polygonEnd: function () {
this._line = NaN;
},
lineStart: function () {
this._point = 0;
},
lineEnd: function () {
if (this._line === 0) this._string.push("Z");
this._point = NaN;
},
point: function (x, y) {
switch (this._point) {
case 0:
{
this._string.push("M", x, ",", y);
this._point = 1;
break;
}
case 1:
{
this._string.push("L", x, ",", y);
break;
}
default:
{
if (this._circle == null) this._circle = circle(this._radius);
this._string.push("M", x, ",", y, this._circle);
break;
}
}
},
result: function () {
if (this._string.length) {
var result = this._string.join("");
this._string = [];
return result;
} else {
return null;
}
}
};
function circle(radius) {
return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
}
function geoPath (projection, context) {
var pointRadius = 4.5,
projectionStream,
contextStream;
function path(object) {
if (object) {
if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
geoStream(object, projectionStream(contextStream));
}
return contextStream.result();
}
path.area = function (object) {
geoStream(object, projectionStream(pathArea));
return pathArea.result();
};
path.measure = function (object) {
geoStream(object, projectionStream(pathMeasure));
return pathMeasure.result();
};
path.bounds = function (object) {
geoStream(object, projectionStream(boundsStream$1));
return boundsStream$1.result();
};
path.centroid = function (object) {
geoStream(object, projectionStream(pathCentroid));
return pathCentroid.result();
};
path.projection = function (_) {
return arguments.length ? (projectionStream = _ == null ? (projection = null, identity$1) : (projection = _).stream, path) : projection;
};
path.context = function (_) {
if (!arguments.length) return context;
contextStream = _ == null ? (context = null, new PathString()) : new PathContext(context = _);
if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
return path;
};
path.pointRadius = function (_) {
if (!arguments.length) return pointRadius;
pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
return path;
};
return path.projection(projection).context(context);
}
function transformer(methods) {
return function (stream) {
var s = new TransformStream();
for (var key in methods) s[key] = methods[key];
s.stream = stream;
return s;
};
}
function TransformStream() {}
TransformStream.prototype = {
constructor: TransformStream,
point: function (x, y) {
this.stream.point(x, y);
},
sphere: function () {
this.stream.sphere();
},
lineStart: function () {
this.stream.lineStart();
},
lineEnd: function () {
this.stream.lineEnd();
},
polygonStart: function () {
this.stream.polygonStart();
},
polygonEnd: function () {
this.stream.polygonEnd();
}
};
function fit(projection, fitBounds, object) {
var clip = projection.clipExtent && projection.clipExtent();
projection.scale(150).translate([0, 0]);
if (clip != null) projection.clipExtent(null);
geoStream(object, projection.stream(boundsStream$1));
fitBounds(boundsStream$1.result());
if (clip != null) projection.clipExtent(clip);
return projection;
}
function fitExtent(projection, extent, object) {
return fit(projection, function (b) {
var w = extent[1][0] - extent[0][0],
h = extent[1][1] - extent[0][1],
k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1])),
x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2,
y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;
projection.scale(150 * k).translate([x, y]);
}, object);
}
function fitSize(projection, size, object) {
return fitExtent(projection, [[0, 0], size], object);
}
function fitWidth(projection, width, object) {
return fit(projection, function (b) {
var w = +width,
k = w / (b[1][0] - b[0][0]),
x = (w - k * (b[1][0] + b[0][0])) / 2,
y = -k * b[0][1];
projection.scale(150 * k).translate([x, y]);
}, object);
}
function fitHeight(projection, height, object) {
return fit(projection, function (b) {
var h = +height,
k = h / (b[1][1] - b[0][1]),
x = -k * b[0][0],
y = (h - k * (b[1][1] + b[0][1])) / 2;
projection.scale(150 * k).translate([x, y]);
}, object);
}
var maxDepth = 16,
// maximum depth of subdivision
cosMinDistance = cos(30 * radians); // cos(minimum angular distance)
function resample (project, delta2) {
return +delta2 ? resample$1(project, delta2) : resampleNone(project);
}
function resampleNone(project) {
return transformer({
point: function (x, y) {
x = project(x, y);
this.stream.point(x[0], x[1]);
}
});
}
function resample$1(project, delta2) {
function resampleLineTo(x0, y0, lambda0, a0, b0, c0, x1, y1, lambda1, a1, b1, c1, depth, stream) {
var dx = x1 - x0,
dy = y1 - y0,
d2 = dx * dx + dy * dy;
if (d2 > 4 * delta2 && depth--) {
var a = a0 + a1,
b = b0 + b1,
c = c0 + c1,
m = sqrt(a * a + b * b + c * c),
phi2 = asin(c /= m),
lambda2 = abs(abs(c) - 1) < epsilon$1 || abs(lambda0 - lambda1) < epsilon$1 ? (lambda0 + lambda1) / 2 : atan2(b, a),
p = project(lambda2, phi2),
x2 = p[0],
y2 = p[1],
dx2 = x2 - x0,
dy2 = y2 - y0,
dz = dy * dx2 - dx * dy2;
if (dz * dz / d2 > delta2 // perpendicular projected distance
|| abs((dx * dx2 + dy * dy2) / d2 - 0.5) > 0.3 // midpoint close to an end
|| a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
// angular distance
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x2, y2, lambda2, a /= m, b /= m, c, depth, stream);
stream.point(x2, y2);
resampleLineTo(x2, y2, lambda2, a, b, c, x1, y1, lambda1, a1, b1, c1, depth, stream);
}
}
}
return function (stream) {
var lambda00, x00, y00, a00, b00, c00,
// first point
lambda0, x0, y0, a0, b0, c0; // previous point
var resampleStream = {
point: point,
lineStart: lineStart,
lineEnd: lineEnd,
polygonStart: function () {
stream.polygonStart();
resampleStream.lineStart = ringStart;
},
polygonEnd: function () {
stream.polygonEnd();
resampleStream.lineStart = lineStart;
}
};
function point(x, y) {
x = project(x, y);
stream.point(x[0], x[1]);
}
function lineStart() {
x0 = NaN;
resampleStream.point = linePoint;
stream.lineStart();
}
function linePoint(lambda, phi) {
var c = cartesian([lambda, phi]),
p = project(lambda, phi);
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x0 = p[0], y0 = p[1], lambda0 = lambda, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
stream.point(x0, y0);
}
function lineEnd() {
resampleStream.point = point;
stream.lineEnd();
}
function ringStart() {
lineStart();
resampleStream.point = ringPoint;
resampleStream.lineEnd = ringEnd;
}
function ringPoint(lambda, phi) {
linePoint(lambda00 = lambda, phi), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
resampleStream.point = linePoint;
}
function ringEnd() {
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x00, y00, lambda00, a00, b00, c00, maxDepth, stream);
resampleStream.lineEnd = lineEnd;
lineEnd();
}
return resampleStream;
};
}
var transformRadians = transformer({
point: function (x, y) {
this.stream.point(x * radians, y * radians);
}
});
function transformRotate(rotate) {
return transformer({
point: function (x, y) {
var r = rotate(x, y);
return this.stream.point(r[0], r[1]);
}
});
}
function scaleTranslate(k, dx, dy, sx, sy) {
function transform(x, y) {
x *= sx;
y *= sy;
return [dx + k * x, dy - k * y];
}
transform.invert = function (x, y) {
return [(x - dx) / k * sx, (dy - y) / k * sy];
};
return transform;
}
function scaleTranslateRotate(k, dx, dy, sx, sy, alpha) {
var cosAlpha = cos(alpha),
sinAlpha = sin(alpha),
a = cosAlpha * k,
b = sinAlpha * k,
ai = cosAlpha / k,
bi = sinAlpha / k,
ci = (sinAlpha * dy - cosAlpha * dx) / k,
fi = (sinAlpha * dx + cosAlpha * dy) / k;
function transform(x, y) {
x *= sx;
y *= sy;
return [a * x - b * y + dx, dy - b * x - a * y];
}
transform.invert = function (x, y) {
return [sx * (ai * x - bi * y + ci), sy * (fi - bi * x - ai * y)];
};
return transform;
}
function projection$1(project) {
return projectionMutator(function () {
return project;
})();
}
function projectionMutator(projectAt) {
var project,
k = 150,
// scale
x = 480,
y = 250,
// translate
lambda = 0,
phi = 0,
// center
deltaLambda = 0,
deltaPhi = 0,
deltaGamma = 0,
rotate,
// pre-rotate
alpha = 0,
// post-rotate angle
sx = 1,
// reflectX
sy = 1,
// reflectX
theta = null,
preclip = clipAntimeridian,
// pre-clip angle
x0 = null,
y0,
x1,
y1,
postclip = identity$1,
// post-clip extent
delta2 = 0.5,
// precision
projectResample,
projectTransform,
projectRotateTransform,
cache,
cacheStream;
function projection(point) {
return projectRotateTransform(point[0] * radians, point[1] * radians);
}
function invert(point) {
point = projectRotateTransform.invert(point[0], point[1]);
return point && [point[0] * degrees, point[1] * degrees];
}
projection.stream = function (stream) {
return cache && cacheStream === stream ? cache : cache = transformRadians(transformRotate(rotate)(preclip(projectResample(postclip(cacheStream = stream)))));
};
projection.preclip = function (_) {
return arguments.length ? (preclip = _, theta = undefined, reset()) : preclip;
};
projection.postclip = function (_) {
return arguments.length ? (postclip = _, x0 = y0 = x1 = y1 = null, reset()) : postclip;
};
projection.clipAngle = function (_) {
return arguments.length ? (preclip = +_ ? clipCircle(theta = _ * radians) : (theta = null, clipAntimeridian), reset()) : theta * degrees;
};
projection.clipExtent = function (_) {
return arguments.length ? (postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity$1) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]];
};
projection.scale = function (_) {
return arguments.length ? (k = +_, recenter()) : k;
};
projection.translate = function (_) {
return arguments.length ? (x = +_[0], y = +_[1], recenter()) : [x, y];
};
projection.center = function (_) {
return arguments.length ? (lambda = _[0] % 360 * radians, phi = _[1] % 360 * radians, recenter()) : [lambda * degrees, phi * degrees];
};
projection.rotate = function (_) {
return arguments.length ? (deltaLambda = _[0] % 360 * radians, deltaPhi = _[1] % 360 * radians, deltaGamma = _.length > 2 ? _[2] % 360 * radians : 0, recenter()) : [deltaLambda * degrees, deltaPhi * degrees, deltaGamma * degrees];
};
projection.angle = function (_) {
return arguments.length ? (alpha = _ % 360 * radians, recenter()) : alpha * degrees;
};
projection.reflectX = function (_) {
return arguments.length ? (sx = _ ? -1 : 1, recenter()) : sx < 0;
};
projection.reflectY = function (_) {
return arguments.length ? (sy = _ ? -1 : 1, recenter()) : sy < 0;
};
projection.precision = function (_) {
return arguments.length ? (projectResample = resample(projectTransform, delta2 = _ * _), reset()) : sqrt(delta2);
};
projection.fitExtent = function (extent, object) {
return fitExtent(projection, extent, object);
};
projection.fitSize = function (size, object) {
return fitSize(projection, size, object);
};
projection.fitWidth = function (width, object) {
return fitWidth(projection, width, object);
};
projection.fitHeight = function (height, object) {
return fitHeight(projection, height, object);
};
function recenter() {
var center = scaleTranslateRotate(k, 0, 0, sx, sy, alpha).apply(null, project(lambda, phi)),
transform = (alpha ? scaleTranslateRotate : scaleTranslate)(k, x - center[0], y - center[1], sx, sy, alpha);
rotate = rotateRadians(deltaLambda, deltaPhi, deltaGamma);
projectTransform = compose(project, transform);
projectRotateTransform = compose(rotate, projectTransform);
projectResample = resample(projectTransform, delta2);
return reset();
}
function reset() {
cache = cacheStream = null;
return projection;
}
return function () {
project = projectAt.apply(this, arguments);
projection.invert = project.invert && invert;
return recenter();
};
}
function mercatorRaw(lambda, phi) {
return [lambda, log(tan((halfPi + phi) / 2))];
}
mercatorRaw.invert = function (x, y) {
return [x, 2 * atan(exp(y)) - halfPi];
};
function geoMercator () {
return mercatorProjection(mercatorRaw).scale(961 / tau);
}
function mercatorProjection(project) {
var m = projection$1(project),
center = m.center,
scale = m.scale,
translate = m.translate,
clipExtent = m.clipExtent,
x0 = null,
y0,
x1,
y1; // clip extent
m.scale = function (_) {
return arguments.length ? (scale(_), reclip()) : scale();
};
m.translate = function (_) {
return arguments.length ? (translate(_), reclip()) : translate();
};
m.center = function (_) {
return arguments.length ? (center(_), reclip()) : center();
};
m.clipExtent = function (_) {
return arguments.length ? (_ == null ? x0 = y0 = x1 = y1 = null : (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reclip()) : x0 == null ? null : [[x0, y0], [x1, y1]];
};
function reclip() {
var k = pi * scale(),
t = m(rotation(m.rotate()).invert([0, 0]));
return clipExtent(x0 == null ? [[t[0] - k, t[1] - k], [t[0] + k, t[1] + k]] : project === mercatorRaw ? [[Math.max(t[0] - k, x0), y0], [Math.min(t[0] + k, x1), y1]] : [[x0, Math.max(t[1] - k, y0)], [x1, Math.min(t[1] + k, y1)]]);
}
return reclip();
}
const EARTH_RADIUS = 6378100;
const RELATIVE_EARTH_RADIUS = EARTH_RADIUS / 100;
const PROJECTION_MERCATOR = geoMercator().translate([0, 0]).center([0, 0]).scale(RELATIVE_EARTH_RADIUS);
function project(point) {
const projection = PROJECTION_MERCATOR;
if (typeof point[2] === 'undefined') {
const result = projection(point);
result[1] *= -1;
return result;
}
const result = projection(point);
result[1] *= -1;
result.push(point[2]);
return result;
}
const PROJECTION_GROUP = {
webmercator: project
};
const projection = (data, options) => {
if (!data || data.length === 0) {
return data;
}
const { projection, as } = options;
const prjFunc = PROJECTION_GROUP[projection];
if (data[0].lng) {
const processData = data.map((item) => {
return Object.assign(Object.assign({}, item), { [as]: prjFunc([item.lng, item.lat]) });
});
return processData;
}
const result = data.map((ele) => {
const { coordinates } = ele.geometry || {};
if (!Array.isArray(coordinates[0])) {
const processData = prjFunc(coordinates);
return Object.assign(Object.assign({}, ele), { [as]: processData });
}
const processData = coordinates.map((item) => {
return Array.isArray(item[0]) ? item.map((coord) => prjFunc(coord)) : prjFunc(item);
});
return Object.assign(Object.assign({}, ele), { [as]: processData, geometry: Object.assign(Object.assign({}, ele.geometry), { [as]: processData }) });
});
return result;
};
var hasOwnProperty = Object.prototype.hasOwnProperty;
// Computes the bounding box of the specified hash of GeoJSON objects.
function bounds (objects) {
var x0 = Infinity,
y0 = Infinity,
x1 = -Infinity,
y1 = -Infinity;
function boundGeometry(geometry) {
if (geometry != null && hasOwnProperty.call(boundGeometryType, geometry.type)) boundGeometryType[geometry.type](geometry);
}
var boundGeometryType = {
GeometryCollection: function (o) {
o.geometries.forEach(boundGeometry);
},
Point: function (o) {
boundPoint(o.coordinates);
},
MultiPoint: function (o) {
o.coordinates.forEach(boundPoint);
},
LineString: function (o) {
boundLine(o.arcs);
},
MultiLineString: function (o) {
o.arcs.forEach(boundLine);
},
Polygon: function (o) {
o.arcs.forEach(boundLine);
},
MultiPolygon: function (o) {
o.arcs.forEach(boundMultiLine);
}
};
function boundPoint(coordinates) {
var x = coordinates[0],
y = coordinates[1];
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
function boundLine(coordinates) {
coordinates.forEach(boundPoint);
}
function boundMultiLine(coordinates) {
coordinates.forEach(boundLine);
}
for (var key in objects) {
boundGeometry(objects[key]);
}
return x1 >= x0 && y1 >= y0 ? [x0, y0, x1, y1] : undefined;
}
function hashset (size, hash, equal, type, empty) {
if (arguments.length === 3) {
type = Array;
empty = null;
}
var store = new type(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
mask = size - 1;
for (var i = 0; i < size; ++i) {
store[i] = empty;
}
function add(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) throw new Error("full hashset");
match = store[index = index + 1 & mask];
}
store[index] = value;
return true;
}
function has(value) {
var index = hash(value) & mask,
match = store[index],
collisions = 0;
while (match != empty) {
if (equal(match, value)) return true;
if (++collisions >= size) break;
match = store[index = index + 1 & mask];
}
return false;
}
function values() {
var values = [];
for (var i = 0, n = store.length; i < n; ++i) {
var match = store[i];
if (match != empty) values.push(match);
}
return values;
}
return {
add: add,
has: has,
values: values
};
}
function hashmap (size, hash, equal, keyType, keyEmpty, valueType) {
if (arguments.length === 3) {
keyType = valueType = Array;
keyEmpty = null;
}
var keystore = new keyType(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
valstore = new valueType(size),
mask = size - 1;
for (var i = 0; i < size; ++i) {
keystore[i] = keyEmpty;
}
function set(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index] = value;
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = index + 1 & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function maybeSet(key, value) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) throw new Error("full hashmap");
matchKey = keystore[index = index + 1 & mask];
}
keystore[index] = key;
valstore[index] = value;
return value;
}
function get(key, missingValue) {
var index = hash(key) & mask,
matchKey = keystore[index],
collisions = 0;
while (matchKey != keyEmpty) {
if (equal(matchKey, key)) return valstore[index];
if (++collisions >= size) break;
matchKey = keystore[index = index + 1 & mask];
}
return missingValue;
}
function keys() {
var keys = [];
for (var i = 0, n = keystore.length; i < n; ++i) {
var matchKey = keystore[i];
if (matchKey != keyEmpty) keys.push(matchKey);
}
return keys;
}
return {
set: set,
maybeSet: maybeSet,
// set if unset
get: get,
keys: keys
};
}
function equalPoint (pointA, pointB) {
return pointA[0] === pointB[0] && pointA[1] === pointB[1];
}
// TODO if quantized, use simpler Int32 hashing?
var buffer = new ArrayBuffer(16),
floats = new Float64Array(buffer),
uints = new Uint32Array(buffer);
function hashPoint (point) {
floats[0] = point[0];
floats[1] = point[1];
var hash = uints[0] ^ uints[1];
hash = hash << 5 ^ hash >> 7 ^ uints[2] ^ uints[3];
return hash & 0x7fffffff;
}
// Given an extracted (pre-)topology, identifies all of the junctions. These are
// the points at which arcs (lines or rings) will need to be cut so that each
// arc is represented uniquely.
//
// A junction is a point where at least one arc deviates from another arc going
// through the same point. For example, consider the point B. If there is a arc
// through ABC and another arc through CBA, then B is not a junction because in
// both cases the adjacent point pairs are {A,C}. However, if there is an
// additional arc ABD, then {A,D} != {A,C}, and thus B becomes a junction.
//
// For a closed ring ABCA, the first point A’s adjacent points are the second
// and last point {B,C}. For a line, the first and last point are always
// considered junctions, even if the line is closed; this ensures that a closed
// line is never rotated.
function join (topology) {
var coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
indexes = index(),
visitedByIndex = new Int32Array(coordinates.length),
leftByIndex = new Int32Array(coordinates.length),
rightByIndex = new Int32Array(coordinates.length),
junctionByIndex = new Int8Array(coordinates.length),
junctionCount = 0,
// upper bound on number of junctions
i,
n,
previousIndex,
currentIndex,
nextIndex;
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = leftByIndex[i] = rightByIndex[i] = -1;
}
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineStart = line[0],
lineEnd = line[1];
currentIndex = indexes[lineStart];
nextIndex = indexes[++lineStart];
++junctionCount, junctionByIndex[currentIndex] = 1; // start
while (++lineStart <= lineEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[lineStart]);
}
++junctionCount, junctionByIndex[nextIndex] = 1; // end
}
for (i = 0, n = coordinates.length; i < n; ++i) {
visitedByIndex[i] = -1;
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0] + 1,
ringEnd = ring[1];
previousIndex = indexes[ringEnd - 1];
currentIndex = indexes[ringStart - 1];
nextIndex = indexes[ringStart];
sequence(i, previousIndex, currentIndex, nextIndex);
while (++ringStart <= ringEnd) {
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[ringStart]);
}
}
function sequence(i, previousIndex, currentIndex, nextIndex) {
if (visitedByIndex[currentIndex] === i) return; // ignore self-intersection
visitedByIndex[currentIndex] = i;
var leftIndex = leftByIndex[currentIndex];
if (leftIndex >= 0) {
var rightIndex = rightByIndex[currentIndex];
if ((leftIndex !== previousIndex || rightIndex !== nextIndex) && (leftIndex !== nextIndex || rightIndex !== previousIndex)) {
++junctionCount, junctionByIndex[currentIndex] = 1;
}
} else {
leftByIndex[currentIndex] = previousIndex;
rightByIndex[currentIndex] = nextIndex;
}
}
function index() {
var indexByPoint = hashmap(coordinates.length * 1.4, hashIndex, equalIndex, Int32Array, -1, Int32Array),
indexes = new Int32Array(coordinates.length);
for (var i = 0, n = coordinates.length; i < n; ++i) {
indexes[i] = indexByPoint.maybeSet(i, i);
}
return indexes;
}
function hashIndex(i) {
return hashPoint(coordinates[i]);
}
function equalIndex(i, j) {
return equalPoint(coordinates[i], coordinates[j]);
}
visitedByIndex = leftByIndex = rightByIndex = null;
var junctionByPoint = hashset(junctionCount * 1.4, hashPoint, equalPoint),
j;
// Convert back to a standard hashset by point for caller convenience.
for (i = 0, n = coordinates.length; i < n; ++i) {
if (junctionByIndex[j = indexes[i]]) {
junctionByPoint.add(coordinates[j]);
}
}
return junctionByPoint;
}
// Given an extracted (pre-)topology, cuts (or rotates) arcs so that all shared
// point sequences are identified. The topology can then be subsequently deduped
// to remove exact duplicate arcs.
function cut (topology) {
var junctions = join(topology),
coordinates = topology.coordinates,
lines = topology.lines,
rings = topology.rings,
next,
i,
n;
for (i = 0, n = lines.length; i < n; ++i) {
var line = lines[i],
lineMid = line[0],
lineEnd = line[1];
while (++lineMid < lineEnd) {
if (junctions.has(coordinates[lineMid])) {
next = {
0: lineMid,
1: line[1]
};
line[1] = lineMid;
line = line.next = next;
}
}
}
for (i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i],
ringStart = ring[0],
ringMid = ringStart,
ringEnd = ring[1],
ringFixed = junctions.has(coordinates[ringStart]);
while (++ringMid < ringEnd) {
if (junctions.has(coordinates[ringMid])) {
if (ringFixed) {
next = {
0: ringMid,
1: ring[1]
};
ring[1] = ringMid;
ring = ring.next = next;
} else {
// For the first junction, we can rotate rather than cut.
rotateArray(coordinates, ringStart, ringEnd, ringEnd - ringMid);
coordinates[ringEnd] = coordinates[ringStart];
ringFixed = true;
ringMid = ringStart; // restart; we may have skipped junctions
}
}
}
}
return topology;
}
function rotateArray(array, start, end, offset) {
reverse$1(array, start, end);
reverse$1(array, start, start + offset);
reverse$1(array, start + offset, end);
}
function reverse$1(array, start, end) {
for (var mid = start + (end-- - start >> 1), t; start < mid; ++start, --end) {
t = array[start], array[start] = array[end], array[end] = t;
}
}
// Given a cut topology, combines duplicate arcs.
function dedup (topology) {
var coordinates = topology.coordinates,
lines = topology.lines,
line,
rings = topology.rings,
ring,
arcCount = lines.length + rings.length,
i,
n;
delete topology.lines;
delete topology.rings;
// Count the number of (non-unique) arcs to initialize the hashmap safely.
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i];
while (line = line.next) ++arcCount;
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i];
while (ring = ring.next) ++arcCount;
}
var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
arcs = topology.arcs = [];
for (i = 0, n = lines.length; i < n; ++i) {
line = lines[i];
do {
dedupLine(line);
} while (line = line.next);
}
for (i = 0, n = rings.length; i < n; ++i) {
ring = rings[i];
if (ring.next) {
// arc is no longer closed
do {
dedupLine(ring);
} while (ring = ring.next);
} else {
dedupRing(ring);
}
}
function dedupLine(arc) {
var startPoint, endPoint, startArcs, startArc, endArcs, endArc, i, n;
// Does this arc match an existing arc in order?
if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
for (i = 0, n = startArcs.length; i < n; ++i) {
startArc = startArcs[i];
if (equalLine(startArc, arc)) {
arc[0] = startArc[0];
arc[1] = startArc[1];
return;
}
}
}
// Does this arc match an existing arc in reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (reverseEqualLine(endArc, arc)) {
arc[1] = endArc[0];
arc[0] = endArc[1];
return;
}
}
}
if (startArcs) startArcs.push(arc);else arcsByEnd.set(startPoint, [arc]);
if (endArcs) endArcs.push(arc);else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function dedupRing(arc) {
var endPoint, endArcs, endArc, i, n;
// Does this arc match an existing line in order, or reverse order?
// Rings are closed, so their start point and end point is the same.
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
// Otherwise, does this arc match an existing ring in order, or reverse order?
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
for (i = 0, n = endArcs.length; i < n; ++i) {
endArc = endArcs[i];
if (equalRing(endArc, arc)) {
arc[0] = endArc[0];
arc[1] = endArc[1];
return;
}
if (reverseEqualRing(endArc, arc)) {
arc[0] = endArc[1];
arc[1] = endArc[0];
return;
}
}
}
if (endArcs) endArcs.push(arc);else arcsByEnd.set(endPoint, [arc]);
arcs.push(arc);
}
function equalLine(arcA, arcB) {
var ia = arcA[0],
ib = arcB[0],
ja = arcA[1],
jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
return true;
}
function reverseEqualLine(arcA, arcB) {
var ia = arcA[0],
ib = arcB[0],
ja = arcA[1],
jb = arcB[1];
if (ia - ja !== ib - jb) return false;
for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
return true;
}
function equalRing(arcA, arcB) {
var ia = arcA[0],
ib = arcB[0],
ja = arcA[1],
jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
}
return true;
}
function reverseEqualRing(arcA, arcB) {
var ia = arcA[0],
ib = arcB[0],
ja = arcA[1],
jb = arcB[1],
n = ja - ia;
if (n !== jb - ib) return false;
var ka = findMinimumOffset(arcA),
kb = n - findMinimumOffset(arcB);
for (var i = 0; i < n; ++i) {
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
}
return true;
}
// Rings are rotated to a consistent, but arbitrary, start point.
// This is necessary to detect when a ring and a rotated copy are dupes.
function findMinimumOffset(arc) {
var start = arc[0],
end = arc[1],
mid = start,
minimum = mid,
minimumPoint = coordinates[mid];
while (++mid < end) {
var point = coordinates[mid];
if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
minimum = mid;
minimumPoint = point;
}
}
return minimum - start;
}
return topology;
}
// Given an array of arcs in absolute (but already quantized!) coordinates,
// converts to fixed-point delta encoding.
// This is a destructive operation that modifies the given arcs!
function delta (arcs) {
var i = -1,
n = arcs.length;
while (++i < n) {
var arc = arcs[i],
j = 0,
k = 1,
m = arc.length,
point = arc[0],
x0 = point[0],
y0 = point[1],
x1,
y1;
while (++j < m) {
point = arc[j], x1 = point[0], y1 = point[1];
if (x1 !== x0 || y1 !== y0) arc[k++] = [x1 - x0, y1 - y0], x0 = x1, y0 = y1;
}
if (k === 1) arc[k++] = [0, 0]; // Each arc must be an array of two or more positions.
arc.length = k;
}
return arcs;
}
// Extracts the lines and rings from the specified hash of geometry objects.
//
// Returns an object with three properties:
//
// * coordinates - shared buffer of [x, y] coordinates
// * lines - lines extracted from the hash, of the form [start, end]
// * rings - rings extracted from the hash, of the form [start, end]
//
// For each ring or line, start and end represent inclusive indexes into the
// coordinates buffer. For rings (and closed lines), coordinates[start] equals
// coordinates[end].
//
// For each line or polygon geometry in the input hash, including nested
// geometries as in geometry collections, the `coordinates` array is replaced
// with an equivalent `arcs` array that, for each line (for line string
// geometries) or ring (for polygon geometries), points to one of the above
// lines or rings.
function extract (objects) {
var index = -1,
lines = [],
rings = [],
coordinates = [];
function extractGeometry(geometry) {
if (geometry && hasOwnProperty.call(extractGeometryType, geometry.type)) extractGeometryType[geometry.type](geometry);
}
var extractGeometryType = {
GeometryCollection: function (o) {
o.geometries.forEach(extractGeometry);
},
LineString: function (o) {
o.arcs = extractLine(o.arcs);
},
MultiLineString: function (o) {
o.arcs = o.arcs.map(extractLine);
},
Polygon: function (o) {
o.arcs = o.arcs.map(extractRing);
},
MultiPolygon: function (o) {
o.arcs = o.arcs.map(extractMultiRing);
}
};
function extractLine(line) {
for (var i = 0, n = line.length; i < n; ++i) coordinates[++index] = line[i];
var arc = {
0: index - n + 1,
1: index
};
lines.push(arc);
return arc;
}
function extractRing(ring) {
for (var i = 0, n = ring.length; i < n; ++i) coordinates[++index] = ring[i];
var arc = {
0: index - n + 1,
1: index
};
rings.push(arc);
return arc;
}
function extractMultiRing(rings) {
return rings.map(extractRing);
}
for (var key in objects) {
extractGeometry(objects[key]);
}
return {
type: "Topology",
coordinates: coordinates,
lines: lines,
rings: rings,
objects: objects
};
}
// Given a hash of GeoJSON objects, returns a hash of GeoJSON geometry objects.
// Any null input geometry objects are represented as {type: null} in the output.
// Any feature.{id,properties,bbox} are transferred to the output geometry object.
// Each output geometry object is a shallow copy of the input (e.g., properties, coordinates)!
function geometry (inputs) {
var outputs = {},
key;
for (key in inputs) outputs[key] = geomifyObject(inputs[key]);
return outputs;
}
function geomifyObject(input) {
return input == null ? {
type: null
} : (input.type === "FeatureCollection" ? geomifyFeatureCollection : input.type === "Feature" ? geomifyFeature : geomifyGeometry)(input);
}
function geomifyFeatureCollection(input) {
var output = {
type: "GeometryCollection",
geometries: input.features.map(geomifyFeature)
};
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
function geomifyFeature(input) {
var output = geomifyGeometry(input.geometry),
key; // eslint-disable-line no-unused-vars
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
for (key in input.properties) {
output.properties = input.properties;
break;
}
return output;
}
function geomifyGeometry(input) {
if (input == null) return {
type: null
};
var output = input.type === "GeometryCollection" ? {
type: "GeometryCollection",
geometries: input.geometries.map(geomifyGeometry)
} : input.type === "Point" || input.type === "MultiPoint" ? {
type: input.type,
coordinates: input.coordinates
} : {
type: input.type,
arcs: input.coordinates
}; // TODO Check for unknown types?
if (input.bbox != null) output.bbox = input.bbox;
return output;
}
function prequantize (objects, bbox, n) {
var x0 = bbox[0],
y0 = bbox[1],
x1 = bbox[2],
y1 = bbox[3],
kx = x1 - x0 ? (n - 1) / (x1 - x0) : 1,
ky = y1 - y0 ? (n - 1) / (y1 - y0) : 1;
function quantizePoint(input) {
return [Math.round((input[0] - x0) * kx), Math.round((input[1] - y0) * ky)];
}
function quantizePoints(input, m) {
var i = -1,
j = 0,
n = input.length,
output = new Array(n),
// pessimistic
pi,
px,
py,
x,
y;
while (++i < n) {
pi = input[i];
x = Math.round((pi[0] - x0) * kx);
y = Math.round((pi[1] - y0) * ky);
if (x !== px || y !== py) output[j++] = [px = x, py = y]; // non-coincident points
}
output.length = j;
while (j < m) j = output.push([output[0][0], output[0][1]]);
return output;
}
function quantizeLine(input) {
return quantizePoints(input, 2);
}
function quantizeRing(input) {
return quantizePoints(input, 4);
}
function quantizePolygon(input) {
return input.map(quantizeRing);
}
function quantizeGeometry(o) {
if (o != null && hasOwnProperty.call(quantizeGeometryType, o.type)) quantizeGeometryType[o.type](o);
}
var quantizeGeometryType = {
GeometryCollection: function (o) {
o.geometries.forEach(quantizeGeometry);
},
Point: function (o) {
o.coordinates = quantizePoint(o.coordinates);
},
MultiPoint: function (o) {
o.coordinates = o.coordinates.map(quantizePoint);
},
LineString: function (o) {
o.arcs = quantizeLine(o.arcs);
},
MultiLineString: function (o) {
o.arcs = o.arcs.map(quantizeLine);
},
Polygon: function (o) {
o.arcs = quantizePolygon(o.arcs);
},
MultiPolygon: function (o) {
o.arcs = o.arcs.map(quantizePolygon);
}
};
for (var key in objects) {
quantizeGeometry(objects[key]);
}
return {
scale: [1 / kx, 1 / ky],
translate: [x0, y0]
};
}
// Constructs the TopoJSON Topology for the specified hash of features.
// Each object in the specified hash must be a GeoJSON object,
// meaning FeatureCollection, a Feature or a geometry object.
function topology (objects, quantization) {
var bbox = bounds(objects = geometry(objects)),
transform = quantization > 0 && bbox && prequantize(objects, bbox, quantization),
topology = dedup(cut(extract(objects))),
coordinates = topology.coordinates,
indexByArc = hashmap(topology.arcs.length * 1.4, hashArc, equalArc);
objects = topology.objects; // for garbage collection
topology.bbox = bbox;
topology.arcs = topology.arcs.map(function (arc, i) {
indexByArc.set(arc, i);
return coordinates.slice(arc[0], arc[1] + 1);
});
delete topology.coordinates;
coordinates = null;
function indexGeometry(geometry) {
if (geometry && hasOwnProperty.call(indexGeometryType, geometry.type)) indexGeometryType[geometry.type](geometry);
}
var indexGeometryType = {
GeometryCollection: function (o) {
o.geometries.forEach(indexGeometry);
},
LineString: function (o) {
o.arcs = indexArcs(o.arcs);
},
MultiLineString: function (o) {
o.arcs = o.arcs.map(indexArcs);
},
Polygon: function (o) {
o.arcs = o.arcs.map(indexArcs);
},
MultiPolygon: function (o) {
o.arcs = o.arcs.map(indexMultiArcs);
}
};
function indexArcs(arc) {
var indexes = [];
do {
var index = indexByArc.get(arc);
indexes.push(arc[0] < arc[1] ? index : ~index);
} while (arc = arc.next);
return indexes;
}
function indexMultiArcs(arcs) {
return arcs.map(indexArcs);
}
for (var key in objects) {
indexGeometry(objects[key]);
}
if (transform) {
topology.transform = transform;
topology.arcs = delta(topology.arcs);
}
return topology;
}
function hashArc(arc) {
var i = arc[0],
j = arc[1],
t;
if (j < i) t = i, i = j, j = t;
return i + 31 * j;
}
function equalArc(arcA, arcB) {
var ia = arcA[0],
ja = arcA[1],
ib = arcB[0],
jb = arcB[1],
t;
if (ja < ia) t = ia, ia = ja, ja = t;
if (jb < ib) t = ib, ib = jb, jb = t;
return ia === ib && ja === jb;
}
var src$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
topology: topology
});
var require$$0 = /*@__PURE__*/getAugmentedNamespace(src$1);
function identity (x) {
return x;
}
function transform (transform) {
if (transform == null) return identity;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function (input, i) {
if (!i) x0 = y0 = 0;
var j = 2,
n = input.length,
output = new Array(n);
output[0] = (x0 += input[0]) * kx + dx;
output[1] = (y0 += input[1]) * ky + dy;
while (j < n) output[j] = input[j], ++j;
return output;
};
}
function bbox (topology) {
var t = transform(topology.transform),
key,
x0 = Infinity,
y0 = x0,
x1 = -x0,
y1 = -x0;
function bboxPoint(p) {
p = t(p);
if (p[0] < x0) x0 = p[0];
if (p[0] > x1) x1 = p[0];
if (p[1] < y0) y0 = p[1];
if (p[1] > y1) y1 = p[1];
}
function bboxGeometry(o) {
switch (o.type) {
case "GeometryCollection":
o.geometries.forEach(bboxGeometry);
break;
case "Point":
bboxPoint(o.coordinates);
break;
case "MultiPoint":
o.coordinates.forEach(bboxPoint);
break;
}
}
topology.arcs.forEach(function (arc) {
var i = -1,
n = arc.length,
p;
while (++i < n) {
p = t(arc[i], i);
if (p[0] < x0) x0 = p[0];
if (p[0] > x1) x1 = p[0];
if (p[1] < y0) y0 = p[1];
if (p[1] > y1) y1 = p[1];
}
});
for (key in topology.objects) {
bboxGeometry(topology.objects[key]);
}
return [x0, y0, x1, y1];
}
function reverse (array, n) {
var t,
j = array.length,
i = j - n;
while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
}
function feature (topology, o) {
if (typeof o === "string") o = topology.objects[o];
return o.type === "GeometryCollection" ? {
type: "FeatureCollection",
features: o.geometries.map(function (o) {
return feature$1(topology, o);
})
} : feature$1(topology, o);
}
function feature$1(topology, o) {
var id = o.id,
bbox = o.bbox,
properties = o.properties == null ? {} : o.properties,
geometry = object(topology, o);
return id == null && bbox == null ? {
type: "Feature",
properties: properties,
geometry: geometry
} : bbox == null ? {
type: "Feature",
id: id,
properties: properties,
geometry: geometry
} : {
type: "Feature",
id: id,
bbox: bbox,
properties: properties,
geometry: geometry
};
}
function object(topology, o) {
var transformPoint = transform(topology.transform),
arcs = topology.arcs;
function arc(i, points) {
if (points.length) points.pop();
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {
points.push(transformPoint(a[k], k));
}
if (i < 0) reverse(points, n);
}
function point(p) {
return transformPoint(p);
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
if (points.length < 2) points.push(points[0]); // This should never happen per the specification.
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) points.push(points[0]); // This may happen if an arc has only two points.
return points;
}
function polygon(arcs) {
return arcs.map(ring);
}
function geometry(o) {
var type = o.type,
coordinates;
switch (type) {
case "GeometryCollection":
return {
type: type,
geometries: o.geometries.map(geometry)
};
case "Point":
coordinates = point(o.coordinates);
break;
case "MultiPoint":
coordinates = o.coordinates.map(point);
break;
case "LineString":
coordinates = line(o.arcs);
break;
case "MultiLineString":
coordinates = o.arcs.map(line);
break;
case "Polygon":
coordinates = polygon(o.arcs);
break;
case "MultiPolygon":
coordinates = o.arcs.map(polygon);
break;
default:
return null;
}
return {
type: type,
coordinates: coordinates
};
}
return geometry(o);
}
function stitch (topology, arcs) {
var stitchedArcs = {},
fragmentByStart = {},
fragmentByEnd = {},
fragments = [],
emptyIndex = -1;
// Stitch empty arcs first, since they may be subsumed by other arcs.
arcs.forEach(function (i, j) {
var arc = topology.arcs[i < 0 ? ~i : i],
t;
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
}
});
arcs.forEach(function (i) {
var e = ends(i),
start = e[0],
end = e[1],
f,
g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = g === f ? f : f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g === f ? f : g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i < 0 ? ~i : i],
p0 = arc[0],
p1;
if (topology.transform) p1 = [0, 0], arc.forEach(function (dp) {
p1[0] += dp[0], p1[1] += dp[1];
});else p1 = arc[arc.length - 1];
return i < 0 ? [p1, p0] : [p0, p1];
}
function flush(fragmentByEnd, fragmentByStart) {
for (var k in fragmentByEnd) {
var f = fragmentByEnd[k];
delete fragmentByStart[f.start];
delete f.start;
delete f.end;
f.forEach(function (i) {
stitchedArcs[i < 0 ? ~i : i] = 1;
});
fragments.push(f);
}
}
flush(fragmentByEnd, fragmentByStart);
flush(fragmentByStart, fragmentByEnd);
arcs.forEach(function (i) {
if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]);
});
return fragments;
}
function mesh (topology) {
return object(topology, meshArcs.apply(this, arguments));
}
function meshArcs(topology, object, filter) {
var arcs, i, n;
if (arguments.length > 1) arcs = extractArcs(topology, object, filter);else for (i = 0, arcs = new Array(n = topology.arcs.length); i < n; ++i) arcs[i] = i;
return {
type: "MultiLineString",
arcs: stitch(topology, arcs)
};
}
function extractArcs(topology, object, filter) {
var arcs = [],
geomsByArc = [],
geom;
function extract0(i) {
var j = i < 0 ? ~i : i;
(geomsByArc[j] || (geomsByArc[j] = [])).push({
i: i,
g: geom
});
}
function extract1(arcs) {
arcs.forEach(extract0);
}
function extract2(arcs) {
arcs.forEach(extract1);
}
function extract3(arcs) {
arcs.forEach(extract2);
}
function geometry(o) {
switch (geom = o, o.type) {
case "GeometryCollection":
o.geometries.forEach(geometry);
break;
case "LineString":
extract1(o.arcs);
break;
case "MultiLineString":
case "Polygon":
extract2(o.arcs);
break;
case "MultiPolygon":
extract3(o.arcs);
break;
}
}
geometry(object);
geomsByArc.forEach(filter == null ? function (geoms) {
arcs.push(geoms[0].i);
} : function (geoms) {
if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i);
});
return arcs;
}
function planarRingArea(ring) {
var i = -1,
n = ring.length,
a,
b = ring[n - 1],
area = 0;
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
return Math.abs(area); // Note: doubled area!
}
function merge (topology) {
return object(topology, mergeArcs.apply(this, arguments));
}
function mergeArcs(topology, objects) {
var polygonsByArc = {},
polygons = [],
groups = [];
objects.forEach(geometry);
function geometry(o) {
switch (o.type) {
case "GeometryCollection":
o.geometries.forEach(geometry);
break;
case "Polygon":
extract(o.arcs);
break;
case "MultiPolygon":
o.arcs.forEach(extract);
break;
}
}
function extract(polygon) {
polygon.forEach(function (ring) {
ring.forEach(function (arc) {
(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
});
});
polygons.push(polygon);
}
function area(ring) {
return planarRingArea(object(topology, {
type: "Polygon",
arcs: [ring]
}).coordinates[0]);
}
polygons.forEach(function (polygon) {
if (!polygon._) {
var group = [],
neighbors = [polygon];
polygon._ = 1;
groups.push(group);
while (polygon = neighbors.pop()) {
group.push(polygon);
polygon.forEach(function (ring) {
ring.forEach(function (arc) {
polygonsByArc[arc < 0 ? ~arc : arc].forEach(function (polygon) {
if (!polygon._) {
polygon._ = 1;
neighbors.push(polygon);
}
});
});
});
}
}
});
polygons.forEach(function (polygon) {
delete polygon._;
});
return {
type: "MultiPolygon",
arcs: groups.map(function (polygons) {
var arcs = [],
n;
// Extract the exterior (unique) arcs.
polygons.forEach(function (polygon) {
polygon.forEach(function (ring) {
ring.forEach(function (arc) {
if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
arcs.push(arc);
}
});
});
});
// Stitch the arcs into one or more rings.
arcs = stitch(topology, arcs);
// If more than one ring is returned,
// at most one of these rings can be the exterior;
// choose the one with the greatest absolute area.
if ((n = arcs.length) > 1) {
for (var i = 1, k = area(arcs[0]), ki, t; i < n; ++i) {
if ((ki = area(arcs[i])) > k) {
t = arcs[0], arcs[0] = arcs[i], arcs[i] = t, k = ki;
}
}
}
return arcs;
}).filter(function (arcs) {
return arcs.length > 0;
})
};
}
function bisect$1 (a, x) {
var lo = 0,
hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (a[mid] < x) lo = mid + 1;else hi = mid;
}
return lo;
}
function neighbors (objects) {
var indexesByArc = {},
// arc index -> array of object indexes
neighbors = objects.map(function () {
return [];
});
function line(arcs, i) {
arcs.forEach(function (a) {
if (a < 0) a = ~a;
var o = indexesByArc[a];
if (o) o.push(i);else indexesByArc[a] = [i];
});
}
function polygon(arcs, i) {
arcs.forEach(function (arc) {
line(arc, i);
});
}
function geometry(o, i) {
if (o.type === "GeometryCollection") o.geometries.forEach(function (o) {
geometry(o, i);
});else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function (arcs, i) {
arcs.forEach(function (arc) {
polygon(arc, i);
});
}
};
objects.forEach(geometry);
for (var i in indexesByArc) {
for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
for (var k = j + 1; k < m; ++k) {
var ij = indexes[j],
ik = indexes[k],
n;
if ((n = neighbors[ij])[i = bisect$1(n, ik)] !== ik) n.splice(i, 0, ik);
if ((n = neighbors[ik])[i = bisect$1(n, ij)] !== ij) n.splice(i, 0, ij);
}
}
}
return neighbors;
}
function untransform (transform) {
if (transform == null) return identity;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function (input, i) {
if (!i) x0 = y0 = 0;
var j = 2,
n = input.length,
output = new Array(n),
x1 = Math.round((input[0] - dx) / kx),
y1 = Math.round((input[1] - dy) / ky);
output[0] = x1 - x0, x0 = x1;
output[1] = y1 - y0, y0 = y1;
while (j < n) output[j] = input[j], ++j;
return output;
};
}
function quantize (topology, transform) {
if (topology.transform) throw new Error("already quantized");
if (!transform || !transform.scale) {
if (!((n = Math.floor(transform)) >= 2)) throw new Error("n must be ≥2");
box = topology.bbox || bbox(topology);
var x0 = box[0],
y0 = box[1],
x1 = box[2],
y1 = box[3],
n;
transform = {
scale: [x1 - x0 ? (x1 - x0) / (n - 1) : 1, y1 - y0 ? (y1 - y0) / (n - 1) : 1],
translate: [x0, y0]
};
} else {
box = topology.bbox;
}
var t = untransform(transform),
box,
key,
inputs = topology.objects,
outputs = {};
function quantizePoint(point) {
return t(point);
}
function quantizeGeometry(input) {
var output;
switch (input.type) {
case "GeometryCollection":
output = {
type: "GeometryCollection",
geometries: input.geometries.map(quantizeGeometry)
};
break;
case "Point":
output = {
type: "Point",
coordinates: quantizePoint(input.coordinates)
};
break;
case "MultiPoint":
output = {
type: "MultiPoint",
coordinates: input.coordinates.map(quantizePoint)
};
break;
default:
return input;
}
if (input.id != null) output.id = input.id;
if (input.bbox != null) output.bbox = input.bbox;
if (input.properties != null) output.properties = input.properties;
return output;
}
function quantizeArc(input) {
var i = 0,
j = 1,
n = input.length,
p,
output = new Array(n); // pessimistic
output[0] = t(input[0], 0);
while (++i < n) if ((p = t(input[i], i))[0] || p[1]) output[j++] = p; // non-coincident points
if (j === 1) output[j++] = [0, 0]; // an arc must have at least two points
output.length = j;
return output;
}
for (key in inputs) outputs[key] = quantizeGeometry(inputs[key]);
return {
type: "Topology",
bbox: box,
transform: transform,
objects: outputs,
arcs: topology.arcs.map(quantizeArc)
};
}
var src = /*#__PURE__*/Object.freeze({
__proto__: null,
bbox: bbox,
feature: feature,
merge: merge,
mergeArcs: mergeArcs,
mesh: mesh,
meshArcs: meshArcs,
neighbors: neighbors,
quantize: quantize,
transform: transform,
untransform: untransform
});
var require$$1 = /*@__PURE__*/getAugmentedNamespace(src);
var geojsonLinestringDissolve = mergeViableLineStrings;
// [Number, Number] -> String
function coordId(coord) {
return coord[0].toString() + ',' + coord[1].toString();
}
// LineString, LineString -> LineString
function mergeLineStrings(a, b) {
var s1 = coordId(a.coordinates[0]);
var e1 = coordId(a.coordinates[a.coordinates.length - 1]);
var s2 = coordId(b.coordinates[0]);
var e2 = coordId(b.coordinates[b.coordinates.length - 1]);
// TODO: handle case where more than one of these is true!
var coords;
if (s1 === e2) {
coords = b.coordinates.concat(a.coordinates.slice(1));
} else if (s2 === e1) {
coords = a.coordinates.concat(b.coordinates.slice(1));
} else if (s1 === s2) {
coords = a.coordinates.slice(1).reverse().concat(b.coordinates);
} else if (e1 === e2) {
coords = a.coordinates.concat(b.coordinates.reverse().slice(1));
} else {
return null;
}
return {
type: 'LineString',
coordinates: coords
};
}
// Merges all connected (non-forking, non-junctioning) line strings into single
// line strings.
// [LineString] -> LineString|MultiLineString
function mergeViableLineStrings(geoms) {
// TODO: assert all are linestrings
var lineStrings = geoms.slice();
var result = [];
while (lineStrings.length > 0) {
var ls = lineStrings.shift();
// Attempt to merge this LineString with the other LineStrings, updating
// the reference as it is merged with others and grows.
lineStrings = lineStrings.reduce(function (accum, cur) {
var merged = mergeLineStrings(ls, cur);
if (merged) {
// Accumulate the merged LineString
ls = merged;
} else {
// Put the unmerged LineString back into the list
accum.push(cur);
}
return accum;
}, []);
result.push(ls);
}
if (result.length === 1) {
result = result[0];
} else {
result = {
type: 'MultiLineString',
coordinates: result.map(function (ls) {
return ls.coordinates;
})
};
}
return result;
}
var meta = {};
/**
* Callback for coordEach
*
* @private
* @callback coordEachCallback
* @param {[number, 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 {Object} layer 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(layer, callback, excludeWrapCoord) {
var i,
j,
k,
g,
l,
geometry,
stopG,
coords,
geometryMaybeCollection,
wrapShrink = 0,
currentIndex = 0,
isGeometryCollection,
isFeatureCollection = layer.type === 'FeatureCollection',
isFeature = layer.type === 'Feature',
stop = isFeatureCollection ? layer.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 ? layer.features[i].geometry : isFeature ? layer.geometry : layer;
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');
}
}
}
}
meta.coordEach = coordEach;
/**
* 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 {Object} layer 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(layer, callback, initialValue, excludeWrapCoord) {
var previousValue = initialValue;
coordEach(layer, function (currentCoords, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentCoords;
} else {
previousValue = callback(previousValue, currentCoords, currentIndex);
}
}, excludeWrapCoord);
return previousValue;
}
meta.coordReduce = coordReduce;
/**
* 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 {Object} layer 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(layer, callback) {
var i;
switch (layer.type) {
case 'FeatureCollection':
for (i = 0; i < layer.features.length; i++) {
callback(layer.features[i].properties, i);
}
break;
case 'Feature':
callback(layer.properties, 0);
break;
}
}
meta.propEach = propEach;
/**
* 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 {Object} layer 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(layer, callback, initialValue) {
var previousValue = initialValue;
propEach(layer, function (currentProperties, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentProperties;
} else {
previousValue = callback(previousValue, currentProperties, currentIndex);
}
});
return previousValue;
}
meta.propReduce = propReduce;
/**
* 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 {Object} layer 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$1(layer, callback) {
if (layer.type === 'Feature') {
callback(layer, 0);
} else if (layer.type === 'FeatureCollection') {
for (var i = 0; i < layer.features.length; i++) {
callback(layer.features[i], i);
}
}
}
meta.featureEach = featureEach$1;
/**
* 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 {Object} layer 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(layer, callback, initialValue) {
var previousValue = initialValue;
featureEach$1(layer, function (currentFeature, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentFeature;
} else {
previousValue = callback(previousValue, currentFeature, currentIndex);
}
});
return previousValue;
}
meta.featureReduce = featureReduce;
/**
* Get all coordinates from any GeoJSON object.
*
* @name coordAll
* @param {Object} layer 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(layer) {
var coords = [];
coordEach(layer, function (coord) {
coords.push(coord);
});
return coords;
}
meta.coordAll = coordAll;
/**
* Iterate over each geometry in any GeoJSON object, similar to Array.forEach()
*
* @name geomEach
* @param {Object} layer any GeoJSON object
* @param {Function} callback a method that takes (currentGeometry, 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.geomEach(features, function (currentGeometry, currentIndex) {
* //=currentGeometry
* //=currentIndex
* });
*/
function geomEach$2(layer, callback) {
var i,
j,
g,
geometry,
stopG,
geometryMaybeCollection,
isGeometryCollection,
currentIndex = 0,
isFeatureCollection = layer.type === 'FeatureCollection',
isFeature = layer.type === 'Feature',
stop = isFeatureCollection ? layer.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 ? layer.features[i].geometry : isFeature ? layer.geometry : layer;
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);
currentIndex++;
} else if (geometry.type === 'GeometryCollection') {
for (j = 0; j < geometry.geometries.length; j++) {
callback(geometry.geometries[j], currentIndex);
currentIndex++;
}
} else {
throw new Error('Unknown Geometry Type');
}
}
}
}
meta.geomEach = geomEach$2;
/**
* 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.
*/
/**
* Reduce geometry in any GeoJSON object, similar to Array.reduce().
*
* @name geomReduce
* @param {Object} layer any GeoJSON object
* @param {Function} callback a method that takes (previousValue, currentGeometry, 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.geomReduce(features, function (previousValue, currentGeometry, currentIndex) {
* //=previousValue
* //=currentGeometry
* //=currentIndex
* return currentGeometry
* });
*/
function geomReduce(layer, callback, initialValue) {
var previousValue = initialValue;
geomEach$2(layer, function (currentGeometry, currentIndex) {
if (currentIndex === 0 && initialValue === undefined) {
previousValue = currentGeometry;
} else {
previousValue = callback(previousValue, currentGeometry, currentIndex);
}
});
return previousValue;
}
meta.geomReduce = geomReduce;
var dist = function e(t) {
switch (t && t.type || null) {
case "FeatureCollection":
return t.features = t.features.reduce(function (t, r) {
return t.concat(e(r));
}, []), t;
case "Feature":
return t.geometry ? e(t.geometry).map(function (e) {
var r = {
type: "Feature",
properties: JSON.parse(JSON.stringify(t.properties)),
geometry: e
};
return void 0 !== t.id && (r.id = t.id), r;
}) : t;
case "MultiPoint":
return t.coordinates.map(function (e) {
return {
type: "Point",
coordinates: e
};
});
case "MultiPolygon":
return t.coordinates.map(function (e) {
return {
type: "Polygon",
coordinates: e
};
});
case "MultiLineString":
return t.coordinates.map(function (e) {
return {
type: "LineString",
coordinates: e
};
});
case "GeometryCollection":
return t.geometries.map(e).reduce(function (e, t) {
return e.concat(t);
}, []);
case "Point":
case "Polygon":
case "LineString":
return [t];
}
};
var createTopology = require$$0.topology;
var mergeTopology = require$$1.merge;
var dissolveLineStrings = geojsonLinestringDissolve;
var geomEach$1 = meta.geomEach;
var flatten$1 = dist;
var geojsonDissolve = dissolve$1;
function toArray(args) {
if (!args.length) return [];
return Array.isArray(args[0]) ? args[0] : Array.prototype.slice.call(args);
}
function dissolvePolygons(geoms) {
// Topojson modifies in place, so we need to deep clone first
var objects = {
geoms: {
type: 'GeometryCollection',
geometries: JSON.parse(JSON.stringify(geoms))
}
};
var topo = createTopology(objects);
return mergeTopology(topo, topo.objects.geoms.geometries);
}
// [GeoJSON] -> String|Null
function getHomogenousType(geoms) {
var type = null;
for (var i = 0; i < geoms.length; i++) {
if (!type) {
type = geoms[i].type;
} else if (type !== geoms[i].type) {
return null;
}
}
return type;
}
// Transform function: attempts to dissolve geojson objects where possible
// [GeoJSON] -> GeoJSON geometry
function dissolve$1() {
// accept an array of geojson objects, or an argument list
var objects = toArray(arguments);
var geoms = objects.reduce(function (acc, o) {
// flatten any Multi-geom into features of simple types
var flat = flatten$1(o);
if (!Array.isArray(flat)) flat = [flat];
for (var i = 0; i < flat.length; i++) {
// get an array of all flatten geometry objects
geomEach$1(flat[i], function (geom) {
acc.push(geom);
});
}
return acc;
}, []);
// Assert homogenity
var type = getHomogenousType(geoms);
if (!type) {
throw new Error('List does not contain only homoegenous GeoJSON');
}
switch (type) {
case 'LineString':
return dissolveLineStrings(geoms);
case 'Polygon':
return dissolvePolygons(geoms);
default:
return geoms;
}
}
var geoDissolve = /*@__PURE__*/getDefaultExportFromCjs(geojsonDissolve);
const dissolve = (data, options) => {
return geoDissolve(data);
};
var simplifyGeojson = {exports: {}};
var Line$1 = function (p1, p2) {
this.p1 = p1;
this.p2 = p2;
};
Line$1.prototype.rise = function () {
return this.p2[1] - this.p1[1];
};
Line$1.prototype.run = function () {
return this.p2[0] - this.p1[0];
};
Line$1.prototype.slope = function () {
return this.rise() / this.run();
};
Line$1.prototype.yIntercept = function () {
return this.p1[1] - this.p1[0] * this.slope(this.p1, this.p2);
};
Line$1.prototype.isVertical = function () {
return !isFinite(this.slope());
};
Line$1.prototype.isHorizontal = function () {
return this.p1[1] == this.p2[1];
};
Line$1.prototype._perpendicularDistanceHorizontal = function (point) {
return Math.abs(this.p1[1] - point[1]);
};
Line$1.prototype._perpendicularDistanceVertical = function (point) {
return Math.abs(this.p1[0] - point[0]);
};
Line$1.prototype._perpendicularDistanceHasSlope = function (point) {
var slope = this.slope();
var y_intercept = this.yIntercept();
return Math.abs(slope * point[0] - point[1] + y_intercept) / Math.sqrt(Math.pow(slope, 2) + 1);
};
Line$1.prototype.perpendicularDistance = function (point) {
if (this.isVertical()) {
return this._perpendicularDistanceVertical(point);
} else if (this.isHorizontal()) {
return this._perpendicularDistanceHorizontal(point);
} else {
return this._perpendicularDistanceHasSlope(point);
}
};
var line = Line$1;
var Line = line;
var simplifyGeometry = function (points, tolerance) {
var dmax = 0;
var index = 0;
for (var i = 1; i <= points.length - 2; i++) {
var d = new Line(points[0], points[points.length - 1]).perpendicularDistance(points[i]);
if (d > dmax) {
index = i;
dmax = d;
}
}
if (dmax > tolerance) {
var results_one = simplifyGeometry(points.slice(0, index), tolerance);
var results_two = simplifyGeometry(points.slice(index, points.length), tolerance);
var results = results_one.concat(results_two);
} else if (points.length > 1) {
results = [points[0], points[points.length - 1]];
} else {
results = [points[0]];
}
return results;
};
var lib = simplifyGeometry;
(function (module) {
var simplify = lib;
module.exports = function (geojson, tolerance, dontClone) {
if (!dontClone) geojson = JSON.parse(JSON.stringify(geojson)); // clone obj
if (geojson.features) return simplifyFeatureCollection(geojson, tolerance);else if (geojson.type && geojson.type === 'Feature') return simplifyFeature(geojson, tolerance);else return new Error('FeatureCollection or individual Feature required');
};
module.exports.simplify = function (coordinates, tolerance) {
return simplify(coordinates, tolerance);
};
// modifies in-place
function simplifyFeature(feat, tolerance) {
var geom = feat.geometry;
var type = geom.type;
if (type === 'LineString') {
geom.coordinates = module.exports.simplify(geom.coordinates, tolerance);
} else if (type === 'Polygon' || type === 'MultiLineString') {
for (var j = 0; j < geom.coordinates.length; j++) {
geom.coordinates[j] = module.exports.simplify(geom.coordinates[j], tolerance);
}
} else if (type === 'MultiPolygon') {
for (var k = 0; k < geom.coordinates.length; k++) {
for (var l = 0; l < geom.coordinates[k].length; l++) {
geom.coordinates[k][l] = module.exports.simplify(geom.coordinates[k][l], tolerance);
}
}
}
return feat;
}
// modifies in-place
function simplifyFeatureCollection(fc, tolerance) {
// process all LineString features, skip non LineStrings
for (var i = 0; i < fc.features.length; i++) {
fc.features[i] = simplifyFeature(fc.features[i], tolerance);
}
return fc;
}
})(simplifyGeojson);
var simplifyGeojsonExports = simplifyGeojson.exports;
var geoSimplify = /*@__PURE__*/getDefaultExportFromCjs(simplifyGeojsonExports);
const mergeDeepImmer = function (target, ...sources) {
return mergeOption(cloneDeep(target), ...sources);
};
function _mergeOptionDeep(target, source, key) {
const sourceValue = source[key];
if (sourceValue === undefined) {
target[key] = null;
}
else if (isObject$2(sourceValue)) {
if (!isObject$2(target[key])) {
target[key] = {};
}
for (const _key in sourceValue) {
_mergeOptionDeep(target[key], sourceValue, _key);
}
}
else {
target[key] = sourceValue;
}
}
function _mergeOptionBase(target, source) {
if (!isObject$2(source)) {
return;
}
if (target === source) {
return;
}
for (const key in source) {
_mergeOptionDeep(target, source, key);
}
}
function mergeOption(target, ...sources) {
if (!target) {
target = {};
}
let sourceIndex = -1;
const length = sources.length;
while (++sourceIndex < length) {
const source = sources[sourceIndex];
_mergeOptionBase(target, source);
}
return target;
}
const DEFAULT_SIMPLIFY_OPTIONS = {
tolerance: 0.01
};
const simplify = (data, options) => {
const mergeOptions = mergeDeepImmer(DEFAULT_SIMPLIFY_OPTIONS, options);
const { tolerance } = mergeOptions;
return geoSimplify(data, tolerance);
};
const mercator = (data, options) => {
const points = [];
data.forEach(item => {
const [x, y] = project([item.lng, item.lat]);
points.push(Object.assign(Object.assign({}, item), { coordinates: [x, y] }));
});
return points;
};
/**
* [Simple linear regression](http://en.wikipedia.org/wiki/Simple_linear_regression)
* is a simple way to find a fitted line
* between a set of coordinates. This algorithm finds the slope and y-intercept of a regression line
* using the least sum of squares.
*
* @param {Array<Array<number>>} data an array of two-element of arrays,
* like `[[0, 1], [2, 3]]`
* @returns {Object} object containing slope and intersect of regression line
* @example
* linearRegression([[0, 0], [1, 1]]); // => { m: 1, b: 0 }
*/
function linearRegression(data) {
var m, b;
// Store data length in a local variable to reduce
// repeated object property lookups
var dataLength = data.length;
//if there's only one point, arbitrarily choose a slope of 0
//and a y-intercept of whatever the y of the initial point is
if (dataLength === 1) {
m = 0;
b = data[0][1];
} else {
// Initialize our sums and scope the `m` and `b`
// variables that define the line.
var sumX = 0,
sumY = 0,
sumXX = 0,
sumXY = 0;
// Use local variables to grab point values
// with minimal object property lookups
var point, x, y;
// Gather the sum of all x values, the sum of all
// y values, and the sum of x^2 and (x*y) for each
// value.
//
// In math notation, these would be SS_x, SS_y, SS_xx, and SS_xy
for (var i = 0; i < dataLength; i++) {
point = data[i];
x = point[0];
y = point[1];
sumX += x;
sumY += y;
sumXX += x * x;
sumXY += x * y;
}
// `m` is the slope of the regression line
m = (dataLength * sumXY - sumX * sumY) / (dataLength * sumXX - sumX * sumX);
// `b` is the y-intercept of the line.
b = sumY / dataLength - m * sumX / dataLength;
}
// Return both values as an object.
return {
m: m,
b: b
};
}
/**
* Given the output of `linearRegression`: an object
* with `m` and `b` values indicating slope and intercept,
* respectively, generate a line function that translates
* x values into y values.
*
* @param {Object} mb object with `m` and `b` members, representing
* slope and intersect of desired line
* @returns {Function} method that computes y-value at any given
* x-value on the line.
* @example
* var l = linearRegressionLine(linearRegression([[0, 0], [1, 1]]));
* l(0) // = 0
* l(2) // = 2
* linearRegressionLine({ b: 0, m: 1 })(1); // => 1
* linearRegressionLine({ b: 1, m: 1 })(1); // => 2
*/
function linearRegressionLine(mb /*: { b: number, m: number }*/) {
// Return a function that computes a `y` value for each
// x value it is given, based on the values of `b` and `a`
// that we just computed.
return function (x) {
return mb.b + mb.m * x;
};
}
/**
* Our default sum is the [Kahan-Babuska algorithm](https://pdfs.semanticscholar.org/1760/7d467cda1d0277ad272deb2113533131dc09.pdf).
* This method is an improvement over the classical
* [Kahan summation algorithm](https://en.wikipedia.org/wiki/Kahan_summation_algorithm).
* It aims at computing the sum of a list of numbers while correcting for
* floating-point errors. Traditionally, sums are calculated as many
* successive additions, each one with its own floating-point roundoff. These
* losses in precision add up as the number of numbers increases. This alternative
* algorithm is more accurate than the simple way of calculating sums by simple
* addition.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x input
* @return {number} sum of all input numbers
* @example
* sum([1, 2, 3]); // => 6
*/
function sum(x) {
// If the array is empty, we needn't bother computing its sum
if (x.length === 0) {
return 0;
}
// Initializing the sum as the first number in the array
var sum = x[0];
// Keeping track of the floating-point error correction
var correction = 0;
var transition;
if (typeof sum !== "number") {
return NaN;
}
for (var i = 1; i < x.length; i++) {
if (typeof x[i] !== "number") {
return NaN;
}
transition = sum + x[i];
// Here we need to update the correction in a different fashion
// if the new absolute value is greater than the absolute sum
if (Math.abs(sum) >= Math.abs(x[i])) {
correction += sum - transition + x[i];
} else {
correction += x[i] - transition + sum;
}
sum = transition;
}
// Returning the corrected sum
return sum + correction;
}
/**
* The mean, _also known as average_,
* is the sum of all values over the number of values.
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @throws {Error} if the length of x is less than one
* @returns {number} mean
* @example
* mean([0, 10]); // => 5
*/
function mean(x) {
if (x.length === 0) {
throw new Error("mean requires at least one data point");
}
return sum(x) / x.length;
}
/**
* The sum of deviations to the Nth power.
* When n=2 it's the sum of squared deviations.
* When n=3 it's the sum of cubed deviations.
*
* @param {Array<number>} x
* @param {number} n power
* @returns {number} sum of nth power deviations
*
* @example
* var input = [1, 2, 3];
* // since the variance of a set is the mean squared
* // deviations, we can calculate that with sumNthPowerDeviations:
* sumNthPowerDeviations(input, 2) / input.length;
*/
function sumNthPowerDeviations(x, n) {
var meanValue = mean(x);
var sum = 0;
var tempValue;
var i;
// This is an optimization: when n is 2 (we're computing a number squared),
// multiplying the number by itself is significantly faster than using
// the Math.pow method.
if (n === 2) {
for (i = 0; i < x.length; i++) {
tempValue = x[i] - meanValue;
sum += tempValue * tempValue;
}
} else {
for (i = 0; i < x.length; i++) {
sum += Math.pow(x[i] - meanValue, n);
}
}
return sum;
}
/**
* The [variance](http://en.wikipedia.org/wiki/Variance)
* is the sum of squared deviations from the mean.
*
* This is an implementation of variance, not sample variance:
* see the `sampleVariance` method if you want a sample measure.
*
* @param {Array<number>} x a population of one or more data points
* @returns {number} variance: a value greater than or equal to zero.
* zero indicates that all values are identical.
* @throws {Error} if x's length is 0
* @example
* variance([1, 2, 3, 4, 5, 6]); // => 2.9166666666666665
*/
function variance(x) {
if (x.length === 0) {
throw new Error("variance requires at least one data point");
}
// Find the mean of squared deviations between the
// mean value and each value.
return sumNthPowerDeviations(x, 2) / x.length;
}
/**
* The [standard deviation](http://en.wikipedia.org/wiki/Standard_deviation)
* is the square root of the variance. This is also known as the population
* standard deviation. It's useful for measuring the amount
* of variation or dispersion in a set of values.
*
* Standard deviation is only appropriate for full-population knowledge: for
* samples of a population, {@link sampleStandardDeviation} is
* more appropriate.
*
* @param {Array<number>} x input
* @returns {number} standard deviation
* @example
* variance([2, 4, 4, 4, 5, 5, 7, 9]); // => 4
* standardDeviation([2, 4, 4, 4, 5, 5, 7, 9]); // => 2
*/
function standardDeviation(x) {
if (x.length === 1) {
return 0;
}
var v = variance(x);
return Math.sqrt(v);
}
/**
* The [R Squared](http://en.wikipedia.org/wiki/Coefficient_of_determination)
* value of data compared with a function `f`
* is the sum of the squared differences between the prediction
* and the actual value.
*
* @param {Array<Array<number>>} x input data: this should be doubly-nested
* @param {Function} func function called on `[i][0]` values within the dataset
* @returns {number} r-squared value
* @example
* var samples = [[0, 0], [1, 1]];
* var regressionLine = linearRegressionLine(linearRegression(samples));
* rSquared(samples, regressionLine); // = 1 this line is a perfect fit
*/
function rSquared(x, func) {
if (x.length < 2) {
return 1;
}
// Compute the average y value for the actual
// data set in order to compute the
// _total sum of squares_
var sum = 0;
for (var i = 0; i < x.length; i++) {
sum += x[i][1];
}
var average = sum / x.length;
// Compute the total sum of squares - the
// squared difference between each point
// and the average of all points.
var sumOfSquares = 0;
for (var j = 0; j < x.length; j++) {
sumOfSquares += Math.pow(average - x[j][1], 2);
}
// Finally estimate the error: the squared
// difference between the estimate and the actual data
// value at each point.
var err = 0;
for (var k = 0; k < x.length; k++) {
err += Math.pow(x[k][1] - func(x[k][0]), 2);
}
// As the error grows larger, its ratio to the
// sum of squares increases and the r squared
// value grows lower.
return 1 - err / sumOfSquares;
}
/**
* The [mode](https://en.wikipedia.org/wiki/Mode_%28statistics%29) is the number
* that appears in a list the highest number of times.
* There can be multiple modes in a list: in the event of a tie, this
* algorithm will return the most recently seen mode.
*
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* This runs in `O(n)` because the input is sorted.
*
* @param {Array<number>} sorted a sample of one or more data points
* @returns {number} mode
* @throws {Error} if sorted is empty
* @example
* modeSorted([0, 0, 1]); // => 0
*/
function modeSorted(sorted) {
// Handle edge cases:
// The mode of an empty list is undefined
if (sorted.length === 0) {
throw new Error("mode requires at least one data point");
} else if (sorted.length === 1) {
return sorted[0];
}
// This assumes it is dealing with an array of size > 1, since size
// 0 and 1 are handled immediately. Hence it starts at index 1 in the
// array.
var last = sorted[0],
// store the mode as we find new modes
value = NaN,
// store how many times we've seen the mode
maxSeen = 0,
// how many times the current candidate for the mode
// has been seen
seenThis = 1;
// end at sorted.length + 1 to fix the case in which the mode is
// the highest number that occurs in the sequence. the last iteration
// compares sorted[i], which is undefined, to the highest number
// in the series
for (var i = 1; i < sorted.length + 1; i++) {
// we're seeing a new number pass by
if (sorted[i] !== last) {
// the last number is the new mode since we saw it more
// often than the old one
if (seenThis > maxSeen) {
maxSeen = seenThis;
value = last;
}
seenThis = 1;
last = sorted[i];
// if this isn't a new number, it's one more occurrence of
// the potential mode
} else {
seenThis++;
}
}
return value;
}
/**
* Sort an array of numbers by their numeric value, ensuring that the
* array is not changed in place.
*
* This is necessary because the default behavior of .sort
* in JavaScript is to sort arrays as string values
*
* [1, 10, 12, 102, 20].sort()
* // output
* [1, 10, 102, 12, 20]
*
* @param {Array<number>} x input array
* @return {Array<number>} sorted array
* @private
* @example
* numericSort([3, 2, 1]) // => [1, 2, 3]
*/
function numericSort(x) {
return x
// ensure the array is not changed in-place
.slice()
// comparator function that treats input as numeric
.sort(function (a, b) {
return a - b;
});
}
/**
* The [mode](https://en.wikipedia.org/wiki/Mode_%28statistics%29) is the number
* that appears in a list the highest number of times.
* There can be multiple modes in a list: in the event of a tie, this
* algorithm will return the most recently seen mode.
*
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* This runs in `O(n log(n))` because it needs to sort the array internally
* before running an `O(n)` search to find the mode.
*
* @param {Array<number>} x input
* @returns {number} mode
* @example
* mode([0, 0, 1]); // => 0
*/
function mode(x) {
// Sorting the array lets us iterate through it below and be sure
// that every time we see a new number it's new and we'll never
// see the same number twice
return modeSorted(numericSort(x));
}
/* globals Map: false */
/**
* The [mode](https://en.wikipedia.org/wiki/Mode_%28statistics%29) is the number
* that appears in a list the highest number of times.
* There can be multiple modes in a list: in the event of a tie, this
* algorithm will return the most recently seen mode.
*
* modeFast uses a Map object to keep track of the mode, instead of the approach
* used with `mode`, a sorted array. As a result, it is faster
* than `mode` and supports any data type that can be compared with `==`.
* It also requires a
* [JavaScript environment with support for Map](https://kangax.github.io/compat-table/es6/#test-Map),
* and will throw an error if Map is not available.
*
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* @param {Array<*>} x a sample of one or more data points
* @returns {?*} mode
* @throws {ReferenceError} if the JavaScript environment doesn't support Map
* @throws {Error} if x is empty
* @example
* modeFast(['rabbits', 'rabbits', 'squirrels']); // => 'rabbits'
*/
function modeFast(x) {
// This index will reflect the incidence of different values, indexing
// them like
// { value: count }
var index = new Map();
// A running `mode` and the number of times it has been encountered.
var mode;
var modeCount = 0;
for (var i = 0; i < x.length; i++) {
var newCount = index.get(x[i]);
if (newCount === undefined) {
newCount = 1;
} else {
newCount++;
}
if (newCount > modeCount) {
mode = x[i];
modeCount = newCount;
}
index.set(x[i], newCount);
}
if (modeCount === 0) {
throw new Error("mode requires at last one data point");
}
return mode;
}
/**
* The min is the lowest number in the array.
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @throws {Error} if the length of x is less than one
* @returns {number} minimum value
* @example
* min([1, 5, -10, 100, 2]); // => -10
*/
function min(x) {
if (x.length === 0) {
throw new Error("min requires at least one data point");
}
var value = x[0];
for (var i = 1; i < x.length; i++) {
if (x[i] < value) {
value = x[i];
}
}
return value;
}
/**
* This computes the maximum number in an array.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @returns {number} maximum value
* @throws {Error} if the length of x is less than one
* @example
* max([1, 2, 3, 4]);
* // => 4
*/
function max(x) {
if (x.length === 0) {
throw new Error("max requires at least one data point");
}
var value = x[0];
for (var i = 1; i < x.length; i++) {
if (x[i] > value) {
value = x[i];
}
}
return value;
}
/**
* This computes the minimum & maximum number in an array.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @returns {Array<number>} minimum & maximum value
* @throws {Error} if the length of x is less than one
* @example
* extent([1, 2, 3, 4]);
* // => [1, 4]
*/
function extent(x) {
if (x.length === 0) {
throw new Error("extent requires at least one data point");
}
var min = x[0];
var max = x[0];
for (var i = 1; i < x.length; i++) {
if (x[i] > max) {
max = x[i];
}
if (x[i] < min) {
min = x[i];
}
}
return [min, max];
}
/**
* The minimum is the lowest number in the array. With a sorted array,
* the first element in the array is always the smallest, so this calculation
* can be done in one step, or constant time.
*
* @param {Array<number>} x input
* @returns {number} minimum value
* @example
* minSorted([-100, -10, 1, 2, 5]); // => -100
*/
function minSorted(x) {
return x[0];
}
/**
* The maximum is the highest number in the array. With a sorted array,
* the last element in the array is always the largest, so this calculation
* can be done in one step, or constant time.
*
* @param {Array<number>} x input
* @returns {number} maximum value
* @example
* maxSorted([-100, -10, 1, 2, 5]); // => 5
*/
function maxSorted(x) {
return x[x.length - 1];
}
/**
* The extent is the lowest & highest number in the array. With a sorted array,
* the first element in the array is always the lowest while the last element is always the largest, so this calculation
* can be done in one step, or constant time.
*
* @param {Array<number>} x input
* @returns {Array<number>} minimum & maximum value
* @example
* extentSorted([-100, -10, 1, 2, 5]); // => [-100, 5]
*/
function extentSorted(x) {
return [x[0], x[x.length - 1]];
}
/**
* The simple [sum](https://en.wikipedia.org/wiki/Summation) of an array
* is the result of adding all numbers together, starting from zero.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x input
* @return {number} sum of all input numbers
* @example
* sumSimple([1, 2, 3]); // => 6
*/
function sumSimple(x) {
var value = 0;
for (var i = 0; i < x.length; i++) {
if (typeof x[i] !== "number") {
return NaN;
}
value += x[i];
}
return value;
}
/**
* The [product](https://en.wikipedia.org/wiki/Product_(mathematics)) of an array
* is the result of multiplying all numbers together, starting using one as the multiplicative identity.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x input
* @return {number} product of all input numbers
* @example
* product([1, 2, 3, 4]); // => 24
*/
function product(x) {
var value = 1;
for (var i = 0; i < x.length; i++) {
value *= x[i];
}
return value;
}
/**
* This is the internal implementation of quantiles: when you know
* that the order is sorted, you don't need to re-sort it, and the computations
* are faster.
*
* @param {Array<number>} x sample of one or more data points
* @param {number} p desired quantile: a number between 0 to 1, inclusive
* @returns {number} quantile value
* @throws {Error} if p ix outside of the range from 0 to 1
* @throws {Error} if x is empty
* @example
* quantileSorted([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20], 0.5); // => 9
*/
function quantileSorted(x, p) {
var idx = x.length * p;
if (x.length === 0) {
throw new Error("quantile requires at least one data point.");
} else if (p < 0 || p > 1) {
throw new Error("quantiles must be between 0 and 1");
} else if (p === 1) {
// If p is 1, directly return the last element
return x[x.length - 1];
} else if (p === 0) {
// If p is 0, directly return the first element
return x[0];
} else if (idx % 1 !== 0) {
// If p is not integer, return the next element in array
return x[Math.ceil(idx) - 1];
} else if (x.length % 2 === 0) {
// If the list has even-length, we'll take the average of this number
// and the next value, if there is one
return (x[idx - 1] + x[idx]) / 2;
} else {
// Finally, in the simple case of an integer value
// with an odd-length list, return the x value at the index.
return x[idx];
}
}
/**
* Rearrange items in `arr` so that all items in `[left, k]` range are the smallest.
* The `k`-th element will have the `(k - left + 1)`-th smallest value in `[left, right]`.
*
* Implements Floyd-Rivest selection algorithm https://en.wikipedia.org/wiki/Floyd-Rivest_algorithm
*
* @param {Array<number>} arr input array
* @param {number} k pivot index
* @param {number} [left] left index
* @param {number} [right] right index
* @returns {void} mutates input array
* @example
* var arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39];
* quickselect(arr, 8);
* // = [39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95]
*/
function quickselect(arr, k, left, right) {
left = left || 0;
right = right || arr.length - 1;
while (right > left) {
// 600 and 0.5 are arbitrary constants chosen in the original paper to minimize execution time
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n);
if (m - n / 2 < 0) {
sd *= -1;
}
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselect(arr, k, newLeft, newRight);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (arr[right] > t) {
swap(arr, left, right);
}
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (arr[i] < t) {
i++;
}
while (arr[j] > t) {
j--;
}
}
if (arr[left] === t) {
swap(arr, left, j);
} else {
j++;
swap(arr, j, right);
}
if (j <= k) {
left = j + 1;
}
if (k <= j) {
right = j - 1;
}
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* The [quantile](https://en.wikipedia.org/wiki/Quantile):
* this is a population quantile, since we assume to know the entire
* dataset in this library. This is an implementation of the
* [Quantiles of a Population](http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population)
* algorithm from wikipedia.
*
* Sample is a one-dimensional array of numbers,
* and p is either a decimal number from 0 to 1 or an array of decimal
* numbers from 0 to 1.
* In terms of a k/q quantile, p = k/q - it's just dealing with fractions or dealing
* with decimal values.
* When p is an array, the result of the function is also an array containing the appropriate
* quantiles in input order
*
* @param {Array<number>} x sample of one or more numbers
* @param {Array<number> | number} p the desired quantile, as a number between 0 and 1
* @returns {number} quantile
* @example
* quantile([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20], 0.5); // => 9
*/
function quantile(x, p) {
var copy = x.slice();
if (Array.isArray(p)) {
// rearrange elements so that each element corresponding to a requested
// quantile is on a place it would be if the array was fully sorted
multiQuantileSelect(copy, p);
// Initialize the result array
var results = [];
// For each requested quantile
for (var i = 0; i < p.length; i++) {
results[i] = quantileSorted(copy, p[i]);
}
return results;
} else {
var idx = quantileIndex(copy.length, p);
quantileSelect(copy, idx, 0, copy.length - 1);
return quantileSorted(copy, p);
}
}
function quantileSelect(arr, k, left, right) {
if (k % 1 === 0) {
quickselect(arr, k, left, right);
} else {
k = Math.floor(k);
quickselect(arr, k, left, right);
quickselect(arr, k + 1, k + 1, right);
}
}
function multiQuantileSelect(arr, p) {
var indices = [0];
for (var i = 0; i < p.length; i++) {
indices.push(quantileIndex(arr.length, p[i]));
}
indices.push(arr.length - 1);
indices.sort(compare);
var stack = [0, indices.length - 1];
while (stack.length) {
var r = Math.ceil(stack.pop());
var l = Math.floor(stack.pop());
if (r - l <= 1) {
continue;
}
var m = Math.floor((l + r) / 2);
quantileSelect(arr, indices[m], Math.floor(indices[l]), Math.ceil(indices[r]));
stack.push(l, m, m, r);
}
}
function compare(a, b) {
return a - b;
}
function quantileIndex(len, p) {
var idx = len * p;
if (p === 1) {
// If p is 1, directly return the last index
return len - 1;
} else if (p === 0) {
// If p is 0, directly return the first index
return 0;
} else if (idx % 1 !== 0) {
// If index is not integer, return the next index in array
return Math.ceil(idx) - 1;
} else if (len % 2 === 0) {
// If the list has even-length, we'll return the middle of two indices
// around quantile to indicate that we need an average value of the two
return idx - 0.5;
} else {
// Finally, in the simple case of an integer index
// with an odd-length list, return the index
return idx;
}
}
/* eslint no-bitwise: 0 */
/**
* This function returns the quantile in which one would find the given value in
* the given array. With a sorted array, leveraging binary search, we can find
* this information in logarithmic time.
*
* @param {Array<number>} x input
* @returns {number} value value
* @example
* quantileRankSorted([1, 2, 3, 4], 3); // => 0.75
* quantileRankSorted([1, 2, 3, 3, 4], 3); // => 0.7
* quantileRankSorted([1, 2, 3, 4], 6); // => 1
* quantileRankSorted([1, 2, 3, 3, 5], 4); // => 0.8
*/
function quantileRankSorted(x, value) {
// Value is lesser than any value in the array
if (value < x[0]) {
return 0;
}
// Value is greater than any value in the array
if (value > x[x.length - 1]) {
return 1;
}
var l = lowerBound(x, value);
// Value is not in the array
if (x[l] !== value) {
return l / x.length;
}
l++;
var u = upperBound(x, value);
// The value exists only once in the array
if (u === l) {
return l / x.length;
}
// Here, we are basically computing the mean of the range of indices
// containing our searched value. But, instead, of initializing an
// array and looping over it, there is a dedicated math formula that
// we apply below to get the result.
var r = u - l + 1;
var sum = r * (u + l) / 2;
var mean = sum / r;
return mean / x.length;
}
function lowerBound(x, value) {
var mid = 0;
var lo = 0;
var hi = x.length;
while (lo < hi) {
mid = lo + hi >>> 1;
if (value <= x[mid]) {
hi = mid;
} else {
lo = -~mid;
}
}
return lo;
}
function upperBound(x, value) {
var mid = 0;
var lo = 0;
var hi = x.length;
while (lo < hi) {
mid = lo + hi >>> 1;
if (value >= x[mid]) {
lo = -~mid;
} else {
hi = mid;
}
}
return lo;
}
/**
* This function returns the quantile in which one would find the given value in
* the given array. It will copy and sort your array before each run, so
* if you know your array is already sorted, you should use `quantileRankSorted`
* instead.
*
* @param {Array<number>} x input
* @returns {number} value value
* @example
* quantileRank([4, 3, 1, 2], 3); // => 0.75
* quantileRank([4, 3, 2, 3, 1], 3); // => 0.7
* quantileRank([2, 4, 1, 3], 6); // => 1
* quantileRank([5, 3, 1, 2, 3], 4); // => 0.8
*/
function quantileRank(x, value) {
// Cloning and sorting the array
var sortedCopy = numericSort(x);
return quantileRankSorted(sortedCopy, value);
}
/**
* The [Interquartile range](http://en.wikipedia.org/wiki/Interquartile_range) is
* a measure of statistical dispersion, or how scattered, spread, or
* concentrated a distribution is. It's computed as the difference between
* the third quartile and first quartile.
*
* @param {Array<number>} x sample of one or more numbers
* @returns {number} interquartile range: the span between lower and upper quartile,
* 0.25 and 0.75
* @example
* interquartileRange([0, 1, 2, 3]); // => 2
*/
function interquartileRange(x) {
// Interquartile range is the span between the upper quartile,
// at `0.75`, and lower quartile, `0.25`
var q1 = quantile(x, 0.75);
var q2 = quantile(x, 0.25);
if (typeof q1 === "number" && typeof q2 === "number") {
return q1 - q2;
}
}
/**
* The [median](http://en.wikipedia.org/wiki/Median) is
* the middle number of a list. This is often a good indicator of 'the middle'
* when there are outliers that skew the `mean()` value.
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* The median isn't necessarily one of the elements in the list: the value
* can be the average of two elements if the list has an even length
* and the two central values are different.
*
* @param {Array<number>} x input
* @returns {number} median value
* @example
* median([10, 2, 5, 100, 2, 1]); // => 3.5
*/
function median(x) {
return +quantile(x, 0.5);
}
/**
* The [Median Absolute Deviation](http://en.wikipedia.org/wiki/Median_absolute_deviation) is
* a robust measure of statistical
* dispersion. It is more resilient to outliers than the standard deviation.
*
* @param {Array<number>} x input array
* @returns {number} median absolute deviation
* @example
* medianAbsoluteDeviation([1, 1, 2, 2, 4, 6, 9]); // => 1
*/
function medianAbsoluteDeviation(x) {
var medianValue = median(x);
var medianAbsoluteDeviations = [];
// Make a list of absolute deviations from the median
for (var i = 0; i < x.length; i++) {
medianAbsoluteDeviations.push(Math.abs(x[i] - medianValue));
}
// Find the median value of that list
return median(medianAbsoluteDeviations);
}
/**
* Split an array into chunks of a specified size. This function
* has the same behavior as [PHP's array_chunk](http://php.net/manual/en/function.array-chunk.php)
* function, and thus will insert smaller-sized chunks at the end if
* the input size is not divisible by the chunk size.
*
* `x` is expected to be an array, and `chunkSize` a number.
* The `x` array can contain any kind of data.
*
* @param {Array} x a sample
* @param {number} chunkSize size of each output array. must be a positive integer
* @returns {Array<Array>} a chunked array
* @throws {Error} if chunk size is less than 1 or not an integer
* @example
* chunk([1, 2, 3, 4, 5, 6], 2);
* // => [[1, 2], [3, 4], [5, 6]]
*/
function chunk(x, chunkSize) {
// a list of result chunks, as arrays in an array
var output = [];
// `chunkSize` must be zero or higher - otherwise the loop below,
// in which we call `start += chunkSize`, will loop infinitely.
// So, we'll detect and throw in that case to indicate
// invalid input.
if (chunkSize < 1) {
throw new Error("chunk size must be a positive number");
}
if (Math.floor(chunkSize) !== chunkSize) {
throw new Error("chunk size must be an integer");
}
// `start` is the index at which `.slice` will start selecting
// new array elements
for (var start = 0; start < x.length; start += chunkSize) {
// for each chunk, slice that part of the array and add it
// to the output. The `.slice` function does not change
// the original array.
output.push(x.slice(start, start + chunkSize));
}
return output;
}
/**
* Sampling with replacement is a type of sampling that allows the same
* item to be picked out of a population more than once.
*
* @param {Array<*>} x an array of any kind of value
* @param {number} n count of how many elements to take
* @param {Function} [randomSource=Math.random] an optional entropy source that
* returns numbers between 0 inclusive and 1 exclusive: the range [0, 1)
* @return {Array} n sampled items from the population
* @example
* var values = [1, 2, 3, 4];
* sampleWithReplacement(values, 2); // returns 2 random values, like [2, 4];
*/
function sampleWithReplacement(x, n, randomSource) {
if (x.length === 0) {
return [];
}
// a custom random number source can be provided if you want to use
// a fixed seed or another random number generator, like
// [random-js](https://www.npmjs.org/package/random-js)
randomSource = randomSource || Math.random;
var length = x.length;
var sample = [];
for (var i = 0; i < n; i++) {
var index = Math.floor(randomSource() * length);
sample.push(x[index]);
}
return sample;
}
/**
* A [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
* in-place - which means that it **will change the order of the original
* array by reference**.
*
* This is an algorithm that generates a random [permutation](https://en.wikipedia.org/wiki/Permutation)
* of a set.
*
* @param {Array} x sample of one or more numbers
* @param {Function} [randomSource=Math.random] an optional entropy source that
* returns numbers between 0 inclusive and 1 exclusive: the range [0, 1)
* @returns {Array} x
* @example
* var x = [1, 2, 3, 4];
* shuffleInPlace(x);
* // x is shuffled to a value like [2, 1, 4, 3]
*/
function shuffleInPlace(x, randomSource) {
// a custom random number source can be provided if you want to use
// a fixed seed or another random number generator, like
// [random-js](https://www.npmjs.org/package/random-js)
randomSource = randomSource || Math.random;
// store the current length of the x to determine
// when no elements remain to shuffle.
var length = x.length;
// temporary is used to hold an item when it is being
// swapped between indices.
var temporary;
// The index to swap at each stage.
var index;
// While there are still items to shuffle
while (length > 0) {
// choose a random index within the subset of the array
// that is not yet shuffled
index = Math.floor(randomSource() * length--);
// store the value that we'll move temporarily
temporary = x[length];
// swap the value at `x[length]` with `x[index]`
x[length] = x[index];
x[index] = temporary;
}
return x;
}
/**
* A [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
* is a fast way to create a random permutation of a finite set. This is
* a function around `shuffle_in_place` that adds the guarantee that
* it will not modify its input.
*
* @param {Array} x sample of 0 or more numbers
* @param {Function} [randomSource=Math.random] an optional entropy source that
* returns numbers between 0 inclusive and 1 exclusive: the range [0, 1)
* @return {Array} shuffled version of input
* @example
* var shuffled = shuffle([1, 2, 3, 4]);
* shuffled; // = [2, 3, 1, 4] or any other random permutation
*/
function shuffle(x, randomSource) {
// slice the original array so that it is not modified
var sample = x.slice();
// and then shuffle that shallow-copied array, in place
return shuffleInPlace(sample, randomSource);
}
/**
* Create a [simple random sample](http://en.wikipedia.org/wiki/Simple_random_sample)
* from a given array of `n` elements.
*
* The sampled values will be in any order, not necessarily the order
* they appear in the input.
*
* @param {Array<any>} x input array. can contain any type
* @param {number} n count of how many elements to take
* @param {Function} [randomSource=Math.random] an optional entropy source that
* returns numbers between 0 inclusive and 1 exclusive: the range [0, 1)
* @return {Array} subset of n elements in original array
*
* @example
* var values = [1, 2, 4, 5, 6, 7, 8, 9];
* sample(values, 3); // returns 3 random values, like [2, 5, 8];
*/
function sample(x, n, randomSource) {
// shuffle the original array using a fisher-yates shuffle
var shuffled = shuffle(x, randomSource);
// and then return a subset of it - the first `n` elements.
return shuffled.slice(0, n);
}
/**
* Create a new column x row matrix.
*
* @private
* @param {number} columns
* @param {number} rows
* @return {Array<Array<number>>} matrix
* @example
* makeMatrix(10, 10);
*/
function makeMatrix(columns, rows) {
var matrix = [];
for (var i = 0; i < columns; i++) {
var column = [];
for (var j = 0; j < rows; j++) {
column.push(0);
}
matrix.push(column);
}
return matrix;
}
/**
* For a sorted input, counting the number of unique values
* is possible in constant time and constant memory. This is
* a simple implementation of the algorithm.
*
* Values are compared with `===`, so objects and non-primitive objects
* are not handled in any special way.
*
* @param {Array<*>} x an array of any kind of value
* @returns {number} count of unique values
* @example
* uniqueCountSorted([1, 2, 3]); // => 3
* uniqueCountSorted([1, 1, 1]); // => 1
*/
function uniqueCountSorted(x) {
var uniqueValueCount = 0,
lastSeenValue;
for (var i = 0; i < x.length; i++) {
if (i === 0 || x[i] !== lastSeenValue) {
lastSeenValue = x[i];
uniqueValueCount++;
}
}
return uniqueValueCount;
}
/**
* Generates incrementally computed values based on the sums and sums of
* squares for the data array
*
* @private
* @param {number} j
* @param {number} i
* @param {Array<number>} sums
* @param {Array<number>} sumsOfSquares
* @return {number}
* @example
* ssq(0, 1, [-1, 0, 2], [1, 1, 5]);
*/
function ssq(j, i, sums, sumsOfSquares) {
var sji; // s(j, i)
if (j > 0) {
var muji = (sums[i] - sums[j - 1]) / (i - j + 1); // mu(j, i)
sji = sumsOfSquares[i] - sumsOfSquares[j - 1] - (i - j + 1) * muji * muji;
} else {
sji = sumsOfSquares[i] - sums[i] * sums[i] / (i + 1);
}
if (sji < 0) {
return 0;
}
return sji;
}
/**
* Function that recursively divides and conquers computations
* for cluster j
*
* @private
* @param {number} iMin Minimum index in cluster to be computed
* @param {number} iMax Maximum index in cluster to be computed
* @param {number} cluster Index of the cluster currently being computed
* @param {Array<Array<number>>} matrix
* @param {Array<Array<number>>} backtrackMatrix
* @param {Array<number>} sums
* @param {Array<number>} sumsOfSquares
*/
function fillMatrixColumn(iMin, iMax, cluster, matrix, backtrackMatrix, sums, sumsOfSquares) {
if (iMin > iMax) {
return;
}
// Start at midpoint between iMin and iMax
var i = Math.floor((iMin + iMax) / 2);
matrix[cluster][i] = matrix[cluster - 1][i - 1];
backtrackMatrix[cluster][i] = i;
var jlow = cluster; // the lower end for j
if (iMin > cluster) {
jlow = Math.max(jlow, backtrackMatrix[cluster][iMin - 1] || 0);
}
jlow = Math.max(jlow, backtrackMatrix[cluster - 1][i] || 0);
var jhigh = i - 1; // the upper end for j
if (iMax < matrix[0].length - 1) {
jhigh = Math.min(jhigh, backtrackMatrix[cluster][iMax + 1] || 0);
}
var sji;
var sjlowi;
var ssqjlow;
var ssqj;
for (var j = jhigh; j >= jlow; --j) {
sji = ssq(j, i, sums, sumsOfSquares);
if (sji + matrix[cluster - 1][jlow - 1] >= matrix[cluster][i]) {
break;
}
// Examine the lower bound of the cluster border
sjlowi = ssq(jlow, i, sums, sumsOfSquares);
ssqjlow = sjlowi + matrix[cluster - 1][jlow - 1];
if (ssqjlow < matrix[cluster][i]) {
// Shrink the lower bound
matrix[cluster][i] = ssqjlow;
backtrackMatrix[cluster][i] = jlow;
}
jlow++;
ssqj = sji + matrix[cluster - 1][j - 1];
if (ssqj < matrix[cluster][i]) {
matrix[cluster][i] = ssqj;
backtrackMatrix[cluster][i] = j;
}
}
fillMatrixColumn(iMin, i - 1, cluster, matrix, backtrackMatrix, sums, sumsOfSquares);
fillMatrixColumn(i + 1, iMax, cluster, matrix, backtrackMatrix, sums, sumsOfSquares);
}
/**
* Initializes the main matrices used in Ckmeans and kicks
* off the divide and conquer cluster computation strategy
*
* @private
* @param {Array<number>} data sorted array of values
* @param {Array<Array<number>>} matrix
* @param {Array<Array<number>>} backtrackMatrix
*/
function fillMatrices(data, matrix, backtrackMatrix) {
var nValues = matrix[0].length;
// Shift values by the median to improve numeric stability
var shift = data[Math.floor(nValues / 2)];
// Cumulative sum and cumulative sum of squares for all values in data array
var sums = [];
var sumsOfSquares = [];
// Initialize first column in matrix & backtrackMatrix
for (var i = 0, shiftedValue = void 0; i < nValues; ++i) {
shiftedValue = data[i] - shift;
if (i === 0) {
sums.push(shiftedValue);
sumsOfSquares.push(shiftedValue * shiftedValue);
} else {
sums.push(sums[i - 1] + shiftedValue);
sumsOfSquares.push(sumsOfSquares[i - 1] + shiftedValue * shiftedValue);
}
// Initialize for cluster = 0
matrix[0][i] = ssq(0, i, sums, sumsOfSquares);
backtrackMatrix[0][i] = 0;
}
// Initialize the rest of the columns
var iMin;
for (var cluster = 1; cluster < matrix.length; ++cluster) {
if (cluster < matrix.length - 1) {
iMin = cluster;
} else {
// No need to compute matrix[K-1][0] ... matrix[K-1][N-2]
iMin = nValues - 1;
}
fillMatrixColumn(iMin, nValues - 1, cluster, matrix, backtrackMatrix, sums, sumsOfSquares);
}
}
/**
* Ckmeans clustering is an improvement on heuristic-based clustering
* approaches like Jenks. The algorithm was developed in
* [Haizhou Wang and Mingzhou Song](http://journal.r-project.org/archive/2011-2/RJournal_2011-2_Wang+Song.pdf)
* as a [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) approach
* to the problem of clustering numeric data into groups with the least
* within-group sum-of-squared-deviations.
*
* Minimizing the difference within groups - what Wang & Song refer to as
* `withinss`, or within sum-of-squares, means that groups are optimally
* homogenous within and the data is split into representative groups.
* This is very useful for visualization, where you may want to represent
* a continuous variable in discrete color or style groups. This function
* can provide groups that emphasize differences between data.
*
* Being a dynamic approach, this algorithm is based on two matrices that
* store incrementally-computed values for squared deviations and backtracking
* indexes.
*
* This implementation is based on Ckmeans 3.4.6, which introduced a new divide
* and conquer approach that improved runtime from O(kn^2) to O(kn log(n)).
*
* Unlike the [original implementation](https://cran.r-project.org/web/packages/Ckmeans.1d.dp/index.html),
* this implementation does not include any code to automatically determine
* the optimal number of clusters: this information needs to be explicitly
* provided.
*
* ### References
* _Ckmeans.1d.dp: Optimal k-means Clustering in One Dimension by Dynamic
* Programming_ Haizhou Wang and Mingzhou Song ISSN 2073-4859
*
* from The R Journal Vol. 3/2, December 2011
* @param {Array<number>} x input data, as an array of number values
* @param {number} nClusters number of desired classes. This cannot be
* greater than the number of values in the data array.
* @returns {Array<Array<number>>} clustered input
* @throws {Error} if the number of requested clusters is higher than the size of the data
* @example
* ckmeans([-1, 2, -1, 2, 4, 5, 6, -1, 2, -1], 3);
* // The input, clustered into groups of similar numbers.
* //= [[-1, -1, -1, -1], [2, 2, 2], [4, 5, 6]]);
*/
function ckmeans(x, nClusters) {
if (nClusters > x.length) {
throw new Error("cannot generate more classes than there are data values");
}
var sorted = numericSort(x);
// we'll use this as the maximum number of clusters
var uniqueCount = uniqueCountSorted(sorted);
// if all of the input values are identical, there's one cluster
// with all of the input in it.
if (uniqueCount === 1) {
return [sorted];
}
// named 'S' originally
var matrix = makeMatrix(nClusters, sorted.length);
// named 'J' originally
var backtrackMatrix = makeMatrix(nClusters, sorted.length);
// This is a dynamic programming way to solve the problem of minimizing
// within-cluster sum of squares. It's similar to linear regression
// in this way, and this calculation incrementally computes the
// sum of squares that are later read.
fillMatrices(sorted, matrix, backtrackMatrix);
// The real work of Ckmeans clustering happens in the matrix generation:
// the generated matrices encode all possible clustering combinations, and
// once they're generated we can solve for the best clustering groups
// very quickly.
var clusters = [];
var clusterRight = backtrackMatrix[0].length - 1;
// Backtrack the clusters from the dynamic programming matrix. This
// starts at the bottom-right corner of the matrix (if the top-left is 0, 0),
// and moves the cluster target with the loop.
for (var cluster = backtrackMatrix.length - 1; cluster >= 0; cluster--) {
var clusterLeft = backtrackMatrix[cluster][clusterRight];
// fill the cluster from the sorted input by taking a slice of the
// array. the backtrack matrix makes this easy - it stores the
// indexes where the cluster should start and end.
clusters[cluster] = sorted.slice(clusterLeft, clusterRight + 1);
if (cluster > 0) {
clusterRight = clusterLeft - 1;
}
}
return clusters;
}
/*
* Pull Breaks Values for Jenks
*
* the second part of the jenks recipe: take the calculated matrices
* and derive an array of n breaks.
*
* @private
*/
function jenksBreaks(data, lowerClassLimits, nClasses) {
var k = data.length;
var kclass = [];
var countNum = nClasses;
// the calculation of classes will never include the upper
// bound, so we need to explicitly set it
kclass[nClasses] = data[data.length - 1];
// the lowerClassLimits matrix is used as indices into itself
// here: the `k` variable is reused in each iteration.
while (countNum > 0) {
kclass[countNum - 1] = data[lowerClassLimits[k][countNum] - 1];
k = lowerClassLimits[k][countNum] - 1;
countNum--;
}
return kclass;
}
/*
* Compute Matrices for Jenks
*
* Compute the matrices required for Jenks breaks. These matrices
* can be used for any classing of data with `classes <= nClasses`
*
* @private
*/
function jenksMatrices(data, nClasses) {
// in the original implementation, these matrices are referred to
// as `LC` and `OP`
//
// * lowerClassLimits (LC): optimal lower class limits
// * varianceCombinations (OP): optimal variance combinations for all classes
var lowerClassLimits = [];
var varianceCombinations = [];
// loop counters
var i, j;
// the variance, as computed at each step in the calculation
var variance = 0;
// Initialize and fill each matrix with zeroes
for (i = 0; i < data.length + 1; i++) {
var tmp1 = [];
var tmp2 = [];
// despite these arrays having the same values, we need
// to keep them separate so that changing one does not change
// the other
for (j = 0; j < nClasses + 1; j++) {
tmp1.push(0);
tmp2.push(0);
}
lowerClassLimits.push(tmp1);
varianceCombinations.push(tmp2);
}
for (i = 1; i < nClasses + 1; i++) {
lowerClassLimits[1][i] = 1;
varianceCombinations[1][i] = 0;
// in the original implementation, 9999999 is used but
// since Javascript has `Infinity`, we use that.
for (j = 2; j < data.length + 1; j++) {
varianceCombinations[j][i] = Infinity;
}
}
for (var l = 2; l < data.length + 1; l++) {
// `SZ` originally. this is the sum of the values seen thus
// far when calculating variance.
var sum = 0;
// `ZSQ` originally. the sum of squares of values seen
// thus far
var sumSquares = 0;
// `WT` originally. This is the number of
var w = 0;
// `IV` originally
var i4 = 0;
// in several instances, you could say `Math.pow(x, 2)`
// instead of `x * x`, but this is slower in some browsers
// introduces an unnecessary concept.
for (var m = 1; m < l + 1; m++) {
// `III` originally
var lowerClassLimit = l - m + 1;
var val = data[lowerClassLimit - 1];
// here we're estimating variance for each potential classing
// of the data, for each potential number of classes. `w`
// is the number of data points considered so far.
w++;
// increase the current sum and sum-of-squares
sum += val;
sumSquares += val * val;
// the variance at this point in the sequence is the difference
// between the sum of squares and the total x 2, over the number
// of samples.
variance = sumSquares - sum * sum / w;
i4 = lowerClassLimit - 1;
if (i4 !== 0) {
for (j = 2; j < nClasses + 1; j++) {
// if adding this element to an existing class
// will increase its variance beyond the limit, break
// the class at this point, setting the `lowerClassLimit`
// at this point.
if (varianceCombinations[l][j] >= variance + varianceCombinations[i4][j - 1]) {
lowerClassLimits[l][j] = lowerClassLimit;
varianceCombinations[l][j] = variance + varianceCombinations[i4][j - 1];
}
}
}
}
lowerClassLimits[l][1] = 1;
varianceCombinations[l][1] = variance;
}
// return the two matrices. for just providing breaks, only
// `lowerClassLimits` is needed, but variances can be useful to
// evaluate goodness of fit.
return {
lowerClassLimits: lowerClassLimits,
varianceCombinations: varianceCombinations
};
}
/**
* The **[jenks natural breaks optimization](http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization)**
* is an algorithm commonly used in cartography and visualization to decide
* upon groupings of data values that minimize variance within themselves
* and maximize variation between themselves.
*
* For instance, cartographers often use jenks in order to choose which
* values are assigned to which colors in a [choropleth](https://en.wikipedia.org/wiki/Choropleth_map)
* map.
*
* @param {Array<number>} data input data, as an array of number values
* @param {number} nClasses number of desired classes
* @returns {Array<number>} array of class break positions
* // split data into 3 break points
* jenks([1, 2, 4, 5, 7, 9, 10, 20], 3) // = [1, 7, 20, 20]
*/
function jenks(data, nClasses) {
if (nClasses > data.length) {
return null;
}
// sort data in numerical order, since this is expected
// by the matrices function
data = data.slice().sort(function (a, b) {
return a - b;
});
// get our basic matrices
var matrices = jenksMatrices(data, nClasses);
// we only need lower class limits here
var lowerClassLimits = matrices.lowerClassLimits;
// extract nClasses out of the computed matrices
return jenksBreaks(data, lowerClassLimits, nClasses);
}
/**
* Given an array of x, this will find the extent of the
* x and return an array of breaks that can be used
* to categorize the x into a number of classes. The
* returned array will always be 1 longer than the number of
* classes because it includes the minimum value.
*
* @param {Array<number>} x an array of number values
* @param {number} nClasses number of desired classes
* @returns {Array<number>} array of class break positions
* @example
* equalIntervalBreaks([1, 2, 3, 4, 5, 6], 4); // => [1, 2.25, 3.5, 4.75, 6]
*/
function equalIntervalBreaks(x, nClasses) {
if (x.length < 2) {
return x;
}
var theMin = min(x);
var theMax = max(x);
// the first break will always be the minimum value
// in the xset
var breaks = [theMin];
// The size of each break is the full range of the x
// divided by the number of classes requested
var breakSize = (theMax - theMin) / nClasses;
// In the case of nClasses = 1, this loop won't run
// and the returned breaks will be [min, max]
for (var i = 1; i < nClasses; i++) {
breaks.push(breaks[0] + breakSize * i);
}
// the last break will always be the
// maximum.
breaks.push(theMax);
return breaks;
}
/**
* [Sample covariance](https://en.wikipedia.org/wiki/Sample_mean_and_covariance) of two datasets:
* how much do the two datasets move together?
* x and y are two datasets, represented as arrays of numbers.
*
* @param {Array<number>} x a sample of two or more data points
* @param {Array<number>} y a sample of two or more data points
* @throws {Error} if x and y do not have equal lengths
* @throws {Error} if x or y have length of one or less
* @returns {number} sample covariance
* @example
* sampleCovariance([1, 2, 3, 4, 5, 6], [6, 5, 4, 3, 2, 1]); // => -3.5
*/
function sampleCovariance(x, y) {
// The two datasets must have the same length which must be more than 1
if (x.length !== y.length) {
throw new Error("sampleCovariance requires samples with equal lengths");
}
if (x.length < 2) {
throw new Error("sampleCovariance requires at least two data points in each sample");
}
// determine the mean of each dataset so that we can judge each
// value of the dataset fairly as the difference from the mean. this
// way, if one dataset is [1, 2, 3] and [2, 3, 4], their covariance
// does not suffer because of the difference in absolute values
var xmean = mean(x);
var ymean = mean(y);
var sum = 0;
// for each pair of values, the covariance increases when their
// difference from the mean is associated - if both are well above
// or if both are well below
// the mean, the covariance increases significantly.
for (var i = 0; i < x.length; i++) {
sum += (x[i] - xmean) * (y[i] - ymean);
}
// this is Bessels' Correction: an adjustment made to sample statistics
// that allows for the reduced degree of freedom entailed in calculating
// values from samples rather than complete populations.
var besselsCorrection = x.length - 1;
// the covariance is weighted by the length of the datasets.
return sum / besselsCorrection;
}
/**
* The [sample variance](https://en.wikipedia.org/wiki/Variance#Sample_variance)
* is the sum of squared deviations from the mean. The sample variance
* is distinguished from the variance by the usage of [Bessel's Correction](https://en.wikipedia.org/wiki/Bessel's_correction):
* instead of dividing the sum of squared deviations by the length of the input,
* it is divided by the length minus one. This corrects the bias in estimating
* a value from a set that you don't know if full.
*
* References:
* * [Wolfram MathWorld on Sample Variance](http://mathworld.wolfram.com/SampleVariance.html)
*
* @param {Array<number>} x a sample of two or more data points
* @throws {Error} if the length of x is less than 2
* @return {number} sample variance
* @example
* sampleVariance([1, 2, 3, 4, 5]); // => 2.5
*/
function sampleVariance(x) {
if (x.length < 2) {
throw new Error("sampleVariance requires at least two data points");
}
var sumSquaredDeviationsValue = sumNthPowerDeviations(x, 2);
// this is Bessels' Correction: an adjustment made to sample statistics
// that allows for the reduced degree of freedom entailed in calculating
// values from samples rather than complete populations.
var besselsCorrection = x.length - 1;
// Find the mean value of that list
return sumSquaredDeviationsValue / besselsCorrection;
}
/**
* The [sample standard deviation](http://en.wikipedia.org/wiki/Standard_deviation#Sample_standard_deviation)
* is the square root of the sample variance.
*
* @param {Array<number>} x input array
* @returns {number} sample standard deviation
* @example
* sampleStandardDeviation([2, 4, 4, 4, 5, 5, 7, 9]).toFixed(2);
* // => '2.14'
*/
function sampleStandardDeviation(x) {
var sampleVarianceX = sampleVariance(x);
return Math.sqrt(sampleVarianceX);
}
/**
* The [correlation](http://en.wikipedia.org/wiki/Correlation_and_dependence) is
* a measure of how correlated two datasets are, between -1 and 1
*
* @param {Array<number>} x first input
* @param {Array<number>} y second input
* @returns {number} sample correlation
* @example
* sampleCorrelation([1, 2, 3, 4, 5, 6], [2, 2, 3, 4, 5, 60]).toFixed(2);
* // => '0.69'
*/
function sampleCorrelation(x, y) {
var cov = sampleCovariance(x, y);
var xstd = sampleStandardDeviation(x);
var ystd = sampleStandardDeviation(y);
return cov / xstd / ystd;
}
/**
* The [rank correlation](https://en.wikipedia.org/wiki/Rank_correlation) is
* a measure of the strength of monotonic relationship between two arrays
*
* @param {Array<number>} x first input
* @param {Array<number>} y second input
* @returns {number} sample rank correlation
*/
function sampleRankCorrelation(x, y) {
var xIndexes = x.map(function (value, index) {
return [value, index];
}).sort(function (a, b) {
return a[0] - b[0];
}).map(function (pair) {
return pair[1];
});
var yIndexes = y.map(function (value, index) {
return [value, index];
}).sort(function (a, b) {
return a[0] - b[0];
}).map(function (pair) {
return pair[1];
});
// At this step, we have an array of indexes
// that map from sorted numbers to their original indexes. We reverse
// that so that it is an array of the sorted destination index.
var xRanks = Array(xIndexes.length);
var yRanks = Array(xIndexes.length);
for (var i = 0; i < xIndexes.length; i++) {
xRanks[xIndexes[i]] = i;
yRanks[yIndexes[i]] = i;
}
return sampleCorrelation(xRanks, yRanks);
}
/**
* [Skewness](http://en.wikipedia.org/wiki/Skewness) is
* a measure of the extent to which a probability distribution of a
* real-valued random variable "leans" to one side of the mean.
* The skewness value can be positive or negative, or even undefined.
*
* Implementation is based on the adjusted Fisher-Pearson standardized
* moment coefficient, which is the version found in Excel and several
* statistical packages including Minitab, SAS and SPSS.
*
* @since 4.1.0
* @param {Array<number>} x a sample of 3 or more data points
* @returns {number} sample skewness
* @throws {Error} if x has length less than 3
* @example
* sampleSkewness([2, 4, 6, 3, 1]); // => 0.590128656384365
*/
function sampleSkewness(x) {
if (x.length < 3) {
throw new Error("sampleSkewness requires at least three data points");
}
var meanValue = mean(x);
var tempValue;
var sumSquaredDeviations = 0;
var sumCubedDeviations = 0;
for (var i = 0; i < x.length; i++) {
tempValue = x[i] - meanValue;
sumSquaredDeviations += tempValue * tempValue;
sumCubedDeviations += tempValue * tempValue * tempValue;
}
// this is Bessels' Correction: an adjustment made to sample statistics
// that allows for the reduced degree of freedom entailed in calculating
// values from samples rather than complete populations.
var besselsCorrection = x.length - 1;
// Find the mean value of that list
var theSampleStandardDeviation = Math.sqrt(sumSquaredDeviations / besselsCorrection);
var n = x.length;
var cubedS = Math.pow(theSampleStandardDeviation, 3);
return n * sumCubedDeviations / ((n - 1) * (n - 2) * cubedS);
}
/**
* [Kurtosis](http://en.wikipedia.org/wiki/Kurtosis) is
* a measure of the heaviness of a distribution's tails relative to its
* variance. The kurtosis value can be positive or negative, or even undefined.
*
* Implementation is based on Fisher's excess kurtosis definition and uses
* unbiased moment estimators. This is the version found in Excel and available
* in several statistical packages, including SAS and SciPy.
*
* @param {Array<number>} x a sample of 4 or more data points
* @returns {number} sample kurtosis
* @throws {Error} if x has length less than 4
* @example
* sampleKurtosis([1, 2, 2, 3, 5]); // => 1.4555765595463122
*/
function sampleKurtosis(x) {
var n = x.length;
if (n < 4) {
throw new Error("sampleKurtosis requires at least four data points");
}
var meanValue = mean(x);
var tempValue;
var secondCentralMoment = 0;
var fourthCentralMoment = 0;
for (var i = 0; i < n; i++) {
tempValue = x[i] - meanValue;
secondCentralMoment += tempValue * tempValue;
fourthCentralMoment += tempValue * tempValue * tempValue * tempValue;
}
return (n - 1) / ((n - 2) * (n - 3)) * (n * (n + 1) * fourthCentralMoment / (secondCentralMoment * secondCentralMoment) - 3 * (n - 1));
}
/**
* Implementation of [Heap's Algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm)
* for generating permutations.
*
* @param {Array} elements any type of data
* @returns {Array<Array>} array of permutations
*/
function permutationsHeap(elements) {
var indexes = new Array(elements.length);
var permutations = [elements.slice()];
for (var i = 0; i < elements.length; i++) {
indexes[i] = 0;
}
for (var i$1 = 0; i$1 < elements.length;) {
if (indexes[i$1] < i$1) {
// At odd indexes, swap from indexes[i] instead
// of from the beginning of the array
var swapFrom = 0;
if (i$1 % 2 !== 0) {
swapFrom = indexes[i$1];
}
// swap between swapFrom and i, using
// a temporary variable as storage.
var temp = elements[swapFrom];
elements[swapFrom] = elements[i$1];
elements[i$1] = temp;
permutations.push(elements.slice());
indexes[i$1]++;
i$1 = 0;
} else {
indexes[i$1] = 0;
i$1++;
}
}
return permutations;
}
/**
* Implementation of Combinations
* Combinations are unique subsets of a collection - in this case, k x from a collection at a time.
* https://en.wikipedia.org/wiki/Combination
* @param {Array} x any type of data
* @param {int} k the number of objects in each group (without replacement)
* @returns {Array<Array>} array of permutations
* @example
* combinations([1, 2, 3], 2); // => [[1,2], [1,3], [2,3]]
*/
function combinations(x, k) {
var i;
var subI;
var combinationList = [];
var subsetCombinations;
var next;
for (i = 0; i < x.length; i++) {
if (k === 1) {
combinationList.push([x[i]]);
} else {
subsetCombinations = combinations(x.slice(i + 1, x.length), k - 1);
for (subI = 0; subI < subsetCombinations.length; subI++) {
next = subsetCombinations[subI];
next.unshift(x[i]);
combinationList.push(next);
}
}
}
return combinationList;
}
/**
* Implementation of [Combinations](https://en.wikipedia.org/wiki/Combination) with replacement
* Combinations are unique subsets of a collection - in this case, k x from a collection at a time.
* 'With replacement' means that a given element can be chosen multiple times.
* Unlike permutation, order doesn't matter for combinations.
*
* @param {Array} x any type of data
* @param {int} k the number of objects in each group (without replacement)
* @returns {Array<Array>} array of permutations
* @example
* combinationsReplacement([1, 2], 2); // => [[1, 1], [1, 2], [2, 2]]
*/
function combinationsReplacement(x, k) {
var combinationList = [];
for (var i = 0; i < x.length; i++) {
if (k === 1) {
// If we're requested to find only one element, we don't need
// to recurse: just push `x[i]` onto the list of combinations.
combinationList.push([x[i]]);
} else {
// Otherwise, recursively find combinations, given `k - 1`. Note that
// we request `k - 1`, so if you were looking for k=3 combinations, we're
// requesting k=2. This -1 gets reversed in the for loop right after this
// code, since we concatenate `x[i]` onto the selected combinations,
// bringing `k` back up to your requested level.
// This recursion may go many levels deep, since it only stops once
// k=1.
var subsetCombinations = combinationsReplacement(x.slice(i, x.length), k - 1);
for (var j = 0; j < subsetCombinations.length; j++) {
combinationList.push([x[i]].concat(subsetCombinations[j]));
}
}
}
return combinationList;
}
/**
* When adding a new value to a list, one does not have to necessary
* recompute the mean of the list in linear time. They can instead use
* this function to compute the new mean by providing the current mean,
* the number of elements in the list that produced it and the new
* value to add.
*
* @since 2.5.0
* @param {number} mean current mean
* @param {number} n number of items in the list
* @param {number} newValue the added value
* @returns {number} the new mean
*
* @example
* addToMean(14, 5, 53); // => 20.5
*/
function addToMean(mean, n, newValue) {
return mean + (newValue - mean) / (n + 1);
}
/**
* When combining two lists of values for which one already knows the means,
* one does not have to necessary recompute the mean of the combined lists in
* linear time. They can instead use this function to compute the combined
* mean by providing the mean & number of values of the first list and the mean
* & number of values of the second list.
*
* @since 3.0.0
* @param {number} mean1 mean of the first list
* @param {number} n1 number of items in the first list
* @param {number} mean2 mean of the second list
* @param {number} n2 number of items in the second list
* @returns {number} the combined mean
*
* @example
* combineMeans(5, 3, 4, 3); // => 4.5
*/
function combineMeans(mean1, n1, mean2, n2) {
return (mean1 * n1 + mean2 * n2) / (n1 + n2);
}
/**
* When combining two lists of values for which one already knows the variances,
* one does not have to necessary recompute the variance of the combined lists
* in linear time. They can instead use this function to compute the combined
* variance by providing the variance, mean & number of values of the first list
* and the variance, mean & number of values of the second list.
*
* @since 3.0.0
* @param {number} variance1 variance of the first list
* @param {number} mean1 mean of the first list
* @param {number} n1 number of items in the first list
* @param {number} variance2 variance of the second list
* @param {number} mean2 mean of the second list
* @param {number} n2 number of items in the second list
* @returns {number} the combined mean
*
* @example
* combineVariances(14 / 3, 5, 3, 8 / 3, 4, 3); // => 47 / 12
*/
function combineVariances(variance1, mean1, n1, variance2, mean2, n2) {
var newMean = combineMeans(mean1, n1, mean2, n2);
return (n1 * (variance1 + Math.pow(mean1 - newMean, 2)) + n2 * (variance2 + Math.pow(mean2 - newMean, 2))) / (n1 + n2);
}
/**
* The [Geometric Mean](https://en.wikipedia.org/wiki/Geometric_mean) is
* a mean function that is more useful for numbers in different
* ranges.
*
* This is the nth root of the input numbers multiplied by each other.
*
* The geometric mean is often useful for
* **[proportional growth](https://en.wikipedia.org/wiki/Geometric_mean#Proportional_growth)**: given
* growth rates for multiple years, like _80%, 16.66% and 42.85%_, a simple
* mean will incorrectly estimate an average growth rate, whereas a geometric
* mean will correctly estimate a growth rate that, over those years,
* will yield the same end value.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @returns {number} geometric mean
* @throws {Error} if x is empty
* @throws {Error} if x contains a negative number
* @example
* var growthRates = [1.80, 1.166666, 1.428571];
* var averageGrowth = ss.geometricMean(growthRates);
* var averageGrowthRates = [averageGrowth, averageGrowth, averageGrowth];
* var startingValue = 10;
* var startingValueMean = 10;
* growthRates.forEach(function(rate) {
* startingValue *= rate;
* });
* averageGrowthRates.forEach(function(rate) {
* startingValueMean *= rate;
* });
* startingValueMean === startingValue;
*/
function geometricMean(x) {
if (x.length === 0) {
throw new Error("geometricMean requires at least one data point");
}
// the starting value.
var value = 1;
for (var i = 0; i < x.length; i++) {
// the geometric mean is only valid for positive numbers
if (x[i] < 0) {
throw new Error("geometricMean requires only non-negative numbers as input");
}
// repeatedly multiply the value by each number
value *= x[i];
}
return Math.pow(value, 1 / x.length);
}
/**
* The [log average](https://en.wikipedia.org/wiki/https://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_logarithms)
* is an equivalent way of computing the geometric mean of an array suitable for large or small products.
*
* It's found by calculating the average logarithm of the elements and exponentiating.
*
* @param {Array<number>} x sample of one or more data points
* @returns {number} geometric mean
* @throws {Error} if x is empty
* @throws {Error} if x contains a negative number
*/
function logAverage(x) {
if (x.length === 0) {
throw new Error("logAverage requires at least one data point");
}
var value = 0;
for (var i = 0; i < x.length; i++) {
if (x[i] < 0) {
throw new Error("logAverage requires only non-negative numbers as input");
}
value += Math.log(x[i]);
}
return Math.exp(value / x.length);
}
/**
* The [Harmonic Mean](https://en.wikipedia.org/wiki/Harmonic_mean) is
* a mean function typically used to find the average of rates.
* This mean is calculated by taking the reciprocal of the arithmetic mean
* of the reciprocals of the input numbers.
*
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x sample of one or more data points
* @returns {number} harmonic mean
* @throws {Error} if x is empty
* @throws {Error} if x contains a negative number
* @example
* harmonicMean([2, 3]).toFixed(2) // => '2.40'
*/
function harmonicMean(x) {
if (x.length === 0) {
throw new Error("harmonicMean requires at least one data point");
}
var reciprocalSum = 0;
for (var i = 0; i < x.length; i++) {
// the harmonic mean is only valid for positive numbers
if (x[i] <= 0) {
throw new Error("harmonicMean requires only positive numbers as input");
}
reciprocalSum += 1 / x[i];
}
// divide n by the reciprocal sum
return x.length / reciprocalSum;
}
/**
* The mean, _also known as average_,
* is the sum of all values over the number of values.
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* The simple mean uses the successive addition method internally
* to calculate it's result. Errors in floating-point addition are
* not accounted for, so if precision is required, the standard {@link mean}
* method should be used instead.
*
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
*
* @param {Array<number>} x sample of one or more data points
* @throws {Error} if the length of x is less than one
* @returns {number} mean
* @example
* mean([0, 10]); // => 5
*/
function meanSimple(x) {
if (x.length === 0) {
throw new Error("meanSimple requires at least one data point");
}
return sumSimple(x) / x.length;
}
/**
* The [median](http://en.wikipedia.org/wiki/Median) is
* the middle number of a list. This is often a good indicator of 'the middle'
* when there are outliers that skew the `mean()` value.
* This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
* a method of finding a typical or central value of a set of numbers.
*
* The median isn't necessarily one of the elements in the list: the value
* can be the average of two elements if the list has an even length
* and the two central values are different.
*
* @param {Array<number>} sorted input
* @returns {number} median value
* @example
* medianSorted([10, 2, 5, 100, 2, 1]); // => 52.5
*/
function medianSorted(sorted) {
return quantileSorted(sorted, 0.5);
}
/**
* When removing a value from a list, one does not have to necessary
* recompute the mean of the list in linear time. They can instead use
* this function to compute the new mean by providing the current mean,
* the number of elements in the list that produced it and the value to remove.
*
* @since 3.0.0
* @param {number} mean current mean
* @param {number} n number of items in the list
* @param {number} value the value to remove
* @returns {number} the new mean
*
* @example
* subtractFromMean(20.5, 6, 53); // => 14
*/
function subtractFromMean(mean, n, value) {
return (mean * n - value) / (n - 1);
}
/**
* The Root Mean Square (RMS) is
* a mean function used as a measure of the magnitude of a set
* of numbers, regardless of their sign.
* This is the square root of the mean of the squares of the
* input numbers.
* This runs in `O(n)`, linear time, with respect to the length of the array.
*
* @param {Array<number>} x a sample of one or more data points
* @returns {number} root mean square
* @throws {Error} if x is empty
* @example
* rootMeanSquare([-1, 1, -1, 1]); // => 1
*/
function rootMeanSquare(x) {
if (x.length === 0) {
throw new Error("rootMeanSquare requires at least one data point");
}
var sumOfSquares = 0;
for (var i = 0; i < x.length; i++) {
sumOfSquares += Math.pow(x[i], 2);
}
return Math.sqrt(sumOfSquares / x.length);
}
/**
* The`coefficient of variation`_ is the ratio of the standard deviation to the mean.
* .._`coefficient of variation`: https://en.wikipedia.org/wiki/Coefficient_of_variation
*
*
* @param {Array} x input
* @returns {number} coefficient of variation
* @example
* coefficientOfVariation([1, 2, 3, 4]).toFixed(3); // => 0.516
* coefficientOfVariation([1, 2, 3, 4, 5]).toFixed(3); // => 0.527
* coefficientOfVariation([-1, 0, 1, 2, 3, 4]).toFixed(3); // => 1.247
*/
function coefficientOfVariation(x) {
return sampleStandardDeviation(x) / mean(x);
}
/**
* This is to compute [a one-sample t-test](https://en.wikipedia.org/wiki/Student%27s_t-test#One-sample_t-test), comparing the mean
* of a sample to a known value, x.
*
* in this case, we're trying to determine whether the
* population mean is equal to the value that we know, which is `x`
* here. Usually the results here are used to look up a
* [p-value](http://en.wikipedia.org/wiki/P-value), which, for
* a certain level of significance, will let you determine that the
* null hypothesis can or cannot be rejected.
*
* @param {Array<number>} x sample of one or more numbers
* @param {number} expectedValue expected value of the population mean
* @returns {number} value
* @example
* tTest([1, 2, 3, 4, 5, 6], 3.385).toFixed(2); // => '0.16'
*/
function tTest(x, expectedValue) {
// The mean of the sample
var sampleMean = mean(x);
// The standard deviation of the sample
var sd = standardDeviation(x);
// Square root the length of the sample
var rootN = Math.sqrt(x.length);
// returning the t value
return (sampleMean - expectedValue) / (sd / rootN);
}
/**
* This is to compute [two sample t-test](http://en.wikipedia.org/wiki/Student's_t-test).
* Tests whether "mean(X)-mean(Y) = difference", (
* in the most common case, we often have `difference == 0` to test if two samples
* are likely to be taken from populations with the same mean value) with
* no prior knowledge on standard deviations of both samples
* other than the fact that they have the same standard deviation.
*
* Usually the results here are used to look up a
* [p-value](http://en.wikipedia.org/wiki/P-value), which, for
* a certain level of significance, will let you determine that the
* null hypothesis can or cannot be rejected.
*
* `diff` can be omitted if it equals 0.
*
* [This is used to reject](https://en.wikipedia.org/wiki/Exclusion_of_the_null_hypothesis)
* a null hypothesis that the two populations that have been sampled into
* `sampleX` and `sampleY` are equal to each other.
*
* @param {Array<number>} sampleX a sample as an array of numbers
* @param {Array<number>} sampleY a sample as an array of numbers
* @param {number} [difference=0]
* @returns {number|null} test result
*
* @example
* tTestTwoSample([1, 2, 3, 4], [3, 4, 5, 6], 0); // => -2.1908902300206643
*/
function tTestTwoSample(sampleX, sampleY, difference) {
var n = sampleX.length;
var m = sampleY.length;
// If either sample doesn't actually have any values, we can't
// compute this at all, so we return `null`.
if (!n || !m) {
return null;
}
// default difference (mu) is zero
if (!difference) {
difference = 0;
}
var meanX = mean(sampleX);
var meanY = mean(sampleY);
var sampleVarianceX = sampleVariance(sampleX);
var sampleVarianceY = sampleVariance(sampleY);
if (typeof meanX === "number" && typeof meanY === "number" && typeof sampleVarianceX === "number" && typeof sampleVarianceY === "number") {
var weightedVariance = ((n - 1) * sampleVarianceX + (m - 1) * sampleVarianceY) / (n + m - 2);
return (meanX - meanY - difference) / Math.sqrt(weightedVariance * (1 / n + 1 / m));
}
}
/**
* This function calculates the Wilcoxon rank sum statistic for the first sample
* with respect to the second. The Wilcoxon rank sum test is a non-parametric
* alternative to the t-test which is equivalent to the
* [Mann-Whitney U test](https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test).
* The statistic is calculated by pooling all the observations together, ranking them,
* and then summing the ranks associated with one of the samples. If this rank sum is
* sufficiently large or small we reject the hypothesis that the two samples come
* from the same distribution in favor of the alternative that one is shifted with
* respect to the other.
*
* @param {Array<number>} sampleX a sample as an array of numbers
* @param {Array<number>} sampleY a sample as an array of numbers
* @returns {number} rank sum for sampleX
*
* @example
* wilcoxonRankSum([1, 4, 8], [9, 12, 15]); // => 6
*/
function wilcoxonRankSum(sampleX, sampleY) {
if (!sampleX.length || !sampleY.length) {
throw new Error("Neither sample can be empty");
}
var pooledSamples = sampleX.map(function (x) {
return {
label: "x",
value: x
};
}).concat(sampleY.map(function (y) {
return {
label: "y",
value: y
};
})).sort(function (a, b) {
return a.value - b.value;
});
for (var rank = 0; rank < pooledSamples.length; rank++) {
pooledSamples[rank].rank = rank;
}
var tiedRanks = [pooledSamples[0].rank];
for (var i = 1; i < pooledSamples.length; i++) {
if (pooledSamples[i].value === pooledSamples[i - 1].value) {
tiedRanks.push(pooledSamples[i].rank);
if (i === pooledSamples.length - 1) {
replaceRanksInPlace(pooledSamples, tiedRanks);
}
} else if (tiedRanks.length > 1) {
replaceRanksInPlace(pooledSamples, tiedRanks);
} else {
tiedRanks = [pooledSamples[i].rank];
}
}
function replaceRanksInPlace(pooledSamples, tiedRanks) {
var average = (tiedRanks[0] + tiedRanks[tiedRanks.length - 1]) / 2;
for (var i = 0; i < tiedRanks.length; i++) {
pooledSamples[tiedRanks[i]].rank = average;
}
}
var rankSum = 0;
for (var i$1 = 0; i$1 < pooledSamples.length; i$1++) {
var sample = pooledSamples[i$1];
if (sample.label === "x") {
rankSum += sample.rank + 1;
}
}
return rankSum;
}
/**
* [Bayesian Classifier](http://en.wikipedia.org/wiki/Naive_Bayes_classifier)
*
* This is a naïve bayesian classifier that takes
* singly-nested objects.
*
* @class
* @example
* var bayes = new BayesianClassifier();
* bayes.train({
* species: 'Cat'
* }, 'animal');
* var result = bayes.score({
* species: 'Cat'
* })
* // result
* // {
* // animal: 1
* // }
*/
var BayesianClassifier = function BayesianClassifier() {
// The number of items that are currently
// classified in the model
this.totalCount = 0;
// Every item classified in the model
this.data = {};
};
/**
* Train the classifier with a new item, which has a single
* dimension of Javascript literal keys and values.
*
* @param {Object} item an object with singly-deep properties
* @param {string} category the category this item belongs to
* @return {undefined} adds the item to the classifier
*/
BayesianClassifier.prototype.train = function train(item, category) {
// If the data object doesn't have any values
// for this category, create a new object for it.
if (!this.data[category]) {
this.data[category] = {};
}
// Iterate through each key in the item.
for (var k in item) {
var v = item[k];
// Initialize the nested object `data[category][k][item[k]]`
// with an object of keys that equal 0.
if (this.data[category][k] === undefined) {
this.data[category][k] = {};
}
if (this.data[category][k][v] === undefined) {
this.data[category][k][v] = 0;
}
// And increment the key for this key/value combination.
this.data[category][k][v]++;
}
// Increment the number of items classified
this.totalCount++;
};
/**
* Generate a score of how well this item matches all
* possible categories based on its attributes
*
* @param {Object} item an item in the same format as with train
* @returns {Object} of probabilities that this item belongs to a
* given category.
*/
BayesianClassifier.prototype.score = function score(item) {
// Initialize an empty array of odds per category.
var odds = {};
var category;
// Iterate through each key in the item,
// then iterate through each category that has been used
// in previous calls to `.train()`
for (var k in item) {
var v = item[k];
for (category in this.data) {
// Create an empty object for storing key - value combinations
// for this category.
odds[category] = {};
// If this item doesn't even have a property, it counts for nothing,
// but if it does have the property that we're looking for from
// the item to categorize, it counts based on how popular it is
// versus the whole population.
if (this.data[category][k]) {
odds[category][k + "_" + v] = (this.data[category][k][v] || 0) / this.totalCount;
} else {
odds[category][k + "_" + v] = 0;
}
}
}
// Set up a new object that will contain sums of these odds by category
var oddsSums = {};
for (category in odds) {
// Tally all of the odds for each category-combination pair -
// the non-existence of a category does not add anything to the
// score.
oddsSums[category] = 0;
for (var combination in odds[category]) {
oddsSums[category] += odds[category][combination];
}
}
return oddsSums;
};
/**
* This is a single-layer [Perceptron Classifier](http://en.wikipedia.org/wiki/Perceptron) that takes
* arrays of numbers and predicts whether they should be classified
* as either 0 or 1 (negative or positive examples).
* @class
* @example
* // Create the model
* var p = new PerceptronModel();
* // Train the model with input with a diagonal boundary.
* for (var i = 0; i < 5; i++) {
* p.train([1, 1], 1);
* p.train([0, 1], 0);
* p.train([1, 0], 0);
* p.train([0, 0], 0);
* }
* p.predict([0, 0]); // 0
* p.predict([0, 1]); // 0
* p.predict([1, 0]); // 0
* p.predict([1, 1]); // 1
*/
var PerceptronModel = function PerceptronModel() {
// The weights, or coefficients of the model;
// weights are only populated when training with data.
this.weights = [];
// The bias term, or intercept; it is also a weight but
// it's stored separately for convenience as it is always
// multiplied by one.
this.bias = 0;
};
/**
* **Predict**: Use an array of features with the weight array and bias
* to predict whether an example is labeled 0 or 1.
*
* @param {Array<number>} features an array of features as numbers
* @returns {number} 1 if the score is over 0, otherwise 0
*/
PerceptronModel.prototype.predict = function predict(features) {
// Only predict if previously trained
// on the same size feature array(s).
if (features.length !== this.weights.length) {
return null;
}
// Calculate the sum of features times weights,
// with the bias added (implicitly times one).
var score = 0;
for (var i = 0; i < this.weights.length; i++) {
score += this.weights[i] * features[i];
}
score += this.bias;
// Classify as 1 if the score is over 0, otherwise 0.
if (score > 0) {
return 1;
} else {
return 0;
}
};
/**
* **Train** the classifier with a new example, which is
* a numeric array of features and a 0 or 1 label.
*
* @param {Array<number>} features an array of features as numbers
* @param {number} label either 0 or 1
* @returns {PerceptronModel} this
*/
PerceptronModel.prototype.train = function train(features, label) {
// Require that only labels of 0 or 1 are considered.
if (label !== 0 && label !== 1) {
return null;
}
// The length of the feature array determines
// the length of the weight array.
// The perceptron will continue learning as long as
// it keeps seeing feature arrays of the same length.
// When it sees a new data shape, it initializes.
if (features.length !== this.weights.length) {
this.weights = features;
this.bias = 1;
}
// Make a prediction based on current weights.
var prediction = this.predict(features);
// Update the weights if the prediction is wrong.
if (typeof prediction === "number" && prediction !== label) {
var gradient = label - prediction;
for (var i = 0; i < this.weights.length; i++) {
this.weights[i] += gradient * features[i];
}
this.bias += gradient;
}
return this;
};
/**
* We use `ε`, epsilon, as a stopping criterion when we want to iterate
* until we're "close enough". Epsilon is a very small number: for
* simple statistics, that number is **0.0001**
*
* This is used in calculations like the binomialDistribution, in which
* the process of finding a value is [iterative](https://en.wikipedia.org/wiki/Iterative_method):
* it progresses until it is close enough.
*
* Below is an example of using epsilon in [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent),
* where we're trying to find a local minimum of a function's derivative,
* given by the `fDerivative` method.
*
* @example
* // From calculation, we expect that the local minimum occurs at x=9/4
* var x_old = 0;
* // The algorithm starts at x=6
* var x_new = 6;
* var stepSize = 0.01;
*
* function fDerivative(x) {
* return 4 * Math.pow(x, 3) - 9 * Math.pow(x, 2);
* }
*
* // The loop runs until the difference between the previous
* // value and the current value is smaller than epsilon - a rough
* // meaure of 'close enough'
* while (Math.abs(x_new - x_old) > ss.epsilon) {
* x_old = x_new;
* x_new = x_old - stepSize * fDerivative(x_old);
* }
*
* console.log('Local minimum occurs at', x_new);
*/
var epsilon = 0.0001;
/**
* A [Factorial](https://en.wikipedia.org/wiki/Factorial), usually written n!, is the product of all positive
* integers less than or equal to n. Often factorial is implemented
* recursively, but this iterative approach is significantly faster
* and simpler.
*
* @param {number} n input, must be an integer number 1 or greater
* @returns {number} factorial: n!
* @throws {Error} if n is less than 0 or not an integer
* @example
* factorial(5); // => 120
*/
function factorial(n) {
// factorial is mathematically undefined for negative numbers
if (n < 0) {
throw new Error("factorial requires a non-negative value");
}
if (Math.floor(n) !== n) {
throw new Error("factorial requires an integer input");
}
// typically you'll expand the factorial function going down, like
// 5! = 5 * 4 * 3 * 2 * 1. This is going in the opposite direction,
// counting from 2 up to the number in question, and since anything
// multiplied by 1 is itself, the loop only needs to start at 2.
var accumulator = 1;
for (var i = 2; i <= n; i++) {
// for each number up to and including the number `n`, multiply
// the accumulator my that number.
accumulator *= i;
}
return accumulator;
}
/**
* Compute the [gamma function](https://en.wikipedia.org/wiki/Gamma_function) of a value using Nemes' approximation.
* The gamma of n is equivalent to (n-1)!, but unlike the factorial function, gamma is defined for all real n except zero
* and negative integers (where NaN is returned). Note, the gamma function is also well-defined for complex numbers,
* though this implementation currently does not handle complex numbers as input values.
* Nemes' approximation is defined [here](https://arxiv.org/abs/1003.6020) as Theorem 2.2.
* Negative values use [Euler's reflection formula](https://en.wikipedia.org/wiki/Gamma_function#Properties) for computation.
*
* @param {number} n Any real number except for zero and negative integers.
* @returns {number} The gamma of the input value.
*
* @example
* gamma(11.5); // 11899423.084037038
* gamma(-11.5); // 2.29575810481609e-8
* gamma(5); // 24
*/
function gamma(n) {
if (Number.isInteger(n)) {
if (n <= 0) {
// gamma not defined for zero or negative integers
return NaN;
} else {
// use factorial for integer inputs
return factorial(n - 1);
}
}
// Decrement n, because approximation is defined for n - 1
n--;
if (n < 0) {
// Use Euler's reflection formula for negative inputs
// see: https://en.wikipedia.org/wiki/Gamma_function#Properties
return Math.PI / (Math.sin(Math.PI * -n) * gamma(-n));
} else {
// Nemes' expansion approximation
var seriesCoefficient = Math.pow(n / Math.E, n) * Math.sqrt(2 * Math.PI * (n + 1 / 6));
var seriesDenom = n + 1 / 4;
var seriesExpansion = 1 + 1 / 144 / Math.pow(seriesDenom, 2) - 1 / 12960 / Math.pow(seriesDenom, 3) - 257 / 207360 / Math.pow(seriesDenom, 4) - 52 / 2612736 / Math.pow(seriesDenom, 5) + 5741173 / 9405849600 / Math.pow(seriesDenom, 6) + 37529 / 18811699200 / Math.pow(seriesDenom, 7);
return seriesCoefficient * seriesExpansion;
}
}
// Define series coefficients
var COEFFICIENTS = [0.99999999999999709182, 57.156235665862923517, -59.597960355475491248, 14.136097974741747174, -0.49191381609762019978, 0.33994649984811888699e-4, 0.46523628927048575665e-4, -0.98374475304879564677e-4, 0.15808870322491248884e-3, -0.21026444172410488319e-3, 0.2174396181152126432e-3, -0.16431810653676389022e-3, 0.84418223983852743293e-4, -0.2619083840158140867e-4, 0.36899182659531622704e-5];
var g = 607 / 128;
var LOGSQRT2PI = Math.log(Math.sqrt(2 * Math.PI));
/**
* Compute the logarithm of the [gamma function](https://en.wikipedia.org/wiki/Gamma_function) of a value using Lanczos' approximation.
* This function takes as input any real-value n greater than 0.
* This function is useful for values of n too large for the normal gamma function (n > 165).
* The code is based on Lanczo's Gamma approximation, defined [here](http://my.fit.edu/~gabdo/gamma.txt).
*
* @param {number} n Any real number greater than zero.
* @returns {number} The logarithm of gamma of the input value.
*
* @example
* gammaln(500); // 2605.1158503617335
* gammaln(2.4); // 0.21685932244884043
*/
function gammaln(n) {
// Return infinity if value not in domain
if (n <= 0) {
return Infinity;
}
// Decrement n, because approximation is defined for n - 1
n--;
// Create series approximation
var a = COEFFICIENTS[0];
for (var i = 1; i < 15; i++) {
a += COEFFICIENTS[i] / (n + i);
}
var tmp = g + 0.5 + n;
// Return natural logarithm of gamma(n)
return LOGSQRT2PI + Math.log(a) - tmp + (n + 0.5) * Math.log(tmp);
}
/**
* The [Bernoulli distribution](http://en.wikipedia.org/wiki/Bernoulli_distribution)
* is the probability discrete
* distribution of a random variable which takes value 1 with success
* probability `p` and value 0 with failure
* probability `q` = 1 - `p`. It can be used, for example, to represent the
* toss of a coin, where "1" is defined to mean "heads" and "0" is defined
* to mean "tails" (or vice versa). It is
* a special case of a Binomial Distribution
* where `n` = 1.
*
* @param {number} p input value, between 0 and 1 inclusive
* @returns {number[]} values of bernoulli distribution at this point
* @throws {Error} if p is outside 0 and 1
* @example
* bernoulliDistribution(0.3); // => [0.7, 0.3]
*/
function bernoulliDistribution(p) /*: number[] */{
// Check that `p` is a valid probability (0 ≤ p ≤ 1)
if (p < 0 || p > 1) {
throw new Error("bernoulliDistribution requires probability to be between 0 and 1 inclusive");
}
return [1 - p, p];
}
/**
* The [Binomial Distribution](http://en.wikipedia.org/wiki/Binomial_distribution) is the discrete probability
* distribution of the number of successes in a sequence of n independent yes/no experiments, each of which yields
* success with probability `probability`. Such a success/failure experiment is also called a Bernoulli experiment or
* Bernoulli trial; when trials = 1, the Binomial Distribution is a Bernoulli Distribution.
*
* @param {number} trials number of trials to simulate
* @param {number} probability
* @returns {number[]} output
*/
function binomialDistribution(trials, probability) /*: ?number[] */{
// Check that `p` is a valid probability (0 ≤ p ≤ 1),
// that `n` is an integer, strictly positive.
if (probability < 0 || probability > 1 || trials <= 0 || trials % 1 !== 0) {
return undefined;
}
// We initialize `x`, the random variable, and `accumulator`, an accumulator
// for the cumulative distribution function to 0. `distribution_functions`
// is the object we'll return with the `probability_of_x` and the
// `cumulativeProbability_of_x`, as well as the calculated mean &
// variance. We iterate until the `cumulativeProbability_of_x` is
// within `epsilon` of 1.0.
var x = 0;
var cumulativeProbability = 0;
var cells = [];
var binomialCoefficient = 1;
// This algorithm iterates through each potential outcome,
// until the `cumulativeProbability` is very close to 1, at
// which point we've defined the vast majority of outcomes
do {
// a [probability mass function](https://en.wikipedia.org/wiki/Probability_mass_function)
cells[x] = binomialCoefficient * Math.pow(probability, x) * Math.pow(1 - probability, trials - x);
cumulativeProbability += cells[x];
x++;
binomialCoefficient = binomialCoefficient * (trials - x + 1) / x;
// when the cumulativeProbability is nearly 1, we've calculated
// the useful range of this distribution
} while (cumulativeProbability < 1 - epsilon);
return cells;
}
/**
* The [Poisson Distribution](http://en.wikipedia.org/wiki/Poisson_distribution)
* is a discrete probability distribution that expresses the probability
* of a given number of events occurring in a fixed interval of time
* and/or space if these events occur with a known average rate and
* independently of the time since the last event.
*
* The Poisson Distribution is characterized by the strictly positive
* mean arrival or occurrence rate, `λ`.
*
* @param {number} lambda location poisson distribution
* @returns {number[]} values of poisson distribution at that point
*/
function poissonDistribution(lambda) /*: ?number[] */{
// Check that lambda is strictly positive
if (lambda <= 0) {
return undefined;
}
// our current place in the distribution
var x = 0;
// and we keep track of the current cumulative probability, in
// order to know when to stop calculating chances.
var cumulativeProbability = 0;
// the calculated cells to be returned
var cells = [];
var factorialX = 1;
// This algorithm iterates through each potential outcome,
// until the `cumulativeProbability` is very close to 1, at
// which point we've defined the vast majority of outcomes
do {
// a [probability mass function](https://en.wikipedia.org/wiki/Probability_mass_function)
cells[x] = Math.exp(-lambda) * Math.pow(lambda, x) / factorialX;
cumulativeProbability += cells[x];
x++;
factorialX *= x;
// when the cumulativeProbability is nearly 1, we've calculated
// the useful range of this distribution
} while (cumulativeProbability < 1 - epsilon);
return cells;
}
/**
* **Percentage Points of the χ2 (Chi-Squared) Distribution**
*
* The [χ2 (Chi-Squared) Distribution](http://en.wikipedia.org/wiki/Chi-squared_distribution) is used in the common
* chi-squared tests for goodness of fit of an observed distribution to a theoretical one, the independence of two
* criteria of classification of qualitative data, and in confidence interval estimation for a population standard
* deviation of a normal distribution from a sample standard deviation.
*
* Values from Appendix 1, Table III of William W. Hines & Douglas C. Montgomery, "Probability and Statistics in
* Engineering and Management Science", Wiley (1980).
*/
var chiSquaredDistributionTable = {
1: {
0.995: 0,
0.99: 0,
0.975: 0,
0.95: 0,
0.9: 0.02,
0.5: 0.45,
0.1: 2.71,
0.05: 3.84,
0.025: 5.02,
0.01: 6.63,
0.005: 7.88
},
2: {
0.995: 0.01,
0.99: 0.02,
0.975: 0.05,
0.95: 0.1,
0.9: 0.21,
0.5: 1.39,
0.1: 4.61,
0.05: 5.99,
0.025: 7.38,
0.01: 9.21,
0.005: 10.6
},
3: {
0.995: 0.07,
0.99: 0.11,
0.975: 0.22,
0.95: 0.35,
0.9: 0.58,
0.5: 2.37,
0.1: 6.25,
0.05: 7.81,
0.025: 9.35,
0.01: 11.34,
0.005: 12.84
},
4: {
0.995: 0.21,
0.99: 0.3,
0.975: 0.48,
0.95: 0.71,
0.9: 1.06,
0.5: 3.36,
0.1: 7.78,
0.05: 9.49,
0.025: 11.14,
0.01: 13.28,
0.005: 14.86
},
5: {
0.995: 0.41,
0.99: 0.55,
0.975: 0.83,
0.95: 1.15,
0.9: 1.61,
0.5: 4.35,
0.1: 9.24,
0.05: 11.07,
0.025: 12.83,
0.01: 15.09,
0.005: 16.75
},
6: {
0.995: 0.68,
0.99: 0.87,
0.975: 1.24,
0.95: 1.64,
0.9: 2.2,
0.5: 5.35,
0.1: 10.65,
0.05: 12.59,
0.025: 14.45,
0.01: 16.81,
0.005: 18.55
},
7: {
0.995: 0.99,
0.99: 1.25,
0.975: 1.69,
0.95: 2.17,
0.9: 2.83,
0.5: 6.35,
0.1: 12.02,
0.05: 14.07,
0.025: 16.01,
0.01: 18.48,
0.005: 20.28
},
8: {
0.995: 1.34,
0.99: 1.65,
0.975: 2.18,
0.95: 2.73,
0.9: 3.49,
0.5: 7.34,
0.1: 13.36,
0.05: 15.51,
0.025: 17.53,
0.01: 20.09,
0.005: 21.96
},
9: {
0.995: 1.73,
0.99: 2.09,
0.975: 2.7,
0.95: 3.33,
0.9: 4.17,
0.5: 8.34,
0.1: 14.68,
0.05: 16.92,
0.025: 19.02,
0.01: 21.67,
0.005: 23.59
},
10: {
0.995: 2.16,
0.99: 2.56,
0.975: 3.25,
0.95: 3.94,
0.9: 4.87,
0.5: 9.34,
0.1: 15.99,
0.05: 18.31,
0.025: 20.48,
0.01: 23.21,
0.005: 25.19
},
11: {
0.995: 2.6,
0.99: 3.05,
0.975: 3.82,
0.95: 4.57,
0.9: 5.58,
0.5: 10.34,
0.1: 17.28,
0.05: 19.68,
0.025: 21.92,
0.01: 24.72,
0.005: 26.76
},
12: {
0.995: 3.07,
0.99: 3.57,
0.975: 4.4,
0.95: 5.23,
0.9: 6.3,
0.5: 11.34,
0.1: 18.55,
0.05: 21.03,
0.025: 23.34,
0.01: 26.22,
0.005: 28.3
},
13: {
0.995: 3.57,
0.99: 4.11,
0.975: 5.01,
0.95: 5.89,
0.9: 7.04,
0.5: 12.34,
0.1: 19.81,
0.05: 22.36,
0.025: 24.74,
0.01: 27.69,
0.005: 29.82
},
14: {
0.995: 4.07,
0.99: 4.66,
0.975: 5.63,
0.95: 6.57,
0.9: 7.79,
0.5: 13.34,
0.1: 21.06,
0.05: 23.68,
0.025: 26.12,
0.01: 29.14,
0.005: 31.32
},
15: {
0.995: 4.6,
0.99: 5.23,
0.975: 6.27,
0.95: 7.26,
0.9: 8.55,
0.5: 14.34,
0.1: 22.31,
0.05: 25,
0.025: 27.49,
0.01: 30.58,
0.005: 32.8
},
16: {
0.995: 5.14,
0.99: 5.81,
0.975: 6.91,
0.95: 7.96,
0.9: 9.31,
0.5: 15.34,
0.1: 23.54,
0.05: 26.3,
0.025: 28.85,
0.01: 32,
0.005: 34.27
},
17: {
0.995: 5.7,
0.99: 6.41,
0.975: 7.56,
0.95: 8.67,
0.9: 10.09,
0.5: 16.34,
0.1: 24.77,
0.05: 27.59,
0.025: 30.19,
0.01: 33.41,
0.005: 35.72
},
18: {
0.995: 6.26,
0.99: 7.01,
0.975: 8.23,
0.95: 9.39,
0.9: 10.87,
0.5: 17.34,
0.1: 25.99,
0.05: 28.87,
0.025: 31.53,
0.01: 34.81,
0.005: 37.16
},
19: {
0.995: 6.84,
0.99: 7.63,
0.975: 8.91,
0.95: 10.12,
0.9: 11.65,
0.5: 18.34,
0.1: 27.2,
0.05: 30.14,
0.025: 32.85,
0.01: 36.19,
0.005: 38.58
},
20: {
0.995: 7.43,
0.99: 8.26,
0.975: 9.59,
0.95: 10.85,
0.9: 12.44,
0.5: 19.34,
0.1: 28.41,
0.05: 31.41,
0.025: 34.17,
0.01: 37.57,
0.005: 40
},
21: {
0.995: 8.03,
0.99: 8.9,
0.975: 10.28,
0.95: 11.59,
0.9: 13.24,
0.5: 20.34,
0.1: 29.62,
0.05: 32.67,
0.025: 35.48,
0.01: 38.93,
0.005: 41.4
},
22: {
0.995: 8.64,
0.99: 9.54,
0.975: 10.98,
0.95: 12.34,
0.9: 14.04,
0.5: 21.34,
0.1: 30.81,
0.05: 33.92,
0.025: 36.78,
0.01: 40.29,
0.005: 42.8
},
23: {
0.995: 9.26,
0.99: 10.2,
0.975: 11.69,
0.95: 13.09,
0.9: 14.85,
0.5: 22.34,
0.1: 32.01,
0.05: 35.17,
0.025: 38.08,
0.01: 41.64,
0.005: 44.18
},
24: {
0.995: 9.89,
0.99: 10.86,
0.975: 12.4,
0.95: 13.85,
0.9: 15.66,
0.5: 23.34,
0.1: 33.2,
0.05: 36.42,
0.025: 39.36,
0.01: 42.98,
0.005: 45.56
},
25: {
0.995: 10.52,
0.99: 11.52,
0.975: 13.12,
0.95: 14.61,
0.9: 16.47,
0.5: 24.34,
0.1: 34.28,
0.05: 37.65,
0.025: 40.65,
0.01: 44.31,
0.005: 46.93
},
26: {
0.995: 11.16,
0.99: 12.2,
0.975: 13.84,
0.95: 15.38,
0.9: 17.29,
0.5: 25.34,
0.1: 35.56,
0.05: 38.89,
0.025: 41.92,
0.01: 45.64,
0.005: 48.29
},
27: {
0.995: 11.81,
0.99: 12.88,
0.975: 14.57,
0.95: 16.15,
0.9: 18.11,
0.5: 26.34,
0.1: 36.74,
0.05: 40.11,
0.025: 43.19,
0.01: 46.96,
0.005: 49.65
},
28: {
0.995: 12.46,
0.99: 13.57,
0.975: 15.31,
0.95: 16.93,
0.9: 18.94,
0.5: 27.34,
0.1: 37.92,
0.05: 41.34,
0.025: 44.46,
0.01: 48.28,
0.005: 50.99
},
29: {
0.995: 13.12,
0.99: 14.26,
0.975: 16.05,
0.95: 17.71,
0.9: 19.77,
0.5: 28.34,
0.1: 39.09,
0.05: 42.56,
0.025: 45.72,
0.01: 49.59,
0.005: 52.34
},
30: {
0.995: 13.79,
0.99: 14.95,
0.975: 16.79,
0.95: 18.49,
0.9: 20.6,
0.5: 29.34,
0.1: 40.26,
0.05: 43.77,
0.025: 46.98,
0.01: 50.89,
0.005: 53.67
},
40: {
0.995: 20.71,
0.99: 22.16,
0.975: 24.43,
0.95: 26.51,
0.9: 29.05,
0.5: 39.34,
0.1: 51.81,
0.05: 55.76,
0.025: 59.34,
0.01: 63.69,
0.005: 66.77
},
50: {
0.995: 27.99,
0.99: 29.71,
0.975: 32.36,
0.95: 34.76,
0.9: 37.69,
0.5: 49.33,
0.1: 63.17,
0.05: 67.5,
0.025: 71.42,
0.01: 76.15,
0.005: 79.49
},
60: {
0.995: 35.53,
0.99: 37.48,
0.975: 40.48,
0.95: 43.19,
0.9: 46.46,
0.5: 59.33,
0.1: 74.4,
0.05: 79.08,
0.025: 83.3,
0.01: 88.38,
0.005: 91.95
},
70: {
0.995: 43.28,
0.99: 45.44,
0.975: 48.76,
0.95: 51.74,
0.9: 55.33,
0.5: 69.33,
0.1: 85.53,
0.05: 90.53,
0.025: 95.02,
0.01: 100.42,
0.005: 104.22
},
80: {
0.995: 51.17,
0.99: 53.54,
0.975: 57.15,
0.95: 60.39,
0.9: 64.28,
0.5: 79.33,
0.1: 96.58,
0.05: 101.88,
0.025: 106.63,
0.01: 112.33,
0.005: 116.32
},
90: {
0.995: 59.2,
0.99: 61.75,
0.975: 65.65,
0.95: 69.13,
0.9: 73.29,
0.5: 89.33,
0.1: 107.57,
0.05: 113.14,
0.025: 118.14,
0.01: 124.12,
0.005: 128.3
},
100: {
0.995: 67.33,
0.99: 70.06,
0.975: 74.22,
0.95: 77.93,
0.9: 82.36,
0.5: 99.33,
0.1: 118.5,
0.05: 124.34,
0.025: 129.56,
0.01: 135.81,
0.005: 140.17
}
};
/**
* The [χ2 (Chi-Squared) Goodness-of-Fit Test](http://en.wikipedia.org/wiki/Goodness_of_fit#Pearson.27s_chi-squared_test)
* uses a measure of goodness of fit which is the sum of differences between observed and expected outcome frequencies
* (that is, counts of observations), each squared and divided by the number of observations expected given the
* hypothesized distribution. The resulting χ2 statistic, `chiSquared`, can be compared to the chi-squared distribution
* to determine the goodness of fit. In order to determine the degrees of freedom of the chi-squared distribution, one
* takes the total number of observed frequencies and subtracts the number of estimated parameters. The test statistic
* follows, approximately, a chi-square distribution with (k − c) degrees of freedom where `k` is the number of non-empty
* cells and `c` is the number of estimated parameters for the distribution.
*
* @param {Array<number>} data
* @param {Function} distributionType a function that returns a point in a distribution:
* for instance, binomial, bernoulli, or poisson
* @param {number} significance
* @returns {number} chi squared goodness of fit
* @example
* // Data from Poisson goodness-of-fit example 10-19 in William W. Hines & Douglas C. Montgomery,
* // "Probability and Statistics in Engineering and Management Science", Wiley (1980).
* var data1019 = [
* 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
* 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
* 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
* 2, 2, 2, 2, 2, 2, 2, 2, 2,
* 3, 3, 3, 3
* ];
* ss.chiSquaredGoodnessOfFit(data1019, ss.poissonDistribution, 0.05); //= false
*/
function chiSquaredGoodnessOfFit(data, distributionType, significance) {
// Estimate from the sample data, a weighted mean.
var inputMean = mean(data);
// Calculated value of the χ2 statistic.
var chiSquared = 0;
// Number of hypothesized distribution parameters estimated, expected to be supplied in the distribution test.
// Lose one degree of freedom for estimating `lambda` from the sample data.
var c = 1;
// The hypothesized distribution.
// Generate the hypothesized distribution.
var hypothesizedDistribution = distributionType(inputMean);
var observedFrequencies = [];
var expectedFrequencies = [];
// Create an array holding a histogram from the sample data, of
// the form `{ value: numberOfOcurrences }`
for (var i = 0; i < data.length; i++) {
if (observedFrequencies[data[i]] === undefined) {
observedFrequencies[data[i]] = 0;
}
observedFrequencies[data[i]]++;
}
// The histogram we created might be sparse - there might be gaps
// between values. So we iterate through the histogram, making
// sure that instead of undefined, gaps have 0 values.
for (var i$1 = 0; i$1 < observedFrequencies.length; i$1++) {
if (observedFrequencies[i$1] === undefined) {
observedFrequencies[i$1] = 0;
}
}
// Create an array holding a histogram of expected data given the
// sample size and hypothesized distribution.
for (var k in hypothesizedDistribution) {
if (k in observedFrequencies) {
expectedFrequencies[+k] = hypothesizedDistribution[k] * data.length;
}
}
// Working backward through the expected frequencies, collapse classes
// if less than three observations are expected for a class.
// This transformation is applied to the observed frequencies as well.
for (var k$1 = expectedFrequencies.length - 1; k$1 >= 0; k$1--) {
if (expectedFrequencies[k$1] < 3) {
expectedFrequencies[k$1 - 1] += expectedFrequencies[k$1];
expectedFrequencies.pop();
observedFrequencies[k$1 - 1] += observedFrequencies[k$1];
observedFrequencies.pop();
}
}
// Iterate through the squared differences between observed & expected
// frequencies, accumulating the `chiSquared` statistic.
for (var k$2 = 0; k$2 < observedFrequencies.length; k$2++) {
chiSquared += Math.pow(observedFrequencies[k$2] - expectedFrequencies[k$2], 2) / expectedFrequencies[k$2];
}
// Calculate degrees of freedom for this test and look it up in the
// `chiSquaredDistributionTable` in order to
// accept or reject the goodness-of-fit of the hypothesized distribution.
// Degrees of freedom, calculated as (number of class intervals -
// number of hypothesized distribution parameters estimated - 1)
var degreesOfFreedom = observedFrequencies.length - c - 1;
return chiSquaredDistributionTable[degreesOfFreedom][significance] < chiSquared;
}
var SQRT_2PI$1 = Math.sqrt(2 * Math.PI);
/**
* [Well-known kernels](https://en.wikipedia.org/wiki/Kernel_(statistics)#Kernel_functions_in_common_use)
* @private
*/
var kernels = {
/**
* The gaussian kernel.
* @private
*/
gaussian: function (u) {
return Math.exp(-0.5 * u * u) / SQRT_2PI$1;
}
};
/**
* Well known bandwidth selection methods
* @private
*/
var bandwidthMethods = {
/**
* The ["normal reference distribution"
* rule-of-thumb](https://stat.ethz.ch/R-manual/R-devel/library/MASS/html/bandwidth.nrd.html),
* a commonly used version of [Silverman's
* rule-of-thumb](https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator).
* @private
*/
nrd: function (x) {
var s = sampleStandardDeviation(x);
var iqr = interquartileRange(x);
if (typeof iqr === "number") {
s = Math.min(s, iqr / 1.34);
}
return 1.06 * s * Math.pow(x.length, -0.2);
}
};
/**
* [Kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation)
* is a useful tool for, among other things, estimating the shape of the
* underlying probability distribution from a sample.
*
* @name kernelDensityEstimation
* @param X sample values
* @param kernel The kernel function to use. If a function is provided, it should return non-negative values and integrate to 1. Defaults to 'gaussian'.
* @param bandwidthMethod The "bandwidth selection" method to use, or a fixed bandwidth value. Defaults to "nrd", the commonly-used ["normal reference distribution" rule-of-thumb](https://stat.ethz.ch/R-manual/R-devel/library/MASS/html/bandwidth.nrd.html).
* @returns {Function} An estimated [probability density function](https://en.wikipedia.org/wiki/Probability_density_function) for the given sample. The returned function runs in `O(X.length)`.
*/
function kernelDensityEstimation(X, kernel, bandwidthMethod) {
var kernelFn;
if (kernel === undefined) {
kernelFn = kernels.gaussian;
} else if (typeof kernel === "string") {
if (!kernels[kernel]) {
throw new Error('Unknown kernel "' + kernel + '"');
}
kernelFn = kernels[kernel];
} else {
kernelFn = kernel;
}
var bandwidth;
if (typeof bandwidthMethod === "undefined") {
bandwidth = bandwidthMethods.nrd(X);
} else if (typeof bandwidthMethod === "string") {
if (!bandwidthMethods[bandwidthMethod]) {
throw new Error('Unknown bandwidth method "' + bandwidthMethod + '"');
}
bandwidth = bandwidthMethods[bandwidthMethod](X);
} else {
bandwidth = bandwidthMethod;
}
return function (x) {
var i = 0;
var sum = 0;
for (i = 0; i < X.length; i++) {
sum += kernelFn((x - X[i]) / bandwidth);
}
return sum / bandwidth / X.length;
};
}
/**
* The [Z-Score, or Standard Score](http://en.wikipedia.org/wiki/Standard_score).
*
* The standard score is the number of standard deviations an observation
* or datum is above or below the mean. Thus, a positive standard score
* represents a datum above the mean, while a negative standard score
* represents a datum below the mean. It is a dimensionless quantity
* obtained by subtracting the population mean from an individual raw
* score and then dividing the difference by the population standard
* deviation.
*
* The z-score is only defined if one knows the population parameters;
* if one only has a sample set, then the analogous computation with
* sample mean and sample standard deviation yields the
* Student's t-statistic.
*
* @param {number} x
* @param {number} mean
* @param {number} standardDeviation
* @return {number} z score
* @example
* zScore(78, 80, 5); // => -0.4
*/
function zScore(x, mean, standardDeviation) {
return (x - mean) / standardDeviation;
}
var SQRT_2PI = Math.sqrt(2 * Math.PI);
function cumulativeDistribution(z) {
var sum = z,
tmp = z;
// 15 iterations are enough for 4-digit precision
for (var i = 1; i < 15; i++) {
tmp *= z * z / (2 * i + 1);
sum += tmp;
}
return Math.round((0.5 + sum / SQRT_2PI * Math.exp(-z * z / 2)) * 1e4) / 1e4;
}
/**
* A standard normal table, also called the unit normal table or Z table,
* is a mathematical table for the values of Φ (phi), which are the values of
* the [cumulative distribution function](https://en.wikipedia.org/wiki/Normal_distribution#Cumulative_distribution_function)
* of the normal distribution. It is used to find the probability that a
* statistic is observed below, above, or between values on the standard
* normal distribution, and by extension, any normal distribution.
*/
var standardNormalTable = [];
for (var z = 0; z <= 3.09; z += 0.01) {
standardNormalTable.push(cumulativeDistribution(z));
}
/**
* **[Cumulative Standard Normal Probability](http://en.wikipedia.org/wiki/Standard_normal_table)**
*
* Since probability tables cannot be
* printed for every normal distribution, as there are an infinite variety
* of normal distributions, it is common practice to convert a normal to a
* standard normal and then use the standard normal table to find probabilities.
*
* You can use `.5 + .5 * errorFunction(x / Math.sqrt(2))` to calculate the probability
* instead of looking it up in a table.
*
* @param {number} z
* @returns {number} cumulative standard normal probability
*/
function cumulativeStdNormalProbability(z) {
// Calculate the position of this value.
var absZ = Math.abs(z);
// Each row begins with a different
// significant digit: 0.5, 0.6, 0.7, and so on. Each value in the table
// corresponds to a range of 0.01 in the input values, so the value is
// multiplied by 100.
var index = Math.min(Math.round(absZ * 100), standardNormalTable.length - 1);
// The index we calculate must be in the table as a positive value,
// but we still pay attention to whether the input is positive
// or negative, and flip the output value as a last step.
if (z >= 0) {
return standardNormalTable[index];
} else {
// due to floating-point arithmetic, values in the table with
// 4 significant figures can nevertheless end up as repeating
// fractions when they're computed here.
return +(1 - standardNormalTable[index]).toFixed(4);
}
}
/**
* **[Logistic Cumulative Distribution Function](https://en.wikipedia.org/wiki/Logistic_distribution)**
*
* @param {number} x
* @returns {number} cumulative standard logistic probability
*/
function cumulativeStdLogisticProbability(x) {
return 1 / (Math.exp(-x) + 1);
}
/**
* **[Gaussian error function](http://en.wikipedia.org/wiki/Error_function)**
*
* The `errorFunction(x/(sd * Math.sqrt(2)))` is the probability that a value in a
* normal distribution with standard deviation sd is within x of the mean.
*
* This function returns a numerical approximation to the exact value.
* It uses Horner's method to evaluate the polynomial of τ (tau).
*
* @param {number} x input
* @return {number} error estimation
* @example
* errorFunction(1).toFixed(2); // => '0.84'
*/
function errorFunction(x) {
var t = 1 / (1 + 0.5 * Math.abs(x));
var tau = t * Math.exp(-x * x + ((((((((0.17087277 * t - 0.82215223) * t + 1.48851587) * t - 1.13520398) * t + 0.27886807) * t - 0.18628806) * t + 0.09678418) * t + 0.37409196) * t + 1.00002368) * t - 1.26551223);
if (x >= 0) {
return 1 - tau;
} else {
return tau - 1;
}
}
/**
* The Inverse [Gaussian error function](http://en.wikipedia.org/wiki/Error_function)
* returns a numerical approximation to the value that would have caused
* `errorFunction()` to return x.
*
* @param {number} x value of error function
* @returns {number} estimated inverted value
*/
function inverseErrorFunction(x) {
var a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
var inv = Math.sqrt(Math.sqrt(Math.pow(2 / (Math.PI * a) + Math.log(1 - x * x) / 2, 2) - Math.log(1 - x * x) / a) - (2 / (Math.PI * a) + Math.log(1 - x * x) / 2));
if (x >= 0) {
return inv;
} else {
return -inv;
}
}
/**
* The [Probit](http://en.wikipedia.org/wiki/Probit)
* is the inverse of cumulativeStdNormalProbability(),
* and is also known as the normal quantile function.
*
* It returns the number of standard deviations from the mean
* where the p'th quantile of values can be found in a normal distribution.
* So, for example, probit(0.5 + 0.6827/2) ≈ 1 because 68.27% of values are
* normally found within 1 standard deviation above or below the mean.
*
* @param {number} p
* @returns {number} probit
*/
function probit(p) {
if (p === 0) {
p = epsilon;
} else if (p >= 1) {
p = 1 - epsilon;
}
return Math.sqrt(2) * inverseErrorFunction(2 * p - 1);
}
/**
* The [Logit](https://en.wikipedia.org/wiki/Logit)
* is the inverse of cumulativeStdLogisticProbability,
* and is also known as the logistic quantile function.
*
* @param {number} p
* @returns {number} logit
*/
function logit(p) {
if (p <= 0 || p >= 1) {
throw new Error("p must be strictly between zero and one");
}
return Math.log(p / (1 - p));
}
/**
* Conducts a [permutation test](https://en.wikipedia.org/wiki/Resampling_(statistics)#Permutation_tests)
* to determine if two data sets are *significantly* different from each other, using
* the difference of means between the groups as the test statistic.
* The function allows for the following hypotheses:
* - two_tail = Null hypothesis: the two distributions are equal.
* - greater = Null hypothesis: observations from sampleX tend to be smaller than those from sampleY.
* - less = Null hypothesis: observations from sampleX tend to be greater than those from sampleY.
* [Learn more about one-tail vs two-tail tests.](https://en.wikipedia.org/wiki/One-_and_two-tailed_tests)
*
* @param {Array<number>} sampleX first dataset (e.g. treatment data)
* @param {Array<number>} sampleY second dataset (e.g. control data)
* @param {string} alternative alternative hypothesis, either 'two_sided' (default), 'greater', or 'less'
* @param {number} k number of values in permutation distribution.
* @param {Function} [randomSource=Math.random] an optional entropy source
* @returns {number} p-value The probability of observing the difference between groups (as or more extreme than what we did), assuming the null hypothesis.
*
* @example
* var control = [2, 5, 3, 6, 7, 2, 5];
* var treatment = [20, 5, 13, 12, 7, 2, 2];
* permutationTest(control, treatment); // ~0.1324
*/
function permutationTest(sampleX, sampleY, alternative, k, randomSource) {
// Set default arguments
if (k === undefined) {
k = 10000;
}
if (alternative === undefined) {
alternative = "two_side";
}
if (alternative !== "two_side" && alternative !== "greater" && alternative !== "less") {
throw new Error("`alternative` must be either 'two_side', 'greater', or 'less'.");
}
// get means for each sample
var meanX = mean(sampleX);
var meanY = mean(sampleY);
// calculate initial test statistic. This will be our point of comparison with
// the generated test statistics.
var testStatistic = meanX - meanY;
// create test-statistic distribution
var testStatDsn = new Array(k);
// combine datsets so we can easily shuffle later
var allData = sampleX.concat(sampleY);
var midIndex = Math.floor(allData.length / 2);
for (var i = 0; i < k; i++) {
// 1. shuffle data assignments
shuffleInPlace(allData, randomSource);
var permLeft = allData.slice(0, midIndex);
var permRight = allData.slice(midIndex, allData.length);
// 2.re-calculate test statistic
var permTestStatistic = mean(permLeft) - mean(permRight);
// 3. store test statistic to build test statistic distribution
testStatDsn[i] = permTestStatistic;
}
// Calculate p-value depending on alternative
// For this test, we calculate the percentage of 'extreme' test statistics (subject to our hypothesis)
// more info on permutation test p-value calculations: https://onlinecourses.science.psu.edu/stat464/node/35
var numExtremeTStats = 0;
if (alternative === "two_side") {
for (var i$1 = 0; i$1 <= k; i$1++) {
if (Math.abs(testStatDsn[i$1]) >= Math.abs(testStatistic)) {
numExtremeTStats += 1;
}
}
} else if (alternative === "greater") {
for (var i$2 = 0; i$2 <= k; i$2++) {
if (testStatDsn[i$2] >= testStatistic) {
numExtremeTStats += 1;
}
}
} else {
// alternative === 'less'
for (var i$3 = 0; i$3 <= k; i$3++) {
if (testStatDsn[i$3] <= testStatistic) {
numExtremeTStats += 1;
}
}
}
return numExtremeTStats / k;
}
/**
* [Sign](https://en.wikipedia.org/wiki/Sign_function) is a function
* that extracts the sign of a real number
*
* @param {number} x input value
* @returns {number} sign value either 1, 0 or -1
* @throws {TypeError} if the input argument x is not a number
* @private
*
* @example
* sign(2); // => 1
*/
function sign(x) {
if (typeof x === "number") {
if (x < 0) {
return -1;
} else if (x === 0) {
return 0;
} else {
return 1;
}
} else {
throw new TypeError("not a number");
}
}
/**
* [Bisection method](https://en.wikipedia.org/wiki/Bisection_method) is a root-finding
* method that repeatedly bisects an interval to find the root.
*
* This function returns a numerical approximation to the exact value.
*
* @param {Function} func input function
* @param {number} start - start of interval
* @param {number} end - end of interval
* @param {number} maxIterations - the maximum number of iterations
* @param {number} errorTolerance - the error tolerance
* @returns {number} estimated root value
* @throws {TypeError} Argument func must be a function
*
* @example
* bisect(Math.cos,0,4,100,0.003); // => 1.572265625
*/
function bisect(func, start, end, maxIterations, errorTolerance) {
if (typeof func !== "function") {
throw new TypeError("func must be a function");
}
for (var i = 0; i < maxIterations; i++) {
var output = (start + end) / 2;
if (func(output) === 0 || Math.abs((end - start) / 2) < errorTolerance) {
return output;
}
if (sign(func(output)) === sign(func(start))) {
start = output;
} else {
end = output;
}
}
throw new Error("maximum number of iterations exceeded");
}
/**
* Calculate Euclidean distance between two points.
* @param {Array<number>} left First N-dimensional point.
* @param {Array<number>} right Second N-dimensional point.
* @returns {number} Distance.
*/
function euclideanDistance(left, right) {
var sum = 0;
for (var i = 0; i < left.length; i++) {
var diff = left[i] - right[i];
sum += diff * diff;
}
return Math.sqrt(sum);
}
/**
* @typedef {Object} kMeansReturn
* @property {Array<number>} labels The labels.
* @property {Array<Array<number>>} centroids The cluster centroids.
*/
/**
* Perform k-means clustering.
*
* @param {Array<Array<number>>} points N-dimensional coordinates of points to be clustered.
* @param {number} numCluster How many clusters to create.
* @param {Function} randomSource An optional entropy source that generates uniform values in [0, 1).
* @return {kMeansReturn} Labels (same length as data) and centroids (same length as numCluster).
* @throws {Error} If any centroids wind up friendless (i.e., without associated points).
*
* @example
* kMeansCluster([[0.0, 0.5], [1.0, 0.5]], 2); // => {labels: [0, 1], centroids: [[0.0, 0.5], [1.0 0.5]]}
*/
function kMeansCluster(points, numCluster, randomSource) {
if (randomSource === void 0) randomSource = Math.random;
var oldCentroids = null;
var newCentroids = sample(points, numCluster, randomSource);
var labels = null;
var change = Number.MAX_VALUE;
while (change !== 0) {
labels = labelPoints(points, newCentroids);
oldCentroids = newCentroids;
newCentroids = calculateCentroids(points, labels, numCluster);
change = calculateChange(newCentroids, oldCentroids);
}
return {
labels: labels,
centroids: newCentroids
};
}
/**
* Label each point according to which centroid it is closest to.
*
* @private
* @param {Array<Array<number>>} points Array of XY coordinates.
* @param {Array<Array<number>>} centroids Current centroids.
* @return {Array<number>} Group labels.
*/
function labelPoints(points, centroids) {
return points.map(function (p) {
var minDist = Number.MAX_VALUE;
var label = -1;
for (var i = 0; i < centroids.length; i++) {
var dist = euclideanDistance(p, centroids[i]);
if (dist < minDist) {
minDist = dist;
label = i;
}
}
return label;
});
}
/**
* Calculate centroids for points given labels.
*
* @private
* @param {Array<Array<number>>} points Array of XY coordinates.
* @param {Array<number>} labels Which groups points belong to.
* @param {number} numCluster Number of clusters being created.
* @return {Array<Array<number>>} Centroid for each group.
* @throws {Error} If any centroids wind up friendless (i.e., without associated points).
*/
function calculateCentroids(points, labels, numCluster) {
// Initialize accumulators.
var dimension = points[0].length;
var centroids = makeMatrix(numCluster, dimension);
var counts = Array(numCluster).fill(0);
// Add points to centroids' accumulators and count points per centroid.
var numPoints = points.length;
for (var i = 0; i < numPoints; i++) {
var point = points[i];
var label = labels[i];
var current = centroids[label];
for (var j = 0; j < dimension; j++) {
current[j] += point[j];
}
counts[label] += 1;
}
// Rescale centroids, checking for any that have no points.
for (var i$1 = 0; i$1 < numCluster; i$1++) {
if (counts[i$1] === 0) {
throw new Error("Centroid " + i$1 + " has no friends");
}
var centroid = centroids[i$1];
for (var j$1 = 0; j$1 < dimension; j$1++) {
centroid[j$1] /= counts[i$1];
}
}
return centroids;
}
/**
* Calculate the difference between old centroids and new centroids.
*
* @private
* @param {Array<Array<number>>} left One list of centroids.
* @param {Array<Array<number>>} right Another list of centroids.
* @return {number} Distance between centroids.
*/
function calculateChange(left, right) {
var total = 0;
for (var i = 0; i < left.length; i++) {
total += euclideanDistance(left[i], right[i]);
}
return total;
}
/**
* Calculate the [silhouette values](https://en.wikipedia.org/wiki/Silhouette_(clustering))
* for clustered data.
*
* @param {Array<Array<number>>} points N-dimensional coordinates of points.
* @param {Array<number>} labels Labels of points. This must be the same length as `points`,
* and values must lie in [0..G-1], where G is the number of groups.
* @return {Array<number>} The silhouette value for each point.
*
* @example
* silhouette([[0.25], [0.75]], [0, 0]); // => [1.0, 1.0]
*/
function silhouette(points, labels) {
if (points.length !== labels.length) {
throw new Error("must have exactly as many labels as points");
}
var groupings = createGroups(labels);
var distances = calculateAllDistances(points);
var result = [];
for (var i = 0; i < points.length; i++) {
var s = 0;
if (groupings[labels[i]].length > 1) {
var a = meanDistanceFromPointToGroup(i, groupings[labels[i]], distances);
var b = meanDistanceToNearestGroup(i, labels, groupings, distances);
s = (b - a) / Math.max(a, b);
}
result.push(s);
}
return result;
}
/**
* Create a lookup table mapping group IDs to point IDs.
*
* @private
* @param {Array<number>} labels Labels of points. This must be the same length as `points`,
* and values must lie in [0..G-1], where G is the number of groups.
* @return {Array<Array<number>>} An array of length G, each of whose entries is an array
* containing the indices of the points in that group.
*/
function createGroups(labels) {
var numGroups = 1 + max(labels);
var result = Array(numGroups);
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
if (result[label] === undefined) {
result[label] = [];
}
result[label].push(i);
}
return result;
}
/**
* Create a lookup table of all inter-point distances.
*
* @private
* @param {Array<Array<number>>} points N-dimensional coordinates of points.
* @return {Array<Array<number>>} A symmetric square array of inter-point distances
* (zero on the diagonal).
*/
function calculateAllDistances(points) {
var numPoints = points.length;
var result = makeMatrix(numPoints, numPoints);
for (var i = 0; i < numPoints; i++) {
for (var j = 0; j < i; j++) {
result[i][j] = euclideanDistance(points[i], points[j]);
result[j][i] = result[i][j];
}
}
return result;
}
/**
* Calculate the mean distance between this point and all the points in the
* nearest group (as determined by which point in another group is closest).
*
* @private
* @param {number} which The index of this point.
* @param {Array<number>} labels Labels of points.
* @param {Array<Array<number>>} groupings An array whose entries are arrays
* containing the indices of the points in that group.
* @param {Array<Array<number>>} distances A symmetric square array of inter-point
* distances.
* @return {number} The mean distance from this point to others in the nearest
* group.
*/
function meanDistanceToNearestGroup(which, labels, groupings, distances) {
var label = labels[which];
var result = Number.MAX_VALUE;
for (var i = 0; i < groupings.length; i++) {
if (i !== label) {
var d = meanDistanceFromPointToGroup(which, groupings[i], distances);
if (d < result) {
result = d;
}
}
}
return result;
}
/**
* Calculate the mean distance between a point and all the points in a group
* (possibly its own).
*
* @private
* @param {number} which The index of this point.
* @param {Array<number>} group The indices of all the points in the group in
* question.
* @param {Array<Array<number>>} distances A symmetric square array of inter-point
* distances.
* @return {number} The mean distance from this point to others in the
* specified group.
*/
function meanDistanceFromPointToGroup(which, group, distances) {
var total = 0;
for (var i = 0; i < group.length; i++) {
total += distances[which][group[i]];
}
return total / group.length;
}
/**
* Calculate the [silhouette metric](https://en.wikipedia.org/wiki/Silhouette_(clustering))
* for a set of N-dimensional points arranged in groups. The metric is the largest
* individual silhouette value for the data.
*
* @param {Array<Array<number>>} points N-dimensional coordinates of points.
* @param {Array<number>} labels Labels of points. This must be the same length as `points`,
* and values must lie in [0..G-1], where G is the number of groups.
* @return {number} The silhouette metric for the groupings.
*
* @example
* silhouetteMetric([[0.25], [0.75]], [0, 0]); // => 1.0
*/
function silhouetteMetric(points, labels) {
var values = silhouette(points, labels);
return max(values);
}
/**
* Relative error.
*
* This is more difficult to calculate than it first appears [1,2]. The usual
* formula for the relative error between an actual value A and an expected
* value E is `|(A-E)/E|`, but:
*
* 1. If the expected value is 0, any other value has infinite relative error,
* which is counter-intuitive: if the expected voltage is 0, getting 1/10th
* of a volt doesn't feel like an infinitely large error.
*
* 2. This formula does not satisfy the mathematical definition of a metric [3].
* [4] solved this problem by defining the relative error as `|ln(|A/E|)|`,
* but that formula only works if all values are positive: for example, it
* reports the relative error of -10 and 10 as 0.
*
* Our implementation sticks with convention and returns:
*
* - 0 if the actual and expected values are both zero
* - Infinity if the actual value is non-zero and the expected value is zero
* - `|(A-E)/E|` in all other cases
*
* [1] https://math.stackexchange.com/questions/677852/how-to-calculate-relative-error-when-true-value-is-zero
* [2] https://en.wikipedia.org/wiki/Relative_change_and_difference
* [3] https://en.wikipedia.org/wiki/Metric_(mathematics)#Definition
* [4] F.W.J. Olver: "A New Approach to Error Arithmetic." SIAM Journal on
* Numerical Analysis, 15(2), 1978, 10.1137/0715024.
*
* @param {number} actual The actual value.
* @param {number} expected The expected value.
* @return {number} The relative error.
*/
function relativeError(actual, expected) {
if (actual === 0 && expected === 0) {
return 0;
}
return Math.abs((actual - expected) / expected);
}
/**
* Approximate equality.
*
* @param {number} actual The value to be tested.
* @param {number} expected The reference value.
* @param {number} tolerance The acceptable relative difference.
* @return {boolean} Whether numbers are within tolerance.
*/
function approxEqual(actual, expected, tolerance) {
if (tolerance === void 0) tolerance = epsilon;
return relativeError(actual, expected) <= tolerance;
}
var simpleStatistics = /*#__PURE__*/Object.freeze({
__proto__: null,
BayesianClassifier: BayesianClassifier,
PerceptronModel: PerceptronModel,
addToMean: addToMean,
approxEqual: approxEqual,
average: mean,
averageSimple: meanSimple,
bayesian: BayesianClassifier,
bernoulliDistribution: bernoulliDistribution,
binomialDistribution: binomialDistribution,
bisect: bisect,
chiSquaredDistributionTable: chiSquaredDistributionTable,
chiSquaredGoodnessOfFit: chiSquaredGoodnessOfFit,
chunk: chunk,
ckmeans: ckmeans,
coefficientOfVariation: coefficientOfVariation,
combinations: combinations,
combinationsReplacement: combinationsReplacement,
combineMeans: combineMeans,
combineVariances: combineVariances,
cumulativeStdLogisticProbability: cumulativeStdLogisticProbability,
cumulativeStdNormalProbability: cumulativeStdNormalProbability,
epsilon: epsilon,
equalIntervalBreaks: equalIntervalBreaks,
erf: errorFunction,
errorFunction: errorFunction,
extent: extent,
extentSorted: extentSorted,
factorial: factorial,
gamma: gamma,
gammaln: gammaln,
geometricMean: geometricMean,
harmonicMean: harmonicMean,
interquartileRange: interquartileRange,
inverseErrorFunction: inverseErrorFunction,
iqr: interquartileRange,
jenks: jenks,
kMeansCluster: kMeansCluster,
kde: kernelDensityEstimation,
kernelDensityEstimation: kernelDensityEstimation,
linearRegression: linearRegression,
linearRegressionLine: linearRegressionLine,
logAverage: logAverage,
logit: logit,
mad: medianAbsoluteDeviation,
max: max,
maxSorted: maxSorted,
mean: mean,
meanSimple: meanSimple,
median: median,
medianAbsoluteDeviation: medianAbsoluteDeviation,
medianSorted: medianSorted,
min: min,
minSorted: minSorted,
mode: mode,
modeFast: modeFast,
modeSorted: modeSorted,
numericSort: numericSort,
perceptron: PerceptronModel,
permutationTest: permutationTest,
permutationsHeap: permutationsHeap,
poissonDistribution: poissonDistribution,
probit: probit,
product: product,
quantile: quantile,
quantileRank: quantileRank,
quantileRankSorted: quantileRankSorted,
quantileSorted: quantileSorted,
quickselect: quickselect,
rSquared: rSquared,
relativeError: relativeError,
rms: rootMeanSquare,
rootMeanSquare: rootMeanSquare,
sample: sample,
sampleCorrelation: sampleCorrelation,
sampleCovariance: sampleCovariance,
sampleKurtosis: sampleKurtosis,
sampleRankCorrelation: sampleRankCorrelation,
sampleSkewness: sampleSkewness,
sampleStandardDeviation: sampleStandardDeviation,
sampleVariance: sampleVariance,
sampleWithReplacement: sampleWithReplacement,
shuffle: shuffle,
shuffleInPlace: shuffleInPlace,
sign: sign,
silhouette: silhouette,
silhouetteMetric: silhouetteMetric,
standardDeviation: standardDeviation,
standardNormalTable: standardNormalTable,
subtractFromMean: subtractFromMean,
sum: sum,
sumNthPowerDeviations: sumNthPowerDeviations,
sumSimple: sumSimple,
tTest: tTest,
tTestTwoSample: tTestTwoSample,
uniqueCountSorted: uniqueCountSorted,
variance: variance,
wilcoxonRankSum: wilcoxonRankSum,
zScore: zScore
});
var DATAVIEW_TYPE;
(function (DATAVIEW_TYPE) {
DATAVIEW_TYPE["DSV"] = "dsv";
DATAVIEW_TYPE["TREE"] = "tree";
DATAVIEW_TYPE["GEO"] = "geo";
DATAVIEW_TYPE["BYTE"] = "bytejson";
DATAVIEW_TYPE["HEX"] = "hex";
DATAVIEW_TYPE["GRAPH"] = "graph";
DATAVIEW_TYPE["TABLE"] = "table";
DATAVIEW_TYPE["GEO_GRATICULE"] = "geo-graticule";
})(DATAVIEW_TYPE || (DATAVIEW_TYPE = {}));
const STATISTICS_METHODS = [
'max',
'mean',
'median',
'min',
'mode',
'product',
'standardDeviation',
'sum',
'sumSimple',
'variance'
];
const DEFAULT_STATISTICS_OPTIONS = {
as: [],
fields: [],
groupBy: null,
operations: ['count', 'max', 'min', 'average', 'sum']
};
const aggregates = {
count(data) {
return data.length;
},
distinct(data, field) {
const values = uniqArray(data.map(row => +row[field]));
return values.length;
}
};
STATISTICS_METHODS.forEach(method => {
aggregates[method] = (data, field) => {
let values = data.map(row => +row[field]);
if (isArray$1(values) && isArray$1(values[0])) {
values = flattenArray(values);
}
return simpleStatistics[method](values);
};
});
aggregates.average = aggregates.mean;
const statistics = (data, options) => {
const mergeOptions = mergeDeepImmer(DEFAULT_STATISTICS_OPTIONS, options);
const { as, fields, groupBy, operations } = mergeOptions;
const groups = {};
data.forEach(d => {
groups[d[groupBy]] = groups[d[groupBy]] || [];
groups[d[groupBy]].push(d);
});
const results = [];
for (const key in groups) {
const result = {
group: key
};
const group = groups[key];
operations.forEach((operation, i) => {
var _a, _b;
const outputName = (_a = as[i]) !== null && _a !== void 0 ? _a : operation;
const field = (_b = fields[i]) !== null && _b !== void 0 ? _b : fields[0];
result[outputName] = aggregates[operation](group, field);
});
results.push(result);
}
return results;
};
const map = (data, options) => {
const { callback } = options;
if (callback) {
data = data.map(callback);
}
return data;
};
const fold = (data, options) => {
const { fields, key, value, retains } = options;
const results = [];
for (let i = 0; i < data.length; i++) {
fields.forEach(field => {
const item = {};
item[key] = field;
item[value] = data[i][field];
if (retains) {
retains.forEach(retain => {
item[retain] = data[i][retain];
});
}
else {
for (const prop in data[i]) {
if (fields.indexOf(prop) === -1) {
item[prop] = data[i][prop];
}
}
}
results.push(item);
});
}
return results;
};
const fields = (data, options) => {
var _a, _b;
if (!(options === null || options === void 0 ? void 0 : options.fields)) {
return data;
}
if (data.length === 0) {
return data;
}
const fields = options.fields;
const dataTemp = data[0];
const filterFields = {};
const sortFields = [];
for (const key in fields) {
if (Object.prototype.hasOwnProperty.call(fields, key)) {
const fieldInfo = fields[key];
if (!fieldInfo.type) {
let dataCheck = dataTemp;
if (!(key in dataTemp)) {
dataCheck = (_a = data.find(d => key in d)) !== null && _a !== void 0 ? _a : dataTemp;
}
fieldInfo.type = typeof dataCheck[key] === 'number' ? 'linear' : 'ordinal';
}
let sortInfo;
if (typeof fieldInfo.sortIndex === 'number') {
sortInfo = {
key,
type: fieldInfo.type,
index: fieldInfo.sortIndex,
sortIndex: {},
sortIndexCount: 0,
sortReverse: fieldInfo.sortReverse === true
};
sortFields.push(sortInfo);
}
if (((_b = fieldInfo.domain) === null || _b === void 0 ? void 0 : _b.length) > 0) {
if (fieldInfo.type === 'ordinal') {
fieldInfo._domainCache = {};
filterFields[key] = fieldInfo;
const _domainCache = {};
fieldInfo.domain.forEach((d, i) => {
_domainCache[d] = i;
fieldInfo._domainCache[d] = i;
});
if (sortInfo) {
sortInfo.sortIndex = _domainCache;
sortInfo.sortIndexCount = fieldInfo.domain.length;
}
}
else if (fieldInfo.domain.length >= 2) {
filterFields[key] = fieldInfo;
}
}
}
}
const filterKeys = Object.keys(filterFields);
if (filterKeys.length > 0) {
data = data.filter(d => {
for (const key in filterFields) {
const fieldInfo = filterFields[key];
if (fieldInfo.type === 'ordinal') {
if (!(d[key] in fieldInfo._domainCache)) {
return false;
}
}
else {
if (fieldInfo.domain[0] > d[key] || fieldInfo.domain[1] < d[key]) {
return false;
}
}
}
return true;
});
}
sortFields.sort((a, b) => a.index - b.index);
data.sort((a, b) => sortData(a, b, sortFields));
return data;
};
function sortData(a, b, sortFields) {
for (let i = 0; i < sortFields.length; i++) {
const sortInfo = sortFields[i];
let v = 0;
if (sortInfo.type === 'ordinal') {
if (sortInfo.sortIndex[b[sortInfo.key]] === undefined) {
sortInfo.sortIndex[b[sortInfo.key]] = sortInfo.sortIndexCount++;
}
if (sortInfo.sortIndex[a[sortInfo.key]] === undefined) {
sortInfo.sortIndex[a[sortInfo.key]] = sortInfo.sortIndexCount++;
}
v = sortInfo.sortIndex[a[sortInfo.key]] - sortInfo.sortIndex[b[sortInfo.key]];
}
else if (sortInfo.type === 'linear') {
v = a[sortInfo.key] - b[sortInfo.key];
}
if (sortInfo.sortReverse) {
v = -v;
}
if (v === 0) {
continue;
}
return v;
}
return 0;
}
var EOL = {},
EOF = {},
QUOTE = 34,
NEWLINE = 10,
RETURN = 13;
function objectConverter(columns) {
return new Function("d", "return {" + columns.map(function (name, i) {
return JSON.stringify(name) + ": d[" + i + "] || \"\"";
}).join(",") + "}");
}
function customConverter(columns, f) {
var object = objectConverter(columns);
return function (row, i) {
return f(object(row), i, columns);
};
}
// Compute unique columns in order of discovery.
function inferColumns(rows) {
var columnSet = Object.create(null),
columns = [];
rows.forEach(function (row) {
for (var column in row) {
if (!(column in columnSet)) {
columns.push(columnSet[column] = column);
}
}
});
return columns;
}
function pad(value, width) {
var s = value + "",
length = s.length;
return length < width ? new Array(width - length + 1).join(0) + s : s;
}
function formatYear(year) {
return year < 0 ? "-" + pad(-year, 6) : year > 9999 ? "+" + pad(year, 6) : pad(year, 4);
}
function formatDate(date) {
var hours = date.getUTCHours(),
minutes = date.getUTCMinutes(),
seconds = date.getUTCSeconds(),
milliseconds = date.getUTCMilliseconds();
return isNaN(date) ? "Invalid Date" : formatYear(date.getUTCFullYear()) + "-" + pad(date.getUTCMonth() + 1, 2) + "-" + pad(date.getUTCDate(), 2) + (milliseconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3) + "Z" : seconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "Z" : minutes || hours ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + "Z" : "");
}
function dsvFormat (delimiter) {
var reFormat = new RegExp("[\"" + delimiter + "\n\r]"),
DELIMITER = delimiter.charCodeAt(0);
function parse(text, f) {
var convert,
columns,
rows = parseRows(text, function (row, i) {
if (convert) return convert(row, i - 1);
columns = row, convert = f ? customConverter(row, f) : objectConverter(row);
});
rows.columns = columns || [];
return rows;
}
function parseRows(text, f) {
var rows = [],
// output rows
N = text.length,
I = 0,
// current character index
n = 0,
// current line number
t,
// current token
eof = N <= 0,
// current token followed by EOF?
eol = false; // current token followed by EOL?
// Strip the trailing newline.
if (text.charCodeAt(N - 1) === NEWLINE) --N;
if (text.charCodeAt(N - 1) === RETURN) --N;
function token() {
if (eof) return EOF;
if (eol) return eol = false, EOL;
// Unescape quotes.
var i,
j = I,
c;
if (text.charCodeAt(j) === QUOTE) {
while (I++ < N && text.charCodeAt(I) !== QUOTE || text.charCodeAt(++I) === QUOTE);
if ((i = I) >= N) eof = true;else if ((c = text.charCodeAt(I++)) === NEWLINE) eol = true;else if (c === RETURN) {
eol = true;
if (text.charCodeAt(I) === NEWLINE) ++I;
}
return text.slice(j + 1, i - 1).replace(/""/g, "\"");
}
// Find next delimiter or newline.
while (I < N) {
if ((c = text.charCodeAt(i = I++)) === NEWLINE) eol = true;else if (c === RETURN) {
eol = true;
if (text.charCodeAt(I) === NEWLINE) ++I;
} else if (c !== DELIMITER) continue;
return text.slice(j, i);
}
// Return last token before EOF.
return eof = true, text.slice(j, N);
}
while ((t = token()) !== EOF) {
var row = [];
while (t !== EOL && t !== EOF) row.push(t), t = token();
if (f && (row = f(row, n++)) == null) continue;
rows.push(row);
}
return rows;
}
function preformatBody(rows, columns) {
return rows.map(function (row) {
return columns.map(function (column) {
return formatValue(row[column]);
}).join(delimiter);
});
}
function format(rows, columns) {
if (columns == null) columns = inferColumns(rows);
return [columns.map(formatValue).join(delimiter)].concat(preformatBody(rows, columns)).join("\n");
}
function formatBody(rows, columns) {
if (columns == null) columns = inferColumns(rows);
return preformatBody(rows, columns).join("\n");
}
function formatRows(rows) {
return rows.map(formatRow).join("\n");
}
function formatRow(row) {
return row.map(formatValue).join(delimiter);
}
function formatValue(value) {
return value == null ? "" : value instanceof Date ? formatDate(value) : reFormat.test(value += "") ? "\"" + value.replace(/"/g, "\"\"") + "\"" : value;
}
return {
parse: parse,
parseRows: parseRows,
format: format,
formatBody: formatBody,
formatRows: formatRows,
formatRow: formatRow,
formatValue: formatValue
};
}
var csv = dsvFormat(",");
var csvParse = csv.parse;
var tsv = dsvFormat("\t");
var tsvParse = tsv.parse;
const DEFAULT_DSV_PARSER_OPTIONS = {
delimiter: ','
};
const dsvParser = (data, options = {}, dataView) => {
dataView.type = DATAVIEW_TYPE.DSV;
const mergeOptions = mergeDeepImmer(DEFAULT_DSV_PARSER_OPTIONS, options);
const { delimiter } = mergeOptions;
if (!isString$1(delimiter)) {
throw new TypeError('Invalid delimiter: must be a string!');
}
return dsvFormat(delimiter).parse(data);
};
const csvParser = (data, options = {}, dataView) => {
dataView.type = DATAVIEW_TYPE.DSV;
return csvParse(data);
};
const tsvParser = (data, options = {}, dataView) => {
dataView.type = DATAVIEW_TYPE.DSV;
return tsvParse(data);
};
var decode_1 = decode$1;
var keys, values, lengths, dim, e;
var geometryTypes = ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'];
function decode$1(pbf) {
dim = 2;
e = Math.pow(10, 6);
lengths = null;
keys = [];
values = [];
var obj = pbf.readFields(readDataField, {});
keys = null;
return obj;
}
function readDataField(tag, obj, pbf) {
if (tag === 1) keys.push(pbf.readString());else if (tag === 2) dim = pbf.readVarint();else if (tag === 3) e = Math.pow(10, pbf.readVarint());else if (tag === 4) readFeatureCollection(pbf, obj);else if (tag === 5) readFeature(pbf, obj);else if (tag === 6) readGeometry(pbf, obj);
}
function readFeatureCollection(pbf, obj) {
obj.type = 'FeatureCollection';
obj.features = [];
return pbf.readMessage(readFeatureCollectionField, obj);
}
function readFeature(pbf, feature) {
feature.type = 'Feature';
var f = pbf.readMessage(readFeatureField, feature);
if (!('geometry' in f)) f.geometry = null;
return f;
}
function readGeometry(pbf, geom) {
geom.type = 'Point';
return pbf.readMessage(readGeometryField, geom);
}
function readFeatureCollectionField(tag, obj, pbf) {
if (tag === 1) obj.features.push(readFeature(pbf, {}));else if (tag === 13) values.push(readValue(pbf));else if (tag === 15) readProps(pbf, obj);
}
function readFeatureField(tag, feature, pbf) {
if (tag === 1) feature.geometry = readGeometry(pbf, {});else if (tag === 11) feature.id = pbf.readString();else if (tag === 12) feature.id = pbf.readSVarint();else if (tag === 13) values.push(readValue(pbf));else if (tag === 14) feature.properties = readProps(pbf, {});else if (tag === 15) readProps(pbf, feature);
}
function readGeometryField(tag, geom, pbf) {
if (tag === 1) geom.type = geometryTypes[pbf.readVarint()];else if (tag === 2) lengths = pbf.readPackedVarint();else if (tag === 3) readCoords(geom, pbf, geom.type);else if (tag === 4) {
geom.geometries = geom.geometries || [];
geom.geometries.push(readGeometry(pbf, {}));
} else if (tag === 13) values.push(readValue(pbf));else if (tag === 15) readProps(pbf, geom);
}
function readCoords(geom, pbf, type) {
if (type === 'Point') geom.coordinates = readPoint(pbf);else if (type === 'MultiPoint') geom.coordinates = readLine(pbf);else if (type === 'LineString') geom.coordinates = readLine(pbf);else if (type === 'MultiLineString') geom.coordinates = readMultiLine(pbf);else if (type === 'Polygon') geom.coordinates = readMultiLine(pbf, true);else if (type === 'MultiPolygon') geom.coordinates = readMultiPolygon(pbf);
}
function readValue(pbf) {
var end = pbf.readVarint() + pbf.pos,
value = null;
while (pbf.pos < end) {
var val = pbf.readVarint(),
tag = val >> 3;
if (tag === 1) value = pbf.readString();else if (tag === 2) value = pbf.readDouble();else if (tag === 3) value = pbf.readVarint();else if (tag === 4) value = -pbf.readVarint();else if (tag === 5) value = pbf.readBoolean();else if (tag === 6) value = JSON.parse(pbf.readString());
}
return value;
}
function readProps(pbf, props) {
var end = pbf.readVarint() + pbf.pos;
while (pbf.pos < end) props[keys[pbf.readVarint()]] = values[pbf.readVarint()];
values = [];
return props;
}
function readPoint(pbf) {
var end = pbf.readVarint() + pbf.pos,
coords = [];
while (pbf.pos < end) coords.push(pbf.readSVarint() / e);
return coords;
}
function readLinePart(pbf, end, len, closed) {
var i = 0,
coords = [],
p,
d;
var prevP = [];
for (d = 0; d < dim; d++) prevP[d] = 0;
while (len ? i < len : pbf.pos < end) {
p = [];
for (d = 0; d < dim; d++) {
prevP[d] += pbf.readSVarint();
p[d] = prevP[d] / e;
}
coords.push(p);
i++;
}
if (closed) coords.push(coords[0]);
return coords;
}
function readLine(pbf) {
return readLinePart(pbf, pbf.readVarint() + pbf.pos);
}
function readMultiLine(pbf, closed) {
var end = pbf.readVarint() + pbf.pos;
if (!lengths) return [readLinePart(pbf, end, null, closed)];
var coords = [];
for (var i = 0; i < lengths.length; i++) coords.push(readLinePart(pbf, end, lengths[i], closed));
lengths = null;
return coords;
}
function readMultiPolygon(pbf) {
var end = pbf.readVarint() + pbf.pos;
if (!lengths) return [[readLinePart(pbf, end, null, true)]];
var coords = [];
var j = 1;
for (var i = 0; i < lengths[0]; i++) {
var rings = [];
for (var k = 0; k < lengths[j]; k++) rings.push(readLinePart(pbf, end, lengths[j + 1 + k], true));
j += lengths[j] + 1;
coords.push(rings);
}
lengths = null;
return coords;
}
var decode = decode_1;
var ieee754$1 = {};
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
ieee754$1.read = function (buffer, offset, isLE, mLen, nBytes) {
var e, m;
var eLen = nBytes * 8 - mLen - 1;
var eMax = (1 << eLen) - 1;
var eBias = eMax >> 1;
var nBits = -7;
var i = isLE ? nBytes - 1 : 0;
var d = isLE ? -1 : 1;
var s = buffer[offset + i];
i += d;
e = s & (1 << -nBits) - 1;
s >>= -nBits;
nBits += eLen;
for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
m = e & (1 << -nBits) - 1;
e >>= -nBits;
nBits += mLen;
for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
if (e === 0) {
e = 1 - eBias;
} else if (e === eMax) {
return m ? NaN : (s ? -1 : 1) * Infinity;
} else {
m = m + Math.pow(2, mLen);
e = e - eBias;
}
return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
};
ieee754$1.write = function (buffer, value, offset, isLE, mLen, nBytes) {
var e, m, c;
var eLen = nBytes * 8 - mLen - 1;
var eMax = (1 << eLen) - 1;
var eBias = eMax >> 1;
var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0;
var i = isLE ? 0 : nBytes - 1;
var d = isLE ? 1 : -1;
var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0;
value = Math.abs(value);
if (isNaN(value) || value === Infinity) {
m = isNaN(value) ? 1 : 0;
e = eMax;
} else {
e = Math.floor(Math.log(value) / Math.LN2);
if (value * (c = Math.pow(2, -e)) < 1) {
e--;
c *= 2;
}
if (e + eBias >= 1) {
value += rt / c;
} else {
value += rt * Math.pow(2, 1 - eBias);
}
if (value * c >= 2) {
e++;
c /= 2;
}
if (e + eBias >= eMax) {
m = 0;
e = eMax;
} else if (e + eBias >= 1) {
m = (value * c - 1) * Math.pow(2, mLen);
e = e + eBias;
} else {
m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
e = 0;
}
}
for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
e = e << mLen | m;
eLen += mLen;
for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
buffer[offset + i - d] |= s * 128;
};
var pbf = Pbf;
var ieee754 = ieee754$1;
function Pbf(buf) {
this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
this.pos = 0;
this.type = 0;
this.length = this.buf.length;
}
Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
// Threshold chosen based on both benchmarking and knowledge about browser string
// data structures (which currently switch structure types at 12 bytes or more)
var TEXT_DECODER_MIN_LENGTH = 12;
var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8');
Pbf.prototype = {
destroy: function () {
this.buf = null;
},
// === READING =================================================================
readFields: function (readField, result, end) {
end = end || this.length;
while (this.pos < end) {
var val = this.readVarint(),
tag = val >> 3,
startPos = this.pos;
this.type = val & 0x7;
readField(tag, result, this);
if (this.pos === startPos) this.skip(val);
}
return result;
},
readMessage: function (readField, result) {
return this.readFields(readField, result, this.readVarint() + this.pos);
},
readFixed32: function () {
var val = readUInt32(this.buf, this.pos);
this.pos += 4;
return val;
},
readSFixed32: function () {
var val = readInt32(this.buf, this.pos);
this.pos += 4;
return val;
},
// 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
readFixed64: function () {
var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
this.pos += 8;
return val;
},
readSFixed64: function () {
var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
this.pos += 8;
return val;
},
readFloat: function () {
var val = ieee754.read(this.buf, this.pos, true, 23, 4);
this.pos += 4;
return val;
},
readDouble: function () {
var val = ieee754.read(this.buf, this.pos, true, 52, 8);
this.pos += 8;
return val;
},
readVarint: function (isSigned) {
var buf = this.buf,
val,
b;
b = buf[this.pos++];
val = b & 0x7f;
if (b < 0x80) return val;
b = buf[this.pos++];
val |= (b & 0x7f) << 7;
if (b < 0x80) return val;
b = buf[this.pos++];
val |= (b & 0x7f) << 14;
if (b < 0x80) return val;
b = buf[this.pos++];
val |= (b & 0x7f) << 21;
if (b < 0x80) return val;
b = buf[this.pos];
val |= (b & 0x0f) << 28;
return readVarintRemainder(val, isSigned, this);
},
readVarint64: function () {
// for compatibility with v2.0.1
return this.readVarint(true);
},
readSVarint: function () {
var num = this.readVarint();
return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
},
readBoolean: function () {
return Boolean(this.readVarint());
},
readString: function () {
var end = this.readVarint() + this.pos;
var pos = this.pos;
this.pos = end;
if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
// longer strings are fast with the built-in browser TextDecoder API
return readUtf8TextDecoder(this.buf, pos, end);
}
// short strings are fast with our custom implementation
return readUtf8(this.buf, pos, end);
},
readBytes: function () {
var end = this.readVarint() + this.pos,
buffer = this.buf.subarray(this.pos, end);
this.pos = end;
return buffer;
},
// verbose for performance reasons; doesn't affect gzipped size
readPackedVarint: function (arr, isSigned) {
if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readVarint(isSigned));
return arr;
},
readPackedSVarint: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readSVarint());
return arr;
},
readPackedBoolean: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readBoolean());
return arr;
},
readPackedFloat: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readFloat());
return arr;
},
readPackedDouble: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readDouble());
return arr;
},
readPackedFixed32: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readFixed32());
return arr;
},
readPackedSFixed32: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readSFixed32());
return arr;
},
readPackedFixed64: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readFixed64());
return arr;
},
readPackedSFixed64: function (arr) {
if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
var end = readPackedEnd(this);
arr = arr || [];
while (this.pos < end) arr.push(this.readSFixed64());
return arr;
},
skip: function (val) {
var type = val & 0x7;
if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;else if (type === Pbf.Fixed32) this.pos += 4;else if (type === Pbf.Fixed64) this.pos += 8;else throw new Error('Unimplemented type: ' + type);
},
// === WRITING =================================================================
writeTag: function (tag, type) {
this.writeVarint(tag << 3 | type);
},
realloc: function (min) {
var length = this.length || 16;
while (length < this.pos + min) length *= 2;
if (length !== this.length) {
var buf = new Uint8Array(length);
buf.set(this.buf);
this.buf = buf;
this.length = length;
}
},
finish: function () {
this.length = this.pos;
this.pos = 0;
return this.buf.subarray(0, this.length);
},
writeFixed32: function (val) {
this.realloc(4);
writeInt32(this.buf, val, this.pos);
this.pos += 4;
},
writeSFixed32: function (val) {
this.realloc(4);
writeInt32(this.buf, val, this.pos);
this.pos += 4;
},
writeFixed64: function (val) {
this.realloc(8);
writeInt32(this.buf, val & -1, this.pos);
writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
this.pos += 8;
},
writeSFixed64: function (val) {
this.realloc(8);
writeInt32(this.buf, val & -1, this.pos);
writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
this.pos += 8;
},
writeVarint: function (val) {
val = +val || 0;
if (val > 0xfffffff || val < 0) {
writeBigVarint(val, this);
return;
}
this.realloc(4);
this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0);
if (val <= 0x7f) return;
this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
if (val <= 0x7f) return;
this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
if (val <= 0x7f) return;
this.buf[this.pos++] = val >>> 7 & 0x7f;
},
writeSVarint: function (val) {
this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
},
writeBoolean: function (val) {
this.writeVarint(Boolean(val));
},
writeString: function (str) {
str = String(str);
this.realloc(str.length * 4);
this.pos++; // reserve 1 byte for short string length
var startPos = this.pos;
// write the string directly to the buffer and see how much was written
this.pos = writeUtf8(this.buf, str, this.pos);
var len = this.pos - startPos;
if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
// finally, write the message length in the reserved place and restore the position
this.pos = startPos - 1;
this.writeVarint(len);
this.pos += len;
},
writeFloat: function (val) {
this.realloc(4);
ieee754.write(this.buf, val, this.pos, true, 23, 4);
this.pos += 4;
},
writeDouble: function (val) {
this.realloc(8);
ieee754.write(this.buf, val, this.pos, true, 52, 8);
this.pos += 8;
},
writeBytes: function (buffer) {
var len = buffer.length;
this.writeVarint(len);
this.realloc(len);
for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
},
writeRawMessage: function (fn, obj) {
this.pos++; // reserve 1 byte for short message length
// write the message directly to the buffer and see how much was written
var startPos = this.pos;
fn(obj, this);
var len = this.pos - startPos;
if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
// finally, write the message length in the reserved place and restore the position
this.pos = startPos - 1;
this.writeVarint(len);
this.pos += len;
},
writeMessage: function (tag, fn, obj) {
this.writeTag(tag, Pbf.Bytes);
this.writeRawMessage(fn, obj);
},
writePackedVarint: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedVarint, arr);
},
writePackedSVarint: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSVarint, arr);
},
writePackedBoolean: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedBoolean, arr);
},
writePackedFloat: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFloat, arr);
},
writePackedDouble: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedDouble, arr);
},
writePackedFixed32: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFixed32, arr);
},
writePackedSFixed32: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr);
},
writePackedFixed64: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFixed64, arr);
},
writePackedSFixed64: function (tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr);
},
writeBytesField: function (tag, buffer) {
this.writeTag(tag, Pbf.Bytes);
this.writeBytes(buffer);
},
writeFixed32Field: function (tag, val) {
this.writeTag(tag, Pbf.Fixed32);
this.writeFixed32(val);
},
writeSFixed32Field: function (tag, val) {
this.writeTag(tag, Pbf.Fixed32);
this.writeSFixed32(val);
},
writeFixed64Field: function (tag, val) {
this.writeTag(tag, Pbf.Fixed64);
this.writeFixed64(val);
},
writeSFixed64Field: function (tag, val) {
this.writeTag(tag, Pbf.Fixed64);
this.writeSFixed64(val);
},
writeVarintField: function (tag, val) {
this.writeTag(tag, Pbf.Varint);
this.writeVarint(val);
},
writeSVarintField: function (tag, val) {
this.writeTag(tag, Pbf.Varint);
this.writeSVarint(val);
},
writeStringField: function (tag, str) {
this.writeTag(tag, Pbf.Bytes);
this.writeString(str);
},
writeFloatField: function (tag, val) {
this.writeTag(tag, Pbf.Fixed32);
this.writeFloat(val);
},
writeDoubleField: function (tag, val) {
this.writeTag(tag, Pbf.Fixed64);
this.writeDouble(val);
},
writeBooleanField: function (tag, val) {
this.writeVarintField(tag, Boolean(val));
}
};
function readVarintRemainder(l, s, p) {
var buf = p.buf,
h,
b;
b = buf[p.pos++];
h = (b & 0x70) >> 4;
if (b < 0x80) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 0x7f) << 3;
if (b < 0x80) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 0x7f) << 10;
if (b < 0x80) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 0x7f) << 17;
if (b < 0x80) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 0x7f) << 24;
if (b < 0x80) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 0x01) << 31;
if (b < 0x80) return toNum(l, h, s);
throw new Error('Expected varint not more than 10 bytes');
}
function readPackedEnd(pbf) {
return pbf.type === Pbf.Bytes ? pbf.readVarint() + pbf.pos : pbf.pos + 1;
}
function toNum(low, high, isSigned) {
if (isSigned) {
return high * 0x100000000 + (low >>> 0);
}
return (high >>> 0) * 0x100000000 + (low >>> 0);
}
function writeBigVarint(val, pbf) {
var low, high;
if (val >= 0) {
low = val % 0x100000000 | 0;
high = val / 0x100000000 | 0;
} else {
low = ~(-val % 0x100000000);
high = ~(-val / 0x100000000);
if (low ^ 0xffffffff) {
low = low + 1 | 0;
} else {
low = 0;
high = high + 1 | 0;
}
}
if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
throw new Error('Given varint doesn\'t fit into 10 bytes');
}
pbf.realloc(10);
writeBigVarintLow(low, high, pbf);
writeBigVarintHigh(high, pbf);
}
function writeBigVarintLow(low, high, pbf) {
pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
low >>>= 7;
pbf.buf[pbf.pos] = low & 0x7f;
}
function writeBigVarintHigh(high, pbf) {
var lsb = (high & 0x07) << 4;
pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 0x7f;
}
function makeRoomForExtraLength(startPos, len, pbf) {
var extraLen = len <= 0x3fff ? 1 : len <= 0x1fffff ? 2 : len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7));
// if 1 byte isn't enough for encoding message length, shift the data to the right
pbf.realloc(extraLen);
for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
}
function writePackedVarint(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);
}
function writePackedSVarint(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);
}
function writePackedFloat(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);
}
function writePackedDouble(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);
}
function writePackedBoolean(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);
}
function writePackedFixed32(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);
}
function writePackedSFixed32(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]);
}
function writePackedFixed64(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);
}
function writePackedSFixed64(arr, pbf) {
for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]);
}
// Buffer code below from https://github.com/feross/buffer, MIT-licensed
function readUInt32(buf, pos) {
return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + buf[pos + 3] * 0x1000000;
}
function writeInt32(buf, val, pos) {
buf[pos] = val;
buf[pos + 1] = val >>> 8;
buf[pos + 2] = val >>> 16;
buf[pos + 3] = val >>> 24;
}
function readInt32(buf, pos) {
return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + (buf[pos + 3] << 24);
}
function readUtf8(buf, pos, end) {
var str = '';
var i = pos;
while (i < end) {
var b0 = buf[i];
var c = null; // codepoint
var bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1;
if (i + bytesPerSequence > end) break;
var b1, b2, b3;
if (bytesPerSequence === 1) {
if (b0 < 0x80) {
c = b0;
}
} else if (bytesPerSequence === 2) {
b1 = buf[i + 1];
if ((b1 & 0xC0) === 0x80) {
c = (b0 & 0x1F) << 0x6 | b1 & 0x3F;
if (c <= 0x7F) {
c = null;
}
}
} else if (bytesPerSequence === 3) {
b1 = buf[i + 1];
b2 = buf[i + 2];
if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | b2 & 0x3F;
if (c <= 0x7FF || c >= 0xD800 && c <= 0xDFFF) {
c = null;
}
}
} else if (bytesPerSequence === 4) {
b1 = buf[i + 1];
b2 = buf[i + 2];
b3 = buf[i + 3];
if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | b3 & 0x3F;
if (c <= 0xFFFF || c >= 0x110000) {
c = null;
}
}
}
if (c === null) {
c = 0xFFFD;
bytesPerSequence = 1;
} else if (c > 0xFFFF) {
c -= 0x10000;
str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
c = 0xDC00 | c & 0x3FF;
}
str += String.fromCharCode(c);
i += bytesPerSequence;
}
return str;
}
function readUtf8TextDecoder(buf, pos, end) {
return utf8TextDecoder.decode(buf.subarray(pos, end));
}
function writeUtf8(buf, str, pos) {
for (var i = 0, c, lead; i < str.length; i++) {
c = str.charCodeAt(i); // code point
if (c > 0xD7FF && c < 0xE000) {
if (lead) {
if (c < 0xDC00) {
buf[pos++] = 0xEF;
buf[pos++] = 0xBF;
buf[pos++] = 0xBD;
lead = c;
continue;
} else {
c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
lead = null;
}
} else {
if (c > 0xDBFF || i + 1 === str.length) {
buf[pos++] = 0xEF;
buf[pos++] = 0xBF;
buf[pos++] = 0xBD;
} else {
lead = c;
}
continue;
}
} else if (lead) {
buf[pos++] = 0xEF;
buf[pos++] = 0xBF;
buf[pos++] = 0xBD;
lead = null;
}
if (c < 0x80) {
buf[pos++] = c;
} else {
if (c < 0x800) {
buf[pos++] = c >> 0x6 | 0xC0;
} else {
if (c < 0x10000) {
buf[pos++] = c >> 0xC | 0xE0;
} else {
buf[pos++] = c >> 0x12 | 0xF0;
buf[pos++] = c >> 0xC & 0x3F | 0x80;
}
buf[pos++] = c >> 0x6 & 0x3F | 0x80;
}
buf[pos++] = c & 0x3F | 0x80;
}
}
return pos;
}
var Pbf$1 = /*@__PURE__*/getDefaultExportFromCjs(pbf);
/**
* Returns a cloned copy of the passed GeoJSON Object, including possible 'Foreign Members'.
* ~3-5x faster than the common JSON.parse + JSON.stringify combo method.
*
* @name clone
* @param {GeoJSON} geojson GeoJSON Object
* @returns {GeoJSON} cloned GeoJSON Object
* @example
* var line = turf.lineString([[-74, 40], [-78, 42], [-82, 35]], {color: 'red'});
*
* var lineCloned = turf.clone(line);
*/
function clone(geojson) {
if (!geojson) {
throw new Error("geojson is required");
}
switch (geojson.type) {
case "Feature":
return cloneFeature(geojson);
case "FeatureCollection":
return cloneFeatureCollection(geojson);
case "Point":
case "LineString":
case "Polygon":
case "MultiPoint":
case "MultiLineString":
case "MultiPolygon":
case "GeometryCollection":
return cloneGeometry(geojson);
default:
throw new Error("unknown GeoJSON type");
}
}
/**
* Clone Feature
*
* @private
* @param {Feature<any>} geojson GeoJSON Feature
* @returns {Feature<any>} cloned Feature
*/
function cloneFeature(geojson) {
var cloned = {
type: "Feature"
};
// Preserve Foreign Members
Object.keys(geojson).forEach(function (key) {
switch (key) {
case "type":
case "properties":
case "geometry":
return;
default:
cloned[key] = geojson[key];
}
});
// Add properties & geometry last
cloned.properties = cloneProperties(geojson.properties);
cloned.geometry = cloneGeometry(geojson.geometry);
return cloned;
}
/**
* Clone Properties
*
* @private
* @param {Object} properties GeoJSON Properties
* @returns {Object} cloned Properties
*/
function cloneProperties(properties) {
var cloned = {};
if (!properties) {
return cloned;
}
Object.keys(properties).forEach(function (key) {
var value = properties[key];
if (typeof value === "object") {
if (value === null) {
// handle null
cloned[key] = null;
} else if (Array.isArray(value)) {
// handle Array
cloned[key] = value.map(function (item) {
return item;
});
} else {
// handle generic Object
cloned[key] = cloneProperties(value);
}
} else {
cloned[key] = value;
}
});
return cloned;
}
/**
* Clone Feature Collection
*
* @private
* @param {FeatureCollection<any>} geojson GeoJSON Feature Collection
* @returns {FeatureCollection<any>} cloned Feature Collection
*/
function cloneFeatureCollection(geojson) {
var cloned = {
type: "FeatureCollection"
};
// Preserve Foreign Members
Object.keys(geojson).forEach(function (key) {
switch (key) {
case "type":
case "features":
return;
default:
cloned[key] = geojson[key];
}
});
// Add features
cloned.features = geojson.features.map(function (feature) {
return cloneFeature(feature);
});
return cloned;
}
/**
* Clone Geometry
*
* @private
* @param {Geometry<any>} geometry GeoJSON Geometry
* @returns {Geometry<any>} cloned Geometry
*/
function cloneGeometry(geometry) {
var geom = {
type: geometry.type
};
if (geometry.bbox) {
geom.bbox = geometry.bbox;
}
if (geometry.type === "GeometryCollection") {
geom.geometries = geometry.geometries.map(function (g) {
return cloneGeometry(g);
});
return geom;
}
geom.coordinates = deepSlice(geometry.coordinates);
return geom;
}
/**
* Deep Slice coordinates
*
* @private
* @param {Coordinates} coords Coordinates
* @returns {Coordinates} all coordinates sliced
*/
function deepSlice(coords) {
var cloned = coords;
if (typeof cloned[0] !== "object") {
return cloned.slice();
}
return cloned.map(function (coord) {
return deepSlice(coord);
});
}
/**
* Unwrap coordinates from a Feature, Geometry Object or an Array
*
* @name getCoords
* @param {Array<any>|Geometry|Feature} coords Feature, Geometry Object or an Array
* @returns {Array<any>} coordinates
* @example
* var poly = turf.polygon([[[119.32, -8.7], [119.55, -8.69], [119.51, -8.54], [119.32, -8.7]]]);
*
* var coords = turf.getCoords(poly);
* //= [[[119.32, -8.7], [119.55, -8.69], [119.51, -8.54], [119.32, -8.7]]]
*/
function getCoords(coords) {
if (Array.isArray(coords)) {
return coords;
}
// Feature
if (coords.type === "Feature") {
if (coords.geometry !== null) {
return coords.geometry.coordinates;
}
} else {
// Geometry
if (coords.coordinates) {
return coords.coordinates;
}
}
throw new Error("coords must be GeoJSON Feature, Geometry Object or an Array");
}
/**
* Takes a ring and return true or false whether or not the ring is clockwise or counter-clockwise.
*
* @name booleanClockwise
* @param {Feature<LineString>|LineString|Array<Array<number>>} line to be evaluated
* @returns {boolean} true/false
* @example
* var clockwiseRing = turf.lineString([[0,0],[1,1],[1,0],[0,0]]);
* var counterClockwiseRing = turf.lineString([[0,0],[1,0],[1,1],[0,0]]);
*
* turf.booleanClockwise(clockwiseRing)
* //=true
* turf.booleanClockwise(counterClockwiseRing)
* //=false
*/
function booleanClockwise(line) {
var ring = getCoords(line);
var sum = 0;
var i = 1;
var prev;
var cur;
while (i < ring.length) {
prev = cur || ring[0];
cur = ring[i];
sum += (cur[0] - prev[0]) * (cur[1] + prev[1]);
i++;
}
return sum > 0;
}
/**
* Callback for featureEach
*
* @callback featureEachCallback
* @param {Feature<any>} currentFeature The current Feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
*/
/**
* Iterate over features in any GeoJSON object, similar to
* Array.forEach.
*
* @name featureEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, featureIndex)
* @returns {void}
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.featureEach(features, function (currentFeature, featureIndex) {
* //=currentFeature
* //=featureIndex
* });
*/
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++) {
if (callback(geojson.features[i], i) === false) break;
}
}
}
/**
* Callback for geomEach
*
* @callback geomEachCallback
* @param {Geometry} currentGeometry The current Geometry being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {Object} featureProperties The current Feature Properties being processed.
* @param {Array<number>} featureBBox The current Feature BBox being processed.
* @param {number|string} featureId The current Feature Id being processed.
*/
/**
* Iterate over each geometry in any GeoJSON object, similar to Array.forEach()
*
* @name geomEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentGeometry, featureIndex, featureProperties, featureBBox, featureId)
* @returns {void}
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.point([36, 53], {hello: 'world'})
* ]);
*
* turf.geomEach(features, function (currentGeometry, featureIndex, featureProperties, featureBBox, featureId) {
* //=currentGeometry
* //=featureIndex
* //=featureProperties
* //=featureBBox
* //=featureId
* });
*/
function geomEach(geojson, callback) {
var i,
j,
g,
geometry,
stopG,
geometryMaybeCollection,
isGeometryCollection,
featureProperties,
featureBBox,
featureId,
featureIndex = 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;
featureProperties = isFeatureCollection ? geojson.features[i].properties : isFeature ? geojson.properties : {};
featureBBox = isFeatureCollection ? geojson.features[i].bbox : isFeature ? geojson.bbox : undefined;
featureId = isFeatureCollection ? geojson.features[i].id : isFeature ? geojson.id : undefined;
isGeometryCollection = geometryMaybeCollection ? geometryMaybeCollection.type === "GeometryCollection" : false;
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (g = 0; g < stopG; g++) {
geometry = isGeometryCollection ? geometryMaybeCollection.geometries[g] : geometryMaybeCollection;
// Handle null Geometry
if (geometry === null) {
if (callback(null, featureIndex, featureProperties, featureBBox, featureId) === false) return false;
continue;
}
switch (geometry.type) {
case "Point":
case "LineString":
case "MultiPoint":
case "Polygon":
case "MultiLineString":
case "MultiPolygon":
{
if (callback(geometry, featureIndex, featureProperties, featureBBox, featureId) === false) return false;
break;
}
case "GeometryCollection":
{
for (j = 0; j < geometry.geometries.length; j++) {
if (callback(geometry.geometries[j], featureIndex, featureProperties, featureBBox, featureId) === false) return false;
}
break;
}
default:
throw new Error("Unknown Geometry Type");
}
}
// Only increase `featureIndex` per each feature
featureIndex++;
}
}
/**
* Callback for flattenEach
*
* @callback flattenEachCallback
* @param {Feature} currentFeature The current flattened feature being processed.
* @param {number} featureIndex The current index of the Feature being processed.
* @param {number} multiFeatureIndex The current index of the Multi-Feature being processed.
*/
/**
* Iterate over flattened features in any GeoJSON object, similar to
* Array.forEach.
*
* @name flattenEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentFeature, featureIndex, multiFeatureIndex)
* @example
* var features = turf.featureCollection([
* turf.point([26, 37], {foo: 'bar'}),
* turf.multiPoint([[40, 30], [36, 53]], {hello: 'world'})
* ]);
*
* turf.flattenEach(features, function (currentFeature, featureIndex, multiFeatureIndex) {
* //=currentFeature
* //=featureIndex
* //=multiFeatureIndex
* });
*/
function flattenEach(geojson, callback) {
geomEach(geojson, function (geometry, featureIndex, properties, bbox, id) {
// Callback for single geometry
var type = geometry === null ? null : geometry.type;
switch (type) {
case null:
case "Point":
case "LineString":
case "Polygon":
if (callback(feature$2(geometry, properties, {
bbox: bbox,
id: id
}), featureIndex, 0) === false) return false;
return;
}
var geomType;
// Callback for multi-geometry
switch (type) {
case "MultiPoint":
geomType = "Point";
break;
case "MultiLineString":
geomType = "LineString";
break;
case "MultiPolygon":
geomType = "Polygon";
break;
}
for (var multiFeatureIndex = 0; multiFeatureIndex < geometry.coordinates.length; multiFeatureIndex++) {
var coordinate = geometry.coordinates[multiFeatureIndex];
var geom = {
type: geomType,
coordinates: coordinate
};
if (callback(feature$2(geom, properties), featureIndex, multiFeatureIndex) === false) return false;
}
});
}
/**
* Rewind {@link LineString|(Multi)LineString} or {@link Polygon|(Multi)Polygon} outer ring counterclockwise and inner rings clockwise (Uses {@link http://en.wikipedia.org/wiki/Shoelace_formula|Shoelace Formula}).
*
* @name rewind
* @param {GeoJSON} geojson input GeoJSON Polygon
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.reverse=false] enable reverse winding
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
* @returns {GeoJSON} rewind Polygon
* @example
* var polygon = turf.polygon([[[121, -29], [138, -29], [138, -18], [121, -18], [121, -29]]]);
*
* var rewind = turf.rewind(polygon);
*
* //addToMap
* var addToMap = [rewind];
*/
function rewind(geojson, options) {
// Optional parameters
options = options || {};
if (!isObject(options)) throw new Error("options is invalid");
var reverse = options.reverse || false;
var mutate = options.mutate || false;
// validation
if (!geojson) throw new Error("<geojson> is required");
if (typeof reverse !== "boolean") throw new Error("<reverse> must be a boolean");
if (typeof mutate !== "boolean") throw new Error("<mutate> must be a boolean");
// prevent input mutation
if (mutate === false) geojson = clone(geojson);
// Support Feature Collection or Geometry Collection
var results = [];
switch (geojson.type) {
case "GeometryCollection":
geomEach(geojson, function (geometry) {
rewindFeature(geometry, reverse);
});
return geojson;
case "FeatureCollection":
featureEach(geojson, function (feature) {
featureEach(rewindFeature(feature, reverse), function (result) {
results.push(result);
});
});
return featureCollection(results);
}
// Support Feature or Geometry Objects
return rewindFeature(geojson, reverse);
}
/**
* Rewind
*
* @private
* @param {Geometry|Feature<any>} geojson Geometry or Feature
* @param {Boolean} [reverse=false] enable reverse winding
* @returns {Geometry|Feature<any>} rewind Geometry or Feature
*/
function rewindFeature(geojson, reverse) {
var type = geojson.type === "Feature" ? geojson.geometry.type : geojson.type;
// Support all GeoJSON Geometry Objects
switch (type) {
case "GeometryCollection":
geomEach(geojson, function (geometry) {
rewindFeature(geometry, reverse);
});
return geojson;
case "LineString":
rewindLineString(getCoords(geojson), reverse);
return geojson;
case "Polygon":
rewindPolygon(getCoords(geojson), reverse);
return geojson;
case "MultiLineString":
getCoords(geojson).forEach(function (lineCoords) {
rewindLineString(lineCoords, reverse);
});
return geojson;
case "MultiPolygon":
getCoords(geojson).forEach(function (lineCoords) {
rewindPolygon(lineCoords, reverse);
});
return geojson;
case "Point":
case "MultiPoint":
return geojson;
}
}
/**
* Rewind LineString - outer ring clockwise
*
* @private
* @param {Array<Array<number>>} coords GeoJSON LineString geometry coordinates
* @param {Boolean} [reverse=false] enable reverse winding
* @returns {void} mutates coordinates
*/
function rewindLineString(coords, reverse) {
if (booleanClockwise(coords) === reverse) coords.reverse();
}
/**
* Rewind Polygon - outer ring counterclockwise and inner rings clockwise.
*
* @private
* @param {Array<Array<Array<number>>>} coords GeoJSON Polygon geometry coordinates
* @param {Boolean} [reverse=false] enable reverse winding
* @returns {void} mutates coordinates
*/
function rewindPolygon(coords, reverse) {
// outer ring
if (booleanClockwise(coords[0]) !== reverse) {
coords[0].reverse();
}
// inner rings
for (var i = 1; i < coords.length; i++) {
if (booleanClockwise(coords[i]) === reverse) {
coords[i].reverse();
}
}
}
/**
* Flattens any {@link GeoJSON} to a {@link FeatureCollection} inspired by [geojson-flatten](https://github.com/tmcw/geojson-flatten).
*
* @name flatten
* @param {GeoJSON} geojson any valid GeoJSON Object
* @returns {FeatureCollection<any>} all Multi-Geometries are flattened into single Features
* @example
* var multiGeometry = turf.multiPolygon([
* [[[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);
}
const geoPathInstance = geoPath();
const DEFAULT_GEOJSON_OPTIONS = {
centroid: false,
name: false,
bbox: false,
rewind: false
};
const MultiToSingle = (feature) => {
if (feature.geometry.type.startsWith('Multi')) {
const f = flatten(feature).features[0];
return Object.assign(Object.assign({}, f), f.properties);
}
return Object.assign(Object.assign({}, feature), feature.properties);
};
const flattenFeature = (data) => {
const featuresArr = [];
data.forEach((item) => {
if (item.type === 'FeatureCollection') {
item.features.forEach((feature) => {
featuresArr.push(MultiToSingle(feature));
});
}
else {
featuresArr.push(MultiToSingle(item));
}
});
return featuresArr;
};
const geoJSONParser = (data, options = {}, dataView) => {
dataView.type = DATAVIEW_TYPE.GEO;
const mergeOptions = mergeDeepImmer(DEFAULT_GEOJSON_OPTIONS, options);
const { centroid, name, bbox, rewind: rewind$1 } = mergeOptions;
if (Array.isArray(data)) {
return flattenFeature(data);
}
let features = data.features;
if (rewind$1) {
features = rewind(data, { reverse: isObject$2(rewind$1) ? rewind$1.reverse : true }).features;
}
features.forEach(feature => {
if (centroid) {
const centroid = geoPathInstance.centroid(feature);
feature.centroidX = centroid[0];
feature.centroidY = centroid[1];
}
if (name) {
feature.name = feature.properties.name;
}
if (bbox) {
const bbox = geoPathInstance.bounds(feature);
feature.bbox = bbox;
}
});
data.features = features;
return data;
};
const DEFAULT_GEOBUF_OPTIONS = {};
const geoBufParser = (data, options = {}, dataView) => {
dataView.type = DATAVIEW_TYPE.GEO;
const mergeOptions = mergeDeepImmer(DEFAULT_GEOJSON_OPTIONS, DEFAULT_GEOBUF_OPTIONS, options);
const geoData = decode(new Pbf$1(data));
return geoJSONParser(geoData, mergeOptions, dataView);
};
const DEFAULT_TOPOJSON_OPTIONS = {};
const topoJSONParser = (data, options, dataView) => {
dataView.type = DATAVIEW_TYPE.GEO;
const mergeOptions = mergeDeepImmer(DEFAULT_GEOJSON_OPTIONS, DEFAULT_TOPOJSON_OPTIONS, options);
const { object } = mergeOptions;
if (!isString$1(object)) {
throw new TypeError('Invalid object: must be a string!');
}
const geoData = feature(data, data.objects[object]);
return geoJSONParser(geoData, mergeOptions, dataView);
};
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const byteJSONParser = (data, options, dataView) => {
dataView.type = DATAVIEW_TYPE.BYTE;
const result = [];
const { layerType } = options;
data.forEach((item) => {
let lType = layerType;
let coord = [];
if (item.from) {
lType = 'FlyLine';
coord = [item.from, item.to];
}
if (item.lng) {
lType = 'Point';
coord = [item.lng, item.lat];
}
if (item.coordinates) {
const suffix = lType === 'Line' ? 'LineString' : 'Polygon';
lType = Array.isArray(item.coordinates[0][0]) ? `Multi${suffix}` : suffix;
coord = item.coordinates;
}
const others = __rest(item, ["coordinates"]);
const dataItem = Object.assign(Object.assign({}, others), { geometry: {
type: lType,
coordinates: coord
} });
result.push(dataItem);
});
return result;
};
function count(node) {
var sum = 0,
children = node.children,
i = children && children.length;
if (!i) sum = 1;else while (--i >= 0) sum += children[i].value;
node.value = sum;
}
function node_count () {
return this.eachAfter(count);
}
function node_each (callback, that) {
let index = -1;
for (const node of this) {
callback.call(that, node, ++index, this);
}
return this;
}
function node_eachBefore (callback, that) {
var node = this,
nodes = [node],
children,
i,
index = -1;
while (node = nodes.pop()) {
callback.call(that, node, ++index, this);
if (children = node.children) {
for (i = children.length - 1; i >= 0; --i) {
nodes.push(children[i]);
}
}
}
return this;
}
function node_eachAfter (callback, that) {
var node = this,
nodes = [node],
next = [],
children,
i,
n,
index = -1;
while (node = nodes.pop()) {
next.push(node);
if (children = node.children) {
for (i = 0, n = children.length; i < n; ++i) {
nodes.push(children[i]);
}
}
}
while (node = next.pop()) {
callback.call(that, node, ++index, this);
}
return this;
}
function node_find (callback, that) {
let index = -1;
for (const node of this) {
if (callback.call(that, node, ++index, this)) {
return node;
}
}
}
function node_sum (value) {
return this.eachAfter(function (node) {
var sum = +value(node.data) || 0,
children = node.children,
i = children && children.length;
while (--i >= 0) sum += children[i].value;
node.value = sum;
});
}
function node_sort (compare) {
return this.eachBefore(function (node) {
if (node.children) {
node.children.sort(compare);
}
});
}
function node_path (end) {
var start = this,
ancestor = leastCommonAncestor(start, end),
nodes = [start];
while (start !== ancestor) {
start = start.parent;
nodes.push(start);
}
var k = nodes.length;
while (end !== ancestor) {
nodes.splice(k, 0, end);
end = end.parent;
}
return nodes;
}
function leastCommonAncestor(a, b) {
if (a === b) return a;
var aNodes = a.ancestors(),
bNodes = b.ancestors(),
c = null;
a = aNodes.pop();
b = bNodes.pop();
while (a === b) {
c = a;
a = aNodes.pop();
b = bNodes.pop();
}
return c;
}
function node_ancestors () {
var node = this,
nodes = [node];
while (node = node.parent) {
nodes.push(node);
}
return nodes;
}
function node_descendants () {
return Array.from(this);
}
function node_leaves () {
var leaves = [];
this.eachBefore(function (node) {
if (!node.children) {
leaves.push(node);
}
});
return leaves;
}
function node_links () {
var root = this,
links = [];
root.each(function (node) {
if (node !== root) {
// Don’t include the root’s parent, if any.
links.push({
source: node.parent,
target: node
});
}
});
return links;
}
function* node_iterator () {
var node = this,
current,
next = [node],
children,
i,
n;
do {
current = next.reverse(), next = [];
while (node = current.pop()) {
yield node;
if (children = node.children) {
for (i = 0, n = children.length; i < n; ++i) {
next.push(children[i]);
}
}
}
} while (next.length);
}
function hierarchy(data, children) {
if (data instanceof Map) {
data = [undefined, data];
if (children === undefined) children = mapChildren;
} else if (children === undefined) {
children = objectChildren;
}
var root = new Node(data),
node,
nodes = [root],
child,
childs,
i,
n;
while (node = nodes.pop()) {
if ((childs = children(node.data)) && (n = (childs = Array.from(childs)).length)) {
node.children = childs;
for (i = n - 1; i >= 0; --i) {
nodes.push(child = childs[i] = new Node(childs[i]));
child.parent = node;
child.depth = node.depth + 1;
}
}
}
return root.eachBefore(computeHeight);
}
function node_copy() {
return hierarchy(this).eachBefore(copyData);
}
function objectChildren(d) {
return d.children;
}
function mapChildren(d) {
return Array.isArray(d) ? d[1] : null;
}
function copyData(node) {
if (node.data.value !== undefined) node.value = node.data.value;
node.data = node.data.data;
}
function computeHeight(node) {
var height = 0;
do node.height = height; while ((node = node.parent) && node.height < ++height);
}
function Node(data) {
this.data = data;
this.depth = this.height = 0;
this.parent = null;
}
Node.prototype = hierarchy.prototype = {
constructor: Node,
count: node_count,
each: node_each,
eachAfter: node_eachAfter,
eachBefore: node_eachBefore,
find: node_find,
sum: node_sum,
sort: node_sort,
path: node_path,
ancestors: node_ancestors,
descendants: node_descendants,
leaves: node_leaves,
links: node_links,
copy: node_copy,
[Symbol.iterator]: node_iterator
};
const DEFAULT_TREE_PARSER_OPTIONS = {
children: d => d.children,
pureData: false
};
const treeParser = (data, options, dataView) => {
dataView.type = DATAVIEW_TYPE.TREE;
const mergeOptions = mergeDeepImmer(DEFAULT_TREE_PARSER_OPTIONS, options);
const { children } = mergeOptions;
if (children && !isFunction$1(children)) {
throw new TypeError('Invalid children: must be a function!');
}
return hierarchy(data, children);
};
const dataViewParser = (data, options, dataView) => {
const dependencyUpdate = isBoolean$1(options === null || options === void 0 ? void 0 : options.dependencyUpdate) ? options === null || options === void 0 ? void 0 : options.dependencyUpdate : true;
if (!data || !isArray$1(data)) {
throw new TypeError('Invalid data: must be DataView array!');
}
if (isArray$1(dataView.rawData)) {
dataView.rawData.forEach(rd => {
if (rd.target) {
rd.target.removeListener('change', dataView.reRunAllTransform);
rd.target.removeListener('markRunning', dataView.markRunning);
}
});
}
if (dependencyUpdate) {
data.forEach(d => {
d.target.addListener('change', dataView.reRunAllTransform);
d.target.addListener('markRunning', dataView.markRunning);
});
}
return data;
};
const tagNameToType = {
svg: 'group',
rect: 'rect',
line: 'rule',
polygon: 'polygon',
path: 'path',
polyline: 'line',
g: 'group',
circle: 'arc',
ellipse: 'arc'
};
const validTagName = Object.keys(tagNameToType);
const validGroupNode = ['g', 'svg', 'text', 'tspan', 'switch'];
const validTextAttributes = ['font-size', 'font-family', 'font-weight', 'font-style', 'text-align', 'text-anchor'];
const validCircleAttributes = ['cx', 'cy', 'r'];
const validEllipseAttributes = ['cx', 'cy', 'rx', 'ry'];
const validLineAttributes = ['x1', 'x2', 'y1', 'y2'];
const validAttributes = [
'visibility',
'x',
'y',
'width',
'height',
'd',
'points',
'stroke',
'stroke-width',
'fill',
'fill-opacity',
'stroke-opacity',
...validTextAttributes,
...validCircleAttributes,
...validEllipseAttributes,
...validLineAttributes
];
const validInheritAttributes = [
'visible',
'fill',
'stroke',
'stroke-width',
'fill-opacity',
'stroke-opacity',
...validTextAttributes
];
const numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g;
function splitNumberSequence(rawStr) {
return rawStr.match(numberReg) || [];
}
const svgParser = (data, option = {}, dataView) => {
let parser = option.customDOMParser;
if (!parser) {
if (window === null || window === void 0 ? void 0 : window.DOMParser) {
parser = (svg) => new DOMParser().parseFromString(svg, 'text/xml');
}
}
if (!parser) {
throw new Error('No Available DOMParser!');
}
const svg = parser(data);
let node = svg.nodeType === 9 ? svg.firstChild : svg;
while (node && (node.nodeName.toLowerCase() !== 'svg' || node.nodeType !== 1)) {
node = node.nextSibling;
}
if (node) {
const result = parseSvgNode(node);
return result;
}
return null;
};
let idx = 0;
function parseSvgNode(svg, opt = {}) {
const elements = [];
const root = parseNode(svg, null);
let width = parseFloat(svg.getAttribute('width') || opt.width);
let height = parseFloat(svg.getAttribute('height') || opt.height);
!isValidNumber$1(width) && (width = null);
!isValidNumber$1(height) && (height = null);
const viewBox = svg.getAttribute('viewBox');
let viewBoxRect;
if (viewBox) {
const viewBoxArr = splitNumberSequence(viewBox);
if (viewBoxArr.length >= 4) {
viewBoxRect = {
x: parseFloat((viewBoxArr[0] || 0)),
y: parseFloat((viewBoxArr[1] || 0)),
width: parseFloat(viewBoxArr[2]),
height: parseFloat(viewBoxArr[3])
};
if (width || height) {
const boundingRect = { x: 0, y: 0, width, height };
const scaleX = boundingRect.width / viewBoxRect.width;
const scaleY = boundingRect.height / viewBoxRect.height;
const scale = Math.min(scaleX, scaleY);
const transLateX = -(viewBoxRect.x + viewBoxRect.width / 2) * scale + (boundingRect.x + boundingRect.width / 2);
const transLateY = -(viewBoxRect.y + viewBoxRect.height / 2) * scale + (boundingRect.y + boundingRect.height / 2);
const viewBoxTransform = new Matrix().translate(transLateX, transLateY).scale(scale, scale);
root.transform = viewBoxTransform;
}
}
}
traverse(svg, root, elements);
return {
root,
width,
height,
elements,
viewBoxRect
};
}
function parseInheritAttributes(parsedElement) {
let inheritedAttrs;
const { parent, attributes } = parsedElement;
const parse = (parent) => {
if (!parent) {
return {};
}
return validInheritAttributes.reduce((acc, attrName) => {
const camelAttrName = toCamelCase(attrName);
if (isValid$1(parent[camelAttrName])) {
acc[camelAttrName] = parent[camelAttrName];
}
return acc;
}, {});
};
if (parent) {
if (!parent._inheritStyle) {
parent._inheritStyle = parse(parent.attributes);
}
inheritedAttrs = merge$2({}, parent._inheritStyle, parse(attributes));
}
else {
inheritedAttrs = parse(attributes);
}
return inheritedAttrs;
}
function parseAttributes(el) {
var _a, _b, _c;
const attrs = {};
const attributes = (_a = el.attributes) !== null && _a !== void 0 ? _a : {};
const style = (_b = el.style) !== null && _b !== void 0 ? _b : {};
for (let i = 0; i < validAttributes.length; i++) {
const attrName = validAttributes[i];
const attrValue = isValid$1(style[attrName]) && style[attrName] !== '' ? style[attrName] : (_c = attributes[attrName]) === null || _c === void 0 ? void 0 : _c.value;
if (isValid$1(attrValue)) {
attrs[toCamelCase(attrName)] = isNaN(+attrValue) ? attrValue : parseFloat(attrValue);
}
}
if (style.display === 'none') {
attrs.visible = false;
}
['fontSize', 'strokeWidth', 'width', 'height'].forEach(attr => {
const attrValue = attrs[attr];
if (isString$1(attrs[attr])) {
attrs[attr] = parseFloat(attrValue);
}
});
return attrs;
}
function parseNode(node, parent) {
var _a, _b, _c, _d, _e;
const tagName = (_a = node.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (node.nodeType === 3 || tagName === 'text' || tagName === 'tspan') {
return parseText(node, parent);
}
if (!validTagName.includes(tagName)) {
return null;
}
const parsed = {
tagName,
graphicType: tagNameToType[tagName],
attributes: parseAttributes(node),
parent,
name: (_b = node.getAttribute('name')) !== null && _b !== void 0 ? _b : (_c = parent === null || parent === void 0 ? void 0 : parent.attributes) === null || _c === void 0 ? void 0 : _c.name,
id: (_d = node.getAttribute('id')) !== null && _d !== void 0 ? _d : `${tagName}-${idx++}`,
transform: parseTransform(node)
};
parsed._inheritStyle = parseInheritAttributes(parsed);
if (parent && !isValid$1(parsed.name)) {
parsed._nameFromParent = (_e = parent.name) !== null && _e !== void 0 ? _e : parent._nameFromParent;
}
return parsed;
}
function parseText(node, parent) {
var _a, _b, _c, _d, _e, _f;
if (!parent) {
return null;
}
const tagName = (_a = node.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (!tagName && parent.graphicType !== 'group') {
return null;
}
const nodeAsGroup = tagName === 'text' || tagName === 'tspan';
const elType = nodeAsGroup ? 'group' : 'text';
const value = nodeAsGroup ? undefined : (_b = node.textContent) === null || _b === void 0 ? void 0 : _b.replace(/\n/g, ' ').replace(/\s+/g, ' ');
if (value === ' ') {
return null;
}
let parsed;
if (nodeAsGroup) {
parsed = {
tagName,
graphicType: elType,
attributes: parseAttributes(node),
parent,
name: node.getAttribute('name'),
id: (_c = node.getAttribute('id')) !== null && _c !== void 0 ? _c : `${tagName}-${idx++}`,
transform: parseTransform(node),
value
};
}
else {
parsed = {
tagName,
graphicType: 'text',
attributes: parseAttributes(node),
parent,
name: parent === null || parent === void 0 ? void 0 : parent.name,
id: (_e = (_d = node.getAttribute) === null || _d === void 0 ? void 0 : _d.call(node, 'id')) !== null && _e !== void 0 ? _e : `${tagName}-${idx++}`,
value
};
}
parsed._inheritStyle = parseInheritAttributes(parsed);
if (!isValid$1(parsed.name)) {
parsed._nameFromParent = (_f = parent.name) !== null && _f !== void 0 ? _f : parent._nameFromParent;
}
if (!nodeAsGroup) {
parsed.attributes = parsed._inheritStyle;
}
else {
if (parent._textGroupStyle) {
parsed._textGroupStyle = merge$2({}, parent._textGroupStyle, parseAttributes(node));
}
else {
parsed._textGroupStyle = parseAttributes(node);
}
}
return parsed;
}
function parseTransform(node) {
var _a, _b;
const transforms = (_a = node.transform) === null || _a === void 0 ? void 0 : _a.baseVal;
if (!transforms) {
return null;
}
const matrix = (_b = transforms.consolidate()) === null || _b === void 0 ? void 0 : _b.matrix;
if (!matrix) {
return null;
}
const { a, b, c, d, e, f } = matrix;
return new Matrix(a, b, c, d, e, f);
}
function traverse(node, parsedParent, result = []) {
var _a;
if (!node) {
return;
}
let parseResult;
if (node.nodeName !== 'svg') {
parseResult = parseNode(node, parsedParent);
}
if (parseResult) {
result.push(parseResult);
}
let child = validGroupNode.includes((_a = node.tagName) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase())
? node.firstChild
: null;
while (child) {
traverse(child, parseResult !== null && parseResult !== void 0 ? parseResult : parsedParent, result);
child = child.nextSibling;
}
}
let idIndex = 0;
const maxId = 100000000;
function getUUID(prefix = 'dataset') {
if (idIndex > maxId) {
idIndex = 0;
}
return prefix + '_' + idIndex++;
}
class DataSet {
constructor(options) {
var _a;
this.options = options;
this.isDataSet = true;
this.transformMap = {};
this.parserMap = {};
this.dataViewMap = {};
this.target = new EventEmitter();
let name;
if (options === null || options === void 0 ? void 0 : options.name) {
name = options.name;
}
else {
name = getUUID('dataset');
}
this.name = name;
this._logger = (_a = options === null || options === void 0 ? void 0 : options.logger) !== null && _a !== void 0 ? _a : Logger.getInstance();
}
setLogger(logger) {
this._logger = logger;
}
getDataView(name) {
return this.dataViewMap[name];
}
setDataView(name, dataView) {
var _a;
if (this.dataViewMap[name]) {
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.error(`Error: dataView ${name} 之前已存在,请重新命名`);
}
this.dataViewMap[name] = dataView;
}
removeDataView(name) {
this.dataViewMap[name] = null;
delete this.dataViewMap[name];
}
registerParser(name, parser) {
var _a;
if (this.parserMap[name]) {
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warn(`Warn: transform ${name} 之前已注册,执行覆盖逻辑`);
}
this.parserMap[name] = parser;
}
removeParser(name) {
this.parserMap[name] = null;
delete this.parserMap[name];
}
getParser(name) {
return this.parserMap[name] || this.parserMap.default;
}
registerTransform(name, transform) {
var _a;
if (this.transformMap[name]) {
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.warn(`Warn: transform ${name} 之前已注册,执行覆盖逻辑`);
}
this.transformMap[name] = transform;
}
removeTransform(name) {
this.transformMap[name] = null;
delete this.transformMap[name];
}
getTransform(name) {
return this.transformMap[name];
}
multipleDataViewAddListener(list, event, call) {
if (!this._callMap) {
this._callMap = new Map();
}
let callAd = this._callMap.get(call);
if (!callAd) {
callAd = () => {
if (list.some(l => l.isRunning)) {
return;
}
call();
};
}
list.forEach(l => {
l.target.addListener(event, callAd);
});
this._callMap.set(call, callAd);
}
allDataViewAddListener(event, call) {
this.multipleDataViewAddListener(Object.values(this.dataViewMap), event, call);
}
multipleDataViewRemoveListener(list, event, call) {
if (this._callMap) {
const callAd = this._callMap.get(call);
if (callAd) {
list.forEach(l => {
l.target.removeListener(event, callAd);
});
}
this._callMap.delete(call);
}
}
multipleDataViewUpdateInParse(newData) {
newData.forEach(d => { var _a; return (_a = this.getDataView(d.name)) === null || _a === void 0 ? void 0 : _a.markRunning(); });
newData.forEach(d => { var _a; return (_a = this.getDataView(d.name)) === null || _a === void 0 ? void 0 : _a.parseNewData(d.data, d.options); });
}
multipleDataViewUpdateInRawData(newData) {
newData.forEach(d => { var _a; return (_a = this.getDataView(d.name)) === null || _a === void 0 ? void 0 : _a.markRunning(); });
newData.forEach(d => { var _a; return (_a = this.getDataView(d.name)) === null || _a === void 0 ? void 0 : _a.updateRawData(d.data, d.options); });
}
destroy() {
this.transformMap = null;
this.parserMap = null;
this.dataViewMap = null;
this._callMap = null;
this.target.removeAllListeners();
}
}
const DataViewDiffRank = '_data-view-diff-rank';
class DataView {
constructor(dataSet, options) {
this.dataSet = dataSet;
this.options = options;
this.isDataView = true;
this.target = new EventEmitter();
this.parseOption = null;
this.transformsArr = [];
this.isRunning = false;
this.rawData = {};
this.history = false;
this.parserData = {};
this.latestData = {};
this._fields = null;
this.reRunAllTransform = (opt = {
pushHistory: true,
emitMessage: true
}) => {
this.isRunning = true;
this.resetTransformData();
this.transformsArr.forEach(t => {
this.executeTransform(t, { pushHistory: opt.pushHistory, emitMessage: false });
if (this.isLastTransform(t)) {
this.diffLastData();
}
});
this.isRunning = false;
opt.emitMessage !== false && this.target.emit('change', []);
return this;
};
this.markRunning = () => {
this.isRunning = true;
this.target.emit('markRunning', []);
};
let name;
if (options === null || options === void 0 ? void 0 : options.name) {
name = options.name;
}
else {
name = getUUID('dataview');
}
this.name = name;
if (options === null || options === void 0 ? void 0 : options.history) {
this.history = options.history;
this.historyData = [];
}
this.dataSet.setDataView(name, this);
this.setFields(options === null || options === void 0 ? void 0 : options.fields);
}
parse(data, options, emit = false) {
var _a;
this.isRunning = true;
if (emit) {
this.target.emit('beforeParse', []);
}
options && (this.parseOption = options);
const cloneData = this.cloneParseData(data, options);
if (options === null || options === void 0 ? void 0 : options.type) {
const parserFn = (_a = this.dataSet.getParser(options.type)) !== null && _a !== void 0 ? _a : this.dataSet.getParser('bytejson');
const parserData = parserFn(cloneData, options.options, this);
this.rawData = cloneData;
this.parserData = parserData;
if (this.history) {
this.historyData.push(cloneData, parserData);
}
this.latestData = parserData;
}
else {
this.parserData = cloneData;
this.rawData = cloneData;
if (this.history) {
this.historyData.push(cloneData);
}
this.latestData = cloneData;
}
this.isRunning = false;
if (emit) {
this.target.emit('afterParse', []);
}
return this;
}
transform(options, execute = true) {
this.isRunning = true;
if (options && options.type) {
let pushOption = true;
if (options.type === 'fields') {
this._fields = options.options.fields;
const index = this.transformsArr.findIndex(_op => _op.type === options.type);
if (index >= 0) {
pushOption = false;
this.transformsArr[index].options.fields = this._fields;
}
}
pushOption && this.transformsArr.push(options);
if (execute) {
const lastTag = this.isLastTransform(options);
this.executeTransform(options);
if (lastTag) {
this.diffLastData();
}
}
}
this.sortTransform();
this.isRunning = false;
return this;
}
isLastTransform(options) {
return this.transformsArr[this.transformsArr.length - 1] === options;
}
sortTransform() {
if (this.transformsArr.length >= 2) {
this.transformsArr.sort((a, b) => { var _a, _b; return ((_a = a.level) !== null && _a !== void 0 ? _a : 0) - ((_b = b.level) !== null && _b !== void 0 ? _b : 0); });
}
}
executeTransform(options, opt = {
pushHistory: true,
emitMessage: true
}) {
const { pushHistory, emitMessage } = opt;
const transformFn = this.dataSet.getTransform(options.type);
const transformData = transformFn(this.latestData, options.options);
if (this.history && pushHistory !== false) {
this.historyData.push(transformData);
}
this.latestData = transformData;
emitMessage !== false && this.target.emit('change', []);
}
resetTransformData() {
this.latestData = this.parserData;
if (this.history) {
this.historyData.length = 0;
this.historyData.push(this.rawData, this.parserData);
}
}
enableDiff(keys) {
this._diffData = true;
this._diffKeys = keys;
this._diffMap = new Map();
this._diffRank = 0;
}
disableDiff() {
this._diffData = false;
this._diffMap = null;
this._diffRank = null;
}
resetDiff() {
this._diffMap = new Map();
this._diffRank = 0;
}
diffLastData() {
var _a;
if (!this._diffData) {
return;
}
if (!this.latestData.forEach) {
return;
}
if (!((_a = this._diffKeys) === null || _a === void 0 ? void 0 : _a.length)) {
return;
}
const next = this._diffRank + 1;
if (this._diffRank === 0) {
this.latestData.forEach((d) => {
d[DataViewDiffRank] = next;
this._diffMap.set(this._diffKeys.reduce((pre, k) => pre + d[k], ''), d);
});
this.latestDataAUD = {
add: Array.from(this.latestData),
del: [],
update: []
};
}
else {
this.latestDataAUD = {
add: [],
del: [],
update: []
};
let tempKey;
this.latestData.forEach((d) => {
d[DataViewDiffRank] = next;
tempKey = this._diffKeys.reduce((pre, k) => pre + d[k], '');
if (this._diffMap.get(tempKey)) {
this.latestDataAUD.update.push(d);
}
else {
this.latestDataAUD.add.push(d);
}
this._diffMap.set(tempKey, d);
});
this._diffMap.forEach((v, k) => {
if (v[DataViewDiffRank] < next) {
this.latestDataAUD.del.push(v);
this._diffMap.delete(k);
}
});
}
this._diffRank = next;
}
cloneParseData(data, options) {
let clone = false;
if (!(data instanceof DataView) && (options === null || options === void 0 ? void 0 : options.clone) === true) {
clone = true;
}
return clone ? cloneDeep(data) : data;
}
parseNewData(data, options) {
this.parse(data, options || this.parseOption);
this.reRunAllTransform();
}
updateRawData(data, options) {
const cloneData = this.cloneParseData(data, options);
this.rawData = cloneData;
this.parserData = cloneData;
this.latestData = cloneData;
this.reRunAllTransform();
}
getFields() {
var _a;
if (this._fields) {
return this._fields;
}
if (((_a = this.parseOption) === null || _a === void 0 ? void 0 : _a.type) === 'dataview' && this.rawData.length === 1 && this.rawData[0].getFields) {
return this.rawData[0].getFields();
}
return null;
}
setFields(f, foreMerge = false) {
if (f && foreMerge) {
this._fields = merge$2({}, this._fields, f);
}
else {
this._fields = f;
}
const fieldsOption = this.transformsArr.find(_op => _op.type === 'fields');
if (!isNil$1(this._fields) && isNil$1(fieldsOption)) {
this.dataSet.registerTransform('fields', fields);
this.transform({
type: 'fields',
options: {
fields: this._fields
}
}, false);
}
else if (fieldsOption) {
fieldsOption.options.fields = this._fields;
}
}
destroy() {
this.dataSet.removeDataView(this.name);
this._diffMap = null;
this._diffRank = null;
this.latestData = null;
this.rawData = null;
this.parserData = null;
this.transformsArr = null;
this.target = null;
}
}
function isDataView(obj) {
return obj instanceof DataView;
}
function readCSVTopNLine(csvFile, n) {
let res = '';
const finish = ['\r\n', '\r', '\n'].some(splitter => {
if (csvFile.includes(splitter)) {
res = csvFile
.split(splitter)
.slice(0, n + 1)
.join(splitter);
return true;
}
return false;
});
if (finish) {
return res;
}
return csvFile;
}
exports.DataSet = DataSet;
exports.DataView = DataView;
exports.byteJSONParser = byteJSONParser;
exports.csvParser = csvParser;
exports.dataViewParser = dataViewParser;
exports.dissolve = dissolve;
exports.dsvParser = dsvParser;
exports.fields = fields;
exports.filter = filter;
exports.fold = fold;
exports.geoBufParser = geoBufParser;
exports.geoJSONParser = geoJSONParser;
exports.isDataView = isDataView;
exports.map = map;
exports.mercator = mercator;
exports.pointToHexbin = pointToHexbin;
exports.projection = projection;
exports.readCSVTopNLine = readCSVTopNLine;
exports.simplify = simplify;
exports.statistics = statistics;
exports.svgParser = svgParser;
exports.topoJSONParser = topoJSONParser;
exports.treeParser = treeParser;
exports.tsvParser = tsvParser;
}));