Compare commits

...

96 Commits

Author SHA1 Message Date
Petr Sloup
c89a5ae029 v3.1.1 2020-12-14 15:27:33 +01:00
Petr Sloup
a3d7f8bcbd Fix the docker image port to fix backward compatibility (#502) 2020-12-14 15:27:05 +01:00
Petr Sloup
7c1420982c v3.1.0 2020-12-11 16:19:48 +01:00
Petr Sloup
51baa9b67b Merge pull request #457 from joakimfors/master
Streamline Dockerfile for caching and size
2020-12-11 16:13:55 +01:00
Joakim Fors
6b3f557b1f Gracefully handle signals from Docker or terminal
Start Xvfb wrapper in background and wait for the process to complete.
Because wait exits immediately when a signal for which a trap has been
set, and the signal handler is executed directly after that, we need to
wait again for the background processes to actually finish before exiting.

The signal handler catches INT and TERM and forwards them to the node
process.

The return value from the first wait is stored and sent as exit value.
2020-12-04 20:57:06 +01:00
Joakim Fors
8d2ddd8f95 Use automatic server number 2020-12-04 16:19:30 +01:00
Joakim Fors
9f59f67087 Clean up dependencies 2020-12-04 16:19:30 +01:00
Joakim Fors
1d21648969 Run as node user instead of root 2020-12-04 16:19:30 +01:00
Joakim Fors
16de5be673 Use simplified entrypoint script 2020-12-04 16:19:30 +01:00
Joakim Fors
6b96f224ae Streamline Dockerfile for caching and size
Move package installation to top of file to enable layer caching
in Docker. Use multi stage builds to reduce final image size.
Ignore everything but essential files when creating Docker build
context.
2020-12-04 16:19:30 +01:00
Petr Sloup
6ff4cae9b9 Merge pull request #484 from candux/addKeyToSprites
add api key to sprite ressources in style json
2020-11-04 20:38:13 +01:00
Petr Sloup
644db6cd8a Merge pull request #487 from bheupers/patch-1
Render up to scale level 22
2020-11-04 18:58:32 +01:00
Petr Sloup
a98404e921 Merge pull request #488 from vcabbage/sharp-update
update sharp to avoid segfault
2020-11-04 18:56:33 +01:00
Petr Sloup
559cfc462c Merge pull request #486 from 0xflotus/patch-1
fixed small errors
2020-11-04 18:56:02 +01:00
Kale Blankenship
559c3a913e update sharp to avoid segfault
Before
e82a585cec
the server may crash with a segfault when under high load.
2020-11-04 09:36:10 -08:00
bheupers
4036d528ec Render up to scale level 22
For detailed city rendering we need to render rasterfiles up to and including level 21. Therefore I propose to change the maximum raster level to 22
2020-11-04 14:42:46 +01:00
0xflotus
6ae4116ccb fixed small errors 2020-10-23 15:05:36 +02:00
0xflotus
39bb7ffbf1 enabled syntax highlighting 2020-10-23 15:04:51 +02:00
David Weber | geOps
e249a3f67d add api key to sprite ressources in style json
this make some setups easier becouse sprites can be handeld
like any other ressources
2020-10-22 19:09:42 +02:00
Petr Sloup
04b49d8a98 Merge pull request #482 from candux/david/fixStyleServer
fix crash in serveAllFonts
2020-10-16 09:24:44 +02:00
David Weber | geOps
309d481117 fix crash in serveAllFonts
variable file does not exist. Backtrace

ReferenceError: file is not defined
  at serve_style.add (/usr/src/app/src/server.js:139:44)
  at Proxy.add (/usr/src/app/src/serve_style.js:115:28)
  at addStyle (/usr/src/app/src/server.js:121:31)
  at fs.readdir (/usr/src/app/src/server.js:215:11)
  at args (fs.js:140:20)
  at internal/util.js:370:14
  at getDirents (internal/fs/utils.js:149:7)
  at FSReqWrap.req.oncomplete (fs.js:775:7)
2020-10-16 09:15:10 +02:00
Petr Sloup
7ce4805cdd Merge pull request #479 from poljvd/patch-1
Fix missing ScaleDenominator in wmts.tmpl
2020-09-28 10:36:32 +02:00
poljvd
5377414710 Fix missing ScaleDenominator in wmts.tmpl
The missing ScaleDenominator causes GeoServer to throw an error when importing the layer.
2020-09-28 10:33:21 +02:00
Petr Sloup
042b8b986a Merge pull request #463 from korpd/fix-xss-parameter-style
Fix reflected XSS in 'style' parameter
2020-07-02 14:47:24 +02:00
Daniel Korp
038bfe29d6 Fix reflected XSS in 'style' parameter 2020-07-02 10:28:25 +02:00
Petr Sloup
f8563e1f2b Merge pull request #462 from korpd/fix-xss-461
Fix reflected XSS in 'key' parameter. Fixes #461
2020-07-02 09:50:48 +02:00
Daniel Korp
10431d70d0 Fix reflected XSS in 'key' parameter. Fixes #461 2020-07-02 09:30:33 +02:00
Petr Sloup
a5a8ae1e95 Merge pull request #413 from zstadler/xvfbMaxStartWaitTime
Increase initial timeout - xvfbMaxStartWaitTime
2020-05-20 16:12:24 +02:00
Petr Sloup
3c411cd1ac Merge pull request #441 from hfs/fix-preview-thumbnail
Fix preview thumbnail for raster data sources
2020-04-23 08:41:17 +02:00
Hermann Schwarting
79bd70942b Fix preview thumbnail for raster data sources
The data sources structure was changed in the 'dynamic_styles' branch
introducing a tileJSON object. Adapt the code filling the preview
template to the change to get the correct file format instead of
"undefined".

See also f2dc13e.

Fix #440 Preview thumbnail missing for raster data source -- file suffix
"undefined"
2020-04-22 21:08:55 +02:00
Petr Sloup
5585f49396 Update serve_rendered.js
Fix accidentally removed #414
2020-04-07 10:29:19 +02:00
Petr Sloup
d5a079d8f4 v3.0.0 2020-03-11 14:01:17 +01:00
Petr Sloup
29d3e72dd3 Replace last references to the old repository/dockerhub 2020-03-11 13:57:45 +01:00
Petr Sloup
c3bff5ac5f Update dependencies 2020-03-11 13:55:05 +01:00
Petr Sloup
e16de39b93 Minor documentation update 2020-03-11 13:43:23 +01:00
Petr Sloup
13415fd29f Minor tileserver-gl-light fix 2020-03-11 09:24:10 +01:00
Petr Sloup
9a9b920455 Merge pull request #419 from vipyoung/patch-1
Updated usage.rst to reflect public_url parameter
2020-02-25 14:30:09 +01:00
Sofiane Abbar
b55b8adb63 Update usage.rst
Added missing documentation for public_url.
2020-02-25 16:21:17 +03:00
Petr Sloup
9e12ee6f8c Merge pull request #417 from smellman/dev-light2
add option to pass npm publish
2020-02-21 20:47:01 +01:00
Taro Matsuzawa
0e85e0058f - added Building docker image document in README_light.md
- change URL schema from http to https
2020-02-18 08:03:52 +09:00
Taro Matsuzawa
3a94a8f9d2 add option to pass npm publish 2020-02-17 12:36:18 +09:00
Petr Sloup
28dbc78264 Merge pull request #416 from tbicr/update-glyph-pbf-composite-version
update glyph-pbf-composite
2020-02-14 17:59:21 +01:00
Paveł Tyślacki
8f77be3037 update glyph-pbf-composite
glyph-pbf-composite v0.0.2 were released without license
as license were added in v0.0.3 it nice to be updated to licensed software version
2020-02-14 18:59:49 +03:00
Petr Sloup
f2dc13e298 Minor tilejson-reading fix when decorating templates 2020-02-10 14:14:07 +01:00
Petr Sloup
cf0eedb379 Merge branch 'dynamic_styles' 2020-02-10 14:00:58 +01:00
Petr Sloup
95bb59dcfe Merge pull request #414 from xabbu42/issue-171
Close connection on errors
2020-01-28 15:46:52 +01:00
Petr Sloup
224a1300df Merge pull request #415 from xabbu42/issue-350
FIx issue #350 Semi-transparent outlines on raw, premultiplied input
2020-01-28 15:42:36 +01:00
Nathan Gass
9fd381640e Close connection on erros 2020-01-28 14:30:36 +01:00
Nathan Gass
b7c384f1ee FIx issue #350 Semi-transparent outlines on raw, premultiplied input 2020-01-28 14:25:00 +01:00
zstadler
8126b31081 Resolve https://github.com/maptiler/tileserver-gl/issues/386 2020-01-28 11:29:42 +02:00
Petr Sloup
caa641c550 Merge pull request #410 from jdesboeufs/relax-light-version-compatibility
Relax Node.js compatibility for -light version
2020-01-17 16:00:00 +01:00
Jérôme Desboeufs
d02ce0663b Relax Node.js compatibility for -light version 2020-01-17 11:22:17 +01:00
Petr Sloup
ea89d11021 Use chokidar instead of node-watch to poll the files (required for docker) 2020-01-15 09:26:59 +01:00
Petr Sloup
aa933e5154 Fix tests 2020-01-08 15:18:51 +01:00
Petr Sloup
298d09845d Add style validation and skip invalid styles 2020-01-07 17:00:08 +01:00
Petr Sloup
d7a34f3a74 Add "serveAllStyles" option + watch the directory 2020-01-07 15:59:38 +01:00
Petr Sloup
cb700181d3 Refactor style/rendered/data serving to allow for dynamic adding/removing of items 2020-01-07 14:32:30 +01:00
Petr Sloup
226b979592 Fix deprecation warnings 2019-12-30 17:18:05 +01:00
Petr Sloup
13ad268b43 Update mapbox-gl-js to v1.6.1 and mapbox-gl-rtl-text to v0.2.3 2019-12-30 17:11:45 +01:00
Petr Sloup
e2387d164b Simplify the startup procedure a little (#271) 2019-12-30 17:08:15 +01:00
Petr Sloup
515c295898 Remove obsolete old code 2019-12-30 17:00:23 +01:00
Petr Sloup
cdc7803ad8 Update travis to node 10 2019-12-30 16:54:36 +01:00
Petr Sloup
b839979351 Remove uselessly huge background images 2019-12-30 16:54:36 +01:00
Petr Sloup
0a7c403f0b Update repo links, footnote, etc. (klokantech->maptiler) 2019-12-30 16:54:35 +01:00
Petr Sloup
b3d810817d Merge pull request #404 from nyurik/refactor
Update code to ES6
2019-12-30 16:54:19 +01:00
Petr Sloup
96161fc656 Merge branch 'master' into refactor 2019-12-30 11:49:37 +01:00
Petr Sloup
58c769b448 README update 2019-12-30 09:25:12 +01:00
Petr Sloup
bdd0a5c868 Merge pull request #394 from korpd/master
Don't multiply tileMargin with scale since the renderer is already taking care of scaling
2019-12-30 09:21:04 +01:00
Petr Sloup
5048388d1f Try to reduce number of broken labels on tile edges (#344, #347) 2019-12-30 09:11:53 +01:00
Petr Sloup
4c7a227e11 Merge pull request #403 from nyurik/node10
Upgrade to node10, latest dependencies
2019-12-30 09:08:35 +01:00
Yuri Astrakhan
7f8be27844 Update code to ES6
* var  ->  let / const
* [].forEach  ->  for (... of ...)
* '...' + var  ->  template strings  `...${var}`
* function  ->  arrow functions `=>`
* use === and !==  instead of == and !=
2019-12-21 14:09:20 -05:00
Yuri Astrakhan
736e8d393a Upgrade to node10, latest dependencies
use node10 (latest node that has binary built @mapbox/mapbox-gl-native v5.0.2)
use latest canvas and sharp API
2019-12-21 12:09:07 -05:00
Daniel Korp
1c24d12b0d Minor fix: Don't multiply tileMargin with scale since the renderer is already taking care of scaling 2019-11-13 15:10:13 +01:00
Petr Sloup
f77ccd06af Merge pull request #379 from stefslon/tile_margin
Add tileMargin option
2019-10-02 12:26:49 +02:00
Petr Sloup
4996848bdc Update package version to 2.6.0 2019-08-26 12:16:06 +02:00
Petr Sloup
aa7ae575d0 Flatten 3D buildings when rendering to remove artifacts 2019-08-26 12:10:49 +02:00
Petr Sloup
9603703908 Minor css fix 2019-08-26 12:10:32 +02:00
stefslon
1445c545b0 Added tileMargin option to documentation 2019-08-13 22:17:34 -04:00
stefslon
17a73b1d4a Add tileMargin option 2019-08-08 21:57:51 -04:00
Petr Sloup
e44104254e Update handlebars 2019-07-12 11:25:08 +02:00
Petr Sloup
58b536036f Merge pull request #370 from golubev/patch-1
update 'installation.rst': fix a mistake
2019-06-25 07:58:53 +02:00
Sergii Golubev
a68e095400 update 'installation.rst': fix a mistake 2019-06-24 18:56:04 +03:00
Petr Sloup
53b28b35f1 Merge pull request #369 from bradh/patch-1
Trivial typo fix in docs
2019-06-24 07:39:44 +02:00
Brad Hards
58f92ae947 Trivial typo fix in docs 2019-06-22 11:02:36 +10:00
Petr Sloup
e506014763 Minor /styles.json fix (close #361) 2019-04-29 06:48:23 +02:00
Petr Sloup
7c5e7e94e9 Merge pull request #358 from disarticulate/master
Use verbose flag to debug corrupted mbtiles
2019-04-25 10:34:28 +02:00
Eric Xanderson
e6747ebb78 Merge pull request #2 from disarticulate/patch-2
verbose output empty tile warning
2019-04-23 20:53:17 -05:00
Eric Xanderson
2a87ad19c9 Merge pull request #1 from disarticulate/patch-1
add verbose to options
2019-04-23 20:52:46 -05:00
Eric Xanderson
ba62f0bf30 verbose output empty tile warning
Empty tile warning details; definitely what you would expect if there's not a catastrophic error, but you're getting blank tiles.

such as: ```
MBTiles error, serving empty { Error: SQLITE_CORRUPT: database disk image is malformed
lily_tile_server |     at Error (native) errno: 11, code: 'SQLITE_CORRUPT' }

```
https://github.com/klokantech/tileserver-gl/issues/323
2019-04-23 20:51:39 -05:00
Eric Xanderson
08369a666d add verbose to options
pass verbose to server for better handling of warnings
2019-04-23 20:43:56 -05:00
Petr Sloup
f771d41788 Merge pull request #355 from cyclemap/master
let the user define a log file or a log format
2019-04-15 08:00:28 +02:00
adrian
9dda95ee8e let the user define a log file or a log format
log file defaults to standard out.

log format defaults to current behavior ('tiny' for production, 'dev' for dev, and not enabled in test)
2019-04-13 11:55:37 -04:00
Petr Sloup
63483a3155 Merge pull request #339 from ibesora/master
Removes body data from http 204 response
2019-01-07 14:16:30 +01:00
Isaac Besora Vilardaga
bca5191ad9 Removes body data from http 204 response 2019-01-07 13:48:22 +01:00
Petr Sloup
245d9765a9 Merge pull request #337 from mcolmant/master
Correctly retrieve the public_url parameter on start
2019-01-03 10:57:05 +01:00
Maxime Colmant
e8134dfeb0 fix: correctly retrieve the public_url parameter on start 2018-12-26 16:26:49 +01:00
35 changed files with 1708 additions and 2970 deletions

View File

@@ -1,7 +1,6 @@
.git
docs/_build
node_modules
test_data
light
config.json
*.mbtiles
*
!src
!public
!package.json
!package-lock.json
!docker-entrypoint.sh

View File

@@ -1,6 +1,6 @@
language: node_js
node_js:
- "6"
- "10"
env:
- CXX=g++-4.8
addons:
@@ -15,7 +15,7 @@ before_install:
- sudo apt-get install -qq xvfb libgles2-mesa-dev libgbm-dev libxxf86vm-dev
install:
- npm install
- wget -O test_data.zip https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/test_data.zip
- wget -O test_data.zip https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
- unzip -q test_data.zip -d test_data
script:
- xvfb-run --server-args="-screen 0 1024x768x24" npm test

View File

@@ -1,29 +1,55 @@
FROM node:6.15.1-stretch
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
FROM node:10-buster AS builder
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq update \
&& apt-get -y --no-install-recommends install \
apt-transport-https \
curl \
unzip \
build-essential \
python \
libcairo2-dev \
libgles2-mesa-dev \
libgbm-dev \
libllvm7 \
libprotobuf-dev \
&& apt-get -y --purge autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY . /usr/src/app
ENV NODE_ENV="production"
RUN cd /usr/src/app && npm install --production
FROM node:10-buster-slim AS final
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq update \
&& apt-get -y --no-install-recommends install \
libgles2-mesa \
libegl1 \
xvfb \
xauth \
&& apt-get -y --purge autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/src/app /app
ENV NODE_ENV="production"
ENV CHOKIDAR_USEPOLLING=1
ENV CHOKIDAR_INTERVAL=500
VOLUME /data
WORKDIR /data
EXPOSE 80
ENTRYPOINT ["/bin/bash", "/usr/src/app/run.sh"]
RUN apt-get -qq update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
apt-transport-https \
curl \
unzip \
build-essential \
python \
libcairo2-dev \
libgles2-mesa-dev \
libgbm-dev \
libllvm3.9 \
libprotobuf-dev \
libxxf86vm-dev \
xvfb \
x11-utils \
&& apt-get clean
USER node:node
RUN mkdir -p /usr/src/app
COPY / /usr/src/app
RUN cd /usr/src/app && npm install --production
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["-p", "80"]

View File

@@ -1,7 +1,8 @@
FROM node:6.15.1-stretch
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
FROM node:10-stretch
ENV NODE_ENV="production"
ENV CHOKIDAR_USEPOLLING=1
ENV CHOKIDAR_INTERVAL=500
EXPOSE 80
VOLUME /data
WORKDIR /data

View File

@@ -2,8 +2,7 @@
# Simply run "docker build -f Dockerfile_test ."
# WARNING: sometimes it fails with a core dumped exception
FROM node:6-stretch
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
FROM node:10-stretch
RUN apt-get -qq update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
@@ -24,7 +23,7 @@ RUN apt-get -qq update \
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN wget -O test_data.zip https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/test_data.zip
RUN wget -O test_data.zip https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
RUN unzip -q test_data.zip -d test_data
ENV NODE_ENV="test"

View File

@@ -2,14 +2,14 @@
# TileServer GL
[![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](https://travis-ci.org/klokantech/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-gl/)
[![Build Status](https://travis-ci.org/maptiler/tileserver-gl.svg?branch=master)](https://travis-ci.org/maptiler/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/maptiler/tileserver-gl/)
Vector and raster maps with GL styles. Server side rendering by Mapbox GL Native. Map tile server for Mapbox GL JS, Android, iOS, Leaflet, OpenLayers, GIS via WMTS, etc.
## Get Started
Make sure you have Node.js version **6** installed (running `node -v` it should output something like `v6.11.3`).
Make sure you have Node.js version **10** installed (running `node -v` it should output something like `v10.17.0`).
Install `tileserver-gl` with server-side raster rendering of vector tiles with npm
@@ -33,10 +33,10 @@ Alternatively, you can use the `tileserver-gl-light` package instead, which is p
## Using Docker
An alternative to npm to start the packed software easier is to install [Docker](http://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
An alternative to npm to start the packed software easier is to install [Docker](https://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
```bash
docker run --rm -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl
docker run --rm -it -v $(pwd):/data -p 8080:80 maptiler/tileserver-gl
```
This will download and start a ready to use container on your computer and the maps are going to be available in webbrowser on localhost:8080.
@@ -45,4 +45,4 @@ On laptop you can use [Docker Kitematic](https://kitematic.com/) and search "til
## Documentation
You can read full documentation of this project at http://tileserver.readthedocs.io/.
You can read full documentation of this project at https://tileserver.readthedocs.io/.

View File

@@ -1,6 +1,6 @@
# TileServer GL light
[![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](https://travis-ci.org/klokantech/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-gl/)
[![Build Status](https://travis-ci.org/maptiler/tileserver-gl.svg?branch=master)](https://travis-ci.org/maptiler/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/maptiler/tileserver-gl/)
Vector maps with GL styles. Map tile server for Mapbox Android, iOS, GL JS, Leaflet, OpenLayers, etc. without server side rendering.
@@ -11,7 +11,25 @@ Then you can simply run `tileserver-gl-light zurich_switzerland.mbtiles` to star
See also `tileserver-gl` which contains server side rendering.
Prepared vector tiles can be downloaded from [OSM2VectorTiles](http://osm2vectortiles.org/).
Prepared vector tiles can be downloaded from [OpenMapTiles.com](https://openmaptiles.com/downloads/planet/).
## Building docker image
You can build TileServer GL light image from source.
```
git clone https://github.com/maptiler/tileserver-gl.git
cd tileserver-gl
node publish.js --no-publish
cd light
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
```
## Documentation
You can read full documentation of this project at http://tileserver.readthedocs.io/.
You can read full documentation of this project at https://tileserver.readthedocs.io/.

23
docker-entrypoint.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
set -e
handle() {
SIGNAL=$(( $? - 128 ))
echo "Caught signal ${SIGNAL}, stopping gracefully"
kill -s ${SIGNAL} $(pidof node) 2>/dev/null
}
trap handle INT TERM
if ! which -- "${1}"; then
# first arg is not an executable
xvfb-run -a --server-args="-screen 0 1024x768x24" -- node /app/ "$@" &
# Wait exits immediately on signals which have traps set. Store return value and wait
# again for all jobs to actually complete before continuing.
wait $! || RETVAL=$?
wait
exit ${RETVAL}
fi
exec "$@"

View File

@@ -4,7 +4,9 @@ Configuration file
The configuration file defines the behavior of the application. It's a regular JSON file.
Example::
Example:
.. code-block:: json
{
"options": {
@@ -27,7 +29,9 @@ Example::
"maxSize": 2048,
"pbfAlias": "pbf",
"serveAllFonts": false,
"serveStaticMaps": true
"serveAllStyles": false,
"serveStaticMaps": true,
"tileMargin": 0
},
"styles": {
"basic": {
@@ -95,6 +99,13 @@ Maximum image side length to be allowed to be rendered (including scale factor).
Be careful when changing this value since there are hardware limits that need to be considered.
Default is ``2048``.
``tileMargin``
--------------
Additional image side length added during tile rendering that is cropped from the delivered tile. This is useful for resolving the issue with cropped labels,
but it does come with a performance degradation, because additional, adjacent vector tiles need to be loaded to generate a single tile.
Default is ``0`` to disable this processing.
``minRendererPoolSizes``
------------------------
@@ -116,6 +127,13 @@ 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]``.
``serveAllStyles``
------------------------
If this option is enabled, all the styles from the ``paths.styles`` will be served. (No recursion, only ``.json`` files are used.)
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.
``watermark``
-----------
@@ -130,7 +148,7 @@ Each item in this object defines one style (map). It can have the following opti
* ``style`` -- name of the style json file [required]
* ``serve_rendered`` -- whether to render the raster tiles for this style or not
* ``serve_data`` -- whether to allow acces to the original tiles, sprites and required glyphs
* ``serve_data`` -- whether to allow access to the original tiles, sprites and required glyphs
* ``tilejson`` -- properties to add to the TileJSON created for the raster data
* ``format`` and ``bounds`` can be especially useful

View File

@@ -43,7 +43,7 @@ Static images
* ``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
* ``padding`` - "percetange" padding for fitted endpoints (area-based and path autofit)
* ``padding`` - "percentage" padding for fitted endpoints (area-based and path autofit)
* value of ``0.1`` means "add 10% size to each side to make sure the area of interest is nicely visible"

View File

@@ -7,12 +7,12 @@ Docker
When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
Just run ``docker run --rm -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl``.
Just run ``docker run --rm -it -v $(pwd):/data -p 8080:80 maptiler/tileserver-gl``.
Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following:
* ``docker run ... klokantech/tileserver-gl my-tiles.mbtiles`` -- explicitly specify which mbtiles to use (if you have more in the folder)
* ``docker run ... klokantech/tileserver-gl --verbose`` -- to see the default config created automatically
* ``docker run ... maptiler/tileserver-gl --mbtiles my-tiles.mbtiles`` -- explicitly specify which mbtiles to use (if you have more in the folder)
* ``docker run ... maptiler/tileserver-gl --verbose`` -- to see the default config created automatically
npm
===
@@ -41,7 +41,7 @@ Alternatively, you can use ``tileserver-gl-light`` package instead, which is pur
From source
===========
Make sure you have Node v6 (nvm install 6) and run::
Make sure you have Node v10 (nvm install 10) and run::
npm install
node .

View File

@@ -17,13 +17,20 @@ Getting started
-b, --bind <address> Bind address
-p, --port <port> Port [8080]
-C|--no-cors Disable Cross-origin resource sharing headers
-u|--public_url <url> Enable exposing the server on subpaths, not necessarily the root of the domain
-V, --verbose More verbose output
-s, --silent Less verbose output
-v, --version Version info
Default styles and configuration
Default preview style and configuration
======
- If no configuration file is specified, the default styles (compatible with openmaptiles) are used.
- If no mbtiles file is specified (and is not found in the current working directory), an extract is downloaded directly from https://openmaptiles.org/
- If no configuration file is specified, a default preview style (compatible with openmaptiles) is used.
- If no mbtiles file is specified (and is not found in the current working directory), a sample file is downloaded (showing the Zurich area)
Reloading configuration
======
It is possible to reload the configuration file without restarting the whole process by sending a SIGHUP signal to the node process.
However, this does not currently work when running the tileserver-gl docker container (the signal is not passed to the subprocess, see https://github.com/maptiler/tileserver-gl/issues/420#issuecomment-597507663).

View File

@@ -1,48 +1,49 @@
{
"name": "tileserver-gl",
"version": "2.5.0",
"version": "3.1.1",
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
"main": "src/main.js",
"bin": "src/main.js",
"authors": [
"Petr Sloup <petr.sloup@klokantech.com>"
],
"repository": {
"type": "git",
"url": "https://github.com/klokantech/tileserver-gl.git"
"url": "https://github.com/maptiler/tileserver-gl.git"
},
"license": "BSD-2-Clause",
"engines": {
"node": ">=6 <7"
"node": ">=10 <11"
},
"scripts": {
"test": "mocha test/**.js --timeout 10000"
"test": "mocha test/**.js --timeout 10000",
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:80 $(docker build -q .)"
},
"dependencies": {
"@mapbox/mapbox-gl-native": "4.0.0",
"@mapbox/mbtiles": "0.10.0",
"@mapbox/glyph-pbf-composite": "0.0.3",
"@mapbox/mapbox-gl-native": "5.0.2",
"@mapbox/mapbox-gl-style-spec": "13.12.0",
"@mapbox/mbtiles": "0.11.0",
"@mapbox/sphericalmercator": "1.1.0",
"@mapbox/vector-tile": "1.3.1",
"advanced-pool": "0.3.3",
"canvas": "1.6.13",
"canvas": "2.6.1",
"chokidar": "3.3.1",
"clone": "2.1.2",
"color": "3.1.0",
"commander": "2.19.0",
"color": "3.1.2",
"commander": "4.1.1",
"cors": "2.8.5",
"express": "4.16.4",
"glyph-pbf-composite": "0.0.2",
"handlebars": "4.0.12",
"http-shutdown": "^1.2.0",
"esm": "3.2.25",
"express": "4.17.1",
"handlebars": "4.7.3",
"http-shutdown": "1.2.2",
"morgan": "1.9.1",
"pbf": "3.1.0",
"proj4": "2.5.0",
"request": "2.88.0",
"sharp": "0.21.1",
"tileserver-gl-styles": "1.2.0"
"pbf": "3.2.1",
"proj4": "2.6.0",
"request": "2.88.2",
"sharp": "0.26.2",
"tileserver-gl-styles": "2.0.0"
},
"devDependencies": {
"mocha": "^5.2.0",
"should": "^13.2.0",
"supertest": "^3.1.0"
"mocha": "^7.1.0",
"should": "^13.2.3",
"supertest": "^4.0.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="2034.203px" height="552.055px" viewBox="0 0 2034.203 552.055" enable-background="new 0 0 2034.203 552.055"
xml:space="preserve">
<g>
<path fill="#3A1888" d="M3.604-242.717"/>
</g>
<g>
<g>
<path fill="#3A1888" d="M152.645,436.647c25.674,25.668,94.015,95.335,93.983,95.406c-0.249,0.454,67.892-67.963,95.032-95.087
l-94.67-94.665L152.645,436.647z"/>
<path fill="#03A1C4" d="M246.99,342.301l94.67,94.665c0.141-0.157,0.314-0.336,0.466-0.477l94.578-94.583l-94.66-94.662
L246.99,342.301z"/>
<path fill="#05D0DF" d="M436.704,341.907l0.243-0.244c52.317-52.312,52.36-137.096,0.157-189.473l-95.06,95.055L436.704,341.907z"
/>
<path fill="#761FE8" d="M151.931,247.245l-94.329,94.326c0.027,0.032,0.043,0.064,0.076,0.092l94.811,94.827
c0.054,0.049,0.108,0.098,0.157,0.157l94.345-94.346L151.931,247.245z"/>
<path fill="#FFAA01" d="M246.99,152.184l95.054,95.061l95.06-95.055c-0.076-0.054-0.103-0.108-0.157-0.162l-94.821-94.816
c-0.022-0.027-0.054-0.054-0.082-0.081L246.99,152.184z"/>
<path fill="#F1175D" d="M57.201,152.514c-51.852,52.377-51.722,136.848,0.4,189.057l94.329-94.326L57.201,152.514z"/>
<path fill="#FB3A1B" d="M246.99,152.184L152.255,57.45l-94.578,94.578c-0.163,0.162-0.309,0.336-0.476,0.486l94.729,94.73
L246.99,152.184z"/>
<path fill="#FBC935" d="M342.044,57.13C289.663,4.846,204.832,4.874,152.488,57.211l-0.233,0.238l94.735,94.734L342.044,57.13z"/>
</g>
<g>
<path fill="#333359" d="M734.146,365.616v-96.875c0-23.851-12.479-45.492-37.077-45.492c-24.224,0-38.517,21.642-38.517,45.492
v96.875h-44.761V184.347h41.46l3.301,22.021c9.542-18.353,30.458-24.949,47.685-24.949c21.669,0,43.306,8.811,53.588,33.754
c16.144-25.685,37.066-33.022,60.537-33.022c51.38,0,76.692,31.551,76.692,85.859v97.605h-44.767V268.01
c0-23.84-9.904-44.037-34.106-44.037c-24.234,0-39.279,20.917-39.279,44.768v96.875H734.146z"/>
<path fill="#333359" d="M1086.026,184.726h42.938v180.89h-42.208l-2.208-26.41c-10.266,21.269-38.516,31.535-58.702,31.914
c-53.556,0.368-93.198-32.655-93.198-96.137c0-62.375,41.477-95.029,94.313-94.662c24.212,0,47.321,11.371,57.587,29.354
L1086.026,184.726z M977.416,274.983c0,34.479,23.85,55.039,53.573,55.039c70.446,0,70.446-109.713,0-109.713
C1001.266,220.309,977.416,240.496,977.416,274.983z"/>
<path fill="#333359" d="M1166.756,441.214V184.726h41.839l2.923,24.949c13.951-20.187,38.175-28.991,58.719-28.991
c55.753,0,92.835,41.471,92.835,94.667c0,52.847-33.401,94.675-91.374,94.675c-19.065,0-47.332-5.888-60.18-25.695v96.884
H1166.756z M1318.305,275.351c0-28.253-19.082-51.378-51.37-51.378c-32.298,0-51.38,23.125-51.38,51.378
c0,28.244,20.922,51.38,51.38,51.38C1297.404,326.731,1318.305,303.595,1318.305,275.351z"/>
<path fill="#333359" d="M1443.064,129.682v54.665h61.642v15.046h-61.642v110.453c0,24.575,5.146,41.823,33.392,41.823
c8.805,0,18.709-2.938,27.882-7.339l6.24,14.666c-11.382,5.521-22.763,9.185-34.122,9.185c-38.527,0-51.002-22.752-51.002-58.335
V199.393h-38.538v-15.046h38.538v-52.831L1443.064,129.682z"/>
<path fill="#333359" d="M1570.027,125.272c0,19.082-28.986,19.082-28.986,0C1541.041,106.2,1570.027,106.2,1570.027,125.272z
M1546.188,183.612v182.004h17.962V183.612H1546.188z"/>
<path fill="#333359" d="M1633.503,108.776v256.84h-17.983v-256.84H1633.503z"/>
<path fill="#333359" d="M1918.606,184.347l0.73,32.304c11.365-24.603,37.066-34.133,60.181-34.133
c13.589-0.367,26.772,3.307,38.896,10.646l-8.08,14.671c-9.525-5.871-20.187-8.441-30.815-8.441
c-33.771,0.379-59.817,27.524-59.817,60.553v105.67h-17.979V184.347H1918.606z"/>
</g>
<g>
<path fill="none" d="M1694.655,305.711c0.006,0.016,0.014,0.031,0.02,0.047l146.748-38.832c-0.007-0.055-0.012-0.11-0.018-0.166
L1694.655,305.711z"/>
<path fill="none" d="M1765.447,197.873c-42.255,0-76.514,34.997-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
C1826.633,219.268,1798.486,197.873,1765.447,197.873z"/>
<g>
<path fill="none" d="M1765.447,198.374c-42.255,0-76.514,34.996-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
C1826.633,219.768,1798.486,198.374,1765.447,198.374z"/>
<path fill="#333359" d="M1765.447,354.709c-31.946,0-59.308-20.014-70.764-48.431l-0.1,0.004l0.091-0.024
c-0.006-0.016-0.014-0.031-0.02-0.047l146.75-38.951c0.006,0.056,0.011,0.111,0.018,0.166l15.616-4.133
c-6.306-45.918-44.904-81.253-91.59-81.253c-51.089,0-92.501,42.31-92.501,94.5s41.412,94.501,92.501,94.501
c38.213,0,71.011-23.675,85.115-57.448l-14.717-6.398C1824.179,335.126,1797.054,354.709,1765.447,354.709z M1688.934,276.542
c0-43.173,34.259-78.169,76.514-78.169c33.039,0,61.186,21.395,71.895,51.361l-147.452,39.137
C1689.267,284.854,1688.934,280.739,1688.934,276.542z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -23,6 +23,9 @@ body{
font-family:'OpenSans', sans-serif, Arial;
font-size: 14px;
margin:0;
background-repeat:no-repeat !important;
background-size: contain !important;
background-image: url(/images/header-map-1280px.png);
}
a{
color: #499DCE;
@@ -81,6 +84,7 @@ section {
}
.item img {
position: absolute;
display: block;
margin: 30px;
width: 128px;
height: 128px;
@@ -134,8 +138,8 @@ footer {
font-size:12px;
}
footer img {
width: 118px;
height: 32px;
width: 113px;
height: 31px;
}
footer .t {
display:none;
@@ -152,28 +156,6 @@ footer a {
word-break: break-all;
}
/* body background image */
body {
background-repeat:no-repeat !important;
background-size: contain !important;
background-image: url(/images/header-map-640px.png);
}
@media only screen and (min-width: 641px) {
body {
background-image: url(/images/header-map-1280px.png);
}
}
@media only screen and (min-width: 1281px) {
body {
background-image: url(/images/header-map-1600px.png);
}
}
@media only screen and (min-width: 1601px) {
body {
background-image: url(/images/header-map-2560px.png);
}
}
/* Responsive */
@media (max-width: 950px) {
section{

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -104,9 +104,9 @@
{{/if}}
</section>
<footer>
<a href="https://www.klokantech.com/" target="_blank"><img src="{{public_url}}images/klokantech.png" /></a>
<a href="https://www.maptiler.com/" target="_blank"><img src="{{public_url}}images/maptiler-logo.svg" /></a>
<p>
<a href="https://github.com/klokantech/tileserver-gl" target="_blank">Powered by TileServer GL ({{server_version}})</a> <a href="https://www.klokantech.com/" target="_blank">an open-source project from Klokan Technologies GmbH.</a> <img src="https://t.klokantech.com/8073932/19" class="t" />
<a href="https://github.com/maptiler/tileserver-gl" target="_blank">Powered by TileServer GL ({{server_version}})</a> <a href="https://www.maptiler.com/" target="_blank">an open-source project from MapTiler.</a>
</p>
</footer>
</body>

View File

@@ -395,7 +395,7 @@
</TileMatrix>
<TileMatrix>
<ows:Identifier>18</ows:Identifier>
<ScaleDenominator></ScaleDenominator>
<ScaleDenominator>1066.3647919249</ScaleDenominator>
<TopLeftCorner>90 -180</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
@@ -404,4 +404,4 @@
</TileMatrix></TileMatrixSet>
</Contents>
<ServiceMetadataURL xlink:href="{{baseUrl}}/wmts/{{id}}/"/>
</Capabilities>
</Capabilities>

View File

@@ -28,11 +28,18 @@ delete packageJson.dependencies['sharp'];
delete packageJson.optionalDependencies;
delete packageJson.devDependencies;
packageJson.engines.node = '>= 10';
var str = JSON.stringify(packageJson, undefined, 2);
fs.writeFileSync('light/package.json', str);
fs.renameSync('light/README_light.md', 'light/README.md');
fs.renameSync('light/Dockerfile_light', 'light/Dockerfile');
// for Build tileserver-gl-light docker image, don't publish
if (process.argv.length > 2 && process.argv[2] == "--no-publish") {
process.exit(0)
}
/* PUBLISH */
// tileserver-gl

4
run.sh
View File

@@ -8,7 +8,7 @@ _term() {
trap _term SIGTERM
trap _term SIGINT
xvfbMaxStartWaitTime=5
xvfbMaxStartWaitTime=60
displayNumber=99
screenNumber=0
@@ -18,7 +18,7 @@ rm -rf /tmp/.X11-unix /tmp/.X${displayNumber}-lock ~/xvfb.pid
echo "Starting Xvfb on display ${displayNumber}"
start-stop-daemon --start --pidfile ~/xvfb.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :${displayNumber} -screen ${screenNumber} 1024x768x24 -ac +extension GLX +render -noreset
# Wait to be able to connect to the port. This will exit if it cannot in 15 minutes.
# Wait to be able to connect to the port. This will exit if it cannot in 1 minute.
timeout ${xvfbMaxStartWaitTime} bash -c "while ! xdpyinfo -display :${displayNumber} >/dev/null; do sleep 0.5; done"
if [ $? -ne 0 ]; then
echo "Could not connect to display ${displayNumber} in ${xvfbMaxStartWaitTime} seconds time."

View File

@@ -2,20 +2,22 @@
'use strict';
var fs = require('fs'),
path = require('path'),
request = require('request');
require = require('esm')(module);
var mbtiles = require('@mapbox/mbtiles');
const fs = require('fs');
const path = require('path');
const request = require('request');
var packageJson = require('../package');
const MBTiles = require('@mapbox/mbtiles');
var args = process.argv;
if (args.length >= 3 && args[2][0] != '-') {
const packageJson = require('../package');
const args = process.argv;
if (args.length >= 3 && args[2][0] !== '-') {
args.splice(2, 0, '--mbtiles');
}
var opts = require('commander')
const opts = require('commander')
.description('tileserver-gl startup options')
.usage('tileserver-gl [mbtiles] [options]')
.option(
@@ -43,7 +45,7 @@ var opts = require('commander')
'Disable Cross-origin resource sharing headers'
)
.option(
'-u|--public_url',
'-u|--public_url <url>',
'Enable exposing the server on subpaths, not necessarily the root of the domain'
)
.option(
@@ -54,16 +56,24 @@ var opts = require('commander')
'-s, --silent',
'Less verbose output'
)
.option(
'-l|--log_file <file>',
'output log file (defaults to standard out)'
)
.option(
'-f|--log_format <format>',
'define the log format: https://github.com/expressjs/morgan#morganformat-options'
)
.version(
packageJson.version,
'-v, --version'
)
.parse(args);
console.log('Starting ' + packageJson.name + ' v' + packageJson.version);
console.log(`Starting ${packageJson.name} v${packageJson.version}`);
var startServer = function(configPath, config) {
var publicUrl = opts.public_url;
const startServer = (configPath, config) => {
let publicUrl = opts.public_url;
if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) {
publicUrl += '/';
}
@@ -73,34 +83,44 @@ var startServer = function(configPath, config) {
bind: opts.bind,
port: opts.port,
cors: opts.cors,
verbose: opts.verbose,
silent: opts.silent,
logFile: opts.log_file,
logFormat: opts.log_format,
publicUrl: publicUrl
});
};
var startWithMBTiles = function(mbtilesFile) {
console.log('Automatically creating config file for ' + mbtilesFile);
const startWithMBTiles = (mbtilesFile) => {
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
console.log(`[INFO] Only a basic preview style will be used.`);
console.log(`[INFO] See documentation to learn how to create config.json file.`);
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
var mbtilesStats = fs.statSync(mbtilesFile);
const mbtilesStats = fs.statSync(mbtilesFile);
if (!mbtilesStats.isFile() || mbtilesStats.size === 0) {
console.log('ERROR: Not valid MBTiles file: ' + mbtilesFile);
console.log(`ERROR: Not valid MBTiles file: ${mbtilesFile}`);
process.exit(1);
}
var instance = new mbtiles(mbtilesFile, function(err) {
instance.getInfo(function(err, info) {
const instance = new MBTiles(mbtilesFile, (err) => {
if (err) {
console.log('ERROR: Unable to open MBTiles.');
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
process.exit(1);
}
instance.getInfo((err, info) => {
if (err || !info) {
console.log('ERROR: Metadata missing in the MBTiles.');
console.log(' Make sure ' + path.basename(mbtilesFile) +
' is valid MBTiles.');
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
process.exit(1);
}
var bounds = info.bounds;
const bounds = info.bounds;
var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
const styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
var config = {
const config = {
"options": {
"paths": {
"root": styleDir,
@@ -113,59 +133,32 @@ var startWithMBTiles = function(mbtilesFile) {
"data": {}
};
if (info.format == 'pbf' &&
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
var omtV = (info.version || '').split('.');
if (info.format === 'pbf' &&
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
config['data']['v' + omtV[0]] = {
config['data'][`v3`] = {
"mbtiles": path.basename(mbtilesFile)
};
var styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
for (var i = 0; i < styles.length; i++) {
var styleName = styles[i];
var styleFileRel = styleName + '/style.json';
var styleFile = path.resolve(styleDir, 'styles', styleFileRel);
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
for (let styleName of styles) {
const styleFileRel = styleName + '/style.json';
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
if (fs.existsSync(styleFile)) {
var styleJSON = require(styleFile);
var omtVersionCompatibility =
((styleJSON || {}).metadata || {})['openmaptiles:version'] || 'x';
var m = omtVersionCompatibility.toLowerCase().split('.');
var isCompatible = !(
m[0] != 'x' && (
m[0] != omtV[0] || (
(m[1] || 'x') != 'x' && (
m[1] != omtV[1] || (
(m[2] || 'x') != 'x' &&
m[2] != omtV[2]
)
)
)
)
);
if (isCompatible) {
var styleObject = {
"style": styleFileRel,
"tilejson": {
"bounds": bounds
}
};
config['styles'][styleName] = styleObject;
} else {
console.log('Style', styleName, 'requires OpenMapTiles version',
omtVersionCompatibility, 'but mbtiles is version', info.version);
}
config['styles'][styleName] = {
"style": styleFileRel,
"tilejson": {
"bounds": bounds
}
};
}
}
} else {
console.log('WARN: MBTiles not in "openmaptiles" format. ' +
'Serving raw data only...');
console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`);
config['data'][(info.id || 'mbtiles')
.replace(/\//g, '_')
.replace(/\:/g, '_')
.replace(/:/g, '_')
.replace(/\?/g, '_')] = {
"mbtiles": path.basename(mbtilesFile)
};
@@ -182,16 +175,15 @@ var startWithMBTiles = function(mbtilesFile) {
});
};
fs.stat(path.resolve(opts.config), function(err, stats) {
fs.stat(path.resolve(opts.config), (err, stats) => {
if (err || !stats.isFile() || stats.size === 0) {
var mbtiles = opts.mbtiles;
let mbtiles = opts.mbtiles;
if (!mbtiles) {
// try to find in the cwd
var files = fs.readdirSync(process.cwd());
for (var i=0; i < files.length; i++) {
var filename = files[i];
const files = fs.readdirSync(process.cwd());
for (let filename of files) {
if (filename.endsWith('.mbtiles')) {
var mbTilesStats = fs.statSync(filename);
const mbTilesStats = fs.statSync(filename);
if (mbTilesStats.isFile() && mbTilesStats.size > 0) {
mbtiles = filename;
break;
@@ -199,16 +191,15 @@ fs.stat(path.resolve(opts.config), function(err, stats) {
}
}
if (mbtiles) {
console.log('No MBTiles specified, using ' + mbtiles);
console.log(`No MBTiles specified, using ${mbtiles}`);
return startWithMBTiles(mbtiles);
} else {
var url = 'https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
var filename = 'zurich_switzerland.mbtiles';
var stream = fs.createWriteStream(filename);
console.log('Downloading sample data (' + filename + ') from ' + url);
stream.on('finish', function() {
return startWithMBTiles(filename);
});
const url = 'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
const filename = 'zurich_switzerland.mbtiles';
const stream = fs.createWriteStream(filename);
console.log(`No MBTiles found`);
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
stream.on('finish', () => startWithMBTiles(filename));
return request.get(url).pipe(stream);
}
}
@@ -216,7 +207,7 @@ fs.stat(path.resolve(opts.config), function(err, stats) {
return startWithMBTiles(mbtiles);
}
} else {
console.log('Using specified config file from ' + opts.config);
console.log(`Using specified config file from ${opts.config}`);
return startServer(opts.config, null);
}
});

View File

@@ -1,190 +1,171 @@
'use strict';
var fs = require('fs'),
path = require('path'),
zlib = require('zlib');
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
var clone = require('clone'),
express = require('express'),
mbtiles = require('@mapbox/mbtiles'),
pbf = require('pbf'),
VectorTile = require('@mapbox/vector-tile').VectorTile;
const clone = require('clone');
const express = require('express');
const MBTiles = require('@mapbox/mbtiles');
const Pbf = require('pbf');
const VectorTile = require('@mapbox/vector-tile').VectorTile;
var tileshrinkGl;
try {
tileshrinkGl = require('tileshrink-gl');
global.addStyleParam = true;
} catch (e) {}
const utils = require('./utils');
var utils = require('./utils');
module.exports = {
init: (options, repo) => {
const app = express().disable('x-powered-by');
module.exports = function(options, repo, params, id, styles, publicUrl) {
var app = express().disable('x-powered-by');
var mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
var tileJSON = {
'tiles': params.domains || options.domains
};
var shrinkers = {};
repo[id] = tileJSON;
var mbtilesFileStats = fs.statSync(mbtilesFile);
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
throw Error('Not valid MBTiles file: ' + mbtilesFile);
}
var source;
var sourceInfoPromise = new Promise(function(resolve, reject) {
source = new mbtiles(mbtilesFile, function(err) {
if (err) {
reject(err);
return;
app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
}
source.getInfo(function(err, info) {
let tileJSONFormat = item.tileJSON.format;
const z = req.params.z | 0;
const x = req.params.x | 0;
const y = req.params.y | 0;
let format = req.params.format;
if (format === options.pbfAlias) {
format = 'pbf';
}
if (format !== tileJSONFormat &&
!(format === 'geojson' && tileJSONFormat === 'pbf')) {
return res.status(404).send('Invalid format');
}
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
z > item.tileJSON.maxzoom ||
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
return res.status(404).send('Out of bounds');
}
item.source.getTile(z, x, y, (err, data, headers) => {
let isGzipped;
if (err) {
reject(err);
return;
}
tileJSON['name'] = id;
tileJSON['format'] = 'pbf';
Object.assign(tileJSON, info);
tileJSON['tilejson'] = '2.0.0';
delete tileJSON['filesize'];
delete tileJSON['mtime'];
delete tileJSON['scheme'];
Object.assign(tileJSON, params.tilejson || {});
utils.fixTileJSONCenter(tileJSON);
if (options.dataDecoratorFunc) {
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
}
resolve();
});
});
});
var tilePattern = '/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)';
app.get(tilePattern, function(req, res, next) {
var z = req.params.z | 0,
x = req.params.x | 0,
y = req.params.y | 0;
var format = req.params.format;
if (format == options.pbfAlias) {
format = 'pbf';
}
if (format != tileJSON.format &&
!(format == 'geojson' && tileJSON.format == 'pbf')) {
return res.status(404).send('Invalid format');
}
if (z < tileJSON.minzoom || 0 || x < 0 || y < 0 ||
z > tileJSON.maxzoom ||
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
return res.status(404).send('Out of bounds');
}
source.getTile(z, x, y, function(err, data, headers) {
if (err) {
if (/does not exist/.test(err.message)) {
return res.status(204).send(err.message);
if (/does not exist/.test(err.message)) {
return res.status(204).send();
} else {
return res.status(500).send(err.message);
}
} else {
return res.status(500).send(err.message);
}
} else {
if (data == null) {
return res.status(404).send('Not found');
} else {
if (tileJSON['format'] == 'pbf') {
var isGzipped = data.slice(0,2).indexOf(
new Buffer([0x1f, 0x8b])) === 0;
var style = req.query.style;
if (style && tileshrinkGl) {
if (!shrinkers[style]) {
var styleJSON = styles[style];
if (styleJSON) {
var sourceName = null;
for (var sourceName_ in styleJSON.sources) {
var source = styleJSON.sources[sourceName_];
if (source &&
source.type == 'vector' &&
source.url.endsWith('/' + id + '.json')) {
sourceName = sourceName_;
}
}
shrinkers[style] = tileshrinkGl.createPBFShrinker(styleJSON, sourceName);
}
}
if (shrinkers[style]) {
if (data == null) {
return res.status(404).send('Not found');
} else {
if (tileJSONFormat === 'pbf') {
isGzipped = data.slice(0, 2).indexOf(
Buffer.from([0x1f, 0x8b])) === 0;
if (options.dataDecoratorFunc) {
if (isGzipped) {
data = zlib.unzipSync(data);
isGzipped = false;
}
data = shrinkers[style](data, z, tileJSON.maxzoom);
//console.log(shrinkers[style].getStats());
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
}
}
if (options.dataDecoratorFunc) {
if (format === 'pbf') {
headers['Content-Type'] = 'application/x-protobuf';
} else if (format === 'geojson') {
headers['Content-Type'] = 'application/json';
if (isGzipped) {
data = zlib.unzipSync(data);
isGzipped = false;
}
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
}
}
if (format == 'pbf') {
headers['Content-Type'] = 'application/x-protobuf';
} else if (format == 'geojson') {
headers['Content-Type'] = 'application/json';
if (isGzipped) {
data = zlib.unzipSync(data);
isGzipped = false;
}
var tile = new VectorTile(new pbf(data));
var geojson = {
"type": "FeatureCollection",
"features": []
};
for (var layerName in tile.layers) {
var layer = tile.layers[layerName];
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i);
var featureGeoJSON = feature.toGeoJSON(x, y, z);
featureGeoJSON.properties.layer = layerName;
geojson.features.push(featureGeoJSON);
const tile = new VectorTile(new Pbf(data));
const geojson = {
"type": "FeatureCollection",
"features": []
};
for (let layerName in tile.layers) {
const layer = tile.layers[layerName];
for (let i = 0; i < layer.length; i++) {
const feature = layer.feature(i);
const featureGeoJSON = feature.toGeoJSON(x, y, z);
featureGeoJSON.properties.layer = layerName;
geojson.features.push(featureGeoJSON);
}
}
data = JSON.stringify(geojson);
}
data = JSON.stringify(geojson);
}
delete headers['ETag']; // do not trust the tile ETag -- regenerate
headers['Content-Encoding'] = 'gzip';
res.set(headers);
delete headers['ETag']; // do not trust the tile ETag -- regenerate
headers['Content-Encoding'] = 'gzip';
res.set(headers);
if (!isGzipped) {
data = zlib.gzipSync(data);
isGzipped = true;
}
if (!isGzipped) {
data = zlib.gzipSync(data);
isGzipped = true;
}
return res.status(200).send(data);
return res.status(200).send(data);
}
}
});
});
app.get('/:id.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
}
const info = clone(item.tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles,
`data/${req.params.id}`, info.format, item.publicUrl, {
'pbf': options.pbfAlias
});
return res.send(info);
});
return app;
},
add: (options, repo, params, id, publicUrl) => {
const mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
let tileJSON = {
'tiles': params.domains || options.domains
};
const mbtilesFileStats = fs.statSync(mbtilesFile);
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
}
let source;
const sourceInfoPromise = new Promise((resolve, reject) => {
source = new MBTiles(mbtilesFile, err => {
if (err) {
reject(err);
return;
}
source.getInfo((err, info) => {
if (err) {
reject(err);
return;
}
tileJSON['name'] = id;
tileJSON['format'] = 'pbf';
Object.assign(tileJSON, info);
tileJSON['tilejson'] = '2.0.0';
delete tileJSON['filesize'];
delete tileJSON['mtime'];
delete tileJSON['scheme'];
Object.assign(tileJSON, params.tilejson || {});
utils.fixTileJSONCenter(tileJSON);
if (options.dataDecoratorFunc) {
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
}
resolve();
});
});
});
return sourceInfoPromise.then(() => {
repo[id] = {
tileJSON,
publicUrl,
source
}
});
});
app.get('/' + id + '.json', function(req, res, next) {
var info = clone(tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles,
'data/' + id, info.format, publicUrl, {
'pbf': options.pbfAlias
});
return res.send(info);
});
return sourceInfoPromise.then(function() {
return app;
});
}
};

View File

@@ -1,66 +1,60 @@
'use strict';
var clone = require('clone'),
express = require('express'),
fs = require('fs'),
path = require('path');
const express = require('express');
const fs = require('fs');
const path = require('path');
var utils = require('./utils');
const utils = require('./utils');
module.exports = function(options, allowedFonts) {
var app = express().disable('x-powered-by');
module.exports = (options, allowedFonts) => {
const app = express().disable('x-powered-by');
var lastModified = new Date().toUTCString();
const lastModified = new Date().toUTCString();
var fontPath = options.paths.fonts;
const fontPath = options.paths.fonts;
var existingFonts = {};
var fontListingPromise = new Promise(function(resolve, reject) {
fs.readdir(options.paths.fonts, function(err, files) {
const existingFonts = {};
const fontListingPromise = new Promise((resolve, reject) => {
fs.readdir(options.paths.fonts, (err, files) => {
if (err) {
reject(err);
return;
}
files.forEach(function(file) {
fs.stat(path.join(fontPath, file), function(err, stats) {
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'))) {
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
existingFonts[path.basename(file)] = true;
}
});
});
}
resolve();
});
});
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf',
function(req, res, next) {
var fontstack = decodeURI(req.params.fontstack);
var range = req.params.range;
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => {
const fontstack = decodeURI(req.params.fontstack);
const range = req.params.range;
utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
fontPath, fontstack, range, existingFonts).then(function(concated) {
fontPath, fontstack, range, existingFonts).then(concated => {
res.header('Content-type', 'application/x-protobuf');
res.header('Last-Modified', lastModified);
return res.send(concated);
}, function(err) {
return res.status(400).send(err);
}
}, err => res.status(400).send(err)
);
});
app.get('/fonts.json', function(req, res, next) {
app.get('/fonts.json', (req, res, next) => {
res.header('Content-type', 'application/json');
return res.send(
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
);
});
return fontListingPromise.then(function() {
return app;
});
return fontListingPromise.then(() => app);
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +1,158 @@
'use strict';
var path = require('path'),
fs = require('fs');
const path = require('path');
const fs = require('fs');
var clone = require('clone'),
express = require('express');
const clone = require('clone');
const express = require('express');
import {validate} from '@mapbox/mapbox-gl-style-spec';
var utils = require('./utils');
const utils = require('./utils');
module.exports = function(options, repo, params, id, publicUrl, reportTiles, reportFont) {
var app = express().disable('x-powered-by');
const httpTester = /^(http(s)?:)?\/\//;
var styleFile = path.resolve(options.paths.styles, params.style);
const fixUrl = (req, url, publicUrl, opt_nokey) => {
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
return url;
}
const queryParams = [];
if (!opt_nokey && req.query.key) {
queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`);
}
let query = '';
if (queryParams.length) {
query = `?${queryParams.join('&')}`;
}
return url.replace(
'local://', utils.getPublicUrl(publicUrl, req)) + query;
};
var styleJSON = clone(require(styleFile));
Object.keys(styleJSON.sources).forEach(function(name) {
var source = styleJSON.sources[name];
var url = source.url;
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
var mbtilesFile = url.substring('mbtiles://'.length);
var fromData = mbtilesFile[0] == '{' &&
mbtilesFile[mbtilesFile.length - 1] == '}';
module.exports = {
init: (options, repo) => {
const app = express().disable('x-powered-by');
if (fromData) {
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
var mapsTo = (params.mapping || {})[mbtilesFile];
if (mapsTo) {
mbtilesFile = mapsTo;
app.get('/:id/style.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
}
const styleJSON_ = clone(item.styleJSON);
for (const name of Object.keys(styleJSON_.sources)) {
const source = styleJSON_.sources[name];
source.url = fixUrl(req, source.url, item.publicUrl);
}
// mapbox-gl-js viewer cannot handle sprite urls with query
if (styleJSON_.sprite) {
styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl, false);
}
if (styleJSON_.glyphs) {
styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl, false);
}
return res.send(styleJSON_);
});
app.get('/:id/sprite:scale(@[23]x)?.:format([\\w]+)', (req, res, next) => {
const item = repo[req.params.id];
if (!item || !item.spritePath) {
return res.sendStatus(404);
}
const scale = req.params.scale,
format = req.params.format;
const filename = `${item.spritePath + (scale || '')}.${format}`;
return fs.readFile(filename, (err, data) => {
if (err) {
console.log('Sprite load error:', filename);
return res.sendStatus(404);
} else {
if (format === 'json') res.header('Content-type', 'application/json');
if (format === 'png') res.header('Content-type', 'image/png');
return res.send(data);
}
});
});
return app;
},
remove: (repo, id) => {
delete repo[id];
},
add: (options, repo, params, id, publicUrl, reportTiles, reportFont) => {
const styleFile = path.resolve(options.paths.styles, params.style);
let styleFileData;
try {
styleFileData = fs.readFileSync(styleFile);
} catch (e) {
console.log('Error reading style file');
return false;
}
let validationErrors = validate(styleFileData);
if (validationErrors.length > 0) {
console.log(`The file "${params.style}" is not valid a valid style file:`);
for (const err of validationErrors) {
console.log(`${err.line}: ${err.message}`);
}
return false;
}
let styleJSON = JSON.parse(styleFileData);
for (const name of Object.keys(styleJSON.sources)) {
const source = styleJSON.sources[name];
const url = source.url;
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
let mbtilesFile = url.substring('mbtiles://'.length);
const fromData = mbtilesFile[0] === '{' &&
mbtilesFile[mbtilesFile.length - 1] === '}';
if (fromData) {
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
const mapsTo = (params.mapping || {})[mbtilesFile];
if (mapsTo) {
mbtilesFile = mapsTo;
}
}
const identifier = reportTiles(mbtilesFile, fromData);
if (!identifier) {
return false;
}
source.url = `local://data/${identifier}.json`;
}
}
for (let obj of styleJSON.layers) {
if (obj['type'] === 'symbol') {
const fonts = (obj['layout'] || {})['text-font'];
if (fonts && fonts.length) {
fonts.forEach(reportFont);
} else {
reportFont('Open Sans Regular');
reportFont('Arial Unicode MS Regular');
}
}
var identifier = reportTiles(mbtilesFile, fromData);
source.url = 'local://data/' + identifier + '.json';
}
});
styleJSON.layers.forEach(function(obj) {
if (obj['type'] == 'symbol') {
var fonts = (obj['layout'] || {})['text-font'];
if (fonts && fonts.length) {
fonts.forEach(reportFont);
} else {
reportFont('Open Sans Regular');
reportFont('Arial Unicode MS Regular');
}
}
});
let spritePath;
var spritePath;
var httpTester = /^(http(s)?:)?\/\//;
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
spritePath = path.join(options.paths.sprites,
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
spritePath = path.join(options.paths.sprites,
styleJSON.sprite
.replace('{style}', path.basename(styleFile, '.json'))
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
);
styleJSON.sprite = 'local://styles/' + id + '/sprite';
}
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
}
.replace('{style}', path.basename(styleFile, '.json'))
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
);
styleJSON.sprite = `local://styles/${id}/sprite`;
}
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
}
repo[id] = styleJSON;
app.get('/' + id + '/style.json', function(req, res, next) {
var fixUrl = function(url, opt_nokey, opt_nostyle) {
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
return url;
}
var queryParams = [];
if (!opt_nostyle && global.addStyleParam) {
queryParams.push('style=' + id);
}
if (!opt_nokey && req.query.key) {
queryParams.unshift('key=' + req.query.key);
}
var query = '';
if (queryParams.length) {
query = '?' + queryParams.join('&');
}
return url.replace(
'local://', utils.getPublicUrl(publicUrl, req)) + query;
repo[id] = {
styleJSON,
spritePath,
publicUrl,
name: styleJSON.name
};
var styleJSON_ = clone(styleJSON);
Object.keys(styleJSON_.sources).forEach(function(name) {
var source = styleJSON_.sources[name];
source.url = fixUrl(source.url);
});
// mapbox-gl-js viewer cannot handle sprite urls with query
if (styleJSON_.sprite) {
styleJSON_.sprite = fixUrl(styleJSON_.sprite, true, true);
}
if (styleJSON_.glyphs) {
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs, false, true);
}
return res.send(styleJSON_);
});
app.get('/' + id + '/sprite:scale(@[23]x)?\.:format([\\w]+)',
function(req, res, next) {
if (!spritePath) {
return res.status(404).send('File not found');
}
var scale = req.params.scale,
format = req.params.format;
var filename = spritePath + (scale || '') + '.' + format;
return fs.readFile(filename, function(err, data) {
if (err) {
console.log('Sprite load error:', filename);
return res.status(404).send('File not found');
} else {
if (format == 'json') res.header('Content-type', 'application/json');
if (format == 'png') res.header('Content-type', 'image/png');
return res.send(data);
}
});
});
return Promise.resolve(app);
return true;
}
};

View File

@@ -4,25 +4,26 @@
process.env.UV_THREADPOOL_SIZE =
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
var fs = require('fs'),
path = require('path');
const fs = require('fs');
const path = require('path');
var clone = require('clone'),
cors = require('cors'),
enableShutdown = require('http-shutdown'),
express = require('express'),
handlebars = require('handlebars'),
mercator = new (require('@mapbox/sphericalmercator'))(),
morgan = require('morgan');
const chokidar = require('chokidar');
const clone = require('clone');
const cors = require('cors');
const enableShutdown = require('http-shutdown');
const express = require('express');
const handlebars = require('handlebars');
const mercator = new (require('@mapbox/sphericalmercator'))();
const morgan = require('morgan');
var packageJson = require('../package'),
serve_font = require('./serve_font'),
serve_rendered = null,
serve_style = require('./serve_style'),
serve_data = require('./serve_data'),
utils = require('./utils');
const packageJson = require('../package');
const serve_font = require('./serve_font');
const serve_style = require('./serve_style');
const serve_data = require('./serve_data');
const utils = require('./utils');
var isLight = packageJson.name.slice(-6) == '-light';
let serve_rendered = null;
const isLight = packageJson.name.slice(-6) === '-light';
if (!isLight) {
// do not require `serve_rendered` in the light package
serve_rendered = require('./serve_rendered');
@@ -31,28 +32,27 @@ if (!isLight) {
function start(opts) {
console.log('Starting server');
var app = express().disable('x-powered-by'),
serving = {
styles: {},
rendered: {},
data: {},
fonts: {}
};
const app = express().disable('x-powered-by'),
serving = {
styles: {},
rendered: {},
data: {},
fonts: {}
};
app.enable('trust proxy');
if (process.env.NODE_ENV == 'production') {
app.use(morgan('tiny', {
skip: function(req, res) { return opts.silent && (res.statusCode == 200 || res.statusCode == 304) }
}));
} else if (process.env.NODE_ENV !== 'test') {
app.use(morgan('dev', {
skip: function(req, res) { return opts.silent && (res.statusCode == 200 || res.statusCode == 304) }
if (process.env.NODE_ENV !== 'test') {
const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
const logFormat = opts.logFormat || defaultLogFormat;
app.use(morgan(logFormat, {
stream: opts.logFile ? fs.createWriteStream(opts.logFile, { flags: 'a' }) : process.stdout,
skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304)
}));
}
var config = opts.config || null;
var configPath = null;
let config = opts.config || null;
let configPath = null;
if (opts.configPath) {
configPath = path.resolve(opts.configPath);
try {
@@ -68,8 +68,8 @@ function start(opts) {
process.exit(1);
}
var options = config.options || {};
var paths = options.paths || {};
const options = config.options || {};
const paths = options.paths || {};
options.paths = paths;
paths.root = path.resolve(
configPath ? path.dirname(configPath) : process.cwd(),
@@ -79,11 +79,11 @@ function start(opts) {
paths.sprites = path.resolve(paths.root, paths.sprites || '');
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
var startupPromises = [];
const startupPromises = [];
var checkPath = function(type) {
const checkPath = type => {
if (!fs.existsSync(paths[type])) {
console.error('The specified path for "' + type + '" does not exist (' + paths[type] + ').');
console.error(`The specified path for "${type}" does not exist (${paths[type]}).`);
process.exit(1);
}
};
@@ -98,136 +98,188 @@ function start(opts) {
} catch (e) {}
}
var data = clone(config.data || {});
const data = clone(config.data || {});
if (opts.cors) {
app.use(cors());
}
Object.keys(config.styles || {}).forEach(function(id) {
var item = config.styles[id];
if (!item.style || item.style.length == 0) {
console.log('Missing "style" property for ' + id);
return;
}
app.use('/data/', serve_data.init(options, serving.data));
app.use('/styles/', serve_style.init(options, serving.styles));
if (serve_rendered) {
startupPromises.push(
serve_rendered.init(options, serving.rendered)
.then(sub => {
app.use('/styles/', sub);
})
);
}
let addStyle = (id, item, allowMoreData, reportFonts) => {
let success = true;
if (item.serve_data !== false) {
startupPromises.push(serve_style(options, serving.styles, item, id, opts.publicUrl,
function(mbtiles, fromData) {
var dataItemId;
Object.keys(data).forEach(function(id) {
success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
(mbtiles, fromData) => {
let dataItemId;
for (const id of Object.keys(data)) {
if (fromData) {
if (id == mbtiles) {
if (id === mbtiles) {
dataItemId = id;
}
} else {
if (data[id].mbtiles == mbtiles) {
if (data[id].mbtiles === mbtiles) {
dataItemId = id;
}
}
});
}
if (dataItemId) { // mbtiles exist in the data config
return dataItemId;
} else if (fromData) {
console.log('ERROR: data "' + mbtiles + '" not found!');
process.exit(1);
} else {
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
while (data[id]) id += '_';
data[id] = {
'mbtiles': mbtiles
};
return id;
}
}, function(font) {
serving.fonts[font] = true;
}).then(function(sub) {
app.use('/styles/', sub);
}));
}
if (item.serve_rendered !== false) {
if (serve_rendered) {
startupPromises.push(
serve_rendered(options, serving.rendered, item, id, opts.publicUrl,
function(mbtiles) {
var mbtilesFile;
Object.keys(data).forEach(function(id) {
if (id == mbtiles) {
mbtilesFile = data[id].mbtiles;
}
});
return mbtilesFile;
if (fromData || !allowMoreData) {
console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
return undefined;
} else {
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
while (data[id]) id += '_';
data[id] = {
'mbtiles': mbtiles
};
return id;
}
).then(function(sub) {
app.use('/styles/', sub);
})
);
}
}, font => {
if (reportFonts) {
serving.fonts[font] = true;
}
});
}
if (success && item.serve_rendered !== false) {
if (serve_rendered) {
startupPromises.push(serve_rendered.add(options, serving.rendered, item, id, opts.publicUrl,
mbtiles => {
let mbtilesFile;
for (const id of Object.keys(data)) {
if (id === mbtiles) {
mbtilesFile = data[id].mbtiles;
}
}
return mbtilesFile;
}
));
} else {
item.serve_rendered = false;
}
}
});
};
for (const id of Object.keys(config.styles || {})) {
const item = config.styles[id];
if (!item.style || item.style.length === 0) {
console.log(`Missing "style" property for ${id}`);
continue;
}
addStyle(id, item, true, true);
}
startupPromises.push(
serve_font(options, serving.fonts).then(function(sub) {
serve_font(options, serving.fonts).then(sub => {
app.use('/', sub);
})
);
Object.keys(data).forEach(function(id) {
var item = data[id];
if (!item.mbtiles || item.mbtiles.length == 0) {
console.log('Missing "mbtiles" property for ' + id);
return;
for (const id of Object.keys(data)) {
const item = data[id];
if (!item.mbtiles || item.mbtiles.length === 0) {
console.log(`Missing "mbtiles" property for ${id}`);
continue;
}
startupPromises.push(
serve_data(options, serving.data, item, id, serving.styles, opts.publicUrl).then(function(sub) {
app.use('/data/', sub);
})
serve_data.add(options, serving.data, item, id, opts.publicUrl)
);
});
}
app.get('/styles.json', function(req, res, next) {
var result = [];
var query = req.query.key ? ('?key=' + req.query.key) : '';
Object.keys(serving.styles).forEach(function(id) {
var styleJSON = serving.styles[id];
if (options.serveAllStyles) {
fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
if (err) {
return;
}
for (const file of files) {
if (file.isFile() &&
path.extname(file.name).toLowerCase() == '.json') {
let id = path.basename(file.name, '.json');
let item = {
style: file.name
};
addStyle(id, item, false, false);
}
}
});
const watcher = chokidar.watch(path.join(options.paths.styles, '*.json'),
{
});
watcher.on('all',
(eventType, filename) => {
if (filename) {
let id = path.basename(filename, '.json');
console.log(`Style "${id}" changed, updating...`);
serve_style.remove(serving.styles, id);
if (serve_rendered) {
serve_rendered.remove(serving.rendered, id);
}
if (eventType == "add" || eventType == "change") {
let item = {
style: filename
};
addStyle(id, item, false, false);
}
}
});
}
app.get('/styles.json', (req, res, next) => {
const result = [];
const query = req.query.key ? (`?key=${encodeURIComponent(req.query.key)}`) : '';
for (const id of Object.keys(serving.styles)) {
const styleJSON = serving.styles[id].styleJSON;
result.push({
version: styleJSON.version,
name: styleJSON.name,
id: id,
url: utils.getPublicUrl(opts.publicUrl, req) +
'/styles/' + id + '/style.json' + query
url: `${utils.getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}`
});
});
}
res.send(result);
});
var addTileJSONs = function(arr, req, type) {
Object.keys(serving[type]).forEach(function(id) {
var info = clone(serving[type][id]);
var path = '';
if (type == 'rendered') {
path = 'styles/' + id;
const addTileJSONs = (arr, req, type) => {
for (const id of Object.keys(serving[type])) {
const info = clone(serving[type][id].tileJSON);
let path = '';
if (type === 'rendered') {
path = `styles/${id}`;
} else {
path = type + '/' + id;
path = `${type}/${id}`;
}
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
'pbf': options.pbfAlias
});
arr.push(info);
});
}
return arr;
};
app.get('/rendered.json', function(req, res, next) {
app.get('/rendered.json', (req, res, next) => {
res.send(addTileJSONs([], req, 'rendered'));
});
app.get('/data.json', function(req, res, next) {
app.get('/data.json', (req, res, next) => {
res.send(addTileJSONs([], req, 'data'));
});
app.get('/index.json', function(req, res, next) {
app.get('/index.json', (req, res, next) => {
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
});
@@ -235,40 +287,40 @@ function start(opts) {
// serve web presentations
app.use('/', express.static(path.join(__dirname, '../public/resources')));
var templates = path.join(__dirname, '../public/templates');
var serveTemplate = function(urlPath, template, dataGetter) {
var templateFile = templates + '/' + template + '.tmpl';
if (template == 'index') {
const templates = path.join(__dirname, '../public/templates');
const serveTemplate = (urlPath, template, dataGetter) => {
let templateFile = `${templates}/${template}.tmpl`;
if (template === 'index') {
if (options.frontPage === false) {
return;
} else if (options.frontPage &&
options.frontPage.constructor === String) {
options.frontPage.constructor === String) {
templateFile = path.resolve(paths.root, options.frontPage);
}
}
startupPromises.push(new Promise(function(resolve, reject) {
fs.readFile(templateFile, function(err, content) {
startupPromises.push(new Promise((resolve, reject) => {
fs.readFile(templateFile, (err, content) => {
if (err) {
err = new Error('Template not found: ' + err.message);
err = new Error(`Template not found: ${err.message}`);
reject(err);
return;
}
var compiled = handlebars.compile(content.toString());
const compiled = handlebars.compile(content.toString());
app.use(urlPath, function(req, res, next) {
var data = {};
app.use(urlPath, (req, res, next) => {
let data = {};
if (dataGetter) {
data = dataGetter(req);
if (!data) {
return res.status(404).send('Not found');
}
}
data['server_version'] = packageJson.name + ' v' + packageJson.version;
data['server_version'] = `${packageJson.name} v${packageJson.version}`;
data['public_url'] = opts.publicUrl || '/';
data['is_light'] = isLight;
data['key_query_part'] =
req.query.key ? 'key=' + req.query.key + '&amp;' : '';
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
req.query.key ? `key=${encodeURIComponent(req.query.key)}&amp;` : '';
data['key_query'] = req.query.key ? `?key=${encodeURIComponent(req.query.key)}` : '';
if (template === 'wmts') res.set('Content-Type', 'text/xml');
return res.status(200).send(compiled(data));
});
@@ -277,59 +329,50 @@ function start(opts) {
}));
};
serveTemplate('/$', 'index', function(req) {
var styles = clone(config.styles || {});
Object.keys(styles).forEach(function(id) {
var style = styles[id];
serveTemplate('/$', 'index', req => {
const styles = clone(serving.styles || {});
for (const id of Object.keys(styles)) {
const style = styles[id];
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
style.serving_data = serving.styles[id];
style.serving_rendered = serving.rendered[id];
if (style.serving_rendered) {
var center = style.serving_rendered.center;
const center = style.serving_rendered.tileJSON.center;
if (center) {
style.viewer_hash = '#' + center[2] + '/' +
center[1].toFixed(5) + '/' +
center[0].toFixed(5);
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
var centerPx = mercator.px([center[0], center[1]], center[2]);
style.thumbnail = center[2] + '/' +
Math.floor(centerPx[0] / 256) + '/' +
Math.floor(centerPx[1] / 256) + '.png';
const centerPx = mercator.px([center[0], center[1]], center[2]);
style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
}
var tiles = utils.getTileUrls(
req, style.serving_rendered.tiles,
'styles/' + id, style.serving_rendered.format, opts.publicUrl);
style.xyz_link = tiles[0];
style.xyz_link = utils.getTileUrls(
req, style.serving_rendered.tileJSON.tiles,
`styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
}
});
var data = clone(serving.data || {});
Object.keys(data).forEach(function(id) {
var data_ = data[id];
var center = data_.center;
}
const data = clone(serving.data || {});
for (const id of Object.keys(data)) {
const data_ = data[id];
const tilejson = data[id].tileJSON;
const center = tilejson.center;
if (center) {
data_.viewer_hash = '#' + center[2] + '/' +
center[1].toFixed(5) + '/' +
center[0].toFixed(5);
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
}
data_.is_vector = data_.format == 'pbf';
data_.is_vector = tilejson.format === 'pbf';
if (!data_.is_vector) {
if (center) {
var centerPx = mercator.px([center[0], center[1]], center[2]);
data_.thumbnail = center[2] + '/' +
Math.floor(centerPx[0] / 256) + '/' +
Math.floor(centerPx[1] / 256) + '.' + data_.format;
const centerPx = mercator.px([center[0], center[1]], center[2]);
data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
}
var tiles = utils.getTileUrls(
req, data_.tiles, 'data/' + id, data_.format, opts.publicUrl, {
'pbf': options.pbfAlias
});
data_.xyz_link = tiles[0];
data_.xyz_link = utils.getTileUrls(
req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
'pbf': options.pbfAlias
})[0];
}
if (data_.filesize) {
var suffix = 'kB';
var size = parseInt(data_.filesize, 10) / 1024;
let suffix = 'kB';
let size = parseInt(data_.filesize, 10) / 1024;
if (size > 1024) {
suffix = 'MB';
size /= 1024;
@@ -338,18 +381,18 @@ function start(opts) {
suffix = 'GB';
size /= 1024;
}
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
data_.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
}
});
}
return {
styles: Object.keys(styles).length ? styles : null,
data: Object.keys(data).length ? data : null
};
});
serveTemplate('/styles/:id/$', 'viewer', function(req) {
var id = req.params.id;
var style = clone((config.styles || {})[id]);
serveTemplate('/styles/:id/$', 'viewer', req => {
const id = req.params.id;
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
if (!style) {
return null;
}
@@ -365,9 +408,9 @@ function start(opts) {
return res.redirect(301, '/styles/' + req.params.id + '/');
});
*/
serveTemplate('/styles/:id/wmts.xml', 'wmts', function(req) {
var id = req.params.id;
var wmts = clone((config.styles || {})[id]);
serveTemplate('/styles/:id/wmts.xml', 'wmts', req => {
const id = req.params.id;
const wmts = clone((serving.styles || {})[id]);
if (!wmts) {
return null;
}
@@ -376,27 +419,27 @@ function start(opts) {
}
wmts.id = id;
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
wmts.baseUrl = (req.get('X-Forwarded-Protocol')?req.get('X-Forwarded-Protocol'):req.protocol) + '://' + req.get('host');
wmts.baseUrl = `${req.get('X-Forwarded-Protocol') ? req.get('X-Forwarded-Protocol') : req.protocol}://${req.get('host')}`;
return wmts;
});
serveTemplate('/data/:id/$', 'data', function(req) {
var id = req.params.id;
var data = clone(serving.data[id]);
serveTemplate('/data/:id/$', 'data', req => {
const id = req.params.id;
const data = clone(serving.data[id]);
if (!data) {
return null;
}
data.id = id;
data.is_vector = data.format == 'pbf';
data.is_vector = data.tileJSON.format === 'pbf';
return data;
});
var startupComplete = false;
var startupPromise = Promise.all(startupPromises).then(function() {
let startupComplete = false;
const startupPromise = Promise.all(startupPromises).then(() => {
console.log('Startup complete');
startupComplete = true;
});
app.get('/health', function(req, res, next) {
app.get('/health', (req, res, next) => {
if (startupComplete) {
return res.status(200).send('OK');
} else {
@@ -404,12 +447,12 @@ function start(opts) {
}
});
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
var address = this.address().address;
const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function () {
let address = this.address().address;
if (address.indexOf('::') === 0) {
address = '[' + address + ']'; // literal IPv6 address
address = `[${address}]`; // literal IPv6 address
}
console.log('Listening at http://%s:%d/', address, this.address().port);
console.log(`Listening at http://${address}:${this.address().port}/`);
});
// add server.shutdown() to gracefully stop serving
@@ -422,27 +465,27 @@ function start(opts) {
};
}
module.exports = function(opts) {
var running = start(opts);
module.exports = opts => {
const running = start(opts);
running.startupPromise.catch(function(err) {
running.startupPromise.catch(err => {
console.error(err.message);
process.exit(1);
});
process.on('SIGINT', function() {
process.on('SIGINT', () => {
process.exit();
});
process.on('SIGHUP', function() {
process.on('SIGHUP', () => {
console.log('Stopping server and reloading config');
running.server.shutdown(function() {
for (var key in require.cache) {
running.server.shutdown(() => {
for (const key in require.cache) {
delete require.cache[key];
}
var restarted = start(opts);
const restarted = start(opts);
running.server = restarted.server;
running.app = restarted.app;
});

View File

@@ -1,75 +1,72 @@
'use strict';
var path = require('path'),
fs = require('fs');
const path = require('path');
const fs = require('fs');
var clone = require('clone'),
glyphCompose = require('glyph-pbf-composite');
const clone = require('clone');
const glyphCompose = require('@mapbox/glyph-pbf-composite');
module.exports.getPublicUrl = function(publicUrl, req) {
return publicUrl || (req.protocol + '://' + req.headers.host + '/')
}
module.exports.getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`;
module.exports.getTileUrls = function(req, domains, path, format, publicUrl, aliases) {
module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
if (domains) {
if (domains.constructor === String && domains.length > 0) {
domains = domains.split(',');
}
var host = req.headers.host;
var hostParts = host.split('.');
var relativeSubdomainsUsable = hostParts.length > 1 &&
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
var newDomains = [];
domains.forEach(function(domain) {
const host = req.headers.host;
const hostParts = host.split('.');
const relativeSubdomainsUsable = hostParts.length > 1 &&
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
const newDomains = [];
for (const domain of domains) {
if (domain.indexOf('*') !== -1) {
if (relativeSubdomainsUsable) {
var newParts = hostParts.slice(1);
const newParts = hostParts.slice(1);
newParts.unshift(domain.replace('*', hostParts[0]));
newDomains.push(newParts.join('.'));
}
} else {
newDomains.push(domain);
}
});
}
domains = newDomains;
}
if (!domains || domains.length == 0) {
domains = [req.headers.host];
}
var key = req.query.key;
var queryParams = [];
const key = req.query.key;
const queryParams = [];
if (req.query.key) {
queryParams.push('key=' + req.query.key);
queryParams.push(`key=${encodeURIComponent(req.query.key)}`);
}
if (req.query.style) {
queryParams.push('style=' + req.query.style);
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
}
var query = queryParams.length > 0 ? ('?' + queryParams.join('&')) : '';
const query = queryParams.length > 0 ? (`?${queryParams.join('&')}`) : '';
if (aliases && aliases[format]) {
format = aliases[format];
}
var uris = [];
const uris = [];
if (!publicUrl) {
domains.forEach(function(domain) {
uris.push(req.protocol + '://' + domain + '/' + path +
'/{z}/{x}/{y}.' + format + query);
});
for (const domain of domains) {
uris.push(`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`);
}
} else {
uris.push(publicUrl + path + '/{z}/{x}/{y}.' + format + query)
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`)
}
return uris;
};
module.exports.fixTileJSONCenter = function(tileJSON) {
module.exports.fixTileJSONCenter = tileJSON => {
if (tileJSON.bounds && !tileJSON.center) {
var fitWidth = 1024;
var tiles = fitWidth / 256;
const fitWidth = 1024;
const tiles = fitWidth / 256;
tileJSON.center = [
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
@@ -81,58 +78,54 @@ module.exports.fixTileJSONCenter = function(tileJSON) {
}
};
var getFontPbf = function(allowedFonts, fontPath, name, range, fallbacks) {
return new Promise(function(resolve, reject) {
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
var filename = path.join(fontPath, name, range + '.pbf');
if (!fallbacks) {
fallbacks = clone(allowedFonts || {});
}
delete fallbacks[name];
fs.readFile(filename, function(err, data) {
if (err) {
console.error('ERROR: Font not found:', name);
if (fallbacks && Object.keys(fallbacks).length) {
var fallbackName;
var fontStyle = name.split(' ').pop();
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
fontStyle = 'Regular';
}
fallbackName = 'Noto Sans ' + fontStyle;
if (!fallbacks[fallbackName]) {
fallbackName = 'Open Sans ' + fontStyle;
if (!fallbacks[fallbackName]) {
fallbackName = Object.keys(fallbacks)[0];
}
}
console.error('ERROR: Trying to use', fallbackName, 'as a fallback');
delete fallbacks[fallbackName];
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
} else {
reject('Font load error: ' + name);
}
} else {
resolve(data);
}
});
} else {
reject('Font not allowed: ' + name);
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promise((resolve, reject) => {
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
const filename = path.join(fontPath, name, `${range}.pbf`);
if (!fallbacks) {
fallbacks = clone(allowedFonts || {});
}
});
};
delete fallbacks[name];
fs.readFile(filename, (err, data) => {
if (err) {
console.error(`ERROR: Font not found: ${name}`);
if (fallbacks && Object.keys(fallbacks).length) {
let fallbackName;
module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, fallbacks) {
var fonts = names.split(',');
var queue = [];
fonts.forEach(function(font) {
let fontStyle = name.split(' ').pop();
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
fontStyle = 'Regular';
}
fallbackName = `Noto Sans ${fontStyle}`;
if (!fallbacks[fallbackName]) {
fallbackName = `Open Sans ${fontStyle}`;
if (!fallbacks[fallbackName]) {
fallbackName = Object.keys(fallbacks)[0];
}
}
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
delete fallbacks[fallbackName];
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
} else {
reject(`Font load error: ${name}`);
}
} else {
resolve(data);
}
});
} else {
reject(`Font not allowed: ${name}`);
}
});
module.exports.getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => {
const fonts = names.split(',');
const queue = [];
for (const font of fonts) {
queue.push(
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
);
});
}
return Promise.all(queue).then(function(values) {
return glyphCompose.combine(values);
});
return Promise.all(queue).then(values => glyphCompose.combine(values));
};

View File

@@ -3,6 +3,8 @@ process.env.NODE_ENV = 'test';
global.should = require('should');
global.supertest = require('supertest');
require = require('esm')(module);
before(function() {
console.log('global setup');
process.chdir('test_data');