Skip to content

Instantly share code, notes, and snippets.

@r-suen
Last active August 30, 2017 14:28
Show Gist options
  • Save r-suen/afc96dc39504cb769adf28ab5874b934 to your computer and use it in GitHub Desktop.
Save r-suen/afc96dc39504cb769adf28ab5874b934 to your computer and use it in GitHub Desktop.
Antimeridian Dilemma

Exploring lines across the antimeridian using D3. Given two points on Earth, how to infer the appropriate way to connect them?

The red path is the great-circle distance, and the black path is a straight line connecting the two points. Drag the map left or right to see how the two paths are drawn differently across the antimeridian.

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Antimeridian Dilemma</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.0/topojson.min.js'></script>
</head>
<style>
body {
margin: 0;
overflow: hidden;
}
rect {
fill: #5f9ea0;
}
.land {
fill: #d2b48c;
}
.border {
fill: none;
stroke: #654321;
stroke-width: 1;
}
.point {
fill: #fff;
stroke: #ff0000;
stroke-width: 2.5;
}
.arc {
fill: none;
stroke: #ff0000;
stroke-width: 5px;
}
.line {
fill: none;
stroke: #000;
stroke-width: 5px;
}
</style>
<body>
<div id='map'></div>
<script>
var points = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [121.597366, 25.105497]
},
"properties": {
"name": "Taipei"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-118.243683, 34.052235]
},
"properties": {
"name": "Los Angeles"
}
},
]
};
var line = {
"type": "LineString",
"coordinates": [[121.597366, 25.105497], [-118.243683, 34.052235]]
};
var dlambda = 0,
zoomtx = 0;
var width = window.innerWidth,
height = window.innerHeight;
var scale = (width > height) ? width : height;
var projection = d3.geoMercator()
.translate([0, 0])
.scale(scale / 2 / Math.PI);
var zoom = d3.zoom()
.scaleExtent([1, 1 << 7])
.on('zoom', zoomed);
var path = d3.geoPath(projection)
.pointRadius(6);
var lambda = d3.scaleLinear()
.domain([0, width])
.range([-180, 180]);
var ybound = projection([0, -85])[1] - height / 2;
var lineShape = d3.line()
.x(d => projection(d)[0])
.y(d => projection(d)[1]);
var svg = d3.select('#map').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
.call(zoom);
svg.append('rect')
.attr('x', -width / 2)
.attr('y', -height / 2)
.attr('width', width)
.attr('height', height);
var g = svg.append('g');
d3.json('world-110m.json', function(error, data) {
if (error) throw error;
g.append('path')
.datum(topojson.feature(data, data.objects.land))
.attr('d', path)
.attr('class', 'land');
g.append('path')
.datum(topojson.mesh(data, data.objects.countries, function (a, b) { return a !== b; }))
.attr('d', path)
.attr('class', 'border');
drawGreatArc();
drawLine();
drawPoints();
});
function zoomed() {
let t = d3.event.transform;
t.y = Math.min(height / 2 * (t.k - 1) + ybound * t.k, Math.max(t.y, height / 2 * (1 - t.k) - ybound * t.k));
if (d3.event.sourceEvent.wheelDelta) {
t.x = Math.min(width / 2 * (t.k - 1), Math.max(t.x, width / 2 * (1 - t.k)));
zoomtx = t.x;
g.attr("transform", t);
svg.selectAll('.border').style('stroke-width', 1 / t.k);
} else if (d3.event.sourceEvent.movementX || d3.event.sourceEvent.movementY) {
t.x = zoomtx;
dlambda += d3.event.sourceEvent.movementX / t.k;
projection.rotate([lambda(-width / 2 + dlambda), 0]);
svg.selectAll('path').attr('d', path);
svg.selectAll('.line').attr('d', lineShape);
g.attr("transform", t);
}
}
function drawPoints() {
g.selectAll('points')
.data(points.features)
.enter().append('path')
.attr('d', path)
.attr('class', 'point');
}
function drawGreatArc() {
g.append('path')
.datum(line)
.attr('d', path)
.attr('class', 'arc');
}
function drawLine() {
g.append('path')
.datum(line.coordinates)
.attr('d', lineShape)
.attr('class', 'line')
}
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Loading
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