Suggest Bun and Deno 2; Upgrade most packages; Remove babel, ESLint and jest as dependencies

This commit is contained in:
Christian Paul 2024-11-03 16:25:18 +01:00
parent 81a7fdea12
commit ec0877830a
9 changed files with 396 additions and 4740 deletions

View File

@ -1,6 +1,6 @@
# MapSCII - The Whole World In Your Console. [![Build Status](https://travis-ci.com/rastapasta/mapscii.svg?branch=master)](https://travis-ci.com/rastapasta/mapscii)
# MapSCII - The Whole World In Your Console.
A node.js based [Vector Tile](http://wiki.openstreetmap.org/wiki/Vector_tiles) to [Braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) and [ASCII](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange) renderer for [xterm](https://en.wikipedia.org/wiki/Xterm)-compatible terminals.
A TypeScript-based [Vector Tile](http://wiki.openstreetmap.org/wiki/Vector_tiles) to [Braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) and [ASCII](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange) renderer for [xterm](https://en.wikipedia.org/wiki/Xterm)-compatible terminals.
<a href="https://asciinema.org/a/117813?autoplay=1" target="_blank">![asciicast](https://cloud.githubusercontent.com/assets/1259904/25480718/497a64e2-2b4a-11e7-9cf0-ed52ee0b89c0.png)</a>
@ -22,7 +22,6 @@ If you're on Windows, use the open source telnet client [PuTTY](https://www.chia
* Work offline and discover local [VectorTile](https://github.com/mapbox/vector-tile-spec)/[MBTiles](https://github.com/mapbox/mbtiles-spec)
* Compatible with most Linux and OSX terminals
* Highly optimized algorithms for a smooth experience
* 100% pure JavaScript! :sunglasses:
## How to run it locally
@ -36,7 +35,7 @@ npx mapscii
### With npm
If you haven't already got Node.js >= version 10, then [go get it](http://nodejs.org/).
If you haven't already got Bun or Deno 2, then [go get it](https://deno.com/).
```
npm install -g mapscii

4920
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,11 @@
"name": "mapscii",
"version": "0.3.1",
"description": "MapSCII is a Braille & ASCII world map renderer for your console, based on OpenStreetMap",
"main": "main.js",
"type": "module",
"scripts": {
"lint": "eslint src",
"start": "node main",
"test": "jest"
"lint": "deno lint",
"start": "deno --allow-all main.ts",
"test": "deno test"
},
"repository": {
"type": "git",
@ -16,9 +15,6 @@
"bin": {
"mapscii": "./bin/mapscii.sh"
},
"engines": {
"node": ">=20"
},
"keywords": [
"map",
"console",
@ -32,24 +28,19 @@
"license": "MIT",
"dependencies": {
"@mapbox/mbtiles": "^0.12.1",
"@mapbox/vector-tile": "^1.3.1",
"@mapbox/vector-tile": "^2.0.3",
"@types/rbush": "^4.0.0",
"bresenham": "0.0.4",
"earcut": "^2.2.2",
"env-paths": "^2.2.0",
"earcut": "^3.0.0",
"env-paths": "^3.0.0",
"keypress": "^0.2.1",
"node-fetch": "^2.6.1",
"pbf": "^3.2.1",
"node-fetch": "^3.3.2",
"pbf": "^4.0.1",
"rbush": "^4.0.1",
"simplify-js": "^1.2.4",
"string-width": "^4.2.0",
"string-width": "^7.2.0",
"term-mouse": "^0.2.2",
"x256": "0.0.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@babel/preset-typescript": "^7.25.7",
"eslint": "^7.8.1",
"eslint-plugin-jest": "^24.0.0",
"jest": "^29.7.0"
}
}

View File

@ -89,13 +89,13 @@ class Canvas {
return true;
}
_polygonExtract(vertices, pointId) {
private _polygonExtract(vertices, pointId) {
return [vertices[pointId * 2], vertices[pointId * 2 + 1]];
}
// Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm"
// -> http://members.chello.at/~easyfilter/bresenham.html
_line(x0, y0, x1, y1, width, color) {
private _line(x0, y0, x1, y1, width, color) {
// Fall back to width-less bresenham algorithm if we dont have a width
if (!(width = Math.max(0, width - 1))) {
return bresenham(x0, y0, x1, y1, (x, y) => {
@ -149,7 +149,7 @@ class Canvas {
/* eslint-enable */
}
_filledRectangle(x, y, width, height, color) {
private _filledRectangle(x, y, width, height, color) {
const pointA = [x, y];
const pointB = [x + width, y];
const pointC = [x, y + height];
@ -158,12 +158,12 @@ class Canvas {
this._filledTriangle(pointC, pointB, pointD, color);
}
_bresenham(pointA, pointB) {
private _bresenham(pointA, pointB) {
return bresenham(pointA[0], pointA[1], pointB[0], pointB[1]);
}
// Draws a filled triangle
_filledTriangle(pointA, pointB, pointC, color) {
private _filledTriangle(pointA, pointB, pointC, color) {
const a = this._bresenham(pointB, pointC);
const b = this._bresenham(pointA, pointC);
const c = this._bresenham(pointA, pointB);

View File

@ -7,9 +7,16 @@
*/
import RBush from 'rbush';
import stringWidth from 'string-width';
import { Feature } from './Renderer.ts';
export default class LabelBuffer {
private tree: RBush;
private tree: RBush<{
minX: number,
minY: number,
maxX: number,
maxY: number,
feature: Feature,
}>;
private margin: number;
constructor() {
@ -31,29 +38,30 @@ export default class LabelBuffer {
const point = this.project(x, y);
if (this._hasSpace(text, point[0], point[1])) {
return this.tree.insert({
this.tree.insert({
...this._calculateArea(text, point[0], point[1], margin),
feature,
});
return true;
} else {
return false;
}
}
featuresAt(x: number, y: number): void {
this.tree.search({minX: x, maxX: x, minY: y, maxY: y});
}
// featuresAt(x: number, y: number) {
// return this.tree.search({minX: x, maxX: x, minY: y, maxY: y});
// }
_hasSpace(text: string, x: number, y: number): boolean {
private _hasSpace(text: string, x: number, y: number): boolean {
return !this.tree.collides(this._calculateArea(text, x, y));
}
_calculateArea(text: string, x: number, y: number, margin = 0) {
private _calculateArea(text: string, x: number, y: number, margin = 0) {
return {
minX: x-margin,
minY: y-margin / 2,
maxX: x+margin + stringWidth(text),
maxY: y+margin / 2,
minX: x - margin,
minY: y - margin / 2,
maxX: x + margin + stringWidth(text),
maxY: y + margin / 2,
};
}
};

View File

@ -75,12 +75,12 @@ class Mapscii {
}
_initTileSource() {
private _initTileSource() {
this.tileSource = new TileSource();
this.tileSource.init(config.source);
}
_initKeyboard() {
private _initKeyboard() {
keypress(config.input);
if (config.input.setRawMode) {
config.input.setRawMode(true);
@ -90,7 +90,7 @@ class Mapscii {
config.input.on('keypress', (_ch, key) => this._onKey(key));
}
_initMouse() {
private _initMouse() {
this.mouse = TermMouse({
input: config.input,
output: config.output,
@ -102,7 +102,7 @@ class Mapscii {
this.mouse.on('move', (event) => this._onMouseMove(event));
}
_initRenderer() {
private _initRenderer() {
const style = JSON.parse(fs.readFileSync(config.styleFile, 'utf8'));
this.renderer = new Renderer(config.output, this.tileSource, style);
@ -115,7 +115,7 @@ class Mapscii {
this.zoom = (config.initialZoom !== null) ? config.initialZoom : this.minZoom;
}
_resizeRenderer() {
private _resizeRenderer() {
this.width = config.size && config.size.width ? config.size.width * 2 : config.output.columns >> 1 << 2;
this.height = config.size && config.size.height ? config.size.height * 4 : config.output.rows * 4 - 4;
@ -124,7 +124,7 @@ class Mapscii {
this.renderer.setSize(this.width, this.height);
}
_colrow2ll(x: number, y: number): {lat: number, lon: number} {
private _colrow2ll(x: number, y: number): {lat: number, lon: number} {
const projected = {
x: (x-0.5)*2,
y: (y-0.5)*4,
@ -139,11 +139,11 @@ class Mapscii {
return utils.normalize(utils.tile2ll(center.x + (dx / size), center.y + (dy / size), z));
}
_updateMousePosition(event: {x: number, y: number}): void {
private _updateMousePosition(event: {x: number, y: number}): void {
this.mousePosition = this._colrow2ll(event.x, event.y);
}
_onClick(event) {
private _onClick(event) {
if (event.x < 0 || event.x > this.width / 2 || event.y < 0 || event.y > this.height / 4) {
return;
}
@ -158,7 +158,7 @@ class Mapscii {
this._draw();
}
_onMouseScroll(event) {
private _onMouseScroll(event) {
this._updateMousePosition(event);
// the location of the pointer, where we want to zoom toward
@ -190,7 +190,7 @@ class Mapscii {
this._draw();
}
_onMouseMove(event: {button: string, x: number, y: number}) {
private _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;
}
@ -229,7 +229,7 @@ class Mapscii {
this.notify(this._getFooter());
}
_onKey(key) {
private _onKey(key) {
if (config.keyCallback && !config.keyCallback(key)) return;
if (!key || !key.name) return;
@ -278,7 +278,7 @@ class Mapscii {
}
}
_draw() {
private _draw() {
this.renderer?.draw(this.center, this.zoom).then((frame) => {
this._write(frame);
this.notify(this._getFooter());
@ -287,7 +287,7 @@ class Mapscii {
});
}
_getFooter() {
private _getFooter() {
// tile = utils.ll2tile(this.center.lon, this.center.lat, this.zoom);
// `tile: ${utils.digits(tile.x, 3)}, ${utils.digits(tile.x, 3)} `+
@ -306,7 +306,7 @@ class Mapscii {
}
}
_write(output): void {
private _write(output): void {
config.output.write(output);
}

View File

@ -17,12 +17,26 @@ import TileSource from './TileSource.ts';
import Tile from './Tile.ts';
export type Layer = {
extent: unknown,
tree: RBush,
extent?: unknown,
tree?: RBush<{
minX: number,
minY: number,
maxX: number,
maxY: number,
feature: Feature,
}>,
scale?: number,
features?: Feature[],
};
export type Layers = Record<string, Layer>;
export type Feature = {
properties: Record<string, unknown>,
// TODO `style` is likely incomplete and incorrect
style: {
type?: string,
},
sort: number,
sorty: number,
};
class Renderer {
@ -86,15 +100,15 @@ class Renderer {
}));
await this._renderTiles(tiles);
return this._getFrame();
} catch(e) {
console.error(e);
} catch (err) {
console.error(err);
} finally {
this.isDrawing = false;
this.lastDrawAt = Date.now();
}
}
_visibleTiles(center, zoom) {
private _visibleTiles(center, zoom) {
const z = utils.baseZoom(zoom);
center = utils.ll2tile(center.lon, center.lat, z);
@ -137,9 +151,9 @@ class Renderer {
return tile;
}
_getTileFeatures(tile: Tile, zoom: number): Tile {
private _getTileFeatures(tile: Tile, zoom: number): Tile {
const position = tile.position;
const layers = {};
const layers: Layers = {};
const drawOrder = this._generateDrawOrder(zoom);
for (const layerId of drawOrder) {
const layer = (tile.data.layers || {})[layerId];
@ -149,12 +163,12 @@ class Renderer {
const scale = layer.extent / utils.tilesizeAtZoom(zoom);
layers[layerId] = {
scale: scale,
scale,
features: layer.tree.search({
minX: -position.x * scale,
minY: -position.y * scale,
maxX: (this.width - position.x) * scale,
maxY: (this.height - position.y) * scale
maxY: (this.height - position.y) * scale,
}),
};
}
@ -162,11 +176,11 @@ class Renderer {
return tile;
}
_renderTiles(tiles) {
private _renderTiles(tiles) {
const labels: {
tile: Tile,
feature: Feature,
scale: unknown,
scale: number,
}[] = [];
if (tiles.length === 0) return;
@ -200,7 +214,10 @@ class Renderer {
}
}
_getFrame() {
private _getFrame(): string {
if (!this.canvas) {
return '';
}
let frame = '';
if (!this.lastDrawAt) {
frame += this.terminal.CLEAR;
@ -210,11 +227,14 @@ class Renderer {
return frame;
}
featuresAt(x: number, y: number): Feature[] {
return this.labelBuffer.featuresAt(x, y);
}
// featuresAt(x: number, y: number) {
// return this.labelBuffer.featuresAt(x, y);
// }
_drawFeature(tile, feature, scale: number): boolean {
private _drawFeature(tile: Tile, feature, scale: number): boolean {
if (!this.canvas) {
return false;
}
let points, placed;
if (feature.style.minzoom && tile.zoom < feature.style.minzoom) {
return false;
@ -278,7 +298,7 @@ class Renderer {
return true;
}
_scaleAndReduce(tile: Tile, feature: Feature, points: {x: number, y: number}[], scale: number, filter = true) {
private _scaleAndReduce(tile: Tile, feature: Feature, points: {x: number, y: number}[], scale: number, filter = true) {
let lastX;
let lastY;
let outside;
@ -326,7 +346,7 @@ class Renderer {
}
}
_generateDrawOrder(zoom) {
private _generateDrawOrder(zoom) {
if (zoom < 2) {
return [
'admin',

View File

@ -60,7 +60,7 @@ class Styler {
return false;
}
_replaceConstants(constants, tree: RBush): void {
private _replaceConstants(constants, tree: RBush): void {
for (const id in tree) {
const node = tree[id];
switch (typeof node) {
@ -79,7 +79,7 @@ class Styler {
}
//TODO Better translation of the long cases.
_compileFilter(filter): (feature: Feature) => boolean {
private _compileFilter(filter): (feature: Feature) => boolean {
let filters;
switch (filter != null ? filter[0] : void 0) {
case 'all':

View File

@ -14,10 +14,10 @@ import x256 from 'x256';
import config from './config.ts';
import utils from './utils.ts';
import Styler from './Styler.ts';
import { Feature, Layers } from './Renderer.ts';
import { Layers } from './Renderer.ts';
class Tile {
public layers: Layers;
public layers: Layers = {};
private styler: Styler;
private tile: VectorTile;
@ -32,7 +32,9 @@ class Tile {
z: number,
};
public zoom: number;
public data: unknown;
public data: {
layers: unknown;
};
constructor(styler: Styler) {
this.styler = styler;
@ -76,7 +78,7 @@ class Tile {
const colorCache: Record<string, string> = {};
for (const name in this.tile.layers) {
const layer = this.tile.layers[name];
const nodes = [];
const nodes: unknown[] = [];
//continue if name is 'water'
for (let i = 0; i < layer.length; i++) {
// TODO: caching of similar attributes to avoid looking up the style each time
@ -159,28 +161,26 @@ class Tile {
return data;
}
private _reduceGeometry(feature: Feature, factor: number): {x: number, y: number}[][] {
const results: {x: number, y: number}[][] = [];
const geometries = feature.loadGeometry();
for (const points of geometries) {
const reduced: {x: number, y: number}[] = [];
let last;
for (const point of points) {
const p = {
x: Math.floor(point.x / factor),
y: Math.floor(point.y / factor)
};
if (last && last.x === p.x && last.y === p.y) {
continue;
}
reduced.push(last = p);
}
results.push(reduced);
}
return results;
}
// private _reduceGeometry(feature: Feature, factor: number): {x: number, y: number}[][] {
// const results: {x: number, y: number}[][] = [];
// const geometries = feature.loadGeometry();
// for (const points of geometries) {
// const reduced: {x: number, y: number}[] = [];
// let last;
// for (const point of points) {
// const p = {
// x: Math.floor(point.x / factor),
// y: Math.floor(point.y / factor)
// };
// if (last && last.x === p.x && last.y === p.y) {
// continue;
// }
// reduced.push(last = p);
// }
// results.push(reduced);
// }
// return results;
// }
}
Tile.prototype.layers = {};
export default Tile;