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