Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active October 31, 2016 22:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andrew-Reid/1e0a0d07b1a44aba4108c6cc70deecaa to your computer and use it in GitHub Desktop.
Save Andrew-Reid/1e0a0d07b1a44aba4108c6cc70deecaa to your computer and use it in GitHub Desktop.
Tissot's Indicatrix

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.

<!DOCTYPE html>
<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>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment