@trailstash/ultra 4.3.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +2 -0
- package/MapLibre-Examples/color-relief.ultra +58 -0
- package/components/button-modal.js +8 -9
- package/components/code-editor.js +2 -0
- package/components/export-modal.js +282 -0
- package/components/fontawesome-icon.js +4 -0
- package/components/share-modal.js +4 -0
- package/components/ultra-ide.js +55 -13
- package/components/ultra-map.js +38 -10
- package/config.mjs +1 -1
- package/docs/assets/MapLibre-Examples/color-relief.png +0 -0
- package/docs/further-reading.md +1 -1
- package/docs/open-with-ultra.md +94 -0
- package/docs/style.md +2 -2
- package/docs/url-parameters.md +1 -1
- package/docs/yaml.md +23 -1
- package/index.html +11 -3
- package/index.js +1 -1
- package/lib/htmlExport.js +74 -0
- package/lib/localStorage.js +0 -3
- package/lib/queryProviders/geojson.js +2 -2
- package/lib/queryProviders/gpx.js +2 -2
- package/lib/queryProviders/kml.js +2 -2
- package/lib/queryProviders/raster.js +2 -2
- package/lib/queryProviders/tcx.js +2 -2
- package/lib/queryProviders/util.js +2 -2
- package/lib/queryProviders/vector.js +21 -18
- package/lib/sandbox.js +93 -0
- package/lib/style.js +3 -3
- package/og:image.ultra +44 -0
- package/package.json +2 -1
- package/pages-config.mjs +12 -0
- package/static/og:image.png +0 -0
package/components/ultra-map.js
CHANGED
|
@@ -15,6 +15,12 @@ import { handleStyleImageMissing } from "../lib/sprites.js";
|
|
|
15
15
|
import { handleMouseClick, handleMouseMove } from "../lib/queryMap.js";
|
|
16
16
|
import { localStorage, optionsFromStorage } from "../lib/localStorage.js";
|
|
17
17
|
import { HTMLControl } from "./html-control.js";
|
|
18
|
+
import makeSandbox from "../lib/sandbox.js";
|
|
19
|
+
|
|
20
|
+
let sandbox;
|
|
21
|
+
try {
|
|
22
|
+
sandbox = await makeSandbox();
|
|
23
|
+
} catch {}
|
|
18
24
|
|
|
19
25
|
const css = new CSSStyleSheet();
|
|
20
26
|
css.replaceSync(`
|
|
@@ -57,6 +63,7 @@ export class UltraMap extends HTMLElement {
|
|
|
57
63
|
#shadow;
|
|
58
64
|
|
|
59
65
|
#cachedBBox;
|
|
66
|
+
#cachedTransform;
|
|
60
67
|
#cachedType;
|
|
61
68
|
#cachedQuery;
|
|
62
69
|
#cachedSource;
|
|
@@ -77,6 +84,9 @@ export class UltraMap extends HTMLElement {
|
|
|
77
84
|
fitBounds: undefined,
|
|
78
85
|
queryProviders,
|
|
79
86
|
persistState: false,
|
|
87
|
+
transform: undefined,
|
|
88
|
+
title: undefined,
|
|
89
|
+
description: undefined,
|
|
80
90
|
};
|
|
81
91
|
|
|
82
92
|
loadSettingsFromQueryParams = UltraMap.defaults.loadSettingsFromQueryParams;
|
|
@@ -100,6 +110,11 @@ export class UltraMap extends HTMLElement {
|
|
|
100
110
|
|
|
101
111
|
queryProviders = UltraMap.defaults.queryProviders;
|
|
102
112
|
|
|
113
|
+
transform = UltraMap.defaults.transform;
|
|
114
|
+
|
|
115
|
+
title = UltraMap.defaults.title;
|
|
116
|
+
description = UltraMap.defaults.description;
|
|
117
|
+
|
|
103
118
|
static CONFIG_SETTINGS = [
|
|
104
119
|
"queryProviders",
|
|
105
120
|
"loadSettingsFromQueryParams",
|
|
@@ -129,6 +144,7 @@ export class UltraMap extends HTMLElement {
|
|
|
129
144
|
"query",
|
|
130
145
|
"fitBounds",
|
|
131
146
|
"mapStyle",
|
|
147
|
+
"transform",
|
|
132
148
|
];
|
|
133
149
|
|
|
134
150
|
constructor() {
|
|
@@ -166,8 +182,8 @@ export class UltraMap extends HTMLElement {
|
|
|
166
182
|
this.#shadow.adoptedStyleSheets.push(css);
|
|
167
183
|
|
|
168
184
|
if (this.loadSettingsFromQueryParams) {
|
|
169
|
-
return Promise.resolve(getQueryFromQueryParams() || this.query)
|
|
170
|
-
async (query) => {
|
|
185
|
+
return Promise.resolve(getQueryFromQueryParams() || this.query)
|
|
186
|
+
.then(async (query) => {
|
|
171
187
|
const querySettings = parseSettings(query);
|
|
172
188
|
const settings = {
|
|
173
189
|
...this,
|
|
@@ -188,8 +204,8 @@ export class UltraMap extends HTMLElement {
|
|
|
188
204
|
});
|
|
189
205
|
|
|
190
206
|
return this.#init(await getStyle(this.mapStyle));
|
|
191
|
-
}
|
|
192
|
-
|
|
207
|
+
})
|
|
208
|
+
.catch(alert);
|
|
193
209
|
} else if (this.persistState) {
|
|
194
210
|
this.options = {
|
|
195
211
|
...this.options,
|
|
@@ -282,8 +298,9 @@ export class UltraMap extends HTMLElement {
|
|
|
282
298
|
}
|
|
283
299
|
|
|
284
300
|
async run(controller) {
|
|
285
|
-
|
|
286
|
-
|
|
301
|
+
const mapStyle = await getStyle(this.mapStyle);
|
|
302
|
+
this.refs.mapLibre.mapStyle = mapStyle;
|
|
303
|
+
const result = await this.#run(controller);
|
|
287
304
|
if (this.#fitBounds) {
|
|
288
305
|
if (
|
|
289
306
|
this.#cachedSource?.type === "geojson" &&
|
|
@@ -299,7 +316,7 @@ export class UltraMap extends HTMLElement {
|
|
|
299
316
|
);
|
|
300
317
|
}
|
|
301
318
|
}
|
|
302
|
-
return
|
|
319
|
+
return result || { mapStyle };
|
|
303
320
|
}
|
|
304
321
|
async #runUnbound(controller) {
|
|
305
322
|
if (!this.query) {
|
|
@@ -320,6 +337,7 @@ export class UltraMap extends HTMLElement {
|
|
|
320
337
|
const query = setQueryBounds(this.query, this.refs.mapLibre.bounds);
|
|
321
338
|
if (
|
|
322
339
|
!this.#cachedSource ||
|
|
340
|
+
this.transform !== this.#cachedTransform ||
|
|
323
341
|
this.type !== this.#cachedType ||
|
|
324
342
|
query !== this.#cachedQuery ||
|
|
325
343
|
(queryProvider.invalidateCacheOnBBox &&
|
|
@@ -331,20 +349,30 @@ export class UltraMap extends HTMLElement {
|
|
|
331
349
|
this.refs.mapLibre.bounds,
|
|
332
350
|
);
|
|
333
351
|
this.#cachedQuery = query;
|
|
352
|
+
this.#cachedTransform = this.transform;
|
|
334
353
|
this.#cachedType = this.type;
|
|
335
|
-
|
|
354
|
+
let source = await queryProvider.source(query, controller, {
|
|
336
355
|
server: this.server,
|
|
337
356
|
bounds: this.refs.mapLibre.bounds,
|
|
338
357
|
});
|
|
358
|
+
if (this.transform) {
|
|
359
|
+
if (sandbox) {
|
|
360
|
+
source.data = await sandbox(this.transform, source.data);
|
|
361
|
+
} else {
|
|
362
|
+
throw new Error("sandbox could not be initialized");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
this.#cachedSource = source;
|
|
339
366
|
}
|
|
340
|
-
|
|
367
|
+
const mapStyle = await getStyle(this.mapStyle, {
|
|
341
368
|
// Don't love this...
|
|
342
369
|
source: this.#cachedSource,
|
|
343
370
|
layers: queryProvider.layers
|
|
344
371
|
? await Promise.resolve(queryProvider.layers("ultra", query))
|
|
345
372
|
: [],
|
|
346
373
|
});
|
|
347
|
-
|
|
374
|
+
this.refs.mapLibre.mapStyle = mapStyle;
|
|
375
|
+
return { data: this.#cachedSource.data, mapStyle };
|
|
348
376
|
} finally {
|
|
349
377
|
this.refs.loadingIndicator.style.display = "none";
|
|
350
378
|
}
|
package/config.mjs
CHANGED
|
Binary file
|
package/docs/further-reading.md
CHANGED
|
@@ -12,7 +12,7 @@ Here are some more resources on Ultra:
|
|
|
12
12
|
|
|
13
13
|
## Talks
|
|
14
14
|
|
|
15
|
-
- [Mapping USA 2025
|
|
15
|
+
- [Mapping USA 2025](https://openstreetmap.us/events/mapping-usa/2025/making-maps-with-ultra/)
|
|
16
16
|
|
|
17
17
|
## Third party blog posts
|
|
18
18
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Open with Ultra
|
|
2
|
+
|
|
3
|
+
**Open with Ultra** is a [bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet) for making it
|
|
4
|
+
easier to use Ultra.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Install it by dragging the **Open with Ultra** link below to your bookmarks bar.
|
|
9
|
+
|
|
10
|
+
<center><a id="bookmarklet">**Open with Ultra**</a></center>
|
|
11
|
+
|
|
12
|
+
<style>
|
|
13
|
+
#bookmarklet {
|
|
14
|
+
display: inline-block;
|
|
15
|
+
background: #526cfe;
|
|
16
|
+
color: white;
|
|
17
|
+
box-shadow: 0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);
|
|
18
|
+
padding: 10px;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
|
|
22
|
+
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.7.1"></script>
|
|
23
|
+
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/dschep/jQuery-Bookmarklet@master/jquery.bookmarklet.js"></script>
|
|
24
|
+
<script>
|
|
25
|
+
const b = document.querySelector("#bookmarklet")
|
|
26
|
+
b.onclick = e => { e.preventDefault(); return false; };
|
|
27
|
+
$(b).bookmarkletHelperArrow();
|
|
28
|
+
const code = () => {
|
|
29
|
+
const origin = "https://overpass-ultra.us";
|
|
30
|
+
if (window.location.host === "overpass-turbo.eu") {
|
|
31
|
+
const search = new URLSearchParams();
|
|
32
|
+
search.set(
|
|
33
|
+
"query",
|
|
34
|
+
JSON.parse(localStorage.getItem("overpass-ide_code")).overpass,
|
|
35
|
+
);
|
|
36
|
+
if (localStorage.getItem("overpass-ide_coords_zoom")) {
|
|
37
|
+
search.set(
|
|
38
|
+
"m",
|
|
39
|
+
[
|
|
40
|
+
parseInt(localStorage.getItem("overpass-ide_coords_zoom")) - 1,
|
|
41
|
+
localStorage.getItem("overpass-ide_coords_lat"),
|
|
42
|
+
localStorage.getItem("overpass-ide_coords_lon"),
|
|
43
|
+
].join("/"),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
window.location = `${origin}/#${search.toString()}`;
|
|
47
|
+
} else if (window.location.host === "qlever.cs.uni-freiburg.de") {
|
|
48
|
+
const search = new URLSearchParams();
|
|
49
|
+
search.set("query", `---\ntype: sparql\nserver: https://qlever.cs.uni-freiburg.de/api/${document.querySelector("#backend-slug").textContent}\n---\n${document.querySelector(".CodeMirror").CodeMirror.getValue()}`);
|
|
50
|
+
window.location = `${origin}/#${search.toString()}`;
|
|
51
|
+
} else if (window.location.host === "sophox.org") {
|
|
52
|
+
const search = new URLSearchParams();
|
|
53
|
+
search.set("query", `---\ntype: sparql\nserver: https://sophox.org/sparql\n---\n${localStorage.getItem("wikibase.queryService.ui.Editor")}`);
|
|
54
|
+
window.location = `${origin}/#${search.toString()}`;
|
|
55
|
+
} else if (window.location.host === "gist.github.com") {
|
|
56
|
+
window.location = `${origin}/#query=gist:${window.location.pathname.split("/").slice(-1)[0]}`;
|
|
57
|
+
} else if (window.location.host === "github.com") {
|
|
58
|
+
window.location = `${origin}/#query=url:https://raw.githubusercontent.com${window.location.pathname.replace("/blob", "")}`;
|
|
59
|
+
} else if (window.location.host === "geojson.io") {
|
|
60
|
+
window.location = `${origin}/#query=${encodeURIComponent(JSON.stringify(window.api.data.all().map))}`;
|
|
61
|
+
} else {
|
|
62
|
+
window.location = `${origin}/#query=${encodeURIComponent(window.location)}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
b.href = `javascript:(${code.toString()})();`
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
## Behavior
|
|
69
|
+
|
|
70
|
+
It features special support for:
|
|
71
|
+
|
|
72
|
+
- [overpass-turbo.eu](https://overpass-turbo.eu) - Loads query & viewport from overpass turbo in
|
|
73
|
+
Ultra
|
|
74
|
+
- [QLever](https://qlever.cs.uni-freiburg.de/) - Loads query & server from the QLever in Ultra
|
|
75
|
+
- [Sophox](https://sopox.org/) - Loads query & server from the Sophox in Ultra
|
|
76
|
+
- [geojson.io](https://geojson.io) - loads the GeoJSON from geojson.io as the query in Ultra
|
|
77
|
+
- [Gists](https://gist.github.com) - loads the current gist as the query in Ultra
|
|
78
|
+
- [Github](https://github.com) - loads the `githubusercontent.com` URL for a file loaded in the web
|
|
79
|
+
UI
|
|
80
|
+
|
|
81
|
+
For all other sites, it loads the URL as the query.
|
|
82
|
+
|
|
83
|
+
Ultra features some query providers which work well with this:
|
|
84
|
+
|
|
85
|
+
- `osmWebsite` - detects `https://openstreetmap.org/[node|way|relation]/:id` URLs and loads that
|
|
86
|
+
object via the Overpass API
|
|
87
|
+
- `osmWiki` - detects `https://wiki.openstreetmap.org/wiki/Key:` and
|
|
88
|
+
`https://wiki.openstreetmap.org/wiki/Tag:` URLs and loads that object with that tag or key via
|
|
89
|
+
the Overpass API
|
|
90
|
+
- `taginfo` - detects `https://taginfo.openstreetmap.org/key/` and
|
|
91
|
+
`https://taginfo.openstreetmap.org/tags/` URLs and loads that object with that tag or key via the
|
|
92
|
+
Overpass API
|
|
93
|
+
- `kml` - detects `https://www.google.com/maps/d` (Google My Maps) URLs and loads that map via the
|
|
94
|
+
KML export.
|
package/docs/style.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Styling
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
front-matter](./yaml.md).
|
|
3
|
+
[MapLibre styling](https://maplibe.org/maplibre-style-spec/) can be attached to a query
|
|
4
|
+
via the `style:` key of the [YAML front-matter](./yaml.md).
|
|
5
5
|
|
|
6
6
|
```
|
|
7
7
|
---
|
package/docs/url-parameters.md
CHANGED
|
@@ -18,7 +18,7 @@ Example: [http://overpass-ultra.us/#query=gist:8ecb8ba0a0136f4f0dbc36de82061de4]
|
|
|
18
18
|
|
|
19
19
|
You can load a query from any url by using a string prefixed with `url:`
|
|
20
20
|
|
|
21
|
-
Example: [http://overpass-ultra.us/#query=url:https%3A%2F%
|
|
21
|
+
Example: [http://overpass-ultra.us/#query=url:https%3A%2F%2Fraw.githubusercontent.com%2FMapRVA%2Fmaprva.org%2F76eb1cd1bc8c022399f578e38f89917b98c97056%2F_ultra-maps%2Fsurveillance.ultra](http://overpass-ultra.us/#query=https%3A%2F%2Fraw.githubusercontent.com%2FMapRVA%2Fmaprva.org%2F76eb1cd1bc8c022399f578e38f89917b98c97056%2F_ultra-maps%2Fsurveillance.ultra)
|
|
22
22
|
|
|
23
23
|
## `q` <small>(lz-string-compressed string)</small>
|
|
24
24
|
|
package/docs/yaml.md
CHANGED
|
@@ -31,7 +31,7 @@ server: https://overpass.private.coffee/api/
|
|
|
31
31
|
|
|
32
32
|
## `popupTemplate`
|
|
33
33
|
|
|
34
|
-
Customize the interactive popup with a [LiquidJS]() template. Or set it to `false` to disable the
|
|
34
|
+
Customize the interactive popup with a [LiquidJS](https://liquidjs.com/) template. Or set it to `false` to disable the
|
|
35
35
|
interactive popup.
|
|
36
36
|
|
|
37
37
|
```
|
|
@@ -306,3 +306,25 @@ Specify which sources can be queried by mouse-click
|
|
|
306
306
|
querySources: [ultra] # this is the default
|
|
307
307
|
---
|
|
308
308
|
```
|
|
309
|
+
|
|
310
|
+
## `transform`
|
|
311
|
+
|
|
312
|
+
This allows you to specify javascript to mutate the query result before it is added to the map
|
|
313
|
+
style. Your code must export a function that accepts GeoJSON as a parameter and returns GeoJSON.
|
|
314
|
+
To import libraries, use a CDN like [skypack.dev](https://skypack.dev) or [esm.sh](https://esm.sh).
|
|
315
|
+
|
|
316
|
+
For example, to buffer highways by 10 meters:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
---
|
|
320
|
+
transform: |
|
|
321
|
+
import { buffer } from "https://cdn.skypack.dev/@turf/buffer";
|
|
322
|
+
|
|
323
|
+
export default function(data) {
|
|
324
|
+
return buffer(data, 0.01);
|
|
325
|
+
}
|
|
326
|
+
---
|
|
327
|
+
[bbox:{{bbox}}];
|
|
328
|
+
way[highway];
|
|
329
|
+
out geom;
|
|
330
|
+
```
|
package/index.html
CHANGED
|
@@ -11,13 +11,21 @@
|
|
|
11
11
|
<title>{{ title | default: "Ultra"}}</title>
|
|
12
12
|
|
|
13
13
|
<!-- Meta tags for SEO and social sharing -->
|
|
14
|
-
{
|
|
15
|
-
<link rel="canonical" href="{{ url }}" />
|
|
16
|
-
{% endif %}
|
|
14
|
+
<meta property="og:title" content="{{ title | default: "Ultra"}}" />
|
|
17
15
|
<meta
|
|
18
16
|
name="description"
|
|
19
17
|
content="{{ description | default: "A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc" }}"
|
|
20
18
|
/>
|
|
19
|
+
<meta
|
|
20
|
+
name="og:description"
|
|
21
|
+
content="{{ description | default: "A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc" }}"
|
|
22
|
+
/>
|
|
23
|
+
<meta property="og:type" content="website" />
|
|
24
|
+
{% if url %}
|
|
25
|
+
<link rel="canonical" href="{{ url }}" />
|
|
26
|
+
<meta property="og:url" content="{{ url }}" />
|
|
27
|
+
<meta property="og:image" content="{{ url }}/og:image.png" />
|
|
28
|
+
{% endif %}
|
|
21
29
|
<meta name="robots" content="index,follow" />
|
|
22
30
|
|
|
23
31
|
<script>
|
package/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { UltraIDE } from "./components/ultra-ide.js";
|
|
|
13
13
|
import { CodeEditor } from "./components/code-editor.js";
|
|
14
14
|
import { NavBar } from "./components/nav-bar.js";
|
|
15
15
|
import { RunButton } from "./components/run-button.js";
|
|
16
|
-
import { DownloadButton } from "./components/
|
|
16
|
+
import { ExportModal as DownloadButton } from "./components/export-modal.js";
|
|
17
17
|
import { StylePicker } from "./components/style-picker.js";
|
|
18
18
|
import { ShareModal as ShareButton } from "./components/share-modal.js";
|
|
19
19
|
import { HelpModal } from "./components/help-modal.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { version as maplibreVersion } from "maplibre-gl/package.json";
|
|
2
|
+
import { version } from "../package.json";
|
|
3
|
+
|
|
4
|
+
let ultraVersion = version;
|
|
5
|
+
if (version === "dev") {
|
|
6
|
+
ultraVersion = "latest";
|
|
7
|
+
} else if (version.includes("-")) {
|
|
8
|
+
ultraVersion = version.split("-")[0];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const htmlExport = (
|
|
12
|
+
style,
|
|
13
|
+
title = "Ultra Export",
|
|
14
|
+
description = "",
|
|
15
|
+
options = {},
|
|
16
|
+
) => {
|
|
17
|
+
return `<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="utf-8" />
|
|
21
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
22
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
23
|
+
|
|
24
|
+
<title>${title}</title>
|
|
25
|
+
<link
|
|
26
|
+
href="https://esm.sh/maplibre-gl@${maplibreVersion}/dist/maplibre-gl.css"
|
|
27
|
+
rel="stylesheet"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
html,
|
|
32
|
+
body,
|
|
33
|
+
#map {
|
|
34
|
+
height: 100%;
|
|
35
|
+
width: 100%;
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div id="map"></div>
|
|
43
|
+
|
|
44
|
+
<script type="module">
|
|
45
|
+
import maplibregl from "https://esm.sh/maplibre-gl@${maplibreVersion}";
|
|
46
|
+
|
|
47
|
+
const style = ${JSON.stringify(style)};
|
|
48
|
+
|
|
49
|
+
maplibregl.setRTLTextPlugin("https://esm.sh/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js", true);
|
|
50
|
+
|
|
51
|
+
import { Protocol as PMTilesProtocol} from "https://esm.sh/pmtiles";
|
|
52
|
+
const pmtiles = new PMTilesProtocol();
|
|
53
|
+
maplibregl.addProtocol("pmtiles", pmtiles.tile);
|
|
54
|
+
|
|
55
|
+
import { Protocol as FallbackGlyphProtocol } from "https://esm.sh/@trailstash/ultra@${ultraVersion}/lib/glyphFallback.js";
|
|
56
|
+
const fallbackGlyphsProtocol = new FallbackGlyphProtocol();
|
|
57
|
+
maplibregl.addProtocol(FallbackGlyphProtocol.name, fallbackGlyphsProtocol.tile);
|
|
58
|
+
style.glyphs = FallbackGlyphProtocol.makeURL(style.glyphs);
|
|
59
|
+
|
|
60
|
+
const options = ${JSON.stringify(options)};
|
|
61
|
+
var map = new maplibregl.Map({
|
|
62
|
+
...options,
|
|
63
|
+
container: "map",
|
|
64
|
+
style,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
import { handleStyleImageMissing } from "https://esm.sh/@trailstash/ultra@${ultraVersion}/lib/sprites.js";
|
|
68
|
+
map.on("styleimagemissing", handleStyleImageMissing);
|
|
69
|
+
|
|
70
|
+
// TODO: popup?
|
|
71
|
+
</script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>`;
|
|
74
|
+
};
|
package/lib/localStorage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
|
|
3
3
|
const layers = (source) => [
|
|
4
4
|
{
|
|
@@ -119,7 +119,7 @@ const geoJsonTypes = [
|
|
|
119
119
|
"MultiPolygon",
|
|
120
120
|
];
|
|
121
121
|
async function detect(query) {
|
|
122
|
-
query = await
|
|
122
|
+
query = await fetchIfHTTP(query);
|
|
123
123
|
try {
|
|
124
124
|
const json = JSON.parse(query);
|
|
125
125
|
if (geoJsonTypes.includes(json.type)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
import { gpx } from "@tmcw/togeojson";
|
|
3
3
|
|
|
4
4
|
const layers = (source) => [
|
|
@@ -76,7 +76,7 @@ const layers = (source) => [
|
|
|
76
76
|
];
|
|
77
77
|
|
|
78
78
|
const detect = async (query) => {
|
|
79
|
-
query = await
|
|
79
|
+
query = await fetchIfHTTP(query);
|
|
80
80
|
const doc = new window.DOMParser().parseFromString(query, "text/xml");
|
|
81
81
|
if (
|
|
82
82
|
!doc.querySelector("parsererror") &&
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
import { kml } from "@tmcw/togeojson";
|
|
3
3
|
|
|
4
4
|
const layers = (source) => [
|
|
@@ -78,7 +78,7 @@ const detect = async (query) => {
|
|
|
78
78
|
if (query.startsWith("https://www.google.com/maps/d/")) {
|
|
79
79
|
return true;
|
|
80
80
|
}
|
|
81
|
-
query = await
|
|
81
|
+
query = await fetchIfHTTP(query);
|
|
82
82
|
const doc = new window.DOMParser().parseFromString(query, "text/xml");
|
|
83
83
|
return (
|
|
84
84
|
!doc.querySelector("parsererror") &&
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
|
|
3
3
|
const layers = (source) => [
|
|
4
4
|
{
|
|
@@ -16,7 +16,7 @@ const detect = async (query) => {
|
|
|
16
16
|
if (query.match(/{(x|y|z)}.*\.(png|webp|jpe?g)$/)) {
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
|
-
query = await
|
|
19
|
+
query = await fetchIfHTTP(query);
|
|
20
20
|
try {
|
|
21
21
|
const json = JSON.parse(query);
|
|
22
22
|
return json.tilejson && IMAGE_FORMATS.has(json.formati);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
import { tcx } from "@tmcw/togeojson";
|
|
3
3
|
|
|
4
4
|
const layers = (source) => [
|
|
@@ -66,7 +66,7 @@ const layers = (source) => [
|
|
|
66
66
|
];
|
|
67
67
|
|
|
68
68
|
const detect = async (query) => {
|
|
69
|
-
query = await
|
|
69
|
+
query = await fetchIfHTTP(query);
|
|
70
70
|
const doc = new window.DOMParser().parseFromString(query, "text/xml");
|
|
71
71
|
if (
|
|
72
72
|
!doc.querySelector("parsererror") &&
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const
|
|
2
|
-
if (query.startsWith("https://")) {
|
|
1
|
+
export const fetchIfHTTP = async (query) => {
|
|
2
|
+
if (query.startsWith("http://") || query.startsWith("https://")) {
|
|
3
3
|
try {
|
|
4
4
|
const resp = await fetch(query, { cors: true });
|
|
5
5
|
if (resp.ok) {
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchIfHTTP } from "./util.js";
|
|
2
2
|
import { PMTiles } from "pmtiles";
|
|
3
3
|
|
|
4
|
-
// Created with https://colorbrewer2.org
|
|
4
|
+
// Created with https://colorbrewer2.org/?type=qualitative&scheme=Set1&n=9
|
|
5
5
|
const colors = [
|
|
6
|
-
"#
|
|
7
|
-
"#
|
|
8
|
-
"#
|
|
9
|
-
"#
|
|
10
|
-
"#
|
|
11
|
-
"#
|
|
12
|
-
"#
|
|
13
|
-
"#
|
|
14
|
-
"#
|
|
15
|
-
"#bc80bd",
|
|
16
|
-
"#ccebc5",
|
|
17
|
-
"#ffed6f",
|
|
6
|
+
"#e41a1c",
|
|
7
|
+
"#377eb8",
|
|
8
|
+
"#4daf4a",
|
|
9
|
+
"#984ea3",
|
|
10
|
+
"#ff7f00",
|
|
11
|
+
"#ffff33",
|
|
12
|
+
"#a65628",
|
|
13
|
+
"#f781bf",
|
|
14
|
+
"#999999",
|
|
18
15
|
];
|
|
19
16
|
|
|
20
17
|
export default {
|
|
@@ -64,6 +61,7 @@ export default {
|
|
|
64
61
|
filter: ["==", ["geometry-type"], "LineString"],
|
|
65
62
|
paint: {
|
|
66
63
|
"line-color": colors[i % colors.length],
|
|
64
|
+
"line-width": 2,
|
|
67
65
|
},
|
|
68
66
|
layout: {
|
|
69
67
|
"line-join": "round",
|
|
@@ -78,7 +76,7 @@ export default {
|
|
|
78
76
|
filter: ["==", ["geometry-type"], "Point"],
|
|
79
77
|
paint: {
|
|
80
78
|
"circle-color": colors[i % colors.length],
|
|
81
|
-
"circle-radius":
|
|
79
|
+
"circle-radius": 3,
|
|
82
80
|
},
|
|
83
81
|
});
|
|
84
82
|
}
|
|
@@ -86,10 +84,15 @@ export default {
|
|
|
86
84
|
return layers;
|
|
87
85
|
},
|
|
88
86
|
detect: async (query) => {
|
|
89
|
-
|
|
87
|
+
if (query.startsWith("pmtiles://")) {
|
|
88
|
+
const pmtiles = new PMTiles(query.slice("pmtiles://".length));
|
|
89
|
+
const tileJSON = await pmtiles.getTileJson();
|
|
90
|
+
return tileJSON.tilejson && tileJSON.vector_layers;
|
|
91
|
+
}
|
|
92
|
+
query = await fetchIfHTTP(query);
|
|
90
93
|
try {
|
|
91
|
-
const
|
|
92
|
-
return
|
|
94
|
+
const tileJSON = JSON.parse(query);
|
|
95
|
+
return tileJSON.tilejson && tileJSON.vector_layers;
|
|
93
96
|
} catch {}
|
|
94
97
|
},
|
|
95
98
|
};
|
package/lib/sandbox.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const makeSandbox = () => {
|
|
2
|
+
const iframe = window.document.createElement("iframe");
|
|
3
|
+
const sandboxId = crypto.randomUUID();
|
|
4
|
+
iframe.style.display = "none";
|
|
5
|
+
iframe.src =
|
|
6
|
+
"data:text/html;base64," +
|
|
7
|
+
btoa(`<!DOCTYPE html>
|
|
8
|
+
<script type="module">
|
|
9
|
+
const p = "${window.location.origin}"
|
|
10
|
+
|
|
11
|
+
window.addEventListener(
|
|
12
|
+
"message",
|
|
13
|
+
async (event) => {
|
|
14
|
+
console.debug(event);
|
|
15
|
+
|
|
16
|
+
if (event.origin !== p) return;
|
|
17
|
+
|
|
18
|
+
let id;
|
|
19
|
+
try {
|
|
20
|
+
const msg = JSON.parse(event.data);
|
|
21
|
+
id = msg.id;
|
|
22
|
+
console.debug(msg)
|
|
23
|
+
|
|
24
|
+
const { default: transform } = await import(msg.transform);
|
|
25
|
+
|
|
26
|
+
const o = await Promise.resolve(transform(msg.data));
|
|
27
|
+
event.source.postMessage(JSON.stringify({id: msg.id, data: o}), p);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
event.source.postMessage(JSON.stringify({id: id, error: e.toString()}), p);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
false,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
window.parent.postMessage(JSON.stringify({id: "${sandboxId}"}), p);
|
|
36
|
+
</script>
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
const run = (transform, data) => {
|
|
40
|
+
const messageID = crypto.randomUUID();
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const listener = (event) => {
|
|
43
|
+
console.debug(event);
|
|
44
|
+
try {
|
|
45
|
+
const msg = JSON.parse(event.data);
|
|
46
|
+
if (msg.id === messageID) {
|
|
47
|
+
window.removeEventListener("message", listener, false);
|
|
48
|
+
if (msg.error) {
|
|
49
|
+
reject(msg.error);
|
|
50
|
+
} else {
|
|
51
|
+
resolve(msg.data);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
console.warn("unexpected msg!");
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(e);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
window.addEventListener("message", listener, false);
|
|
62
|
+
iframe.contentWindow.postMessage(
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
id: messageID,
|
|
65
|
+
transform: `data:text/javascript;base64,${btoa(transform)}`,
|
|
66
|
+
data,
|
|
67
|
+
}),
|
|
68
|
+
"*",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const listener = (event) => {
|
|
75
|
+
window.removeEventListener("message", listener, false);
|
|
76
|
+
console.debug(event);
|
|
77
|
+
try {
|
|
78
|
+
const msg = JSON.parse(event.data);
|
|
79
|
+
if (msg.id === sandboxId) {
|
|
80
|
+
resolve(run);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.warn("unexpected msg!");
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error(e);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
window.addEventListener("message", listener, false);
|
|
90
|
+
window.document.body.appendChild(iframe);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
export default makeSandbox;
|