@signalk/charts-plugin 3.0.0 → 3.1.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/README.md +64 -21
- package/package.json +7 -7
- package/plugin/charts.js +17 -28
- package/plugin/index.js +23 -22
- package/plugin/types.d.ts +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Signal K Node server plugin to provide chart metadata, such as name, description and location of the actual chart tile data.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Chart metadata is derived from the following supported chart file types:
|
|
6
|
+
- MBTiles _(.mbtiles)_
|
|
7
|
+
- TMS _(tilemapresource.xml and tiles)_
|
|
8
|
+
|
|
9
|
+
Additionally, chart metadata can be entered via the plugin configuration for other chart sources and types _(e.g. WMS, WMTS, S-57 tiles and tilejson)_.
|
|
10
|
+
|
|
11
|
+
Chart metadata is made available to both v1 and v2 Signal K `resources` api paths.
|
|
6
12
|
|
|
7
13
|
| Server Version | API | Path |
|
|
8
14
|
|--- |--- |--- |
|
|
@@ -10,44 +16,60 @@ Supports both v1 and v2 Signal K resources api paths.
|
|
|
10
16
|
| 2.x.x | v2 | `/signalk/v2/api/resources/charts` |
|
|
11
17
|
|
|
12
18
|
|
|
13
|
-
_Note:
|
|
19
|
+
_Note: Version 2 resource paths will only be made available on Signal K server v2.0.0 and later_
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
## Usage
|
|
16
22
|
|
|
17
|
-
1. Install
|
|
23
|
+
1. Install `@signalk/signalk-charts` from the Signal K Server Appstore
|
|
18
24
|
|
|
19
|
-
2. Configure plugin in **Plugin Config**
|
|
25
|
+
2. Configure the plugin in the Admin UI _(**Server -> Plugin Config -> Signal K Charts**)_
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
3. Activate the plugin
|
|
22
28
|
|
|
29
|
+
Chart metadata will then be available to client apps via the resources api `/resources/charts` for example:
|
|
30
|
+
- [Freeboard SK](https://www.npmjs.com/package/@signalk/freeboard-sk)
|
|
31
|
+
- [Tuktuk Chart Plotter](https://www.npmjs.com/package/tuktuk-chart-plotter)
|
|
23
32
|
|
|
24
|
-
3. Add "Chart paths" in plugin configuration. Defaults to `${signalk-configuration-path}/charts`
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
## Configuration
|
|
27
35
|
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
### Local Chart Files
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
To use chart files stored on the Signal K Server the plugin needs to know where your local chart files
|
|
40
|
+
are stored to generate the chart metadata.
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
You can either:
|
|
43
|
+
1. Put the chart files in the default location _(`/home/<user>/.signalk/charts`)_
|
|
44
|
+
2. Add configuration entries for the folders where the chart files are stored.
|
|
34
45
|
|
|
46
|
+
<img src="https://user-images.githubusercontent.com/1435910/39382493-57c1e4dc-4a6e-11e8-93e1-cedb4c7662f4.png" alt="Chart paths configuration" width="450"/>
|
|
35
47
|
|
|
48
|
+
>**Note:** After chart files have been added to folders they will be processed after the plugin has been restarted! _(disable / enable the plugin)_
|
|
36
49
|
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
### Online chart providers
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
If your chart source is not local to the Signal K Server you can add "Online Chart Providers" and enter the required charts metadata for the source.
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
You will need to provide the following information:
|
|
56
|
+
1. A chart name for client applications to display
|
|
57
|
+
2. The URL to the chart source
|
|
58
|
+
3. Select the chart image format
|
|
59
|
+
4. The minimum and maximum zoom levels where chart data is available.
|
|
60
|
+
|
|
61
|
+
You can also provide a description detailing the chart content.
|
|
62
|
+
|
|
63
|
+
<img src="https://github.com/user-attachments/assets/77cb3aaf-5471-4e55-b05d-aad70cacab6a" alt="Online chart providers configuration" width="450"/>
|
|
64
|
+
|
|
65
|
+
For WMS & WMTS sources you can specify the layers you wish to display.
|
|
66
|
+
|
|
67
|
+
<img src="https://github.com/user-attachments/assets/b9bfba38-8468-4eca-aeb3-96a80fcbc7a6" alt="Online chart provider layers" width="450"/>
|
|
43
68
|
|
|
44
|
-
7. Use one of the client apps supporting Signal K charts, for example:
|
|
45
|
-
- [Freeboard SK](https://www.npmjs.com/package/@signalk/freeboard-sk)
|
|
46
|
-
- [Tuktuk Chart Plotter](https://www.npmjs.com/package/tuktuk-chart-plotter)
|
|
47
69
|
|
|
48
70
|
### Supported chart formats
|
|
49
71
|
|
|
50
|
-
- [MBTiles](https://github.com/mapbox/mbtiles-spec)
|
|
72
|
+
- [MBTiles](https://github.com/mapbox/mbtiles-spec) files
|
|
51
73
|
- Directory with cached [TMS](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification) tiles and `tilemapresource.xml`
|
|
52
74
|
- Directory with XYZ tiles and `metadata.json`
|
|
53
75
|
- Online [TMS](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification)
|
|
@@ -57,13 +79,34 @@ Publicly available MBTiles charts can be found from:
|
|
|
57
79
|
- [Finnish Transport Agency nautical charts](https://github.com/vokkim/rannikkokartat-mbtiles)
|
|
58
80
|
- [Signal K World Coastline Map](https://github.com/netAction/signalk-world-coastline-map), download [MBTiles release](https://github.com/netAction/signalk-world-coastline-map/releases/download/v1.0/signalk-world-coastline-map-database.tgz)
|
|
59
81
|
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
60
85
|
### API
|
|
61
86
|
|
|
62
87
|
Plugin adds support for `/resources/charts` endpoints described in [Signal K specification](http://signalk.org/specification/1.0.0/doc/otherBranches.html#resourcescharts):
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
#### List available charts
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
GET /signalk/v2/api/resources/charts/`
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Return metadata for selected chart
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
|
|
99
|
+
GET /signalk/v2/api/resources/charts/${identifier}`
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Chart Tiles
|
|
103
|
+
Chart tiles are retrieved using the url defined in the chart metadata.
|
|
104
|
+
|
|
105
|
+
For local chart files located in the Chart Path(s) defined in the plugin configuration, the url will be:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
/signalk/chart-tiles/${identifier}/${z}/${x}/${y}
|
|
109
|
+
```
|
|
67
110
|
|
|
68
111
|
License
|
|
69
112
|
-------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalk/charts-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Signal K plugin to provide chart support for Signal K server",
|
|
5
5
|
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,29 +31,29 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@mapbox/mbtiles": "^0.12.1",
|
|
34
|
-
"@signalk/server-api": "^2.0.0
|
|
35
|
-
"baconjs": "1.0.1",
|
|
34
|
+
"@signalk/server-api": "^2.0.0",
|
|
36
35
|
"bluebird": "3.5.1",
|
|
37
36
|
"lodash": "^4.17.11",
|
|
38
|
-
"xml2js": "0.
|
|
37
|
+
"xml2js": "0.6.2"
|
|
39
38
|
},
|
|
40
39
|
"repository": {
|
|
41
40
|
"type": "git",
|
|
42
41
|
"url": "https://github.com/SignalK/charts-plugin"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
44
|
+
"@types/baconjs": "^0.7.34",
|
|
45
45
|
"@types/express": "^4.17.17",
|
|
46
46
|
"@types/lodash": "^4.14.191",
|
|
47
47
|
"@types/node": "^18.14.4",
|
|
48
48
|
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
|
49
49
|
"@typescript-eslint/parser": "^5.52.0",
|
|
50
|
-
"body-parser": "1.18.2",
|
|
50
|
+
"body-parser": "^1.18.2",
|
|
51
51
|
"chai": "4.1.2",
|
|
52
52
|
"chai-http": "^4.2.1",
|
|
53
53
|
"eslint": "^8.34.0",
|
|
54
54
|
"eslint-config-prettier": "^8.6.0",
|
|
55
|
-
"express": "4.
|
|
56
|
-
"mocha": "
|
|
55
|
+
"express": "^4.19.2",
|
|
56
|
+
"mocha": "^11.7.1",
|
|
57
57
|
"prettier": "^2.8.4",
|
|
58
58
|
"typescript": "^4.5.4"
|
|
59
59
|
}
|
package/plugin/charts.js
CHANGED
|
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
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
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
27
|
};
|
|
@@ -110,13 +101,13 @@ function openMbtilesFile(file, filename) {
|
|
|
110
101
|
type: 'tilelayer',
|
|
111
102
|
scale: parseInt(res.metadata.scale) || 250000,
|
|
112
103
|
v1: {
|
|
113
|
-
tilemapUrl: `~
|
|
104
|
+
tilemapUrl: `~tilePath~/${identifier}/{z}/{x}/{y}`,
|
|
114
105
|
chartLayers: res.metadata.vector_layers
|
|
115
106
|
? parseVectorLayers(res.metadata.vector_layers)
|
|
116
107
|
: []
|
|
117
108
|
},
|
|
118
109
|
v2: {
|
|
119
|
-
url: `~
|
|
110
|
+
url: `~tilePath~/${identifier}/{z}/{x}/{y}`,
|
|
120
111
|
layers: res.metadata.vector_layers
|
|
121
112
|
? parseVectorLayers(res.metadata.vector_layers)
|
|
122
113
|
: []
|
|
@@ -133,24 +124,22 @@ function parseVectorLayers(layers) {
|
|
|
133
124
|
return layers.map((l) => l.id);
|
|
134
125
|
}
|
|
135
126
|
function directoryToMapInfo(file, identifier) {
|
|
136
|
-
function loadInfo() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
async function loadInfo() {
|
|
128
|
+
const tilemapResource = path_1.default.join(file, 'tilemapresource.xml');
|
|
129
|
+
const metadataJson = path_1.default.join(file, 'metadata.json');
|
|
130
|
+
try {
|
|
131
|
+
await fs_1.promises.stat(tilemapResource);
|
|
132
|
+
return parseTilemapResource(tilemapResource);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
140
135
|
try {
|
|
141
|
-
|
|
142
|
-
return
|
|
136
|
+
await fs_1.promises.stat(metadataJson);
|
|
137
|
+
return parseMetadataJson(metadataJson);
|
|
143
138
|
}
|
|
144
|
-
catch
|
|
145
|
-
|
|
146
|
-
yield fs_1.promises.stat(metadataJson);
|
|
147
|
-
return parseMetadataJson(metadataJson);
|
|
148
|
-
}
|
|
149
|
-
catch (_b) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
152
141
|
}
|
|
153
|
-
}
|
|
142
|
+
}
|
|
154
143
|
}
|
|
155
144
|
return loadInfo()
|
|
156
145
|
.then((info) => {
|
|
@@ -163,11 +152,11 @@ function directoryToMapInfo(file, identifier) {
|
|
|
163
152
|
(info._fileFormat = 'directory'),
|
|
164
153
|
(info._filePath = file),
|
|
165
154
|
(info.v1 = {
|
|
166
|
-
tilemapUrl: `~
|
|
155
|
+
tilemapUrl: `~tilePath~/${identifier}/{z}/{x}/{y}`,
|
|
167
156
|
chartLayers: []
|
|
168
157
|
});
|
|
169
158
|
info.v2 = {
|
|
170
|
-
url: `~
|
|
159
|
+
url: `~tilePath~/${identifier}/{z}/{x}/{y}`,
|
|
171
160
|
layers: []
|
|
172
161
|
};
|
|
173
162
|
return info;
|
package/plugin/index.js
CHANGED
|
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
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
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
27
|
};
|
|
@@ -43,6 +34,7 @@ const charts_1 = require("./charts");
|
|
|
43
34
|
const constants_1 = require("./constants");
|
|
44
35
|
const MIN_ZOOM = 1;
|
|
45
36
|
const MAX_ZOOM = 24;
|
|
37
|
+
const chartTilesPath = '/signalk/chart-tiles';
|
|
46
38
|
module.exports = (app) => {
|
|
47
39
|
let chartProviders = {};
|
|
48
40
|
let pluginStarted = false;
|
|
@@ -52,7 +44,9 @@ module.exports = (app) => {
|
|
|
52
44
|
};
|
|
53
45
|
const configBasePath = app.config.configPath;
|
|
54
46
|
const defaultChartsPath = path_1.default.join(configBasePath, '/charts');
|
|
55
|
-
const serverMajorVersion = app.config.version
|
|
47
|
+
const serverMajorVersion = app.config.version
|
|
48
|
+
? parseInt(app.config.version.split('.')[0])
|
|
49
|
+
: '1';
|
|
56
50
|
ensureDirectoryExists(defaultChartsPath);
|
|
57
51
|
// ******** REQUIRED PLUGIN DEFINITION *******
|
|
58
52
|
const CONFIG_SCHEMA = {
|
|
@@ -103,7 +97,14 @@ module.exports = (app) => {
|
|
|
103
97
|
type: 'string',
|
|
104
98
|
title: 'Map source / server type',
|
|
105
99
|
default: 'tilelayer',
|
|
106
|
-
enum: [
|
|
100
|
+
enum: [
|
|
101
|
+
'tilelayer',
|
|
102
|
+
'S-57',
|
|
103
|
+
'WMS',
|
|
104
|
+
'WMTS',
|
|
105
|
+
'mapstyleJSON',
|
|
106
|
+
'tileJSON'
|
|
107
|
+
],
|
|
107
108
|
description: 'Map data source type served by the supplied url. (Use tilelayer for xyz / tms tile sources.)'
|
|
108
109
|
},
|
|
109
110
|
format: {
|
|
@@ -153,8 +154,8 @@ module.exports = (app) => {
|
|
|
153
154
|
}
|
|
154
155
|
};
|
|
155
156
|
const doStartup = (config) => {
|
|
156
|
-
app.debug(
|
|
157
|
-
props =
|
|
157
|
+
app.debug(`** loaded config: ${config}`);
|
|
158
|
+
props = { ...config };
|
|
158
159
|
const chartPaths = _.isEmpty(props.chartPaths)
|
|
159
160
|
? [defaultChartsPath]
|
|
160
161
|
: resolveUniqueChartPaths(props.chartPaths, configBasePath);
|
|
@@ -168,7 +169,7 @@ module.exports = (app) => {
|
|
|
168
169
|
pluginStarted === false && registerRoutes();
|
|
169
170
|
pluginStarted = true;
|
|
170
171
|
const urlBase = `${app.config.ssl ? 'https' : 'http'}://localhost:${'getExternalPort' in app.config ? app.config.getExternalPort() : 3000}`;
|
|
171
|
-
app.debug(
|
|
172
|
+
app.debug(`**urlBase** ${urlBase}`);
|
|
172
173
|
app.setPluginStatus('Started');
|
|
173
174
|
const loadProviders = bluebird
|
|
174
175
|
.mapSeries(chartPaths, (chartPath) => (0, charts_1.findCharts)(chartPath))
|
|
@@ -186,7 +187,7 @@ module.exports = (app) => {
|
|
|
186
187
|
};
|
|
187
188
|
const registerRoutes = () => {
|
|
188
189
|
app.debug('** Registering API paths **');
|
|
189
|
-
app.get(
|
|
190
|
+
app.get(`${chartTilesPath}/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, async (req, res) => {
|
|
190
191
|
const { identifier, z, x, y } = req.params;
|
|
191
192
|
const provider = chartProviders[identifier];
|
|
192
193
|
if (!provider) {
|
|
@@ -201,7 +202,7 @@ module.exports = (app) => {
|
|
|
201
202
|
console.log(`Unknown chart provider fileformat ${provider._fileFormat}`);
|
|
202
203
|
res.status(500).send();
|
|
203
204
|
}
|
|
204
|
-
})
|
|
205
|
+
});
|
|
205
206
|
app.debug('** Registering v1 API paths **');
|
|
206
207
|
app.get(constants_1.apiRoutePrefix[1] + '/charts/:identifier', (req, res) => {
|
|
207
208
|
const { identifier } = req.params;
|
|
@@ -231,11 +232,11 @@ module.exports = (app) => {
|
|
|
231
232
|
type: 'charts',
|
|
232
233
|
methods: {
|
|
233
234
|
listResources: (params) => {
|
|
234
|
-
app.debug(`** listResources()
|
|
235
|
+
app.debug(`** listResources() ${params}`);
|
|
235
236
|
return Promise.resolve(_.mapValues(chartProviders, (provider) => sanitizeProvider(provider, 2)));
|
|
236
237
|
},
|
|
237
238
|
getResource: (id) => {
|
|
238
|
-
app.debug(`** getResource()
|
|
239
|
+
app.debug(`** getResource() ${id}`);
|
|
239
240
|
const provider = chartProviders[id];
|
|
240
241
|
if (provider) {
|
|
241
242
|
return Promise.resolve(sanitizeProvider(provider, 2));
|
|
@@ -276,8 +277,8 @@ const convertOnlineProviderConfig = (provider) => {
|
|
|
276
277
|
name: provider.name,
|
|
277
278
|
description: provider.description,
|
|
278
279
|
bounds: [-180, -90, 180, 90],
|
|
279
|
-
minzoom: Math.min(Math.max(1, provider.minzoom),
|
|
280
|
-
maxzoom: Math.min(Math.max(1, provider.maxzoom),
|
|
280
|
+
minzoom: Math.min(Math.max(1, provider.minzoom), 24),
|
|
281
|
+
maxzoom: Math.min(Math.max(1, provider.maxzoom), 24),
|
|
281
282
|
format: provider.format,
|
|
282
283
|
scale: 250000,
|
|
283
284
|
type: provider.serverType ? provider.serverType : 'tilelayer',
|
|
@@ -297,11 +298,11 @@ const sanitizeProvider = (provider, version = 1) => {
|
|
|
297
298
|
let v;
|
|
298
299
|
if (version === 1) {
|
|
299
300
|
v = _.merge({}, provider.v1);
|
|
300
|
-
v.tilemapUrl = v.tilemapUrl.replace('~
|
|
301
|
+
v.tilemapUrl = v.tilemapUrl.replace('~tilePath~', chartTilesPath);
|
|
301
302
|
}
|
|
302
303
|
else if (version === 2) {
|
|
303
304
|
v = _.merge({}, provider.v2);
|
|
304
|
-
v.url = v.url ? v.url.replace('~
|
|
305
|
+
v.url = v.url ? v.url.replace('~tilePath~', chartTilesPath) : '';
|
|
305
306
|
}
|
|
306
307
|
provider = _.omit(provider, [
|
|
307
308
|
'_filePath',
|
package/plugin/types.d.ts
CHANGED
|
@@ -11,11 +11,11 @@ export interface ChartProvider {
|
|
|
11
11
|
scale: number;
|
|
12
12
|
v1?: {
|
|
13
13
|
tilemapUrl: string;
|
|
14
|
-
chartLayers
|
|
14
|
+
chartLayers?: string[];
|
|
15
15
|
};
|
|
16
16
|
v2?: {
|
|
17
17
|
url: string;
|
|
18
|
-
layers
|
|
18
|
+
layers?: string[];
|
|
19
19
|
};
|
|
20
20
|
bounds?: number[];
|
|
21
21
|
minzoom?: number;
|