Compare commits

...

79 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
34 changed files with 1126 additions and 638 deletions
+4 -1
View File
@@ -1,4 +1,7 @@
.git .git
docs/_build
node_modules node_modules
test_data test_data
test light
config.json
*.mbtiles
+1
View File
@@ -3,5 +3,6 @@ node_modules
test_data test_data
data data
light light
plugins
config.json config.json
*.mbtiles *.mbtiles
+2 -4
View File
@@ -15,9 +15,7 @@ before_install:
- sudo apt-get install -qq xvfb - sudo apt-get install -qq xvfb
install: install:
- npm install - npm install
- wget -O test_data.zip https://github.com/klokantech/tileserver-gl-data/archive/v0.8.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 tmp_test_data - unzip -q test_data.zip -d test_data
- mkdir test_data
- mv tmp_test_data/tileserver-gl-data-*/* -t test_data
script: script:
- xvfb-run --server-args="-screen 0 1024x768x24" npm test - xvfb-run --server-args="-screen 0 1024x768x24" npm test
+2 -1
View File
@@ -9,6 +9,7 @@ RUN apt-get -qq update \
build-essential \ build-essential \
python \ python \
libcairo2-dev \ libcairo2-dev \
libprotobuf-dev \
xvfb \ xvfb \
&& echo "deb https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \ && echo "deb https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
&& echo "deb-src https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \ && echo "deb-src https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \
@@ -26,4 +27,4 @@ VOLUME /data
WORKDIR /data WORKDIR /data
EXPOSE 80 EXPOSE 80
CMD ["/usr/src/app/run.sh"] ENTRYPOINT ["/usr/src/app/run.sh"]
+12
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"]
+34 -6
View File
@@ -1,18 +1,46 @@
![tileserver-gl](https://cloud.githubusercontent.com/assets/59284/18173467/fa3aa2ca-7069-11e6-86b1-0f1266befeb6.jpeg)
# TileServer GL # 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/) [![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-gl/)
Vector and raster maps with GL styles. Server side rendering by Mapbox GL Native. Map tile server for Mapbox GL JS, Android, iOS, Leaflet, OpenLayers, GIS via WMTS, etc.
## Quickstart ## Get Started
Use `npm install -g tileserver-gl` to install the package from npm.
Then you can simply run `tileserver-gl zurich_switzerland.mbtiles` to start the server for the given mbtiles. Install `tileserver-gl` with server-side raster rendering of vector tiles with 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. ```bash
npm install -g tileserver-gl
```
Or you can use `docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl` to run the server inside a docker container. Now download vector tiles from [OSM2VectorTiles](http://osm2vectortiles.org/downloads/).
Prepared vector tiles can be downloaded from [OSM2VectorTiles](http://osm2vectortiles.org/). ```bash
curl -o zurich_switzerland.mbtiles https://osm2vectortiles-downloads.os.zhdk.cloud.switch.ch/v2.0/extracts/zurich_switzerland.mbtiles
```
Start `tileserver-gl` with the downloaded vector tiles.
```bash
tileserver-gl zurich_switzerland.mbtiles
```
Alternatively, you can use the `tileserver-gl-light` package instead, which is pure javascript (does not have any native dependencies) and can run anywhere, but does not contain rasterization on the server side made with MapBox GL Native.
## Using Docker
An alternative to npm to start the packed software easier is to install [Docker](http://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
```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 ## Documentation
You can read full documentation of this project at http://tileserver.readthedocs.io/. You can read full documentation of this project at http://tileserver.readthedocs.io/.
+17
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/.
+48 -6
View File
@@ -10,7 +10,7 @@ Example::
"options": { "options": {
"paths": { "paths": {
"root": "", "root": "",
"fonts": "glyphs", "fonts": "fonts",
"sprites": "sprites", "sprites": "sprites",
"styles": "styles", "styles": "styles",
"mbtiles": "" "mbtiles": ""
@@ -20,10 +20,13 @@ Example::
"127.0.0.1:8080" "127.0.0.1:8080"
], ],
"formatQuality": { "formatQuality": {
"png": 90,
"jpeg": 80, "jpeg": 80,
"webp": 90 "webp": 90,
} "pngQuantization": false,
"png": 90
},
"maxSize": 2048,
"serveAllFonts": false
}, },
"styles": { "styles": {
"basic": { "basic": {
@@ -69,6 +72,13 @@ You can use this to optionally specify on what domains the rendered tiles are ac
Quality of the compression of individual image formats. [0-100] Quality of the compression of individual image formats. [0-100]
The value for ``png`` is only used when ``pngQuantization`` is ``true``.
``maxSize``
-----------
Maximum image side length to be allowed to be rendered (including scale factor). Default is ``2048``.
``styles`` ``styles``
========== ==========
@@ -90,11 +100,14 @@ Each item specifies one data source which should be made accessible by the serve
The mbtiles file does not need to be specified here unless you explicitly want to serve the raw data. The mbtiles file does not need to be specified here unless you explicitly want to serve the raw data.
Referencing local mbtiles from style Referencing local files from style JSON
==================================== =======================================
You can link various data sources from the style JSON (for example even remote TileJSONs). 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``. 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. The TileServer-GL will try to find the file ``switzerland.mbtiles`` in ``root`` + ``mbtiles`` path.
@@ -110,3 +123,32 @@ For example::
Alternatively, you can use ``mbtiles://{zurich-vector}`` to reference existing data object from the config. 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. 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``. 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).
+5
View File
@@ -13,3 +13,8 @@ Securing
======== ========
Nginx can be used to add protection via https, password, referrer, IP address restriction, access keys, etc. 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.
+11 -1
View File
@@ -15,7 +15,7 @@ Rendered tiles
============== ==============
* Rendered tiles are served at ``/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}`` * Rendered tiles are served at ``/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}``
* The optional ``@2x`` (or ``@3x``) part can be used to render HiDPI (retina) tiles * The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp`` * Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
* TileJSON at ``/styles/{id}/rendered.json`` * TileJSON at ``/styles/{id}/rendered.json``
@@ -43,14 +43,24 @@ Static images
* value of ``0.1`` means "add 10% size to each side to make sure the area of interest is nicely visible" * value of ``0.1`` means "add 10% size to each side to make sure the area of interest is nicely visible"
* 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. * The static images are not available in the ``tileserver-gl-light`` version.
Source data Source data
=========== ===========
* Source data are served at ``/data/{mbtiles}/{z}/{x}/{y}.{format}`` * 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 at ``/data/{mbtiles}.json``
TileJSON arrays TileJSON arrays
=============== ===============
Array of all TileJSONs is at ``/index.json`` (``/rendered.json``; ``/data.json``) 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``
+30 -1
View File
@@ -9,11 +9,29 @@ When running docker image, no special installation is needed -- the docker will
Just run ``docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl``. 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 npm
=== ===
Just run ``npm install -g tileserver-gl``. 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 ``tileserver-gl-light`` on npm
============================== ==============================
@@ -23,7 +41,18 @@ Alternatively, you can use ``tileserver-gl-light`` package instead, which is pur
From source From source
=========== ===========
Make sure you have Node v4 or higher (nvm install 4) and run:: Make sure you have Node v4 (nvm install 4) and run::
npm install npm install
node . 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
+24 -16
View File
@@ -1,7 +1,7 @@
{ {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "1.0.0", "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", "bin": "src/main.js",
"authors": [ "authors": [
@@ -12,33 +12,41 @@
"url": "https://github.com/klokantech/tileserver-gl.git" "url": "https://github.com/klokantech/tileserver-gl.git"
}, },
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": {
"node": ">=4.2.1 <5"
},
"scripts": { "scripts": {
"test": "mocha test/**.js" "test": "mocha test/**.js"
}, },
"dependencies": { "dependencies": {
"async": "2.0.1", "async": "2.1.4",
"advanced-pool": "0.3.2", "advanced-pool": "0.3.2",
"base64url": "2.0.0", "base64url": "2.0.0",
"canvas": "1.4.0", "canvas": "1.6.2",
"clone": "1.0.2", "clone": "2.1.0",
"color": "0.11.3", "color": "1.0.3",
"cors": "2.7.1", "cors": "2.8.1",
"express": "4.14.0", "express": "4.14.0",
"glyph-pbf-composite": "0.0.2", "glyph-pbf-composite": "0.0.2",
"handlebars": "4.0.5", "handlebars": "4.0.6",
"mapbox-gl-native": "3.3.2", "mapbox-gl-native": "3.4.2",
"mbtiles": "0.9.0", "mbtiles": "0.9.0",
"morgan": "1.7.0", "morgan": "1.7.0",
"node-pngquant-native": "1.0.4", "node-pngquant-native": "1.0.4",
"nomnom": "1.8.1", "nomnom": "1.8.1",
"request": "2.74.0", "pbf": "3.0.5",
"sharp": "0.16.0", "request": "2.79.0",
"sphericalmercator": "1.0.5", "sharp": "0.16.2",
"tileserver-gl-styles": "0.3.0" "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": "^10.0.0", "should": "^11.1.1",
"mocha": "^2.5.0", "mocha": "^3.2.0",
"supertest": "^1.2.0" "supertest": "^2.0.1"
} }
} }
+13
View File
@@ -36,6 +36,16 @@ a:hover{
font-size: 32px; font-size: 32px;
text-align:center; text-align:center;
margin:90px 0 0 0; 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{ section{
margin: 15px auto; margin: 15px auto;
@@ -169,6 +179,9 @@ body {
.title{ .title{
margin: 25px 0 0 0; margin: 25px 0 0 0;
} }
.title.light:after {
font-size:.6em;
}
.title img{ .title img{
width: 200px; width: 200px;
} }
+40
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
+50 -9
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,37 +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-geolocate { .mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
padding: 5px; 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");
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%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M13%2C7%20L10.5%2C11.75%20L10.25%2C10%20z%20M13.888%2C6.112%20C13.615%2C5.84%2013.382%2C6.076%2012.5%2C6.5%20C10.14%2C7.634%206%2C10%206%2C10%20L9.5%2C10.5%20L10%2C14%20C10%2C14%2012.366%2C9.86%2013.5%2C7.5%20C13.924%2C6.617%2014.16%2C6.385%2013.888%2C6.112%27%2F%3E%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 > div.arrow { .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;
@@ -109,6 +140,16 @@
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; top: 0;
File diff suppressed because one or more lines are too long
+31 -76
View File
@@ -6,14 +6,15 @@
<title>{{name}} - TileServer GL</title> <title>{{name}} - TileServer GL</title>
{{#is_vector}} {{#is_vector}}
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css{{&key_query}}" /> <link rel="stylesheet" type="text/css" href="/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.js{{&key_query}}"></script>
<script src="/mapbox-gl-inspect.min.js{{&key_query}}"></script>
<style> <style>
body {background:#000;color:#ccc;} body {background:#fff;color:#333;font-family:Arial, sans-serif;}
#map {position:absolute;top:0;left:0;right:250px;bottom:0;} #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;} h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
#layerList {position:absolute;top:35px;right:0;bottom:60%;width:240px;overflow:auto;} #layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;}
#layerList div div {width:15px;height:15px;display:inline-block;} #layerList div div {width:15px;height:15px;display:inline-block;}
#propertyList {position:absolute;top:40%;bottom:0;right:0;width:240px;overflow:auto;color:#fff;}
</style> </style>
{{/is_vector}} {{/is_vector}}
{{^is_vector}} {{^is_vector}}
@@ -35,87 +36,37 @@
<script> <script>
var map = new mapboxgl.Map({ var map = new mapboxgl.Map({
container: 'map', container: 'map',
hash: true hash: true,
}); style: {
map.addControl(new mapboxgl.Navigation());
function generateColor(str) {
var rgb = [0, 0, 0];
for (var i = 0; i < str.length; i++) {
var v = str.charCodeAt(i);
rgb[v % 3] = (rgb[i % 3] + (13*(v%13))) % 12;
}
var r = 4 + rgb[0];
var g = 4 + rgb[1];
var b = 4 + rgb[2];
r = (r * 16) + r;
g = (g * 16) + g;
b = (b * 16) + b;
return [r, g, b, 1];
};
function initLayer(data) {
var layer;
var layerList = document.getElementById('layerList');
var layers_ = [];
data['vector_layers'].forEach(function(el) {
var color = generateColor(el['id']);
var colorText = 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + color[3] + ')';
layers_.push({
id: el['id'] + Math.random(),
source: 'vector_layer_',
'source-layer': el['id'],
interactive: true,
type: 'line',
paint: {'line-color': colorText}
});
var item = document.createElement('div');
item.innerHTML = '<div style="' +
'background:rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',1);' +
'"></div> ' + el['id'];
layerList.appendChild(item);
});
map.setStyle({
version: 8, version: 8,
sources: { sources: {
'vector_layer_': { 'vector_layer_': {
type: 'vector', type: 'vector',
tiles: data['tiles'], url: '/data/{{id}}.json{{&key_query}}'
minzoom: data['minzoom'],
maxzoom: data['maxzoom']
} }
}, },
layers: layers_ layers: []
}
}); });
map.addControl(new mapboxgl.NavigationControl());
return layer; var inspect = new MapboxInspect({
} showInspectMap: true,
showInspectButton: false
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
initLayer(xhttp.response);
}
};
xhttp.responseType = 'json';
xhttp.open('GET', '/data/{{id}}.json{{&key_query}}', true);
xhttp.send();
var propertyList = document.getElementById('propertyList');
map.on('mousemove', function(e) {
propertyList.innerHTML = '';
var width = 3, height = 3;
var features = map.queryRenderedFeatures([
[e.point.x - width / 2, e.point.y - height / 2],
[e.point.x + width / 2, e.point.y + height / 2]
]);
if (features) {
var html = '';
features.forEach(function(feature) {
html += JSON.stringify(feature.properties, null, 2) + '\n';
}); });
propertyList.innerHTML = html; 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> </script>
{{/is_vector}} {{/is_vector}}
@@ -124,6 +75,10 @@
<div id='map'></div> <div id='map'></div>
<script> <script>
var map = L.mapbox.map('map', '/data/{{id}}.json{{&key_query}}', { zoomControl: false }); var map = L.mapbox.map('map', '/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); new L.Control.Zoom({ position: 'topright' }).addTo(map);
setTimeout(function() { setTimeout(function() {
new L.Hash(map); new L.Hash(map);
+9 -5
View File
@@ -17,8 +17,8 @@
</head> </head>
<body> <body>
<section> <section>
<h1 class="title"><img src="/images/logo.png" alt="TileServer GL" /></h1> <h1 class="title {{#if is_light}}light{{/if}}"><img src="/images/logo.png" alt="TileServer GL" /></h1>
<h2 class="subtitle">Vector and raster maps with GL styles</h2> <h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
<h2 class="box-header">Styles</h2> <h2 class="box-header">Styles</h2>
<div class="box"> <div class="box">
{{#each styles}} {{#each styles}}
@@ -32,8 +32,12 @@
<h3>{{name}}</h3> <h3>{{name}}</h3>
<p class="identifier">identifier: {{@key}}</p> <p class="identifier">identifier: {{@key}}</p>
<p class="services"> <p class="services">
services:
{{#if serving_data}}
<a href="/styles/{{@key}}.json{{&../key_query}}">GL Style</a>
{{/if}}
{{#if serving_rendered}} {{#if serving_rendered}}
services: <a href="/styles/{{@key}}/rendered.json{{&../key_query}}">TileJSON</a> {{#if serving_data}}| {{/if}}<a href="/styles/{{@key}}/rendered.json{{&../key_query}}">TileJSON</a>
{{/if}} {{/if}}
{{#if wmts_link}} {{#if wmts_link}}
| <a href="{{&wmts_link}}">WMTS</a> | <a href="{{&wmts_link}}">WMTS</a>
@@ -85,10 +89,10 @@
</div> </div>
<div class="viewers"> <div class="viewers">
{{#is_vector}} {{#is_vector}}
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">X-Ray view</a> <a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Inspect</a>
{{/is_vector}} {{/is_vector}}
{{^is_vector}} {{^is_vector}}
<a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Raster view</a> <a class="btn" href="/data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
{{/is_vector}} {{/is_vector}}
</div> </div>
</div> </div>
+1 -1
View File
@@ -29,7 +29,7 @@
style: '/styles/{{id}}.json{{&key_query}}', style: '/styles/{{id}}.json{{&key_query}}',
hash: true hash: true
}); });
map.addControl(new mapboxgl.Navigation()); map.addControl(new mapboxgl.NavigationControl());
} else { } else {
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&key_query}}', { zoomControl: false }); var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&key_query}}', { zoomControl: false });
new L.Control.Zoom({ position: 'topright' }).addTo(map); new L.Control.Zoom({ position: 'topright' }).addTo(map);
+4
View File
@@ -20,15 +20,19 @@ var fs = require('fs');
var packageJson = require('./package'); var packageJson = require('./package');
packageJson.name += '-light'; packageJson.name += '-light';
packageJson.description = 'Map tile server for JSON GL styles - serving vector tiles';
delete packageJson.dependencies['canvas']; delete packageJson.dependencies['canvas'];
delete packageJson.dependencies['mapbox-gl-native']; delete packageJson.dependencies['mapbox-gl-native'];
delete packageJson.dependencies['node-pngquant-native']; delete packageJson.dependencies['node-pngquant-native'];
delete packageJson.dependencies['sharp']; delete packageJson.dependencies['sharp'];
delete packageJson.optionalDependencies;
delete packageJson.devDependencies; delete packageJson.devDependencies;
var str = JSON.stringify(packageJson, undefined, 2); var str = JSON.stringify(packageJson, undefined, 2);
fs.writeFileSync('light/package.json', str); fs.writeFileSync('light/package.json', str);
fs.renameSync('light/README_light.md', 'light/README.md');
fs.renameSync('light/Dockerfile_light', 'light/Dockerfile');
/* PUBLISH */ /* PUBLISH */
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
cd /data cd /data
xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/ -p 80 xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/ -p 80 "$@"
+52 -18
View File
@@ -70,10 +70,6 @@ var startWithMBTiles = function(mbtilesFile) {
} }
var instance = new mbtiles(mbtilesFile, function(err) { var instance = new mbtiles(mbtilesFile, function(err) {
instance.getInfo(function(err, info) { instance.getInfo(function(err, info) {
if (info.format != 'pbf') {
console.log('ERROR: MBTiles format is not "pbf".');
process.exit(1);
}
var bounds = info.bounds; var bounds = info.bounds;
var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/"); var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
@@ -82,34 +78,72 @@ var startWithMBTiles = function(mbtilesFile) {
"options": { "options": {
"paths": { "paths": {
"root": styleDir, "root": styleDir,
"fonts": "glyphs", "fonts": "fonts",
"sprites": "sprites",
"styles": "styles", "styles": "styles",
"mbtiles": path.dirname(mbtilesFile) "mbtiles": path.dirname(mbtilesFile)
} }
}, },
"styles": {}, "styles": {},
"data": { "data": {}
"osm2vectortiles": {
"mbtiles": path.basename(mbtilesFile)
}
}
}; };
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')); var styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
for (var i=0; i < styles.length; i++) { for (var i = 0; i < styles.length; i++) {
var styleFilename = styles[i]; var styleName = styles[i];
if (styleFilename.endsWith('.json')) { 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 = { var styleObject = {
"style": path.basename(styleFilename), "style": styleFileRel,
"tilejson": { "tilejson": {
"bounds": bounds "bounds": bounds
} }
}; };
config['styles'][path.basename(styleFilename, '.json')] = config['styles'][styleName] = styleObject;
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) { if (opts.verbose) {
console.log(JSON.stringify(config, undefined, 2)); console.log(JSON.stringify(config, undefined, 2));
@@ -142,7 +176,7 @@ fs.stat(path.resolve(opts.config), function(err, stats) {
console.log('No MBTiles specified, using ' + mbtiles); console.log('No MBTiles specified, using ' + mbtiles);
return startWithMBTiles(mbtiles); return startWithMBTiles(mbtiles);
} else { } else {
var url = 'https://github.com/klokantech/tileserver-gl-styles/releases/download/v0.3.0/zurich_switzerland.mbtiles'; var url = 'https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
var filename = 'zurich_switzerland.mbtiles'; var filename = 'zurich_switzerland.mbtiles';
var stream = fs.createWriteStream(filename); var stream = fs.createWriteStream(filename);
console.log('Downloading sample data (' + filename + ') from ' + url); console.log('Downloading sample data (' + filename + ') from ' + url);
+82 -13
View File
@@ -1,15 +1,23 @@
'use strict'; 'use strict';
var fs = require('fs'), var fs = require('fs'),
path = require('path'); path = require('path'),
zlib = require('zlib');
var clone = require('clone'), var clone = require('clone'),
express = require('express'), express = require('express'),
mbtiles = require('mbtiles'); mbtiles = require('mbtiles'),
pbf = require('pbf'),
VectorTile = require('vector-tile').VectorTile;
var tileshrinkGl;
try {
tileshrinkGl = require('tileshrink-gl');
} catch (e) {}
var utils = require('./utils'); var utils = require('./utils');
module.exports = function(options, repo, params, id) { module.exports = function(options, repo, params, id, styles) {
var app = express().disable('x-powered-by'); var app = express().disable('x-powered-by');
var mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles); var mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
@@ -17,6 +25,8 @@ module.exports = function(options, repo, params, id) {
'tiles': params.domains || options.domains 'tiles': params.domains || options.domains
}; };
var shrinkers = {};
repo[id] = tileJSON; repo[id] = tileJSON;
var mbtilesFileStats = fs.statSync(mbtilesFile); var mbtilesFileStats = fs.statSync(mbtilesFile);
@@ -31,8 +41,8 @@ module.exports = function(options, repo, params, id) {
Object.assign(tileJSON, info); Object.assign(tileJSON, info);
tileJSON['tilejson'] = '2.0.0'; tileJSON['tilejson'] = '2.0.0';
tileJSON['basename'] = id; delete tileJSON['filesize'];
tileJSON['filesize'] = mbtilesFileStats['size']; delete tileJSON['mtime'];
delete tileJSON['scheme']; delete tileJSON['scheme'];
Object.assign(tileJSON, params.tilejson || {}); Object.assign(tileJSON, params.tilejson || {});
@@ -46,7 +56,8 @@ module.exports = function(options, repo, params, id) {
var z = req.params.z | 0, var z = req.params.z | 0,
x = req.params.x | 0, x = req.params.x | 0,
y = req.params.y | 0; y = req.params.y | 0;
if (req.params.format != tileJSON.format) { if (req.params.format != tileJSON.format &&
!(req.params.format == 'geojson' && tileJSON.format == 'pbf')) {
return res.status(404).send('Invalid format'); return res.status(404).send('Invalid format');
} }
if (z < tileJSON.minzoom || 0 || x < 0 || y < 0 || if (z < tileJSON.minzoom || 0 || x < 0 || y < 0 ||
@@ -62,16 +73,74 @@ module.exports = function(options, repo, params, id) {
return res.status(500).send(err.message); return res.status(500).send(err.message);
} }
} else { } else {
if (tileJSON['format'] == 'pbf') {
headers['Content-Type'] = 'application/x-protobuf';
headers['Content-Encoding'] = 'gzip';
}
delete headers['ETag']; // do not trust the tile ETag -- regenerate
res.set(headers);
if (data == null) { if (data == null) {
return res.status(404).send('Not found'); return res.status(404).send('Not found');
} else { } 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); return res.status(200).send(data);
} }
} }
+29 -3
View File
@@ -1,7 +1,9 @@
'use strict'; 'use strict';
var clone = require('clone'), var clone = require('clone'),
express = require('express'); express = require('express'),
fs = require('fs'),
path = require('path');
var utils = require('./utils'); var utils = require('./utils');
@@ -12,14 +14,31 @@ module.exports = function(options, allowedFonts) {
var fontPath = options.paths.fonts; var fontPath = options.paths.fonts;
app.get('/:fontstack/:range([\\d]+-[\\d]+).pbf', var existingFonts = {};
fs.readdir(options.paths.fonts, function(err, files) {
files.forEach(function(file) {
fs.stat(path.join(fontPath, file), function(err, stats) {
if (!err) {
if (stats.isDirectory() &&
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
existingFonts[path.basename(file)] = true;
}
}
});
});
});
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;
return utils.getFontsPbf(allowedFonts, fontPath, fontstack, range, return utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
fontPath, fontstack, range, existingFonts,
function(err, concated) { function(err, concated) {
if (err || concated.length === 0) { if (err || concated.length === 0) {
console.log(err);
console.log(concated.length);
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');
@@ -29,5 +48,12 @@ module.exports = function(options, allowedFonts) {
}); });
}); });
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;
}; };
+90 -27
View File
@@ -15,7 +15,7 @@ var Canvas = require('canvas'),
clone = require('clone'), clone = require('clone'),
Color = require('color'), Color = require('color'),
express = require('express'), express = require('express'),
mercator = new (require('sphericalmercator'))(), mercator = new (require('@mapbox/sphericalmercator'))(),
mbgl = require('mapbox-gl-native'), mbgl = require('mapbox-gl-native'),
mbtiles = require('mbtiles'), mbtiles = require('mbtiles'),
pngquant = require('node-pngquant-native'), pngquant = require('node-pngquant-native'),
@@ -24,7 +24,7 @@ var Canvas = require('canvas'),
var utils = require('./utils'); var utils = require('./utils');
var FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)'; var FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
var SCALE_PATTERN = '@[23]x'; var SCALE_PATTERN = '@[234]x';
var getScale = function(scale) { var getScale = function(scale) {
return (scale || '@1x').slice(1, 2) | 0; return (scale || '@1x').slice(1, 2) | 0;
@@ -49,6 +49,19 @@ module.exports = function(options, repo, params, id, dataResolver) {
sources: {} 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 styleJSON;
var createPool = function(ratio, min, max) { var createPool = function(ratio, min, max) {
var createRenderer = function(ratio, createCallback) { var createRenderer = function(ratio, createCallback) {
@@ -67,7 +80,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
var parts = req.url.split('/'); var parts = req.url.split('/');
var fontstack = unescape(parts[2]); var fontstack = unescape(parts[2]);
var range = parts[3].split('.')[0]; var range = parts[3].split('.')[0];
utils.getFontsPbf(null, options.paths[protocol], fontstack, range, utils.getFontsPbf(null, options.paths[protocol], fontstack, range, existingFonts,
function(err, concated) { function(err, concated) {
callback(err, {data: concated}); callback(err, {data: concated});
}); });
@@ -144,15 +157,24 @@ module.exports = function(options, repo, params, id, dataResolver) {
}); });
}; };
styleJSON = clone(require(path.join(options.paths.styles, styleFile))); var styleJSONPath = path.join(options.paths.styles, styleFile);
styleJSON.sprite = 'sprites://' + path.basename(styleFile, '.json'); styleJSON = clone(require(styleJSONPath));
styleJSON.glyphs = 'fonts://{fontstack}/{range}.pbf';
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 = { var tileJSON = {
'tilejson': '2.0.0', 'tilejson': '2.0.0',
'name': styleJSON.name, 'name': styleJSON.name,
'attribution': '', 'attribution': '',
'basename': id,
'minzoom': 0, 'minzoom': 0,
'maxzoom': 20, 'maxzoom': 20,
'bounds': [-180, -85.0511, 180, 85.0511], 'bounds': [-180, -85.0511, 180, 85.0511],
@@ -169,7 +191,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
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) {
// found mbtiles source, replace with info from local file // found mbtiles source, replace with info from local file
delete source.url; delete source.url;
@@ -194,27 +216,32 @@ module.exports = function(options, repo, params, id, dataResolver) {
} }
map.sources[name] = new mbtiles(mbtilesFile, function(err) { map.sources[name] = new mbtiles(mbtilesFile, function(err) {
map.sources[name].getInfo(function(err, info) { map.sources[name].getInfo(function(err, info) {
if (err) {
console.error(err);
}
var type = source.type; var type = source.type;
Object.assign(source, info); Object.assign(source, info);
source.type = type; source.type = type;
source.basename = name;
source.tiles = [ source.tiles = [
// meta url which will be detected when requested // meta url which will be detected when requested
'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf') 'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf')
]; ];
delete source.scheme;
if (source.format == 'pbf') { if (source.format == 'pbf') {
map.sources[name].emptyTile = new Buffer(0); map.sources[name].emptyTile = new Buffer(0);
} else { } else {
var color = new Color(source.color || '#fff'); var color = new Color(source.color || 'rgba(255,255,255,0)');
var format = source.format; var format = source.format;
if (format == 'jpg') { if (format == 'jpg') {
format = 'jpeg'; format = 'jpeg';
} }
sharp(new Buffer(color.rgbArray()), { var array = color.array();
var channels = array.length == 4 && format != 'jpeg' ? 4 : 3;
sharp(new Buffer(array), {
raw: { raw: {
width: 1, width: 1,
height: 1, height: 1,
channels: 3 channels: channels
} }
}).toFormat(format).toBuffer(function(err, buffer, info) { }).toFormat(format).toBuffer(function(err, buffer, info) {
map.sources[name].emptyTile = buffer; map.sources[name].emptyTile = buffer;
@@ -239,6 +266,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
map.renderers[1] = createPool(1, 4, 16); map.renderers[1] = createPool(1, 4, 16);
map.renderers[2] = createPool(2, 2, 8); map.renderers[2] = createPool(2, 2, 8);
map.renderers[3] = createPool(3, 2, 4); map.renderers[3] = createPool(3, 2, 4);
map.renderers[4] = createPool(4, 2, 4);
}); });
repo[id] = tileJSON; repo[id] = tileJSON;
@@ -253,7 +281,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
return res.status(400).send('Invalid center'); return res.status(400).send('Invalid center');
} }
if (Math.min(width, height) <= 0 || if (Math.min(width, height) <= 0 ||
Math.max(width, height) * scale > 2048) { Math.max(width, height) * scale > (options.maxSize || 2048)) {
return res.status(400).send('Invalid size'); return res.status(400).send('Invalid size');
} }
if (format == 'png' || format == 'webp') { if (format == 'png' || format == 'webp') {
@@ -316,10 +344,14 @@ module.exports = function(options, repo, params, id, dataResolver) {
} }
if (format == 'png') { if (format == 'png') {
var usePngQuant =
(options.formatQuality || {}).pngQuantization === true;
if (usePngQuant) {
buffer = pngquant.compress(buffer, { buffer = pngquant.compress(buffer, {
quality: [0, formatQuality || 90] quality: [0, formatQuality || 90]
}); });
} }
}
res.set({ res.set({
'Last-Modified': lastModified, 'Last-Modified': lastModified,
@@ -426,30 +458,33 @@ module.exports = function(options, repo, params, id, dataResolver) {
var minCorner = mercator.px([bbox[0], bbox[3]], z), var minCorner = mercator.px([bbox[0], bbox[3]], z),
maxCorner = mercator.px([bbox[2], bbox[1]], z); maxCorner = mercator.px([bbox[2], bbox[1]], z);
while ((((maxCorner[0] - minCorner[0]) * (1 + 2 * padding) > w) || var w_ = w / (1 + 2 * padding);
((maxCorner[1] - minCorner[1]) * (1 + 2 * padding) > h)) && z > 0) { var h_ = h / (1 + 2 * padding);
z--;
minCorner[0] /= 2; z -= Math.max(
minCorner[1] /= 2; Math.log((maxCorner[0] - minCorner[0]) / w_),
maxCorner[0] /= 2; Math.log((maxCorner[1] - minCorner[1]) / h_)
maxCorner[1] /= 2; ) / Math.LN2;
}
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
return z; return z;
}; };
var staticPattern = var staticPattern =
'/static/%s/:width(\\d+)x:height(\\d+)' + '/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' +
':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)'; ':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)';
var centerPattern = var centerPattern =
util.format(':lon(%s),:lat(%s),:z(\\d+)(@:bearing(%s)(,:pitch(%s))?)?', util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN); FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, centerPattern), function(req, res, next) { app.get(util.format(staticPattern, centerPattern), function(req, res, next) {
var z = req.params.z | 0, var raw = req.params.raw;
x = +req.params.lon, var z = +req.params.z,
y = +req.params.lat, x = +req.params.x,
y = +req.params.y,
bearing = +(req.params.bearing || '0'), bearing = +(req.params.bearing || '0'),
pitch = +(req.params.pitch || '0'), pitch = +(req.params.pitch || '0'),
w = req.params.width | 0, w = req.params.width | 0,
@@ -457,6 +492,16 @@ module.exports = function(options, repo, params, id, dataResolver) {
scale = getScale(req.params.scale), scale = getScale(req.params.scale),
format = req.params.format; 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 path = extractPathFromQuery(req.query);
var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query); path, req.query);
@@ -470,8 +515,19 @@ module.exports = function(options, repo, params, id, dataResolver) {
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN); FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, boundsPattern), function(req, res, next) { app.get(util.format(staticPattern, boundsPattern), function(req, res, next) {
var raw = req.params.raw;
var bbox = [+req.params.minx, +req.params.miny, var bbox = [+req.params.minx, +req.params.miny,
+req.params.maxx, +req.params.maxy]; +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, var w = req.params.width | 0,
h = req.params.height | 0, h = req.params.height | 0,
scale = getScale(req.params.scale), scale = getScale(req.params.scale),
@@ -498,6 +554,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
return res.status(400).send('Invalid path'); return res.status(400).send('Invalid path');
} }
var raw = req.params.raw;
var w = req.params.width | 0, var w = req.params.width | 0,
h = req.params.height | 0, h = req.params.height | 0,
bearing = 0, bearing = 0,
@@ -517,6 +574,12 @@ module.exports = function(options, repo, params, id, dataResolver) {
x = (bbox[0] + bbox[2]) / 2, x = (bbox[0] + bbox[2]) / 2,
y = (bbox[1] + bbox[3]) / 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, var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query); path, req.query);
+39 -18
View File
@@ -16,7 +16,7 @@ module.exports = function(options, repo, params, id, reportTiles, 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 mbtilesFile = url.substring('mbtiles://'.length); var mbtilesFile = url.substring('mbtiles://'.length);
var fromData = mbtilesFile[0] == '{' && var fromData = mbtilesFile[0] == '{' &&
mbtilesFile[mbtilesFile.length - 1] == '}'; mbtilesFile[mbtilesFile.length - 1] == '}';
@@ -29,33 +29,47 @@ module.exports = function(options, repo, params, id, reportTiles, reportFont) {
} }
}); });
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('/' + id + '.json', function(req, res, next) { app.get('/' + id + '.json', function(req, res, next) {
var fixUrl = function(url, opt_nokey) { var fixUrl = function(url, opt_nokey, opt_nostyle) {
var query = ''; var queryParams = [];
if (!opt_nostyle) {
queryParams.push('style=' + id);
}
if (!opt_nokey && req.query.key) { if (!opt_nokey && req.query.key) {
query = '?key=' + 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 + '/') + query; 'local://', req.protocol + '://' + req.headers.host + '/') + query;
@@ -67,13 +81,20 @@ module.exports = function(options, repo, params, id, reportTiles, reportFont) {
source.url = fixUrl(source.url); source.url = fixUrl(source.url);
}); });
// mapbox-gl-js viewer cannot handle sprite urls with query // mapbox-gl-js viewer cannot handle sprite urls with query
styleJSON_.sprite = fixUrl(styleJSON_.sprite, true); if (styleJSON_.sprite) {
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs); 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('/' + 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;
+19 -14
View File
@@ -12,7 +12,7 @@ var base64url = require('base64url'),
cors = require('cors'), cors = require('cors'),
express = require('express'), express = require('express'),
handlebars = require('handlebars'), handlebars = require('handlebars'),
mercator = new (require('sphericalmercator'))(), mercator = new (require('@mapbox/sphericalmercator'))(),
morgan = require('morgan'); morgan = require('morgan');
var packageJson = require('../package'), var packageJson = require('../package'),
@@ -22,7 +22,8 @@ var packageJson = require('../package'),
serve_data = require('./serve_data'), serve_data = require('./serve_data'),
utils = require('./utils'); utils = require('./utils');
if (packageJson.name.slice(-6) !== '-light') { var isLight = packageJson.name.slice(-6) == '-light';
if (!isLight) {
// do not require `serve_rendered` in the light package // do not require `serve_rendered` in the light package
serve_rendered = require('./serve_rendered'); serve_rendered = require('./serve_rendered');
} }
@@ -35,10 +36,7 @@ module.exports = function(opts, callback) {
styles: {}, styles: {},
rendered: {}, rendered: {},
data: {}, 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');
@@ -140,10 +138,7 @@ module.exports = function(opts, callback) {
} }
}); });
if (Object.keys(serving.styles).length > 0) { app.use('/', serve_font(options, serving.fonts));
// serve fonts only if serving some styles
app.use('/fonts/', serve_font(options, serving.fonts));
}
Object.keys(data).forEach(function(id) { Object.keys(data).forEach(function(id) {
var item = data[id]; var item = data[id];
@@ -152,7 +147,7 @@ module.exports = function(opts, callback) {
return; return;
} }
app.use('/data/', serve_data(options, serving.data, 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) {
@@ -174,8 +169,13 @@ 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;
@@ -212,6 +212,7 @@ module.exports = function(opts, callback) {
} }
} }
data['server_version'] = packageJson.name + ' v' + packageJson.version; data['server_version'] = packageJson.name + ' v' + packageJson.version;
data['is_light'] = isLight;
data['key_query_part'] = data['key_query_part'] =
req.query.key ? 'key=' + req.query.key + '&amp;' : ''; req.query.key ? 'key=' + req.query.key + '&amp;' : '';
data['key_query'] = req.query.key ? '?key=' + req.query.key : ''; data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
@@ -247,7 +248,7 @@ module.exports = function(opts, callback) {
var tiles = utils.getTileUrls( var tiles = utils.getTileUrls(
req, style.serving_rendered.tiles, req, style.serving_rendered.tiles,
'styles/' + id, style.serving_rendered.format); 'styles/' + id + '/rendered', style.serving_rendered.format);
style.xyz_link = tiles[0]; style.xyz_link = tiles[0];
} }
}); });
@@ -335,6 +336,10 @@ module.exports = function(opts, callback) {
return callback(); return callback();
}); });
process.on('SIGINT', function() {
process.exit();
});
setTimeout(callback, 1000); setTimeout(callback, 1000);
return { return {
app: app, app: app,
+26 -6
View File
@@ -4,7 +4,8 @@ var async = require('async'),
path = require('path'), path = require('path'),
fs = require('fs'); fs = require('fs');
var glyphCompose = require('glyph-pbf-composite'); 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) {
@@ -18,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) {
@@ -44,13 +52,25 @@ module.exports.fixTileJSONCenter = function(tileJSON) {
} }
}; };
module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, callback) { module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, fallbacks, callback) {
var getFontPbf = function(name, range, callback) { var getFontPbf = function(allowedFonts, name, range, callback, fallbacks) {
if (!allowedFonts || allowedFonts[name]) { if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
var filename = path.join(fontPath, name, range + '.pbf'); var filename = path.join(fontPath, name, range + '.pbf');
if (!fallbacks) {
fallbacks = clone(allowedFonts || {});
}
delete fallbacks[name];
return fs.readFile(filename, function(err, data) { return fs.readFile(filename, function(err, data) {
if (err) { 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)); return callback(new Error('Font load error: ' + name));
}
} else { } else {
return callback(null, data); return callback(null, data);
} }
@@ -64,7 +84,7 @@ module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, call
var queue = []; var queue = [];
fonts.forEach(function(font) { fonts.forEach(function(font) {
queue.push(function(callback) { queue.push(function(callback) {
getFontPbf(font, range, callback); getFontPbf(allowedFonts, font, range, callback, clone(allowedFonts || fallbacks));
}); });
}); });
+4 -5
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);
}); });
@@ -64,6 +63,6 @@ describe('Metadata', function() {
}); });
}); });
testTileJSON('/styles/bright/rendered.json', 'bright'); testTileJSON('/styles/test-style/rendered.json');
testTileJSON('/data/zurich-vector.json', 'zurich-vector'); testTileJSON('/data/openmaptiles.json');
}); });
+3 -2
View File
@@ -12,7 +12,7 @@ var testStatic = function(prefix, q, format, status, scale, type, query) {
}); });
}; };
var prefix = 'bright'; var prefix = 'test-style';
describe('Static endpoints', function() { describe('Static endpoints', function() {
describe('center-based', function() { describe('center-based', function() {
@@ -28,6 +28,8 @@ describe('Static endpoints', function() {
testStatic(prefix, '0,0,0/300x300', 'png', 200, 2); testStatic(prefix, '0,0,0/300x300', 'png', 200, 2);
testStatic(prefix, '0,0,0/300x300', 'png', 200, 3); testStatic(prefix, '0,0,0/300x300', 'png', 200, 3);
testStatic(prefix, '0,0,1.5/256x256', 'png', 200);
testStatic(prefix, '80,40,20/600x300', 'png', 200, 3); testStatic(prefix, '80,40,20/600x300', 'png', 200, 3);
testStatic(prefix, '8.5,40.5,20/300x150', 'png', 200, 3); testStatic(prefix, '8.5,40.5,20/300x150', 'png', 200, 3);
testStatic(prefix, '-8.5,-40.5,20/300x150', 'png', 200, 3); testStatic(prefix, '-8.5,-40.5,20/300x150', 'png', 200, 3);
@@ -48,7 +50,6 @@ describe('Static endpoints', function() {
testStatic(prefix, '0,0,0/256x256', 'png', 404, 1); testStatic(prefix, '0,0,0/256x256', 'png', 404, 1);
testStatic(prefix, '0,0,-1/256x256', 'png', 404); testStatic(prefix, '0,0,-1/256x256', 'png', 404);
testStatic(prefix, '0,0,1.5/256x256', 'png', 404);
testStatic(prefix, '0,0,0/256.5x256.5', 'png', 404); testStatic(prefix, '0,0,0/256.5x256.5', 'png', 404);
testStatic(prefix, '0,0,0,/256x256', 'png', 404); testStatic(prefix, '0,0,0,/256x256', 'png', 404);
+1 -1
View File
@@ -8,7 +8,7 @@ var testIs = function(url, type, status) {
}); });
}; };
var prefix = 'bright'; var prefix = 'test-style';
describe('Styles', function() { describe('Styles', function() {
describe('/styles/' + prefix + '.json is valid style', function() { describe('/styles/' + prefix + '.json is valid style', function() {
+1 -1
View File
@@ -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() {
+3 -2
View File
@@ -9,7 +9,7 @@ var testTile = function(prefix, z, x, y, format, status, scale, type) {
}); });
}; };
var prefix = 'bright'; var prefix = 'test-style';
describe('Raster tiles', function() { describe('Raster tiles', function() {
describe('valid requests', function() { describe('valid requests', function() {
@@ -26,6 +26,7 @@ describe('Raster tiles', function() {
testTile(prefix, 0, 0, 0, 'png', 200, 2); testTile(prefix, 0, 0, 0, 'png', 200, 2);
testTile(prefix, 0, 0, 0, 'png', 200, 3); testTile(prefix, 0, 0, 0, 'png', 200, 3);
testTile(prefix, 2, 1, 1, 'png', 200, 3); testTile(prefix, 2, 1, 1, 'png', 200, 3);
testTile(prefix, 0, 0, 0, 'png', 200, 4);
}); });
}); });
@@ -39,7 +40,7 @@ describe('Raster tiles', function() {
testTile(prefix, 0, 0, 0, 'pbf', 400); testTile(prefix, 0, 0, 0, 'pbf', 400);
testTile(prefix, 0, 0, 0, 'png', 404, 1); testTile(prefix, 0, 0, 0, 'png', 404, 1);
testTile(prefix, 0, 0, 0, 'png', 404, 4); testTile(prefix, 0, 0, 0, 'png', 404, 5);
//testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this //testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this
}); });