chore: initial Observable workspace with technical README
This commit is contained in:
47
docs/data/ca-earthquakes-last365d.json.js
Normal file
47
docs/data/ca-earthquakes-last365d.json.js
Normal 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));
|
||||
41
docs/data/in-n-out-ca-csv.json.js
Normal file
41
docs/data/in-n-out-ca-csv.json.js
Normal 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())));
|
||||
118
docs/data/in-n-out-ca-overpass.json.js
Normal file
118
docs/data/in-n-out-ca-overpass.json.js
Normal 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
8
docs/data/sample.json.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const now = new Date().toISOString();
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
message: "Sample data loader OK",
|
||||
generatedAt: now,
|
||||
}),
|
||||
);
|
||||
83
docs/data/ufo-ca-sightings-v2.json.js
Normal file
83
docs/data/ufo-ca-sightings-v2.json.js
Normal 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));
|
||||
83
docs/data/ufo-ca-sightings.json.js
Normal file
83
docs/data/ufo-ca-sightings.json.js
Normal 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));
|
||||
Reference in New Issue
Block a user