JavaScript
d3.js
地図
GIS
geojson

大円軌跡とそれを囲む矩形(円筒投影法)

More than 1 year has passed since last update.

こんにちは。
大円の軌跡(部分)とそれを囲む円筒投影法矩形を d3.js を使って描いてみました。geojson の LineString を与えると大円軌跡を描きます。矩形に必要な緯線を作るなどの各種処理は「Geographic Bounding Boxes」から持ってきました1。投影法はBraun投影です。

braun.jpg

boundingBox.html
<!DOCTYPE html>
<meta charset="utf-8">
<title>Braun projection</title>
<style>
.frame {
  fill: none;
  stroke: #000;
  stroke-width: 3px;
}
.fill {
  fill: #fff;
}
.arc {
  fill: none;
  stroke: #0fc;
  stroke-width: 1.5px;
}
.graticule {
  fill: none;
  stroke: #777;
  stroke-width: 0.5px;
  stroke-opacity: 0.5;
}
.land {
  fill: #888;
}
.boundary {
  fill: none;
  stroke: #fff;
  stroke-width: 0.5px;
}
.bounds {
  fill: #f00;
  fill-opacity: .1;
  stroke: #f00;
  stroke-width: 0.2px;
  pointer-events: none;
}
</style>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<script>
var geojson_geometry = {type: "LineString", coordinates: [[110, 60], [-110, 60]]};

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

var projection = d3.geoProjection(braunRaw)
     .scale((width - 3) / (2 * Math.PI))
    .translate([width/2, height/2])
    .precision(0.1);

function braunRaw(lambda, phi) {
  return [lambda, Math.tan(phi/2)*2];
}

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

var graticule = d3.geoGraticule();

svg.append("defs").append("path")
    .datum(graticule.outline())
    .attr("id", "sphere")
    .attr("d", path);

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

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

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

svg.append("path")
    .datum(geojson_geometry)
    .attr("class", "arc")
    .attr("d", path);

svg.append("path")
    .datum(boundRect(geojson_geometry))
    .attr("class", "bounds")
    .attr("d", path);


d3.json("https://unpkg.com/world-atlas@1/world/50m.json", function(error, world) {
  svg.insert("path", ".graticule")
      .datum(topojson.feature(world, world.objects.land))
      .attr("class", "land")
      .attr("d", path);

  svg.insert("path", ".graticule")
      .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
      .attr("class", "boundary")
      .attr("d", path);
});

function boundRect(pnts) {
  var coords = [[pnts[0][0],pnts[0][1]]]
      .concat(parallel(pnts[0][0], pnts[1][0], pnts[0][1]))
      .concat(parallel(pnts[0][0], pnts[1][0], pnts[1][1]).reverse());
  return rfc7946tod3({type: "Polygon", coordinates: [coords]})
}

function parallel(λ0, λ1, φ)  {
  if (λ0 > λ1) λ1 += 360;
  var dλ = λ1 - λ0,
      step = dλ / Math.ceil(dλ);
  return d3.range(λ0, λ1 + 0.5 * step, step).map(function(λ) { return [normalise(λ), φ]; });
}

function normalise(x) {
  return (x + 180) % 360 - 180;
}

// https://github.com/tyrasd/rfc7946-to-d3
function rfc7946tod3(geojson) {
    switch ((geojson && geojson.type) || null) {
        case 'FeatureCollection':
            geojson.features.forEach(rfc7946tod3)
            break
        case 'GeometryCollection':
            geojson.geometries.forEach(rfc7946tod3)
            break
        case 'Feature':
            rfc7946tod3(geojson.geometry)
            break
        case 'MultiPolygon':
            geojson.coordinates.forEach(function(polygon) {
                polygon.forEach(function(ring) {
                    ring.reverse()
                })
            })
            break
        case 'Polygon':
            geojson.coordinates.forEach(function(ring) {
                ring.reverse()
            })
            break
    }
    return geojson
}
</script>


  1. 内部で gojson の polygon 処理に、rfc7946-to-d3.js を使っています。