Simple network flooding represented with voronoi. Click anywhere on the map to see the network distance to the rest of the network. Basically the same thing as coloring the nodes but with more visual artifacts because voronoi can be problematic.
This example implements a simple weighted breadth-first search to calculate distance and should work with any topojson file that has linestrings. This kind of thing is very useful for identifying areas of a network dataset that need more correcting or additional data.
xxxxxxxxxx
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<title>Voronoi Network Flooding</title>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="d3map.css" />
<link type="text/css" rel="stylesheet" href="https://raw.githubusercontent.com/emeeks/d3-carto-map/master/examples/example.css" />
</head>
<style>
html,body {
height: 100%;
width: 100%;
margin: 0;
}
#map {
height: 100%;
width: 100%;
position: absolute;
}
.reproject {
position: absolute;
z-index: 99;
left: 50px;
top: 250px;
}
.node {
fill: blue;
stroke: black;
stroke-width: 1
}
.voronoi {
fill-opacity: .5;
stroke-opacity: .25;
}
.roads {
stroke: brown;
stroke-width: 1px;
fill: none;
}
</style>
<script>
function makeSomeMaps() {
pathSource = 0;
map = d3.carto.map();
d3.select("#map").call(map);
map.centerOn([-88,39],"latlong");
map.setScale(4);
map.refresh();
wcLayer = d3.carto.layer.tile();
wcLayer
.tileType("stamen")
.path("watercolor")
.label("Watercolor")
.visibility(true)
postLayer = d3.carto.layer.topojson();
postLayer
.path("uspost.topojson")
.label("Postal Routes")
.cssClass("roads")
.renderMode("svg")
.on("load", createMatrix);
map.addCartoLayer(wcLayer).addCartoLayer(postLayer);
function createMatrix() {
postdata = postLayer.features()
edgeList = [];
edgeMap = {};
nodes = [];
nodeHash = {};
for (x in postdata) {
var line = postdata[x].geometry.coordinates;
var lS = line[0];
var lE = line[line.length - 1];
var nA = [lS,lE];
for (y in nA) {
if (!nodeHash["node" + Math.ceil(nA[y][0] * 1000) + (nA[y][1] * 1000)]) {
var newNode = {label: "Node " + nodes.length, id: nodes.length.toString(), coordinates: [nA[y]], x: nA[y][0], y: nA[y][1]}
nodeHash["node" + Math.ceil(nA[y][0] * 1000) + (nA[y][1] * 1000)] = newNode;
nodes.push(newNode)
}
}
postdata[x].properties.source = nodeHash["node" + Math.ceil(lS[0] * 1000) + (lS[1] * 1000)];
postdata[x].properties.target = nodeHash["node" + Math.ceil(lE[0] * 1000) + (lE[1] * 1000)];
postdata[x].properties.cost = d3.geo.length(postdata[x]) * 6371;
}
var xExtent = d3.extent(nodes, function(d) {return d.x});
var yExtent = d3.extent(nodes, function(d) {return d.y});
voronoi = d3.geom.voronoi()
.clipExtent([xExtent,yExtent])
.clipExtent([[xExtent[0],yExtent[0]],[xExtent[1],yExtent[1]]])
.x(function (d) {return d.x})
.y(function (d) {return d.y});
vorFeatures = voronoi(nodes).map(function(d,i) {
d.push(d[0]);
return {type:"Feature", id: i, node: nodes[i], geometry: {type: "Polygon", coordinates: [d]}}
})
featureLayer = d3.carto.layer.featureArray();
featureLayer
.features(vorFeatures)
.label("Feature Array")
.cssClass("voronoi")
.renderMode("svg")
.clickableFeatures(true)
.on("load", function() {d3.selectAll("path.voronoi").on("click", function(d) {flood(d.node.id)})});
map.addCartoLayer(featureLayer)
for (x in postdata) {
if (edgeMap[postdata[x].properties.source.id]) {
edgeMap[postdata[x].properties.source.id][postdata[x].properties.target.id] = postdata[x].properties.cost;
}
else {
edgeMap[postdata[x].properties.source.id] = {};
edgeMap[postdata[x].properties.source.id][postdata[x].properties.target.id] = postdata[x].properties.cost;
}
if (edgeMap[postdata[x].properties.target.id]) {
edgeMap[postdata[x].properties.target.id][postdata[x].properties.source.id] = postdata[x].properties.cost;
}
else {
edgeMap[postdata[x].properties.target.id] = {};
edgeMap[postdata[x].properties.target.id][postdata[x].properties.source.id] = postdata[x].properties.cost;
}
}
graph = new Graph(edgeMap);
}
function flood(siteID) {
siteDistance = d3.keys(graph.map).map(function(d) {return Infinity});
siteDistance[siteID] = 0;
var map = graph.map;
var calculatedSites = [siteID];
var connectedSites = d3.keys(graph.map[siteID]);
var visitedSites = [siteID];
var sitesToVisit = [siteID];
var currentNode = siteID;
var currentCost = 0;
while (sitesToVisit.length > 0) {
sitesToVisit.splice(0,1);
for (x in connectedSites) {
if (calculatedSites.indexOf(connectedSites[x]) == -1) {
calculatedSites.push(connectedSites[x]);
siteDistance[connectedSites[x]] = currentCost + map[currentNode][connectedSites[x]];
}
else {
siteDistance[connectedSites[x]] = Math.min(currentCost + map[currentNode][connectedSites[x]], siteDistance[connectedSites[x]]);
}
if (visitedSites.indexOf(connectedSites[x]) == -1 && sitesToVisit.indexOf(connectedSites[x]) == -1) {
sitesToVisit.push(connectedSites[x]);
}
}
visitedSites.push(currentNode)
//sort sitesToVisit
sitesToVisit = sitesToVisit.sort(function(a,b) {
if (siteDistance[a] < siteDistance[b])
return -1;
if (siteDistance[a] > siteDistance[b])
return 1;
return 0;
})
currentNode = sitesToVisit[0];
currentCost = siteDistance[currentNode];
connectedSites = d3.keys(graph.map[currentNode]);
}
color = d3.scale.linear().domain([0,500,3500]).range(["green","yellow","red"])
d3.selectAll("path.voronoi").style("fill", "lightgray")
.transition()
.delay(function(d) {return siteDistance[d.id] == Infinity ? 5000 : siteDistance[d.id] * 2})
.duration(1000)
.style("fill", function(d) {return siteDistance[d.id] == Infinity ? "gray" : color(siteDistance[d.id])})
}
}
</script>
<body onload="makeSomeMaps()">
<div id="map"></div>
<footer>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="https://d3js.org/topojson.v1.min.js" type="text/javascript">
</script>
<script src="https://d3js.org/d3.geo.projection.v0.min.js" type="text/javascript">
</script>
<script src="/emeeks/f3105fda25ff785dc5ed/example/tile.js" type="text/javascript">
</script>
<script src="/emeeks/f3105fda25ff785dc5ed/example/d3.quadtiles.js" type="text/javascript">
</script>
<script src="/emeeks/f3105fda25ff785dc5ed/example/d3.geo.raster.js" type="text/javascript">
</script>
<script src="https://cdn.jsdelivr.net/gh/emeeks/d3-carto-map/d3.carto.map.js" type="text/javascript">
</script>
<script src="dijkstra.js" type="text/javascript">
</script>
</footer>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
Modified http://d3js.org/topojson.v1.min.js to a secure url
Modified http://d3js.org/d3.geo.projection.v0.min.js to a secure url
Updated missing url http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/tile.js to /emeeks/f3105fda25ff785dc5ed/example/tile.js
Updated missing url http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.quadtiles.js to /emeeks/f3105fda25ff785dc5ed/example/d3.quadtiles.js
Updated missing url http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.geo.raster.js to /emeeks/f3105fda25ff785dc5ed/example/d3.geo.raster.js
Updated missing url https://rawgit.com/emeeks/d3-carto-map/master/d3.carto.map.js to https://cdn.jsdelivr.net/gh/emeeks/d3-carto-map/d3.carto.map.js
https://d3js.org/d3.v3.min.js
https://d3js.org/topojson.v1.min.js
https://d3js.org/d3.geo.projection.v0.min.js
https://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/tile.js
https://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.quadtiles.js
https://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.geo.raster.js
https://rawgit.com/emeeks/d3-carto-map/master/d3.carto.map.js