chore: initial Observable workspace with technical README

This commit is contained in:
Kerboul
2026-03-19 16:28:35 +01:00
commit c390caf2e2
16 changed files with 4980 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
const now = new Date();
const start = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
const params = new URLSearchParams({
format: "geojson",
starttime: start.toISOString(),
endtime: now.toISOString(),
minmagnitude: "2.5",
minlatitude: "32",
maxlatitude: "42.5",
minlongitude: "-125",
maxlongitude: "-114",
orderby: "time-asc",
});
const url = `https://earthquake.usgs.gov/fdsnws/event/1/query?${params}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`USGS request failed: ${response.status}`);
}
const payload = await response.json();
const features = Array.isArray(payload.features) ? payload.features : [];
const quakes = features
.map((feature) => {
const [longitude, latitude] = feature.geometry?.coordinates ?? [];
const mag = Number(feature.properties?.mag);
if (
!Number.isFinite(latitude) ||
!Number.isFinite(longitude) ||
!Number.isFinite(mag)
) {
return null;
}
return {
id: feature.id,
latitude,
longitude,
mag,
place: feature.properties?.place ?? "",
time: feature.properties?.time ?? null,
};
})
.filter(Boolean);
process.stdout.write(JSON.stringify(quakes));

View File

@@ -0,0 +1,41 @@
const csvUrl =
"https://raw.githubusercontent.com/madmao/castle-compass/master/csv/In%20N%20Out.csv";
const response = await fetch(csvUrl);
if (!response.ok) {
throw new Error(`In-N-Out CSV request failed: ${response.status}`);
}
const rows = (await response.text()).split(/\r?\n/).filter(Boolean);
const stores = rows
.map((line, index) => {
const [latRaw, lonRaw] = line.split(",");
const latitude = Number(latRaw);
const longitude = Number(lonRaw);
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
const inCaliforniaBounds =
latitude >= 32 &&
latitude <= 42.5 &&
longitude >= -125 &&
longitude <= -114;
if (!inCaliforniaBounds) return null;
return {
id: `ino/${index}`,
name: "In-N-Out Burger",
latitude,
longitude,
};
})
.filter(Boolean);
const dedup = new Map();
for (const store of stores) {
const key = `${store.latitude.toFixed(5)},${store.longitude.toFixed(5)}`;
if (!dedup.has(key)) dedup.set(key, store);
}
process.stdout.write(JSON.stringify(Array.from(dedup.values())));

View File

@@ -0,0 +1,118 @@
const query = `
[out:json][timeout:120];
area["ISO3166-2"="US-CA"][admin_level=4]->.searchArea;
(
node["amenity"="fast_food"]["brand"="In-N-Out Burger"](area.searchArea);
way["amenity"="fast_food"]["brand"="In-N-Out Burger"](area.searchArea);
relation["amenity"="fast_food"]["brand"="In-N-Out Burger"](area.searchArea);
node["amenity"="fast_food"]["name"~"In-N-Out",i](area.searchArea);
way["amenity"="fast_food"]["name"~"In-N-Out",i](area.searchArea);
relation["amenity"="fast_food"]["name"~"In-N-Out",i](area.searchArea);
);
out center tags;
`;
function normalizeStores(elements) {
const stores = elements
.map((element) => {
const lat = Number(element.lat ?? element.center?.lat);
const lon = Number(element.lon ?? element.center?.lon);
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
const tags = element.tags ?? {};
const name = tags.name || "In-N-Out Burger";
const city = tags["addr:city"] || "Ville inconnue";
const street = tags["addr:street"] || "";
const housenumber = tags["addr:housenumber"] || "";
const postcode = tags["addr:postcode"] || "";
return {
id: `${element.type}/${element.id}`,
latitude: lat,
longitude: lon,
name,
city,
address: [housenumber, street].filter(Boolean).join(" "),
postcode,
};
})
.filter(Boolean);
const dedup = new Map();
for (const store of stores) {
const key = `${store.latitude.toFixed(5)},${store.longitude.toFixed(5)}`;
if (!dedup.has(key)) dedup.set(key, store);
}
return Array.from(dedup.values());
}
async function fetchFromOverpass() {
const endpoints = [
"https://overpass-api.de/api/interpreter",
"https://overpass.kumi.systems/api/interpreter",
"https://overpass.openstreetmap.fr/api/interpreter",
];
for (const endpoint of endpoints) {
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
},
body: new URLSearchParams({ data: query }),
});
if (!response.ok) continue;
const payload = await response.json();
const elements = Array.isArray(payload.elements) ? payload.elements : [];
const stores = normalizeStores(elements);
if (stores.length >= 50) return stores;
} catch {}
}
return null;
}
async function fetchFallbackCsv() {
const url =
"https://raw.githubusercontent.com/madmao/castle-compass/master/csv/In%20N%20Out.csv";
const response = await fetch(url);
if (!response.ok)
throw new Error(`Fallback CSV request failed: ${response.status}`);
const lines = (await response.text()).split(/\r?\n/).filter(Boolean);
const stores = lines
.map((line, index) => {
const [latRaw, lonRaw] = line.split(",");
const latitude = Number(latRaw);
const longitude = Number(lonRaw);
if (!Number.isFinite(latitude) || !Number.isFinite(longitude))
return null;
const inCaliforniaBounds =
latitude >= 32 &&
latitude <= 42.5 &&
longitude >= -125 &&
longitude <= -114;
if (!inCaliforniaBounds) return null;
return {
id: `fallback/${index}`,
latitude,
longitude,
name: "In-N-Out Burger",
city: "Ville inconnue",
address: "",
postcode: "",
};
})
.filter(Boolean);
return stores;
}
const stores = (await fetchFromOverpass()) ?? (await fetchFallbackCsv());
process.stdout.write(JSON.stringify(stores));

8
docs/data/sample.json.js Normal file
View File

@@ -0,0 +1,8 @@
const now = new Date().toISOString();
process.stdout.write(
JSON.stringify({
message: "Sample data loader OK",
generatedAt: now,
}),
);

View File

@@ -0,0 +1,83 @@
const candidates = [
"https://raw.githubusercontent.com/richaude/blue-book/master/scrubbed.csv",
"https://raw.githubusercontent.com/alecfirrincieli/UFO-sightings/main/ufo-sightings.csv",
];
let csvText = null;
for (const url of candidates) {
try {
const response = await fetch(url);
if (!response.ok) continue;
const text = await response.text();
if (text && text.includes("datetime,city,state")) {
csvText = text;
break;
}
} catch {}
}
if (!csvText) {
throw new Error("Unable to fetch UFO sightings dataset from public mirrors.");
}
const { csvParse } = await import("d3-dsv");
const rows = csvParse(csvText);
const normalizeKey = (value) =>
String(value ?? "")
.trim()
.toLowerCase();
function readField(row, key) {
const target = normalizeKey(key);
for (const [field, value] of Object.entries(row)) {
if (normalizeKey(field) === target) return value;
}
return undefined;
}
function parseNumber(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
const ufo = rows
.map((row, index) => {
const state = String(readField(row, "state") ?? "")
.trim()
.toLowerCase();
const country = String(readField(row, "country") ?? "")
.trim()
.toLowerCase();
const latitude = parseNumber(readField(row, "latitude"));
const longitude = parseNumber(readField(row, "longitude"));
if (state !== "ca" || country !== "us") return null;
if (latitude === null || longitude === null) return null;
const inCaliforniaBounds =
latitude >= 32 &&
latitude <= 42.5 &&
longitude >= -125 &&
longitude <= -114;
if (!inCaliforniaBounds) return null;
const durationSeconds =
parseNumber(readField(row, "duration (seconds)")) ?? 0;
return {
id: `ufo/${index}`,
datetime: readField(row, "datetime") ?? null,
city: readField(row, "city") ?? "Unknown",
state,
shape: String(readField(row, "shape") ?? "unknown").toLowerCase(),
durationSeconds,
comments: readField(row, "comments") ?? "",
latitude,
longitude,
};
})
.filter(Boolean);
process.stdout.write(JSON.stringify(ufo));

View File

@@ -0,0 +1,83 @@
const candidates = [
"https://raw.githubusercontent.com/richaude/blue-book/master/scrubbed.csv",
"https://raw.githubusercontent.com/alecfirrincieli/UFO-sightings/main/ufo-sightings.csv",
];
let csvText = null;
for (const url of candidates) {
try {
const response = await fetch(url);
if (!response.ok) continue;
const text = await response.text();
if (text && text.includes("datetime,city,state")) {
csvText = text;
break;
}
} catch {}
}
if (!csvText) {
throw new Error("Unable to fetch UFO sightings dataset from public mirrors.");
}
const { csvParse } = await import("d3-dsv");
const rows = csvParse(csvText);
const normalizeKey = (value) =>
String(value ?? "")
.trim()
.toLowerCase();
function readField(row, key) {
const target = normalizeKey(key);
for (const [field, value] of Object.entries(row)) {
if (normalizeKey(field) === target) return value;
}
return undefined;
}
function parseNumber(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
const ufo = rows
.map((row, index) => {
const state = String(readField(row, "state") ?? "")
.trim()
.toLowerCase();
const country = String(readField(row, "country") ?? "")
.trim()
.toLowerCase();
const latitude = parseNumber(readField(row, "latitude"));
const longitude = parseNumber(readField(row, "longitude"));
if (state !== "ca" || country !== "us") return null;
if (latitude === null || longitude === null) return null;
const inCaliforniaBounds =
latitude >= 32 &&
latitude <= 42.5 &&
longitude >= -125 &&
longitude <= -114;
if (!inCaliforniaBounds) return null;
const durationSeconds =
parseNumber(readField(row, "duration (seconds)")) ?? 0;
return {
id: `ufo/${index}`,
datetime: readField(row, "datetime") ?? null,
city: readField(row, "city") ?? "Unknown",
state,
shape: String(readField(row, "shape") ?? "unknown").toLowerCase(),
durationSeconds,
comments: readField(row, "comments") ?? "",
latitude,
longitude,
};
})
.filter(Boolean);
process.stdout.write(JSON.stringify(ufo));