From 9d8a2d76f101b762c032c300bc1f78d66a257309 Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Sun, 3 Nov 2024 15:36:18 +0100 Subject: [PATCH] More types and refactoring; require @mapbox/mbtiles --- main.ts | 2 ++ src/BrailleBuffer.ts | 77 +++++++++++++++++++++++------------------- src/Canvas.ts | 4 +-- src/LabelBuffer.ts | 10 +++--- src/Mapscii.ts | 76 ++++++++++++++++++++++------------------- src/Renderer.ts | 4 +-- src/Styler.ts | 5 +-- src/Tile.ts | 1 + src/TileSource.test.ts | 2 +- src/TileSource.ts | 34 ++++++++++--------- src/config.ts | 2 ++ src/utils.ts | 23 +++++++------ 12 files changed, 133 insertions(+), 107 deletions(-) diff --git a/main.ts b/main.ts index 997601f..4a46187 100644 --- a/main.ts +++ b/main.ts @@ -7,6 +7,8 @@ TODO: params parsing and so on #*/ +import process from "node:process"; + import config from './src/config.ts'; import Mapscii from './src/Mapscii.ts'; import yargs from 'yargs/yargs'; diff --git a/src/BrailleBuffer.ts b/src/BrailleBuffer.ts index 8aba8f4..e0d43c7 100644 --- a/src/BrailleBuffer.ts +++ b/src/BrailleBuffer.ts @@ -32,18 +32,18 @@ const termReset = '\x1B[39;49m'; class BrailleBuffer { private brailleMap: number[][]; private pixelBuffer: Buffer; - private charBuffer: unknown[] | null; + private charBuffer: string[]; private foregroundBuffer: Buffer; private backgroundBuffer: Buffer; private height: number; private width: number; - private globalBackground: string | null; - private asciiToBraille: unknown[]; + private globalBackground: number | null; + private asciiToBraille: string[]; constructor(width: number, height: number) { this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]; - this.charBuffer = null; + this.charBuffer = []; this.asciiToBraille = []; @@ -61,42 +61,42 @@ class BrailleBuffer { this.clear(); } - clear() { + clear(): void { this.pixelBuffer.fill(0); this.charBuffer = []; this.foregroundBuffer.fill(0); this.backgroundBuffer.fill(0); } - setGlobalBackground(background: string) { + setGlobalBackground(background: number): void { this.globalBackground = background; } - setBackground(x, y, color) { + setBackground(x: number, y: number, color: number): void { if (0 <= x && x < this.width && 0 <= y && y < this.height) { const idx = this._project(x, y); this.backgroundBuffer[idx] = color; } } - setPixel(x, y, color) { + setPixel(x: number, y: number, color: number): void { this._locate(x, y, (idx, mask) => { this.pixelBuffer[idx] |= mask; this.foregroundBuffer[idx] = color; }); } - unsetPixel(x, y) { + unsetPixel(x: number, y: number): void { this._locate(x, y, (idx, mask) => { this.pixelBuffer[idx] &= ~mask; }); } - _project(x, y) { + private _project(x: number, y: number): number { return (x>>1) + (this.width>>1)*(y>>2); } - _locate(x, y, cb) { + private _locate(x: number, y: number, cb: (idx: number, mask: number) => unknown) { if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) { return; } @@ -105,57 +105,64 @@ class BrailleBuffer { return cb(idx, mask); } - _mapBraille() { + private _mapBraille(): string[] { this.asciiToBraille = [' ']; - const masks = []; + const masks: { + char: string, + covered: number, + mask: number, + }[] = []; for (const char in asciiMap) { - const bits = asciiMap[char]; + const bits: number[] | undefined = asciiMap[char]; if (!(bits instanceof Array)) continue; for (const mask of bits) { masks.push({ - mask: mask, - char: char, + char, + covered: 0, + mask, }); } } //TODO Optimize this part - var i, k; - const results = []; + let i: number, k: number; + const results: string[] = []; for (i = k = 1; k <= 255; i = ++k) { const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128); - results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) { + const char = masks.reduce((best, mask) => { const covered = utils.population(mask.mask & braille); if (!best || best.covered < covered) { return { - char: mask.char, - covered: covered, + ...mask, + covered, }; } else { return best; } - }), void 0).char); + }).char; + this.asciiToBraille[i] = char; + results.push(char); } return results; } - _termColor(foreground, background) { - background |= this.globalBackground; - if (foreground && background) { - return `\x1B[38;5;${foreground};48;5;${background}m`; + private _termColor(foreground: number, background: number): string { + const actualBackground = background ?? this.globalBackground; + if (foreground && actualBackground) { + return `\x1B[38;5;${foreground};48;5;${actualBackground}m`; } else if (foreground) { return `\x1B[49;38;5;${foreground}m`; - } else if (background) { - return `\x1B[39;48;5;${background}m`; + } else if (actualBackground) { + return `\x1B[39;48;5;${actualBackground}m`; } else { return termReset; } } - frame() { - const output = []; - let currentColor = null; + frame(): string { + const output: string[] = []; + let currentColor: string | null = null; let skip = 0; for (let y = 0; y < this.height/4; y++) { @@ -182,7 +189,7 @@ class BrailleBuffer { } else { if (!skip) { if (config.useBraille) { - output.push(String.fromCharCode(0x2800+this.pixelBuffer[idx])); + output.push(String.fromCharCode(0x2800 + this.pixelBuffer[idx])); } else { output.push(this.asciiToBraille[this.pixelBuffer[idx]]); } @@ -193,11 +200,11 @@ class BrailleBuffer { } } - output.push(termReset+config.delimeter); + output.push(termReset + config.delimeter); return output.join(''); } - setChar(char, x, y, color) { + setChar(char: string, x: number, y: number, color: number): void { if (0 <= x && x < this.width && 0 <= y && y < this.height) { const idx = this._project(x, y); this.charBuffer[idx] = char; @@ -205,7 +212,7 @@ class BrailleBuffer { } } - writeText(text, x, y, color, center = true) { + writeText(text, x, y, color, center = true): void { if (center) { x -= text.length/2+1; } diff --git a/src/Canvas.ts b/src/Canvas.ts index a92dcb4..d15787d 100644 --- a/src/Canvas.ts +++ b/src/Canvas.ts @@ -77,7 +77,7 @@ class Canvas { let triangles; try { triangles = earcut(vertices, holes); - } catch (error) { + } catch { return false; } for (let i = 0; i < triangles.length; i += 3) { @@ -169,7 +169,7 @@ class Canvas { const c = this._bresenham(pointA, pointB); const points = a.concat(b).concat(c).filter((point) => { - var ref; + let ref; return (0 <= (ref = point.y) && ref < this.height); }).sort(function(a, b) { if (a.y === b.y) { diff --git a/src/LabelBuffer.ts b/src/LabelBuffer.ts index 45d5eff..7bd4418 100644 --- a/src/LabelBuffer.ts +++ b/src/LabelBuffer.ts @@ -7,7 +7,6 @@ */ import RBush from 'rbush'; import stringWidth from 'string-width'; -import { Feature } from './Renderer.ts'; export default class LabelBuffer { private tree: RBush; @@ -32,15 +31,16 @@ export default class LabelBuffer { const point = this.project(x, y); if (this._hasSpace(text, point[0], point[1])) { - const data = this._calculateArea(text, point[0], point[1], margin); - data.feature = feature; - return this.tree.insert(data); + return this.tree.insert({ + ...this._calculateArea(text, point[0], point[1], margin), + feature, + }); } else { return false; } } - featuresAt(x: number, y: number): Feature[] { + featuresAt(x: number, y: number): void { this.tree.search({minX: x, maxX: x, minY: y, maxY: y}); } diff --git a/src/Mapscii.ts b/src/Mapscii.ts index 1010722..af9a655 100644 --- a/src/Mapscii.ts +++ b/src/Mapscii.ts @@ -5,6 +5,8 @@ UI and central command center */ import fs from 'node:fs'; +import process from "node:process"; + import keypress from 'keypress'; import TermMouse from 'term-mouse'; @@ -20,14 +22,24 @@ class Mapscii { private width: number | null; private height: number | null; private canvas: Canvas | null; - private mouse: any; - private mouseDragging: boolean; - private mousePosition: any; + private mouse: TermMouse | null; + private mouseDragging: { + x: number, + y: number, + center: { + x: number, + y: number, + }, + } | false; + private mousePosition: {lat: number, lon: number} | null; private tileSource: TileSource | null; private renderer: Renderer | null; private zoom: number; private minZoom: number | null; - private center: any; + private center: { + lat: number, + lon: number, + }; constructor(options) { this.width = null; @@ -36,10 +48,7 @@ class Mapscii { this.mouse = null; this.mouseDragging = false; - this.mousePosition = { - x: 0, - y: 0, - }; + this.mousePosition = null; this.tileSource = null; this.renderer = null; @@ -78,7 +87,7 @@ class Mapscii { } config.input.resume(); - config.input.on('keypress', (ch, key) => this._onKey(key)); + config.input.on('keypress', (_ch, key) => this._onKey(key)); } _initMouse() { @@ -115,34 +124,34 @@ class Mapscii { this.renderer.setSize(this.width, this.height); } - _colrow2ll(x, y) { + _colrow2ll(x: number, y: number): {lat: number, lon: number} { const projected = { x: (x-0.5)*2, y: (y-0.5)*4, }; const size = utils.tilesizeAtZoom(this.zoom); - const [dx, dy] = [projected.x-this.width/2, projected.y-this.height/2]; + const [dx, dy] = [projected.x - this.width / 2, projected.y - this.height / 2]; const z = utils.baseZoom(this.zoom); const center = utils.ll2tile(this.center.lon, this.center.lat, z); - return utils.normalize(utils.tile2ll(center.x+(dx/size), center.y+(dy/size), z)); + return utils.normalize(utils.tile2ll(center.x + (dx / size), center.y + (dy / size), z)); } - _updateMousePosition(event) { + _updateMousePosition(event: {x: number, y: number}): void { this.mousePosition = this._colrow2ll(event.x, event.y); } _onClick(event) { - if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) { + if (event.x < 0 || event.x > this.width / 2 || event.y < 0 || event.y > this.height / 4) { return; } this._updateMousePosition(event); if (this.mouseDragging && event.button === 'left') { this.mouseDragging = false; - } else { + } else if (this.mousePosition !== null) { this.setCenter(this.mousePosition.lat, this.mousePosition.lon); } @@ -181,8 +190,8 @@ class Mapscii { this._draw(); } - _onMouseMove(event) { - if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) { + _onMouseMove(event: {button: string, x: number, y: number}) { + if (event.x < 0 || event.x > this.width / 2 || event.y < 0 || event.y > this.height / 4) { return; } if (config.mouseCallback && !config.mouseCallback(event)) { @@ -192,14 +201,14 @@ class Mapscii { // start dragging if (event.button === 'left') { if (this.mouseDragging) { - const dx = (this.mouseDragging.x-event.x)*2; - const dy = (this.mouseDragging.y-event.y)*4; + const dx = (this.mouseDragging.x - event.x) * 2; + const dy = (this.mouseDragging.y - event.y) * 4; const size = utils.tilesizeAtZoom(this.zoom); const newCenter = utils.tile2ll( - this.mouseDragging.center.x+(dx/size), - this.mouseDragging.center.y+(dy/size), + this.mouseDragging.center.x + (dx / size), + this.mouseDragging.center.y + (dy / size), utils.baseZoom(this.zoom) ); @@ -284,7 +293,7 @@ class Mapscii { let footer = `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `; footer += ` zoom: ${utils.digits(this.zoom, 2)} `; - if (this.mousePosition.lat !== undefined) { + if (this.mousePosition !== null) { footer += ` mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `; } return footer; @@ -297,26 +306,25 @@ class Mapscii { } } - _write(output) { + _write(output): void { config.output.write(output); } - zoomBy(step) { - if (this.zoom+step < this.minZoom) { - return this.zoom = this.minZoom; + zoomBy(step: number): void { + if (this.zoom + step < this.minZoom) { + this.zoom = this.minZoom; + } else if (this.zoom + step > config.maxZoom) { + this.zoom = config.maxZoom; + } else { + this.zoom += step; } - if (this.zoom+step > config.maxZoom) { - return this.zoom = config.maxZoom; - } - - this.zoom += step; } - moveBy(lat, lon) { - this.setCenter(this.center.lat+lat, this.center.lon+lon); + moveBy(lat: number, lon: number): void { + this.setCenter(this.center.lat + lat, this.center.lon + lon); } - setCenter(lat, lon) { + setCenter(lat: number, lon: number): void { this.center = utils.normalize({ lon: lon, lat: lat, diff --git a/src/Renderer.ts b/src/Renderer.ts index 963aea4..b87dfb1 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -79,7 +79,7 @@ class Renderer { this.canvas?.clear(); try { - let tiles = this._visibleTiles(center, zoom); + const tiles = this._visibleTiles(center, zoom); await Promise.all(tiles.map(async(tile) => { await this._getTile(tile); this._getTileFeatures(tile, zoom); @@ -165,7 +165,7 @@ class Renderer { _renderTiles(tiles) { const labels: { tile: Tile, - feature: any, + feature: Feature, scale: unknown, }[] = []; if (tiles.length === 0) return; diff --git a/src/Styler.ts b/src/Styler.ts index 60a5ded..52e69db 100644 --- a/src/Styler.ts +++ b/src/Styler.ts @@ -8,6 +8,7 @@ Compiles layer filter instructions into a chain of true/false returning anonymous functions to improve rendering speed compared to realtime parsing. */ +import type RBush from 'rbush'; import { Feature } from "./Renderer.ts"; @@ -19,7 +20,7 @@ class Styler { constructor(style) { this.styleById = {}; this.styleByLayer = {}; - var base, name; + let base, name; this.styleName = style.name; if (style.constants) { this._replaceConstants(style.constants, style.layers); @@ -45,7 +46,7 @@ class Styler { } } - getStyleFor(layer, feature): unknown | false { + getStyleFor(layer: string, feature: Feature): unknown | false { if (!this.styleByLayer[layer]) { return false; } diff --git a/src/Tile.ts b/src/Tile.ts index 551d72b..b565ac6 100644 --- a/src/Tile.ts +++ b/src/Tile.ts @@ -32,6 +32,7 @@ class Tile { z: number, }; public zoom: number; + public data: unknown; constructor(styler: Styler) { this.styler = styler; diff --git a/src/TileSource.test.ts b/src/TileSource.test.ts index fc0afe3..a09f074 100644 --- a/src/TileSource.test.ts +++ b/src/TileSource.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'jest'; -import TileSource from './TileSource.ts'; +import TileSource, { Mode } from './TileSource.ts'; describe('TileSource', () => { describe('with a HTTP source', () => { diff --git a/src/TileSource.ts b/src/TileSource.ts index 3efed8b..1a1acbd 100644 --- a/src/TileSource.ts +++ b/src/TileSource.ts @@ -12,16 +12,18 @@ import fetch from 'node-fetch'; import envPaths from 'env-paths'; const paths = envPaths('mapscii'); -import Tile from './Tile.ts'; import config from './config.ts'; +import Tile from './Tile.ts'; +import Styler from './Styler.ts'; // https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3) // To maximize MapSCIIā€™s compatibility, MBTiles support must be manually added via // $> npm install -g @mapbox/mbtiles -let MBTiles = null; -try { - MBTiles = await import('@mapbox/mbtiles'); -} catch (err) {void 0;} +// let MBTiles = null; +// try { +// MBTiles = await import('@mapbox/mbtiles'); +// } catch {void 0;} +import MBTiles from '@mapbox/mbtiles'; export enum Mode { MBTiles = 1, @@ -31,14 +33,14 @@ export enum Mode { class TileSource { private source: string; - private cache: any; + private cache: Record; private cacheSize: number; - private cached: any; - private mode: Mode | null; - private mbtiles: any; - private styler: any; + private cached: unknown[]; + public mode: Mode | null; + private mbtiles: MBTiles | null; + private styler: Styler; - init(source: string) { + init(source: string): void { this.source = source; this.cache = {}; @@ -68,9 +70,9 @@ class TileSource { } } - loadMBTiles(source) { + loadMBTiles(source): Promise { return new Promise((resolve, reject) => { - new MBTiles(source, (err, mbtiles) => { + new MBTiles(`${source}?mode=ro`, (err, mbtiles) => { if (err) { reject(err); } @@ -95,7 +97,7 @@ class TileSource { } if (this.cached.length > this.cacheSize) { - const overflow = Math.abs(this.cacheSize - this.cache.length); + const overflow = Math.abs(this.cacheSize - this.cached.length); for (const tile in this.cached.splice(0, overflow)) { delete this.cache[tile]; } @@ -151,7 +153,7 @@ class TileSource { private _initPersistence() { try { this._createFolder(paths.cache); - } catch (error) { + } catch { config.persistDownloadedTiles = false; } } @@ -166,7 +168,7 @@ class TileSource { private _getPersited(z: number, x: number, y: number) { try { return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`)); - } catch (error) { + } catch { return false; } } diff --git a/src/config.ts b/src/config.ts index 4674350..47fd7bb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,5 @@ +import process from "node:process"; + export default { language: 'en', diff --git a/src/utils.ts b/src/utils.ts index 31da7a0..0e22fe6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -34,7 +34,7 @@ const utils = { return angle * 0.017453292519943295; }, - ll2tile: (lon: number, lat: number, zoom: number) => { + ll2tile: (lon: number, lat: number, zoom: number): {x: number, y: number, z: number} => { return { x: (lon+180)/360*Math.pow(2, zoom), y: (1-Math.log(Math.tan(lat*Math.PI/180)+1/Math.cos(lat*Math.PI/180))/Math.PI)/2*Math.pow(2, zoom), @@ -42,7 +42,7 @@ const utils = { }; }, - tile2ll: (x: number, y: number, zoom: number) => { + tile2ll: (x: number, y: number, zoom: number): {lat: number, lon: number} => { const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom); return { @@ -55,21 +55,24 @@ const utils = { return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom)); }, - hex2rgb: (color: any): [r: number, g: number, b: number] => { - if (typeof color !== 'string') return [255, 0, 0]; + hex2rgb: (color: unknown): [r: number, g: number, b: number] => { + if (typeof color !== 'string') { + return [255, 0, 0]; + } if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) { throw new Error(`${color} isn't a supported hex color`); } - color = color.substring(1); - const decimal = parseInt(color, 16); + const decimal = parseInt(color.substring(1), 16); - if (color.length === 3) { + if (color.length === 4) { const rgb = [decimal>>8, (decimal>>4)&15, decimal&15]; - return rgb.map((c) => { - return c + (c<<4); - }); + return [ + rgb[0] + (rgb[0]<<4), + rgb[1] + (rgb[1]<<4), + rgb[2] + (rgb[2]<<4), + ]; } else { return [(decimal>>16)&255, (decimal>>8)&255, decimal&255]; }