@signalk/charts-plugin
Version:
Signal K plugin to provide chart support for Signal K server
255 lines (254 loc) • 9.72 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findCharts = void 0;
const bluebird = __importStar(require("bluebird"));
const path_1 = __importDefault(require("path"));
const mbtiles_1 = __importDefault(require("@mapbox/mbtiles"));
const xml2js = __importStar(require("xml2js"));
const fs_1 = require("fs");
const _ = __importStar(require("lodash"));
function findCharts(chartBaseDir) {
return fs_1.promises
.readdir(chartBaseDir, { withFileTypes: true })
.then((files) => {
return bluebird.mapSeries(files, (file) => {
const isMbtilesFile = file.name.match(/\.mbtiles$/i);
const filePath = path_1.default.resolve(chartBaseDir, file.name);
const isDirectory = file.isDirectory();
if (isMbtilesFile) {
return openMbtilesFile(filePath, file.name);
}
else if (isDirectory) {
return directoryToMapInfo(filePath, file.name);
}
else {
return Promise.resolve(null);
}
});
})
.then((result) => _.filter(result, _.identity))
.then((charts) => _.reduce(charts,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(result, chart) => {
result[chart.identifier] = chart;
return result;
}, {}))
.catch((err) => {
console.error(`Error reading charts directory ${chartBaseDir}:${err.message}`);
});
}
exports.findCharts = findCharts;
function openMbtilesFile(file, filename) {
return (new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new mbtiles_1.default(file, (err, mbtiles) => {
if (err) {
return reject(err);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mbtiles.getInfo((err, metadata) => {
if (err) {
return reject(err);
}
return resolve({ mbtiles, metadata });
});
});
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((res) => {
if (_.isEmpty(res.metadata) || res.metadata.bounds === undefined) {
return null;
}
const identifier = filename.replace(/\.mbtiles$/i, '');
const data = {
_fileFormat: 'mbtiles',
_filePath: file,
_mbtilesHandle: res.mbtiles,
_flipY: false,
identifier,
name: res.metadata.name || res.metadata.id,
description: res.metadata.description,
bounds: res.metadata.bounds,
minzoom: res.metadata.minzoom,
maxzoom: res.metadata.maxzoom,
format: res.metadata.format,
type: 'tilelayer',
scale: parseInt(res.metadata.scale) || 250000,
v1: {
tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
chartLayers: res.metadata.vector_layers
? parseVectorLayers(res.metadata.vector_layers)
: []
},
v2: {
url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
layers: res.metadata.vector_layers
? parseVectorLayers(res.metadata.vector_layers)
: []
}
};
return data;
})
.catch((e) => {
console.error(`Error loading chart ${file}`, e.message);
return null;
}));
}
function parseVectorLayers(layers) {
return layers.map((l) => l.id);
}
function directoryToMapInfo(file, identifier) {
function loadInfo() {
return __awaiter(this, void 0, void 0, function* () {
const tilemapResource = path_1.default.join(file, 'tilemapresource.xml');
const metadataJson = path_1.default.join(file, 'metadata.json');
try {
yield fs_1.promises.stat(tilemapResource);
return parseTilemapResource(tilemapResource);
}
catch (_a) {
try {
yield fs_1.promises.stat(metadataJson);
return parseMetadataJson(metadataJson);
}
catch (_b) {
return null;
}
}
});
}
return loadInfo()
.then((info) => {
if (info) {
if (!info.format) {
console.error(`Missing format metadata for chart ${identifier}`);
return null;
}
info.identifier = identifier;
(info._fileFormat = 'directory'),
(info._filePath = file),
(info.v1 = {
tilemapUrl: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
chartLayers: []
});
info.v2 = {
url: `~basePath~/charts/${identifier}/{z}/{x}/{y}`,
layers: []
};
return info;
}
return null;
})
.catch((e) => {
console.error(`Error getting charts from ${file}`, e.message);
return undefined;
});
}
function parseTilemapResource(tilemapResource) {
return (fs_1.promises
.readFile(tilemapResource)
.then(bluebird.promisify(xml2js.parseString))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.then((parsed) => {
const result = parsed.TileMap;
const name = _.get(result, 'Title.0');
const format = _.get(result, 'TileFormat.0.$.extension');
const scale = _.get(result, 'Metadata.0.$.scale');
const bbox = _.get(result, 'BoundingBox.0.$');
const zoomLevels = _.map(_.get(result, 'TileSets.0.TileSet') || [], (set) => parseInt(_.get(set, '$.href')));
const res = {
_flipY: true,
name,
description: name,
bounds: bbox
? [
parseFloat(bbox.minx),
parseFloat(bbox.miny),
parseFloat(bbox.maxx),
parseFloat(bbox.maxy)
]
: undefined,
minzoom: !_.isEmpty(zoomLevels) ? _.min(zoomLevels) : undefined,
maxzoom: !_.isEmpty(zoomLevels) ? _.max(zoomLevels) : undefined,
format,
type: 'tilelayer',
scale: parseInt(scale) || 250000,
identifier: '',
_filePath: ''
};
return res;
}));
}
function parseMetadataJson(metadataJson) {
return fs_1.promises
.readFile(metadataJson, { encoding: 'utf8' })
.then((txt) => {
return JSON.parse(txt);
})
.then((metadata) => {
function parseBounds(bounds) {
if (_.isString(bounds)) {
return _.map(bounds.split(','), (bound) => parseFloat(_.trim(bound)));
}
else if (_.isArray(bounds) && bounds.length === 4) {
return bounds;
}
else {
return undefined;
}
}
const res = {
_flipY: false,
name: metadata.name || metadata.id,
description: metadata.description || '',
bounds: parseBounds(metadata.bounds),
minzoom: parseIntIfNotUndefined(metadata.minzoom),
maxzoom: parseIntIfNotUndefined(metadata.maxzoom),
format: metadata.format,
type: metadata.type,
scale: parseInt(metadata.scale) || 250000,
identifier: '',
_filePath: ''
};
return res;
});
}
function parseIntIfNotUndefined(val) {
const parsed = parseInt(val);
return _.isFinite(parsed) ? parsed : undefined;
}