Compare commits

..

9 Commits

Author SHA1 Message Date
Petr Pridal 00aaafa6b5 Web links to maptiler instead of klokantech 2020-05-13 08:55:14 +02:00
Dalibor Janak bb45d0bf6d Update logo 2020-04-29 16:20:48 +02:00
Petr Pridal 0dc8d45265 Update CNAME 2016-12-08 08:51:15 +01:00
Petr Pridal 42171015b9 Update index.html 2016-08-26 07:21:51 +02:00
Petr Pridal edfb05186f Update index.html 2016-08-26 07:18:25 +02:00
Petr Pridal 41027dd975 Center <p> in section 2016-08-05 00:57:10 +02:00
Petr Pridal 8585720380 Add link to GitHub on the website 2016-08-05 00:52:38 +02:00
Petr Pridal cf729bc893 Add CNAME for www.tileserver.org 2016-08-05 00:50:26 +02:00
Petr Pridal 539125df6f Initial commit 2016-08-05 00:42:12 +02:00
44 changed files with 304 additions and 2505 deletions
-4
View File
@@ -1,4 +0,0 @@
.git
node_modules
test_data
test
-3
View File
@@ -1,3 +0,0 @@
node_modules
test_data
config.json
-22
View File
@@ -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
View File
@@ -0,0 +1 @@
tileserver.org
-22
View File
@@ -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"]
-47
View File
@@ -1,47 +0,0 @@
# tileserver-gl
[![Build Status](https://travis-ci.org/klokantech/tileserver-gl.svg?branch=master)](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`)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="2034.203px" height="552.055px" viewBox="0 0 2034.203 552.055" enable-background="new 0 0 2034.203 552.055"
xml:space="preserve">
<g>
<path fill="#3A1888" d="M3.604-242.717"/>
</g>
<g>
<g>
<path fill="#3A1888" d="M152.645,436.647c25.674,25.668,94.015,95.335,93.983,95.406c-0.249,0.454,67.892-67.963,95.032-95.087
l-94.67-94.665L152.645,436.647z"/>
<path fill="#03A1C4" d="M246.99,342.301l94.67,94.665c0.141-0.157,0.314-0.336,0.466-0.477l94.578-94.583l-94.66-94.662
L246.99,342.301z"/>
<path fill="#05D0DF" d="M436.704,341.907l0.243-0.244c52.317-52.312,52.36-137.096,0.157-189.473l-95.06,95.055L436.704,341.907z"
/>
<path fill="#761FE8" d="M151.931,247.245l-94.329,94.326c0.027,0.032,0.043,0.064,0.076,0.092l94.811,94.827
c0.054,0.049,0.108,0.098,0.157,0.157l94.345-94.346L151.931,247.245z"/>
<path fill="#FFAA01" d="M246.99,152.184l95.054,95.061l95.06-95.055c-0.076-0.054-0.103-0.108-0.157-0.162l-94.821-94.816
c-0.022-0.027-0.054-0.054-0.082-0.081L246.99,152.184z"/>
<path fill="#F1175D" d="M57.201,152.514c-51.852,52.377-51.722,136.848,0.4,189.057l94.329-94.326L57.201,152.514z"/>
<path fill="#FB3A1B" d="M246.99,152.184L152.255,57.45l-94.578,94.578c-0.163,0.162-0.309,0.336-0.476,0.486l94.729,94.73
L246.99,152.184z"/>
<path fill="#FBC935" d="M342.044,57.13C289.663,4.846,204.832,4.874,152.488,57.211l-0.233,0.238l94.735,94.734L342.044,57.13z"/>
</g>
<g>
<path fill="#333359" d="M734.146,365.616v-96.875c0-23.851-12.479-45.492-37.077-45.492c-24.224,0-38.517,21.642-38.517,45.492
v96.875h-44.761V184.347h41.46l3.301,22.021c9.542-18.353,30.458-24.949,47.685-24.949c21.669,0,43.306,8.811,53.588,33.754
c16.144-25.685,37.066-33.022,60.537-33.022c51.38,0,76.692,31.551,76.692,85.859v97.605h-44.767V268.01
c0-23.84-9.904-44.037-34.106-44.037c-24.234,0-39.279,20.917-39.279,44.768v96.875H734.146z"/>
<path fill="#333359" d="M1086.026,184.726h42.938v180.89h-42.208l-2.208-26.41c-10.266,21.269-38.516,31.535-58.702,31.914
c-53.556,0.368-93.198-32.655-93.198-96.137c0-62.375,41.477-95.029,94.313-94.662c24.212,0,47.321,11.371,57.587,29.354
L1086.026,184.726z M977.416,274.983c0,34.479,23.85,55.039,53.573,55.039c70.446,0,70.446-109.713,0-109.713
C1001.266,220.309,977.416,240.496,977.416,274.983z"/>
<path fill="#333359" d="M1166.756,441.214V184.726h41.839l2.923,24.949c13.951-20.187,38.175-28.991,58.719-28.991
c55.753,0,92.835,41.471,92.835,94.667c0,52.847-33.401,94.675-91.374,94.675c-19.065,0-47.332-5.888-60.18-25.695v96.884
H1166.756z M1318.305,275.351c0-28.253-19.082-51.378-51.37-51.378c-32.298,0-51.38,23.125-51.38,51.378
c0,28.244,20.922,51.38,51.38,51.38C1297.404,326.731,1318.305,303.595,1318.305,275.351z"/>
<path fill="#333359" d="M1443.064,129.682v54.665h61.642v15.046h-61.642v110.453c0,24.575,5.146,41.823,33.392,41.823
c8.805,0,18.709-2.938,27.882-7.339l6.24,14.666c-11.382,5.521-22.763,9.185-34.122,9.185c-38.527,0-51.002-22.752-51.002-58.335
V199.393h-38.538v-15.046h38.538v-52.831L1443.064,129.682z"/>
<path fill="#333359" d="M1570.027,125.272c0,19.082-28.986,19.082-28.986,0C1541.041,106.2,1570.027,106.2,1570.027,125.272z
M1546.188,183.612v182.004h17.962V183.612H1546.188z"/>
<path fill="#333359" d="M1633.503,108.776v256.84h-17.983v-256.84H1633.503z"/>
<path fill="#333359" d="M1918.606,184.347l0.73,32.304c11.365-24.603,37.066-34.133,60.181-34.133
c13.589-0.367,26.772,3.307,38.896,10.646l-8.08,14.671c-9.525-5.871-20.187-8.441-30.815-8.441
c-33.771,0.379-59.817,27.524-59.817,60.553v105.67h-17.979V184.347H1918.606z"/>
</g>
<g>
<path fill="none" d="M1694.655,305.711c0.006,0.016,0.014,0.031,0.02,0.047l146.748-38.832c-0.007-0.055-0.012-0.11-0.018-0.166
L1694.655,305.711z"/>
<path fill="none" d="M1765.447,197.873c-42.255,0-76.514,34.997-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
C1826.633,219.268,1798.486,197.873,1765.447,197.873z"/>
<g>
<path fill="none" d="M1765.447,198.374c-42.255,0-76.514,34.996-76.514,78.169c0,4.196,0.333,8.312,0.956,12.329l147.452-39.137
C1826.633,219.768,1798.486,198.374,1765.447,198.374z"/>
<path fill="#333359" d="M1765.447,354.709c-31.946,0-59.308-20.014-70.764-48.431l-0.1,0.004l0.091-0.024
c-0.006-0.016-0.014-0.031-0.02-0.047l146.75-38.951c0.006,0.056,0.011,0.111,0.018,0.166l15.616-4.133
c-6.306-45.918-44.904-81.253-91.59-81.253c-51.089,0-92.501,42.31-92.501,94.5s41.412,94.501,92.501,94.501
c38.213,0,71.011-23.675,85.115-57.448l-14.717-6.398C1824.179,335.126,1797.054,354.709,1765.447,354.709z M1688.934,276.542
c0-43.173,34.259-78.169,76.514-78.169c33.039,0,61.186,21.395,71.895,51.361l-147.452,39.137
C1689.267,284.854,1688.934,280.739,1688.934,276.542z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+204
View File
@@ -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;
}
}
+32
View File
@@ -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>
-37
View File
@@ -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"
}
}
File diff suppressed because one or more lines are too long
-162
View File
@@ -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);
-244
View File
@@ -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;
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-127
View File
@@ -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>
File diff suppressed because one or more lines are too long
-40
View File
@@ -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>
-2
View File
@@ -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
-28
View File
@@ -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
});
-87
View File
@@ -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;
};
-60
View File
@@ -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;
};
-365
View File
@@ -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;
};
-82
View File
@@ -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;
};
-279
View File
@@ -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
};
};
-39
View File
@@ -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
)
];
}
};
-69
View File
@@ -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');
});
-20
View File
@@ -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'); });
});
-80
View File
@@ -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);
});
});
});
-49
View File
@@ -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);
});
-28
View File
@@ -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
});
});
-44
View File
@@ -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
});
});