UNPKG

@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
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, };