Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00aaafa6b5 | |||
| bb45d0bf6d | |||
| 0dc8d45265 | |||
| 42171015b9 | |||
| edfb05186f | |||
| 41027dd975 | |||
| 8585720380 | |||
| cf729bc893 | |||
| 539125df6f |
@@ -1,4 +0,0 @@
|
||||
.git
|
||||
node_modules
|
||||
test_data
|
||||
test
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
test_data
|
||||
config.json
|
||||
@@ -1,22 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq xvfb
|
||||
install:
|
||||
- npm install
|
||||
- wget -O test_data.zip https://github.com/klokantech/tileserver-gl-data/archive/v0.0.3.zip
|
||||
- unzip -q test_data.zip -d tmp_test_data
|
||||
- mkdir test_data
|
||||
- mv tmp_test_data/tileserver-gl-data-*/* -t test_data
|
||||
script:
|
||||
- xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM debian:stretch
|
||||
MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
|
||||
|
||||
RUN apt-get -qq update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
|
||||
curl \
|
||||
build-essential \
|
||||
python \
|
||||
xvfb \
|
||||
&& curl -sL https://deb.nodesource.com/setup_4.x | bash - \
|
||||
&& apt-get -y install nodejs \
|
||||
&& apt-get clean
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
COPY / /usr/src/app
|
||||
RUN cd /usr/src/app && npm install --production
|
||||
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["/usr/src/app/run.sh"]
|
||||
@@ -1,47 +0,0 @@
|
||||
# tileserver-gl
|
||||
[](https://travis-ci.org/klokantech/tileserver-gl)
|
||||
|
||||
## Installation
|
||||
|
||||
### Docker
|
||||
- `docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl`
|
||||
|
||||
### Without docker
|
||||
- Make sure you have Node v4 or higher (`nvm install 4`)
|
||||
- `npm install`
|
||||
- `node src/main.js`
|
||||
|
||||
## Sample data
|
||||
Sample data can be downloaded at https://github.com/klokantech/tileserver-gl-data/archive/master.zip
|
||||
|
||||
#### 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`)
|
||||
|
||||
## 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
|
||||
See https://github.com/klokantech/tileserver-gl-data/blob/master/config.json
|
||||
|
||||
**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
|
||||
|
||||
- If you visit the server on the configured port (default 8080) you should see your maps appearing in the browser.
|
||||
- Style is served at `/styles/{id}.json` (+ array at `/styles.json`)
|
||||
- Sprites at `/styles/{id}/sprite[@2x].{format}`
|
||||
- Fonts at `/fonts/{fontstack}/{start}-{end}.pbf`
|
||||
- Rendered tiles are at `/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}`
|
||||
- The optional `@2x` (or `@3x`) part can be used to render HiDPI (retina) tiles
|
||||
- Available formats: `png`, `jpg` (`jpeg`), `webp`
|
||||
- TileJSON at `/styles/{id}/rendered.json`
|
||||
- Static images are rendered at:
|
||||
- `/styles/{id}/static/{lon},{lat},{zoom}/{width}x{height}[@2x].{format}` (center-based)
|
||||
- `/styles/{id}/static/{minx},{miny},{maxx},{maxy}/{zoom}[@2x].{format}` (area-based)
|
||||
- Source data at `/data/{mbtiles}/{z}/{x}/{y}.{format}`
|
||||
- TileJSON at `/data/{mbtiles}.json`
|
||||
- Array of all TileJSONs at `/index.json` (`/rendered.json`; `/data.json`)
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 530 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="2034.203px" height="552.055px" viewBox="0 0 2034.203 552.055" enable-background="new 0 0 2034.203 552.055"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#3A1888" d="M3.604-242.717"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#3A1888" d="M152.645,436.647c25.674,25.668,94.015,95.335,93.983,95.406c-0.249,0.454,67.892-67.963,95.032-95.087
|
||||
l-94.67-94.665L152.645,436.647z"/>
|
||||
<path fill="#03A1C4" d="M246.99,342.301l94.67,94.665c0.141-0.157,0.314-0.336,0.466-0.477l94.578-94.583l-94.66-94.662
|
||||
L246.99,342.301z"/>
|
||||
<path fill="#05D0DF" d="M436.704,341.907l0.243-0.244c52.317-52.312,52.36-137.096,0.157-189.473l-95.06,95.055L436.704,341.907z"
|
||||
/>
|
||||
<path fill="#761FE8" d="M151.931,247.245l-94.329,94.326c0.027,0.032,0.043,0.064,0.076,0.092l94.811,94.827
|
||||
c0.054,0.049,0.108,0.098,0.157,0.157l94.345-94.346L151.931,247.245z"/>
|
||||
<path fill="#FFAA01" d="M246.99,152.184l95.054,95.061l95.06-95.055c-0.076-0.054-0.103-0.108-0.157-0.162l-94.821-94.816
|
||||
c-0.022-0.027-0.054-0.054-0.082-0.081L246.99,152.184z"/>
|
||||
<path fill="#F1175D" d="M57.201,152.514c-51.852,52.377-51.722,136.848,0.4,189.057l94.329-94.326L57.201,152.514z"/>
|
||||
<path fill="#FB3A1B" d="M246.99,152.184L152.255,57.45l-94.578,94.578c-0.163,0.162-0.309,0.336-0.476,0.486l94.729,94.73
|
||||
L246.99,152.184z"/>
|
||||
<path fill="#FBC935" d="M342.044,57.13C289.663,4.846,204.832,4.874,152.488,57.211l-0.233,0.238l94.735,94.734L342.044,57.13z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#333359" d="M734.146,365.616v-96.875c0-23.851-12.479-45.492-37.077-45.492c-24.224,0-38.517,21.642-38.517,45.492
|
||||
v96.875h-44.761V184.347h41.46l3.301,22.021c9.542-18.353,30.458-24.949,47.685-24.949c21.669,0,43.306,8.811,53.588,33.754
|
||||
c16.144-25.685,37.066-33.022,60.537-33.022c51.38,0,76.692,31.551,76.692,85.859v97.605h-44.767V268.01
|
||||
c0-23.84-9.904-44.037-34.106-44.037c-24.234,0-39.279,20.917-39.279,44.768v96.875H734.146z"/>
|
||||
<path fill="#333359" d="M1086.026,184.726h42.938v180.89h-42.208l-2.208-26.41c-10.266,21.269-38.516,31.535-58.702,31.914
|
||||
c-53.556,0.368-93.198-32.655-93.198-96.137c0-62.375,41.477-95.029,94.313-94.662c24.212,0,47.321,11.371,57.587,29.354
|
||||
L1086.026,184.726z M977.416,274.983c0,34.479,23.85,55.039,53.573,55.039c70.446,0,70.446-109.713,0-109.713
|
||||
C1001.266,220.309,977.416,240.496,977.416,274.983z"/>
|
||||
<path fill="#333359" d="M1166.756,441.214V184.726h41.839l2.923,24.949c13.951-20.187,38.175-28.991,58.719-28.991
|
||||
c55.753,0,92.835,41.471,92.835,94.667c0,52.847-33.401,94.675-91.374,94.675c-19.065,0-47.332-5.888-60.18-25.695v96.884
|
||||
H1166.756z M1318.305,275.351c0-28.253-19.082-51.378-51.37-51.378c-32.298,0-51.38,23.125-51.38,51.378
|
||||
c0,28.244,20.922,51.38,51.38,51.38C1297.404,326.731,1318.305,303.595,1318.305,275.351z"/>
|
||||
<path fill="#333359" d="M1443.064,129.682v54.665h61.642v15.046h-61.642v110.453c0,24.575,5.146,41.823,33.392,41.823
|
||||
c8.805,0,18.709-2.938,27.882-7.339l6.24,14.666c-11.382,5.521-22.763,9.185-34.122,9.185c-38.527,0-51.002-22.752-51.002-58.335
|
||||
V199.393h-38.538v-15.046h38.538v-52.831L1443.064,129.682z"/>
|
||||
<path fill="#333359" d="M1570.027,125.272c0,19.082-28.986,19.082-28.986,0C1541.041,106.2,1570.027,106.2,1570.027,125.272z
|
||||
M1546.188,183.612v182.004h17.962V183.612H1546.188z"/>
|
||||
<path fill="#333359" d="M1633.503,108.776v256.84h-17.983v-256.84H1633.503z"/>
|
||||
<path fill="#333359" d="M1918.606,184.347l0.73,32.304c11.365-24.603,37.066-34.133,60.181-34.133
|
||||
c13.589-0.367,26.772,3.307,38.896,10.646l-8.08,14.671c-9.525-5.871-20.187-8.441-30.815-8.441
|
||||
c-33.771,0.379-59.817,27.524-59.817,60.553v105.67h-17.979V184.347H1918.606z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="none" d="M1694.655,305.711c0.006,0.016,0.014,0.031,0.02,0.047l146.748-38.832c-0.007-0.055-0.012-0.11-0.018-0.166
|
||||
L1694.655,305.711z"/>
|
||||
<path fill="none" d="M1765.447,197.873c-42.255,0-76.514,34.997-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
|
||||
C1826.633,219.268,1798.486,197.873,1765.447,197.873z"/>
|
||||
<g>
|
||||
<path fill="none" d="M1765.447,198.374c-42.255,0-76.514,34.996-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
|
||||
C1826.633,219.768,1798.486,198.374,1765.447,198.374z"/>
|
||||
<path fill="#333359" d="M1765.447,354.709c-31.946,0-59.308-20.014-70.764-48.431l-0.1,0.004l0.091-0.024
|
||||
c-0.006-0.016-0.014-0.031-0.02-0.047l146.75-38.951c0.006,0.056,0.011,0.111,0.018,0.166l15.616-4.133
|
||||
c-6.306-45.918-44.904-81.253-91.59-81.253c-51.089,0-92.501,42.31-92.501,94.5s41.412,94.501,92.501,94.501
|
||||
c38.213,0,71.011-23.675,85.115-57.448l-14.717-6.398C1824.179,335.126,1797.054,354.709,1765.447,354.709z M1688.934,276.542
|
||||
c0-43.173,34.259-78.169,76.514-78.169c33.039,0,61.186,21.395,71.895,51.361l-147.452,39.137
|
||||
C1689.267,284.854,1688.934,280.739,1688.934,276.542z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,204 @@
|
||||
@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;
|
||||
}
|
||||
section{
|
||||
margin: 15px auto;
|
||||
width: 930px;
|
||||
padding: 30px 0;
|
||||
}
|
||||
section p {
|
||||
text-align: center;
|
||||
}
|
||||
.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;
|
||||
width:180px;
|
||||
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: 252px;
|
||||
height: auto;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TileServerGL</title>
|
||||
<link rel="stylesheet" type="text/css" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<h1 class="title"><img src="/images/logo.png" alt="TileServerGL" /></h1>
|
||||
<h2 class="subtitle">Vector and raster maps with GL styles</h2>
|
||||
<p>
|
||||
An open-source map server made for vector tiles, and able to render into raster tiles with MapBox GL Native engine on the server side.
|
||||
</p>
|
||||
<p>
|
||||
It provides maps to web and mobile applications. Supported are Mapbox GL JS, Android SDK, iOS SDK, Leaflet, OpenLayers, HighDPI/Retina, GIS via WMTS, etc.
|
||||
</p>
|
||||
<p>
|
||||
For details see the project repository on GitHub:<br/>
|
||||
<a href="https://github.com/maptiler/tileserver-gl">https://github.com/maptiler/tileserver-gl</a>
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<a href="https://www.maptiler.com/" target="_blank"><img src="/images/maptiler-logo.svg" /></a>
|
||||
<p>
|
||||
<a href="https://github.com/maptiler/tileserver-gl" target="_blank">TileServer GL</a>
|
||||
<a href="https://www.maptiler.com/" target="_blank">is an open-source project from MapTiler team.</a>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "tileserver-gl",
|
||||
"version": "0.0.3",
|
||||
"description": "Map tile server for JSON GL styles - serverside generated raster tiles",
|
||||
"main": "src/main.js",
|
||||
"authors": [
|
||||
"Petr Sloup <petr.sloup@klokantech.com>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/klokantech/tileserver-gl.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha test/**.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "1.5.2",
|
||||
"advanced-pool": "0.3.1",
|
||||
"clone": "1.0.2",
|
||||
"color": "0.11.1",
|
||||
"cors": "2.7.1",
|
||||
"express": "4.13.4",
|
||||
"handlebars": "4.0.5",
|
||||
"mapbox-gl-native": "3.1.2",
|
||||
"mbtiles": "0.9.0",
|
||||
"morgan": "1.7.0",
|
||||
"nomnom": "1.8.1",
|
||||
"request": "2.72.0",
|
||||
"sharp": "0.14.1",
|
||||
"sphericalmercator": "1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"should": "^8.3.0",
|
||||
"mocha": "^2.4.5",
|
||||
"supertest": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
(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);
|
||||
@@ -1,244 +0,0 @@
|
||||
.mapboxgl-map {
|
||||
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.mapboxgl-canvas-container.mapboxgl-interactive,
|
||||
.mapboxgl-ctrl-nav-compass {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.mapboxgl-canvas-container.mapboxgl-interactive:active,
|
||||
.mapboxgl-ctrl-nav-compass:active {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-top-left,
|
||||
.mapboxgl-ctrl-top-right,
|
||||
.mapboxgl-ctrl-bottom-left,
|
||||
.mapboxgl-ctrl-bottom-right { position:absolute; }
|
||||
.mapboxgl-ctrl-top-left { top:0; left:0; }
|
||||
.mapboxgl-ctrl-top-right { top:0; right:0; }
|
||||
.mapboxgl-ctrl-bottom-left { bottom:0; left:0; }
|
||||
.mapboxgl-ctrl-bottom-right { right:0; bottom:0; }
|
||||
|
||||
.mapboxgl-ctrl { clear:both; }
|
||||
.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-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; }
|
||||
.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl { margin:0 10px 10px 0; float:right; }
|
||||
|
||||
.mapboxgl-ctrl-group {
|
||||
border-radius: 4px;
|
||||
-moz-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
|
||||
-webkit-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
|
||||
box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.mapboxgl-ctrl-group > button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: block;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0,0,0,0);
|
||||
cursor: pointer;
|
||||
}
|
||||
/* https://bugzilla.mozilla.org/show_bug.cgi?id=140562 */
|
||||
.mapboxgl-ctrl > button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.mapboxgl-ctrl > button:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.mapboxgl-ctrl > button:hover {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
.mapboxgl-ctrl-icon,
|
||||
.mapboxgl-ctrl-icon > div.arrow {
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
|
||||
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%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 {
|
||||
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");
|
||||
}
|
||||
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > div.arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
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-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
|
||||
padding: 0 5px;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
margin: 0;
|
||||
}
|
||||
.mapboxgl-ctrl-attrib a {
|
||||
color: rgba(0,0,0,0.75);
|
||||
text-decoration: none;
|
||||
}
|
||||
.mapboxgl-ctrl-attrib a:hover {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.mapboxgl-ctrl-attrib .mapbox-improve-map {
|
||||
font-weight: bold;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.mapboxgl-popup {
|
||||
position: absolute;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top,
|
||||
.mapboxgl-popup-anchor-top-left,
|
||||
.mapboxgl-popup-anchor-top-right {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom,
|
||||
.mapboxgl-popup-anchor-bottom-left,
|
||||
.mapboxgl-popup-anchor-bottom-right {
|
||||
-webkit-flex-direction: column-reverse;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.mapboxgl-popup-anchor-left {
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.mapboxgl-popup-anchor-right {
|
||||
-webkit-flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.mapboxgl-popup-tip {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 10px solid transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-top: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-bottom: none;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-left: none;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-right: none;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.mapboxgl-popup-close-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border: none;
|
||||
border-radius: 0 3px 0 0;
|
||||
cursor: pointer;
|
||||
background-color: rgba(0,0,0,0);
|
||||
}
|
||||
.mapboxgl-popup-close-button:hover {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
.mapboxgl-popup-content {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.10);
|
||||
padding: 10px 10px 15px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.mapboxgl-crosshair,
|
||||
.mapboxgl-crosshair .mapboxgl-interactive,
|
||||
.mapboxgl-crosshair .mapboxgl-interactive:active {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.mapboxgl-boxzoom {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: #fff;
|
||||
border: 2px dotted #202020;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@media print {
|
||||
.mapbox-improve-map {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{name}} - TileServer GL</title>
|
||||
{{#is_vector}}
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css" />
|
||||
<script src="/mapbox-gl.js"></script>
|
||||
<style>
|
||||
body {background:#000;color:#ccc;}
|
||||
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
||||
h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
|
||||
#layerList {position:absolute;top:35px;right:0;bottom:60%;width:240px;overflow:auto;}
|
||||
#layerList div div {width:15px;height:15px;display:inline-block;}
|
||||
#propertyList {position:absolute;top:40%;bottom:0;right:0;width:240px;overflow:auto;color:#fff;}
|
||||
</style>
|
||||
{{/is_vector}}
|
||||
{{^is_vector}}
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox.css" />
|
||||
<script src="/mapbox.js"></script>
|
||||
<script src="/leaflet-hash.js"></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
|
||||
});
|
||||
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,
|
||||
sources: {
|
||||
'vector_layer_': {
|
||||
type: 'vector',
|
||||
tiles: data['tiles'],
|
||||
minzoom: data['minzoom'],
|
||||
maxzoom: data['maxzoom']
|
||||
}
|
||||
},
|
||||
layers: layers_
|
||||
});
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
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', true);
|
||||
xhttp.send();
|
||||
|
||||
var propertyList = document.getElementById('propertyList');
|
||||
map.on('mousemove', function(e) {
|
||||
propertyList.innerHTML = '';
|
||||
map.featuresAt(e.point, {radius: 3}, function(err, features) {
|
||||
if (err) throw err;
|
||||
if (features[0]) {
|
||||
propertyList.innerHTML = JSON.stringify(features[0].properties, null, 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
</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', { zoomControl: false });
|
||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
||||
setTimeout(function() {
|
||||
new L.Hash(map);
|
||||
}, 0);
|
||||
</script>
|
||||
{{/is_vector}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{name}} - TileServer GL</title>
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox-gl.css" />
|
||||
<script src="/mapbox-gl.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/mapbox.css" />
|
||||
<script src="/mapbox.js"></script>
|
||||
<script src="/leaflet-hash.js"></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 preference = (location.search || '').substr(1);
|
||||
if (preference != 'vector' && preference != 'raster') {
|
||||
preference = mapboxgl.supported() ? 'vector' : 'raster';
|
||||
}
|
||||
if (preference == 'vector') {
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
style: '/styles/{{id}}.json',
|
||||
hash: true
|
||||
});
|
||||
map.addControl(new mapboxgl.Navigation());
|
||||
} else {
|
||||
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json', { zoomControl: false });
|
||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
||||
setTimeout(function() {
|
||||
new L.Hash(map);
|
||||
}, 0);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
xvfb-run --server-args="-screen 0 1024x768x24" node /usr/src/app/src/main.js -p 80 -c /data/config.json
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var opts = require('nomnom')
|
||||
.option('config', {
|
||||
abbr: 'c',
|
||||
default: 'config.json',
|
||||
help: 'Configuration file'
|
||||
})
|
||||
.option('port', {
|
||||
abbr: 'p',
|
||||
default: 8080,
|
||||
help: 'Port'
|
||||
})
|
||||
.option('version', {
|
||||
abbr: 'v',
|
||||
flag: true,
|
||||
help: 'Version info',
|
||||
callback: function() {
|
||||
return 'version ' + require('../package.json').version;
|
||||
}
|
||||
}).parse();
|
||||
|
||||
return require('./server')({
|
||||
config: opts.config,
|
||||
port: opts.port
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var crypto = require('crypto'),
|
||||
fs = require('fs'),
|
||||
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 = path.join(options.paths.mbtiles, params.mbtiles);
|
||||
var tileJSON = {
|
||||
'tiles': params.domains || options.domains
|
||||
};
|
||||
|
||||
repo[id] = tileJSON;
|
||||
|
||||
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';
|
||||
tileJSON['basename'] = id;
|
||||
tileJSON['filesize'] = fs.statSync(mbtilesFile)['size'];
|
||||
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) {
|
||||
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 {
|
||||
var md5 = crypto.createHash('md5').update(data).digest('base64');
|
||||
headers['content-md5'] = md5;
|
||||
if (tileJSON['format'] == 'pbf') {
|
||||
headers['content-type'] = 'application/x-protobuf';
|
||||
headers['content-encoding'] = 'gzip';
|
||||
}
|
||||
res.set(headers);
|
||||
|
||||
if (data == null) {
|
||||
return res.status(404).send('Not found');
|
||||
} else {
|
||||
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;
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
path = require('path'),
|
||||
fs = require('fs');
|
||||
|
||||
var clone = require('clone'),
|
||||
express = require('express');
|
||||
|
||||
|
||||
module.exports = function(options, allowedFonts) {
|
||||
var app = express().disable('x-powered-by');
|
||||
|
||||
var fontPath = options.paths.fonts;
|
||||
|
||||
var getFontPbf = function(name, range, callback) {
|
||||
// if some of the files failed to load (does not exist or not allowed),
|
||||
// return empty buffer so the other fonts can still work
|
||||
if (allowedFonts[name]) {
|
||||
var filename = path.join(fontPath, name, range + '.pbf');
|
||||
return fs.readFile(filename, function(err, data) {
|
||||
if (err) {
|
||||
console.log('Font load error:', filename);
|
||||
return callback(null, new Buffer([]));
|
||||
} else {
|
||||
return callback(null, data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return callback(null, new Buffer([]));
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/:fontstack/:range([\\d]+-[\\d]+).pbf',
|
||||
function(req, res, next) {
|
||||
var fontstack = decodeURI(req.params.fontstack);
|
||||
var range = req.params.range;
|
||||
|
||||
var fonts = fontstack.split(',');
|
||||
|
||||
var queue = [];
|
||||
fonts.forEach(function(font) {
|
||||
queue.push(function(callback) {
|
||||
getFontPbf(font, range, callback);
|
||||
});
|
||||
});
|
||||
|
||||
return async.parallel(queue, function(err, results) {
|
||||
var concated = Buffer.concat(results);
|
||||
if (err || concated.length == 0) {
|
||||
return res.status(400).send('');
|
||||
} else {
|
||||
res.header('Content-type', 'application/x-protobuf');
|
||||
return res.send(concated);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
};
|
||||
@@ -1,365 +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'),
|
||||
Color = require('color'),
|
||||
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,
|
||||
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 (headers['ETag']) {
|
||||
response.etag = headers['ETag'];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
'attribution': '',
|
||||
'basename': id,
|
||||
'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.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) {
|
||||
var type = source.type;
|
||||
Object.assign(source, info);
|
||||
source.type = type;
|
||||
source.basename = name;
|
||||
source.tiles = [
|
||||
// meta url which will be detected when requested
|
||||
'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf')
|
||||
];
|
||||
if (source.format == 'pbf') {
|
||||
map.sources[name].emptyTile = new Buffer(0);
|
||||
} else {
|
||||
var color = new Color(source.color || '#fff');
|
||||
var format = source.format;
|
||||
if (format == 'jpg') {
|
||||
format = 'jpeg';
|
||||
}
|
||||
sharp(new Buffer(color.rgbArray()), {
|
||||
raw: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3
|
||||
}
|
||||
}).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);
|
||||
});
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
var formatEncoding = (params.formatEncoding || {})[format] ||
|
||||
(options.formatEncoding || {})[format];
|
||||
if (format == 'png') {
|
||||
image.compressionLevel(formatEncoding || 6)
|
||||
.withoutAdaptiveFiltering();
|
||||
} else if (format == 'jpeg') {
|
||||
image.quality(formatEncoding || 80);
|
||||
} else if (format == 'webp') {
|
||||
image.quality(formatEncoding || 90);
|
||||
}
|
||||
image.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/%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('/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;
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs');
|
||||
|
||||
var clone = require('clone'),
|
||||
express = require('express');
|
||||
|
||||
|
||||
module.exports = function(options, repo, params, id, reportTiles, reportFont) {
|
||||
var app = express().disable('x-powered-by');
|
||||
|
||||
var styleFile = path.join(options.paths.styles, params.style);
|
||||
|
||||
var styleJSON = clone(require(styleFile));
|
||||
Object.keys(styleJSON.sources).forEach(function(name) {
|
||||
var source = styleJSON.sources[name];
|
||||
var url = source.url;
|
||||
if (url.lastIndexOf('mbtiles:', 0) === 0) {
|
||||
var mbtiles = url.substring('mbtiles://'.length);
|
||||
var identifier = reportTiles(mbtiles);
|
||||
source.url = 'local://data/' + identifier + '.json';
|
||||
}
|
||||
});
|
||||
|
||||
var findFontReferences = function(obj) {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
var value = obj[key];
|
||||
if (key == 'text-font') {
|
||||
if (value && value.length > 0) {
|
||||
value.forEach(reportFont);
|
||||
}
|
||||
} else if (value && typeof value == 'object') {
|
||||
findFontReferences(value);
|
||||
}
|
||||
});
|
||||
};
|
||||
styleJSON.layers.forEach(findFontReferences);
|
||||
|
||||
var spritePath = path.join(options.paths.sprites,
|
||||
path.basename(styleFile, '.json'));
|
||||
|
||||
styleJSON.sprite = 'local://styles/' + id + '/sprite';
|
||||
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
||||
|
||||
repo[id] = styleJSON;
|
||||
|
||||
app.get('/' + id + '.json', function(req, res, next) {
|
||||
var fixUrl = function(url) {
|
||||
return url.replace(
|
||||
'local://', req.protocol + '://' + req.headers.host + '/');
|
||||
};
|
||||
|
||||
var styleJSON_ = clone(styleJSON);
|
||||
Object.keys(styleJSON_.sources).forEach(function(name) {
|
||||
var source = styleJSON_.sources[name];
|
||||
source.url = fixUrl(source.url);
|
||||
});
|
||||
styleJSON_.sprite = fixUrl(styleJSON_.sprite);
|
||||
styleJSON_.glyphs = fixUrl(styleJSON_.glyphs);
|
||||
return res.send(styleJSON_);
|
||||
});
|
||||
|
||||
app.get('/' + id + '/sprite:scale(@[23]x)?\.:format([\\w]+)',
|
||||
function(req, res, next) {
|
||||
var scale = req.params.scale,
|
||||
format = req.params.format;
|
||||
var filename = spritePath + (scale || '') + '.' + format;
|
||||
return fs.readFile(filename, function(err, data) {
|
||||
if (err) {
|
||||
console.log('Sprite load error:', filename);
|
||||
return res.status(404).send('File not found');
|
||||
} else {
|
||||
if (format == 'json') res.header('Content-type', 'application/json');
|
||||
if (format == 'png') res.header('Content-type', 'image/png');
|
||||
return res.send(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return app;
|
||||
};
|
||||
@@ -1,279 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
process.env.UV_THREADPOOL_SIZE =
|
||||
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
var clone = require('clone'),
|
||||
cors = require('cors'),
|
||||
express = require('express'),
|
||||
handlebars = require('handlebars'),
|
||||
mercator = new (require('sphericalmercator'))(),
|
||||
morgan = require('morgan');
|
||||
|
||||
var serve_font = require('./serve_font'),
|
||||
serve_rendered = require('./serve_rendered'),
|
||||
serve_style = require('./serve_style'),
|
||||
serve_data = require('./serve_data'),
|
||||
utils = require('./utils');
|
||||
|
||||
module.exports = function(opts, callback) {
|
||||
var app = express().disable('x-powered-by'),
|
||||
serving = {
|
||||
styles: {},
|
||||
rendered: {},
|
||||
data: {},
|
||||
fonts: { // default fonts, always expose these (if they exist)
|
||||
'Open Sans Regular': true,
|
||||
'Arial Unicode MS Regular': true
|
||||
}
|
||||
};
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
callback = callback || function() {};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' &&
|
||||
process.env.NODE_ENV !== 'test') {
|
||||
app.use(morgan('dev'));
|
||||
}
|
||||
|
||||
var configPath = path.resolve(opts.config);
|
||||
|
||||
var config;
|
||||
try {
|
||||
config = require(configPath);
|
||||
} catch (e) {
|
||||
console.log('ERROR: Config file not found or invalid!');
|
||||
console.log(' See README.md for instructions and sample data.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var options = config.options || {};
|
||||
var paths = options.paths || {};
|
||||
options.paths = paths;
|
||||
paths.root = path.resolve(process.cwd(), paths.root || '');
|
||||
paths.styles = path.resolve(paths.root, paths.styles || '');
|
||||
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
||||
|
||||
var data = clone(config.data || {});
|
||||
|
||||
Object.keys(config.styles || {}).forEach(function(id) {
|
||||
var item = config.styles[id];
|
||||
if (!item.style || item.style.length == 0) {
|
||||
console.log('Missing "style" property for ' + id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.serve_data !== false) {
|
||||
app.use('/styles/', serve_style(options, serving.styles, item, id,
|
||||
function(mbtiles) {
|
||||
var dataItemId;
|
||||
Object.keys(data).forEach(function(id) {
|
||||
if (data[id].mbtiles == mbtiles) {
|
||||
dataItemId = id;
|
||||
}
|
||||
});
|
||||
if (dataItemId) { // mbtiles exist in the data config
|
||||
return dataItemId;
|
||||
} else {
|
||||
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
||||
while (data[id]) id += '_';
|
||||
data[id] = {
|
||||
'mbtiles': mbtiles
|
||||
};
|
||||
return id;
|
||||
}
|
||||
}, function(font) {
|
||||
serving.fonts[font] = true;
|
||||
}));
|
||||
}
|
||||
if (item.serve_rendered !== false) {
|
||||
app.use('/styles/' + id + '/',
|
||||
serve_rendered(options, serving.rendered, item, id));
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(serving.styles).length > 0) {
|
||||
// serve fonts only if serving some styles
|
||||
app.use('/fonts/', serve_font(options, serving.fonts));
|
||||
}
|
||||
|
||||
app.use(cors());
|
||||
|
||||
Object.keys(data).forEach(function(id) {
|
||||
var item = data[id];
|
||||
if (!item.mbtiles || item.mbtiles.length == 0) {
|
||||
console.log('Missing "mbtiles" property for ' + id);
|
||||
return;
|
||||
}
|
||||
|
||||
app.use('/data/', serve_data(options, serving.data, item, id));
|
||||
});
|
||||
|
||||
app.get('/styles.json', function(req, res, next) {
|
||||
var result = [];
|
||||
Object.keys(serving.styles).forEach(function(id) {
|
||||
var styleJSON = serving.styles[id];
|
||||
result.push({
|
||||
version: styleJSON.version,
|
||||
name: styleJSON.name,
|
||||
id: id,
|
||||
url: req.protocol + '://' + req.headers.host + '/styles/' + id + '.json'
|
||||
});
|
||||
});
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
var addTileJSONs = function(arr, req, type) {
|
||||
Object.keys(serving[type]).forEach(function(id) {
|
||||
var info = clone(serving[type][id]);
|
||||
info.tiles = utils.getTileUrls(req, info.tiles,
|
||||
type + '/' + id, info.format);
|
||||
arr.push(info);
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
|
||||
app.get('/rendered.json', function(req, res, next) {
|
||||
res.send(addTileJSONs([], req, 'rendered'));
|
||||
});
|
||||
app.get('/data.json', function(req, res, next) {
|
||||
res.send(addTileJSONs([], req, 'data'));
|
||||
});
|
||||
app.get('/index.json', function(req, res, next) {
|
||||
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
|
||||
});
|
||||
|
||||
//------------------------------------
|
||||
// serve web presentations
|
||||
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
||||
|
||||
var templates = path.join(__dirname, '../public/templates');
|
||||
var serveTemplate = function(path, template, dataGetter) {
|
||||
fs.readFile(templates + '/' + template + '.tmpl', function(err, content) {
|
||||
if (err) {
|
||||
console.log('Template not found:', err);
|
||||
}
|
||||
var compiled = handlebars.compile(content.toString());
|
||||
|
||||
app.use(path, function(req, res, next) {
|
||||
var data = {};
|
||||
if (dataGetter) {
|
||||
data = dataGetter(req.params);
|
||||
if (!data) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
}
|
||||
return res.status(200).send(compiled(data));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
serveTemplate('/$', 'index', function() {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
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(params) {
|
||||
var id = 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(params) {
|
||||
var id = 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, function() {
|
||||
console.log('Listening at http://%s:%d/',
|
||||
this.address().address, this.address().port);
|
||||
|
||||
return callback();
|
||||
});
|
||||
|
||||
setTimeout(callback, 1000);
|
||||
return {
|
||||
app: app,
|
||||
server: server
|
||||
};
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.getTileUrls = function(req, domains, path, format) {
|
||||
|
||||
if (domains) {
|
||||
if (domains.constructor === String && domains.length > 0) {
|
||||
domains = domains.split(',');
|
||||
}
|
||||
}
|
||||
if (!domains || domains.length == 0) {
|
||||
domains = [req.headers.host];
|
||||
}
|
||||
|
||||
var key = req.query.key;
|
||||
var query = (key && key.length > 0) ? ('?key=' + key) : '';
|
||||
|
||||
var uris = [];
|
||||
domains.forEach(function(domain) {
|
||||
uris.push(req.protocol + '://' + domain + '/' + path +
|
||||
'/{z}/{x}/{y}.' + format + query);
|
||||
});
|
||||
|
||||
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
|
||||
)
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
var testTileJSONArray = function(url) {
|
||||
describe(url + ' is array of TileJSONs', function() {
|
||||
it('is json', function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('is non-empty array', function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(function(res) {
|
||||
res.body.should.be.Array();
|
||||
res.body.length.should.be.greaterThan(0);
|
||||
}).end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var testTileJSON = function(url, basename) {
|
||||
describe(url + ' is TileJSON', function() {
|
||||
it('is json', function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('has valid basename and tiles', function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(function(res) {
|
||||
res.body.basename.should.equal(basename);
|
||||
res.body.tiles.length.should.be.greaterThan(0);
|
||||
}).end(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Metadata', function() {
|
||||
testTileJSONArray('/index.json');
|
||||
testTileJSONArray('/rendered.json');
|
||||
testTileJSONArray('/data.json');
|
||||
|
||||
describe('/styles.json is valid array', function() {
|
||||
it('is json', function(done) {
|
||||
supertest(app)
|
||||
.get('/styles.json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /application\/json/, done);
|
||||
});
|
||||
|
||||
it('contains valid item', function(done) {
|
||||
supertest(app)
|
||||
.get('/styles.json')
|
||||
.expect(function(res) {
|
||||
res.body.should.be.Array();
|
||||
res.body.length.should.be.greaterThan(0);
|
||||
res.body[0].version.should.equal(8);
|
||||
res.body[0].id.should.be.String();
|
||||
res.body[0].name.should.be.String();
|
||||
}).end(done);
|
||||
});
|
||||
});
|
||||
|
||||
testTileJSON('/styles/test/rendered.json', 'test');
|
||||
testTileJSON('/data/zurich-vector.json', 'zurich-vector');
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
global.should = require('should');
|
||||
global.supertest = require('supertest');
|
||||
|
||||
before(function() {
|
||||
console.log('global setup');
|
||||
process.chdir('test_data');
|
||||
var running = require('../src/server')({
|
||||
config: 'config.json',
|
||||
port: 8888
|
||||
});
|
||||
global.app = running.app;
|
||||
global.server = running.server;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
console.log('global teardown');
|
||||
global.server.close(function() { console.log('Done'); });
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
var testStatic = function(prefix, q, format, status, scale, type) {
|
||||
if (scale) q += '@' + scale + 'x';
|
||||
var path = '/styles/' + prefix + '/static/' + q + '.' + format;
|
||||
it(path + ' returns ' + status, function(done) {
|
||||
var test = supertest(app).get(path);
|
||||
if (status) test.expect(status);
|
||||
if (type) test.expect('Content-Type', type);
|
||||
test.end(done);
|
||||
});
|
||||
};
|
||||
|
||||
describe('Static endpoints', function() {
|
||||
describe('center-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic('test', '0,0,0/256x256', 'png', 200, undefined, /image\/png/);
|
||||
testStatic('test', '0,0,0/256x256', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '0,0,0/256x256', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '0,0,0/256x256', 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic('test', '0,0,0/300x300', 'png', 200, 2);
|
||||
testStatic('test', '0,0,0/300x300', 'png', 200, 3);
|
||||
|
||||
testStatic('test', '80,40,20/600x300', 'png', 200, 3);
|
||||
testStatic('test', '8.5,40.5,20/300x150', 'png', 200, 3);
|
||||
testStatic('test', '-8.5,-40.5,20/300x150', 'png', 200, 3);
|
||||
|
||||
testStatic('test', '8,40,2,0,0/300x150', 'png', 200);
|
||||
testStatic('test', '8,40,2,180,45/300x150', 'png', 200, 2);
|
||||
testStatic('test', '8,40,2,10/300x150', 'png', 200, 3);
|
||||
testStatic('test', '8,40,2,10.3,20.4/300x300', 'png', 200);
|
||||
testStatic('test', '0,0,2,390,120/300x300', 'png', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testStatic('test', '190,0,0/256x256', 'png', 400);
|
||||
testStatic('test', '0,86,0/256x256', 'png', 400);
|
||||
testStatic('test', '80,40,20/0x0', 'png', 400);
|
||||
testStatic('test', '0,0,0/256x256', 'gif', 400);
|
||||
testStatic('test', '0,0,0/256x256', 'png', 404, 1);
|
||||
|
||||
testStatic('test', '0,0,-1/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,1.5/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,0/256.5x256.5', 'png', 404);
|
||||
|
||||
testStatic('test', '0,0,0,/256x256', 'png', 404);
|
||||
testStatic('test', '0,0,0,0,/256x256', 'png', 404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('area-based', function() {
|
||||
describe('valid requests', function() {
|
||||
describe('various formats', function() {
|
||||
testStatic('test', '-180,-80,180,80/0', 'png', 200, undefined, /image\/png/);
|
||||
testStatic('test', '-180,-80,180,80/0', 'jpg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '-180,-80,180,80/0', 'jpeg', 200, undefined, /image\/jpeg/);
|
||||
testStatic('test', '-180,-80,180,80/0', 'webp', 200, undefined, /image\/webp/);
|
||||
});
|
||||
|
||||
describe('different parameters', function() {
|
||||
testStatic('test', '-180,-90,180,90/0', 'png', 200, 2);
|
||||
testStatic('test', '0,0,1,1/3', 'png', 200, 3);
|
||||
|
||||
testStatic('test', '-280,-80,0,80/0', 'png', 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests return 4xx', function() {
|
||||
testStatic('test', '0,87,1,88/5', 'png', 400);
|
||||
|
||||
testStatic('test', '18,-9,-18,9/0', 'png', 400);
|
||||
testStatic('test', '0,0,1,1/1', 'gif', 400);
|
||||
|
||||
testStatic('test', '-180,-80,180,80/0.5', 'png', 404);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
var testIs = function(url, type, status) {
|
||||
it(url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
||||
function(done) {
|
||||
supertest(app)
|
||||
.get(url)
|
||||
.expect(status || 200)
|
||||
.expect('Content-Type', type, done);
|
||||
});
|
||||
};
|
||||
|
||||
describe('Styles', function() {
|
||||
describe('/styles/test.json is valid style', function() {
|
||||
testIs('/styles/test.json', /application\/json/);
|
||||
|
||||
it('contains expected properties', function(done) {
|
||||
supertest(app)
|
||||
.get('/styles/test.json')
|
||||
.expect(function(res) {
|
||||
res.body.version.should.equal(8);
|
||||
res.body.name.should.be.String();
|
||||
res.body.sources.should.be.Object();
|
||||
res.body.glyphs.should.be.String();
|
||||
res.body.sprite.should.be.String();
|
||||
res.body.layers.should.be.Array();
|
||||
}).end(done);
|
||||
});
|
||||
});
|
||||
describe('/styles/streets.json is not served', function() {
|
||||
testIs('/styles/streets.json', /./, 404);
|
||||
});
|
||||
|
||||
describe('/styles/test/sprite[@2x].{format}', function() {
|
||||
testIs('/styles/test/sprite.json', /application\/json/);
|
||||
testIs('/styles/test/sprite@2x.json', /application\/json/);
|
||||
testIs('/styles/test/sprite.png', /image\/png/);
|
||||
testIs('/styles/test/sprite@2x.png', /image\/png/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fonts', function() {
|
||||
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Open Sans Regular/65280-65533.pbf', /application\/x-protobuf/);
|
||||
testIs('/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
|
||||
/application\/x-protobuf/);
|
||||
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
||||
|
||||
testIs('/fonts/Nonsense/0-255.pbf', /./, 400);
|
||||
testIs('/fonts/Nonsense1,Nonsense2/0-255.pbf', /./, 400);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
var testTile = function(prefix, z, x, y, status) {
|
||||
var path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf';
|
||||
it(path + ' returns ' + status, function(done) {
|
||||
var test = supertest(app).get(path);
|
||||
if (status) test.expect(status);
|
||||
if (status == 200) test.expect('Content-Type', /application\/x-protobuf/);
|
||||
test.end(done);
|
||||
});
|
||||
};
|
||||
|
||||
var prefix = 'zurich-vector';
|
||||
|
||||
describe('Vector tiles', function() {
|
||||
describe('existing tiles', function() {
|
||||
testTile(prefix, 0, 0, 0, 200);
|
||||
testTile(prefix, 14, 8581, 5738, 200);
|
||||
});
|
||||
|
||||
describe('non-existent requests return 4xx', function() {
|
||||
testTile('non_existent', 0, 0, 0, 404);
|
||||
testTile(prefix, -1, 0, 0, 404); // err zoom
|
||||
testTile(prefix, 20, 0, 0, 404); // zoom out of bounds
|
||||
testTile(prefix, 0, 1, 0, 404);
|
||||
testTile(prefix, 0, 0, 1, 404);
|
||||
|
||||
testTile(prefix, 14, 0, 0, 404); // non existent tile
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
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); //TODO: test this
|
||||
});
|
||||
});
|
||||