mirror of
https://github.com/rastapasta/mapscii.git
synced 2025-06-21 10:08:11 +02:00
Merge a45870de45703a332153c961f7ff3b667b712ee1 into 4fe9a60a0c9da952dadc5214a9ca5c68c447fdf8
This commit is contained in:
commit
4648bea0a1
@ -1,16 +1,17 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"es2021": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
@ -33,4 +34,4 @@ module.exports = {
|
||||
"always"
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
10
main.js
10
main.js
@ -7,11 +7,11 @@
|
||||
|
||||
TODO: params parsing and so on
|
||||
#*/
|
||||
'use strict';
|
||||
const config = require('./src/config');
|
||||
const Mapscii = require('./src/Mapscii');
|
||||
const argv = require('yargs')
|
||||
.option('latitude', {
|
||||
import config from './src/config.js';
|
||||
import Mapscii from './src/Mapscii.js';
|
||||
import yargs from 'yargs';
|
||||
|
||||
const argv = yargs().option('latitude', {
|
||||
alias: 'lat',
|
||||
description: 'Latitude of initial centre',
|
||||
default: config.initialLat,
|
||||
|
9869
package-lock.json
generated
9869
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -3,10 +3,11 @@
|
||||
"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"
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -32,21 +33,21 @@
|
||||
"dependencies": {
|
||||
"@mapbox/vector-tile": "^1.3.1",
|
||||
"bresenham": "0.0.4",
|
||||
"earcut": "^2.2.2",
|
||||
"env-paths": "^2.2.0",
|
||||
"earcut": "^2.2.4",
|
||||
"env-paths": "^3.0.0",
|
||||
"keypress": "^0.2.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^3.3.0",
|
||||
"pbf": "^3.2.1",
|
||||
"rbush": "^3.0.1",
|
||||
"simplify-js": "^1.2.4",
|
||||
"string-width": "^4.2.0",
|
||||
"string-width": "^5.1.2",
|
||||
"term-mouse": "^0.2.2",
|
||||
"x256": "0.0.2",
|
||||
"yargs": "^15.4.1"
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.8.1",
|
||||
"eslint-plugin-jest": "^24.0.0",
|
||||
"jest": "^26.4.2"
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"jest": "^29.4.3"
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,9 @@
|
||||
|
||||
Will either be merged into node-drawille or become an own module at some point
|
||||
*/
|
||||
'use strict';
|
||||
const stringWidth = require('string-width');
|
||||
const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
import stringWidth from 'string-width';
|
||||
import config from './config.js';
|
||||
import * as utils from './utils.js';
|
||||
|
||||
const asciiMap = {
|
||||
// '▬': [2+32, 4+64],
|
||||
@ -29,7 +28,7 @@ const asciiMap = {
|
||||
};
|
||||
const termReset = '\x1B[39;49m';
|
||||
|
||||
class BrailleBuffer {
|
||||
export default class BrailleBuffer {
|
||||
constructor(width, height) {
|
||||
this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];
|
||||
|
||||
@ -114,9 +113,8 @@ class BrailleBuffer {
|
||||
}
|
||||
|
||||
//TODO Optimize this part
|
||||
var i, k;
|
||||
const results = [];
|
||||
for (i = k = 1; k <= 255; i = ++k) {
|
||||
for (let i = 1; i <= 255; i++) {
|
||||
const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128);
|
||||
results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) {
|
||||
const covered = utils.population(mask.mask & braille);
|
||||
@ -207,5 +205,3 @@ class BrailleBuffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BrailleBuffer;
|
||||
|
10
src/BrailleBuffer.spec.js
Normal file
10
src/BrailleBuffer.spec.js
Normal file
@ -0,0 +1,10 @@
|
||||
import BrailleBuffer from './BrailleBuffer.js';
|
||||
|
||||
const termReset = '\x1B[39;49m';
|
||||
|
||||
describe('BrailleBuffer', () => {
|
||||
test('starts a frame with term reset characters', async () => {
|
||||
const brailleBuffer = new BrailleBuffer(1, 1);
|
||||
expect(brailleBuffer.frame().startsWith(termReset)).toBe(true);
|
||||
});
|
||||
});
|
@ -10,12 +10,11 @@
|
||||
|
||||
Will most likely be turned into a stand alone module at some point
|
||||
*/
|
||||
'use strict';
|
||||
const bresenham = require('bresenham');
|
||||
const earcut = require('earcut');
|
||||
const BrailleBuffer = require('./BrailleBuffer');
|
||||
import bresenham from 'bresenham';
|
||||
import earcut from 'earcut';
|
||||
import BrailleBuffer from './BrailleBuffer.js';
|
||||
|
||||
class Canvas {
|
||||
export default class Canvas {
|
||||
constructor(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@ -164,9 +163,8 @@ class Canvas {
|
||||
const b = this._bresenham(pointA, pointC);
|
||||
const c = this._bresenham(pointA, pointB);
|
||||
|
||||
const points = a.concat(b).concat(c).filter((point) => {
|
||||
var ref;
|
||||
return (0 <= (ref = point.y) && ref < this.height);
|
||||
const points = a.concat(b).concat(c).filter(({y}) => {
|
||||
return 0 <= y && y < this.height;
|
||||
}).sort(function(a, b) {
|
||||
if (a.y === b.y) {
|
||||
return a.x - b.x;
|
||||
@ -198,5 +196,3 @@ class Canvas {
|
||||
}
|
||||
|
||||
Canvas.prototype.stack = [];
|
||||
|
||||
module.exports = Canvas;
|
||||
|
@ -5,11 +5,10 @@
|
||||
Using 2D spatial indexing to avoid overlapping labels and markers
|
||||
and to find labels underneath a mouse cursor's position
|
||||
*/
|
||||
'use strict';
|
||||
const RBush = require('rbush');
|
||||
const stringWidth = require('string-width');
|
||||
import RBush from 'rbush';
|
||||
import stringWidth from 'string-width';
|
||||
|
||||
module.exports = class LabelBuffer {
|
||||
export default class LabelBuffer {
|
||||
|
||||
constructor() {
|
||||
this.tree = new RBush();
|
||||
@ -54,4 +53,4 @@ module.exports = class LabelBuffer {
|
||||
maxY: y+margin/2,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
103
src/Mapscii.js
103
src/Mapscii.js
@ -4,17 +4,16 @@
|
||||
|
||||
UI and central command center
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const keypress = require('keypress');
|
||||
const TermMouse = require('term-mouse');
|
||||
import fsPromises from 'fs/promises';
|
||||
import keypress from 'keypress';
|
||||
import TermMouse from 'term-mouse';
|
||||
|
||||
const Renderer = require('./Renderer');
|
||||
const TileSource = require('./TileSource');
|
||||
const utils = require('./utils');
|
||||
let config = require('./config');
|
||||
import Renderer from './Renderer.js';
|
||||
import TileSource from './TileSource.js';
|
||||
import * as utils from './utils.js';
|
||||
import MapsciiConfig from './config.js';
|
||||
|
||||
class Mapscii {
|
||||
export default class Mapscii {
|
||||
constructor(options) {
|
||||
this.width = null;
|
||||
this.height = null;
|
||||
@ -32,45 +31,48 @@ class Mapscii {
|
||||
|
||||
this.zoom = 0;
|
||||
this.minZoom = null;
|
||||
config = Object.assign(config, options);
|
||||
this.config = {
|
||||
...MapsciiConfig,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.center = {
|
||||
lat: config.initialLat,
|
||||
lon: config.initialLon
|
||||
lat: this.config.initialLat,
|
||||
lon: this.config.initialLon,
|
||||
};
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!config.headless) {
|
||||
if (!this.config.headless) {
|
||||
this._initKeyboard();
|
||||
this._initMouse();
|
||||
}
|
||||
this._initTileSource();
|
||||
this._initRenderer();
|
||||
this._draw();
|
||||
await this._initTileSource();
|
||||
await this._initRenderer();
|
||||
await this._draw();
|
||||
this.notify('Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.');
|
||||
}
|
||||
|
||||
|
||||
_initTileSource() {
|
||||
async _initTileSource() {
|
||||
this.tileSource = new TileSource();
|
||||
this.tileSource.init(config.source);
|
||||
await this.tileSource.init(this.config.source);
|
||||
}
|
||||
|
||||
_initKeyboard() {
|
||||
keypress(config.input);
|
||||
if (config.input.setRawMode) {
|
||||
config.input.setRawMode(true);
|
||||
keypress(this.config.input);
|
||||
if (this.config.input.setRawMode) {
|
||||
this.config.input.setRawMode(true);
|
||||
}
|
||||
config.input.resume();
|
||||
this.config.input.resume();
|
||||
|
||||
config.input.on('keypress', (ch, key) => this._onKey(key));
|
||||
this.config.input.on('keypress', (ch, key) => this._onKey(key));
|
||||
}
|
||||
|
||||
_initMouse() {
|
||||
this.mouse = TermMouse({
|
||||
input: config.input,
|
||||
output: config.output,
|
||||
input: this.config.input,
|
||||
output: this.config.output,
|
||||
});
|
||||
this.mouse.start();
|
||||
|
||||
@ -79,22 +81,22 @@ class Mapscii {
|
||||
this.mouse.on('move', (event) => this._onMouseMove(event));
|
||||
}
|
||||
|
||||
_initRenderer() {
|
||||
const style = JSON.parse(fs.readFileSync(config.styleFile, 'utf8'));
|
||||
this.renderer = new Renderer(config.output, this.tileSource, style);
|
||||
async _initRenderer() {
|
||||
const style = JSON.parse(await fsPromises.readFile(this.config.styleFile, 'utf8'));
|
||||
this.renderer = new Renderer(this.tileSource, style);
|
||||
|
||||
config.output.on('resize', () => {
|
||||
this.config.output.on('resize', () => {
|
||||
this._resizeRenderer();
|
||||
this._draw();
|
||||
});
|
||||
|
||||
this._resizeRenderer();
|
||||
this.zoom = (config.initialZoom !== null) ? config.initialZoom : this.minZoom;
|
||||
this.zoom = (this.config.initialZoom !== null) ? this.config.initialZoom : this.minZoom;
|
||||
}
|
||||
|
||||
_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;
|
||||
this.width = this.config.size && this.config.size.width ? this.config.size.width * 2 : this.config.output.columns >> 1 << 2;
|
||||
this.height = this.config.size && this.config.size.height ? this.config.size.height * 4 : this.config.output.rows * 4 - 4;
|
||||
|
||||
this.minZoom = 4-Math.log(4096/this.width)/Math.LN2;
|
||||
|
||||
@ -142,7 +144,7 @@ class Mapscii {
|
||||
const targetMouseLonLat = this._colrow2ll(event.x, event.y);
|
||||
|
||||
// zoom toward the center
|
||||
this.zoomBy(config.zoomStep * (event.button === 'up' ? 1 : -1));
|
||||
this.zoomBy(this.config.zoomStep * (event.button === 'up' ? 1 : -1));
|
||||
|
||||
// the location the pointer ended up after zooming
|
||||
const offsetMouseLonLat = this._colrow2ll(event.x, event.y);
|
||||
@ -171,7 +173,7 @@ class Mapscii {
|
||||
if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) {
|
||||
return;
|
||||
}
|
||||
if (config.mouseCallback && !config.mouseCallback(event)) {
|
||||
if (this.config.mouseCallback && !this.config.mouseCallback(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -207,25 +209,25 @@ class Mapscii {
|
||||
}
|
||||
|
||||
_onKey(key) {
|
||||
if (config.keyCallback && !config.keyCallback(key)) return;
|
||||
if (this.config.keyCallback && !this.config.keyCallback(key)) return;
|
||||
if (!key || !key.name) return;
|
||||
|
||||
// check if the pressed key is configured
|
||||
let draw = true;
|
||||
switch (key.name) {
|
||||
case 'q':
|
||||
if (config.quitCallback) {
|
||||
config.quitCallback();
|
||||
if (this.config.quitCallback) {
|
||||
this.config.quitCallback();
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
this.zoomBy(config.zoomStep);
|
||||
this.zoomBy(this.config.zoomStep);
|
||||
break;
|
||||
case 'y':
|
||||
case 'z':
|
||||
this.zoomBy(-config.zoomStep);
|
||||
this.zoomBy(-this.config.zoomStep);
|
||||
break;
|
||||
case 'left':
|
||||
case 'h':
|
||||
@ -244,7 +246,7 @@ class Mapscii {
|
||||
this.moveBy(-6/Math.pow(2, this.zoom), 0);
|
||||
break;
|
||||
case 'c':
|
||||
config.useBraille = !config.useBraille;
|
||||
this.config.useBraille = !this.config.useBraille;
|
||||
break;
|
||||
default:
|
||||
draw = false;
|
||||
@ -255,13 +257,14 @@ class Mapscii {
|
||||
}
|
||||
}
|
||||
|
||||
_draw() {
|
||||
this.renderer.draw(this.center, this.zoom).then((frame) => {
|
||||
async _draw() {
|
||||
try {
|
||||
const frame = await this.renderer.draw(this.center, this.zoom);
|
||||
this._write(frame);
|
||||
this.notify(this._getFooter());
|
||||
}).catch(() => {
|
||||
} catch {
|
||||
this.notify('renderer is busy');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getFooter() {
|
||||
@ -277,22 +280,22 @@ class Mapscii {
|
||||
}
|
||||
|
||||
notify(text) {
|
||||
config.onUpdate && config.onUpdate();
|
||||
if (!config.headless) {
|
||||
this.config.onUpdate && this.config.onUpdate();
|
||||
if (!this.config.headless) {
|
||||
this._write('\r\x1B[K' + text);
|
||||
}
|
||||
}
|
||||
|
||||
_write(output) {
|
||||
config.output.write(output);
|
||||
this.config.output.write(output);
|
||||
}
|
||||
|
||||
zoomBy(step) {
|
||||
if (this.zoom+step < this.minZoom) {
|
||||
return this.zoom = this.minZoom;
|
||||
}
|
||||
if (this.zoom+step > config.maxZoom) {
|
||||
return this.zoom = config.maxZoom;
|
||||
if (this.zoom+step > this.config.maxZoom) {
|
||||
return this.zoom = this.config.maxZoom;
|
||||
}
|
||||
|
||||
this.zoom += step;
|
||||
@ -309,5 +312,3 @@ class Mapscii {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Mapscii;
|
||||
|
@ -4,19 +4,17 @@
|
||||
|
||||
The Console Vector Tile renderer - bäm!
|
||||
*/
|
||||
'use strict';
|
||||
const x256 = require('x256');
|
||||
const simplify = require('simplify-js');
|
||||
import x256 from 'x256';
|
||||
import simplify from 'simplify-js';
|
||||
|
||||
const Canvas = require('./Canvas');
|
||||
const LabelBuffer = require('./LabelBuffer');
|
||||
const Styler = require('./Styler');
|
||||
const utils = require('./utils');
|
||||
const config = require('./config');
|
||||
import Canvas from './Canvas.js';
|
||||
import LabelBuffer from './LabelBuffer.js';
|
||||
import Styler from './Styler.js';
|
||||
import * as utils from './utils.js';
|
||||
import config from './config.js';
|
||||
|
||||
class Renderer {
|
||||
constructor(output, tileSource, style) {
|
||||
this.output = output;
|
||||
export default class Renderer {
|
||||
constructor(tileSource, style) {
|
||||
this.tileSource = tileSource;
|
||||
this.labelBuffer = new LabelBuffer();
|
||||
this.styler = new Styler(style);
|
||||
@ -331,5 +329,3 @@ Renderer.prototype.lastDrawAt = 0;
|
||||
Renderer.prototype.labelBuffer = null;
|
||||
Renderer.prototype.tileSource = null;
|
||||
Renderer.prototype.tilePadding = 64;
|
||||
|
||||
module.exports = Renderer;
|
||||
|
21
src/Renderer.spec.js
Normal file
21
src/Renderer.spec.js
Normal file
@ -0,0 +1,21 @@
|
||||
import fsPromises from 'fs/promises';
|
||||
import Renderer from './Renderer';
|
||||
import TileSource from './TileSource.js';
|
||||
|
||||
const center = {
|
||||
lat: 52.51298,
|
||||
lon: 13.42012,
|
||||
};
|
||||
|
||||
describe('Renderer', () => {
|
||||
describe('with a HTTP source', () => {
|
||||
test('does not crash when creating a Renderer', async () => {
|
||||
const tileSource = new TileSource();
|
||||
await tileSource.init('http://mapscii.me/');
|
||||
const style = JSON.parse(await fsPromises.readFile('./styles/dark.json'));
|
||||
const renderer = new Renderer(tileSource, style);
|
||||
renderer.setSize(30, 30);
|
||||
expect(await renderer.draw(center, 13)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -8,13 +8,11 @@
|
||||
Compiles layer filter instructions into a chain of true/false returning
|
||||
anonymous functions to improve rendering speed compared to realtime parsing.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
class Styler {
|
||||
export default class Styler {
|
||||
constructor(style) {
|
||||
this.styleById = {};
|
||||
this.styleByLayer = {};
|
||||
var base, name;
|
||||
this.styleName = style.name;
|
||||
if (style.constants) {
|
||||
this._replaceConstants(style.constants, style.layers);
|
||||
@ -31,10 +29,7 @@ class Styler {
|
||||
|
||||
layer.appliesTo = this._compileFilter(layer.filter);
|
||||
|
||||
//TODO Better translation of: @styleByLayer[style['source-layer']] ?= []
|
||||
if ((base = this.styleByLayer)[name = layer['source-layer']] == null) {
|
||||
base[name] = [];
|
||||
}
|
||||
this.styleByLayer[layer['source-layer']] ??= [];
|
||||
this.styleByLayer[layer['source-layer']].push(layer);
|
||||
this.styleById[layer.id] = layer;
|
||||
}
|
||||
@ -129,5 +124,3 @@ class Styler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Styler;
|
||||
|
12
src/Styler.spec.js
Normal file
12
src/Styler.spec.js
Normal file
@ -0,0 +1,12 @@
|
||||
import fsPromises from 'fs/promises';
|
||||
import Styler from './Styler';
|
||||
|
||||
describe('Styler', () => {
|
||||
describe('getStyleFor', () => {
|
||||
test('returns false for landuse_park, line', async () => {
|
||||
const style = JSON.parse(await fsPromises.readFile('./styles/dark.json'));
|
||||
const styler = new Styler(style);
|
||||
expect(styler.getStyleFor('landuse_park', 'line')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
39
src/Tile.js
39
src/Tile.js
@ -4,29 +4,24 @@
|
||||
|
||||
Handling of and access to single VectorTiles
|
||||
*/
|
||||
'use strict';
|
||||
const VectorTile = require('@mapbox/vector-tile').VectorTile;
|
||||
const Protobuf = require('pbf');
|
||||
const zlib = require('zlib');
|
||||
const RBush = require('rbush');
|
||||
const x256 = require('x256');
|
||||
import { VectorTile } from '@mapbox/vector-tile';
|
||||
import Protobuf from 'pbf';
|
||||
import zlib from 'zlib';
|
||||
import RBush from 'rbush';
|
||||
import x256 from 'x256';
|
||||
|
||||
const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
import config from './config.js';
|
||||
import * as utils from './utils.js';
|
||||
|
||||
class Tile {
|
||||
export default class Tile {
|
||||
constructor(styler) {
|
||||
this.styler = styler;
|
||||
}
|
||||
|
||||
load(buffer) {
|
||||
return this._unzipIfNeeded(buffer).then((buffer) => {
|
||||
return this._loadTile(buffer);
|
||||
}).then(() => {
|
||||
return this._loadLayers();
|
||||
}).then(() => {
|
||||
return this;
|
||||
});
|
||||
async load(buffer) {
|
||||
const unzippedBuffer = await this._unzipIfNeeded(buffer);
|
||||
this._loadTile(unzippedBuffer);
|
||||
this._loadLayers();
|
||||
}
|
||||
|
||||
_loadTile(buffer) {
|
||||
@ -122,10 +117,10 @@ class Tile {
|
||||
}
|
||||
|
||||
_addBoundaries(deep, data) {
|
||||
let minX = 2e308;
|
||||
let maxX = -2e308;
|
||||
let minY = 2e308;
|
||||
let maxY = -2e308;
|
||||
let minX = 2e307;
|
||||
let maxX = -2e307;
|
||||
let minY = 2e307;
|
||||
let maxY = -2e307;
|
||||
const points = (deep ? data.points[0] : data.points);
|
||||
for (const p of points) {
|
||||
if (p.x < minX) minX = p.x;
|
||||
@ -163,5 +158,3 @@ class Tile {
|
||||
}
|
||||
|
||||
Tile.prototype.layers = {};
|
||||
|
||||
module.exports = Tile;
|
||||
|
@ -6,15 +6,14 @@
|
||||
* remote TileServer
|
||||
* local MBTiles and VectorTiles
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fetch = require('node-fetch');
|
||||
const envPaths = require('env-paths');
|
||||
import fsPromises from 'fs/promises';
|
||||
import path from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
import envPaths from 'env-paths';
|
||||
const paths = envPaths('mapscii');
|
||||
|
||||
const Tile = require('./Tile');
|
||||
const config = require('./config');
|
||||
import Tile from './Tile.js';
|
||||
import config from './config.js';
|
||||
|
||||
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
|
||||
// To maximize MapSCII’s compatibility, MBTiles support must be manually added via
|
||||
@ -30,8 +29,8 @@ const modes = {
|
||||
HTTP: 3,
|
||||
};
|
||||
|
||||
class TileSource {
|
||||
init(source) {
|
||||
export default class TileSource {
|
||||
async init(source) {
|
||||
this.source = source;
|
||||
|
||||
this.cache = {};
|
||||
@ -44,7 +43,7 @@ class TileSource {
|
||||
|
||||
if (this.source.startsWith('http')) {
|
||||
if (config.persistDownloadedTiles) {
|
||||
this._initPersistence();
|
||||
await this._initPersistence();
|
||||
}
|
||||
|
||||
this.mode = modes.HTTP;
|
||||
@ -102,15 +101,16 @@ class TileSource {
|
||||
}
|
||||
}
|
||||
|
||||
_getHTTP(z, x, y) {
|
||||
async _getHTTP(z, x, y) {
|
||||
let promise;
|
||||
const persistedTile = this._getPersited(z, x, y);
|
||||
const persistedTile = await this._getPersited(z, x, y);
|
||||
if (config.persistDownloadedTiles && persistedTile) {
|
||||
promise = Promise.resolve(persistedTile);
|
||||
} else {
|
||||
promise = fetch(this.source + [z,x,y].join('/') + '.pbf')
|
||||
.then((res) => res.buffer())
|
||||
.then((buffer) => {
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((arrayBuffer) => {
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
if (config.persistDownloadedTiles) {
|
||||
this._persistTile(z, x, y, buffer);
|
||||
return buffer;
|
||||
@ -122,7 +122,7 @@ class TileSource {
|
||||
});
|
||||
}
|
||||
|
||||
_getMBTile(z, x, y) {
|
||||
async _getMBTile(z, x, y) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.mbtiles.getTile(z, x, y, (err, buffer) => {
|
||||
if (err) {
|
||||
@ -133,40 +133,41 @@ class TileSource {
|
||||
});
|
||||
}
|
||||
|
||||
_createTile(z, x, y, buffer) {
|
||||
async _createTile(z, x, y, buffer) {
|
||||
const name = [z, x, y].join('-');
|
||||
this.cached.push(name);
|
||||
|
||||
const tile = this.cache[name] = new Tile(this.styler);
|
||||
return tile.load(buffer);
|
||||
await tile.load(buffer);
|
||||
return tile;
|
||||
}
|
||||
|
||||
_initPersistence() {
|
||||
async _initPersistence() {
|
||||
try {
|
||||
this._createFolder(paths.cache);
|
||||
await this._createFolder(paths.cache);
|
||||
} catch (error) {
|
||||
config.persistDownloadedTiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
_persistTile(z, x, y, buffer) {
|
||||
async _persistTile(z, x, y, buffer) {
|
||||
const zoom = z.toString();
|
||||
this._createFolder(path.join(paths.cache, zoom));
|
||||
await this._createFolder(path.join(paths.cache, zoom));
|
||||
const filePath = path.join(paths.cache, zoom, `${x}-${y}.pbf`);
|
||||
return fs.writeFile(filePath, buffer, () => null);
|
||||
return fsPromises.writeFile(filePath, buffer);
|
||||
}
|
||||
|
||||
_getPersited(z, x, y) {
|
||||
async _getPersited(z, x, y) {
|
||||
try {
|
||||
return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
|
||||
return await fsPromises.readFile(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_createFolder(path) {
|
||||
async _createFolder(path) {
|
||||
try {
|
||||
fs.mkdirSync(path);
|
||||
await fsPromises.mkdir(path);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.code === 'EEXIST') return true;
|
||||
@ -174,5 +175,3 @@ class TileSource {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TileSource;
|
||||
|
@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
const TileSource = require('./TileSource');
|
||||
import TileSource from './TileSource.js';
|
||||
|
||||
describe('TileSource', () => {
|
||||
describe('with a HTTP source', () => {
|
||||
|
BIN
src/__snapshots__/Renderer.spec.js.snap
Normal file
BIN
src/__snapshots__/Renderer.spec.js.snap
Normal file
Binary file not shown.
@ -1,4 +1,10 @@
|
||||
module.exports = {
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default {
|
||||
language: 'en',
|
||||
|
||||
// TODO: adapt to osm2vectortiles successor openmaptiles v3)
|
||||
@ -7,7 +13,7 @@ module.exports = {
|
||||
|
||||
//source: __dirname+"/../mbtiles/regensburg.mbtiles",
|
||||
|
||||
styleFile: __dirname+'/../styles/dark.json',
|
||||
styleFile: __dirname + '/../styles/dark.json',
|
||||
|
||||
initialZoom: null,
|
||||
maxZoom: 18,
|
||||
|
53
src/utils.js
53
src/utils.js
@ -4,15 +4,13 @@
|
||||
|
||||
methods used all around
|
||||
*/
|
||||
'use strict';
|
||||
const config = require('./config');
|
||||
import config from './config.js';
|
||||
|
||||
const constants = {
|
||||
RADIUS: 6378137,
|
||||
};
|
||||
|
||||
const utils = {
|
||||
clamp: (num, min, max) => {
|
||||
export const clamp = (num, min, max) => {
|
||||
if (num <= min) {
|
||||
return min;
|
||||
} else if (num >= max) {
|
||||
@ -20,43 +18,43 @@ const utils = {
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
baseZoom: (zoom) => {
|
||||
export const baseZoom = (zoom) => {
|
||||
return Math.min(config.tileRange, Math.max(0, Math.floor(zoom)));
|
||||
},
|
||||
};
|
||||
|
||||
tilesizeAtZoom: (zoom) => {
|
||||
return config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom));
|
||||
},
|
||||
export const tilesizeAtZoom = (zoom) => {
|
||||
return config.projectSize * Math.pow(2, zoom-baseZoom(zoom));
|
||||
};
|
||||
|
||||
deg2rad: (angle) => {
|
||||
export const deg2rad = (angle) => {
|
||||
// (angle / 180) * Math.PI
|
||||
return angle * 0.017453292519943295;
|
||||
},
|
||||
};
|
||||
|
||||
ll2tile: (lon, lat, zoom) => {
|
||||
export const ll2tile = (lon, lat, zoom) => {
|
||||
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),
|
||||
z: zoom,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
tile2ll: (x, y, zoom) => {
|
||||
export const tile2ll = (x, y, zoom) => {
|
||||
const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom);
|
||||
|
||||
return {
|
||||
lon: x/Math.pow(2, zoom)*360-180,
|
||||
lat: 180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
metersPerPixel: (zoom, lat = 0) => {
|
||||
export const metersPerPixel = (zoom, lat = 0) => {
|
||||
return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom));
|
||||
},
|
||||
};
|
||||
|
||||
hex2rgb: (color) => {
|
||||
export const hex2rgb = (color) => {
|
||||
if (typeof color !== 'string') return [255, 0, 0];
|
||||
|
||||
if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) {
|
||||
@ -74,13 +72,13 @@ const utils = {
|
||||
} else {
|
||||
return [(decimal>>16)&255, (decimal>>8)&255, decimal&255];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
digits: (number, digits) => {
|
||||
return Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits);
|
||||
},
|
||||
export const digits = (number, digits) => {
|
||||
return (Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)).toFixed(digits);
|
||||
};
|
||||
|
||||
normalize: (ll) => {
|
||||
export const normalize = (ll) => {
|
||||
if (ll.lon < -180) ll.lon += 360;
|
||||
if (ll.lon > 180) ll.lon -= 360;
|
||||
|
||||
@ -88,16 +86,13 @@ const utils = {
|
||||
if (ll.lat < -85.0511) ll.lat = -85.0511;
|
||||
|
||||
return ll;
|
||||
},
|
||||
};
|
||||
|
||||
population: (val) => {
|
||||
export const population = (val) => {
|
||||
let bits = 0;
|
||||
while (val > 0) {
|
||||
bits += val & 1;
|
||||
val >>= 1;
|
||||
}
|
||||
return bits;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = utils;
|
||||
|
@ -1,7 +1,18 @@
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
import * as utils from './utils.js';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('digits', () => {
|
||||
describe.each([
|
||||
[1, 0, '1'],
|
||||
[1, 1, '1.0'],
|
||||
[3.1415, 3, '3.141'],
|
||||
])('when given value=%f and digits=%f', (value, digits, expected_value) => {
|
||||
test(`returns ${expected_value}`, () => {
|
||||
expect(utils.digits(value, digits)).toEqual(expected_value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hex2rgb', () => {
|
||||
describe.each([
|
||||
['#ff0000', 255, 0, 0],
|
||||
@ -22,9 +33,8 @@ describe('utils', () => {
|
||||
expect(wrapper).toThrow('isn\'t a supported hex color');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize', () => {
|
||||
describe('normalize', () => {
|
||||
describe.each([
|
||||
[0, 0, 0, 0],
|
||||
[61, 48, 61, 48],
|
||||
@ -44,4 +54,5 @@ describe('normalize', () => {
|
||||
expect(utils.normalize(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user