An attempt at Tissot's Indicatrix in d3.js v4.
It uses a spherical earth for ease of calculation, but as the earth is a slight ellipsoid, there could be a small amount of error. But this is small enough it shouldn't distort the graphics.
Each indicator circle/ellipse (depending on projection) represents a circular area with a 500 km radius. The number of indicators can be changed with the across/high variables.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<style>
svg {
background: #9ecae1;
}
.boundary {
fill:none;
stroke: white;
stroke-width: 0.5px;
}
.land {
fill: #41ab5d;
}
.indicator {
stroke: steelblue;
stroke-width: 1px;
fill: white;
fill-opacity: 0.4;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v1.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
</head>
<body>
<script type="text/javascript">
var width = 960,
height = 735;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geoMercator().scale(155).center([0,40]);
var path = d3.geoPath().projection(projection);
var g = svg.append("g");
d3.json("world.json", function(error, world) {
// Add land masses from world.json:
g.insert("path", ".land")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
g.insert("path", ".boundary")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
// Create some points to build the indicatrix around
var across = 8;
var high = 5;
var points = [];
for (i=0;i<across;i++) {
for (j=0;j<high;j++) {
if (j == 0) { points.push([ i * 360/across + 180/across -180, 0 ]) }
else {
points.push([ i * 360/across + 180/across - 180 , 0 + j/(high-1) * 72 ])
points.push([ i * 360/across + 180/across - 180, 0 - j/(high-1) * 72 ])
}
}
}
// Build some features around each point
var features = [];
for (j=0; j < points.length; j++) {
var point = points[j];
var r = 500000; // radius in m for each indicator
var data = [];
for (i=0;i<37;i++) {
var p = getPoint(point, i, r);
data.push ([ p[0],p[1] ]);
}
features.push(
{ "type":"Feature", "geometry": { "type": "Polygon", "coordinates": [data] } }
);
}
var geoJSON = { "type": "FeatureCollection", "features": features }
// Append those features
g.append("path").attr("d",path(geoJSON)).attr("class","indicator");
});
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Latitude/longitude spherical geodesy tools (c) Chris Veness 2002-2016 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/latlong.html */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html */
function getPoint(p1,i,d) {
var bearing = i * 10 * Math.PI / 180
var R = 6371e3; // metres
var λ1 = p1[0] * Math.PI/180 ;
var φ1 = p1[1] * Math.PI/180 ;
var φ2 = Math.asin( Math.sin(φ1)*Math.cos(d/R) + Math.cos(φ1)*Math.sin(d/R)*Math.cos(bearing) );
var λ2 = λ1 + Math.atan2(Math.sin(bearing)*Math.sin(d/R)*Math.cos(φ1), Math.cos(d/R)-Math.sin(φ1)*Math.sin(φ2));
φ2 = φ2 * 180/Math.PI;
λ2 = λ2 * 180/Math.PI;
λ2 = (λ2+540)%360-180;
return [λ2,φ2];
}
</script>
</body>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-geo-projection.v1.min.js
https://d3js.org/topojson.v1.min.js