diff --git a/src/airocean.js b/src/airocean.js index dca7920..3e93928 100644 --- a/src/airocean.js +++ b/src/airocean.js @@ -17,19 +17,16 @@ import { import { range } from "d3-array"; function airoceanRaw(faceProjection) { - var theta = atan(0.5) * degrees; + const theta = atan(0.5) * degrees; // construction inspired by // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates - var vertices = [[0, 90], [0, -90]].concat( - range(10).map(function(i) { - var phi = (i * 36 + 180) % 360 - 180; - return [phi, i & 1 ? theta : -theta]; - }) + const vertices = [[0, 90], [0, -90]].concat( + range(10).map((i) => [(i * 36 + 180) % 360 - 180, i & 1 ? theta : -theta]) ); // icosahedron - var polyhedron = [ + const polyhedron = [ [0, 3, 11], [0, 5, 3], [0, 7, 5], @@ -50,22 +47,16 @@ function airoceanRaw(faceProjection) { [1, 6, 8], [1, 8, 10], [1, 10, 2] // South - ].map(function(face) { - return face.map(function(i) { - return vertices[i]; - }); - }); + ].map((face) => face.map((i) => vertices[i])); // add centroid - polyhedron.forEach(function(face) { - face.centroid = centroid({ type: "MultiPoint", coordinates: face }); - }); + polyhedron.forEach((face) => (face.centroid = centroid({ type: "MultiPoint", coordinates: face }))); // split the relevant faces: // * face[15] in the centroid: this will become face[15], face[20] and face[21] // * face[14] in the middle of the side: this will become face[14] and face[22] (function() { - var face, tmp, mid, centroid; + let face, tmp, mid, centroid; // Split face[15] in 3 faces at centroid. face = polyhedron[15]; @@ -87,11 +78,11 @@ function airoceanRaw(faceProjection) { tmp = face.slice(); // compute planar midpoint - var proj = gnomonic() + const proj = gnomonic() .scale(1) .translate([0, 0]) .rotate([-centroid[0], -centroid[1]]); - var a = proj(face[1]), + const a = proj(face[1]), b = proj(face[2]); mid = proj.invert([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]); face[1] = mid; // (new) face[14] @@ -113,20 +104,17 @@ function airoceanRaw(faceProjection) { polyhedron.push(face); // face[23] })(); - var airocean = function(faceProjection) { + const airocean = function(faceProjection) { faceProjection = faceProjection || - function(face) { - // for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face}); - var c = face.centroid; - return gnomonic() + // for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face}); + ((face) => gnomonic() .scale(1) .translate([0, 0]) - .rotate([-c[0], -c[1]]); - }; + .rotate([-face.centroid[0], -face.centroid[1]])); - var faces = polyhedron.map(function(face, i) { - var polygon = face.slice(); + const faces = polyhedron.map((face, i) => { + const polygon = face.slice(); polygon.push(polygon[0]); return { @@ -144,7 +132,7 @@ function airoceanRaw(faceProjection) { }); // Connect each face to a parent face. - var parents = [ + const parents = [ // N -1, // 0 0, // 1 @@ -177,19 +165,19 @@ function airoceanRaw(faceProjection) { 19 // 23 ]; - parents.forEach(function(d, i) { - var node = faces[d]; + parents.forEach((d, i) => { + const node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); function face(lambda, phi) { - for (var i = 0; i < faces.length; i++) { + for (let i = 0; i < faces.length; ++i) { if (faces[i].contains(lambda, phi)) return faces[i]; } } // Polyhedral projection - var proj = polyhedral( + const proj = polyhedral( faces[0], // the root face face // a function that returns a face given coords ); @@ -202,8 +190,8 @@ function airoceanRaw(faceProjection) { } export default function () { - var p = airoceanRaw(function(face) { - var c = face.centroid; + const p = airoceanRaw((face) => { + const c = face.centroid; face.direction = Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 0 : 60; diff --git a/src/cahillKeyes.js b/src/cahillKeyes.js index 4493be0..bef707f 100644 --- a/src/cahillKeyes.js +++ b/src/cahillKeyes.js @@ -15,13 +15,11 @@ import {solve2d} from "./newton.js"; export default function(faceProjection) { faceProjection = faceProjection || - function() { - return cahillKeyesProjection().scale(1); - }; + (() => cahillKeyesProjection().scale(1)); - var octahedron = [[0, 90], [-90, 0], [0, 0], [90, 0], [180, 0], [0, -90]]; + const octa = [[0, 90], [-90, 0], [0, 0], [90, 0], [180, 0], [0, -90]]; - octahedron = [ + const octahedron = [ [0, 2, 1], [0, 3, 2], [5, 1, 2], @@ -30,22 +28,18 @@ export default function(faceProjection) { [0, 4, 3], [5, 4, 1], [5, 3, 4] - ].map(function(face) { - return face.map(function(i) { - return octahedron[i]; - }); - }); + ].map((face) => face.map((i) => octa[i])); - var ck = octahedron.map(function(face) { - var xyz = face.map(cartesianDegrees), + const ck = octahedron.map((face) => { + const xyz = face.map(cartesianDegrees), n = xyz.length, - a = xyz[n - 1], - b, theta = 17 * radians, cosTheta = cos(theta), sinTheta = sin(theta), hexagon = []; - for (var i = 0; i < n; ++i) { + let a = xyz[n - 1]; + let b; + for (let i = 0; i < n; ++i) { b = xyz[i]; hexagon.push( sphericalDegrees([ @@ -64,15 +58,15 @@ export default function(faceProjection) { return hexagon; }); - var cornerNormals = []; + const cornerNormals = []; - var parents = [-1, 3, 0, 2, 0, 1, 4, 5]; + const parents = [-1, 3, 0, 2, 0, 1, 4, 5]; - ck.forEach(function(hexagon, j) { - var face = octahedron[j], + ck.forEach((hexagon, j) => { + const face = octahedron[j], n = face.length, normals = (cornerNormals[j] = []); - for (var i = 0; i < n; ++i) { + for (let i = 0; i < n; ++i) { ck.push([ face[i], hexagon[(i * 2 + 2) % (2 * n)], @@ -88,15 +82,10 @@ export default function(faceProjection) { } }); - var faces = ck.map(function(face) { - return { - project: faceProjection(face), - face: face - }; - }); + const faces = ck.map((face) => ({project: faceProjection(face), face})); - parents.forEach(function(d, i) { - var parent = faces[d]; + parents.forEach((d, i) => { + const parent = faces[d]; parent && (parent.children || (parent.children = [])).push(faces[i]); }); return polyhedral(faces[0], face, 0, true) @@ -105,17 +94,17 @@ export default function(faceProjection) { .center([0,-17]); function face(lambda, phi) { - var cosPhi = cos(phi), - p = [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)]; + const cosPhi = cos(phi); + const p = [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)]; - var hexagon = + const hexagon = lambda < -pi / 2 ? phi < 0 ? 6 : 4 : lambda < 0 ? phi < 0 ? 2 : 0 : lambda < pi / 2 ? (phi < 0 ? 3 : 1) : phi < 0 ? 7 : 5; - var n = cornerNormals[hexagon]; + const n = cornerNormals[hexagon]; return faces[ cartesianDot(n[0], p) < 0 @@ -132,15 +121,15 @@ export default function(faceProjection) { // web site http://www.genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline.html export function cahillKeyesRaw(mg) { - var CK = { + const CK = { lengthMG: mg // magic scaling length }; preliminaries(); function preliminaries() { - var pointN, lengthMB, lengthMN, lengthNG, pointU; - var m = 29, // meridian + let pointN, lengthMB, lengthMN, lengthNG, pointU; + let m = 29, // meridian p = 15, // parallel p73a, lF, @@ -213,20 +202,17 @@ export function cahillKeyesRaw(mg) { // distance between two 2D coordinates function distance(p1, p2) { - var deltaX = p1[0] - p2[0], - deltaY = p1[1] - p2[1]; - return sqrt(deltaX * deltaX + deltaY * deltaY); + return Math.hypot(p1[0] - p2[0], p1[1] - p2[1]); } // return 2D point at position length/totallength of the line // defined by two 2D points, start and end. function interpolate(length, totalLength, start, end) { - var xy = [ + return [ start[0] + (end[0] - start[0]) * length / totalLength, start[1] + (end[1] - start[1]) * length / totalLength ]; - return xy; } // return the 2D point intersection between two lines defined @@ -234,13 +220,10 @@ export function cahillKeyesRaw(mg) { function lineIntersection(point1, slope1, point2, slope2) { // s1/s2 = slope in degrees - var m1 = tan(slope1 * radians), - m2 = tan(slope2 * radians), - p = [0, 0]; - p[0] = - (m1 * point1[0] - m2 * point2[0] - point1[1] + point2[1]) / (m1 - m2); - p[1] = m1 * (p[0] - point1[0]) + point1[1]; - return p; + const m1 = tan(slope1 * radians); + const m2 = tan(slope2 * radians); + const x = (m1 * point1[0] - m2 * point2[0] - point1[1] + point2[1]) / (m1 - m2); + return [x, m1 * (x - point1[0]) + point1[1]]; } // return the 2D point intercepting a circumference centered @@ -251,7 +234,7 @@ export function cahillKeyesRaw(mg) { // Equations from "Intersection of a Line and a Sphere (or circle)/Line Segment" // at http://paulbourke.net/geometry/circlesphere/ function circleLineIntersection(cc, r, p1, p2) { - var x1 = p1[0], + let x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], @@ -290,7 +273,7 @@ export function cahillKeyesRaw(mg) { // [original CKOG uses clockwise rotation] function rotate(xy, angle) { - var xynew = [0, 0]; + const xynew = [0, 0]; if (angle === -60) { xynew[0] = xy[0] * CK.cos60 + xy[1] * CK.sin60; @@ -300,7 +283,7 @@ export function cahillKeyesRaw(mg) { xynew[1] = -xy[0] * CK.sin60 - xy[1] * CK.cos60; } else { // !!!!! This should not happen for this projection!!!! - // the general algorith: cos(angle) * xy + sin(angle) * perpendicular(xy) + // the general algorithm: cos(angle) * xy + sin(angle) * perpendicular(xy) // return cos(angle * radians) * xy + sin(angle * radians) * perpendicular(xy); //console.log("rotate: angle " + angle + " different than -60 or -120!"); // counterclockwise @@ -317,15 +300,10 @@ export function cahillKeyesRaw(mg) { } function equator(m) { - var l = CK.deltaMEq * m, - jointE = [0, 0]; - if (l <= CK.lengthGF) { - jointE = [CK.pointG[0], l]; - } else { - l = l - CK.lengthGF; - jointE = interpolate(l, CK.lengthAB, CK.pointF, CK.pointE); - } - return jointE; + const l = CK.deltaMEq * m; + return (l <= CK.lengthGF) + ? [CK.pointG[0], l] + : interpolate(l - CK.lengthGF, CK.lengthAB, CK.pointF, CK.pointE); } function jointE(m) { @@ -340,8 +318,7 @@ export function cahillKeyesRaw(mg) { if (m === 0) { return [CK.pointA + CK.lengthAB, 0]; } - var xy = lineIntersection(CK.pointA, m, CK.pointM, 2 * m / 3); - return xy; + return lineIntersection(CK.pointA, m, CK.pointM, 2 * m / 3); } function lengthTorridSegment(m) { @@ -353,7 +330,7 @@ export function cahillKeyesRaw(mg) { } function parallel73(m) { - var p73 = [0, 0], + let p73 = [0, 0], jF = jointF(m), lF = 0, xy = [0, 0]; @@ -387,17 +364,15 @@ export function cahillKeyesRaw(mg) { // special functions to transform lon/lat to x/y function ll2mp(lon, lat) { - var south = [0, 6, 7, 8, 5], - o = truncate((lon + 180) / 90 + 1), - p, // parallel - m = (lon + 720) % 90 - 45, // meridian - s = sign(m); + const south = [0, 6, 7, 8, 5]; + let o = truncate((lon + 180) / 90 + 1); + let m = (lon + 720) % 90 - 45; // meridian + const s = sign(m); m = abs(m); if (o === 5) o = 1; if (lat < 0) o = south[o]; - p = abs(lat); - return [m, p, s, o]; + return [m, abs(lat), s, o]; } function zoneA(m, p) { @@ -409,7 +384,7 @@ export function cahillKeyesRaw(mg) { } function zoneC(m, p) { - var l = 104 * (90 - p); + const l = 104 * (90 - p); return [ CK.pointA[0] + l * cos(m * radians), CK.pointA[1] + l * sin(m * radians) @@ -422,7 +397,7 @@ export function cahillKeyesRaw(mg) { } function zoneE(m, p) { - var l = 1560 + (75 - p) * 100; + const l = 1560 + (75 - p) * 100; return [ CK.pointA[0] + l * cos(m * radians), CK.pointA[1] + l * sin(m * radians) @@ -434,152 +409,103 @@ export function cahillKeyesRaw(mg) { } function zoneG(m, p) { - var l = p - 15; + const l = p - 15; return interpolate(l, 58, CK.pointD, CK.pointT); } function zoneH(m, p) { - var p75 = parallel75(45), + const p75 = parallel75(45), p73a = parallel73(m), p73 = p73a.parallel73, lF = distance(CK.pointT, CK.pointB), lF75 = distance(CK.pointB, p75), - l = (75 - p) * (lF75 + lF) / 2, - xy = [0, 0]; - if (l <= lF75) { - xy = interpolate(l, lF75, p75, CK.pointB); - } else { - l = l - lF75; - xy = interpolate(l, lF, CK.pointB, p73); - } - return xy; + l = (75 - p) * (lF75 + lF) / 2; + return (l <= lF75) + ? interpolate(l, lF75, p75, CK.pointB) + : interpolate(l - lF75, lF, CK.pointB, p73); } function zoneI(m, p) { - var p73a = parallel73(m), + const p73a = parallel73(m), lT = lengthTorridSegment(m), lM = lengthMiddleSegment(m), - l = p * (lT + lM + p73a.lengthParallel73) / 73, - xy; - if (l <= lT) { - xy = interpolate(l, lT, jointE(m), jointT(m)); - } else if (l <= lT + lM) { - l = l - lT; - xy = interpolate(l, lM, jointT(m), jointF(m)); - } else { - l = l - lT - lM; - xy = interpolate(l, p73a.lengthParallel73, jointF(m), p73a.parallel73); - } - return xy; + l = p * (lT + lM + p73a.lengthParallel73) / 73; + return (l <= lT) + ? interpolate(l, lT, jointE(m), jointT(m)) + : (l <= lT + lM) + ? interpolate(l - lT, lM, jointT(m), jointF(m)) + : interpolate(l - lT - lM, p73a.lengthParallel73, jointF(m), p73a.parallel73); } function zoneJ(m, p) { - var p75 = parallel75(m), + const p75 = parallel75(m), lF75 = distance(jointF(m), p75), p73a = parallel73(m), p73 = p73a.parallel73, lF = p73a.lengthParallel73, - l = (75 - p) * (lF75 - lF) / 2, - xy = [0, 0]; + l = (75 - p) * (lF75 - lF) / 2; - if (l <= lF75) { - xy = interpolate(l, lF75, p75, jointF(m)); - } else { - l = l - lF75; - xy = interpolate(l, -lF, jointF(m), p73); - } - return xy; + return (l <= lF75) + ? interpolate(l, lF75, p75, jointF(m)) + : interpolate(l - lF75, -lF, jointF(m), p73); } function zoneK(m, p, l15) { - var l = p * l15 / 15, + const l = p * l15 / 15, lT = lengthTorridSegment(m), - lM = lengthMiddleSegment(m), - xy = [0, 0]; - if (l <= lT) { + lM = lengthMiddleSegment(m); + return (l <= lT) // point is in torrid segment - xy = interpolate(l, lT, jointE(m), jointT(m)); - } else { + ? interpolate(l, lT, jointE(m), jointT(m)) // point is in middle segment - l = l - lT; - xy = interpolate(l, lM, jointT(m), jointF(m)); - } - return xy; + : interpolate(l - lT, lM, jointT(m), jointF(m)); } function zoneL(m, p, l15) { - var p73a = parallel73(m), + const p73a = parallel73(m), p73 = p73a.parallel73, lT = lengthTorridSegment(m), lM = lengthMiddleSegment(m), - xy, lF = p73a.lengthParallel73, l = l15 + (p - 15) * (lT + lM + lF - l15) / 58; - if (l <= lT) { - //on torrid segment - xy = interpolate(l, lT, jointE(m), jointF(m)); - } else if (l <= lT + lM) { - //on middle segment - l = l - lT; - xy = interpolate(l, lM, jointT(m), jointF(m)); - } else { - //on frigid segment - l = l - lT - lM; - xy = interpolate(l, lF, jointF(m), p73); - } - return xy; + return (l <= lT) + // on torrid segment + ? interpolate(l, lT, jointE(m), jointF(m)) + : (l <= lT + lM) + // on middle segment + ? interpolate(l - lT, lM, jointT(m), jointF(m)) + // on frigid segment + : interpolate(l - lT - lM, lF, jointF(m), p73); } // convert half-octant meridian,parallel to x,y coordinates. // arguments are meridian, parallel function mp2xy(m, p) { - var xy = [0, 0], - lT, - p15a, - p15, - flag15, - l15; - - if (m === 0) { - // zones (a) and (b) - if (p >= 75) { - xy = zoneA(m, p); - } else { - xy = zoneB(m, p); - } - } else if (p >= 75) { - xy = zoneC(m, p); - } else if (p === 0) { - xy = zoneD(m, p); - } else if (p >= 73 && m <= 30) { - xy = zoneE(m, p); - } else if (m === 45) { - if (p <= 15) { - xy = zoneF(m, p); - } else if (p <= 73) { - xy = zoneG(m, p); - } else { - xy = zoneH(m, p); - } - } else { - if (m <= 29) { - xy = zoneI(m, p); - } else { + // zones (a) and (b) + if (m === 0) return (p >= 75) ? zoneA(m, p) : zoneB(m, p); + else if (p >= 75) return zoneC(m, p); + else if (p === 0) return zoneD(m, p); + else if (p >= 73 && m <= 30) return zoneE(m, p); + else if (m === 45) return (p <= 15) ? zoneF(m, p) : (p <= 73) ? zoneG(m, p) : zoneH(m, p); + else { + if (m <= 29) return zoneI(m, p); + else { // supple zones (j), (k) and (l) - if (p >= 73) { - xy = zoneJ(m, p); - } else { + if (p >= 73) return zoneJ(m, p); + else { + const lT = lengthTorridSegment(m); + let l15; + //zones (k) and (l) - p15a = circleLineIntersection( + let p15a = circleLineIntersection( CK.pointC, CK.radius, jointT(m), jointF(m) ); - flag15 = p15a[0]; - p15 = p15a[1]; - lT = lengthTorridSegment(m); + let flag15 = p15a[0]; + let p15 = p15a[1]; if (flag15 === 1) { // intersection is in middle segment l15 = lT + distance(jointT(m), p15); @@ -599,22 +525,16 @@ export function cahillKeyesRaw(mg) { } l15 = lT - distance(jointT(m), p15); } - if (p <= 15) { - xy = zoneK(m, p, l15); - } else { - //zone (l) - xy = zoneL(m, p, l15); - } + return (p <= 15) ? zoneK(m, p, l15) : zoneL(m, p, l15); } } } - return xy; } // from half-octant to megamap (single rotated octant) function mj2g(xy, octant) { - var xynew = [0, 0]; + let xynew; if (octant === 0) { xynew = rotate(xy, -60); @@ -642,14 +562,7 @@ export function cahillKeyesRaw(mg) { } else if (octant === 8) { xynew = rotate([2 * CK.lengthMG - xy[0], xy[1]], -120); xynew[0] += CK.lengthMG; - } else { - // TODO trap this some way. - // ERROR! - // print "Error converting to M-map coordinates; there is no Octant octant!\n"; - //console.log("mj2g: something weird happened!"); - return xynew; } - return xynew; } @@ -657,17 +570,15 @@ export function cahillKeyesRaw(mg) { function forward(lambda, phi) { // lambda, phi are in radians. - var lon = lambda * degrees, + const lon = lambda * degrees, lat = phi * degrees, res = ll2mp(lon, lat), m = res[0], // 0 ≤ m ≤ 45 p = res[1], // 0 ≤ p ≤ 90 s = res[2], // -1 / 1 = side of m o = res[3], // octant - xy = mp2xy(m, p), - mm = mj2g([xy[0], s * xy[1]], o); - - return mm; + xy = mp2xy(m, p); + return mj2g([xy[0], s * xy[1]], o); } forward.invert = solve2d(forward); @@ -676,7 +587,7 @@ export function cahillKeyesRaw(mg) { } function cahillKeyesProjection() { - var mg = 10000, - m = projectionMutator(cahillKeyesRaw); + const mg = 10000; + const m = projectionMutator(cahillKeyesRaw); return m(mg); } diff --git a/src/cartesian.js b/src/cartesian.js index a28980a..acaac73 100644 --- a/src/cartesian.js +++ b/src/cartesian.js @@ -1,16 +1,16 @@ -import {asin, atan2, cos, degrees, epsilon2, radians, sin, sqrt} from "./math.js"; +import {asin, atan2, cos, degrees, epsilon2, radians, sin, hypot} from "./math.js"; export function spherical(cartesian) { return [atan2(cartesian[1], cartesian[0]), asin(cartesian[2])]; } export function sphericalDegrees(cartesian) { - var c = spherical(cartesian); + const c = spherical(cartesian); return [c[0] * degrees, c[1] * degrees]; } export function cartesian(spherical) { - var lambda = spherical[0], phi = spherical[1], cosPhi = cos(phi); + const lambda = spherical[0], phi = spherical[1], cosPhi = cos(phi); return [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)]; } @@ -37,13 +37,13 @@ export function cartesianScale(vector, k) { // TODO return d export function cartesianNormalizeInPlace(d) { - var l = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + const l = hypot(d[0], d[1], d[2]); d[0] /= l, d[1] /= l, d[2] /= l; } export function cartesianEqual(a, b) { - var dx = b[0] - a[0], - dy = b[1] - a[1], - dz = b[2] - a[2]; + const dx = b[0] - a[0]; + const dy = b[1] - a[1]; + const dz = b[2] - a[2]; return dx * dx + dy * dy + dz * dz < epsilon2 * epsilon2; } diff --git a/src/clip/buffer.js b/src/clip/buffer.js index f246f41..1bf6a2c 100644 --- a/src/clip/buffer.js +++ b/src/clip/buffer.js @@ -1,11 +1,11 @@ import noop from "../noop.js"; export default function() { - var lines = [], - line; + let lines = []; + let line; return { point: function(x, y, i, t) { - var point = [x, y]; + const point = [x, y]; // when called by clipPolygon, store index and t if (arguments.length > 2) { point.index = i; point.t = t; } line.push(point); @@ -18,7 +18,7 @@ export default function() { if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); }, result: function() { - var result = lines; + const result = lines; lines = []; line = null; return result; diff --git a/src/clip/index.js b/src/clip/index.js index c9b8195..1ea704d 100644 --- a/src/clip/index.js +++ b/src/clip/index.js @@ -8,18 +8,18 @@ export default function(pointVisible, clipLine, interpolate, start, sort) { if (typeof sort === "undefined") sort = compareIntersection; return function(sink) { - var line = clipLine(sink), - ringBuffer = clipBuffer(), - ringSink = clipLine(ringBuffer), - polygonStarted = false, + const line = clipLine(sink); + const ringBuffer = clipBuffer(); + const ringSink = clipLine(ringBuffer); + let polygonStarted = false, polygon, segments, ring; - var clip = { - point: point, - lineStart: lineStart, - lineEnd: lineEnd, + const clip = { + point, + lineStart, + lineEnd, polygonStart: function() { clip.point = pointRing; clip.lineStart = ringStart; @@ -32,7 +32,7 @@ export default function(pointVisible, clipLine, interpolate, start, sort) { clip.lineStart = lineStart; clip.lineEnd = lineEnd; segments = merge(segments); - var startInside = polygonContains(polygon, start); + const startInside = polygonContains(polygon, start); if (segments.length) { if (!polygonStarted) sink.polygonStart(), polygonStarted = true; clipRejoin(segments, sort, startInside, interpolate, sink); @@ -43,9 +43,7 @@ export default function(pointVisible, clipLine, interpolate, start, sort) { if (polygonStarted) sink.polygonEnd(), polygonStarted = false; segments = polygon = null; }, - sphere: function() { - interpolate(null, null, 1, sink); - } + sphere: () => interpolate(null, null, 1, sink) }; function point(lambda, phi) { @@ -80,11 +78,10 @@ export default function(pointVisible, clipLine, interpolate, start, sort) { pointRing(ring[0][0], ring[0][1], true); ringSink.lineEnd(); - var clean = ringSink.clean(), - ringSegments = ringBuffer.result(), - i, n = ringSegments.length, m, - segment, - point; + const clean = ringSink.clean(); + const ringSegments = ringBuffer.result(); + const n = ringSegments.length; + let m, segment, point; ring.pop(); polygon.push(ring); @@ -98,7 +95,7 @@ export default function(pointVisible, clipLine, interpolate, start, sort) { if ((m = segment.length - 1) > 0) { if (!polygonStarted) sink.polygonStart(), polygonStarted = true; sink.lineStart(); - for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]); + for (let i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]); sink.lineEnd(); } return; diff --git a/src/clip/polygon.js b/src/clip/polygon.js index 2bf132b..20c3b27 100644 --- a/src/clip/polygon.js +++ b/src/clip/polygon.js @@ -4,12 +4,12 @@ import {cartesian, cartesianCross, cartesianDot, cartesianEqual, spherical} from import {intersectCoincident, intersectPointOnLine, intersectSegment, intersect} from "../intersect.js"; import {default as polygonContains} from "../polygonContains.js"; -var clipNone = function(stream) { return stream; }; +const clipNone = function(stream) { return stream; }; // clipPolygon export default function(geometry) { function clipGeometry(geometry) { - var polygons; + let polygons; if (geometry.type === "MultiPolygon") { polygons = geometry.coordinates; @@ -19,10 +19,10 @@ export default function(geometry) { return clipNone; } - var clips = polygons.map(function(polygon) { + const clips = polygons.map((polygon) => { polygon = polygon.map(ringRadians); - var pointVisible = visible(polygon), - segments = ringSegments(polygon[0]); // todo holes? + const pointVisible = visible(polygon); + const segments = ringSegments(polygon[0]); // todo holes? return clip( pointVisible, clipLine(segments, pointVisible), @@ -32,25 +32,25 @@ export default function(geometry) { ); }); - var clipPolygon = function(stream) { - var clipstream = clips.map(clip => clip(stream)); + const clipPolygon = function(stream) { + const clipstream = clips.map(clip => clip(stream)); return { - point: function(lambda, phi) { + point: (lambda, phi) => { clipstream.forEach(clip => clip.point(lambda, phi)); }, - lineStart: function() { + lineStart: () => { clipstream.forEach(clip => clip.lineStart()); }, - lineEnd: function() { + lineEnd: () => { clipstream.forEach(clip => clip.lineEnd()); }, - polygonStart: function() { + polygonStart: () => { clipstream.forEach(clip => clip.polygonStart()); }, - polygonEnd: function() { + polygonEnd: () => { clipstream.forEach(clip => clip.polygonEnd()); }, - sphere: function() { + sphere: () => { clipstream.forEach(clip => clip.sphere()); } }; @@ -66,17 +66,14 @@ export default function(geometry) { } function ringRadians(ring) { - return ring.map(function(point) { - return [point[0] * radians, point[1] * radians]; - }); + return ring.map((point) => [point[0] * radians, point[1] * radians]); } function ringSegments(ring) { - var c, - c0, - segments = []; - ring.forEach(function(point, i) { - c = cartesian(point); + const segments = []; + let c0; + ring.forEach((point, i) => { + const c = cartesian(point); if (i) segments.push(new intersectSegment(c0, c)); c0 = c; return point; @@ -90,14 +87,12 @@ function clipPolygonSort(a, b) { } function interpolate(segments, polygon) { - return function(from, to, direction, stream) { + return (from, to, direction, stream) => { if (from == null) { stream.polygonStart(); - polygon.forEach(function(ring) { + polygon.forEach((ring) => { stream.lineStart(); - ring.forEach(function(point) { - stream.point(point[0], point[1]); - }); + ring.forEach((point) => stream.point(point[0], point[1])); stream.lineEnd(); }); stream.polygonEnd(); @@ -107,12 +102,12 @@ function interpolate(segments, polygon) { to.index != null ) { for ( - var i = from.index; + let i = from.index; i !== to.index; i = (i + direction + segments.length) % segments.length ) { - var segment = segments[i], - point = spherical(direction > 0 ? segment.to : segment.from); + const segment = segments[i]; + const point = spherical(direction > 0 ? segment.to : segment.from); stream.point(point[0], point[1]); } } else if ( @@ -121,13 +116,9 @@ function interpolate(segments, polygon) { from.index != null && to.index != null ) { - for ( - i = 0; - i < segments.length; - i++ - ) { - segment = segments[(from.index + i * direction + segments.length)%segments.length], - point = spherical(direction > 0 ? segment.to : segment.from); + for (let i = 0; i < segments.length; ++i) { + const segment = segments[(from.index + i * direction + segments.length)%segments.length]; + const point = spherical(direction > 0 ? segment.to : segment.from); stream.point(point[0], point[1]); } } @@ -136,14 +127,12 @@ function interpolate(segments, polygon) { // Geodesic coordinates for two 3D points. function clipPolygonDistance(a, b) { - var axb = cartesianCross(a, b); + const axb = cartesianCross(a, b); return atan2(sqrt(cartesianDot(axb, axb)), cartesianDot(a, b)); } function visible(polygon) { - return function(lambda, phi) { - return polygonContains(polygon, [lambda, phi]); - }; + return (lambda, phi) => polygonContains(polygon, [lambda, phi]); } function randsign(i, j) { @@ -152,7 +141,7 @@ function randsign(i, j) { function clipLine(segments, pointVisible) { return function(stream) { - var point0, lambda00, phi00, v00, v0, clean, line, lines = []; + let point0, lambda00, phi00, v00, v0, clean, line, lines = []; return { lineStart: function() { point0 = null; @@ -161,31 +150,24 @@ function clipLine(segments, pointVisible) { }, lineEnd: function() { if (v0) lines.push(line); - lines.forEach(function(line) { + lines.forEach((line) => { stream.lineStart(); - line.forEach(function(point) { - stream.point(...point); // can have 4 dimensions - }); + line.forEach((point) => stream.point(...point)); // can have 4 dimensions stream.lineEnd(); }); lines = []; }, - point: function(lambda, phi, close) { + point: (lambda, phi, close) => { if (cos(lambda) == -1) lambda -= sign(sin(lambda)) * 1e-5; // move away from -180/180 https://github.com/d3/d3-geo/pull/108#issuecomment-323798937 if (close) (lambda = lambda00), (phi = phi00); - var point = cartesian([lambda, phi]), - v = v0, - intersection, - i, - j, - s, - t; + let point = cartesian([lambda, phi]); + let v = v0; if (point0) { - var segment = new intersectSegment(point0, point), - intersections = []; - for (i = 0, j = 100; i < segments.length && j > 0; ++i) { - s = segments[i]; - intersection = intersect(segment, s); + const intersections = []; + let segment = new intersectSegment(point0, point); + for (let i = 0, j = 100; i < segments.length && j > 0; ++i) { + const s = segments[i]; + const intersection = intersect(segment, s); if (intersection) { if ( intersection === intersectCoincident || @@ -194,22 +176,15 @@ function clipLine(segments, pointVisible) { cartesianEqual(intersection, s.from) || cartesianEqual(intersection, s.to) ) { - t = 1e-4; - lambda = - ((lambda + 3 * pi + randsign(i, j) * t) % (2 * pi)) - pi; - phi = min( - pi / 2 - 1e-4, - max(1e-4 - pi / 2, phi + randsign(i, j) * t) - ); - segment = new intersectSegment( - point0, - (point = cartesian([lambda, phi])) - ); + const t = 1e-4; + lambda = ((lambda + 3 * pi + randsign(i, j) * t) % (2 * pi)) - pi; + phi = min(pi / 2 - 1e-4, max(1e-4 - pi / 2, phi + randsign(i, j) * t)); + segment = new intersectSegment(point0, (point = cartesian([lambda, phi]))); (i = -1), --j; intersections.length = 0; continue; } - var sph = spherical(intersection); + const sph = spherical(intersection); intersection.distance = clipPolygonDistance(point0, intersection); intersection.index = i; intersection.t = clipPolygonDistance(s.from, intersection); @@ -221,11 +196,9 @@ function clipLine(segments, pointVisible) { } if (intersections.length) { clean = 0; - intersections.sort(function(a, b) { - return a.distance - b.distance; - }); - for (i = 0; i < intersections.length; ++i) { - intersection = intersections[i]; + intersections.sort((a, b) => a.distance - b.distance); + for (let i = 0; i < intersections.length; ++i) { + const intersection = intersections[i]; v = !v; if (v) { line = []; @@ -248,10 +221,10 @@ function clipLine(segments, pointVisible) { } if (v) line.push([lambda, phi]); } else { - for (i = 0, j = 100; i < segments.length && j > 0; ++i) { - s = segments[i]; + for (let i = 0, j = 100; i < segments.length && j > 0; ++i) { + const s = segments[i]; if (intersectPointOnLine(point, s)) { - t = 1e-4; + const t = 1e-4; lambda = ((lambda + 3 * pi + randsign(i, j) * t) % (2 * pi)) - pi; phi = min( pi / 2 - 1e-4, @@ -268,9 +241,7 @@ function clipLine(segments, pointVisible) { }, // Rejoin first and last segments if there were intersections and the first // and last points were visible. - clean: function() { - return clean | ((v00 && v0) << 1); - } + clean: () => clean | ((v00 && v0) << 1) }; }; } diff --git a/src/clip/rejoin.js b/src/clip/rejoin.js index 645fbe3..a7cbba4 100644 --- a/src/clip/rejoin.js +++ b/src/clip/rejoin.js @@ -13,25 +13,26 @@ function Intersection(point, points, other, entry) { // into its visible line segments, and rejoins the segments by interpolating // along the clip edge. export default function(segments, compareIntersection, startInside, interpolate, stream) { - var subject = [], - clip = [], - i, - n; + const subject = []; + const clip = []; - segments.forEach(function(segment) { + segments.forEach((segment) => { + let n; if ((n = segment.length - 1) <= 0) return; - var n, p0 = segment[0], p1 = segment[n], x; - + let p0 = segment[0]; + const p1 = segment[n]; + // If the first and last points of a segment are coincident, then treat as a // closed ring. TODO if all rings are closed, then the winding order of the // exterior ring should be checked. if (pointEqual(p0, p1)) { stream.lineStart(); - for (i = 0; i < n; ++i) stream.point((p0 = segment[i])[0], p0[1]); + for (let i = 0; i < n; ++i) stream.point((p0 = segment[i])[0], p0[1]); stream.lineEnd(); return; } - + + let x; subject.push(x = new Intersection(p0, segment, null, true)); clip.push(x.o = new Intersection(p0, null, x, false)); subject.push(x = new Intersection(p1, segment, null, false)); @@ -44,17 +45,17 @@ export default function(segments, compareIntersection, startInside, interpolate, link(subject); link(clip); - for (i = 0, n = clip.length; i < n; ++i) { + for (let i = 0, n = clip.length; i < n; ++i) { clip[i].e = startInside = !startInside; } - var start = subject[0], + let start = subject[0], points, point; while (1) { // Find first unvisited intersection. - var current = start, + let current = start, isSubject = true; while (current.v) if ((current = current.n) === start) return; points = current.z; @@ -63,7 +64,7 @@ export default function(segments, compareIntersection, startInside, interpolate, current.v = current.o.v = true; if (current.e) { if (isSubject) { - for (i = 0, n = points.length; i < n; ++i) stream.point((point = points[i])[0], point[1]); + for (let i = 0, n = points.length; i < n; ++i) stream.point((point = points[i])[0], point[1]); } else { interpolate(current.x, current.n.x, 1, stream); } @@ -71,7 +72,7 @@ export default function(segments, compareIntersection, startInside, interpolate, } else { if (isSubject) { points = current.p.z; - for (i = points.length - 1; i >= 0; --i) stream.point((point = points[i])[0], point[1]); + for (let i = points.length - 1; i >= 0; --i) stream.point((point = points[i])[0], point[1]); } else { interpolate(current.x, current.p.x, -1, stream); } @@ -86,9 +87,9 @@ export default function(segments, compareIntersection, startInside, interpolate, } function link(array) { - if (!(n = array.length)) return; - var n, - i = 0, + const n = array.length; + if (!n) return; + let i = 0, a = array[0], b; while (++i < n) { diff --git a/src/collignon.js b/src/collignon.js index 530c900..57f322f 100644 --- a/src/collignon.js +++ b/src/collignon.js @@ -2,11 +2,11 @@ import {asin, pi, sin, sqrt, sqrtPi} from "./math.js"; export function collignonRaw(lambda, phi) { - var alpha = sqrt(1 - sin(phi)); + const alpha = sqrt(1 - sin(phi)); return [(2 / sqrtPi) * lambda * alpha, sqrtPi * (1 - alpha)]; } collignonRaw.invert = function(x, y) { - var lambda = (lambda = y / sqrtPi - 1) * lambda; - return [lambda > 0 ? x * sqrt(pi / lambda) / 2 : 0, asin(1 - lambda)]; + const lambda = (y / sqrtPi - 1); + return [lambda ? x * sqrt(pi) / lambda / 2 : 0, asin(1 - lambda ** 2)]; }; diff --git a/src/complex.js b/src/complex.js index f821b6f..62356c4 100644 --- a/src/complex.js +++ b/src/complex.js @@ -1,7 +1,7 @@ import { abs, atan2, cos, exp, halfPi, log, pow, sin, sqrt } from "./math.js"; export function complexAtan(x, y) { - var x2 = x * x, + const x2 = x * x, y_1 = y + 1, t = 1 - x2 - y * y; return [ @@ -37,8 +37,8 @@ export function complexNorm(a) { } export function complexLogHypot(a, b) { - var _a = abs(a), - _b = abs(b); + const _a = abs(a); + const _b = abs(b); if (a === 0) return log(_b); if (b === 0) return log(_a); if (_a < 3000 && _b < 3000) return log(a * a + b * b) * 0.5; @@ -47,7 +47,7 @@ export function complexLogHypot(a, b) { // adapted from https://github.com/infusion/Complex.js export function complexPow(a, n) { - var b = a[1], + let b = a[1], arg, loh; a = a[0]; diff --git a/src/complexLog.js b/src/complexLog.js index f8ca625..81c383d 100644 --- a/src/complexLog.js +++ b/src/complexLog.js @@ -15,14 +15,14 @@ import { complexMul, complexLogHypot } from "./complex.js"; import { default as clipPolygon } from "./clip/polygon.js"; // Default planar projection and cutoff latitude, see below for an explanation of these settings. -var DEFAULT_PLANAR_PROJECTION_RAW = azimuthalEqualAreaRaw; -var DEFAULT_CUTOFF_LATITUDE = -0.05; +const DEFAULT_PLANAR_PROJECTION_RAW = azimuthalEqualAreaRaw; +const DEFAULT_CUTOFF_LATITUDE = -0.05; // Offset used to prevent logarithm of 0. -var CARTESIAN_OFFSET = 1e-10; +const CARTESIAN_OFFSET = 1e-10; // Projection parameters for the default 960x500 projection area. -var DEFAULT_PROJECTION_PARAMS = { +const DEFAULT_PROJECTION_PARAMS = { angle: 90, center: [0, 5.022570623227068], scale: 79.92959180396787, @@ -31,7 +31,7 @@ var DEFAULT_PROJECTION_PARAMS = { // Vertices of the clipping polygon in spherical coordinates. // It contains the whole world except a small strip along longitude 0/180 crossing the south pole. -var CLIP_POLY_SPHERICAL = [ +const CLIP_POLY_SPHERICAL = [ [-180, -1e-4], [180, -1e-4], [1e-4, DEFAULT_CUTOFF_LATITUDE], @@ -39,18 +39,18 @@ var CLIP_POLY_SPHERICAL = [ ] // Clipping polygon precision. -var N_SIDE = 5; -var N_BOTTOM = 50; +const N_SIDE = 5; +const N_BOTTOM = 50; export function complexLogRaw(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW) { function forward(lambda, phi) { // Project on plane. // Interpret projected point on complex plane. - var aziComp = planarProjectionRaw(lambda, phi); + const azi1 = planarProjectionRaw(lambda, phi); // Rotate by -90 degrees in complex plane so the following complex log projection will be horizontally centered - aziComp = complexMul(aziComp, [cos(-pi / 2), sin(-pi / 2)]); + const aziComp = complexMul(azi1, [cos(-pi / 2), sin(-pi / 2)]); // Small offset to prevent logarithm of 0. if (aziComp[0] == 0 && aziComp[1] == 0) { @@ -59,17 +59,15 @@ export function complexLogRaw(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RA } // Apply complex logarithm. - var logComp = [complexLogHypot(aziComp[0], aziComp[1]), atan2(aziComp[1], aziComp[0])]; - - return logComp; + return [complexLogHypot(aziComp[0], aziComp[1]), atan2(aziComp[1], aziComp[0])]; } function invert(x, y) { // Inverse complex logarithm (complex exponential function). - var invLogComp = [exp(x) * cos(y), exp(x) * sin(y)]; + const inv1 = [exp(x) * cos(y), exp(x) * sin(y)]; // Undo rotation. - invLogComp = complexMul(invLogComp, [cos(pi / 2), sin(pi / 2)]); + const invLogComp = complexMul(inv1, [cos(pi / 2), sin(pi / 2)]); // Invert azimuthal equal area. return planarProjectionRaw.invert(invLogComp[0], invLogComp[1]); @@ -81,8 +79,8 @@ export function complexLogRaw(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RA export default function(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW, cutoffLatitude = DEFAULT_CUTOFF_LATITUDE) { - var mutator = projectionMutator(complexLogRaw); - var projection = mutator(planarProjectionRaw); + const mutator = projectionMutator(complexLogRaw); + const projection = mutator(planarProjectionRaw); // Projection used to project onto the complex plane. projection.planarProjectionRaw = function(_) { @@ -96,11 +94,11 @@ export default function(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW, cut } function clipped(projection) { - var angle = projection.angle(); - var scale = projection.scale(); - var center = projection.center(); - var translate = projection.translate(); - var rotate = projection.rotate(); + const angle = projection.angle(); + const scale = projection.scale(); + const center = projection.center(); + const translate = projection.translate(); + const rotate = projection.rotate(); projection .angle(DEFAULT_PROJECTION_PARAMS.angle) @@ -111,12 +109,12 @@ export default function(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW, cut .preclip(); // These are corner vertices of a rectangle in the projected complex log view. - var topLeft = projection(CLIP_POLY_SPHERICAL[0]); - var topRight = projection(CLIP_POLY_SPHERICAL[1]); - var bottomRight = projection([CLIP_POLY_SPHERICAL[2][0], cutoffLatitude]); - var bottomLeft = projection([CLIP_POLY_SPHERICAL[3][0], cutoffLatitude]); - var width = abs(topRight[0] - topLeft[0]); - var height = abs(bottomRight[1] - topRight[1]); + const topLeft = projection(CLIP_POLY_SPHERICAL[0]); + const topRight = projection(CLIP_POLY_SPHERICAL[1]); + const bottomRight = projection([CLIP_POLY_SPHERICAL[2][0], cutoffLatitude]); + const bottomLeft = projection([CLIP_POLY_SPHERICAL[3][0], cutoffLatitude]); + const width = abs(topRight[0] - topLeft[0]); + const height = abs(bottomRight[1] - topRight[1]); // Prevent overlapping polygons that result from paths that go from one side to the other, // so cut along 180°/-180° degree line (left and right in complex log projected view). @@ -137,7 +135,7 @@ export default function(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW, cut // N_BOTTOM determines how many vertices to insert along the bottom (marked as - above). // // The resulting polygon vertices are back-projected to spherical coordinates. - var polygon = { + const polygon = { type: "Polygon", coordinates: [ [ diff --git a/src/cox.js b/src/cox.js index 862b94b..38b400f 100644 --- a/src/cox.js +++ b/src/cox.js @@ -5,18 +5,16 @@ import { lagrangeRaw } from "./lagrange.js"; import { complexAdd, complexMul, complexNorm2, complexPow } from "./complex.js"; // w1 = gamma(1/n) * gamma(1 - 2/n) / n / gamma(1 - 1/n) -// https://bl.ocks.org/Fil/852557838117687bbd985e4b38ff77d4 -var w = [-1 / 2, sqrt(3) / 2], +// https://blocks.roadtolarissa.com/Fil/852557838117687bbd985e4b38ff77d4 +const w = [-1 / 2, sqrt(3) / 2], w1 = [1.7666387502854533, 0], m = 0.3 * 0.3; // Approximate \int _0 ^sm(z) dt / (1 - t^3)^(2/3) // sm maps a triangle to a disc, sm^-1 does the opposite function sm_1(z) { - var k = [0, 0]; - // rotate to have s ~= 1 - var rot = complexPow( + const rot = complexPow( w, scan( [0, 1, 2].map(function(i) { @@ -25,11 +23,11 @@ function sm_1(z) { ) ); - var y = complexMul(rot, z); + let y = complexMul(rot, z); y = [1 - y[0], -y[1]]; // McIlroy formula 5 p6 and table for F3 page 16 - var F0 = [ + const F0 = [ 1.44224957030741, 0.240374928384568, 0.0686785509670194, @@ -52,10 +50,10 @@ function sm_1(z) { -3.92135372833465e-7 ]; - var F = [0, 0]; - for (var i = F0.length; i--; ) F = complexAdd([F0[i], 0], complexMul(F, y)); + let F = [0, 0]; + for (let i = F0.length; i--; ) F = complexAdd([F0[i], 0], complexMul(F, y)); - k = complexMul( + let k = complexMul( complexAdd(w1, complexMul([-F[0], -F[1]], complexPow(y, 1 - 2 / 3))), complexMul(rot, rot) ); @@ -64,18 +62,18 @@ function sm_1(z) { // https://www.wolframalpha.com/input/?i=(-2%2F3+choose+k)++*+(-1)%5Ek++%2F+(k%2B1)+with+k%3D0,1,2,3,4 // the difference is _very_ tiny but necessary // if we want projection(0,0) === [0,0] - var n = complexNorm2(z); + const n = complexNorm2(z); if (n < m) { - var H0 = [ + const H0 = [ 1, 1 / 3, 5 / 27, 10 / 81, 22 / 243 //… ]; - var z3 = complexPow(z, [3, 0]); - var h = [0, 0]; - for (i = H0.length; i--; ) h = complexAdd([H0[i], 0], complexMul(h, z3)); + const z3 = complexPow(z, [3, 0]); + let h = [0, 0]; + for (let i = H0.length; i--; ) h = complexAdd([H0[i], 0], complexMul(h, z3)); h = complexMul(h, z); k = complexAdd(complexMul(k, [n / m, 0]), complexMul(h, [1 - n / m, 0])); } @@ -83,17 +81,17 @@ function sm_1(z) { return k; } -var lagrange1_2 = lagrangeRaw ? lagrangeRaw(0.5) : null; +const lagrange1_2 = lagrangeRaw ? lagrangeRaw(0.5) : null; export function coxRaw(lambda, phi) { - var s = lagrange1_2(lambda, phi); - var t = sm_1([s[1] / 2, s[0] / 2]); + const s = lagrange1_2(lambda, phi); + const t = sm_1([s[1] / 2, s[0] / 2]); return [t[1], t[0]]; } // the Sphere should go *exactly* to the vertices of the triangles // because they are singular points function sphere() { - var c = 2 * asin(1 / sqrt(5)) * degrees; + const c = 2 * asin(1 / sqrt(5)) * degrees; return { type: "Polygon", coordinates: [ @@ -103,11 +101,11 @@ function sphere() { } export default function() { - var p = projection(coxRaw); + const p = projection(coxRaw); - var stream_ = p.stream; + const stream_ = p.stream; p.stream = function(stream) { - var rotate = p.rotate(), + const rotate = p.rotate(), rotateStream = stream_(stream), sphereStream = (p.rotate([0, 0]), stream_(stream)); p.rotate(rotate); diff --git a/src/cubic.js b/src/cubic.js index 5ed9da6..61122c6 100644 --- a/src/cubic.js +++ b/src/cubic.js @@ -8,21 +8,18 @@ import voronoi from "./polyhedral/voronoi.js"; import { default as cube } from "./polyhedral/cube.js"; export default function() { - var polygons = { + const polygons = { type: "FeatureCollection", - features: cube.map(function(face) { - face = face.slice(); - face.push(face[0]); - return { - geometry: { - type: "Polygon", - coordinates: [face] - } - }; - }) + features: cube.map((face) => ({ + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [[...face, face[0]]] + } + })) }; - var parents = [-1, 0, 1, 5, 3, 2]; + const parents = [-1, 0, 1, 5, 3, 2]; return voronoi() .polygons(polygons) diff --git a/src/grayfuller.js b/src/grayfuller.js index 46954d5..da3edd5 100644 --- a/src/grayfuller.js +++ b/src/grayfuller.js @@ -13,15 +13,15 @@ import { abs, atan2, cos, epsilon, sin, sqrt } from "./math.js"; import { geoGnomonicRaw as gnomonicRaw } from "d3-geo"; export default function GrayFullerRaw() { - var SQRT_3 = sqrt(3); + const SQRT_3 = sqrt(3); // Gray’s constants - var Z = sqrt(5 + 2 * sqrt(5)) / sqrt(15), - el = sqrt(8) / sqrt(5 + sqrt(5)), - dve = sqrt(3 + sqrt(5)) / sqrt(5 + sqrt(5)); + const Z = sqrt(5 + 2 * sqrt(5)) / sqrt(15); + const el = sqrt(8) / sqrt(5 + sqrt(5)); + const dve = sqrt(3 + sqrt(5)) / sqrt(5 + sqrt(5)); - var grayfuller = function(lambda, phi) { - var cosPhi = cos(phi), + const grayfuller = function(lambda, phi) { + const cosPhi = cos(phi), s = Z / (cosPhi * cos(lambda)), x = cosPhi * sin(lambda) * s, y = sin(phi) * s, @@ -38,14 +38,14 @@ export default function GrayFullerRaw() { // something meaningless (but far away enough) if (x * x + y * y > 5) return [0, 3]; - var R = 2.9309936378128416, - p = gnomonicRaw.invert(x / R, y / R); + const R = 2.9309936378128416; + const p = gnomonicRaw.invert(x / R, y / R); - var j = 0; + let j = 0, dx, dy; do { - var f = grayfuller(p[0], p[1]), - dx = x - f[0], - dy = y - f[1]; + const f = grayfuller(p[0], p[1]); + dx = x - f[0], + dy = y - f[1]; p[0] += 0.2 * dx; p[1] += 0.2 * dy; } while (j++ < 30 && abs(dx) + abs(dy) > epsilon); diff --git a/src/icosahedral.js b/src/icosahedral.js index da5cd3c..2978f3b 100644 --- a/src/icosahedral.js +++ b/src/icosahedral.js @@ -10,19 +10,16 @@ import voronoi from "./polyhedral/voronoi.js"; export default function() { - var theta = atan(0.5) * degrees; + const theta = atan(0.5) * degrees; // construction inspired by // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates - var vertices = [[0, 90], [0, -90]].concat( - [0,1,2,3,4,5,6,7,8,9].map(function(i) { - var phi = (i * 36 + 180) % 360 - 180; - return [phi, i & 1 ? theta : -theta]; - }) + const vertices = [[0, 90], [0, -90]].concat( + [0,1,2,3,4,5,6,7,8,9].map((i) => [(i * 36 + 180) % 360 - 180, i & 1 ? theta : -theta]) ); // icosahedron - var polyhedron = [ + const polyhedron = [ [0, 3, 11], [0, 5, 3], [0, 7, 5], @@ -43,26 +40,20 @@ export default function() { [1, 6, 8], [1, 8, 10], [1, 10, 2] // South - ].map(function(face) { - return face.map(function(i) { - return vertices[i]; - }); - }); + ].map((face) => face.map((i) => vertices[i])); - var polygons = { + const polygons = { type: "FeatureCollection", - features: polyhedron.map(function(face) { - face.push(face[0]); - return { - geometry: { - type: "Polygon", - coordinates: [ face ] - } - }; - }) + features: polyhedron.map((face) => ({ + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [[...face, face[0]]] + } + })) }; -var parents = [ +const parents = [ // N -1, // 0 7, // 1 @@ -92,13 +83,12 @@ var parents = [ ]; return voronoi() - .parents(parents) - .angle(0) - .polygons(polygons) - .rotate([108,0]) - .scale(131.777) + .parents(parents) + .polygons(polygons) + .rotate([108,0]) + .scale(131.777) .center([162, 0]); - } +} /* diff --git a/src/imago.js b/src/imago.js index 809549f..5a7d8cf 100644 --- a/src/imago.js +++ b/src/imago.js @@ -15,6 +15,7 @@ import { epsilon, floor, halfPi, + hypot, pi, pow, sign, @@ -26,9 +27,7 @@ import { geoProjectionMutator as projectionMutator } from "d3-geo"; import { default as clipPolygon } from "./clip/polygon.js"; import { solve } from "./newton.js"; -var hypot = Math.hypot; - -var ASIN_ONE_THD = asin(1 / 3), +const ASIN_ONE_THD = asin(1 / 3), centrums = [ [halfPi, 0, 0, -halfPi, 0, sqrt(3)], [-ASIN_ONE_THD, 0, pi, halfPi, 0, -sqrt(3)], @@ -217,15 +216,15 @@ export function imagoRaw(k) { } export function imagoBlock() { - var k = 0.68, - m = projectionMutator(imagoRaw), - p = m(k); + let k = 0.68; + const m = projectionMutator(imagoRaw); + const p = m(k); p.k = function (_) { return arguments.length ? m((k = +_)) : k; }; - var a = -atan(1 / sqrt(2)) * degrees, + const a = -atan(1 / sqrt(2)) * degrees, border = [ [-180 + epsilon, a + epsilon], [0, 90], @@ -248,7 +247,7 @@ export function imagoBlock() { } function imagoWideRaw(k, shift) { - var imago = imagoRaw(k); + const imago = imagoRaw(k); const height = configuration.height; function forward(lon, lat) { @@ -269,9 +268,7 @@ function imagoWideRaw(k, shift) { function invert(x, y) { x = (x - shift) / height; - if (x > 1.5) { - x -= 2; - } + if (x > 1.5) x -= 2; if (x > 0.5) { x = 1 - x; @@ -286,10 +283,10 @@ function imagoWideRaw(k, shift) { } export default function () { - var k = 0.59, - shift = 1.16, - m = projectionMutator(imagoWideRaw), - p = m(k, shift); + let k = 0.59; + let shift = 1.16; + const m = projectionMutator(imagoWideRaw); + const p = m(k, shift); p.shift = function (_) { return arguments.length ? clipped(m(k, (shift = +_))) : shift; diff --git a/src/intersect.js b/src/intersect.js index 5e1b8c7..f68f716 100644 --- a/src/intersect.js +++ b/src/intersect.js @@ -16,20 +16,20 @@ export function intersect(a, b) { if (cartesianEqual(a.to, b.from) || cartesianEqual(a.to, b.to)) return a.to; - var lc = (a.l + b.l < pi) ? cos(a.l + b.l) - epsilon : -1; + const lc = (a.l + b.l < pi) ? cos(a.l + b.l) - epsilon : -1; if (cartesianDot(a.from, b.from) < lc || cartesianDot(a.from, b.to) < lc || cartesianDot(a.to, b.from) < lc || cartesianDot(a.to, b.to) < lc) return; - var axb = cartesianCross(a.normal, b.normal); + const axb = cartesianCross(a.normal, b.normal); cartesianNormalizeInPlace(axb); - var a0 = cartesianDot(axb, a.fromNormal), - a1 = cartesianDot(axb, a.toNormal), - b0 = cartesianDot(axb, b.fromNormal), - b1 = cartesianDot(axb, b.toNormal); + let a0 = cartesianDot(axb, a.fromNormal); + let a1 = cartesianDot(axb, a.toNormal); + let b0 = cartesianDot(axb, b.fromNormal); + let b1 = cartesianDot(axb, b.toNormal); // check if the candidate lies on both segments // or is almost equal to one of the four points @@ -70,21 +70,20 @@ export function intersect(a, b) { } export function intersectPointOnLine(p, a) { - var a0 = cartesianDot(p, a.fromNormal), - a1 = cartesianDot(p, a.toNormal); + const a0 = cartesianDot(p, a.fromNormal); + const a1 = cartesianDot(p, a.toNormal); p = cartesianDot(p, a.normal); - return abs(p) < epsilon2 && (a0 > -epsilon2 && a1 < epsilon2 || a0 < epsilon2 && a1 > -epsilon2); } -export var intersectCoincident = {}; +export const intersectCoincident = {}; export default function(a, b) { - var ca = a.map(p => cartesian(p.map(d => d * radians))), - cb = b.map(p => cartesian(p.map(d => d * radians))); - var i = intersect( + const ca = a.map(p => cartesian(p.map(d => d * radians))); + const cb = b.map(p => cartesian(p.map(d => d * radians))); + const i = intersect( new intersectSegment(ca[0], ca[1]), new intersectSegment(cb[0], cb[1]) ); - return !i ? i : spherical(i).map(d => d * degrees); + return i ? spherical(i).map((d) => d * degrees) : null; } diff --git a/src/lagrange.js b/src/lagrange.js index ab50f1f..2661be2 100644 --- a/src/lagrange.js +++ b/src/lagrange.js @@ -5,24 +5,21 @@ export function lagrangeRaw(n) { function forward(lambda, phi) { if (abs(abs(phi) - halfPi) < epsilon) return [0, phi < 0 ? -2 : 2]; - var sinPhi = sin(phi), - v = pow((1 + sinPhi) / (1 - sinPhi), n / 2), - c = 0.5 * (v + 1 / v) + cos(lambda *= n); - return [ - 2 * sin(lambda) / c, - (v - 1 / v) / c - ]; + const sinPhi = sin(phi); + const v = pow((1 + sinPhi) / (1 - sinPhi), n / 2); + const c = 0.5 * (v + 1 / v) + cos(lambda *= n); + return [2 * sin(lambda) / c, (v - 1 / v) / c]; } - forward.invert = function(x, y) { - var y0 = abs(y); + forward.invert = (x, y) => { + const y0 = abs(y); if (abs(y0 - 2) < epsilon) return x ? null : [0, sign(y) * halfPi]; if (y0 > 2) return null; x /= 2, y /= 2; - var x2 = x * x, - y2 = y * y, - t = 2 * y / (1 + x2 + y2); // tanh(nPhi) + const x2 = x * x; + const y2 = y * y; + let t = 2 * y / (1 + x2 + y2); // tanh(nPhi) t = pow((1 + t) / (1 - t), 1 / n); return [ atan2(2 * x, 1 - x2 - y2) / n, diff --git a/src/math.js b/src/math.js index 2d82df3..be621ef 100644 --- a/src/math.js +++ b/src/math.js @@ -1,33 +1,34 @@ -export var abs = Math.abs; -export var atan = Math.atan; -export var atan2 = Math.atan2; -export var ceil = Math.ceil; -export var cos = Math.cos; -export var exp = Math.exp; -export var floor = Math.floor; -export var log = Math.log; -export var max = Math.max; -export var min = Math.min; -export var pow = Math.pow; -export var round = Math.round; -export var sign = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }; -export var sin = Math.sin; -export var tan = Math.tan; +export const abs = Math.abs; +export const atan = Math.atan; +export const atan2 = Math.atan2; +export const ceil = Math.ceil; +export const cos = Math.cos; +export const exp = Math.exp; +export const floor = Math.floor; +export const hypot = Math.hypot; +export const log = Math.log; +export const max = Math.max; +export const min = Math.min; +export const pow = Math.pow; +export const round = Math.round; +export const sign = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }; +export const sin = Math.sin; +export const tan = Math.tan; -export var epsilon = 1e-6; -export var epsilon2 = 1e-12; -export var pi = Math.PI; -export var halfPi = pi / 2; -export var quarterPi = pi / 4; -export var sqrt1_2 = Math.SQRT1_2; -export var sqrt2 = sqrt(2); -export var sqrtPi = sqrt(pi); -export var tau = pi * 2; -export var degrees = 180 / pi; -export var radians = pi / 180; +export const epsilon = 1e-6; +export const epsilon2 = 1e-12; +export const pi = Math.PI; +export const halfPi = pi / 2; +export const quarterPi = pi / 4; +export const sqrt1_2 = Math.SQRT1_2; +export const sqrt2 = sqrt(2); +export const sqrtPi = sqrt(pi); +export const tau = pi * 2; +export const degrees = 180 / pi; +export const radians = pi / 180; export function sinci(x) { - return x ? x / Math.sin(x) : 1; + return x ? x / sin(x) : 1; } export function asin(x) { diff --git a/src/newton.js b/src/newton.js index 9489def..e98f3e6 100644 --- a/src/newton.js +++ b/src/newton.js @@ -3,7 +3,7 @@ import {abs, epsilon, epsilon2} from "./math.js"; // Approximate Newton-Raphson // Solve f(x) = y, start from x export function solve(f, y, x) { - var steps = 100, delta, f0, f1; + let steps = 100, delta, f0, f1; x = x === undefined ? 0 : +x; y = +y; do { @@ -18,17 +18,17 @@ export function solve(f, y, x) { // Approximate Newton-Raphson in 2D // Solve f(a,b) = [x,y] export function solve2d(f, MAX_ITERATIONS = 40, eps = epsilon2) { - return function(x, y, a = 0, b = 0) { - var err2, da, db; - for (var i = 0; i < MAX_ITERATIONS; i++) { - var p = f(a, b), + return (x, y, a = 0, b = 0) => { + let err2, da, db; + for (let i = 0; i < MAX_ITERATIONS; ++i) { + const p = f(a, b), // diffs tx = p[0] - x, ty = p[1] - y; if (abs(tx) < eps && abs(ty) < eps) break; // we're there! // backtrack if we overshot - var h = tx * tx + ty * ty; + const h = tx * tx + ty * ty; if (h > err2) { a -= da /= 2; b -= db /= 2; @@ -37,7 +37,7 @@ export function solve2d(f, MAX_ITERATIONS = 40, eps = epsilon2) { err2 = h; // partial derivatives - var ea = (a > 0 ? -1 : 1) * eps, + const ea = (a > 0 ? -1 : 1) * eps, eb = (b > 0 ? -1 : 1) * eps, pa = f(a + ea, b), pb = f(a, b + eb), diff --git a/src/polygonContains.js b/src/polygonContains.js index 7bd1a64..45d6c9c 100644 --- a/src/polygonContains.js +++ b/src/polygonContains.js @@ -3,15 +3,15 @@ import {cartesian, cartesianCross, cartesianNormalizeInPlace} from "./cartesian. import {asin, atan2, cos, epsilon, pi, quarterPi, sin, tau} from "./math.js"; export default function(polygon, point) { - var lambda = point[0], - phi = point[1], - normal = [sin(lambda), -cos(lambda), 0], - angle = 0, - winding = 0; + const lambda = point[0]; + const phi = point[1]; + const normal = [sin(lambda), -cos(lambda), 0]; + let angle = 0; + let winding = 0; const sum = new Adder(); - for (var i = 0, n = polygon.length; i < n; ++i) { + for (let i = 0, n = polygon.length; i < n; ++i) { if (!(m = (ring = polygon[i]).length)) continue; var ring, m, @@ -21,7 +21,7 @@ export default function(polygon, point) { sinPhi0 = sin(phi0), cosPhi0 = cos(phi0); - for (var j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) { + for (let j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) { var point1 = ring[j], lambda1 = point1[0], phi1 = point1[1] / 2 + quarterPi, @@ -39,11 +39,11 @@ export default function(polygon, point) { // Are the longitudes either side of the point’s meridian (lambda), // and are the latitudes smaller than the parallel (phi)? if (antimeridian ^ lambda0 >= lambda ^ lambda1 >= lambda) { - var arc = cartesianCross(cartesian(point0), cartesian(point1)); + const arc = cartesianCross(cartesian(point0), cartesian(point1)); cartesianNormalizeInPlace(arc); - var intersection = cartesianCross(normal, arc); + const intersection = cartesianCross(normal, arc); cartesianNormalizeInPlace(intersection); - var phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin(intersection[2]); + const phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin(intersection[2]); if (phi > phiArc || phi === phiArc && (arc[0] || arc[1])) { winding += antimeridian ^ delta >= 0 ? 1 : -1; } diff --git a/src/polyhedral/butterfly.js b/src/polyhedral/butterfly.js index 75fd394..051fa68 100644 --- a/src/polyhedral/butterfly.js +++ b/src/polyhedral/butterfly.js @@ -3,28 +3,25 @@ import {pi} from "../math.js"; import polyhedral from "./index.js"; import octahedron from "./octahedron.js"; -export default function(faceProjection) { +export default function(faceProjection = ((face) => { + const c = centroid({type: "MultiPoint", coordinates: face}); + return gnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]); +})) { + const faces = octahedron.map((face) => ({face, project: faceProjection(face)})); - faceProjection = faceProjection || function(face) { - var c = centroid({type: "MultiPoint", coordinates: face}); - return gnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]); - }; - - var faces = octahedron.map(function(face) { - return {face: face, project: faceProjection(face)}; - }); - - [-1, 0, 0, 1, 0, 1, 4, 5].forEach(function(d, i) { - var node = faces[d]; + [-1, 0, 0, 1, 0, 1, 4, 5].forEach((d, i) => { + const node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); - return polyhedral(faces[0], function(lambda, phi) { - return faces[lambda < -pi / 2 ? phi < 0 ? 6 : 4 - : lambda < 0 ? phi < 0 ? 2 : 0 - : lambda < pi / 2 ? phi < 0 ? 3 : 1 - : phi < 0 ? 7 : 5]; - }) + return polyhedral( + faces[0], + (lambda, phi) => faces[ + lambda < -pi / 2 ? phi < 0 ? 6 : 4 + : lambda < 0 ? phi < 0 ? 2 : 0 + : lambda < pi / 2 ? phi < 0 ? 3 : 1 + : phi < 0 ? 7 : 5] + ) .angle(-30) .scale(101.858) .center([0, 45]); diff --git a/src/polyhedral/collignon.js b/src/polyhedral/collignon.js index 78a1b3e..8d3537d 100644 --- a/src/polyhedral/collignon.js +++ b/src/polyhedral/collignon.js @@ -4,39 +4,32 @@ import {pi, sqrt} from "../math.js"; import polyhedral from "./index.js"; import octahedron from "./octahedron.js"; -var kx = 2 / sqrt(3); +const kx = 2 / sqrt(3); function collignonK(a, b) { - var p = collignonRaw(a, b); + const p = collignonRaw(a, b); return [p[0] * kx, p[1]]; } -collignonK.invert = function(x,y) { - return collignonRaw.invert(x / kx, y); -}; +collignonK.invert = (x,y) => collignonRaw.invert(x / kx, y); -export default function(faceProjection) { +export default function(faceProjection = (face) => { + const c = centroid({type: "MultiPoint", coordinates: face}); + return projection(collignonK).translate([0, 0]).scale(1).rotate(c[1] > 0 ? [-c[0], 0] : [180 - c[0], 180]); +}) { + const faces = octahedron.map((face) => ({face, project: faceProjection(face)})); - faceProjection = faceProjection || function(face) { - var c = centroid({type: "MultiPoint", coordinates: face}); - return projection(collignonK).translate([0, 0]).scale(1).rotate(c[1] > 0 ? [-c[0], 0] : [180 - c[0], 180]); - }; - - var faces = octahedron.map(function(face) { - return {face: face, project: faceProjection(face)}; - }); - - [-1, 0, 0, 1, 0, 1, 4, 5].forEach(function(d, i) { - var node = faces[d]; + [-1, 0, 0, 1, 0, 1, 4, 5].forEach((d, i) => { + const node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); - return polyhedral(faces[0], function(lambda, phi) { - return faces[lambda < -pi / 2 ? phi < 0 ? 6 : 4 + return polyhedral( + faces[0], + (lambda, phi) => faces[lambda < -pi / 2 ? phi < 0 ? 6 : 4 : lambda < 0 ? phi < 0 ? 2 : 0 : lambda < pi / 2 ? phi < 0 ? 3 : 1 - : phi < 0 ? 7 : 5]; - }) + : phi < 0 ? 7 : 5]) .angle(-30) .scale(121.906) .center([0, 48.5904]); diff --git a/src/polyhedral/cube.js b/src/polyhedral/cube.js index 7e55211..97969ee 100644 --- a/src/polyhedral/cube.js +++ b/src/polyhedral/cube.js @@ -1,8 +1,8 @@ import {atan, degrees, sqrt1_2} from "../math.js"; -var phi1 = atan(sqrt1_2) * degrees; +const phi1 = atan(sqrt1_2) * degrees; -var cube = [ +const cube = [ [0, phi1], [90, phi1], [180, phi1], [-90, phi1], [0, -phi1], [90, -phi1], [180, -phi1], [-90, -phi1] ]; @@ -14,8 +14,4 @@ export default [ [2, 3, 7, 6], [3, 0, 4, 7], [4, 5, 6, 7] // S -].map(function(face) { - return face.map(function(i) { - return cube[i]; - }); -}); +].map((face) => face.map((i) => cube[i])); diff --git a/src/polyhedral/dodecahedral.js b/src/polyhedral/dodecahedral.js index 3fecbc3..a6b0c0d 100644 --- a/src/polyhedral/dodecahedral.js +++ b/src/polyhedral/dodecahedral.js @@ -2,38 +2,36 @@ import {acos, asin, degrees, sqrt} from "../math.js"; import voronoi from "./voronoi.js"; export default function() { - var A0 = asin(1/sqrt(3)) * degrees, - A1 = acos((sqrt(5) - 1) / sqrt(3) / 2) * degrees, - A2 = 90 - A1, - A3 = acos(-(1 + sqrt(5)) / sqrt(3) / 2) * degrees; + const A0 = asin(1/sqrt(3)) * degrees; + const A1 = acos((sqrt(5) - 1) / sqrt(3) / 2) * degrees; + const A2 = 90 - A1; + const A3 = acos(-(1 + sqrt(5)) / sqrt(3) / 2) * degrees; - var dodecahedron = [ - [[45,A0],[0,A1],[180,A1],[135,A0],[90,A2]], - [[45,A0],[A2,0],[-A2,0],[-45,A0],[0,A1]], - [[45,A0],[90,A2],[90,-A2],[45,-A0],[A2,0]], - [[0,A1],[-45,A0],[-90,A2],[-135,A0],[180,A1]], - [[A2,0],[45,-A0],[0,-A1],[-45,-A0],[-A2,0]], - [[90,A2],[135,A0],[A3,0],[135,-A0],[90,-A2]], - [[45,-A0],[90,-A2],[135,-A0],[180,-A1],[0,-A1]], - [[135,A0],[180,A1],[-135,A0],[-A3,0],[A3,0]], - [[-45,A0],[-A2,0],[-45,-A0],[-90,-A2],[-90,A2]], - [[-45,-A0],[0,-A1],[180,-A1],[-135,-A0],[-90,-A2]], - [[135,-A0],[A3,0],[-A3,0],[-135,-A0],[180,-A1]], - [[-135,A0],[-90,A2],[-90,-A2],[-135,-A0],[-A3,0]] + const dodecahedron = [ + [[45,A0],[0,A1],[180,A1],[135,A0],[90,A2]], + [[45,A0],[A2,0],[-A2,0],[-45,A0],[0,A1]], + [[45,A0],[90,A2],[90,-A2],[45,-A0],[A2,0]], + [[0,A1],[-45,A0],[-90,A2],[-135,A0],[180,A1]], + [[A2,0],[45,-A0],[0,-A1],[-45,-A0],[-A2,0]], + [[90,A2],[135,A0],[A3,0],[135,-A0],[90,-A2]], + [[45,-A0],[90,-A2],[135,-A0],[180,-A1],[0,-A1]], + [[135,A0],[180,A1],[-135,A0],[-A3,0],[A3,0]], + [[-45,A0],[-A2,0],[-45,-A0],[-90,-A2],[-90,A2]], + [[-45,-A0],[0,-A1],[180,-A1],[-135,-A0],[-90,-A2]], + [[135,-A0],[A3,0],[-A3,0],[-135,-A0],[180,-A1]], + [[-135,A0],[-90,A2],[-90,-A2],[-135,-A0],[-A3,0]] ]; - var polygons = { + const polygons = { type: "FeatureCollection", - features: dodecahedron.map(function(face) { - face.push(face[0]); - return { - geometry: { - type: "Polygon", - coordinates: [ face ] - } - }; - }) + features: dodecahedron.map((face) => ({ + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ [...face, face[0]] ] + } + })) }; return voronoi() diff --git a/src/polyhedral/index.js b/src/polyhedral/index.js index 8566c7c..ca33efd 100644 --- a/src/polyhedral/index.js +++ b/src/polyhedral/index.js @@ -17,67 +17,55 @@ export default function(tree, face) { node.edges = faceEdges(node.face); // Find shared edge. if (parent.face) { - var shared = node.shared = sharedEdge(node.face, parent.face), - m = matrix(shared.map(parent.project), shared.map(node.project)); + const shared = node.shared = sharedEdge(node.face, parent.face); + const m = matrix(shared.map(parent.project), shared.map(node.project)); node.transform = parent.transform ? multiply(parent.transform, m) : m; // Replace shared edge in parent edges array. - var edges = parent.edges; - for (var i = 0, n = edges.length; i < n; ++i) { + let edges = parent.edges; + for (let i = 0, n = edges.length; i < n; ++i) { if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = node; if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = node; } edges = node.edges; - for (i = 0, n = edges.length; i < n; ++i) { + for (let i = 0, n = edges.length; i < n; ++i) { if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = parent; if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = parent; } } else { node.transform = parent.transform; } - if (node.children) { - node.children.forEach(function(child) { - recurse(child, node); - }); - } + if (node.children) node.children.forEach((child) => recurse(child, node)); return node; } function forward(lambda, phi) { - var node = face(lambda, phi), - point = node.project([lambda * degrees, phi * degrees]), - t; - if (t = node.transform) { - return [ - t[0] * point[0] + t[1] * point[1] + t[2], - -(t[3] * point[0] + t[4] * point[1] + t[5]) - ]; - } - point[1] = -point[1]; - return point; + const node = face(lambda, phi); + const point = node.project([lambda * degrees, phi * degrees]); + const t = node.transform; + return t + ? [t[0] * point[0] + t[1] * point[1] + t[2], -(t[3] * point[0] + t[4] * point[1] + t[5])] + : [point[0], -point[1]]; } // Naive inverse! A faster solution would use bounding boxes, or even a // polygonal quadtree. if (hasInverse(tree)) forward.invert = function(x, y) { - var coordinates = faceInvert(tree, [x, -y]); + const coordinates = faceInvert(tree, [x, -y]); return coordinates && (coordinates[0] *= radians, coordinates[1] *= radians, coordinates); }; function faceInvert(node, coordinates) { - var invert = node.project.invert, - t = node.transform, - point = coordinates; + const invert = node.project.invert; + let point = coordinates; + let p; + let t = node.transform; if (t) { t = inverse(t); - point = [ - t[0] * point[0] + t[1] * point[1] + t[2], - (t[3] * point[0] + t[4] * point[1] + t[5]) - ]; + point = [t[0] * point[0] + t[1] * point[1] + t[2], (t[3] * point[0] + t[4] * point[1] + t[5])]; } if (invert && node === faceDegrees(p = invert(point))) return p; - var p, - children = node.children; - for (var i = 0, n = children && children.length; i < n; ++i) { + const children = node.children; + for (let i = 0, n = children && children.length; i < n; ++i) { if (p = faceInvert(children[i], coordinates)) return p; } } @@ -86,10 +74,10 @@ export default function(tree, face) { return face(coordinates[0] * radians, coordinates[1] * radians); } - var proj = projection(forward); + const proj = projection(forward); // run around the mesh of faces and stream all vertices to create the clipping polygon - var polygon = []; + const polygon = []; outline({point: function(lambda, phi) { polygon.push([lambda, phi]); }}, tree); polygon.push(polygon[0]); proj.preclip(clipPolygon({ type: "Polygon", coordinates: [ polygon ] })); @@ -99,7 +87,7 @@ export default function(tree, face) { } function outline(stream, node, parent) { - var point, + let point, edges = node.edges, n = edges.length, edge, @@ -118,7 +106,7 @@ function outline(stream, node, parent) { if (edges[j] === parent) break; } ++j; - for (var i = 0; i < n; ++i) { + for (let i = 0; i < n; ++i) { edge = edges[(i + j) % n]; if (Array.isArray(edge)) { if (!inside) { @@ -135,10 +123,11 @@ function outline(stream, node, parent) { // Finds a shared edge given two clockwise polygons. function sharedEdge(a, b) { - var x, y, n = a.length, found = null; - for (var i = 0; i < n; ++i) { + const n = a.length; + let x, y, found = null; + for (let i = 0; i < n; ++i) { x = a[i]; - for (var j = b.length; --j >= 0;) { + for (let j = b.length; --j >= 0;) { y = b[j]; if (x[0] === y[0] && x[1] === y[1]) { if (found) return [found, x]; @@ -150,9 +139,9 @@ function sharedEdge(a, b) { // Converts an array of n face vertices to an array of n + 1 edges. function faceEdges(face) { - var n = face.length, - edges = []; - for (var a = face[n - 1], i = 0; i < n; ++i) edges.push([a, a = face[i]]); + const n = face.length; + const edges = []; + for (let i = 0, a = face[n - 1]; i < n; ++i) edges.push([a, a = face[i]]); return edges; } diff --git a/src/polyhedral/matrix.js b/src/polyhedral/matrix.js index 0e28b63..cbb35eb 100644 --- a/src/polyhedral/matrix.js +++ b/src/polyhedral/matrix.js @@ -7,10 +7,10 @@ import {atan2, cos, sin, sqrt} from "../math.js"; // Transform matrix for [a0, a1] -> [b0, b1]. export default function(a, b) { - var u = subtract(a[1], a[0]), - v = subtract(b[1], b[0]), - phi = angle(u, v), - s = length(u) / length(v); + const u = subtract(a[1], a[0]); + const v = subtract(b[1], b[0]); + const phi = angle(u, v); + const s = length(u) / length(v); return multiply([ 1, 0, a[0][0], @@ -29,7 +29,7 @@ export default function(a, b) { // Inverts a transform matrix. export function inverse(m) { - var k = 1 / (m[0] * m[4] - m[1] * m[3]); + const k = 1 / (m[0] * m[4] - m[1] * m[3]); return [ k * m[4], -k * m[1], k * (m[1] * m[5] - m[2] * m[4]), -k * m[3], k * m[0], k * (m[2] * m[3] - m[0] * m[5]) diff --git a/src/polyhedral/octahedron.js b/src/polyhedral/octahedron.js index 8229c12..fe38b48 100644 --- a/src/polyhedral/octahedron.js +++ b/src/polyhedral/octahedron.js @@ -1,5 +1,5 @@ // TODO generate on-the-fly to avoid external modification. -var octahedron = [ +const octahedron = [ [0, 90], [-90, 0], [0, 0], [90, 0], [180, 0], [0, -90] @@ -14,8 +14,4 @@ export default [ [0, 4, 3], [5, 4, 1], [5, 3, 4] -].map(function(face) { - return face.map(function(i) { - return octahedron[i]; - }); -}); +].map((face) => face.map((i) => octahedron[i])); diff --git a/src/polyhedral/voronoi.js b/src/polyhedral/voronoi.js index be79e33..ea6ac74 100644 --- a/src/polyhedral/voronoi.js +++ b/src/polyhedral/voronoi.js @@ -6,38 +6,36 @@ import { import { degrees } from "../math.js"; import polyhedral from "./index.js"; -export default function(parents, polygons, faceProjection, find) { - parents = parents || []; - polygons = polygons || { features: [] }; - find = find || find0; +// it is possible to pass a specific projection on each face +// by default is is a gnomonic projection centered on the face's centroid +// scale 1 by convention +const faceProjection0 = (face) => gnomonic() + .scale(1) + .translate([0, 0]) + .rotate([-face.site[0], -face.site[1]]); - // it is possible to pass a specific projection on each face - // by default is is a gnomonic projection centered on the face's centroid - // scale 1 by convention - faceProjection = - faceProjection || - function(face) { - return gnomonic() - .scale(1) - .translate([0, 0]) - .rotate([-face.site[0], -face.site[1]]); - }; - - var faces = []; +export default function( + parents = [], + polygons = { features: [] }, + faceProjection = faceProjection0, + find +) { + if (find === undefined) find = find0; + let faces = []; function build_tree() { // the faces from the polyhedron each yield // - face: its vertices // - site: its voronoi site (default: centroid) // - project: local projection on this face - faces = polygons.features.map(function(feature, i) { - var polygon = feature.geometry.coordinates[0]; - var face = polygon.slice(0, -1); + faces = polygons.features.map((feature, i) => { + const polygon = feature.geometry.coordinates[0]; + const face = polygon.slice(0, -1); face.site = feature.properties && feature.properties.sitecoordinates ? feature.properties.sitecoordinates : centroid(feature.geometry); return { - face: face, + face, site: face.site, id: i, project: faceProjection(face) @@ -46,18 +44,18 @@ export default function(parents, polygons, faceProjection, find) { // Build a tree of the faces, starting with face 0 (North Pole) // which has no parent (-1) - parents.forEach(function(d, i) { - var node = faces[d]; + parents.forEach((d, i) => { + const node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); } // a basic function to find the polygon that contains the point function find0(lambda, phi) { - var d0 = Infinity; - var found = -1; - for (var i = 0; i < faces.length; i++) { - var d = distance(faces[i].site, [lambda, phi]); + let d0 = Infinity; + let found = -1; + for (let i = 0; i < faces.length; i++) { + const d = distance(faces[i].site, [lambda, phi]); if (d < d0) { d0 = d; found = i; @@ -70,10 +68,10 @@ export default function(parents, polygons, faceProjection, find) { return faces[find(lambda * degrees, phi * degrees)]; } - var p = gnomonic(); + let p = gnomonic(); function reset() { - var rotate = p.rotate(), + let rotate = p.rotate(), translate = p.translate(), center = p.center(), scale = p.scale(), diff --git a/src/polyhedral/waterman.js b/src/polyhedral/waterman.js index 8806bb2..2070a69 100644 --- a/src/polyhedral/waterman.js +++ b/src/polyhedral/waterman.js @@ -3,20 +3,16 @@ import {asin, atan2, cos, degrees, max, min, pi, radians, sin} from "../math.js" import polyhedral from "./index.js"; import octahedron from "./octahedron.js"; -export default function(faceProjection) { - - faceProjection = faceProjection || function(face) { - var c = face.length === 6 ? centroid({type: "MultiPoint", coordinates: face}) : face[0]; - return gnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]); - }; - - var w5 = octahedron.map(function(face) { - var xyz = face.map(cartesian), - n = xyz.length, - a = xyz[n - 1], - b, - hexagon = []; - for (var i = 0; i < n; ++i) { +export default function(faceProjection = ((face) => { + const c = face.length === 6 ? centroid({type: "MultiPoint", coordinates: face}) : face[0]; + return gnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]); +})) { + const w5 = octahedron.map((face) => { + const xyz = face.map(cartesian); + const n = xyz.length; + const hexagon = []; + let a = xyz[n - 1], b; + for (let i = 0; i < n; ++i) { b = xyz[i]; hexagon.push(spherical([ a[0] * 0.9486832980505138 + b[0] * 0.31622776601683794, @@ -32,15 +28,15 @@ export default function(faceProjection) { return hexagon; }); - var cornerNormals = []; + const cornerNormals = []; - var parents = [-1, 0, 0, 1, 0, 1, 4, 5]; + const parents = [-1, 0, 0, 1, 0, 1, 4, 5]; - w5.forEach(function(hexagon, j) { - var face = octahedron[j], + w5.forEach((hexagon, j) => { + const face = octahedron[j], n = face.length, normals = cornerNormals[j] = []; - for (var i = 0; i < n; ++i) { + for (let i = 0; i < n; ++i) { w5.push([ face[i], hexagon[(i * 2 + 2) % (2 * n)], @@ -54,28 +50,26 @@ export default function(faceProjection) { } }); - var faces = w5.map(function(face) { - return { - project: faceProjection(face), - face: face - }; - }); + const faces = w5.map((face) => ({ + project: faceProjection(face), + face + })); - parents.forEach(function(d, i) { - var parent = faces[d]; + parents.forEach((d, i) => { + const parent = faces[d]; parent && (parent.children || (parent.children = [])).push(faces[i]); }); function face(lambda, phi) { - var cosphi = cos(phi), - p = [cosphi * cos(lambda), cosphi * sin(lambda), sin(phi)]; + const cosphi = cos(phi); + const p = [cosphi * cos(lambda), cosphi * sin(lambda), sin(phi)]; - var hexagon = lambda < -pi / 2 ? phi < 0 ? 6 : 4 + const hexagon = lambda < -pi / 2 ? phi < 0 ? 6 : 4 : lambda < 0 ? phi < 0 ? 2 : 0 : lambda < pi / 2 ? phi < 0 ? 3 : 1 : phi < 0 ? 7 : 5; - var n = cornerNormals[hexagon]; + const n = cornerNormals[hexagon]; return faces[dot(n[0], p) < 0 ? 8 + 3 * hexagon : dot(n[1], p) < 0 ? 8 + 3 * hexagon + 1 @@ -90,7 +84,8 @@ export default function(faceProjection) { } function dot(a, b) { - for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i]; + let s = 0; + for (let i = 0; i < a.length; ++i) s += a[i] * b[i]; return s; } @@ -112,9 +107,9 @@ function spherical(cartesian) { // Converts spherical coordinates (degrees) to 3D Cartesian. function cartesian(coordinates) { - var lambda = coordinates[0] * radians, - phi = coordinates[1] * radians, - cosphi = cos(phi); + const lambda = coordinates[0] * radians; + const phi = coordinates[1] * radians; + const cosphi = cos(phi); return [ cosphi * cos(lambda), cosphi * sin(lambda), diff --git a/src/tetrahedralLee.js b/src/tetrahedralLee.js index 184d63d..27452ef 100644 --- a/src/tetrahedralLee.js +++ b/src/tetrahedralLee.js @@ -5,7 +5,7 @@ import { geoContains, } from "d3-geo"; import polyhedral from "./polyhedral/index.js"; -import { scan } from "d3-array"; +import { greatest } from "d3-array"; import { abs, asin, degrees, sqrt } from "./math.js"; import { complexAdd, @@ -18,32 +18,27 @@ import { solve2d } from "./newton.js"; export function leeRaw(lambda, phi) { // return d3.geoGnomonicRaw(...arguments); - var w = [-1 / 2, sqrt(3) / 2], - k = [0, 0], + const w = [-1 / 2, sqrt(3) / 2]; + let k = [0, 0], h = [0, 0], - i, z = complexMul(geoStereographicRaw(lambda, phi), [sqrt(2), 0]); // rotate to have s ~= 1 - var sector = scan( - [0, 1, 2].map(function (i) { - return -complexMul(z, complexPow(w, [i, 0]))[0]; - }) - ); - var rot = complexPow(w, [sector, 0]); + const sector = greatest([0, 1, 2], (i) => complexMul(z, complexPow(w, [i, 0]))[0]); + const rot = complexPow(w, [sector, 0]); - var n = complexNorm(z); + const n = complexNorm(z); if (n > 0.3) { // if |z| > 0.5, use the approx based on y = (1-z) // McIlroy formula 6 p6 and table for G page 16 - var y = complexSub([1, 0], complexMul(rot, z)); + const y = complexSub([1, 0], complexMul(rot, z)); // w1 = gamma(1/3) * gamma(1/2) / 3 / gamma(5/6); // https://bl.ocks.org/Fil/1aeff1cfda7188e9fbf037d8e466c95c - var w1 = 1.4021821053254548; + const w1 = 1.4021821053254548; - var G0 = [ + const G0 = [ 1.15470053837925, 0.192450089729875, 0.0481125224324687, 0.010309826235529, 3.34114739114366e-4, -1.50351632601465e-3, -1.2304417796231e-3, -6.75190201960282e-4, -2.84084537293856e-4, @@ -53,8 +48,8 @@ export function leeRaw(lambda, phi) { -3.57526015225576e-7, -2.2175964844211e-7, ]; - var G = [0, 0]; - for (i = G0.length; i--; ) { + let G = [0, 0]; + for (let i = G0.length; i--; ) { G = complexAdd([G0[i], 0], complexMul(G, y)); } @@ -69,11 +64,9 @@ export function leeRaw(lambda, phi) { // 1 + z^3/2 + (3 z^6)/8 + (5 z^9)/16 + (35 z^12)/128 + (63 z^15)/256 + (231 z^18)/1024 + O(z^21) // https://www.wolframalpha.com/input/?i=integral+of+1+%2B+z%5E3%2F2+%2B+(3+z%5E6)%2F8+%2B+(5+z%5E9)%2F16+%2B+(35+z%5E12)%2F128+%2B+(63+z%5E15)%2F256+%2B+(231+z%5E18)%2F1024 // (231 z^19)/19456 + (63 z^16)/4096 + (35 z^13)/1664 + z^10/32 + (3 z^7)/56 + z^4/8 + z + constant - var H0 = [1, 1 / 8, 3 / 56, 1 / 32, 35 / 1664, 63 / 4096, 231 / 19456]; - var z3 = complexPow(z, [3, 0]); - for (i = H0.length; i--; ) { - h = complexAdd([H0[i], 0], complexMul(h, z3)); - } + const H0 = [1, 1 / 8, 3 / 56, 1 / 32, 35 / 1664, 63 / 4096, 231 / 19456]; + const z3 = complexPow(z, [3, 0]); + for (let i = H0.length; i--; ) h = complexAdd([H0[i], 0], complexMul(h, z3)); h = complexMul(h, z); } @@ -81,74 +74,57 @@ export function leeRaw(lambda, phi) { if (n > 0.5) return k; // in between 0.3 and 0.5, interpolate - var t = (n - 0.3) / (0.5 - 0.3); + const t = (n - 0.3) / (0.5 - 0.3); return complexAdd(complexMul(k, [t, 0]), complexMul(h, [1 - t, 0])); } -var leeSolver = solve2d(leeRaw); +const leeSolver = solve2d(leeRaw); leeRaw.invert = function (x, y) { if (x > 1.5) return false; // immediately avoid using the wrong face - var p = leeSolver(x, y, x, y * 0.5), - q = leeRaw(p[0], p[1]); + const p = leeSolver(x, y, x, y * 0.5); + const q = leeRaw(p[0], p[1]); q[0] -= x; q[1] -= y; - if (q[0] * q[0] + q[1] * q[1] < 1e-8) return p; - return [-10, 0]; // far out of the face + return (q[0] * q[0] + q[1] * q[1] < 1e-8) + ? p + : [-10, 0]; // far out of the face }; -var asin1_3 = asin(1 / 3); -var centers = [ +const asin1_3 = asin(1 / 3); +const centers = [ [0, 90], [-180, -asin1_3 * degrees], [-60, -asin1_3 * degrees], [60, -asin1_3 * degrees], ]; -var tetrahedron = [ +const tetrahedron = [ [1, 2, 3], [0, 2, 1], [0, 3, 2], [0, 1, 3], -].map(function (face) { - return face.map(function (i) { - return centers[i]; - }); -}); - -export default function () { - var faceProjection = function (face) { - var c = geoCentroid({ type: "MultiPoint", coordinates: face }), - rotate = [-c[0], -c[1], 30]; - if (abs(c[1]) == 90) { - rotate = [0, -c[1], -30]; - } - return projection(leeRaw).scale(1).translate([0, 0]).rotate(rotate); - }; +].map((face) => face.map((i) => centers[i])); - var faces = tetrahedron.map(function (face) { - return { face: face, project: faceProjection(face) }; - }); +export default function (faceProjection = (face) => { + const c = geoCentroid({ type: "MultiPoint", coordinates: face }); + const rotate = (abs(c[1]) == 90) ? [0, -c[1], -30] : [-c[0], -c[1], 30]; + return projection(leeRaw).scale(1).translate([0, 0]).rotate(rotate); +}) { + const faces = tetrahedron.map((face) => ({ face, project: faceProjection(face) })); - [-1, 0, 0, 0].forEach(function (d, i) { - var node = faces[d]; + [-1, 0, 0, 0].forEach((d, i) => { + const node = faces[d]; node && (node.children || (node.children = [])).push(faces[i]); }); - var p = polyhedral(faces[0], function (lambda, phi) { + const p = polyhedral(faces[0], (lambda, phi) => { lambda *= degrees; phi *= degrees; - for (var i = 0; i < faces.length; i++) { + for (let i = 0; i < faces.length; ++i) { if ( geoContains( { type: "Polygon", - coordinates: [ - [ - tetrahedron[i][0], - tetrahedron[i][1], - tetrahedron[i][2], - tetrahedron[i][0], - ], - ], + coordinates: [ [...tetrahedron[i], tetrahedron[i][0]] ], }, [lambda, phi] ) @@ -158,12 +134,10 @@ export default function () { } }); - return ( - p + return p .rotate([30, 180]) // North Pole aspect, needs clipPolygon // .rotate([-30, 0]) // South Pole aspect .angle(30) .scale(118.662) - .translate([480, 195.47]) - ); + .translate([480, 195.47]); } diff --git a/test/projectionEqual.js b/test/projectionEqual.js deleted file mode 100644 index 6e2c6ef..0000000 --- a/test/projectionEqual.js +++ /dev/null @@ -1,34 +0,0 @@ -var tape = require("tape"); - -tape.Test.prototype.projectionEqual = function(projection, location, point, delta) { - this._assert(planarEqual(projection(location), point, delta || 1e-6) - && sphericalEqual(projection.invert(point), location, delta || 1e-3), { - message: "should be projected equivalents", - operator: "planarEqual", - actual: [projection.invert(point), projection(location)], - expected: [location, point] - }); -}; - -function planarEqual(actual, expected, delta) { - return Array.isArray(actual) - && actual.length === 2 - && inDelta(actual[0], expected[0], delta) - && inDelta(actual[1], expected[1], delta); -} - -function sphericalEqual(actual, expected, delta) { - return Array.isArray(actual) - && actual.length === 2 - && longitudeEqual(actual[0], expected[0], delta) - && inDelta(actual[1], expected[1], delta); -} - -function longitudeEqual(actual, expected, delta) { - actual = Math.abs(actual - expected) % 360; - return actual <= delta || actual >= 360 - delta; -} - -function inDelta(actual, expected, delta) { - return Math.abs(actual - expected) <= delta; -}