Translate to TypeScript (runs with "bun run main.ts")

This commit is contained in:
Christian Paul 2024-11-02 16:01:04 +01:00
parent 4fe9a60a0c
commit 98051b62ca
16 changed files with 3861 additions and 4002 deletions

View File

@ -1,11 +1,11 @@
module.exports = { export default {
"env": { "env": {
"es6": true, "es6": true,
"node": true, "node": true,
"jest": true "jest": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 2018 "ecmaVersion": 2020
}, },
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",

3
babel.config.json Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-typescript"]
}

View File

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

7542
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"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",
@ -16,7 +17,7 @@
"mapscii": "./bin/mapscii.sh" "mapscii": "./bin/mapscii.sh"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=20"
}, },
"keywords": [ "keywords": [
"map", "map",
@ -42,11 +43,12 @@
"string-width": "^4.2.0", "string-width": "^4.2.0",
"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.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-typescript": "^7.25.7",
"eslint": "^7.8.1", "eslint": "^7.8.1",
"eslint-plugin-jest": "^24.0.0", "eslint-plugin-jest": "^24.0.0",
"jest": "^26.4.2" "jest": "^29.7.0"
} }
} }

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';
const config = require('./config'); import utils from './utils';
const utils = require('./utils');
const asciiMap = { const asciiMap = {
// '▬': [2+32, 4+64], // '▬': [2+32, 4+64],
@ -30,13 +29,20 @@ const asciiMap = {
const termReset = '\x1B[39;49m'; const termReset = '\x1B[39;49m';
class BrailleBuffer { class BrailleBuffer {
constructor(width, height) { private brailleMap: number[][];
private pixelBuffer: Buffer;
private charBuffer: unknown[] | null;
private foregroundBuffer: Buffer;
private backgroundBuffer: Buffer;
private height: number;
private width: number;
private globalBackground: string | null;
private asciiToBraille: unknown[];
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.pixelBuffer = null;
this.charBuffer = null; this.charBuffer = null;
this.foregroundBuffer = null;
this.backgroundBuffer = null;
this.asciiToBraille = []; this.asciiToBraille = [];
@ -61,7 +67,7 @@ class BrailleBuffer {
this.backgroundBuffer.fill(0); this.backgroundBuffer.fill(0);
} }
setGlobalBackground(background) { setGlobalBackground(background: string) {
this.globalBackground = background; this.globalBackground = background;
} }
@ -208,4 +214,4 @@ class BrailleBuffer {
} }
} }
module.exports = BrailleBuffer; export default BrailleBuffer;

View File

@ -10,13 +10,17 @@
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';
const BrailleBuffer = require('./BrailleBuffer');
class Canvas { class Canvas {
constructor(width, height) { private buffer: BrailleBuffer;
private height: number;
private stack: unknown[] = [];
private width: number;
constructor(width: number, height: number) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.buffer = new BrailleBuffer(width, height); this.buffer = new BrailleBuffer(width, height);
@ -30,7 +34,7 @@ class Canvas {
this.buffer.clear(); this.buffer.clear();
} }
text(text, x, y, color, center = false) { text(text: string, x: number, y: number, color, center = false) {
this.buffer.writeText(text, x, y, color, center); this.buffer.writeText(text, x, y, color, center);
} }
@ -55,8 +59,8 @@ class Canvas {
} }
polygon(rings, color) { polygon(rings, color) {
const vertices = []; const vertices: number[] = [];
const holes = []; const holes: number[] = [];
for (const ring of rings) { for (const ring of rings) {
if (vertices.length) { if (vertices.length) {
if (ring.length < 3) continue; if (ring.length < 3) continue;
@ -197,6 +201,4 @@ class Canvas {
} }
} }
Canvas.prototype.stack = []; export default Canvas;
module.exports = Canvas;

View File

@ -5,11 +5,12 @@
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 {
private tree: RBush;
private margin: number;
constructor() { constructor() {
this.tree = new RBush(); this.tree = new RBush();
@ -20,11 +21,11 @@ module.exports = class LabelBuffer {
this.tree.clear(); this.tree.clear();
} }
project(x, y) { project(x: number, y: number) {
return [Math.floor(x/2), Math.floor(y/4)]; return [Math.floor(x/2), Math.floor(y/4)];
} }
writeIfPossible(text, x, y, feature, margin) { writeIfPossible(text: string, x: number, y: number, feature, margin: number) {
margin = margin || this.margin; margin = margin || this.margin;
const point = this.project(x, y); const point = this.project(x, y);
@ -38,15 +39,15 @@ module.exports = class LabelBuffer {
} }
} }
featuresAt(x, y) { featuresAt(x: number, y: number) {
this.tree.search({minX: x, maxX: x, minY: y, maxY: y}); this.tree.search({minX: x, maxX: x, minY: y, maxY: y});
} }
_hasSpace(text, x, y) { _hasSpace(text: string, x: number, y: number) {
return !this.tree.collides(this._calculateArea(text, x, y)); return !this.tree.collides(this._calculateArea(text, x, y));
} }
_calculateArea(text, x, y, margin = 0) { _calculateArea(text: string, x: number, y: number, margin = 0) {
return { return {
minX: x-margin, minX: x-margin,
minY: y-margin/2, minY: y-margin/2,

View File

@ -4,17 +4,31 @@
UI and central command center UI and central command center
*/ */
'use strict'; import fs from 'fs';
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 type Canvas from './Canvas';
const TileSource = require('./TileSource'); import Renderer from './Renderer';
const utils = require('./utils'); import TileSource from './TileSource';
let config = require('./config'); import utils from './utils';
import globalConfig from './config';
let config = globalConfig;
class Mapscii { class Mapscii {
private width: number | null;
private height: number | null;
private canvas: Canvas | null;
private mouse: any;
private mouseDragging: boolean;
private mousePosition: any;
private tileSource: TileSource | null;
private renderer: Renderer | null;
private zoom: number;
private minZoom: number | null;
private center: any;
constructor(options) { constructor(options) {
this.width = null; this.width = null;
this.height = null; this.height = null;
@ -310,4 +324,4 @@ class Mapscii {
} }
} }
module.exports = Mapscii; export default Mapscii;

View File

@ -4,18 +4,37 @@
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';
const LabelBuffer = require('./LabelBuffer'); import LabelBuffer from './LabelBuffer';
const Styler = require('./Styler'); import Styler from './Styler';
const utils = require('./utils'); import utils from './utils';
const config = require('./config'); import config from './config';
import TileSource from './TileSource';
class Renderer { class Renderer {
constructor(output, tileSource, style) { private canvas: Canvas | undefined;
private height: number;
private labelBuffer: LabelBuffer;
private output: unknown;
private styler: Styler;
private tileSource: TileSource;
private width: number;
private _seen: Record<string, boolean>;
private terminal = {
CLEAR: '\x1B[2J',
MOVE: '\x1B[?6h',
};
private isDrawing = false;
private lastDrawAt = 0;
private tilePadding = 64;
constructor(output, tileSource: TileSource, style) {
this.output = output; this.output = output;
this.tileSource = tileSource; this.tileSource = tileSource;
this.labelBuffer = new LabelBuffer(); this.labelBuffer = new LabelBuffer();
@ -23,13 +42,13 @@ class Renderer {
this.tileSource.useStyler(this.styler); this.tileSource.useStyler(this.styler);
} }
setSize(width, height) { setSize(width: number, height: number) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.canvas = new Canvas(width, height); this.canvas = new Canvas(width, height);
} }
async draw(center, zoom) { async draw(center, zoom: number) {
if (this.isDrawing) return Promise.reject(); if (this.isDrawing) return Promise.reject();
this.isDrawing = true; this.isDrawing = true;
@ -321,15 +340,4 @@ class Renderer {
} }
} }
Renderer.prototype.terminal = { export default Renderer;
CLEAR: '\x1B[2J',
MOVE: '\x1B[?6h',
};
Renderer.prototype.isDrawing = false;
Renderer.prototype.lastDrawAt = 0;
Renderer.prototype.labelBuffer = null;
Renderer.prototype.tileSource = null;
Renderer.prototype.tilePadding = 64;
module.exports = Renderer;

View File

@ -8,9 +8,12 @@
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 { class Styler {
public styleById: Record<string, Record<string, unknown>>;
public styleByLayer: Record<string, unknown[]>;
private styleName: string;
constructor(style) { constructor(style) {
this.styleById = {}; this.styleById = {};
this.styleByLayer = {}; this.styleByLayer = {};
@ -130,4 +133,4 @@ class Styler {
} }
} }
module.exports = Styler; export default Styler;

View File

@ -4,18 +4,22 @@
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';
const utils = require('./utils'); import utils from './utils';
import Styler from './Styler';
class Tile { class Tile {
constructor(styler) { public layers: any[];
private styler: Styler;
private tile: VectorTile;
constructor(styler: Styler) {
this.styler = styler; this.styler = styler;
} }
@ -29,11 +33,11 @@ class Tile {
}); });
} }
_loadTile(buffer) { private _loadTile(buffer) {
this.tile = new VectorTile(new Protobuf(buffer)); this.tile = new VectorTile(new Protobuf(buffer));
} }
_unzipIfNeeded(buffer) { private _unzipIfNeeded(buffer) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this._isGzipped(buffer)) { if (this._isGzipped(buffer)) {
zlib.gunzip(buffer, (err, data) => { zlib.gunzip(buffer, (err, data) => {
@ -48,11 +52,11 @@ class Tile {
}); });
} }
_isGzipped(buffer) { private _isGzipped(buffer) {
return buffer.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0; return buffer.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
} }
_loadLayers() { private _loadLayers() {
const layers = {}; const layers = {};
const colorCache = {}; const colorCache = {};
for (const name in this.tile.layers) { for (const name in this.tile.layers) {
@ -121,7 +125,7 @@ class Tile {
return this.layers = layers; return this.layers = layers;
} }
_addBoundaries(deep, data) { private _addBoundaries(deep, data) {
let minX = 2e308; let minX = 2e308;
let maxX = -2e308; let maxX = -2e308;
let minY = 2e308; let minY = 2e308;
@ -140,7 +144,7 @@ class Tile {
return data; return data;
} }
_reduceGeometry(feature, factor) { private _reduceGeometry(feature, factor) {
const results = []; const results = [];
const geometries = feature.loadGeometry(); const geometries = feature.loadGeometry();
for (const points of geometries) { for (const points of geometries) {
@ -164,4 +168,4 @@ class Tile {
Tile.prototype.layers = {}; Tile.prototype.layers = {};
module.exports = Tile; export default Tile;

View File

@ -1,12 +1,12 @@
'use strict'; import { describe, expect, test } from 'jest';
const TileSource = require('./TileSource'); import TileSource from './TileSource.ts';
describe('TileSource', () => { describe('TileSource', () => {
describe('with a HTTP source', () => { describe('with a HTTP source', () => {
test('sets the mode to 3', async () => { test('sets the mode to 3', async () => {
const tileSource = new TileSource(); const tileSource = new TileSource();
await tileSource.init('http://mapscii.me/'); await tileSource.init('http://mapscii.me/');
expect(tileSource.mode).toBe(3); expect(tileSource.mode).toBe(Mode.HTTP);
}); });
}); });
}); });

View File

@ -6,32 +6,39 @@
* remote TileServer * remote TileServer
* local MBTiles and VectorTiles * local MBTiles and VectorTiles
*/ */
'use strict'; import fs from 'fs';
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';
const config = require('./config'); import config from './config';
// 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
// $> npm install -g @mapbox/mbtiles // $> npm install -g @mapbox/mbtiles
let MBTiles = null; let MBTiles = null;
try { try {
MBTiles = require('@mapbox/mbtiles'); MBTiles = await import('@mapbox/mbtiles');
} catch (err) {void 0;} } catch (err) {void 0;}
const modes = { export enum Mode {
MBTiles: 1, MBTiles = 1,
VectorTile: 2, VectorTile = 2,
HTTP: 3, HTTP = 3,
}; };
class TileSource { class TileSource {
init(source) { private source: string;
private cache: any;
private cacheSize: number;
private cached: any;
private mode: Mode | null;
private mbtiles: any;
private styler: any;
init(source: string) {
this.source = source; this.source = source;
this.cache = {}; this.cache = {};
@ -47,14 +54,14 @@ class TileSource {
this._initPersistence(); this._initPersistence();
} }
this.mode = modes.HTTP; this.mode = Mode.HTTP;
} else if (this.source.endsWith('.mbtiles')) { } else if (this.source.endsWith('.mbtiles')) {
if (!MBTiles) { if (!MBTiles) {
throw new Error('MBTiles support must be installed with following command: \'npm install -g @mapbox/mbtiles\''); throw new Error('MBTiles support must be installed with following command: \'npm install -g @mapbox/mbtiles\'');
} }
this.mode = modes.MBTiles; this.mode = Mode.MBTiles;
this.loadMBTiles(source); this.loadMBTiles(source);
} else { } else {
throw new Error('source type isn\'t supported yet'); throw new Error('source type isn\'t supported yet');
@ -77,7 +84,7 @@ class TileSource {
this.styler = styler; this.styler = styler;
} }
getTile(z, x, y) { getTile(z: number, x: number, y: number) {
if (!this.mode) { if (!this.mode) {
throw new Error('no TileSource defined'); throw new Error('no TileSource defined');
} }
@ -95,14 +102,14 @@ class TileSource {
} }
switch (this.mode) { switch (this.mode) {
case modes.MBTiles: case Mode.MBTiles:
return this._getMBTile(z, x, y); return this._getMBTile(z, x, y);
case modes.HTTP: case Mode.HTTP:
return this._getHTTP(z, x, y); return this._getHTTP(z, x, y);
} }
} }
_getHTTP(z, x, y) { private _getHTTP(z: number, x: number, y: number) {
let promise; let promise;
const persistedTile = this._getPersited(z, x, y); const persistedTile = this._getPersited(z, x, y);
if (config.persistDownloadedTiles && persistedTile) { if (config.persistDownloadedTiles && persistedTile) {
@ -122,7 +129,7 @@ class TileSource {
}); });
} }
_getMBTile(z, x, y) { private _getMBTile(z: number, x: number, y: number) {
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,7 +140,7 @@ class TileSource {
}); });
} }
_createTile(z, x, y, buffer) { private _createTile(z: number, x: number, y: number, buffer) {
const name = [z, x, y].join('-'); const name = [z, x, y].join('-');
this.cached.push(name); this.cached.push(name);
@ -141,7 +148,7 @@ class TileSource {
return tile.load(buffer); return tile.load(buffer);
} }
_initPersistence() { private _initPersistence() {
try { try {
this._createFolder(paths.cache); this._createFolder(paths.cache);
} catch (error) { } catch (error) {
@ -149,14 +156,14 @@ class TileSource {
} }
} }
_persistTile(z, x, y, buffer) { private _persistTile(z, x, y, buffer) {
const zoom = z.toString(); const zoom = z.toString();
this._createFolder(path.join(paths.cache, zoom)); 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 fs.writeFile(filePath, buffer, () => null);
} }
_getPersited(z, x, y) { 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 (error) {
@ -164,7 +171,7 @@ class TileSource {
} }
} }
_createFolder(path) { private _createFolder(path: string): true {
try { try {
fs.mkdirSync(path); fs.mkdirSync(path);
return true; return true;
@ -175,4 +182,4 @@ class TileSource {
} }
} }
module.exports = TileSource; export default TileSource;

View File

@ -1,4 +1,4 @@
module.exports = { export default {
language: 'en', language: 'en',
// TODO: adapt to osm2vectortiles successor openmaptiles v3) // TODO: adapt to osm2vectortiles successor openmaptiles v3)
@ -7,7 +7,7 @@ module.exports = {
//source: __dirname+"/../mbtiles/regensburg.mbtiles", //source: __dirname+"/../mbtiles/regensburg.mbtiles",
styleFile: __dirname+'/../styles/dark.json', styleFile: import.meta.dirname+'/../styles/dark.json',
initialZoom: null, initialZoom: null,
maxZoom: 18, maxZoom: 18,

View File

@ -4,15 +4,14 @@
methods used all around methods used all around
*/ */
'use strict'; import config from './config';
const config = require('./config');
const constants = { const constants = {
RADIUS: 6378137, RADIUS: 6378137,
}; };
const utils = { const utils = {
clamp: (num, min, max) => { clamp: (num: number, min: number, max: number): number => {
if (num <= min) { if (num <= min) {
return min; return min;
} else if (num >= max) { } else if (num >= max) {
@ -22,20 +21,20 @@ const utils = {
} }
}, },
baseZoom: (zoom) => { baseZoom: (zoom: number): number => {
return Math.min(config.tileRange, Math.max(0, Math.floor(zoom))); return Math.min(config.tileRange, Math.max(0, Math.floor(zoom)));
}, },
tilesizeAtZoom: (zoom) => { tilesizeAtZoom: (zoom: number): number => {
return config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom)); return config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom));
}, },
deg2rad: (angle) => { deg2rad: (angle: number): number => {
// (angle / 180) * Math.PI // (angle / 180) * Math.PI
return angle * 0.017453292519943295; return angle * 0.017453292519943295;
}, },
ll2tile: (lon, lat, zoom) => { ll2tile: (lon: number, lat: number, zoom: 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),
@ -43,7 +42,7 @@ const utils = {
}; };
}, },
tile2ll: (x, y, zoom) => { tile2ll: (x: number, y: number, zoom: 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 {
@ -52,18 +51,18 @@ const utils = {
}; };
}, },
metersPerPixel: (zoom, lat = 0) => { metersPerPixel: (zoom: number, lat = 0): number => {
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) => { hex2rgb: (color: any): [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.substr(1); color = color.substring(1);
const decimal = parseInt(color, 16); const decimal = parseInt(color, 16);
if (color.length === 3) { if (color.length === 3) {
@ -76,11 +75,11 @@ const utils = {
} }
}, },
digits: (number, digits) => { digits: (number: number, digits: number): number => {
return Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits); return Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits);
}, },
normalize: (ll) => { normalize: (ll: {lat: number, lon: number}): {lat: number, lon: number} => {
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;
@ -90,7 +89,7 @@ const utils = {
return ll; return ll;
}, },
population: (val) => { population: (val: number): number => {
let bits = 0; let bits = 0;
while (val > 0) { while (val > 0) {
bits += val & 1; bits += val & 1;
@ -100,4 +99,4 @@ const utils = {
}, },
}; };
module.exports = utils; export default utils;