A quick merger attempt of a rough draft d3 marker clusterer and a very rough draft of unified geotile layout and geoprojection.
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg, canvas {
position: absolute;
top: 0px;
left: 0px;
}
</style>
<svg width="960" height="500"></svg>
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="d3-cluster.js"></script>
<script src="d3-slippy.js"></script>
<script>
// Canvas and SVG:
var canvas = d3.select("canvas");
var ctx = canvas.node().getContext("2d");
var svg = d3.select("svg");
// Basic parameters:
var width = +canvas.attr("width");
var height = +canvas.attr("height");
var center = [-75.5, 38];
var slippy = d3.geoTile()
.tileSet("ESRI_OceanBasemap")
.size(svg)
.center(center)
.scale(32768/Math.PI/2)
.zoomTranslate([0,0])
// Create a g for the tiles:
var raster = svg.append("g");
d3.csv("shipwrecks.csv").then(function(data) {
// Set up nodes:
resetNodes(data); // initialize
// Set up clusterer
var cluster = d3.cluster()
.nodes(data)
.on("tick", ticked)
// Set up zoom.
var zoom = d3.zoom()
.on("zoom",zoomed);
// Call the zoom:
canvas.call(zoom)
.call(zoom.transform, slippy.zoomIdentity());
function zoomed() {
cluster.stop();
// Scale values:
var k = d3.event.transform.k;
var x = d3.event.transform.x
var y = d3.event.transform.y;
var t0 = slippy.zoomTranslate();
var t = [x,y];
// Update the cluster:
// If scale changes:
if (slippy.zoomScale() != k) {
slippy.zoomTransform(d3.event.transform)
resetNodes(data);
cluster.nodes(data)
.alpha(1);
}
// Otherwise, If translated only:
else {
slippy.zoomTranslate([x,y])
clear();
data.forEach(function(node) {
node.x += t[0] - t0[0];
node.y += t[1] - t0[1];
drawCircle(node);
})
}
cluster.restart();
raster.call(slippy.tile)
}
function ticked(x,y) {
clear();
// Update cluster nodes:
this.nodes(data.filter(function(d) { return d.r != 0; }))
// draw still active nodes:
data.filter(function(d) { return d.r != 0; })
.forEach(drawCircle)
attribute(); // tile attribution
}
})
// Helper functions:
function resetNodes(nodes) {
nodes.forEach(function(node) {
var p = slippy([+node.long,+node.lat]);
node.x = p[0];
node.y = p[1];
node.r = 2;
node.a = Math.PI * node.r * node.r;
node.collided = false;
node.count = 1;
})
}
function drawCircle(d) {
ctx.fillStyle = d.collided ? ( d.r > 20 ? "#a8ddb5" : "#43a2ca" ) : "#0868ac";
ctx.beginPath();
ctx.moveTo(d.x, d.y);
ctx.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
ctx.fill();
if(d.r > 20) drawText(d);
}
function drawText(d) {
ctx.font = d.r / 3 + "px Arial";
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText(d.count,d.x,d.y+d.r/9);
}
function clear() {
ctx.clearRect(0, 0, width, height);
}
function attribute() {
ctx.font = "10px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "black";
ctx.fillText("Tiles \u00A9 Esri - Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri", 4, height-4);
}
function stringify(scale, translate) {
var k = scale / 256, r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
</script>
https://d3js.org/d3.v5.min.js
https://d3js.org/d3-tile.v0.0.min.js