@trailstash/ultra
Version:
A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc
226 lines (212 loc) • 5.88 kB
JavaScript
import osm2geojson from "osm2geojson-ultra";
import { setQueryBounds } from "../bounds.js";
import { layers, popupTemplate, popupContextBuilder } from "./osm.js";
// Overpass queries are detected from common strings found in queries written in OverpassQL
const overpassQLRegexes = [
/\[bbox:{{bbox}}\]/,
/\({{bbox}}\)/,
/\[out:json\]/,
/\[out:xml\]/,
/node\[[^\]]+]/,
/way\[[^\]]+]/,
/relation\[[^\]]+]/,
/nwr\[[^\]]+]/,
/nw\[[^\]]+]/,
/nr\[[^\]]+]/,
/wr\[[^\]]+]/,
/area\[[^\]]+]/,
/node\([^)]\)/,
/way\([^)]\)/,
/relation\([^)]\)/,
/nwr\([^)]\)/,
/nw\([^)]\)/,
/nr\([^)]\)/,
/wr\([^)]\)/,
/area\([^)]\)/,
/out( meta| geom| center| skel)*;/,
];
function detect(query) {
const doc = new window.DOMParser().parseFromString(query, "text/xml");
if (
!doc.querySelector("parsererror") &&
Array.from(doc.childNodes).some((x) => x.nodeName === "osm-script")
) {
return true;
}
for (const re of overpassQLRegexes) {
if (query.match(re)) {
return true;
}
}
return false;
}
const overpass = {
source: async function (query, controller, { server }) {
if (!server) {
server = "https://overpass-api.de/api";
}
return {
type: "geojson",
data: await callInterpreter(query, controller, server),
attribution:
'\u003Ca href="https://www.openstreetmap.org/copyright" target="_blank"\u003E© OpenStreetMap contributors\u003C/a\u003E',
generateId: true,
};
},
layers,
popupTemplate,
popupContextBuilder,
detect,
};
export default overpass;
export async function callInterpreter(
query,
controller,
apiRoot = "https://overpass-api.de/api",
) {
const resp = await fetch(apiRoot + "/interpreter", {
body: query,
method: "POST",
mode: "cors",
signal: controller.signal,
});
if (
resp.status == 200 &&
!resp.headers.get("content-type").startsWith("text/html")
) {
let geoJSON;
if (resp.headers.get("content-type") == "application/osm3s+xml") {
const text = await resp.text();
const dom = new window.DOMParser().parseFromString(text, "text/xml");
if (dom.querySelector("remark")) {
alert(dom.querySelector("remark").textContent);
return { type: "FeatureCollection", features: [] };
}
geoJSON = osm2geojson(text, {
completeFeature: true,
renderTagged: true,
excludeWay: false,
});
} else {
const json = await resp.json();
if (json.remark) {
alert(json.remark);
return { type: "FeatureCollection", features: [] };
}
geoJSON = osm2geojson(json, {
completeFeature: true,
renderTagged: true,
excludeWay: false,
});
}
return geoJSON;
} else {
const dom = new window.DOMParser().parseFromString(
await resp.text(),
"text/xml",
);
alert(
Array.from(dom.body.querySelectorAll("p"))
.slice(1)
.map((p) => p.textContent)
.join("\n"),
);
return { type: "FeatureCollection", features: [] };
}
}
const osmUrlRe = /^https:\/\/www.openstreetmap.org\/(node|way|relation)\/(\d+)/;
export const osmWebsite = {
...overpass,
source: async function (query, controller, { server, bounds }) {
const urlMatch = query.match(osmUrlRe);
if (urlMatch) {
const [type, id] = urlMatch.slice(1);
return overpass.source(`${type}(${id});out geom;`, controller, {
server,
});
} else {
throw new Error(`Don't know how to load ${query}`);
}
},
detect: function (query) {
return !!query.match(osmUrlRe);
},
fitBounds: true,
invalidateCacheOnBBox: true,
};
export const taginfo = {
...overpass,
source: async function (query, controller, { server, bounds, zoom }) {
let [type, val] = new URL(query).pathname.split("/").slice(-2);
let overpassQuery;
switch (type) {
case "keys":
overpassQuery = setQueryBounds(
`[bbox:{{bbox}}]; nwr["${val}"]; out geom;`,
bounds,
zoom,
);
break;
case "tags":
let key;
[key, val] = val.split("=");
overpassQuery = setQueryBounds(
`[bbox:{{bbox}}]; nwr["${key}"="${val}"]; out geom;`,
bounds,
zoom,
);
break;
}
if (!overpassQuery) {
throw new Error(`Don't know how to load ${query}`);
}
return overpass.source(overpassQuery, controller, { server });
},
detect: function (query) {
return (
query.startsWith("https://taginfo.openstreetmap.org") ||
query.startsWith("https://taginfo.geofabrik.de")
);
},
fitBounds: true,
invalidateCacheOnBBox: true,
};
export const osmWiki = {
...overpass,
source: async function (query, controller, { server, bounds, zoom }) {
const url = new URL(query);
const page = decodeURIComponent(url.pathname.slice(6));
let overpassQuery;
if (page.split(":")[0] === "Tag") {
const [key, value] = page.split(":")[1].split("=");
overpassQuery = setQueryBounds(
`[bbox:{{bbox}}];
nwr["${key}"="${value}"];
out geom;`,
bounds,
zoom,
);
} else if (page.split(":")[0] === "Key") {
overpassQuery = setQueryBounds(
`[bbox:{{bbox}}];
nwr["${page.split(":")[1]}"];
out geom;`,
bounds,
zoom,
);
}
if (!overpassQuery) {
throw new Error(`Don't know how to load ${query}`);
}
return overpass.source(overpassQuery, controller, { server });
},
detect: function (query, bounds, zoom) {
if (!query.startsWith("https://wiki.openstreetmap.org/wiki/")) {
return;
}
const url = new URL(query);
const page = decodeURIComponent(url.pathname.slice(6));
return page.split(":")[0] === "Tag" || page.split(":")[0] === "Key";
},
invalidateCacheOnBBox: true,
};