mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 23:53:08 +01:00
Non-perfect translation of Renderer.js
This commit is contained in:
parent
29b5b3ccb1
commit
283c245a88
342
src/Renderer.js
Normal file
342
src/Renderer.js
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
termap - Terminal Map Viewer
|
||||
by Michael Strassburger <codepoet@cpan.org>
|
||||
|
||||
The Console Vector Tile renderer - bäm!
|
||||
*/
|
||||
'use strict';
|
||||
const Promise = require('bluebird');
|
||||
const x256 = require('x256');
|
||||
const simplify = require('simplify-js');
|
||||
|
||||
const Canvas = require('./Canvas');
|
||||
const LabelBuffer = require('./LabelBuffer');
|
||||
const Styler = require('./Styler');
|
||||
const Tile = require('./Tile');
|
||||
const utils = require('./utils');
|
||||
const config = require('./config');
|
||||
|
||||
class Renderer {
|
||||
constructor(output, tileSource) {
|
||||
this.output = output;
|
||||
this.tileSource = tileSource;
|
||||
this.labelBuffer = new LabelBuffer();
|
||||
}
|
||||
|
||||
loadStyleFile(file) {
|
||||
this.styler = new Styler(file);
|
||||
this.tileSource.useStyler(this.styler);
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.canvas = new Canvas(width, height);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return Promise.resolve(this._visibleTiles(center, zoom)).map((tile) => {
|
||||
return this._getTile(tile);
|
||||
}).map((tile) => {
|
||||
return this._getTileFeatures(tile, zoom);
|
||||
}).then((tiles) => {
|
||||
return this._renderTiles(tiles);
|
||||
}).then(() => {
|
||||
return this._getFrame();
|
||||
}).catch(function(e) {
|
||||
return console.log(e);
|
||||
}).finally((frame) => {
|
||||
this.isDrawing = false;
|
||||
this.lastDrawAt = Date.now();
|
||||
return frame;
|
||||
});
|
||||
}
|
||||
|
||||
_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: zoom,
|
||||
position: position,
|
||||
size: tileSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
_getTile(tile) {
|
||||
return this.tileSource
|
||||
.getTile(tile.xyz.z, tile.xyz.x, tile.xyz.y)
|
||||
.then((data) => {
|
||||
tile.data = data;
|
||||
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) {
|
||||
var drawn, feature, i, j, k, l, label, labels, layer, layerId, len, len1, len2, len3, ref, ref1, tile;
|
||||
drawn = {};
|
||||
labels = [];
|
||||
ref = this._generateDrawOrder(tiles[0].xyz.z);
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
layerId = ref[i];
|
||||
for (j = 0, len1 = tiles.length; j < len1; j++) {
|
||||
tile = tiles[j];
|
||||
if (!(layer = tile.layers[layerId])) {
|
||||
continue;
|
||||
}
|
||||
ref1 = layer.features;
|
||||
for (k = 0, len2 = ref1.length; k < len2; k++) {
|
||||
feature = ref1[k];
|
||||
// continue if feature.id and drawn[feature.id]
|
||||
// drawn[feature.id] = true
|
||||
if (layerId.match(/label/)) {
|
||||
labels.push({
|
||||
tile: tile,
|
||||
feature: feature,
|
||||
scale: layer.scale
|
||||
});
|
||||
} else {
|
||||
this._drawFeature(tile, feature, layer.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
labels.sort(function(a, b) {
|
||||
return a.feature.sorty - b.feature.sort;
|
||||
});
|
||||
const results = [];
|
||||
for (const label of labels) {
|
||||
results.push(this._drawFeature(label.tile, label.feature, label.scale));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
_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, genericSymbol;
|
||||
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 text = feature.label || (genericSymbol = config.poiMarker);
|
||||
|
||||
if (this._seen[text] && !genericSymbol) {
|
||||
return false;
|
||||
}
|
||||
|
||||
placed = false;
|
||||
const points2 = this._scaleAndReduce(tile, feature, feature.points, scale);
|
||||
for (const point of points2) {
|
||||
let ref1, ref2;
|
||||
const x = point.x - text.length;
|
||||
const margin = ((ref1 = config.layers[feature.layer]) != null ? ref1.margin : void 0) || 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 if (((ref2 = config.layers[feature.layer]) != null ? ref2.cluster : void 0) && 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;
|
||||
}
|
||||
}
|
||||
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;
|
Loading…
Reference in New Issue
Block a user