@signalk/charts-plugin 2.3.1 → 3.0.0
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/README.md +28 -8
- package/package.json +23 -4
- package/plugin/charts.d.ts +1 -0
- package/plugin/charts.js +254 -0
- package/plugin/constants.d.ts +4 -0
- package/plugin/constants.js +7 -0
- package/plugin/index.d.ts +1 -0
- package/plugin/index.js +352 -0
- package/plugin/types.d.ts +38 -0
- package/plugin/types.js +2 -0
- package/.github/workflows/publish.yml +0 -18
- package/.github/workflows/release_on_tag.yml +0 -27
- package/.github/workflows/require_pr_label.yml +0 -13
- package/.github/workflows/test.yml +0 -22
- package/index.js +0 -1
- package/src/charts.js +0 -175
- package/src/constants.js +0 -3
- package/src/plugin.js +0 -231
- package/test/charts/empty-test/.gitkeep +0 -0
- package/test/charts/test.mbtiles +0 -0
- package/test/charts/tms-tiles/4/8/10.png +0 -0
- package/test/charts/tms-tiles/4/8/11.png +0 -0
- package/test/charts/tms-tiles/4/9/10.png +0 -0
- package/test/charts/tms-tiles/4/9/11.png +0 -0
- package/test/charts/tms-tiles/5/16/21.png +0 -0
- package/test/charts/tms-tiles/5/16/22.png +0 -0
- package/test/charts/tms-tiles/5/16/23.png +0 -0
- package/test/charts/tms-tiles/5/17/21.png +0 -0
- package/test/charts/tms-tiles/5/17/22.png +0 -0
- package/test/charts/tms-tiles/5/17/23.png +0 -0
- package/test/charts/tms-tiles/5/18/21.png +0 -0
- package/test/charts/tms-tiles/5/18/22.png +0 -0
- package/test/charts/tms-tiles/5/18/23.png +0 -0
- package/test/charts/tms-tiles/tilemapresource.xml +0 -14
- package/test/charts/unpacked-tiles/3/2/3.png +0 -0
- package/test/charts/unpacked-tiles/4/4/6.png +0 -0
- package/test/charts/unpacked-tiles/4/5/6.png +0 -0
- package/test/charts/unpacked-tiles/metadata.json +0 -15
- package/test/charts-2/test2.mbtiles +0 -0
- package/test/expected-charts.json +0 -53
- package/test/plugin-test.js +0 -196
package/plugin/index.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const bluebird = __importStar(require("bluebird"));
|
|
39
|
+
const path_1 = __importDefault(require("path"));
|
|
40
|
+
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const _ = __importStar(require("lodash"));
|
|
42
|
+
const charts_1 = require("./charts");
|
|
43
|
+
const constants_1 = require("./constants");
|
|
44
|
+
const MIN_ZOOM = 1;
|
|
45
|
+
const MAX_ZOOM = 24;
|
|
46
|
+
module.exports = (app) => {
|
|
47
|
+
let chartProviders = {};
|
|
48
|
+
let pluginStarted = false;
|
|
49
|
+
let props = {
|
|
50
|
+
chartPaths: [],
|
|
51
|
+
onlineChartProviders: []
|
|
52
|
+
};
|
|
53
|
+
const configBasePath = app.config.configPath;
|
|
54
|
+
const defaultChartsPath = path_1.default.join(configBasePath, '/charts');
|
|
55
|
+
const serverMajorVersion = app.config.version ? parseInt(app.config.version.split('.')[0]) : '1';
|
|
56
|
+
ensureDirectoryExists(defaultChartsPath);
|
|
57
|
+
// ******** REQUIRED PLUGIN DEFINITION *******
|
|
58
|
+
const CONFIG_SCHEMA = {
|
|
59
|
+
title: 'Signal K Charts',
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
chartPaths: {
|
|
63
|
+
type: 'array',
|
|
64
|
+
title: 'Chart paths',
|
|
65
|
+
description: `Add one or more paths to find charts. Defaults to "${defaultChartsPath}"`,
|
|
66
|
+
items: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
title: 'Path',
|
|
69
|
+
description: `Path for chart files, relative to "${configBasePath}"`
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
onlineChartProviders: {
|
|
73
|
+
type: 'array',
|
|
74
|
+
title: 'Online chart providers',
|
|
75
|
+
items: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
title: 'Provider',
|
|
78
|
+
required: ['name', 'minzoom', 'maxzoom', 'format', 'url'],
|
|
79
|
+
properties: {
|
|
80
|
+
name: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
title: 'Name'
|
|
83
|
+
},
|
|
84
|
+
description: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
title: 'Description'
|
|
87
|
+
},
|
|
88
|
+
minzoom: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
title: `Minimum zoom level, between [${MIN_ZOOM}, ${MAX_ZOOM}]`,
|
|
91
|
+
maximum: MAX_ZOOM,
|
|
92
|
+
minimum: MIN_ZOOM,
|
|
93
|
+
default: MIN_ZOOM
|
|
94
|
+
},
|
|
95
|
+
maxzoom: {
|
|
96
|
+
type: 'number',
|
|
97
|
+
title: `Maximum zoom level, between [${MIN_ZOOM}, ${MAX_ZOOM}]`,
|
|
98
|
+
maximum: MAX_ZOOM,
|
|
99
|
+
minimum: MIN_ZOOM,
|
|
100
|
+
default: 15
|
|
101
|
+
},
|
|
102
|
+
serverType: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
title: 'Map source / server type',
|
|
105
|
+
default: 'tilelayer',
|
|
106
|
+
enum: ['tilelayer', 'S-57', 'WMS', 'WMTS', 'mapstyleJSON', 'tileJSON'],
|
|
107
|
+
description: 'Map data source type served by the supplied url. (Use tilelayer for xyz / tms tile sources.)'
|
|
108
|
+
},
|
|
109
|
+
format: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
title: 'Format',
|
|
112
|
+
default: 'png',
|
|
113
|
+
enum: ['png', 'jpg', 'pbf'],
|
|
114
|
+
description: 'Format of map tiles: raster (png, jpg, etc.) / vector (pbf).'
|
|
115
|
+
},
|
|
116
|
+
url: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
title: 'URL',
|
|
119
|
+
description: 'Map URL (for tilelayer include {z}, {x} and {y} parameters, e.g. "http://example.org/{z}/{x}/{y}.png")'
|
|
120
|
+
},
|
|
121
|
+
style: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
title: 'Vector Map Style',
|
|
124
|
+
description: 'Path to file containing map style definitions for Vector maps (e.g. "http://example.org/styles/mymapstyle.json")'
|
|
125
|
+
},
|
|
126
|
+
layers: {
|
|
127
|
+
type: 'array',
|
|
128
|
+
title: 'Layers',
|
|
129
|
+
description: 'List of map layer ids to display. (Use with WMS / WMTS types.)',
|
|
130
|
+
items: {
|
|
131
|
+
title: 'Layer Name',
|
|
132
|
+
description: 'Name of layer to display',
|
|
133
|
+
type: 'string'
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const CONFIG_UISCHEMA = {};
|
|
142
|
+
const plugin = {
|
|
143
|
+
id: 'charts',
|
|
144
|
+
name: 'Signal K Charts',
|
|
145
|
+
schema: () => CONFIG_SCHEMA,
|
|
146
|
+
uiSchema: () => CONFIG_UISCHEMA,
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
+
start: (settings) => {
|
|
149
|
+
return doStartup(settings); // return required for tests
|
|
150
|
+
},
|
|
151
|
+
stop: () => {
|
|
152
|
+
app.setPluginStatus('stopped');
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const doStartup = (config) => {
|
|
156
|
+
app.debug('** loaded config: ', config);
|
|
157
|
+
props = Object.assign({}, config);
|
|
158
|
+
const chartPaths = _.isEmpty(props.chartPaths)
|
|
159
|
+
? [defaultChartsPath]
|
|
160
|
+
: resolveUniqueChartPaths(props.chartPaths, configBasePath);
|
|
161
|
+
const onlineProviders = _.reduce(props.onlineChartProviders, (result, data) => {
|
|
162
|
+
const provider = convertOnlineProviderConfig(data);
|
|
163
|
+
result[provider.identifier] = provider;
|
|
164
|
+
return result;
|
|
165
|
+
}, {});
|
|
166
|
+
app.debug(`Start charts plugin. Chart paths: ${chartPaths.join(', ')}, online charts: ${Object.keys(onlineProviders).length}`);
|
|
167
|
+
// Do not register routes if plugin has been started once already
|
|
168
|
+
pluginStarted === false && registerRoutes();
|
|
169
|
+
pluginStarted = true;
|
|
170
|
+
const urlBase = `${app.config.ssl ? 'https' : 'http'}://localhost:${'getExternalPort' in app.config ? app.config.getExternalPort() : 3000}`;
|
|
171
|
+
app.debug('**urlBase**', urlBase);
|
|
172
|
+
app.setPluginStatus('Started');
|
|
173
|
+
const loadProviders = bluebird
|
|
174
|
+
.mapSeries(chartPaths, (chartPath) => (0, charts_1.findCharts)(chartPath))
|
|
175
|
+
.then((list) => _.reduce(list, (result, charts) => _.merge({}, result, charts), {}));
|
|
176
|
+
return loadProviders
|
|
177
|
+
.then((charts) => {
|
|
178
|
+
app.debug(`Chart plugin: Found ${_.keys(charts).length} charts from ${chartPaths.join(', ')}.`);
|
|
179
|
+
chartProviders = _.merge({}, charts, onlineProviders);
|
|
180
|
+
})
|
|
181
|
+
.catch((e) => {
|
|
182
|
+
console.error(`Error loading chart providers`, e.message);
|
|
183
|
+
chartProviders = {};
|
|
184
|
+
app.setPluginError(`Error loading chart providers`);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const registerRoutes = () => {
|
|
188
|
+
app.debug('** Registering API paths **');
|
|
189
|
+
app.get(`/signalk/:version(v[1-2])/api/resources/charts/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
190
|
+
const { identifier, z, x, y } = req.params;
|
|
191
|
+
const provider = chartProviders[identifier];
|
|
192
|
+
if (!provider) {
|
|
193
|
+
return res.sendStatus(404);
|
|
194
|
+
}
|
|
195
|
+
switch (provider._fileFormat) {
|
|
196
|
+
case 'directory':
|
|
197
|
+
return serveTileFromFilesystem(res, provider, parseInt(z), parseInt(x), parseInt(y));
|
|
198
|
+
case 'mbtiles':
|
|
199
|
+
return serveTileFromMbtiles(res, provider, parseInt(z), parseInt(x), parseInt(y));
|
|
200
|
+
default:
|
|
201
|
+
console.log(`Unknown chart provider fileformat ${provider._fileFormat}`);
|
|
202
|
+
res.status(500).send();
|
|
203
|
+
}
|
|
204
|
+
}));
|
|
205
|
+
app.debug('** Registering v1 API paths **');
|
|
206
|
+
app.get(constants_1.apiRoutePrefix[1] + '/charts/:identifier', (req, res) => {
|
|
207
|
+
const { identifier } = req.params;
|
|
208
|
+
const provider = chartProviders[identifier];
|
|
209
|
+
if (provider) {
|
|
210
|
+
return res.json(sanitizeProvider(provider));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
return res.status(404).send('Not found');
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
app.get(constants_1.apiRoutePrefix[1] + '/charts', (req, res) => {
|
|
217
|
+
const sanitized = _.mapValues(chartProviders, (provider) => sanitizeProvider(provider));
|
|
218
|
+
res.json(sanitized);
|
|
219
|
+
});
|
|
220
|
+
// v2 routes
|
|
221
|
+
if (serverMajorVersion === 2) {
|
|
222
|
+
app.debug('** Registering v2 API paths **');
|
|
223
|
+
registerAsProvider();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
// Resources API provider registration
|
|
227
|
+
const registerAsProvider = () => {
|
|
228
|
+
app.debug('** Registering as Resource Provider for `charts` **');
|
|
229
|
+
try {
|
|
230
|
+
app.registerResourceProvider({
|
|
231
|
+
type: 'charts',
|
|
232
|
+
methods: {
|
|
233
|
+
listResources: (params) => {
|
|
234
|
+
app.debug(`** listResources()`, params);
|
|
235
|
+
return Promise.resolve(_.mapValues(chartProviders, (provider) => sanitizeProvider(provider, 2)));
|
|
236
|
+
},
|
|
237
|
+
getResource: (id) => {
|
|
238
|
+
app.debug(`** getResource()`, id);
|
|
239
|
+
const provider = chartProviders[id];
|
|
240
|
+
if (provider) {
|
|
241
|
+
return Promise.resolve(sanitizeProvider(provider, 2));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
throw new Error('Chart not found!');
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
|
+
setResource: (id, value) => {
|
|
249
|
+
throw new Error(`Not implemented!\n Cannot set ${id} to ${value}`);
|
|
250
|
+
},
|
|
251
|
+
deleteResource: (id) => {
|
|
252
|
+
throw new Error(`Not implemented!\n Cannot delete ${id}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
app.debug('Failed Provider Registration!');
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
return plugin;
|
|
262
|
+
};
|
|
263
|
+
const responseHttpOptions = {
|
|
264
|
+
headers: {
|
|
265
|
+
'Cache-Control': 'public, max-age=7776000' // 90 days
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const resolveUniqueChartPaths = (chartPaths, configBasePath) => {
|
|
269
|
+
const paths = _.map(chartPaths, (chartPath) => path_1.default.resolve(configBasePath, chartPath));
|
|
270
|
+
return _.uniq(paths);
|
|
271
|
+
};
|
|
272
|
+
const convertOnlineProviderConfig = (provider) => {
|
|
273
|
+
const id = _.kebabCase(_.deburr(provider.name));
|
|
274
|
+
const data = {
|
|
275
|
+
identifier: id,
|
|
276
|
+
name: provider.name,
|
|
277
|
+
description: provider.description,
|
|
278
|
+
bounds: [-180, -90, 180, 90],
|
|
279
|
+
minzoom: Math.min(Math.max(1, provider.minzoom), 19),
|
|
280
|
+
maxzoom: Math.min(Math.max(1, provider.maxzoom), 19),
|
|
281
|
+
format: provider.format,
|
|
282
|
+
scale: 250000,
|
|
283
|
+
type: provider.serverType ? provider.serverType : 'tilelayer',
|
|
284
|
+
style: provider.style ? provider.style : null,
|
|
285
|
+
v1: {
|
|
286
|
+
tilemapUrl: provider.url,
|
|
287
|
+
chartLayers: provider.layers ? provider.layers : null
|
|
288
|
+
},
|
|
289
|
+
v2: {
|
|
290
|
+
url: provider.url,
|
|
291
|
+
layers: provider.layers ? provider.layers : null
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
return data;
|
|
295
|
+
};
|
|
296
|
+
const sanitizeProvider = (provider, version = 1) => {
|
|
297
|
+
let v;
|
|
298
|
+
if (version === 1) {
|
|
299
|
+
v = _.merge({}, provider.v1);
|
|
300
|
+
v.tilemapUrl = v.tilemapUrl.replace('~basePath~', constants_1.apiRoutePrefix[1]);
|
|
301
|
+
}
|
|
302
|
+
else if (version === 2) {
|
|
303
|
+
v = _.merge({}, provider.v2);
|
|
304
|
+
v.url = v.url ? v.url.replace('~basePath~', constants_1.apiRoutePrefix[2]) : '';
|
|
305
|
+
}
|
|
306
|
+
provider = _.omit(provider, [
|
|
307
|
+
'_filePath',
|
|
308
|
+
'_fileFormat',
|
|
309
|
+
'_mbtilesHandle',
|
|
310
|
+
'_flipY',
|
|
311
|
+
'v1',
|
|
312
|
+
'v2'
|
|
313
|
+
]);
|
|
314
|
+
return _.merge(provider, v);
|
|
315
|
+
};
|
|
316
|
+
const ensureDirectoryExists = (path) => {
|
|
317
|
+
if (!fs_1.default.existsSync(path)) {
|
|
318
|
+
fs_1.default.mkdirSync(path);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const serveTileFromFilesystem = (res, provider, z, x, y) => {
|
|
322
|
+
const { format, _flipY, _filePath } = provider;
|
|
323
|
+
const flippedY = Math.pow(2, z) - 1 - y;
|
|
324
|
+
const file = _filePath
|
|
325
|
+
? path_1.default.resolve(_filePath, `${z}/${x}/${_flipY ? flippedY : y}.${format}`)
|
|
326
|
+
: '';
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
328
|
+
res.sendFile(file, responseHttpOptions, (err) => {
|
|
329
|
+
if (err && err.code === 'ENOENT') {
|
|
330
|
+
res.sendStatus(404);
|
|
331
|
+
}
|
|
332
|
+
else if (err) {
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
const serveTileFromMbtiles = (res, provider, z, x, y) => {
|
|
338
|
+
provider._mbtilesHandle.getTile(z, x, y, (err, tile, headers) => {
|
|
339
|
+
if (err && err.message && err.message === 'Tile does not exist') {
|
|
340
|
+
res.sendStatus(404);
|
|
341
|
+
}
|
|
342
|
+
else if (err) {
|
|
343
|
+
console.error(`Error fetching tile ${provider.identifier}/${z}/${x}/${y}:`, err);
|
|
344
|
+
res.sendStatus(500);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
headers['Cache-Control'] = responseHttpOptions.headers['Cache-Control'];
|
|
348
|
+
res.writeHead(200, headers);
|
|
349
|
+
res.end(tile);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type MapSourceType = 'tilelayer' | 'S-57' | 'WMS' | 'WMTS' | 'mapstyleJSON' | 'tileJSON';
|
|
2
|
+
export interface ChartProvider {
|
|
3
|
+
_fileFormat?: 'mbtiles' | 'directory';
|
|
4
|
+
_filePath: string;
|
|
5
|
+
_mbtilesHandle?: any;
|
|
6
|
+
_flipY?: boolean;
|
|
7
|
+
identifier: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
type: MapSourceType;
|
|
11
|
+
scale: number;
|
|
12
|
+
v1?: {
|
|
13
|
+
tilemapUrl: string;
|
|
14
|
+
chartLayers: string[];
|
|
15
|
+
};
|
|
16
|
+
v2?: {
|
|
17
|
+
url: string;
|
|
18
|
+
layers: string[];
|
|
19
|
+
};
|
|
20
|
+
bounds?: number[];
|
|
21
|
+
minzoom?: number;
|
|
22
|
+
maxzoom?: number;
|
|
23
|
+
format?: string;
|
|
24
|
+
style?: string;
|
|
25
|
+
layers?: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface OnlineChartProvider {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
minzoom: number;
|
|
31
|
+
maxzoom: number;
|
|
32
|
+
serverType: MapSourceType;
|
|
33
|
+
format: 'png' | 'jpg';
|
|
34
|
+
url: string;
|
|
35
|
+
style: string;
|
|
36
|
+
layers: string[];
|
|
37
|
+
}
|
|
38
|
+
export {};
|
package/plugin/types.js
ADDED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
on:
|
|
3
|
-
workflow_run:
|
|
4
|
-
workflows: ["Release on tag"]
|
|
5
|
-
types: [completed]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
build:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v2
|
|
12
|
-
- uses: actions/setup-node@v1
|
|
13
|
-
with:
|
|
14
|
-
node-version: '16.x'
|
|
15
|
-
registry-url: 'https://registry.npmjs.org'
|
|
16
|
-
- run: npm publish --access public
|
|
17
|
-
env:
|
|
18
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
name: 'Release on tag'
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
tags:
|
|
5
|
-
- '*'
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
release:
|
|
9
|
-
permissions:
|
|
10
|
-
contents: write
|
|
11
|
-
if: startsWith(github.ref, 'refs/tags/')
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- name: Build Changelog
|
|
15
|
-
id: github_release
|
|
16
|
-
uses: mikepenz/release-changelog-builder-action@v1
|
|
17
|
-
env:
|
|
18
|
-
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
19
|
-
|
|
20
|
-
- name: Create Release
|
|
21
|
-
uses: actions/create-release@v1
|
|
22
|
-
with:
|
|
23
|
-
tag_name: ${{ github.ref }}
|
|
24
|
-
release_name: ${{ github.ref }}
|
|
25
|
-
body: ${{steps.github_release.outputs.changelog}}
|
|
26
|
-
env:
|
|
27
|
-
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
name: Pull Request Labels
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
types: [opened, labeled, unlabeled, synchronize]
|
|
5
|
-
jobs:
|
|
6
|
-
label:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
steps:
|
|
9
|
-
- uses: mheap/github-action-required-labels@v1
|
|
10
|
-
with:
|
|
11
|
-
mode: exactly
|
|
12
|
-
count: 1
|
|
13
|
-
labels: "fix, feature, doc, chore, test, ignore, other, dependencies"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: CI test
|
|
2
|
-
|
|
3
|
-
on: [push, pull_request]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
node-version: [10.x, 16.x]
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v2
|
|
15
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
-
uses: actions/setup-node@v1
|
|
17
|
-
with:
|
|
18
|
-
node-version: ${{ matrix.node-version }}
|
|
19
|
-
- run: npm install
|
|
20
|
-
- run: npm test
|
|
21
|
-
env:
|
|
22
|
-
CI: true
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./src/plugin')
|
package/src/charts.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
const debug = require('debug')('signalk-charts-plugin')
|
|
2
|
-
const Promise = require('bluebird')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const MBTiles = require('@mapbox/mbtiles')
|
|
5
|
-
const xml2js = require('xml2js')
|
|
6
|
-
const fs = Promise.promisifyAll(require('fs'))
|
|
7
|
-
const _ = require('lodash')
|
|
8
|
-
const {apiRoutePrefix} = require('./constants')
|
|
9
|
-
|
|
10
|
-
function findCharts(chartBaseDir) {
|
|
11
|
-
return fs
|
|
12
|
-
.readdirAsync(chartBaseDir)
|
|
13
|
-
.then(files => {
|
|
14
|
-
return Promise.mapSeries(files, filename => {
|
|
15
|
-
const isMbtilesFile = filename.match(/\.mbtiles$/i)
|
|
16
|
-
const file = path.resolve(chartBaseDir, filename)
|
|
17
|
-
const isDirectory = fs.statSync(file).isDirectory()
|
|
18
|
-
if (isMbtilesFile) {
|
|
19
|
-
return openMbtilesFile(file, filename)
|
|
20
|
-
} else if (isDirectory) {
|
|
21
|
-
return directoryToMapInfo(file, filename)
|
|
22
|
-
} else {
|
|
23
|
-
return Promise.resolve(null)
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
.then(result => _.filter(result, _.identity))
|
|
28
|
-
.then(charts => _.reduce(charts, (result, chart) => {
|
|
29
|
-
result[chart.identifier] = chart
|
|
30
|
-
return result
|
|
31
|
-
}, {}))
|
|
32
|
-
.catch(err => {
|
|
33
|
-
console.error(`Error reading charts directory ${chartBaseDir}:${err.message}`)
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function openMbtilesFile(file, filename) {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
new MBTiles(file, (err, mbtiles) => {
|
|
40
|
-
if (err) {
|
|
41
|
-
return reject(err)
|
|
42
|
-
}
|
|
43
|
-
mbtiles.getInfo((err, metadata) => {
|
|
44
|
-
if (err) {
|
|
45
|
-
return reject(err)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return resolve({mbtiles, metadata})
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
}).then(({mbtiles, metadata}) => {
|
|
52
|
-
if (_.isEmpty(metadata) || metadata.bounds === undefined) {
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
|
-
const identifier = filename.replace(/\.mbtiles$/i, '')
|
|
56
|
-
return {
|
|
57
|
-
_fileFormat: 'mbtiles',
|
|
58
|
-
_mbtilesHandle: mbtiles,
|
|
59
|
-
_flipY: false,
|
|
60
|
-
identifier,
|
|
61
|
-
name: metadata.name || metadata.id,
|
|
62
|
-
description: metadata.description,
|
|
63
|
-
bounds: metadata.bounds,
|
|
64
|
-
minzoom: metadata.minzoom,
|
|
65
|
-
maxzoom: metadata.maxzoom,
|
|
66
|
-
format: metadata.format,
|
|
67
|
-
type: 'tilelayer',
|
|
68
|
-
tilemapUrl: `${apiRoutePrefix}/charts/${identifier}/{z}/{x}/{y}`,
|
|
69
|
-
scale: metadata.scale || '250000'
|
|
70
|
-
}
|
|
71
|
-
}).catch(e => {
|
|
72
|
-
console.error(`Error loading chart ${file}`, e.message)
|
|
73
|
-
return null
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function parseTilemapResource(tilemapResource) {
|
|
78
|
-
return fs
|
|
79
|
-
.readFileAsync(tilemapResource)
|
|
80
|
-
.then(Promise.promisify(xml2js.parseString))
|
|
81
|
-
.then(parsed => {
|
|
82
|
-
const result = parsed.TileMap
|
|
83
|
-
const name = _.get(result, 'Title.0')
|
|
84
|
-
const format = _.get(result, 'TileFormat.0.$.extension')
|
|
85
|
-
const scale = _.get(result, 'Metadata.0.$.scale')
|
|
86
|
-
const bbox = _.get(result, 'BoundingBox.0.$')
|
|
87
|
-
const zoomLevels = _.map(_.get(result, 'TileSets.0.TileSet')||[], set => parseInt(_.get(set, '$.href')))
|
|
88
|
-
return {
|
|
89
|
-
_flipY: true,
|
|
90
|
-
name,
|
|
91
|
-
description: name,
|
|
92
|
-
bounds: bbox ? [parseFloat(bbox.minx), parseFloat(bbox.miny), parseFloat(bbox.maxx), parseFloat(bbox.maxy)] : undefined,
|
|
93
|
-
minzoom: !_.isEmpty(zoomLevels) ? _.min(zoomLevels) : undefined,
|
|
94
|
-
maxzoom: !_.isEmpty(zoomLevels) ? _.max(zoomLevels) : undefined,
|
|
95
|
-
format,
|
|
96
|
-
type: 'tilelayer',
|
|
97
|
-
scale: scale || '250000'
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseMetadataJson(metadataJson) {
|
|
103
|
-
return fs
|
|
104
|
-
.readFileAsync(metadataJson)
|
|
105
|
-
.then(JSON.parse)
|
|
106
|
-
.then(metadata => {
|
|
107
|
-
function parseBounds(bounds) {
|
|
108
|
-
if (_.isString(bounds)) {
|
|
109
|
-
return _.map(bounds.split(','), bound => parseFloat(_.trim(bound)))
|
|
110
|
-
} else if (_.isArray(bounds) && bounds.length === 4) {
|
|
111
|
-
return bounds
|
|
112
|
-
} else {
|
|
113
|
-
return undefined
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
_flipY: false,
|
|
118
|
-
name: metadata.name || metadata.id,
|
|
119
|
-
description: metadata.description,
|
|
120
|
-
bounds: parseBounds(metadata.bounds),
|
|
121
|
-
minzoom: parseIntIfNotUndefined(metadata.minzoom),
|
|
122
|
-
maxzoom: parseIntIfNotUndefined(metadata.maxzoom),
|
|
123
|
-
format: metadata.format,
|
|
124
|
-
type: 'tilelayer',
|
|
125
|
-
scale: metadata.scale || '250000'
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function directoryToMapInfo(file, identifier) {
|
|
131
|
-
function loadInfo() {
|
|
132
|
-
const tilemapResource = path.join(file, 'tilemapresource.xml')
|
|
133
|
-
const metadataJson = path.join(file, 'metadata.json')
|
|
134
|
-
|
|
135
|
-
const hasTilemapResource = fs.existsSync(tilemapResource)
|
|
136
|
-
const hasMetadataJson = fs.existsSync(metadataJson)
|
|
137
|
-
if (hasTilemapResource) {
|
|
138
|
-
return parseTilemapResource(tilemapResource)
|
|
139
|
-
} else if (hasMetadataJson) {
|
|
140
|
-
return parseMetadataJson(metadataJson)
|
|
141
|
-
} else {
|
|
142
|
-
return Promise.resolve(null)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return loadInfo()
|
|
147
|
-
.then(info => {
|
|
148
|
-
if (info) {
|
|
149
|
-
if (!info.format) {
|
|
150
|
-
console.error(`Missing format metadata for chart ${identifier}`)
|
|
151
|
-
return null
|
|
152
|
-
}
|
|
153
|
-
return _.merge(info, {
|
|
154
|
-
identifier,
|
|
155
|
-
_fileFormat: 'directory',
|
|
156
|
-
_filePath: file,
|
|
157
|
-
tilemapUrl: `${apiRoutePrefix}/charts/${identifier}/{z}/{x}/{y}`,
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
return null
|
|
161
|
-
})
|
|
162
|
-
.catch(e => {
|
|
163
|
-
console.error(`Error getting charts from ${file}`, e.message)
|
|
164
|
-
return undefined
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function parseIntIfNotUndefined(val) {
|
|
169
|
-
const parsed = parseInt(val)
|
|
170
|
-
return _.isFinite(parsed) ? parsed : undefined
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = {
|
|
174
|
-
findCharts
|
|
175
|
-
}
|
package/src/constants.js
DELETED