three-globe
Version:
Globe data visualization as a ThreeJS reusable 3D object
1,388 lines (1,315 loc) • 184 kB
JavaScript
import { BackSide, BufferAttribute, Color, Mesh, ShaderMaterial, LineBasicMaterial, LineSegments, MeshPhongMaterial, SphereGeometry, SRGBColorSpace, TextureLoader, BufferGeometry, CylinderGeometry, Matrix4, MeshBasicMaterial, MeshLambertMaterial, Object3D, Vector3, CubicBezierCurve3, Curve, Float32BufferAttribute, Group, Line, NormalBlending, QuadraticBezierCurve3, TubeGeometry, DoubleSide, Euler, CircleGeometry, Vector2 } from 'three';
import Kapsule from 'kapsule';
import * as TWEEN from '@tweenjs/tween.js';
import { GeoJsonGeometry } from 'three-geojson-geometry';
import { geoGraticule10, geoDistance, geoInterpolate } from 'd3-geo';
import * as _bfg from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import accessorFn from 'accessor-fn';
import tinyColor from 'tinycolor2';
import dataJoint from 'data-joint';
import _FrameTicker from 'frame-ticker';
import { scaleLinear } from 'd3-scale';
import { ConicPolygonGeometry } from 'three-conic-polygon-geometry';
import indexBy from 'index-array-by';
import { latLngToCell, cellToLatLng, cellToBoundary, polygonToCells } from 'h3-js';
import { Line2, LineGeometry, LineMaterial } from 'three-fatline';
import { interpolateArray } from 'd3-interpolate';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
import { Font } from 'three/examples/jsm/loaders/FontLoader.js';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
function _iterableToArrayLimit(arr, i) {
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (_x = (_i = _i.call(arr)).next, 0 === i) {
if (Object(_i) !== _i) return;
_n = !1;
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
} catch (err) {
_d = !0, _e = err;
} finally {
try {
if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly && (symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})), keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
return target;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
Object.defineProperty(subClass, "prototype", {
writable: false
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _construct(Parent, args, Class) {
if (_isNativeReflectConstruct()) {
_construct = Reflect.construct.bind();
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
return _construct.apply(null, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError("Derived constructors may only return object or undefined");
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
var materialDispose = function materialDispose(material) {
if (material instanceof Array) {
material.forEach(materialDispose);
} else {
if (material.map) {
material.map.dispose();
}
material.dispose();
}
};
var deallocate = function deallocate(obj) {
if (obj.geometry) {
obj.geometry.dispose();
}
if (obj.material) {
materialDispose(obj.material);
}
if (obj.texture) {
obj.texture.dispose();
}
if (obj.children) {
obj.children.forEach(deallocate);
}
};
var emptyObject = function emptyObject(obj) {
if (obj && obj.children) while (obj.children.length) {
var childObj = obj.children[0];
obj.remove(childObj);
deallocate(childObj);
}
};
function linkKapsule (kapsulePropName, kapsuleType) {
var dummyK = new kapsuleType(); // To extract defaults
return {
linkProp: function linkProp(prop) {
// link property config
return {
"default": dummyK[prop](),
onChange: function onChange(v, state) {
state[kapsulePropName][prop](v);
},
triggerUpdate: false
};
},
linkMethod: function linkMethod(method) {
// link method pass-through
return function (state) {
var kapsuleInstance = state[kapsulePropName];
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var returnVal = kapsuleInstance[method].apply(kapsuleInstance, args);
return returnVal === kapsuleInstance ? this // chain based on the parent object, not the inner kapsule
: returnVal;
};
}
};
}
var GLOBE_RADIUS = 100;
function getGlobeRadius() {
return GLOBE_RADIUS;
}
function polar2Cartesian(lat, lng) {
var relAltitude = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var phi = (90 - lat) * Math.PI / 180;
var theta = (90 - lng) * Math.PI / 180;
var r = GLOBE_RADIUS * (1 + relAltitude);
return {
x: r * Math.sin(phi) * Math.cos(theta),
y: r * Math.cos(phi),
z: r * Math.sin(phi) * Math.sin(theta)
};
}
function cartesian2Polar(_ref) {
var x = _ref.x,
y = _ref.y,
z = _ref.z;
var r = Math.sqrt(x * x + y * y + z * z);
var phi = Math.acos(y / r);
var theta = Math.atan2(z, x);
return {
lat: 90 - phi * 180 / Math.PI,
lng: 90 - theta * 180 / Math.PI - (theta < -Math.PI / 2 ? 360 : 0),
// keep within [-180, 180] boundaries
altitude: r / GLOBE_RADIUS - 1
};
}
function deg2Rad$1(deg) {
return deg * Math.PI / 180;
}
var THREE$f = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
BackSide: BackSide,
BufferAttribute: BufferAttribute,
Color: Color,
Mesh: Mesh,
ShaderMaterial: ShaderMaterial
};
var fragmentShader = "\nuniform vec3 color;\nuniform float coefficient;\nuniform float power;\nvarying vec3 vVertexNormal;\nvarying vec3 vVertexWorldPosition;\nvoid main() {\n vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;\n vec3 viewCameraToVertex\t= (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;\n viewCameraToVertex = normalize(viewCameraToVertex);\n float intensity\t= pow(\n coefficient + dot(vVertexNormal, viewCameraToVertex),\n power\n );\n gl_FragColor = vec4(color, intensity);\n}";
var vertexShader = "\nvarying vec3 vVertexWorldPosition;\nvarying vec3 vVertexNormal;\nvoid main() {\n vVertexNormal\t= normalize(normalMatrix * normal);\n vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;\n gl_Position\t= projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n";
var defaultOptions = {
backside: true,
coefficient: 0.5,
color: 'gold',
size: 2,
power: 1
};
// Based off: http://stemkoski.blogspot.fr/2013/07/shaders-in-threejs-glow-and-halo.html
function createGlowMaterial(coefficient, color, power) {
return new THREE$f.ShaderMaterial({
depthWrite: false,
fragmentShader: fragmentShader,
transparent: true,
uniforms: {
coefficient: {
value: coefficient
},
color: {
value: new THREE$f.Color(color)
},
power: {
value: power
}
},
vertexShader: vertexShader
});
}
function createGlowGeometry(geometry, size) {
// expect BufferGeometry
var glowGeometry = geometry.clone();
// Resize vertex positions according to normals
var position = new Float32Array(geometry.attributes.position.count * 3);
for (var idx = 0, len = position.length; idx < len; idx++) {
var normal = geometry.attributes.normal.array[idx];
var curPos = geometry.attributes.position.array[idx];
position[idx] = curPos + normal * size;
}
glowGeometry.setAttribute('position', new THREE$f.BufferAttribute(position, 3));
return glowGeometry;
}
function createGlowMesh(geometry) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultOptions;
var backside = options.backside,
coefficient = options.coefficient,
color = options.color,
size = options.size,
power = options.power;
var glowGeometry = createGlowGeometry(geometry, size);
var glowMaterial = createGlowMaterial(coefficient, color, power);
if (backside) {
glowMaterial.side = THREE$f.BackSide;
}
return new THREE$f.Mesh(glowGeometry, glowMaterial);
}
var THREE$e = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
Color: Color,
LineBasicMaterial: LineBasicMaterial,
LineSegments: LineSegments,
Mesh: Mesh,
MeshPhongMaterial: MeshPhongMaterial,
SphereGeometry: SphereGeometry,
SRGBColorSpace: SRGBColorSpace,
TextureLoader: TextureLoader
};
//
var GlobeLayerKapsule = Kapsule({
props: {
globeImageUrl: {},
bumpImageUrl: {},
showGlobe: {
"default": true,
onChange: function onChange(showGlobe, state) {
state.globeObj.visible = !!showGlobe;
},
triggerUpdate: false
},
showGraticules: {
"default": false,
onChange: function onChange(showGraticules, state) {
state.graticulesObj.visible = !!showGraticules;
},
triggerUpdate: false
},
showAtmosphere: {
"default": true,
onChange: function onChange(showAtmosphere, state) {
state.atmosphereObj && (state.atmosphereObj.visible = !!showAtmosphere);
},
triggerUpdate: false
},
atmosphereColor: {
"default": 'lightskyblue'
},
atmosphereAltitude: {
"default": 0.15
},
onReady: {
"default": function _default() {},
triggerUpdate: false
}
},
methods: {
globeMaterial: function globeMaterial(state, _globeMaterial) {
if (_globeMaterial !== undefined) {
state.globeObj.material = _globeMaterial || state.defaultGlobeMaterial;
return this;
}
return state.globeObj.material;
},
_destructor: function _destructor(state) {
emptyObject(state.globeObj);
emptyObject(state.graticulesObj);
}
},
stateInit: function stateInit() {
// create globe
var globeGeometry = new THREE$e.SphereGeometry(GLOBE_RADIUS, 75, 75);
var defaultGlobeMaterial = new THREE$e.MeshPhongMaterial({
color: 0x000000,
transparent: true
});
var globeObj = new THREE$e.Mesh(globeGeometry, defaultGlobeMaterial);
globeObj.rotation.y = -Math.PI / 2; // face prime meridian along Z axis
globeObj.__globeObjType = 'globe'; // Add object type
// create graticules
var graticulesObj = new THREE$e.LineSegments(new GeoJsonGeometry(geoGraticule10(), GLOBE_RADIUS, 2), new THREE$e.LineBasicMaterial({
color: 'lightgrey',
transparent: true,
opacity: 0.1
}));
return {
globeObj: globeObj,
graticulesObj: graticulesObj,
defaultGlobeMaterial: defaultGlobeMaterial
};
},
init: function init(threeObj, state) {
// Clear the scene
emptyObject(threeObj);
// Main three object to manipulate
state.scene = threeObj;
state.scene.add(state.globeObj); // add globe
state.scene.add(state.graticulesObj); // add graticules
state.ready = false;
},
update: function update(state, changedProps) {
var globeMaterial = state.globeObj.material;
if (changedProps.hasOwnProperty('globeImageUrl')) {
if (!state.globeImageUrl) {
// Black globe if no image
!globeMaterial.color && (globeMaterial.color = new THREE$e.Color(0x000000));
} else {
new THREE$e.TextureLoader().load(state.globeImageUrl, function (texture) {
texture.colorSpace = THREE$e.SRGBColorSpace;
globeMaterial.map = texture;
globeMaterial.color = null;
globeMaterial.needsUpdate = true;
// ready when first globe image finishes loading (asynchronously to allow 1 frame to load texture)
!state.ready && (state.ready = true) && setTimeout(state.onReady);
});
}
}
if (changedProps.hasOwnProperty('bumpImageUrl')) {
if (!state.bumpImageUrl) {
globeMaterial.bumpMap = null;
globeMaterial.needsUpdate = true;
} else {
state.bumpImageUrl && new THREE$e.TextureLoader().load(state.bumpImageUrl, function (texture) {
globeMaterial.bumpMap = texture;
globeMaterial.needsUpdate = true;
});
}
}
if (changedProps.hasOwnProperty('atmosphereColor') || changedProps.hasOwnProperty('atmosphereAltitude')) {
if (state.atmosphereObj) {
// recycle previous atmosphere object
state.scene.remove(state.atmosphereObj);
emptyObject(state.atmosphereObj);
}
if (state.atmosphereColor && state.atmosphereAltitude) {
var obj = state.atmosphereObj = createGlowMesh(state.globeObj.geometry, {
backside: true,
color: state.atmosphereColor,
size: GLOBE_RADIUS * state.atmosphereAltitude,
power: 3.5,
// dispersion
coefficient: 0.1
});
obj.visible = !!state.showAtmosphere;
obj.__globeObjType = 'atmosphere'; // Add object type
state.scene.add(obj);
}
}
if (!state.ready && !state.globeImageUrl) {
// ready immediately if there's no globe image
state.ready = true;
state.onReady();
}
}
});
var colorStr2Hex = function colorStr2Hex(str) {
return isNaN(str) ? parseInt(tinyColor(str).toHex(), 16) : str;
};
var colorAlpha = function colorAlpha(str) {
return isNaN(str) ? tinyColor(str).getAlpha() : 1;
};
var color2ShaderArr = function color2ShaderArr(str) {
var includeAlpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var rgba = tinyColor(str).toRgb();
var rgbArr = ['r', 'g', 'b'].map(function (d) {
return rgba[d] / 255;
});
return includeAlpha ? [].concat(_toConsumableArray(rgbArr), [rgba.a]) : rgbArr;
};
function setMaterialOpacity(material, opacity, depthWrite) {
material.opacity = opacity;
material.transparent = opacity < 1;
material.depthWrite = depthWrite === undefined ? opacity >= 1 : depthWrite; // depthWrite=false recommended for transparent materials, to prevent transparency issues https://discourse.threejs.org/t/threejs-and-the-transparent-problem/11553/31
return material;
}
function threeDigest(data, scene) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
_ref$removeDelay = _ref.removeDelay,
removeDelay = _ref$removeDelay === void 0 ? 0 : _ref$removeDelay;
return dataJoint(data, scene.children, function (obj) {
return scene.add(obj);
}, function (obj) {
var removeFn = function removeFn() {
scene.remove(obj);
emptyObject(obj);
obj && obj.hasOwnProperty('__data') && delete obj.__data.__currentTargetD;
};
removeDelay ? setTimeout(removeFn, removeDelay) : removeFn();
}, _objectSpread2({
objBindAttr: '__threeObj'
}, options));
}
var THREE$d = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
BufferAttribute: BufferAttribute,
BufferGeometry: BufferGeometry,
Color: Color,
CylinderGeometry: CylinderGeometry,
Matrix4: Matrix4,
Mesh: Mesh,
MeshBasicMaterial: MeshBasicMaterial,
MeshLambertMaterial: MeshLambertMaterial,
Object3D: Object3D,
Vector3: Vector3
};
var bfg$2 = Object.assign({}, _bfg);
var BufferGeometryUtils$2 = bfg$2.BufferGeometryUtils || bfg$2;
//
// support multiple method names for backwards threejs compatibility
var applyMatrix4Fn$1 = new THREE$d.BufferGeometry().applyMatrix4 ? 'applyMatrix4' : 'applyMatrix';
var PointsLayerKapsule = Kapsule({
props: {
pointsData: {
"default": []
},
pointLat: {
"default": 'lat'
},
pointLng: {
"default": 'lng'
},
pointColor: {
"default": function _default() {
return '#ffffaa';
}
},
pointAltitude: {
"default": 0.1
},
// in units of globe radius
pointRadius: {
"default": 0.25
},
// in deg
pointResolution: {
"default": 12,
triggerUpdate: false
},
// how many slice segments in the cylinder's circumference
pointsMerge: {
"default": false
},
// boolean. Whether to merge all points into a single mesh for rendering performance
pointsTransitionDuration: {
"default": 1000,
triggerUpdate: false
} // ms
},
init: function init(threeObj, state) {
// Clear the scene
emptyObject(threeObj);
// Main three object to manipulate
state.scene = threeObj;
},
update: function update(state) {
// Data accessors
var latAccessor = accessorFn(state.pointLat);
var lngAccessor = accessorFn(state.pointLng);
var altitudeAccessor = accessorFn(state.pointAltitude);
var radiusAccessor = accessorFn(state.pointRadius);
var colorAccessor = accessorFn(state.pointColor);
// shared geometry
var pointGeometry = new THREE$d.CylinderGeometry(1, 1, 1, state.pointResolution);
pointGeometry[applyMatrix4Fn$1](new THREE$d.Matrix4().makeRotationX(Math.PI / 2));
pointGeometry[applyMatrix4Fn$1](new THREE$d.Matrix4().makeTranslation(0, 0, -0.5));
var pxPerDeg = 2 * Math.PI * GLOBE_RADIUS / 360;
var pointMaterials = {}; // indexed by color
var scene = state.pointsMerge ? new THREE$d.Object3D() : state.scene; // use fake scene if merging points
threeDigest(state.pointsData, scene, {
createObj: createObj,
updateObj: updateObj
});
if (state.pointsMerge) {
// merge points into a single mesh
var pointsGeometry = !state.pointsData.length ? new THREE$d.BufferGeometry() : (BufferGeometryUtils$2.mergeGeometries || BufferGeometryUtils$2.mergeBufferGeometries)(state.pointsData.map(function (d) {
var obj = d.__threeObj;
d.__threeObj = undefined; // unbind merged points
var geom = obj.geometry.clone();
// apply mesh world transform to vertices
obj.updateMatrix();
geom[applyMatrix4Fn$1](obj.matrix);
// color vertices
var color = new THREE$d.Color(colorAccessor(d));
var nVertices = geom.attributes.position.count;
var colors = new Float32Array(nVertices * 3);
for (var i = 0, len = nVertices; i < len; i++) {
var idx = i * 3;
colors[idx] = color.r;
colors[idx + 1] = color.g;
colors[idx + 2] = color.b;
}
geom.setAttribute('color', new THREE$d.BufferAttribute(colors, 3));
return geom;
}));
var points = new THREE$d.Mesh(pointsGeometry, new THREE$d.MeshBasicMaterial({
color: 0xffffff,
vertexColors: true
}));
points.__globeObjType = 'points'; // Add object type
points.__data = state.pointsData; // Attach obj data
emptyObject(state.scene);
state.scene.add(points);
}
//
function createObj() {
var obj = new THREE$d.Mesh(pointGeometry);
obj.__globeObjType = 'point'; // Add object type
return obj;
}
function updateObj(obj, d) {
var applyUpdate = function applyUpdate(td) {
var _obj$__currentTargetD = obj.__currentTargetD = td,
r = _obj$__currentTargetD.r,
alt = _obj$__currentTargetD.alt,
lat = _obj$__currentTargetD.lat,
lng = _obj$__currentTargetD.lng;
// position cylinder ground
Object.assign(obj.position, polar2Cartesian(lat, lng));
// orientate outwards
var globeCenter = state.pointsMerge ? new THREE$d.Vector3(0, 0, 0) : state.scene.localToWorld(new THREE$d.Vector3(0, 0, 0)); // translate from local to world coords
obj.lookAt(globeCenter);
// scale radius and altitude
obj.scale.x = obj.scale.y = Math.min(30, r) * pxPerDeg;
obj.scale.z = Math.max(alt * GLOBE_RADIUS, 0.1); // avoid non-invertible matrix
};
var targetD = {
alt: +altitudeAccessor(d),
r: +radiusAccessor(d),
lat: +latAccessor(d),
lng: +lngAccessor(d)
};
var currentTargetD = obj.__currentTargetD || Object.assign({}, targetD, {
alt: -1e-3
});
if (Object.keys(targetD).some(function (k) {
return currentTargetD[k] !== targetD[k];
})) {
if (state.pointsMerge || !state.pointsTransitionDuration || state.pointsTransitionDuration < 0) {
// set final position
applyUpdate(targetD);
} else {
// animate
new TWEEN.Tween(currentTargetD).to(targetD, state.pointsTransitionDuration).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(applyUpdate).start();
}
}
if (!state.pointsMerge) {
// Update materials on individual points
var color = colorAccessor(d);
var opacity = color ? colorAlpha(color) : 0;
var showCyl = !!opacity;
obj.visible = showCyl;
if (showCyl) {
if (!pointMaterials.hasOwnProperty(color)) {
pointMaterials[color] = new THREE$d.MeshLambertMaterial({
color: colorStr2Hex(color),
transparent: opacity < 1,
opacity: opacity
});
}
obj.material = pointMaterials[color];
}
}
}
}
});
var _excluded = ["stroke"];
var THREE$c = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
BufferGeometry: BufferGeometry,
CubicBezierCurve3: CubicBezierCurve3,
Curve: Curve,
Float32BufferAttribute: Float32BufferAttribute,
Group: Group,
Line: Line,
Mesh: Mesh,
NormalBlending: NormalBlending,
QuadraticBezierCurve3: QuadraticBezierCurve3,
ShaderMaterial: ShaderMaterial,
TubeGeometry: TubeGeometry,
Vector3: Vector3
};
var FrameTicker$2 = _FrameTicker["default"] || _FrameTicker;
//
// support both modes for backwards threejs compatibility
var setAttributeFn$1 = new THREE$c.BufferGeometry().setAttribute ? 'setAttribute' : 'addAttribute';
var gradientShaders$1 = {
uniforms: {
// dash param defaults, all relative to full length
dashOffset: {
value: 0
},
dashSize: {
value: 1
},
gapSize: {
value: 0
},
dashTranslate: {
value: 0
} // used for animating the dash
},
vertexShader: "\n uniform float dashTranslate; \n\n attribute vec4 vertexColor;\n varying vec4 vColor;\n \n attribute float vertexRelDistance;\n varying float vRelDistance;\n\n void main() {\n // pass through colors and distances\n vColor = vertexColor;\n vRelDistance = vertexRelDistance + dashTranslate;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",
fragmentShader: "\n uniform float dashOffset; \n uniform float dashSize;\n uniform float gapSize; \n \n varying vec4 vColor;\n varying float vRelDistance;\n \n void main() {\n // ignore pixels in the gap\n if (vRelDistance < dashOffset) discard;\n if (mod(vRelDistance - dashOffset, dashSize + gapSize) > dashSize) discard;\n \n // set px color: [r, g, b, a], interpolated between vertices \n gl_FragColor = vColor; \n }\n "
};
var ArcsLayerKapsule = Kapsule({
props: {
arcsData: {
"default": []
},
arcStartLat: {
"default": 'startLat'
},
arcStartLng: {
"default": 'startLng'
},
arcEndLat: {
"default": 'endLat'
},
arcEndLng: {
"default": 'endLng'
},
arcColor: {
"default": function _default() {
return '#ffffaa';
}
},
// single color, array of colors or color interpolation fn
arcAltitude: {},
// in units of globe radius
arcAltitudeAutoScale: {
"default": 0.5
},
// scale altitude proportional to great-arc distance between the two points
arcStroke: {},
// in deg
arcCurveResolution: {
"default": 64,
triggerUpdate: false
},
// how many straight segments in the curve
arcCircularResolution: {
"default": 6,
triggerUpdate: false
},
// how many slice segments in the tube's circumference
arcDashLength: {
"default": 1
},
// in units of line length
arcDashGap: {
"default": 0
},
arcDashInitialGap: {
"default": 0
},
arcDashAnimateTime: {
"default": 0
},
// ms
arcsTransitionDuration: {
"default": 1000,
triggerUpdate: false
} // ms
},
methods: {
pauseAnimation: function pauseAnimation(state) {
var _state$ticker;
(_state$ticker = state.ticker) === null || _state$ticker === void 0 ? void 0 : _state$ticker.pause();
},
resumeAnimation: function resumeAnimation(state) {
var _state$ticker2;
(_state$ticker2 = state.ticker) === null || _state$ticker2 === void 0 ? void 0 : _state$ticker2.resume();
},
_destructor: function _destructor(state) {
var _state$ticker3;
(_state$ticker3 = state.ticker) === null || _state$ticker3 === void 0 ? void 0 : _state$ticker3.dispose();
}
},
init: function init(threeObj, state) {
// Clear the scene
emptyObject(threeObj);
// Main three object to manipulate
state.scene = threeObj;
// Kick-off dash animations
state.ticker = new FrameTicker$2();
state.ticker.onTick.add(function (_, timeDelta) {
state.arcsData.filter(function (d) {
return d.__threeObj && d.__threeObj.children.length && d.__threeObj.children[0].material && d.__threeObj.children[0].__dashAnimateStep;
}).forEach(function (d) {
var obj = d.__threeObj.children[0];
var step = obj.__dashAnimateStep * timeDelta;
var curTranslate = obj.material.uniforms.dashTranslate.value % 1e9; // reset after 1B loops
obj.material.uniforms.dashTranslate.value = curTranslate + step;
});
});
},
update: function update(state) {
// Data accessors
var startLatAccessor = accessorFn(state.arcStartLat);
var startLngAccessor = accessorFn(state.arcStartLng);
var endLatAccessor = accessorFn(state.arcEndLat);
var endLngAccessor = accessorFn(state.arcEndLng);
var altitudeAccessor = accessorFn(state.arcAltitude);
var altitudeAutoScaleAccessor = accessorFn(state.arcAltitudeAutoScale);
var strokeAccessor = accessorFn(state.arcStroke);
var colorAccessor = accessorFn(state.arcColor);
var dashLengthAccessor = accessorFn(state.arcDashLength);
var dashGapAccessor = accessorFn(state.arcDashGap);
var dashInitialGapAccessor = accessorFn(state.arcDashInitialGap);
var dashAnimateTimeAccessor = accessorFn(state.arcDashAnimateTime);
var sharedMaterial = new THREE$c.ShaderMaterial(_objectSpread2(_objectSpread2({}, gradientShaders$1), {}, {
transparent: true,
blending: THREE$c.NormalBlending
}));
threeDigest(state.arcsData, state.scene, {
createObj: function createObj() {
var obj = new THREE$c.Group(); // populated in updateObj
obj.__globeObjType = 'arc'; // Add object type
return obj;
},
updateObj: function updateObj(group, arc) {
var stroke = strokeAccessor(arc);
var useTube = stroke !== null && stroke !== undefined;
if (!group.children.length || useTube !== (group.children[0].type === 'Mesh')) {
// create or swap object types
emptyObject(group);
var _obj = useTube ? new THREE$c.Mesh() : new THREE$c.Line(new THREE$c.BufferGeometry());
_obj.material = sharedMaterial.clone(); // Separate material instance per object to have dedicated uniforms (but shared shaders)
group.add(_obj);
}
var obj = group.children[0];
// set dash uniforms
Object.assign(obj.material.uniforms, {
dashSize: {
value: dashLengthAccessor(arc)
},
gapSize: {
value: dashGapAccessor(arc)
},
dashOffset: {
value: dashInitialGapAccessor(arc)
}
});
// set dash animation step
var dashAnimateTime = dashAnimateTimeAccessor(arc);
obj.__dashAnimateStep = dashAnimateTime > 0 ? 1000 / dashAnimateTime : 0; // per second
// calculate vertex colors (to create gradient)
var vertexColorArray = calcColorVertexArray(colorAccessor(arc),
// single, array of colors or interpolator
state.arcCurveResolution,
// numSegments
useTube ? state.arcCircularResolution + 1 : 1 // num vertices per segment
);
// calculate vertex relative distances (for dashed lines)
var vertexRelDistanceArray = calcVertexRelDistances(state.arcCurveResolution,
// numSegments
useTube ? state.arcCircularResolution + 1 : 1,
// num vertices per segment
true // run from end to start, to animate in the correct direction
);
obj.geometry[setAttributeFn$1]('vertexColor', vertexColorArray);
obj.geometry[setAttributeFn$1]('vertexRelDistance', vertexRelDistanceArray);
var applyUpdate = function applyUpdate(td) {
var _arc$__currentTargetD = arc.__currentTargetD = td,
stroke = _arc$__currentTargetD.stroke,
curveD = _objectWithoutProperties(_arc$__currentTargetD, _excluded);
var curve = calcCurve(curveD);
if (useTube) {
obj.geometry && obj.geometry.dispose();
obj.geometry = new THREE$c.TubeGeometry(curve, state.arcCurveResolution, stroke / 2, state.arcCircularResolution);
obj.geometry[setAttributeFn$1]('vertexColor', vertexColorArray);
obj.geometry[setAttributeFn$1]('vertexRelDistance', vertexRelDistanceArray);
} else {
obj.geometry.setFromPoints(curve.getPoints(state.arcCurveResolution));
}
};
var targetD = {
stroke: stroke,
alt: altitudeAccessor(arc),
altAutoScale: +altitudeAutoScaleAccessor(arc),
startLat: +startLatAccessor(arc),
startLng: +startLngAccessor(arc),
endLat: +endLatAccessor(arc),
endLng: +endLngAccessor(arc)
};
var currentTargetD = arc.__currentTargetD || Object.assign({}, targetD, {
altAutoScale: -1e-3
});
if (Object.keys(targetD).some(function (k) {
return currentTargetD[k] !== targetD[k];
})) {
if (!state.arcsTransitionDuration || state.arcsTransitionDuration < 0) {
// set final position
applyUpdate(targetD);
} else {
// animate
new TWEEN.Tween(currentTargetD).to(targetD, state.arcsTransitionDuration).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(applyUpdate).start();
}
}
}
});
//
function calcCurve(_ref) {
var alt = _ref.alt,
altAutoScale = _ref.altAutoScale,
startLat = _ref.startLat,
startLng = _ref.startLng,
endLat = _ref.endLat,
endLng = _ref.endLng;
var getVec = function getVec(_ref2) {
var _ref3 = _slicedToArray(_ref2, 3),
lng = _ref3[0],
lat = _ref3[1],
alt = _ref3[2];
var _polar2Cartesian = polar2Cartesian(lat, lng, alt),
x = _polar2Cartesian.x,
y = _polar2Cartesian.y,
z = _polar2Cartesian.z;
return new THREE$c.Vector3(x, y, z);
};
//calculate curve
var startPnt = [startLng, startLat];
var endPnt = [endLng, endLat];
var altitude = alt;
(altitude === null || altitude === undefined) && (
// by default set altitude proportional to the great-arc distance
altitude = geoDistance(startPnt, endPnt) / 2 * altAutoScale);
if (altitude) {
var interpolate = geoInterpolate(startPnt, endPnt);
var _map = [0.25, 0.75].map(function (t) {
return [].concat(_toConsumableArray(interpolate(t)), [altitude * 1.5]);
}),
_map2 = _slicedToArray(_map, 2),
m1Pnt = _map2[0],
m2Pnt = _map2[1];
var curve = _construct(THREE$c.CubicBezierCurve3, _toConsumableArray([startPnt, m1Pnt, m2Pnt, endPnt].map(getVec)));
//const mPnt = [...interpolate(0.5), altitude * 2];
//curve = new THREE.QuadraticBezierCurve3(...[startPnt, mPnt, endPnt].map(getVec));
return curve;
} else {
// ground line
var _alt = 0.001; // slightly above the ground to prevent occlusion
return calcSphereArc.apply(void 0, _toConsumableArray([[].concat(startPnt, [_alt]), [].concat(endPnt, [_alt])].map(getVec)));
}
//
function calcSphereArc(startVec, endVec) {
var angle = startVec.angleTo(endVec);
var getGreatCirclePoint = angle === 0 ? function () {
return startVec.clone();
} // points exactly overlap
: function (t) {
return new THREE$c.Vector3().addVectors(startVec.clone().multiplyScalar(Math.sin((1 - t) * angle)), endVec.clone().multiplyScalar(Math.sin(t * angle))).divideScalar(Math.sin(angle));
};
var sphereArc = new THREE$c.Curve();
sphereArc.getPoint = getGreatCirclePoint;
return sphereArc;
}
}
function calcColorVertexArray(colors, numSegments) {
var numVerticesPerSegment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
var numVerticesGroup = numSegments + 1; // one between every two segments and two at the ends
var getVertexColor;
if (colors instanceof Array || colors instanceof Function) {
var colorInterpolator = colors instanceof Array ? scaleLinear() // array of colors, interpolate at each step
.domain(colors.map(function (_, idx) {
return idx / (colors.length - 1);
})) // same number of stops as colors
.range(colors) : colors; // already interpolator fn
getVertexColor = function getVertexColor(t) {
return color2ShaderArr(colorInterpolator(t));
};
} else {
// single color, use constant
var vertexColor = color2ShaderArr(colors);
getVertexColor = function getVertexColor() {
return vertexColor;
};
}
var vertexColorArray = new THREE$c.Float32BufferAttribute(numVerticesGroup * 4 * numVerticesPerSegment, 4);
for (var v = 0, l = numVerticesGroup; v < l; v++) {
var _vertexColor = getVertexColor(v / (l - 1));
for (var s = 0; s < numVerticesPerSegment; s++) {
vertexColorArray.set(_vertexColor, (v * numVerticesPerSegment + s) * 4);
}
}
return vertexColorArray;
}
function calcVertexRelDistances(numSegments) {
var numVerticesPerSegment = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var invert = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var numVerticesGroup = numSegments + 1; // one between every two segments and two at the ends
var arrLen = numVerticesGroup * numVerticesPerSegment;
var vertexDistanceArray = new THREE$c.Float32BufferAttribute(arrLen, 1);
for (var v = 0, l = numVerticesGroup; v < l; v++) {
var relDistance = v / (l - 1);
for (var s = 0; s < numVerticesPerSegment; s++) {
var idx = v * numVerticesPerSegment + s;
var pos = invert ? arrLen - 1 - idx : idx;
vertexDistanceArray.setX(pos, relDistance);
}
}
return vertexDistanceArray;
}
}
});
var THREE$b = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
BufferAttribute: BufferAttribute,
BufferGeometry: BufferGeometry,
Color: Color,
DoubleSide: DoubleSide,
Mesh: Mesh,
MeshBasicMaterial: MeshBasicMaterial,
MeshLambertMaterial: MeshLambertMaterial,
Object3D: Object3D
};
var bfg$1 = Object.assign({}, _bfg);
var BufferGeometryUtils$1 = bfg$1.BufferGeometryUtils || bfg$1;
//
// support multiple method names for backwards threejs compatibility
var applyMatrix4Fn = new THREE$b.BufferGeometry().applyMatrix4 ? 'applyMatrix4' : 'applyMatrix';
var HexBinLayerKapsule = Kapsule({
props: {
hexBinPointsData: {
"default": []
},
hexBinPointLat: {
"default": 'lat'
},
hexBinPointLng: {
"default": 'lng'
},
hexBinPointWeight: {
"default": 1
},
hexBinResolution: {
"default": 4
},
// 0-15. Level 0 partitions the earth in 122 (mostly) hexagonal cells. Each subsequent level sub-divides the previous in roughly 7 hexagons.
hexMargin: {
"default": 0.2
},
// in fraction of diameter
hexTopCurvatureResolution: {
"default": 5
},
// in angular degrees
hexTopColor: {
"default": function _default() {
return '#ffffaa';
}
},
hexSideColor: {
"default": function _default() {
return '#ffffaa';
}
},
hexAltitude: {
"default": function _default(_ref) {
var sumWeight = _ref.sumWeight;
return sumWeight * 0.01;
}
},
// in units of globe radius
hexBinMerge: {
"default": false
},
// boolean. Whether to merge all hex geometries into a single mesh for rendering performance
hexTransitionDuration: {
"default": 1000,
triggerUpdate: false
} // ms
},
init: function init(threeObj, state) {
// Clear the scene
emptyObject(threeObj);
// Main three object to manipulate
state.scene = threeObj;
},
update: function update(state) {
// Accessors
var latAccessor = accessorFn(state.hexBinPointLat);
var lngAccessor = accessorFn(state.hexBinPointLng);
var weightAccessor = accessorFn(state.hexBinPointWeight);
var altitudeAccessor = accessorFn(state.hexAltitude);
var topColorAccessor = accessorFn(state.hexTopColor);
var sideColorAccessor = accessorFn(state.hexSideColor);
var marginAccessor = accessorFn(state.hexMargin);
var byH3Idx = indexBy(state.hexBinPointsData.map(function (d) {
return _objectSpread2(_objectSpread2({}, d), {}, {
h3Idx: latLngToCell(latAccessor(d), lngAccessor(d), state.hexBinResolution)
});
}), 'h3Idx');
var hexBins = Object.entries(byH3Idx).map(function (_ref2) {
var _ref3 = _slicedToArray(_ref2, 2),
h3Idx = _ref3[0],
points = _ref3[1];
return {
h3Idx: h3Idx,
points: points,
sumWeight: points.reduce(function (agg, d) {
return agg + +weightAccessor(d);
}, 0)
};
});
var hexMaterials = {}; // indexed by color
var scene = state.hexBinMerge ? new THREE$b.Object3D() : state.scene; // use fake scene if merging hex points
threeDigest(hexBins, scene, {
createObj: createObj,
updateObj: updateObj,
idAccessor: function idAccessor(d) {
return d.h3Idx;
}
});
if (state.hexBinMerge) {
// merge points into a single mesh
var hexPointsGeometry = !hexBins.length ? new THREE$b.BufferGeometry() : (BufferGeometryUtils$1.mergeGeometries || BufferGeometryUtils$1.mergeBufferGeometries)(hexBins.map(function (d) {
var obj = d.__threeObj;
d.__threeObj = undefined; // unbind merged points
// use non-indexed geometry so that groups can be colored separately, otherwise different groups share vertices
var geom = obj.geometry.toNonIndexed();
// apply mesh world transform to vertices
obj.updateMatrix();
geom[applyMatrix4Fn](obj.matrix);
// color vertices
var topColor = new THREE$b.Color(topColorAccessor(d));
var sideColor = new THREE$b.Color(sideColorAccessor(d));
var nVertices = geom.attributes.position.count;
var topFaceIdx = geom.groups[0].count; // starting vertex index of top group
var colors = new Float32Array(nVertices * 3);
for (var i = 0, len = nVertices; i < len; i++) {
var idx = i * 3;
var c = i >= topFaceIdx ? topColor : sideColor;
colors[idx] = c.r;
colors[idx + 1] = c.g;
colors[idx + 2] = c.b;
}
geom.setAttribute('color', new THREE$b.BufferAttribute(colors, 3));
return geom;
}));
var hexPoints = new THREE$b.Mesh(hexPointsGeometry, new THREE$b.MeshBasicMaterial({
color: 0xffffff,
vertexColors: true,
side: THREE$b.DoubleSide
}));
hexPoints.__globeObjType = 'hexBinPoints'; // Add object type
hexPoints.__data = hexBins; // Attach obj data
emptyObject(state.scene);
state.scene.add(hexPoints);
}
//
function createObj(d) {
var obj = new THREE$b.Mesh();
obj.__hexCenter = cellToLatLng(d.h3Idx);
obj.__hexGeoJson = cellToBoundary(d.h3Idx, true).reverse(); // correct polygon winding
// stitch longitudes at the anti-meridian
var centerLng = obj.__hexCenter[1];
obj.__hexGeoJson.forEach(function (d) {
var edgeLng = d[0];
if (Math.abs(centerLng - edgeLng) > 170) {
// normalize large lng distances
d[0] += centerLng > edgeLng ? 360 : -360;
}
});
obj.__globeObjType = 'hexbin'; // Add object type
return obj;
}
function updateObj(obj, d) {
// compute new geojson with relative margin
var relNum = function relNum(st, end, rat) {
return st - (st - end) * rat;
};
var margin = Math.max(0, Math.min(1, +marginAccessor(d)));
var _obj$__hexCenter = _slicedToArray(obj.__hexCenter, 2),
clat = _obj$__hexCenter[0],
clng = _obj$__hexCenter[1];
var geoJson = margin === 0 ? obj.__hexGeoJson : obj.__hexGeoJson.map(function (_ref4) {
var _ref5 = _slicedToArray(_ref4, 2),
elng = _ref5[0],
elat = _ref5[1];
return [[elng, clng], [elat, clat]].map(function (_ref6) {
var _ref7 = _slicedToArray(_ref6, 2),
st = _ref7[0],
end = _ref7[1];
return relNum(st, end, margin);
});
});
var topCurvatureResolution = state.hexTopCurvatureResolution;
obj.geometry && obj.geometry.dispose();
obj.geometry = new ConicPolygonGeometry([geoJson], 0, GLOBE_RADIUS, false, true, true, topCurvatureResolution);
var targetD = {
alt: +altitudeAccessor(d)
};
var applyUpdate = function applyUpdate(td) {
var _obj$__currentTargetD = obj.__currentTargetD = td,
alt = _obj$__currentTargetD.alt;
obj.scale.x = obj.scale.y = obj.scale.z = 1 + alt; // scale according to altitude
};
var currentTargetD = obj.__currentTargetD || Object.assign({}, targetD, {
alt: -1e-3
});
if (Object.keys(targetD).some(function (k) {
return currentTargetD[k] !== targetD[k];
})) {
if (state.hexBinMerge || !state.hexTransitionDuration || state.hexTransitionDuration < 0) {
// set final position
applyUpdate(tar