three-globe
Version:
Globe data visualization as a ThreeJS reusable 3D object
1,412 lines (1,218 loc) • 122 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var three = require('three');
var Kapsule = _interopDefault(require('kapsule'));
var TWEEN = _interopDefault(require('@tweenjs/tween.js'));
var threeGeojsonGeometry = require('three-geojson-geometry');
var d3Geo = require('d3-geo');
var accessorFn = _interopDefault(require('accessor-fn'));
var tinyColor = _interopDefault(require('tinycolor2'));
var dataJoint = _interopDefault(require('data-joint'));
var FrameTicker = _interopDefault(require('frame-ticker'));
var d3Scale = require('d3-scale');
var threeConicPolygonGeometry = require('three-conic-polygon-geometry');
var indexBy = _interopDefault(require('index-array-by'));
var h3Js = require('h3-js');
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (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 = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
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
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || 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 {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _construct(Parent, args, Class) {
if (isNativeReflectConstruct()) {
_construct = Reflect.construct;
} 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;
}
return _assertThisInitialized(self);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) {
return;
}
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
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) {
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 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,
altitude: r / GLOBE_RADIUS - 1
};
}
var THREE = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
AdditiveBlending: three.AdditiveBlending,
BackSide: three.BackSide,
Color: three.Color,
LineBasicMaterial: three.LineBasicMaterial,
LineSegments: three.LineSegments,
Mesh: three.Mesh,
MeshPhongMaterial: three.MeshPhongMaterial,
ShaderMaterial: three.ShaderMaterial,
SphereGeometry: three.SphereGeometry,
TextureLoader: three.TextureLoader
};
var GlobeLayerKapsule = Kapsule({
props: {
globeImageUrl: {
onChange: function onChange(_, state) {
state.globeNeedsUpdate = true;
}
},
bumpImageUrl: {
onChange: function onChange(_, state) {
state.globeNeedsUpdate = true;
}
},
showAtmosphere: {
"default": true,
onChange: function onChange(showAtmosphere, state) {
state.atmosphereObj.visible = !!showAtmosphere;
},
triggerUpdate: false
},
showGraticules: {
"default": false,
onChange: function onChange(showGraticules, state) {
state.graticulesObj.visible = !!showGraticules;
},
triggerUpdate: false
}
},
stateInit: function stateInit() {
// create globe
var globeGeometry = new THREE.SphereGeometry(GLOBE_RADIUS, 75, 75);
var globeObj = new THREE.Mesh(globeGeometry, new THREE.MeshPhongMaterial({
color: 0x000000
}));
globeObj.rotation.y = -Math.PI / 2; // face prime meridian along Z axis
globeObj.__globeObjType = 'globe'; // Add object type
// create atmosphere
var atmosphereObj;
{
var shaders = {
uniforms: {
coeficient: {
value: 0.8
},
power: {
value: 12
}
},
vertexShader: "\n varying vec3 vNormal;\n void main() {\n vNormal = normalize(normalMatrix * normal);\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n ",
fragmentShader: "\n uniform float\tcoeficient;\n uniform float\tpower;\n\n varying vec3 vNormal;\n void main() {\n float intensity = pow(coeficient - dot(vNormal, vec3(0, 0, 1.0)), power);\n gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * intensity;\n }\n "
};
var material = new THREE.ShaderMaterial(_objectSpread2({}, shaders, {
side: THREE.BackSide,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false
}));
atmosphereObj = new THREE.Mesh(globeGeometry, material);
atmosphereObj.scale.set(1.1, 1.1, 1.1);
atmosphereObj.__globeObjType = 'atmosphere'; // Add object type
} // create graticules
var graticulesObj = new THREE.LineSegments(new threeGeojsonGeometry.GeoJsonGeometry(d3Geo.geoGraticule10(), GLOBE_RADIUS, 2), new THREE.LineBasicMaterial({
color: 'lightgrey',
transparent: true,
opacity: 0.1
}));
return {
globeObj: globeObj,
atmosphereObj: atmosphereObj,
graticulesObj: graticulesObj
};
},
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.atmosphereObj); // add atmosphere
state.scene.add(state.graticulesObj); // add graticules
},
update: function update(state) {
var globeMaterial = state.globeObj.material; // Black globe if no image
globeMaterial.color = new THREE.Color(0x000000);
state.globeImageUrl && new THREE.TextureLoader().load(state.globeImageUrl, function (texture) {
globeMaterial.map = texture;
globeMaterial.color = null;
globeMaterial.needsUpdate = true;
});
state.bumpImageUrl && new THREE.TextureLoader().load(state.bumpImageUrl, function (texture) {
globeMaterial.bumpMap = texture;
globeMaterial.needsUpdate = true;
});
}
});
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 rgba = tinyColor(str).toRgb();
return [].concat(_toConsumableArray(['r', 'g', 'b'].map(function (d) {
return rgba[d] / 255;
})), [rgba.a]);
};
function threeDigest(data, scene) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
return dataJoint(data, scene.children, function (obj) {
return scene.add(obj);
}, function (obj) {
return scene.remove(obj);
}, _objectSpread2({
objBindAttr: '__threeObj'
}, options));
}
var THREE$1 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
Color: three.Color,
CylinderGeometry: three.CylinderGeometry,
CylinderBufferGeometry: three.CylinderBufferGeometry,
FaceColors: three.FaceColors,
Geometry: three.Geometry,
Matrix4: three.Matrix4,
Mesh: three.Mesh,
MeshBasicMaterial: three.MeshBasicMaterial,
MeshLambertMaterial: three.MeshLambertMaterial,
Object3D: three.Object3D
};
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$1[state.pointsMerge ? 'CylinderGeometry' : 'CylinderBufferGeometry'](1, 1, 1, state.pointResolution);
pointGeometry.applyMatrix(new THREE$1.Matrix4().makeRotationX(Math.PI / 2));
pointGeometry.applyMatrix(new THREE$1.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$1.Object3D() : state.scene; // use fake scene if merging points
threeDigest(state.pointsData, scene, {
createObj: createObj,
updateObj: updateObj,
exitObj: emptyObject
});
if (state.pointsMerge) {
// merge points into a single mesh
var pointsGeometry = new THREE$1.Geometry();
state.pointsData.forEach(function (d) {
var obj = d.__threeObj;
d.__threeObj = undefined; // unbind merged points
// color faces
var color = new THREE$1.Color(colorAccessor(d));
obj.geometry.faces.forEach(function (face) {
return face.color = color;
});
obj.updateMatrix();
pointsGeometry.merge(obj.geometry, obj.matrix);
});
var points = new THREE$1.Mesh(pointsGeometry, new THREE$1.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE$1.FaceColors,
morphTargets: false
}));
points.__globeObjType = 'points'; // Add object type
points.__data = state.pointsData; // Attach obj data
state.scene.add(points);
} //
function createObj() {
var obj = new THREE$1.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
obj.lookAt(0, 0, 0); // 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 = colorAlpha(color);
if (!pointMaterials.hasOwnProperty(color)) {
pointMaterials[color] = new THREE$1.MeshLambertMaterial({
color: colorStr2Hex(color),
transparent: opacity < 1,
opacity: opacity
});
}
obj.material = pointMaterials[color];
}
}
}
});
var THREE$2 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
AdditiveBlending: three.AdditiveBlending,
BufferGeometry: three.BufferGeometry,
CubicBezierCurve3: three.CubicBezierCurve3,
Curve: three.Curve,
Float32BufferAttribute: three.Float32BufferAttribute,
Line: three.Line,
Mesh: three.Mesh,
QuadraticBezierCurve3: three.QuadraticBezierCurve3,
ShaderMaterial: three.ShaderMaterial,
TubeBufferGeometry: three.TubeBufferGeometry,
Vector3: three.Vector3
};
var gradientShaders = {
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';
}
},
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
},
init: function init(threeObj, state) {
// Clear the scene
emptyObject(threeObj); // Main three object to manipulate
state.scene = threeObj; // Kick-off dash animations
new FrameTicker().onTick.add(function (_, timeDelta) {
state.arcsData.filter(function (d) {
return d.__threeObj && d.__threeObj.material && d.__threeObj.__dashAnimateStep;
}).forEach(function (d) {
var obj = d.__threeObj;
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$2.ShaderMaterial(_objectSpread2({}, gradientShaders, {
transparent: true,
blending: THREE$2.AdditiveBlending
}));
threeDigest(state.arcsData, state.scene, {
exitObj: emptyObject,
createObj: function createObj(arc) {
var stroke = strokeAccessor(arc);
var useTube = stroke !== null && stroke !== undefined;
var obj = useTube ? new THREE$2.Mesh() : new THREE$2.Line(new THREE$2.BufferGeometry());
obj.material = sharedMaterial.clone(); // Separate material instance per object to have dedicated uniforms (but shared shaders)
obj.__globeObjType = 'arc'; // Add object type
return obj;
},
updateObj: function updateObj(obj, arc) {
var stroke = strokeAccessor(arc);
var useTube = stroke !== null && stroke !== undefined; // 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 or array of colors
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.addAttribute('vertexColor', vertexColorArray);
obj.geometry.addAttribute('vertexRelDistance', vertexRelDistanceArray);
var applyUpdate = function applyUpdate(td) {
var _arc$__currentTargetD = arc.__currentTargetD = td,
stroke = _arc$__currentTargetD.stroke,
curveD = _objectWithoutProperties(_arc$__currentTargetD, ["stroke"]);
var curve = calcCurve(curveD);
if (useTube) {
obj.geometry && obj.geometry.dispose();
obj.geometry = new THREE$2.TubeBufferGeometry(curve, state.arcCurveResolution, stroke / 2, state.arcCircularResolution);
obj.geometry.addAttribute('vertexColor', vertexColorArray);
obj.geometry.addAttribute('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$2.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 = d3Geo.geoDistance(startPnt, endPnt) / 2 * altAutoScale);
if (altitude) {
var interpolate = d3Geo.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$2.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 = function getGreatCirclePoint(t) {
return new THREE$2.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$2.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) {
// array of colors, interpolate at each step
var colorScale = d3Scale.scaleLinear().domain(colors.map(function (_, idx) {
return idx / (colors.length - 1);
})) // same number of stops as colors
.range(colors);
getVertexColor = function getVertexColor(t) {
return color2ShaderArr(colorScale(t));
};
} else {
// single color, use constant
var vertexColor = color2ShaderArr(colors);
getVertexColor = function getVertexColor() {
return vertexColor;
};
}
var vertexColorArray = new THREE$2.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$2.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$3 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
Color: three.Color,
FaceColors: three.FaceColors,
Geometry: three.Geometry,
Mesh: three.Mesh,
MeshBasicMaterial: three.MeshBasicMaterial,
MeshLambertMaterial: three.MeshLambertMaterial,
Object3D: three.Object3D
};
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
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({}, d, {
h3Idx: h3Js.geoToH3(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$3.Object3D() : state.scene; // use fake scene if merging hex points
threeDigest(hexBins, scene, {
createObj: createObj,
updateObj: updateObj,
exitObj: emptyObject,
idAccessor: function idAccessor(d) {
return d.h3Idx;
}
});
if (state.hexBinMerge) {
// merge points into a single mesh
var hexPointsGeometry = new THREE$3.Geometry();
hexBins.forEach(function (d) {
var obj = d.__threeObj;
d.__threeObj = undefined; // unbind merged points
// color faces
var topColor = new THREE$3.Color(topColorAccessor(d));
var sideColor = new THREE$3.Color(sideColorAccessor(d));
var topFaceIdx = obj.geometry.faces.length - 4;
obj.geometry.faces.forEach(function (face, idx) {
return face.color = idx >= topFaceIdx ? topColor : sideColor;
});
obj.updateMatrix();
hexPointsGeometry.merge(obj.geometry, obj.matrix);
});
var hexPoints = new THREE$3.Mesh(hexPointsGeometry, new THREE$3.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE$3.FaceColors,
morphTargets: false
}));
hexPoints.__globeObjType = 'hexBinPoints'; // Add object type
hexPoints.__data = hexBins; // Attach obj data
state.scene.add(hexPoints);
} //
function createObj(d) {
var obj = new THREE$3.Mesh();
obj.__hexCenter = h3Js.h3ToGeo(d.h3Idx);
obj.__hexGeoJson = h3Js.h3ToGeoBoundary(d.h3Idx, true); // 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) {
var GeometryClass = state.hexBinMerge ? threeConicPolygonGeometry.ConicPolygonGeometry : threeConicPolygonGeometry.ConicPolygonGeometry;
var applyUpdate = function applyUpdate(td) {
var _obj$__currentTargetD = obj.__currentTargetD = td,
alt = _obj$__currentTargetD.alt,
margin = _obj$__currentTargetD.margin; // compute new geojson with relative margin
var relNum = function relNum(st, end, rat) {
return st - (st - end) * rat;
};
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);
});
});
obj.geometry = new GeometryClass([geoJson], GLOBE_RADIUS, GLOBE_RADIUS * (1 + alt), false);
};
var targetD = {
alt: +altitudeAccessor(d),
margin: Math.max(0, Math.min(1, +marginAccessor(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.hexBinMerge || !state.hexTransitionDuration || state.hexTransitionDuration < 0) {
// set final position
applyUpdate(targetD);
} else {
// animate
new TWEEN.Tween(currentTargetD).to(targetD, state.hexTransitionDuration).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(applyUpdate).start();
}
}
if (!state.hexBinMerge) {
// Update materials on individual hex points
var sideColor = sideColorAccessor(d);
var topColor = topColorAccessor(d);
[sideColor, topColor].forEach(function (color) {
if (!hexMaterials.hasOwnProperty(color)) {
var opacity = colorAlpha(color);
hexMaterials[color] = new THREE$3.MeshLambertMaterial({
color: colorStr2Hex(color),
transparent: opacity < 1,
opacity: opacity
});
}
});
obj.material = [sideColor, topColor].map(function (color) {
return hexMaterials[color];
});
}
}
}
});
var THREE$4 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
: {
DoubleSide: three.DoubleSide,
Group: three.Group,
Line: three.Line,
LineBasicMaterial: three.LineBasicMaterial,
Mesh: three.Mesh,
MeshLambertMaterial: three.MeshLambertMaterial
};
var PolygonsLayerKapsule = Kapsule({
props: {
polygonsData: {
"default": []
},
polygonGeoJsonGeometry: {
"default": 'geometry'
},
polygonSideColor: {
"default": function _default() {
return '#ffffaa';
}
},
polygonCapColor: {
"default": function _default() {
return '#ffffaa';
}
},
polygonStrokeColor: {},
polygonAltitude: {
"default": 0.1
},
// in units of globe radius
polygonsTransitionDuration: {
"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 geoJsonAccessor = accessorFn(state.polygonGeoJsonGeometry);
var altitudeAccessor = accessorFn(state.polygonAltitude);
var capColorAccessor = accessorFn(state.polygonCapColor);
var sideColorAccessor = accessorFn(state.polygonSideColor);
var strokeColorAccessor = accessorFn(state.polygonStrokeColor);
var singlePolygons = [];
state.polygonsData.forEach(function (polygon) {
var objAttrs = {
data: polygon,
capColor: capColorAccessor(polygon),
sideColor: sideColorAccessor(polygon),
strokeColor: strokeColorAccessor(polygon),
altitude: +altitudeAccessor(polygon)
};
var geoJson = geoJsonAccessor(polygon);
var geoId = polygon.__id || "".concat(Math.round(Math.random() * 1e9)); // generate and stamp polygon ids to keep track in digest
polygon.__id = geoId;
if (geoJson.type === 'Polygon') {
singlePolygons.push(_objectSpread2({
id: "".concat(geoId, "_0"),
coords: geoJson.coordinates
}, objAttrs));
} else if (geoJson.type === 'MultiPolygon') {
singlePolygons.push.apply(singlePolygons, _toConsumableArray(geoJson.coordinates.map(function (coords, idx) {
return _objectSpread2({
id: "".concat(geoId, "_").concat(idx),
coords: coords
}, objAttrs);
})));
} else {
console.warn("Unsupported GeoJson geometry type: ".concat(geoJson.type, ". Skipping geometry..."));
}
});
threeDigest(singlePolygons, state.scene, {
idAccessor: function idAccessor(d) {
return d.id;
},
exitObj: emptyObject,
createObj: function createObj() {
var obj = new THREE$4.Group(); // conic geometry
obj.add(new THREE$4.Mesh(undefined, [new THREE$4.MeshLambertMaterial({
side: THREE$4.DoubleSide,
depthWrite: true
}), // side material
new THREE$4.MeshLambertMaterial({
side: THREE$4.DoubleSide,
depthWrite: true
}) // cap material
])); // polygon stroke
obj.add(new THREE$4.Line(undefined, new THREE$4.LineBasicMaterial()));
obj.__globeObjType = 'polygon'; // Add object type
return obj;
},
updateObj: function updateObj(obj, _ref) {
var coords = _ref.coords,
capColor = _ref.capColor,
sideColor = _ref.sideColor,
strokeColor = _ref.strokeColor,
altitude = _ref.altitude;
var _obj$children = _slicedToArray(obj.children, 2),
conicObj = _obj$children[0],
strokeObj = _obj$children[1]; // hide stroke if no color set
var addStroke = !!strokeColor;
strokeObj.visible = addStroke; // update materials
[sideColor, capColor].forEach(function (color, materialIdx) {
// conic object
var material = conicObj.material[materialIdx];
var opacity = colorAlpha(color);
material.color.set(colorStr2Hex(color));
material.transparent = opacity < 1;
material.opacity = opacity;
});
if (addStroke) {
// stroke object
var material = strokeObj.material;
var opacity = colorAlpha(strokeColor);
material.color.set(colorStr2Hex(strokeColor));
material.transparent = opacity < 1;
material.opacity = opacity;
}
var geoJsonGeometry = {
type: 'Polygon',
coordinates: coords
};
var applyUpdate = function applyUpdate(td) {
var _obj$__currentTargetD = obj.__currentTargetD = td,
alt = _obj$__currentTargetD.alt;
conicObj.geometry = new threeConicPolygonGeometry.ConicPolygonBufferGeometry(coords, GLOBE_RADIUS, GLOBE_RADIUS * (1 + alt), false);
addStroke && (strokeObj.geometry = new threeGeojsonGeometry.GeoJsonGeometry(geoJsonGeometry, GLOBE_RADIUS * (1 + alt + 1e-4))); // stroke slightly above the conic mesh
};
var targetD = {
alt: altitude
};
var currentTargetD = obj.__currentTargetD || {
alt: -1e-3
};
if (Object.keys(targetD).some(function (k) {
return currentTargetD[k] !== targetD[k];
})) {
if (!state.polygonsTransitionDuration || state.polygonsTransitionDuration < 0) {
// set final position
applyUpdate(targetD);
} else {
// animate
new TWEEN.Tween(currentTargetD).to(targetD, state.polygonsTransitionDuration).easing(TWEEN.Easing.Quadratic.InOut).onUpdate(applyUpdate).start();
}
}
}
});
}
});
var glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311