New index and viewer (+ templating system)

This commit is contained in:
Petr Sloup 2016-03-16 20:47:11 +01:00
parent d0c0430dca
commit 837cb7d1fb
12 changed files with 392 additions and 92 deletions

View file

@ -19,6 +19,7 @@
"clone": "1.0.2", "clone": "1.0.2",
"cors": "2.7.1", "cors": "2.7.1",
"express": "4.13.4", "express": "4.13.4",
"handlebars": "4.0.5",
"mapbox-gl-native": "3.0.2-earcut", "mapbox-gl-native": "3.0.2-earcut",
"mbtiles": "0.8.2", "mbtiles": "0.8.2",
"morgan": "1.7.0", "morgan": "1.7.0",

File diff suppressed because one or more lines are too long

View file

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

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

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TileServer GL</title>
<style>
h3 {display:inline-block;}
</style>
<body>
<h1>TileServer GL</h1>
<h2>Styles</h2>
<ul>
{{#each styles}}
<li>
{{#if serving_raster}}
<img src="/raster/{{@key}}/0/0/0.png" width="32" alt="{{name}} preview" />
{{else}}
N/A
{{/if}}
<h3>{{name}}</h3>
<span>(id: {{@key}})</span>
{{#if serving_style}}
<a href="/styles/{{@key}}/?vector">Viewer (vector)</a>
{{/if}}
{{#if serving_raster}}
<a href="/styles/{{@key}}/?raster">Viewer (raster)</a>
{{/if}}
{{#if serving_style}}
{{#if serving_raster}}
<a href="/styles/{{@key}}/">Viewer (auto)</a>
{{/if}}
{{/if}}
{{#if serving_raster}}
<a href="/raster/{{@key}}.json">TileJSON</a>
{{/if}}
</li>
{{/each}}
</ul>
<h2>Data</h2>
<ul>
{{#each data}}
<li>
<h3>{{name}}</h3>
<span>(id: {{@key}})</span>
<a href="/vector/{{@key}}/">X-Ray viewer</a>
<a href="/vector/{{@key}}.json">TileJSON</a>
</li>
{{/each}}
</ul>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!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>
<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', '/raster/{{id}}.json', { zoomControl: false });
new L.Control.Zoom({ position: 'topright' }).addTo(map);
setTimeout(function() {
new L.Hash(map);
}, 0);
}
</script>
</body>
</html>

View file

@ -143,6 +143,18 @@ module.exports = function(options, repo, params, id) {
}; };
Object.assign(tileJSON, params.tilejson || {}); Object.assign(tileJSON, params.tilejson || {});
tileJSON.tiles = params.domains || options.domains; tileJSON.tiles = params.domains || options.domains;
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
)
];
}
var queue = []; var queue = [];
Object.keys(styleJSON.sources).forEach(function(name) { Object.keys(styleJSON.sources).forEach(function(name) {

View file

@ -10,6 +10,7 @@ var fs = require('fs'),
var clone = require('clone'), var clone = require('clone'),
cors = require('cors'), cors = require('cors'),
express = require('express'), express = require('express'),
handlebars = require('handlebars'),
morgan = require('morgan'); morgan = require('morgan');
var serve_font = require('./serve_font'), var serve_font = require('./serve_font'),
@ -147,8 +148,61 @@ module.exports = function(opts, callback) {
res.send(addTileJSONs(addTileJSONs([], req, 'raster'), req, 'vector')); res.send(addTileJSONs(addTileJSONs([], req, 'raster'), req, 'vector'));
}); });
// serve viewer on the root //------------------------------------
app.use('/', express.static(path.join(__dirname, '../public'))); // serve web presentations
app.use('/', express.static(path.join(__dirname, '../public/resources')));
handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
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) {
styles[id].name = (serving.styles[id] || serving.raster[id]).name;
styles[id].serving_style = serving.styles[id];
styles[id].serving_raster = serving.raster[id];
});
return {
styles: styles,
data: serving.vector
};
});
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.raster[id]).name;
style.serving_style = serving.styles[id];
style.serving_raster = serving.raster[id];
return style;
});
var server = app.listen(process.env.PORT || opts.port, function() { var server = app.listen(process.env.PORT || opts.port, function() {
console.log('Listening at http://%s:%d/', console.log('Listening at http://%s:%d/',