@trailstash/ultra 3.7.1 → 3.8.0

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.
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Query OSM using QLever
3
+ description: Load OSM data from the QLever osm-planet dataset. The sparql provider uses https://qlever.cs.uni-freiburg.de/api/osm-planet by default.
4
+ type: sparql
5
+ options:
6
+ zoom: -0.1
7
+ center: [0, 30]
8
+ ---
9
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
10
+ PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
11
+ SELECT * WHERE {
12
+ ?osm_id osmkey:amenity "bicycle_repair_station" .
13
+ ?osm_id geo:hasGeometry/geo:asWKT ?geometry
14
+ }
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Viewing features at low zoom levels
3
+ description: >
4
+ Because of how MapLibre handles GeoJSON sources with
5
+ [geojson-vt](https://github.com/mapbox/geojson-vt), Ultra cannot implement a feature similar to
6
+ overpass turbo's "show small features as points" feature. To work around this, query for the
7
+ center of features instead of their full geometry.
8
+ options:
9
+ zoom: 0
10
+ center: [-100, 40]
11
+ ---
12
+ nwr[sport=water_ski][water];
13
+ out center;
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Query Wikidata
3
+ description: Load Wikidata data from the Wikidata Query Service.
4
+ type: sparql
5
+ server: https://query.wikidata.org/sparql
6
+ options:
7
+ zoom: -0.1
8
+ center: [0, 30]
9
+ ---
10
+ SELECT DISTINCT * WHERE {
11
+ ?item wdt:P31/wdt:P279* wd:Q16917;
12
+ wdt:P625 ?geo .
13
+ }
@@ -75,7 +75,7 @@ function generateExamplesFolder(examplesFolder) {
75
75
  indexArray.push({
76
76
  title: config.title,
77
77
  mdFileName,
78
- description: config.title,
78
+ description: config.description,
79
79
  queryContent,
80
80
  });
81
81
  const exampleMarkdown = generateMarkdownForExample(
@@ -293,7 +293,9 @@ export class UltraMap extends HTMLElement {
293
293
  this.refs.mapLibre.mapStyle = await getStyle(this.mapStyle, {
294
294
  // Don't love this...
295
295
  source: this.#cachedSource,
296
- layers: queryProvider.layers ? queryProvider.layers("ultra") : [],
296
+ layers: queryProvider.layers
297
+ ? await Promise.resolve(queryProvider.layers("ultra", query))
298
+ : [],
297
299
  });
298
300
  return this.#cachedSource.data;
299
301
  }
Binary file
package/docs/resources.md CHANGED
@@ -50,6 +50,11 @@ The [`kml` provider](./yaml.md#kml) can directly load Google My Maps URLs.
50
50
  [Natural Earth Data](https://www.naturalearthdata.com/) data is available as GeoJSON from the
51
51
  [geojson.xyz CDN](https://geojson.xyz)
52
52
 
53
+ ### US Census Bureau
54
+
55
+ State boundaries from the [US Census Bureau](https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html)
56
+ data is available as GeoJSON from [github.com/glynnbird/usstatesgeojson](https://github.com/glynnbird/usstatesgeojson)
57
+
53
58
  ### Wilderness.net
54
59
 
55
60
  [Wilderness.net](https://wilderness.net) provides GIS data for Wildernes Areas in the USA. The data
package/docs/yaml.md CHANGED
@@ -51,15 +51,18 @@ Specify the type of source in the query.
51
51
  Automatically detects and chooses the right query provider out of the following:
52
52
 
53
53
  - `overpass`
54
- - `osmWebsite`
55
- - `osmWiki`
56
54
  - `geojson`
57
- - `osmxml`
58
- - `osmjson`
59
55
  - `kml`
60
56
  - `gpx`
61
57
  - `tcx`
58
+ - `vector`
59
+ - `raster`
62
60
  - `raw`
61
+ - `osmxml`
62
+ - `osmjson`
63
+ - `osmWebsite`
64
+ - `osmWiki`
65
+ - `taginfo`
63
66
 
64
67
  See each provider for how it is auto-detected
65
68
 
@@ -92,6 +95,16 @@ Detected if:
92
95
 
93
96
  - A URL to the OSM wiki starting with `Tag:` or `Key:`
94
97
 
98
+
99
+ ### `taginfo`
100
+
101
+ Perform an Overpass query using `nwr` and `out geom;` for an taginfo URL for keys or tags.
102
+
103
+ Detected if:
104
+
105
+ - A URL starting with http://taginfo.openstreetmap.org or http://taginfo.geofabrik.de and with a
106
+ path ending in `/keys/:key` or `/tags/:key=:value`.
107
+
95
108
  ### `osmxml`
96
109
 
97
110
  An [OSM XML](https://wiki.openstreetmap.org/wiki/OSM_XML) document or a URL to one. Converted into
@@ -116,6 +129,16 @@ Detected if:
116
129
  - Parses as a URL and the path matches that of an `node/:id`, `way/:id/full`, or
117
130
  `relation/:id/full` OSM API route
118
131
 
132
+ ### `sparql`
133
+
134
+ Make a [GeoSPARQL](https://www.ogc.org/publications/standard/geosparql/) query to load data. By
135
+ default, uses the [Qlever osm-planet](https://qlever.cs.uni-freiburg.de/osm-planet) server.
136
+
137
+ Examples:
138
+
139
+ * [Qlever](./Examples/qlever.md)
140
+ * [Wikidata Query Service](./Examples/wikidata-sparql.md)
141
+
119
142
  ### `ohsome`
120
143
 
121
144
  An [ohsome API](https://api.ohsome.org/) [filter](https://docs.ohsome.org/ohsome-api/v1/filter.html).
@@ -171,12 +194,21 @@ A line delimited list of [tile URLs](https://maplibre.org/maplibre-style-spec/so
171
194
  a [raster source](https://maplibre.org/maplibre-style-spec/sources/#raster) or a [TileJSON
172
195
  url](https://maplibre.org/maplibre-style-spec/sources/#url_1)
173
196
 
197
+ Detected if
198
+
199
+ - tile URLs ending in `.png`, `.jpg`, `.jpeg`, or `.webp`.
200
+ - A JSON document or URL to a JSON document containing a `tilejson` key and a `format` key equal to `png`, `jpg`, or `webp`.
201
+
174
202
  ### `vector`
175
203
 
176
204
  A line delimited list of [tile URLs](https://maplibre.org/maplibre-style-spec/sources/#tiles_2) for
177
205
  a [vector source](https://maplibre.org/maplibre-style-spec/sources/#vector) or a [TileJSON
178
206
  url](https://maplibre.org/maplibre-style-spec/sources/#url_2)
179
207
 
208
+ Detected if
209
+
210
+ - A JSON document or URL to a JSON document containing a `tilejson` key and a `format` key equal to `pbf`.
211
+
180
212
  ## `options`
181
213
 
182
214
  When an Ultra query is run in "interactive map" mode, you can specify the
@@ -1,6 +1,7 @@
1
1
  import AutoProvider from "./auto.js";
2
- import overpass, { osmWebsite, osmWiki } from "./overpass.js";
2
+ import overpass, { osmWebsite, osmWiki, taginfo } from "./overpass.js";
3
3
  import ohsome from "./ohsome.js";
4
+ import sparql from "./sparql.js";
4
5
  import geojson from "./geojson.js";
5
6
  import raster from "./raster.js";
6
7
  import vector from "./vector.js";
@@ -11,19 +12,21 @@ import tcx from "./tcx.js";
11
12
  import raw from "./raw.js";
12
13
 
13
14
  export const all = {
14
- raw,
15
- raster,
16
- vector,
17
15
  overpass,
18
- osmWebsite,
19
- osmWiki,
20
- osmxml,
21
- osmjson,
16
+ ohsome,
17
+ sparql,
22
18
  geojson,
23
19
  kml,
24
20
  gpx,
25
21
  tcx,
26
- ohsome,
22
+ osmxml,
23
+ osmjson,
24
+ raster,
25
+ vector,
26
+ raw,
27
+ osmWebsite,
28
+ osmWiki,
29
+ taginfo,
27
30
  };
28
31
 
29
32
  export default { auto: new AutoProvider(all), ...all };
@@ -27,7 +27,7 @@ const popupContextBuilder = ({ properties, geometry }) => {
27
27
  return templateContext;
28
28
  };
29
29
 
30
- const overpass = {
30
+ const ohsome = {
31
31
  source: async function (query, controller, { server, bounds }) {
32
32
  if (!server) {
33
33
  server = "https://api.ohsome.org/v1";
@@ -60,4 +60,4 @@ const overpass = {
60
60
  popupContextBuilder,
61
61
  invalidateCacheOnBBox: true,
62
62
  };
63
- export default overpass;
63
+ export default ohsome;
@@ -129,6 +129,43 @@ export const osmWebsite = {
129
129
  invalidateCacheOnBBox: true,
130
130
  };
131
131
 
132
+ export const taginfo = {
133
+ ...overpass,
134
+ source: async function (query, controller, { server, bounds }) {
135
+ let [type, val] = new URL(query).pathname.split("/").slice(-2);
136
+ let overpassQuery;
137
+ switch (type) {
138
+ case "keys":
139
+ overpassQuery = setQueryBounds(
140
+ `[bbox:{{bbox}}]; nwr["${val}"]; out geom;`,
141
+ bounds,
142
+ );
143
+ break;
144
+ case "tags":
145
+ let key;
146
+ [key, val] = val.split("=");
147
+ overpassQuery = setQueryBounds(
148
+ `[bbox:{{bbox}}]; nwr["${key}"="${val}"]; out geom;`,
149
+ bounds,
150
+ );
151
+ break;
152
+ }
153
+ if (!overpassQuery) {
154
+ throw new Error(`Don't know how to load ${query}`);
155
+ }
156
+
157
+ return overpass.source(overpassQuery, controller, { server });
158
+ },
159
+ detect: function (query) {
160
+ return (
161
+ query.startsWith("https://taginfo.openstreetmap.org") ||
162
+ query.startsWith("https://taginfo.geofabrik.de")
163
+ );
164
+ },
165
+ fitBounds: true,
166
+ invalidateCacheOnBBox: true,
167
+ };
168
+
132
169
  export const osmWiki = {
133
170
  ...overpass,
134
171
  source: async function (query, controller, { server, bounds }) {
@@ -5,19 +5,35 @@ const layers = (source) => [
5
5
  source,
6
6
  },
7
7
  ];
8
+ const IMAGE_FORMATS = new Set(["webp", "png", "jpg"]);
9
+ const detect = async (query) => {
10
+ query = query.trim();
11
+ if (query.includes("{bbox-epsg-3857}")) {
12
+ return true;
13
+ }
14
+ if (query.match(/{(x|y|z)}.*\.(png|webp|jpe?g)$/)) {
15
+ return true;
16
+ }
17
+ query = await fetchIfHTTPS(query);
18
+ try {
19
+ const json = JSON.parse(query);
20
+ return json.tilejson && IMAGE_FORMATS.has(json.formati);
21
+ } catch {}
22
+ };
8
23
  export default {
9
24
  source: (query) => {
10
25
  const source = {
11
26
  type: "raster",
12
- tileSize: 256,
13
27
  layers,
14
28
  };
15
29
  if (query.match(/{(x|y|z|bbox-epsg-3857)}/)) {
16
30
  source.tiles = query.split("\n").filter((x) => !!x);
31
+ source.tileSize = 256;
17
32
  } else {
18
33
  source.url = query.trim();
19
34
  }
20
35
  return source;
21
36
  },
22
37
  layers,
38
+ detect,
23
39
  };
@@ -0,0 +1,57 @@
1
+ import { layers } from "./osm.js";
2
+
3
+ import sparql2geojson from "../sparql2geojson.js";
4
+
5
+ export const popupTemplate = `
6
+ <h3>columns</h3>
7
+ {%- for prop in properties %}
8
+ {%- if prop[0] startswith "https://" %}
9
+ <code>{{ prop[0] }} = <a href="{{ prop[1] }}" target="_blank">{{ prop[1] }}</a></code>
10
+ {%- else %}
11
+ <code>{{ prop[0] }} = {{ prop[1] }}</code>
12
+ {%- endif %}
13
+ <br>
14
+ {%- endfor %}
15
+ `;
16
+
17
+ const OSM_SERVERS = new Set([
18
+ "https://qlever.cs.uni-freiburg.de/api/osm-planet",
19
+ "https://sophox.org/sparql",
20
+ ]);
21
+
22
+ const source = async function (query, controller, { server, bounds }) {
23
+ if (!server) {
24
+ server = "https://qlever.cs.uni-freiburg.de/api/osm-planet";
25
+ }
26
+ const resp = await fetch(server, {
27
+ method: "POST",
28
+ body: query,
29
+ signal: controller.signal,
30
+ headers: {
31
+ "Content-Type": "application/sparql-query",
32
+ Accept: "application/sparql-results+json",
33
+ },
34
+ });
35
+ if (!resp.ok) {
36
+ throw new Error(`SparQL API returned ${resp.status}: ${await resp.text()}`);
37
+ }
38
+ const data = sparql2geojson(await resp.json());
39
+ let attribution = "";
40
+ if (OSM_SERVERS.has(server)) {
41
+ attribution =
42
+ '\u003Ca href="https://www.openstreetmap.org/copyright" target="_blank"\u003E© OpenStreetMap contributors\u003C/a\u003E';
43
+ }
44
+ return {
45
+ type: "geojson",
46
+ data,
47
+ attribution,
48
+ generateId: true,
49
+ };
50
+ };
51
+
52
+ const sparql = {
53
+ source,
54
+ layers,
55
+ popupTemplate,
56
+ };
57
+ export default sparql;
@@ -1,3 +1,22 @@
1
+ import { fetchIfHTTPS } from "./util.js";
2
+ import { PMTiles } from "pmtiles";
3
+
4
+ // Created with https://colorbrewer2.org/#type=qualitative&scheme=Paired&n=12
5
+ const colors = [
6
+ "#8dd3c7",
7
+ "#ffffb3",
8
+ "#bebada",
9
+ "#fb8072",
10
+ "#80b1d3",
11
+ "#fdb462",
12
+ "#b3de69",
13
+ "#fccde5",
14
+ "#d9d9d9",
15
+ "#bc80bd",
16
+ "#ccebc5",
17
+ "#ffed6f",
18
+ ];
19
+
1
20
  export default {
2
21
  source: (query) => {
3
22
  const source = {
@@ -10,4 +29,67 @@ export default {
10
29
  }
11
30
  return source;
12
31
  },
32
+ layers: async (source, query) => {
33
+ const layers = [];
34
+ if (!query.match(/{[xyz]}/)) {
35
+ let tileJSON;
36
+ if (query.startsWith("pmtiles://")) {
37
+ const pmtiles = new PMTiles(query.slice("pmtiles://".length));
38
+ tileJSON = await pmtiles.getTileJson();
39
+ } else {
40
+ const resp = await fetch(query);
41
+ if (!resp.ok) {
42
+ return layers;
43
+ }
44
+ tileJSON = await resp.json();
45
+ }
46
+ for (const i in tileJSON.vector_layers) {
47
+ const { id } = tileJSON.vector_layers[i];
48
+ layers.push({
49
+ id: `${source}-vector-${i}-fill`,
50
+ source,
51
+ "source-layer": id,
52
+ type: "fill",
53
+ filter: ["==", ["geometry-type"], "Polygon"],
54
+ paint: {
55
+ "fill-opacity": 0.5,
56
+ "fill-color": colors[i % colors.length],
57
+ },
58
+ });
59
+ layers.push({
60
+ id: `${source}-vector-${i}-line`,
61
+ source,
62
+ "source-layer": id,
63
+ type: "line",
64
+ filter: ["==", ["geometry-type"], "LineString"],
65
+ paint: {
66
+ "line-color": colors[i % colors.length],
67
+ },
68
+ layout: {
69
+ "line-join": "round",
70
+ "line-cap": "round",
71
+ },
72
+ });
73
+ layers.push({
74
+ id: `${source}-vector-${i}-point`,
75
+ source,
76
+ "source-layer": id,
77
+ type: "circle",
78
+ filter: ["==", ["geometry-type"], "Point"],
79
+ paint: {
80
+ "circle-color": colors[i % colors.length],
81
+ "circle-radius": 2,
82
+ },
83
+ });
84
+ }
85
+ }
86
+ return layers;
87
+ },
88
+ detect: async (query) => {
89
+ query = await fetchIfHTTPS(query);
90
+ try {
91
+ const json = JSON.parse(query);
92
+ return json.tilejson && json.format === "pbf";
93
+ } catch {}
94
+ },
13
95
  };
@@ -0,0 +1,28 @@
1
+ import wkt from "wkt";
2
+
3
+ const wktDataTypes = new Set([
4
+ "http://www.opengis.net/ont/geosparql#wktLiteral",
5
+ "http://www.openlinksw.com/schemas/virtrdf#Geometry",
6
+ ]);
7
+
8
+ export default function (sparqlJSON) {
9
+ return {
10
+ type: "FeatureCollection",
11
+ features: sparqlJSON.results.bindings.map((record) => {
12
+ const properties = {};
13
+ let geometry;
14
+ for (const [key, { datatype, value }] of Object.entries(record)) {
15
+ if (wktDataTypes.has(datatype)) {
16
+ geometry = wkt.parse(value);
17
+ } else {
18
+ properties[key] = value;
19
+ }
20
+ }
21
+ return {
22
+ type: "Feature",
23
+ geometry,
24
+ properties,
25
+ };
26
+ }),
27
+ };
28
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.7.1",
6
+ "version": "3.8.0",
7
7
  "description": "A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc",
8
8
  "main": "index.js",
9
9
  "scripts": {
@@ -69,6 +69,7 @@
69
69
  "normalize.css": "^8.0.1",
70
70
  "osmtogeojson": "^3.0.0-beta.5",
71
71
  "pmtiles": "^3.2.0",
72
+ "wkt": "^0.1.1",
72
73
  "yaml": "^2.5.1"
73
74
  },
74
75
  "peerDependencies": {