Compare commits
399 Commits
v0.5
...
d2d2d38daf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d2d38daf | ||
|
|
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 | ||
|
|
7de3d8b9c7 | ||
|
|
bdc3d20524 | ||
|
|
5d93b1d4f9 | ||
|
|
d30027e992 | ||
|
|
c233d23523 | ||
|
|
1109c77ec2 | ||
|
|
88cf9b37a9 | ||
|
|
2d207f792b | ||
|
|
d67a57861d | ||
|
|
27e9dbfb4e | ||
|
|
a199008fa3 | ||
|
|
e88b786073 | ||
|
|
c03b0a12f8 | ||
|
|
a234041cd1 | ||
|
|
49a779970e | ||
|
|
9545c2594e | ||
|
|
6c23d95feb | ||
|
|
366380395e | ||
|
|
8ea665297f | ||
|
|
f6580c0342 | ||
|
|
34a139040c | ||
|
|
66bea8a42b | ||
|
|
28790fda30 | ||
|
|
0b16af0084 | ||
|
|
e1654a51de | ||
|
|
5372bab6c5 | ||
|
|
6a960e8593 | ||
|
|
1577cfa54a | ||
|
|
640038a115 | ||
|
|
49a8562441 | ||
|
|
1e402ed207 | ||
|
|
f8949c1aa9 | ||
|
|
7b952ee5c0 | ||
|
|
0673c8990a | ||
|
|
37386bfb29 | ||
|
|
b93bc5fadc | ||
|
|
ca7a6ac515 | ||
|
|
9cc3f9df6a | ||
|
|
e6ebb918fb | ||
|
|
ba0f441490 | ||
|
|
e3ac9fbb4e | ||
|
|
2f2e4b8f42 | ||
|
|
bfadeb799b | ||
|
|
2da1bac6c7 | ||
|
|
6f5a7ed704 | ||
|
|
3722c653f1 | ||
|
|
6231f9f7a7 | ||
|
|
1079ece860 | ||
|
|
841cb82f85 | ||
|
|
5c1d396ed2 | ||
|
|
4992bc4194 | ||
|
|
f14d8b1e57 | ||
|
|
14350a4338 | ||
|
|
01fbaad962 | ||
|
|
26e6d56d9b | ||
|
|
66e3a3da1b | ||
|
|
63482a2a39 | ||
|
|
58ccc5477c | ||
|
|
bbbb38d70f | ||
|
|
2ef0540478 | ||
|
|
85692e9659 | ||
|
|
b66ae40a1e | ||
|
|
88022f9297 | ||
|
|
b127dacc0d | ||
|
|
c162a6647d | ||
|
|
b48f7edc1d | ||
|
|
6d68c93871 | ||
|
|
1fef895e67 | ||
|
|
920b6c19b6 | ||
|
|
7f280feeb1 | ||
|
|
1aec8f386e | ||
|
|
d05606de4d | ||
|
|
b9930dd195 | ||
|
|
4082828790 | ||
|
|
473e7b6f4a | ||
|
|
3d993458b5 | ||
|
|
b2dcf97d9a | ||
|
|
d4ddbec45d | ||
|
|
d32e46ba2f | ||
|
|
0ec992eb21 | ||
|
|
7a55ac9ebb | ||
|
|
78fcb893f7 | ||
|
|
19dcd52cc8 | ||
|
|
557f83121c | ||
|
|
2d29a21596 | ||
|
|
945e568b2b | ||
|
|
e1a996a267 | ||
|
|
bdfa92532e | ||
|
|
e14793c54b | ||
|
|
37db733e02 | ||
|
|
1c401f41a2 | ||
|
|
0018ed9524 | ||
|
|
dfb07b8286 | ||
|
|
c1de2a7903 | ||
|
|
509d32da68 | ||
|
|
bbc14abb4a | ||
|
|
442baee1ce | ||
|
|
0e6c9bfeb0 | ||
|
|
d3a685e51c | ||
|
|
99af9eae23 | ||
|
|
d330a9743c | ||
|
|
becb46ab80 | ||
|
|
ef3f34f778 | ||
|
|
1aaabd2dff | ||
|
|
bcac161a25 | ||
|
|
9d362a5b5e | ||
|
|
21883f490f | ||
|
|
70515947ca | ||
|
|
63090802ae | ||
|
|
131b5e2f81 | ||
|
|
6e085af7cc | ||
|
|
345d96d5e6 | ||
|
|
f1c835c21d | ||
|
|
15ed6d74bf | ||
|
|
a3d8240aac | ||
|
|
098f057e55 | ||
|
|
18ba6f5059 | ||
|
|
820dbdd3dd | ||
|
|
acd7683f18 | ||
|
|
09859c10c1 | ||
|
|
f201deecdc | ||
|
|
b49521d2ed | ||
|
|
30c9bc8979 | ||
|
|
69340bbc83 | ||
|
|
ff9d5a8e5d | ||
|
|
23c2aa54d0 | ||
|
|
a8fd1b38b7 | ||
|
|
7c28bf2b21 | ||
|
|
f9f26f0d65 | ||
|
|
de60a0a076 | ||
|
|
90b9af3d95 | ||
|
|
78aea26318 | ||
|
|
292b1b6b44 | ||
|
|
de7f5f0366 | ||
|
|
161de17803 | ||
|
|
f9e5afbf49 | ||
|
|
513e2dac8f | ||
|
|
e428ab60f1 | ||
|
|
9d1a2bf995 | ||
|
|
bbda49589c | ||
|
|
a571d5f39b | ||
|
|
68d997a655 | ||
|
|
7d2f8ab062 | ||
|
|
4c2157842c | ||
|
|
25c87ccf23 | ||
|
|
97457a23f2 | ||
|
|
e9cad399c9 | ||
|
|
ee1cb21dfd | ||
|
|
f524f1465e | ||
|
|
1004f4cce1 | ||
|
|
b4d6490e00 | ||
|
|
3cf8ce9903 | ||
|
|
f21ee2691b | ||
|
|
845e31b3f2 | ||
|
|
c2a18d7329 | ||
|
|
d120b46966 | ||
|
|
afda5d00bc | ||
|
|
b3cb047d3d | ||
|
|
af47c9c23b | ||
|
|
2ab9d9e168 | ||
|
|
8a7f44d4c2 | ||
|
|
aa9a469bb4 | ||
|
|
53c1ef5786 | ||
|
|
8074cfb4c7 | ||
|
|
ffc72789ae | ||
|
|
4fc76251c3 | ||
|
|
b048990e14 | ||
|
|
b257855e38 | ||
|
|
fb758be730 | ||
|
|
7accdfa7da | ||
|
|
7efe22cf7f | ||
|
|
5a20bf4fac | ||
|
|
a0fbf7fb79 | ||
|
|
ed0af943da | ||
|
|
e4ce4877b6 | ||
|
|
a0a086e95a | ||
|
|
dbaca66b2c | ||
|
|
d465142275 | ||
|
|
7f06f09696 | ||
|
|
f5c5570fca | ||
|
|
cb10936a1d | ||
|
|
a43c96f6cc | ||
|
|
a310c130f3 | ||
|
|
14f406cc34 | ||
|
|
3c29cb0f65 | ||
|
|
d993c805fe | ||
|
|
6443ade489 | ||
|
|
eeb27ed481 | ||
|
|
15bc393aa8 | ||
|
|
d6e2baef67 | ||
|
|
6100013718 | ||
|
|
4eea8a91b8 | ||
|
|
d57f9f7a60 | ||
|
|
1307b29ff9 | ||
|
|
a9443edfc6 | ||
|
|
e6a1bf407c | ||
|
|
0ba2dcce53 |
@@ -1,4 +1,6 @@
|
||||
.git
|
||||
node_modules
|
||||
test_data
|
||||
test
|
||||
*
|
||||
!src
|
||||
!public
|
||||
!package.json
|
||||
!package-lock.json
|
||||
!docker-entrypoint.sh
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
docs/_build
|
||||
node_modules
|
||||
test_data
|
||||
data
|
||||
light
|
||||
plugins
|
||||
config.json
|
||||
*.mbtiles
|
||||
|
||||
10
.travis.yml
10
.travis.yml
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4"
|
||||
- "10"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
@@ -12,12 +12,10 @@ addons:
|
||||
before_install:
|
||||
- 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 xvfb
|
||||
- sudo apt-get install -qq xvfb libgles2-mesa-dev libgbm-dev libxxf86vm-dev
|
||||
install:
|
||||
- npm install
|
||||
- wget -O test_data.zip https://github.com/klokantech/tileserver-gl-data/archive/v0.0.3.zip
|
||||
- unzip -q test_data.zip -d tmp_test_data
|
||||
- mkdir test_data
|
||||
- mv tmp_test_data/tileserver-gl-data-*/* -t test_data
|
||||
- wget -O test_data.zip https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
||||
- unzip -q test_data.zip -d test_data
|
||||
script:
|
||||
- xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
||||
|
||||
62
Dockerfile
62
Dockerfile
@@ -1,23 +1,55 @@
|
||||
FROM debian:stretch
|
||||
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
|
||||
FROM node:10-buster AS builder
|
||||
|
||||
RUN apt-get -qq update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
|
||||
curl \
|
||||
build-essential \
|
||||
python \
|
||||
libcairo2-dev \
|
||||
xvfb \
|
||||
&& curl -sL https://deb.nodesource.com/setup_4.x | bash - \
|
||||
&& apt-get -y install nodejs \
|
||||
&& apt-get clean
|
||||
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -qq update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
apt-transport-https \
|
||||
curl \
|
||||
unzip \
|
||||
build-essential \
|
||||
python \
|
||||
libcairo2-dev \
|
||||
libgles2-mesa-dev \
|
||||
libgbm-dev \
|
||||
libllvm7 \
|
||||
libprotobuf-dev \
|
||||
&& apt-get -y --purge autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
COPY / /usr/src/app
|
||||
RUN cd /usr/src/app && npm install --production
|
||||
|
||||
|
||||
FROM node:10-buster-slim AS final
|
||||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -qq update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
libgles2-mesa \
|
||||
libegl1 \
|
||||
xvfb \
|
||||
xauth \
|
||||
&& apt-get -y --purge autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /usr/src/app /app
|
||||
|
||||
ENV NODE_ENV="production"
|
||||
ENV CHOKIDAR_USEPOLLING=1
|
||||
ENV CHOKIDAR_INTERVAL=500
|
||||
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["/usr/src/app/run.sh"]
|
||||
|
||||
USER node:node
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["-p", "80"]
|
||||
|
||||
13
Dockerfile_light
Normal file
13
Dockerfile_light
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM node:10-stretch
|
||||
|
||||
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
|
||||
COPY / /usr/src/app
|
||||
RUN cd /usr/src/app && npm install --production
|
||||
34
Dockerfile_test
Normal file
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
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!
|
||||
6
PUBLISHING.md
Normal file
6
PUBLISHING.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Publishing new version
|
||||
|
||||
- Update version in `package.json`
|
||||
- `git tag vx.x.x`
|
||||
- `git push --tags`
|
||||
- `node publish.js` (publishes packages to npm)
|
||||
81
README.md
81
README.md
@@ -1,59 +1,48 @@
|
||||

|
||||
|
||||
|
||||
# TileServer GL
|
||||
[](https://travis-ci.org/klokantech/tileserver-gl)
|
||||
[](https://hub.docker.com/r/klokantech/tileserver-gl/)
|
||||
[](https://travis-ci.org/maptiler/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.
|
||||
|
||||
## Installation
|
||||
## Get Started
|
||||
|
||||
### Docker
|
||||
- `docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl`
|
||||
Make sure you have Node.js version **10** installed (running `node -v` it should output something like `v10.17.0`).
|
||||
|
||||
### Without docker
|
||||
- Make sure you have Node v4 or higher (`nvm install 4`)
|
||||
- `npm install`
|
||||
- `node src/main.js`
|
||||
Install `tileserver-gl` with server-side raster rendering of vector tiles with npm
|
||||
|
||||
## Sample data
|
||||
Sample data can be downloaded at https://github.com/klokantech/tileserver-gl-data/archive/master.zip
|
||||
```bash
|
||||
npm install -g tileserver-gl
|
||||
```
|
||||
|
||||
#### Usage
|
||||
- unpack somewhere and `cd` to the directory
|
||||
- `docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl`
|
||||
- (or `node path/to/repo/src/main.js`)
|
||||
Now download vector tiles from [OpenMapTiles](https://openmaptiles.org/downloads/).
|
||||
|
||||
## Configuration
|
||||
```bash
|
||||
curl -o zurich_switzerland.mbtiles https://[GET-YOUR-LINK]/extracts/zurich_switzerland.mbtiles
|
||||
```
|
||||
|
||||
Create `config.json` file in the root directory.
|
||||
The config file can contain definition of several paths where the tiles will be served.
|
||||
Start `tileserver-gl` with the downloaded vector tiles.
|
||||
|
||||
### Example configuration file
|
||||
See https://github.com/klokantech/tileserver-gl-data/blob/master/config.json
|
||||
```bash
|
||||
tileserver-gl zurich_switzerland.mbtiles
|
||||
```
|
||||
|
||||
**Note**: To specify local mbtiles as source of the vector tiles inside the style, use urls with `mbtiles` protocol with path relative to the `cwd + options.paths.root + options.paths.mbtiles`. (For example `mbtiles://switzerland.mbtiles`)
|
||||
Alternatively, you can use the `tileserver-gl-light` package instead, which is pure javascript (does not have any native dependencies) and can run anywhere, but does not contain rasterization on the server side made with MapBox GL Native.
|
||||
|
||||
## Available URLs
|
||||
## Using Docker
|
||||
|
||||
- If you visit the server on the configured port (default 8080) you should see your maps appearing in the browser.
|
||||
- Style is served at `/styles/{id}.json` (+ array at `/styles.json`)
|
||||
- Sprites at `/styles/{id}/sprite[@2x].{format}`
|
||||
- Fonts at `/fonts/{fontstack}/{start}-{end}.pbf`
|
||||
- Rendered tiles are at `/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}`
|
||||
- The optional `@2x` (or `@3x`) part can be used to render HiDPI (retina) tiles
|
||||
- Available formats: `png`, `jpg` (`jpeg`), `webp`
|
||||
- TileJSON at `/styles/{id}/rendered.json`
|
||||
- Static images are rendered at:
|
||||
- `/styles/{id}/static/{lon},{lat},{zoom}[@{bearing}[,{pitch}]]/{width}x{height}[@2x].{format}` (center-based)
|
||||
- `/styles/{id}/static/{minx},{miny},{maxx},{maxy}/{width}x{height}[@2x].{format}` (area-based)
|
||||
- `/styles/{id}/static/auto/{width}x{height}[@2x].{format}` (autofit path -- see below)
|
||||
- The static image endpoints additionally support following query parameters:
|
||||
- `path` - comma-separated `lng,lat`, pipe-separated pairs
|
||||
- e.g. `5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8`
|
||||
- `latlng` - indicates the `path` coordinates are in `lat,lng` order rather than the usual `lng,lat`
|
||||
- `fill` - color to use as the fill (e.g. `red`, `rgba(255,255,255,0.5)`, `#0000ff`)
|
||||
- `stroke` - color of the path stroke
|
||||
- `width` - width of the stroke
|
||||
- `padding` - "percetange" 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"
|
||||
- Source data at `/data/{mbtiles}/{z}/{x}/{y}.{format}`
|
||||
- TileJSON at `/data/{mbtiles}.json`
|
||||
- Array of all TileJSONs at `/index.json` (`/rendered.json`; `/data.json`)
|
||||
An alternative to npm to start the packed software easier is to install [Docker](https://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v $(pwd):/data -p 8080:80 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.
|
||||
|
||||
On laptop you can use [Docker Kitematic](https://kitematic.com/) and search "tileserver-gl" and run it, then drop in the 'data' folder the MBTiles.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can read full documentation of this project at https://tileserver.readthedocs.io/.
|
||||
|
||||
35
README_light.md
Normal file
35
README_light.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# TileServer GL light
|
||||
[](https://travis-ci.org/maptiler/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.
|
||||
|
||||
## Quickstart
|
||||
Use `npm install -g tileserver-gl-light` to install the package from npm.
|
||||
|
||||
Then you can simply run `tileserver-gl-light zurich_switzerland.mbtiles` to start the server for the given mbtiles.
|
||||
|
||||
See also `tileserver-gl` which contains server side rendering.
|
||||
|
||||
Prepared vector tiles can be downloaded from [OpenMapTiles.com](https://openmaptiles.com/downloads/planet/).
|
||||
|
||||
## Building docker image
|
||||
|
||||
You can build TileServer GL light image from source.
|
||||
|
||||
```
|
||||
git clone https://github.com/maptiler/tileserver-gl.git
|
||||
cd tileserver-gl
|
||||
node publish.js --no-publish
|
||||
cd light
|
||||
docker build -t tileserver-gl-light .
|
||||
```
|
||||
|
||||
[Download from OpenMapTiles.com](https://openmaptiles.com/downloads/planet/) or [create](https://github.com/openmaptiles/openmaptiles) your vector tile, and run following in directory contains your *.mbtiles.
|
||||
|
||||
```
|
||||
docker run --rm -it -v $(pwd):/data -p 8000:80 tileserver-gl-light
|
||||
```
|
||||
|
||||
## Documentation
|
||||
You can read full documentation of this project at https://tileserver.readthedocs.io/.
|
||||
31
docker-entrypoint.sh
Executable file
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 "$@"
|
||||
177
docs/Makefile
Normal file
177
docs/Makefile
Normal file
@@ -0,0 +1,177 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/TileServerGL.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/TileServerGL.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/TileServerGL"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/TileServerGL"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
258
docs/conf.py
Normal file
258
docs/conf.py
Normal file
@@ -0,0 +1,258 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# TileServer GL documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Aug 4 23:48:49 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'TileServer GL'
|
||||
copyright = u'2016, Klokan Technologies GmbH'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'TileServerGLdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'TileServerGL.tex', u'TileServer GL Documentation',
|
||||
u'Klokan Technologies GmbH', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'tileservergl', u'TileServer GL Documentation',
|
||||
[u'Klokan Technologies GmbH'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'TileServerGL', u'TileServer GL Documentation',
|
||||
u'Klokan Technologies GmbH', 'TileServerGL', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
216
docs/config.rst
Normal file
216
docs/config.rst
Normal file
@@ -0,0 +1,216 @@
|
||||
==================
|
||||
Configuration file
|
||||
==================
|
||||
|
||||
The configuration file defines the behavior of the application. It's a regular JSON file.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"options": {
|
||||
"paths": {
|
||||
"root": "",
|
||||
"fonts": "fonts",
|
||||
"sprites": "sprites",
|
||||
"styles": "styles",
|
||||
"mbtiles": ""
|
||||
},
|
||||
"domains": [
|
||||
"localhost:8080",
|
||||
"127.0.0.1:8080"
|
||||
],
|
||||
"formatQuality": {
|
||||
"jpeg": 80,
|
||||
"webp": 90
|
||||
},
|
||||
"maxScaleFactor": 3,
|
||||
"maxSize": 2048,
|
||||
"pbfAlias": "pbf",
|
||||
"serveAllFonts": false,
|
||||
"serveAllStyles": false,
|
||||
"serveStaticMaps": true,
|
||||
"tileMargin": 0
|
||||
},
|
||||
"styles": {
|
||||
"basic": {
|
||||
"style": "basic.json",
|
||||
"tilejson": {
|
||||
"type": "overlay",
|
||||
"bounds": [8.44806, 47.32023, 8.62537, 47.43468]
|
||||
}
|
||||
},
|
||||
"hybrid": {
|
||||
"style": "satellite-hybrid.json",
|
||||
"serve_rendered": false,
|
||||
"tilejson": {
|
||||
"format": "webp"
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"zurich-vector": {
|
||||
"mbtiles": "zurich.mbtiles"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
``options``
|
||||
===========
|
||||
|
||||
``paths``
|
||||
---------
|
||||
|
||||
Defines where to look for the different types of input data.
|
||||
|
||||
The value of ``root`` is used as prefix for all data types.
|
||||
|
||||
``domains``
|
||||
-----------
|
||||
|
||||
You can use this to optionally specify on what domains the rendered tiles are accessible. This can be used for basic load-balancing or to bypass browser's limit for the number of connections per domain.
|
||||
|
||||
``frontPage``
|
||||
-----------------
|
||||
|
||||
Path to the html (relative to ``root`` path) to use as a front page.
|
||||
|
||||
Use ``true`` (or nothing) to serve the default TileServer GL front page with list of styles and data.
|
||||
Use ``false`` to disable the front page altogether (404).
|
||||
|
||||
``formatQuality``
|
||||
-----------------
|
||||
|
||||
Quality of the compression of individual image formats. [0-100]
|
||||
|
||||
``maxScaleFactor``
|
||||
-----------
|
||||
|
||||
Maximum scale factor to allow in raster tile and static maps requests (e.g. ``@3x`` suffix).
|
||||
Also see ``maxSize`` below.
|
||||
Default value is ``3``, maximum ``9``.
|
||||
|
||||
``maxSize``
|
||||
-----------
|
||||
|
||||
Maximum image side length to be allowed to be rendered (including scale factor).
|
||||
Be careful when changing this value since there are hardware limits that need to be considered.
|
||||
Default is ``2048``.
|
||||
|
||||
``tileMargin``
|
||||
--------------
|
||||
|
||||
Additional image side length added during tile rendering that is cropped from the delivered tile. This is useful for resolving the issue with cropped labels,
|
||||
but it does come with a performance degradation, because additional, adjacent vector tiles need to be loaded to generate a single tile.
|
||||
Default is ``0`` to disable this processing.
|
||||
|
||||
``minRendererPoolSizes``
|
||||
------------------------
|
||||
|
||||
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``
|
||||
-----------
|
||||
|
||||
Optional string to be rendered into the raster tiles (and static maps) as watermark (bottom-left corner).
|
||||
Can be used for hard-coding attributions etc. (can also be specified per-style).
|
||||
Not used by default.
|
||||
|
||||
``styles``
|
||||
==========
|
||||
|
||||
Each item in this object defines one style (map). It can have the following options:
|
||||
|
||||
* ``style`` -- name of the style json file [required]
|
||||
* ``serve_rendered`` -- whether to render the raster tiles for this style or not
|
||||
* ``serve_data`` -- whether to allow access to the original tiles, sprites and required glyphs
|
||||
* ``tilejson`` -- properties to add to the TileJSON created for the raster data
|
||||
|
||||
* ``format`` and ``bounds`` can be especially useful
|
||||
|
||||
``data``
|
||||
========
|
||||
|
||||
Each item specifies one data source which should be made accessible by the server. It has the following options:
|
||||
|
||||
* ``mbtiles`` -- name of the mbtiles file [required]
|
||||
|
||||
The mbtiles file does not need to be specified here unless you explicitly want to serve the raw data.
|
||||
|
||||
Referencing local files from style JSON
|
||||
=======================================
|
||||
|
||||
You can link various data sources from the style JSON (for example even remote TileJSONs).
|
||||
|
||||
MBTiles
|
||||
-------
|
||||
|
||||
To specify that you want to use local mbtiles, use to following syntax: ``mbtiles://switzerland.mbtiles``.
|
||||
The TileServer-GL will try to find the file ``switzerland.mbtiles`` in ``root`` + ``mbtiles`` path.
|
||||
|
||||
For example::
|
||||
|
||||
"sources": {
|
||||
"source1": {
|
||||
"url": "mbtiles://switzerland.mbtiles",
|
||||
"type": "vector"
|
||||
}
|
||||
}
|
||||
|
||||
Alternatively, you can use ``mbtiles://{zurich-vector}`` to reference existing data object from the config.
|
||||
In this case, the server will look into the ``config.json`` to determine what mbtiles file to use.
|
||||
For the config above, this is equivalent to ``mbtiles://zurich.mbtiles``.
|
||||
|
||||
Sprites
|
||||
-------
|
||||
|
||||
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 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.png``
|
||||
* ``sprite@2x.json``
|
||||
* ``sprite@2x.png``
|
||||
|
||||
You can also use the following placeholders in the sprite path for easier use:
|
||||
|
||||
* ``{style}`` -- gets replaced with the name of the style file (``xxx.json``)
|
||||
* ``{styleJsonFolder}`` -- gets replaced with the path to the style file
|
||||
|
||||
Fonts (glyphs)
|
||||
--------------
|
||||
|
||||
Similarly to the sprites, the style JSON also needs to contain proper paths to the font glyphs (in the ``glyphs`` property) and can be both local and remote.
|
||||
|
||||
It should contain the following placeholders:
|
||||
|
||||
* ``{fontstack}`` -- name of the font and variant
|
||||
* ``{range}`` -- range of the glyphs
|
||||
|
||||
For example ``"glyphs": "{fontstack}/{range}.pbf"`` will instruct TileServer-GL to look for the files such as ``fonts/Open Sans/0-255.pbf`` (``fonts`` come from the ``paths`` property of the ``config.json`` example above).
|
||||
20
docs/deployment.rst
Normal file
20
docs/deployment.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
==========
|
||||
Deployment
|
||||
==========
|
||||
|
||||
Typically - you should use nginx/lighttpd/apache on the frontend - and the tileserver-gl server is hidden behind it in production deployment.
|
||||
|
||||
Caching
|
||||
=======
|
||||
|
||||
There is a plenty of options you can use to create proper caching infrastructure: Varnish, CloudFlare, ...
|
||||
|
||||
Securing
|
||||
========
|
||||
|
||||
Nginx can be used to add protection via https, password, referrer, IP address restriction, access keys, etc.
|
||||
|
||||
Running behind a proxy or a load-balancer
|
||||
=========================================
|
||||
|
||||
If you need to run TileServer GL behind a proxy, make sure the proxy sends ``X-Forwarded-*`` headers to the server (most importantly ``X-Forwarded-Host`` and ``X-Forwarded-Proto``) to ensures the URLs generated inside TileJSON etc. are using the desired domain and protocol.
|
||||
77
docs/endpoints.rst
Normal file
77
docs/endpoints.rst
Normal file
@@ -0,0 +1,77 @@
|
||||
===================
|
||||
Available endpoints
|
||||
===================
|
||||
|
||||
If you visit the server on the configured port (default 8080) you can see your maps appearing in the browser.
|
||||
|
||||
Styles
|
||||
======
|
||||
* Styles are served at ``/styles/{id}/style.json`` (+ array at ``/styles.json``)
|
||||
|
||||
* Sprites at ``/styles/{id}/sprite[@2x].{format}``
|
||||
* Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf``
|
||||
|
||||
Rendered tiles
|
||||
==============
|
||||
* 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
|
||||
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
|
||||
* TileJSON at ``/styles/{id}.json``
|
||||
|
||||
* 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
|
||||
=============
|
||||
* Several endpoints:
|
||||
|
||||
* ``/styles/{id}/static/{lon},{lat},{zoom}[@{bearing}[,{pitch}]]/{width}x{height}[@2x].{format}`` (center-based)
|
||||
* ``/styles/{id}/static/{minx},{miny},{maxx},{maxy}/{width}x{height}[@2x].{format}`` (area-based)
|
||||
* ``/styles/{id}/static/auto/{width}x{height}[@2x].{format}`` (autofit path -- see below)
|
||||
|
||||
* All the static image endpoints additionally support following query parameters:
|
||||
|
||||
* ``path`` - comma-separated ``lng,lat``, pipe-separated pairs
|
||||
|
||||
* e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
|
||||
|
||||
* ``latlng`` - indicates the ``path`` coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
|
||||
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
||||
* ``stroke`` - color of the path stroke
|
||||
* ``width`` - width of the stroke
|
||||
* ``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"
|
||||
|
||||
* You can also use (experimental) ``/styles/{id}/static/raw/...`` endpoints with raw spherical mercator coordinates (EPSG:3857) instead of WGS84.
|
||||
|
||||
* The static images are not available in the ``tileserver-gl-light`` version.
|
||||
|
||||
Source data
|
||||
===========
|
||||
* Source data are served at ``/data/{mbtiles}/{z}/{x}/{y}.{format}``
|
||||
|
||||
* Format depends on the source file (usually ``png`` or ``pbf``)
|
||||
|
||||
* ``geojson`` is also available (useful for inspecting the tiles) in case the original format is ``pbf``
|
||||
|
||||
* TileJSON at ``/data/{mbtiles}.json``
|
||||
|
||||
TileJSON arrays
|
||||
===============
|
||||
Array of all TileJSONs is at ``/index.json`` (``/rendered.json``; ``/data.json``)
|
||||
|
||||
List of available fonts
|
||||
=======================
|
||||
Array of names of the available fonts is at ``/fonts.json``
|
||||
|
||||
Health check
|
||||
============
|
||||
Endpoint reporting health status is at ``/health`` and currently returns:
|
||||
|
||||
* ``503`` Starting - for a short period before everything is initialized
|
||||
* ``200`` OK - when the server is running
|
||||
28
docs/index.rst
Normal file
28
docs/index.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
.. TileServer GL documentation master file, created by
|
||||
sphinx-quickstart on Thu Aug 4 23:48:49 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to TileServer GL's documentation!
|
||||
=========================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation
|
||||
usage
|
||||
config
|
||||
deployment
|
||||
endpoints
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
58
docs/installation.rst
Normal file
58
docs/installation.rst
Normal file
@@ -0,0 +1,58 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
Docker
|
||||
======
|
||||
|
||||
When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
|
||||
|
||||
Just run ``docker run --rm -it -v $(pwd):/data -p 8080:80 maptiler/tileserver-gl``.
|
||||
|
||||
Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following:
|
||||
|
||||
* ``docker run ... maptiler/tileserver-gl --mbtiles my-tiles.mbtiles`` -- explicitly specify which mbtiles to use (if you have more in the folder)
|
||||
* ``docker run ... maptiler/tileserver-gl --verbose`` -- to see the default config created automatically
|
||||
|
||||
npm
|
||||
===
|
||||
|
||||
Just run ``npm install -g tileserver-gl``.
|
||||
|
||||
|
||||
Native dependencies
|
||||
-------------------
|
||||
|
||||
There are some native dependencies that you need to make sure are installed if you plan to run the TileServer GL natively without docker.
|
||||
The precise package names you need to install may differ on various platforms.
|
||||
|
||||
These are required on Debian 9:
|
||||
* ``build-essential``
|
||||
* ``libcairo2-dev``
|
||||
* ``libprotobuf-dev``
|
||||
|
||||
|
||||
``tileserver-gl-light`` on npm
|
||||
==============================
|
||||
|
||||
Alternatively, you can use ``tileserver-gl-light`` package instead, which is pure javascript (does not have any native dependencies) and can run anywhere, but does not contain rasterization features.
|
||||
|
||||
|
||||
From source
|
||||
===========
|
||||
|
||||
Make sure you have Node v10 (nvm install 10) and run::
|
||||
|
||||
npm install
|
||||
node .
|
||||
|
||||
|
||||
On OSX
|
||||
======
|
||||
|
||||
Make sure to have dependencies of canvas_ package installed::
|
||||
|
||||
brew install pkg-config cairo libpng jpeg giflib
|
||||
|
||||
|
||||
.. _canvas: https://www.npmjs.com/package/canvas
|
||||
38
docs/usage.rst
Normal file
38
docs/usage.rst
Normal file
@@ -0,0 +1,38 @@
|
||||
=====
|
||||
Usage
|
||||
=====
|
||||
|
||||
Getting started
|
||||
======
|
||||
::
|
||||
|
||||
Usage: main.js tileserver-gl [mbtiles] [options]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
--mbtiles <file> MBTiles file (uses demo configuration);
|
||||
ignored if the configuration file is also specified
|
||||
-c, --config <file> Configuration file [config.json]
|
||||
-b, --bind <address> Bind address
|
||||
-p, --port <port> Port [8080]
|
||||
-C|--no-cors Disable Cross-origin resource sharing headers
|
||||
-u|--public_url <url> Enable exposing the server on subpaths, not necessarily the root of the domain
|
||||
-V, --verbose More verbose output
|
||||
-s, --silent Less verbose output
|
||||
-v, --version Version info
|
||||
|
||||
|
||||
Default preview style and configuration
|
||||
======
|
||||
|
||||
- If no configuration file is specified, a default preview style (compatible with openmaptiles) is used.
|
||||
- If no mbtiles file is specified (and is not found in the current working directory), a sample file is downloaded (showing the Zurich area)
|
||||
|
||||
Reloading 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.
|
||||
60
package.json
60
package.json
@@ -1,39 +1,49 @@
|
||||
{
|
||||
"name": "tileserver-gl",
|
||||
"version": "0.5.0",
|
||||
"description": "Map tile server for JSON GL styles - serverside generated raster tiles",
|
||||
"version": "3.1.1",
|
||||
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
|
||||
"main": "src/main.js",
|
||||
"authors": [
|
||||
"Petr Sloup <petr.sloup@klokantech.com>"
|
||||
],
|
||||
"bin": "src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/klokantech/tileserver-gl.git"
|
||||
"url": "https://github.com/maptiler/tileserver-gl.git"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=10 <11"
|
||||
},
|
||||
"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": {
|
||||
"async": "1.5.2",
|
||||
"advanced-pool": "0.3.2",
|
||||
"canvas": "1.4.0",
|
||||
"clone": "1.0.2",
|
||||
"color": "0.11.3",
|
||||
"cors": "2.7.1",
|
||||
"express": "4.14.0",
|
||||
"handlebars": "4.0.5",
|
||||
"mapbox-gl-native": "3.2.1",
|
||||
"mbtiles": "0.9.0",
|
||||
"morgan": "1.7.0",
|
||||
"nomnom": "1.8.1",
|
||||
"request": "2.72.0",
|
||||
"sharp": "0.15.0",
|
||||
"sphericalmercator": "1.0.5"
|
||||
"@mapbox/glyph-pbf-composite": "0.0.3",
|
||||
"@mapbox/mapbox-gl-native": "5.0.2",
|
||||
"@mapbox/mapbox-gl-style-spec": "13.12.0",
|
||||
"@mapbox/mbtiles": "0.11.0",
|
||||
"@mapbox/sphericalmercator": "1.1.0",
|
||||
"@mapbox/vector-tile": "1.3.1",
|
||||
"advanced-pool": "0.3.3",
|
||||
"canvas": "2.6.1",
|
||||
"chokidar": "3.3.1",
|
||||
"clone": "2.1.2",
|
||||
"color": "3.1.2",
|
||||
"commander": "4.1.1",
|
||||
"cors": "2.8.5",
|
||||
"esm": "3.2.25",
|
||||
"express": "4.17.1",
|
||||
"handlebars": "4.7.3",
|
||||
"http-shutdown": "1.2.2",
|
||||
"morgan": "1.9.1",
|
||||
"pbf": "3.2.1",
|
||||
"proj4": "2.6.0",
|
||||
"request": "2.88.2",
|
||||
"sharp": "0.26.2",
|
||||
"tileserver-gl-styles": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"should": "^9.0.0",
|
||||
"mocha": "^2.5.0",
|
||||
"supertest": "^1.2.0"
|
||||
"mocha": "^7.1.0",
|
||||
"should": "^13.2.3",
|
||||
"supertest": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/resources/favicon.ico
Normal file
BIN
public/resources/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/resources/fonts/OpenSans-Bold.ttf
Normal file
BIN
public/resources/fonts/OpenSans-Bold.ttf
Normal file
Binary file not shown.
BIN
public/resources/fonts/OpenSans-Italic.ttf
Normal file
BIN
public/resources/fonts/OpenSans-Italic.ttf
Normal file
Binary file not shown.
BIN
public/resources/fonts/OpenSans-Regular.ttf
Normal file
BIN
public/resources/fonts/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
public/resources/images/header-map-1280px.png
Normal file
BIN
public/resources/images/header-map-1280px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
BIN
public/resources/images/logo.png
Normal file
BIN
public/resources/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
67
public/resources/images/maptiler-logo.svg
Normal file
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 |
BIN
public/resources/images/placeholder.png
Normal file
BIN
public/resources/images/placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
File diff suppressed because one or more lines are too long
40
public/resources/mapbox-gl-inspect.css
Normal file
40
public/resources/mapbox-gl-inspect.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.mapbox-gl-inspect_popup {
|
||||
color: #333;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_feature:not(:last-child) {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_layer:before {
|
||||
content: '#';
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_layer {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_property {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_property-value {
|
||||
display: table-cell;
|
||||
|
||||
}
|
||||
|
||||
.mapbox-gl-inspect_property-name {
|
||||
display: table-cell;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-inspect {
|
||||
background-image: url('data:image/svg+xml;charset=utf8,<svg%20xmlns="http://www.w3.org/2000/svg"%20fill="#333333%22%20preserveAspectRatio=%22xMidYMid%20meet%22%20viewBox=%22-10%20-10%2060%2060%22%3E%3Cg%3E%3Cpath%20d=%22m15%2021.6q0-2%201.5-3.5t3.5-1.5%203.5%201.5%201.5%203.5-1.5%203.6-3.5%201.4-3.5-1.4-1.5-3.6z%20m18.4%2011.1l-6.4-6.5q1.4-2.1%201.4-4.6%200-3.4-2.5-5.8t-5.9-2.4-5.9%202.4-2.5%205.8%202.5%205.9%205.9%202.5q2.4%200%204.6-1.4l7.4%207.4q-0.9%200.6-2%200.6h-20q-1.3%200-2.3-0.9t-1.1-2.3l0.1-26.8q0-1.3%201-2.3t2.3-0.9h13.4l10%2010v19.3z%22%3E%3C/path%3E%3C/g%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-map {
|
||||
background-image: url('data:image/svg+xml;charset=utf8,<svg%20xmlns="http://www.w3.org/2000/svg"%20fill="#333333%22%20viewBox=%22-10%20-10%2060%2060%22%20preserveAspectRatio=%22xMidYMid%20meet%22%3E%3Cg%3E%3Cpath%20d=%22m25%2031.640000000000004v-19.766666666666673l-10-3.511666666666663v19.766666666666666z%20m9.140000000000008-26.640000000000004q0.8599999999999923%200%200.8599999999999923%200.8600000000000003v25.156666666666666q0%200.625-0.625%200.783333333333335l-9.375%203.1999999999999993-10-3.5133333333333354-8.906666666666668%203.4383333333333326-0.2333333333333334%200.07833333333333314q-0.8616666666666664%200-0.8616666666666664-0.8599999999999994v-25.156666666666663q0-0.625%200.6233333333333331-0.7833333333333332l9.378333333333334-3.198333333333334%2010%203.5133333333333336%208.905000000000001-3.4383333333333344z%22%3E%3C/path%3E%3C/g%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
1
public/resources/mapbox-gl-inspect.min.js
vendored
Normal file
1
public/resources/mapbox-gl-inspect.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
public/resources/mapbox-gl-rtl-text.js
Normal file
6
public/resources/mapbox-gl-rtl-text.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -65,5 +65,3 @@ module.exports=window.L=require("leaflet/dist/leaflet-src");
|
||||
"use strict";function contains(n,t){if(!t||!t.length)return!1;for(var r=0;r<t.length;r++)if(t[r]===n)return!0;return!1}module.exports={idUrl:function(n,t){-1===n.indexOf("/")?t.loadID(n):t.loadURL(n)},log:function(n){"object"==typeof console&&"function"==typeof console.error&&console.error(n)},strict:function(n,t){if(typeof n!==t)throw new Error("Invalid argument: "+t+" expected")},strict_instance:function(n,t,r){if(!(n instanceof t))throw new Error("Invalid argument: "+r+" expected")},strict_oneof:function(n,t){if(!contains(n,t))throw new Error("Invalid argument: "+n+" given, valid values are "+t.join(", "))},strip_tags:function(n){return n.replace(/<[^<]+>/g,"")},lbounds:function(n){return new L.LatLngBounds([[n[1],n[0]],[n[3],n[2]]])}};
|
||||
},{}]},{},[17])
|
||||
|
||||
|
||||
//# sourceMappingURL=mapbox.js.map
|
||||
@@ -2,23 +2,25 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{name}} - TileServer GL</title>
|
||||
{{#is_vector}}
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css{{&access_key_query}}" />
|
||||
<script src="/mapbox-gl.js{{&access_key_query}}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl.css{{&key_query}}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl-inspect.css{{&key_query}}" />
|
||||
<script src="{{public_url}}mapbox-gl.js{{&key_query}}"></script>
|
||||
<script src="{{public_url}}mapbox-gl-inspect.min.js{{&key_query}}"></script>
|
||||
<style>
|
||||
body {background:#000;color:#ccc;}
|
||||
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
||||
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
||||
h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
|
||||
#layerList {position:absolute;top:35px;right:0;bottom:60%;width:240px;overflow:auto;}
|
||||
#layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;}
|
||||
#layerList div div {width:15px;height:15px;display:inline-block;}
|
||||
#propertyList {position:absolute;top:40%;bottom:0;right:0;width:240px;overflow:auto;color:#fff;}
|
||||
</style>
|
||||
{{/is_vector}}
|
||||
{{^is_vector}}
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&access_key_query}}" />
|
||||
<script src="/mapbox.js{{&access_key_query}}"></script>
|
||||
<script src="/leaflet-hash.js{{&access_key_query}}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox.css{{&key_query}}" />
|
||||
<script src="{{public_url}}mapbox.js{{&key_query}}"></script>
|
||||
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
||||
<style>
|
||||
body { margin:0; padding:0; }
|
||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||
@@ -34,81 +36,37 @@
|
||||
<script>
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
hash: true
|
||||
});
|
||||
map.addControl(new mapboxgl.Navigation());
|
||||
|
||||
function generateColor(str) {
|
||||
var rgb = [0, 0, 0];
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var v = str.charCodeAt(i);
|
||||
rgb[v % 3] = (rgb[i % 3] + (13*(v%13))) % 12;
|
||||
}
|
||||
var r = 4 + rgb[0];
|
||||
var g = 4 + rgb[1];
|
||||
var b = 4 + rgb[2];
|
||||
r = (r * 16) + r;
|
||||
g = (g * 16) + g;
|
||||
b = (b * 16) + b;
|
||||
return [r, g, b, 1];
|
||||
};
|
||||
|
||||
function initLayer(data) {
|
||||
var layer;
|
||||
var layerList = document.getElementById('layerList');
|
||||
var layers_ = [];
|
||||
data['vector_layers'].forEach(function(el) {
|
||||
var color = generateColor(el['id']);
|
||||
var colorText = 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + color[3] + ')';
|
||||
layers_.push({
|
||||
id: el['id'] + Math.random(),
|
||||
source: 'vector_layer_',
|
||||
'source-layer': el['id'],
|
||||
interactive: true,
|
||||
type: 'line',
|
||||
paint: {'line-color': colorText}
|
||||
});
|
||||
var item = document.createElement('div');
|
||||
item.innerHTML = '<div style="' +
|
||||
'background:rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',1);' +
|
||||
'"></div> ' + el['id'];
|
||||
layerList.appendChild(item);
|
||||
});
|
||||
map.setStyle({
|
||||
hash: true,
|
||||
style: {
|
||||
version: 8,
|
||||
sources: {
|
||||
'vector_layer_': {
|
||||
type: 'vector',
|
||||
tiles: data['tiles'],
|
||||
minzoom: data['minzoom'],
|
||||
maxzoom: data['maxzoom']
|
||||
url: '{{public_url}}data/{{id}}.json{{&key_query}}'
|
||||
}
|
||||
},
|
||||
layers: layers_
|
||||
});
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState == 4 && xhttp.status == 200) {
|
||||
initLayer(xhttp.response);
|
||||
layers: []
|
||||
}
|
||||
};
|
||||
xhttp.responseType = 'json';
|
||||
xhttp.open('GET', '/data/{{id}}.json{{&access_key_query}}', true);
|
||||
xhttp.send();
|
||||
|
||||
var propertyList = document.getElementById('propertyList');
|
||||
map.on('mousemove', function(e) {
|
||||
propertyList.innerHTML = '';
|
||||
map.featuresAt(e.point, {radius: 3}, function(err, features) {
|
||||
if (err) throw err;
|
||||
if (features[0]) {
|
||||
propertyList.innerHTML = JSON.stringify(features[0].properties, null, 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
map.addControl(new mapboxgl.NavigationControl());
|
||||
var inspect = new MapboxInspect({
|
||||
showInspectMap: true,
|
||||
showInspectButton: false
|
||||
});
|
||||
map.addControl(inspect);
|
||||
map.on('styledata', function() {
|
||||
var layerList = document.getElementById('layerList');
|
||||
layerList.innerHTML = '';
|
||||
Object.keys(inspect.sources).forEach(function(sourceId) {
|
||||
var layerIds = inspect.sources[sourceId];
|
||||
layerIds.forEach(function(layerId) {
|
||||
var item = document.createElement('div');
|
||||
item.innerHTML = '<div style="' +
|
||||
'background:' + inspect.assignLayerColor(layerId) + ';' +
|
||||
'"></div> ' + layerId;
|
||||
layerList.appendChild(item);
|
||||
});
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{{/is_vector}}
|
||||
@@ -116,7 +74,11 @@
|
||||
<h1 style="display:none;">{{name}}</h1>
|
||||
<div id='map'></div>
|
||||
<script>
|
||||
var map = L.mapbox.map('map', '/data/{{id}}.json{{&access_key_query}}', { zoomControl: false });
|
||||
var map = L.mapbox.map('map', '{{public_url}}data/{{id}}.json{{&key_query}}', { zoomControl: false });
|
||||
map.eachLayer(function(layer) {
|
||||
// do not add scale prefix even if retina display is detected
|
||||
layer.scalePrefix = '.';
|
||||
});
|
||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
||||
setTimeout(function() {
|
||||
new L.Hash(map);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,12 +2,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{name}} - TileServer GL</title>
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css{{&access_key_query}}" />
|
||||
<script src="/mapbox-gl.js{{&access_key_query}}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&access_key_query}}" />
|
||||
<script src="/mapbox.js{{&access_key_query}}"></script>
|
||||
<script src="/leaflet-hash.js{{&access_key_query}}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox-gl.css{{&key_query}}" />
|
||||
<script src="{{public_url}}mapbox-gl.js{{&key_query}}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{public_url}}mapbox.css{{&key_query}}" />
|
||||
<script src="{{public_url}}mapbox.js{{&key_query}}"></script>
|
||||
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
||||
<style>
|
||||
body { margin:0; padding:0; }
|
||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||
@@ -23,14 +24,15 @@
|
||||
(q.indexOf('raster') >= 0 ? 'raster' :
|
||||
(mapboxgl.supported() ? 'vector' : 'raster'));
|
||||
if (preference == 'vector') {
|
||||
mapboxgl.setRTLTextPlugin('{{public_url}}mapbox-gl-rtl-text.js{{&key_query}}');
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
style: '/styles/{{id}}.json{{&access_key_query}}',
|
||||
style: '{{public_url}}styles/{{id}}/style.json{{&key_query}}',
|
||||
hash: true
|
||||
});
|
||||
map.addControl(new mapboxgl.Navigation());
|
||||
map.addControl(new mapboxgl.NavigationControl());
|
||||
} else {
|
||||
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&access_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);
|
||||
setTimeout(function() {
|
||||
new L.Hash(map);
|
||||
|
||||
407
public/templates/wmts.tmpl
Normal file
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>
|
||||
53
publish.js
Normal file
53
publish.js
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* This script creates `tileserver-gl-light` version
|
||||
* (without native dependencies) and publishes
|
||||
* `tileserver-gl` and `tileserver-gl-light` to npm.
|
||||
*/
|
||||
|
||||
/* CREATE tileserver-gl-light */
|
||||
|
||||
// SYNC THE `light` FOLDER
|
||||
require('child_process').execSync('rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light', {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
// PATCH `package.json`
|
||||
var fs = require('fs');
|
||||
var packageJson = require('./package');
|
||||
|
||||
packageJson.name += '-light';
|
||||
packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles';
|
||||
delete packageJson.dependencies['canvas'];
|
||||
delete packageJson.dependencies['@mapbox/mapbox-gl-native'];
|
||||
delete packageJson.dependencies['sharp'];
|
||||
|
||||
delete packageJson.optionalDependencies;
|
||||
delete packageJson.devDependencies;
|
||||
|
||||
packageJson.engines.node = '>= 10';
|
||||
|
||||
var str = JSON.stringify(packageJson, undefined, 2);
|
||||
fs.writeFileSync('light/package.json', str);
|
||||
fs.renameSync('light/README_light.md', 'light/README.md');
|
||||
fs.renameSync('light/Dockerfile_light', 'light/Dockerfile');
|
||||
|
||||
// for Build tileserver-gl-light docker image, don't publish
|
||||
if (process.argv.length > 2 && process.argv[2] == "--no-publish") {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
/* PUBLISH */
|
||||
|
||||
// tileserver-gl
|
||||
require('child_process').execSync('npm publish .', {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
// tileserver-gl-light
|
||||
require('child_process').execSync('npm publish light', {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
37
run.sh
37
run.sh
@@ -1,2 +1,37 @@
|
||||
#!/bin/bash
|
||||
xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/src/main.js -p 80 -c /data/config.json
|
||||
|
||||
_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
|
||||
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
|
||||
|
||||
229
src/main.js
229
src/main.js
@@ -2,27 +2,212 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var opts = require('nomnom')
|
||||
.option('config', {
|
||||
abbr: 'c',
|
||||
default: 'config.json',
|
||||
help: 'Configuration file'
|
||||
})
|
||||
.option('port', {
|
||||
abbr: 'p',
|
||||
default: 8080,
|
||||
help: 'Port'
|
||||
})
|
||||
.option('version', {
|
||||
abbr: 'v',
|
||||
flag: true,
|
||||
help: 'Version info',
|
||||
callback: function() {
|
||||
return 'version ' + require('../package.json').version;
|
||||
}
|
||||
}).parse();
|
||||
require = require('esm')(module);
|
||||
|
||||
return require('./server')({
|
||||
config: opts.config,
|
||||
port: opts.port
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const request = require('request');
|
||||
|
||||
const MBTiles = require('@mapbox/mbtiles');
|
||||
|
||||
const packageJson = require('../package');
|
||||
|
||||
const args = process.argv;
|
||||
if (args.length >= 3 && args[2][0] !== '-') {
|
||||
args.splice(2, 0, '--mbtiles');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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')({
|
||||
configPath: configPath,
|
||||
config: config,
|
||||
bind: opts.bind,
|
||||
port: opts.port,
|
||||
cors: opts.cors,
|
||||
verbose: opts.verbose,
|
||||
silent: opts.silent,
|
||||
logFile: opts.log_file,
|
||||
logFormat: opts.log_format,
|
||||
publicUrl: publicUrl
|
||||
});
|
||||
};
|
||||
|
||||
const startWithMBTiles = (mbtilesFile) => {
|
||||
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
|
||||
console.log(`[INFO] Only a basic preview style will be used.`);
|
||||
console.log(`[INFO] See documentation to learn how to create config.json file.`);
|
||||
|
||||
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
|
||||
|
||||
const mbtilesStats = fs.statSync(mbtilesFile);
|
||||
if (!mbtilesStats.isFile() || mbtilesStats.size === 0) {
|
||||
console.log(`ERROR: Not valid MBTiles file: ${mbtilesFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const instance = new MBTiles(mbtilesFile, (err) => {
|
||||
if (err) {
|
||||
console.log('ERROR: Unable to open MBTiles.');
|
||||
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
instance.getInfo((err, info) => {
|
||||
if (err || !info) {
|
||||
console.log('ERROR: Metadata missing in the MBTiles.');
|
||||
console.log(` Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
||||
process.exit(1);
|
||||
}
|
||||
const bounds = info.bounds;
|
||||
|
||||
const styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
|
||||
|
||||
const config = {
|
||||
"options": {
|
||||
"paths": {
|
||||
"root": styleDir,
|
||||
"fonts": "fonts",
|
||||
"styles": "styles",
|
||||
"mbtiles": path.dirname(mbtilesFile)
|
||||
}
|
||||
},
|
||||
"styles": {},
|
||||
"data": {}
|
||||
};
|
||||
|
||||
if (info.format === 'pbf' &&
|
||||
info.name.toLowerCase().indexOf('openmaptiles') > -1) {
|
||||
|
||||
config['data'][`v3`] = {
|
||||
"mbtiles": path.basename(mbtilesFile)
|
||||
};
|
||||
|
||||
|
||||
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
||||
for (let styleName of styles) {
|
||||
const styleFileRel = styleName + '/style.json';
|
||||
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
||||
if (fs.existsSync(styleFile)) {
|
||||
config['styles'][styleName] = {
|
||||
"style": styleFileRel,
|
||||
"tilejson": {
|
||||
"bounds": bounds
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`);
|
||||
config['data'][(info.id || 'mbtiles')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/:/g, '_')
|
||||
.replace(/\?/g, '_')] = {
|
||||
"mbtiles": path.basename(mbtilesFile)
|
||||
};
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
console.log(JSON.stringify(config, undefined, 2));
|
||||
} else {
|
||||
console.log('Run with --verbose to see the config file here.');
|
||||
}
|
||||
|
||||
return startServer(null, config);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
fs.stat(path.resolve(opts.config), (err, stats) => {
|
||||
if (err || !stats.isFile() || stats.size === 0) {
|
||||
let mbtiles = opts.mbtiles;
|
||||
if (!mbtiles) {
|
||||
// try to find in the cwd
|
||||
const files = fs.readdirSync(process.cwd());
|
||||
for (let filename of files) {
|
||||
if (filename.endsWith('.mbtiles')) {
|
||||
const mbTilesStats = fs.statSync(filename);
|
||||
if (mbTilesStats.isFile() && mbTilesStats.size > 0) {
|
||||
mbtiles = filename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mbtiles) {
|
||||
console.log(`No MBTiles specified, using ${mbtiles}`);
|
||||
return startWithMBTiles(mbtiles);
|
||||
} else {
|
||||
const url = 'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
||||
const filename = 'zurich_switzerland.mbtiles';
|
||||
const stream = fs.createWriteStream(filename);
|
||||
console.log(`No MBTiles found`);
|
||||
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
|
||||
stream.on('finish', () => startWithMBTiles(filename));
|
||||
return request.get(url).pipe(stream);
|
||||
}
|
||||
}
|
||||
if (mbtiles) {
|
||||
return startWithMBTiles(mbtiles);
|
||||
}
|
||||
} else {
|
||||
console.log(`Using specified config file from ${opts.config}`);
|
||||
return startServer(opts.config, null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,87 +1,171 @@
|
||||
'use strict';
|
||||
|
||||
var crypto = require('crypto'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
|
||||
var clone = require('clone'),
|
||||
express = require('express'),
|
||||
mbtiles = require('mbtiles');
|
||||
const clone = require('clone');
|
||||
const express = require('express');
|
||||
const MBTiles = require('@mapbox/mbtiles');
|
||||
const Pbf = require('pbf');
|
||||
const VectorTile = require('@mapbox/vector-tile').VectorTile;
|
||||
|
||||
var utils = require('./utils');
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = function(options, repo, params, id) {
|
||||
var app = express().disable('x-powered-by');
|
||||
module.exports = {
|
||||
init: (options, repo) => {
|
||||
const app = express().disable('x-powered-by');
|
||||
|
||||
var mbtilesFile = path.join(options.paths.mbtiles, params.mbtiles);
|
||||
var tileJSON = {
|
||||
'tiles': params.domains || options.domains
|
||||
};
|
||||
app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
let tileJSONFormat = item.tileJSON.format;
|
||||
const z = req.params.z | 0;
|
||||
const x = req.params.x | 0;
|
||||
const y = req.params.y | 0;
|
||||
let format = req.params.format;
|
||||
if (format === options.pbfAlias) {
|
||||
format = 'pbf';
|
||||
}
|
||||
if (format !== tileJSONFormat &&
|
||||
!(format === 'geojson' && tileJSONFormat === 'pbf')) {
|
||||
return res.status(404).send('Invalid format');
|
||||
}
|
||||
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
|
||||
z > item.tileJSON.maxzoom ||
|
||||
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
||||
return res.status(404).send('Out of bounds');
|
||||
}
|
||||
item.source.getTile(z, x, y, (err, data, headers) => {
|
||||
let isGzipped;
|
||||
if (err) {
|
||||
if (/does not exist/.test(err.message)) {
|
||||
return res.status(204).send();
|
||||
} else {
|
||||
return res.status(500).send(err.message);
|
||||
}
|
||||
} else {
|
||||
if (data == null) {
|
||||
return res.status(404).send('Not found');
|
||||
} else {
|
||||
if (tileJSONFormat === 'pbf') {
|
||||
isGzipped = data.slice(0, 2).indexOf(
|
||||
Buffer.from([0x1f, 0x8b])) === 0;
|
||||
if (options.dataDecoratorFunc) {
|
||||
if (isGzipped) {
|
||||
data = zlib.unzipSync(data);
|
||||
isGzipped = false;
|
||||
}
|
||||
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
||||
}
|
||||
}
|
||||
if (format === 'pbf') {
|
||||
headers['Content-Type'] = 'application/x-protobuf';
|
||||
} else if (format === 'geojson') {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
repo[id] = tileJSON;
|
||||
if (isGzipped) {
|
||||
data = zlib.unzipSync(data);
|
||||
isGzipped = false;
|
||||
}
|
||||
|
||||
var source = new mbtiles(mbtilesFile, function(err) {
|
||||
source.getInfo(function(err, info) {
|
||||
tileJSON['name'] = id;
|
||||
tileJSON['format'] = 'pbf';
|
||||
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);
|
||||
}
|
||||
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
||||
headers['Content-Encoding'] = 'gzip';
|
||||
res.set(headers);
|
||||
|
||||
Object.assign(tileJSON, info);
|
||||
if (!isGzipped) {
|
||||
data = zlib.gzipSync(data);
|
||||
isGzipped = true;
|
||||
}
|
||||
|
||||
tileJSON['tilejson'] = '2.0.0';
|
||||
tileJSON['basename'] = id;
|
||||
tileJSON['filesize'] = fs.statSync(mbtilesFile)['size'];
|
||||
delete tileJSON['scheme'];
|
||||
|
||||
Object.assign(tileJSON, params.tilejson || {});
|
||||
utils.fixTileJSONCenter(tileJSON);
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var tilePattern = '/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w]+)';
|
||||
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);
|
||||
});
|
||||
|
||||
app.get(tilePattern, function(req, res, next) {
|
||||
var z = req.params.z | 0,
|
||||
x = req.params.x | 0,
|
||||
y = req.params.y | 0;
|
||||
if (req.params.format != tileJSON.format) {
|
||||
return res.status(404).send('Invalid format');
|
||||
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}`);
|
||||
}
|
||||
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 {
|
||||
return res.status(500).send(err.message);
|
||||
let source;
|
||||
const sourceInfoPromise = new Promise((resolve, reject) => {
|
||||
source = new MBTiles(mbtilesFile, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
var md5 = crypto.createHash('md5').update(data).digest('base64');
|
||||
headers['content-md5'] = md5;
|
||||
if (tileJSON['format'] == 'pbf') {
|
||||
headers['content-type'] = 'application/x-protobuf';
|
||||
headers['content-encoding'] = 'gzip';
|
||||
}
|
||||
res.set(headers);
|
||||
source.getInfo((err, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
tileJSON['name'] = id;
|
||||
tileJSON['format'] = 'pbf';
|
||||
|
||||
if (data == null) {
|
||||
return res.status(404).send('Not found');
|
||||
} else {
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
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);
|
||||
return res.send(info);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
path = require('path'),
|
||||
fs = require('fs');
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
var clone = require('clone'),
|
||||
express = require('express');
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = (options, allowedFonts) => {
|
||||
const app = express().disable('x-powered-by');
|
||||
|
||||
module.exports = function(options, allowedFonts) {
|
||||
var app = express().disable('x-powered-by');
|
||||
const lastModified = new Date().toUTCString();
|
||||
|
||||
var fontPath = options.paths.fonts;
|
||||
const fontPath = options.paths.fonts;
|
||||
|
||||
var getFontPbf = function(name, range, callback) {
|
||||
// if some of the files failed to load (does not exist or not allowed),
|
||||
// return empty buffer so the other fonts can still work
|
||||
if (allowedFonts[name]) {
|
||||
var filename = path.join(fontPath, name, range + '.pbf');
|
||||
return fs.readFile(filename, function(err, data) {
|
||||
if (err) {
|
||||
console.log('Font load error:', filename);
|
||||
return callback(null, new Buffer([]));
|
||||
} else {
|
||||
return callback(null, data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return callback(null, new Buffer([]));
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/:fontstack/:range([\\d]+-[\\d]+).pbf',
|
||||
function(req, res, next) {
|
||||
var fontstack = decodeURI(req.params.fontstack);
|
||||
var range = req.params.range;
|
||||
|
||||
var fonts = fontstack.split(',');
|
||||
|
||||
var queue = [];
|
||||
fonts.forEach(function(font) {
|
||||
queue.push(function(callback) {
|
||||
getFontPbf(font, range, callback);
|
||||
});
|
||||
});
|
||||
|
||||
return async.parallel(queue, function(err, results) {
|
||||
var concated = Buffer.concat(results);
|
||||
if (err || concated.length == 0) {
|
||||
return res.status(400).send('');
|
||||
} else {
|
||||
res.header('Content-type', 'application/x-protobuf');
|
||||
return res.send(concated);
|
||||
const existingFonts = {};
|
||||
const fontListingPromise = new Promise((resolve, reject) => {
|
||||
fs.readdir(options.paths.fonts, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
fs.stat(path.join(fontPath, file), (err, stats) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (stats.isDirectory() &&
|
||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
|
||||
existingFonts[path.basename(file)] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => {
|
||||
const fontstack = decodeURI(req.params.fontstack);
|
||||
const range = req.params.range;
|
||||
|
||||
utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
|
||||
fontPath, fontstack, range, existingFonts).then(concated => {
|
||||
res.header('Content-type', 'application/x-protobuf');
|
||||
res.header('Last-Modified', lastModified);
|
||||
return res.send(concated);
|
||||
}, err => res.status(400).send(err)
|
||||
);
|
||||
});
|
||||
|
||||
app.get('/fonts.json', (req, res, next) => {
|
||||
res.header('Content-type', 'application/json');
|
||||
return res.send(
|
||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
|
||||
);
|
||||
});
|
||||
|
||||
return fontListingPromise.then(() => app);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,87 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
var clone = require('clone'),
|
||||
express = require('express');
|
||||
const clone = require('clone');
|
||||
const express = require('express');
|
||||
import {validate} from '@mapbox/mapbox-gl-style-spec';
|
||||
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = function(options, repo, params, id, reportTiles, reportFont) {
|
||||
var app = express().disable('x-powered-by');
|
||||
const httpTester = /^(http(s)?:)?\/\//;
|
||||
|
||||
var styleFile = path.join(options.paths.styles, params.style);
|
||||
const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
||||
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
|
||||
return url;
|
||||
}
|
||||
const queryParams = [];
|
||||
if (!opt_nokey && req.query.key) {
|
||||
queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`);
|
||||
}
|
||||
let query = '';
|
||||
if (queryParams.length) {
|
||||
query = `?${queryParams.join('&')}`;
|
||||
}
|
||||
return url.replace(
|
||||
'local://', utils.getPublicUrl(publicUrl, req)) + query;
|
||||
};
|
||||
|
||||
var styleJSON = clone(require(styleFile));
|
||||
Object.keys(styleJSON.sources).forEach(function(name) {
|
||||
var source = styleJSON.sources[name];
|
||||
var url = source.url;
|
||||
if (url.lastIndexOf('mbtiles:', 0) === 0) {
|
||||
var mbtiles = url.substring('mbtiles://'.length);
|
||||
var identifier = reportTiles(mbtiles);
|
||||
source.url = 'local://data/' + identifier + '.json';
|
||||
}
|
||||
});
|
||||
module.exports = {
|
||||
init: (options, repo) => {
|
||||
const app = express().disable('x-powered-by');
|
||||
|
||||
var findFontReferences = function(obj) {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
var value = obj[key];
|
||||
if (key == 'text-font') {
|
||||
if (value && value.length > 0) {
|
||||
value.forEach(reportFont);
|
||||
}
|
||||
} else if (value && typeof value == 'object') {
|
||||
findFontReferences(value);
|
||||
app.get('/:id/style.json', (req, res, next) => {
|
||||
const item = repo[req.params.id];
|
||||
if (!item) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
const styleJSON_ = clone(item.styleJSON);
|
||||
for (const name of Object.keys(styleJSON_.sources)) {
|
||||
const source = styleJSON_.sources[name];
|
||||
source.url = fixUrl(req, source.url, item.publicUrl);
|
||||
}
|
||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
||||
if (styleJSON_.sprite) {
|
||||
styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl, false);
|
||||
}
|
||||
if (styleJSON_.glyphs) {
|
||||
styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl, false);
|
||||
}
|
||||
return res.send(styleJSON_);
|
||||
});
|
||||
};
|
||||
styleJSON.layers.forEach(findFontReferences);
|
||||
|
||||
var spritePath = path.join(options.paths.sprites,
|
||||
path.basename(styleFile, '.json'));
|
||||
|
||||
styleJSON.sprite = 'local://styles/' + id + '/sprite';
|
||||
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
||||
|
||||
repo[id] = styleJSON;
|
||||
|
||||
app.get('/' + id + '.json', function(req, res, next) {
|
||||
var fixUrl = function(url, opt_nokey) {
|
||||
var query = '';
|
||||
if (!opt_nokey && req.query.key) {
|
||||
query = '?key=' + req.query.key;
|
||||
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);
|
||||
}
|
||||
return url.replace(
|
||||
'local://', req.protocol + '://' + req.headers.host + '/') + query;
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let spritePath;
|
||||
|
||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
||||
spritePath = path.join(options.paths.sprites,
|
||||
styleJSON.sprite
|
||||
.replace('{style}', path.basename(styleFile, '.json'))
|
||||
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
||||
);
|
||||
styleJSON.sprite = `local://styles/${id}/sprite`;
|
||||
}
|
||||
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
||||
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
||||
}
|
||||
|
||||
repo[id] = {
|
||||
styleJSON,
|
||||
spritePath,
|
||||
publicUrl,
|
||||
name: styleJSON.name
|
||||
};
|
||||
|
||||
var styleJSON_ = clone(styleJSON);
|
||||
Object.keys(styleJSON_.sources).forEach(function(name) {
|
||||
var source = styleJSON_.sources[name];
|
||||
source.url = fixUrl(source.url);
|
||||
});
|
||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
||||
styleJSON_.sprite = fixUrl(styleJSON_.sprite, true);
|
||||
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs);
|
||||
return res.send(styleJSON_);
|
||||
});
|
||||
|
||||
app.get('/' + id + '/sprite:scale(@[23]x)?\.:format([\\w]+)',
|
||||
function(req, res, next) {
|
||||
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 app;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
572
src/server.js
572
src/server.js
@@ -4,151 +4,319 @@
|
||||
process.env.UV_THREADPOOL_SIZE =
|
||||
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
var clone = require('clone'),
|
||||
cors = require('cors'),
|
||||
express = require('express'),
|
||||
handlebars = require('handlebars'),
|
||||
mercator = new (require('sphericalmercator'))(),
|
||||
morgan = require('morgan');
|
||||
const chokidar = require('chokidar');
|
||||
const clone = require('clone');
|
||||
const cors = require('cors');
|
||||
const enableShutdown = require('http-shutdown');
|
||||
const express = require('express');
|
||||
const handlebars = require('handlebars');
|
||||
const mercator = new (require('@mapbox/sphericalmercator'))();
|
||||
const morgan = require('morgan');
|
||||
|
||||
var serve_font = require('./serve_font'),
|
||||
serve_rendered = require('./serve_rendered'),
|
||||
serve_style = require('./serve_style'),
|
||||
serve_data = require('./serve_data'),
|
||||
utils = require('./utils');
|
||||
const packageJson = require('../package');
|
||||
const serve_font = require('./serve_font');
|
||||
const serve_style = require('./serve_style');
|
||||
const serve_data = require('./serve_data');
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = function(opts, callback) {
|
||||
var app = express().disable('x-powered-by'),
|
||||
serving = {
|
||||
styles: {},
|
||||
rendered: {},
|
||||
data: {},
|
||||
fonts: { // default fonts, always expose these (if they exist)
|
||||
'Open Sans Regular': true,
|
||||
'Arial Unicode MS Regular': true
|
||||
}
|
||||
};
|
||||
let serve_rendered = null;
|
||||
const isLight = packageJson.name.slice(-6) === '-light';
|
||||
if (!isLight) {
|
||||
// do not require `serve_rendered` in the light package
|
||||
serve_rendered = require('./serve_rendered');
|
||||
}
|
||||
|
||||
function start(opts) {
|
||||
console.log('Starting server');
|
||||
|
||||
const app = express().disable('x-powered-by'),
|
||||
serving = {
|
||||
styles: {},
|
||||
rendered: {},
|
||||
data: {},
|
||||
fonts: {}
|
||||
};
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
callback = callback || function() {};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' &&
|
||||
process.env.NODE_ENV !== 'test') {
|
||||
app.use(morgan('dev'));
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
||||
const logFormat = opts.logFormat || defaultLogFormat;
|
||||
app.use(morgan(logFormat, {
|
||||
stream: opts.logFile ? fs.createWriteStream(opts.logFile, { flags: 'a' }) : process.stdout,
|
||||
skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304)
|
||||
}));
|
||||
}
|
||||
|
||||
var configPath = path.resolve(opts.config);
|
||||
|
||||
var config;
|
||||
try {
|
||||
config = require(configPath);
|
||||
} catch (e) {
|
||||
console.log('ERROR: Config file not found or invalid!');
|
||||
console.log(' See README.md for instructions and sample data.');
|
||||
let config = opts.config || null;
|
||||
let configPath = null;
|
||||
if (opts.configPath) {
|
||||
configPath = path.resolve(opts.configPath);
|
||||
try {
|
||||
config = clone(require(configPath));
|
||||
} catch (e) {
|
||||
console.log('ERROR: Config file not found or invalid!');
|
||||
console.log(' See README.md for instructions and sample data.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (!config) {
|
||||
console.log('ERROR: No config file not specified!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var options = config.options || {};
|
||||
var paths = options.paths || {};
|
||||
const options = config.options || {};
|
||||
const paths = options.paths || {};
|
||||
options.paths = paths;
|
||||
paths.root = path.resolve(process.cwd(), paths.root || '');
|
||||
paths.root = path.resolve(
|
||||
configPath ? path.dirname(configPath) : process.cwd(),
|
||||
paths.root || '');
|
||||
paths.styles = path.resolve(paths.root, paths.styles || '');
|
||||
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
||||
|
||||
var data = clone(config.data || {});
|
||||
const startupPromises = [];
|
||||
|
||||
app.use(cors());
|
||||
|
||||
Object.keys(config.styles || {}).forEach(function(id) {
|
||||
var item = config.styles[id];
|
||||
if (!item.style || item.style.length == 0) {
|
||||
console.log('Missing "style" property for ' + id);
|
||||
return;
|
||||
const checkPath = type => {
|
||||
if (!fs.existsSync(paths[type])) {
|
||||
console.error(`The specified path for "${type}" does not exist (${paths[type]}).`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
checkPath('styles');
|
||||
checkPath('fonts');
|
||||
checkPath('sprites');
|
||||
checkPath('mbtiles');
|
||||
|
||||
if (options.dataDecorator) {
|
||||
try {
|
||||
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const data = clone(config.data || {});
|
||||
|
||||
if (opts.cors) {
|
||||
app.use(cors());
|
||||
}
|
||||
|
||||
app.use('/data/', serve_data.init(options, serving.data));
|
||||
app.use('/styles/', serve_style.init(options, serving.styles));
|
||||
if (serve_rendered) {
|
||||
startupPromises.push(
|
||||
serve_rendered.init(options, serving.rendered)
|
||||
.then(sub => {
|
||||
app.use('/styles/', sub);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let addStyle = (id, item, allowMoreData, reportFonts) => {
|
||||
let success = true;
|
||||
if (item.serve_data !== false) {
|
||||
app.use('/styles/', serve_style(options, serving.styles, item, id,
|
||||
function(mbtiles) {
|
||||
var dataItemId;
|
||||
Object.keys(data).forEach(function(id) {
|
||||
if (data[id].mbtiles == mbtiles) {
|
||||
dataItemId = id;
|
||||
success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
|
||||
(mbtiles, fromData) => {
|
||||
let dataItemId;
|
||||
for (const id of Object.keys(serving.data)) {
|
||||
if (fromData) {
|
||||
if (id === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
} else {
|
||||
if (serving.data[id].mbtiles === mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (dataItemId) { // mbtiles exist in the data config
|
||||
return dataItemId;
|
||||
} else {
|
||||
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||
while (data[id]) id += '_';
|
||||
data[id] = {
|
||||
'mbtiles': mbtiles
|
||||
};
|
||||
return id;
|
||||
if (fromData || !allowMoreData) {
|
||||
console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
|
||||
return undefined;
|
||||
} else {
|
||||
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||
//while (data[id]) id += '_';
|
||||
data[id] = {
|
||||
'mbtiles': mbtiles
|
||||
};
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}, function(font) {
|
||||
serving.fonts[font] = true;
|
||||
}));
|
||||
}, font => {
|
||||
if (reportFonts) {
|
||||
serving.fonts[font] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (item.serve_rendered !== false) {
|
||||
app.use('/styles/' + id + '/',
|
||||
serve_rendered(options, serving.rendered, item, id));
|
||||
if (success && item.serve_rendered !== false) {
|
||||
if (serve_rendered) {
|
||||
startupPromises.push(serve_rendered.add(options, serving.rendered, item, id, opts.publicUrl,
|
||||
mbtiles => {
|
||||
let mbtilesFile;
|
||||
for (const id of Object.keys(data)) {
|
||||
if (id === mbtiles) {
|
||||
mbtilesFile = data[id].mbtiles;
|
||||
}
|
||||
}
|
||||
return mbtilesFile;
|
||||
}
|
||||
));
|
||||
} else {
|
||||
item.serve_rendered = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (Object.keys(serving.styles).length > 0) {
|
||||
// serve fonts only if serving some styles
|
||||
app.use('/fonts/', serve_font(options, serving.fonts));
|
||||
let addData = (id, item) => {
|
||||
console.log(`Add data ${id}`);
|
||||
Promise.all([serve_data.add(options, serving.data, item, id, opts.publicUrl)]);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Object.keys(data).forEach(function(id) {
|
||||
var item = data[id];
|
||||
if (!item.mbtiles || item.mbtiles.length == 0) {
|
||||
console.log('Missing "mbtiles" property for ' + id);
|
||||
return;
|
||||
startupPromises.push(
|
||||
serve_font(options, serving.fonts).then(sub => {
|
||||
app.use('/', sub);
|
||||
})
|
||||
);
|
||||
|
||||
for (const id of Object.keys(data)) {
|
||||
const item = data[id];
|
||||
if (!item.mbtiles || item.mbtiles.length === 0) {
|
||||
console.log(`Missing "mbtiles" property for ${id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
app.use('/data/', serve_data(options, serving.data, item, id));
|
||||
});
|
||||
startupPromises.push(
|
||||
serve_data.add(options, serving.data, item, id, opts.publicUrl)
|
||||
);
|
||||
}
|
||||
|
||||
app.get('/styles.json', function(req, res, next) {
|
||||
var result = [];
|
||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
||||
Object.keys(serving.styles).forEach(function(id) {
|
||||
var styleJSON = serving.styles[id];
|
||||
if (options.serveAllStyles) {
|
||||
fs.readdir(options.paths.mbtiles, {withFileTypes: true}, (err, files) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
if (file.isFile() && path.extname(file.name).toLowerCase() == '.mbtiles') {
|
||||
let id = path.basename(file.name, '.mbtiles');
|
||||
let item = {
|
||||
mbtiles: file.name
|
||||
};
|
||||
addData(id, item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
if (file.isFile() &&
|
||||
path.extname(file.name).toLowerCase() == '.json') {
|
||||
let id = path.basename(file.name, '.json');
|
||||
let item = {
|
||||
style: file.name
|
||||
};
|
||||
addStyle(id, item, true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const watcherData = chokidar.watch(path.join(options.paths.mbtiles, '*.mbtiles'),
|
||||
{
|
||||
});
|
||||
watcherData.on('all', (eventType, filename) => {
|
||||
if (filename) {
|
||||
let id = path.basename(filename, '.mbtiles');
|
||||
console.log(`Data "${id}" added`);
|
||||
|
||||
if (eventType == "add") {
|
||||
let item = {
|
||||
mbtiles: filename
|
||||
};
|
||||
addData(id, item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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, true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/styles.json', (req, res, next) => {
|
||||
const result = [];
|
||||
const query = req.query.key ? (`?key=${encodeURIComponent(req.query.key)}`) : '';
|
||||
for (const id of Object.keys(serving.styles)) {
|
||||
const styleJSON = serving.styles[id].styleJSON;
|
||||
result.push({
|
||||
version: styleJSON.version,
|
||||
name: styleJSON.name,
|
||||
id: id,
|
||||
url: req.protocol + '://' + req.headers.host +
|
||||
'/styles/' + id + '.json' + query
|
||||
url: `${utils.getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}`
|
||||
});
|
||||
});
|
||||
}
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
var addTileJSONs = function(arr, req, type) {
|
||||
Object.keys(serving[type]).forEach(function(id) {
|
||||
var info = clone(serving[type][id]);
|
||||
info.tiles = utils.getTileUrls(req, info.tiles,
|
||||
type + '/' + id, info.format);
|
||||
const addTileJSONs = (arr, req, type) => {
|
||||
for (const id of Object.keys(serving[type])) {
|
||||
const info = clone(serving[type][id].tileJSON);
|
||||
let path = '';
|
||||
if (type === 'rendered') {
|
||||
path = `styles/${id}`;
|
||||
} else {
|
||||
path = `${type}/${id}`;
|
||||
}
|
||||
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
|
||||
'pbf': options.pbfAlias
|
||||
});
|
||||
arr.push(info);
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
app.get('/rendered.json', function(req, res, next) {
|
||||
app.get('/rendered.json', (req, res, next) => {
|
||||
res.send(addTileJSONs([], req, 'rendered'));
|
||||
});
|
||||
app.get('/data.json', function(req, res, next) {
|
||||
app.get('/data.json', (req, res, next) => {
|
||||
res.send(addTileJSONs([], req, 'data'));
|
||||
});
|
||||
app.get('/index.json', function(req, res, next) {
|
||||
app.get('/index.json', (req, res, next) => {
|
||||
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
|
||||
});
|
||||
|
||||
@@ -156,82 +324,92 @@ module.exports = function(opts, callback) {
|
||||
// serve web presentations
|
||||
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
||||
|
||||
var templates = path.join(__dirname, '../public/templates');
|
||||
var serveTemplate = function(path, template, dataGetter) {
|
||||
fs.readFile(templates + '/' + template + '.tmpl', function(err, content) {
|
||||
if (err) {
|
||||
console.log('Template not found:', err);
|
||||
const templates = path.join(__dirname, '../public/templates');
|
||||
const serveTemplate = (urlPath, template, dataGetter) => {
|
||||
let templateFile = `${templates}/${template}.tmpl`;
|
||||
if (template === 'index') {
|
||||
if (options.frontPage === false) {
|
||||
return;
|
||||
} else if (options.frontPage &&
|
||||
options.frontPage.constructor === String) {
|
||||
templateFile = path.resolve(paths.root, options.frontPage);
|
||||
}
|
||||
var compiled = handlebars.compile(content.toString());
|
||||
|
||||
app.use(path, function(req, res, next) {
|
||||
var data = {};
|
||||
if (dataGetter) {
|
||||
data = dataGetter(req);
|
||||
if (!data) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
}
|
||||
startupPromises.push(new Promise((resolve, reject) => {
|
||||
fs.readFile(templateFile, (err, content) => {
|
||||
if (err) {
|
||||
err = new Error(`Template not found: ${err.message}`);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
data['access_key'] = req.query.key;
|
||||
data['access_key_query'] = req.query.key ? '?key=' + req.query.key : '';
|
||||
return res.status(200).send(compiled(data));
|
||||
const compiled = handlebars.compile(content.toString());
|
||||
|
||||
app.use(urlPath, (req, res, next) => {
|
||||
let data = {};
|
||||
if (dataGetter) {
|
||||
data = dataGetter(req);
|
||||
if (!data) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
}
|
||||
data['server_version'] = `${packageJson.name} v${packageJson.version}`;
|
||||
data['public_url'] = opts.publicUrl || '/';
|
||||
data['is_light'] = isLight;
|
||||
data['key_query_part'] =
|
||||
req.query.key ? `key=${encodeURIComponent(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));
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
serveTemplate('/$', 'index', function(req) {
|
||||
var styles = clone(config.styles || {});
|
||||
Object.keys(styles).forEach(function(id) {
|
||||
var style = styles[id];
|
||||
serveTemplate('/$', 'index', req => {
|
||||
const styles = clone(serving.styles || {});
|
||||
for (const id of Object.keys(styles)) {
|
||||
const style = styles[id];
|
||||
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
|
||||
style.serving_data = serving.styles[id];
|
||||
style.serving_rendered = serving.rendered[id];
|
||||
if (style.serving_rendered) {
|
||||
var center = style.serving_rendered.center;
|
||||
const center = style.serving_rendered.tileJSON.center;
|
||||
if (center) {
|
||||
style.viewer_hash = '#' + center[2] + '/' +
|
||||
center[1].toFixed(5) + '/' +
|
||||
center[0].toFixed(5);
|
||||
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
||||
|
||||
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||
style.thumbnail = center[2] + '/' +
|
||||
Math.floor(centerPx[0] / 256) + '/' +
|
||||
Math.floor(centerPx[1] / 256) + '.png';
|
||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||
style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
|
||||
}
|
||||
|
||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
||||
style.wmts_link = 'https://wmts.maptiler.com/' +
|
||||
new Buffer(req.protocol + '://' + req.headers.host +
|
||||
'/styles/' + id + '/rendered.json' + query).toString('base64') +
|
||||
'/wmts';
|
||||
style.xyz_link = utils.getTileUrls(
|
||||
req, style.serving_rendered.tileJSON.tiles,
|
||||
`styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
|
||||
}
|
||||
});
|
||||
var data = clone(serving.data || {});
|
||||
Object.keys(data).forEach(function(id) {
|
||||
var data_ = data[id];
|
||||
var center = data_.center;
|
||||
}
|
||||
const data = clone(serving.data || {});
|
||||
for (const id of Object.keys(data)) {
|
||||
const data_ = data[id];
|
||||
const tilejson = data[id].tileJSON;
|
||||
const center = tilejson.center;
|
||||
if (center) {
|
||||
data_.viewer_hash = '#' + center[2] + '/' +
|
||||
center[1].toFixed(5) + '/' +
|
||||
center[0].toFixed(5);
|
||||
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
|
||||
}
|
||||
data_.is_vector = data_.format == 'pbf';
|
||||
data_.is_vector = tilejson.format === 'pbf';
|
||||
if (!data_.is_vector) {
|
||||
if (center) {
|
||||
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||
data_.thumbnail = center[2] + '/' +
|
||||
Math.floor(centerPx[0] / 256) + '/' +
|
||||
Math.floor(centerPx[1] / 256) + '.' + data_.format;
|
||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||
data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
||||
}
|
||||
|
||||
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
||||
data_.wmts_link = 'https://wmts.maptiler.com/' +
|
||||
new Buffer(req.protocol + '://' + req.headers.host +
|
||||
'/data/' + id + '.json' + query).toString('base64') + '/wmts';
|
||||
data_.xyz_link = utils.getTileUrls(
|
||||
req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
|
||||
'pbf': options.pbfAlias
|
||||
})[0];
|
||||
}
|
||||
if (data_.filesize) {
|
||||
var suffix = 'kB';
|
||||
var size = parseInt(data_.filesize, 10) / 1024;
|
||||
let suffix = 'kB';
|
||||
let size = parseInt(data_.filesize, 10) / 1024;
|
||||
if (size > 1024) {
|
||||
suffix = 'MB';
|
||||
size /= 1024;
|
||||
@@ -240,18 +418,18 @@ module.exports = function(opts, callback) {
|
||||
suffix = 'GB';
|
||||
size /= 1024;
|
||||
}
|
||||
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
|
||||
data_.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
styles: styles,
|
||||
data: data
|
||||
styles: Object.keys(styles).length ? styles : null,
|
||||
data: Object.keys(data).length ? data : null
|
||||
};
|
||||
});
|
||||
|
||||
serveTemplate('/styles/:id/$', 'viewer', function(req) {
|
||||
var id = req.params.id;
|
||||
var style = clone((config.styles || {})[id]);
|
||||
serveTemplate('/styles/:id/$', 'viewer', req => {
|
||||
const id = req.params.id;
|
||||
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
|
||||
if (!style) {
|
||||
return null;
|
||||
}
|
||||
@@ -267,28 +445,88 @@ module.exports = function(opts, callback) {
|
||||
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) {
|
||||
var id = req.params.id;
|
||||
var data = clone(serving.data[id]);
|
||||
serveTemplate('/data/:id/$', 'data', req => {
|
||||
const id = req.params.id;
|
||||
const data = clone(serving.data[id]);
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
data.id = id;
|
||||
data.is_vector = data.format == 'pbf';
|
||||
data.is_vector = data.tileJSON.format === 'pbf';
|
||||
return data;
|
||||
});
|
||||
|
||||
var server = app.listen(process.env.PORT || opts.port, function() {
|
||||
console.log('Listening at http://%s:%d/',
|
||||
this.address().address, this.address().port);
|
||||
|
||||
return callback();
|
||||
let startupComplete = false;
|
||||
const startupPromise = Promise.all(startupPromises).then(() => {
|
||||
console.log('Startup complete');
|
||||
startupComplete = true;
|
||||
});
|
||||
app.get('/health', (req, res, next) => {
|
||||
if (startupComplete) {
|
||||
return res.status(200).send('OK');
|
||||
} else {
|
||||
return res.status(503).send('Starting');
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(callback, 1000);
|
||||
const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function () {
|
||||
let address = this.address().address;
|
||||
if (address.indexOf('::') === 0) {
|
||||
address = `[${address}]`; // literal IPv6 address
|
||||
}
|
||||
console.log(`Listening at http://${address}:${this.address().port}/`);
|
||||
});
|
||||
|
||||
// add server.shutdown() to gracefully stop serving
|
||||
enableShutdown(server);
|
||||
|
||||
return {
|
||||
app: app,
|
||||
server: server
|
||||
server: server,
|
||||
startupPromise: startupPromise
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = opts => {
|
||||
const running = start(opts);
|
||||
|
||||
running.startupPromise.catch(err => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
process.exit();
|
||||
});
|
||||
|
||||
process.on('SIGHUP', () => {
|
||||
console.log('Stopping server and reloading config');
|
||||
|
||||
running.server.shutdown(() => {
|
||||
for (const key in require.cache) {
|
||||
delete require.cache[key];
|
||||
}
|
||||
|
||||
const restarted = start(opts);
|
||||
running.server = restarted.server;
|
||||
running.app = restarted.app;
|
||||
});
|
||||
});
|
||||
|
||||
return running;
|
||||
};
|
||||
|
||||
114
src/utils.js
114
src/utils.js
@@ -1,32 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.getTileUrls = function(req, domains, path, format) {
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const clone = require('clone');
|
||||
const glyphCompose = require('@mapbox/glyph-pbf-composite');
|
||||
|
||||
|
||||
module.exports.getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`;
|
||||
|
||||
module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
||||
|
||||
if (domains) {
|
||||
if (domains.constructor === String && domains.length > 0) {
|
||||
domains = domains.split(',');
|
||||
}
|
||||
const host = req.headers.host;
|
||||
const hostParts = host.split('.');
|
||||
const relativeSubdomainsUsable = hostParts.length > 1 &&
|
||||
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(host);
|
||||
const newDomains = [];
|
||||
for (const domain of domains) {
|
||||
if (domain.indexOf('*') !== -1) {
|
||||
if (relativeSubdomainsUsable) {
|
||||
const newParts = hostParts.slice(1);
|
||||
newParts.unshift(domain.replace('*', hostParts[0]));
|
||||
newDomains.push(newParts.join('.'));
|
||||
}
|
||||
} else {
|
||||
newDomains.push(domain);
|
||||
}
|
||||
}
|
||||
domains = newDomains;
|
||||
}
|
||||
if (!domains || domains.length == 0) {
|
||||
domains = [req.headers.host];
|
||||
}
|
||||
|
||||
var key = req.query.key;
|
||||
var query = (key && key.length > 0) ? ('?key=' + key) : '';
|
||||
const key = req.query.key;
|
||||
const queryParams = [];
|
||||
if (req.query.key) {
|
||||
queryParams.push(`key=${encodeURIComponent(req.query.key)}`);
|
||||
}
|
||||
if (req.query.style) {
|
||||
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
|
||||
}
|
||||
const query = queryParams.length > 0 ? (`?${queryParams.join('&')}`) : '';
|
||||
|
||||
var uris = [];
|
||||
domains.forEach(function(domain) {
|
||||
uris.push(req.protocol + '://' + domain + '/' + path +
|
||||
'/{z}/{x}/{y}.' + format + query);
|
||||
});
|
||||
if (aliases && aliases[format]) {
|
||||
format = aliases[format];
|
||||
}
|
||||
|
||||
const uris = [];
|
||||
if (!publicUrl) {
|
||||
for (const domain of domains) {
|
||||
uris.push(`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`);
|
||||
}
|
||||
} else {
|
||||
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`)
|
||||
}
|
||||
|
||||
return uris;
|
||||
};
|
||||
|
||||
module.exports.fixTileJSONCenter = function(tileJSON) {
|
||||
module.exports.fixTileJSONCenter = tileJSON => {
|
||||
if (tileJSON.bounds && !tileJSON.center) {
|
||||
var fitWidth = 1024;
|
||||
var tiles = fitWidth / 256;
|
||||
const fitWidth = 1024;
|
||||
const tiles = fitWidth / 256;
|
||||
tileJSON.center = [
|
||||
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
||||
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
||||
@@ -37,3 +77,55 @@ module.exports.fixTileJSONCenter = function(tileJSON) {
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) => new Promise((resolve, reject) => {
|
||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
||||
const filename = path.join(fontPath, name, `${range}.pbf`);
|
||||
if (!fallbacks) {
|
||||
fallbacks = clone(allowedFonts || {});
|
||||
}
|
||||
delete fallbacks[name];
|
||||
fs.readFile(filename, (err, data) => {
|
||||
if (err) {
|
||||
console.error(`ERROR: Font not found: ${name}`);
|
||||
if (fallbacks && Object.keys(fallbacks).length) {
|
||||
let fallbackName;
|
||||
|
||||
let fontStyle = name.split(' ').pop();
|
||||
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
||||
fontStyle = 'Regular';
|
||||
}
|
||||
fallbackName = `Noto Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = `Open Sans ${fontStyle}`;
|
||||
if (!fallbacks[fallbackName]) {
|
||||
fallbackName = Object.keys(fallbacks)[0];
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
||||
delete fallbacks[fallbackName];
|
||||
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
|
||||
} else {
|
||||
reject(`Font load error: ${name}`);
|
||||
}
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(`Font not allowed: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.getFontsPbf = (allowedFonts, fontPath, names, range, fallbacks) => {
|
||||
const fonts = names.split(',');
|
||||
const queue = [];
|
||||
for (const font of fonts) {
|
||||
queue.push(
|
||||
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(queue).then(values => glyphCompose.combine(values));
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ var testTileJSONArray = function(url) {
|
||||
});
|
||||
};
|
||||
|
||||
var testTileJSON = function(url, basename) {
|
||||
var testTileJSON = function(url) {
|
||||
describe(url + ' is TileJSON', function() {
|
||||
it('is json', function(done) {
|
||||
supertest(app)
|
||||
@@ -27,11 +27,10 @@ var testTileJSON = function(url, basename) {
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('has valid basename and tiles', function(done) {
|
||||
it('has valid tiles', function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(function(res) {
|
||||
res.body.basename.should.equal(basename);
|
||||
res.body.tiles.length.should.be.greaterThan(0);
|
||||
}).end(done);
|
||||
});
|
||||
@@ -39,6 +38,14 @@ var testTileJSON = function(url, basename) {
|
||||
};
|
||||
|
||||
describe('Metadata', function() {
|
||||
describe('/health', function() {
|
||||
it('returns 200', function(done) {
|
||||
supertest(app)
|
||||
.get('/health')
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
testTileJSONArray('/index.json');
|
||||
testTileJSONArray('/rendered.json');
|
||||
testTileJSONArray('/data.json');
|
||||
@@ -64,6 +71,6 @@ describe('Metadata', function() {
|
||||
});
|
||||
});
|
||||
|
||||
testTileJSON('/styles/test/rendered.json', 'test');
|
||||
testTileJSON('/data/zurich-vector.json', 'zurich-vector');
|
||||
testTileJSON('/styles/test-style.json');
|
||||
testTileJSON('/data/openmaptiles.json');
|
||||
});
|
||||
|
||||
@@ -3,18 +3,22 @@ process.env.NODE_ENV = 'test';
|
||||
global.should = require('should');
|
||||
global.supertest = require('supertest');
|
||||
|
||||
require = require('esm')(module);
|
||||
|
||||
before(function() {
|
||||
console.log('global setup');
|
||||
process.chdir('test_data');
|
||||
var running = require('../src/server')({
|
||||
config: 'config.json',
|
||||
port: 8888
|
||||
configPath: 'config.json',
|
||||
port: 8888,
|
||||
publicUrl: '/test/'
|
||||
});
|
||||
global.app = running.app;
|
||||
global.server = running.server;
|
||||
return running.startupPromise;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
console.log('global teardown');
|
||||
global.server.close(function() { console.log('Done'); });
|
||||
global.server.close(function() { console.log('Done'); process.exit(); });
|
||||
});
|
||||
|
||||
@@ -12,88 +12,91 @@ var testStatic = function(prefix, q, format, status, scale, type, query) {
|
||||
});
|
||||
};
|
||||
|
||||
var prefix = 'test-style';
|
||||
|
||||
describe('Static endpoints', function() {
|
||||
describe('center-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic('test', '0,0,0/256x256', 'png', 200, undefined, /image\/png/);
|
||||
testStatic('test', '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '0,0,0/256x256', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '0,0,0/256x256', 'webp', 200, undefined, /image\/webp/);
|
||||
testStatic(prefix, '0,0,0/256x256', 'png', 200, undefined, /image\/png/);
|
||||
testStatic(prefix, '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic(prefix, '0,0,0/256x256', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic(prefix, '0,0,0/256x256', 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic('test', '0,0,0/300x300', 'png', 200, 2);
|
||||
testStatic('test', '0,0,0/300x300', 'png', 200, 3);
|
||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 2);
|
||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 3);
|
||||
|
||||
testStatic('test', '80,40,20/600x300', 'png', 200, 3);
|
||||
testStatic('test', '8.5,40.5,20/300x150', 'png', 200, 3);
|
||||
testStatic('test', '-8.5,-40.5,20/300x150', 'png', 200, 3);
|
||||
testStatic(prefix, '0,0,1.5/256x256', 'png', 200);
|
||||
|
||||
testStatic('test', '8,40,2@0,0/300x150', 'png', 200);
|
||||
testStatic('test', '8,40,2@180,45/300x150', 'png', 200, 2);
|
||||
testStatic('test', '8,40,2@10/300x150', 'png', 200, 3);
|
||||
testStatic('test', '8,40,2@10.3,20.4/300x300', 'png', 200);
|
||||
testStatic('test', '0,0,2@390,120/300x300', 'png', 200);
|
||||
testStatic(prefix, '80,40,20/600x300', 'png', 200, 3);
|
||||
testStatic(prefix, '8.5,40.5,20/300x150', 'png', 200, 3);
|
||||
testStatic(prefix, '-8.5,-40.5,20/300x150', 'png', 200, 3);
|
||||
|
||||
testStatic(prefix, '8,40,2@0,0/300x150', 'png', 200);
|
||||
testStatic(prefix, '8,40,2@180,45/300x150', 'png', 200, 2);
|
||||
testStatic(prefix, '8,40,2@10/300x150', 'png', 200, 3);
|
||||
testStatic(prefix, '8,40,2@10.3,20.4/300x300', 'png', 200);
|
||||
testStatic(prefix, '0,0,2@390,120/300x300', 'png', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testStatic('test', '190,0,0/256x256', 'png', 400);
|
||||
testStatic('test', '0,86,0/256x256', 'png', 400);
|
||||
testStatic('test', '80,40,20/0x0', 'png', 400);
|
||||
testStatic('test', '0,0,0/256x256', 'gif', 400);
|
||||
testStatic('test', '0,0,0/256x256', 'png', 404, 1);
|
||||
testStatic(prefix, '190,0,0/256x256', 'png', 400);
|
||||
testStatic(prefix, '0,86,0/256x256', 'png', 400);
|
||||
testStatic(prefix, '80,40,20/0x0', 'png', 400);
|
||||
testStatic(prefix, '0,0,0/256x256', 'gif', 400);
|
||||
testStatic(prefix, '0,0,0/256x256', 'png', 404, 1);
|
||||
|
||||
testStatic('test', '0,0,-1/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,1.5/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,0/256.5x256.5', 'png', 404);
|
||||
testStatic(prefix, '0,0,-1/256x256', 'png', 404);
|
||||
testStatic(prefix, '0,0,0/256.5x256.5', 'png', 404);
|
||||
|
||||
testStatic('test', '0,0,0,/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,0,0,/256x256', 'png', 404);
|
||||
testStatic(prefix, '0,0,0,/256x256', 'png', 404);
|
||||
testStatic(prefix, '0,0,0,0,/256x256', 'png', 404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('area-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic('test', '-180,-80,180,80/10x10', 'png', 200, undefined, /image\/png/);
|
||||
testStatic('test', '-180,-80,180,80/10x10', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '-180,-80,180,80/10x10', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '-180,-80,180,80/10x10', 'webp', 200, undefined, /image\/webp/);
|
||||
testStatic(prefix, '-180,-80,180,80/10x10', 'png', 200, undefined, /image\/png/);
|
||||
testStatic(prefix, '-180,-80,180,80/10x10', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic(prefix, '-180,-80,180,80/10x10', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic(prefix, '-180,-80,180,80/10x10', 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic('test', '-180,-90,180,90/20x20', 'png', 200, 2);
|
||||
testStatic('test', '0,0,1,1/200x200', 'png', 200, 3);
|
||||
testStatic(prefix, '-180,-90,180,90/20x20', 'png', 200, 2);
|
||||
testStatic(prefix, '0,0,1,1/200x200', 'png', 200, 3);
|
||||
|
||||
testStatic('test', '-280,-80,0,80/280x160', 'png', 200);
|
||||
testStatic(prefix, '-280,-80,0,80/280x160', 'png', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testStatic('test', '0,87,1,88/5x2', 'png', 400);
|
||||
testStatic(prefix, '0,87,1,88/5x2', 'png', 400);
|
||||
|
||||
testStatic('test', '0,0,1,1/1x1', 'gif', 400);
|
||||
testStatic(prefix, '0,0,1,1/1x1', 'gif', 400);
|
||||
|
||||
testStatic('test', '-180,-80,180,80/0.5x2.6', 'png', 404);
|
||||
testStatic(prefix, '-180,-80,180,80/0.5x2.6', 'png', 404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autofit path', function() {
|
||||
describe('valid requests', function() {
|
||||
testStatic('test', 'auto/256x256', 'png', 200, undefined, /image\/png/, '?path=10,10|20,20');
|
||||
testStatic(prefix, 'auto/256x256', 'png', 200, undefined, /image\/png/, '?path=10,10|20,20');
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic('test', 'auto/20x20', 'png', 200, 2, /image\/png/, '?path=10,10|20,20');
|
||||
testStatic('test', 'auto/200x200', 'png', 200, 3, /image\/png/, '?path=-10,-10|-20,-20');
|
||||
testStatic(prefix, 'auto/20x20', 'png', 200, 2, /image\/png/, '?path=10,10|20,20');
|
||||
testStatic(prefix, 'auto/200x200', 'png', 200, 3, /image\/png/, '?path=-10,-10|-20,-20');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testStatic('test', 'auto/256x256', 'png', 400);
|
||||
testStatic('test', 'auto/256x256', 'png', 400, undefined, undefined, '?path=10,10');
|
||||
testStatic('test', 'auto/2560x2560', 'png', 400, undefined, undefined, '?path=10,10|20,20');
|
||||
testStatic(prefix, 'auto/256x256', 'png', 400);
|
||||
testStatic(prefix, 'auto/256x256', 'png', 400, undefined, undefined, '?path=10,10');
|
||||
testStatic(prefix, 'auto/2560x2560', 'png', 400, undefined, undefined, '?path=10,10|20,20');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,41 +8,44 @@ var testIs = function(url, type, status) {
|
||||
});
|
||||
};
|
||||
|
||||
var prefix = 'test-style';
|
||||
|
||||
describe('Styles', function() {
|
||||
describe('/styles/test.json is valid style', function() {
|
||||
testIs('/styles/test.json', /application\/json/);
|
||||
describe('/styles/' + prefix + '/style.json is valid style', function() {
|
||||
testIs('/styles/' + prefix + '/style.json', /application\/json/);
|
||||
|
||||
it('contains expected properties', function(done) {
|
||||
supertest(app)
|
||||
.get('/styles/test.json')
|
||||
.get('/styles/' + prefix + '/style.json')
|
||||
.expect(function(res) {
|
||||
res.body.version.should.equal(8);
|
||||
res.body.name.should.be.String();
|
||||
res.body.sources.should.be.Object();
|
||||
res.body.glyphs.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();
|
||||
}).end(done);
|
||||
});
|
||||
});
|
||||
describe('/styles/streets.json is not served', function() {
|
||||
testIs('/styles/streets.json', /./, 404);
|
||||
describe('/styles/streets/style.json is not served', function() {
|
||||
testIs('/styles/streets/style.json', /./, 404);
|
||||
});
|
||||
|
||||
describe('/styles/test/sprite[@2x].{format}', function() {
|
||||
testIs('/styles/test/sprite.json', /application\/json/);
|
||||
testIs('/styles/test/sprite@2x.json', /application\/json/);
|
||||
testIs('/styles/test/sprite.png', /image\/png/);
|
||||
testIs('/styles/test/sprite@2x.png', /image\/png/);
|
||||
describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
|
||||
testIs('/styles/' + prefix + '/sprite.json', /application\/json/);
|
||||
testIs('/styles/' + prefix + '/sprite@2x.json', /application\/json/);
|
||||
testIs('/styles/' + prefix + '/sprite.png', /image\/png/);
|
||||
testIs('/styles/' + prefix + '/sprite@2x.png', /image\/png/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fonts', function() {
|
||||
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Open Sans Regular/65280-65533.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Open Sans Regular/65280-65535.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
|
||||
/application\/x-protobuf/);
|
||||
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /./, 400);
|
||||
|
||||
testIs('/fonts/Nonsense/0-255.pbf', /./, 400);
|
||||
testIs('/fonts/Nonsense1,Nonsense2/0-255.pbf', /./, 400);
|
||||
|
||||
@@ -8,7 +8,7 @@ var testTile = function(prefix, z, x, y, status) {
|
||||
});
|
||||
};
|
||||
|
||||
var prefix = 'zurich-vector';
|
||||
var prefix = 'openmaptiles';
|
||||
|
||||
describe('Vector tiles', function() {
|
||||
describe('existing tiles', function() {
|
||||
@@ -23,6 +23,6 @@ describe('Vector tiles', function() {
|
||||
testTile(prefix, 0, 1, 0, 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) {
|
||||
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) {
|
||||
var test = supertest(app).get(path);
|
||||
test.expect(status);
|
||||
@@ -9,35 +9,37 @@ var testTile = function(prefix, z, x, y, format, status, scale, type) {
|
||||
});
|
||||
};
|
||||
|
||||
var prefix = 'test-style';
|
||||
|
||||
describe('Raster tiles', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testTile('test', 0, 0, 0, 'png', 200, undefined, /image\/png/);
|
||||
testTile('test', 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testTile('test', 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testTile('test', 0, 0, 0, 'webp', 200, undefined, /image\/webp/);
|
||||
testTile(prefix, 0, 0, 0, 'png', 200, undefined, /image\/png/);
|
||||
testTile(prefix, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testTile(prefix, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testTile(prefix, 0, 0, 0, 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different coordinates and scales', function() {
|
||||
testTile('test', 1, 1, 1, 'png', 200);
|
||||
testTile(prefix, 1, 1, 1, 'png', 200);
|
||||
|
||||
testTile('test', 0, 0, 0, 'png', 200, 2);
|
||||
testTile('test', 0, 0, 0, 'png', 200, 3);
|
||||
testTile('test', 2, 1, 1, 'png', 200, 3);
|
||||
testTile(prefix, 0, 0, 0, 'png', 200, 2);
|
||||
testTile(prefix, 0, 0, 0, 'png', 200, 3);
|
||||
testTile(prefix, 2, 1, 1, 'png', 200, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testTile('non_existent', 0, 0, 0, 'png', 404);
|
||||
testTile('test', -1, 0, 0, 'png', 404);
|
||||
testTile('test', 25, 0, 0, 'png', 404);
|
||||
testTile('test', 0, 1, 0, 'png', 404);
|
||||
testTile('test', 0, 0, 1, 'png', 404);
|
||||
testTile('test', 0, 0, 0, 'gif', 400);
|
||||
testTile('test', 0, 0, 0, 'pbf', 400);
|
||||
testTile(prefix, -1, 0, 0, 'png', 404);
|
||||
testTile(prefix, 25, 0, 0, 'png', 404);
|
||||
testTile(prefix, 0, 1, 0, 'png', 404);
|
||||
testTile(prefix, 0, 0, 1, 'png', 404);
|
||||
testTile(prefix, 0, 0, 0, 'gif', 400);
|
||||
testTile(prefix, 0, 0, 0, 'pbf', 400);
|
||||
|
||||
testTile('test', 0, 0, 0, 'png', 404, 1);
|
||||
testTile('test', 0, 0, 0, 'png', 404, 4);
|
||||
testTile(prefix, 0, 0, 0, 'png', 404, 1);
|
||||
testTile(prefix, 0, 0, 0, 'png', 404, 5);
|
||||
|
||||
//testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user