@trailstash/ultra 4.1.5 → 4.2.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.
Files changed (36) hide show
  1. package/Examples/alt-bbox-format.ultra +1 -1
  2. package/Examples/emoji.ultra +29 -0
  3. package/Examples/postpass.ultra +10 -0
  4. package/Examples/qlever.ultra +1 -2
  5. package/Examples/sophox.ultra +1 -2
  6. package/build-emoji-sprites.js +32 -0
  7. package/build-sprites.sh +3 -5
  8. package/docs/assets/Examples/emoji.png +0 -0
  9. package/docs/assets/Examples/postpass.png +0 -0
  10. package/docs/assets/showcase/DecomissionedAircraftMap.png +0 -0
  11. package/docs/assets/showcase/HighSchoolMascotMap.png +0 -0
  12. package/docs/assets/showcase/teambaden.png +0 -0
  13. package/docs/assets/showcase/water-crisis.png +0 -0
  14. package/docs/further-reading.md +5 -1
  15. package/docs/query-shortcuts.md +26 -3
  16. package/docs/resources.md +5 -2
  17. package/docs/showcase.md +34 -0
  18. package/docs/yaml.md +15 -0
  19. package/lib/bounds.js +11 -0
  20. package/lib/queryProviders/index.js +5 -1
  21. package/lib/queryProviders/osm.js +4 -0
  22. package/lib/queryProviders/postpass.js +76 -0
  23. package/lib/queryProviders/sparql.js +21 -3
  24. package/package.json +2 -2
  25. package/static/sprites/emoji.json +13750 -0
  26. package/static/sprites/emoji.png +0 -0
  27. package/static/sprites/emoji@2x.json +13750 -0
  28. package/static/sprites/emoji@2x.png +0 -0
  29. package/static/sprites/maki.json +35 -27
  30. package/static/sprites/maki.png +0 -0
  31. package/static/sprites/maki@2x.json +832 -824
  32. package/static/sprites/maki@2x.png +0 -0
  33. package/static/sprites/temaki.json +260 -252
  34. package/static/sprites/temaki.png +0 -0
  35. package/static/sprites/temaki@2x.json +2126 -2118
  36. package/static/sprites/temaki@2x.png +0 -0
@@ -6,4 +6,4 @@ options:
6
6
  center: [-122.6847, 45.5112]
7
7
  zoom: 15
8
8
  ---
9
- https://api.ohsome.org/v1/elements/geometry?bboxes={{west}},{{south}},{{east}},{{north}}&filter=amenity%3Dbicycle_repair_station
9
+ https://api.ohsome.org/v1/elements/geometry?bboxes={{wsen}}&filter=amenity%3Dbicycle_repair_station
@@ -0,0 +1,29 @@
1
+ ---
2
+ title: Emojis
3
+ description: Use the bundled emoji sprites to make a world map with flags for each country
4
+ style:
5
+ layers:
6
+ - id: geolines
7
+ visibility: none
8
+ - id: geolines-label
9
+ visibility: none
10
+ - id: countries-label
11
+ visibility: none
12
+ - type: symbol
13
+ icon-overlap: always
14
+ icon-image:
15
+ - coalesce
16
+ - - image
17
+ - - concat
18
+ - 'emoji:flag-'
19
+ - - downcase
20
+ - - coalesce
21
+ - [ get, ISO3166-1:alpha2 ]
22
+ - [ get, ISO3166-1 ]
23
+ - [ get, country_code_iso3166_1_alpha_2 ]
24
+ - - image
25
+ - emoji:question
26
+ extends: https://demotiles.maplibre.org/style.json
27
+ ---
28
+ node[place=country];
29
+ out center;
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Postpass
3
+ description: Query OpenStreetMap using [Postpass](https://github.com/woodpeck/postpass)
4
+ options:
5
+ center: [-122.6847, 45.5112]
6
+ zoom: 15
7
+ ---
8
+ SELECT osm_id, way, tags FROM planet_osm_point
9
+ WHERE amenity = 'bicycle_repair_station'
10
+ AND way && ST_MakeEnvelope({{wsen}}, 4326)
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  title: Query OSM using QLever
3
3
  description: Load OSM data from the QLever osm-planet dataset.
4
- type: sparql
5
- server: https://qlever.cs.uni-freiburg.de/api/osm-planet
4
+ type: qlever
6
5
  options:
7
6
  zoom: -0.1
8
7
  center: [0, 30]
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  title: Query OSM using Sophox
3
3
  description: Load OSM data from Sophox
4
- type: sparql
5
- server: https://sophox.org/sparql
4
+ type: sophox
6
5
  options:
7
6
  zoom: -0.1
8
7
  center: [0, 30]
@@ -0,0 +1,32 @@
1
+ import fs from "fs";
2
+ import emoji from "emoji-datasource-google/emoji.json" with { type: "json" };
3
+
4
+ for (const [size, name] of [
5
+ [32, "emoji"],
6
+ [64, "emoji@2x"],
7
+ ]) {
8
+ const spriteJSON = {};
9
+
10
+ for (const { sheet_x, sheet_y, short_names, image } of emoji) {
11
+ const x = sheet_x * (size + 2) + 1;
12
+ const y = sheet_y * (size + 2) + 1;
13
+ for (const shortName of short_names) {
14
+ spriteJSON[shortName] = {
15
+ height: size,
16
+ pixelRatio: size / 32,
17
+ width: size,
18
+ x,
19
+ y,
20
+ };
21
+ }
22
+ }
23
+
24
+ fs.writeFileSync(
25
+ `static/sprites/${name}.json`,
26
+ JSON.stringify(spriteJSON, null, 2),
27
+ );
28
+ fs.copyFileSync(
29
+ `node_modules/emoji-datasource-google/img/google/sheets/${size}.png`,
30
+ `static/sprites/${name}.png`,
31
+ );
32
+ }
package/build-sprites.sh CHANGED
@@ -10,19 +10,17 @@ mkdir -p static/sprites
10
10
  curl -L -o maki.zip https://github.com/mapbox/maki/archive/refs/heads/main.zip
11
11
  unzip -u maki.zip
12
12
  for icon in maki-main/icons/*.svg; do
13
- npx -- svg2png $icon -o ${icon%.svg*}.png -w 30 -h 30
14
- rm $icon
13
+ rsvg-convert -f svg -o $icon $icon -w 30 -h 30
15
14
  done
16
15
  npx -- sprite-one ./static/sprites/maki -i maki-main/icons/ --sdf
17
16
  npx -- sprite-one --ratio=2 ./static/sprites/maki@2x -i maki-main/icons/ --sdf
18
17
  cp maki-main/LICENSE* static/sprites/maki.license
19
18
  rm -rf maki.zip maki-main
20
- #
19
+
21
20
  curl -L -o temaki.zip https://github.com/rapideditor/temaki/archive/refs/heads/main.zip
22
21
  unzip -u temaki.zip
23
22
  for icon in temaki-main/icons/*.svg; do
24
- npx -- svg2png $icon -o ${icon%.svg*}.png -w 30 -h 30
25
- rm $icon
23
+ rsvg-convert -f svg -o $icon $icon -w 30 -h 30
26
24
  done
27
25
  npx -- sprite-one ./static/sprites/temaki -i temaki-main/icons/ --sdf
28
26
  npx -- sprite-one --ratio=2 ./static/sprites/temaki@2x -i temaki-main/icons/ --sdf
Binary file
@@ -2,7 +2,7 @@ Here are some more resources on Ultra:
2
2
 
3
3
  ## OSM Wiki
4
4
 
5
- [Overpass Ultra on the OpenStreetMap Wiki](https://wiki.openstreetmap.org/wiki/Overpass_Ultra)
5
+ [Ultra on the OpenStreetMap Wiki](https://wiki.openstreetmap.org/wiki/Ultra)
6
6
 
7
7
  ## Diary Entries
8
8
 
@@ -18,3 +18,7 @@ Here are some more resources on Ultra:
18
18
 
19
19
  - [Ultraschnell: Overpass Ultra](https://geoobserver.de/2024/01/25/ultraschnell-overpass-ultra/) 🇩🇪
20
20
  - [Overpass Ultra & MapLibre: Heatmaps](https://geoobserver.de/2024/02/23/overpass-ultra-maplibre-heatmaps/) 🇩🇪
21
+
22
+ ## Third party tutorials
23
+ - [Ultra (Overpass Ultra) Tutorial)](https://felipevaldez.com/ultra_tutorial/ultra_tutorial.html)
24
+ ([disponible en español 🇪🇸](https://fmvaldezg.codeberg.page/ultra_tutorial_sp/ultra_tutorial.html))
@@ -12,12 +12,35 @@ out center;
12
12
 
13
13
  ### Other formats
14
14
 
15
- If you need to specify the coordinates in a different order, you can use the following shortcuts
16
- for the min and max of both lattitude and longitude:
15
+ If you need to specify your bounding box in a different format, for example for an HTTP GeoJSON API
16
+ or [Postpass](https://github.com/wookdpeck/postpass), you can specify just the specific lat/lon
17
+ vaulues or a comma-delimited set of values.
18
+
19
+ #### Short form
20
+
21
+ The short form is wrapped it double curly braces and consists of a string of one or more of the
22
+ letters `n`/`s`/`e`/`w` which corespond to the north/south-most latitudes and east/west-most
23
+ longitudes.
24
+
25
+ This means you can use simple 1-character shortcuts like:
26
+
27
+ - `{{s}}`
28
+ - `{{n}}`
29
+ - `{{e}}`
30
+ - `{{w}}`
31
+
32
+ Or a longer string like `{{wsen}}` which is equivalent to `{{w}}{{s}}{{e}}{{n}}`.
33
+
34
+ [Example](./Examples/alt-bbox-format.md)
35
+
36
+ #### Long form
37
+
38
+ The original long form of he individual min and max of both latitude and longitude are also
39
+ available:
17
40
 
18
41
  - `{{south}}`
19
42
  - `{{north}}`
20
43
  - `{{east}}`
21
44
  - `{{west}}`
22
45
 
23
- [Example](./Examples/alt-bbox-format.md)
46
+
package/docs/resources.md CHANGED
@@ -86,6 +86,10 @@ https://mappingsupport.com/p/surf_gis/list-federal-state-county-city-GIS-servers
86
86
 
87
87
  ## Base Style resources
88
88
 
89
+ ### Protomaps API
90
+
91
+ The [Protomaps API](https://protomaps.com/api) is free for non-commercial use.
92
+
89
93
  ### TrailStash Style Server
90
94
 
91
95
  Most of the styles made available in the **Style Picker** menu on
@@ -95,8 +99,7 @@ Most of the styles made available in the **Style Picker** menu on
95
99
  #### OpenMapTiles Styles
96
100
 
97
101
  Most of the [OpenMapTiles Styles](https://openmaptiles.org/styles/) are available, with fonts and
98
- sprites hosted along-side the style, and tiles hosted by the [OSM US Tile
99
- Server](#openstreetmap-us-tileserver).
102
+ sprites hosted along-side the style, and tiles hosted by [OpenFreeMap](#openfreemap).
100
103
 
101
104
  #### Protomaps
102
105
 
@@ -0,0 +1,34 @@
1
+ Here are some maps made with Ultra
2
+
3
+ ## DecomissionedAircraftMap
4
+
5
+ ![](../assets/showcase/DecomissionedAircraftMap.png)
6
+
7
+ [View Map](https://overpass-ultra.us/#map&query=url:https://raw.githubusercontent.com/watmildon/DecomissionedAircraftMap/refs/heads/main/AircraftMap.ultra)
8
+ |
9
+ [Github Repo](https://github.com/watmildon/DecomissionedAircraftMap)
10
+
11
+ ## HighSchoolMascotMap
12
+
13
+
14
+ ![](../assets/showcase/HighSchoolMascotMap.png)
15
+
16
+ [View Map](https://overpass-ultra.us/#map&query=url:https://raw.githubusercontent.com/watmildon/HighSchoolMascotMap/refs/heads/main/HSMascotMap.ultra)
17
+ |
18
+ [Github Repo](https://github.com/watmildon/HighSchoolMascotMap)
19
+
20
+ ## MapRVA Jan 2025 Water Crisis Maps
21
+
22
+ ![](../assets/showcase/water-crisis.png)
23
+
24
+ [View Maps](https://maprva.org/projects/water-crisis/)
25
+ |
26
+ [Github Repo](https://github.com/maprva/2025-water-crisis)
27
+
28
+ ## Team Baden Parking Map
29
+
30
+ ![](../assets/showcase/teambaden.png)
31
+
32
+ [View Map](https://overpass-ultra.us/#query=gist:aea22c9b953409e29558952fac49b945&run&map)
33
+ |
34
+ [Article](https://teambaden.ch/aktuell/viel-laerm-um-nichts-das-team-hat-die-parkplatz-zahlen/)
package/docs/yaml.md CHANGED
@@ -152,6 +152,16 @@ Examples:
152
152
  * [Wikidata Query Service](./Examples/wikidata-sparql.md)
153
153
  * [Sophox](./Examples/sophox.md)
154
154
 
155
+ ### `qlever`
156
+
157
+ Make a [SPARQL](https://www.w3.org/TR/sparql11-query/) query against QLever to load data.
158
+
159
+ Use [server](#server) to specify a backend slug. Default: `osm-planet`.
160
+
161
+ ### `sophox`
162
+
163
+ Make a [SPARQL](https://www.w3.org/TR/sparql11-query/) query against Sophox to load data.
164
+
155
165
  ### `ohsome`
156
166
 
157
167
  An [ohsome API](https://api.ohsome.org/) [filter](https://docs.ohsome.org/ohsome-api/v1/filter.html).
@@ -239,6 +249,11 @@ Detected if
239
249
 
240
250
  - A JSON document or URL to a JSON document containing a `tilejson` key and a `format` key equal to `pbf`.
241
251
 
252
+ ### `postpass`
253
+
254
+ An SQL query to be executed on a [Postpass](https://github.com/woodpeck/postpass) server. Server
255
+ results are modified to have the same structure as [overpass](#overpass)
256
+
242
257
  ## `options`
243
258
 
244
259
  When an Ultra query is run in "interactive map" mode, you can specify the
package/lib/bounds.js CHANGED
@@ -1,5 +1,16 @@
1
+ const dirs = {
2
+ n: "{{north}}",
3
+ s: "{{south}}",
4
+ e: "{{east}}",
5
+ w: "{{west}}",
6
+ };
1
7
  export function setQueryBounds(query, bounds) {
2
8
  return query
9
+ .replace(/{{[wsen]+}}/g, (match) =>
10
+ Array.from(match.slice(2, -2))
11
+ .map((dir) => dirs[dir])
12
+ .join(","),
13
+ )
3
14
  .replace(/{{bbox}}/g, `{{south}},{{west}},{{north}},{{east}}`)
4
15
  .replace(/{{south}}/g, bounds.getSouth())
5
16
  .replace(/{{west}}/g, bounds.getWest())
@@ -1,7 +1,8 @@
1
1
  import AutoProvider from "./auto.js";
2
2
  import overpass, { osmWebsite, osmWiki, taginfo } from "./overpass.js";
3
3
  import ohsome from "./ohsome.js";
4
- import sparql from "./sparql.js";
4
+ import postpass from "./postpass.js";
5
+ import { sparql, sophox, qlever } from "./sparql.js";
5
6
  import geojson from "./geojson.js";
6
7
  import raster from "./raster.js";
7
8
  import vector from "./vector.js";
@@ -14,8 +15,11 @@ import esri from "./esri.js";
14
15
 
15
16
  export const all = {
16
17
  overpass,
18
+ postpass,
17
19
  ohsome,
18
20
  sparql,
21
+ qlever,
22
+ sophox,
19
23
  geojson,
20
24
  kml,
21
25
  gpx,
@@ -19,11 +19,14 @@ export const flattenProperties = (geoJSON) => {
19
19
  };
20
20
 
21
21
  export const popupTemplate = `
22
+ {%- if type and id %}
22
23
  <h2>
23
24
  {{ type }}
24
25
  <a href="https://openstreetmap.org/{{ type }}/{{ id }}" target="_blank">{{ id }}</a>
25
26
  <a href="https://openstreetmap.org/edit?{{ type }}={{ id }}" target="_blank">✏️</a>
26
27
  </h2>
28
+ {%- endif %}
29
+ {%- if tags.size > 0 %}
27
30
  <h3>Tags</h3>
28
31
  {%- for tag in tags %}
29
32
  {%- if tag[0] contains "website" %}
@@ -38,6 +41,7 @@ export const popupTemplate = `
38
41
  {%- endif %}
39
42
  <br>
40
43
  {%- endfor %}
44
+ {%- endif %}
41
45
  {%- if meta %}
42
46
  <h3>Meta</h3>
43
47
  {%- for tag in meta %}
@@ -0,0 +1,76 @@
1
+ import osmtogeojson from "osmtogeojson";
2
+ import { setQueryBounds } from "../bounds.js";
3
+
4
+ import { layers, popupTemplate, popupContextBuilder } from "./osm.js";
5
+
6
+ const nonTags = {
7
+ osm_id: true,
8
+ tags: true,
9
+ way_area: true,
10
+ z_order: true,
11
+ };
12
+
13
+ const flattenPropertiesAndInferIdAndType = (geoJSON) => {
14
+ // prety major assumption basked into this as Postpass executes arbitrary SQL so the returned
15
+ // data could be anything. This assumes that the query returns un-renamed columns.
16
+ geoJSON.features = geoJSON.features.map((x) => {
17
+ const properties = {};
18
+ if (x.properties.osm_id) {
19
+ properties["@type"] = x.geometry.type === "Point" ? "node" : "way";
20
+ properties["@id"] = x.properties.osm_id;
21
+ if (properties["@id"] < 0) {
22
+ properties["@id"] = -properties["@id"];
23
+ properties["@type"] = "relation";
24
+ }
25
+ }
26
+ if (x.properties.tags) {
27
+ for (const [k, v] of Object.entries(x.properties.tags)) {
28
+ properties[k] = v;
29
+ }
30
+ }
31
+ for (const [key, value] of Object.entries(x.properties)) {
32
+ if (!nonTags[key]) {
33
+ properties.tags[key] = value;
34
+ }
35
+ }
36
+ return {
37
+ ...x,
38
+ properties,
39
+ };
40
+ });
41
+ return geoJSON;
42
+ };
43
+
44
+ const postpass = {
45
+ source: async function (query, controller, { server, bounds }) {
46
+ if (!server) {
47
+ server = "http://postpass.geofabrik.de/api/0.1/interpreter";
48
+ }
49
+ const resp = await fetch(server, {
50
+ method: "POST",
51
+ body: new URLSearchParams([["data", query]]),
52
+ signal: controller.signal,
53
+ headers: {
54
+ "Content-Type": "application/x-www-form-urlencoded",
55
+ Accept: "application/json",
56
+ },
57
+ });
58
+ if (!resp.ok) {
59
+ throw new Error(
60
+ `Postpass API returned ${resp.status}:\n${await resp.text()}`,
61
+ );
62
+ }
63
+ const data = await resp.json();
64
+ return {
65
+ type: "geojson",
66
+ data: flattenPropertiesAndInferIdAndType(data),
67
+ attribution:
68
+ '\u003Ca href="https://www.openstreetmap.org/copyright" target="_blank"\u003E© OpenStreetMap contributors\u003C/a\u003E',
69
+ generateId: true,
70
+ };
71
+ },
72
+ layers,
73
+ popupTemplate,
74
+ popupContextBuilder,
75
+ };
76
+ export default postpass;
@@ -14,9 +14,12 @@ export const popupTemplate = `
14
14
  {%- endfor %}
15
15
  `;
16
16
 
17
+ const SOPHOX = "https://sophox.org/sparql";
18
+ const QLEVER_ROOT = "https://qlever.cs.uni-freiburg.de/api/";
19
+
17
20
  const OSM_SERVERS = new Set([
18
- "https://qlever.cs.uni-freiburg.de/api/osm-planet",
19
- "https://sophox.org/sparql",
21
+ new URL("osm-planet", QLEVER_ROOT).toString(),
22
+ SOPHOX,
20
23
  ]);
21
24
 
22
25
  const source = async function (query, controller, { server, bounds }) {
@@ -49,9 +52,24 @@ const source = async function (query, controller, { server, bounds }) {
49
52
  };
50
53
  };
51
54
 
52
- const sparql = {
55
+ export const sparql = {
53
56
  source,
54
57
  layers,
55
58
  popupTemplate,
56
59
  };
57
60
  export default sparql;
61
+
62
+ export const sophox = {
63
+ ...sparql,
64
+ source: (query, controller, { server, bounds }) =>
65
+ source(query, controller, { server: server || SOPHOX, bounds }),
66
+ };
67
+
68
+ export const qlever = {
69
+ ...sparql,
70
+ source: (query, controller, { server, bounds }) =>
71
+ source(query, controller, {
72
+ server: new URL(server || "osm-planet", QLEVER_ROOT).toString(),
73
+ bounds,
74
+ }),
75
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "4.1.5",
6
+ "version": "4.2.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": {
@@ -43,7 +43,7 @@
43
43
  "homepage": "https://overpass-ultra.us/",
44
44
  "devDependencies": {
45
45
  "@types/node": "^22.6.1",
46
- "@unvt/sprite-one": "^0.1.1",
46
+ "@unvt/sprite-one": "github:dschep/sprite-one",
47
47
  "esbuild-jest": "^0.5.0",
48
48
  "jest": "^29.7.0",
49
49
  "jest-environment-jsdom": "^29.7.0",