tileserver-gl-light 4.4.0 โ†’ 4.4.2

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/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ public
@@ -1,9 +1,7 @@
1
1
  name: 'Auto Merge PRs'
2
2
 
3
3
  on:
4
- pull_request:
5
- branches:
6
- - master
4
+ workflow_call:
7
5
 
8
6
  permissions:
9
7
  pull-requests: write
@@ -12,8 +10,6 @@ permissions:
12
10
  jobs:
13
11
  automerge:
14
12
  runs-on: ubuntu-latest
15
- if: >
16
- github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
17
13
  steps:
18
14
  - uses: fastify/github-action-merge-dependabot@v3
19
15
  with:
@@ -0,0 +1,51 @@
1
+ name: 'Continuous Integration'
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ permissions:
7
+ checks: write
8
+ contents: read
9
+
10
+ jobs:
11
+ ci:
12
+ runs-on: ubuntu-20.04
13
+ steps:
14
+ - name: Check out repository โœจ (non-dependabot)
15
+ if: ${{ github.actor != 'dependabot[bot]' }}
16
+ uses: actions/checkout@v3
17
+
18
+ - name: Check out repository ๐ŸŽ‰ (dependabot)
19
+ if: ${{ github.actor == 'dependabot[bot]' }}
20
+ uses: actions/checkout@v3
21
+ with:
22
+ ref: ${{ github.event.pull_request.head.sha }}
23
+
24
+ - name: Setup node env ๐Ÿ“ฆ
25
+ uses: actions/setup-node@v3
26
+ with:
27
+ node-version-file: 'package.json'
28
+ check-latest: true
29
+ cache: 'npm'
30
+
31
+ - name: Install dependencies ๐Ÿš€
32
+ run: npm ci --prefer-offline --no-audit --omit=optional
33
+
34
+ - name: Run linter(s) ๐Ÿ’…
35
+ uses: wearerequired/lint-action@v2
36
+ with:
37
+ github_token: ${{ secrets.GITHUB_TOKEN }}
38
+ continue_on_error: false
39
+ git_name: github-actions[bot]
40
+ git_email: github-actions[bot]@users.noreply.github.com
41
+ auto_fix: false
42
+ eslint: true
43
+ eslint_extensions: js,cjs,mjs,ts
44
+ prettier: true
45
+ prettier_extensions: js,cjs,ts,json
46
+
47
+ - name: Run hadolint ๐Ÿณ
48
+ uses: hadolint/hadolint-action@v3.0.0
49
+ with:
50
+ dockerfile: Dockerfile
51
+ ignore: DL3008,DL3015
@@ -1,3 +1,5 @@
1
+ name: 'CodeQL'
2
+
1
3
  on:
2
4
  push:
3
5
  branches:
@@ -1,12 +1,7 @@
1
1
  name: 'Continuous Testing'
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - master
7
- pull_request:
8
- branches:
9
- - master
4
+ workflow_call:
10
5
 
11
6
  permissions:
12
7
  checks: write
@@ -55,7 +50,7 @@ jobs:
55
50
 
56
51
  - name: Run tests ๐Ÿงช
57
52
  run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
58
-
53
+
59
54
  - name: Set up QEMU
60
55
  uses: docker/setup-qemu-action@v2
61
56
  with:
@@ -63,10 +58,13 @@ jobs:
63
58
 
64
59
  - name: Set up Docker Buildx
65
60
  uses: docker/setup-buildx-action@v2
66
-
61
+
67
62
  - name: Test Docker Build
68
63
  uses: docker/build-push-action@v3
69
64
  with:
70
65
  context: .
71
66
  push: false
72
67
  platforms: linux/arm64,linux/amd64
68
+ # experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
69
+ cache-from: type=gha
70
+ cache-to: type=gha,mode=max
@@ -0,0 +1,43 @@
1
+ name: 'The Pipeline'
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ concurrency:
12
+ group: ci-${{ github.ref }}-1
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ extract-branch:
17
+ name: 'Fetch branch'
18
+ runs-on: ubuntu-latest
19
+ outputs:
20
+ current_branch: ${{ steps.get-branch.outputs.current_branch }}
21
+ steps:
22
+ - name: Extract branch name ๐Ÿ•Š
23
+ id: get-branch
24
+ run: echo "current_branch=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT
25
+ ci:
26
+ name: 'CI'
27
+ needs:
28
+ - extract-branch
29
+ uses: maptiler/tileserver-gl/.github/workflows/ci.yml@master
30
+ ct:
31
+ name: 'CT'
32
+ needs:
33
+ - extract-branch
34
+ uses: maptiler/tileserver-gl/.github/workflows/ct.yml@master
35
+ automerger:
36
+ name: 'Automerge Dependabot PRs'
37
+ needs:
38
+ - ci
39
+ - ct
40
+ - extract-branch
41
+ if: >
42
+ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
43
+ uses: maptiler/tileserver-gl/.github/workflows/automerger.yml@master
@@ -1,13 +1,13 @@
1
- name: "Build, Test, Release"
1
+ name: 'Build, Test, Release'
2
2
 
3
- on:
3
+ on:
4
4
  workflow_dispatch:
5
5
  inputs:
6
6
  docker_user:
7
- description: 'Docker Username'
7
+ description: 'Docker Username'
8
8
  required: true
9
9
  docker_token:
10
- description: 'Docker Token'
10
+ description: 'Docker Token'
11
11
  required: true
12
12
  npm_token:
13
13
  description: 'NPM Token'
@@ -15,7 +15,7 @@ on:
15
15
 
16
16
  jobs:
17
17
  release:
18
- name: "Build, Test, Publish"
18
+ name: 'Build, Test, Publish'
19
19
  runs-on: ubuntu-20.04
20
20
  steps:
21
21
  - name: Check out repository โœจ
@@ -86,6 +86,9 @@ jobs:
86
86
  push: true
87
87
  tags: maptiler/tileserver-gl:latest, maptiler/tileserver-gl:v${{ env.PACKAGE_VERSION }}
88
88
  platforms: linux/arm64,linux/amd64
89
+ # experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
90
+ cache-from: type=gha
91
+ cache-to: type=gha,mode=max
89
92
 
90
93
  - name: Create Tileserver Light Directory
91
94
  run: node publish.js --no-publish
@@ -110,3 +113,6 @@ jobs:
110
113
  push: true
111
114
  tags: maptiler/tileserver-gl-light:latest, maptiler/tileserver-gl-light:v${{ env.PACKAGE_VERSION }}
112
115
  platforms: linux/arm64,linux/amd64
116
+ # experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
117
+ cache-from: type=gha
118
+ cache-to: type=gha,mode=max
package/.hadolint.yml ADDED
@@ -0,0 +1,3 @@
1
+ ignored:
2
+ - DL3008
3
+ - DL3015
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 18
@@ -0,0 +1 @@
1
+ public
@@ -39,6 +39,8 @@ Static images
39
39
 
40
40
  * e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
41
41
  * can be provided multiple times
42
+ * or pass the path as per [Maptiler Cloud API](https://docs.maptiler.com/cloud/api/static-maps/)
43
+ * Match pattern: ((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))
42
44
 
43
45
  * ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
44
46
  * ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileserver-gl-light",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
4
4
  "description": "Map tile server for JSON GL styles - serving vector tiles",
5
5
  "main": "src/main.js",
6
6
  "bin": "src/main.js",
@@ -19,6 +19,7 @@
19
19
  "dependencies": {
20
20
  "@mapbox/glyph-pbf-composite": "0.0.3",
21
21
  "@mapbox/mbtiles": "0.12.1",
22
+ "@mapbox/polyline": "^1.1.1",
22
23
  "@mapbox/sphericalmercator": "1.2.0",
23
24
  "@mapbox/vector-tile": "1.3.1",
24
25
  "@maplibre/maplibre-gl-style-spec": "17.0.2",
@@ -26,17 +27,17 @@
26
27
  "chokidar": "3.5.3",
27
28
  "clone": "2.1.2",
28
29
  "color": "4.2.3",
29
- "commander": "9.4.1",
30
+ "commander": "10.0.0",
30
31
  "cors": "2.8.5",
31
32
  "express": "4.18.2",
32
33
  "handlebars": "4.7.7",
33
34
  "http-shutdown": "1.2.2",
34
35
  "morgan": "1.10.0",
35
36
  "pbf": "3.2.1",
36
- "proj4": "2.8.0",
37
+ "proj4": "2.8.1",
37
38
  "request": "2.88.2",
38
- "tileserver-gl-styles": "2.0.0",
39
- "sanitize-filename": "1.6.3"
39
+ "sanitize-filename": "1.6.3",
40
+ "tileserver-gl-styles": "2.0.0"
40
41
  },
41
42
  "keywords": [
42
43
  "maptiler",
@@ -5,10 +5,10 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>{{name}} - TileServer GL</title>
7
7
  {{#is_vector}}
8
- <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css{{&key_query}}" />
9
- <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
10
- <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl.js{{&key_query}}"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-compat.js{{&key_query}}"><\/script>'); }</script>
11
- <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl-inspect.min.js{{&key_query}}"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-inspect-compat.min.js{{&key_query}}"><\/script>'); }</script>
8
+ <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css" />
9
+ <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css" />
10
+ <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl.js"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-compat.js"><\/script>'); }</script>
11
+ <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl-inspect.min.js"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-inspect-compat.min.js"><\/script>'); }</script>
12
12
  <style>
13
13
  body {background:#fff;color:#333;font-family:Arial, sans-serif;}
14
14
  #map {position:absolute;top:0;left:0;right:250px;bottom:0;}
@@ -18,10 +18,10 @@
18
18
  </style>
19
19
  {{/is_vector}}
20
20
  {{^is_vector}}
21
- <link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css{{&key_query}}" />
22
- <script src="{{public_url}}leaflet.js{{&key_query}}"></script>
23
- <script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
24
- <script src="{{public_url}}L.TileLayer.NoGap.js{{&key_query}}"></script>
21
+ <link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css" />
22
+ <script src="{{public_url}}leaflet.js"></script>
23
+ <script src="{{public_url}}leaflet-hash.js"></script>
24
+ <script src="{{public_url}}L.TileLayer.NoGap.js"></script>
25
25
  <style>
26
26
  body { margin:0; padding:0; }
27
27
  #map { position:absolute; top:0; bottom:0; width:100%; }
@@ -35,6 +35,9 @@
35
35
  <div id="layerList"></div>
36
36
  <pre id="propertyList"></pre>
37
37
  <script>
38
+ var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
39
+ var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
40
+
38
41
  var map = new maplibregl.Map({
39
42
  container: 'map',
40
43
  hash: true,
@@ -44,7 +47,7 @@
44
47
  sources: {
45
48
  'vector_layer_': {
46
49
  type: 'vector',
47
- url: '{{public_url}}data/{{id}}.json{{&key_query}}'
50
+ url: '{{public_url}}data/{{id}}.json' + keyParam
48
51
  }
49
52
  },
50
53
  layers: []
@@ -76,11 +79,14 @@
76
79
  <h1 style="display:none;">{{name}}</h1>
77
80
  <div id='map'></div>
78
81
  <script>
82
+ var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
83
+ var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
84
+
79
85
  var map = L.map('map', { zoomControl: false });
80
86
  new L.Control.Zoom({ position: 'topright' }).addTo(map);
81
87
 
82
88
  var tile_urls = [], tile_attribution, tile_minzoom, tile_maxzoom;
83
- var url = '{{public_url}}data/{{id}}.json{{&key_query}}';
89
+ var url = '{{public_url}}data/{{id}}.json' + keyParam;
84
90
  var req = new XMLHttpRequest();
85
91
  req.overrideMimeType("application/json");
86
92
  req.open('GET', url, true);
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>TileServer GL - Server for vector and raster maps with GL styles</title>
7
- <link rel="stylesheet" type="text/css" href="{{public_url}}index.css{{&key_query}}" />
7
+ <link rel="stylesheet" type="text/css" href="{{public_url}}index.css" />
8
8
  <script>
9
9
  function toggle_xyz(id) {
10
10
  var el = document.getElementById(id);
@@ -110,4 +110,4 @@
110
110
  </p>
111
111
  </footer>
112
112
  </body>
113
- </html>
113
+ </html>
@@ -4,14 +4,14 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>{{name}} - TileServer GL</title>
7
- <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css{{&key_query}}" />
8
- <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
9
- <link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css{{&key_query}}" />
10
- <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl.js{{&key_query}}"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-compat.js{{&key_query}}"><\/script>'); }</script>
11
- <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl-inspect.min.js{{&key_query}}"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-inspect-compat.min.js{{&key_query}}"><\/script>'); }</script>
12
- <script src="{{public_url}}leaflet.js{{&key_query}}"></script>
13
- <script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
14
- <script src="{{public_url}}L.TileLayer.NoGap.js{{&key_query}}"></script>
7
+ <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css" />
8
+ <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css" />
9
+ <link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css" />
10
+ <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl.js"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-compat.js"><\/script>'); }</script>
11
+ <script>if (typeof Symbol !== 'undefined') { document.write('<script src="{{public_url}}maplibre-gl-inspect.min.js"><\/script>'); } else { document.write('<script src="{{public_url}}maplibre-gl-inspect-compat.min.js"><\/script>'); }</script>
12
+ <script src="{{public_url}}leaflet.js"></script>
13
+ <script src="{{public_url}}leaflet-hash.js"></script>
14
+ <script src="{{public_url}}L.TileLayer.NoGap.js"></script>
15
15
  <style>
16
16
  body { margin:0; padding:0; }
17
17
  #map { position:absolute; top:0; bottom:0; width:100%; }
@@ -26,11 +26,15 @@
26
26
  q.indexOf('vector') >= 0 ? 'vector' :
27
27
  (q.indexOf('raster') >= 0 ? 'raster' :
28
28
  (maplibregl.supported() ? 'vector' : 'raster'));
29
+
30
+ var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
31
+ var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
32
+
29
33
  if (preference == 'vector') {
30
- maplibregl.setRTLTextPlugin('{{public_url}}mapbox-gl-rtl-text.js{{&key_query}}');
34
+ maplibregl.setRTLTextPlugin('{{public_url}}mapbox-gl-rtl-text.js' + keyParam);
31
35
  var map = new maplibregl.Map({
32
36
  container: 'map',
33
- style: '{{public_url}}styles/{{id}}/style.json{{&key_query}}',
37
+ style: '{{public_url}}styles/{{id}}/style.json' + keyParam,
34
38
  hash: true,
35
39
  maplibreLogo: true
36
40
  });
@@ -49,7 +53,7 @@
49
53
  new L.Control.Zoom({ position: 'topright' }).addTo(map);
50
54
 
51
55
  var tile_urls = [], tile_attribution, tile_minzoom, tile_maxzoom;
52
- var url = '{{public_url}}styles/{{id}}.json{{&key_query}}';
56
+ var url = '{{public_url}}styles/{{id}}.json' + keyParam;
53
57
  var req = new XMLHttpRequest();
54
58
  req.overrideMimeType("application/json");
55
59
  req.open('GET', url, true);
package/src/serve_data.js CHANGED
@@ -8,7 +8,7 @@ import clone from 'clone';
8
8
  import express from 'express';
9
9
  import MBTiles from '@mapbox/mbtiles';
10
10
  import Pbf from 'pbf';
11
- import VectorTile from '@mapbox/vector-tile';
11
+ import { VectorTile } from '@mapbox/vector-tile';
12
12
 
13
13
  import { getTileUrls, fixTileJSONCenter } from './utils.js';
14
14
 
@@ -15,11 +15,14 @@ import sanitize from 'sanitize-filename';
15
15
  import SphericalMercator from '@mapbox/sphericalmercator';
16
16
  import mlgl from '@maplibre/maplibre-gl-native';
17
17
  import MBTiles from '@mapbox/mbtiles';
18
+ import polyline from '@mapbox/polyline';
18
19
  import proj4 from 'proj4';
19
20
  import request from 'request';
20
21
  import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
21
22
 
22
23
  const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
24
+ const PATH_PATTERN =
25
+ /^((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))/;
23
26
  const httpTester = /^(http(s)?:)?\/\//;
24
27
 
25
28
  const mercator = new SphericalMercator();
@@ -147,47 +150,67 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
147
150
  * @param {Function} transformer Optional transform function.
148
151
  */
149
152
  const extractPathsFromQuery = (query, transformer) => {
153
+ // Initiate paths array
154
+ const paths = [];
150
155
  // Return an empty list if no paths have been provided
151
- if (!query.path) {
152
- return [];
156
+ if ('path' in query && !query.path) {
157
+ return paths;
153
158
  }
154
-
155
- const paths = [];
156
-
157
- // Check if multiple paths have been provided and mimic a list if it's a
158
- // single path.
159
- const providedPaths = Array.isArray(query.path) ? query.path : [query.path];
160
-
161
- // Iterate through paths, parse and validate them
162
- for (const provided_path of providedPaths) {
163
- const currentPath = [];
164
-
165
- // Extract coordinate-list from path
166
- const pathParts = (provided_path || '').split('|');
167
-
168
- // Iterate through coordinate-list, parse the coordinates and validate them
169
- for (const pair of pathParts) {
170
- // Extract coordinates from coordinate pair
171
- const pairParts = pair.split(',');
172
-
173
- // Ensure we have two coordinates
174
- if (pairParts.length === 2) {
175
- const pair = parseCoordinates(pairParts, query, transformer);
176
-
177
- // Ensure coordinates could be parsed and skip them if not
178
- if (pair === null) {
179
- continue;
159
+ // Parse paths provided via path query argument
160
+ if ('path' in query) {
161
+ const providedPaths = Array.isArray(query.path) ? query.path : [query.path];
162
+ // Iterate through paths, parse and validate them
163
+ for (const providedPath of providedPaths) {
164
+ // Logic for pushing coords to path when path includes google polyline
165
+ if (
166
+ providedPath.includes('enc:') &&
167
+ PATH_PATTERN.test(decodeURIComponent(providedPath))
168
+ ) {
169
+ const encodedPaths = providedPath.split(',');
170
+ for (const path of encodedPaths) {
171
+ const line = path
172
+ .split('|')
173
+ .filter(
174
+ (x) =>
175
+ !x.startsWith('fill') &&
176
+ !x.startsWith('stroke') &&
177
+ !x.startsWith('width'),
178
+ )
179
+ .join('')
180
+ .replace('enc:', '');
181
+ const coords = polyline.decode(line).map(([lat, lng]) => [lng, lat]);
182
+ paths.push(coords);
180
183
  }
184
+ } else {
185
+ // Iterate through paths, parse and validate them
186
+ const currentPath = [];
187
+
188
+ // Extract coordinate-list from path
189
+ const pathParts = (providedPath || '').split('|');
190
+
191
+ // Iterate through coordinate-list, parse the coordinates and validate them
192
+ for (const pair of pathParts) {
193
+ // Extract coordinates from coordinate pair
194
+ const pairParts = pair.split(',');
195
+ // Ensure we have two coordinates
196
+ if (pairParts.length === 2) {
197
+ const pair = parseCoordinates(pairParts, query, transformer);
198
+
199
+ // Ensure coordinates could be parsed and skip them if not
200
+ if (pair === null) {
201
+ continue;
202
+ }
181
203
 
182
- // Add the coordinate-pair to the current path if they are valid
183
- currentPath.push(pair);
204
+ // Add the coordinate-pair to the current path if they are valid
205
+ currentPath.push(pair);
206
+ }
207
+ }
208
+ // Extend list of paths with current path if it contains coordinates
209
+ if (currentPath.length) {
210
+ paths.push(currentPath);
211
+ }
184
212
  }
185
213
  }
186
-
187
- // Extend list of paths with current path if it contains coordinates
188
- if (currentPath.length) {
189
- paths.push(currentPath);
190
- }
191
214
  }
192
215
  return paths;
193
216
  };
@@ -422,65 +445,109 @@ const drawMarkers = async (ctx, markers, z) => {
422
445
  * @param {number} z Map zoom level.
423
446
  */
424
447
  const drawPath = (ctx, path, query, z) => {
425
- if (!path || path.length < 2) {
426
- return null;
427
- }
448
+ const renderPath = (splitPaths) => {
449
+ if (!path || path.length < 2) {
450
+ return null;
451
+ }
428
452
 
429
- ctx.beginPath();
453
+ ctx.beginPath();
430
454
 
431
- // Transform coordinates to pixel on canvas and draw lines between points
432
- for (const pair of path) {
433
- const px = precisePx(pair, z);
434
- ctx.lineTo(px[0], px[1]);
435
- }
455
+ // Transform coordinates to pixel on canvas and draw lines between points
456
+ for (const pair of path) {
457
+ const px = precisePx(pair, z);
458
+ ctx.lineTo(px[0], px[1]);
459
+ }
436
460
 
437
- // Check if first coordinate matches last coordinate
438
- if (
439
- path[0][0] === path[path.length - 1][0] &&
440
- path[0][1] === path[path.length - 1][1]
441
- ) {
442
- ctx.closePath();
443
- }
461
+ // Check if first coordinate matches last coordinate
462
+ if (
463
+ path[0][0] === path[path.length - 1][0] &&
464
+ path[0][1] === path[path.length - 1][1]
465
+ ) {
466
+ ctx.closePath();
467
+ }
444
468
 
445
- // Optionally fill drawn shape with a rgba color from query
446
- if (query.fill !== undefined) {
447
- ctx.fillStyle = query.fill;
448
- ctx.fill();
449
- }
469
+ // Optionally fill drawn shape with a rgba color from query
470
+ const pathHasFill =
471
+ splitPaths.filter((x) => x.startsWith('fill')).length > 0;
472
+ if (query.fill !== undefined || pathHasFill) {
473
+ if ('fill' in query) {
474
+ ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
475
+ }
476
+ if (pathHasFill) {
477
+ ctx.fillStyle = splitPaths
478
+ .find((x) => x.startsWith('fill:'))
479
+ .replace('fill:', '');
480
+ }
481
+ ctx.fill();
482
+ }
450
483
 
451
- // Get line width from query and fall back to 1 if not provided
452
- const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
453
-
454
- // Ensure line width is valid
455
- if (lineWidth > 0) {
456
- // Get border width from query and fall back to 10% of line width
457
- const borderWidth =
458
- query.borderwidth !== undefined
459
- ? parseFloat(query.borderwidth)
460
- : lineWidth * 0.1;
461
-
462
- // Set rendering style for the start and end points of the path
463
- // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
464
- ctx.lineCap = query.linecap || 'butt';
465
-
466
- // Set rendering style for overlapping segments of the path with differing directions
467
- // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
468
- ctx.lineJoin = query.linejoin || 'miter';
469
-
470
- // In order to simulate a border we draw the path two times with the first
471
- // beeing the wider border part.
472
- if (query.border !== undefined && borderWidth > 0) {
473
- // We need to double the desired border width and add it to the line width
474
- // in order to get the desired border on each side of the line.
475
- ctx.lineWidth = lineWidth + borderWidth * 2;
476
- // Set border style as rgba
477
- ctx.strokeStyle = query.border;
478
- ctx.stroke();
484
+ // Get line width from query and fall back to 1 if not provided
485
+ const pathHasWidth =
486
+ splitPaths.filter((x) => x.startsWith('width')).length > 0;
487
+ if (query.width !== undefined || pathHasWidth) {
488
+ let lineWidth = 1;
489
+ // Get line width from query
490
+ if ('width' in query) {
491
+ lineWidth = Number(query.width);
492
+ }
493
+ // Get line width from path in query
494
+ if (pathHasWidth) {
495
+ lineWidth = Number(
496
+ splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
497
+ );
498
+ }
499
+ // Get border width from query and fall back to 10% of line width
500
+ const borderWidth =
501
+ query.borderwidth !== undefined
502
+ ? parseFloat(query.borderwidth)
503
+ : lineWidth * 0.1;
504
+
505
+ // Set rendering style for the start and end points of the path
506
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
507
+ ctx.lineCap = query.linecap || 'butt';
508
+
509
+ // Set rendering style for overlapping segments of the path with differing directions
510
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
511
+ ctx.lineJoin = query.linejoin || 'miter';
512
+
513
+ // In order to simulate a border we draw the path two times with the first
514
+ // beeing the wider border part.
515
+ if (query.border !== undefined && borderWidth > 0) {
516
+ // We need to double the desired border width and add it to the line width
517
+ // in order to get the desired border on each side of the line.
518
+ ctx.lineWidth = lineWidth + borderWidth * 2;
519
+ // Set border style as rgba
520
+ ctx.strokeStyle = query.border;
521
+ ctx.stroke();
522
+ }
523
+ ctx.lineWidth = lineWidth;
479
524
  }
480
525
 
481
- ctx.lineWidth = lineWidth;
482
- ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
526
+ const pathHasStroke =
527
+ splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
528
+ if (query.stroke !== undefined || pathHasStroke) {
529
+ if ('stroke' in query) {
530
+ ctx.strokeStyle = query.stroke;
531
+ }
532
+ // Path Width gets higher priority
533
+ if (pathHasWidth) {
534
+ ctx.strokeStyle = splitPaths
535
+ .find((x) => x.startsWith('stroke:'))
536
+ .replace('stroke:', '');
537
+ }
538
+ } else {
539
+ ctx.strokeStyle = 'rgba(0,64,255,0.7)';
540
+ }
483
541
  ctx.stroke();
542
+ };
543
+
544
+ // Check if path in query is valid
545
+ if (Array.isArray(query.path)) {
546
+ for (let i = 0; i < query.path.length; i += 1) {
547
+ renderPath(decodeURIComponent(query.path.at(i)).split('|'));
548
+ }
549
+ } else {
550
+ renderPath(decodeURIComponent(query.path).split('|'));
484
551
  }
485
552
  };
486
553
 
package/run.sh DELETED
@@ -1,37 +0,0 @@
1
- #!/bin/bash
2
-
3
- _term() {
4
- echo "Caught signal, stopping gracefully"
5
- kill -TERM "$child" 2>/dev/null
6
- }
7
-
8
- trap _term SIGTERM
9
- trap _term SIGINT
10
-
11
- xvfbMaxStartWaitTime=60
12
- displayNumber=99
13
- screenNumber=0
14
-
15
- # Delete files if they were not cleaned by last run
16
- rm -rf /tmp/.X11-unix /tmp/.X${displayNumber}-lock ~/xvfb.pid
17
-
18
- echo "Starting Xvfb on display ${displayNumber}"
19
- start-stop-daemon --start --pidfile ~/xvfb.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :${displayNumber} -screen ${screenNumber} 1024x768x24 -ac +extension GLX +render -noreset
20
-
21
- # Wait to be able to connect to the port. This will exit if it cannot in 1 minute.
22
- timeout ${xvfbMaxStartWaitTime} bash -c "while ! xdpyinfo -display :${displayNumber} >/dev/null; do sleep 0.5; done"
23
- if [ $? -ne 0 ]; then
24
- echo "Could not connect to display ${displayNumber} in ${xvfbMaxStartWaitTime} seconds time."
25
- exit 1
26
- fi
27
-
28
- export DISPLAY=:${displayNumber}.${screenNumber}
29
-
30
- echo
31
- cd /data
32
- node /usr/src/app/ "$@" &
33
- child=$!
34
- wait "$child"
35
-
36
- start-stop-daemon --stop --retry 5 --pidfile ~/xvfb.pid # stop xvfb when exiting
37
- rm ~/xvfb.pid