Merge branch 'master' into windows

Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net>
This commit is contained in:
Andrew Calcutt 2023-11-23 11:52:31 -05:00
commit e0d1a1c708
13 changed files with 701 additions and 681 deletions

View file

@ -28,8 +28,8 @@ docker build -t tileserver-gl-light .
[Download from OpenMapTiles.com](https://openmaptiles.com/downloads/planet/) or [create](https://github.com/openmaptiles/openmaptiles) your vector tile, and run following in directory contains your *.mbtiles.
```
docker run --rm -it -v $(pwd):/data -p 8000:80 tileserver-gl-light
docker run --rm -it -v $(pwd):/data -p 8080:8080 tileserver-gl-light
```
## Documentation
You can read full documentation of this project at https://tileserver.readthedocs.io/.
You can read full documentation of this project at https://maptiler-tileserver.readthedocs.io/

View file

@ -132,6 +132,19 @@ If you have plenty of memory, try setting these equal to or slightly above your
If you need to conserve memory, try lower values for scale factors that are less common.
Default is ``[16, 8, 4]``.
``pbfAlias``
------------------------
Some CDNs did not handle .pbf extension as a static file correctly.
The default URLs (with .pbf) are always available, but an alternative can be set.
An example extension suffix would be ".pbf.pict".
``serveAllFonts``
------------------------
If this option is enabled, all the fonts from the ``paths.fonts`` will be served.
Otherwise only the fonts referenced by available styles will be served.
``serveAllStyles``
------------------------
@ -139,10 +152,16 @@ If this option is enabled, all the styles from the ``paths.styles`` will be serv
The process will also watch for changes in this directory and remove/add more styles dynamically.
It is recommended to also use the ``serveAllFonts`` option when using this option.
``serveStaticMaps``
------------------------
If this option is enabled, all the static map endpoints will be served.
Default is ``true``.
``watermark``
-----------
Optional string to be rendered into the raster tiles (and static maps) as watermark (bottom-left corner).
Optional string to be rendered into the raster tiles and static maps as watermark (bottom-left corner).
Not used by default.
``staticAttributionText``

View file

@ -52,14 +52,14 @@ Static images
* can be provided multiple times
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
* ``stroke`` - color of the path stroke
* ``width`` - width of the stroke
* ``linecap`` - rendering style for the start and end points of the path
* ``linejoin`` - rendering style for overlapping segments of the path with differing directions
* ``border`` - color of the optional border path stroke
* ``borderwidth`` - width of the border stroke (default 10% of width)
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat`` for paths and markers
* ``fill`` - default color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``) for all paths
* ``stroke`` - default color of the path stroke for all paths
* ``width`` - default width of the stroke for all paths
* ``linecap`` - rendering style for the start and end points of all paths - see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
* ``linejoin`` - rendering style for joining successive segments of all paths when the direction changes - see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
* ``border`` - color of the optional border stroke for all paths ; the border is like a halo around the stroke
* ``borderwidth`` - width of the border stroke (default 10% of stroke width) for all paths
* ``marker`` - Marker in format ``lng,lat|iconPath|option|option|...``
* Will be rendered with the bottom center at the provided location

286
package-lock.json generated
View file

@ -16,7 +16,7 @@
"@mapbox/vector-tile": "1.3.1",
"@maplibre/maplibre-gl-native": "5.2.0",
"@maplibre/maplibre-gl-style-spec": "18.0.0",
"@sindresorhus/fnv1a": "3.0.0",
"@sindresorhus/fnv1a": "3.1.0",
"advanced-pool": "0.3.3",
"axios": "^1.6.2",
"canvas": "2.11.2",
@ -40,12 +40,12 @@
"tileserver-gl": "src/main.js"
},
"devDependencies": {
"@commitlint/cli": "^18.4.1",
"@commitlint/config-conventional": "^18.4.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"chai": "4.3.10",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsdoc": "^46.9.0",
"eslint-plugin-prettier": "^5.0.1",
@ -207,16 +207,16 @@
}
},
"node_modules/@commitlint/cli": {
"version": "18.4.1",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.4.1.tgz",
"integrity": "sha512-4+jljfd29Udw9RDDyigavLO9LvdbmB8O9xjDzVZ0R3lJuG7nCeyHgnKWIVpFaN590isZMV/cMeQK0gH7hRF40A==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.4.3.tgz",
"integrity": "sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==",
"dev": true,
"dependencies": {
"@commitlint/format": "^18.4.0",
"@commitlint/lint": "^18.4.0",
"@commitlint/load": "^18.4.1",
"@commitlint/read": "^18.4.0",
"@commitlint/types": "^18.4.0",
"@commitlint/format": "^18.4.3",
"@commitlint/lint": "^18.4.3",
"@commitlint/load": "^18.4.3",
"@commitlint/read": "^18.4.3",
"@commitlint/types": "^18.4.3",
"execa": "^5.0.0",
"lodash.isfunction": "^3.0.9",
"resolve-from": "5.0.0",
@ -231,9 +231,9 @@
}
},
"node_modules/@commitlint/config-conventional": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.4.0.tgz",
"integrity": "sha512-vArwCZopsZs0FnGsh9AR7uUTPZ5oVGk8+qnEZWq2KTsMjrE0k80b+oZ32GSQmXQT2iMKVrDC8pKX5uKNkCe9Sw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.4.3.tgz",
"integrity": "sha512-729eRRaNta7JZF07qf6SAGSghoDEp9mH7yHU0m7ff0q89W97wDrWCyZ3yoV3mcQJwbhlmVmZPTkPcm7qiAu8WA==",
"dev": true,
"dependencies": {
"conventional-changelog-conventionalcommits": "^7.0.2"
@ -243,12 +243,12 @@
}
},
"node_modules/@commitlint/config-validator": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.4.0.tgz",
"integrity": "sha512-1y6qHMU3o4cYQSK+Y9EnmH6H1GRiwQGjnLIUOIKlekrmfc8MrMk1ByNmb8od4vK3qHJAaL/77/5n+1uyyIF5dA==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.4.3.tgz",
"integrity": "sha512-FPZZmTJBARPCyef9ohRC9EANiQEKSWIdatx5OlgeHKu878dWwpyeFauVkhzuBRJFcCA4Uvz/FDtlDKs008IHcA==",
"dev": true,
"dependencies": {
"@commitlint/types": "^18.4.0",
"@commitlint/types": "^18.4.3",
"ajv": "^8.11.0"
},
"engines": {
@ -256,12 +256,12 @@
}
},
"node_modules/@commitlint/ensure": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.4.0.tgz",
"integrity": "sha512-N5cJo/n61ULSwz3W5Iz/IZJ0I9H/PaHc+OMcF2XcRVbLa6B3YwzEW66XGCRKVULlsBNSrIH6tk5un9ayXAXIdw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.4.3.tgz",
"integrity": "sha512-MI4fwD9TWDVn4plF5+7JUyLLbkOdzIRBmVeNlk4dcGlkrVA+/l5GLcpN66q9LkFsFv6G2X31y89ApA3hqnqIFg==",
"dev": true,
"dependencies": {
"@commitlint/types": "^18.4.0",
"@commitlint/types": "^18.4.3",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"lodash.snakecase": "^4.1.1",
@ -273,21 +273,21 @@
}
},
"node_modules/@commitlint/execute-rule": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.4.0.tgz",
"integrity": "sha512-g013SWki6ZWhURBLOSXTaVQGWHdA0QlPJGiW4a+YpThezmJOemvc4LiKVpn13AjSKQ40QnmBqpBrxujOaSo+3A==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.4.3.tgz",
"integrity": "sha512-t7FM4c+BdX9WWZCPrrbV5+0SWLgT3kCq7e7/GhHCreYifg3V8qyvO127HF796vyFql75n4TFF+5v1asOOWkV1Q==",
"dev": true,
"engines": {
"node": ">=v18"
}
},
"node_modules/@commitlint/format": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.4.0.tgz",
"integrity": "sha512-MiAe4D5/ahty38CzULdQbpRa3ReKZtx0kyigOWcntq+N5uqez+Ac4/MO7H+3j1kC4G7nfJVfBu6TqcXeyNvhCQ==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.4.3.tgz",
"integrity": "sha512-8b+ItXYHxAhRAXFfYki5PpbuMMOmXYuzLxib65z2XTqki59YDQJGpJ/wB1kEE5MQDgSTQWtKUrA8n9zS/1uIDQ==",
"dev": true,
"dependencies": {
"@commitlint/types": "^18.4.0",
"@commitlint/types": "^18.4.3",
"chalk": "^4.1.0"
},
"engines": {
@ -295,12 +295,12 @@
}
},
"node_modules/@commitlint/is-ignored": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.4.0.tgz",
"integrity": "sha512-vyBKBj3Q4N3Xe4ZQcJXW9ef6gVrDL9Fl2HXnnC3F0Qt/F6E4runhJkEuUh5DB3WCXTJUHIJkByKPqrnz4RNrZw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.4.3.tgz",
"integrity": "sha512-ZseOY9UfuAI32h9w342Km4AIaTieeFskm2ZKdrG7r31+c6zGBzuny9KQhwI9puc0J3GkUquEgKJblCl7pMnjwg==",
"dev": true,
"dependencies": {
"@commitlint/types": "^18.4.0",
"@commitlint/types": "^18.4.3",
"semver": "7.5.4"
},
"engines": {
@ -308,30 +308,30 @@
}
},
"node_modules/@commitlint/lint": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.4.0.tgz",
"integrity": "sha512-Wkkf1DPVeLdHYGqtzMBfWoMbUtCojvlzDR89OKVic1rid41iZbb0FzTcwgMYs/1TNWNxoIq9PVVwY7ovLX1aJQ==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.4.3.tgz",
"integrity": "sha512-18u3MRgEXNbnYkMOWoncvq6QB8/90m9TbERKgdPqVvS+zQ/MsuRhdvHYCIXGXZxUb0YI4DV2PC4bPneBV/fYuA==",
"dev": true,
"dependencies": {
"@commitlint/is-ignored": "^18.4.0",
"@commitlint/parse": "^18.4.0",
"@commitlint/rules": "^18.4.0",
"@commitlint/types": "^18.4.0"
"@commitlint/is-ignored": "^18.4.3",
"@commitlint/parse": "^18.4.3",
"@commitlint/rules": "^18.4.3",
"@commitlint/types": "^18.4.3"
},
"engines": {
"node": ">=v18"
}
},
"node_modules/@commitlint/load": {
"version": "18.4.1",
"resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.4.1.tgz",
"integrity": "sha512-o/plBiPJQgbSq/4ipDpsq4HCmURjBAEjr1EO/p2falr3VhwV0WGXTvb8NlihgI8xtSyO6lHvtycrE535GMLQbA==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.4.3.tgz",
"integrity": "sha512-v6j2WhvRQJrcJaj5D+EyES2WKTxPpxENmNpNG3Ww8MZGik3jWRXtph0QTzia5ZJyPh2ib5aC/6BIDymkUUM58Q==",
"dev": true,
"dependencies": {
"@commitlint/config-validator": "^18.4.0",
"@commitlint/execute-rule": "^18.4.0",
"@commitlint/resolve-extends": "^18.4.0",
"@commitlint/types": "^18.4.0",
"@commitlint/config-validator": "^18.4.3",
"@commitlint/execute-rule": "^18.4.3",
"@commitlint/resolve-extends": "^18.4.3",
"@commitlint/types": "^18.4.3",
"@types/node": "^18.11.9",
"chalk": "^4.1.0",
"cosmiconfig": "^8.3.6",
@ -346,22 +346,22 @@
}
},
"node_modules/@commitlint/message": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.4.0.tgz",
"integrity": "sha512-3kg6NQO6pJ+VdBTWi51KInT8ngkxPJaW+iI7URtUALjKcO9K4XY3gf80ZPmS1hDessrjb7qCr1lau8eWMINAQw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.4.3.tgz",
"integrity": "sha512-ddJ7AztWUIoEMAXoewx45lKEYEOeOlBVWjk8hDMUGpprkuvWULpaXczqdjwVtjrKT3JhhN+gMs8pm5G3vB2how==",
"dev": true,
"engines": {
"node": ">=v18"
}
},
"node_modules/@commitlint/parse": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.4.0.tgz",
"integrity": "sha512-SxTCSUZH8CJNYWOlFg18YUQ2RLz8ubXKbpHUIiSNwCbiQx7UDCydp1JnhoB4sOYOxgV8d3nuDwYluRU5KnEY4A==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.4.3.tgz",
"integrity": "sha512-eoH7CXM9L+/Me96KVcfJ27EIIbA5P9sqw3DqjJhRYuhaULIsPHFs5S5GBDCqT0vKZQDx0DgxhMpW6AQbnKrFtA==",
"dev": true,
"dependencies": {
"@commitlint/types": "^18.4.0",
"conventional-changelog-angular": "^6.0.0",
"@commitlint/types": "^18.4.3",
"conventional-changelog-angular": "^7.0.0",
"conventional-commits-parser": "^5.0.0"
},
"engines": {
@ -369,13 +369,13 @@
}
},
"node_modules/@commitlint/read": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.4.0.tgz",
"integrity": "sha512-IpnABCbDeOw5npZ09SZZGLfd3T7cFtsxUYm6wT3aGmIB2fXKE3fMeuj3jxXjMibiGIyA3Z5voCMuOcKWpkNySA==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.4.3.tgz",
"integrity": "sha512-H4HGxaYA6OBCimZAtghL+B+SWu8ep4X7BwgmedmqWZRHxRLcX2q0bWBtUm5FsMbluxbOfrJwOs/Z0ah4roP/GQ==",
"dev": true,
"dependencies": {
"@commitlint/top-level": "^18.4.0",
"@commitlint/types": "^18.4.0",
"@commitlint/top-level": "^18.4.3",
"@commitlint/types": "^18.4.3",
"fs-extra": "^11.0.0",
"git-raw-commits": "^2.0.11",
"minimist": "^1.2.6"
@ -385,13 +385,13 @@
}
},
"node_modules/@commitlint/resolve-extends": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.4.0.tgz",
"integrity": "sha512-qhgU6ach+S6sJMD9NjCYiEycOObGhxzWQLQzqlScJCv9zkPs15Bg0ffLXTQ3z7ipXv46XEKYMnSJzjLRw2Tlkg==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.4.3.tgz",
"integrity": "sha512-30sk04LZWf8+SDgJrbJCjM90gTg2LxsD9cykCFeFu+JFHvBFq5ugzp2eO/DJGylAdVaqxej3c7eTSE64hR/lnw==",
"dev": true,
"dependencies": {
"@commitlint/config-validator": "^18.4.0",
"@commitlint/types": "^18.4.0",
"@commitlint/config-validator": "^18.4.3",
"@commitlint/types": "^18.4.3",
"import-fresh": "^3.0.0",
"lodash.mergewith": "^4.6.2",
"resolve-from": "^5.0.0",
@ -402,15 +402,15 @@
}
},
"node_modules/@commitlint/rules": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.4.0.tgz",
"integrity": "sha512-T3ChRxQZ6g0iNCpVLc6KeQId0/86TnyQA8PFkng+dWElO2DAA5km/yirgKZV1Xlc+gF7Rf6d+a0ottxdKpOY+w==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.4.3.tgz",
"integrity": "sha512-8KIeukDf45BiY+Lul1T0imSNXF0sMrlLG6JpLLKolkmYVQ6PxxoNOriwyZ3UTFFpaVbPy0rcITaV7U9JCAfDTA==",
"dev": true,
"dependencies": {
"@commitlint/ensure": "^18.4.0",
"@commitlint/message": "^18.4.0",
"@commitlint/to-lines": "^18.4.0",
"@commitlint/types": "^18.4.0",
"@commitlint/ensure": "^18.4.3",
"@commitlint/message": "^18.4.3",
"@commitlint/to-lines": "^18.4.3",
"@commitlint/types": "^18.4.3",
"execa": "^5.0.0"
},
"engines": {
@ -418,18 +418,18 @@
}
},
"node_modules/@commitlint/to-lines": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.4.0.tgz",
"integrity": "sha512-bZXuCtfBPjNgtEnG3gwJrveIgfKK2UdhIhFvKpMTrQl/gAwoto/3mzmE7qGAHwmuP4eZ2U8X7iwMnqIlWmv2Tw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.4.3.tgz",
"integrity": "sha512-fy1TAleik4Zfru1RJ8ZU6cOSvgSVhUellxd3WZV1D5RwHZETt1sZdcA4mQN2y3VcIZsUNKkW0Mq8CM9/L9harQ==",
"dev": true,
"engines": {
"node": ">=v18"
}
},
"node_modules/@commitlint/top-level": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.4.0.tgz",
"integrity": "sha512-TfulcA8UHF7MZ6tm4Ci3aqZgMBZa1OoCg4prccWHvwG/hsHujZ7+0FKbeKqDbcSli/YWm4NJwEjl4uh5itIJeA==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.4.3.tgz",
"integrity": "sha512-E6fJPBLPFL5R8+XUNSYkj4HekIOuGMyJo3mIx2PkYc3clel+pcWQ7TConqXxNWW4x1ugigiIY2RGot55qUq1hw==",
"dev": true,
"dependencies": {
"find-up": "^5.0.0"
@ -439,9 +439,9 @@
}
},
"node_modules/@commitlint/types": {
"version": "18.4.0",
"resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.4.0.tgz",
"integrity": "sha512-MKeaFxt0I9fhqUb2E+YIzX/gZtmkuodJET/XKiZIMvXUff8Ee4Ih86eLg+yAm2jf1pwGBmU02uNOp0y094w2Uw==",
"version": "18.4.3",
"resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.4.3.tgz",
"integrity": "sha512-cvzx+vtY/I2hVBZHCLrpoh+sA0hfuzHwDc+BAFPimYLjJkpHnghQM+z8W/KyLGkygJh3BtI3xXXq+dKjnSWEmA==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0"
@ -556,9 +556,9 @@
}
},
"node_modules/@eslint/js": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -969,9 +969,9 @@
}
},
"node_modules/@sindresorhus/fnv1a": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.0.0.tgz",
"integrity": "sha512-M6pmbdZqAryzjZ4ELAzrdCMoMZk5lH/fshKrapfSeXdf2W+GDqZvPmfXaNTZp43//FVbSwkTPwpEMnehSyskkQ==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz",
"integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
@ -1005,9 +1005,9 @@
"integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ=="
},
"node_modules/@types/node": {
"version": "18.18.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz",
"integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==",
"version": "18.18.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.11.tgz",
"integrity": "sha512-c1vku6qnTeujJneYH94/4aq73XrVcsJe35UPyAsSok1ijiKrkRzK+AxQPSpNMUnC03roWBBwJx/9I8V7lQoxmA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@ -1025,16 +1025,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
"integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz",
"integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.11.0",
"@typescript-eslint/type-utils": "6.11.0",
"@typescript-eslint/utils": "6.11.0",
"@typescript-eslint/visitor-keys": "6.11.0",
"@typescript-eslint/scope-manager": "6.12.0",
"@typescript-eslint/type-utils": "6.12.0",
"@typescript-eslint/utils": "6.12.0",
"@typescript-eslint/visitor-keys": "6.12.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@ -1060,15 +1060,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
"integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz",
"integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.11.0",
"@typescript-eslint/types": "6.11.0",
"@typescript-eslint/typescript-estree": "6.11.0",
"@typescript-eslint/visitor-keys": "6.11.0",
"@typescript-eslint/scope-manager": "6.12.0",
"@typescript-eslint/types": "6.12.0",
"@typescript-eslint/typescript-estree": "6.12.0",
"@typescript-eslint/visitor-keys": "6.12.0",
"debug": "^4.3.4"
},
"engines": {
@ -1088,13 +1088,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
"integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz",
"integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.11.0",
"@typescript-eslint/visitor-keys": "6.11.0"
"@typescript-eslint/types": "6.12.0",
"@typescript-eslint/visitor-keys": "6.12.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@ -1105,13 +1105,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
"integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz",
"integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "6.11.0",
"@typescript-eslint/utils": "6.11.0",
"@typescript-eslint/typescript-estree": "6.12.0",
"@typescript-eslint/utils": "6.12.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@ -1132,9 +1132,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
"integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz",
"integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@ -1145,13 +1145,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
"integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz",
"integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.11.0",
"@typescript-eslint/visitor-keys": "6.11.0",
"@typescript-eslint/types": "6.12.0",
"@typescript-eslint/visitor-keys": "6.12.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -1172,17 +1172,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
"integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz",
"integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.11.0",
"@typescript-eslint/types": "6.11.0",
"@typescript-eslint/typescript-estree": "6.11.0",
"@typescript-eslint/scope-manager": "6.12.0",
"@typescript-eslint/types": "6.12.0",
"@typescript-eslint/typescript-estree": "6.12.0",
"semver": "^7.5.4"
},
"engines": {
@ -1197,12 +1197,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
"integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz",
"integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.11.0",
"@typescript-eslint/types": "6.12.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@ -2117,15 +2117,15 @@
}
},
"node_modules/conventional-changelog-angular": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz",
"integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
"integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==",
"dev": true,
"dependencies": {
"compare-func": "^2.0.0"
},
"engines": {
"node": ">=14"
"node": ">=16"
}
},
"node_modules/conventional-changelog-conventionalcommits": {
@ -2809,15 +2809,15 @@
}
},
"node_modules/eslint": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.53.0",
"@eslint/js": "8.54.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",

View file

@ -6,7 +6,7 @@
"bin": "src/main.js",
"type": "module",
"scripts": {
"test": "mocha test/**.js --timeout 10000",
"test": "mocha test/**.js --timeout 10000 --exit",
"lint:yml": "yamllint --schema=CORE_SCHEMA *.{yml,yaml}",
"lint:js": "npm run lint:eslint && npm run lint:prettier",
"lint:js:fix": "npm run lint:eslint:fix && npm run lint:prettier:fix",
@ -14,7 +14,7 @@
"lint:eslint:fix": "eslint --fix \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs}\" --ignore-path .gitignore",
"lint:prettier": "prettier --check \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
"lint:prettier:fix": "prettier --write \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:8080 $(docker build -q .)",
"docker": "docker build . && docker run --rm -i -p 8080:8080 $(docker build -q .)",
"prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){ process.exit(1) } \" || husky install"
},
"dependencies": {
@ -25,7 +25,7 @@
"@mapbox/vector-tile": "1.3.1",
"@maplibre/maplibre-gl-native": "5.2.0",
"@maplibre/maplibre-gl-style-spec": "18.0.0",
"@sindresorhus/fnv1a": "3.0.0",
"@sindresorhus/fnv1a": "3.1.0",
"advanced-pool": "0.3.3",
"axios": "^1.6.2",
"canvas": "2.11.2",
@ -46,12 +46,12 @@
"tileserver-gl-styles": "2.0.0"
},
"devDependencies": {
"@commitlint/cli": "^18.4.1",
"@commitlint/config-conventional": "^18.4.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"chai": "4.3.10",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsdoc": "^46.9.0",
"eslint-plugin-prettier": "^5.0.1",

View file

@ -82,9 +82,6 @@
<div class="identifier">type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data {{#if source_type}} | ext: {{source_type}}{{/if}}</div>
<p class="services">
services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a>
{{#if wmts_link}}
| <a href="{{&wmts_link}}">WMTS</a>
{{/if}}
{{#if xyz_link}}
| <a href="#" onclick="return toggle_xyz('xyz_data_{{@key}}');">XYZ</a>
<input id="xyz_data_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />

View file

@ -1,9 +1,9 @@
import * as http from 'http';
var options = {
const options = {
timeout: 2000,
};
var url = 'http://localhost:8080/health';
var request = http.request(url, options, (res) => {
const url = 'http://localhost:8080/health';
const request = http.request(url, options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);

303
src/render.js Normal file
View file

@ -0,0 +1,303 @@
'use strict';
import { createCanvas, Image } from 'canvas';
import SphericalMercator from '@mapbox/sphericalmercator';
const mercator = new SphericalMercator();
/**
* Transforms coordinates to pixels.
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
* @param {number} zoom Map zoom level.
*/
const precisePx = (ll, zoom) => {
const px = mercator.px(ll, 20);
const scale = Math.pow(2, zoom - 20);
return [px[0] * scale, px[1] * scale];
};
/**
* Draws a marker in canvas context.
* @param {object} ctx Canvas context object.
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
* @param {number} z Map zoom level.
*/
const drawMarker = (ctx, marker, z) => {
return new Promise((resolve) => {
const img = new Image();
const pixelCoords = precisePx(marker.location, z);
const getMarkerCoordinates = (imageWidth, imageHeight, scale) => {
// Images are placed with their top-left corner at the provided location
// within the canvas but we expect icons to be centered and above it.
// Substract half of the images width from the x-coordinate to center
// the image in relation to the provided location
let xCoordinate = pixelCoords[0] - imageWidth / 2;
// Substract the images height from the y-coordinate to place it above
// the provided location
let yCoordinate = pixelCoords[1] - imageHeight;
// Since image placement is dependent on the size offsets have to be
// scaled as well. Additionally offsets are provided as either positive or
// negative values so we always add them
if (marker.offsetX) {
xCoordinate = xCoordinate + marker.offsetX * scale;
}
if (marker.offsetY) {
yCoordinate = yCoordinate + marker.offsetY * scale;
}
return {
x: xCoordinate,
y: yCoordinate,
};
};
const drawOnCanvas = () => {
// Check if the images should be resized before beeing drawn
const defaultScale = 1;
const scale = marker.scale ? marker.scale : defaultScale;
// Calculate scaled image sizes
const imageWidth = img.width * scale;
const imageHeight = img.height * scale;
// Pass the desired sizes to get correlating coordinates
const coords = getMarkerCoordinates(imageWidth, imageHeight, scale);
// Draw the image on canvas
if (scale != defaultScale) {
ctx.drawImage(img, coords.x, coords.y, imageWidth, imageHeight);
} else {
ctx.drawImage(img, coords.x, coords.y);
}
// Resolve the promise when image has been drawn
resolve();
};
img.onload = drawOnCanvas;
img.onerror = (err) => {
throw err;
};
img.src = marker.icon;
});
};
/**
* Draws a list of markers onto a canvas.
* Wraps drawing of markers into list of promises and awaits them.
* It's required because images are expected to load asynchronous in canvas js
* even when provided from a local disk.
* @param {object} ctx Canvas context object.
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
* @param {number} z Map zoom level.
*/
const drawMarkers = async (ctx, markers, z) => {
const markerPromises = [];
for (const marker of markers) {
// Begin drawing marker
markerPromises.push(drawMarker(ctx, marker, z));
}
// Await marker drawings before continuing
await Promise.all(markerPromises);
};
/**
* Draws a list of coordinates onto a canvas and styles the resulting path.
* @param {object} ctx Canvas context object.
* @param {List[Number]} path List of coordinates.
* @param {object} query Request query parameters.
* @param {string} pathQuery Path query parameter.
* @param {number} z Map zoom level.
*/
const drawPath = (ctx, path, query, pathQuery, z) => {
const splitPaths = pathQuery.split('|');
if (!path || path.length < 2) {
return null;
}
ctx.beginPath();
// Transform coordinates to pixel on canvas and draw lines between points
for (const pair of path) {
const px = precisePx(pair, z);
ctx.lineTo(px[0], px[1]);
}
// Check if first coordinate matches last coordinate
if (
path[0][0] === path[path.length - 1][0] &&
path[0][1] === path[path.length - 1][1]
) {
ctx.closePath();
}
// Optionally fill drawn shape with a rgba color from query
const pathHasFill = splitPaths.filter((x) => x.startsWith('fill')).length > 0;
if (query.fill !== undefined || pathHasFill) {
if ('fill' in query) {
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
}
if (pathHasFill) {
ctx.fillStyle = splitPaths
.find((x) => x.startsWith('fill:'))
.replace('fill:', '');
}
ctx.fill();
}
// Get line width from query and fall back to 1 if not provided
const pathHasWidth =
splitPaths.filter((x) => x.startsWith('width')).length > 0;
if (query.width !== undefined || pathHasWidth) {
let lineWidth = 1;
// Get line width from query
if ('width' in query) {
lineWidth = Number(query.width);
}
// Get line width from path in query
if (pathHasWidth) {
lineWidth = Number(
splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
);
}
// Get border width from query and fall back to 10% of line width
const borderWidth =
query.borderwidth !== undefined
? parseFloat(query.borderwidth)
: lineWidth * 0.1;
// Set rendering style for the start and end points of the path
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
ctx.lineCap = query.linecap || 'butt';
// Set rendering style for overlapping segments of the path with differing directions
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
ctx.lineJoin = query.linejoin || 'miter';
// In order to simulate a border we draw the path two times with the first
// beeing the wider border part.
if (query.border !== undefined && borderWidth > 0) {
// We need to double the desired border width and add it to the line width
// in order to get the desired border on each side of the line.
ctx.lineWidth = lineWidth + borderWidth * 2;
// Set border style as rgba
ctx.strokeStyle = query.border;
ctx.stroke();
}
ctx.lineWidth = lineWidth;
}
const pathHasStroke =
splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
if (query.stroke !== undefined || pathHasStroke) {
if ('stroke' in query) {
ctx.strokeStyle = query.stroke;
}
// Path Stroke gets higher priority
if (pathHasStroke) {
ctx.strokeStyle = splitPaths
.find((x) => x.startsWith('stroke:'))
.replace('stroke:', '');
}
} else {
ctx.strokeStyle = 'rgba(0,64,255,0.7)';
}
ctx.stroke();
};
export const renderOverlay = async (
z,
x,
y,
bearing,
pitch,
w,
h,
scale,
paths,
markers,
query,
) => {
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
return null;
}
const center = precisePx([x, y], z);
const mapHeight = 512 * (1 << z);
const maxEdge = center[1] + h / 2;
const minEdge = center[1] - h / 2;
if (maxEdge > mapHeight) {
center[1] -= maxEdge - mapHeight;
} else if (minEdge < 0) {
center[1] -= minEdge;
}
const canvas = createCanvas(scale * w, scale * h);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
if (bearing) {
ctx.translate(w / 2, h / 2);
ctx.rotate((-bearing / 180) * Math.PI);
ctx.translate(-center[0], -center[1]);
} else {
// optimized path
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
}
// Draw provided paths if any
paths.forEach((path, i) => {
const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
drawPath(ctx, path, query, pathQuery, z);
});
// Await drawing of markers before rendering the canvas
await drawMarkers(ctx, markers, z);
return canvas.toBuffer();
};
export const renderWatermark = (width, height, scale, text) => {
const canvas = createCanvas(scale * width, scale * height);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.font = '10px sans-serif';
ctx.strokeWidth = '1px';
ctx.strokeStyle = 'rgba(255,255,255,.4)';
ctx.strokeText(text, 5, height - 5);
ctx.fillStyle = 'rgba(0,0,0,.4)';
ctx.fillText(text, 5, height - 5);
return canvas;
};
export const renderAttribution = (width, height, scale, text) => {
const canvas = createCanvas(scale * width, scale * height);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.font = '10px sans-serif';
const textMetrics = ctx.measureText(text);
const textWidth = textMetrics.width;
const textHeight = 14;
const padding = 6;
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.fillRect(
width - textWidth - padding,
height - textHeight - padding,
textWidth + padding,
textHeight + padding,
);
ctx.fillStyle = 'rgba(0,0,0,.8)';
ctx.fillText(text, width - textWidth - padding / 2, height - textHeight + 8);
return canvas;
};

View file

@ -1,10 +1,8 @@
'use strict';
import express from 'express';
import fs from 'node:fs';
import path from 'path';
import { getFontsPbf } from './utils.js';
import { getFontsPbf, listFonts } from './utils.js';
export const serve_font = (options, allowedFonts) => {
const app = express().disable('x-powered-by');
@ -14,29 +12,6 @@ export const serve_font = (options, allowedFonts) => {
const fontPath = options.paths.fonts;
const existingFonts = {};
const fontListingPromise = new Promise((resolve, reject) => {
fs.readdir(options.paths.fonts, (err, files) => {
if (err) {
reject(err);
return;
}
for (const file of files) {
fs.stat(path.join(fontPath, file), (err, stats) => {
if (err) {
reject(err);
return;
}
if (
stats.isDirectory() &&
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))
) {
existingFonts[path.basename(file)] = true;
}
});
}
resolve();
});
});
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => {
const fontstack = decodeURI(req.params.fontstack);
@ -65,5 +40,8 @@ export const serve_font = (options, allowedFonts) => {
);
});
return fontListingPromise.then(() => app);
return listFonts(options.paths.fonts).then((fonts) => {
Object.assign(existingFonts, fonts);
return app;
});
};

View file

@ -6,7 +6,7 @@ import path from 'path';
import url from 'url';
import util from 'util';
import zlib from 'zlib';
import { createCanvas, Image } from 'canvas';
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
import sharp from 'sharp'; // sharp has to be required before node-canvas on linux but after it on windows. see https://github.com/lovell/sharp/issues/371
import clone from 'clone';
import Color from 'color';
@ -20,6 +20,7 @@ import proj4 from 'proj4';
import axios from 'axios';
import {
getFontsPbf,
listFonts,
getTileUrls,
isValidHttpUrl,
fixTileJSONCenter,
@ -330,263 +331,6 @@ const extractMarkersFromQuery = (query, options, transformer) => {
return markers;
};
/**
* Transforms coordinates to pixels.
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
* @param {number} zoom Map zoom level.
*/
const precisePx = (ll, zoom) => {
const px = mercator.px(ll, 20);
const scale = Math.pow(2, zoom - 20);
return [px[0] * scale, px[1] * scale];
};
/**
* Draws a marker in cavans context.
* @param {object} ctx Canvas context object.
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
* @param {number} z Map zoom level.
*/
const drawMarker = (ctx, marker, z) => {
return new Promise((resolve) => {
const img = new Image();
const pixelCoords = precisePx(marker.location, z);
const getMarkerCoordinates = (imageWidth, imageHeight, scale) => {
// Images are placed with their top-left corner at the provided location
// within the canvas but we expect icons to be centered and above it.
// Substract half of the images width from the x-coordinate to center
// the image in relation to the provided location
let xCoordinate = pixelCoords[0] - imageWidth / 2;
// Substract the images height from the y-coordinate to place it above
// the provided location
let yCoordinate = pixelCoords[1] - imageHeight;
// Since image placement is dependent on the size offsets have to be
// scaled as well. Additionally offsets are provided as either positive or
// negative values so we always add them
if (marker.offsetX) {
xCoordinate = xCoordinate + marker.offsetX * scale;
}
if (marker.offsetY) {
yCoordinate = yCoordinate + marker.offsetY * scale;
}
return {
x: xCoordinate,
y: yCoordinate,
};
};
const drawOnCanvas = () => {
// Check if the images should be resized before beeing drawn
const defaultScale = 1;
const scale = marker.scale ? marker.scale : defaultScale;
// Calculate scaled image sizes
const imageWidth = img.width * scale;
const imageHeight = img.height * scale;
// Pass the desired sizes to get correlating coordinates
const coords = getMarkerCoordinates(imageWidth, imageHeight, scale);
// Draw the image on canvas
if (scale != defaultScale) {
ctx.drawImage(img, coords.x, coords.y, imageWidth, imageHeight);
} else {
ctx.drawImage(img, coords.x, coords.y);
}
// Resolve the promise when image has been drawn
resolve();
};
img.onload = drawOnCanvas;
img.onerror = (err) => {
throw err;
};
img.src = marker.icon;
});
};
/**
* Draws a list of markers onto a canvas.
* Wraps drawing of markers into list of promises and awaits them.
* It's required because images are expected to load asynchronous in canvas js
* even when provided from a local disk.
* @param {object} ctx Canvas context object.
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
* @param {number} z Map zoom level.
*/
const drawMarkers = async (ctx, markers, z) => {
const markerPromises = [];
for (const marker of markers) {
// Begin drawing marker
markerPromises.push(drawMarker(ctx, marker, z));
}
// Await marker drawings before continuing
await Promise.all(markerPromises);
};
/**
* Draws a list of coordinates onto a canvas and styles the resulting path.
* @param {object} ctx Canvas context object.
* @param {List[Number]} path List of coordinates.
* @param {object} query Request query parameters.
* @param {string} pathQuery Path query parameter.
* @param {number} z Map zoom level.
*/
const drawPath = (ctx, path, query, pathQuery, z) => {
const splitPaths = pathQuery.split('|');
if (!path || path.length < 2) {
return null;
}
ctx.beginPath();
// Transform coordinates to pixel on canvas and draw lines between points
for (const pair of path) {
const px = precisePx(pair, z);
ctx.lineTo(px[0], px[1]);
}
// Check if first coordinate matches last coordinate
if (
path[0][0] === path[path.length - 1][0] &&
path[0][1] === path[path.length - 1][1]
) {
ctx.closePath();
}
// Optionally fill drawn shape with a rgba color from query
const pathHasFill = splitPaths.filter((x) => x.startsWith('fill')).length > 0;
if (query.fill !== undefined || pathHasFill) {
if ('fill' in query) {
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
}
if (pathHasFill) {
ctx.fillStyle = splitPaths
.find((x) => x.startsWith('fill:'))
.replace('fill:', '');
}
ctx.fill();
}
// Get line width from query and fall back to 1 if not provided
const pathHasWidth =
splitPaths.filter((x) => x.startsWith('width')).length > 0;
if (query.width !== undefined || pathHasWidth) {
let lineWidth = 1;
// Get line width from query
if ('width' in query) {
lineWidth = Number(query.width);
}
// Get line width from path in query
if (pathHasWidth) {
lineWidth = Number(
splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
);
}
// Get border width from query and fall back to 10% of line width
const borderWidth =
query.borderwidth !== undefined
? parseFloat(query.borderwidth)
: lineWidth * 0.1;
// Set rendering style for the start and end points of the path
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
ctx.lineCap = query.linecap || 'butt';
// Set rendering style for overlapping segments of the path with differing directions
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
ctx.lineJoin = query.linejoin || 'miter';
// In order to simulate a border we draw the path two times with the first
// beeing the wider border part.
if (query.border !== undefined && borderWidth > 0) {
// We need to double the desired border width and add it to the line width
// in order to get the desired border on each side of the line.
ctx.lineWidth = lineWidth + borderWidth * 2;
// Set border style as rgba
ctx.strokeStyle = query.border;
ctx.stroke();
}
ctx.lineWidth = lineWidth;
}
const pathHasStroke =
splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
if (query.stroke !== undefined || pathHasStroke) {
if ('stroke' in query) {
ctx.strokeStyle = query.stroke;
}
// Path Stroke gets higher priority
if (pathHasStroke) {
ctx.strokeStyle = splitPaths
.find((x) => x.startsWith('stroke:'))
.replace('stroke:', '');
}
} else {
ctx.strokeStyle = 'rgba(0,64,255,0.7)';
}
ctx.stroke();
};
const renderOverlay = async (
z,
x,
y,
bearing,
pitch,
w,
h,
scale,
paths,
markers,
query,
) => {
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
return null;
}
const center = precisePx([x, y], z);
const mapHeight = 512 * (1 << z);
const maxEdge = center[1] + h / 2;
const minEdge = center[1] - h / 2;
if (maxEdge > mapHeight) {
center[1] -= maxEdge - mapHeight;
} else if (minEdge < 0) {
center[1] -= minEdge;
}
const canvas = createCanvas(scale * w, scale * h);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
if (bearing) {
ctx.translate(w / 2, h / 2);
ctx.rotate((-bearing / 180) * Math.PI);
ctx.translate(-center[0], -center[1]);
} else {
// optimized path
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
}
// Draw provided paths if any
paths.forEach((path, i) => {
const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
drawPath(ctx, path, query, pathQuery, z);
});
// Await drawing of markers before rendering the canvas
await drawMarkers(ctx, markers, z);
return canvas.toBuffer();
};
const calcZForBBox = (bbox, w, h, query) => {
let z = 25;
@ -608,42 +352,8 @@ const calcZForBBox = (bbox, w, h, query) => {
return z;
};
const existingFonts = {};
let maxScaleFactor = 2;
export const serve_rendered = {
init: (options, repo) => {
const fontListingPromise = new Promise((resolve, reject) => {
fs.readdir(options.paths.fonts, (err, files) => {
if (err) {
reject(err);
return;
}
for (const file of files) {
fs.stat(path.join(options.paths.fonts, file), (err, stats) => {
if (err) {
reject(err);
return;
}
if (stats.isDirectory()) {
existingFonts[path.basename(file)] = true;
}
});
}
resolve();
});
});
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
let scalePattern = '';
for (let i = 2; i <= maxScaleFactor; i++) {
scalePattern += i.toFixed();
}
scalePattern = `@[${scalePattern}]x`;
const app = express().disable('x-powered-by');
const respondImage = (
const respondImage = (
options,
item,
z,
lon,
@ -655,10 +365,9 @@ export const serve_rendered = {
scale,
format,
res,
next,
opt_overlay,
opt_mode = 'tile',
) => {
overlay = null,
mode = 'tile',
) => {
if (
Math.abs(lon) > 180 ||
Math.abs(lat) > 85.06 ||
@ -686,7 +395,7 @@ export const serve_rendered = {
const tileMargin = Math.max(options.tileMargin || 0, 0);
let pool;
if (opt_mode === 'tile' && tileMargin === 0) {
if (mode === 'tile' && tileMargin === 0) {
pool = item.map.renderers[scale];
} else {
pool = item.map.renderers_static[scale];
@ -716,10 +425,7 @@ export const serve_rendered = {
pool.release(renderer);
if (err) {
console.error(err);
return res
.status(500)
.header('Content-Type', 'text/plain')
.send(err);
return res.status(500).header('Content-Type', 'text/plain').send(err);
}
// Fix semi-transparent outlines on raw, premultiplied input
@ -765,48 +471,22 @@ export const serve_rendered = {
image.resize(width * scale, height * scale);
}
var composite_array = [];
if (opt_overlay) {
composite_array.push({ input: opt_overlay });
const composite_array = [];
if (overlay) {
composite_array.push({ input: overlay });
}
if (item.watermark) {
const canvas = createCanvas(scale * width, scale * height);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.font = '10px sans-serif';
ctx.strokeWidth = '1px';
ctx.strokeStyle = 'rgba(255,255,255,.4)';
ctx.strokeText(item.watermark, 5, height - 5);
ctx.fillStyle = 'rgba(0,0,0,.4)';
ctx.fillText(item.watermark, 5, height - 5);
const canvas = renderWatermark(width, height, scale, item.watermark);
composite_array.push({ input: canvas.toBuffer() });
}
if (opt_mode === 'static' && item.staticAttributionText) {
const canvas = createCanvas(scale * width, scale * height);
const ctx = canvas.getContext('2d');
ctx.scale(scale, scale);
ctx.font = '10px sans-serif';
const text = item.staticAttributionText;
const textMetrics = ctx.measureText(text);
const textWidth = textMetrics.width;
const textHeight = 14;
const padding = 6;
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.fillRect(
width - textWidth - padding,
height - textHeight - padding,
textWidth + padding,
textHeight + padding,
);
ctx.fillStyle = 'rgba(0,0,0,.8)';
ctx.fillText(
if (mode === 'static' && item.staticAttributionText) {
const canvas = renderAttribution(
width,
height,
scale,
item.staticAttributionText,
width - textWidth - padding / 2,
height - textHeight + 8,
);
composite_array.push({ input: canvas.toBuffer() });
@ -838,7 +518,21 @@ export const serve_rendered = {
});
});
});
};
};
const existingFonts = {};
let maxScaleFactor = 2;
export const serve_rendered = {
init: (options, repo) => {
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
let scalePattern = '';
for (let i = 2; i <= maxScaleFactor; i++) {
scalePattern += i.toFixed();
}
scalePattern = `@[${scalePattern}]x`;
const app = express().disable('x-powered-by');
app.get(
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
@ -880,6 +574,7 @@ export const serve_rendered = {
z,
);
return respondImage(
options,
item,
z,
tileCenter[0],
@ -891,7 +586,6 @@ export const serve_rendered = {
scale,
format,
res,
next,
);
},
);
@ -962,6 +656,7 @@ export const serve_rendered = {
);
return respondImage(
options,
item,
z,
x,
@ -973,7 +668,6 @@ export const serve_rendered = {
scale,
format,
res,
next,
overlay,
'static',
);
@ -1043,6 +737,7 @@ export const serve_rendered = {
req.query,
);
return respondImage(
options,
item,
z,
x,
@ -1054,7 +749,6 @@ export const serve_rendered = {
scale,
format,
res,
next,
overlay,
'static',
);
@ -1177,6 +871,7 @@ export const serve_rendered = {
);
return respondImage(
options,
item,
z,
x,
@ -1188,7 +883,6 @@ export const serve_rendered = {
scale,
format,
res,
next,
overlay,
'static',
);
@ -1215,7 +909,10 @@ export const serve_rendered = {
return res.send(info);
});
return Promise.all([fontListingPromise]).then(() => app);
return listFonts(options.paths.fonts).then((fonts) => {
Object.assign(existingFonts, fonts);
return app;
});
},
add: async (options, repo, params, id, publicUrl, dataResolver) => {
const map = {
@ -1618,7 +1315,7 @@ export const serve_rendered = {
}
});
return Promise.all([renderersReadyPromise]);
return renderersReadyPromise;
},
remove: (repo, id) => {
const item = repo[id];

View file

@ -98,9 +98,7 @@ export const serve_style = {
const validationErrors = validate(styleFileData);
if (validationErrors.length > 0) {
console.log(
`The file "${params.style}" is not valid a valid style file:`,
);
console.log(`The file "${params.style}" is not a valid style file:`);
for (const err of validationErrors) {
console.log(`${err.line}: ${err.message}`);
}

View file

@ -163,6 +163,35 @@ export const getFontsPbf = (
return Promise.all(queue).then((values) => glyphCompose.combine(values));
};
export const listFonts = async (fontPath) => {
const existingFonts = {};
const fontListingPromise = new Promise((resolve, reject) => {
fs.readdir(fontPath, (err, files) => {
if (err) {
reject(err);
return;
}
for (const file of files) {
fs.stat(path.join(fontPath, file), (err, stats) => {
if (err) {
reject(err);
return;
}
if (
stats.isDirectory() &&
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))
) {
existingFonts[path.basename(file)] = true;
}
});
}
resolve();
});
});
await fontListingPromise;
return existingFonts;
};
export const isValidHttpUrl = (string) => {
let url;

View file

@ -24,6 +24,5 @@ after(function () {
console.log('global teardown');
global.server.close(function () {
console.log('Done');
process.exit();
});
});