UNPKG

three-globe

Version:

Globe data visualization as a ThreeJS reusable 3D object

1,388 lines (1,315 loc) 184 kB
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