block by fil 137b06fcdb6f3ab8cc12e39d7d95e987

Loximuthal projection & rhumb line

Full Screen

The loximuthal projection is available as d3.geoLoximuthal in the extended geographic projections plugin.

Projection introduced by Karl Siemon in 1935, it was published and forgotten – then independently recreated by Waldo R. Tobler in 1966, who named it. Implemented in d3-geo-projection by @JasonDavies.

With the loximuthal projection, rhumb lines (aka loxodromes, in green) are straight lines. Formula adapted from http://www.mathpages.com/home/kmath502/kmath502.htm

Mouse over the map to change the reference parallel.

forked from mbostock‘s block: Loximuthal

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  background: white;
}

.stroke {
  fill: none;
  stroke: #000;
  stroke-width: 3px;
}

.fill {
  fill: #f6f9fd;
}

.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
}

.land {
  fill: #162d73;
}

#rhumb {
	stroke: #dd4f98;
  stroke-opacity: 0.5;
  stroke-width: 1.5;
  fill: none;
}

</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>

var width = 960,
    height = 530;

var projection = d3.geoLoximuthal()
.rotate([-10,0]);

var path = d3.geoPath()
    .projection(projection);

var graticule = d3.geoGraticule();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.append("defs").append("path")
    .datum({type: "Sphere"})
    .attr("id", "sphere");

svg.append("use")
    .attr("class", "stroke")
    .attr("xlink:href", "#sphere");

svg.append("use")
    .attr("class", "fill")
    .attr("xlink:href", "#sphere");

svg.append("path")
    .datum(graticule)
    .attr("class", "graticule");

d3.json("https://unpkg.com/world-atlas/world/110m.json", function(error, world) {
  if (error) throw error;

  svg.insert("path", ".graticule")
      .datum(topojson.feature(world, world.objects.land))
      .attr("class", "land");

  draw();
});

  var rhumb = [], lat0 = -85, lon0 = 0, K = 0.001, k = 0.06 / Math.sqrt(1 + K*K), radians = Math.PI / 180;
  for(var i = 0; i < 3000; i++) {
    var s = i,
        lat = lat0 + k * s,
        lon = lon0 + 1/K * Math.log(
          (1 - Math.sin(lat * radians))
          * Math.cos(lat0 * radians)
          / (1 - Math.sin(lat0 * radians))
          / Math.cos(lat * radians)
        );
    if (Math.abs(lat) < 85) rhumb.push([lon % 360, lat]);
  }
  var rhumbline = {type: "LineString", coordinates: rhumb };
  svg.append('path').datum(rhumbline)
  .attr('id', 'rhumb');

  draw();

  svg.on('mousemove click', function() {
    var p = projection.invert(d3.mouse(this));
    if (p && Math.abs(p[1]) < 88) projection.parallel(p[1]);
    draw();
  })

  function draw() {
    const margin = 10;
    const width = Math.min(960, window.innerWidth),
          height = Math.min(width / 2, window.innerHeight);
    svg.attr('height', height).attr('width', width)
    projection.fitExtent([[margin, margin], [width - margin, height - margin]], {
      type: "Sphere"
    });

    svg.selectAll('path').attr('d', path)
  }

  d3.select(window).on('resize', draw);
</script>