by Philippe Rivière and Martin Hardouin-Duparc
forked from Fil's block: Vanishing Earth
forked from Fil's block: Translucent Earth
forked from Fil's block: Projection Paint [UNLISTED]
license: gpl-3.0 |
by Philippe Rivière and Martin Hardouin-Duparc
forked from Fil's block: Vanishing Earth
forked from Fil's block: Translucent Earth
forked from Fil's block: Projection Paint [UNLISTED]
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<body> | |
<script src="https://unpkg.com/d3@4"></script> | |
<script src="https://unpkg.com/d3-geo-projection"></script> | |
<script src="https://unpkg.com/topojson"></script> | |
<script> | |
var width = 960, | |
height = 500; | |
var radius = height / 2 - 20, | |
scale = radius, | |
velocity = .015; | |
var projection = d3.geoOrthographic() | |
.fitExtent([[0,0],[300,300]], {type:"Sphere"}); | |
var canvas = d3.select("body").append("canvas") | |
.attr("width", width) | |
.attr("height", height); | |
var context = canvas.node().getContext("2d"); | |
// retina display | |
var devicePixelRatio = window.devicePixelRatio || 1; | |
canvas.style('width', canvas.attr('width')+'px'); | |
canvas.style('height', canvas.attr('height')+'px'); | |
canvas.attr('width', canvas.attr('width') * devicePixelRatio); | |
canvas.attr('height', canvas.attr('height') * devicePixelRatio); | |
context.scale(devicePixelRatio,devicePixelRatio); | |
var path = d3.geoPath() | |
.projection(projection) | |
.context(context); | |
var backprojection = d3.geoProjection(function(a,b) { | |
return d3.geoOrthographicRaw(-a,b); | |
}).clipAngle(90) | |
.translate(projection.translate()) | |
.scale(projection.scale()) | |
var backpath = d3.geoPath() | |
.projection(backprojection) | |
.context(context); | |
var projection2, path2; | |
var projname = 'Bertin1953'; | |
var projs = ['Bertin1953', 'Mercator', 'Gnomonic', 'Equirectangular']; | |
newproj = function() { | |
projection2 = d3['geo' + projname]().fitExtent([[300,0],[960,500]], {type:"Sphere"}); | |
path2 = d3.geoPath() | |
.projection(projection2) | |
.context(context); | |
projname = projs[Math.random() * projs.length | 0]; | |
}; | |
newproj() | |
d3.json("https://unpkg.com/world-atlas/world/110m.json", function(error, world) { | |
if (error) throw error; | |
var land = topojson.feature(world, world.objects.land); | |
var points = (d3.merge(d3.merge(land.features[0].geometry.coordinates))) | |
.map((d,i) => (d.rank = i, d)) | |
//.filter((d,i)=> i%2) | |
// random order | |
//points = points.sort((a,b) => Math.random() - Math.random()) | |
// by longitude | |
//points = points.sort((a,b) => -a[0]+b[0]) | |
// by latitude | |
//points = points.sort((a,b) => -a[1]+b[1]) | |
points.forEach(function(point) { | |
point.vecteur = Math.random() < 0.04; | |
}) | |
render = function(elapsed) { | |
context.clearRect(0, 0, width, height); | |
var rotate = [velocity * elapsed, -40]; | |
// if (elapsed > 10000) projection2 = d3.geoEquirectangular().fitExtent([[300,0],[960,500]], {type:"Sphere"}), path2 = d3.geoPath().projection(projection2); | |
projection.rotate(rotate); | |
if (projection2.rotate) projection2.rotate([rotate[0], 0]); | |
var t_intro = 3000, | |
t_projection = 5000, | |
t_lancer = 2800, | |
t_pleine = t_lancer + 3000, | |
t_disparition = 1500; | |
var periode = (t_intro + t_projection + t_lancer + t_pleine + t_disparition), | |
N = points.length, | |
t = elapsed % periode; | |
if (t < 40) newproj(); | |
var tt = (t - (t_intro + t_projection + t_lancer))/(t_pleine + t_disparition); | |
if (tt > 0 && tt < 1) { | |
context.beginPath(); | |
path2(land); | |
context.fillStyle = '#f4f4f4f4'; | |
context.globalAlpha = Math.min(1, 7 * tt * (1-tt)); | |
context.fill(); | |
context.globalAlpha = 1; | |
} | |
points.forEach(function(point,i){ | |
point.A = projection(point); | |
point.B = projection2(point); | |
point.segment = [point.A,point.B]; | |
point.bout = 1; | |
point.ti = t - i / N * t_projection - t_intro; | |
point.td = t - i / N * t_projection * 0.5 - t_intro; | |
if (point.ti < 0) return point.bout = 0; | |
if (point.ti > t_projection + t_lancer + t_pleine + t_disparition) return point.bout = 0; | |
if (point.ti < 0.4 * t_lancer) { | |
var p = 1 - point.ti / (0.4 * t_lancer), | |
C = [p * point.A[0] + (1-p) * point.B[0], p * point.A[1] + (1-p)*point.B[1]]; | |
point.segment = [point.A,C]; | |
point.bout = 0.5; | |
} else if (point.ti < 0.7 * t_lancer) { | |
var p = -(point.ti - 0.7 * t_lancer) / ((0.7 - 0.4) * t_lancer), | |
C = [p * point.A[0] + (1-p) * point.B[0], p * point.A[1] + (1-p)*point.B[1]]; | |
point.segment = [C,point.B]; | |
point.bout = 1; | |
} | |
else if (point.td < t_lancer + t_pleine) { | |
point.segment = [point.B,point.B]; | |
point.bout = 1; | |
} | |
else { | |
// disparition | |
point.segment = [point.B,point.B]; | |
point.bout = Math.max(0,1 - (point.td - t_lancer - t_pleine) / t_disparition); | |
} | |
}); | |
points.forEach(point => { | |
var p = projection.invert(projection(point)); | |
point.derriere = (d3.geoDistance(p, point) > 0.01); | |
}) | |
context.beginPath(); | |
points.filter(point => point.bout) | |
.filter(point => point.vecteur) | |
.filter(point => point.derriere) | |
.forEach(function(point){ | |
context.moveTo(...point.segment[0]); | |
context.lineTo(...point.segment[1]); | |
}); | |
context.lineWidth = .5; | |
context.strokeStyle = 'rgba(100,100,100,0.6)'; | |
context.stroke(); | |
backprojection.rotate([rotate[0] + 180, -rotate[1]]); | |
context.beginPath(); | |
path({type:"Sphere"}); | |
context.fillStyle = 'white'; | |
context.fill(); | |
context.beginPath(); | |
backpath(land); | |
context.fillStyle = '#f4f4f4f4'; | |
context.fill(); | |
context.beginPath(); | |
path(d3.geoGraticule()()); | |
path2(d3.geoGraticule()()); | |
context.lineWidth = 0.05; | |
context.strokeStyle = 'black'; | |
context.stroke(); | |
context.beginPath(); | |
path(land); | |
//path2(land); | |
context.lineWidth = 1; | |
context.strokeStyle = 'black'; | |
context.stroke(); | |
context.fillStyle = '#eee'; | |
context.fill(); | |
context.beginPath(); | |
points.filter(point => point.bout) | |
.filter(point => point.vecteur) | |
.filter(point => !point.derriere) | |
.forEach(function(point){ | |
context.moveTo(...point.segment[0]); | |
context.lineTo(...point.segment[1]); | |
}); | |
context.lineWidth = .5; | |
context.strokeStyle = 'rgba(100,100,100,0.7)'; | |
context.stroke(); | |
context.beginPath(); | |
var last = [0,0]; | |
points | |
.slice() | |
.sort((a,b) => a.rank - b.rank) | |
.filter(point => point.bout).forEach(function(point){ | |
context.lineWidth = 1; | |
// context.moveTo(...point.segment[1]); | |
// context.lineTo(point.segment[1][0]+point.bout*2, point.segment[1][1]+point.bout); | |
var p = point.segment[1]; | |
if (Math.max(Math.abs(p[0] - last[0]), Math.abs(p[1] - last[1])) < 10) | |
context.lineTo(p[0], p[1]); | |
else { | |
context.moveTo(p[0]-1, p[1]); | |
context.lineTo(p[0], p[1]); | |
} | |
last = p; | |
}) | |
context.lineWidth = 1.; | |
context.strokeStyle = '#333'; | |
context.stroke(); | |
context.beginPath(); | |
path({type: "Sphere"}); | |
path2({type: "Sphere"}); | |
context.lineWidth = 1.; | |
context.strokeStyle = 'black'; | |
context.stroke(); | |
}; | |
render(0); | |
d3.timer(render) | |
}); | |
d3.select(self.frameElement).style("height", height + "px"); | |
</script> |