--- title: In-N-Out Seismic Challenge (CA) toc: false --- # Quel In-N-Out de Californie est le plus « secoué » ? 🌋🍔 Problématique zinzin : **si tu veux manger un burger sans mini-frisson tectonique, quel In-N-Out faut-il éviter ?** Datasets utilisés : - `docs/data/in-n-out-ca-overpass.json.js` (OpenStreetMap / Overpass, avec ville et adresse) - `docs/data/ca-earthquakes-last365d.json.js` (USGS, séismes M≥2.5 sur 365 jours) ```js const [stores, quakes] = await Promise.all([ FileAttachment("./data/in-n-out-ca-overpass.json").json(), FileAttachment("./data/ca-earthquakes-last365d.json").json(), ]); ``` ```js const [d3, topojson] = await Promise.all([ import("https://cdn.jsdelivr.net/npm/d3@7/+esm"), import("https://cdn.jsdelivr.net/npm/topojson-client@3/+esm"), ]); const usAtlasUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; const us = await fetch(usAtlasUrl).then((r) => r.json()); const states = topojson.feature(us, us.objects.states); const california = states.features.find((s) => Number(s.id) === 6); ``` ```js function haversineKm(lat1, lon1, lat2, lon2) { const toRad = (deg) => (deg * Math.PI) / 180; const R = 6371; const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(a)); } const californiaStores = stores.filter((store) => d3.geoContains(california, [store.longitude, store.latitude]), ); const quakesInCaBox = quakes.filter( (quake) => quake.latitude >= 32 && quake.latitude <= 42.5 && quake.longitude >= -125 && quake.longitude <= -114, ); const radiusKm = 75; const shakeStats = californiaStores.map((store) => { const distances = quakesInCaBox.map((quake) => { const distanceKm = haversineKm( store.latitude, store.longitude, quake.latitude, quake.longitude, ); return { quake, distanceKm }; }); const nearest = d3.least(distances, (d) => d.distanceKm); const nearby = distances.filter((d) => d.distanceKm <= radiusKm); const quakeCount = nearby.length; const maxMagnitude = d3.max(nearby, (d) => d.quake.mag) ?? 0; const avgMagnitude = d3.mean(nearby, (d) => d.quake.mag) ?? 0; const nearestKm = nearest?.distanceKm ?? Infinity; const shakeIndex = quakeCount * 1.5 + maxMagnitude * 12 + Math.max(0, radiusKm - Math.min(nearestKm, radiusKm)) * 0.25; return { ...store, quakeCount, maxMagnitude, avgMagnitude, nearestKm, shakeIndex, }; }); const ranked = d3.sort(shakeStats, (d) => -d.shakeIndex); const top20 = ranked.slice(0, 20); const safest20 = d3.sort(shakeStats, (d) => d.shakeIndex).slice(0, 20); const boss = ranked[0]; ``` ```js display(html`
${boss?.name ?? "N/A"} — ${boss?.city ?? "Ville inconnue"}
Shake Index: ${boss ? boss.shakeIndex.toFixed(1) : "N/A"}
Séismes à ≤ ${radiusKm} km: ${boss?.quakeCount ?? 0} · Magnitude max: ${boss ? boss.maxMagnitude.toFixed(1) : "0.0"} · Plus proche: ${boss && Number.isFinite(boss.nearestKm) ? boss.nearestKm.toFixed(1) : "∞"} km