Published
Edited
Nov 9, 2021
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function getTraceDrawing(feature, options = {}) {
const {
width: w = 100,
height: h = w,
padding: pad = 5,
pathLength = 100 // Reference length for 100%
} = options;

const extent = [
[pad, pad],
[w - pad, h - pad]
];
const projection = d3.geoIdentity().fitExtent(extent, feature);
const geoPath = d3.geoPath().projection(projection);

const img = DOM.svg(w, h);
img.append(
svg`<title>${feature._traceId}`,
svg`<g fill=none stroke-width=1 stroke=black>${feature.features.map(
(f) => svg`<path d="${geoPath(f)}" pathLength=${pathLength} />`
)}`
);
return img;
}
Insert cell
function featureFromTrace(xml, validTypes = ["LineString"]) {
const dom = new DOMParser().parseFromString(xml, "text/xml");
const json = toGeoJSON.gpx(dom);
const valid = new Set(validTypes);

json.features = json.features.filter((f) => {
return valid.has(f.geometry.type);
});
if (!json.features.length) return null;
return json;
}
Insert cell
randomTraces = {
more;
const total = 100;
const concurrency = 10;

const abort = resolvablePromise();
invalidation.then(abort.resolve);

const valids = validsWithSize.filter((d) => d[1] < 10000); // all files smaller than 10kb
const ids = Array.from(new Set(d3.shuffle(valids).map(([id]) => id)));
let i = 0;
const results = [];

const next = async () => {
if (abort.resolved || i >= ids.length) return;
if (results.length >= total) return abort.resolve();
const id = ids[i++];
try {
const xml = await fetchTrace(id, { invalidated: abort });
if (xml) {
const json = featureFromTrace(xml);
if (json) {
json._traceId = id;
results.push(json);
}
}
} catch (err) {
console.warn(`Error for trace ID ${id}: ${err}`);
}
return next();
};

mutable isLoading = true;
await Promise.all(Array.from({ length: concurrency }, next));
mutable isLoading = false;
return results;
}
Insert cell
async function fetchTrace(id, { invalidated = invalidation } = {}) {
const controller = new AbortController();
invalidated.then(() => controller.abort());

return fetch(`https://www.openstreetmap.org/trace/${id}/data`, {
signal: controller.signal
})
.then((res) => (res.status !== 200 ? null : res))
.catch((err) => null)
.then((res) => (res ? getText(res) : null))
.catch((err) => {
if (err.name === "AbortError") return null;
throw err;
});

function getText(res) {
const mime = res.headers.get("Content-Type");
switch (mime) {
// sometimes it's pure XML text (e.g. ID 3826980)
case "application/gpx+xml":
return res.text();

// sometimes it's GZIP (e.g. ID 3878474)
case "application/x-gzip":
return res.arrayBuffer().then((buffer) => {
const decompressed = fflate.decompressSync(new Uint8Array(buffer));
return fflate.strFromU8(decompressed);
});

// e.g. ID 3853356
//case "application/x-zip":
// todo

case "application/x-bzip2":
return res.arrayBuffer().then((buffer) => {
const decompressed = bzip2.simple(
bzip2.array(new Uint8Array(buffer))
);
return fflate.strFromU8(decompressed);
});

default:
throw Error(`unhandled MIME type: ${mime}`);
}
}
}
Insert cell
// A promise that can be resolved externally, and has a flag for its resolution status.
// API:
// - *promise*.resolve()
// - *promise*.resolved
function resolvablePromise() {
let resolve,
resolved = false;
const promise = new Promise((res) => {
resolve = res;
});
return Object.defineProperties(promise, {
resolve: {
value: (value = undefined) => {
resolved = true;
return resolve(value);
}
},
resolved: {
get: () => resolved
}
});
}
Insert cell
feedURL = "https://www.openstreetmap.org/traces/rss"
Insert cell
mutable isLoading = true
Insert cell
Insert cell
items = {
const feed = await parseRSSCORS(feedURL);
return feed.items.map((item) => {
const linksplit = item.link.split("/");
return {
...item,
dataURL: `https://www.openstreetmap.org/trace/${
linksplit[linksplit.length - 1]
}/data`
};
});
}
Insert cell
validsWithSize = FileAttachment("validIdsSize.json").json()
Insert cell
import { fetchp, parseRSSCORS } from "@radames/hello-rss-parser"
Insert cell
toGeoJSON = import("@tmcw/togeojson")
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
fflate = import("https://cdn.skypack.dev/fflate?min")
Insert cell
bzip2 = import("https://cdn.skypack.dev/bzip2")
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