New index and viewer (+ templating system)
This commit is contained in:
parent
d0c0430dca
commit
837cb7d1fb
12 changed files with 392 additions and 92 deletions
|
@ -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
162
public/resources/leaflet-hash.js
Normal file
162
public/resources/leaflet-hash.js
Normal 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);
|
1
public/resources/mapbox.css
Normal file
1
public/resources/mapbox.css
Normal file
File diff suppressed because one or more lines are too long
69
public/resources/mapbox.js
Normal file
69
public/resources/mapbox.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -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>
|
|
52
public/templates/index.tmpl
Normal file
52
public/templates/index.tmpl
Normal 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>
|
39
public/templates/viewer.tmpl
Normal file
39
public/templates/viewer.tmpl
Normal 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>
|
|
@ -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) {
|
||||||
|
|
|
@ -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/',
|
||||||
|
|
Loading…
Reference in a new issue