tileblaster 0.4.9 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/UNLICENSE.md +9 -0
- package/bin/tileblaster.js +75 -25
- package/builtins/cache.js +160 -0
- package/builtins/check.js +115 -0
- package/builtins/compress.js +91 -0
- package/builtins/cors.js +33 -0
- package/builtins/deliver.js +34 -0
- package/builtins/dump.js +19 -0
- package/builtins/edit.js +30 -0
- package/builtins/mbtiles.js +52 -0
- package/builtins/modernize.js +57 -0
- package/builtins/noop.js +5 -0
- package/builtins/optimize.js +119 -0
- package/builtins/parse.js +52 -0
- package/builtins/pmtiles.js +80 -0
- package/builtins/sharp.js +46 -0
- package/builtins/tileserver.js +106 -0
- package/builtins/versatiles.js +68 -0
- package/config.dist.js +153 -0
- package/docs/config.md +343 -0
- package/docs/examples.md +257 -0
- package/docs/tileblaster.png +0 -0
- package/docs/tileblaster.svg +261 -0
- package/docs/todo.md +140 -0
- package/lib/debug.js +68 -0
- package/lib/decompress.js +44 -0
- package/lib/load.js +26 -0
- package/lib/mime.js +51 -0
- package/lib/purge.js +109 -0
- package/lib/retrieve.js +15 -0
- package/lib/rfc822date.js +9 -0
- package/lib/router.js +95 -0
- package/lib/store.js +161 -0
- package/lib/strtpl.js +15 -0
- package/lib/tasks.js +50 -0
- package/package.json +25 -13
- package/plugins/example.js +22 -0
- package/readme.md +66 -33
- package/tileblaster.js +469 -0
- package/LICENSE +0 -21
- package/config.js.dist +0 -81
- package/lib/tileblaster.js +0 -789
package/tileblaster.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
|
|
6
|
+
const decompress = require("./lib/decompress");
|
|
7
|
+
const retrieve = require("./lib/retrieve");
|
|
8
|
+
const router = require("./lib/router");
|
|
9
|
+
const strtpl = require("./lib/strtpl");
|
|
10
|
+
const debug = require("./lib/debug");
|
|
11
|
+
const store = require("./lib/store");
|
|
12
|
+
const tasks = require("./lib/tasks");
|
|
13
|
+
const load = require("./lib/load");
|
|
14
|
+
const mime = require("./lib/mime");
|
|
15
|
+
|
|
16
|
+
const quu = require("quu");
|
|
17
|
+
const sharp = load("sharp"); // optional dep
|
|
18
|
+
|
|
19
|
+
const tileblaster = module.exports = function tileblaster(config){
|
|
20
|
+
if (!(this instanceof tileblaster)) return new tileblaster(...arguments);
|
|
21
|
+
const self = this;
|
|
22
|
+
|
|
23
|
+
// configure
|
|
24
|
+
self.config = {};
|
|
25
|
+
self.configure(config);
|
|
26
|
+
|
|
27
|
+
// libraries (expose to plugins)
|
|
28
|
+
self.lib = { retrieve, debug, load, tasks, mime, store, strtpl, decompress, sharp };
|
|
29
|
+
|
|
30
|
+
// plugins
|
|
31
|
+
self.plugins = {};
|
|
32
|
+
self.loadPlugins(self.config.plugins);
|
|
33
|
+
|
|
34
|
+
// load builtins
|
|
35
|
+
self.builtins = {};
|
|
36
|
+
self.loadBuiltins([ "cors", "parse", "check", "noop", "tileserver", "versatiles", "pmtiles", "mbtiles", "edit", "compress", "cache", "deliver", "dump", "modernize", "optimize", "sharp" ]);
|
|
37
|
+
|
|
38
|
+
// assemble task lists for maps
|
|
39
|
+
self.maps = {};
|
|
40
|
+
self.prepareMaps();
|
|
41
|
+
|
|
42
|
+
self.queue = quu(self.config.queue);
|
|
43
|
+
|
|
44
|
+
// router
|
|
45
|
+
self.router = router({ mountpoint: self.config.server.mount });
|
|
46
|
+
|
|
47
|
+
// default route
|
|
48
|
+
self.router.default(function(req, res){
|
|
49
|
+
res.statusCode = 404;
|
|
50
|
+
res.setHeader("content-type", "text/plain");
|
|
51
|
+
res.end("not found.");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// index route
|
|
55
|
+
self.router.route("/", function(req, res){
|
|
56
|
+
res.statusCode = 200;
|
|
57
|
+
/*
|
|
58
|
+
res.setHeader("content-type", "text/plain");
|
|
59
|
+
res.end("tileblaster ready.");
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
// fancy index page
|
|
63
|
+
res.setHeader("content-type", "text/html");
|
|
64
|
+
res.end('<!doctype html><html lang="en"><head><meta charset="utf-8"><title>tileblaster</title><style>body{display: flex;height: 100vh;width: 100vw;margin: 0;padding: 0;justify-content: center;align-items: center;background: rgb(122,32,168);background: radial-gradient(circle, rgba(122,32,168,1) 8%, rgba(252,70,167,1) 100%);}svg{max-width: 90vw;max-height: 90vh;}</style></head><body><svg width="300" height="300" version="1.1" viewBox="0 50 150 150" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="a" x1="400" x2="325" y1="468.69" y2="425.39" gradientTransform="matrix(3 2.359e-8 -2.359e-8 3 -800 -894.08)" gradientUnits="userSpaceOnUse"><stop stop-color="#8332ff" offset="0"/><stop stop-color="#e816ff" stop-opacity=".99492" offset="1"/></linearGradient><filter id="c" x="-.090127" y="-.084742" width="1.1803" height="1.1695" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="3.6361132"/></filter><filter id="b" x="-.060783" y="-.070186" width="1.1216" height="1.1404" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="11.396758"/></filter></defs><g transform="translate(179.54 1.2884)"><g transform="matrix(1.1362 0 0 1.1362 14.238 -16.848)"><path d="m-160.68 91.293-1e-5 64.834 56.148 32.417 56.148-32.417 1e-5 -64.834-56.148-32.417z" fill="#191919" stroke-linecap="round" stroke-width="1.1762"/><g transform="matrix(.22459 -.12967 .12967 .22459 -210.23 70.313)" fill="#31e2ff" filter="url(#b)" opacity=".12376" style="mix-blend-mode:normal"><path d="m137.5 187.23-112.5 194.85 112.5 194.86 150-4e-5 -37.5-64.952-75 5e-5 -75-129.9 37.5-64.951h75l-37.5 64.951 75 129.9 150 1e-5 75-129.9-75-129.9h-75l37.5-64.951-75-1e-5 -37.5 64.951-75 1e-5 37.5-64.951zm150 129.9 75-1e-5 37.5 64.951-37.5 64.951-75-2e-5 -37.5-64.951z" stroke-linecap="round" stroke-width="13.606"/><path d="m137.5 187.23-112.5 194.86 125-173.21 62.5-21.65zm150 0-37.5 64.951 50-43.301 62.5-21.648z" opacity=".18033"/><path d="m225 295.48-87.5 21.65h75zm250 86.604-87.5 108.25-137.5 21.65h150zm-200 173.21-137.5 21.65h150z" opacity=".18033"/></g><path d="m-154.03 95.135-2.6e-4 58.351 50.533 29.176 33.689-19.45-16.844-9.7252-16.844 9.7252-33.689-19.45 1e-4 -19.45 16.844-9.7252-1.1e-4 19.45 33.689 19.45 33.689-19.45 1.86e-4 -38.9-33.689-19.45-16.844 9.7252 9e-5 -19.45-16.844 9.7252-8e-5 19.45-16.845 9.7252 1.1e-4 -19.45zm50.533 9.7248 16.844-9.7252 16.844 9.725-8.8e-5 19.45-16.844 9.7252-16.844-9.725z" fill="#840577" fill-opacity=".39927" stroke-linecap="round" stroke-width="3.5286"/><g transform="matrix(.22459 -.12967 .12967 .22459 -210.23 70.313)"><path d="m137.5 187.23-112.5 194.85 112.5 194.86 150-4e-5 -37.5-64.952-75 5e-5 -75-129.9 37.5-64.951h75l-37.5 64.951 75 129.9 150 1e-5 75-129.9-75-129.9h-75l37.5-64.951-75-1e-5 -37.5 64.951-75 1e-5 37.5-64.951zm150 129.9 75-1e-5 37.5 64.951-37.5 64.951-75-2e-5 -37.5-64.951z" fill="url(#a)" stroke-linecap="round" stroke-width="13.606"/><path d="m137.5 187.23-112.5 194.86 125-173.21 62.5-21.65zm150 0-37.5 64.951 50-43.301 62.5-21.648z" fill="#fff" opacity=".18033"/><path d="m225 295.48-87.5 21.65h75zm250 86.604-87.5 108.25-137.5 21.65h150zm-200 173.21-137.5 21.65h150z" opacity=".18033"/></g><path transform="matrix(.98017 0 0 .98017 -2.0724 2.4526)" d="m-71.412 72.221-5.0958 2.9419v5.8849l-15.288 8.8263h-10.192l-10.193 5.8849-13.386 7.7282-1.2015 0.6935-0.70125 0.40462-25.481 14.711v17.654l5.0963 2.9425 10.192-5.8844 5.0963 2.9419v35.307l5.0958 2.9425 20.385-11.769-5.0963-2.9425v-29.422l10.193-5.8844v-5.8849l5.0958-2.9419v-5.8844l5.0963-2.9424v-5.8844l35.673-20.596-5.0963-2.9425v-5.8844l-5.0958 2.9419z" fill="#010101" filter="url(#c)" opacity=".57673" stroke-width=".23538" style="mix-blend-mode:normal"/><g transform="matrix(.1998 -.11536 .11536 .1998 -191.07 74.765)"><path d="m75 338.79 12.5 21.651-75 129.9 12.5 21.651h100l-12.5-21.651 87.5-151.55z" fill="#949fab"/><path d="m12.5 317.14 37.5-64.952h250l37.5 21.651-37.5 64.952h-275z" fill="#e6e9ed" stroke-linecap="round" stroke-width="4.5354"/><path d="m187.5 360.44h50l-12.5 21.651h-50z" fill="#ed5564" stroke-linecap="round" stroke-width="4.5354"/><path d="m300 252.18-25 43.301-12.5-21.651 12.5-21.651h-25l-25 43.301-11.862-20.546 11.862-22.756h-25l-25 43.301-12.351-21.393 12.352-21.908h100z" fill="#8994a2" stroke-linecap="round" stroke-width="4.5354"/><path d="m337.5 273.83-25 43.301 175-1e-5 -12.5-21.651 12.5-21.651z" fill="#949fab" stroke-linecap="round" stroke-width="4.5354"/><path d="m412.5 273.83 12.5-21.651h25l12.5 21.651h-50" fill="#8994a2" stroke-linecap="round" stroke-width="4.5354"/><path d="m200 338.79 75 1e-5 -12.5 21.651h-75z" fill="#8994a2" stroke-linecap="round" stroke-width="4.5354"/><path d="m12.5 317.14 37.5-64.952h250l-237.5 21.651z" fill="#fff" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m112.5 490.34-12.5-21.651 75-129.9h100l-87.5 21.651z" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m325 295.48 12.5-21.651h150z" fill="#fff" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m312.5 317.14 162.5-21.651 12.5 21.651z" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m337.5 273.83-62.5 64.952h25z" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m87.5 360.44-62.5 151.55-12.5-21.651z" fill="#fff" fill-opacity=".096774" stroke-linecap="round" stroke-width="4.5354"/><path d="m175 382.09h-25l12.5 21.651h-25l12.5 21.651h-25l12.5 21.651h-25l12.5 21.651z" fill-opacity=".13109" stroke-linecap="round" stroke-width="4.5354"/><path d="m175 382.09 12.5-21.651h50z" fill-opacity=".13109" stroke-linecap="round" stroke-width="4.5354"/><path d="m412.5 273.83 12.5-21.651h25z" fill="#fff" fill-opacity=".13109" stroke-linecap="round" stroke-width="4.5354"/></g></g></g></svg></body></html>');
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// map use
|
|
69
|
+
self.router.use(function(req, res, next){
|
|
70
|
+
req.steps = (req.path) ? req.path.split("/").slice(1) : [];
|
|
71
|
+
if (self.config.maps.hasOwnProperty(req.steps[0])) {
|
|
72
|
+
req.route = "@map";
|
|
73
|
+
req.map = req.steps[0];
|
|
74
|
+
req.tilepath = "/"+req.steps.slice(1).join("/");
|
|
75
|
+
};
|
|
76
|
+
next();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// map route
|
|
80
|
+
self.router.route("@map", function(req, res){
|
|
81
|
+
|
|
82
|
+
// mark response stream if something pipes into it
|
|
83
|
+
// to avoid sending to response stream twice
|
|
84
|
+
res.once("pipe", function(){
|
|
85
|
+
res.piped = true;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// assemble args and initial data
|
|
89
|
+
const args = {
|
|
90
|
+
req, res,
|
|
91
|
+
data: {
|
|
92
|
+
map: req.map,
|
|
93
|
+
req: {},
|
|
94
|
+
tile: {
|
|
95
|
+
default: true,
|
|
96
|
+
// default tile
|
|
97
|
+
dest: req.path, // destination file - modification use extensions?
|
|
98
|
+
buffer: Buffer.alloc(0), // empty buffer
|
|
99
|
+
status: 204,
|
|
100
|
+
headers: {
|
|
101
|
+
"expires": new Date(Date.now()).toUTCString(),
|
|
102
|
+
}, // response header specific to tile
|
|
103
|
+
// explicit mimetye
|
|
104
|
+
mimetype: "application/octet-stream", // mimetype
|
|
105
|
+
type: "bin", // type
|
|
106
|
+
compression: false,
|
|
107
|
+
language: null,
|
|
108
|
+
expires: true, // instant expires
|
|
109
|
+
},
|
|
110
|
+
tiles: [], // modified versions
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
self.queue.push(function(next){ // queue tasks
|
|
115
|
+
|
|
116
|
+
// create tasks from map
|
|
117
|
+
tasks(self.maps[req.map]).run(args, function(err, { res }){
|
|
118
|
+
|
|
119
|
+
next(); // free queue
|
|
120
|
+
|
|
121
|
+
// FIXME provide more context
|
|
122
|
+
if (err) debug.error(err);
|
|
123
|
+
|
|
124
|
+
// end if response is already sent
|
|
125
|
+
if (!res.writable || res.destroyed || res.finished || res.closed || res.piped) return; // FIXME handle errors anyway
|
|
126
|
+
|
|
127
|
+
// send default error FIXME configure verbose errors
|
|
128
|
+
if (err) {
|
|
129
|
+
res.statusCode = 500;
|
|
130
|
+
res.setHeader("content-type", "text/plain");
|
|
131
|
+
res.end("Error.");
|
|
132
|
+
return;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// default response: no content
|
|
136
|
+
res.statusCode = 200; // FIXME 204
|
|
137
|
+
res.setHeader("content-length", "0");
|
|
138
|
+
res.end("");
|
|
139
|
+
return;
|
|
140
|
+
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// listen
|
|
148
|
+
self.servers = [];
|
|
149
|
+
self.listen(self.router.serve);
|
|
150
|
+
|
|
151
|
+
// handle messages from main thred
|
|
152
|
+
process.on("message", function(message){
|
|
153
|
+
if (message === "shutdown") self.shutdown();
|
|
154
|
+
// FIXME: reload config without shutdown?
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// shutdown on SIGINT
|
|
158
|
+
process.on("SIGINT", function(){
|
|
159
|
+
debug.info("SIGINT received");
|
|
160
|
+
self.shutdown();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// shutdown on SIGTERM
|
|
164
|
+
process.on("SIGTERM", function(){
|
|
165
|
+
debug.info("SIGTERM received");
|
|
166
|
+
self.shutdown();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return this;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// graceful shutdown
|
|
173
|
+
tileblaster.prototype.shutdown = function(){
|
|
174
|
+
const self = this;
|
|
175
|
+
self.shutdown = function(){}; // ensure shutdown only runs once
|
|
176
|
+
debug.info("shutting down");
|
|
177
|
+
let closed = 0;
|
|
178
|
+
self.servers.forEach(function(server){
|
|
179
|
+
server.close(function(){
|
|
180
|
+
(function(fn){
|
|
181
|
+
if (!server.socket) return fn();
|
|
182
|
+
fs.unlink(server.socket, fn);
|
|
183
|
+
})(function(){
|
|
184
|
+
if (++closed === self.servers.length) {
|
|
185
|
+
debug.info("All Servers Closed");
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
// watchdog
|
|
192
|
+
setTimeout(function(){
|
|
193
|
+
debug.warn("Closing servers timed out");
|
|
194
|
+
process.exit(1);
|
|
195
|
+
},3000);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// prepare jobs for maps
|
|
199
|
+
tileblaster.prototype.prepareMaps = function(){
|
|
200
|
+
const self = this;
|
|
201
|
+
self.maps = Object.entries(self.config.maps).reduce(function(maps, [ mapid, map ]){
|
|
202
|
+
maps[mapid] = self.prepareJobs(mapid, map);
|
|
203
|
+
return maps;
|
|
204
|
+
},{});
|
|
205
|
+
|
|
206
|
+
return this;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// prepare jobs FIXME merge with prepareMaps?
|
|
210
|
+
tileblaster.prototype.prepareJobs = function(mapid, map){
|
|
211
|
+
const self = this;
|
|
212
|
+
return map.map(function(job){
|
|
213
|
+
|
|
214
|
+
// job is a straight function
|
|
215
|
+
if (typeof job === "function") return job;
|
|
216
|
+
|
|
217
|
+
// if not an object, passthrough
|
|
218
|
+
if (typeof job !== "object") {
|
|
219
|
+
debug.warn("Invalid Task in map '%s'", mapid);
|
|
220
|
+
return function({}, next){ next(); };
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// job is builtin
|
|
224
|
+
if (job.hasOwnProperty("builtin")) {
|
|
225
|
+
if (self.builtins.hasOwnProperty(job.builtin)) {
|
|
226
|
+
const fn = function(args, next, skip){
|
|
227
|
+
return self.builtins[job.builtin].call(self, { ...args, opts: job }, next, skip);
|
|
228
|
+
};
|
|
229
|
+
fn.label = job.builtin;
|
|
230
|
+
return fn;
|
|
231
|
+
} else {
|
|
232
|
+
// unknown plugin, pass through
|
|
233
|
+
debug.warn("Unknown builtin '%s' in map '%s'", job.builtin, mapid);
|
|
234
|
+
return function({}, next){ next(); };
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// job is plugin
|
|
239
|
+
if (job.hasOwnProperty("plugin")) {
|
|
240
|
+
if (self.plugins.hasOwnProperty(job.plugin)) {
|
|
241
|
+
const fn = function(args, next, skip){
|
|
242
|
+
return self.plugins[job.plugin].call(self, { ...args, opts: job }, next, skip);
|
|
243
|
+
};
|
|
244
|
+
fn.label = "plugin:"+job.plugin;
|
|
245
|
+
return fn;
|
|
246
|
+
} else {
|
|
247
|
+
// unknown plugin, pass through
|
|
248
|
+
debug.warn("Unknown plugin '%s' in map '%s'", job.plugin, mapid);
|
|
249
|
+
return function({}, next){ next(); };
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// unknown job type, passthrough with warning
|
|
254
|
+
debug.warn("Invalid Task type in map '%s'", reqmapid);
|
|
255
|
+
return function({}, next){ next(); };
|
|
256
|
+
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// load builtins
|
|
261
|
+
tileblaster.prototype.loadBuiltins = function(builtins){
|
|
262
|
+
const self = this;
|
|
263
|
+
self.builtins = builtins.reduce(function(builtins, builtin){
|
|
264
|
+
let builtinPath = path.resolve(__dirname,"builtins",builtin);
|
|
265
|
+
if (load.exists(builtinPath)) {
|
|
266
|
+
debug.info("Loaded builtin '%s'", builtin);
|
|
267
|
+
builtins[builtin] = require(builtinPath);
|
|
268
|
+
} else {
|
|
269
|
+
debug.warn("Missing builtin: '%s'", builtin);
|
|
270
|
+
};
|
|
271
|
+
return builtins;
|
|
272
|
+
},{});
|
|
273
|
+
return this;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// load plugins
|
|
277
|
+
tileblaster.prototype.loadPlugins = function(){
|
|
278
|
+
const self = this;
|
|
279
|
+
|
|
280
|
+
self.plugins = Object.entries(self.config.plugins).reduce(function(plugins, [ name, plugin ]){
|
|
281
|
+
|
|
282
|
+
let pluginname = name.trim().toLowerCase().replace(/[^a-z0-9\-\_\.]+/g,'');
|
|
283
|
+
if (pluginname !== name) debug.warn("Warning: Plugin name has been sanitized: '%s' → '%s'", name, pluginname);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
if (load.exists(plugin)) {
|
|
287
|
+
plugins[pluginname] = require(plugin);
|
|
288
|
+
debug.info("Loaded Plugin '%s'", pluginname);
|
|
289
|
+
} else {
|
|
290
|
+
let localPlugin = path.resolve(self.config.paths.plugins, plugin);
|
|
291
|
+
if (load.exists(localPlugin)) {
|
|
292
|
+
plugins[pluginname] = require(localPlugin);
|
|
293
|
+
debug.info("Loaded Plugin '%s'", pluginname);
|
|
294
|
+
} else {
|
|
295
|
+
let packagePlugin = path.resolve(__dirname, "plugins", plugin);
|
|
296
|
+
if (load.exists(packagePlugin)) {
|
|
297
|
+
plugins[pluginname] = require(packagePlugin);
|
|
298
|
+
debug.info("Loaded Plugin '%s'", pluginname);
|
|
299
|
+
} else {
|
|
300
|
+
debug.warn("Error loading plugin '%s': Not found", pluginname);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch(err) {
|
|
305
|
+
debug.warn("Error loading plugin '%s':", pluginname, err);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return plugins;
|
|
309
|
+
|
|
310
|
+
},{});
|
|
311
|
+
|
|
312
|
+
return this;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// read config and fill in the blanks
|
|
316
|
+
tileblaster.prototype.configure = function(config){
|
|
317
|
+
const self = this;
|
|
318
|
+
|
|
319
|
+
// check config file version
|
|
320
|
+
if (!config.hasOwnProperty("version") || config.version !== 1) {
|
|
321
|
+
debug.error("Config has no version property. Possibly an old config file? Exiting.")
|
|
322
|
+
process.exit(1);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// check if any maps are configured
|
|
326
|
+
if (!config.hasOwnProperty("maps") || Object.keys(config.maps) === 0) {
|
|
327
|
+
debug.error("Config has no maps configured. Exiting.")
|
|
328
|
+
process.exit(1);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// check if any listen instructions are given
|
|
332
|
+
if (!config.hasOwnProperty("listen") || config.listen.length === 0) {
|
|
333
|
+
debug.error("Config has no listen instructions. Exiting.")
|
|
334
|
+
process.exit(1);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// set config
|
|
338
|
+
self.config = {
|
|
339
|
+
id: "tileblaster",
|
|
340
|
+
threads: 1,
|
|
341
|
+
queue: 12,
|
|
342
|
+
url: null,
|
|
343
|
+
mount: null,
|
|
344
|
+
paths: {},
|
|
345
|
+
plugins: {},
|
|
346
|
+
listen: [],
|
|
347
|
+
maps: {},
|
|
348
|
+
...config,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// clamp number of threads to number of cores and queue size
|
|
352
|
+
self.config.threads = Math.max(1, Math.min(self.config.threads, os.cpus().length));
|
|
353
|
+
self.config.queue = Math.max(1, Math.min(self.config.queue, 100));
|
|
354
|
+
|
|
355
|
+
// warn user if queue size is less than 12 over all threads
|
|
356
|
+
if ((self.config.queue * self.config.threads) < 12) debug.warn("Warning: Queue size of %d is pretty small.", self.config.queue);
|
|
357
|
+
|
|
358
|
+
// url and mount
|
|
359
|
+
if (!self.config.server.url) self.config.server.url = "/";
|
|
360
|
+
if (!self.config.server.mount) self.config.server.mount = (self.config.server.url === "/") ? "/" : self.config.server.url.replace(/^https?:\/\/.*?\//,"/");
|
|
361
|
+
|
|
362
|
+
// remove trailing slashes from url and mount
|
|
363
|
+
while (self.config.server.url.length > 1 && self.config.server.url.charCodeAt(self.config.server.url.length-1) === 47) self.config.server.url = self.config.server.url.slice(0, -1);
|
|
364
|
+
while (self.config.server.mount.length > 1 && self.config.server.mount.charCodeAt(self.config.server.mount.length-1) === 47) self.config.server.mount = self.config.server.mount.slice(0, -1);
|
|
365
|
+
|
|
366
|
+
// paths
|
|
367
|
+
if (!self.config.paths.work) self.config.paths.work = path.resolve(os.homedir(), "tileblaster");
|
|
368
|
+
["data","logs","plugins","sockets"].forEach(function(p){
|
|
369
|
+
if (!self.config.paths[p]) self.config.paths[p] = path.resolve(self.config.paths.work, p);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// default host to localhost if port is set
|
|
373
|
+
if (self.config.port && !self.config.host) self.config.host = "localhost";
|
|
374
|
+
|
|
375
|
+
// listen
|
|
376
|
+
self.config.listen = self.config.listen.filter(function(listen){
|
|
377
|
+
return listen.hasOwnProperty("port") || listen.hasOwnProperty("socket");
|
|
378
|
+
}).map(function(listen){
|
|
379
|
+
// ensure hostname is set
|
|
380
|
+
if (listen.hasOwnProperty("port")) {
|
|
381
|
+
if (typeof listen.port !== "number") listen.port = parseInt(listen.port,10);
|
|
382
|
+
if (!listen.host) listen.host = "localhost";
|
|
383
|
+
}
|
|
384
|
+
else if (listen.hasOwnProperty("socket")) {
|
|
385
|
+
listen.socket = path.resolve(self.config.paths.sockets, listen.socket);
|
|
386
|
+
};
|
|
387
|
+
return listen;
|
|
388
|
+
}).filter(function(listen){
|
|
389
|
+
// check for port and host types and NaN
|
|
390
|
+
return listen.socket || (!isNaN(listen.port) && typeof listen.host === "string");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// check again if any listen instructions are given
|
|
394
|
+
if (self.config.listen.length === 0) {
|
|
395
|
+
debug.error("Config has no valid listen instructions. Exiting.")
|
|
396
|
+
process.exit(1);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// maps
|
|
400
|
+
self.config.maps = Object.entries(config.maps).reduce(function(maps, [ id, map ]){
|
|
401
|
+
let mapid = id.trim().toLowerCase().replace(/[^a-z0-9\-\_\.]+/g,'');
|
|
402
|
+
if (mapid !== id) debug.warn("Warning: Map id has been sanitized: '%s' → '%s'", id, mapid);
|
|
403
|
+
maps[mapid] = map;
|
|
404
|
+
return maps;
|
|
405
|
+
},{});
|
|
406
|
+
|
|
407
|
+
return this;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// listen handler
|
|
411
|
+
tileblaster.prototype.listen = function(router){
|
|
412
|
+
const self = this;
|
|
413
|
+
|
|
414
|
+
self.config.listen.forEach(function(listen){
|
|
415
|
+
|
|
416
|
+
const server = http.createServer({ keepAlive: true }, function(){
|
|
417
|
+
router.call(self.router, ...arguments);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (listen.port) {
|
|
421
|
+
|
|
422
|
+
server.listen(listen.port, listen.port, function(err){
|
|
423
|
+
if (err) return debug.error("listen: ERROR binding port '%s:%d':", listen.host, listen.port, err);
|
|
424
|
+
debug.info("Listening on '%s:%d'", listen.host, listen.port);
|
|
425
|
+
self.servers.push(server);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
} else if (listen.socket) {
|
|
429
|
+
|
|
430
|
+
// create different sockets per instance
|
|
431
|
+
if (self.config.threads > 1) {
|
|
432
|
+
let ext = path.extname(listen.socket);
|
|
433
|
+
listen.socket = path.join(path.dirname(listen.socket), path.basename(listen.socket, ext) + self.config.id + ext);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ensure socket dir exists
|
|
437
|
+
fs.mkdir(path.dirname(listen.socket), { recursive: true }, function(err){
|
|
438
|
+
if (err && err.code !== "ENOENT") return debug.error("Creating socket dir '%s':", path.dirname(listen.socket), err);
|
|
439
|
+
|
|
440
|
+
// inlink old socket
|
|
441
|
+
fs.unlink(listen.socket, function(err) { // try unlink leftover socket
|
|
442
|
+
if (err && err.code !== "ENOENT") return debug.error("Deleting socket '%s':", listen.socket, err);
|
|
443
|
+
|
|
444
|
+
server.listen(listen.socket, function(err) {
|
|
445
|
+
if (err) return debug.error("Binding to socket '%s':", listen.socket, err);
|
|
446
|
+
|
|
447
|
+
// store socket path
|
|
448
|
+
server.socket = listen.socket;
|
|
449
|
+
|
|
450
|
+
debug.info("Listening on socket '%s'", listen.socket);
|
|
451
|
+
self.servers.push(server);
|
|
452
|
+
if (listen.mode) fs.chmod(listen.socket, listen.mode, function(err){
|
|
453
|
+
if (err) return debug.error("Changing permissions of socket '%s' to '%s':", listen.socket, listen.perms.toString(8), err);
|
|
454
|
+
});
|
|
455
|
+
if (listen.group) fs.chown(listen.socket, os.userInfo().uid, listen.group, function(err){
|
|
456
|
+
if (err) return debug.error("Changing gid of socket '%s' to '%s':", listen.socket, listen.gid, err);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
return self;
|
|
469
|
+
};
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
This is free and unencumbered software released into the public domain.
|
|
2
|
-
|
|
3
|
-
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
4
|
-
software, either in source code form or as a compiled binary, for any purpose,
|
|
5
|
-
commercial or non-commercial, and by any means.
|
|
6
|
-
|
|
7
|
-
In jurisdictions that recognize copyright laws, the author or authors of this
|
|
8
|
-
software dedicate any and all copyright interest in the software to the public
|
|
9
|
-
domain. We make this dedication for the benefit of the public at large and to
|
|
10
|
-
the detriment of our heirs and successors. We intend this dedication to be an
|
|
11
|
-
overt act of relinquishment in perpetuity of all present and future rights to
|
|
12
|
-
this software under copyright law.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
18
|
-
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
19
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
-
|
|
21
|
-
For more information, please refer to <https://unlicense.org/>
|
package/config.js.dist
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
|
|
3
|
-
// path to socket file
|
|
4
|
-
"socket": "/path/to/tileblaster.sock",
|
|
5
|
-
|
|
6
|
-
// path to tile directory
|
|
7
|
-
"tiles": "/path/to/tiles",
|
|
8
|
-
|
|
9
|
-
// parallel request queue size
|
|
10
|
-
"queue": 100,
|
|
11
|
-
|
|
12
|
-
// parallel request queue size
|
|
13
|
-
"id": "tileblaster",
|
|
14
|
-
|
|
15
|
-
// provide errors as response headers
|
|
16
|
-
"hints": false,
|
|
17
|
-
|
|
18
|
-
// nsa heartbeat server, if so desired
|
|
19
|
-
"heartbeat": "udp4://nsa.example.com:30826",
|
|
20
|
-
|
|
21
|
-
// clean up every so often
|
|
22
|
-
"cleanup": "15m",
|
|
23
|
-
|
|
24
|
-
// maps
|
|
25
|
-
"maps": {
|
|
26
|
-
|
|
27
|
-
// map id, accessible via //server/mapid/z/x/y.ext
|
|
28
|
-
"example": {
|
|
29
|
-
|
|
30
|
-
// backend url:
|
|
31
|
-
// * {s} subdomains specified in sub
|
|
32
|
-
// * {z}, {x}, {y} tile coordinates
|
|
33
|
-
// * {e} extension
|
|
34
|
-
// * {r} resolution marker
|
|
35
|
-
"url": "https://{s}.tiles.example.com/tiles/{z}/{x}/{y}{r}.{e}",
|
|
36
|
-
|
|
37
|
-
// url points to versatiles container (→ https://github.com/versatiles-org/versatiles-spec)
|
|
38
|
-
"versatiles": false,
|
|
39
|
-
|
|
40
|
-
// backend uses tms instead of zxy
|
|
41
|
-
"tms": false,
|
|
42
|
-
|
|
43
|
-
// possible extensions
|
|
44
|
-
"ext": ["mvt","json","topojson","png","jpg"],
|
|
45
|
-
|
|
46
|
-
// valid zoom levels
|
|
47
|
-
"zoom": [0,16],
|
|
48
|
-
|
|
49
|
-
// valid subdomains
|
|
50
|
-
"sub": ["a","b","c","d"],
|
|
51
|
-
|
|
52
|
-
// bounding box, west, south, east, north
|
|
53
|
-
"bbox": [-180,-90,180,90],
|
|
54
|
-
|
|
55
|
-
// valid mimetypes from backend server
|
|
56
|
-
"mime": ["application/vnd.mapbox-vector-tile","application/x-protobuf","application/json"],
|
|
57
|
-
|
|
58
|
-
// valid resolution markers
|
|
59
|
-
"res": ["@2x","@4x"],
|
|
60
|
-
|
|
61
|
-
// optimize tiles
|
|
62
|
-
// * .png with `optipng`
|
|
63
|
-
// * .jpg with `mozjpeg`
|
|
64
|
-
"optimize": true,
|
|
65
|
-
|
|
66
|
-
// compress tiles (only makes sense for non-rastered tiles like json, pbf, mvt)
|
|
67
|
-
"compress": [ "gz", "br" ],
|
|
68
|
-
|
|
69
|
-
// write tiles to disk
|
|
70
|
-
"cache": true,
|
|
71
|
-
|
|
72
|
-
// minimum time tiles are kept
|
|
73
|
-
"expires": "1d",
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
// more backends here
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
};
|