Merge a45870de45703a332153c961f7ff3b667b712ee1 into 4fe9a60a0c9da952dadc5214a9ca5c68c447fdf8

This commit is contained in:
Christian Paul 2023-02-26 17:57:28 +01:00 committed by GitHub
commit 4648bea0a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 6798 additions and 3682 deletions

View File

@ -1,16 +1,17 @@
module.exports = { module.exports = {
"env": { "env": {
"es6": true,
"node": true, "node": true,
"es2021": true,
"jest": true "jest": true
}, },
"parserOptions": {
"ecmaVersion": 2018
},
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:jest/recommended" "plugin:jest/recommended"
], ],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": { "rules": {
"indent": [ "indent": [
"error", "error",
@ -33,4 +34,4 @@ module.exports = {
"always" "always"
] ]
} }
}; }

10
main.js
View File

@ -7,11 +7,11 @@
TODO: params parsing and so on TODO: params parsing and so on
#*/ #*/
'use strict'; import config from './src/config.js';
const config = require('./src/config'); import Mapscii from './src/Mapscii.js';
const Mapscii = require('./src/Mapscii'); import yargs from 'yargs';
const argv = require('yargs')
.option('latitude', { const argv = yargs().option('latitude', {
alias: 'lat', alias: 'lat',
description: 'Latitude of initial centre', description: 'Latitude of initial centre',
default: config.initialLat, default: config.initialLat,

9869
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,11 @@
"version": "0.3.1", "version": "0.3.1",
"description": "MapSCII is a Braille & ASCII world map renderer for your console, based on OpenStreetMap", "description": "MapSCII is a Braille & ASCII world map renderer for your console, based on OpenStreetMap",
"main": "main.js", "main": "main.js",
"type": "module",
"scripts": { "scripts": {
"lint": "eslint src", "lint": "eslint src",
"start": "node main", "start": "node main",
"test": "jest" "test": "NODE_OPTIONS=--experimental-vm-modules jest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -32,21 +33,21 @@
"dependencies": { "dependencies": {
"@mapbox/vector-tile": "^1.3.1", "@mapbox/vector-tile": "^1.3.1",
"bresenham": "0.0.4", "bresenham": "0.0.4",
"earcut": "^2.2.2", "earcut": "^2.2.4",
"env-paths": "^2.2.0", "env-paths": "^3.0.0",
"keypress": "^0.2.1", "keypress": "^0.2.1",
"node-fetch": "^2.6.1", "node-fetch": "^3.3.0",
"pbf": "^3.2.1", "pbf": "^3.2.1",
"rbush": "^3.0.1", "rbush": "^3.0.1",
"simplify-js": "^1.2.4", "simplify-js": "^1.2.4",
"string-width": "^4.2.0", "string-width": "^5.1.2",
"term-mouse": "^0.2.2", "term-mouse": "^0.2.2",
"x256": "0.0.2", "x256": "0.0.2",
"yargs": "^15.4.1" "yargs": "^17.7.1"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.8.1", "eslint": "^8.35.0",
"eslint-plugin-jest": "^24.0.0", "eslint-plugin-jest": "^27.2.1",
"jest": "^26.4.2" "jest": "^29.4.3"
} }
} }

View File

@ -11,10 +11,9 @@
Will either be merged into node-drawille or become an own module at some point Will either be merged into node-drawille or become an own module at some point
*/ */
'use strict'; import stringWidth from 'string-width';
const stringWidth = require('string-width'); import config from './config.js';
const config = require('./config'); import * as utils from './utils.js';
const utils = require('./utils');
const asciiMap = { const asciiMap = {
// '▬': [2+32, 4+64], // '▬': [2+32, 4+64],
@ -29,7 +28,7 @@ const asciiMap = {
}; };
const termReset = '\x1B[39;49m'; const termReset = '\x1B[39;49m';
class BrailleBuffer { export default class BrailleBuffer {
constructor(width, height) { constructor(width, height) {
this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]; this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];
@ -114,9 +113,8 @@ class BrailleBuffer {
} }
//TODO Optimize this part //TODO Optimize this part
var i, k;
const results = []; 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); const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128);
results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) { results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) {
const covered = utils.population(mask.mask & braille); const covered = utils.population(mask.mask & braille);
@ -207,5 +205,3 @@ class BrailleBuffer {
} }
} }
} }
module.exports = BrailleBuffer;

10
src/BrailleBuffer.spec.js Normal file
View 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);
});
});

View File

@ -10,12 +10,11 @@
Will most likely be turned into a stand alone module at some point Will most likely be turned into a stand alone module at some point
*/ */
'use strict'; import bresenham from 'bresenham';
const bresenham = require('bresenham'); import earcut from 'earcut';
const earcut = require('earcut'); import BrailleBuffer from './BrailleBuffer.js';
const BrailleBuffer = require('./BrailleBuffer');
class Canvas { export default class Canvas {
constructor(width, height) { constructor(width, height) {
this.width = width; this.width = width;
this.height = height; this.height = height;
@ -164,9 +163,8 @@ class Canvas {
const b = this._bresenham(pointA, pointC); const b = this._bresenham(pointA, pointC);
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(({y}) => {
var ref; return 0 <= y && y < 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) {
return a.x - b.x; return a.x - b.x;
@ -198,5 +196,3 @@ class Canvas {
} }
Canvas.prototype.stack = []; Canvas.prototype.stack = [];
module.exports = Canvas;

View File

@ -5,11 +5,10 @@
Using 2D spatial indexing to avoid overlapping labels and markers Using 2D spatial indexing to avoid overlapping labels and markers
and to find labels underneath a mouse cursor's position and to find labels underneath a mouse cursor's position
*/ */
'use strict'; import RBush from 'rbush';
const RBush = require('rbush'); import stringWidth from 'string-width';
const stringWidth = require('string-width');
module.exports = class LabelBuffer { export default class LabelBuffer {
constructor() { constructor() {
this.tree = new RBush(); this.tree = new RBush();
@ -54,4 +53,4 @@ module.exports = class LabelBuffer {
maxY: y+margin/2, maxY: y+margin/2,
}; };
} }
}; }

View File

@ -4,17 +4,16 @@
UI and central command center UI and central command center
*/ */
'use strict'; import fsPromises from 'fs/promises';
const fs = require('fs'); import keypress from 'keypress';
const keypress = require('keypress'); import TermMouse from 'term-mouse';
const TermMouse = require('term-mouse');
const Renderer = require('./Renderer'); import Renderer from './Renderer.js';
const TileSource = require('./TileSource'); import TileSource from './TileSource.js';
const utils = require('./utils'); import * as utils from './utils.js';
let config = require('./config'); import MapsciiConfig from './config.js';
class Mapscii { export default class Mapscii {
constructor(options) { constructor(options) {
this.width = null; this.width = null;
this.height = null; this.height = null;
@ -32,45 +31,48 @@ class Mapscii {
this.zoom = 0; this.zoom = 0;
this.minZoom = null; this.minZoom = null;
config = Object.assign(config, options); this.config = {
...MapsciiConfig,
...options,
};
this.center = { this.center = {
lat: config.initialLat, lat: this.config.initialLat,
lon: config.initialLon lon: this.config.initialLon,
}; };
} }
async init() { async init() {
if (!config.headless) { if (!this.config.headless) {
this._initKeyboard(); this._initKeyboard();
this._initMouse(); this._initMouse();
} }
this._initTileSource(); await this._initTileSource();
this._initRenderer(); await this._initRenderer();
this._draw(); await this._draw();
this.notify('Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.'); 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 = new TileSource();
this.tileSource.init(config.source); await this.tileSource.init(this.config.source);
} }
_initKeyboard() { _initKeyboard() {
keypress(config.input); keypress(this.config.input);
if (config.input.setRawMode) { if (this.config.input.setRawMode) {
config.input.setRawMode(true); 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() { _initMouse() {
this.mouse = TermMouse({ this.mouse = TermMouse({
input: config.input, input: this.config.input,
output: config.output, output: this.config.output,
}); });
this.mouse.start(); this.mouse.start();
@ -79,22 +81,22 @@ class Mapscii {
this.mouse.on('move', (event) => this._onMouseMove(event)); this.mouse.on('move', (event) => this._onMouseMove(event));
} }
_initRenderer() { async _initRenderer() {
const style = JSON.parse(fs.readFileSync(config.styleFile, 'utf8')); const style = JSON.parse(await fsPromises.readFile(this.config.styleFile, 'utf8'));
this.renderer = new Renderer(config.output, this.tileSource, style); this.renderer = new Renderer(this.tileSource, style);
config.output.on('resize', () => { this.config.output.on('resize', () => {
this._resizeRenderer(); this._resizeRenderer();
this._draw(); this._draw();
}); });
this._resizeRenderer(); this._resizeRenderer();
this.zoom = (config.initialZoom !== null) ? config.initialZoom : this.minZoom; this.zoom = (this.config.initialZoom !== null) ? this.config.initialZoom : this.minZoom;
} }
_resizeRenderer() { _resizeRenderer() {
this.width = config.size && config.size.width ? config.size.width * 2 : config.output.columns >> 1 << 2; this.width = this.config.size && this.config.size.width ? this.config.size.width * 2 : this.config.output.columns >> 1 << 2;
this.height = config.size && config.size.height ? config.size.height * 4 : config.output.rows * 4 - 4; 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; this.minZoom = 4-Math.log(4096/this.width)/Math.LN2;
@ -142,7 +144,7 @@ class Mapscii {
const targetMouseLonLat = this._colrow2ll(event.x, event.y); const targetMouseLonLat = this._colrow2ll(event.x, event.y);
// zoom toward the center // 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 // the location the pointer ended up after zooming
const offsetMouseLonLat = this._colrow2ll(event.x, event.y); 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) { 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 (this.config.mouseCallback && !this.config.mouseCallback(event)) {
return; return;
} }
@ -207,25 +209,25 @@ class Mapscii {
} }
_onKey(key) { _onKey(key) {
if (config.keyCallback && !config.keyCallback(key)) return; if (this.config.keyCallback && !this.config.keyCallback(key)) return;
if (!key || !key.name) return; if (!key || !key.name) return;
// check if the pressed key is configured // check if the pressed key is configured
let draw = true; let draw = true;
switch (key.name) { switch (key.name) {
case 'q': case 'q':
if (config.quitCallback) { if (this.config.quitCallback) {
config.quitCallback(); this.config.quitCallback();
} else { } else {
process.exit(0); process.exit(0);
} }
break; break;
case 'a': case 'a':
this.zoomBy(config.zoomStep); this.zoomBy(this.config.zoomStep);
break; break;
case 'y': case 'y':
case 'z': case 'z':
this.zoomBy(-config.zoomStep); this.zoomBy(-this.config.zoomStep);
break; break;
case 'left': case 'left':
case 'h': case 'h':
@ -244,7 +246,7 @@ class Mapscii {
this.moveBy(-6/Math.pow(2, this.zoom), 0); this.moveBy(-6/Math.pow(2, this.zoom), 0);
break; break;
case 'c': case 'c':
config.useBraille = !config.useBraille; this.config.useBraille = !this.config.useBraille;
break; break;
default: default:
draw = false; draw = false;
@ -255,13 +257,14 @@ class Mapscii {
} }
} }
_draw() { async _draw() {
this.renderer.draw(this.center, this.zoom).then((frame) => { try {
const frame = await this.renderer.draw(this.center, this.zoom);
this._write(frame); this._write(frame);
this.notify(this._getFooter()); this.notify(this._getFooter());
}).catch(() => { } catch {
this.notify('renderer is busy'); this.notify('renderer is busy');
}); }
} }
_getFooter() { _getFooter() {
@ -277,22 +280,22 @@ class Mapscii {
} }
notify(text) { notify(text) {
config.onUpdate && config.onUpdate(); this.config.onUpdate && this.config.onUpdate();
if (!config.headless) { if (!this.config.headless) {
this._write('\r\x1B[K' + text); this._write('\r\x1B[K' + text);
} }
} }
_write(output) { _write(output) {
config.output.write(output); this.config.output.write(output);
} }
zoomBy(step) { zoomBy(step) {
if (this.zoom+step < this.minZoom) { if (this.zoom+step < this.minZoom) {
return this.zoom = this.minZoom; return this.zoom = this.minZoom;
} }
if (this.zoom+step > config.maxZoom) { if (this.zoom+step > this.config.maxZoom) {
return this.zoom = config.maxZoom; return this.zoom = this.config.maxZoom;
} }
this.zoom += step; this.zoom += step;
@ -309,5 +312,3 @@ class Mapscii {
}); });
} }
} }
module.exports = Mapscii;

View File

@ -4,19 +4,17 @@
The Console Vector Tile renderer - bäm! The Console Vector Tile renderer - bäm!
*/ */
'use strict'; import x256 from 'x256';
const x256 = require('x256'); import simplify from 'simplify-js';
const simplify = require('simplify-js');
const Canvas = require('./Canvas'); import Canvas from './Canvas.js';
const LabelBuffer = require('./LabelBuffer'); import LabelBuffer from './LabelBuffer.js';
const Styler = require('./Styler'); import Styler from './Styler.js';
const utils = require('./utils'); import * as utils from './utils.js';
const config = require('./config'); import config from './config.js';
class Renderer { export default class Renderer {
constructor(output, tileSource, style) { constructor(tileSource, style) {
this.output = output;
this.tileSource = tileSource; this.tileSource = tileSource;
this.labelBuffer = new LabelBuffer(); this.labelBuffer = new LabelBuffer();
this.styler = new Styler(style); this.styler = new Styler(style);
@ -331,5 +329,3 @@ Renderer.prototype.lastDrawAt = 0;
Renderer.prototype.labelBuffer = null; Renderer.prototype.labelBuffer = null;
Renderer.prototype.tileSource = null; Renderer.prototype.tileSource = null;
Renderer.prototype.tilePadding = 64; Renderer.prototype.tilePadding = 64;
module.exports = Renderer;

21
src/Renderer.spec.js Normal file
View 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();
});
});
});

View File

@ -8,13 +8,11 @@
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.
*/ */
'use strict';
class Styler { export default class Styler {
constructor(style) { constructor(style) {
this.styleById = {}; this.styleById = {};
this.styleByLayer = {}; this.styleByLayer = {};
var 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);
@ -31,10 +29,7 @@ class Styler {
layer.appliesTo = this._compileFilter(layer.filter); layer.appliesTo = this._compileFilter(layer.filter);
//TODO Better translation of: @styleByLayer[style['source-layer']] ?= [] this.styleByLayer[layer['source-layer']] ??= [];
if ((base = this.styleByLayer)[name = layer['source-layer']] == null) {
base[name] = [];
}
this.styleByLayer[layer['source-layer']].push(layer); this.styleByLayer[layer['source-layer']].push(layer);
this.styleById[layer.id] = layer; this.styleById[layer.id] = layer;
} }
@ -129,5 +124,3 @@ class Styler {
} }
} }
} }
module.exports = Styler;

12
src/Styler.spec.js Normal file
View 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);
});
});
});

View File

@ -4,29 +4,24 @@
Handling of and access to single VectorTiles Handling of and access to single VectorTiles
*/ */
'use strict'; import { VectorTile } from '@mapbox/vector-tile';
const VectorTile = require('@mapbox/vector-tile').VectorTile; import Protobuf from 'pbf';
const Protobuf = require('pbf'); import zlib from 'zlib';
const zlib = require('zlib'); import RBush from 'rbush';
const RBush = require('rbush'); import x256 from 'x256';
const x256 = require('x256');
const config = require('./config'); import config from './config.js';
const utils = require('./utils'); import * as utils from './utils.js';
class Tile { export default class Tile {
constructor(styler) { constructor(styler) {
this.styler = styler; this.styler = styler;
} }
load(buffer) { async load(buffer) {
return this._unzipIfNeeded(buffer).then((buffer) => { const unzippedBuffer = await this._unzipIfNeeded(buffer);
return this._loadTile(buffer); this._loadTile(unzippedBuffer);
}).then(() => { this._loadLayers();
return this._loadLayers();
}).then(() => {
return this;
});
} }
_loadTile(buffer) { _loadTile(buffer) {
@ -122,10 +117,10 @@ class Tile {
} }
_addBoundaries(deep, data) { _addBoundaries(deep, data) {
let minX = 2e308; let minX = 2e307;
let maxX = -2e308; let maxX = -2e307;
let minY = 2e308; let minY = 2e307;
let maxY = -2e308; let maxY = -2e307;
const points = (deep ? data.points[0] : data.points); const points = (deep ? data.points[0] : data.points);
for (const p of points) { for (const p of points) {
if (p.x < minX) minX = p.x; if (p.x < minX) minX = p.x;
@ -163,5 +158,3 @@ class Tile {
} }
Tile.prototype.layers = {}; Tile.prototype.layers = {};
module.exports = Tile;

View File

@ -6,15 +6,14 @@
* remote TileServer * remote TileServer
* local MBTiles and VectorTiles * local MBTiles and VectorTiles
*/ */
'use strict'; import fsPromises from 'fs/promises';
const fs = require('fs'); import path from 'path';
const path = require('path'); import fetch from 'node-fetch';
const fetch = require('node-fetch'); import envPaths from 'env-paths';
const envPaths = require('env-paths');
const paths = envPaths('mapscii'); const paths = envPaths('mapscii');
const Tile = require('./Tile'); import Tile from './Tile.js';
const config = require('./config'); import config from './config.js';
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3) // https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCIIs compatibility, MBTiles support must be manually added via // To maximize MapSCIIs compatibility, MBTiles support must be manually added via
@ -30,8 +29,8 @@ const modes = {
HTTP: 3, HTTP: 3,
}; };
class TileSource { export default class TileSource {
init(source) { async init(source) {
this.source = source; this.source = source;
this.cache = {}; this.cache = {};
@ -44,7 +43,7 @@ class TileSource {
if (this.source.startsWith('http')) { if (this.source.startsWith('http')) {
if (config.persistDownloadedTiles) { if (config.persistDownloadedTiles) {
this._initPersistence(); await this._initPersistence();
} }
this.mode = modes.HTTP; this.mode = modes.HTTP;
@ -102,15 +101,16 @@ class TileSource {
} }
} }
_getHTTP(z, x, y) { async _getHTTP(z, x, y) {
let promise; let promise;
const persistedTile = this._getPersited(z, x, y); const persistedTile = await this._getPersited(z, x, y);
if (config.persistDownloadedTiles && persistedTile) { if (config.persistDownloadedTiles && persistedTile) {
promise = Promise.resolve(persistedTile); promise = Promise.resolve(persistedTile);
} else { } else {
promise = fetch(this.source + [z,x,y].join('/') + '.pbf') promise = fetch(this.source + [z,x,y].join('/') + '.pbf')
.then((res) => res.buffer()) .then((res) => res.arrayBuffer())
.then((buffer) => { .then((arrayBuffer) => {
const buffer = Buffer.from(arrayBuffer);
if (config.persistDownloadedTiles) { if (config.persistDownloadedTiles) {
this._persistTile(z, x, y, buffer); this._persistTile(z, x, y, buffer);
return buffer; return buffer;
@ -122,7 +122,7 @@ class TileSource {
}); });
} }
_getMBTile(z, x, y) { async _getMBTile(z, x, y) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.mbtiles.getTile(z, x, y, (err, buffer) => { this.mbtiles.getTile(z, x, y, (err, buffer) => {
if (err) { 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('-'); const name = [z, x, y].join('-');
this.cached.push(name); this.cached.push(name);
const tile = this.cache[name] = new Tile(this.styler); const tile = this.cache[name] = new Tile(this.styler);
return tile.load(buffer); await tile.load(buffer);
return tile;
} }
_initPersistence() { async _initPersistence() {
try { try {
this._createFolder(paths.cache); await this._createFolder(paths.cache);
} catch (error) { } catch (error) {
config.persistDownloadedTiles = false; config.persistDownloadedTiles = false;
} }
} }
_persistTile(z, x, y, buffer) { async _persistTile(z, x, y, buffer) {
const zoom = z.toString(); 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`); 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 { 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) { } catch (error) {
return false; return false;
} }
} }
_createFolder(path) { async _createFolder(path) {
try { try {
fs.mkdirSync(path); await fsPromises.mkdir(path);
return true; return true;
} catch (error) { } catch (error) {
if (error.code === 'EEXIST') return true; if (error.code === 'EEXIST') return true;
@ -174,5 +175,3 @@ class TileSource {
} }
} }
} }
module.exports = TileSource;

View File

@ -1,5 +1,4 @@
'use strict'; import TileSource from './TileSource.js';
const TileSource = require('./TileSource');
describe('TileSource', () => { describe('TileSource', () => {
describe('with a HTTP source', () => { describe('with a HTTP source', () => {

Binary file not shown.

View File

@ -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', language: 'en',
// TODO: adapt to osm2vectortiles successor openmaptiles v3) // TODO: adapt to osm2vectortiles successor openmaptiles v3)
@ -7,7 +13,7 @@ module.exports = {
//source: __dirname+"/../mbtiles/regensburg.mbtiles", //source: __dirname+"/../mbtiles/regensburg.mbtiles",
styleFile: __dirname+'/../styles/dark.json', styleFile: __dirname + '/../styles/dark.json',
initialZoom: null, initialZoom: null,
maxZoom: 18, maxZoom: 18,

View File

@ -4,15 +4,13 @@
methods used all around methods used all around
*/ */
'use strict'; import config from './config.js';
const config = require('./config');
const constants = { const constants = {
RADIUS: 6378137, RADIUS: 6378137,
}; };
const utils = { export const clamp = (num, min, max) => {
clamp: (num, min, max) => {
if (num <= min) { if (num <= min) {
return min; return min;
} else if (num >= max) { } else if (num >= max) {
@ -20,43 +18,43 @@ const utils = {
} else { } else {
return num; return num;
} }
}, };
baseZoom: (zoom) => { export const baseZoom = (zoom) => {
return Math.min(config.tileRange, Math.max(0, Math.floor(zoom))); return Math.min(config.tileRange, Math.max(0, Math.floor(zoom)));
}, };
tilesizeAtZoom: (zoom) => { export const tilesizeAtZoom = (zoom) => {
return config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom)); return config.projectSize * Math.pow(2, zoom-baseZoom(zoom));
}, };
deg2rad: (angle) => { export const deg2rad = (angle) => {
// (angle / 180) * Math.PI // (angle / 180) * Math.PI
return angle * 0.017453292519943295; return angle * 0.017453292519943295;
}, };
ll2tile: (lon, lat, zoom) => { export const ll2tile = (lon, lat, zoom) => {
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),
z: 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); const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom);
return { return {
lon: x/Math.pow(2, zoom)*360-180, lon: x/Math.pow(2, zoom)*360-180,
lat: 180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))), 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)); 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 (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)) {
@ -74,13 +72,13 @@ const utils = {
} else { } else {
return [(decimal>>16)&255, (decimal>>8)&255, decimal&255]; return [(decimal>>16)&255, (decimal>>8)&255, decimal&255];
} }
}, };
digits: (number, digits) => { export const digits = (number, digits) => {
return Math.floor(number*Math.pow(10, digits))/Math.pow(10, 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;
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; if (ll.lat < -85.0511) ll.lat = -85.0511;
return ll; return ll;
}, };
population: (val) => { export const population = (val) => {
let bits = 0; let bits = 0;
while (val > 0) { while (val > 0) {
bits += val & 1; bits += val & 1;
val >>= 1; val >>= 1;
} }
return bits; return bits;
},
}; };
module.exports = utils;

View File

@ -1,7 +1,18 @@
'use strict'; import * as utils from './utils.js';
const utils = require('./utils');
describe('utils', () => { 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('hex2rgb', () => {
describe.each([ describe.each([
['#ff0000', 255, 0, 0], ['#ff0000', 255, 0, 0],
@ -22,9 +33,8 @@ describe('utils', () => {
expect(wrapper).toThrow('isn\'t a supported hex color'); expect(wrapper).toThrow('isn\'t a supported hex color');
}); });
}); });
});
describe('normalize', () => { describe('normalize', () => {
describe.each([ describe.each([
[0, 0, 0, 0], [0, 0, 0, 0],
[61, 48, 61, 48], [61, 48, 61, 48],
@ -44,4 +54,5 @@ describe('normalize', () => {
expect(utils.normalize(input)).toEqual(expected); expect(utils.normalize(input)).toEqual(expected);
}); });
}); });
});
}); });