Repository: rastapasta/mapscii
Branch: master
Commit: 4fe9a60a0c9d
Files: 29
Total size: 123.9 KB
Directory structure:
gitextract_o17myi0p/
├── .eslintrc.js
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── BugReport.md
│ ├── FeatureRequest.md
│ ├── Question.md
│ └── Support.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── AUTHORS
├── LICENSE
├── README.md
├── bin/
│ └── mapscii.sh
├── main.js
├── package.json
├── snap/
│ └── snapcraft.yaml
├── src/
│ ├── BrailleBuffer.js
│ ├── Canvas.js
│ ├── LabelBuffer.js
│ ├── Mapscii.js
│ ├── Renderer.js
│ ├── Styler.js
│ ├── Tile.js
│ ├── TileSource.js
│ ├── TileSource.spec.js
│ ├── config.js
│ ├── utils.js
│ └── utils.spec.js
└── styles/
├── bright.json
└── dark.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
module.exports = {
"env": {
"es6": true,
"node": true,
"jest": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"extends": [
"eslint:recommended",
"plugin:jest/recommended"
],
"rules": {
"indent": [
"error",
2,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"no-console": 0,
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
};
================================================
FILE: .github/ISSUE_TEMPLATE/BugReport.md
================================================
---
name: "\U0001F41B Bug report"
about: Something isn’t working as expected
---
**Steps to reproduce**
<!--
A step by step description of how to get to the error state.
Are you using the the telnet or the local client?
If you run MapSCII locally, what is your NodeJS version? (run `node -v`)
It might help to know which operating system and keyboard language your are using.
-->
**Current behavior**
<!--
A clear and concise description of what the bug is.
Please include any JavaScript errors
-->
**Expected behavior**
<!--
What did you expect to happen?
-->
================================================
FILE: .github/ISSUE_TEMPLATE/FeatureRequest.md
================================================
---
name: "\U0001F680 Feature request"
about: I have a suggestion (and might want to implement myself)
---
<!-- Consider opening a pull request instead: it’s a more productive way to discuss new features -->
**The problem**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Proposed solution**
<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->
**Alternative solutions**
<!-- A clear and concise description of any alternative solutions or features you’ve considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
================================================
FILE: .github/ISSUE_TEMPLATE/Question.md
================================================
---
name: "\U0001F914 Support question"
about: I have a question or don’t know how to do something
---
--------------^ Click “Preview”!
If you have a support question, feel free to ask it here.
Before submitting a new question, make sure you:
- Searched opened and closed [GitHub issues](https://github.com/rastapasta/mapscii/issues?utf8=%E2%9C%93&q=is%3Aissue).
- Read [the introduction](https://github.com/rastapasta/mapscii/blob/master/README.md).
- Read [the wiki article](hhttps://wiki.openstreetmap.org/wiki/Mapscii).
================================================
FILE: .github/ISSUE_TEMPLATE/Support.md
================================================
---
name: "\U0001F984 Support MapSCII’s development"
about: I want to support efforts in maintaining this community-driven project
---
--------------^ Click “Preview”!
Developing and maintaining an open source project is a big effort. MapSCII isn’t supported by any big company, and all the contributors are working on it in their free time. We need your help to make it sustainable.
There are many ways you can help:
- Answer questions in [GitHub issues](https://github.com/rastapasta/mapscii/issues).
- Review [pull requests](https://github.com/rastapasta/mapscii/pulls).
- Fix bugs and add new features.
- Write articles and talk about MapSCII on conferences and meetups (we’re always happy to review your texts and slides).
================================================
FILE: .gitignore
================================================
node_modules
bundle*
*.log
tmp
mbtiles
tiles
.DS_Store
================================================
FILE: .npmignore
================================================
node_modules
bundle*
*.log
tmp
mbtiles
tiles
.DS_Store
docs
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "14"
- "16"
- "18"
script:
- npm run-script lint
- npm test
================================================
FILE: AUTHORS
================================================
# This is the list of MapSCII authors for copyright purposes.
#
Michael Straßburger
Christian Paul (https://chrpaul.de)
Jannis R <mail@jannisr.de>
Alexander Zhukov (https://github.com/ZhukovAlexander)
Quincy Morgan (https://github.com/quincylvania)
lennonhill (https://github.com/lennonhill)
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Michael Straßburger
Copyright (c) 2019 The MapSCII authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# MapSCII - The Whole World In Your Console. [](https://travis-ci.com/rastapasta/mapscii)
A node.js based [Vector Tile](http://wiki.openstreetmap.org/wiki/Vector_tiles) to [Braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) and [ASCII](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange) renderer for [xterm](https://en.wikipedia.org/wiki/Xterm)-compatible terminals.
<a href="https://asciinema.org/a/117813?autoplay=1" target="_blank"></a>
## Try it out!
```sh
$ telnet mapscii.me
```
If you're on Windows, use the open source telnet client [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) to connect.
## Features
* Use your mouse to drag and zoom in and out!
* Discover Point-of-Interests around any given location
* Highly customizable layer styling with [Mapbox Styles](https://www.mapbox.com/mapbox-gl-style-spec/) support
* Connect to any public or private vector tile server
* Or just use the supplied and optimized [OSM2VectorTiles](https://github.com/osm2vectortiles) based one
* Work offline and discover local [VectorTile](https://github.com/mapbox/vector-tile-spec)/[MBTiles](https://github.com/mapbox/mbtiles-spec)
* Compatible with most Linux and OSX terminals
* Highly optimized algorithms for a smooth experience
* 100% pure JavaScript! :sunglasses:
## How to run it locally
With a modern node installation available, just start it with
```
npx mapscii
```
## How to install it locally
### With npm
If you haven't already got Node.js >= version 10, then [go get it](http://nodejs.org/).
```
npm install -g mapscii
```
If you're on OSX, or get an error about file permissions, you may need to do ```sudo npm install -g mapscii```
### With snap
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):
sudo snap install mapscii
(This snap is maintained by [@nathanhaines](https://github.com/nathanhaines/))
## Running
This is pretty simple too.
```
mapscii
```
## Keyboard shortcuts
* Arrows **up**, **down**, **left**, **right** to scroll around
* Press **a** or **z** to zoom in and out
* Press **c** to switch to block character mode
* Press **q** to quit
## Mouse control
If your terminal supports mouse events you can drag the map and use your scroll wheel to zoom in and out.
## Behind the scenes
### Libraries
#### Mastering the console
* [`x256`](https://github.com/substack/node-x256) for converting RGB values to closest xterm-256 [color code](https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg)
* [`term-mouse`](https://github.com/CoderPuppy/term-mouse) for mouse handling
* [`keypress`](https://github.com/TooTallNate/keypress) for input handling
* [`string-width`](https://github.com/sindresorhus/string-width) to determine visual string lengths
#### Discovering the map data
* [`vector-tile`](https://github.com/mapbox/vector-tile-js) for [VectorTile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) parsing
* [`pbf`](https://github.com/mapbox/pbf) for [Protobuf](https://developers.google.com/protocol-buffers/) decoding
* [`mbtiles`](https://github.com/mapbox/node-mbtiles) for [MBTiles](https://github.com/mapbox/mbtiles-spec/blob/master/1.2/spec.md) parsing
#### Juggling the vectors and numbers
* [`earcut`](https://github.com/mapbox/earcut) for polygon triangulation
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing of geo and label data
* [`bresenham`](https://github.com/madbence/node-bresenham) for line point calculations
* [`simplify-js`](https://github.com/mourner/simplify-js) for polyline simplifications
#### Handling the flow
* [`node-fetch`](https://github.com/bitinn/node-fetch) for HTTP requests
* [`env-paths`](https://github.com/sindresorhus/env-paths) to determine where to persist downloaded tiles
### TODOs
* MapSCII
* [ ] GeoJSON support via [geojson-vt](https://github.com/mapbox/geojson-vt)
* [ ] CLI support
* [-] startup parameters
* [X] TileSource
* [X] Style
* [X] center position
* [X] zoom
* [ ] demo mode?
* [ ] mouse control
* [ ] hover POIs/labels
* [ ] hover maybe even polygons/-lines?
* Styler
* [ ] respect zoom based style ranges
* Renderer
* [ ] download and process tiles in a different thread ([#3](https://github.com/rastapasta/mapscii/issues/3))
* [ ] optimize renderer for large areas ([#6](https://github.com/rastapasta/mapscii/issues/6))
* [ ] label drawing
* [ ] multi line label?
* TileSource
* [ ] implement single vector-tile handling
## Special thanks
* [lukasmartinelli](https://github.com/lukasmartinelli) & [manuelroth](https://github.com/manuelroth) for all their work on [OSM2VectorTiles](https://github.com/osm2vectortiles) (global vector tiles from [OSM Planet](https://wiki.openstreetmap.org/wiki/Planet.osm))
* [mourner](https://github.com/mourner) for all his work on mindblowing GIS algorithms (like the used [earcut](https://github.com/mapbox/earcut), [rbush](https://github.com/mourner/rbush), [simplify-js](https://github.com/mourner/simplify-js), ..)
## Licenses
### Map data
#### The Open Data Commons Open Database License (oDbl)
[OpenStreetMap](https://www.openstreetmap.org) is open data, licensed under the [Open Data Commons Open Database License](http://opendatacommons.org/licenses/odbl/) (ODbL) by the [OpenStreetMap Foundation](http://osmfoundation.org/) (OSMF).
You are free to copy, distribute, transmit and adapt our data, as long as you credit OpenStreetMap and its contributors. If you alter or build upon our data, you may distribute the result only under the same licence. The full [legal code](http://opendatacommons.org/licenses/odbl/1.0/) explains your rights and responsibilities.
The cartography in our map tiles, and our documentation, are licenced under the [Creative Commons Attribution-ShareAlike 2.0](http://creativecommons.org/licenses/by-sa/2.0/) licence (CC BY-SA).
### MapSCII
* [License](./LICENSE)
* [Authors](./AUTHORS)
================================================
FILE: bin/mapscii.sh
================================================
#!/bin/sh
':' //; # Based on https://github.com/MrRio/vtop/blob/master/bin/vtop.js
':' //; export TERM=xterm-256color
':' //; exec "$(command -v node || command -v nodejs)" "$0" "$@"
'use strict'
require('../main.js');
================================================
FILE: main.js
================================================
/*#
MapSCII - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Discover the planet in your console!
This scripts boots up the application.
TODO: params parsing and so on
#*/
'use strict';
const config = require('./src/config');
const Mapscii = require('./src/Mapscii');
const argv = require('yargs')
.option('latitude', {
alias: 'lat',
description: 'Latitude of initial centre',
default: config.initialLat,
type: 'number',
})
.option('longitude', {
alias: 'lon',
description: 'Longitude of initial centre',
default: config.initialLon,
type: 'number',
})
.option('zoom', {
alias: 'z',
description: 'Initial zoom',
default: config.initialZoom,
type: 'number',
})
.option('width', {
alias: 'w',
description: 'Fixed width of rendering',
type: 'number',
})
.option('height', {
alias: 'h',
description: 'Fixed height of rendering',
type: 'number',
})
.option('braille', {
alias: 'b',
description: 'Activate braille rendering',
default: config.useBraille,
type: 'boolean',
})
.option('headless', {
alias: 'H',
description: 'Activate headless mode',
default: config.headless,
type: 'boolean',
})
.option('tile_source', {
alias: 'tileSource',
description: 'URL or path to osm2vectortiles source',
default: config.source,
type: 'string',
})
.option('style_file', {
alias: 'style',
description: 'path to json style file',
default: config.styleFile,
type: 'string',
})
.strict()
.argv;
const options = {
initialLat: argv.latitude,
initialLon: argv.longitude,
initialZoom: argv.zoom,
size: {
width: argv.width,
height: argv.height
},
useBraille: argv.braille,
headless: argv.headless,
source: argv.tile_source,
styleFile: argv.style_file,
};
const mapscii = new Mapscii(options);
mapscii.init().catch((err) => {
console.error('Failed to start MapSCII.');
console.error(err);
});
================================================
FILE: package.json
================================================
{
"name": "mapscii",
"version": "0.3.1",
"description": "MapSCII is a Braille & ASCII world map renderer for your console, based on OpenStreetMap",
"main": "main.js",
"scripts": {
"lint": "eslint src",
"start": "node main",
"test": "jest"
},
"repository": {
"type": "git",
"url": "https://github.com/rastapasta/mapscii.git"
},
"bin": {
"mapscii": "./bin/mapscii.sh"
},
"engines": {
"node": ">=10"
},
"keywords": [
"map",
"console",
"terminal",
"ascii",
"osm",
"vectortile",
"mbtiles"
],
"author": "Michael Straßburger <codepoet@cpan.org>",
"license": "MIT",
"dependencies": {
"@mapbox/vector-tile": "^1.3.1",
"bresenham": "0.0.4",
"earcut": "^2.2.2",
"env-paths": "^2.2.0",
"keypress": "^0.2.1",
"node-fetch": "^2.6.1",
"pbf": "^3.2.1",
"rbush": "^3.0.1",
"simplify-js": "^1.2.4",
"string-width": "^4.2.0",
"term-mouse": "^0.2.2",
"x256": "0.0.2",
"yargs": "^15.4.1"
},
"devDependencies": {
"eslint": "^7.8.1",
"eslint-plugin-jest": "^24.0.0",
"jest": "^26.4.2"
}
}
================================================
FILE: snap/snapcraft.yaml
================================================
name: mapscii
version: master
summary: The Whole World In Your Console
description: |
A node.js based Vector Tile to Braille and ASCII renderer for
xterm-compatible terminals.
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: strict
apps:
mapscii:
command: mapscii
plugs: [network]
parts:
mapscii:
source: .
plugin: nodejs
================================================
FILE: src/BrailleBuffer.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Simple pixel to braille character mapper
Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)
* added color support
* added text label support
* general optimizations
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');
const asciiMap = {
// '▬': [2+32, 4+64],
// '¯': [1+16],
'▀': [1+2+16+32],
'▄': [4+8+64+128],
'■': [2+4+32+64],
'▌': [1+2+4+8],
'▐': [16+32+64+128],
// '▓': [1+4+32+128, 2+8+16+64],
'█': [255],
};
const termReset = '\x1B[39;49m';
class BrailleBuffer {
constructor(width, height) {
this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];
this.pixelBuffer = null;
this.charBuffer = null;
this.foregroundBuffer = null;
this.backgroundBuffer = null;
this.asciiToBraille = [];
this.globalBackground = null;
this.width = width;
this.height = height;
const size = width*height/8;
this.pixelBuffer = Buffer.alloc(size);
this.foregroundBuffer = Buffer.alloc(size);
this.backgroundBuffer = Buffer.alloc(size);
this._mapBraille();
this.clear();
}
clear() {
this.pixelBuffer.fill(0);
this.charBuffer = [];
this.foregroundBuffer.fill(0);
this.backgroundBuffer.fill(0);
}
setGlobalBackground(background) {
this.globalBackground = background;
}
setBackground(x, y, color) {
if (0 <= x && x < this.width && 0 <= y && y < this.height) {
const idx = this._project(x, y);
this.backgroundBuffer[idx] = color;
}
}
setPixel(x, y, color) {
this._locate(x, y, (idx, mask) => {
this.pixelBuffer[idx] |= mask;
this.foregroundBuffer[idx] = color;
});
}
unsetPixel(x, y) {
this._locate(x, y, (idx, mask) => {
this.pixelBuffer[idx] &= ~mask;
});
}
_project(x, y) {
return (x>>1) + (this.width>>1)*(y>>2);
}
_locate(x, y, cb) {
if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) {
return;
}
const idx = this._project(x, y);
const mask = this.brailleMap[y & 3][x & 1];
return cb(idx, mask);
}
_mapBraille() {
this.asciiToBraille = [' '];
const masks = [];
for (const char in asciiMap) {
const bits = asciiMap[char];
if (!(bits instanceof Array)) continue;
for (const mask of bits) {
masks.push({
mask: mask,
char: char,
});
}
}
//TODO Optimize this part
var i, k;
const results = [];
for (i = k = 1; k <= 255; i = ++k) {
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);
if (!best || best.covered < covered) {
return {
char: mask.char,
covered: covered,
};
} else {
return best;
}
}), void 0).char);
}
return results;
}
_termColor(foreground, background) {
background |= this.globalBackground;
if (foreground && background) {
return `\x1B[38;5;${foreground};48;5;${background}m`;
} else if (foreground) {
return `\x1B[49;38;5;${foreground}m`;
} else if (background) {
return `\x1B[39;48;5;${background}m`;
} else {
return termReset;
}
}
frame() {
const output = [];
let currentColor = null;
let skip = 0;
for (let y = 0; y < this.height/4; y++) {
skip = 0;
for (let x = 0; x < this.width/2; x++) {
const idx = y*this.width/2 + x;
if (idx && !x) {
output.push(config.delimeter);
}
const colorCode = this._termColor(this.foregroundBuffer[idx], this.backgroundBuffer[idx]);
if (currentColor !== colorCode) {
output.push(currentColor = colorCode);
}
const char = this.charBuffer[idx];
if (char) {
skip += stringWidth(char)-1;
if (skip+x < this.width/2) {
output.push(char);
}
} else {
if (!skip) {
if (config.useBraille) {
output.push(String.fromCharCode(0x2800+this.pixelBuffer[idx]));
} else {
output.push(this.asciiToBraille[this.pixelBuffer[idx]]);
}
} else {
skip--;
}
}
}
}
output.push(termReset+config.delimeter);
return output.join('');
}
setChar(char, x, y, color) {
if (0 <= x && x < this.width && 0 <= y && y < this.height) {
const idx = this._project(x, y);
this.charBuffer[idx] = char;
this.foregroundBuffer[idx] = color;
}
}
writeText(text, x, y, color, center = true) {
if (center) {
x -= text.length/2+1;
}
for (let i = 0; i < text.length; i++) {
this.setChar(text.charAt(i), x+i*2, y, color);
}
}
}
module.exports = BrailleBuffer;
================================================
FILE: src/Canvas.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Canvas-like painting abstraction for BrailleBuffer
Implementation inspired by node-drawille-canvas (https://github.com/madbence/node-drawille-canvas)
* added support for filled polygons
* improved text rendering
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');
class Canvas {
constructor(width, height) {
this.width = width;
this.height = height;
this.buffer = new BrailleBuffer(width, height);
}
frame() {
return this.buffer.frame();
}
clear() {
this.buffer.clear();
}
text(text, x, y, color, center = false) {
this.buffer.writeText(text, x, y, color, center);
}
line(from, to, color, width = 1) {
this._line(from.x, from.y, to.x, to.y, color, width);
}
polyline(points, color, width = 1) {
for (let i = 1; i < points.length; i++) {
const x1 = points[i - 1].x;
const y1 = points[i - 1].y;
this._line(x1, y1, points[i].x, points[i].y, width, color);
}
}
setBackground(color) {
this.buffer.setGlobalBackground(color);
}
background(x, y, color) {
this.buffer.setBackground(x, y, color);
}
polygon(rings, color) {
const vertices = [];
const holes = [];
for (const ring of rings) {
if (vertices.length) {
if (ring.length < 3) continue;
holes.push(vertices.length / 2);
} else {
if (ring.length < 3) return false;
}
for (const point of ring) {
vertices.push(point.x);
vertices.push(point.y);
}
}
let triangles;
try {
triangles = earcut(vertices, holes);
} catch (error) {
return false;
}
for (let i = 0; i < triangles.length; i += 3) {
const pa = this._polygonExtract(vertices, triangles[i]);
const pb = this._polygonExtract(vertices, triangles[i + 1]);
const pc = this._polygonExtract(vertices, triangles[i + 2]);
this._filledTriangle(pa, pb, pc, color);
}
return true;
}
_polygonExtract(vertices, pointId) {
return [vertices[pointId * 2], vertices[pointId * 2 + 1]];
}
// Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm"
// -> http://members.chello.at/~easyfilter/bresenham.html
_line(x0, y0, x1, y1, width, color) {
// Fall back to width-less bresenham algorithm if we dont have a width
if (!(width = Math.max(0, width - 1))) {
return bresenham(x0, y0, x1, y1, (x, y) => {
return this.buffer.setPixel(x, y, color);
});
}
const dx = Math.abs(x1 - x0);
const sx = x0 < x1 ? 1 : -1;
const dy = Math.abs(y1 - y0);
const sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
const ed = dx + dy === 0 ? 1 : Math.sqrt(dx * dx + dy * dy);
width = (width + 1) / 2;
/* eslint-disable no-constant-condition */
while (true) {
this.buffer.setPixel(x0, y0, color);
let e2 = err;
let x2 = x0;
if (2 * e2 >= -dx) {
e2 += dy;
let y2 = y0;
while (e2 < ed * width && (y1 !== y2 || dx > dy)) {
this.buffer.setPixel(x0, y2 += sy, color);
e2 += dx;
}
if (x0 === x1) {
break;
}
e2 = err;
err -= dy;
x0 += sx;
}
if (2 * e2 <= dy) {
e2 = dx - e2;
while (e2 < ed * width && (x1 !== x2 || dx < dy)) {
this.buffer.setPixel(x2 += sx, y0, color);
e2 += dy;
}
if (y0 === y1) {
break;
}
err += dx;
y0 += sy;
}
}
/* eslint-enable */
}
_filledRectangle(x, y, width, height, color) {
const pointA = [x, y];
const pointB = [x + width, y];
const pointC = [x, y + height];
const pointD = [x + width, y + height];
this._filledTriangle(pointA, pointB, pointC, color);
this._filledTriangle(pointC, pointB, pointD, color);
}
_bresenham(pointA, pointB) {
return bresenham(pointA[0], pointA[1], pointB[0], pointB[1]);
}
// Draws a filled triangle
_filledTriangle(pointA, pointB, pointC, color) {
const a = this._bresenham(pointB, pointC);
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);
}).sort(function(a, b) {
if (a.y === b.y) {
return a.x - b.x;
} else {
return a.y - b.y;
}
});
for (let i = 0; i < points.length; i++) {
const point = points[i];
const next = points[i * 1 + 1];
if (point.y === (next || {}).y) {
const left = Math.max(0, point.x);
const right = Math.min(this.width - 1, next.x);
if (left >= 0 && right <= this.width) {
for (let x = left; x <= right; x++) {
this.buffer.setPixel(x, point.y, color);
}
}
} else {
this.buffer.setPixel(point.x, point.y, color);
}
if (!next) {
break;
}
}
}
}
Canvas.prototype.stack = [];
module.exports = Canvas;
================================================
FILE: src/LabelBuffer.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
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');
module.exports = class LabelBuffer {
constructor() {
this.tree = new RBush();
this.margin = 5;
}
clear() {
this.tree.clear();
}
project(x, y) {
return [Math.floor(x/2), Math.floor(y/4)];
}
writeIfPossible(text, x, y, feature, margin) {
margin = margin || this.margin;
const point = this.project(x, y);
if (this._hasSpace(text, point[0], point[1])) {
const data = this._calculateArea(text, point[0], point[1], margin);
data.feature = feature;
return this.tree.insert(data);
} else {
return false;
}
}
featuresAt(x, y) {
this.tree.search({minX: x, maxX: x, minY: y, maxY: y});
}
_hasSpace(text, x, y) {
return !this.tree.collides(this._calculateArea(text, x, y));
}
_calculateArea(text, x, y, margin = 0) {
return {
minX: x-margin,
minY: y-margin/2,
maxX: x+margin+stringWidth(text),
maxY: y+margin/2,
};
}
};
================================================
FILE: src/Mapscii.js
================================================
/*
MapSCII - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
UI and central command center
*/
'use strict';
const fs = require('fs');
const keypress = require('keypress');
const TermMouse = require('term-mouse');
const Renderer = require('./Renderer');
const TileSource = require('./TileSource');
const utils = require('./utils');
let config = require('./config');
class Mapscii {
constructor(options) {
this.width = null;
this.height = null;
this.canvas = null;
this.mouse = null;
this.mouseDragging = false;
this.mousePosition = {
x: 0,
y: 0,
};
this.tileSource = null;
this.renderer = null;
this.zoom = 0;
this.minZoom = null;
config = Object.assign(config, options);
this.center = {
lat: config.initialLat,
lon: config.initialLon
};
}
async init() {
if (!config.headless) {
this._initKeyboard();
this._initMouse();
}
this._initTileSource();
this._initRenderer();
this._draw();
this.notify('Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.');
}
_initTileSource() {
this.tileSource = new TileSource();
this.tileSource.init(config.source);
}
_initKeyboard() {
keypress(config.input);
if (config.input.setRawMode) {
config.input.setRawMode(true);
}
config.input.resume();
config.input.on('keypress', (ch, key) => this._onKey(key));
}
_initMouse() {
this.mouse = TermMouse({
input: config.input,
output: config.output,
});
this.mouse.start();
this.mouse.on('click', (event) => this._onClick(event));
this.mouse.on('scroll', (event) => this._onMouseScroll(event));
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);
config.output.on('resize', () => {
this._resizeRenderer();
this._draw();
});
this._resizeRenderer();
this.zoom = (config.initialZoom !== null) ? 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.minZoom = 4-Math.log(4096/this.width)/Math.LN2;
this.renderer.setSize(this.width, this.height);
}
_colrow2ll(x, y) {
const projected = {
x: (x-0.5)*2,
y: (y-0.5)*4,
};
const size = utils.tilesizeAtZoom(this.zoom);
const [dx, dy] = [projected.x-this.width/2, projected.y-this.height/2];
const z = utils.baseZoom(this.zoom);
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));
}
_updateMousePosition(event) {
this.mousePosition = this._colrow2ll(event.x, event.y);
}
_onClick(event) {
if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) {
return;
}
this._updateMousePosition(event);
if (this.mouseDragging && event.button === 'left') {
this.mouseDragging = false;
} else {
this.setCenter(this.mousePosition.lat, this.mousePosition.lon);
}
this._draw();
}
_onMouseScroll(event) {
this._updateMousePosition(event);
// the location of the pointer, where we want to zoom toward
const targetMouseLonLat = this._colrow2ll(event.x, event.y);
// zoom toward the center
this.zoomBy(config.zoomStep * (event.button === 'up' ? 1 : -1));
// the location the pointer ended up after zooming
const offsetMouseLonLat = this._colrow2ll(event.x, event.y);
const z = utils.baseZoom(this.zoom);
// the projected locations
const targetMouseTile = utils.ll2tile(targetMouseLonLat.lon, targetMouseLonLat.lat, z);
const offsetMouseTile = utils.ll2tile(offsetMouseLonLat.lon, offsetMouseLonLat.lat, z);
// the projected center
const centerTile = utils.ll2tile(this.center.lon, this.center.lat, z);
// calculate a new center that puts the pointer back in the target location
const offsetCenterLonLat = utils.tile2ll(
centerTile.x - (offsetMouseTile.x - targetMouseTile.x),
centerTile.y - (offsetMouseTile.y - targetMouseTile.y),
z
);
// move to the new center
this.setCenter(offsetCenterLonLat.lat, offsetCenterLonLat.lon);
this._draw();
}
_onMouseMove(event) {
if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) {
return;
}
if (config.mouseCallback && !config.mouseCallback(event)) {
return;
}
// start dragging
if (event.button === 'left') {
if (this.mouseDragging) {
const dx = (this.mouseDragging.x-event.x)*2;
const dy = (this.mouseDragging.y-event.y)*4;
const size = utils.tilesizeAtZoom(this.zoom);
const newCenter = utils.tile2ll(
this.mouseDragging.center.x+(dx/size),
this.mouseDragging.center.y+(dy/size),
utils.baseZoom(this.zoom)
);
this.setCenter(newCenter.lat, newCenter.lon);
this._draw();
} else {
this.mouseDragging = {
x: event.x,
y: event.y,
center: utils.ll2tile(this.center.lon, this.center.lat, utils.baseZoom(this.zoom)),
};
}
}
this._updateMousePosition(event);
this.notify(this._getFooter());
}
_onKey(key) {
if (config.keyCallback && !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();
} else {
process.exit(0);
}
break;
case 'a':
this.zoomBy(config.zoomStep);
break;
case 'y':
case 'z':
this.zoomBy(-config.zoomStep);
break;
case 'left':
case 'h':
this.moveBy(0, -8/Math.pow(2, this.zoom));
break;
case 'right':
case 'l':
this.moveBy(0, 8/Math.pow(2, this.zoom));
break;
case 'up':
case 'k':
this.moveBy(6/Math.pow(2, this.zoom), 0);
break;
case 'down':
case 'j':
this.moveBy(-6/Math.pow(2, this.zoom), 0);
break;
case 'c':
config.useBraille = !config.useBraille;
break;
default:
draw = false;
}
if (draw) {
this._draw();
}
}
_draw() {
this.renderer.draw(this.center, this.zoom).then((frame) => {
this._write(frame);
this.notify(this._getFooter());
}).catch(() => {
this.notify('renderer is busy');
});
}
_getFooter() {
// tile = utils.ll2tile(this.center.lon, this.center.lat, this.zoom);
// `tile: ${utils.digits(tile.x, 3)}, ${utils.digits(tile.x, 3)} `+
let footer = `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `;
footer += ` zoom: ${utils.digits(this.zoom, 2)} `;
if (this.mousePosition.lat !== undefined) {
footer += ` mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `;
}
return footer;
}
notify(text) {
config.onUpdate && config.onUpdate();
if (!config.headless) {
this._write('\r\x1B[K' + text);
}
}
_write(output) {
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;
}
this.zoom += step;
}
moveBy(lat, lon) {
this.setCenter(this.center.lat+lat, this.center.lon+lon);
}
setCenter(lat, lon) {
this.center = utils.normalize({
lon: lon,
lat: lat,
});
}
}
module.exports = Mapscii;
================================================
FILE: src/Renderer.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
The Console Vector Tile renderer - bäm!
*/
'use strict';
const x256 = require('x256');
const simplify = require('simplify-js');
const Canvas = require('./Canvas');
const LabelBuffer = require('./LabelBuffer');
const Styler = require('./Styler');
const utils = require('./utils');
const config = require('./config');
class Renderer {
constructor(output, tileSource, style) {
this.output = output;
this.tileSource = tileSource;
this.labelBuffer = new LabelBuffer();
this.styler = new Styler(style);
this.tileSource.useStyler(this.styler);
}
setSize(width, height) {
this.width = width;
this.height = height;
this.canvas = new Canvas(width, height);
}
async draw(center, zoom) {
if (this.isDrawing) return Promise.reject();
this.isDrawing = true;
this.labelBuffer.clear();
this._seen = {};
let ref;
const color = ((ref = this.styler.styleById['background']) !== null ?
ref.paint['background-color']
:
void 0
);
if (color) {
this.canvas.setBackground(x256(utils.hex2rgb(color)));
}
this.canvas.clear();
try {
let tiles = this._visibleTiles(center, zoom);
await Promise.all(tiles.map(async(tile) => {
await this._getTile(tile);
this._getTileFeatures(tile, zoom);
}));
await this._renderTiles(tiles);
return this._getFrame();
} catch(e) {
console.error(e);
} finally {
this.isDrawing = false;
this.lastDrawAt = Date.now();
}
}
_visibleTiles(center, zoom) {
const z = utils.baseZoom(zoom);
center = utils.ll2tile(center.lon, center.lat, z);
const tiles = [];
const tileSize = utils.tilesizeAtZoom(zoom);
for (let y = Math.floor(center.y) - 1; y <= Math.floor(center.y) + 1; y++) {
for (let x = Math.floor(center.x) - 1; x <= Math.floor(center.x) + 1; x++) {
const tile = {x, y, z};
const position = {
x: this.width / 2 - (center.x - tile.x) * tileSize,
y: this.height / 2 - (center.y - tile.y) * tileSize,
};
const gridSize = Math.pow(2, z);
tile.x %= gridSize;
if (tile.x < 0) {
tile.x = z === 0 ? 0 : tile.x + gridSize;
}
if (tile.y < 0 || tile.y >= gridSize || position.x + tileSize < 0 || position.y + tileSize < 0 || position.x > this.width || position.y > this.height) {
continue;
}
tiles.push({
xyz: tile,
zoom,
position,
size: tileSize,
});
}
}
return tiles;
}
async _getTile(tile) {
tile.data = await this.tileSource.getTile(tile.xyz.z, tile.xyz.x, tile.xyz.y);
return tile;
}
_getTileFeatures(tile, zoom) {
const position = tile.position;
const layers = {};
const drawOrder = this._generateDrawOrder(zoom);
for (const layerId of drawOrder) {
const layer = (tile.data.layers || {})[layerId];
if (!layer) {
continue;
}
const scale = layer.extent / utils.tilesizeAtZoom(zoom);
layers[layerId] = {
scale: scale,
features: layer.tree.search({
minX: -position.x * scale,
minY: -position.y * scale,
maxX: (this.width - position.x) * scale,
maxY: (this.height - position.y) * scale
}),
};
}
tile.layers = layers;
return tile;
}
_renderTiles(tiles) {
const labels = [];
if (tiles.length === 0) return;
const drawOrder = this._generateDrawOrder(tiles[0].xyz.z);
for (const layerId of drawOrder) {
for (const tile of tiles) {
const layer = tile.layers[layerId];
if (!layer) continue;
for (const feature of layer.features) {
// continue if feature.id and drawn[feature.id]
// drawn[feature.id] = true;
if (layerId.match(/label/)) {
labels.push({
tile,
feature,
scale: layer.scale
});
} else {
this._drawFeature(tile, feature, layer.scale);
}
}
}
}
labels.sort((a, b) => {
return a.feature.sorty - b.feature.sort;
});
for (const label of labels) {
this._drawFeature(label.tile, label.feature, label.scale);
}
}
_getFrame() {
let frame = '';
if (!this.lastDrawAt) {
frame += this.terminal.CLEAR;
}
frame += this.terminal.MOVE;
frame += this.canvas.frame();
return frame;
}
featuresAt(x, y) {
return this.labelBuffer.featuresAt(x, y);
}
_drawFeature(tile, feature, scale) {
let points, placed;
if (feature.style.minzoom && tile.zoom < feature.style.minzoom) {
return false;
} else if (feature.style.maxzoom && tile.zoom > feature.style.maxzoom) {
return false;
}
switch (feature.style.type) {
case 'line': {
let width = feature.style.paint['line-width'];
if (width instanceof Object) {
// TODO: apply the correct zoom based value
width = width.stops[0][1];
}
points = this._scaleAndReduce(tile, feature, feature.points, scale);
if (points.length) {
this.canvas.polyline(points, feature.color, width);
}
break;
}
case 'fill': {
points = feature.points.map((p) => {
return this._scaleAndReduce(tile, feature, p, scale, false);
});
this.canvas.polygon(points, feature.color);
break;
}
case 'symbol': {
const genericSymbol = config.poiMarker;
const text = feature.label || config.poiMarker;
if (this._seen[text] && !genericSymbol) {
return false;
}
placed = false;
const pointsOfInterest = this._scaleAndReduce(tile, feature, feature.points, scale);
for (const point of pointsOfInterest) {
const x = point.x - text.length;
const layerMargin = (config.layers[feature.layer] || {}).margin;
const margin = layerMargin || config.labelMargin;
if (this.labelBuffer.writeIfPossible(text, x, point.y, feature, margin)) {
this.canvas.text(text, x, point.y, feature.color);
placed = true;
break;
} else {
const cluster = (config.layers[feature.layer] || {}).cluster;
if (cluster && this.labelBuffer.writeIfPossible(config.poiMarker, point.x, point.y, feature, 3)) {
this.canvas.text(config.poiMarker, point.x, point.y, feature.color);
placed = true;
break;
}
}
}
if (placed) {
this._seen[text] = true;
}
break;
}
}
return true;
}
_scaleAndReduce(tile, feature, points, scale, filter = true) {
let lastX;
let lastY;
let outside;
const scaled = [];
const minX = -this.tilePadding;
const minY = -this.tilePadding;
const maxX = this.width + this.tilePadding;
const maxY = this.height + this.tilePadding;
for (const point of points) {
const x = Math.floor(tile.position.x + (point.x / scale));
const y = Math.floor(tile.position.y + (point.y / scale));
if (lastX === x && lastY === y) {
continue;
}
lastY = y;
lastX = x;
if (filter) {
if (x < minX || x > maxX || y < minY || y > maxY) {
if (outside) {
continue;
}
outside = true;
} else {
if (outside) {
outside = null;
scaled.push({x: lastX, y: lastY});
}
}
}
scaled.push({x, y});
}
if (feature.style.type !== 'symbol') {
if (scaled.length < 2) {
return [];
}
if (config.simplifyPolylines) {
return simplify(scaled, .5, true);
} else {
return scaled;
}
} else {
return scaled;
}
}
_generateDrawOrder(zoom) {
if (zoom < 2) {
return [
'admin',
'water',
'country_label',
'marine_label',
];
} else {
return [
'landuse',
'water',
'marine_label',
'building',
'road',
'admin',
'country_label',
'state_label',
'water_label',
'place_label',
'rail_station_label',
'poi_label',
'road_label',
'housenum_label',
];
}
}
}
Renderer.prototype.terminal = {
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;
================================================
FILE: src/Styler.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Minimalistic parser and compiler for Mapbox (Studio) Map Style files
See: https://www.mapbox.com/mapbox-gl-style-spec/
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 {
constructor(style) {
this.styleById = {};
this.styleByLayer = {};
var base, name;
this.styleName = style.name;
if (style.constants) {
this._replaceConstants(style.constants, style.layers);
}
for (const layer of style.layers) {
if (layer.ref && this.styleById[layer.ref]) {
for (const ref of ['type', 'source-layer', 'minzoom', 'maxzoom', 'filter']) {
if (this.styleById[layer.ref][ref] && !layer[ref]) {
layer[ref] = this.styleById[layer.ref][ref];
}
}
}
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']].push(layer);
this.styleById[layer.id] = layer;
}
}
getStyleFor(layer, feature) {
if (!this.styleByLayer[layer]) {
return false;
}
for (const style of this.styleByLayer[layer]) {
if (style.appliesTo(feature)) {
return style;
}
}
return false;
}
_replaceConstants(constants, tree) {
for (const id in tree) {
const node = tree[id];
switch (typeof node) {
case 'object':
if (node.constructor.name.match(/Stream/)) {
continue;
}
this._replaceConstants(constants, node);
break;
case 'string':
if (node.charAt(0) === '@') {
tree[id] = constants[node];
}
}
}
}
//TODO Better translation of the long cases.
_compileFilter(filter) {
let filters;
switch (filter != null ? filter[0] : void 0) {
case 'all':
filter = filter.slice(1);
filters = (() => {
return filter.map((sub) => this._compileFilter(sub));
}).call(this);
return (feature) => !!filters.find((appliesTo) => {
return !appliesTo(feature);
});
case 'any':
filter = filter.slice(1);
filters = (() => {
return filter.map((sub) => this._compileFilter(sub));
}).call(this);
return (feature) => !!filters.find((appliesTo) => {
return appliesTo(feature);
});
case 'none':
filter = filter.slice(1);
filters = (() => {
return filter.map((sub) => this._compileFilter(sub));
}).call(this);
return (feature) => !filters.find((appliesTo) => {
return !appliesTo(feature);
});
case '==':
return (feature) => feature.properties[filter[1]] === filter[2];
case '!=':
return (feature) => feature.properties[filter[1]] !== filter[2];
case 'in':
return (feature) => !!filter.slice(2).find((value) => {
return feature.properties[filter[1]] === value;
});
case '!in':
return (feature) => !filter.slice(2).find((value) => {
return feature.properties[filter[1]] === value;
});
case 'has':
return (feature) => !!feature.properties[filter[1]];
case '!has':
return (feature) => !feature.properties[filter[1]];
case '>':
return (feature) => feature.properties[filter[1]] > filter[2];
case '>=':
return (feature) => feature.properties[filter[1]] >= filter[2];
case '<':
return (feature) => feature.properties[filter[1]] < filter[2];
case '<=':
return (feature) => feature.properties[filter[1]] <= filter[2];
default:
return () => true;
}
}
}
module.exports = Styler;
================================================
FILE: src/Tile.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
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');
const config = require('./config');
const utils = require('./utils');
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;
});
}
_loadTile(buffer) {
this.tile = new VectorTile(new Protobuf(buffer));
}
_unzipIfNeeded(buffer) {
return new Promise((resolve, reject) => {
if (this._isGzipped(buffer)) {
zlib.gunzip(buffer, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
} else {
resolve(buffer);
}
});
}
_isGzipped(buffer) {
return buffer.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
}
_loadLayers() {
const layers = {};
const colorCache = {};
for (const name in this.tile.layers) {
const layer = this.tile.layers[name];
const nodes = [];
//continue if name is 'water'
for (let i = 0; i < layer.length; i++) {
// TODO: caching of similar attributes to avoid looking up the style each time
//continue if @styler and not @styler.getStyleFor layer, feature
const feature = layer.feature(i);
feature.properties.$type = [undefined, 'Point', 'LineString', 'Polygon'][feature.type];
let style;
if (this.styler) {
style = this.styler.getStyleFor(name, feature);
if (!style) {
continue;
}
}
let color = (
style.paint['line-color'] ||
style.paint['fill-color'] ||
style.paint['text-color']
);
// TODO: style zoom stops handling
if (color instanceof Object) {
color = color.stops[0][1];
}
const colorCode = colorCache[color] || (colorCache[color] = x256(utils.hex2rgb(color)));
// TODO: monkey patching test case for tiles with a reduced extent 4096 / 8 -> 512
// use feature.loadGeometry() again as soon as we got a 512 extent tileset
const geometries = feature.loadGeometry(); //@_reduceGeometry feature, 8
const sort = feature.properties.localrank || feature.properties.scalerank;
const label = style.type === 'symbol' ? feature.properties['name_' + config.language] || feature.properties.name_en || feature.properties.name || feature.properties.house_num : void 0;
if (style.type === 'fill') {
nodes.push(this._addBoundaries(true, {
// id: feature.id
layer: name,
style,
label,
sort,
points: geometries,
color: colorCode,
}));
} else {
for (const points of geometries) {
nodes.push(this._addBoundaries(false, {
//id: feature.id,
layer: name,
style,
label,
sort,
points,
color: colorCode,
}));
}
}
}
const tree = new RBush(18);
tree.load(nodes);
layers[name] = {
extent: layer.extent,
tree,
};
}
return this.layers = layers;
}
_addBoundaries(deep, data) {
let minX = 2e308;
let maxX = -2e308;
let minY = 2e308;
let maxY = -2e308;
const points = (deep ? data.points[0] : data.points);
for (const p of points) {
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.y < minY) minY = p.y;
if (p.y > maxY) maxY = p.y;
}
data.minX = minX;
data.maxX = maxX;
data.minY = minY;
data.maxY = maxY;
return data;
}
_reduceGeometry(feature, factor) {
const results = [];
const geometries = feature.loadGeometry();
for (const points of geometries) {
const reduced = [];
let last;
for (const point of points) {
const p = {
x: Math.floor(point.x / factor),
y: Math.floor(point.y / factor)
};
if (last && last.x === p.x && last.y === p.y) {
continue;
}
reduced.push(last = p);
}
results.push(reduced);
}
return results;
}
}
Tile.prototype.layers = {};
module.exports = Tile;
================================================
FILE: src/TileSource.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Source for VectorTiles - supports
* 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');
const paths = envPaths('mapscii');
const Tile = require('./Tile');
const config = require('./config');
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCII’s compatibility, MBTiles support must be manually added via
// $> npm install -g @mapbox/mbtiles
let MBTiles = null;
try {
MBTiles = require('@mapbox/mbtiles');
} catch (err) {void 0;}
const modes = {
MBTiles: 1,
VectorTile: 2,
HTTP: 3,
};
class TileSource {
init(source) {
this.source = source;
this.cache = {};
this.cacheSize = 16;
this.cached = [];
this.mode = null;
this.mbtiles = null;
this.styler = null;
if (this.source.startsWith('http')) {
if (config.persistDownloadedTiles) {
this._initPersistence();
}
this.mode = modes.HTTP;
} else if (this.source.endsWith('.mbtiles')) {
if (!MBTiles) {
throw new Error('MBTiles support must be installed with following command: \'npm install -g @mapbox/mbtiles\'');
}
this.mode = modes.MBTiles;
this.loadMBTiles(source);
} else {
throw new Error('source type isn\'t supported yet');
}
}
loadMBTiles(source) {
return new Promise((resolve, reject) => {
new MBTiles(source, (err, mbtiles) => {
if (err) {
reject(err);
}
this.mbtiles = mbtiles;
resolve();
});
});
}
useStyler(styler) {
this.styler = styler;
}
getTile(z, x, y) {
if (!this.mode) {
throw new Error('no TileSource defined');
}
const cached = this.cache[[z, x, y].join('-')];
if (cached) {
return Promise.resolve(cached);
}
if (this.cached.length > this.cacheSize) {
const overflow = Math.abs(this.cacheSize - this.cache.length);
for (const tile in this.cached.splice(0, overflow)) {
delete this.cache[tile];
}
}
switch (this.mode) {
case modes.MBTiles:
return this._getMBTile(z, x, y);
case modes.HTTP:
return this._getHTTP(z, x, y);
}
}
_getHTTP(z, x, y) {
let promise;
const persistedTile = 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) => {
if (config.persistDownloadedTiles) {
this._persistTile(z, x, y, buffer);
return buffer;
}
});
}
return promise.then((buffer) => {
return this._createTile(z, x, y, buffer);
});
}
_getMBTile(z, x, y) {
return new Promise((resolve, reject) => {
this.mbtiles.getTile(z, x, y, (err, buffer) => {
if (err) {
reject(err);
}
resolve(this._createTile(z, x, y, buffer));
});
});
}
_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);
}
_initPersistence() {
try {
this._createFolder(paths.cache);
} catch (error) {
config.persistDownloadedTiles = false;
}
}
_persistTile(z, x, y, buffer) {
const zoom = z.toString();
this._createFolder(path.join(paths.cache, zoom));
const filePath = path.join(paths.cache, zoom, `${x}-${y}.pbf`);
return fs.writeFile(filePath, buffer, () => null);
}
_getPersited(z, x, y) {
try {
return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
} catch (error) {
return false;
}
}
_createFolder(path) {
try {
fs.mkdirSync(path);
return true;
} catch (error) {
if (error.code === 'EEXIST') return true;
throw error;
}
}
}
module.exports = TileSource;
================================================
FILE: src/TileSource.spec.js
================================================
'use strict';
const TileSource = require('./TileSource');
describe('TileSource', () => {
describe('with a HTTP source', () => {
test('sets the mode to 3', async () => {
const tileSource = new TileSource();
await tileSource.init('http://mapscii.me/');
expect(tileSource.mode).toBe(3);
});
});
});
================================================
FILE: src/config.js
================================================
module.exports = {
language: 'en',
// TODO: adapt to osm2vectortiles successor openmaptiles v3)
// mapscii.me hosts the last available version, 2016-06-20
source: 'http://mapscii.me/',
//source: __dirname+"/../mbtiles/regensburg.mbtiles",
styleFile: __dirname+'/../styles/dark.json',
initialZoom: null,
maxZoom: 18,
zoomStep: 0.2,
// sf lat: 37.787946, lon: -122.407522
// iceland lat: 64.124229, lon: -21.811552
// rgbg
// lat: 49.019493, lon: 12.098341
initialLat: 52.51298,
initialLon: 13.42012,
simplifyPolylines: false,
useBraille: true,
// Downloaded files get persisted in ~/.mapscii
persistDownloadedTiles: true,
tileRange: 14,
projectSize: 256,
labelMargin: 5,
layers: {
housenum_label: {
margin: 4
},
poi_label: {
cluster: true,
margin: 5,
},
place_label: {
cluster: true,
},
state_label: {
cluster: true,
},
},
input: process.stdin,
output: process.stdout,
headless: false,
delimeter: '\n\r',
poiMarker: '◉',
};
================================================
FILE: src/utils.js
================================================
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
methods used all around
*/
'use strict';
const config = require('./config');
const constants = {
RADIUS: 6378137,
};
const utils = {
clamp: (num, min, max) => {
if (num <= min) {
return min;
} else if (num >= max) {
return max;
} else {
return num;
}
},
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));
},
deg2rad: (angle) => {
// (angle / 180) * Math.PI
return angle * 0.017453292519943295;
},
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) => {
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) => {
return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom));
},
hex2rgb: (color) => {
if (typeof color !== 'string') return [255, 0, 0];
if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) {
throw new Error(`${color} isn't a supported hex color`);
}
color = color.substr(1);
const decimal = parseInt(color, 16);
if (color.length === 3) {
const rgb = [decimal>>8, (decimal>>4)&15, decimal&15];
return rgb.map((c) => {
return c + (c<<4);
});
} 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);
},
normalize: (ll) => {
if (ll.lon < -180) ll.lon += 360;
if (ll.lon > 180) ll.lon -= 360;
if (ll.lat > 85.0511) ll.lat = 85.0511;
if (ll.lat < -85.0511) ll.lat = -85.0511;
return ll;
},
population: (val) => {
let bits = 0;
while (val > 0) {
bits += val & 1;
val >>= 1;
}
return bits;
},
};
module.exports = utils;
================================================
FILE: src/utils.spec.js
================================================
'use strict';
const utils = require('./utils');
describe('utils', () => {
describe('hex2rgb', () => {
describe.each([
['#ff0000', 255, 0, 0],
['#ffff00', 255, 255, 0],
['#0000ff', 0, 0, 255],
['#112233', 17, 34, 51],
['#888', 136, 136, 136],
])('when given "%s"', (input, r, g, b) => {
test(`returns [${r},${g},${b}]`, () => {
expect(utils.hex2rgb(input)).toEqual([r, g, b]);
});
});
test('throws an Error when given "33"', () => {
function wrapper() {
utils.hex2rgb('33');
}
expect(wrapper).toThrow('isn\'t a supported hex color');
});
});
});
describe('normalize', () => {
describe.each([
[0, 0, 0, 0],
[61, 48, 61, 48],
[-61, -48, -61, -48],
[181, 85.06, -179, 85.0511],
[-181, -85.06, 179, -85.0511],
])('when given lon=%f and lat=%f', (lon, lat, expected_lon, expected_lat) => {
const input = {
lon,
lat,
};
test(`returns lon=${expected_lon} and lat=${expected_lat}`, () => {
const expected = {
lon: expected_lon,
lat: expected_lat,
};
expect(utils.normalize(input)).toEqual(expected);
});
});
});
================================================
FILE: styles/bright.json
================================================
{
"name": "Bright",
"constants": {
"@background": "#000",
"@water": "#5f87ff",
"@building": "#99b",
"@housenum_label": "#88a",
"@country_label_1": "#ff0",
"@country_label_2": "#ff0",
"@country_label_3": "#ff0",
"@country_label_4": "#ff0",
"@admin_level_2": "#fff",
"@admin_level_3": "#aac",
"@admin_level_4": "#777",
"@poi_label_1": "#ff0",
"@poi_label_2": "#ee0",
"@poi_label_3": "#cc0",
"@poi_label_4": "#aa0",
"@place_label_city": "#f00",
"@place_label_town": "#d33",
"@place_label_village": "#c33",
"@place_label_other": "#b33"
},
"layers": [
{
"type": "background",
"id": "background",
"paint": {
"background-color": "@background"
}
},
{
"type": "fill",
"id": "landuse_hospital",
"paint": {
"fill-color": "#fde"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"hospital"
]
},
{
"type": "line",
"id": "waterway",
"paint": {
"line-color": "#303090",
"line-width": {
"base": 1.3,
"stops": [
[
13,
0.5
],
[
20,
2
]
]
}
},
"source-layer": "waterway",
"filter": [
"all",
[
"!=",
"class",
"river"
],
[
"!=",
"class",
"stream"
],
[
"!=",
"class",
"canal"
]
]
},
{
"type": "line",
"id": "waterway_river",
"paint": {
"line-color": "#303090",
"line-width": {
"base": 1.2,
"stops": [
[
11,
0.5
],
[
20,
6
]
]
}
},
"source-layer": "waterway",
"filter": [
"==",
"class",
"river"
]
},
{
"type": "line",
"id": "waterway_stream_canal",
"paint": {
"line-color": "#a0c8f0",
"line-width": {
"base": 1.3,
"stops": [
[
13,
0.5
],
[
20,
6
]
]
}
},
"source-layer": "waterway",
"filter": [
"in",
"class",
"stream",
"canal"
]
},
{
"type": "fill",
"id": "water",
"paint": {
"fill-color": "@water"
},
"source-layer": "water"
},
{
"type": "fill",
"id": "aeroway_fill",
"paint": {
"fill-color": "#f0ede9"
},
"source-layer": "aeroway",
"filter": [
"==",
"$type",
"Polygon"
],
"minzoom": 11
},
{
"type": "line",
"id": "aeroway_runway",
"paint": {
"line-color": "#f0ede9",
"line-width": {
"base": 1.2,
"stops": [
[
11,
3
],
[
20,
16
]
]
}
},
"source-layer": "aeroway",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"type",
"runway"
]
],
"minzoom": 11
},
{
"type": "line",
"id": "aeroway_taxiway",
"paint": {
"line-color": "#f0ede9",
"line-width": {
"base": 1.2,
"stops": [
[
11,
0.5
],
[
20,
6
]
]
}
},
"source-layer": "aeroway",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"type",
"taxiway"
]
],
"minzoom": 11
},
{
"type": "line",
"id": "building",
"paint": {
"line-color": "@building"
},
"source-layer": "building",
"minzoom": 14.5
},
{
"type": "line",
"id": "tunnel_motorway_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"motorway_link"
]
]
},
{
"type": "line",
"id": "tunnel_service_track_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1
],
[
16,
4
],
[
20,
11
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"service",
"track"
]
]
},
{
"type": "line",
"id": "tunnel_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"link"
]
]
},
{
"type": "line",
"id": "tunnel_street_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
12,
0.5
],
[
13,
1
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"street",
"street_limited"
]
]
},
{
"type": "line",
"id": "tunnel_secondary_tertiary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
8,
1.5
],
[
20,
17
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"secondary",
"tertiary"
]
]
},
{
"type": "line",
"id": "tunnel_trunk_primary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"trunk",
"primary"
]
]
},
{
"type": "line",
"id": "tunnel_motorway_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"motorway"
]
]
},
{
"type": "line",
"id": "tunnel_path_pedestrian",
"paint": {
"line-color": "#cba",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1.2
],
[
20,
4
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"path",
"pedestrian"
]
]
]
},
{
"type": "line",
"id": "tunnel_major_rail",
"paint": {
"line-color": "#bbb",
"line-width": {
"base": 1.4,
"stops": [
[
14,
0.4
],
[
15,
0.75
],
[
20,
2
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"major_rail",
"minor_rail"
]
]
},
{
"type": "line",
"id": "road_motorway_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"motorway_link"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
],
"minzoom": 12
},
{
"type": "line",
"id": "road_service_track_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1
],
[
16,
4
],
[
20,
11
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"service",
"track"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"link"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
],
"minzoom": 13
},
{
"type": "line",
"id": "road_street_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
12,
0.5
],
[
13,
1
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"in",
"class",
"street",
"street_limited"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
]
},
{
"type": "line",
"id": "road_secondary_tertiary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
8,
1.5
],
[
20,
17
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"secondary",
"tertiary"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_trunk_primary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"trunk",
"primary"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_motorway_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"motorway"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
],
"minzoom": 5
},
{
"type": "line",
"id": "road_path_pedestrian",
"paint": {
"line-color": "#cba",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1.2
],
[
20,
4
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"in",
"class",
"path",
"pedestrian"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
]
},
{
"type": "line",
"id": "road_major_rail",
"paint": {
"line-color": "#bbb",
"line-width": {
"base": 1.4,
"stops": [
[
14,
0.4
],
[
15,
0.75
],
[
20,
2
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"major_rail"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "bridge_motorway_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"motorway_link"
]
]
},
{
"type": "line",
"id": "bridge_service_track_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1
],
[
16,
4
],
[
20,
11
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"service",
"track"
]
]
},
{
"type": "line",
"id": "bridge_link_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
12,
1
],
[
13,
3
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"link"
]
]
},
{
"type": "line",
"id": "bridge_street_casing",
"paint": {
"line-color": "#cfcdca",
"line-width": {
"base": 1.2,
"stops": [
[
12,
0.5
],
[
13,
1
],
[
14,
4
],
[
20,
15
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"street",
"street_limited"
]
]
},
{
"type": "line",
"id": "bridge_secondary_tertiary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
8,
1.5
],
[
20,
17
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"secondary",
"tertiary"
]
]
},
{
"type": "line",
"id": "bridge_trunk_primary_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"trunk",
"primary"
]
]
},
{
"type": "line",
"id": "bridge_motorway_casing",
"paint": {
"line-color": "#e9ac77",
"line-width": {
"base": 1.2,
"stops": [
[
5,
0.4
],
[
6,
0.6
],
[
7,
1.5
],
[
20,
22
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"motorway"
]
]
},
{
"type": "line",
"id": "bridge_path_pedestrian",
"paint": {
"line-color": "#cba",
"line-width": {
"base": 1.2,
"stops": [
[
15,
1.2
],
[
20,
4
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"path",
"pedestrian"
]
]
]
},
{
"type": "line",
"id": "bridge_major_rail",
"paint": {
"line-color": "#bbb",
"line-width": {
"base": 1.4,
"stops": [
[
14,
0.4
],
[
15,
0.75
],
[
20,
2
]
]
}
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"major_rail"
]
]
},
{
"type": "line",
"id": "admin_level_4",
"paint": {
"line-color": "@admin_level_4",
"line-width": 1
},
"source-layer": "admin",
"filter": [
"all",
[
">=",
"admin_level",
4
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_3",
"paint": {
"line-color": "@admin_level_3",
"line-width": 1
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
3
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_2",
"paint": {
"line-color": "@admin_level_2",
"line-width": {
"base": 1,
"stops": [
[
4,
1.4
],
[
5,
2
],
[
12,
8
]
]
}
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
2
],
[
"==",
"disputed",
0
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_2_disputed",
"paint": {
"line-color": "#9e9cab",
"line-width": {
"base": 1,
"stops": [
[
4,
1.4
],
[
5,
2
],
[
12,
8
]
]
}
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
2
],
[
"==",
"disputed",
1
],
[
"==",
"maritime",
0
]
]
},
{
"type": "symbol",
"id": "water_label",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "water_label",
"filter": [
"==",
"$type",
"Point"
],
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "poi_label_4",
"paint": {
"text-color": "@poi_label_4"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
4
]
],
"minzoom": 16,
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "poi_label_3",
"paint": {
"text-color": "@poi_label_3"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
3
]
],
"minzoom": 15,
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "poi_label_2",
"paint": {
"text-color": "@poi_label_2"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
2
]
],
"minzoom": 14,
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "rail_station_label",
"paint": {
"text-color": "#666"
},
"source-layer": "rail_station_label",
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "poi_label_1",
"paint": {
"text-color": "@poi_label_1"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
1
]
],
"minzoom": 13,
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "airport_label",
"paint": {
"text-color": "#666"
},
"source-layer": "airport_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"in",
"scalerank",
1,
2,
3
]
],
"minzoom": 11,
"layout": {
"text-size": 12
}
},
{
"type": "symbol",
"id": "road_label",
"paint": {
"text-color": "#765"
},
"source-layer": "road_label",
"filter": [
"!=",
"class",
"ferry"
],
"layout": {
"text-size": {
"base": 1,
"stops": [
[
13,
12
],
[
14,
13
]
]
}
}
},
{
"type": "symbol",
"id": "housenum_label",
"paint": {
"text-color": "@housenum_label"
},
"minzoom": 16.5,
"source-layer": "housenum_label"
},
{
"type": "symbol",
"id": "place_label_other",
"paint": {
"text-color": "@place_label_other"
},
"source-layer": "place_label",
"filter": [
"in",
"type",
"hamlet",
"suburb",
"neighbourhood",
"island",
"islet"
],
"layout": {
"text-size": {
"base": 1.2,
"stops": [
[
12,
10
],
[
15,
14
]
]
}
}
},
{
"type": "symbol",
"id": "place_label_village",
"paint": {
"text-color": "@place_label_village"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"village"
],
"layout": {
"text-size": {
"base": 1.2,
"stops": [
[
10,
12
],
[
15,
22
]
]
}
}
},
{
"type": "symbol",
"id": "place_label_town",
"paint": {
"text-color": "@place_label_town"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"town"
],
"layout": {
"text-size": {
"base": 1.2,
"stops": [
[
10,
14
],
[
15,
24
]
]
}
}
},
{
"type": "symbol",
"id": "place_label_city",
"paint": {
"text-color": "@place_label_city"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"city"
],
"layout": {
"text-size": {
"base": 1.2,
"stops": [
[
7,
14
],
[
11,
24
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_line_4",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
">=",
"labelrank",
4
]
],
"layout": {
"text-size": {
"stops": [
[
3,
11
],
[
4,
12
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_4",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
">=",
"labelrank",
4
]
],
"layout": {
"text-size": {
"stops": [
[
3,
11
],
[
4,
12
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_line_3",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
3
]
],
"layout": {
"text-size": {
"stops": [
[
3,
11
],
[
4,
14
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_point_3",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
3
]
],
"layout": {
"text-size": {
"stops": [
[
3,
11
],
[
4,
14
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_line_2",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
2
]
],
"layout": {
"text-size": {
"stops": [
[
3,
14
],
[
4,
16
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_point_2",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
2
]
],
"layout": {
"text-size": {
"stops": [
[
3,
14
],
[
4,
16
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_line_1",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
1
]
],
"layout": {
"text-size": {
"stops": [
[
3,
18
],
[
4,
22
]
]
}
}
},
{
"type": "symbol",
"id": "marine_label_point_1",
"paint": {
"text-color": "#74aee9"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
1
]
],
"layout": {
"text-size": {
"stops": [
[
3,
18
],
[
4,
22
]
]
}
}
},
{
"type": "symbol",
"id": "country_label_4",
"paint": {
"text-color": "@country_label_4"
},
"source-layer": "country_label",
"filter": [
">=",
"scalerank",
4
],
"layout": {
"text-size": {
"stops": [
[
4,
11
],
[
6,
15
]
]
}
}
},
{
"type": "symbol",
"id": "country_label_3",
"paint": {
"text-color": "@country_label_3"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
3
],
"layout": {
"text-size": {
"stops": [
[
3,
11
],
[
7,
17
]
]
}
}
},
{
"type": "symbol",
"id": "country_label_2",
"paint": {
"text-color": "@country_label_2"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
2
],
"layout": {
"text-size": {
"stops": [
[
2,
11
],
[
5,
17
]
]
}
}
},
{
"type": "symbol",
"id": "country_label_1",
"paint": {
"text-color": "@country_label_1"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
1
],
"layout": {
"text-size": {
"stops": [
[
1,
11
],
[
4,
17
]
]
}
}
}
]
}
================================================
FILE: styles/dark.json
================================================
{
"name": "dark",
"constants": {
"@admin_level_2": "#fff",
"@admin_level_2_disputed": "#fff",
"@admin_level_2_maritime": "#9bf",
"@admin_level_3": "#aac",
"@admin_level_3_maritime": "#8af",
"@admin_level_4": "#777",
"@aeroway_fill": "#f0ede9",
"@aeroway_runway": "#f0ede9",
"@aeroway_taxiway": "#f0ede9",
"@airport_label": "#666",
"@background": "#000",
"@bridge_link": "#fea",
"@bridge_major_rail": "#bbb",
"@bridge_major_rail_hatching": "#bbb",
"@bridge_motorway": "#fc8",
"@bridge_motorway_link": "#fc8",
"@bridge_path_pedestrian": "#cba",
"@bridge_secondary_tertiary": "#fea",
"@bridge_service_track": "#fff",
"@bridge_street": "#fff",
"@bridge_trunk_primary": "#fea",
"@building": "#99b",
"@country_label_1": "#ff0",
"@country_label_2": "#ff0",
"@country_label_3": "#ff0",
"@country_label_4": "#ff0",
"@landuse_cemetery": "#e0e4dd",
"@landuse_hospital": "#d9b",
"@landuse_overlay_national_park": "#d8e8c8",
"@landuse_park": "#7b5",
"@landuse_school": "#f0e8f8",
"@landuse_wood": "#6a4",
"@marine_label_4": "#74aee9",
"@marine_label_line_1": "#74aee9",
"@marine_label_line_2": "#74aee9",
"@marine_label_line_3": "#74aee9",
"@marine_label_line_4": "#74aee9",
"@marine_label_point_1": "#74aee9",
"@marine_label_point_2": "#74aee9",
"@marine_label_point_3": "#74aee9",
"@place_label_city": "#f00",
"@place_label_town": "#d33",
"@place_label_village": "#c33",
"@place_label_other": "#b33",
"@poi_label_1": "#ff0",
"@poi_label_2": "#ee0",
"@poi_label_3": "#cc0",
"@poi_label_4": "#aa0",
"@rail_station_label": "#666",
"@road_label": "#765",
"@road_link": "#fea",
"@road_major_rail": "#bbb",
"@road_major_rail_hatching": "#bbb",
"@road_motorway": "#fc8",
"@road_motorway_link": "#fc8",
"@road_path_pedestrian": "#cba",
"@road_secondary_tertiary": "#fea",
"@road_service_track": "#fff",
"@road_street": "#fff",
"@road_trunk_primary": "#fea",
"@tunnel_link": "#fff4c6",
"@tunnel_major_rail": "#bbb",
"@tunnel_major_rail_hatching": "#bbb",
"@tunnel_motorway": "#ffdaa6",
"@tunnel_motorway_link": "#fc8",
"@tunnel_path_pedestrian": "#cba",
"@tunnel_secondary_tertiary": "#fff4c6",
"@tunnel_service_track": "#fff",
"@tunnel_street": "#fff",
"@tunnel_trunk_primary": "#fff4c6",
"@water": "#5f87ff",
"@water_label": "#74aee9",
"@waterway": "#a0c8f0",
"@waterway_river": "#a0c8f0",
"@waterway_stream_canal": "#a0c8f0"
},
"layers": [
{
"type": "background",
"id": "background",
"paint": {
"background-color": "@background"
}
},
{
"type": "fill",
"id": "landuse_overlay_national_park",
"paint": {
"fill-color": "@landuse_overlay_national_park"
},
"source-layer": "landuse_overlay",
"filter": [
"==",
"class",
"national_park"
]
},
{
"type": "fill",
"id": "landuse_park",
"paint": {
"fill-color": "@landuse_park"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"park"
]
},
{
"type": "line",
"id": "landuse_cemetery",
"paint": {
"fill-color": "@landuse_cemetery"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"cemetery"
]
},
{
"type": "line",
"id": "landuse_hospital",
"paint": {
"line-color": "@landuse_hospital"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"hospital"
]
},
{
"type": "line",
"id": "landuse_school",
"paint": {
"line-color": "@landuse_school"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"school"
]
},
{
"type": "line",
"id": "landuse_wood",
"paint": {
"line-color": "@landuse_wood"
},
"source-layer": "landuse",
"filter": [
"==",
"class",
"wood"
]
},
{
"type": "line",
"id": "waterway",
"paint": {
"line-color": "@waterway"
},
"source-layer": "waterway",
"filter": [
"all",
[
"!=",
"class",
"river"
],
[
"!=",
"class",
"stream"
],
[
"!=",
"class",
"canal"
]
]
},
{
"type": "line",
"id": "waterway_river",
"paint": {
"line-color": "@waterway_river"
},
"source-layer": "waterway",
"filter": [
"==",
"class",
"river"
]
},
{
"type": "line",
"id": "waterway_stream_canal",
"paint": {
"line-color": "@waterway_stream_canal"
},
"source-layer": "waterway",
"filter": [
"in",
"class",
"stream",
"canal"
]
},
{
"type": "fill",
"id": "water",
"paint": {
"fill-color": "@water"
},
"source-layer": "water"
},
{
"type": "fill",
"id": "aeroway_fill",
"paint": {
"fill-color": "@aeroway_fill"
},
"source-layer": "aeroway",
"filter": [
"==",
"$type",
"Polygon"
],
"minzoom": 11
},
{
"type": "line",
"id": "aeroway_runway",
"paint": {
"line-color": "@aeroway_runway"
},
"source-layer": "aeroway",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"type",
"runway"
]
],
"minzoom": 11
},
{
"type": "line",
"id": "aeroway_taxiway",
"paint": {
"line-color": "@aeroway_taxiway"
},
"source-layer": "aeroway",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"type",
"taxiway"
]
],
"minzoom": 11
},
{
"type": "line",
"id": "building",
"paint": {
"line-color": "@building"
},
"source-layer": "building"
},
{
"type": "line",
"id": "tunnel_path_pedestrian",
"paint": {
"line-color": "@tunnel_path_pedestrian"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"path",
"pedestrian"
]
]
]
},
{
"type": "line",
"id": "tunnel_motorway_link",
"paint": {
"line-color": "@tunnel_motorway_link"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"motorway_link"
]
]
},
{
"type": "line",
"id": "tunnel_service_track",
"paint": {
"line-color": "@tunnel_service_track"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"service",
"track"
]
]
},
{
"type": "line",
"id": "tunnel_link",
"paint": {
"line-color": "@tunnel_link"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"link"
]
]
},
{
"type": "line",
"id": "tunnel_street",
"paint": {
"line-color": "@tunnel_street"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"street",
"street_limited"
]
]
},
{
"type": "line",
"id": "tunnel_secondary_tertiary",
"paint": {
"line-color": "@tunnel_secondary_tertiary"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"secondary",
"tertiary"
]
]
},
{
"type": "line",
"id": "tunnel_trunk_primary",
"paint": {
"line-color": "@tunnel_trunk_primary"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"trunk",
"primary"
]
]
},
{
"type": "line",
"id": "tunnel_motorway",
"paint": {
"line-color": "@tunnel_motorway"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"==",
"class",
"motorway"
]
]
},
{
"type": "line",
"id": "tunnel_major_rail",
"paint": {
"line-color": "@tunnel_major_rail"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"major_rail",
"minor_rail"
]
]
},
{
"type": "line",
"id": "tunnel_major_rail_hatching",
"paint": {
"line-color": "@tunnel_major_rail_hatching"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"tunnel"
],
[
"in",
"class",
"major_rail",
"minor_rail"
]
]
},
{
"type": "line",
"id": "road_path_pedestrian",
"paint": {
"line-color": "@road_path_pedestrian"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"in",
"class",
"path",
"pedestrian"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
]
},
{
"type": "line",
"id": "road_motorway_link",
"paint": {
"line-color": "@road_motorway_link"
},
"source-layer": "road",
"minzoom": 12,
"filter": [
"all",
[
"==",
"class",
"motorway_link"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_service_track",
"paint": {
"line-color": "@road_service_track"
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"service",
"track"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_link",
"paint": {
"line-color": "@road_link"
},
"source-layer": "road",
"minzoom": 13,
"filter": [
"all",
[
"==",
"class",
"link"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_street",
"paint": {
"line-color": "@road_street"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"in",
"class",
"street",
"street_limited"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
]
},
{
"type": "line",
"id": "road_secondary_tertiary",
"paint": {
"line-color": "@road_secondary_tertiary"
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"secondary",
"tertiary"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_trunk_primary",
"paint": {
"line-color": "@road_trunk_primary"
},
"source-layer": "road",
"filter": [
"all",
[
"in",
"class",
"trunk",
"primary"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_motorway",
"paint": {
"line-color": "@road_motorway"
},
"source-layer": "road",
"minzoom": 5,
"filter": [
"all",
[
"==",
"class",
"motorway"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_major_rail",
"paint": {
"line-color": "@road_major_rail"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"major_rail"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "road_major_rail_hatching",
"paint": {
"line-color": "@road_major_rail_hatching"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"class",
"major_rail"
],
[
"!in",
"structure",
"bridge",
"tunnel"
]
]
},
{
"type": "line",
"id": "bridge_path_pedestrian",
"paint": {
"line-color": "@bridge_path_pedestrian"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"path",
"pedestrian"
]
]
]
},
{
"type": "line",
"id": "bridge_motorway_link",
"paint": {
"line-color": "@bridge_motorway_link"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"motorway_link"
]
]
},
{
"type": "line",
"id": "bridge_service_track",
"paint": {
"line-color": "@bridge_service_track"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"service",
"track"
]
]
},
{
"type": "line",
"id": "bridge_link",
"paint": {
"line-color": "@bridge_link"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"link"
]
]
},
{
"type": "line",
"id": "bridge_street",
"paint": {
"line-color": "@bridge_street"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"street",
"street_limited"
]
]
},
{
"type": "line",
"id": "bridge_secondary_tertiary",
"paint": {
"line-color": "@bridge_secondary_tertiary"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"secondary",
"tertiary"
]
]
},
{
"type": "line",
"id": "bridge_trunk_primary",
"paint": {
"line-color": "@bridge_trunk_primary"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"in",
"class",
"trunk",
"primary"
]
]
},
{
"type": "line",
"id": "bridge_motorway",
"paint": {
"line-color": "@bridge_motorway"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"motorway"
]
]
},
{
"type": "line",
"id": "bridge_major_rail",
"paint": {
"line-color": "@bridge_major_rail"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"major_rail"
]
]
},
{
"type": "line",
"id": "bridge_major_rail_hatching",
"paint": {
"line-color": "@bridge_major_rail_hatching"
},
"source-layer": "road",
"filter": [
"all",
[
"==",
"structure",
"bridge"
],
[
"==",
"class",
"major_rail"
]
]
},
{
"type": "line",
"id": "admin_level_3",
"paint": {
"line-color": "@admin_level_3"
},
"source-layer": "admin",
"filter": [
"all",
[
">=",
"admin_level",
4
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_3",
"paint": {
"line-color": "@admin_level_3"
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
3
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_2",
"paint": {
"line-color": "@admin_level_2"
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
2
],
[
"==",
"disputed",
0
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_2_disputed",
"paint": {
"line-color": "@admin_level_2_disputed"
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
2
],
[
"==",
"disputed",
1
],
[
"==",
"maritime",
0
]
]
},
{
"type": "line",
"id": "admin_level_3_maritime",
"paint": {
"line-color": "@admin_level_3_maritime"
},
"source-layer": "admin",
"filter": [
"all",
[
">=",
"admin_level",
3
],
[
"==",
"maritime",
1
]
]
},
{
"type": "line",
"id": "admin_level_2_maritime",
"paint": {
"line-color": "@admin_level_2_maritime"
},
"source-layer": "admin",
"filter": [
"all",
[
"==",
"admin_level",
2
],
[
"==",
"maritime",
1
]
]
},
{
"type": "symbol",
"id": "water_label",
"paint": {
"text-color": "@water_label"
},
"source-layer": "water_label",
"filter": [
"==",
"$type",
"Point"
]
},
{
"type": "symbol",
"id": "poi_label_4",
"paint": {
"text-color": "@poi_label_4"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
4
]
],
"minzoom": 16
},
{
"type": "symbol",
"id": "poi_label_3",
"paint": {
"text-color": "@poi_label_3"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
3
]
],
"minzoom": 15
},
{
"type": "symbol",
"id": "poi_label_2",
"paint": {
"text-color": "@poi_label_2"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
2
]
],
"minzoom": 14
},
{
"type": "symbol",
"id": "rail_station_label",
"paint": {
"text-color": "@rail_station_label"
},
"source-layer": "rail_station_label"
},
{
"type": "symbol",
"id": "poi_label_1",
"paint": {
"text-color": "@poi_label_1"
},
"source-layer": "poi_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"scalerank",
1
]
],
"minzoom": 13
},
{
"type": "symbol",
"id": "airport_label",
"paint": {
"text-color": "@airport_label"
},
"source-layer": "airport_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"in",
"scalerank",
1,
2,
3
]
],
"minzoom": 11
},
{
"type": "symbol",
"id": "road_label",
"paint": {
"text-color": "@road_label"
},
"source-layer": "road_label",
"filter": [
"!=",
"class",
"ferry"
],
"minzoom": 15.5
},
{
"type": "symbol",
"id": "place_label_other",
"paint": {
"text-color": "@place_label_other"
},
"source-layer": "place_label",
"filter": [
"in",
"type",
"hamlet",
"suburb",
"neighbourhood",
"island",
"islet"
]
},
{
"type": "symbol",
"id": "place_label_village",
"paint": {
"text-color": "@place_label_village"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"village"
]
},
{
"type": "symbol",
"id": "place_label_town",
"paint": {
"text-color": "@place_label_town"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"town"
]
},
{
"type": "symbol",
"id": "place_label_city",
"paint": {
"text-color": "@place_label_city"
},
"source-layer": "place_label",
"filter": [
"==",
"type",
"city"
]
},
{
"type": "symbol",
"id": "marine_label_line_4",
"paint": {
"text-color": "@marine_label_line_4"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
">=",
"labelrank",
4
]
]
},
{
"type": "symbol",
"id": "marine_label_4",
"paint": {
"text-color": "@marine_label_4"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
">=",
"labelrank",
4
]
]
},
{
"type": "symbol",
"id": "marine_label_line_3",
"paint": {
"text-color": "@marine_label_line_3"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
3
]
]
},
{
"type": "symbol",
"id": "marine_label_point_3",
"paint": {
"text-color": "@marine_label_point_3"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
3
]
]
},
{
"type": "symbol",
"id": "marine_label_line_2",
"paint": {
"text-color": "@marine_label_line_2"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
2
]
]
},
{
"type": "symbol",
"id": "marine_label_point_2",
"paint": {
"text-color": "@marine_label_point_2"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
2
]
]
},
{
"type": "symbol",
"id": "marine_label_line_1",
"paint": {
"text-color": "@marine_label_line_1"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"LineString"
],
[
"==",
"labelrank",
1
]
]
},
{
"type": "symbol",
"id": "marine_label_point_1",
"paint": {
"text-color": "@marine_label_point_1"
},
"source-layer": "marine_label",
"filter": [
"all",
[
"==",
"$type",
"Point"
],
[
"==",
"labelrank",
1
]
]
},
{
"type": "symbol",
"id": "country_label_4",
"paint": {
"text-color": "@country_label_4"
},
"source-layer": "country_label",
"filter": [
">=",
"scalerank",
4
]
},
{
"type": "symbol",
"id": "country_label_3",
"paint": {
"text-color": "@country_label_3"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
3
]
},
{
"type": "symbol",
"id": "country_label_2",
"paint": {
"text-color": "@country_label_2"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
2
]
},
{
"type": "symbol",
"id": "country_label_1",
"paint": {
"text-color": "@country_label_1"
},
"source-layer": "country_label",
"filter": [
"==",
"scalerank",
1
]
}
]
}
gitextract_o17myi0p/
├── .eslintrc.js
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── BugReport.md
│ ├── FeatureRequest.md
│ ├── Question.md
│ └── Support.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── AUTHORS
├── LICENSE
├── README.md
├── bin/
│ └── mapscii.sh
├── main.js
├── package.json
├── snap/
│ └── snapcraft.yaml
├── src/
│ ├── BrailleBuffer.js
│ ├── Canvas.js
│ ├── LabelBuffer.js
│ ├── Mapscii.js
│ ├── Renderer.js
│ ├── Styler.js
│ ├── Tile.js
│ ├── TileSource.js
│ ├── TileSource.spec.js
│ ├── config.js
│ ├── utils.js
│ └── utils.spec.js
└── styles/
├── bright.json
└── dark.json
SYMBOL INDEX (97 symbols across 9 files)
FILE: src/BrailleBuffer.js
class BrailleBuffer (line 32) | class BrailleBuffer {
method constructor (line 33) | constructor(width, height) {
method clear (line 57) | clear() {
method setGlobalBackground (line 64) | setGlobalBackground(background) {
method setBackground (line 68) | setBackground(x, y, color) {
method setPixel (line 75) | setPixel(x, y, color) {
method unsetPixel (line 82) | unsetPixel(x, y) {
method _project (line 88) | _project(x, y) {
method _locate (line 92) | _locate(x, y, cb) {
method _mapBraille (line 101) | _mapBraille() {
method _termColor (line 136) | _termColor(foreground, background) {
method frame (line 149) | frame() {
method setChar (line 193) | setChar(char, x, y, color) {
method writeText (line 201) | writeText(text, x, y, color, center = true) {
FILE: src/Canvas.js
class Canvas (line 18) | class Canvas {
method constructor (line 19) | constructor(width, height) {
method frame (line 25) | frame() {
method clear (line 29) | clear() {
method text (line 33) | text(text, x, y, color, center = false) {
method line (line 37) | line(from, to, color, width = 1) {
method polyline (line 41) | polyline(points, color, width = 1) {
method setBackground (line 49) | setBackground(color) {
method background (line 53) | background(x, y, color) {
method polygon (line 57) | polygon(rings, color) {
method _polygonExtract (line 88) | _polygonExtract(vertices, pointId) {
method _line (line 94) | _line(x0, y0, x1, y1, width, color) {
method _filledRectangle (line 148) | _filledRectangle(x, y, width, height, color) {
method _bresenham (line 157) | _bresenham(pointA, pointB) {
method _filledTriangle (line 162) | _filledTriangle(pointA, pointB, pointC, color) {
FILE: src/LabelBuffer.js
method constructor (line 14) | constructor() {
method clear (line 19) | clear() {
method project (line 23) | project(x, y) {
method writeIfPossible (line 27) | writeIfPossible(text, x, y, feature, margin) {
method featuresAt (line 41) | featuresAt(x, y) {
method _hasSpace (line 45) | _hasSpace(text, x, y) {
method _calculateArea (line 49) | _calculateArea(text, x, y, margin = 0) {
FILE: src/Mapscii.js
class Mapscii (line 17) | class Mapscii {
method constructor (line 18) | constructor(options) {
method init (line 43) | async init() {
method _initTileSource (line 55) | _initTileSource() {
method _initKeyboard (line 60) | _initKeyboard() {
method _initMouse (line 70) | _initMouse() {
method _initRenderer (line 82) | _initRenderer() {
method _resizeRenderer (line 95) | _resizeRenderer() {
method _colrow2ll (line 104) | _colrow2ll(x, y) {
method _updateMousePosition (line 119) | _updateMousePosition(event) {
method _onClick (line 123) | _onClick(event) {
method _onMouseScroll (line 138) | _onMouseScroll(event) {
method _onMouseMove (line 170) | _onMouseMove(event) {
method _onKey (line 209) | _onKey(key) {
method _draw (line 258) | _draw() {
method _getFooter (line 267) | _getFooter() {
method notify (line 279) | notify(text) {
method _write (line 286) | _write(output) {
method zoomBy (line 290) | zoomBy(step) {
method moveBy (line 301) | moveBy(lat, lon) {
method setCenter (line 305) | setCenter(lat, lon) {
FILE: src/Renderer.js
class Renderer (line 17) | class Renderer {
method constructor (line 18) | constructor(output, tileSource, style) {
method setSize (line 26) | setSize(width, height) {
method draw (line 32) | async draw(center, zoom) {
method _visibleTiles (line 67) | _visibleTiles(center, zoom) {
method _getTile (line 105) | async _getTile(tile) {
method _getTileFeatures (line 110) | _getTileFeatures(tile, zoom) {
method _renderTiles (line 135) | _renderTiles(tiles) {
method _getFrame (line 169) | _getFrame() {
method featuresAt (line 179) | featuresAt(x, y) {
method _drawFeature (line 183) | _drawFeature(tile, feature, scale) {
method _scaleAndReduce (line 247) | _scaleAndReduce(tile, feature, points, scale, filter = true) {
method _generateDrawOrder (line 295) | _generateDrawOrder(zoom) {
FILE: src/Styler.js
class Styler (line 13) | class Styler {
method constructor (line 14) | constructor(style) {
method getStyleFor (line 43) | getStyleFor(layer, feature) {
method _replaceConstants (line 57) | _replaceConstants(constants, tree) {
method _compileFilter (line 76) | _compileFilter(filter) {
FILE: src/Tile.js
class Tile (line 17) | class Tile {
method constructor (line 18) | constructor(styler) {
method load (line 22) | load(buffer) {
method _loadTile (line 32) | _loadTile(buffer) {
method _unzipIfNeeded (line 36) | _unzipIfNeeded(buffer) {
method _isGzipped (line 51) | _isGzipped(buffer) {
method _loadLayers (line 55) | _loadLayers() {
method _addBoundaries (line 124) | _addBoundaries(deep, data) {
method _reduceGeometry (line 143) | _reduceGeometry(feature, factor) {
FILE: src/TileSource.js
class TileSource (line 33) | class TileSource {
method init (line 34) | init(source) {
method loadMBTiles (line 64) | loadMBTiles(source) {
method useStyler (line 76) | useStyler(styler) {
method getTile (line 80) | getTile(z, x, y) {
method _getHTTP (line 105) | _getHTTP(z, x, y) {
method _getMBTile (line 125) | _getMBTile(z, x, y) {
method _createTile (line 136) | _createTile(z, x, y, buffer) {
method _initPersistence (line 144) | _initPersistence() {
method _persistTile (line 152) | _persistTile(z, x, y, buffer) {
method _getPersited (line 159) | _getPersited(z, x, y) {
method _createFolder (line 167) | _createFolder(path) {
FILE: src/utils.spec.js
function wrapper (line 19) | function wrapper() {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (140K chars).
[
{
"path": ".eslintrc.js",
"chars": 646,
"preview": "module.exports = {\n \"env\": {\n \"es6\": true,\n \"node\": true,\n \"jest\": true\n },\n \"parserOption"
},
{
"path": ".github/ISSUE_TEMPLATE/BugReport.md",
"chars": 567,
"preview": "---\nname: \"\\U0001F41B Bug report\"\nabout: Something isn’t working as expected\n---\n\n**Steps to reproduce**\n\n<!--\nA step by"
},
{
"path": ".github/ISSUE_TEMPLATE/FeatureRequest.md",
"chars": 684,
"preview": "---\nname: \"\\U0001F680 Feature request\"\nabout: I have a suggestion (and might want to implement myself)\n---\n\n<!-- Conside"
},
{
"path": ".github/ISSUE_TEMPLATE/Question.md",
"chars": 528,
"preview": "---\nname: \"\\U0001F914 Support question\"\nabout: I have a question or don’t know how to do something\n---\n\n--------------^ "
},
{
"path": ".github/ISSUE_TEMPLATE/Support.md",
"chars": 732,
"preview": "---\nname: \"\\U0001F984 Support MapSCII’s development\"\nabout: I want to support efforts in maintaining this community-driv"
},
{
"path": ".gitignore",
"chars": 55,
"preview": "node_modules\nbundle*\n*.log\ntmp\nmbtiles\ntiles\n.DS_Store\n"
},
{
"path": ".npmignore",
"chars": 60,
"preview": "node_modules\nbundle*\n*.log\ntmp\nmbtiles\ntiles\n.DS_Store\ndocs\n"
},
{
"path": ".travis.yml",
"chars": 100,
"preview": "language: node_js\nnode_js:\n - \"14\"\n - \"16\"\n - \"18\"\n\nscript:\n - npm run-script lint\n - npm test\n"
},
{
"path": "AUTHORS",
"chars": 292,
"preview": "# This is the list of MapSCII authors for copyright purposes.\n#\nMichael Straßburger\nChristian Paul (https://chrpaul.de)\n"
},
{
"path": "LICENSE",
"chars": 1115,
"preview": "MIT License\n\nCopyright (c) 2017 Michael Straßburger\nCopyright (c) 2019 The MapSCII authors\n\nPermission is hereby granted"
},
{
"path": "README.md",
"chars": 6216,
"preview": "# MapSCII - The Whole World In Your Console. [;\n\ndescribe('TileSource', () => {\n describe('with a HTTP source"
},
{
"path": "src/config.js",
"chars": 1058,
"preview": "module.exports = {\n language: 'en',\n\n // TODO: adapt to osm2vectortiles successor openmaptiles v3)\n // mapscii.me hos"
},
{
"path": "src/utils.js",
"chars": 2275,
"preview": "/*\n termap - Terminal Map Viewer\n by Michael Strassburger <codepoet@cpan.org>\n\n methods used all around\n*/\n'use stric"
},
{
"path": "src/utils.spec.js",
"chars": 1194,
"preview": "'use strict';\nconst utils = require('./utils');\n\ndescribe('utils', () => {\n describe('hex2rgb', () => {\n describe.ea"
},
{
"path": "styles/bright.json",
"chars": 37179,
"preview": "{\n \"name\": \"Bright\",\n \"constants\": {\n \"@background\": \"#000\",\n \"@water\": \"#5f87ff\",\n\n \"@building\": \"#99b\",\n "
},
{
"path": "styles/dark.json",
"chars": 28488,
"preview": "{\n \"name\": \"dark\",\n \"constants\": {\n \"@admin_level_2\": \"#fff\",\n \"@admin_level_2_disputed\": \"#fff\",\n \"@admin_le"
}
]
About this extraction
This page contains the full source code of the rastapasta/mapscii GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (123.9 KB), approximately 34.1k tokens, and a symbol index with 97 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.