mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 23:53:08 +01:00
Suggest Bun and Deno 2; Upgrade most packages; Remove babel, ESLint and jest as dependencies
This commit is contained in:
parent
81a7fdea12
commit
ec0877830a
@ -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
4920
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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':
|
||||
|
52
src/Tile.ts
52
src/Tile.ts
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user