Published
Edited
Feb 23, 2020
Importers
1 star
Insert cell
md`# Midnight

Des fonctions qui récupèrent les données et les mettent en forme`
Insert cell
Insert cell
ods = await fetch("https://airtable-proxy.tristramg.now.sh/api/get-ods").then(r => r.json())
Insert cell
stats = function(route){
const validCities = _(route).map(name => citiesIdByName[name]).compact().value()
const segments = []
const missingSegments = []
let distance = 0;
for (let i = 1; i < validCities.length; i++) {
const from = cities[validCities[i - 1]]
const to = cities[validCities[i]]
const od = ods[validCities[i - 1] + validCities[i]] || ods[validCities[i] + validCities[i - 1]]
distance += haversine([from.longitude, from.latitude], [to.longitude, to.latitude])
if (od !== undefined) {
segments.push(od)
}
}
const thisODs = []
let airplane = 0
for (let i = 0; i < validCities.length; i++) {
for (let j = i + 1; j < validCities.length; j++) {
const od = ods[validCities[i] + validCities[j]] || ods[validCities[j] + validCities[i]]
if (od === undefined) {
missingSegments.push([cities[validCities[i]].City, cities[validCities[j]].City])
} else {
thisODs.push(od)
airplane += (od['airplane seats'] || 0)
}
}
}

const citiesData = _.map(validCities, city => cities[city])
return {
citiesData,
foundCities: validCities.length !== 0,
missingCities: _.filter(route, city => citiesIdByName[city] === undefined),
airplane,
duration: _(segments).map(s => s.Duration).sum(),
segments,
missingSegments,
distance,
competition: _(thisODs).map('Competition').uniq().compact().value(),
remarks: _(thisODs).map('Remarks').uniq().compact().value(),
'operational difficulties': _(thisODs).map('operational difficulties').uniq().compact().value(),
}
}
Insert cell
statsWithBranches = function(routes) {
const allStats = _.map(routes, stats)
const citiesData = _(allStats).flatMap('citiesData').union().value()
return {
citiesData,
missingCities: _(allStats).flatMap('missingCities').union().value(),
cityPop: _(citiesData).map('Population').sum(),
cityAreaPop: _(citiesData).map('Population urban area').sum(),
airplane: _.sumBy(allStats, s => s.airplane),
duration: _(allStats).map('duration').max(),
distance: _(allStats).map('distance').max(),
segments: _(allStats).flatMap('segments').union().uniq().value(),
missingSegments: _(allStats).flatMap('missingSegments').union().uniq().value(),
competition: _(allStats).flatMap('competition').union().value(),
remarks: _(allStats).flatMap('remarks').union().value(),
'operational difficulties': _(allStats).flatMap('operational difficulties').union().value(),
}
}
Insert cell
statsWithBranches([['Paris', 'Bruxelles', 'Hannover', 'Berlin'],
['Paris', 'Bruxelles', 'Bremen', 'Hamburg', 'Flens']])
Insert cell
citiesIdByName = _(cities).map((c, id) => [c.City, id]).fromPairs().value()
Insert cell
summary = function(routes) {
const stat = statsWithBranches(routes)
return html`
<h2>Statistiques</h2>
<dl>
<dt>Durée totale</dt><dd>${stat.duration} heures</dd>
<dt>Population des villes</dt><dd>${(stat.cityPop / 1e6).toFixed(2)} millions</dd>
<dt>Population des aires urbaines</dt><dd>${(stat.cityAreaPop / 1e6).toFixed(2)} millions</dd>
<dt>Sièges d’avion proposés par semaine et par sens</dt><dd>${stat.airplane}</dd>
<dt>Longueur (à vol d’oiseau)</dt><dd>${stat.distance.toFixed(0)} km</dd>
</dl>
`
}
Insert cell
pathOfRoute = function(route, context, projection) {
let first = true;
for (const cityName of route) {
const cityId = citiesIdByName[cityName]
const city = cities[cityId]
if (city !== undefined) {
const point = projection([city.longitude, city.latitude])
if (first) {
context.moveTo(point[0], point[1])
first = false
} else {
context.lineTo(point[0], point[1])
}
}
}
}
Insert cell
pathOfRoutes = function(routes, context, projection) {
for (const route of routes) {
pathOfRoute(route, context, projection)
}
}
Insert cell
map = function(routes, localWidth){
if(localWidth === undefined) {
localWidth = width
}
const height = localWidth * 0.7;
const projection = d3.geoConicConformal()
.parallels([35, 65])
.rotate([-5, 0])
.scale(localWidth * 1.3)
.center([10, 50])
.translate([localWidth / 2, height / 2])
.clipExtent([[0, 0], [localWidth, height]])
.precision(0.2)

const context = DOM.context2d(localWidth, height);
const path = d3.geoPath(projection, context);
const graticule = d3.geoGraticule10();
context.beginPath(), path(graticule), context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(countries), context.strokeStyle = "#555", context.fillStyle = "rgba(0, 0, 0, .8)", context.fill(), context.stroke();
for (const [name, city] of Object.entries(cities)) {
context.beginPath(), path({type: "Point", coordinates: [city.longitude, city.latitude]}), path.pointRadius(2), context.fillStyle = "#a55", context.fill();
}
context.beginPath(), pathOfRoutes(routes, context, projection), context.strokeStyle = "#aaf", context.lineWidth = 2, context.stroke()

return context.canvas;
}
Insert cell
ranking = function(routes) {
const trainSize = 400
const stat = statsWithBranches(routes)
const airplane = 400 * 7 * 100 / stat.airplane
const cityPop = 400 * 365 * 1000 / stat.cityPop
const cityAreaPop = 400 * 365 * 1000 / stat.cityAreaPop
return html`
<h2>Ranking</h2>
<p> Tous les chiffres sont pour un départ par jour et par sens, pour un train long (14 voitures standard, soit 400m)</p>
<dl>
<dt>Équivalent en part de l’aérien</dt><dd>${airplane.toFixed(2)} %</dd>
<dt>Part de la population des villes</dt><dd>${cityPop.toFixed(2)} ‰</dd>
<dt>Part de la population des aires urbaines</dt><dd>${cityAreaPop.toFixed(2)} ‰</dd>
<dt>Compétition</dt><dd>${_.join(stat.competition, ', ')}</dd>
<dt>Difficultées opérationnelles</dt><dd>${_.join(stat['operational difficulties'], ', ')}</dd>
<dt>Remarques</dt><dd>${_.join(stat.remarks, ', ')}</dd>
</dl>
`
}
Insert cell
debug = function(routes) {
const stat = statsWithBranches(routes)
let result = ''
if (stat.missingCities.length > 0) {
result += "### Arrêts non retrouvés\n"
for (const c of stat.missingCities) {
result += `* ${c}\n`
}
}
if (stat.missingSegments.length > 0) {
result += "### Segments manquants\n"
for (const s of stat.missingSegments) {
result += `* ${s[0]} ⇌ ${s[1]}\n`
}
}

return md`${result}`
}
Insert cell
d3 = require("d3-fetch@1", "d3-geo@1", "d3-shape@1")
Insert cell
Insert cell
Insert cell
countries = topojson.feature(world, world.objects.countries)
Insert cell
Insert cell
haversine = function([lon1, lat1], [lon2, lat2]) {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}

const R = 6371; // km
//has a problem with the .toRad() method below.
const x1 = lat2-lat1;
const dLat = x1.toRad();
const x2 = lon2-lon1;
const dLon = x2.toRad();
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
Insert cell
fiche = function(routes, start) {
return html`
${timetable(routes, start || 20)}
${ranking(routes)}
<div>
<div class="summary" style="width: 45%; float: left;">${summary(routes)}</div>
<div class="map" style="width: 45%; margin-left: 50%;">${map(routes, width/2)}</div>
</div>

<h2>Debug</h2>
${debug(routes)}
`
}
Insert cell
timetable = function(routes, start) {
function duration(origin, destination) {
const o = citiesIdByName[origin]
const d = citiesIdByName[destination]
const od = ods[o + d] || ods[d + o]
return od? od.Duration : 0
}
const stops = {}
for (const route in routes) {
let time = start
let prev = undefined
for (const stop of routes[route]) {
if (prev) {
time += duration(prev, stop)
}
prev = stop
if (!stops[stop]) {
stops[stop] = routes.map(x => '')
}
console.log(stop, stops[stop][0], time)
if(stops[stop][0] != formatTime(time)) {
stops[stop][route] = formatTime(time)
}
}
}
const tabStop = []
for (const stop in stops) {
tabStop.push([stop].concat(stops[stop]))
}
return html`
<table>
<thead><th>Départ</th>${routes.map(x=> html`<th></th>`)}
</thead>
${tabStop.map(x => html`<tr>${x.map(x => html`<td>${x}</td>`)}</tr>`)}
`

}
Insert cell
timetable([['Paris', 'Bruxelles', 'Hannover', 'Berlin'],
['Paris', 'Bruxelles', 'Bremen', 'Hamburg']], 20)
Insert cell
map([['Paris', 'Bruxelles', 'Hannover', 'Berlin']])
Insert cell
formatTime = function(time) {
const hours = Math.floor(time % 24)
const mins = Math.floor((time * 60) % 60)
return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more