@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.
Files changed (41) hide show
  1. package/README.md +28 -8
  2. package/package.json +23 -4
  3. package/plugin/charts.d.ts +1 -0
  4. package/plugin/charts.js +254 -0
  5. package/plugin/constants.d.ts +4 -0
  6. package/plugin/constants.js +7 -0
  7. package/plugin/index.d.ts +1 -0
  8. package/plugin/index.js +352 -0
  9. package/plugin/types.d.ts +38 -0
  10. package/plugin/types.js +2 -0
  11. package/.github/workflows/publish.yml +0 -18
  12. package/.github/workflows/release_on_tag.yml +0 -27
  13. package/.github/workflows/require_pr_label.yml +0 -13
  14. package/.github/workflows/test.yml +0 -22
  15. package/index.js +0 -1
  16. package/src/charts.js +0 -175
  17. package/src/constants.js +0 -3
  18. package/src/plugin.js +0 -231
  19. package/test/charts/empty-test/.gitkeep +0 -0
  20. package/test/charts/test.mbtiles +0 -0
  21. package/test/charts/tms-tiles/4/8/10.png +0 -0
  22. package/test/charts/tms-tiles/4/8/11.png +0 -0
  23. package/test/charts/tms-tiles/4/9/10.png +0 -0
  24. package/test/charts/tms-tiles/4/9/11.png +0 -0
  25. package/test/charts/tms-tiles/5/16/21.png +0 -0
  26. package/test/charts/tms-tiles/5/16/22.png +0 -0
  27. package/test/charts/tms-tiles/5/16/23.png +0 -0
  28. package/test/charts/tms-tiles/5/17/21.png +0 -0
  29. package/test/charts/tms-tiles/5/17/22.png +0 -0
  30. package/test/charts/tms-tiles/5/17/23.png +0 -0
  31. package/test/charts/tms-tiles/5/18/21.png +0 -0
  32. package/test/charts/tms-tiles/5/18/22.png +0 -0
  33. package/test/charts/tms-tiles/5/18/23.png +0 -0
  34. package/test/charts/tms-tiles/tilemapresource.xml +0 -14
  35. package/test/charts/unpacked-tiles/3/2/3.png +0 -0
  36. package/test/charts/unpacked-tiles/4/4/6.png +0 -0
  37. package/test/charts/unpacked-tiles/4/5/6.png +0 -0
  38. package/test/charts/unpacked-tiles/metadata.json +0 -15
  39. package/test/charts-2/test2.mbtiles +0 -0
  40. package/test/expected-charts.json +0 -53
  41. package/test/plugin-test.js +0 -196
package/README.md CHANGED
@@ -1,27 +1,47 @@
1
- # Signal K Node server Charts plugin
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. Add "Chart paths" in plugin configuration. Defaults to `${signalk-configuration-path}/charts`
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. Add optional online chart providers
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
- ![image](https://user-images.githubusercontent.com/38519157/102832518-90077100-443e-11eb-9a1d-d0806bb2b10b.png)
21
39
 
22
- 5. Activate plugin
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
- 6. Use one of the client apps supporting Signal K charts, for example:
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](http://tileservice.charts.noaa.gov/), quick download [cheat-sheet](https://github.com/vokkim/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": "2.3.1",
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>;
@@ -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,4 @@
1
+ export declare const apiRoutePrefix: {
2
+ 1: string;
3
+ 2: string;
4
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiRoutePrefix = void 0;
4
+ exports.apiRoutePrefix = {
5
+ 1: '/signalk/v1/api/resources',
6
+ 2: '/signalk/v2/api/resources'
7
+ };
@@ -0,0 +1 @@
1
+ export {};