|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<head> |
|
<link rel="stylesheet" type="text/css" href="styles.css"> |
|
</head> |
|
<body> |
|
<svg width="960" height="960"></svg> |
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/d3-geo-projection.v1.min.js"></script> |
|
<script src="https://d3js.org/topojson.v1.min.js"></script> |
|
|
|
<script> |
|
|
|
d3.select(self.frameElement).style("height", "960px"); |
|
|
|
var svg = d3.select("svg"), |
|
width = +svg.attr("width"), |
|
height = +svg.attr("height"); |
|
|
|
var projection = d3.geoKavrayskiy7() |
|
.scale(75000) |
|
.rotate([139.10, 0]) |
|
.center([0,64.18]) |
|
.translate([width / 2, height / 2]) |
|
.precision(.1); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
// Add a few groups for proper layering |
|
var g = svg.append("g"); |
|
var g2 = svg.append("g"); |
|
var g3 = svg.append("g"); |
|
var g4 = svg.append("g"); |
|
|
|
//////////////////////////////////////////////////////////// |
|
// Add the contour map |
|
d3.json("topo.json", function(error, contours) { |
|
var paths = g.selectAll(".contour") |
|
.data(topojson.feature(contours, contours.objects.contours).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", function(d,i) { return "contour c" + d.properties.gridcode; }); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Add water and streams to the map |
|
d3.json("water.json", function(error, water) { |
|
var paths = g2.selectAll(".water") |
|
.data(topojson.feature(water, water.objects.water).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "water"); |
|
}); |
|
d3.json("streams.json", function(error, water) { |
|
var paths = g2.selectAll(".streams") |
|
.data(topojson.feature(water, water.objects.water).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "streams"); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Add the Yukon Ditch and Acklen ditch |
|
d3.json("aqueducts.json", function(error, aqueduct) { |
|
var paths = g3.selectAll(".aqueduct") |
|
.data(topojson.feature(aqueduct, aqueduct.objects.aqueduct).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "aqueduct") |
|
.attr("id",function(d,i) { return "aqueduct" + i }); |
|
|
|
// Add circles along the aqueduct paths |
|
paths.each(function(d,i) { |
|
var currentPath = d3.select("#aqueduct" + i).node(); |
|
var totalLength = currentPath.getTotalLength(); |
|
var numberOfSymbols = Math.round(totalLength/25); |
|
var spacingOfSymbols = totalLength/numberOfSymbols; |
|
var intialSpacng = spacingOfSymbols/2; |
|
var j = 0; |
|
|
|
while (j < numberOfSymbols) { |
|
var p = currentPath.getPointAtLength( (spacingOfSymbols/2) + (spacingOfSymbols * j) ); |
|
var point = g3.append("circle") |
|
.attr("cx", p.x) |
|
.attr("cy", p.y) |
|
.attr("r", 3) |
|
.attr("class","aqueduct-symbol"); |
|
j++; |
|
} |
|
}); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Add transmission lines |
|
d3.json("power.json", function(error, power) { |
|
var paths = g3.selectAll(".power") |
|
.data(topojson.feature(power, power.objects.power).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "power"); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Add the railroad |
|
d3.json("railroad.json", function(error, railroad) { |
|
var paths = g3.selectAll(".railroad") |
|
.data(topojson.feature(railroad, railroad.objects.railroad).features) |
|
.enter().append("path") |
|
.attr("d", path) |
|
.attr("class", "rail") |
|
.attr("id",function(d,i) { return "r" + i }); |
|
|
|
// Figure out how many cross hatches are needed for each track section |
|
paths.each(function(d,i) { |
|
var currentPath = d3.select("#r" + i).node(); |
|
var totalLength = currentPath.getTotalLength(); |
|
var numberOfSymbols = Math.round(totalLength/6); |
|
var spacingOfSymbols = totalLength/numberOfSymbols; |
|
var intialSpacng = spacingOfSymbols/2; |
|
var j = 0; |
|
|
|
// Add each cross hatch at the right angle |
|
while (j < numberOfSymbols) { |
|
var p1 = currentPath.getPointAtLength( (spacingOfSymbols/2 - 5) + (spacingOfSymbols * j) ); |
|
var p2 = currentPath.getPointAtLength( (spacingOfSymbols/2 + 5) + (spacingOfSymbols * j) ); |
|
var p3 = currentPath.getPointAtLength( (spacingOfSymbols/2) + (spacingOfSymbols * j) ); |
|
|
|
var r = 3; // length of cross hatch from line |
|
|
|
var m = (p2.y - p1.y) / (p1.x - p2.x); |
|
m = 1/m; |
|
var k = r / Math.sqrt( 1 + (m*m) ); |
|
|
|
if (m == Infinity) { |
|
p1.x = p3.x; |
|
p1.y = p3.y + r; |
|
p2.y = p3.y - r; |
|
p2.x = p3.x; |
|
} |
|
else { |
|
p1.x = p3.x + k; |
|
p1.y = p3.y + (m * k); |
|
p2.x = p3.x - k; |
|
p2.y = p3.y - (m * k); |
|
} |
|
|
|
var line = g3.append("line") |
|
.attr("x1",p1.x) |
|
.attr("x2",p2.x) |
|
.attr("y1",p1.y) |
|
.attr("y2",p2.y) |
|
.attr("class", "rail") |
|
.attr("stroke-width",1); |
|
j++; |
|
} |
|
}); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Add some labels |
|
d3.csv("points.csv", function ( error, data ) { |
|
var points = g4.selectAll(".point") |
|
.data(data) |
|
.enter() |
|
.append("circle") |
|
.attr("cx", function(d) { return projection([d.lon,d.lat])[0]; }) |
|
.attr("cy", function(d) { return projection([d.lon,d.lat])[1]; }) |
|
.attr("r",5) |
|
.attr("class","point") |
|
.attr("stroke", function(d) { return d.color }); |
|
|
|
var text = g4.selectAll(".label") |
|
.data(data) |
|
.enter() |
|
.append("text") |
|
.attr("x", function(d) { return projection([d.lon,d.lat])[0] - d.offset; }) |
|
.attr("y", function(d) { return projection([d.lon,d.lat])[1] - 2; }) |
|
.attr("text-anchor", function(d) { return d.anchor }) |
|
.text(function(d) { return " " + d.name }) |
|
.attr("class","text") |
|
.attr("font-size",12); |
|
}); |
|
//////////////////////////////////////////////////////////// |
|
// Regional Labels |
|
g4.append("text") |
|
.attr("x", projection([-139.228,63.97])[0] ) |
|
.attr("y", projection([-139.228,63.97])[1] ) |
|
.attr("text-anchor","middle") |
|
.attr("class","region-label text") |
|
.text("The Gold Fields"); |
|
|
|
g4.append("text") |
|
.attr("x", projection([-138.799,64.3966])[0] ) |
|
.attr("y", projection([-138.799,64.3966])[1] ) |
|
.attr("text-anchor","middle") |
|
.attr("class","region-label text") |
|
.text("Tombstone Range"); |
|
//////////////////////////////////////////////////////////// |
|
// Add a title |
|
g4.append("text") |
|
.attr("x", 200 ) |
|
.attr("y", 100 ) |
|
.attr("class","text") |
|
.attr("font-size",24) |
|
.attr("text-decoration","underline") |
|
.text("The Yukon Ditch"); |
|
//////////////////////////////////////////////////////////// |
|
// Add a scale |
|
var baseWidth = width / 4; |
|
var p1 = projection.invert([width/2 - baseWidth/2, height / 2]); |
|
var p2 = projection.invert([width/2 + baseWidth/2, height / 2]); |
|
var distance = getDistance(p1,p2); |
|
var unit = "m"; |
|
var multiply = 1; |
|
var bestFit = 1; |
|
var increment = 0.1; |
|
var scaleDistance = 0; |
|
var scaleWidth = 0; |
|
|
|
if ( distance > 1000 ) { |
|
unit = "km"; multiply = 0.001; |
|
} |
|
// Adjust distance to a round(er) number |
|
var i = 0; |
|
while (i < 400) { |
|
var temp = getDistance( projection.invert([ width/2 - (baseWidth / 2) + (increment * i), height / 2 ]), projection.invert([ width/2 + baseWidth/2 - (increment * i), height / 2 ])); |
|
var ratio = temp / temp.toPrecision(1); |
|
|
|
// If the second distance is moving away from a cleaner number, reverse direction. |
|
if (i == 1) { |
|
if (Math.abs(1 - ratio) > bestFit) { increment = - increment; } |
|
} |
|
// If we are moving away from a best fit after that, break |
|
else if (i > 2) { |
|
if (Math.abs(1 - ratio) > bestFit) { break } |
|
} |
|
// See if the current distance is the cleanest number |
|
if (Math.abs(1-ratio) < bestFit) { |
|
bestFit = Math.abs(1 - ratio); |
|
scaleDistance = temp; |
|
scaleWidth = (baseWidth) - (2 * increment * i); |
|
} |
|
i++; |
|
} |
|
|
|
// Now to build the scale |
|
var margin = { |
|
left: 600, // scale is this far from the left |
|
bottom:200 // scale is this far off the bottom |
|
} |
|
var bars = []; |
|
var smallBars = 10; |
|
var bigBars = 4; |
|
var odd = true; |
|
var label = false; |
|
|
|
// Populate an array to represent the bars on the scale |
|
for (i = 0; i < smallBars; i++) { |
|
if (smallBars - 1 > i ) { label = false; } else { label = true; } |
|
bars.push( {width: 1 / (smallBars * (bigBars + 1)), offset: i / (smallBars * (bigBars + 1)), label: label, odd: odd } ); |
|
odd = !odd; |
|
} |
|
for (i = 0; i < bigBars; i++) { |
|
bars.push( {width: 1 / (bigBars + 1), offset: (i + 1) / (bigBars + 1), label: true, odd: odd } ); |
|
odd = !odd; |
|
} |
|
|
|
// Append the scale |
|
g.selectAll(".scaleBar") |
|
.data(bars).enter() |
|
.append("rect") |
|
.attr("x", function(d) { return d.offset * scaleWidth + margin.left }) |
|
.attr("y", height - margin.bottom) |
|
.attr("width", function(d) { return d.width * scaleWidth}) |
|
.attr("height", 10) |
|
.attr("fill", function (d) { if (d.odd) { return "#ccc"; } else { return "#222"; } }); |
|
g.selectAll(".scaleText") |
|
.data(bars).enter() |
|
.filter( function (d) { return d.label == true }) |
|
.append("text") |
|
.attr("class","scaleText") |
|
.attr("x",0) |
|
.attr("y",0) |
|
.style("text-anchor","start") |
|
.text(function(d) { return d3.format(",")(((d.offset + d.width) * scaleDistance).toPrecision(2) * multiply); }) |
|
.attr("transform", function(d) { return "translate("+ ((d.offset + d.width) * scaleWidth + margin.left )+","+ (height - margin.bottom -5) +") rotate(-45)" }); |
|
g.append("text") |
|
.attr("x", scaleWidth/2 + margin.left) |
|
.attr("y", height - margin.bottom + 25) |
|
.text( function() { if(unit == "km") { return "kilometers"; } else { return "metres";} }) |
|
.style("text-anchor","middle") |
|
.attr("class","scaleText"); |
|
// End Scale ----------------------------------------- |
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ |
|
/* Latitude/longitude spherical geodesy tools (c) Chris Veness 2002-2016 */ |
|
/* MIT Licence */ |
|
/* www.movable-type.co.uk/scripts/latlong.html */ |
|
/* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html */ |
|
function getDistance(p1,p2) { |
|
|
|
var lat1 = p1[1]; |
|
var lat2 = p2[1]; |
|
var lon1 = p1[0]; |
|
var lon2 = p2[0]; |
|
|
|
var R = 6371e3; // metres |
|
var φ1 = lat1* Math.PI / 180; |
|
var φ2 = lat2* Math.PI / 180; |
|
var Δφ = (lat2-lat1)* Math.PI / 180; |
|
var Δλ = (lon2-lon1)* Math.PI / 180; |
|
var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + |
|
Math.cos(φ1) * Math.cos(φ2) * |
|
Math.sin(Δλ/2) * Math.sin(Δλ/2); |
|
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); |
|
var distance = R * c; |
|
|
|
return distance; |
|
|
|
} |
|
</script> |