Compare commits

..

199 Commits

Author SHA1 Message Date
Petr Sloup
66e3a3da1b Update package version to 1.3.2 2017-01-17 21:18:49 +01:00
Petr Sloup
63482a2a39 Generate version-based identifier for data when autogenerating config 2017-01-17 21:18:12 +01:00
Petr Sloup
58ccc5477c Update package version to 1.3.1 2017-01-17 20:48:43 +01:00
Petr Sloup
bbbb38d70f Update style dependency 2017-01-17 20:48:26 +01:00
Petr Sloup
2ef0540478 Remove optional dependencies from tileserver-gl-light 2017-01-17 20:47:46 +01:00
Petr Sloup
85692e9659 Specify font family in the Inspect mode (close #106) 2017-01-17 12:09:03 +01:00
Petr Sloup
b66ae40a1e Allow layer list in inspect mode use more space (close #106) 2017-01-16 16:27:18 +01:00
Petr Sloup
88022f9297 Update package version to 1.3.0 2017-01-13 11:04:14 +01:00
Petr Sloup
b127dacc0d Slightly stricter .dockerignore 2017-01-13 11:03:12 +01:00
Petr Sloup
c162a6647d Update package version to 1.3.0-beta.3 2017-01-12 15:26:34 +01:00
Petr Sloup
b48f7edc1d Serve fontstacks.json and possibly fonts even without any style 2017-01-12 15:23:02 +01:00
Petr Sloup
6d68c93871 Fix broken requires 2017-01-12 14:05:02 +01:00
Petr Sloup
1fef895e67 Update native dependency info in documentation 2017-01-12 13:57:40 +01:00
Petr Sloup
920b6c19b6 Remove deprecated engineStrict 2017-01-12 13:54:44 +01:00
Petr Sloup
7f280feeb1 Minor dependency package name update 2017-01-12 13:48:05 +01:00
Petr Sloup
1aec8f386e Update package version to 1.3.0-beta.2 2017-01-10 16:19:58 +01:00
Petr Sloup
d05606de4d Improved font serving 2017-01-10 16:17:51 +01:00
Petr Sloup
b9930dd195 Fix interim empty tile generation for raster layers 2017-01-10 15:47:56 +01:00
Petr Sloup
4082828790 Rename button from "X-Ray" to "Inspect" (see #103) 2017-01-10 15:17:34 +01:00
Petr Sloup
473e7b6f4a Merge pull request #103 from lukasmartinelli/master
Use Mapbox GL Inspect for X-Ray View
2017-01-10 15:15:06 +01:00
Lukas Martinelli
3d993458b5 Add different inspect viewer for data 2017-01-09 14:30:20 +01:00
Lukas Martinelli
b2dcf97d9a Add Mapbox GL Inspect plugin 2017-01-09 14:28:49 +01:00
Lukas Martinelli
d4ddbec45d Upgrade to Mapbox GL v0.30 2017-01-09 14:28:42 +01:00
Petr Sloup
d32e46ba2f Allow options to be passed to the docker + document it (close #87) 2016-12-21 07:01:16 +01:00
Petr Sloup
0ec992eb21 Explicitly handle SIGINT to terminate on Ctrl+C from docker
See https://github.com/nodejs/node/issues/4182
2016-12-21 06:49:16 +01:00
Petr Sloup
7a55ac9ebb Add paragraph about native dependencies (close #90) 2016-12-20 14:53:27 +01:00
Petr Sloup
78fcb893f7 Add paragraph about running behind a proxy (#50, #85) 2016-12-20 14:41:22 +01:00
Petr Sloup
19dcd52cc8 Update documentation to reflect recent changes 2016-12-20 14:32:33 +01:00
Petr Sloup
557f83121c Fix URLs in /index.json and /rendered.json 2016-12-20 13:56:23 +01:00
Petr Sloup
2d29a21596 Do not add basename to tilejson and remove sensitive properties (see #69) 2016-12-20 13:55:36 +01:00
Petr Sloup
945e568b2b Merge pull request #93 from ArsenyN/bugfix-RemoteVectorSource
Fix: Right code for working with external vector tiles.
2016-12-20 13:21:44 +01:00
Petr Sloup
e1a996a267 Minor Dockerfile fix 2016-12-19 21:45:57 +01:00
Petr Sloup
bdfa92532e Update package version to 1.3.0-beta.1 2016-12-19 21:24:14 +01:00
Petr Sloup
e14793c54b Add engines and engineStrict to package.json (close #79) 2016-12-19 21:07:39 +01:00
Arseny Novikov
37db733e02 Fix for this comment: https://github.com/klokantech/tileserver-gl/issues/91#issuecomment-267939138
With this commit code will have right behaviour with external vector tiles.
2016-12-19 14:13:03 +03:00
Petr Sloup
1c401f41a2 Update tests and links to use the new data 2016-12-15 10:51:18 +01:00
Petr Sloup
0018ed9524 Prepare for the new tile schema and compatibility checking 2016-12-14 08:37:17 +01:00
Petr Sloup
dfb07b8286 Show layer name in data viewer (close #83) 2016-12-13 08:48:28 +01:00
Petr Sloup
c1de2a7903 Upgrade MB GL JS to v0.28.0 (close #88) 2016-12-13 08:48:12 +01:00
Petr Sloup
509d32da68 Improved font serving 2016-12-09 13:19:34 +01:00
Petr Sloup
bbc14abb4a Fix sprite and glyph loading from remote URLs 2016-12-08 18:22:11 +01:00
Petr Sloup
442baee1ce Dockerfile improvements 2016-12-08 18:18:19 +01:00
Petr Sloup
0e6c9bfeb0 Improved data name autodetection 2016-12-08 09:41:39 +01:00
Petr Sloup
d3a685e51c Fix unzip bug 2016-12-07 11:03:03 +01:00
Petr Sloup
99af9eae23 Update dependencies 2016-12-06 22:16:35 +01:00
Petr Sloup
d330a9743c Minor bugfix 2016-12-06 22:16:35 +01:00
Petr Sloup
becb46ab80 Add "Style JSON" link to the front page 2016-12-06 22:16:29 +01:00
Petr Sloup
ef3f34f778 Add geojson endpoints (close #78) 2016-12-06 21:26:30 +01:00
Petr Sloup
1aaabd2dff Experimental integration of first plugin 2016-12-06 20:11:18 +01:00
Petr Sloup
bcac161a25 Minor route pattern improvement 2016-12-05 23:21:31 +01:00
Petr Sloup
9d362a5b5e Experimental static endpoints working with raw mercator coordinates 2016-12-05 23:02:48 +01:00
Petr Sloup
21883f490f Update package version to 1.2.0 2016-12-05 16:45:27 +01:00
Petr Sloup
70515947ca Merge pull request #73 from LKajan/x-ray-points
X-Ray style: add circle symbols for point layers
2016-12-05 16:42:16 +01:00
Lauri Kajan
63090802ae fixing typo 2016-11-16 11:28:15 +02:00
Lauri Kajan
131b5e2f81 added circle symbols for point layers 2016-11-16 10:16:14 +02:00
Petr Sloup
6e085af7cc Update package version to 1.1.5 2016-10-25 12:16:07 +02:00
Petr Sloup
345d96d5e6 Fix static endpoints for (near-)whole-world queries (close #63) 2016-10-21 14:49:39 +02:00
Petr Sloup
f1c835c21d Update package version to 1.1.4 2016-10-07 15:21:58 +02:00
Petr Sloup
15ed6d74bf Fix incorrect XYZ URLs (close #58) 2016-10-07 15:21:12 +02:00
Petr Sloup
a3d8240aac Do not add scale prefix when viewing raster data tiles (close #57) 2016-10-07 11:16:22 +02:00
Petr Sloup
098f057e55 Update package version to 1.1.3 2016-09-30 09:51:20 +02:00
Petr Sloup
18ba6f5059 Ignore tiling scheme of the source for internal meta urls (#54) 2016-09-30 09:50:05 +02:00
Petr Sloup
820dbdd3dd Update package version to 1.1.2 2016-09-27 17:34:39 +02:00
Petr Sloup
acd7683f18 Update dependencies 2016-09-27 17:30:24 +02:00
Petr Sloup
09859c10c1 Serve any vector/raster mbtiles, use styles only for osm2vt (close #51, #52) 2016-09-27 17:26:05 +02:00
Petr Sloup
f201deecdc Merge pull request #48 from rastapasta/patch-1
adding info for OSX setup
2016-09-22 08:55:41 +02:00
Petr Pridal
b49521d2ed Update README.md 2016-09-01 17:53:15 +02:00
Michael Straßburger
30c9bc8979 adding info for OSX setup 2016-08-29 12:31:33 +02:00
Petr Sloup
69340bbc83 Update package version to 1.1.1 2016-08-25 12:52:51 +02:00
Petr Sloup
ff9d5a8e5d Update READMEs and descriptions 2016-08-25 12:52:32 +02:00
Petr Sloup
23c2aa54d0 Update tests 2016-08-25 11:09:01 +02:00
Petr Sloup
a8fd1b38b7 Return 404 for negative zoom levels on static endpoint 2016-08-25 11:08:19 +02:00
Petr Sloup
7c28bf2b21 Update version to 1.1.0 2016-08-25 10:49:27 +02:00
Petr Sloup
f9f26f0d65 Indicate clearly when running light version on index.html 2016-08-25 10:44:29 +02:00
Petr Sloup
de60a0a076 Disable png quantization by default for now 2016-08-25 10:23:42 +02:00
Petr Sloup
90b9af3d95 Allow max image side length to be configurable 2016-08-25 09:41:33 +02:00
Petr Sloup
78aea26318 Allow @4x requests 2016-08-25 09:38:03 +02:00
Petr Sloup
292b1b6b44 Update dependencies 2016-08-25 09:28:17 +02:00
Petr Sloup
de7f5f0366 Allow and use floating-point zoom levels in the static endpoints 2016-08-25 09:22:12 +02:00
Petr Sloup
161de17803 Update package version to 1.0.0 2016-08-24 17:05:23 +02:00
Petr Pridal
f9e5afbf49 Update index.tmpl 2016-08-24 16:29:49 +02:00
Petr Sloup
513e2dac8f Fix incorrect variable name 2016-08-24 16:28:41 +02:00
Petr Sloup
e428ab60f1 Update README.md 2016-08-24 14:22:19 +02:00
Petr Sloup
9d1a2bf995 Add note about tileserver-gl-light to docs 2016-08-24 14:20:42 +02:00
Petr Sloup
bbda49589c Add PUBLISHING.md 2016-08-24 14:20:16 +02:00
Petr Sloup
a571d5f39b Improve and rename publishing script for creating light version 2016-08-24 14:19:16 +02:00
Petr Sloup
68d997a655 Update to tileserver-gl-styles v0.3.0 2016-08-24 14:05:01 +02:00
Petr Sloup
7d2f8ab062 Add note about tileserver-gl-light to the docs 2016-08-24 13:50:47 +02:00
Petr Sloup
4c2157842c Minor fix for autodetected bounds in tilejson 2016-08-24 13:19:36 +02:00
Petr Sloup
25c87ccf23 Initial documentation (close #39) 2016-08-23 00:32:09 +02:00
Petr Sloup
97457a23f2 Update "sharp" dependency version to 0.16.0 2016-08-21 10:14:49 +02:00
Petr Sloup
e9cad399c9 New "mbtiles://{name}" reference syntax (#27) 2016-08-21 10:08:17 +02:00
Petr Sloup
ee1cb21dfd Use pngquant (close #43); rename option formatEncoding->formatQuality 2016-08-21 09:41:28 +02:00
Petr Sloup
f524f1465e Fix race condition 2016-08-20 14:29:10 +02:00
Petr Sloup
1004f4cce1 Add XYZ to the list of services (close #42) 2016-08-20 14:01:12 +02:00
Petr Sloup
b4d6490e00 Large refactoring of usage (only mbtiles, default styles, ...) (#27) 2016-08-20 13:31:11 +02:00
Petr Sloup
3cf8ce9903 Add mbtiles_data: "metaprotocol" 2016-08-20 10:28:45 +02:00
Petr Sloup
f21ee2691b Merge branch 'v2_tiles' 2016-08-19 08:28:19 +02:00
Petr Sloup
845e31b3f2 More sensible errors for non-existent mbtiles 2016-08-19 08:25:12 +02:00
Petr Sloup
c2a18d7329 Update mapbox-gl-js to v0.21.0 2016-08-10 11:40:34 +08:00
Petr Sloup
d120b46966 Update mapbox-gl-native to v3.3.2 2016-08-10 11:35:54 +08:00
Petr Sloup
afda5d00bc Merge pull request #36 from efi-the-forking-continues/master
new feature: command line option for bind address
2016-08-07 20:45:29 +08:00
Thomas Efer
b3cb047d3d set default binding to undefined
this will result in default behavior in app.listen within server.js
2016-08-07 11:34:46 +02:00
Petr Sloup
af47c9c23b Update package version to 0.9.1 2016-08-06 18:41:24 +08:00
Petr Sloup
2ab9d9e168 Print the package name on the index 2016-08-06 18:40:50 +08:00
Petr Sloup
8a7f44d4c2 Add script for building "tileserver-gl-light" package (#27) 2016-08-06 18:37:17 +08:00
Thomas Efer
aa9a469bb4 tell server to adhere to new option for network interface binding 2016-08-05 14:30:22 +02:00
Thomas Efer
53c1ef5786 Added option to specify network interface binding 2016-08-05 14:28:34 +02:00
Petr Pridal
8074cfb4c7 Documentation - the ReadTheDocs skeleton 2016-08-04 23:55:20 +02:00
Petr Pridal
ffc72789ae Update version to 0.9.0 2016-07-28 11:22:50 +02:00
Dalibor Janák
4fc76251c3 Responsive CSS for the web page - fixes (closes #33) 2016-07-27 17:56:54 +02:00
Dalibor Janák
b048990e14 Responsive CSS for the web page #33 2016-07-27 14:05:33 +02:00
Petr Sloup
b257855e38 Update version to 0.8.3 2016-07-27 12:33:19 +08:00
Petr Sloup
fb758be730 Update tests to use v0.8 dataset 2016-07-27 12:32:53 +08:00
Petr Sloup
7accdfa7da Very early CPU-cheap 304 based solely on last-modified 2016-07-27 12:31:47 +08:00
Petr Sloup
7efe22cf7f Minor typo fix 2016-07-26 19:02:44 +07:00
Petr Sloup
5a20bf4fac Update version to 0.8.2 2016-07-26 18:57:39 +07:00
Petr Sloup
a0fbf7fb79 Fix font compositing (close #32) 2016-07-26 18:56:32 +07:00
Petr Sloup
ed0af943da New design (close #31) 2016-07-26 17:27:08 +07:00
Petr Sloup
e4ce4877b6 Update version to 0.8.1 2016-07-26 10:43:33 +08:00
Petr Sloup
a0a086e95a Update dependencies 2016-07-26 10:43:33 +08:00
Petr Sloup
dbaca66b2c Add `Last-Modified' headers to improve caching 2016-07-26 10:43:33 +08:00
Petr Sloup
d465142275 Merge pull request #28 from stirringhalo/master
Avoid curl | bash
2016-07-22 19:02:22 +08:00
stirringhalo
7f06f09696 Duplicate nodejs install 2016-07-20 15:18:58 -04:00
stirringhalo
f5c5570fca Avoid curl | bash 2016-07-18 18:42:09 -04:00
Petr Sloup
cb10936a1d Update version to 0.8.0 2016-07-13 14:06:58 +02:00
Petr Sloup
a43c96f6cc Print package version to log and index 2016-07-13 14:04:42 +02:00
Petr Sloup
a310c130f3 Download sample data when running docker image without config (close #23) 2016-07-13 14:03:41 +02:00
Petr Sloup
14f406cc34 Add "bin" entry to package.json 2016-07-13 13:08:48 +02:00
Petr Sloup
3c29cb0f65 ETag/caching fixes and improvements 2016-07-13 12:56:59 +02:00
Petr Sloup
d993c805fe Update dependencies 2016-07-13 12:56:41 +02:00
Petr Sloup
6443ade489 Update version to 0.7.0 2016-06-30 12:25:03 +02:00
Petr Sloup
eeb27ed481 Downgrade mapbox-gl-js to v0.16.0
To fix rendering issues with certain datasets on older devices
2016-06-30 12:22:46 +02:00
Petr Sloup
15bc393aa8 Use URl-safe base64 alphabet 2016-06-30 12:19:32 +02:00
Petr Sloup
d6e2baef67 Update version to 0.6.0 2016-06-29 20:31:13 +02:00
Petr Sloup
6100013718 Fix serving the same style twice 2016-06-29 20:28:56 +02:00
Petr Sloup
4eea8a91b8 Add favicon.ico 2016-06-29 20:25:36 +02:00
Petr Sloup
d57f9f7a60 Fix and improve xray viewer 2016-06-29 20:23:37 +02:00
Petr Sloup
1307b29ff9 Cleaner implementation of keys 2016-06-29 20:17:58 +02:00
Petr Sloup
a9443edfc6 Update mapbox-gl-js to v0.19.0 2016-06-29 20:08:21 +02:00
Petr Sloup
e6a1bf407c Remove sourceMappingURL 2016-06-29 20:08:09 +02:00
Petr Sloup
0ba2dcce53 Always use http for wmts 2016-06-29 19:55:06 +02:00
Petr Sloup
ebd6662ebb Minor WMTS link bugfix 2016-06-29 16:23:49 +02:00
Petr Sloup
6898be5bea Update version 2016-06-29 16:12:42 +02:00
Petr Sloup
a55b5ad69c Fix viewer vector/raster autodetection 2016-06-29 15:14:51 +02:00
Petr Sloup
1104bf8a57 Add links to WMTS services from index.html 2016-06-29 15:08:56 +02:00
Petr Sloup
1d4503b507 If key present in query, propagate it to other linked urls 2016-06-29 14:57:11 +02:00
Petr Sloup
2d5dccc1e0 Update dependencies 2016-06-28 07:48:30 +02:00
Petr Sloup
fc32cc24e5 Properly add CORS to vector style endpoints 2016-06-27 09:06:30 +02:00
Petr Sloup
575c1df524 Add tests for autofit static endpoints 2016-06-27 08:11:02 +02:00
Petr Sloup
d8f35bb70a Update area-based static tests 2016-06-27 08:01:28 +02:00
Petr Sloup
4af6fb686f Change bearing and pitch format in static url 2016-06-27 07:56:54 +02:00
Petr Sloup
391c21a966 Update static endpoints in README.md 2016-06-27 07:45:04 +02:00
Petr Sloup
2d2c43aeb5 Support bearing in path overlays 2016-06-27 07:42:38 +02:00
Petr Sloup
900ca4ed3e Refactor area-based static maps urls 2016-06-27 07:18:48 +02:00
Petr Sloup
9d63634078 Rename path static map type to auto 2016-06-27 07:09:24 +02:00
Petr Sloup
0b98651b48 Add dynamic path queries to all static endpoints 2016-06-27 07:08:27 +02:00
Petr Sloup
d3aeab5d89 Decrease image size limit to 2048x2048 2016-06-27 06:53:29 +02:00
Petr Sloup
01bff86c6d Initial implementation of path rendering 2016-06-24 12:26:26 +02:00
Petr Sloup
9a21382984 Update dependencies 2016-06-24 09:24:22 +02:00
Petr Sloup
9e391562ab Add bearing and pitch to the README.md 2016-06-24 08:57:53 +02:00
Petr Pridal
646b65d695 Add Docker Hub badge to README.md 2016-06-05 16:54:25 +02:00
Petr Pridal
c9e21b676c Update README.md 2016-06-05 16:46:46 +02:00
Petr Pridal
c50da54e64 Merge pull request #19 from klokantech/license
License added. Closes #8
2016-06-05 16:45:32 +02:00
Petr Pridal
b8b56846f2 Create LICENSE.md 2016-06-05 16:44:27 +02:00
Petr Pridal
8bb625652d Add license to package.json 2016-06-05 16:40:59 +02:00
Petr Sloup
801c523c73 Update run.sh 2016-05-30 22:22:21 +02:00
Petr Sloup
8e289684d5 Update version to v0.0.3 2016-05-04 13:57:44 +02:00
Petr Sloup
59cc66095f Minor test update 2016-05-04 13:57:00 +02:00
Petr Sloup
187da7bb58 Add raster view for raster data 2016-05-04 13:53:47 +02:00
Petr Sloup
daa94dc806 Chain attributions from mbtiles into the tilejson of the rendered tiles 2016-05-04 13:13:37 +02:00
Petr Sloup
5d940066d9 Display filesizes of the mbtiles 2016-05-04 13:07:09 +02:00
Petr Sloup
10caaa1e8b Minor travis script fix 2016-05-03 17:49:04 +02:00
Petr Sloup
9edf7c0cae Fix empty raster tiles 2016-05-03 17:42:47 +02:00
Petr Sloup
f979e25fd7 Update travis script to use new data 2016-05-03 16:24:13 +02:00
Petr Sloup
d3a9b6cfbf Update README with links to tileserver-gl-data 2016-05-03 15:49:39 +02:00
Petr Sloup
2a55517a2d Update dependencies 2016-05-03 15:12:17 +02:00
Petr Sloup
927a3eda87 Minor README fix 2016-05-03 15:12:10 +02:00
Petr Sloup
54073cecce Simplify URL for "static"
/styles/{id}/rendered/static/... -> /styles/{id}/static/...
2016-04-22 15:50:56 +02:00
Petr Sloup
287a632295 Update readme 2016-04-22 12:37:39 +02:00
Petr Sloup
a25ce62662 New urls for source data tiles and rendered tiles 2016-04-22 12:33:20 +02:00
Petr Sloup
c0fb4fd400 Support for raster mbtiles (issue #13) 2016-04-21 18:23:13 +02:00
Petr Sloup
d486a8595b Use node 4 for travis and docker 2016-04-18 11:05:52 +02:00
Petr Sloup
3b92c6109b Do not install dev dependencies when build docker image 2016-04-18 10:47:45 +02:00
Petr Sloup
b6ad565e31 Update dependencies 2016-04-18 10:47:36 +02:00
Petr Sloup
f794f6b8ba Minor xray viewer fix 2016-04-15 15:42:54 +02:00
Dalibor Janák
640a18ca49 Css moved to external file + minor style fixes 2016-04-06 11:21:04 +02:00
Dalibor Janák
75f64924b5 Basic index styling #11 2016-04-05 16:19:34 +02:00
Petr Sloup
ec5d282d87 Update npm dependencies 2016-04-05 13:04:13 +02:00
Petr Sloup
bdea327437 Use CORS 2016-04-05 13:02:33 +02:00
Petr Sloup
6cf006ec50 More strict routing pattern matching (fix tests) 2016-03-17 11:45:55 +01:00
Petr Sloup
34befd43c9 Add xray viewer for vector data 2016-03-17 11:31:33 +01:00
Petr Sloup
c132d7fba8 Add redirect from /raster/:id/ to /styles/:id/ 2016-03-17 11:04:51 +01:00
Petr Sloup
62a6917778 Show proper thumbnails on index 2016-03-17 11:01:54 +01:00
Petr Sloup
403bc949a5 Pregenerate permalinks for the viewers 2016-03-17 10:51:16 +01:00
Petr Sloup
837cb7d1fb New index and viewer (+ templating system) 2016-03-16 20:47:11 +01:00
Petr Sloup
d0c0430dca Improve config usability (close #10) 2016-03-14 16:11:29 +01:00
Petr Sloup
1ade82bf05 More user-friendly error message for invalid config (close #7) 2016-03-11 20:29:21 +01:00
Petr Sloup
5a94689385 Make compressionLevel/quality configurable + change defaults 2016-03-11 16:40:05 +01:00
62 changed files with 4634 additions and 1161 deletions

View File

@@ -1,4 +1,7 @@
.git .git
docs/_build
node_modules node_modules
test_data test_data
test light
config.json
*.mbtiles

5
.gitignore vendored
View File

@@ -1,3 +1,8 @@
docs/_build
node_modules node_modules
test_data test_data
data
light
plugins
config.json config.json
*.mbtiles

View File

@@ -1,6 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- "5" - "4"
env: env:
- CXX=g++-4.8 - CXX=g++-4.8
addons: addons:
@@ -11,10 +11,11 @@ addons:
- g++-4.8 - g++-4.8
before_install: before_install:
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
- sudo apt-get install -qq xvfb - sudo apt-get install -qq xvfb
install: install:
- npm install - npm install
- wget https://github.com/klokantech/tileserver-gl/releases/download/v0.0.2/test_data.zip - wget -O test_data.zip https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/test_data.zip
- unzip -q test_data.zip -d test_data - unzip -q test_data.zip -d test_data
script: script:
- xvfb-run --server-args="-screen 0 1024x768x24" npm test - xvfb-run --server-args="-screen 0 1024x768x24" npm test

View File

@@ -3,20 +3,28 @@ MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
RUN apt-get -qq update \ RUN apt-get -qq update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \ && DEBIAN_FRONTEND=noninteractive apt-get -y install \
apt-transport-https \
curl \ curl \
unzip \
build-essential \ build-essential \
python \ python \
libcairo2-dev \
libprotobuf-dev \
xvfb \ xvfb \
&& curl -sL https://deb.nodesource.com/setup_5.x | bash - \ && echo "deb https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
&& apt-get -y install nodejs \ && echo "deb-src https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
&& apt-get -qq update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated install \
nodejs \
&& rm /etc/apt/sources.list.d/nodejs.list \
&& apt-get clean && apt-get clean
RUN mkdir -p /usr/src/app RUN mkdir -p /usr/src/app
COPY / /usr/src/app COPY / /usr/src/app
RUN cd /usr/src/app && npm install RUN cd /usr/src/app && npm install --production
VOLUME /data VOLUME /data
WORKDIR /data WORKDIR /data
EXPOSE 80 EXPOSE 80
CMD ["/usr/src/app/run.sh"] ENTRYPOINT ["/usr/src/app/run.sh"]

12
Dockerfile_light Normal file
View File

@@ -0,0 +1,12 @@
FROM node:4
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
RUN mkdir -p /usr/src/app
COPY / /usr/src/app
RUN cd /usr/src/app && npm install --production
VOLUME /data
WORKDIR /data
EXPOSE 80
ENTRYPOINT ["node", "/usr/src/app/", "-p", "80"]

976
LICENSE.md Normal file
View File

@@ -0,0 +1,976 @@
TileServer GL
=============
Copyright (c) 2016, Klokan Technologies GmbH
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
mapbox-gl-native copyright (c) 2014-2016 Mapbox.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
Mapbox GL uses portions of Android Gesture Detectors Framework.
Copyright (c) 2012, Almer Thie
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
Mapbox GL uses portions of Android Support Library.
Copyright (c) 2005-2013, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===========================================================================
Mapbox GL uses portions of Boost.
Distributed under the Boost Software License, Version 1.0.
http://www.boost.org/LICENSE_1_0.txt
===========================================================================
Mapbox GL uses portions of Clipper.
Author : Angus Johnson
Version : 6.1.3a
Date : 22 January 2014
Website : http://www.angusj.com
Copyright : Angus Johnson 2010-2014
License:
Use, modification & distribution is subject to Boost Software License Ver 1.
http://www.boost.org/LICENSE_1_0.txt
Attributions:
The code in this library is an extension of Bala Vatti's clipping algorithm:
"A generic solution to polygon clipping"
Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.
http://portal.acm.org/citation.cfm?id=129906
Computer graphics and geometric modeling: implementation and algorithms
By Max K. Agoston
Springer; 1 edition (January 4, 2005)
http://books.google.com/books?q=vatti+clipping+agoston
See also:
"Polygon Offsetting by Computing Winding Numbers"
Paper no. DETC2005-85513 pp. 565-575
ASME 2005 International Design Engineering Technical Conferences
and Computers and Information in Engineering Conference (IDETC/CIE2005)
September 24-28, 2005 , Long Beach, California, USA
http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf
===========================================================================
Mapbox GL uses portions of BugshotKit.
The MIT License (MIT)
Copyright (c) 2014 marcoarment
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
===========================================================================
Mapbox GL uses portions of CSS Color Parser.
(c) Dean McNamee <dean@gmail.com>, 2012.
C++ port by Konstantin Käfer <mail@kkaefer.com>, 2014.
https://github.com/deanm/css-color-parser-js
https://github.com/kkaefer/css-color-parser-cpp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
===========================================================================
Mapbox GL uses portions of GLFW.
Copyright (c) 2002-2006 Marcus Geelnard
Copyright (c) 2006-2010 Camilla Berglund <elmindreda@elmindreda.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would
be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
===========================================================================
Mapbox GL uses portions of libc++.
The libc++ library is dual licensed under both the University of Illinois
"BSD-Like" license and the MIT license. As a user of this code you may choose
to use it under either license. As a contributor, you agree to allow your code
to be used under both.
Full text of the relevant licenses is included below.
====
University of Illinois/NCSA
Open Source License
Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT
All rights reserved.
Developed by:
LLVM Team
University of Illinois at Urbana-Champaign
http://llvm.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLVM Team, University of Illinois at
Urbana-Champaign, nor the names of its contributors may be used to
endorse or promote products derived from this Software without specific
prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
====
Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===========================================================================
Mapbox GL uses portions of libcurl.
COPYRIGHT AND PERMISSION NOTICE
Copyright (c) 1996 - 2015, Daniel Stenberg, <daniel@haxx.se>.
All rights reserved.
Permission to use, copy, modify, and distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization of the copyright holder.
===========================================================================
Mapbox GL uses portions of libjpeg-turbo.
This software is based in part on the work of the Independent JPEG Group.
Copyright (C)2009-2015 D. R. Commander. All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the libjpeg-turbo Project nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
TurboJPEG/LJT: this implements the TurboJPEG API using libjpeg or libjpeg-turbo
===========================================================================
Mapbox GL uses portions of libpng.
This copy of the libpng notices is provided for your convenience. In case of
any discrepancy between this copy and the notices in the file png.h that is
included in the libpng distribution, the latter shall prevail.
COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
If you modify libpng you may insert additional notices immediately following
this sentence.
This code is released under the libpng license.
libpng versions 1.0.7, July 1, 2000, through 1.6.18, July 23, 2015, are
Copyright (c) 2000-2002, 2004, 2006-2015 Glenn Randers-Pehrson, and are
distributed according to the same disclaimer and license as libpng-1.0.6
with the following individuals added to the list of Contributing Authors:
Simon-Pierre Cadieux
Eric S. Raymond
Mans Rullgard
Cosmin Truta
Gilles Vollant
James Yu
and with the following additions to the disclaimer:
There is no warranty against interference with your enjoyment of the
library or against infringement. There is no warranty that our
efforts or the library will fulfill any of your particular purposes
or needs. This library is provided with all faults, and the entire
risk of satisfactory quality, performance, accuracy, and effort is with
the user.
libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
Copyright (c) 1998-2000 Glenn Randers-Pehrson, and are distributed according
to the same disclaimer and license as libpng-0.96, with the following
individuals added to the list of Contributing Authors:
Tom Lane
Glenn Randers-Pehrson
Willem van Schaik
libpng versions 0.89, June 1996, through 0.96, May 1997, are
Copyright (c) 1996-1997 Andreas Dilger, and are
distributed according to the same disclaimer and license as libpng-0.88,
with the following individuals added to the list of Contributing Authors:
John Bowler
Kevin Bracey
Sam Bushell
Magnus Holmgren
Greg Roelofs
Tom Tanner
libpng versions 0.5, May 1995, through 0.88, January 1996, are
Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
For the purposes of this copyright and license, "Contributing Authors"
is defined as the following set of individuals:
Andreas Dilger
Dave Martindale
Guy Eric Schalnat
Paul Schmidt
Tim Wegner
The PNG Reference Library is supplied "AS IS". The Contributing Authors
and Group 42, Inc. disclaim all warranties, expressed or implied,
including, without limitation, the warranties of merchantability and of
fitness for any purpose. The Contributing Authors and Group 42, Inc.
assume no liability for direct, indirect, incidental, special, exemplary,
or consequential damages, which may result from the use of the PNG
Reference Library, even if advised of the possibility of such damage.
Permission is hereby granted to use, copy, modify, and distribute this
source code, or portions hereof, for any purpose, without fee, subject
to the following restrictions:
1. The origin of this source code must not be misrepresented.
2. Altered versions must be plainly marked as such and must not
be misrepresented as being the original source.
3. This Copyright notice may not be removed or altered from any
source or altered source distribution.
The Contributing Authors and Group 42, Inc. specifically permit, without
fee, and encourage the use of this source code as a component to
supporting the PNG file format in commercial products. If you use this
source code in a product, acknowledgment is not required but would be
appreciated.
===========================================================================
Mapbox GL uses portions of libuv.
libuv is part of the Node project: http://nodejs.org/
libuv may be distributed alone under Node's license:
====
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
====
This license applies to all parts of libuv that are not externally
maintained libraries.
The externally maintained libraries used by libuv are:
- tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license.
- inet_pton and inet_ntop implementations, contained in src/inet.c, are
copyright the Internet Systems Consortium, Inc., and licensed under the ISC
license.
- stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three
clause BSD license.
- pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile
Communications AB. Three clause BSD license.
- android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design
Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement
n° 289016). Three clause BSD license.
===========================================================================
Mapbox GL uses portions of libzip.
Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
The authors can be contacted at <libzip@nih.at>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The names of the authors may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
Mapbox GL uses portions of LOST.
Copyright (c) 2014 Mapzen
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===========================================================================
Mapbox GL uses portions of the Mapbox iOS SDK, which was derived from the
Route-Me open source project, including the Alpstein fork of it.
The Route-Me license appears below.
Copyright (c) 2008-2013, Route-Me Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
===========================================================================
Mapbox GL uses portions of nunicode.
Copyright (c) 2013 Aleksey Tulinov <aleksey.tulinov@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===========================================================================
Mapbox GL uses portions of OkHTTP.
Copyright 2014 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===========================================================================
Mapbox GL uses portions of OpenSSL.
LICENSE ISSUES
==============
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts. Actually both licenses are BSD-style
Open Source licenses. In case of any license issues related to OpenSSL
please contact openssl-core@openssl.org.
OpenSSL License
---------------
Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. All advertising materials mentioning features or use of this
software must display the following acknowledgment:
"This product includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
endorse or promote products derived from this software without
prior written permission. For written permission, please contact
openssl-core@openssl.org.
5. Products derived from this software may not be called "OpenSSL"
nor may "OpenSSL" appear in their names without prior written
permission of the OpenSSL Project.
6. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit (http://www.openssl.org/)"
THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
This product includes cryptographic software written by Eric Young
(eay@cryptsoft.com). This product includes software written by Tim
Hudson (tjh@cryptsoft.com).
Original SSLeay License
-----------------------
Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
All rights reserved.
This package is an SSL implementation written
by Eric Young (eay@cryptsoft.com).
The implementation was written so as to conform with Netscapes SSL.
This library is free for commercial and non-commercial use as long as
The following conditions are aheared to. The following conditions
apply to all code found in this distribution, be it the RC4, RSA,
lhash, DES, etc., code; not just the SSL code. The SSL documentation
included with this distribution is covered by the same copyright terms
except that the holder is Tim Hudson (tjh@cryptsoft.com).
Copyright remains Eric Young's, and as such any Copyright notices in
the code are not to be removed.
If this package is used in a product, Eric Young should be given attribution
as the author of the parts of the library used.
This can be in the form of a textual message at program startup or
in documentation (online or textual) provided with the package.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
"This product includes cryptographic software written by
Eric Young (eay@cryptsoft.com)"
The word 'cryptographic' can be left out if the rouines from the library
being used are not cryptographic related :-).
4. If you include any Windows specific code (or a derivative thereof) from
the apps directory (application code) you must include an acknowledgement:
"This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
The licence and distribution terms for any publically available version or
derivative of this code cannot be changed. i.e. this code cannot simply be
copied and put under another distribution licence
[including the GNU Public Licence.]
===========================================================================
Mapbox GL uses portions of RapidJSON.
Tencent is pleased to support the open source community by making RapidJSON
available.
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights
reserved.
If you have downloaded a copy of the RapidJSON binary from Tencent, please note
that the RapidJSON binary is licensed under the MIT License. If you have
downloaded a copy of the RapidJSON source code from Tencent, please note that
RapidJSON source code is licensed under the MIT License, except for the third-
party components listed below which are subject to different license terms.
Your integration of RapidJSON into your own projects may require compliance with
the MIT License, as well as the other licenses applicable to the third-party
components included within RapidJSON. To avoid the problematic JSON license in
your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as
it's the only code under the JSON license. A copy of the MIT License is included
in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the BSD License:
--------------------------------------------------------------------
The msinttypes r29
Copyright (c) 2006-2013 Alexander Chemeris
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of copyright holder nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Open Source Software Licensed Under the JSON License:
--------------------------------------------------------------------
json.org
Copyright (c) 2002 JSON.org
All Rights Reserved.
JSON_checker
Copyright (c) 2002 JSON.org
All Rights Reserved.
Terms of the JSON License:
---------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Terms of the MIT License:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
===========================================================================
Mapbox GL uses portions of Reachability.
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
===========================================================================
Mapbox GL uses portions of SQLite.
2001 September 15
The author disclaims copyright to this source code. In place of
a legal notice, here is a blessing:
May you do good and not evil.
May you find forgiveness for yourself and forgive others.
May you share freely, never taking more than you give.
===========================================================================
Mapbox GL uses portions of SVPulsingAnnotationView.
Copyright (c) 2013, Sam Vermette <hello@samvermette.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
===========================================================================
Mapbox GL uses portions of zlib.
Acknowledgments:
The deflate format used by zlib was defined by Phil Katz. The deflate and
zlib specifications were written by L. Peter Deutsch. Thanks to all the
people who reported problems and suggested various improvements in zlib; they
are too numerous to cite here.
Copyright notice:
(C) 1995-2013 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
===========================================================================
Mapbox GL uses portions of Realm Objective-C.
Copyright 2015 Realm Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

6
PUBLISHING.md Normal file
View 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)

126
README.md
View File

@@ -1,96 +1,46 @@
# tileserver-gl ![tileserver-gl](https://cloud.githubusercontent.com/assets/59284/18173467/fa3aa2ca-7069-11e6-86b1-0f1266befeb6.jpeg)
# TileServer GL
[![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](https://travis-ci.org/klokantech/tileserver-gl) [![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](https://travis-ci.org/klokantech/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-gl/)
## Installation 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.
### Docker ## Get Started
- `docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl`
### Without docker Install `tileserver-gl` with server-side raster rendering of vector tiles with npm
- Make sure you have Node v4 or higher (`nvm install 4`)
- `npm install`
- `node src/main.js`
## Sample data ```bash
Sample data can be downloaded at https://github.com/klokantech/tileserver-gl/releases/download/v0.0.2/test_data.zip 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`)
#### Data
- tiles from http://osm2vectortiles.org/
- styles modified from https://github.com/klokantech/osm2vectortiles-gl-styles
## Configuration
Create `config.json` file in the root directory.
The config file can contain definition of several paths where the tiles will be served.
### Example configuration file
```json
{
"options": {
"paths": {
"root": "",
"fonts": "glyphs",
"sprites": "sprites",
"styles": "styles",
"mbtiles": ""
},
"domains": [
"localhost:8080",
"127.0.0.1:8080"
]
},
"styles": {
"test": {
"style": "basic-v8.json",
"tilejson": {
"type": "overlay",
"bounds": [8.44806, 47.32023, 8.62537, 47.43468]
}
},
"hybrid": {
"style": "satellite-hybrid-v8.json",
"raster": false,
"tilejson": {
"format": "webp",
"center": [8.536715, 47.377455, 6]
}
},
"streets": {
"style": "streets-v8.json",
"vector": false,
"tilejson": {
"center": [8.536715, 47.377455, 6]
}
}
},
"vector": {
"zurich-vector": {
"mbtiles": "zurich.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`)
## Available URLs Now download vector tiles from [OSM2VectorTiles](http://osm2vectortiles.org/downloads/).
- If you visit the server on the configured port (default 8080) you should see your maps appearing in the browser. ```bash
- Style is served at `/styles/{id}.json` (+ array at `/styles.json`) curl -o zurich_switzerland.mbtiles https://osm2vectortiles-downloads.os.zhdk.cloud.switch.ch/v2.0/extracts/zurich_switzerland.mbtiles
- Sprites at `/styles/{id}/sprite[@2x].{format}` ```
- Fonts at `/fonts/{fontstack}/{start}-{end}.pbf`
- Rasterized tiles are at `/raster/{id}/{z}/{x}/{y}[@2x].{format}` Start `tileserver-gl` with the downloaded vector tiles.
- The optional `@2x` (or `@3x`) part can be used to render HiDPI (retina) tiles
- Available formats: `png`, `jpg` (`jpeg`), `webp` ```bash
- TileJSON at `/raster/{id}.json` tileserver-gl zurich_switzerland.mbtiles
- Static images are rendered at: ```
- `/static/{id}/{lon},{lat},{zoom}/{width}x{height}[@2x].{format}` (center-based)
- `/static/{id}/{minx},{miny},{maxx},{maxy}/{zoom}[@2x].{format}` (area-based) 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.
- Vector tiles at `/vector/{mbtiles}/{z}/{x}/{y}.pbf`
- TileJSON at `/vector/{mbtiles}.json` ## Using Docker
- Array of all TileJSONs at `/index.json` (`/raster.json`; `/vector.json`)
An alternative to npm to start the packed software easier is to install [Docker](http://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
```bash
docker run -it -v $(pwd):/data -p 8080:80 klokantech/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 http://tileserver.readthedocs.io/.

17
README_light.md Normal file
View File

@@ -0,0 +1,17 @@
# TileServer GL light
[![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](https://travis-ci.org/klokantech/tileserver-gl)
[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-gl/)
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 [OSM2VectorTiles](http://osm2vectortiles.org/).
## Documentation
You can read full documentation of this project at http://tileserver.readthedocs.io/.

177
docs/Makefile Normal file
View 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
View 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

154
docs/config.rst Normal file
View File

@@ -0,0 +1,154 @@
==================
Configuration file
==================
The configuration file defines the behavior of the application. It's a regular JSON file.
Example::
{
"options": {
"paths": {
"root": "",
"fonts": "fonts",
"sprites": "sprites",
"styles": "styles",
"mbtiles": ""
},
"domains": [
"localhost:8080",
"127.0.0.1:8080"
],
"formatQuality": {
"jpeg": 80,
"webp": 90,
"pngQuantization": false,
"png": 90
},
"maxSize": 2048,
"serveAllFonts": false
},
"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.
``formatQuality``
-----------------
Quality of the compression of individual image formats. [0-100]
The value for ``png`` is only used when ``pngQuantization`` is ``true``.
``maxSize``
-----------
Maximum image side length to be allowed to be rendered (including scale factor). Default is ``2048``.
``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 acces 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 remove http(s) location (e.g. ``https://mycdn.com/my-style/sprite``). Several possible extension are added to this path, so the following files should be present:
* ``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
View 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-Forwaded-Proto``) to ensures the URLs generated inside TileJSON etc. are using the desired domain and protocol.

66
docs/endpoints.rst Normal file
View File

@@ -0,0 +1,66 @@
===================
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}.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}/rendered/{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}/rendered.json``
* The rendered tiles are not available in the ``tileserver-gl-light`` version.
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`` - "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"
* 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 ``/fontstacks.json``

28
docs/index.rst Normal file
View 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
View 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 -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl``.
Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following:
* ``docker run ... klokantech/tileserver-gl my-tiles.mbtiles`` -- explicitly specify which mbtiles to use (if you have more in the folder)
* ``docker run ... klokantech/tileserver-gl --verbose`` -- to see the default config created automatically
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 v4 (nvm install 4) 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

28
docs/usage.rst Normal file
View File

@@ -0,0 +1,28 @@
=====
Usage
=====
Getting started
======
::
Usage: tileserver-gl [mbtiles] [options]
mbtiles MBTiles file (uses demo configuration);
ignored if the configuration file is also specified
Options:
-c, --config Configuration file [config.json]
-b, --bind Bind address
-p, --port Port [8080]
-V, --verbose More verbose output
-v, --version Version info
Default styles and configuration
======
- If no configuration file is specified, the default styles (compatible with osm2vectortiles) are used.
- If no mbtiles file is specified (and is not found in the current working directory), an extract is downloaded directly from osm2vectortiles.

View File

@@ -1,8 +1,9 @@
{ {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "0.0.2", "version": "1.3.2",
"description": "Map tile server for JSON GL styles - serverside generated raster tiles", "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
"main": "src/main.js", "main": "src/main.js",
"bin": "src/main.js",
"authors": [ "authors": [
"Petr Sloup <petr.sloup@klokantech.com>" "Petr Sloup <petr.sloup@klokantech.com>"
], ],
@@ -10,26 +11,42 @@
"type": "git", "type": "git",
"url": "https://github.com/klokantech/tileserver-gl.git" "url": "https://github.com/klokantech/tileserver-gl.git"
}, },
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.2.1 <5"
},
"scripts": { "scripts": {
"test": "mocha test/**.js" "test": "mocha test/**.js"
}, },
"dependencies": { "dependencies": {
"async": "1.5.2", "async": "2.1.4",
"advanced-pool": "0.3.1", "advanced-pool": "0.3.2",
"clone": "1.0.2", "base64url": "2.0.0",
"cors": "2.7.1", "canvas": "1.6.2",
"express": "4.13.4", "clone": "2.1.0",
"mapbox-gl-native": "3.0.2-earcut", "color": "1.0.3",
"mbtiles": "0.8.2", "cors": "2.8.1",
"express": "4.14.0",
"glyph-pbf-composite": "0.0.2",
"handlebars": "4.0.6",
"mapbox-gl-native": "3.4.2",
"mbtiles": "0.9.0",
"morgan": "1.7.0", "morgan": "1.7.0",
"node-pngquant-native": "1.0.4",
"nomnom": "1.8.1", "nomnom": "1.8.1",
"request": "2.69.0", "pbf": "3.0.5",
"sharp": "0.13.1", "request": "2.79.0",
"sphericalmercator": "1.0.4" "sharp": "0.16.2",
"tileserver-gl-styles": "1.1.0",
"vector-tile": "1.3.0",
"@mapbox/sphericalmercator": "1.0.5"
},
"optionalDependencies": {
"tileshrink-gl": "./plugins/tileshrink-gl"
}, },
"devDependencies": { "devDependencies": {
"should": "^8.2.2", "should": "^11.1.1",
"mocha": "^2.4.5", "mocha": "^3.2.0",
"supertest": "^1.2.0" "supertest": "^2.0.1"
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

212
public/resources/index.css Normal file
View File

@@ -0,0 +1,212 @@
@font-face {
font-family: 'OpenSans';
src: url('/fonts/OpenSans-Regular.ttf');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src: url('/fonts/OpenSans-Italic.ttf');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'OpenSans';
src: url('/fonts/OpenSans-Bold.ttf');
font-weight: bold;
font-style: normal;
}
body{
background-color: #fff;
color: #212121;
font-family:'OpenSans', sans-serif, Arial;
font-size: 14px;
margin:0;
}
a{
color: #499DCE;
transition: color .2s;
}
a:hover{
color: #395D73;
}
.title {
font-weight: bold;
font-size: 32px;
text-align:center;
margin:90px 0 0 0;
position:relative;
}
.title.light:after {
content: "light";
display: block;
position: absolute;
left: 50%;
bottom: -5px;
color: #499DCE;
font-size:.8em;
}
section{
margin: 15px auto;
width: 930px;
padding: 30px 0;
}
.title img {
width: 320px;
}
.subtitle {
font-size: 26px;
font-weight:normal;
text-align:center;
margin:10px 0 95px 0;
}
.box-header {
text-align:left;
text-transform:uppercase;
border:1px solid #ededed;
margin:25px 0 0 0;
padding:12px 30px;
font-size:20px;
background:#fff;
}
.item{
background:#fff;
height: 191px;
border: 1px solid #ededed;
border-top:none;
}
.item:nth-child(odd) {
background-color:#fbfbfb;
}
.item img{
position: absolute;
margin: 30px;
width: 128px;
height: 128px;
border: 1px solid #ccc;
}
.details {
float:left;
height: 128px;
padding: 20px 30px 20px 188px;
}
.details h3 {
font-size:18px;
margin-top: 25px;
}
.details p {
padding:0;
margin:18px 0;
}
.viewers {
float:right;
text-align:center;
width: 120px;
margin-top: 25px;
padding-right: 30px;
}
.btn {
display:block;
margin: 0;
line-height: 36px;
}
.btn:first-child {
position: relative;
padding: 0;
overflow: hidden;
border-radius:4px;
background-color: #499DCE;
background: linear-gradient(90deg, #5aaad8, #4a9ecf);
color: #fff;
text-decoration: none;
font-weight: bold;
}
.btn:first-child:hover{
background: #395D73;
}
footer{
width:100%;
border-top:1px solid #ededed;
text-align:center;
color:#d3d3d3;
padding-top:10px;
font-size:12px;
}
footer img{
width: 118px;
height: 32px;
}
footer p {
margin-top:0;
}
footer a {
color: #787878;
text-decoration: none;
}
/* body background image */
body {
background-repeat:no-repeat !important;
background-size: contain !important;
background-image: url(/images/header-map-640px.png);
}
@media only screen and (min-width: 641px) {
body {
background-image: url(/images/header-map-1280px.png);
}
}
@media only screen and (min-width: 1281px) {
body {
background-image: url(/images/header-map-1600px.png);
}
}
@media only screen and (min-width: 1601px) {
body {
background-image: url(/images/header-map-2560px.png);
}
}
/* Responsive */
@media (max-width: 950px) {
section{
margin: 0;
width: 96%;
padding: 2%;
}
}
@media (max-width: 600px) {
.title{
margin: 25px 0 0 0;
}
.title.light:after {
font-size:.6em;
}
.title img{
width: 200px;
}
.subtitle{
font-size: 20px;
margin: 0 0 35px 0;
}
.item{
height: 245px;
}
.viewers{
float: left;
text-align: left;
width: 100%;
margin-left: 30px;
margin-top: 15px;
}
.viewers a{
display: inline-block;
vertical-align: middle;
}
.btn{
margin: 0 20px 0 0;
}
.btn:first-child{
padding: 0 20px;
}
}

View File

@@ -0,0 +1,162 @@
(function(window) {
var HAS_HASHCHANGE = (function() {
var doc_mode = window.documentMode;
return ('onhashchange' in window) &&
(doc_mode === undefined || doc_mode > 7);
})();
L.Hash = function(map) {
this.onHashChange = L.Util.bind(this.onHashChange, this);
if (map) {
this.init(map);
}
};
L.Hash.parseHash = function(hash) {
if(hash.indexOf('#') === 0) {
hash = hash.substr(1);
}
var args = hash.split("/");
if (args.length == 3) {
var zoom = parseInt(args[0], 10),
lat = parseFloat(args[1]),
lon = parseFloat(args[2]);
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
return false;
} else {
return {
center: new L.LatLng(lat, lon),
zoom: zoom
};
}
} else {
return false;
}
};
L.Hash.formatHash = function(map) {
var center = map.getCenter(),
zoom = map.getZoom(),
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
return "#" + [zoom,
center.lat.toFixed(precision),
center.lng.toFixed(precision)
].join("/");
},
L.Hash.prototype = {
map: null,
lastHash: null,
parseHash: L.Hash.parseHash,
formatHash: L.Hash.formatHash,
init: function(map) {
this.map = map;
// reset the hash
this.lastHash = null;
this.onHashChange();
if (!this.isListening) {
this.startListening();
}
},
removeFrom: function(map) {
if (this.changeTimeout) {
clearTimeout(this.changeTimeout);
}
if (this.isListening) {
this.stopListening();
}
this.map = null;
},
onMapMove: function() {
// bail if we're moving the map (updating from a hash),
// or if the map is not yet loaded
if (this.movingMap || !this.map._loaded) {
return false;
}
var hash = this.formatHash(this.map);
if (this.lastHash != hash) {
location.replace(hash);
this.lastHash = hash;
}
},
movingMap: false,
update: function() {
var hash = location.hash;
if (hash === this.lastHash) {
return;
}
var parsed = this.parseHash(hash);
if (parsed) {
this.movingMap = true;
this.map.setView(parsed.center, parsed.zoom);
this.movingMap = false;
} else {
this.onMapMove(this.map);
}
},
// defer hash change updates every 100ms
changeDefer: 100,
changeTimeout: null,
onHashChange: function() {
// throttle calls to update() so that they only happen every
// `changeDefer` ms
if (!this.changeTimeout) {
var that = this;
this.changeTimeout = setTimeout(function() {
that.update();
that.changeTimeout = null;
}, this.changeDefer);
}
},
isListening: false,
hashChangeInterval: null,
startListening: function() {
this.map.on("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.addListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
this.hashChangeInterval = setInterval(this.onHashChange, 50);
}
this.isListening = true;
},
stopListening: function() {
this.map.off("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
}
this.isListening = false;
}
};
L.hash = function(map) {
return new L.Hash(map);
};
L.Map.prototype.addHash = function() {
this._hash = L.hash(this);
};
L.Map.prototype.removeHash = function() {
this._hash.removeFrom();
};
})(window);

View 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');
}

File diff suppressed because one or more lines are too long

View File

@@ -21,13 +21,13 @@
.mapboxgl-ctrl-top-left, .mapboxgl-ctrl-top-left,
.mapboxgl-ctrl-top-right, .mapboxgl-ctrl-top-right,
.mapboxgl-ctrl-bottom-left, .mapboxgl-ctrl-bottom-left,
.mapboxgl-ctrl-bottom-right { position:absolute; } .mapboxgl-ctrl-bottom-right { position:absolute; pointer-events:none; z-index:2; }
.mapboxgl-ctrl-top-left { top:0; left:0; } .mapboxgl-ctrl-top-left { top:0; left:0; }
.mapboxgl-ctrl-top-right { top:0; right:0; } .mapboxgl-ctrl-top-right { top:0; right:0; }
.mapboxgl-ctrl-bottom-left { bottom:0; left:0; } .mapboxgl-ctrl-bottom-left { bottom:0; left:0; }
.mapboxgl-ctrl-bottom-right { right:0; bottom:0; } .mapboxgl-ctrl-bottom-right { right:0; bottom:0; }
.mapboxgl-ctrl { clear:both; } .mapboxgl-ctrl { clear:both; pointer-events:auto }
.mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; } .mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; }
.mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; } .mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; }
.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; } .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; }
@@ -65,32 +65,68 @@
background-color: rgba(0,0,0,0.05); background-color: rgba(0,0,0,0.05);
} }
.mapboxgl-ctrl-icon, .mapboxgl-ctrl-icon,
.mapboxgl-ctrl-icon > div.arrow { .mapboxgl-ctrl-icon > span.arrow {
speak: none; speak: none;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out { .mapboxgl-ctrl-icon {
padding: 5px; padding: 5px;
}
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
} }
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in { .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
padding: 5px;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
} }
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > div.arrow { .mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23333%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
}
.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.watching {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%2300f%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
}
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > span.arrow {
width: 20px; width: 20px;
height: 20px; height: 20px;
margin: 5px; margin: 5px;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
display: inline-block;
} }
.mapboxgl-ctrl.mapboxgl-ctrl-attrib { .mapboxgl-ctrl.mapboxgl-ctrl-attrib {
padding: 0 5px; padding: 0 5px;
background-color: rgba(255,255,255,0.5); background-color: rgba(255, 255, 255, .5);
margin: 0; margin: 0;
} }
.mapboxgl-ctrl-attrib.compact {
padding-top: 2px;
padding-bottom: 2px;
margin: 0 10px 10px 10px;
position: relative;
padding-right: 24px;
background-color: #fff;
border-radius: 3px 12px 12px 3px;
visibility: hidden;
}
.mapboxgl-ctrl-attrib.compact:hover {
visibility: visible;
}
.mapboxgl-ctrl-attrib.compact:after {
content: '';
cursor: pointer;
position: absolute;
bottom: 0;
right: 0;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%09%3Cpath%20fill%3D%27%23333333%27%20fill-rule%3D%27evenodd%27%20d%3D%27M4%2C10a6%2C6%200%201%2C0%2012%2C0a6%2C6%200%201%2C0%20-12%2C0%20M9%2C7a1%2C1%200%201%2C0%202%2C0a1%2C1%200%201%2C0%20-2%2C0%20M9%2C10a1%2C1%200%201%2C1%202%2C0l0%2C3a1%2C1%200%201%2C1%20-2%2C0%27%20%2F%3E%0D%0A%3C%2Fsvg%3E");
background-color: rgba(255, 255, 255, .5);
width: 24px;
height: 24px;
box-sizing: border-box;
visibility: visible;
border-radius: 12px;
}
.mapboxgl-ctrl-attrib a { .mapboxgl-ctrl-attrib a {
color: rgba(0,0,0,0.75); color: rgba(0,0,0,0.75);
text-decoration: none; text-decoration: none;
@@ -104,8 +140,20 @@
margin-left: 2px; margin-left: 2px;
} }
.mapboxgl-ctrl-scale {
background-color: rgba(255,255,255,0.75);
font-size: 10px;
border-width: medium 2px 2px;
border-style: none solid solid;
border-color: #333;
padding: 0 5px;
color: #333;
}
.mapboxgl-popup { .mapboxgl-popup {
position: absolute; position: absolute;
top: 0;
left: 0;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
will-change: transform; will-change: transform;
@@ -222,6 +270,13 @@
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.mapboxgl-marker {
position: absolute;
top: 0;
left: 0;
will-change: transform;
}
.mapboxgl-crosshair, .mapboxgl-crosshair,
.mapboxgl-crosshair .mapboxgl-interactive, .mapboxgl-crosshair .mapboxgl-interactive,
.mapboxgl-crosshair .mapboxgl-interactive:active { .mapboxgl-crosshair .mapboxgl-interactive:active {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,58 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="mapbox-gl.css" />
<script src="mapbox-gl.js"></script>
<title>Offline vector tiles</title>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
#dropdown { position: absolute; top: 10px; left:10px; }
</style>
<body>
<div id='map'></div>
<select id='dropdown'></select>
<script>
var styles;
var request = new XMLHttpRequest();
request.responseType = 'json';
request.open('GET', '/styles.json', true);
request.onload = function(e) {
if (request.readyState != 4) return;
if (request.status === 200) {
styles = request.response;
}
var map = new mapboxgl.Map({
container: 'map',
style: 'styles/' + styles[0].id + '.json',
center: [0, 0],
zoom: 0,
hash: true
});
map.addControl(new mapboxgl.Navigation());
var select = document.getElementById('dropdown');
for (var i in styles) {
var style = styles[i];
var el = document.createElement('option');
el.textContent = style.name + ' (' + style.id + '.json)';
el.value = style.id;
select.appendChild(el);
};
select.onchange = function() {
mapboxgl.util.getJSON(
'styles/' + document.getElementById('dropdown').value + '.json',
function (err, style) {
if (err) throw err;
map.setStyle(style);
});
}
};
request.send(null);
</script>
</body>
</html>

View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<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{{&key_query}}" />
<link rel="stylesheet" type="text/css" href="/mapbox-gl-inspect.css{{&key_query}}" />
<script src="/mapbox-gl.js{{&key_query}}"></script>
<script src="/mapbox-gl-inspect.min.js{{&key_query}}"></script>
<style>
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:0;width:240px;overflow:auto;}
#layerList div div {width:15px;height:15px;display:inline-block;}
</style>
{{/is_vector}}
{{^is_vector}}
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&key_query}}" />
<script src="/mapbox.js{{&key_query}}"></script>
<script src="/leaflet-hash.js{{&key_query}}"></script>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
{{/is_vector}}
</head>
<body>
{{#is_vector}}
<h1>{{name}}</h1>
<div id="map"></div>
<div id="layerList"></div>
<pre id="propertyList"></pre>
<script>
var map = new mapboxgl.Map({
container: 'map',
hash: true,
style: {
version: 8,
sources: {
'vector_layer_': {
type: 'vector',
url: '/data/{{id}}.json{{&key_query}}'
}
},
layers: []
}
});
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}}
{{^is_vector}}
<h1 style="display:none;">{{name}}</h1>
<div id='map'></div>
<script>
var map = L.mapbox.map('map', '/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);
}, 0);
</script>
{{/is_vector}}
</body>
</html>

109
public/templates/index.tmpl Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TileServer GL - Server for vector and raster maps with GL styles</title>
<link rel="stylesheet" type="text/css" href="/index.css{{&key_query}}" />
<script>
function toggle_xyz(id) {
var el = document.getElementById(id);
var s = el.style;
s.display = s.display == 'none' ? 'inline-block' : 'none';
el.setSelectionRange(0, el.value.length);
return false;
}
</script>
</head>
<body>
<section>
<h1 class="title {{#if is_light}}light{{/if}}"><img src="/images/logo.png" alt="TileServer GL" /></h1>
<h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
<h2 class="box-header">Styles</h2>
<div class="box">
{{#each styles}}
<div class="item">
{{#if thumbnail}}
<img src="/styles/{{@key}}/rendered/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
{{else}}
<img src="/images/placeholder.png" alt="{{name}} preview" />
{{/if}}
<div class="details">
<h3>{{name}}</h3>
<p class="identifier">identifier: {{@key}}</p>
<p class="services">
services:
{{#if serving_data}}
<a href="/styles/{{@key}}.json{{&../key_query}}">GL Style</a>
{{/if}}
{{#if serving_rendered}}
{{#if serving_data}}| {{/if}}<a href="/styles/{{@key}}/rendered.json{{&../key_query}}">TileJSON</a>
{{/if}}
{{#if wmts_link}}
| <a href="{{&wmts_link}}">WMTS</a>
{{/if}}
{{#if xyz_link}}
| <a href="#" onclick="return toggle_xyz('xyz_style_{{@key}}');">XYZ</a>
<input id="xyz_style_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
{{/if}}
</p>
</div>
<div class="viewers">
{{#if serving_data}}
{{#if serving_rendered}}
<a class="btn" href="/styles/{{@key}}/{{&../key_query}}{{viewer_hash}}">Viewer</a>
{{/if}}
{{/if}}
{{#if serving_rendered}}
<a class="btn" href="/styles/{{@key}}/?{{&../key_query_part}}raster{{viewer_hash}}">Raster</a>
{{/if}}
{{#if serving_data}}
<a class="btn" href="/styles/{{@key}}/?{{&../key_query_part}}vector{{viewer_hash}}">Vector</a>
{{/if}}
</div>
</div>
{{/each}}
</div>
<h2 class="box-header">Data</h2>
<div class="box">
{{#each data}}
<div class="item">
{{#if thumbnail}}
<img src="/data/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
{{else}}
<img src="/images/placeholder.png" alt="{{name}} preview" />
{{/if}}
<div class="details">
<h3>{{name}}</h3>
<p class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}} | type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data</p>
<p class="services">
services: <a href="/data/{{@key}}.json{{&../key_query}}">TileJSON</a>
{{#if wmts_link}}
| <a href="{{&wmts_link}}">WMTS</a>
{{/if}}
{{#if xyz_link}}
| <a href="#" onclick="return toggle_xyz('xyz_data_{{@key}}');">XYZ</a>
<input id="xyz_data_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
{{/if}}
</p>
</div>
<div class="viewers">
{{#is_vector}}
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Inspect</a>
{{/is_vector}}
{{^is_vector}}
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
{{/is_vector}}
</div>
</div>
{{/each}}
</div>
</section>
<footer>
<a href="https://www.klokantech.com/" target="_blank"><img src="/images/klokantech.png" /></a>
<p>
<a href="https://github.com/klokantech/tileserver-gl" target="_blank">Powered by TileServer GL ({{server_version}})</a> <a href="https://www.klokantech.com/" target="_blank">open-source project from Klokan Technologies GmbH.</a>
</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<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{{&key_query}}" />
<script src="/mapbox-gl.js{{&key_query}}"></script>
<link rel="stylesheet" type="text/css" href="/mapbox.css{{&key_query}}" />
<script src="/mapbox.js{{&key_query}}"></script>
<script src="/leaflet-hash.js{{&key_query}}"></script>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<h1 style="display:none;">{{name}}</h1>
<div id='map'></div>
<script>
var q = (location.search || '').substr(1).split('&');
var preference =
q.indexOf('vector') >= 0 ? 'vector' :
(q.indexOf('raster') >= 0 ? 'raster' :
(mapboxgl.supported() ? 'vector' : 'raster'));
if (preference == 'vector') {
var map = new mapboxgl.Map({
container: 'map',
style: '/styles/{{id}}.json{{&key_query}}',
hash: true
});
map.addControl(new mapboxgl.NavigationControl());
} else {
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&key_query}}', { zoomControl: false });
new L.Control.Zoom({ position: 'topright' }).addTo(map);
setTimeout(function() {
new L.Hash(map);
}, 0);
}
</script>
</body>
</html>

47
publish.js Normal file
View File

@@ -0,0 +1,47 @@
#!/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-gl-native'];
delete packageJson.dependencies['node-pngquant-native'];
delete packageJson.dependencies['sharp'];
delete packageJson.optionalDependencies;
delete packageJson.devDependencies;
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');
/* PUBLISH */
// tileserver-gl
require('child_process').execSync('npm publish .', {
stdio: 'inherit'
});
// tileserver-gl-light
require('child_process').execSync('npm publish light', {
stdio: 'inherit'
});

3
run.sh
View File

@@ -1,2 +1,3 @@
#!/bin/bash #!/bin/bash
xvfb-run --server-args="-screen 0 1024x768x24" node /usr/src/app/src/main.js -p 80 -c /data/config.json cd /data
xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/ -p 80 "$@"

View File

@@ -2,27 +2,195 @@
'use strict'; 'use strict';
var fs = require('fs'),
path = require('path'),
request = require('request');
var mbtiles = require('mbtiles');
var packageJson = require('../package');
var opts = require('nomnom') var opts = require('nomnom')
.option('mbtiles', {
default: undefined,
help: 'MBTiles file (uses demo configuration);\n' +
'\t ignored if the configuration file is also specified',
position: 0
})
.option('config', { .option('config', {
abbr: 'c', abbr: 'c',
default: 'config.json', default: 'config.json',
help: 'Configuration file' help: 'Configuration file'
}) })
.option('bind', {
abbr: 'b',
default: undefined,
help: 'Bind address'
})
.option('port', { .option('port', {
abbr: 'p', abbr: 'p',
default: 8080, default: 8080,
help: 'Port' help: 'Port'
}) })
.option('verbose', {
abbr: 'V',
flag: true,
help: 'More verbose output'
})
.option('version', { .option('version', {
abbr: 'v', abbr: 'v',
flag: true, flag: true,
help: 'Version info', help: 'Version info',
callback: function() { callback: function() {
return 'version ' + require('../package.json').version; return packageJson.name + ' v' + packageJson.version;
} }
}).parse(); }).parse();
console.log('Starting ' + packageJson.name + ' v' + packageJson.version);
var startServer = function(configPath, config) {
return require('./server')({ return require('./server')({
config: opts.config, configPath: configPath,
config: config,
bind: opts.bind,
port: opts.port port: opts.port
}); });
};
var startWithMBTiles = function(mbtilesFile) {
console.log('Automatically creating config file for ' + mbtilesFile);
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
var mbtilesStats = fs.statSync(mbtilesFile);
if (!mbtilesStats.isFile() || mbtilesStats.size === 0) {
console.log('ERROR: Not valid MBTiles file: ' + mbtilesFile);
process.exit(1);
}
var instance = new mbtiles(mbtilesFile, function(err) {
instance.getInfo(function(err, info) {
var bounds = info.bounds;
var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
var 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) {
var omtV = (info.version || '').split('.');
config['data']['v' + omtV[0]] = {
"mbtiles": path.basename(mbtilesFile)
};
var styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
for (var i = 0; i < styles.length; i++) {
var styleName = styles[i];
var styleFileRel = styleName + '/style.json';
var styleFile = path.resolve(styleDir, 'styles', styleFileRel);
if (fs.existsSync(styleFile)) {
var styleJSON = require(styleFile);
var omtVersionCompatibility =
((styleJSON || {}).metadata || {})['openmaptiles:version'] || 'x';
var m = omtVersionCompatibility.toLowerCase().split('.');
var isCompatible = !(
m[0] != 'x' && (
m[0] != omtV[0] || (
(m[1] || 'x') != 'x' && (
m[1] != omtV[1] || (
(m[2] || 'x') != 'x' &&
m[2] != omtV[2]
)
)
)
)
);
if (isCompatible) {
var styleObject = {
"style": styleFileRel,
"tilejson": {
"bounds": bounds
}
};
config['styles'][styleName] = styleObject;
} else {
console.log('Style', styleName, 'requires OpenMapTiles version',
omtVersionCompatibility, 'but mbtiles is version', info.version);
}
}
}
} 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), function(err, stats) {
if (err || !stats.isFile() || stats.size === 0) {
var mbtiles = opts.mbtiles;
if (!mbtiles) {
// try to find in the cwd
var files = fs.readdirSync(process.cwd());
for (var i=0; i < files.length; i++) {
var filename = files[i];
if (filename.endsWith('.mbtiles')) {
var 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 {
var url = 'https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
var filename = 'zurich_switzerland.mbtiles';
var stream = fs.createWriteStream(filename);
console.log('Downloading sample data (' + filename + ') from ' + url);
stream.on('finish', function() {
return startWithMBTiles(filename);
});
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);
}
});

158
src/serve_data.js Normal file
View File

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

View File

@@ -1,60 +1,59 @@
'use strict'; 'use strict';
var async = require('async'),
path = require('path'),
fs = require('fs');
var clone = require('clone'), var clone = require('clone'),
express = require('express'); express = require('express'),
fs = require('fs'),
path = require('path');
var utils = require('./utils');
module.exports = function(options, allowedFonts) { module.exports = function(options, allowedFonts) {
var app = express().disable('x-powered-by'); var app = express().disable('x-powered-by');
var lastModified = new Date().toUTCString();
var fontPath = options.paths.fonts; var fontPath = options.paths.fonts;
var getFontPbf = function(name, range, callback) { var existingFonts = {};
// if some of the files failed to load (does not exist or not allowed), fs.readdir(options.paths.fonts, function(err, files) {
// return empty buffer so the other fonts can still work files.forEach(function(file) {
if (allowedFonts[name]) { fs.stat(path.join(fontPath, file), function(err, stats) {
var filename = path.join(fontPath, name, range + '.pbf'); if (!err) {
return fs.readFile(filename, function(err, data) { if (stats.isDirectory() &&
if (err) { fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
console.log('Font load error:', filename); existingFonts[path.basename(file)] = true;
return callback(null, new Buffer([])); }
} else {
return callback(null, data);
} }
}); });
} else { });
return callback(null, new Buffer([])); });
}
};
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf',
function(req, res, next) { function(req, res, next) {
var fontstack = decodeURI(req.params.fontstack); var fontstack = decodeURI(req.params.fontstack);
var range = req.params.range; var range = req.params.range;
var fonts = fontstack.split(','); return utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
fontPath, fontstack, range, existingFonts,
var queue = []; function(err, concated) {
fonts.forEach(function(font) { if (err || concated.length === 0) {
queue.push(function(callback) { console.log(err);
getFontPbf(font, range, callback); console.log(concated.length);
});
});
return async.parallel(queue, function(err, results) {
var concated = Buffer.concat(results);
if (err || concated.length == 0) {
return res.status(400).send(''); return res.status(400).send('');
} else { } else {
res.header('Content-type', 'application/x-protobuf'); res.header('Content-type', 'application/x-protobuf');
res.header('Last-Modified', lastModified);
return res.send(concated); return res.send(concated);
} }
}); });
}); });
app.get('/fontstacks.json', function(req, res, next) {
res.header('Content-type', 'application/json');
return res.send(
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort()
);
});
return app; return app;
}; };

View File

@@ -1,319 +0,0 @@
'use strict';
var async = require('async'),
advancedPool = require('advanced-pool'),
crypto = require('crypto'),
fs = require('fs'),
path = require('path'),
util = require('util'),
zlib = require('zlib');
var clone = require('clone'),
express = require('express'),
mercator = new (require('sphericalmercator'))(),
mbgl = require('mapbox-gl-native'),
mbtiles = require('mbtiles'),
request = require('request'),
sharp = require('sharp');
var utils = require('./utils');
var FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
var SCALE_PATTERN = '@[23]x';
var getScale = function(scale) {
return (scale || '@1x').slice(1, 2) | 0;
};
mbgl.on('message', function(e) {
if (e.severity == 'WARNING' || e.severity == 'ERROR') {
console.log('mbgl:', e);
}
});
module.exports = function(options, repo, params, id) {
var app = express().disable('x-powered-by');
var rootPath = options.paths.root;
var styleFile = params.style;
var map = {
renderers: [],
sources: {}
};
var styleJSON;
var createPool = function(ratio, min, max) {
var createRenderer = function(ratio, createCallback) {
var renderer = new mbgl.Map({
ratio: ratio,
request: function(req, callback) {
var protocol = req.url.split(':')[0];
//console.log('Handling request:', req);
if (protocol == 'sprites' || protocol == 'fonts') {
var dir = options.paths[protocol];
var file = unescape(req.url).substring(protocol.length + 3);
fs.readFile(path.join(dir, file), function(err, data) {
callback(err, { data: data });
});
} else if (protocol == 'mbtiles') {
var parts = req.url.split('/');
var source = map.sources[parts[2]];
var z = parts[3] | 0,
x = parts[4] | 0,
y = parts[5].split('.')[0] | 0;
source.getTile(z, x, y, function(err, data, headers) {
if (err) {
//console.log('MBTiles error, serving empty', err);
callback(null, { data: new Buffer(0) });
} else {
var response = {};
if (headers['Last-Modified']) {
response.modified = new Date(headers['Last-Modified']);
}
if (headers['ETag']) {
response.etag = headers['ETag'];
}
response.data = zlib.unzipSync(data);
callback(null, response);
}
});
} else if (protocol == 'http' || protocol == 'https') {
request({
url: req.url,
encoding: null,
gzip: true
}, function(err, res, body) {
if (err) {
//console.log('HTTP tile error', err);
callback(null, { data: new Buffer(0) });
} else if (res.statusCode == 200) {
var response = {};
if (res.headers.modified) {
response.modified = new Date(res.headers.modified);
}
if (res.headers.expires) {
response.expires = new Date(res.headers.expires);
}
if (res.headers.etag) {
response.etag = res.headers.etag;
}
response.data = body;
callback(null, response);
} else {
//console.log('HTTP error', JSON.parse(body).message);
callback(null, { data: new Buffer(0) });
}
});
}
}
});
renderer.load(styleJSON);
createCallback(null, renderer);
};
return new advancedPool.Pool({
min: min,
max: max,
create: createRenderer.bind(null, ratio),
destroy: function(renderer) {
renderer.release();
}
});
};
styleJSON = require(path.join(options.paths.styles, styleFile));
styleJSON.sprite = 'sprites://' + path.basename(styleFile, '.json');
styleJSON.glyphs = 'fonts://{fontstack}/{range}.pbf';
var tileJSON = {
'tilejson': '2.0.0',
'name': styleJSON.name,
'basename': id,
'minzoom': 0,
'maxzoom': 20,
'bounds': [-180, -85.0511, 180, 85.0511],
'format': 'png',
'type': 'baselayer'
};
Object.assign(tileJSON, params.tilejson || {});
tileJSON.tiles = params.domains || options.domains;
var queue = [];
Object.keys(styleJSON.sources).forEach(function(name) {
var source = styleJSON.sources[name];
var url = source.url;
if (url.lastIndexOf('mbtiles:', 0) === 0) {
// found mbtiles source, replace with info from local file
delete source.url;
queue.push(function(callback) {
var mbtilesFile = url.substring('mbtiles://'.length);
map.sources[name] = new mbtiles(
path.join(options.paths.mbtiles, mbtilesFile), function(err) {
map.sources[name].getInfo(function(err, info) {
Object.assign(source, info);
source.basename = name;
source.tiles = [
// meta url which will be detected when requested
'mbtiles://' + name + '/{z}/{x}/{y}.pbf'
];
callback(null);
});
});
});
}
});
async.parallel(queue, function(err, results) {
// TODO: make pool sizes configurable
map.renderers[1] = createPool(1, 4, 16);
map.renderers[2] = createPool(2, 2, 8);
map.renderers[3] = createPool(3, 2, 4);
});
repo[id] = tileJSON;
var tilePattern = '/raster/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+)' +
':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)';
var respondImage = function(z, lon, lat, bearing, pitch,
width, height, scale, format, res, next) {
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06) {
return res.status(400).send('Invalid center');
}
if (Math.min(width, height) <= 0 ||
Math.max(width, height) * scale > 6000) {
return res.status(400).send('Invalid size');
}
if (format == 'png' || format == 'webp') {
} else if (format == 'jpg' || format == 'jpeg') {
format = 'jpeg';
} else {
return res.status(400).send('Invalid format');
}
var pool = map.renderers[scale];
pool.acquire(function(err, renderer) {
var mbglZ = Math.max(0, z - 1);
var params = {
zoom: mbglZ,
center: [lon, lat],
bearing: bearing,
pitch: pitch,
width: width,
height: height
};
if (z == 0) {
params.width *= 2;
params.height *= 2;
}
renderer.render(params, function(err, data) {
pool.release(renderer);
if (err) console.log(err);
var image = sharp(data, {
raw: {
width: params.width * scale,
height: params.height * scale,
channels: 4
}
});
if (z == 0) {
// HACK: when serving zoom 0, resize the 0 tile from 512 to 256
image.resize(width * scale, height * scale);
}
image.toFormat(format)
.compressionLevel(9)
.toBuffer(function(err, buffer, info) {
if (!buffer) {
return res.status(404).send('Not found');
}
var md5 = crypto.createHash('md5').update(buffer).digest('base64');
res.set({
'content-md5': md5,
'content-type': 'image/' + format
});
return res.status(200).send(buffer);
});
});
});
};
app.get(tilePattern, function(req, res, next) {
var z = req.params.z | 0,
x = req.params.x | 0,
y = req.params.y | 0,
scale = getScale(req.params.scale),
format = req.params.format;
if (z < 0 || x < 0 || y < 0 ||
z > 20 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
return res.status(404).send('Out of bounds');
}
var tileSize = 256;
var tileCenter = mercator.ll([
((x + 0.5) / (1 << z)) * (256 << z),
((y + 0.5) / (1 << z)) * (256 << z)
], z);
return respondImage(z, tileCenter[0], tileCenter[1], 0, 0,
tileSize, tileSize, scale, format, res, next);
});
var staticPattern =
'/static/' + id + '/%s:scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)';
var centerPattern =
util.format(':lon(%s),:lat(%s),:z(\\d+):bearing(,%s)?:pitch(,%s)?/' +
':width(\\d+)x:height(\\d+)',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, centerPattern), function(req, res, next) {
var z = req.params.z | 0,
x = +req.params.lon,
y = +req.params.lat,
bearing = +(req.params.bearing || ',0').substring(1),
pitch = +(req.params.pitch || ',0').substring(1),
w = req.params.width | 0,
h = req.params.height | 0,
scale = getScale(req.params.scale),
format = req.params.format;
return respondImage(z, x, y, bearing, pitch,
w, h, scale, format, res, next);
});
var boundsPattern =
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)/:z(\\d+)',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, boundsPattern), function(req, res, next) {
var bbox = [+req.params.minx, +req.params.miny,
+req.params.maxx, +req.params.maxy];
var z = req.params.z | 0,
x = (bbox[0] + bbox[2]) / 2,
y = (bbox[1] + bbox[3]) / 2;
var minCorner = mercator.px([bbox[0], bbox[3]], z),
maxCorner = mercator.px([bbox[2], bbox[1]], z);
var w = (maxCorner[0] - minCorner[0]) | 0,
h = (maxCorner[1] - minCorner[1]) | 0,
scale = getScale(req.params.scale),
format = req.params.format;
return respondImage(z, x, y, 0, 0, w, h, scale, format, res, next);
});
app.get('/raster/' + id + '.json', function(req, res, next) {
var info = clone(tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles,
'raster/' + id, info.format);
return res.send(info);
});
return app;
};

598
src/serve_rendered.js Normal file
View File

@@ -0,0 +1,598 @@
'use strict';
var async = require('async'),
advancedPool = require('advanced-pool'),
fs = require('fs'),
path = require('path'),
util = require('util'),
zlib = require('zlib');
// sharp has to be required before node-canvas
// see https://github.com/lovell/sharp/issues/371
var sharp = require('sharp');
var Canvas = require('canvas'),
clone = require('clone'),
Color = require('color'),
express = require('express'),
mercator = new (require('@mapbox/sphericalmercator'))(),
mbgl = require('mapbox-gl-native'),
mbtiles = require('mbtiles'),
pngquant = require('node-pngquant-native'),
request = require('request');
var utils = require('./utils');
var FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
var SCALE_PATTERN = '@[234]x';
var getScale = function(scale) {
return (scale || '@1x').slice(1, 2) | 0;
};
mbgl.on('message', function(e) {
if (e.severity == 'WARNING' || e.severity == 'ERROR') {
console.log('mbgl:', e);
}
});
module.exports = function(options, repo, params, id, dataResolver) {
var app = express().disable('x-powered-by');
var lastModified = new Date().toUTCString();
var rootPath = options.paths.root;
var styleFile = params.style;
var map = {
renderers: [],
sources: {}
};
var existingFonts = {};
fs.readdir(options.paths.fonts, function(err, files) {
files.forEach(function(file) {
fs.stat(path.join(options.paths.fonts, file), function(err, stats) {
if (!err) {
if (stats.isDirectory()) {
existingFonts[path.basename(file)] = true;
}
}
});
});
});
var styleJSON;
var createPool = function(ratio, min, max) {
var createRenderer = function(ratio, createCallback) {
var renderer = new mbgl.Map({
ratio: ratio,
request: function(req, callback) {
var protocol = req.url.split(':')[0];
//console.log('Handling request:', req);
if (protocol == 'sprites') {
var dir = options.paths[protocol];
var file = unescape(req.url).substring(protocol.length + 3);
fs.readFile(path.join(dir, file), function(err, data) {
callback(err, { data: data });
});
} else if (protocol == 'fonts') {
var parts = req.url.split('/');
var fontstack = unescape(parts[2]);
var range = parts[3].split('.')[0];
utils.getFontsPbf(null, options.paths[protocol], fontstack, range, existingFonts,
function(err, concated) {
callback(err, {data: concated});
});
} else if (protocol == 'mbtiles') {
var parts = req.url.split('/');
var source = map.sources[parts[2]];
var z = parts[3] | 0,
x = parts[4] | 0,
y = parts[5].split('.')[0] | 0,
format = parts[5].split('.')[1];
source.getTile(z, x, y, function(err, data, headers) {
if (err) {
//console.log('MBTiles error, serving empty', err);
callback(null, { data: source.emptyTile });
} else {
var response = {};
if (headers['Last-Modified']) {
response.modified = new Date(headers['Last-Modified']);
}
if (format == 'pbf') {
response.data = zlib.unzipSync(data);
} else {
response.data = data;
}
callback(null, response);
}
});
} else if (protocol == 'http' || protocol == 'https') {
request({
url: req.url,
encoding: null,
gzip: true
}, function(err, res, body) {
if (err) {
//console.log('HTTP tile error', err);
callback(null, { data: new Buffer(0) });
} else if (res.statusCode == 200) {
var response = {};
if (res.headers.modified) {
response.modified = new Date(res.headers.modified);
}
if (res.headers.expires) {
response.expires = new Date(res.headers.expires);
}
if (res.headers.etag) {
response.etag = res.headers.etag;
}
response.data = body;
callback(null, response);
} else {
//console.log('HTTP error', JSON.parse(body).message);
callback(null, { data: new Buffer(0) });
}
});
}
}
});
renderer.load(styleJSON);
createCallback(null, renderer);
};
return new advancedPool.Pool({
min: min,
max: max,
create: createRenderer.bind(null, ratio),
destroy: function(renderer) {
renderer.release();
}
});
};
var styleJSONPath = path.join(options.paths.styles, styleFile);
styleJSON = clone(require(styleJSONPath));
var httpTester = /^(http(s)?:)?\/\//;
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
styleJSON.sprite = 'sprites://' +
styleJSON.sprite
.replace('{style}', path.basename(styleFile, '.json'))
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
}
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
styleJSON.glyphs = 'fonts://' + styleJSON.glyphs;
}
var tileJSON = {
'tilejson': '2.0.0',
'name': styleJSON.name,
'attribution': '',
'minzoom': 0,
'maxzoom': 20,
'bounds': [-180, -85.0511, 180, 85.0511],
'format': 'png',
'type': 'baselayer'
};
var attributionOverride = params.tilejson && params.tilejson.attribution;
Object.assign(tileJSON, params.tilejson || {});
tileJSON.tiles = params.domains || options.domains;
utils.fixTileJSONCenter(tileJSON);
var queue = [];
Object.keys(styleJSON.sources).forEach(function(name) {
var source = styleJSON.sources[name];
var url = source.url;
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
// found mbtiles source, replace with info from local file
delete source.url;
var mbtilesFile = url.substring('mbtiles://'.length);
var fromData = mbtilesFile[0] == '{' &&
mbtilesFile[mbtilesFile.length - 1] == '}';
if (fromData) {
mbtilesFile = dataResolver(
mbtilesFile.substr(1, mbtilesFile.length - 2));
if (!mbtilesFile) {
console.log('ERROR: data "' + mbtilesFile + '" not found!');
process.exit(1);
}
}
queue.push(function(callback) {
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
var mbtilesFileStats = fs.statSync(mbtilesFile);
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
throw Error('Not valid MBTiles file: ' + mbtilesFile);
}
map.sources[name] = new mbtiles(mbtilesFile, function(err) {
map.sources[name].getInfo(function(err, info) {
if (err) {
console.error(err);
}
var type = source.type;
Object.assign(source, info);
source.type = type;
source.tiles = [
// meta url which will be detected when requested
'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf')
];
delete source.scheme;
if (source.format == 'pbf') {
map.sources[name].emptyTile = new Buffer(0);
} else {
var color = new Color(source.color || 'rgba(255,255,255,0)');
var format = source.format;
if (format == 'jpg') {
format = 'jpeg';
}
var array = color.array();
var channels = array.length == 4 && format != 'jpeg' ? 4 : 3;
sharp(new Buffer(array), {
raw: {
width: 1,
height: 1,
channels: channels
}
}).toFormat(format).toBuffer(function(err, buffer, info) {
map.sources[name].emptyTile = buffer;
});
}
if (!attributionOverride &&
source.attribution && source.attribution.length > 0) {
if (tileJSON.attribution.length > 0) {
tileJSON.attribution += '; ';
}
tileJSON.attribution += source.attribution;
}
callback(null);
});
});
});
}
});
async.parallel(queue, function(err, results) {
// TODO: make pool sizes configurable
map.renderers[1] = createPool(1, 4, 16);
map.renderers[2] = createPool(2, 2, 8);
map.renderers[3] = createPool(3, 2, 4);
map.renderers[4] = createPool(4, 2, 4);
});
repo[id] = tileJSON;
var tilePattern = '/rendered/:z(\\d+)/:x(\\d+)/:y(\\d+)' +
':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)';
var respondImage = function(z, lon, lat, bearing, pitch,
width, height, scale, format, res, next,
opt_overlay) {
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06) {
return res.status(400).send('Invalid center');
}
if (Math.min(width, height) <= 0 ||
Math.max(width, height) * scale > (options.maxSize || 2048)) {
return res.status(400).send('Invalid size');
}
if (format == 'png' || format == 'webp') {
} else if (format == 'jpg' || format == 'jpeg') {
format = 'jpeg';
} else {
return res.status(400).send('Invalid format');
}
var pool = map.renderers[scale];
pool.acquire(function(err, renderer) {
var mbglZ = Math.max(0, z - 1);
var params = {
zoom: mbglZ,
center: [lon, lat],
bearing: bearing,
pitch: pitch,
width: width,
height: height
};
if (z == 0) {
params.width *= 2;
params.height *= 2;
}
renderer.render(params, function(err, data) {
pool.release(renderer);
if (err) console.log(err);
var image = sharp(data, {
raw: {
width: params.width * scale,
height: params.height * scale,
channels: 4
}
});
if (z == 0) {
// HACK: when serving zoom 0, resize the 0 tile from 512 to 256
image.resize(width * scale, height * scale);
}
if (opt_overlay) {
image.overlayWith(opt_overlay);
}
image.toFormat(format);
var formatQuality = (params.formatQuality || {})[format] ||
(options.formatQuality || {})[format];
if (format == 'png') {
image.withoutAdaptiveFiltering();
} else if (format == 'jpeg') {
image.quality(formatQuality || 80);
} else if (format == 'webp') {
image.quality(formatQuality || 90);
}
image.toBuffer(function(err, buffer, info) {
if (!buffer) {
return res.status(404).send('Not found');
}
if (format == 'png') {
var usePngQuant =
(options.formatQuality || {}).pngQuantization === true;
if (usePngQuant) {
buffer = pngquant.compress(buffer, {
quality: [0, formatQuality || 90]
});
}
}
res.set({
'Last-Modified': lastModified,
'Content-Type': 'image/' + format
});
return res.status(200).send(buffer);
});
});
});
};
app.get(tilePattern, function(req, res, next) {
var modifiedSince = req.get('if-modified-since'), cc = req.get('cache-control');
if (modifiedSince && (!cc || cc.indexOf('no-cache') == -1)) {
if (new Date(lastModified) <= new Date(modifiedSince)) {
return res.sendStatus(304);
}
}
var z = req.params.z | 0,
x = req.params.x | 0,
y = req.params.y | 0,
scale = getScale(req.params.scale),
format = req.params.format;
if (z < 0 || x < 0 || y < 0 ||
z > 20 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
return res.status(404).send('Out of bounds');
}
var tileSize = 256;
var tileCenter = mercator.ll([
((x + 0.5) / (1 << z)) * (256 << z),
((y + 0.5) / (1 << z)) * (256 << z)
], z);
return respondImage(z, tileCenter[0], tileCenter[1], 0, 0,
tileSize, tileSize, scale, format, res, next);
});
var extractPathFromQuery = function(query) {
var pathParts = (query.path || '').split('|');
var path = [];
pathParts.forEach(function(pair) {
var pairParts = pair.split(',');
if (pairParts.length == 2) {
if (query.latlng == '1' || query.latlng == 'true') {
path.push([+(pairParts[1]), +(pairParts[0])]);
} else {
path.push([+(pairParts[0]), +(pairParts[1])]);
}
}
});
return path;
};
var renderOverlay = function(z, x, y, bearing, pitch, w, h, scale,
path, query) {
if (!path || path.length < 2) {
return null;
}
var precisePx = function(ll, zoom) {
var px = mercator.px(ll, 20);
var scale = Math.pow(2, zoom - 20);
return [px[0] * scale, px[1] * scale];
};
var canvas = new Canvas(scale * w, scale * h);
var ctx = canvas.getContext('2d');
var center = precisePx([x, y], z);
ctx.scale(scale, scale);
if (bearing) {
ctx.translate(w / 2, h / 2);
ctx.rotate(-bearing / 180 * Math.PI);
ctx.translate(-center[0], -center[1]);
} else {
// optimized path
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
}
var lineWidth = query.width !== undefined ?
parseFloat(query.width) : 1;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
ctx.beginPath();
path.forEach(function(pair) {
var px = precisePx(pair, z);
ctx.lineTo(px[0], px[1]);
});
if (path[0][0] == path[path.length - 1][0] &&
path[0][1] == path[path.length - 1][1]) {
ctx.closePath();
}
ctx.fill();
if (lineWidth > 0) {
ctx.stroke();
}
return canvas.toBuffer();
};
var calcZForBBox = function(bbox, w, h, query) {
var z = 25;
var padding = query.padding !== undefined ?
parseFloat(query.padding) : 0.1;
var minCorner = mercator.px([bbox[0], bbox[3]], z),
maxCorner = mercator.px([bbox[2], bbox[1]], z);
var w_ = w / (1 + 2 * padding);
var h_ = h / (1 + 2 * padding);
z -= Math.max(
Math.log((maxCorner[0] - minCorner[0]) / w_),
Math.log((maxCorner[1] - minCorner[1]) / h_)
) / Math.LN2;
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
return z;
};
var staticPattern =
'/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' +
':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)';
var centerPattern =
util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, centerPattern), function(req, res, next) {
var raw = req.params.raw;
var z = +req.params.z,
x = +req.params.x,
y = +req.params.y,
bearing = +(req.params.bearing || '0'),
pitch = +(req.params.pitch || '0'),
w = req.params.width | 0,
h = req.params.height | 0,
scale = getScale(req.params.scale),
format = req.params.format;
if (z < 0) {
return res.status(404).send('Invalid zoom');
}
if (raw) {
var ll = mercator.inverse([x, y]);
x = ll[0];
y = ll[1];
}
var path = extractPathFromQuery(req.query);
var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query);
return respondImage(z, x, y, bearing, pitch, w, h, scale, format,
res, next, overlay);
});
var boundsPattern =
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, boundsPattern), function(req, res, next) {
var raw = req.params.raw;
var bbox = [+req.params.minx, +req.params.miny,
+req.params.maxx, +req.params.maxy];
if (raw) {
var minCorner = mercator.inverse(bbox.slice(0, 2));
var maxCorner = mercator.inverse(bbox.slice(2));
bbox[0] = minCorner[0];
bbox[1] = minCorner[1];
bbox[2] = maxCorner[0];
bbox[3] = maxCorner[1];
}
var w = req.params.width | 0,
h = req.params.height | 0,
scale = getScale(req.params.scale),
format = req.params.format;
var z = calcZForBBox(bbox, w, h, req.query),
x = (bbox[0] + bbox[2]) / 2,
y = (bbox[1] + bbox[3]) / 2,
bearing = 0,
pitch = 0;
var path = extractPathFromQuery(req.query);
var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query);
return respondImage(z, x, y, bearing, pitch, w, h, scale, format,
res, next, overlay);
});
var autoPattern = 'auto';
app.get(util.format(staticPattern, autoPattern), function(req, res, next) {
var path = extractPathFromQuery(req.query);
if (path.length < 2) {
return res.status(400).send('Invalid path');
}
var raw = req.params.raw;
var w = req.params.width | 0,
h = req.params.height | 0,
bearing = 0,
pitch = 0,
scale = getScale(req.params.scale),
format = req.params.format;
var bbox = [Infinity, Infinity, -Infinity, -Infinity];
path.forEach(function(pair) {
bbox[0] = Math.min(bbox[0], pair[0]);
bbox[1] = Math.min(bbox[1], pair[1]);
bbox[2] = Math.max(bbox[2], pair[0]);
bbox[3] = Math.max(bbox[3], pair[1]);
});
var z = calcZForBBox(bbox, w, h, req.query),
x = (bbox[0] + bbox[2]) / 2,
y = (bbox[1] + bbox[3]) / 2;
if (raw) {
var ll = mercator.inverse([x, y]);
x = ll[0];
y = ll[1];
}
var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query);
return respondImage(z, x, y, bearing, pitch, w, h, scale, format,
res, next, overlay);
});
app.get('/rendered.json', function(req, res, next) {
var info = clone(tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles,
'styles/' + id + '/rendered', info.format);
return res.send(info);
});
return app;
};

View File

@@ -7,7 +7,7 @@ var clone = require('clone'),
express = require('express'); express = require('express');
module.exports = function(options, repo, params, id, reportVector, reportFont) { module.exports = function(options, repo, params, id, reportTiles, reportFont) {
var app = express().disable('x-powered-by'); var app = express().disable('x-powered-by');
var styleFile = path.join(options.paths.styles, params.style); var styleFile = path.join(options.paths.styles, params.style);
@@ -16,39 +16,63 @@ module.exports = function(options, repo, params, id, reportVector, reportFont) {
Object.keys(styleJSON.sources).forEach(function(name) { Object.keys(styleJSON.sources).forEach(function(name) {
var source = styleJSON.sources[name]; var source = styleJSON.sources[name];
var url = source.url; var url = source.url;
if (url.lastIndexOf('mbtiles:', 0) === 0) { if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
var mbtiles = url.substring('mbtiles://'.length); var mbtilesFile = url.substring('mbtiles://'.length);
var identifier = reportVector(mbtiles); var fromData = mbtilesFile[0] == '{' &&
source.url = 'local://vector/' + identifier + '.json'; mbtilesFile[mbtilesFile.length - 1] == '}';
if (fromData) {
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
}
var identifier = reportTiles(mbtilesFile, fromData);
source.url = 'local://data/' + identifier + '.json';
} }
}); });
var findFontReferences = function(obj) { styleJSON.layers.forEach(function(obj) {
Object.keys(obj).forEach(function(key) { if (obj['type'] == 'symbol') {
var value = obj[key]; var fonts = (obj['layout'] || {})['text-font'];
if (key == 'text-font') { if (fonts && fonts.length) {
if (value && value.length > 0) { fonts.forEach(reportFont);
value.forEach(reportFont); } else {
reportFont('Open Sans Regular');
reportFont('Arial Unicode MS Regular');
} }
} else if (value && typeof value == 'object') {
findFontReferences(value);
} }
}); });
};
styleJSON.layers.forEach(findFontReferences);
var spritePath = path.join(options.paths.sprites, var spritePath;
path.basename(styleFile, '.json'));
var httpTester = /^(http(s)?:)?\/\//;
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'; styleJSON.sprite = 'local://styles/' + id + '/sprite';
}
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf'; styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
}
repo[id] = styleJSON; repo[id] = styleJSON;
app.get('/styles/' + id + '.json', function(req, res, next) { app.get('/' + id + '.json', function(req, res, next) {
var fixUrl = function(url) { var fixUrl = function(url, opt_nokey, opt_nostyle) {
var queryParams = [];
if (!opt_nostyle) {
queryParams.push('style=' + id);
}
if (!opt_nokey && req.query.key) {
queryParams.unshift('key=' + req.query.key);
}
var query = '';
if (queryParams.length) {
query = '?' + queryParams.join('&');
}
return url.replace( return url.replace(
'local://', req.protocol + '://' + req.headers.host + '/'); 'local://', req.protocol + '://' + req.headers.host + '/') + query;
}; };
var styleJSON_ = clone(styleJSON); var styleJSON_ = clone(styleJSON);
@@ -56,13 +80,21 @@ module.exports = function(options, repo, params, id, reportVector, reportFont) {
var source = styleJSON_.sources[name]; var source = styleJSON_.sources[name];
source.url = fixUrl(source.url); source.url = fixUrl(source.url);
}); });
styleJSON_.sprite = fixUrl(styleJSON_.sprite); // mapbox-gl-js viewer cannot handle sprite urls with query
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs); if (styleJSON_.sprite) {
styleJSON_.sprite = fixUrl(styleJSON_.sprite, true, true);
}
if (styleJSON_.glyphs) {
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs, false, true);
}
return res.send(styleJSON_); return res.send(styleJSON_);
}); });
app.get('/styles/' + id + '/sprite:scale(@[23]x)?\.:format([\\w]+)', app.get('/' + id + '/sprite:scale(@[23]x)?\.:format([\\w]+)',
function(req, res, next) { function(req, res, next) {
if (!spritePath) {
return res.status(404).send('File not found');
}
var scale = req.params.scale, var scale = req.params.scale,
format = req.params.format; format = req.params.format;
var filename = spritePath + (scale || '') + '.' + format; var filename = spritePath + (scale || '') + '.' + format;

View File

@@ -1,79 +0,0 @@
'use strict';
var crypto = require('crypto'),
path = require('path');
var clone = require('clone'),
express = require('express'),
mbtiles = require('mbtiles');
var utils = require('./utils');
module.exports = function(options, repo, params, id) {
var app = express().disable('x-powered-by');
var mbtilesFile = params.mbtiles;
var tileJSON = {
'tiles': params.domains || options.domains
};
repo[id] = tileJSON;
var source = new mbtiles(path.join(options.paths.mbtiles, mbtilesFile),
function(err) {
source.getInfo(function(err, info) {
tileJSON['name'] = id;
Object.assign(tileJSON, info);
tileJSON['tilejson'] = '2.0.0';
tileJSON['basename'] = id;
tileJSON['format'] = 'pbf';
Object.assign(tileJSON, params.tilejson || {});
});
});
var tilePattern = '/vector/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+).pbf';
app.get(tilePattern, function(req, res, next) {
var z = req.params.z | 0,
x = req.params.x | 0,
y = req.params.y | 0;
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);
}
} else {
var md5 = crypto.createHash('md5').update(data).digest('base64');
headers['content-md5'] = md5;
headers['content-type'] = 'application/x-protobuf';
headers['content-encoding'] = 'gzip';
res.set(headers);
if (data == null) {
return res.status(404).send('Not found');
} else {
return res.status(200).send(data);
}
}
});
});
app.get('/vector/' + id + '.json', function(req, res, next) {
var info = clone(tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles,
'vector/' + id, info.format);
return res.send(info);
});
return app;
};

View File

@@ -7,27 +7,36 @@ process.env.UV_THREADPOOL_SIZE =
var fs = require('fs'), var fs = require('fs'),
path = require('path'); path = require('path');
var clone = require('clone'), var base64url = require('base64url'),
clone = require('clone'),
cors = require('cors'), cors = require('cors'),
express = require('express'), express = require('express'),
handlebars = require('handlebars'),
mercator = new (require('@mapbox/sphericalmercator'))(),
morgan = require('morgan'); morgan = require('morgan');
var serve_font = require('./serve_font'), var packageJson = require('../package'),
serve_raster = require('./serve_raster'), serve_font = require('./serve_font'),
serve_rendered = null,
serve_style = require('./serve_style'), serve_style = require('./serve_style'),
serve_vector = require('./serve_vector'), serve_data = require('./serve_data'),
utils = require('./utils'); utils = require('./utils');
var isLight = packageJson.name.slice(-6) == '-light';
if (!isLight) {
// do not require `serve_rendered` in the light package
serve_rendered = require('./serve_rendered');
}
module.exports = function(opts, callback) { module.exports = function(opts, callback) {
console.log('Starting server');
var app = express().disable('x-powered-by'), var app = express().disable('x-powered-by'),
serving = { serving = {
styles: {}, styles: {},
raster: {}, rendered: {},
vector: {}, data: {},
fonts: { // default fonts, always expose these (if they exist) fonts: {}
'Open Sans Regular': true,
'Arial Unicode MS Regular': true
}
}; };
app.enable('trust proxy'); app.enable('trust proxy');
@@ -39,19 +48,37 @@ module.exports = function(opts, callback) {
app.use(morgan('dev')); app.use(morgan('dev'));
} }
var configPath = path.resolve(opts.config), var config = opts.config || null;
config = require(configPath); var 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 options = config.options || {};
var paths = options.paths || {}; var paths = options.paths || {};
options.paths = paths; options.paths = paths;
paths.root = path.join(process.cwd(), paths.root || ''); paths.root = path.resolve(
paths.styles = path.join(paths.root, paths.styles || ''); configPath ? path.dirname(configPath) : process.cwd(),
paths.fonts = path.join(paths.root, paths.fonts || ''); paths.root || '');
paths.sprites = path.join(paths.root, paths.sprites || ''); paths.styles = path.resolve(paths.root, paths.styles || '');
paths.mbtiles = path.join(paths.root, paths.mbtiles || ''); 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 vector = clone(config.vector); var data = clone(config.data || {});
app.use(cors());
Object.keys(config.styles || {}).forEach(function(id) { Object.keys(config.styles || {}).forEach(function(id) {
var item = config.styles[id]; var item = config.styles[id];
@@ -60,21 +87,30 @@ module.exports = function(opts, callback) {
return; return;
} }
if (item.vector !== false) { if (item.serve_data !== false) {
app.use('/', serve_style(options, serving.styles, item, id, app.use('/styles/', serve_style(options, serving.styles, item, id,
function(mbtiles) { function(mbtiles, fromData) {
var vectorItemId; var dataItemId;
Object.keys(vector).forEach(function(id) { Object.keys(data).forEach(function(id) {
if (vector[id].mbtiles == mbtiles) { if (fromData) {
vectorItemId = id; if (id == mbtiles) {
dataItemId = id;
}
} else {
if (data[id].mbtiles == mbtiles) {
dataItemId = id;
}
} }
}); });
if (vectorItemId) { // mbtiles exist in the vector config if (dataItemId) { // mbtiles exist in the data config
return vectorItemId; return dataItemId;
} else if (fromData) {
console.log('ERROR: data "' + mbtiles + '" not found!');
process.exit(1);
} else { } else {
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles; var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
while (vector[id]) id += '_'; while (data[id]) id += '_';
vector[id] = { data[id] = {
'mbtiles': mbtiles 'mbtiles': mbtiles
}; };
return id; return id;
@@ -83,37 +119,48 @@ module.exports = function(opts, callback) {
serving.fonts[font] = true; serving.fonts[font] = true;
})); }));
} }
if (item.raster !== false) { if (item.serve_rendered !== false) {
app.use('/', serve_raster(options, serving.raster, item, id)); if (serve_rendered) {
app.use('/styles/' + id + '/',
serve_rendered(options, serving.rendered, item, id,
function(mbtiles) {
var mbtilesFile;
Object.keys(data).forEach(function(id) {
if (id == mbtiles) {
mbtilesFile = data[id].mbtiles;
}
});
return mbtilesFile;
}));
} else {
item.serve_rendered = false;
}
} }
}); });
if (Object.keys(serving.styles).length > 0) {
// serve fonts only if serving some styles
app.use('/', serve_font(options, serving.fonts)); app.use('/', serve_font(options, serving.fonts));
}
//TODO: cors Object.keys(data).forEach(function(id) {
var item = data[id];
Object.keys(vector).forEach(function(id) {
var item = vector[id];
if (!item.mbtiles || item.mbtiles.length == 0) { if (!item.mbtiles || item.mbtiles.length == 0) {
console.log('Missing "mbtiles" property for ' + id); console.log('Missing "mbtiles" property for ' + id);
return; return;
} }
app.use('/', serve_vector(options, serving.vector, item, id)); app.use('/data/', serve_data(options, serving.data, item, id, serving.styles));
}); });
app.get('/styles.json', function(req, res, next) { app.get('/styles.json', function(req, res, next) {
var result = []; var result = [];
var query = req.query.key ? ('?key=' + req.query.key) : '';
Object.keys(serving.styles).forEach(function(id) { Object.keys(serving.styles).forEach(function(id) {
var styleJSON = serving.styles[id]; var styleJSON = serving.styles[id];
result.push({ result.push({
version: styleJSON.version, version: styleJSON.version,
name: styleJSON.name, name: styleJSON.name,
id: id, id: id,
url: req.protocol + '://' + req.headers.host + '/styles/' + id + '.json' url: req.protocol + '://' + req.headers.host +
'/styles/' + id + '.json' + query
}); });
}); });
res.send(result); res.send(result);
@@ -122,33 +169,177 @@ module.exports = function(opts, callback) {
var addTileJSONs = function(arr, req, type) { var addTileJSONs = function(arr, req, type) {
Object.keys(serving[type]).forEach(function(id) { Object.keys(serving[type]).forEach(function(id) {
var info = clone(serving[type][id]); var info = clone(serving[type][id]);
info.tiles = utils.getTileUrls(req, info.tiles, var path = '';
type + '/' + id, info.format); if (type == 'rendered') {
path = 'styles/' + id + '/rendered';
} else {
path = type + '/' + id;
}
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format);
arr.push(info); arr.push(info);
}); });
return arr; return arr;
}; };
app.get('/raster.json', function(req, res, next) { app.get('/rendered.json', function(req, res, next) {
res.send(addTileJSONs([], req, 'raster')); res.send(addTileJSONs([], req, 'rendered'));
}); });
app.get('/vector.json', function(req, res, next) { app.get('/data.json', function(req, res, next) {
res.send(addTileJSONs([], req, 'vector')); res.send(addTileJSONs([], req, 'data'));
}); });
app.get('/index.json', function(req, res, next) { app.get('/index.json', function(req, res, next) {
res.send(addTileJSONs(addTileJSONs([], req, 'raster'), req, 'vector')); res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
}); });
// serve viewer on the root //------------------------------------
app.use('/', express.static(path.join(__dirname, '../public'))); // serve web presentations
app.use('/', express.static(path.join(__dirname, '../public/resources')));
var server = app.listen(process.env.PORT || opts.port, function() { 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);
}
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');
}
}
data['server_version'] = packageJson.name + ' v' + packageJson.version;
data['is_light'] = isLight;
data['key_query_part'] =
req.query.key ? 'key=' + req.query.key + '&amp;' : '';
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
return res.status(200).send(compiled(data));
});
});
};
serveTemplate('/$', 'index', function(req) {
var styles = clone(config.styles || {});
Object.keys(styles).forEach(function(id) {
var 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;
if (center) {
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';
}
var query = req.query.key ? ('?key=' + req.query.key) : '';
style.wmts_link = 'http://wmts.maptiler.com/' +
base64url('http://' + req.headers.host +
'/styles/' + id + '/rendered.json' + query) + '/wmts';
var tiles = utils.getTileUrls(
req, style.serving_rendered.tiles,
'styles/' + id + '/rendered', style.serving_rendered.format);
style.xyz_link = tiles[0];
}
});
var data = clone(serving.data || {});
Object.keys(data).forEach(function(id) {
var data_ = data[id];
var center = data_.center;
if (center) {
data_.viewer_hash = '#' + center[2] + '/' +
center[1].toFixed(5) + '/' +
center[0].toFixed(5);
}
data_.is_vector = data_.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;
}
var query = req.query.key ? ('?key=' + req.query.key) : '';
data_.wmts_link = 'http://wmts.maptiler.com/' +
base64url('http://' + req.headers.host +
'/data/' + id + '.json' + query) + '/wmts';
var tiles = utils.getTileUrls(
req, data_.tiles, 'data/' + id, data_.format);
data_.xyz_link = tiles[0];
}
if (data_.filesize) {
var suffix = 'kB';
var size = parseInt(data_.filesize, 10) / 1024;
if (size > 1024) {
suffix = 'MB';
size /= 1024;
}
if (size > 1024) {
suffix = 'GB';
size /= 1024;
}
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
}
});
return {
styles: styles,
data: data
};
});
serveTemplate('/styles/:id/$', 'viewer', function(req) {
var id = req.params.id;
var style = clone((config.styles || {})[id]);
if (!style) {
return null;
}
style.id = id;
style.name = (serving.styles[id] || serving.rendered[id]).name;
style.serving_data = serving.styles[id];
style.serving_rendered = serving.rendered[id];
return style;
});
/*
app.use('/rendered/:id/$', function(req, res, next) {
return res.redirect(301, '/styles/' + req.params.id + '/');
});
*/
serveTemplate('/data/:id/$', 'data', function(req) {
var id = req.params.id;
var data = clone(serving.data[id]);
if (!data) {
return null;
}
data.id = id;
data.is_vector = data.format == 'pbf';
return data;
});
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
console.log('Listening at http://%s:%d/', console.log('Listening at http://%s:%d/',
this.address().address, this.address().port); this.address().address, this.address().port);
return callback(); return callback();
}); });
process.on('SIGINT', function() {
process.exit();
});
setTimeout(callback, 1000); setTimeout(callback, 1000);
return { return {
app: app, app: app,

View File

@@ -1,5 +1,12 @@
'use strict'; 'use strict';
var async = require('async'),
path = require('path'),
fs = require('fs');
var clone = require('clone'),
glyphCompose = require('glyph-pbf-composite');
module.exports.getTileUrls = function(req, domains, path, format) { module.exports.getTileUrls = function(req, domains, path, format) {
if (domains) { if (domains) {
@@ -12,7 +19,14 @@ module.exports.getTileUrls = function(req, domains, path, format) {
} }
var key = req.query.key; var key = req.query.key;
var query = (key && key.length > 0) ? ('?key=' + key) : ''; var queryParams = [];
if (req.query.key) {
queryParams.push('key=' + req.query.key);
}
if (req.query.style) {
queryParams.push('style=' + req.query.style);
}
var query = queryParams.length > 0 ? ('?' + queryParams.join('&')) : '';
var uris = []; var uris = [];
domains.forEach(function(domain) { domains.forEach(function(domain) {
@@ -22,3 +36,63 @@ module.exports.getTileUrls = function(req, domains, path, format) {
return uris; return uris;
}; };
module.exports.fixTileJSONCenter = function(tileJSON) {
if (tileJSON.bounds && !tileJSON.center) {
var fitWidth = 1024;
var tiles = fitWidth / 256;
tileJSON.center = [
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
Math.round(
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
Math.LN2
)
];
}
};
module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, fallbacks, callback) {
var getFontPbf = function(allowedFonts, name, range, callback, fallbacks) {
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
var filename = path.join(fontPath, name, range + '.pbf');
if (!fallbacks) {
fallbacks = clone(allowedFonts || {});
}
delete fallbacks[name];
return fs.readFile(filename, function(err, data) {
if (err) {
console.error('ERROR: Font not found:', name);
if (fallbacks && Object.keys(fallbacks).length) {
var fallbackName = Object.keys(fallbacks)[0];
console.error('ERROR: Trying to use', fallbackName, 'as a fallback');
delete fallbacks[fallbackName];
return getFontPbf(null, fallbackName, range, callback, fallbacks);
} else {
return callback(new Error('Font load error: ' + name));
}
} else {
return callback(null, data);
}
});
} else {
return callback(new Error('Font not allowed: ' + name));
}
};
var fonts = names.split(',');
var queue = [];
fonts.forEach(function(font) {
queue.push(function(callback) {
getFontPbf(allowedFonts, font, range, callback, clone(allowedFonts || fallbacks));
});
});
return async.parallel(queue, function(err, results) {
if (err) {
callback(err, new Buffer([]));
} else {
callback(err, glyphCompose.combine(results));
}
});
};

View File

@@ -18,7 +18,7 @@ var testTileJSONArray = function(url) {
}); });
}; };
var testTileJSON = function(url, basename) { var testTileJSON = function(url) {
describe(url + ' is TileJSON', function() { describe(url + ' is TileJSON', function() {
it('is json', function(done) { it('is json', function(done) {
supertest(app) supertest(app)
@@ -27,11 +27,10 @@ var testTileJSON = function(url, basename) {
.expect('Content-Type', /application\/json/, done); .expect('Content-Type', /application\/json/, done);
}); });
it('has valid basename and tiles', function(done) { it('has valid tiles', function(done) {
supertest(app) supertest(app)
.get(url) .get(url)
.expect(function(res) { .expect(function(res) {
res.body.basename.should.equal(basename);
res.body.tiles.length.should.be.greaterThan(0); res.body.tiles.length.should.be.greaterThan(0);
}).end(done); }).end(done);
}); });
@@ -40,8 +39,8 @@ var testTileJSON = function(url, basename) {
describe('Metadata', function() { describe('Metadata', function() {
testTileJSONArray('/index.json'); testTileJSONArray('/index.json');
testTileJSONArray('/raster.json'); testTileJSONArray('/rendered.json');
testTileJSONArray('/vector.json'); testTileJSONArray('/data.json');
describe('/styles.json is valid array', function() { describe('/styles.json is valid array', function() {
it('is json', function(done) { it('is json', function(done) {
@@ -64,6 +63,6 @@ describe('Metadata', function() {
}); });
}); });
testTileJSON('/raster/test.json', 'test'); testTileJSON('/styles/test-style/rendered.json');
testTileJSON('/vector/zurich-vector.json', 'zurich-vector'); testTileJSON('/data/openmaptiles.json');
}); });

View File

@@ -7,7 +7,7 @@ before(function() {
console.log('global setup'); console.log('global setup');
process.chdir('test_data'); process.chdir('test_data');
var running = require('../src/server')({ var running = require('../src/server')({
config: 'config.json', configPath: 'config.json',
port: 8888 port: 8888
}); });
global.app = running.app; global.app = running.app;

View File

@@ -1,6 +1,9 @@
var testStatic = function(prefix, q, format, status, scale, type) { var testStatic = function(prefix, q, format, status, scale, type, query) {
if (scale) q += '@' + scale + 'x'; if (scale) q += '@' + scale + 'x';
var path = '/static/' + prefix + '/' + q + '.' + format; var path = '/styles/' + prefix + '/static/' + q + '.' + format;
if (query) {
path += query;
}
it(path + ' returns ' + status, function(done) { it(path + ' returns ' + status, function(done) {
var test = supertest(app).get(path); var test = supertest(app).get(path);
if (status) test.expect(status); if (status) test.expect(status);
@@ -9,72 +12,91 @@ var testStatic = function(prefix, q, format, status, scale, type) {
}); });
}; };
var prefix = 'test-style';
describe('Static endpoints', function() { describe('Static endpoints', function() {
describe('center-based', function() { describe('center-based', function() {
describe('valid requests', function() { describe('valid requests', function() {
describe('various formats', function() { describe('various formats', function() {
testStatic('test', '0,0,0/256x256', 'png', 200, undefined, /image\/png/); testStatic(prefix, '0,0,0/256x256', 'png', 200, undefined, /image\/png/);
testStatic('test', '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/); testStatic(prefix, '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/);
testStatic('test', '0,0,0/256x256', 'jpeg', 200, undefined, /image\/jpeg/); testStatic(prefix, '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', 'webp', 200, undefined, /image\/webp/);
}); });
describe('different parameters', function() { describe('different parameters', function() {
testStatic('test', '0,0,0/300x300', 'png', 200, 2); testStatic(prefix, '0,0,0/300x300', 'png', 200, 2);
testStatic('test', '0,0,0/300x300', 'png', 200, 3); testStatic(prefix, '0,0,0/300x300', 'png', 200, 3);
testStatic('test', '80,40,20/600x300', 'png', 200, 3); testStatic(prefix, '0,0,1.5/256x256', 'png', 200);
testStatic('test', '8.5,40.5,20/300x150', 'png', 200, 3);
testStatic('test', '-8.5,-40.5,20/300x150', 'png', 200, 3);
testStatic('test', '8,40,2,0,0/300x150', 'png', 200); testStatic(prefix, '80,40,20/600x300', 'png', 200, 3);
testStatic('test', '8,40,2,180,45/300x150', 'png', 200, 2); testStatic(prefix, '8.5,40.5,20/300x150', 'png', 200, 3);
testStatic('test', '8,40,2,10/300x150', 'png', 200, 3); testStatic(prefix, '-8.5,-40.5,20/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, '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() { describe('invalid requests return 4xx', function() {
testStatic('test', '190,0,0/256x256', 'png', 400); testStatic(prefix, '190,0,0/256x256', 'png', 400);
testStatic('test', '0,86,0/256x256', 'png', 400); testStatic(prefix, '0,86,0/256x256', 'png', 400);
testStatic('test', '80,40,20/0x0', 'png', 400); testStatic(prefix, '80,40,20/0x0', 'png', 400);
testStatic('test', '0,0,0/256x256', 'gif', 400); testStatic(prefix, '0,0,0/256x256', 'gif', 400);
testStatic('test', '0,0,0/256x256', 'png', 404, 1); testStatic(prefix, '0,0,0/256x256', 'png', 404, 1);
testStatic('test', '0,0,-1/256x256', 'png', 404); testStatic(prefix, '0,0,-1/256x256', 'png', 404);
testStatic('test', '0,0,1.5/256x256', 'png', 404); testStatic(prefix, '0,0,0/256.5x256.5', 'png', 404);
testStatic('test', '0,0,0/256.5x256.5', 'png', 404);
testStatic('test', '0,0,0,/256x256', 'png', 404); testStatic(prefix, '0,0,0,/256x256', 'png', 404);
testStatic('test', '0,0,0,0,/256x256', 'png', 404); testStatic(prefix, '0,0,0,0,/256x256', 'png', 404);
}); });
}); });
describe('area-based', function() { describe('area-based', function() {
describe('valid requests', function() { describe('valid requests', function() {
describe('various formats', function() { describe('various formats', function() {
testStatic('test', '-180,-80,180,80/0', 'png', 200, undefined, /image\/png/); testStatic(prefix, '-180,-80,180,80/10x10', 'png', 200, undefined, /image\/png/);
testStatic('test', '-180,-80,180,80/0', 'jpg', 200, undefined, /image\/jpeg/); testStatic(prefix, '-180,-80,180,80/10x10', 'jpg', 200, undefined, /image\/jpeg/);
testStatic('test', '-180,-80,180,80/0', 'jpeg', 200, undefined, /image\/jpeg/); testStatic(prefix, '-180,-80,180,80/10x10', 'jpeg', 200, undefined, /image\/jpeg/);
testStatic('test', '-180,-80,180,80/0', 'webp', 200, undefined, /image\/webp/); testStatic(prefix, '-180,-80,180,80/10x10', 'webp', 200, undefined, /image\/webp/);
}); });
describe('different parameters', function() { describe('different parameters', function() {
testStatic('test', '-180,-90,180,90/0', 'png', 200, 2); testStatic(prefix, '-180,-90,180,90/20x20', 'png', 200, 2);
testStatic('test', '0,0,1,1/3', 'png', 200, 3); testStatic(prefix, '0,0,1,1/200x200', 'png', 200, 3);
testStatic('test', '-280,-80,0,80/0', 'png', 200); testStatic(prefix, '-280,-80,0,80/280x160', 'png', 200);
}); });
}); });
describe('invalid requests return 4xx', function() { describe('invalid requests return 4xx', function() {
testStatic('test', '0,87,1,88/5', 'png', 400); testStatic(prefix, '0,87,1,88/5x2', 'png', 400);
testStatic('test', '18,-9,-18,9/0', 'png', 400); testStatic(prefix, '0,0,1,1/1x1', 'gif', 400);
testStatic('test', '0,0,1,1/1', 'gif', 400);
testStatic('test', '-180,-80,180,80/0.5', 'png', 404); testStatic(prefix, '-180,-80,180,80/0.5x2.6', 'png', 404);
});
});
describe('autofit path', function() {
describe('valid requests', function() {
testStatic(prefix, 'auto/256x256', 'png', 200, undefined, /image\/png/, '?path=10,10|20,20');
describe('different parameters', function() {
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(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');
}); });
}); });
}); });

View File

@@ -8,13 +8,15 @@ var testIs = function(url, type, status) {
}); });
}; };
var prefix = 'test-style';
describe('Styles', function() { describe('Styles', function() {
describe('/styles/test.json is valid style', function() { describe('/styles/' + prefix + '.json is valid style', function() {
testIs('/styles/test.json', /application\/json/); testIs('/styles/' + prefix + '.json', /application\/json/);
it('contains expected properties', function(done) { it('contains expected properties', function(done) {
supertest(app) supertest(app)
.get('/styles/test.json') .get('/styles/' + prefix + '.json')
.expect(function(res) { .expect(function(res) {
res.body.version.should.equal(8); res.body.version.should.equal(8);
res.body.name.should.be.String(); res.body.name.should.be.String();
@@ -29,20 +31,20 @@ describe('Styles', function() {
testIs('/styles/streets.json', /./, 404); testIs('/styles/streets.json', /./, 404);
}); });
describe('/styles/test/sprite[@2x].{format}', function() { describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
testIs('/styles/test/sprite.json', /application\/json/); testIs('/styles/' + prefix + '/sprite.json', /application\/json/);
testIs('/styles/test/sprite@2x.json', /application\/json/); testIs('/styles/' + prefix + '/sprite@2x.json', /application\/json/);
testIs('/styles/test/sprite.png', /image\/png/); testIs('/styles/' + prefix + '/sprite.png', /image\/png/);
testIs('/styles/test/sprite@2x.png', /image\/png/); testIs('/styles/' + prefix + '/sprite@2x.png', /image\/png/);
}); });
}); });
describe('Fonts', function() { describe('Fonts', function() {
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/); 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', testIs('/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
/application\/x-protobuf/); /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/Nonsense/0-255.pbf', /./, 400);
testIs('/fonts/Nonsense1,Nonsense2/0-255.pbf', /./, 400); testIs('/fonts/Nonsense1,Nonsense2/0-255.pbf', /./, 400);

View File

@@ -1,5 +1,5 @@
var testTile = function(prefix, z, x, y, status) { var testTile = function(prefix, z, x, y, status) {
var path = '/vector/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf'; var path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf';
it(path + ' returns ' + status, function(done) { it(path + ' returns ' + status, function(done) {
var test = supertest(app).get(path); var test = supertest(app).get(path);
if (status) test.expect(status); if (status) test.expect(status);
@@ -8,7 +8,7 @@ var testTile = function(prefix, z, x, y, status) {
}); });
}; };
var prefix = 'zurich-vector'; var prefix = 'openmaptiles';
describe('Vector tiles', function() { describe('Vector tiles', function() {
describe('existing tiles', function() { describe('existing tiles', function() {

View File

@@ -1,44 +0,0 @@
var testTile = function(prefix, z, x, y, format, status, scale, type) {
if (scale) y += '@' + scale + 'x';
var path = '/raster/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format;
it(path + ' returns ' + status, function(done) {
var test = supertest(app).get(path);
test.expect(status);
if (type) test.expect('Content-Type', type);
test.end(done);
});
};
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/);
});
describe('different coordinates and scales', function() {
testTile('test', 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);
});
});
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('test', 0, 0, 0, 'png', 404, 1);
testTile('test', 0, 0, 0, 'png', 404, 4);
testTile('hybrid', 0, 0, 0, 'png', 404);
});
});

47
test/tiles_rendered.js Normal file
View File

@@ -0,0 +1,47 @@
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;
it(path + ' returns ' + status, function(done) {
var test = supertest(app).get(path);
test.expect(status);
if (type) test.expect('Content-Type', type);
test.end(done);
});
};
var prefix = 'test-style';
describe('Raster tiles', function() {
describe('valid requests', function() {
describe('various formats', function() {
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(prefix, 1, 1, 1, 'png', 200);
testTile(prefix, 0, 0, 0, 'png', 200, 2);
testTile(prefix, 0, 0, 0, 'png', 200, 3);
testTile(prefix, 2, 1, 1, 'png', 200, 3);
testTile(prefix, 0, 0, 0, 'png', 200, 4);
});
});
describe('invalid requests return 4xx', function() {
testTile('non_existent', 0, 0, 0, 'png', 404);
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(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
});
});