From d5919ba41ea3b7c06b02eb28ee0b3172a5985dd5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:23:43 +0930 Subject: [PATCH 01/17] Serve chart tiles from path outside of `/resources/charts`. Removing the use of a shared path aligns better with multi-provider support. Tile paths have a fully qualified path, remove use of relative path. --- CHANELOG.md | 15 ++++++++++ src/charts.ts | 8 ++--- src/constants.ts | 4 --- src/index.ts | 78 ++++++++++++++++++++++++++++++------------------ src/types.ts | 8 ++++- 5 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 CHANELOG.md delete mode 100644 src/constants.ts diff --git a/CHANELOG.md b/CHANELOG.md new file mode 100644 index 0000000..5be70f4 --- /dev/null +++ b/CHANELOG.md @@ -0,0 +1,15 @@ +## Change Log + +### 3.1.0 + +- **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. + +- **Updated**: Tile url value is now a fully qualified url rather than a relative path. + + +--- + +### 3.0.0 + +- **Added**: Signal K v2 Resources API support. +- **Updated**: Ported to Typescript. \ No newline at end of file diff --git a/src/charts.ts b/src/charts.ts index 1ae32c9..b3022c8 100644 --- a/src/charts.ts +++ b/src/charts.ts @@ -80,13 +80,13 @@ function openMbtilesFile(file: string, filename: string) { type: 'tilelayer', scale: parseInt(res.metadata.scale) || 250000, v1: { - tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`, + tilemapUrl: `~basePath~/${identifier}/{z}/{x}/{y}`, chartLayers: res.metadata.vector_layers ? parseVectorLayers(res.metadata.vector_layers) : [] }, v2: { - url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`, + url: `~basePath~/${identifier}/{z}/{x}/{y}`, layers: res.metadata.vector_layers ? parseVectorLayers(res.metadata.vector_layers) : [] @@ -133,11 +133,11 @@ function directoryToMapInfo(file: string, identifier: string) { ;(info._fileFormat = 'directory'), (info._filePath = file), (info.v1 = { - tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`, + tilemapUrl: `~basePath~/${identifier}/{z}/{x}/{y}`, chartLayers: [] }) info.v2 = { - url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`, + url: `~basePath~/${identifier}/{z}/{x}/{y}`, layers: [] } diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index e3cd161..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const apiRoutePrefix = { - 1: '/signalk/v1/api/resources', - 2: '/signalk/v2/api/resources' -} diff --git a/src/index.ts b/src/index.ts index e76d479..c89cfdf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ import path from 'path' import fs from 'fs' import * as _ from 'lodash' import { findCharts } from './charts' -import { apiRoutePrefix } from './constants' import { ChartProvider, OnlineChartProvider } from './types' import { Request, Response, Application } from 'express' import { OutgoingHttpHeaders } from 'http' @@ -13,9 +12,6 @@ import { ResourceProviderRegistry } from '@signalk/server-api' -const MIN_ZOOM = 1 -const MAX_ZOOM = 24 - interface Config { chartPaths: string[] onlineChartProviders: OnlineChartProvider[] @@ -38,6 +34,11 @@ interface ChartProviderApp } } +const MIN_ZOOM = 1 +const MAX_ZOOM = 24 +let basePath: string +const chartTilesPath = 'chart-tiles' + module.exports = (app: ChartProviderApp): Plugin => { let chartProviders: { [key: string]: ChartProvider } = {} let pluginStarted = false @@ -47,7 +48,9 @@ module.exports = (app: ChartProviderApp): Plugin => { } const configBasePath = app.config.configPath const defaultChartsPath = path.join(configBasePath, '/charts') - const serverMajorVersion = app.config.version ? parseInt(app.config.version.split('.')[0]) : '1' + const serverMajorVersion = app.config.version + ? parseInt(app.config.version.split('.')[0]) + : '1' ensureDirectoryExists(defaultChartsPath) // ******** REQUIRED PLUGIN DEFINITION ******* @@ -99,7 +102,14 @@ module.exports = (app: ChartProviderApp): Plugin => { type: 'string', title: 'Map source / server type', default: 'tilelayer', - enum: ['tilelayer', 'S-57', 'WMS', 'WMTS', 'mapstyleJSON', 'tileJSON'], + enum: [ + 'tilelayer', + 'S-57', + 'WMS', + 'WMTS', + 'mapstyleJSON', + 'tileJSON' + ], description: 'Map data source type served by the supplied url. (Use tilelayer for xyz / tms tile sources.)' }, @@ -147,9 +157,8 @@ module.exports = (app: ChartProviderApp): Plugin => { name: 'Signal K Charts', schema: () => CONFIG_SCHEMA, uiSchema: () => CONFIG_UISCHEMA, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - start: (settings: any) => { - return doStartup(settings) // return required for tests + start: (settings: object) => { + return doStartup(settings as Config) // return required for tests }, stop: () => { app.setPluginStatus('stopped') @@ -158,12 +167,28 @@ module.exports = (app: ChartProviderApp): Plugin => { const doStartup = (config: Config) => { app.debug('** loaded config: ', config) + + // Do not register routes if plugin has been started once already + pluginStarted === false && registerRoutes() + pluginStarted = true + basePath = `${app.config.ssl ? 'https' : 'http'}://localhost:${ + 'getExternalPort' in app.config ? app.config.getExternalPort() : 3000 + }/${chartTilesPath}` + app.debug('**basePath**', basePath) + app.setPluginStatus('Started') + + return loadCharts(config) + } + + // Load chart files + const loadCharts = (config: Config) => { props = { ...config } const chartPaths = _.isEmpty(props.chartPaths) ? [defaultChartsPath] : resolveUniqueChartPaths(props.chartPaths, configBasePath) + // load from config const onlineProviders = _.reduce( props.onlineChartProviders, (result: { [key: string]: object }, data) => { @@ -173,21 +198,13 @@ module.exports = (app: ChartProviderApp): Plugin => { }, {} ) + app.debug( `Start charts plugin. Chart paths: ${chartPaths.join( ', ' )}, online charts: ${Object.keys(onlineProviders).length}` ) - // Do not register routes if plugin has been started once already - pluginStarted === false && registerRoutes() - pluginStarted = true - const urlBase = `${app.config.ssl ? 'https' : 'http'}://localhost:${ - 'getExternalPort' in app.config ? app.config.getExternalPort() : 3000 - }` - app.debug('**urlBase**', urlBase) - app.setPluginStatus('Started') - const loadProviders = bluebird .mapSeries(chartPaths, (chartPath: string) => findCharts(chartPath)) .then((list: ChartProvider[]) => @@ -214,7 +231,7 @@ module.exports = (app: ChartProviderApp): Plugin => { app.debug('** Registering API paths **') app.get( - `/signalk/:version(v[1-2])/api/resources/charts/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, + `/${chartTilesPath}/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, async (req: Request, res: Response) => { const { identifier, z, x, y } = req.params const provider = chartProviders[identifier] @@ -250,7 +267,7 @@ module.exports = (app: ChartProviderApp): Plugin => { app.debug('** Registering v1 API paths **') app.get( - apiRoutePrefix[1] + '/charts/:identifier', + '/signalk/v1/api/resources/charts/:identifier', (req: Request, res: Response) => { const { identifier } = req.params const provider = chartProviders[identifier] @@ -262,12 +279,15 @@ module.exports = (app: ChartProviderApp): Plugin => { } ) - app.get(apiRoutePrefix[1] + '/charts', (req: Request, res: Response) => { - const sanitized = _.mapValues(chartProviders, (provider) => - sanitizeProvider(provider) - ) - res.json(sanitized) - }) + app.get( + '/signalk/v1/api/resources/charts', + (req: Request, res: Response) => { + const sanitized = _.mapValues(chartProviders, (provider) => + sanitizeProvider(provider) + ) + res.json(sanitized) + } + ) // v2 routes if (serverMajorVersion === 2) { @@ -364,10 +384,10 @@ const sanitizeProvider = (provider: ChartProvider, version = 1) => { let v if (version === 1) { v = _.merge({}, provider.v1) - v.tilemapUrl = v.tilemapUrl.replace('~basePath~', apiRoutePrefix[1]) - } else if (version === 2) { + v.tilemapUrl = v.tilemapUrl.replace('~basePath~', basePath) + } else { v = _.merge({}, provider.v2) - v.url = v.url ? v.url.replace('~basePath~', apiRoutePrefix[2]) : '' + v.url = v.url ? v.url.replace('~basePath~', basePath) : '' } provider = _.omit(provider, [ '_filePath', diff --git a/src/types.ts b/src/types.ts index dd3e7da..9bdc2e6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,10 @@ -type MapSourceType = 'tilelayer' | 'S-57' | 'WMS' | 'WMTS' | 'mapstyleJSON' | 'tileJSON' +type MapSourceType = + | 'tilelayer' + | 'S-57' + | 'WMS' + | 'WMTS' + | 'mapstyleJSON' + | 'tileJSON' export interface ChartProvider { _fileFormat?: 'mbtiles' | 'directory' From b34da1038cbf1e83bdc1cee575a01def2401270c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:00:51 +0930 Subject: [PATCH 02/17] Added detection, processing and serving of mapbox style json files. --- CHANELOG.md | 2 ++ src/charts.ts | 37 +++++++++++++++++++++++++++++++++++-- src/index.ts | 37 +++++++++++++++++++++++++++++++++---- src/types.ts | 4 ++-- tsconfig.json | 4 ++-- 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/CHANELOG.md b/CHANELOG.md index 5be70f4..6b43df5 100644 --- a/CHANELOG.md +++ b/CHANELOG.md @@ -2,6 +2,8 @@ ### 3.1.0 +- **Added**: Detection, processing and serving of mapbox style json files. Files served from `/chart-styles` + - **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. - **Updated**: Tile url value is now a fully qualified url rather than a relative path. diff --git a/src/charts.ts b/src/charts.ts index b3022c8..c5c4c3e 100644 --- a/src/charts.ts +++ b/src/charts.ts @@ -14,10 +14,13 @@ export function findCharts(chartBaseDir: string) { const isMbtilesFile = file.name.match(/\.mbtiles$/i) const filePath = path.resolve(chartBaseDir, file.name) const isDirectory = file.isDirectory() + const isMbstylesFile = file.name.match(/\.json$/i) if (isMbtilesFile) { return openMbtilesFile(filePath, file.name) } else if (isDirectory) { return directoryToMapInfo(filePath, file.name) + } else if (isMbstylesFile) { + return openMbstylesFile(filePath, file.name) } else { return Promise.resolve(null) } @@ -80,13 +83,13 @@ function openMbtilesFile(file: string, filename: string) { type: 'tilelayer', scale: parseInt(res.metadata.scale) || 250000, v1: { - tilemapUrl: `~basePath~/${identifier}/{z}/{x}/{y}`, + tilemapUrl: `~basePath~/~tilePath~/${identifier}/{z}/{x}/{y}`, chartLayers: res.metadata.vector_layers ? parseVectorLayers(res.metadata.vector_layers) : [] }, v2: { - url: `~basePath~/${identifier}/{z}/{x}/{y}`, + url: `~basePath~/~tilePath~/${identifier}/{z}/{x}/{y}`, layers: res.metadata.vector_layers ? parseVectorLayers(res.metadata.vector_layers) : [] @@ -101,6 +104,36 @@ function openMbtilesFile(file: string, filename: string) { ) } +export function encStyleToId(filename: string) { + return filename.replace('.json', '').replaceAll(' ', '-').toLocaleLowerCase() +} + +async function openMbstylesFile(file: string, filename: string) { + const json = JSON.parse(await fs.readFile(file, 'utf8')) + const identifier = encStyleToId(filename) + return { + _flipY: false, + name: json.name, + description: '', + identifier, + bounds: undefined, + minzoom: undefined, + maxzoom: undefined, + format: undefined, + type: 'mapstyleJSON', + scale: 250000, + _filePath: file, + v1: { + tilemapUrl: `~basePath~/~stylePath~/${filename}`, + chartLayers: undefined + }, + v2: { + url: `~basePath~/~stylePath~/${filename}`, + layers: undefined + } + } +} + function parseVectorLayers(layers: Array<{ id: string }>) { return layers.map((l) => l.id) } diff --git a/src/index.ts b/src/index.ts index c89cfdf..87e8d28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import * as bluebird from 'bluebird' import path from 'path' import fs from 'fs' import * as _ from 'lodash' -import { findCharts } from './charts' +import { findCharts, encStyleToId } from './charts' import { ChartProvider, OnlineChartProvider } from './types' import { Request, Response, Application } from 'express' import { OutgoingHttpHeaders } from 'http' @@ -38,6 +38,7 @@ const MIN_ZOOM = 1 const MAX_ZOOM = 24 let basePath: string const chartTilesPath = 'chart-tiles' +const chartStylesPath = 'chart-styles' module.exports = (app: ChartProviderApp): Plugin => { let chartProviders: { [key: string]: ChartProvider } = {} @@ -173,7 +174,7 @@ module.exports = (app: ChartProviderApp): Plugin => { pluginStarted = true basePath = `${app.config.ssl ? 'https' : 'http'}://localhost:${ 'getExternalPort' in app.config ? app.config.getExternalPort() : 3000 - }/${chartTilesPath}` + }` app.debug('**basePath**', basePath) app.setPluginStatus('Started') @@ -230,6 +231,7 @@ module.exports = (app: ChartProviderApp): Plugin => { const registerRoutes = () => { app.debug('** Registering API paths **') + app.debug(`** Registering map tile path (${chartTilesPath} **`) app.get( `/${chartTilesPath}/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, async (req: Request, res: Response) => { @@ -264,6 +266,23 @@ module.exports = (app: ChartProviderApp): Plugin => { } ) + app.debug(`** Registering MapBox styles path (${chartStylesPath} **`) + app.get( + `/${chartStylesPath}/:style`, + async (req: Request, res: Response) => { + const { style } = req.params + const identifier = encStyleToId(style) + const provider = chartProviders[identifier] + res.sendFile(provider._filePath) + /*res.json({ + path: req.path, + style, + identifier, + file: provider._filePath + })*/ + } + ) + app.debug('** Registering v1 API paths **') app.get( @@ -384,10 +403,20 @@ const sanitizeProvider = (provider: ChartProvider, version = 1) => { let v if (version === 1) { v = _.merge({}, provider.v1) - v.tilemapUrl = v.tilemapUrl.replace('~basePath~', basePath) + v.tilemapUrl = v.tilemapUrl + ? v.tilemapUrl + .replace('~basePath~', basePath) + .replace('~stylePath~', chartStylesPath) + .replace('~tilePath~', chartTilesPath) + : '' } else { v = _.merge({}, provider.v2) - v.url = v.url ? v.url.replace('~basePath~', basePath) : '' + v.url = v.url + ? v.url + .replace('~basePath~', basePath) + .replace('~stylePath~', chartStylesPath) + .replace('~tilePath~', chartTilesPath) + : '' } provider = _.omit(provider, [ '_filePath', diff --git a/src/types.ts b/src/types.ts index 9bdc2e6..dad6bba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,11 +19,11 @@ export interface ChartProvider { scale: number v1?: { tilemapUrl: string - chartLayers: string[] + chartLayers?: string[] } v2?: { url: string - layers: string[] + layers?: string[] } bounds?: number[] minzoom?: number diff --git a/tsconfig.json b/tsconfig.json index 8987f83..b16c5ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "ES2022", "module": "commonjs", "outDir": "./plugin", "esModuleInterop": true, @@ -21,7 +21,7 @@ "ignoreCompilerErrors": true, "excludePrivate": true, "excludeNotExported": true, - "target": "ES5", + "target": "ES2022", "moduleResolution": "node", "preserveConstEnums": true, "stripInternal": true, From dfe4cd4f78276f8f70f9bc5a57e0c7c5b9196aa9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:51:53 +0930 Subject: [PATCH 03/17] TMS tiles path --- src/charts.ts | 4 ++-- src/index.ts | 38 ++++++++++++++++---------------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/charts.ts b/src/charts.ts index c5c4c3e..2c5e8d8 100644 --- a/src/charts.ts +++ b/src/charts.ts @@ -166,11 +166,11 @@ function directoryToMapInfo(file: string, identifier: string) { ;(info._fileFormat = 'directory'), (info._filePath = file), (info.v1 = { - tilemapUrl: `~basePath~/${identifier}/{z}/{x}/{y}`, + tilemapUrl: `~basePath~/~tilePath~/${identifier}/{z}/{x}/{y}`, chartLayers: [] }) info.v2 = { - url: `~basePath~/${identifier}/{z}/{x}/{y}`, + url: `~basePath~/~tilePath~/${identifier}/{z}/{x}/{y}`, layers: [] } diff --git a/src/index.ts b/src/index.ts index 87e8d28..f523959 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,14 +39,11 @@ const MAX_ZOOM = 24 let basePath: string const chartTilesPath = 'chart-tiles' const chartStylesPath = 'chart-styles' +let chartPaths: Array +let onlineProviders = {} module.exports = (app: ChartProviderApp): Plugin => { let chartProviders: { [key: string]: ChartProvider } = {} - let pluginStarted = false - let props: Config = { - chartPaths: [], - onlineChartProviders: [] - } const configBasePath = app.config.configPath const defaultChartsPath = path.join(configBasePath, '/charts') const serverMajorVersion = app.config.version @@ -158,8 +155,8 @@ module.exports = (app: ChartProviderApp): Plugin => { name: 'Signal K Charts', schema: () => CONFIG_SCHEMA, uiSchema: () => CONFIG_UISCHEMA, - start: (settings: object) => { - return doStartup(settings as Config) // return required for tests + start: (config: object) => { + return doStartup(config as Config) // return required for tests }, stop: () => { app.setPluginStatus('stopped') @@ -169,29 +166,19 @@ module.exports = (app: ChartProviderApp): Plugin => { const doStartup = (config: Config) => { app.debug('** loaded config: ', config) - // Do not register routes if plugin has been started once already - pluginStarted === false && registerRoutes() - pluginStarted = true + registerRoutes() basePath = `${app.config.ssl ? 'https' : 'http'}://localhost:${ 'getExternalPort' in app.config ? app.config.getExternalPort() : 3000 }` app.debug('**basePath**', basePath) app.setPluginStatus('Started') - return loadCharts(config) - } - - // Load chart files - const loadCharts = (config: Config) => { - props = { ...config } - - const chartPaths = _.isEmpty(props.chartPaths) + chartPaths = _.isEmpty(config.chartPaths) ? [defaultChartsPath] - : resolveUniqueChartPaths(props.chartPaths, configBasePath) + : resolveUniqueChartPaths(config.chartPaths, configBasePath) - // load from config - const onlineProviders = _.reduce( - props.onlineChartProviders, + onlineProviders = _.reduce( + config.onlineChartProviders, (result: { [key: string]: object }, data) => { const provider = convertOnlineProviderConfig(data) result[provider.identifier] = provider @@ -206,6 +193,13 @@ module.exports = (app: ChartProviderApp): Plugin => { )}, online charts: ${Object.keys(onlineProviders).length}` ) + return loadCharts() + } + + // Load chart files + const loadCharts = () => { + app.debug(`Loading Charts....`) + const loadProviders = bluebird .mapSeries(chartPaths, (chartPath: string) => findCharts(chartPath)) .then((list: ChartProvider[]) => From dd3123bbcf4a611aba018ee9e3712100f3a1cdce Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:47:54 +0930 Subject: [PATCH 04/17] Update tests. --- test/expected-charts.json | 6 +++--- test/plugin-test.js | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/test/expected-charts.json b/test/expected-charts.json index 5c8b180..46984ba 100644 --- a/test/expected-charts.json +++ b/test/expected-charts.json @@ -14,7 +14,7 @@ "minzoom": 3, "name": "MBTILES_19", "scale": 250000, - "tilemapUrl": "/signalk/v1/api/resources/charts/test/{z}/{x}/{y}", + "tilemapUrl": "http://localhost:3000/chart-tiles/test/{z}/{x}/{y}", "type": "tilelayer" }, "tms-tiles": { @@ -32,7 +32,7 @@ "minzoom": 4, "name": "Översikt Svenska Sjökort", "scale": 4000000, - "tilemapUrl": "/signalk/v1/api/resources/charts/tms-tiles/{z}/{x}/{y}", + "tilemapUrl": "http://localhost:3000/chart-tiles/tms-tiles/{z}/{x}/{y}", "type": "tilelayer" }, "unpacked-tiles": { @@ -50,7 +50,7 @@ "minzoom": 3, "name": "NOAA MBTiles test file", "scale": 250000, - "tilemapUrl": "/signalk/v1/api/resources/charts/unpacked-tiles/{z}/{x}/{y}", + "tilemapUrl": "http://localhost:3000/chart-tiles/unpacked-tiles/{z}/{x}/{y}", "type": "tilelayer" } } \ No newline at end of file diff --git a/test/plugin-test.js b/test/plugin-test.js index f9bcfa5..63a0027 100644 --- a/test/plugin-test.js +++ b/test/plugin-test.js @@ -109,7 +109,8 @@ describe('GET /resources/charts', () => { }) -describe('GET /resources/charts/:identifier/:z/:x/:y', () => { + +describe('GET /chart-tiles/:identifier/:z/:x/:y', () => { let plugin let testServer beforeEach(() => @@ -123,7 +124,7 @@ describe('GET /resources/charts/:identifier/:z/:x/:y', () => { it('returns correct tile from MBTiles file', () => { return plugin.start({}) - .then(() => get(testServer, '/signalk/v1/api/resources/charts/test/4/5/6')) + .then(() => get(testServer, '/chart-tiles/test/4/5/6')) .then(response => { // unpacked-tiles contains same tiles as the test.mbtiles file expectTileResponse(response, 'charts/unpacked-tiles/4/5/6.png', 'image/png') @@ -133,7 +134,7 @@ describe('GET /resources/charts/:identifier/:z/:x/:y', () => { it('returns correct tile from directory', () => { const expectedTile = fs.readFileSync(path.resolve(__dirname, 'charts/unpacked-tiles/4/4/6.png')) return plugin.start({}) - .then(() => get(testServer, '/signalk/v1/api/resources/charts/unpacked-tiles/4/4/6')) + .then(() => get(testServer, '/chart-tiles/unpacked-tiles/4/4/6')) .then(response => { expectTileResponse(response, 'charts/unpacked-tiles/4/4/6.png', 'image/png') }) @@ -143,7 +144,7 @@ describe('GET /resources/charts/:identifier/:z/:x/:y', () => { const expectedTile = fs.readFileSync(path.resolve(__dirname, 'charts/tms-tiles/5/17/21.png')) // Y-coordinate flipped return plugin.start({}) - .then(() => get(testServer, '/signalk/v1/api/resources/charts/tms-tiles/5/17/10')) + .then(() => get(testServer, '/chart-tiles/tms-tiles/5/17/10')) .then(response => { expectTileResponse(response, 'charts/tms-tiles/5/17/21.png', 'image/png') }) @@ -151,7 +152,7 @@ describe('GET /resources/charts/:identifier/:z/:x/:y', () => { it('returns 404 for missing tile', () => { return plugin.start({}) - .then(() => get(testServer, '/signalk/v1/api/resources/charts/tms-tiles/5/55/10')) + .then(() => get(testServer, '/chart-tiles/tms-tiles/5/55/10')) .catch(e => e.response) .then(response => { expect(response.status).to.equal(404) @@ -160,7 +161,7 @@ describe('GET /resources/charts/:identifier/:z/:x/:y', () => { it('returns 404 for wrong chart identifier', () => { return plugin.start({}) - .then(() => get(testServer, '/signalk/v1/api/resources/charts/foo/4/4/6')) + .then(() => get(testServer, '/chart-tiles/foo/4/4/6')) .catch(e => e.response) .then(response => { expect(response.status).to.equal(404) From 5b4f6518ca7bd8afc241a112f2a729d2be25eca0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:28:06 +0930 Subject: [PATCH 05/17] Add MapBox auth token config option. This is appended to the url path to the mapbox style json file. --- src/index.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index f523959..2d9e116 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { interface Config { chartPaths: string[] onlineChartProviders: OnlineChartProvider[] + accessToken: string } interface ChartProviderApp @@ -41,6 +42,7 @@ const chartTilesPath = 'chart-tiles' const chartStylesPath = 'chart-styles' let chartPaths: Array let onlineProviders = {} +let accessTokenGlobal = '' module.exports = (app: ChartProviderApp): Plugin => { let chartProviders: { [key: string]: ChartProvider } = {} @@ -56,6 +58,11 @@ module.exports = (app: ChartProviderApp): Plugin => { title: 'Signal K Charts', type: 'object', properties: { + accessToken: { + type: 'string', + title: 'MapBox Access Token (optional)', + description: `Token to append to mapbox style urls for authentication. e.g. "?access_token=xxxxx"` + }, chartPaths: { type: 'array', title: 'Chart paths', @@ -173,6 +180,8 @@ module.exports = (app: ChartProviderApp): Plugin => { app.debug('**basePath**', basePath) app.setPluginStatus('Started') + accessTokenGlobal = config.accessToken ?? '' + chartPaths = _.isEmpty(config.chartPaths) ? [defaultChartsPath] : resolveUniqueChartPaths(config.chartPaths, configBasePath) @@ -268,12 +277,6 @@ module.exports = (app: ChartProviderApp): Plugin => { const identifier = encStyleToId(style) const provider = chartProviders[identifier] res.sendFile(provider._filePath) - /*res.json({ - path: req.path, - style, - identifier, - file: provider._filePath - })*/ } ) @@ -393,20 +396,30 @@ const convertOnlineProviderConfig = (provider: OnlineChartProvider) => { return data } +const applyAccessToken = (uri: string) => { + if (uri.includes('access_token') || !uri.includes('~stylePath~')) { + return uri + } else { + return `${uri}?access_token=${accessTokenGlobal}` + } +} + const sanitizeProvider = (provider: ChartProvider, version = 1) => { let v if (version === 1) { v = _.merge({}, provider.v1) - v.tilemapUrl = v.tilemapUrl - ? v.tilemapUrl + const uri = applyAccessToken(v?.tilemapUrl) + v.tilemapUrl = uri + ? uri .replace('~basePath~', basePath) .replace('~stylePath~', chartStylesPath) .replace('~tilePath~', chartTilesPath) : '' } else { v = _.merge({}, provider.v2) - v.url = v.url - ? v.url + const uri = applyAccessToken(v?.url) + v.url = uri + ? uri .replace('~basePath~', basePath) .replace('~stylePath~', chartStylesPath) .replace('~tilePath~', chartTilesPath) From a0b490969baee54970bba5e18f9a5e7776e06daa Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 30 Sep 2024 08:55:31 +0930 Subject: [PATCH 06/17] Revert to use of relative paths for map tiles url. Ensure operation behind proxy is not impacted. --- CHANELOG.md | 3 --- src/index.ts | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANELOG.md b/CHANELOG.md index 6b43df5..e12082a 100644 --- a/CHANELOG.md +++ b/CHANELOG.md @@ -6,9 +6,6 @@ - **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. -- **Updated**: Tile url value is now a fully qualified url rather than a relative path. - - --- ### 3.0.0 diff --git a/src/index.ts b/src/index.ts index 2d9e116..9508105 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ interface ChartProviderApp const MIN_ZOOM = 1 const MAX_ZOOM = 24 -let basePath: string +const basePath = '' const chartTilesPath = 'chart-tiles' const chartStylesPath = 'chart-styles' let chartPaths: Array @@ -174,10 +174,6 @@ module.exports = (app: ChartProviderApp): Plugin => { app.debug('** loaded config: ', config) registerRoutes() - basePath = `${app.config.ssl ? 'https' : 'http'}://localhost:${ - 'getExternalPort' in app.config ? app.config.getExternalPort() : 3000 - }` - app.debug('**basePath**', basePath) app.setPluginStatus('Started') accessTokenGlobal = config.accessToken ?? '' From a037b1c0698163f4c604b0352f7548dc560161c8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 30 Sep 2024 08:57:31 +0930 Subject: [PATCH 07/17] Revert to use of relative paths for map tiles url. Ensure operation behind proxy is not impacted. --- test/expected-charts.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/expected-charts.json b/test/expected-charts.json index 46984ba..4da2e3a 100644 --- a/test/expected-charts.json +++ b/test/expected-charts.json @@ -14,7 +14,7 @@ "minzoom": 3, "name": "MBTILES_19", "scale": 250000, - "tilemapUrl": "http://localhost:3000/chart-tiles/test/{z}/{x}/{y}", + "tilemapUrl": "/chart-tiles/test/{z}/{x}/{y}", "type": "tilelayer" }, "tms-tiles": { @@ -32,7 +32,7 @@ "minzoom": 4, "name": "Översikt Svenska Sjökort", "scale": 4000000, - "tilemapUrl": "http://localhost:3000/chart-tiles/tms-tiles/{z}/{x}/{y}", + "tilemapUrl": "/chart-tiles/tms-tiles/{z}/{x}/{y}", "type": "tilelayer" }, "unpacked-tiles": { @@ -50,7 +50,7 @@ "minzoom": 3, "name": "NOAA MBTiles test file", "scale": 250000, - "tilemapUrl": "http://localhost:3000/chart-tiles/unpacked-tiles/{z}/{x}/{y}", + "tilemapUrl": "/chart-tiles/unpacked-tiles/{z}/{x}/{y}", "type": "tilelayer" } } \ No newline at end of file From e4aaa1167716baae8bb63f58bbb1bf8262038446 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:12:11 +0930 Subject: [PATCH 08/17] chore: doc --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 046e35a..2a2ec6a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,14 @@ Signal K Node server plugin to provide chart metadata, such as name, description and location of the actual chart tile data. -Supports both v1 and v2 Signal K resources api paths. +Chart metadata is derived from the following supported chart files: +- Mapbox Tiles _(.mbtiles)_ +- Mapbox Style _(.json)_ +- TMS _(tilemapresource.xml and tiles)_ + +Additionally chart metadata can be defined for other chart sources and types _(e.g. WMS, WMTS, S-57 tiles and tileJSON)_. + +Chart metadata made available to both v1 and v2 Signal K resources api paths. | Server Version | API | Path | |--- |--- |--- | From ae72a4a42560850e3f36b231a31cae87576413ab Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:01:31 +0930 Subject: [PATCH 09/17] Watch chart folders for changes and refresh providers. --- CHANELOG.md | 2 ++ src/index.ts | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANELOG.md b/CHANELOG.md index e12082a..02206e1 100644 --- a/CHANELOG.md +++ b/CHANELOG.md @@ -4,6 +4,8 @@ - **Added**: Detection, processing and serving of mapbox style json files. Files served from `/chart-styles` +- **Added**: Watch chart folders for changes and refresh chart providers (#28) + - **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. --- diff --git a/src/index.ts b/src/index.ts index 9508105..c8ddd3a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import * as bluebird from 'bluebird' import path from 'path' -import fs from 'fs' +import fs, { FSWatcher } from 'fs' import * as _ from 'lodash' import { findCharts, encStyleToId } from './charts' import { ChartProvider, OnlineChartProvider } from './types' @@ -43,6 +43,8 @@ const chartStylesPath = 'chart-styles' let chartPaths: Array let onlineProviders = {} let accessTokenGlobal = '' +let lastWatchEvent: number | undefined +const watchers: Array = [] module.exports = (app: ChartProviderApp): Plugin => { let chartProviders: { [key: string]: ChartProvider } = {} @@ -166,6 +168,7 @@ module.exports = (app: ChartProviderApp): Plugin => { return doStartup(config as Config) // return required for tests }, stop: () => { + watchers.forEach((w) => w.close()) app.setPluginStatus('stopped') } } @@ -192,6 +195,15 @@ module.exports = (app: ChartProviderApp): Plugin => { {} ) + chartPaths.forEach((p) => { + console.log('watching..', p) + watchers.push( + fs.watch(p, 'utf8', (eventType, filename) => + handleWatchEvent(eventType, filename) + ) + ) + }) + app.debug( `Start charts plugin. Chart paths: ${chartPaths.join( ', ' @@ -227,6 +239,21 @@ module.exports = (app: ChartProviderApp): Plugin => { }) } + const refreshProviders = async () => { + const td = Date.now() - (lastWatchEvent as number) + app.debug(`last watch event time elapsed = ${td}`) + if (lastWatchEvent && td > 10000) { + app.debug(`reloading Charts`) + lastWatchEvent = undefined + loadCharts() + } + } + + const handleWatchEvent = (eventType: string, filename: string) => { + console.log('***', eventType, filename) + lastWatchEvent = Date.now() + } + const registerRoutes = () => { app.debug('** Registering API paths **') @@ -235,10 +262,12 @@ module.exports = (app: ChartProviderApp): Plugin => { `/${chartTilesPath}/:identifier/:z([0-9]*)/:x([0-9]*)/:y([0-9]*)`, async (req: Request, res: Response) => { const { identifier, z, x, y } = req.params + await refreshProviders() const provider = chartProviders[identifier] if (!provider) { return res.sendStatus(404) } + switch (provider._fileFormat) { case 'directory': return serveTileFromFilesystem( @@ -271,6 +300,7 @@ module.exports = (app: ChartProviderApp): Plugin => { async (req: Request, res: Response) => { const { style } = req.params const identifier = encStyleToId(style) + await refreshProviders() const provider = chartProviders[identifier] res.sendFile(provider._filePath) } @@ -280,8 +310,9 @@ module.exports = (app: ChartProviderApp): Plugin => { app.get( '/signalk/v1/api/resources/charts/:identifier', - (req: Request, res: Response) => { + async (req: Request, res: Response) => { const { identifier } = req.params + await refreshProviders() const provider = chartProviders[identifier] if (provider) { return res.json(sanitizeProvider(provider)) @@ -293,7 +324,8 @@ module.exports = (app: ChartProviderApp): Plugin => { app.get( '/signalk/v1/api/resources/charts', - (req: Request, res: Response) => { + async (req: Request, res: Response) => { + await refreshProviders() const sanitized = _.mapValues(chartProviders, (provider) => sanitizeProvider(provider) ) @@ -315,18 +347,20 @@ module.exports = (app: ChartProviderApp): Plugin => { app.registerResourceProvider({ type: 'charts', methods: { - listResources: (params: { + listResources: async (params: { [key: string]: number | string | object | null }) => { app.debug(`** listResources()`, params) + await refreshProviders() return Promise.resolve( _.mapValues(chartProviders, (provider) => sanitizeProvider(provider, 2) ) ) }, - getResource: (id: string) => { + getResource: async (id: string) => { app.debug(`** getResource()`, id) + await refreshProviders() const provider = chartProviders[id] if (provider) { return Promise.resolve(sanitizeProvider(provider, 2)) From 46e834e824ee7af8ab74e7cc5f5c9de2811e15aa Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:07:34 +0930 Subject: [PATCH 10/17] remove watcher parameters --- src/index.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index c8ddd3a..1aa9556 100644 --- a/src/index.ts +++ b/src/index.ts @@ -197,11 +197,7 @@ module.exports = (app: ChartProviderApp): Plugin => { chartPaths.forEach((p) => { console.log('watching..', p) - watchers.push( - fs.watch(p, 'utf8', (eventType, filename) => - handleWatchEvent(eventType, filename) - ) - ) + watchers.push(fs.watch(p, 'utf8', () => handleWatchEvent())) }) app.debug( @@ -249,8 +245,7 @@ module.exports = (app: ChartProviderApp): Plugin => { } } - const handleWatchEvent = (eventType: string, filename: string) => { - console.log('***', eventType, filename) + const handleWatchEvent = () => { lastWatchEvent = Date.now() } From ba0c4bf3d08b0621e1ff6c37dbabf603ff8e628a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:14:39 +0930 Subject: [PATCH 11/17] Fix test. --- test/plugin-test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/plugin-test.js b/test/plugin-test.js index 63a0027..421f19d 100644 --- a/test/plugin-test.js +++ b/test/plugin-test.js @@ -120,7 +120,10 @@ describe('GET /chart-tiles/:identifier/:z/:x/:y', () => { testServer = server }) ) - afterEach(done => testServer.close(() => done())) + afterEach(done => { + plugin.stop() + testServer.close(() => done()) + }) it('returns correct tile from MBTiles file', () => { return plugin.start({}) From 55f4775950d533c542972b2ff65fef836c80730c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:45:22 +0930 Subject: [PATCH 12/17] chore: update package dependencies --- CHANELOG.md | 2 ++ package.json | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANELOG.md b/CHANELOG.md index 02206e1..c99c506 100644 --- a/CHANELOG.md +++ b/CHANELOG.md @@ -8,6 +8,8 @@ - **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. +- **Updated**: Updated package dependencies (#35) + --- ### 3.0.0 diff --git a/package.json b/package.json index 6c7e308..bf3e4aa 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,9 @@ "dependencies": { "@mapbox/mbtiles": "^0.12.1", "@signalk/server-api": "^2.0.0-beta.3", - "baconjs": "1.0.1", "bluebird": "3.5.1", "lodash": "^4.17.11", - "xml2js": "0.4.19" + "xml2js": "^0.6.2" }, "repository": { "type": "git", @@ -47,12 +46,12 @@ "@types/node": "^18.14.4", "@typescript-eslint/eslint-plugin": "^5.52.0", "@typescript-eslint/parser": "^5.52.0", - "body-parser": "1.18.2", + "body-parser": "^1.18.2", "chai": "4.1.2", "chai-http": "^4.2.1", "eslint": "^8.34.0", "eslint-config-prettier": "^8.6.0", - "express": "4.19.2", + "express": "^4.19.2", "mocha": "5.0.0", "prettier": "^2.8.4", "typescript": "^4.5.4" From 1491e4eb3cd6ec8f336abf3f356ccad940bdb2b3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:13:53 +1030 Subject: [PATCH 13/17] use async / await in loadProviders --- src/index.ts | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1aa9556..e6b1b60 100644 --- a/src/index.ts +++ b/src/index.ts @@ -173,7 +173,7 @@ module.exports = (app: ChartProviderApp): Plugin => { } } - const doStartup = (config: Config) => { + const doStartup = async (config: Config) => { app.debug('** loaded config: ', config) registerRoutes() @@ -196,7 +196,7 @@ module.exports = (app: ChartProviderApp): Plugin => { ) chartPaths.forEach((p) => { - console.log('watching..', p) + app.debug('watching folder..', p) watchers.push(fs.watch(p, 'utf8', () => handleWatchEvent())) }) @@ -210,38 +210,39 @@ module.exports = (app: ChartProviderApp): Plugin => { } // Load chart files - const loadCharts = () => { + const loadCharts = async () => { app.debug(`Loading Charts....`) - const loadProviders = bluebird - .mapSeries(chartPaths, (chartPath: string) => findCharts(chartPath)) - .then((list: ChartProvider[]) => - _.reduce(list, (result, charts) => _.merge({}, result, charts), {}) + try { + const plist = await bluebird.mapSeries(chartPaths, (chartPath: string) => + findCharts(chartPath) ) - - return loadProviders - .then((charts: { [key: string]: ChartProvider }) => { - app.debug( - `Chart plugin: Found ${ - _.keys(charts).length - } charts from ${chartPaths.join(', ')}.` - ) - chartProviders = _.merge({}, charts, onlineProviders) - }) - .catch((e: Error) => { - console.error(`Error loading chart providers`, e.message) - chartProviders = {} - app.setPluginError(`Error loading chart providers`) - }) + const charts = _.reduce( + plist, + (result, charts) => _.merge({}, result, charts), + {} + ) + app.debug( + `Chart plugin: Found ${ + _.keys(charts).length + } charts from ${chartPaths.join(', ')}.` + ) + chartProviders = _.merge({}, charts, onlineProviders) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + console.error(`Error loading chart providers`, e.message) + chartProviders = {} + app.setPluginError(`Error loading chart providers`) + } } const refreshProviders = async () => { const td = Date.now() - (lastWatchEvent as number) app.debug(`last watch event time elapsed = ${td}`) - if (lastWatchEvent && td > 10000) { - app.debug(`reloading Charts`) + if (lastWatchEvent && td > 5000) { + app.debug(`Reloading Charts...`) lastWatchEvent = undefined - loadCharts() + await loadCharts() } } From 5163475ec452df12d95bbd2d26606a33be70f84b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:27:14 +1030 Subject: [PATCH 14/17] Align chart types --- src/charts.ts | 2 +- src/index.ts | 4 ++-- src/types.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/charts.ts b/src/charts.ts index 2c5e8d8..7a389d3 100644 --- a/src/charts.ts +++ b/src/charts.ts @@ -120,7 +120,7 @@ async function openMbstylesFile(file: string, filename: string) { minzoom: undefined, maxzoom: undefined, format: undefined, - type: 'mapstyleJSON', + type: 'mapboxstyle', scale: 250000, _filePath: file, v1: { diff --git a/src/index.ts b/src/index.ts index e6b1b60..9a6d70e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,8 +114,8 @@ module.exports = (app: ChartProviderApp): Plugin => { 'S-57', 'WMS', 'WMTS', - 'mapstyleJSON', - 'tileJSON' + 'mapboxstyle', + 'tilejson' ], description: 'Map data source type served by the supplied url. (Use tilelayer for xyz / tms tile sources.)' diff --git a/src/types.ts b/src/types.ts index dad6bba..f133bfb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,8 +3,8 @@ type MapSourceType = | 'S-57' | 'WMS' | 'WMTS' - | 'mapstyleJSON' - | 'tileJSON' + | 'mapboxstyle' + | 'tilejson' export interface ChartProvider { _fileFormat?: 'mbtiles' | 'directory' From 67ae54ed28eef88968d5de80c6965a2118c4f8d6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:27:26 +1030 Subject: [PATCH 15/17] chore: docs --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2a2ec6a..ef62f5e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ Signal K Node server plugin to provide chart metadata, such as name, description Chart metadata is derived from the following supported chart files: - Mapbox Tiles _(.mbtiles)_ -- Mapbox Style _(.json)_ +- Mapbox Styles _(.json)_ - TMS _(tilemapresource.xml and tiles)_ -Additionally chart metadata can be defined for other chart sources and types _(e.g. WMS, WMTS, S-57 tiles and tileJSON)_. +Additionally chart metadata can be defined for other chart sources and types _(e.g. WMS, WMTS, S-57 tiles and tilejson)_. Chart metadata made available to both v1 and v2 Signal K resources api paths. @@ -39,7 +39,10 @@ _Note: v2 resource paths will only be made available on Signal K server >= v2._ Online chart providers configuration +6. (Optional): Add Mapbox access token. + When provided, the access token will added to the url of Mapbox Styles _e.g. `?access_token=xyz123`_ + ![image](https://github.com/user-attachments/assets/b4d4d048-2ab1-4bf1-896b-2ca0031ec77f) _WMS example:_ @@ -53,8 +56,10 @@ _WMS example:_ - [Tuktuk Chart Plotter](https://www.npmjs.com/package/tuktuk-chart-plotter) ### Supported chart formats +pk.eyJ1IjoiYWRhbTIyMjIiLCJhIjoiY2l5dGJhaW96MDAwcDJ3bzM0MXk2aTB0bSJ9.kgHNRDiGEmq12toljp2-kA - [MBTiles](https://github.com/mapbox/mbtiles-spec) file +- [Mapbox Style](https://docs.mapbox.com/help/glossary/style/) JSON file _e.g. `bright-v9.json`_ - Directory with cached [TMS](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification) tiles and `tilemapresource.xml` - Directory with XYZ tiles and `metadata.json` - Online [TMS](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification) @@ -68,9 +73,46 @@ Publicly available MBTiles charts can be found from: Plugin adds support for `/resources/charts` endpoints described in [Signal K specification](http://signalk.org/specification/1.0.0/doc/otherBranches.html#resourcescharts): -- `GET /signalk/v1/api/resources/charts/` returns metadata for all available charts -- `GET /signalk/v1/api/resources/charts/${identifier}/` returns metadata for selected chart -- `GET /signalk/v1/api/resources/charts/${identifier}/${z}/${x}/${y}` returns a single tile for selected offline chart. As charts-plugin isn't proxy, online charts is not available via this request. You should look the metadata to find proper request. +- Return metadata for all available charts + +```bash +# v1 API +GET /signalk/v1/api/resources/charts/` + +# v2 API +GET /signalk/v2/api/resources/charts/` +``` + +- Return metadata for selected chart + +```bash +# v1 API +GET /signalk/v1/api/resources/charts/${identifier}` + +# v2 API +GET /signalk/v2/api/resources/charts/${identifier}` +``` + +#### Chart Tiles +Chart tiles are retrieved using the url defined in the chart metadata. + +For chart files placed in the path(s) defined in the plugin configuration, the url will be: + +```bash +/chart-tiles/${identifier}/${z}/${x}/${y} +``` + +#### Mapbox Styles + +For Mapbox Styles JSON files the url returned in the metadata will be: + +```bash +/chart-styles/${mapboxstyle.json} + +# when access token is defined +/chart-styles/${mapboxstyle.json}?access_token=${token} +``` + License ------- From 4a8e51547e60fed73cf90a838ef7ddbc31ba2a48 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:29:11 +1030 Subject: [PATCH 16/17] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf3e4aa..0ef7f08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/charts-plugin", - "version": "3.0.0", + "version": "3.1.0", "description": "Signal K plugin to provide chart support for Signal K server", "main": "plugin/index.js", "scripts": { From 0d82e9ae23020ee4ba1a7f2aab14fdda5ea34ee9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:32:07 +1030 Subject: [PATCH 17/17] chore: docs --- CHANELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANELOG.md b/CHANELOG.md index c99c506..85b1deb 100644 --- a/CHANELOG.md +++ b/CHANELOG.md @@ -4,9 +4,11 @@ - **Added**: Detection, processing and serving of mapbox style json files. Files served from `/chart-styles` +- **Added**: Ability to provide a Mapbox access token in the plugin configuration. + - **Added**: Watch chart folders for changes and refresh chart providers (#28) -- **Updated**: Moved the map tile url path to be out from under `resources/charts` to `/chart-tiles`. This better aligns with v2 multiple-provider support. +- **Updated**: Move the serving of map tiles out from under `resources/charts` to `/chart-tiles` to better aligns with v2 multiple-provider support. - **Updated**: Updated package dependencies (#35)