geoTissot = {
function transpose(mat) {
return [mat[0], mat[2], mat[1], mat[3]]
}
function dot(a, b) {
return [
a[0] * b[0] + a[1] * b[2],
a[0] * b[1] + a[1] * b[3],
a[2] * b[0] + a[3] * b[2],
a[2] * b[1] + a[3] * b[3]
]
}
const delta = Math.pow(10, -5.2),
rad = Math.PI / 180,
fn = function() {};
return function() {
let projection = null;
function tissot(geojson) {
let xy,
stream = projection.stream({
point: function(lambda, phi) { xy = [lambda, phi] },
lineStart: fn,
lineEnd: fn,
polygonStart: fn,
polygonEnd: fn
}),
sf = projection.scale() * rad, // scale factor
indicatrices = [];
return d3.geoStream(geojson, {
point: function(lambda, phi) {
const cosPhi = Math.cos(phi * rad);
// skip poles (division by zero)
if (1e-6 < cosPhi) {
let p = (xy = null, stream.point(lambda, phi), xy);
if (!!xy) {
let dLambda = lambda > 0 ? lambda - delta : lambda + delta,
dL = (xy = null, stream.point(dLambda, phi), xy);
if (!!xy) {
let dPhi = phi > 0 ? phi - delta : phi + delta,
dP = (xy = null, stream.point(lambda, dPhi), xy);
if (!!xy) {
let M = (lambda - dLambda) * sf,
m = (phi - dPhi) * sf,
// derivates
dxdL = (p[0] - dL[0]) / M,
dydL = (p[1] - dL[1]) / M,
dxdP = (p[0] - dP[0]) / m,
dydP = (p[1] - dP[1]) / m,
// indicatrix
h = Math.sqrt(dxdP * dxdP + dydP * dydP),
k = Math.sqrt(dxdL * dxdL + dydL * dydL) / cosPhi,
s = -(dydP * dxdL - dxdP * dydL) / cosPhi,
A = Math.sqrt(Math.max(0, h * h + k * k + s * 2)),
B = Math.sqrt(Math.max(0, h * h + k * k - s * 2)),
a = Math.abs((A + B) / 2),
b = Math.abs((A - B) / 2),
w = Math.asin(Math.max(-1, Math.min(1, B / A))) * 2,
// indicatrix rotation
mat = dot([dxdL, dxdP, dydL, dydP], [1 / cosPhi, 0, 0, 1]),
z = dot(mat, transpose(mat)),
theta = Math.atan2(z[2] + z[1], z[0] - z[3]) / 2;
indicatrices.push({
coordinates: [lambda, phi], h, k, s, w, a, b, theta
});
}
}
}
}
}
}),
indicatrices;
}
tissot.projection = function(p) {
return arguments.length ? (projection = p, tissot) : projection;
}
return tissot;
}
}