Non-perfect translation of Renderer.js

This commit is contained in:
Christian Paul 2017-12-22 21:33:10 -08:00
parent 29b5b3ccb1
commit 283c245a88

342
src/Renderer.js Normal file
View 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;