Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d5be67efc | ||
|
|
aba60f0c6a | ||
|
|
6027d89623 | ||
|
|
8680a8006a | ||
|
|
09ded526ef | ||
|
|
c89a5ae029 | ||
|
|
a3d7f8bcbd | ||
|
|
7c1420982c | ||
|
|
51baa9b67b | ||
|
|
6b3f557b1f | ||
|
|
8d2ddd8f95 | ||
|
|
9f59f67087 | ||
|
|
1d21648969 | ||
|
|
16de5be673 | ||
|
|
6b96f224ae | ||
|
|
6ff4cae9b9 | ||
|
|
644db6cd8a | ||
|
|
a98404e921 | ||
|
|
559cfc462c | ||
|
|
559c3a913e | ||
|
|
4036d528ec | ||
|
|
6ae4116ccb | ||
|
|
39bb7ffbf1 | ||
|
|
e249a3f67d | ||
|
|
04b49d8a98 | ||
|
|
309d481117 | ||
|
|
7ce4805cdd | ||
|
|
5377414710 | ||
|
|
042b8b986a | ||
|
|
038bfe29d6 | ||
|
|
f8563e1f2b | ||
|
|
10431d70d0 | ||
|
|
a5a8ae1e95 | ||
|
|
3c411cd1ac | ||
|
|
79bd70942b | ||
|
|
5585f49396 | ||
|
|
d5a079d8f4 | ||
|
|
29d3e72dd3 | ||
|
|
c3bff5ac5f | ||
|
|
e16de39b93 | ||
|
|
13415fd29f | ||
|
|
9a9b920455 | ||
|
|
b55b8adb63 | ||
|
|
9e12ee6f8c | ||
|
|
0e85e0058f | ||
|
|
3a94a8f9d2 | ||
|
|
28dbc78264 | ||
|
|
8f77be3037 | ||
|
|
f2dc13e298 | ||
|
|
cf0eedb379 | ||
|
|
95bb59dcfe | ||
|
|
224a1300df | ||
|
|
9fd381640e | ||
|
|
b7c384f1ee | ||
|
|
8126b31081 | ||
|
|
caa641c550 | ||
|
|
d02ce0663b | ||
|
|
ea89d11021 | ||
|
|
aa933e5154 | ||
|
|
298d09845d | ||
|
|
d7a34f3a74 | ||
|
|
cb700181d3 | ||
|
|
226b979592 | ||
|
|
13ad268b43 | ||
|
|
e2387d164b | ||
|
|
515c295898 | ||
|
|
cdc7803ad8 | ||
|
|
b839979351 | ||
|
|
0a7c403f0b | ||
|
|
b3d810817d | ||
|
|
96161fc656 | ||
|
|
58c769b448 | ||
|
|
bdd0a5c868 | ||
|
|
5048388d1f | ||
|
|
4c7a227e11 | ||
|
|
7f8be27844 | ||
|
|
736e8d393a | ||
|
|
1c24d12b0d | ||
|
|
f77ccd06af | ||
|
|
4996848bdc | ||
|
|
aa7ae575d0 | ||
|
|
9603703908 | ||
|
|
1445c545b0 | ||
|
|
17a73b1d4a | ||
|
|
e44104254e | ||
|
|
58b536036f | ||
|
|
a68e095400 | ||
|
|
53b28b35f1 | ||
|
|
58f92ae947 | ||
|
|
e506014763 | ||
|
|
7c5e7e94e9 | ||
|
|
e6747ebb78 | ||
|
|
2a87ad19c9 | ||
|
|
ba62f0bf30 | ||
|
|
08369a666d | ||
|
|
f771d41788 | ||
|
|
9dda95ee8e | ||
|
|
63483a3155 | ||
|
|
bca5191ad9 | ||
|
|
245d9765a9 | ||
|
|
e8134dfeb0 | ||
|
|
a1a8996d3f | ||
|
|
bea528ab64 | ||
|
|
cde0233130 | ||
|
|
c99a1a5425 | ||
|
|
243bdbdaa0 | ||
|
|
0631a76a51 | ||
|
|
8e1071aad0 | ||
|
|
e283569897 | ||
|
|
507da8567e | ||
|
|
9cd6feb56d | ||
|
|
059b0f11a0 | ||
|
|
7acbc93ba9 | ||
|
|
bc3d79bfdc | ||
|
|
58168eb0a5 | ||
|
|
f993b01387 | ||
|
|
782c28cd64 | ||
|
|
76a0064f0c | ||
|
|
55fa8bf8c1 | ||
|
|
0c42fdd6b3 | ||
|
|
e8b3c02546 | ||
|
|
56089c0a62 | ||
|
|
857ff2d8d7 | ||
|
|
e310fa5165 | ||
|
|
71fab2a2a1 | ||
|
|
1c59e3d742 | ||
|
|
6e24f6ec8e | ||
|
|
5d5ed1153f | ||
|
|
baf01a117b | ||
|
|
2cc2cc1b3b | ||
|
|
45ed8d90ca | ||
|
|
8e66736955 | ||
|
|
72ea5ab05d | ||
|
|
9d642fa0b3 | ||
|
|
1d734f9b42 | ||
|
|
80146ed7d9 | ||
|
|
a28df7ef8f | ||
|
|
c333197a7c | ||
|
|
45df72df51 | ||
|
|
e737753891 | ||
|
|
edff8ce06c | ||
|
|
df05ce3f2a | ||
|
|
a74ca20375 | ||
|
|
1c890b0157 | ||
|
|
7a9f04a024 | ||
|
|
1354c7f663 | ||
|
|
da3cb1e7f5 | ||
|
|
19e821a8fe | ||
|
|
659289d85c | ||
|
|
734d1f01f0 | ||
|
|
c1055a9647 | ||
|
|
2afb460191 | ||
|
|
8eb736b821 | ||
|
|
83e20b7a4e | ||
|
|
81f65af3a8 | ||
|
|
f5ea790878 | ||
|
|
a0eb5800fd | ||
|
|
27eb7f0ed8 | ||
|
|
2f9059d09e | ||
|
|
e11c8f9315 | ||
|
|
650718e0f6 | ||
|
|
5ed632c229 | ||
|
|
f545076986 | ||
|
|
3d48485475 | ||
|
|
c060dedf20 | ||
|
|
cd1f5fd04a | ||
|
|
dc6be5047c | ||
|
|
47ff33166d | ||
|
|
875521c5a8 | ||
|
|
82f179b07c | ||
|
|
51d6ca0880 | ||
|
|
89878015bb | ||
|
|
ac948b6dee | ||
|
|
440775dbbf | ||
|
|
ea408f8ec9 | ||
|
|
da646868c3 | ||
|
|
d60996e3c6 | ||
|
|
02cf45bbd9 | ||
|
|
d9f8582279 | ||
|
|
54f11a2125 | ||
|
|
598c8c590b | ||
|
|
438a18eec1 | ||
|
|
7bdb7afcb9 | ||
|
|
2e46700cd9 | ||
|
|
bb09f3df64 | ||
|
|
42f24c2c99 | ||
|
|
427a0f0687 | ||
|
|
da5ea4b426 | ||
|
|
2208ff1e24 | ||
|
|
cf521058be | ||
|
|
a9b38022bb | ||
|
|
654bdda629 | ||
|
|
537313840e | ||
|
|
d30f8464b2 | ||
|
|
698c527e94 | ||
|
|
8007f1386c | ||
|
|
4f2fdf602b | ||
|
|
6d7397647a | ||
|
|
8fd7a9b42b | ||
|
|
95470143b6 | ||
|
|
de83021c3d |
@@ -1,7 +1,6 @@
|
|||||||
.git
|
*
|
||||||
docs/_build
|
!src
|
||||||
node_modules
|
!public
|
||||||
test_data
|
!package.json
|
||||||
light
|
!package-lock.json
|
||||||
config.json
|
!docker-entrypoint.sh
|
||||||
*.mbtiles
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "4"
|
- "10"
|
||||||
env:
|
env:
|
||||||
- CXX=g++-4.8
|
- CXX=g++-4.8
|
||||||
addons:
|
addons:
|
||||||
@@ -12,10 +12,10 @@ addons:
|
|||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
|
- sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
|
||||||
- sudo apt-get install -qq xvfb
|
- sudo apt-get install -qq xvfb libgles2-mesa-dev libgbm-dev libxxf86vm-dev
|
||||||
install:
|
install:
|
||||||
- npm 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
|
- unzip -q test_data.zip -d test_data
|
||||||
script:
|
script:
|
||||||
- xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
- xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
||||||
|
|||||||
71
Dockerfile
@@ -1,32 +1,55 @@
|
|||||||
FROM debian:stretch
|
FROM node:10-buster AS builder
|
||||||
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
|
|
||||||
|
|
||||||
RUN apt-get -qq update \
|
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
|
&& apt-get -qq update \
|
||||||
apt-transport-https \
|
&& apt-get -y --no-install-recommends install \
|
||||||
curl \
|
apt-transport-https \
|
||||||
unzip \
|
curl \
|
||||||
build-essential \
|
unzip \
|
||||||
python \
|
build-essential \
|
||||||
libcairo2-dev \
|
python \
|
||||||
libprotobuf-dev \
|
libcairo2-dev \
|
||||||
xvfb \
|
libgles2-mesa-dev \
|
||||||
&& echo "deb https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
|
libgbm-dev \
|
||||||
&& echo "deb-src https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
|
libllvm7 \
|
||||||
&& apt-get -qq update \
|
libprotobuf-dev \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated install \
|
&& apt-get -y --purge autoremove \
|
||||||
nodejs \
|
&& apt-get clean \
|
||||||
&& rm /etc/apt/sources.list.d/nodejs.list \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
&& apt-get clean
|
|
||||||
|
COPY . /usr/src/app
|
||||||
|
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
|
||||||
COPY / /usr/src/app
|
|
||||||
RUN cd /usr/src/app && npm install --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
|
VOLUME /data
|
||||||
WORKDIR /data
|
WORKDIR /data
|
||||||
|
|
||||||
ENV NODE_ENV="production"
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
ENTRYPOINT ["/usr/src/app/run.sh"]
|
|
||||||
|
USER node:node
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["-p", "80"]
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
FROM node:4
|
FROM node:10-stretch
|
||||||
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
|
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
ENV CHOKIDAR_USEPOLLING=1
|
||||||
|
ENV CHOKIDAR_INTERVAL=500
|
||||||
|
EXPOSE 80
|
||||||
|
VOLUME /data
|
||||||
|
WORKDIR /data
|
||||||
|
ENTRYPOINT ["node", "/usr/src/app/", "-p", "80"]
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
RUN mkdir -p /usr/src/app
|
||||||
COPY / /usr/src/app
|
COPY / /usr/src/app
|
||||||
RUN cd /usr/src/app && npm install --production
|
RUN cd /usr/src/app && npm install --production
|
||||||
|
|
||||||
VOLUME /data
|
|
||||||
WORKDIR /data
|
|
||||||
|
|
||||||
ENV NODE_ENV="production"
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
ENTRYPOINT ["node", "/usr/src/app/", "-p", "80"]
|
|
||||||
|
|||||||
34
Dockerfile_test
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Run tests inside docker without requiring full installation of dependencies on local machine
|
||||||
|
# Simply run "docker build -f Dockerfile_test ."
|
||||||
|
# WARNING: sometimes it fails with a core dumped exception
|
||||||
|
|
||||||
|
FROM node:10-stretch
|
||||||
|
|
||||||
|
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 \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
COPY package.json .
|
||||||
|
RUN npm install
|
||||||
|
COPY / .
|
||||||
|
RUN xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
||||||
21
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
It is great you want to help us making TileServer GL project better!
|
||||||
|
|
||||||
|
This is the right place only for a software bug report or a new software feature request.
|
||||||
|
|
||||||
|
NOTE: Questions about OpenMapTiles data, OpenMapTiles Server, TileHosting and other software/products do not belong here (and will not be answered)!
|
||||||
|
|
||||||
|
The usage and installation questions belongs to https://stackoverflow.com/questions/tagged/openmaptiles
|
||||||
|
A guaranteed support and consulting from the core developers via https://openmaptiles.com/support/
|
||||||
|
|
||||||
|
Please search this GitHub repo for similar requests before posting (check also closed tickets).
|
||||||
|
|
||||||
|
When reporting a problem you have with the TileServer GL software please provide:
|
||||||
|
|
||||||
|
- Clear description of the problem: What steps will lead to reproducing the error on our computer? What is exactly wrong?
|
||||||
|
- Version of the TileServer GL software you have used
|
||||||
|
- Version and name of the operating system you use or other details of your setup
|
||||||
|
- Information about your used config / styles / vector tiles
|
||||||
|
- URL / link to the specific location in a live map demo where a bug is visible is always great
|
||||||
|
- Screenshot of the problem are cool! Drag&drop an image to the report here...
|
||||||
|
|
||||||
|
We love pull requests! If you are able to code, please send us your fix or code modification via GitHub... Thanks!
|
||||||
14
README.md
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
|
|
||||||
# TileServer GL
|
# TileServer GL
|
||||||
[](https://travis-ci.org/klokantech/tileserver-gl)
|
[](https://travis-ci.org/maptiler/tileserver-gl)
|
||||||
[](https://hub.docker.com/r/klokantech/tileserver-gl/)
|
[](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.
|
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
|
## Get Started
|
||||||
|
|
||||||
|
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
|
Install `tileserver-gl` with server-side raster rendering of vector tiles with npm
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -18,7 +20,7 @@ npm install -g tileserver-gl
|
|||||||
Now download vector tiles from [OpenMapTiles](https://openmaptiles.org/downloads/).
|
Now download vector tiles from [OpenMapTiles](https://openmaptiles.org/downloads/).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -o zurich_switzerland.mbtiles https://openmaptiles.os.zhdk.cloud.switch.ch/v3.3/extracts/zurich_switzerland.mbtiles
|
curl -o zurich_switzerland.mbtiles https://[GET-YOUR-LINK]/extracts/zurich_switzerland.mbtiles
|
||||||
```
|
```
|
||||||
|
|
||||||
Start `tileserver-gl` with the downloaded vector tiles.
|
Start `tileserver-gl` with the downloaded vector tiles.
|
||||||
@@ -31,10 +33,10 @@ Alternatively, you can use the `tileserver-gl-light` package instead, which is p
|
|||||||
|
|
||||||
## Using Docker
|
## 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
|
```bash
|
||||||
docker run -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.
|
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.
|
||||||
@@ -43,4 +45,4 @@ On laptop you can use [Docker Kitematic](https://kitematic.com/) and search "til
|
|||||||
|
|
||||||
## Documentation
|
## 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/.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# TileServer GL light
|
# TileServer GL light
|
||||||
[](https://travis-ci.org/klokantech/tileserver-gl)
|
[](https://travis-ci.org/maptiler/tileserver-gl)
|
||||||
[](https://hub.docker.com/r/klokantech/tileserver-gl/)
|
[](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.
|
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.
|
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
|
## 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/.
|
||||||
31
docker-entrypoint.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
SIGNAL=$(( $? - 128 ))
|
||||||
|
echo "Caught signal ${SIGNAL}, refreshing"
|
||||||
|
kill -s ${SIGNAL} $(pidof node) 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
trap refresh HUP
|
||||||
|
|
||||||
|
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 "$@"
|
||||||
@@ -4,7 +4,9 @@ Configuration file
|
|||||||
|
|
||||||
The configuration file defines the behavior of the application. It's a regular JSON file.
|
The configuration file defines the behavior of the application. It's a regular JSON file.
|
||||||
|
|
||||||
Example::
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
@@ -21,15 +23,15 @@ Example::
|
|||||||
],
|
],
|
||||||
"formatQuality": {
|
"formatQuality": {
|
||||||
"jpeg": 80,
|
"jpeg": 80,
|
||||||
"webp": 90,
|
"webp": 90
|
||||||
"pngQuantization": false,
|
|
||||||
"png": 90
|
|
||||||
},
|
},
|
||||||
"maxScaleFactor": 3,
|
"maxScaleFactor": 3,
|
||||||
"maxSize": 2048,
|
"maxSize": 2048,
|
||||||
"pbfAlias": "pbf",
|
"pbfAlias": "pbf",
|
||||||
"serveAllFonts": false,
|
"serveAllFonts": false,
|
||||||
"serveStaticMaps": true
|
"serveAllStyles": false,
|
||||||
|
"serveStaticMaps": true,
|
||||||
|
"tileMargin": 0
|
||||||
},
|
},
|
||||||
"styles": {
|
"styles": {
|
||||||
"basic": {
|
"basic": {
|
||||||
@@ -83,8 +85,6 @@ Use ``false`` to disable the front page altogether (404).
|
|||||||
|
|
||||||
Quality of the compression of individual image formats. [0-100]
|
Quality of the compression of individual image formats. [0-100]
|
||||||
|
|
||||||
The value for ``png`` is only used when ``pngQuantization`` is ``true``.
|
|
||||||
|
|
||||||
``maxScaleFactor``
|
``maxScaleFactor``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -99,6 +99,41 @@ 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.
|
Be careful when changing this value since there are hardware limits that need to be considered.
|
||||||
Default is ``2048``.
|
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``
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Minimum amount of raster tile renderers per scale factor.
|
||||||
|
The value is an array: the first element is the minimum amount of renderers for scale factor one, the second for scale factor two and so on.
|
||||||
|
If the array has less elements than ``maxScaleFactor``, then the last element is used for all remaining scale factors as well.
|
||||||
|
Selecting renderer pool sizes is a trade-off between memory use and speed.
|
||||||
|
A reasonable value will depend on your hardware and your amount of styles and scale factors.
|
||||||
|
If you have plenty of memory, you'll want to set this equal to ``maxRendererPoolSizes`` to avoid increased latency due to renderer destruction and recreation.
|
||||||
|
If you need to conserve memory, you'll want something lower than ``maxRendererPoolSizes``, possibly allocating more renderers to scale factors that are more common.
|
||||||
|
Default is ``[8, 4, 2]``.
|
||||||
|
|
||||||
|
``maxRendererPoolSizes``
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Maximum amount of raster tile renderers per scale factor.
|
||||||
|
The value and considerations are similar to ``minRendererPoolSizes`` above.
|
||||||
|
If you have plenty of memory, try setting these equal to or slightly above your processor count, e.g. if you have four processors, try a value of ``[6]``.
|
||||||
|
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``
|
``watermark``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -113,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]
|
* ``style`` -- name of the style json file [required]
|
||||||
* ``serve_rendered`` -- whether to render the raster tiles for this style or not
|
* ``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
|
* ``tilejson`` -- properties to add to the TileJSON created for the raster data
|
||||||
|
|
||||||
* ``format`` and ``bounds`` can be especially useful
|
* ``format`` and ``bounds`` can be especially useful
|
||||||
@@ -156,7 +191,7 @@ Sprites
|
|||||||
|
|
||||||
If your style requires any sprites, make sure the style JSON contains proper path in the ``sprite`` property.
|
If your style requires any sprites, make sure the style JSON contains proper path in the ``sprite`` property.
|
||||||
|
|
||||||
It can be a local path (e.g. ``my-style/sprite``) or remove http(s) location (e.g. ``https://mycdn.com/my-style/sprite``). Several possible extension are added to this path, so the following files should be present:
|
It can be a local path (e.g. ``my-style/sprite``) or remote http(s) location (e.g. ``https://mycdn.com/my-style/sprite``). Several possible extension are added to this path, so the following files should be present:
|
||||||
|
|
||||||
* ``sprite.json``
|
* ``sprite.json``
|
||||||
* ``sprite.png``
|
* ``sprite.png``
|
||||||
|
|||||||
@@ -6,21 +6,25 @@ If you visit the server on the configured port (default 8080) you can see your m
|
|||||||
|
|
||||||
Styles
|
Styles
|
||||||
======
|
======
|
||||||
* Styles are served at ``/styles/{id}.json`` (+ array at ``/styles.json``)
|
* Styles are served at ``/styles/{id}/style.json`` (+ array at ``/styles.json``)
|
||||||
|
|
||||||
* Sprites at ``/styles/{id}/sprite[@2x].{format}``
|
* Sprites at ``/styles/{id}/sprite[@2x].{format}``
|
||||||
* Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf``
|
* Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf``
|
||||||
|
|
||||||
Rendered tiles
|
Rendered tiles
|
||||||
==============
|
==============
|
||||||
* Rendered tiles are served at ``/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}``
|
* Rendered tiles are served at ``/styles/{id}/{z}/{x}/{y}[@2x].{format}``
|
||||||
|
|
||||||
* The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles
|
* The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles
|
||||||
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
|
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
|
||||||
* TileJSON at ``/styles/{id}/rendered.json``
|
* TileJSON at ``/styles/{id}.json``
|
||||||
|
|
||||||
* The rendered tiles are not available in the ``tileserver-gl-light`` version.
|
* The rendered tiles are not available in the ``tileserver-gl-light`` version.
|
||||||
|
|
||||||
|
WMTS Capabilities
|
||||||
|
==============
|
||||||
|
* WMTS Capabilities are served at ``/styles/{id}/wmts.xml``
|
||||||
|
|
||||||
Static images
|
Static images
|
||||||
=============
|
=============
|
||||||
* Several endpoints:
|
* Several endpoints:
|
||||||
@@ -39,7 +43,7 @@ Static images
|
|||||||
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
||||||
* ``stroke`` - color of the path stroke
|
* ``stroke`` - color of the path stroke
|
||||||
* ``width`` - width of the 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"
|
* value of ``0.1`` means "add 10% size to each side to make sure the area of interest is nicely visible"
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ Docker
|
|||||||
|
|
||||||
When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
|
When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
|
||||||
|
|
||||||
Just run ``docker run -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:
|
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 ... maptiler/tileserver-gl --mbtiles 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 --verbose`` -- to see the default config created automatically
|
||||||
|
|
||||||
npm
|
npm
|
||||||
===
|
===
|
||||||
@@ -41,7 +41,7 @@ Alternatively, you can use ``tileserver-gl-light`` package instead, which is pur
|
|||||||
From source
|
From source
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Make sure you have Node v4 (nvm install 4) and run::
|
Make sure you have Node v10 (nvm install 10) and run::
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
node .
|
node .
|
||||||
|
|||||||
@@ -4,25 +4,35 @@ Usage
|
|||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
======
|
======
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
Usage: tileserver-gl [mbtiles] [options]
|
Usage: main.js tileserver-gl [mbtiles] [options]
|
||||||
|
|
||||||
mbtiles MBTiles file (uses demo configuration);
|
Options:
|
||||||
ignored if the configuration file is also specified
|
|
||||||
|
|
||||||
Options:
|
-h, --help output usage information
|
||||||
-c, --config Configuration file [config.json]
|
--mbtiles <file> MBTiles file (uses demo configuration);
|
||||||
-b, --bind Bind address
|
ignored if the configuration file is also specified
|
||||||
-p, --port Port [8080]
|
-c, --config <file> Configuration file [config.json]
|
||||||
-V, --verbose More verbose output
|
-b, --bind <address> Bind address
|
||||||
-v, --version Version info
|
-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 preview style and configuration
|
||||||
Default styles and configuration
|
|
||||||
======
|
======
|
||||||
|
|
||||||
- If no configuration file is specified, the default styles (compatible with openmaptiles) are used.
|
- 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), an extract is downloaded directly from https://openmaptiles.org/
|
- 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 the configuration
|
||||||
|
======
|
||||||
|
|
||||||
|
It is possible to reload the configuration file without restarting the whole process by sending a SIGHUP signal to the node process.
|
||||||
|
|
||||||
|
- The `docker kill -s HUP tileserver-gl` command can be used when running the tileserver-gl docker container.
|
||||||
|
- The `docker-compose -s HUP tileserver-gl-service-name` can be used when tileserver-gl is run as a docker-compose service.
|
||||||
|
|||||||
63
package.json
@@ -1,50 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "tileserver-gl",
|
"name": "tileserver-gl",
|
||||||
"version": "1.7.0",
|
"version": "3.1.1",
|
||||||
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
|
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"bin": "src/main.js",
|
"bin": "src/main.js",
|
||||||
"authors": [
|
|
||||||
"Petr Sloup <petr.sloup@klokantech.com>"
|
|
||||||
],
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/klokantech/tileserver-gl.git"
|
"url": "https://github.com/maptiler/tileserver-gl.git"
|
||||||
},
|
},
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.1 <5"
|
"node": ">=10 <11"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha test/**.js"
|
"test": "mocha test/**.js --timeout 10000",
|
||||||
|
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:80 $(docker build -q .)"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-native": "3.4.4",
|
"@mapbox/glyph-pbf-composite": "0.0.3",
|
||||||
"@mapbox/sphericalmercator": "1.0.5",
|
"@mapbox/mapbox-gl-native": "5.0.2",
|
||||||
"advanced-pool": "0.3.2",
|
"@mapbox/mapbox-gl-style-spec": "13.12.0",
|
||||||
"base64url": "2.0.0",
|
"@mapbox/mbtiles": "0.11.0",
|
||||||
"canvas": "1.6.5",
|
"@mapbox/sphericalmercator": "1.1.0",
|
||||||
"clone": "2.1.1",
|
"@mapbox/vector-tile": "1.3.1",
|
||||||
"color": "1.0.3",
|
"advanced-pool": "0.3.3",
|
||||||
"cors": "2.8.3",
|
"canvas": "2.6.1",
|
||||||
"express": "4.15.2",
|
"chokidar": "3.3.1",
|
||||||
"glyph-pbf-composite": "0.0.2",
|
"clone": "2.1.2",
|
||||||
"handlebars": "4.0.8",
|
"color": "3.1.2",
|
||||||
"http-shutdown": "^1.2.0",
|
"commander": "4.1.1",
|
||||||
"mbtiles": "0.9.0",
|
"cors": "2.8.5",
|
||||||
"morgan": "1.8.1",
|
"esm": "3.2.25",
|
||||||
"node-pngquant-native": "1.0.4",
|
"express": "4.17.1",
|
||||||
"nomnom": "1.8.1",
|
"handlebars": "4.7.3",
|
||||||
"pbf": "3.0.5",
|
"http-shutdown": "1.2.2",
|
||||||
"proj4": "2.4.3",
|
"morgan": "1.9.1",
|
||||||
"request": "2.81.0",
|
"pbf": "3.2.1",
|
||||||
"sharp": "0.17.3",
|
"proj4": "2.6.0",
|
||||||
"tileserver-gl-styles": "1.1.1",
|
"request": "2.88.2",
|
||||||
"vector-tile": "1.3.0"
|
"sharp": "0.26.2",
|
||||||
|
"tileserver-gl-styles": "2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"should": "^11.2.0",
|
"mocha": "^7.1.0",
|
||||||
"mocha": "^3.2.0",
|
"should": "^13.2.3",
|
||||||
"supertest": "^3.0.0"
|
"supertest": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 530 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
67
public/resources/images/maptiler-logo.svg
Normal 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 |
@@ -23,12 +23,15 @@ body{
|
|||||||
font-family:'OpenSans', sans-serif, Arial;
|
font-family:'OpenSans', sans-serif, Arial;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin:0;
|
margin:0;
|
||||||
|
background-repeat:no-repeat !important;
|
||||||
|
background-size: contain !important;
|
||||||
|
background-image: url(/images/header-map-1280px.png);
|
||||||
}
|
}
|
||||||
a{
|
a{
|
||||||
color: #499DCE;
|
color: #499DCE;
|
||||||
transition: color .2s;
|
transition: color .2s;
|
||||||
}
|
}
|
||||||
a:hover{
|
a:hover {
|
||||||
color: #395D73;
|
color: #395D73;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
@@ -47,7 +50,7 @@ a:hover{
|
|||||||
color: #499DCE;
|
color: #499DCE;
|
||||||
font-size:.8em;
|
font-size:.8em;
|
||||||
}
|
}
|
||||||
section{
|
section {
|
||||||
margin: 15px auto;
|
margin: 15px auto;
|
||||||
width: 930px;
|
width: 930px;
|
||||||
padding: 30px 0;
|
padding: 30px 0;
|
||||||
@@ -70,7 +73,7 @@ section{
|
|||||||
font-size:20px;
|
font-size:20px;
|
||||||
background:#fff;
|
background:#fff;
|
||||||
}
|
}
|
||||||
.item{
|
.item {
|
||||||
background:#fff;
|
background:#fff;
|
||||||
height: 191px;
|
height: 191px;
|
||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
@@ -79,8 +82,9 @@ section{
|
|||||||
.item:nth-child(odd) {
|
.item:nth-child(odd) {
|
||||||
background-color:#fbfbfb;
|
background-color:#fbfbfb;
|
||||||
}
|
}
|
||||||
.item img{
|
.item img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: block;
|
||||||
margin: 30px;
|
margin: 30px;
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
@@ -122,10 +126,10 @@ section{
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.btn:first-child:hover{
|
.btn:first-child:hover {
|
||||||
background: #395D73;
|
background: #395D73;
|
||||||
}
|
}
|
||||||
footer{
|
footer {
|
||||||
width:100%;
|
width:100%;
|
||||||
border-top:1px solid #ededed;
|
border-top:1px solid #ededed;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
@@ -133,9 +137,9 @@ footer{
|
|||||||
padding-top:10px;
|
padding-top:10px;
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
}
|
}
|
||||||
footer img{
|
footer img {
|
||||||
width: 118px;
|
width: 113px;
|
||||||
height: 32px;
|
height: 31px;
|
||||||
}
|
}
|
||||||
footer .t {
|
footer .t {
|
||||||
display:none;
|
display:none;
|
||||||
@@ -147,27 +151,9 @@ footer a {
|
|||||||
color: #787878;
|
color: #787878;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.details h3, .identifier {
|
||||||
/* body background image */
|
max-width: 550px;
|
||||||
body {
|
word-break: break-all;
|
||||||
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 */
|
/* Responsive */
|
||||||
@@ -185,31 +171,31 @@ body {
|
|||||||
.title.light:after {
|
.title.light:after {
|
||||||
font-size:.6em;
|
font-size:.6em;
|
||||||
}
|
}
|
||||||
.title img{
|
.title img {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
.subtitle{
|
.subtitle {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 0 0 35px 0;
|
margin: 0 0 35px 0;
|
||||||
}
|
}
|
||||||
.item{
|
.item {
|
||||||
height: 245px;
|
height: 245px;
|
||||||
}
|
}
|
||||||
.viewers{
|
.viewers {
|
||||||
float: left;
|
float: left;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
.viewers a{
|
.viewers a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.btn{
|
.btn {
|
||||||
margin: 0 20px 0 0;
|
margin: 0 20px 0 0;
|
||||||
}
|
}
|
||||||
.btn:first-child{
|
.btn:first-child {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{name}} - TileServer GL</title>
|
<title>{{name}} - TileServer GL</title>
|
||||||
{{#is_vector}}
|
{{#is_vector}}
|
||||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl.css{{&key_query}}" />
|
||||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl-inspect.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl-inspect.css{{&key_query}}" />
|
||||||
<script src="/mapbox-gl.js{{&key_query}}"></script>
|
<script src="{{public_url}}mapbox-gl.js{{&key_query}}"></script>
|
||||||
<script src="/mapbox-gl-inspect.min.js{{&key_query}}"></script>
|
<script src="{{public_url}}mapbox-gl-inspect.min.js{{&key_query}}"></script>
|
||||||
<style>
|
<style>
|
||||||
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
||||||
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
</style>
|
</style>
|
||||||
{{/is_vector}}
|
{{/is_vector}}
|
||||||
{{^is_vector}}
|
{{^is_vector}}
|
||||||
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox.css{{&key_query}}" />
|
||||||
<script src="/mapbox.js{{&key_query}}"></script>
|
<script src="{{public_url}}mapbox.js{{&key_query}}"></script>
|
||||||
<script src="/leaflet-hash.js{{&key_query}}"></script>
|
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
||||||
<style>
|
<style>
|
||||||
body { margin:0; padding:0; }
|
body { margin:0; padding:0; }
|
||||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
sources: {
|
sources: {
|
||||||
'vector_layer_': {
|
'vector_layer_': {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
url: '/data/{{id}}.json{{&key_query}}'
|
url: '{{public_url}}data/{{id}}.json{{&key_query}}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layers: []
|
layers: []
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<h1 style="display:none;">{{name}}</h1>
|
<h1 style="display:none;">{{name}}</h1>
|
||||||
<div id='map'></div>
|
<div id='map'></div>
|
||||||
<script>
|
<script>
|
||||||
var map = L.mapbox.map('map', '/data/{{id}}.json{{&key_query}}', { zoomControl: false });
|
var map = L.mapbox.map('map', '{{public_url}}data/{{id}}.json{{&key_query}}', { zoomControl: false });
|
||||||
map.eachLayer(function(layer) {
|
map.eachLayer(function(layer) {
|
||||||
// do not add scale prefix even if retina display is detected
|
// do not add scale prefix even if retina display is detected
|
||||||
layer.scalePrefix = '.';
|
layer.scalePrefix = '.';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>TileServer GL - Server for vector and raster maps with GL styles</title>
|
<title>TileServer GL - Server for vector and raster maps with GL styles</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/index.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}index.css{{&key_query}}" />
|
||||||
<script>
|
<script>
|
||||||
function toggle_xyz(id) {
|
function toggle_xyz(id) {
|
||||||
var el = document.getElementById(id);
|
var el = document.getElementById(id);
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section>
|
<section>
|
||||||
<h1 class="title {{#if is_light}}light{{/if}}"><img src="/images/logo.png" alt="TileServer GL" /></h1>
|
<h1 class="title {{#if is_light}}light{{/if}}"><img src="{{public_url}}images/logo.png" alt="TileServer GL" /></h1>
|
||||||
<h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
|
<h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
|
||||||
{{#if styles}}
|
{{#if styles}}
|
||||||
<h2 class="box-header">Styles</h2>
|
<h2 class="box-header">Styles</h2>
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
{{#each styles}}
|
{{#each styles}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{#if thumbnail}}
|
{{#if thumbnail}}
|
||||||
<img src="/styles/{{@key}}/rendered/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
<img src="{{public_url}}styles/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
||||||
{{else}}
|
{{else}}
|
||||||
<img src="/images/placeholder.png" alt="{{name}} preview" />
|
<img src="{{public_url}}images/placeholder.png" alt="{{name}} preview" />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<h3>{{name}}</h3>
|
<h3>{{name}}</h3>
|
||||||
@@ -35,13 +35,13 @@
|
|||||||
<p class="services">
|
<p class="services">
|
||||||
services:
|
services:
|
||||||
{{#if serving_data}}
|
{{#if serving_data}}
|
||||||
<a href="/styles/{{@key}}.json{{&../key_query}}">GL Style</a>
|
<a href="{{public_url}}styles/{{@key}}/style.json{{&../key_query}}">GL Style</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if serving_rendered}}
|
{{#if serving_rendered}}
|
||||||
{{#if serving_data}}| {{/if}}<a href="/styles/{{@key}}/rendered.json{{&../key_query}}">TileJSON</a>
|
{{#if serving_data}}| {{/if}}<a href="{{public_url}}styles/{{@key}}.json{{&../key_query}}">TileJSON</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if wmts_link}}
|
{{#if serving_rendered}}
|
||||||
| <a href="{{&wmts_link}}">WMTS</a>
|
| <a href="/styles/{{@key}}/wmts.xml{{&../key_query}}">WMTS</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if xyz_link}}
|
{{#if xyz_link}}
|
||||||
| <a href="#" onclick="return toggle_xyz('xyz_style_{{@key}}');">XYZ</a>
|
| <a href="#" onclick="return toggle_xyz('xyz_style_{{@key}}');">XYZ</a>
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
<div class="viewers">
|
<div class="viewers">
|
||||||
{{#if serving_data}}
|
{{#if serving_data}}
|
||||||
{{#if serving_rendered}}
|
{{#if serving_rendered}}
|
||||||
<a class="btn" href="/styles/{{@key}}/{{&../key_query}}{{viewer_hash}}">Viewer</a>
|
<a class="btn" href="{{public_url}}styles/{{@key}}/{{&../key_query}}{{viewer_hash}}">Viewer</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if serving_rendered}}
|
{{#if serving_rendered}}
|
||||||
<a class="btn" href="/styles/{{@key}}/?{{&../key_query_part}}raster{{viewer_hash}}">Raster</a>
|
<a class="btn" href="{{public_url}}styles/{{@key}}/?{{&../key_query_part}}raster{{viewer_hash}}">Raster</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if serving_data}}
|
{{#if serving_data}}
|
||||||
<a class="btn" href="/styles/{{@key}}/?{{&../key_query_part}}vector{{viewer_hash}}">Vector</a>
|
<a class="btn" href="{{public_url}}styles/{{@key}}/?{{&../key_query_part}}vector{{viewer_hash}}">Vector</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,15 +72,15 @@
|
|||||||
{{#each data}}
|
{{#each data}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
{{#if thumbnail}}
|
{{#if thumbnail}}
|
||||||
<img src="/data/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
<img src="{{public_url}}data/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
||||||
{{else}}
|
{{else}}
|
||||||
<img src="/images/placeholder.png" alt="{{name}} preview" />
|
<img src="{{public_url}}images/placeholder.png" alt="{{name}} preview" />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<h3>{{name}}</h3>
|
<h3>{{name}}</h3>
|
||||||
<p class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}} | type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data</p>
|
<p class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}} | type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data</p>
|
||||||
<p class="services">
|
<p class="services">
|
||||||
services: <a href="/data/{{@key}}.json{{&../key_query}}">TileJSON</a>
|
services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a>
|
||||||
{{#if wmts_link}}
|
{{#if wmts_link}}
|
||||||
| <a href="{{&wmts_link}}">WMTS</a>
|
| <a href="{{&wmts_link}}">WMTS</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -92,10 +92,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="viewers">
|
<div class="viewers">
|
||||||
{{#is_vector}}
|
{{#is_vector}}
|
||||||
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Inspect</a>
|
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Inspect</a>
|
||||||
{{/is_vector}}
|
{{/is_vector}}
|
||||||
{{^is_vector}}
|
{{^is_vector}}
|
||||||
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
|
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
|
||||||
{{/is_vector}}
|
{{/is_vector}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,10 +104,10 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<a href="https://www.klokantech.com/" target="_blank"><img src="/images/klokantech.png" /></a>
|
<a href="https://www.maptiler.com/" target="_blank"><img src="{{public_url}}images/maptiler-logo.svg" /></a>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{name}} - TileServer GL</title>
|
<title>{{name}} - TileServer GL</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl.css{{&key_query}}" />
|
||||||
<script src="/mapbox-gl.js{{&key_query}}"></script>
|
<script src="{{public_url}}mapbox-gl.js{{&key_query}}"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&key_query}}" />
|
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox.css{{&key_query}}" />
|
||||||
<script src="/mapbox.js{{&key_query}}"></script>
|
<script src="{{public_url}}mapbox.js{{&key_query}}"></script>
|
||||||
<script src="/leaflet-hash.js{{&key_query}}"></script>
|
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
||||||
<style>
|
<style>
|
||||||
body { margin:0; padding:0; }
|
body { margin:0; padding:0; }
|
||||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||||
@@ -24,15 +24,15 @@
|
|||||||
(q.indexOf('raster') >= 0 ? 'raster' :
|
(q.indexOf('raster') >= 0 ? 'raster' :
|
||||||
(mapboxgl.supported() ? 'vector' : 'raster'));
|
(mapboxgl.supported() ? 'vector' : 'raster'));
|
||||||
if (preference == 'vector') {
|
if (preference == 'vector') {
|
||||||
mapboxgl.setRTLTextPlugin('/mapbox-gl-rtl-text.js{{&key_query}}');
|
mapboxgl.setRTLTextPlugin('{{public_url}}mapbox-gl-rtl-text.js{{&key_query}}');
|
||||||
var map = new mapboxgl.Map({
|
var map = new mapboxgl.Map({
|
||||||
container: 'map',
|
container: 'map',
|
||||||
style: '/styles/{{id}}.json{{&key_query}}',
|
style: '{{public_url}}styles/{{id}}/style.json{{&key_query}}',
|
||||||
hash: true
|
hash: true
|
||||||
});
|
});
|
||||||
map.addControl(new mapboxgl.NavigationControl());
|
map.addControl(new mapboxgl.NavigationControl());
|
||||||
} else {
|
} else {
|
||||||
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&key_query}}', { zoomControl: false });
|
var map = L.mapbox.map('map', '{{public_url}}styles/{{id}}.json{{&key_query}}', { zoomControl: false });
|
||||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
new L.Hash(map);
|
new L.Hash(map);
|
||||||
|
|||||||
407
public/templates/wmts.tmpl
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
|
||||||
|
<!-- Service Identification -->
|
||||||
|
<ows:ServiceIdentification>
|
||||||
|
<ows:Title>TileServer GL</ows:Title>
|
||||||
|
<ows:ServiceType>OGC WMTS</ows:ServiceType>
|
||||||
|
<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
|
||||||
|
</ows:ServiceIdentification>
|
||||||
|
<!-- Operations Metadata -->
|
||||||
|
<ows:OperationsMetadata>
|
||||||
|
<ows:Operation name="GetCapabilities">
|
||||||
|
<ows:DCP>
|
||||||
|
<ows:HTTP>
|
||||||
|
<ows:Get xlink:href="{{baseUrl}}/wmts/{{id}}/">
|
||||||
|
<ows:Constraint name="GetEncoding">
|
||||||
|
<ows:AllowedValues>
|
||||||
|
<ows:Value>RESTful</ows:Value>
|
||||||
|
</ows:AllowedValues>
|
||||||
|
</ows:Constraint>
|
||||||
|
</ows:Get>
|
||||||
|
</ows:HTTP>
|
||||||
|
</ows:DCP>
|
||||||
|
</ows:Operation>
|
||||||
|
<ows:Operation name="GetTile">
|
||||||
|
<ows:DCP>
|
||||||
|
<ows:HTTP>
|
||||||
|
<ows:Get xlink:href="{{baseUrl}}/styles/">
|
||||||
|
<ows:Constraint name="GetEncoding">
|
||||||
|
<ows:AllowedValues>
|
||||||
|
<ows:Value>RESTful</ows:Value>
|
||||||
|
</ows:AllowedValues>
|
||||||
|
</ows:Constraint>
|
||||||
|
</ows:Get>
|
||||||
|
</ows:HTTP>
|
||||||
|
</ows:DCP>
|
||||||
|
</ows:Operation>
|
||||||
|
</ows:OperationsMetadata>
|
||||||
|
<Contents>
|
||||||
|
<Layer>
|
||||||
|
<ows:Title>{{name}}</ows:Title>
|
||||||
|
<ows:Identifier>{{id}}</ows:Identifier>
|
||||||
|
<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
|
||||||
|
<ows:LowerCorner>-180 -85.051128779807</ows:LowerCorner>
|
||||||
|
<ows:UpperCorner>180 85.051128779807</ows:UpperCorner>
|
||||||
|
</ows:WGS84BoundingBox>
|
||||||
|
<Style isDefault="true">
|
||||||
|
<ows:Identifier>default</ows:Identifier>
|
||||||
|
</Style>
|
||||||
|
<Format>image/png</Format>
|
||||||
|
<TileMatrixSetLink>
|
||||||
|
<TileMatrixSet>GoogleMapsCompatible</TileMatrixSet>
|
||||||
|
</TileMatrixSetLink>
|
||||||
|
<ResourceURL format="image/png" resourceType="tile" template="{{baseUrl}}/styles/{{id}}/{TileMatrix}/{TileCol}/{TileRow}.png{{key_query}}"/>
|
||||||
|
</Layer><TileMatrixSet>
|
||||||
|
<ows:Title>GoogleMapsCompatible</ows:Title>
|
||||||
|
<ows:Abstract>GoogleMapsCompatible EPSG:3857</ows:Abstract>
|
||||||
|
<ows:Identifier>GoogleMapsCompatible</ows:Identifier>
|
||||||
|
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::3857</ows:SupportedCRS>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>0</ows:Identifier>
|
||||||
|
<ScaleDenominator>559082264.02872</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>1</MatrixWidth>
|
||||||
|
<MatrixHeight>1</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>1</ows:Identifier>
|
||||||
|
<ScaleDenominator>279541132.01436</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>2</MatrixWidth>
|
||||||
|
<MatrixHeight>2</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>2</ows:Identifier>
|
||||||
|
<ScaleDenominator>139770566.00718</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>4</MatrixWidth>
|
||||||
|
<MatrixHeight>4</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>3</ows:Identifier>
|
||||||
|
<ScaleDenominator>69885283.00359</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>8</MatrixWidth>
|
||||||
|
<MatrixHeight>8</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>4</ows:Identifier>
|
||||||
|
<ScaleDenominator>34942641.501795</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>16</MatrixWidth>
|
||||||
|
<MatrixHeight>16</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>5</ows:Identifier>
|
||||||
|
<ScaleDenominator>17471320.750897</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>32</MatrixWidth>
|
||||||
|
<MatrixHeight>32</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>6</ows:Identifier>
|
||||||
|
<ScaleDenominator>8735660.3754487</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>64</MatrixWidth>
|
||||||
|
<MatrixHeight>64</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>7</ows:Identifier>
|
||||||
|
<ScaleDenominator>4367830.1877244</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>128</MatrixWidth>
|
||||||
|
<MatrixHeight>128</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>8</ows:Identifier>
|
||||||
|
<ScaleDenominator>2183915.0938622</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>256</MatrixWidth>
|
||||||
|
<MatrixHeight>256</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>9</ows:Identifier>
|
||||||
|
<ScaleDenominator>1091957.5469311</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>512</MatrixWidth>
|
||||||
|
<MatrixHeight>512</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>10</ows:Identifier>
|
||||||
|
<ScaleDenominator>545978.77346554</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>1024</MatrixWidth>
|
||||||
|
<MatrixHeight>1024</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>11</ows:Identifier>
|
||||||
|
<ScaleDenominator>272989.38673277</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>2048</MatrixWidth>
|
||||||
|
<MatrixHeight>2048</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>12</ows:Identifier>
|
||||||
|
<ScaleDenominator>136494.69336639</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>4096</MatrixWidth>
|
||||||
|
<MatrixHeight>4096</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>13</ows:Identifier>
|
||||||
|
<ScaleDenominator>68247.346683193</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>8192</MatrixWidth>
|
||||||
|
<MatrixHeight>8192</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>14</ows:Identifier>
|
||||||
|
<ScaleDenominator>34123.673341597</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>16384</MatrixWidth>
|
||||||
|
<MatrixHeight>16384</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>15</ows:Identifier>
|
||||||
|
<ScaleDenominator>17061.836670798</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>32768</MatrixWidth>
|
||||||
|
<MatrixHeight>32768</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>16</ows:Identifier>
|
||||||
|
<ScaleDenominator>8530.9183353991</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>65536</MatrixWidth>
|
||||||
|
<MatrixHeight>65536</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>17</ows:Identifier>
|
||||||
|
<ScaleDenominator>4265.4591676996</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>131072</MatrixWidth>
|
||||||
|
<MatrixHeight>131072</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>18</ows:Identifier>
|
||||||
|
<ScaleDenominator>2132.7295838498</ScaleDenominator>
|
||||||
|
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>262144</MatrixWidth>
|
||||||
|
<MatrixHeight>262144</MatrixHeight>
|
||||||
|
</TileMatrix></TileMatrixSet><TileMatrixSet>
|
||||||
|
<ows:Title>WGS84</ows:Title>
|
||||||
|
<ows:Abstract>WGS84 EPSG:4326</ows:Abstract>
|
||||||
|
<ows:Identifier>WGS84</ows:Identifier>
|
||||||
|
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>0</ows:Identifier>
|
||||||
|
<ScaleDenominator>279541132.01436</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>2</MatrixWidth>
|
||||||
|
<MatrixHeight>1</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>1</ows:Identifier>
|
||||||
|
<ScaleDenominator>139770566.00718</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>4</MatrixWidth>
|
||||||
|
<MatrixHeight>2</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>2</ows:Identifier>
|
||||||
|
<ScaleDenominator>69885283.00359</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>8</MatrixWidth>
|
||||||
|
<MatrixHeight>4</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>3</ows:Identifier>
|
||||||
|
<ScaleDenominator>34942641.501795</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>16</MatrixWidth>
|
||||||
|
<MatrixHeight>8</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>4</ows:Identifier>
|
||||||
|
<ScaleDenominator>17471320.750897</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>32</MatrixWidth>
|
||||||
|
<MatrixHeight>16</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>5</ows:Identifier>
|
||||||
|
<ScaleDenominator>8735660.3754487</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>64</MatrixWidth>
|
||||||
|
<MatrixHeight>32</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>6</ows:Identifier>
|
||||||
|
<ScaleDenominator>4367830.1877244</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>128</MatrixWidth>
|
||||||
|
<MatrixHeight>64</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>7</ows:Identifier>
|
||||||
|
<ScaleDenominator>2183915.0938622</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>256</MatrixWidth>
|
||||||
|
<MatrixHeight>128</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>8</ows:Identifier>
|
||||||
|
<ScaleDenominator>1091957.5469311</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>512</MatrixWidth>
|
||||||
|
<MatrixHeight>256</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>9</ows:Identifier>
|
||||||
|
<ScaleDenominator>545978.77346554</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>1024</MatrixWidth>
|
||||||
|
<MatrixHeight>512</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>10</ows:Identifier>
|
||||||
|
<ScaleDenominator>272989.38673277</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>2048</MatrixWidth>
|
||||||
|
<MatrixHeight>1024</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>11</ows:Identifier>
|
||||||
|
<ScaleDenominator>136494.69336639</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>4096</MatrixWidth>
|
||||||
|
<MatrixHeight>2048</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>12</ows:Identifier>
|
||||||
|
<ScaleDenominator>68247.346683193</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>8192</MatrixWidth>
|
||||||
|
<MatrixHeight>4096</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>13</ows:Identifier>
|
||||||
|
<ScaleDenominator>34123.673341597</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>16384</MatrixWidth>
|
||||||
|
<MatrixHeight>8192</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>14</ows:Identifier>
|
||||||
|
<ScaleDenominator>17061.836670798</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>32768</MatrixWidth>
|
||||||
|
<MatrixHeight>16384</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>15</ows:Identifier>
|
||||||
|
<ScaleDenominator>8530.9183353991</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>65536</MatrixWidth>
|
||||||
|
<MatrixHeight>32768</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>16</ows:Identifier>
|
||||||
|
<ScaleDenominator>4265.4591676996</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>131072</MatrixWidth>
|
||||||
|
<MatrixHeight>65536</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>17</ows:Identifier>
|
||||||
|
<ScaleDenominator>2132.7295838498</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>262144</MatrixWidth>
|
||||||
|
<MatrixHeight>131072</MatrixHeight>
|
||||||
|
</TileMatrix>
|
||||||
|
<TileMatrix>
|
||||||
|
<ows:Identifier>18</ows:Identifier>
|
||||||
|
<ScaleDenominator>1066.3647919249</ScaleDenominator>
|
||||||
|
<TopLeftCorner>90 -180</TopLeftCorner>
|
||||||
|
<TileWidth>256</TileWidth>
|
||||||
|
<TileHeight>256</TileHeight>
|
||||||
|
<MatrixWidth>524288</MatrixWidth>
|
||||||
|
<MatrixHeight>262144</MatrixHeight>
|
||||||
|
</TileMatrix></TileMatrixSet>
|
||||||
|
</Contents>
|
||||||
|
<ServiceMetadataURL xlink:href="{{baseUrl}}/wmts/{{id}}/"/>
|
||||||
|
</Capabilities>
|
||||||
@@ -23,17 +23,23 @@ packageJson.name += '-light';
|
|||||||
packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles';
|
packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles';
|
||||||
delete packageJson.dependencies['canvas'];
|
delete packageJson.dependencies['canvas'];
|
||||||
delete packageJson.dependencies['@mapbox/mapbox-gl-native'];
|
delete packageJson.dependencies['@mapbox/mapbox-gl-native'];
|
||||||
delete packageJson.dependencies['node-pngquant-native'];
|
|
||||||
delete packageJson.dependencies['sharp'];
|
delete packageJson.dependencies['sharp'];
|
||||||
|
|
||||||
delete packageJson.optionalDependencies;
|
delete packageJson.optionalDependencies;
|
||||||
delete packageJson.devDependencies;
|
delete packageJson.devDependencies;
|
||||||
|
|
||||||
|
packageJson.engines.node = '>= 10';
|
||||||
|
|
||||||
var str = JSON.stringify(packageJson, undefined, 2);
|
var str = JSON.stringify(packageJson, undefined, 2);
|
||||||
fs.writeFileSync('light/package.json', str);
|
fs.writeFileSync('light/package.json', str);
|
||||||
fs.renameSync('light/README_light.md', 'light/README.md');
|
fs.renameSync('light/README_light.md', 'light/README.md');
|
||||||
fs.renameSync('light/Dockerfile_light', 'light/Dockerfile');
|
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 */
|
/* PUBLISH */
|
||||||
|
|
||||||
// tileserver-gl
|
// tileserver-gl
|
||||||
|
|||||||
36
run.sh
@@ -1,3 +1,37 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
_term() {
|
||||||
|
echo "Caught signal, stopping gracefully"
|
||||||
|
kill -TERM "$child" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
trap _term SIGTERM
|
||||||
|
trap _term SIGINT
|
||||||
|
|
||||||
|
xvfbMaxStartWaitTime=60
|
||||||
|
displayNumber=99
|
||||||
|
screenNumber=0
|
||||||
|
|
||||||
|
# Delete files if they were not cleaned by last run
|
||||||
|
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 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."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DISPLAY=:${displayNumber}.${screenNumber}
|
||||||
|
|
||||||
|
echo
|
||||||
cd /data
|
cd /data
|
||||||
xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/ -p 80 "$@"
|
node /usr/src/app/ -p 80 "$@" &
|
||||||
|
child=$!
|
||||||
|
wait "$child"
|
||||||
|
|
||||||
|
start-stop-daemon --stop --retry 5 --pidfile ~/xvfb.pid # stop xvfb when exiting
|
||||||
|
rm ~/xvfb.pid
|
||||||
|
|||||||
236
src/main.js
@@ -2,90 +2,125 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs'),
|
require = require('esm')(module);
|
||||||
path = require('path'),
|
|
||||||
request = require('request');
|
|
||||||
|
|
||||||
var mbtiles = require('mbtiles');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const request = require('request');
|
||||||
|
|
||||||
var packageJson = require('../package');
|
const MBTiles = require('@mapbox/mbtiles');
|
||||||
|
|
||||||
var opts = require('nomnom')
|
const packageJson = require('../package');
|
||||||
.option('mbtiles', {
|
|
||||||
default: undefined,
|
|
||||||
help: 'MBTiles file (uses demo configuration);\n' +
|
|
||||||
'\t ignored if the configuration file is also specified',
|
|
||||||
position: 0
|
|
||||||
})
|
|
||||||
.option('config', {
|
|
||||||
abbr: 'c',
|
|
||||||
default: 'config.json',
|
|
||||||
help: 'Configuration file'
|
|
||||||
})
|
|
||||||
.option('bind', {
|
|
||||||
abbr: 'b',
|
|
||||||
default: undefined,
|
|
||||||
help: 'Bind address'
|
|
||||||
})
|
|
||||||
.option('port', {
|
|
||||||
abbr: 'p',
|
|
||||||
default: 8080,
|
|
||||||
help: 'Port'
|
|
||||||
})
|
|
||||||
.option('cors', {
|
|
||||||
default: true,
|
|
||||||
help: 'Enable Cross-origin resource sharing headers'
|
|
||||||
})
|
|
||||||
.option('verbose', {
|
|
||||||
abbr: 'V',
|
|
||||||
flag: true,
|
|
||||||
help: 'More verbose output'
|
|
||||||
})
|
|
||||||
.option('version', {
|
|
||||||
abbr: 'v',
|
|
||||||
flag: true,
|
|
||||||
help: 'Version info',
|
|
||||||
callback: function() {
|
|
||||||
return packageJson.name + ' v' + packageJson.version;
|
|
||||||
}
|
|
||||||
}).parse();
|
|
||||||
|
|
||||||
|
const args = process.argv;
|
||||||
|
if (args.length >= 3 && args[2][0] !== '-') {
|
||||||
|
args.splice(2, 0, '--mbtiles');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Starting ' + packageJson.name + ' v' + packageJson.version);
|
const opts = require('commander')
|
||||||
|
.description('tileserver-gl startup options')
|
||||||
|
.usage('tileserver-gl [mbtiles] [options]')
|
||||||
|
.option(
|
||||||
|
'--mbtiles <file>',
|
||||||
|
'MBTiles file (uses demo configuration);\n' +
|
||||||
|
'\t ignored if the configuration file is also specified'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-c, --config <file>',
|
||||||
|
'Configuration file [config.json]',
|
||||||
|
'config.json'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-b, --bind <address>',
|
||||||
|
'Bind address'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-p, --port <port>',
|
||||||
|
'Port [8080]',
|
||||||
|
8080,
|
||||||
|
parseInt
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-C|--no-cors',
|
||||||
|
'Disable Cross-origin resource sharing headers'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-u|--public_url <url>',
|
||||||
|
'Enable exposing the server on subpaths, not necessarily the root of the domain'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-V, --verbose',
|
||||||
|
'More verbose output'
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'-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);
|
||||||
|
|
||||||
var startServer = function(configPath, config) {
|
console.log(`Starting ${packageJson.name} v${packageJson.version}`);
|
||||||
|
|
||||||
|
const startServer = (configPath, config) => {
|
||||||
|
let publicUrl = opts.public_url;
|
||||||
|
if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) {
|
||||||
|
publicUrl += '/';
|
||||||
|
}
|
||||||
return require('./server')({
|
return require('./server')({
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
config: config,
|
config: config,
|
||||||
bind: opts.bind,
|
bind: opts.bind,
|
||||||
port: opts.port,
|
port: opts.port,
|
||||||
cors: opts.cors
|
cors: opts.cors,
|
||||||
|
verbose: opts.verbose,
|
||||||
|
silent: opts.silent,
|
||||||
|
logFile: opts.log_file,
|
||||||
|
logFormat: opts.log_format,
|
||||||
|
publicUrl: publicUrl
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var startWithMBTiles = function(mbtilesFile) {
|
const startWithMBTiles = (mbtilesFile) => {
|
||||||
console.log('Automatically creating config file for ' + 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);
|
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
|
||||||
|
|
||||||
var mbtilesStats = fs.statSync(mbtilesFile);
|
const mbtilesStats = fs.statSync(mbtilesFile);
|
||||||
if (!mbtilesStats.isFile() || mbtilesStats.size === 0) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
var instance = new mbtiles(mbtilesFile, function(err) {
|
const instance = new MBTiles(mbtilesFile, (err) => {
|
||||||
instance.getInfo(function(err, info) {
|
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) {
|
if (err || !info) {
|
||||||
console.log('ERROR: Metadata missing in the MBTiles.');
|
console.log('ERROR: Metadata missing in the MBTiles.');
|
||||||
console.log(' Make sure ' + path.basename(mbtilesFile) +
|
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
||||||
' is valid MBTiles.');
|
|
||||||
process.exit(1);
|
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": {
|
"options": {
|
||||||
"paths": {
|
"paths": {
|
||||||
"root": styleDir,
|
"root": styleDir,
|
||||||
@@ -98,59 +133,32 @@ var startWithMBTiles = function(mbtilesFile) {
|
|||||||
"data": {}
|
"data": {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (info.format == 'pbf' &&
|
if (info.format === 'pbf' &&
|
||||||
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
|
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
|
||||||
var omtV = (info.version || '').split('.');
|
|
||||||
|
|
||||||
config['data']['v' + omtV[0]] = {
|
config['data'][`v3`] = {
|
||||||
"mbtiles": path.basename(mbtilesFile)
|
"mbtiles": path.basename(mbtilesFile)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
||||||
for (var i = 0; i < styles.length; i++) {
|
for (let styleName of styles) {
|
||||||
var styleName = styles[i];
|
const styleFileRel = styleName + '/style.json';
|
||||||
var styleFileRel = styleName + '/style.json';
|
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
||||||
var styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
|
||||||
if (fs.existsSync(styleFile)) {
|
if (fs.existsSync(styleFile)) {
|
||||||
var styleJSON = require(styleFile);
|
config['styles'][styleName] = {
|
||||||
var omtVersionCompatibility =
|
"style": styleFileRel,
|
||||||
((styleJSON || {}).metadata || {})['openmaptiles:version'] || 'x';
|
"tilejson": {
|
||||||
var m = omtVersionCompatibility.toLowerCase().split('.');
|
"bounds": bounds
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('WARN: MBTiles not in "openmaptiles" format. ' +
|
console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`);
|
||||||
'Serving raw data only...');
|
|
||||||
config['data'][(info.id || 'mbtiles')
|
config['data'][(info.id || 'mbtiles')
|
||||||
.replace(/\//g, '_')
|
.replace(/\//g, '_')
|
||||||
.replace(/\:/g, '_')
|
.replace(/:/g, '_')
|
||||||
.replace(/\?/g, '_')] = {
|
.replace(/\?/g, '_')] = {
|
||||||
"mbtiles": path.basename(mbtilesFile)
|
"mbtiles": path.basename(mbtilesFile)
|
||||||
};
|
};
|
||||||
@@ -167,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) {
|
if (err || !stats.isFile() || stats.size === 0) {
|
||||||
var mbtiles = opts.mbtiles;
|
let mbtiles = opts.mbtiles;
|
||||||
if (!mbtiles) {
|
if (!mbtiles) {
|
||||||
// try to find in the cwd
|
// try to find in the cwd
|
||||||
var files = fs.readdirSync(process.cwd());
|
const files = fs.readdirSync(process.cwd());
|
||||||
for (var i=0; i < files.length; i++) {
|
for (let filename of files) {
|
||||||
var filename = files[i];
|
|
||||||
if (filename.endsWith('.mbtiles')) {
|
if (filename.endsWith('.mbtiles')) {
|
||||||
var mbTilesStats = fs.statSync(filename);
|
const mbTilesStats = fs.statSync(filename);
|
||||||
if (mbTilesStats.isFile() && mbTilesStats.size > 0) {
|
if (mbTilesStats.isFile() && mbTilesStats.size > 0) {
|
||||||
mbtiles = filename;
|
mbtiles = filename;
|
||||||
break;
|
break;
|
||||||
@@ -184,16 +191,15 @@ fs.stat(path.resolve(opts.config), function(err, stats) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mbtiles) {
|
if (mbtiles) {
|
||||||
console.log('No MBTiles specified, using ' + mbtiles);
|
console.log(`No MBTiles specified, using ${mbtiles}`);
|
||||||
return startWithMBTiles(mbtiles);
|
return startWithMBTiles(mbtiles);
|
||||||
} else {
|
} else {
|
||||||
var url = 'https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
const url = 'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
||||||
var filename = 'zurich_switzerland.mbtiles';
|
const filename = 'zurich_switzerland.mbtiles';
|
||||||
var stream = fs.createWriteStream(filename);
|
const stream = fs.createWriteStream(filename);
|
||||||
console.log('Downloading sample data (' + filename + ') from ' + url);
|
console.log(`No MBTiles found`);
|
||||||
stream.on('finish', function() {
|
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
|
||||||
return startWithMBTiles(filename);
|
stream.on('finish', () => startWithMBTiles(filename));
|
||||||
});
|
|
||||||
return request.get(url).pipe(stream);
|
return request.get(url).pipe(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +207,7 @@ fs.stat(path.resolve(opts.config), function(err, stats) {
|
|||||||
return startWithMBTiles(mbtiles);
|
return startWithMBTiles(mbtiles);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Using specified config file from ' + opts.config);
|
console.log(`Using specified config file from ${opts.config}`);
|
||||||
return startServer(opts.config, null);
|
return startServer(opts.config, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,173 +1,171 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs'),
|
const fs = require('fs');
|
||||||
path = require('path'),
|
const path = require('path');
|
||||||
zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
|
|
||||||
var clone = require('clone'),
|
const clone = require('clone');
|
||||||
express = require('express'),
|
const express = require('express');
|
||||||
mbtiles = require('mbtiles'),
|
const MBTiles = require('@mapbox/mbtiles');
|
||||||
pbf = require('pbf'),
|
const Pbf = require('pbf');
|
||||||
VectorTile = require('vector-tile').VectorTile;
|
const VectorTile = require('@mapbox/vector-tile').VectorTile;
|
||||||
|
|
||||||
var tileshrinkGl;
|
const utils = require('./utils');
|
||||||
try {
|
|
||||||
tileshrinkGl = require('tileshrink-gl');
|
|
||||||
global.addStyleParam = true;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
var utils = require('./utils');
|
module.exports = {
|
||||||
|
init: (options, repo) => {
|
||||||
|
const app = express().disable('x-powered-by');
|
||||||
|
|
||||||
module.exports = function(options, repo, params, id, styles) {
|
app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
|
||||||
var app = express().disable('x-powered-by');
|
const item = repo[req.params.id];
|
||||||
|
if (!item) {
|
||||||
var mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
|
return res.sendStatus(404);
|
||||||
var tileJSON = {
|
}
|
||||||
'tiles': params.domains || options.domains
|
let tileJSONFormat = item.tileJSON.format;
|
||||||
};
|
const z = req.params.z | 0;
|
||||||
|
const x = req.params.x | 0;
|
||||||
var shrinkers = {};
|
const y = req.params.y | 0;
|
||||||
|
let format = req.params.format;
|
||||||
repo[id] = tileJSON;
|
if (format === options.pbfAlias) {
|
||||||
|
format = 'pbf';
|
||||||
var mbtilesFileStats = fs.statSync(mbtilesFile);
|
}
|
||||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
|
if (format !== tileJSONFormat &&
|
||||||
throw Error('Not valid MBTiles file: ' + mbtilesFile);
|
!(format === 'geojson' && tileJSONFormat === 'pbf')) {
|
||||||
}
|
return res.status(404).send('Invalid format');
|
||||||
var source;
|
}
|
||||||
var sourceInfoPromise = new Promise(function(resolve, reject) {
|
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
|
||||||
source = new mbtiles(mbtilesFile, function(err) {
|
z > item.tileJSON.maxzoom ||
|
||||||
source.getInfo(function(err, info) {
|
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
||||||
tileJSON['name'] = id;
|
return res.status(404).send('Out of bounds');
|
||||||
tileJSON['format'] = 'pbf';
|
}
|
||||||
|
item.source.getTile(z, x, y, (err, data, headers) => {
|
||||||
Object.assign(tileJSON, info);
|
let isGzipped;
|
||||||
|
if (err) {
|
||||||
tileJSON['tilejson'] = '2.0.0';
|
if (/does not exist/.test(err.message)) {
|
||||||
delete tileJSON['filesize'];
|
return res.status(204).send();
|
||||||
delete tileJSON['mtime'];
|
} else {
|
||||||
delete tileJSON['scheme'];
|
return res.status(500).send(err.message);
|
||||||
|
}
|
||||||
Object.assign(tileJSON, params.tilejson || {});
|
|
||||||
utils.fixTileJSONCenter(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(404).send(err.message);
|
|
||||||
} else {
|
} else {
|
||||||
return res.status(500).send(err.message);
|
if (data == null) {
|
||||||
}
|
return res.status(404).send('Not found');
|
||||||
} else {
|
} else {
|
||||||
if (data == null) {
|
if (tileJSONFormat === 'pbf') {
|
||||||
return res.status(404).send('Not found');
|
isGzipped = data.slice(0, 2).indexOf(
|
||||||
} else {
|
Buffer.from([0x1f, 0x8b])) === 0;
|
||||||
if (tileJSON['format'] == 'pbf') {
|
if (options.dataDecoratorFunc) {
|
||||||
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 (isGzipped) {
|
if (isGzipped) {
|
||||||
data = zlib.unzipSync(data);
|
data = zlib.unzipSync(data);
|
||||||
isGzipped = false;
|
isGzipped = false;
|
||||||
}
|
}
|
||||||
data = shrinkers[style](data, z, tileJSON.maxzoom);
|
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
||||||
//console.log(shrinkers[style].getStats());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (format === 'pbf') {
|
||||||
if (format == 'pbf') {
|
headers['Content-Type'] = 'application/x-protobuf';
|
||||||
headers['Content-Type'] = 'application/x-protobuf';
|
} else if (format === 'geojson') {
|
||||||
} else if (format == 'geojson') {
|
headers['Content-Type'] = 'application/json';
|
||||||
headers['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
if (isGzipped) {
|
if (isGzipped) {
|
||||||
data = zlib.unzipSync(data);
|
data = zlib.unzipSync(data);
|
||||||
isGzipped = false;
|
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';
|
||||||
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
res.set(headers);
|
||||||
headers['Content-Encoding'] = 'gzip';
|
|
||||||
res.set(headers);
|
|
||||||
|
|
||||||
if (!isGzipped) {
|
if (!isGzipped) {
|
||||||
data = zlib.gzipSync(data);
|
data = zlib.gzipSync(data);
|
||||||
isGzipped = true;
|
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, {
|
|
||||||
'pbf': options.pbfAlias
|
|
||||||
});
|
|
||||||
return res.send(info);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
sourceInfoPromise.then(function() {
|
|
||||||
resolve(app);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,62 +1,60 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var clone = require('clone'),
|
const express = require('express');
|
||||||
express = require('express'),
|
const fs = require('fs');
|
||||||
fs = require('fs'),
|
const path = require('path');
|
||||||
path = require('path');
|
|
||||||
|
|
||||||
var utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
module.exports = function(options, allowedFonts) {
|
module.exports = (options, allowedFonts) => {
|
||||||
var app = express().disable('x-powered-by');
|
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 = {};
|
const existingFonts = {};
|
||||||
var fontListingPromise = new Promise(function(resolve, reject) {
|
const fontListingPromise = new Promise((resolve, reject) => {
|
||||||
fs.readdir(options.paths.fonts, function(err, files) {
|
fs.readdir(options.paths.fonts, (err, files) => {
|
||||||
files.forEach(function(file) {
|
if (err) {
|
||||||
fs.stat(path.join(fontPath, file), function(err, stats) {
|
reject(err);
|
||||||
if (!err) {
|
return;
|
||||||
if (stats.isDirectory() &&
|
}
|
||||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
|
for (const file of files) {
|
||||||
existingFonts[path.basename(file)] = true;
|
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();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf',
|
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => {
|
||||||
function(req, res, next) {
|
const fontstack = decodeURI(req.params.fontstack);
|
||||||
var fontstack = decodeURI(req.params.fontstack);
|
const range = req.params.range;
|
||||||
var range = req.params.range;
|
|
||||||
|
|
||||||
utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
|
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('Content-type', 'application/x-protobuf');
|
||||||
res.header('Last-Modified', lastModified);
|
res.header('Last-Modified', lastModified);
|
||||||
return res.send(concated);
|
return res.send(concated);
|
||||||
}, function(err) {
|
}, err => res.status(400).send(err)
|
||||||
return 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');
|
res.header('Content-type', 'application/json');
|
||||||
return res.send(
|
return res.send(
|
||||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
|
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return fontListingPromise.then(() => app);
|
||||||
fontListingPromise.then(function() {
|
|
||||||
resolve(app);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,121 +1,158 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path');
|
||||||
fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
var clone = require('clone'),
|
const clone = require('clone');
|
||||||
express = require('express');
|
const express = require('express');
|
||||||
|
import {validate} from '@mapbox/mapbox-gl-style-spec';
|
||||||
|
|
||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
module.exports = function(options, repo, params, id, reportTiles, reportFont) {
|
const httpTester = /^(http(s)?:)?\/\//;
|
||||||
var app = express().disable('x-powered-by');
|
|
||||||
|
|
||||||
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));
|
module.exports = {
|
||||||
Object.keys(styleJSON.sources).forEach(function(name) {
|
init: (options, repo) => {
|
||||||
var source = styleJSON.sources[name];
|
const app = express().disable('x-powered-by');
|
||||||
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] == '}';
|
|
||||||
|
|
||||||
if (fromData) {
|
app.get('/:id/style.json', (req, res, next) => {
|
||||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
const item = repo[req.params.id];
|
||||||
var mapsTo = (params.mapping || {})[mbtilesFile];
|
if (!item) {
|
||||||
if (mapsTo) {
|
return res.sendStatus(404);
|
||||||
mbtilesFile = mapsTo;
|
}
|
||||||
|
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) {
|
let spritePath;
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var spritePath;
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||||
|
spritePath = path.join(options.paths.sprites,
|
||||||
var httpTester = /^(http(s)?:)?\/\//;
|
|
||||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
||||||
spritePath = path.join(options.paths.sprites,
|
|
||||||
styleJSON.sprite
|
styleJSON.sprite
|
||||||
.replace('{style}', path.basename(styleFile, '.json'))
|
.replace('{style}', path.basename(styleFile, '.json'))
|
||||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
||||||
);
|
);
|
||||||
styleJSON.sprite = 'local://styles/' + id + '/sprite';
|
styleJSON.sprite = `local://styles/${id}/sprite`;
|
||||||
}
|
}
|
||||||
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
||||||
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
||||||
}
|
}
|
||||||
|
|
||||||
repo[id] = styleJSON;
|
repo[id] = {
|
||||||
|
styleJSON,
|
||||||
app.get('/' + id + '.json', function(req, res, next) {
|
spritePath,
|
||||||
var fixUrl = function(url, opt_nokey, opt_nostyle) {
|
publicUrl,
|
||||||
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
|
name: styleJSON.name
|
||||||
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://', req.protocol + '://' + req.headers.host + '/') + query;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var styleJSON_ = clone(styleJSON);
|
return true;
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|||||||
465
src/server.js
@@ -4,26 +4,26 @@
|
|||||||
process.env.UV_THREADPOOL_SIZE =
|
process.env.UV_THREADPOOL_SIZE =
|
||||||
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
|
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
|
||||||
|
|
||||||
var fs = require('fs'),
|
const fs = require('fs');
|
||||||
path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
var base64url = require('base64url'),
|
const chokidar = require('chokidar');
|
||||||
clone = require('clone'),
|
const clone = require('clone');
|
||||||
cors = require('cors'),
|
const cors = require('cors');
|
||||||
enableShutdown = require('http-shutdown'),
|
const enableShutdown = require('http-shutdown');
|
||||||
express = require('express'),
|
const express = require('express');
|
||||||
handlebars = require('handlebars'),
|
const handlebars = require('handlebars');
|
||||||
mercator = new (require('@mapbox/sphericalmercator'))(),
|
const mercator = new (require('@mapbox/sphericalmercator'))();
|
||||||
morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
|
|
||||||
var packageJson = require('../package'),
|
const packageJson = require('../package');
|
||||||
serve_font = require('./serve_font'),
|
const serve_font = require('./serve_font');
|
||||||
serve_rendered = null,
|
const serve_style = require('./serve_style');
|
||||||
serve_style = require('./serve_style'),
|
const serve_data = require('./serve_data');
|
||||||
serve_data = require('./serve_data'),
|
const utils = require('./utils');
|
||||||
utils = require('./utils');
|
|
||||||
|
|
||||||
var isLight = packageJson.name.slice(-6) == '-light';
|
let serve_rendered = null;
|
||||||
|
const isLight = packageJson.name.slice(-6) === '-light';
|
||||||
if (!isLight) {
|
if (!isLight) {
|
||||||
// do not require `serve_rendered` in the light package
|
// do not require `serve_rendered` in the light package
|
||||||
serve_rendered = require('./serve_rendered');
|
serve_rendered = require('./serve_rendered');
|
||||||
@@ -32,24 +32,27 @@ if (!isLight) {
|
|||||||
function start(opts) {
|
function start(opts) {
|
||||||
console.log('Starting server');
|
console.log('Starting server');
|
||||||
|
|
||||||
var app = express().disable('x-powered-by'),
|
const app = express().disable('x-powered-by'),
|
||||||
serving = {
|
serving = {
|
||||||
styles: {},
|
styles: {},
|
||||||
rendered: {},
|
rendered: {},
|
||||||
data: {},
|
data: {},
|
||||||
fonts: {}
|
fonts: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
|
|
||||||
if (process.env.NODE_ENV == 'production') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
app.use(morgan('tiny'));
|
const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
||||||
} else if (process.env.NODE_ENV !== 'test') {
|
const logFormat = opts.logFormat || defaultLogFormat;
|
||||||
app.use(morgan('dev'));
|
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;
|
let config = opts.config || null;
|
||||||
var configPath = null;
|
let configPath = null;
|
||||||
if (opts.configPath) {
|
if (opts.configPath) {
|
||||||
configPath = path.resolve(opts.configPath);
|
configPath = path.resolve(opts.configPath);
|
||||||
try {
|
try {
|
||||||
@@ -65,8 +68,8 @@ function start(opts) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = config.options || {};
|
const options = config.options || {};
|
||||||
var paths = options.paths || {};
|
const paths = options.paths || {};
|
||||||
options.paths = paths;
|
options.paths = paths;
|
||||||
paths.root = path.resolve(
|
paths.root = path.resolve(
|
||||||
configPath ? path.dirname(configPath) : process.cwd(),
|
configPath ? path.dirname(configPath) : process.cwd(),
|
||||||
@@ -76,11 +79,11 @@ function start(opts) {
|
|||||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||||
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
||||||
|
|
||||||
var startupPromises = [];
|
const startupPromises = [];
|
||||||
|
|
||||||
var checkPath = function(type) {
|
const checkPath = type => {
|
||||||
if (!fs.existsSync(paths[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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -89,136 +92,205 @@ function start(opts) {
|
|||||||
checkPath('sprites');
|
checkPath('sprites');
|
||||||
checkPath('mbtiles');
|
checkPath('mbtiles');
|
||||||
|
|
||||||
var data = clone(config.data || {});
|
if (options.dataDecorator) {
|
||||||
|
try {
|
||||||
|
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = clone(config.data || {});
|
||||||
|
|
||||||
if (opts.cors) {
|
if (opts.cors) {
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(config.styles || {}).forEach(function(id) {
|
app.use('/data/', serve_data.init(options, serving.data));
|
||||||
var item = config.styles[id];
|
app.use('/styles/', serve_style.init(options, serving.styles));
|
||||||
if (!item.style || item.style.length == 0) {
|
if (serve_rendered) {
|
||||||
console.log('Missing "style" property for ' + id);
|
startupPromises.push(
|
||||||
return;
|
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) {
|
if (item.serve_data !== false) {
|
||||||
startupPromises.push(serve_style(options, serving.styles, item, id,
|
success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
|
||||||
function(mbtiles, fromData) {
|
(mbtiles, fromData) => {
|
||||||
var dataItemId;
|
let dataItemId;
|
||||||
Object.keys(data).forEach(function(id) {
|
for (const id of Object.keys(data)) {
|
||||||
if (fromData) {
|
if (fromData) {
|
||||||
if (id == mbtiles) {
|
if (id === mbtiles) {
|
||||||
dataItemId = id;
|
dataItemId = id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (data[id].mbtiles == mbtiles) {
|
if (data[id].mbtiles === mbtiles) {
|
||||||
dataItemId = id;
|
dataItemId = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if (dataItemId) { // mbtiles exist in the data config
|
if (dataItemId) { // mbtiles exist in the data config
|
||||||
return dataItemId;
|
return dataItemId;
|
||||||
} else if (fromData) {
|
|
||||||
console.log('ERROR: data "' + mbtiles + '" not found!');
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
} else {
|
||||||
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
if (fromData || !allowMoreData) {
|
||||||
while (data[id]) id += '_';
|
console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
|
||||||
data[id] = {
|
return undefined;
|
||||||
'mbtiles': mbtiles
|
} else {
|
||||||
};
|
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||||
return id;
|
while (data[id]) id += '_';
|
||||||
}
|
data[id] = {
|
||||||
}, function(font) {
|
'mbtiles': mbtiles
|
||||||
serving.fonts[font] = true;
|
};
|
||||||
}).then(function(sub) {
|
return id;
|
||||||
app.use('/styles/', sub);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (item.serve_rendered !== false) {
|
|
||||||
if (serve_rendered) {
|
|
||||||
startupPromises.push(
|
|
||||||
serve_rendered(options, serving.rendered, item, id,
|
|
||||||
function(mbtiles) {
|
|
||||||
var mbtilesFile;
|
|
||||||
Object.keys(data).forEach(function(id) {
|
|
||||||
if (id == mbtiles) {
|
|
||||||
mbtilesFile = data[id].mbtiles;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mbtilesFile;
|
|
||||||
}
|
}
|
||||||
).then(function(sub) {
|
}
|
||||||
app.use('/styles/' + id + '/', 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 {
|
} else {
|
||||||
item.serve_rendered = false;
|
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(
|
startupPromises.push(
|
||||||
serve_font(options, serving.fonts).then(function(sub) {
|
serve_font(options, serving.fonts).then(sub => {
|
||||||
app.use('/', sub);
|
app.use('/', sub);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.keys(data).forEach(function(id) {
|
for (const id of Object.keys(data)) {
|
||||||
var item = data[id];
|
const item = data[id];
|
||||||
if (!item.mbtiles || item.mbtiles.length == 0) {
|
if (!item.mbtiles || item.mbtiles.length === 0) {
|
||||||
console.log('Missing "mbtiles" property for ' + id);
|
console.log(`Missing "mbtiles" property for ${id}`);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
startupPromises.push(
|
startupPromises.push(
|
||||||
serve_data(options, serving.data, item, id, serving.styles).then(function(sub) {
|
serve_data.add(options, serving.data, item, id, opts.publicUrl)
|
||||||
app.use('/data/', sub);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
app.get('/styles.json', function(req, res, next) {
|
if (options.serveAllStyles) {
|
||||||
var result = [];
|
fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
|
||||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
if (err) {
|
||||||
Object.keys(serving.styles).forEach(function(id) {
|
return;
|
||||||
var styleJSON = serving.styles[id];
|
}
|
||||||
|
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({
|
result.push({
|
||||||
version: styleJSON.version,
|
version: styleJSON.version,
|
||||||
name: styleJSON.name,
|
name: styleJSON.name,
|
||||||
id: id,
|
id: id,
|
||||||
url: req.protocol + '://' + req.headers.host +
|
url: `${utils.getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}`
|
||||||
'/styles/' + id + '.json' + query
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
res.send(result);
|
res.send(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
var addTileJSONs = function(arr, req, type) {
|
app.get('/process', (req, res, next) => {
|
||||||
Object.keys(serving[type]).forEach(function(id) {
|
const result = {};
|
||||||
var info = clone(serving[type][id]);
|
const id = req.query.style || '';
|
||||||
var path = '';
|
const item = {
|
||||||
if (type == 'rendered') {
|
'style': id + '.json'
|
||||||
path = 'styles/' + id + '/rendered';
|
};
|
||||||
|
result[id] = true;
|
||||||
|
addStyle(id, item, true, true);
|
||||||
|
res.send(serving.styles);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
path = type + '/' + id;
|
path = `${type}/${id}`;
|
||||||
}
|
}
|
||||||
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, {
|
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
|
||||||
'pbf': options.pbfAlias
|
'pbf': options.pbfAlias
|
||||||
});
|
});
|
||||||
arr.push(info);
|
arr.push(info);
|
||||||
});
|
}
|
||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
app.get('/rendered.json', function(req, res, next) {
|
app.get('/rendered.json', (req, res, next) => {
|
||||||
res.send(addTileJSONs([], req, 'rendered'));
|
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'));
|
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'));
|
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,38 +298,41 @@ function start(opts) {
|
|||||||
// serve web presentations
|
// serve web presentations
|
||||||
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
||||||
|
|
||||||
var templates = path.join(__dirname, '../public/templates');
|
const templates = path.join(__dirname, '../public/templates');
|
||||||
var serveTemplate = function(urlPath, template, dataGetter) {
|
const serveTemplate = (urlPath, template, dataGetter) => {
|
||||||
var templateFile = templates + '/' + template + '.tmpl';
|
let templateFile = `${templates}/${template}.tmpl`;
|
||||||
if (template == 'index') {
|
if (template === 'index') {
|
||||||
if (options.frontPage === false) {
|
if (options.frontPage === false) {
|
||||||
return;
|
return;
|
||||||
} else if (options.frontPage &&
|
} else if (options.frontPage &&
|
||||||
options.frontPage.constructor === String) {
|
options.frontPage.constructor === String) {
|
||||||
templateFile = path.resolve(paths.root, options.frontPage);
|
templateFile = path.resolve(paths.root, options.frontPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startupPromises.push(new Promise(function(resolve, reject) {
|
startupPromises.push(new Promise((resolve, reject) => {
|
||||||
fs.readFile(templateFile, function(err, content) {
|
fs.readFile(templateFile, (err, content) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Template not found:', err);
|
err = new Error(`Template not found: ${err.message}`);
|
||||||
reject(err);
|
reject(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var compiled = handlebars.compile(content.toString());
|
const compiled = handlebars.compile(content.toString());
|
||||||
|
|
||||||
app.use(urlPath, function(req, res, next) {
|
app.use(urlPath, (req, res, next) => {
|
||||||
var data = {};
|
let data = {};
|
||||||
if (dataGetter) {
|
if (dataGetter) {
|
||||||
data = dataGetter(req);
|
data = dataGetter(req);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return res.status(404).send('Not found');
|
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['is_light'] = isLight;
|
||||||
data['key_query_part'] =
|
data['key_query_part'] =
|
||||||
req.query.key ? 'key=' + req.query.key + '&' : '';
|
req.query.key ? `key=${encodeURIComponent(req.query.key)}&` : '';
|
||||||
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
|
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));
|
return res.status(200).send(compiled(data));
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
@@ -265,69 +340,50 @@ function start(opts) {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
serveTemplate('/$', 'index', function(req) {
|
serveTemplate('/$', 'index', req => {
|
||||||
var styles = clone(config.styles || {});
|
const styles = clone(serving.styles || {});
|
||||||
Object.keys(styles).forEach(function(id) {
|
for (const id of Object.keys(styles)) {
|
||||||
var style = styles[id];
|
const style = styles[id];
|
||||||
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
|
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
|
||||||
style.serving_data = serving.styles[id];
|
style.serving_data = serving.styles[id];
|
||||||
style.serving_rendered = serving.rendered[id];
|
style.serving_rendered = serving.rendered[id];
|
||||||
if (style.serving_rendered) {
|
if (style.serving_rendered) {
|
||||||
var center = style.serving_rendered.center;
|
const center = style.serving_rendered.tileJSON.center;
|
||||||
if (center) {
|
if (center) {
|
||||||
style.viewer_hash = '#' + center[2] + '/' +
|
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
||||||
center[1].toFixed(5) + '/' +
|
|
||||||
center[0].toFixed(5);
|
|
||||||
|
|
||||||
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||||
style.thumbnail = center[2] + '/' +
|
style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
|
||||||
Math.floor(centerPx[0] / 256) + '/' +
|
|
||||||
Math.floor(centerPx[1] / 256) + '.png';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
style.xyz_link = utils.getTileUrls(
|
||||||
style.wmts_link = 'http://wmts.maptiler.com/' +
|
req, style.serving_rendered.tileJSON.tiles,
|
||||||
base64url('http://' + req.headers.host +
|
`styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
|
||||||
'/styles/' + id + '/rendered.json' + query) + '/wmts';
|
|
||||||
|
|
||||||
var tiles = utils.getTileUrls(
|
|
||||||
req, style.serving_rendered.tiles,
|
|
||||||
'styles/' + id + '/rendered', style.serving_rendered.format);
|
|
||||||
style.xyz_link = tiles[0];
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
var data = clone(serving.data || {});
|
const data = clone(serving.data || {});
|
||||||
Object.keys(data).forEach(function(id) {
|
for (const id of Object.keys(data)) {
|
||||||
var data_ = data[id];
|
const data_ = data[id];
|
||||||
var center = data_.center;
|
const tilejson = data[id].tileJSON;
|
||||||
|
const center = tilejson.center;
|
||||||
if (center) {
|
if (center) {
|
||||||
data_.viewer_hash = '#' + center[2] + '/' +
|
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
||||||
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 (!data_.is_vector) {
|
||||||
if (center) {
|
if (center) {
|
||||||
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||||
data_.thumbnail = center[2] + '/' +
|
data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
||||||
Math.floor(centerPx[0] / 256) + '/' +
|
|
||||||
Math.floor(centerPx[1] / 256) + '.' + data_.format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
data_.xyz_link = utils.getTileUrls(
|
||||||
data_.wmts_link = 'http://wmts.maptiler.com/' +
|
req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
|
||||||
base64url('http://' + req.headers.host +
|
'pbf': options.pbfAlias
|
||||||
'/data/' + id + '.json' + query) + '/wmts';
|
})[0];
|
||||||
|
|
||||||
var tiles = utils.getTileUrls(
|
|
||||||
req, data_.tiles, 'data/' + id, data_.format, {
|
|
||||||
'pbf': options.pbfAlias
|
|
||||||
});
|
|
||||||
data_.xyz_link = tiles[0];
|
|
||||||
}
|
}
|
||||||
if (data_.filesize) {
|
if (data_.filesize) {
|
||||||
var suffix = 'kB';
|
let suffix = 'kB';
|
||||||
var size = parseInt(data_.filesize, 10) / 1024;
|
let size = parseInt(data_.filesize, 10) / 1024;
|
||||||
if (size > 1024) {
|
if (size > 1024) {
|
||||||
suffix = 'MB';
|
suffix = 'MB';
|
||||||
size /= 1024;
|
size /= 1024;
|
||||||
@@ -336,18 +392,18 @@ function start(opts) {
|
|||||||
suffix = 'GB';
|
suffix = 'GB';
|
||||||
size /= 1024;
|
size /= 1024;
|
||||||
}
|
}
|
||||||
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
|
data_.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return {
|
return {
|
||||||
styles: Object.keys(styles).length ? styles : null,
|
styles: Object.keys(styles).length ? styles : null,
|
||||||
data: Object.keys(data).length ? data : null
|
data: Object.keys(data).length ? data : null
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
serveTemplate('/styles/:id/$', 'viewer', function(req) {
|
serveTemplate('/styles/:id/$', 'viewer', req => {
|
||||||
var id = req.params.id;
|
const id = req.params.id;
|
||||||
var style = clone((config.styles || {})[id]);
|
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
|
||||||
if (!style) {
|
if (!style) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -363,24 +419,38 @@ function start(opts) {
|
|||||||
return res.redirect(301, '/styles/' + req.params.id + '/');
|
return res.redirect(301, '/styles/' + req.params.id + '/');
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
serveTemplate('/styles/:id/wmts.xml', 'wmts', req => {
|
||||||
|
const id = req.params.id;
|
||||||
|
const wmts = clone((serving.styles || {})[id]);
|
||||||
|
if (!wmts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (wmts.hasOwnProperty("serve_rendered") && !wmts.serve_rendered) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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')}`;
|
||||||
|
return wmts;
|
||||||
|
});
|
||||||
|
|
||||||
serveTemplate('/data/:id/$', 'data', function(req) {
|
serveTemplate('/data/:id/$', 'data', req => {
|
||||||
var id = req.params.id;
|
const id = req.params.id;
|
||||||
var data = clone(serving.data[id]);
|
const data = clone(serving.data[id]);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
data.id = id;
|
data.id = id;
|
||||||
data.is_vector = data.format == 'pbf';
|
data.is_vector = data.tileJSON.format === 'pbf';
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
var startupComplete = false;
|
let startupComplete = false;
|
||||||
var startupPromise = Promise.all(startupPromises).then(function() {
|
const startupPromise = Promise.all(startupPromises).then(() => {
|
||||||
console.log('Startup complete');
|
console.log('Startup complete');
|
||||||
startupComplete = true;
|
startupComplete = true;
|
||||||
});
|
});
|
||||||
app.get('/health', function(req, res, next) {
|
app.get('/health', (req, res, next) => {
|
||||||
if (startupComplete) {
|
if (startupComplete) {
|
||||||
return res.status(200).send('OK');
|
return res.status(200).send('OK');
|
||||||
} else {
|
} else {
|
||||||
@@ -388,12 +458,12 @@ function start(opts) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
|
const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function () {
|
||||||
var address = this.address().address;
|
let address = this.address().address;
|
||||||
if (address.indexOf('::') === 0) {
|
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
|
// add server.shutdown() to gracefully stop serving
|
||||||
@@ -406,22 +476,27 @@ function start(opts) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(opts) {
|
module.exports = opts => {
|
||||||
var running = start(opts);
|
const running = start(opts);
|
||||||
|
|
||||||
process.on('SIGINT', function() {
|
running.startupPromise.catch(err => {
|
||||||
|
console.error(err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGHUP', function() {
|
process.on('SIGHUP', () => {
|
||||||
console.log('Stopping server and reloading config');
|
console.log('Stopping server and reloading config');
|
||||||
|
|
||||||
running.server.shutdown(function() {
|
running.server.shutdown(() => {
|
||||||
for (var key in require.cache) {
|
for (const key in require.cache) {
|
||||||
delete require.cache[key];
|
delete require.cache[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
var restarted = start(opts);
|
const restarted = start(opts);
|
||||||
running.server = restarted.server;
|
running.server = restarted.server;
|
||||||
running.app = restarted.app;
|
running.app = restarted.app;
|
||||||
});
|
});
|
||||||
|
|||||||
139
src/utils.js
@@ -1,66 +1,72 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path');
|
||||||
fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
var clone = require('clone'),
|
const clone = require('clone');
|
||||||
glyphCompose = require('glyph-pbf-composite');
|
const glyphCompose = require('@mapbox/glyph-pbf-composite');
|
||||||
|
|
||||||
module.exports.getTileUrls = function(req, domains, path, format, aliases) {
|
|
||||||
|
module.exports.getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`;
|
||||||
|
|
||||||
|
module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||||
|
|
||||||
if (domains) {
|
if (domains) {
|
||||||
if (domains.constructor === String && domains.length > 0) {
|
if (domains.constructor === String && domains.length > 0) {
|
||||||
domains = domains.split(',');
|
domains = domains.split(',');
|
||||||
}
|
}
|
||||||
var host = req.headers.host;
|
const host = req.headers.host;
|
||||||
var hostParts = host.split('.');
|
const hostParts = host.split('.');
|
||||||
var relativeSubdomainsUsable = hostParts.length > 1 &&
|
const relativeSubdomainsUsable = hostParts.length > 1 &&
|
||||||
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
|
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
|
||||||
var newDomains = [];
|
const newDomains = [];
|
||||||
domains.forEach(function(domain) {
|
for (const domain of domains) {
|
||||||
if (domain.indexOf('*') !== -1) {
|
if (domain.indexOf('*') !== -1) {
|
||||||
if (relativeSubdomainsUsable) {
|
if (relativeSubdomainsUsable) {
|
||||||
var newParts = hostParts.slice(1);
|
const newParts = hostParts.slice(1);
|
||||||
newParts.unshift(domain.replace('*', hostParts[0]));
|
newParts.unshift(domain.replace('*', hostParts[0]));
|
||||||
newDomains.push(newParts.join('.'));
|
newDomains.push(newParts.join('.'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newDomains.push(domain);
|
newDomains.push(domain);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
domains = newDomains;
|
domains = newDomains;
|
||||||
}
|
}
|
||||||
if (!domains || domains.length == 0) {
|
if (!domains || domains.length == 0) {
|
||||||
domains = [req.headers.host];
|
domains = [req.headers.host];
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = req.query.key;
|
const key = req.query.key;
|
||||||
var queryParams = [];
|
const queryParams = [];
|
||||||
if (req.query.key) {
|
if (req.query.key) {
|
||||||
queryParams.push('key=' + req.query.key);
|
queryParams.push(`key=${encodeURIComponent(req.query.key)}`);
|
||||||
}
|
}
|
||||||
if (req.query.style) {
|
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]) {
|
if (aliases && aliases[format]) {
|
||||||
format = aliases[format];
|
format = aliases[format];
|
||||||
}
|
}
|
||||||
|
|
||||||
var uris = [];
|
const uris = [];
|
||||||
domains.forEach(function(domain) {
|
if (!publicUrl) {
|
||||||
uris.push(req.protocol + '://' + domain + '/' + path +
|
for (const domain of domains) {
|
||||||
'/{z}/{x}/{y}.' + format + query);
|
uris.push(`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`);
|
||||||
});
|
}
|
||||||
|
} else {
|
||||||
|
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`)
|
||||||
|
}
|
||||||
|
|
||||||
return uris;
|
return uris;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.fixTileJSONCenter = function(tileJSON) {
|
module.exports.fixTileJSONCenter = tileJSON => {
|
||||||
if (tileJSON.bounds && !tileJSON.center) {
|
if (tileJSON.bounds && !tileJSON.center) {
|
||||||
var fitWidth = 1024;
|
const fitWidth = 1024;
|
||||||
var tiles = fitWidth / 256;
|
const tiles = fitWidth / 256;
|
||||||
tileJSON.center = [
|
tileJSON.center = [
|
||||||
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
||||||
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
||||||
@@ -72,47 +78,54 @@ module.exports.fixTileJSONCenter = function(tileJSON) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var getFontPbf = function(allowedFonts, fontPath, name, range, fallbacks) {
|
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promise((resolve, reject) => {
|
||||||
return new Promise(function(resolve, reject) {
|
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
||||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
const filename = path.join(fontPath, name, `${range}.pbf`);
|
||||||
var filename = path.join(fontPath, name, range + '.pbf');
|
if (!fallbacks) {
|
||||||
if (!fallbacks) {
|
fallbacks = clone(allowedFonts || {});
|
||||||
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 = 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);
|
|
||||||
}
|
}
|
||||||
});
|
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) {
|
let fontStyle = name.split(' ').pop();
|
||||||
var fonts = names.split(',');
|
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
||||||
var queue = [];
|
fontStyle = 'Regular';
|
||||||
fonts.forEach(function(font) {
|
}
|
||||||
|
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(
|
queue.push(
|
||||||
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
|
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return Promise.all(queue).then(values => glyphCompose.combine(values));
|
||||||
Promise.all(queue).then(function(values) {
|
|
||||||
return resolve(glyphCompose.combine(values));
|
|
||||||
}, reject);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,6 +71,6 @@ describe('Metadata', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testTileJSON('/styles/test-style/rendered.json');
|
testTileJSON('/styles/test-style.json');
|
||||||
testTileJSON('/data/openmaptiles.json');
|
testTileJSON('/data/openmaptiles.json');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ process.env.NODE_ENV = 'test';
|
|||||||
global.should = require('should');
|
global.should = require('should');
|
||||||
global.supertest = require('supertest');
|
global.supertest = require('supertest');
|
||||||
|
|
||||||
|
require = require('esm')(module);
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
console.log('global setup');
|
console.log('global setup');
|
||||||
process.chdir('test_data');
|
process.chdir('test_data');
|
||||||
var running = require('../src/server')({
|
var running = require('../src/server')({
|
||||||
configPath: 'config.json',
|
configPath: 'config.json',
|
||||||
port: 8888
|
port: 8888,
|
||||||
|
publicUrl: '/test/'
|
||||||
});
|
});
|
||||||
global.app = running.app;
|
global.app = running.app;
|
||||||
global.server = running.server;
|
global.server = running.server;
|
||||||
@@ -17,5 +20,5 @@ before(function() {
|
|||||||
|
|
||||||
after(function() {
|
after(function() {
|
||||||
console.log('global teardown');
|
console.log('global teardown');
|
||||||
global.server.close(function() { console.log('Done'); });
|
global.server.close(function() { console.log('Done'); process.exit(); });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,24 +11,25 @@ var testIs = function(url, type, status) {
|
|||||||
var prefix = 'test-style';
|
var prefix = 'test-style';
|
||||||
|
|
||||||
describe('Styles', function() {
|
describe('Styles', function() {
|
||||||
describe('/styles/' + prefix + '.json is valid style', function() {
|
describe('/styles/' + prefix + '/style.json is valid style', function() {
|
||||||
testIs('/styles/' + prefix + '.json', /application\/json/);
|
testIs('/styles/' + prefix + '/style.json', /application\/json/);
|
||||||
|
|
||||||
it('contains expected properties', function(done) {
|
it('contains expected properties', function(done) {
|
||||||
supertest(app)
|
supertest(app)
|
||||||
.get('/styles/' + prefix + '.json')
|
.get('/styles/' + prefix + '/style.json')
|
||||||
.expect(function(res) {
|
.expect(function(res) {
|
||||||
res.body.version.should.equal(8);
|
res.body.version.should.equal(8);
|
||||||
res.body.name.should.be.String();
|
res.body.name.should.be.String();
|
||||||
res.body.sources.should.be.Object();
|
res.body.sources.should.be.Object();
|
||||||
res.body.glyphs.should.be.String();
|
res.body.glyphs.should.be.String();
|
||||||
res.body.sprite.should.be.String();
|
res.body.sprite.should.be.String();
|
||||||
|
res.body.sprite.should.equal('/test/styles/test-style/sprite');
|
||||||
res.body.layers.should.be.Array();
|
res.body.layers.should.be.Array();
|
||||||
}).end(done);
|
}).end(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('/styles/streets.json is not served', function() {
|
describe('/styles/streets/style.json is not served', function() {
|
||||||
testIs('/styles/streets.json', /./, 404);
|
testIs('/styles/streets/style.json', /./, 404);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
|
describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ describe('Vector tiles', function() {
|
|||||||
testTile(prefix, 0, 1, 0, 404);
|
testTile(prefix, 0, 1, 0, 404);
|
||||||
testTile(prefix, 0, 0, 1, 404);
|
testTile(prefix, 0, 0, 1, 404);
|
||||||
|
|
||||||
testTile(prefix, 14, 0, 0, 404); // non existent tile
|
testTile(prefix, 14, 0, 0, 204); // non existent tile
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var testTile = function(prefix, z, x, y, format, status, scale, type) {
|
var testTile = function(prefix, z, x, y, format, status, scale, type) {
|
||||||
if (scale) y += '@' + scale + 'x';
|
if (scale) y += '@' + scale + 'x';
|
||||||
var path = '/styles/' + prefix + '/rendered/' + z + '/' + x + '/' + y + '.' + format;
|
var path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format;
|
||||||
it(path + ' returns ' + status, function(done) {
|
it(path + ' returns ' + status, function(done) {
|
||||||
var test = supertest(app).get(path);
|
var test = supertest(app).get(path);
|
||||||
test.expect(status);
|
test.expect(status);
|
||||||
|
|||||||