block by fil 7ab1d07c71826ae0f7ccdfb4bd4211e3

Spherical alpha shapes with d3-geo-voronoi

Full Screen

This example fixes zpconn‘s block: Spherical alpha shapes, by using d3-geo-voronoi.

We merge the triangles thanks to Jason Davies’s block http://bl.ocks.org/jasondavies/1554783.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
	.point {
		stroke: #000;
		stroke-width: .5px;
		fill: red;
	}

	.graticule {
		stroke: #000;
		stroke-width: .25px;
		fill: white;
	}

	.geom {
		fill: none;
		stroke: #000;
		stroke-width: 1.0;
	}
</style>
<body>
	<script src="https://d3js.org/d3.v4.min.js"></script>
	<script src="https://d3js.org/topojson.v2.min.js"></script>
	<script src="https://unpkg.com/d3-geo-voronoi"></script>

	<div id="map"></div>

	<script>
		var degrees = 180 / Math.PI,
		    cities = [],
		    width = 1200,
		    height = 625;

    var projection = d3.geoOrthographic()
			.scale(height / 3 - 1);

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

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

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

		svg.append("path")
			.attr("class", "point");


		var update = function () {
			var points = cities.map(function (d) {
				return d.coordinates;
			});

			svg.select(".point")
				.datum({type: "MultiPoint", coordinates: points})
				.attr("d", path);

			var alpha = 0.406,
				distance2 = function (a, b) {
					var d = d3.geoDistance(
						a, b
					);
					return d * d;
				},
				alpha2 = alpha * alpha;

      var tr = d3.geoVoronoi().triangles(points).features
      .map(s => s.properties.sites)
      .filter(t => distance2(t[0], t[1]) < alpha2 &&
						   distance2(t[1], t[2]) < alpha2 &&
						   distance2(t[0], t[2]) < alpha2);

			svg.append('path')
      .datum({
        type: "Polygon",
        coordinates: boundary(tr)
      })
			   .attr("d", path)
			   .attr("class", "geom")
      ;

    
    // gentle animation
      d3.interval(function(elapsed) {
    projection.rotate([ elapsed / 150, 0 ]);
    svg.selectAll('path')
        .attr('d', path);
}, 50);
    };

    
		d3.json("cities.json", function (data) {
			cities = data;
			update();
		});

  
// Computes boundaries of connected triangles, given an array of triangles.
// Jason Davies - //bl.ocks.org/jasondavies/1554783
function boundary(mesh) {
  var counts = {},
      edges = {},
      r,
      result = [];
  // Traverse the edges of all triangles and discard any edges that appear twice.
  mesh.forEach(function(triangle) {
    for (var i = 0; i < 3; i++) {
      var edge = [triangle[i], triangle[(i + 1) % 3]].sort(ascendingCoords).map(String);
      (edges[edge[0]] = (edges[edge[0]] || [])).push(edge[1]);
      (edges[edge[1]] = (edges[edge[1]] || [])).push(edge[0]);
      var k = edge.join(":");
      if (counts[k]) delete counts[k];
      else counts[k] = 1;
    }
  });
  while (1) {
    var k = null;
    // Pick an arbitrary starting point on a boundary.
    for (k in counts) break;
    if (k == null) break;
    result.push(r = k.split(":").map(function(d) { return d.split(",").map(Number); }));
    delete counts[k];
    var q = r[1];
    while (q[0] !== r[0][0] || q[1] !== r[0][1]) {
      var p = q,
          qs = edges[p.join(",")],
          n = qs.length;
      for (var i = 0; i < n; i++) {
        q = qs[i].split(",").map(Number);
        var edge = [p, q].sort(ascendingCoords).join(":");
        if (counts[edge]) {
          delete counts[edge];
          r.push(q);
          break;
        }
      }
    }
  }
  return result;
}

function ascendingCoords(a, b) {
  return a[0] === b[0] ? b[1] - a[1] : b[0] - a[0];
}
  </script>
</body>