@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/README.md
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
|
-
# Signal K
|
|
1
|
+
# Signal K server Charts plugin
|
|
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
|
+
Supports both v1 and v2 Signal K resources api paths.
|
|
6
|
+
|
|
7
|
+
| Server Version | API | Path |
|
|
8
|
+
|--- |--- |--- |
|
|
9
|
+
| 1.x.x | v1 | `/signalk/v1/api/resources/charts` |
|
|
10
|
+
| 2.x.x | v2 | `/signalk/v2/api/resources/charts` |
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_Note: v2 resource paths will only be made available on Signal K server >= v2._
|
|
14
|
+
|
|
5
15
|
### Usage
|
|
6
16
|
|
|
7
17
|
1. Install "Signal K Charts" plugin from Signal K Appstore
|
|
8
18
|
|
|
9
|
-
2.
|
|
19
|
+
2. Configure plugin in **Plugin Config**
|
|
20
|
+
|
|
21
|
+
- Add "Chart paths" which are the paths to the folders where chart files are stored. Defaults to `${signalk-configuration-path}/charts`
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
3. Add "Chart paths" in plugin configuration. Defaults to `${signalk-configuration-path}/charts`
|
|
10
25
|
|
|
11
26
|
<img src="https://user-images.githubusercontent.com/1435910/39382493-57c1e4dc-4a6e-11e8-93e1-cedb4c7662f4.png" alt="Chart paths configuration" width="450"/>
|
|
12
27
|
|
|
13
|
-
3. Put charts into selected paths
|
|
14
28
|
|
|
15
|
-
4.
|
|
29
|
+
4. Put charts into selected paths
|
|
30
|
+
|
|
31
|
+
5. Add optional online chart providers
|
|
16
32
|
|
|
17
33
|
<img src="https://user-images.githubusercontent.com/1435910/45048136-c65d2e80-b083-11e8-99db-01e8cece9f89.png" alt="Online chart providers configuration" width="450"/>
|
|
18
34
|
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
19
38
|
_WMS example:_
|
|
20
|
-

|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
<img src="https://user-images.githubusercontent.com/38519157/102832518-90077100-443e-11eb-9a1d-d0806bb2b10b.png" alt="server type configuration" width="450"/>
|
|
41
|
+
|
|
42
|
+
6. Activate plugin
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
7. Use one of the client apps supporting Signal K charts, for example:
|
|
25
45
|
- [Freeboard SK](https://www.npmjs.com/package/@signalk/freeboard-sk)
|
|
26
46
|
- [Tuktuk Chart Plotter](https://www.npmjs.com/package/tuktuk-chart-plotter)
|
|
27
47
|
|
|
@@ -33,7 +53,7 @@ _WMS example:_
|
|
|
33
53
|
- Online [TMS](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification)
|
|
34
54
|
|
|
35
55
|
Publicly available MBTiles charts can be found from:
|
|
36
|
-
- [NOAA Nautical charts](
|
|
56
|
+
- [NOAA Nautical charts](https://distribution.charts.noaa.gov/ncds/index.html)
|
|
37
57
|
- [Finnish Transport Agency nautical charts](https://github.com/vokkim/rannikkokartat-mbtiles)
|
|
38
58
|
- [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)
|
|
39
59
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalk/charts-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Signal K plugin to provide chart support for Signal K server",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"build-declaration": "tsc --declaration --allowJs false",
|
|
9
|
+
"watch": "npm run build -- -w",
|
|
10
|
+
"start": "npm run build -- -w",
|
|
11
|
+
"prepare": "tsc",
|
|
12
|
+
"prettier": "prettier -w src/",
|
|
13
|
+
"lint": "eslint -c .eslintrc.js --ext .ts --ext .js --fix src/",
|
|
14
|
+
"format": "npm run prettier && npm run lint",
|
|
15
|
+
"ci-lint": "eslint -c .eslintrc.js --ext .ts --ext .js src/ && prettier --check src/",
|
|
7
16
|
"test": "mocha",
|
|
8
|
-
"deploy": "npm test && git tag -a v$npm_package_version -m 'Release v$npm_package_version'; git push --tags; npm publish --access public --scope @signalk ."
|
|
17
|
+
"deploy": "npm build && npm test && git tag -a v$npm_package_version -m 'Release v$npm_package_version'; git push --tags; npm publish --access public --scope @signalk ."
|
|
9
18
|
},
|
|
10
19
|
"license": "Apache-2.0",
|
|
11
20
|
"keywords": [
|
|
@@ -22,6 +31,7 @@
|
|
|
22
31
|
},
|
|
23
32
|
"dependencies": {
|
|
24
33
|
"@mapbox/mbtiles": "^0.12.1",
|
|
34
|
+
"@signalk/server-api": "^2.0.0-beta.3",
|
|
25
35
|
"baconjs": "1.0.1",
|
|
26
36
|
"bluebird": "3.5.1",
|
|
27
37
|
"lodash": "^4.17.11",
|
|
@@ -32,10 +42,19 @@
|
|
|
32
42
|
"url": "https://github.com/SignalK/charts-plugin"
|
|
33
43
|
},
|
|
34
44
|
"devDependencies": {
|
|
45
|
+
"@types/express": "^4.17.17",
|
|
46
|
+
"@types/lodash": "^4.14.191",
|
|
47
|
+
"@types/node": "^18.14.4",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
|
49
|
+
"@typescript-eslint/parser": "^5.52.0",
|
|
35
50
|
"body-parser": "1.18.2",
|
|
36
51
|
"chai": "4.1.2",
|
|
37
52
|
"chai-http": "^4.2.1",
|
|
53
|
+
"eslint": "^8.34.0",
|
|
54
|
+
"eslint-config-prettier": "^8.6.0",
|
|
38
55
|
"express": "4.16.2",
|
|
39
|
-
"mocha": "5.0.0"
|
|
56
|
+
"mocha": "5.0.0",
|
|
57
|
+
"prettier": "^2.8.4",
|
|
58
|
+
"typescript": "^4.5.4"
|
|
40
59
|
}
|
|
41
60
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findCharts(chartBaseDir: string): Promise<any>;
|
package/plugin/charts.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
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
|
+
exports.findCharts = void 0;
|
|
39
|
+
const bluebird = __importStar(require("bluebird"));
|
|
40
|
+
const path_1 = __importDefault(require("path"));
|
|
41
|
+
const mbtiles_1 = __importDefault(require("@mapbox/mbtiles"));
|
|
42
|
+
const xml2js = __importStar(require("xml2js"));
|
|
43
|
+
const fs_1 = require("fs");
|
|
44
|
+
const _ = __importStar(require("lodash"));
|
|
45
|
+
function findCharts(chartBaseDir) {
|
|
46
|
+
return fs_1.promises
|
|
47
|
+
.readdir(chartBaseDir, { withFileTypes: true })
|
|
48
|
+
.then((files) => {
|
|
49
|
+
return bluebird.mapSeries(files, (file) => {
|
|
50
|
+
const isMbtilesFile = file.name.match(/\.mbtiles$/i);
|
|
51
|
+
const filePath = path_1.default.resolve(chartBaseDir, file.name);
|
|
52
|
+
const isDirectory = file.isDirectory();
|
|
53
|
+
if (isMbtilesFile) {
|
|
54
|
+
return openMbtilesFile(filePath, file.name);
|
|
55
|
+
}
|
|
56
|
+
else if (isDirectory) {
|
|
57
|
+
return directoryToMapInfo(filePath, file.name);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return Promise.resolve(null);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
})
|
|
64
|
+
.then((result) => _.filter(result, _.identity))
|
|
65
|
+
.then((charts) => _.reduce(charts,
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
(result, chart) => {
|
|
68
|
+
result[chart.identifier] = chart;
|
|
69
|
+
return result;
|
|
70
|
+
}, {}))
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
console.error(`Error reading charts directory ${chartBaseDir}:${err.message}`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
exports.findCharts = findCharts;
|
|
76
|
+
function openMbtilesFile(file, filename) {
|
|
77
|
+
return (new Promise((resolve, reject) => {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
new mbtiles_1.default(file, (err, mbtiles) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
return reject(err);
|
|
82
|
+
}
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
mbtiles.getInfo((err, metadata) => {
|
|
85
|
+
if (err) {
|
|
86
|
+
return reject(err);
|
|
87
|
+
}
|
|
88
|
+
return resolve({ mbtiles, metadata });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
})
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
.then((res) => {
|
|
94
|
+
if (_.isEmpty(res.metadata) || res.metadata.bounds === undefined) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const identifier = filename.replace(/\.mbtiles$/i, '');
|
|
98
|
+
const data = {
|
|
99
|
+
_fileFormat: 'mbtiles',
|
|
100
|
+
_filePath: file,
|
|
101
|
+
_mbtilesHandle: res.mbtiles,
|
|
102
|
+
_flipY: false,
|
|
103
|
+
identifier,
|
|
104
|
+
name: res.metadata.name || res.metadata.id,
|
|
105
|
+
description: res.metadata.description,
|
|
106
|
+
bounds: res.metadata.bounds,
|
|
107
|
+
minzoom: res.metadata.minzoom,
|
|
108
|
+
maxzoom: res.metadata.maxzoom,
|
|
109
|
+
format: res.metadata.format,
|
|
110
|
+
type: 'tilelayer',
|
|
111
|
+
scale: parseInt(res.metadata.scale) || 250000,
|
|
112
|
+
v1: {
|
|
113
|
+
tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
|
|
114
|
+
chartLayers: res.metadata.vector_layers
|
|
115
|
+
? parseVectorLayers(res.metadata.vector_layers)
|
|
116
|
+
: []
|
|
117
|
+
},
|
|
118
|
+
v2: {
|
|
119
|
+
url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
|
|
120
|
+
layers: res.metadata.vector_layers
|
|
121
|
+
? parseVectorLayers(res.metadata.vector_layers)
|
|
122
|
+
: []
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return data;
|
|
126
|
+
})
|
|
127
|
+
.catch((e) => {
|
|
128
|
+
console.error(`Error loading chart ${file}`, e.message);
|
|
129
|
+
return null;
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
function parseVectorLayers(layers) {
|
|
133
|
+
return layers.map((l) => l.id);
|
|
134
|
+
}
|
|
135
|
+
function directoryToMapInfo(file, identifier) {
|
|
136
|
+
function loadInfo() {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
const tilemapResource = path_1.default.join(file, 'tilemapresource.xml');
|
|
139
|
+
const metadataJson = path_1.default.join(file, 'metadata.json');
|
|
140
|
+
try {
|
|
141
|
+
yield fs_1.promises.stat(tilemapResource);
|
|
142
|
+
return parseTilemapResource(tilemapResource);
|
|
143
|
+
}
|
|
144
|
+
catch (_a) {
|
|
145
|
+
try {
|
|
146
|
+
yield fs_1.promises.stat(metadataJson);
|
|
147
|
+
return parseMetadataJson(metadataJson);
|
|
148
|
+
}
|
|
149
|
+
catch (_b) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return loadInfo()
|
|
156
|
+
.then((info) => {
|
|
157
|
+
if (info) {
|
|
158
|
+
if (!info.format) {
|
|
159
|
+
console.error(`Missing format metadata for chart ${identifier}`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
info.identifier = identifier;
|
|
163
|
+
(info._fileFormat = 'directory'),
|
|
164
|
+
(info._filePath = file),
|
|
165
|
+
(info.v1 = {
|
|
166
|
+
tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
|
|
167
|
+
chartLayers: []
|
|
168
|
+
});
|
|
169
|
+
info.v2 = {
|
|
170
|
+
url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
|
|
171
|
+
layers: []
|
|
172
|
+
};
|
|
173
|
+
return info;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
})
|
|
177
|
+
.catch((e) => {
|
|
178
|
+
console.error(`Error getting charts from ${file}`, e.message);
|
|
179
|
+
return undefined;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function parseTilemapResource(tilemapResource) {
|
|
183
|
+
return (fs_1.promises
|
|
184
|
+
.readFile(tilemapResource)
|
|
185
|
+
.then(bluebird.promisify(xml2js.parseString))
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
.then((parsed) => {
|
|
188
|
+
const result = parsed.TileMap;
|
|
189
|
+
const name = _.get(result, 'Title.0');
|
|
190
|
+
const format = _.get(result, 'TileFormat.0.$.extension');
|
|
191
|
+
const scale = _.get(result, 'Metadata.0.$.scale');
|
|
192
|
+
const bbox = _.get(result, 'BoundingBox.0.$');
|
|
193
|
+
const zoomLevels = _.map(_.get(result, 'TileSets.0.TileSet') || [], (set) => parseInt(_.get(set, '$.href')));
|
|
194
|
+
const res = {
|
|
195
|
+
_flipY: true,
|
|
196
|
+
name,
|
|
197
|
+
description: name,
|
|
198
|
+
bounds: bbox
|
|
199
|
+
? [
|
|
200
|
+
parseFloat(bbox.minx),
|
|
201
|
+
parseFloat(bbox.miny),
|
|
202
|
+
parseFloat(bbox.maxx),
|
|
203
|
+
parseFloat(bbox.maxy)
|
|
204
|
+
]
|
|
205
|
+
: undefined,
|
|
206
|
+
minzoom: !_.isEmpty(zoomLevels) ? _.min(zoomLevels) : undefined,
|
|
207
|
+
maxzoom: !_.isEmpty(zoomLevels) ? _.max(zoomLevels) : undefined,
|
|
208
|
+
format,
|
|
209
|
+
type: 'tilelayer',
|
|
210
|
+
scale: parseInt(scale) || 250000,
|
|
211
|
+
identifier: '',
|
|
212
|
+
_filePath: ''
|
|
213
|
+
};
|
|
214
|
+
return res;
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
function parseMetadataJson(metadataJson) {
|
|
218
|
+
return fs_1.promises
|
|
219
|
+
.readFile(metadataJson, { encoding: 'utf8' })
|
|
220
|
+
.then((txt) => {
|
|
221
|
+
return JSON.parse(txt);
|
|
222
|
+
})
|
|
223
|
+
.then((metadata) => {
|
|
224
|
+
function parseBounds(bounds) {
|
|
225
|
+
if (_.isString(bounds)) {
|
|
226
|
+
return _.map(bounds.split(','), (bound) => parseFloat(_.trim(bound)));
|
|
227
|
+
}
|
|
228
|
+
else if (_.isArray(bounds) && bounds.length === 4) {
|
|
229
|
+
return bounds;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const res = {
|
|
236
|
+
_flipY: false,
|
|
237
|
+
name: metadata.name || metadata.id,
|
|
238
|
+
description: metadata.description || '',
|
|
239
|
+
bounds: parseBounds(metadata.bounds),
|
|
240
|
+
minzoom: parseIntIfNotUndefined(metadata.minzoom),
|
|
241
|
+
maxzoom: parseIntIfNotUndefined(metadata.maxzoom),
|
|
242
|
+
format: metadata.format,
|
|
243
|
+
type: metadata.type,
|
|
244
|
+
scale: parseInt(metadata.scale) || 250000,
|
|
245
|
+
identifier: '',
|
|
246
|
+
_filePath: ''
|
|
247
|
+
};
|
|
248
|
+
return res;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function parseIntIfNotUndefined(val) {
|
|
252
|
+
const parsed = parseInt(val);
|
|
253
|
+
return _.isFinite(parsed) ? parsed : undefined;
|
|
254
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|