mirror of
https://github.com/rastapasta/mapscii.git
synced 2025-02-16 09:29:13 +01:00
Decaffeinate Mapscii class
This commit is contained in:
parent
6033f0be3c
commit
2d22e33ff1
@ -1,226 +0,0 @@
|
|||||||
###
|
|
||||||
mapscii - Terminal Map Viewer
|
|
||||||
by Michael Strassburger <codepoet@cpan.org>
|
|
||||||
|
|
||||||
UI and central command center
|
|
||||||
###
|
|
||||||
|
|
||||||
keypress = require 'keypress'
|
|
||||||
TermMouse = require 'term-mouse'
|
|
||||||
Promise = require 'bluebird'
|
|
||||||
|
|
||||||
Renderer = require './Renderer'
|
|
||||||
TileSource = require './TileSource'
|
|
||||||
utils = require './utils'
|
|
||||||
config = require './config'
|
|
||||||
|
|
||||||
module.exports = class Mapscii
|
|
||||||
width: null
|
|
||||||
height: null
|
|
||||||
canvas: null
|
|
||||||
mouse: null
|
|
||||||
|
|
||||||
mouseDragging: false
|
|
||||||
mousePosition:
|
|
||||||
x: 0, y: 0
|
|
||||||
|
|
||||||
tileSource: null
|
|
||||||
renderer: null
|
|
||||||
|
|
||||||
zoom: 0
|
|
||||||
center:
|
|
||||||
# sf lat: 37.787946, lon: -122.407522
|
|
||||||
# iceland lat: 64.124229, lon: -21.811552
|
|
||||||
# rgbg
|
|
||||||
# lat: 49.019493, lon: 12.098341
|
|
||||||
lat: 52.51298, lon: 13.42012
|
|
||||||
|
|
||||||
minZoom: null
|
|
||||||
|
|
||||||
constructor: (options) ->
|
|
||||||
config[key] = val for key, val of options
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
Promise
|
|
||||||
.resolve()
|
|
||||||
.then =>
|
|
||||||
unless config.headless
|
|
||||||
@_initKeyboard()
|
|
||||||
@_initMouse()
|
|
||||||
|
|
||||||
@_initTileSource()
|
|
||||||
|
|
||||||
.then =>
|
|
||||||
@_initRenderer()
|
|
||||||
|
|
||||||
.then =>
|
|
||||||
@_draw()
|
|
||||||
.then => @notify("Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.")
|
|
||||||
|
|
||||||
_initTileSource: ->
|
|
||||||
@tileSource = new TileSource()
|
|
||||||
@tileSource.init config.source
|
|
||||||
|
|
||||||
_initKeyboard: ->
|
|
||||||
keypress config.input
|
|
||||||
config.input.setRawMode true if config.input.setRawMode
|
|
||||||
config.input.resume()
|
|
||||||
|
|
||||||
config.input.on 'keypress', (ch, key) => @_onKey key
|
|
||||||
|
|
||||||
_initMouse: ->
|
|
||||||
@mouse = TermMouse input: config.input, output: config.output
|
|
||||||
@mouse.start()
|
|
||||||
|
|
||||||
@mouse.on 'click', (event) => @_onClick event
|
|
||||||
@mouse.on 'scroll', (event) => @_onMouseScroll event
|
|
||||||
@mouse.on 'move', (event) => @_onMouseMove event
|
|
||||||
|
|
||||||
_initRenderer: ->
|
|
||||||
@renderer = new Renderer config.output, @tileSource
|
|
||||||
@renderer.loadStyleFile config.styleFile
|
|
||||||
|
|
||||||
config.output.on 'resize', =>
|
|
||||||
@_resizeRenderer()
|
|
||||||
@_draw()
|
|
||||||
|
|
||||||
@_resizeRenderer()
|
|
||||||
@zoom = if config.initialZoom isnt null then config.initialZoom else @minZoom
|
|
||||||
|
|
||||||
_resizeRenderer: (cb) ->
|
|
||||||
if config.size
|
|
||||||
@width = config.size.width
|
|
||||||
@height = config.size.height
|
|
||||||
else
|
|
||||||
@width = config.output.columns >> 1 << 2
|
|
||||||
@height = config.output.rows * 4 - 4
|
|
||||||
|
|
||||||
@minZoom = 4-Math.log(4096/@width)/Math.LN2
|
|
||||||
|
|
||||||
@renderer.setSize @width, @height
|
|
||||||
|
|
||||||
_updateMousePosition: (event) ->
|
|
||||||
projected =
|
|
||||||
x: (event.x-.5)*2
|
|
||||||
y: (event.y-.5)*4
|
|
||||||
|
|
||||||
size = utils.tilesizeAtZoom @zoom
|
|
||||||
[dx, dy] = [projected.x-@width/2, projected.y-@height/2]
|
|
||||||
|
|
||||||
z = utils.baseZoom @zoom
|
|
||||||
center = utils.ll2tile @center.lon, @center.lat, z
|
|
||||||
|
|
||||||
@mousePosition = utils.normalize utils.tile2ll center.x+(dx/size), center.y+(dy/size), z
|
|
||||||
|
|
||||||
_onClick: (event) ->
|
|
||||||
return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
|
|
||||||
@_updateMousePosition event
|
|
||||||
|
|
||||||
if @mouseDragging and event.button is "left"
|
|
||||||
@mouseDragging = false
|
|
||||||
else
|
|
||||||
@setCenter @mousePosition.lat, @mousePosition.lon
|
|
||||||
|
|
||||||
@_draw()
|
|
||||||
|
|
||||||
_onMouseScroll: (event) ->
|
|
||||||
@_updateMousePosition event
|
|
||||||
# TODO: handle .x/y for directed zoom
|
|
||||||
@zoomBy config.zoomStep * if event.button is "up" then 1 else -1
|
|
||||||
@_draw()
|
|
||||||
|
|
||||||
_onMouseMove: (event) ->
|
|
||||||
return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
|
|
||||||
return if config.mouseCallback and not config.mouseCallback event
|
|
||||||
|
|
||||||
# start dragging
|
|
||||||
if event.button is "left"
|
|
||||||
if @mouseDragging
|
|
||||||
dx = (@mouseDragging.x-event.x)*2
|
|
||||||
dy = (@mouseDragging.y-event.y)*4
|
|
||||||
|
|
||||||
size = utils.tilesizeAtZoom @zoom
|
|
||||||
|
|
||||||
newCenter = utils.tile2ll @mouseDragging.center.x+(dx/size),
|
|
||||||
@mouseDragging.center.y+(dy/size),
|
|
||||||
utils.baseZoom(@zoom)
|
|
||||||
|
|
||||||
@setCenter newCenter.lat, newCenter.lon
|
|
||||||
|
|
||||||
@_draw()
|
|
||||||
|
|
||||||
else
|
|
||||||
@mouseDragging =
|
|
||||||
x: event.x,
|
|
||||||
y: event.y,
|
|
||||||
center: utils.ll2tile @center.lon, @center.lat, utils.baseZoom(@zoom)
|
|
||||||
|
|
||||||
@_updateMousePosition event
|
|
||||||
@notify @_getFooter()
|
|
||||||
|
|
||||||
_onKey: (key) ->
|
|
||||||
if config.keyCallback and not config.keyCallback key
|
|
||||||
return
|
|
||||||
|
|
||||||
# check if the pressed key is configured
|
|
||||||
draw = switch key?.name
|
|
||||||
when "q"
|
|
||||||
if config.quitCallback
|
|
||||||
config.quitCallback()
|
|
||||||
else
|
|
||||||
process.exit 0
|
|
||||||
|
|
||||||
when "a" then @zoomBy config.zoomStep
|
|
||||||
when "z", "y"
|
|
||||||
@zoomBy -config.zoomStep
|
|
||||||
|
|
||||||
when "left" then @moveBy 0, -8/Math.pow(2, @zoom)
|
|
||||||
when "right" then @moveBy 0, 8/Math.pow(2, @zoom)
|
|
||||||
when "up" then @moveBy 6/Math.pow(2, @zoom), 0
|
|
||||||
when "down" then @moveBy -6/Math.pow(2, @zoom), 0
|
|
||||||
|
|
||||||
when "c"
|
|
||||||
config.useBraille = !config.useBraille
|
|
||||||
true
|
|
||||||
|
|
||||||
else
|
|
||||||
null
|
|
||||||
|
|
||||||
if draw isnt null
|
|
||||||
@_draw()
|
|
||||||
|
|
||||||
_draw: ->
|
|
||||||
@renderer
|
|
||||||
.draw @center, @zoom
|
|
||||||
.then (frame) =>
|
|
||||||
@_write frame
|
|
||||||
@notify @_getFooter()
|
|
||||||
.catch =>
|
|
||||||
@notify "renderer is busy"
|
|
||||||
|
|
||||||
_getFooter: ->
|
|
||||||
# tile = utils.ll2tile @center.lon, @center.lat, @zoom
|
|
||||||
# "tile: #{utils.digits tile.x, 3}, #{utils.digits tile.x, 3} "+
|
|
||||||
|
|
||||||
"center: #{utils.digits @center.lat, 3}, #{utils.digits @center.lon, 3} "+
|
|
||||||
"zoom: #{utils.digits @zoom, 2} "+
|
|
||||||
"mouse: #{utils.digits @mousePosition.lat, 3}, #{utils.digits @mousePosition.lon, 3} "
|
|
||||||
|
|
||||||
notify: (text) ->
|
|
||||||
config.onUpdate() if config.onUpdate
|
|
||||||
@_write "\r\x1B[K"+text unless config.headless
|
|
||||||
|
|
||||||
_write: (output) ->
|
|
||||||
config.output.write output
|
|
||||||
|
|
||||||
zoomBy: (step) ->
|
|
||||||
return @zoom = @minZoom if @zoom+step < @minZoom
|
|
||||||
return @zoom = config.maxZoom if @zoom+step > config.maxZoom
|
|
||||||
|
|
||||||
@zoom += step
|
|
||||||
|
|
||||||
moveBy: (lat, lon) ->
|
|
||||||
@setCenter @center.lat+lat, @center.lon+lon
|
|
||||||
|
|
||||||
setCenter: (lat, lon) ->
|
|
||||||
@center = utils.normalize lon: lon, lat: lat
|
|
287
src/Mapscii.js
Normal file
287
src/Mapscii.js
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
mapscii - Terminal Map Viewer
|
||||||
|
by Michael Strassburger <codepoet@cpan.org>
|
||||||
|
|
||||||
|
UI and central command center
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
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;
|
||||||
|
// sf lat: 37.787946, lon: -122.407522
|
||||||
|
// iceland lat: 64.124229, lon: -21.811552
|
||||||
|
// rgbg
|
||||||
|
// lat: 49.019493, lon: 12.098341
|
||||||
|
this.center = {
|
||||||
|
lat: 52.51298,
|
||||||
|
lon: 13.42012,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.minZoom = null;
|
||||||
|
config = Object.assign(config, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
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.");
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_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() {
|
||||||
|
this.renderer = new Renderer(config.output, this.tileSource);
|
||||||
|
this.renderer.loadStyleFile(config.styleFile);
|
||||||
|
|
||||||
|
config.output.on('resize', () => {
|
||||||
|
this._resizeRenderer();
|
||||||
|
this._draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._resizeRenderer()
|
||||||
|
this.zoom = (config.initialZoom !== null) ? config.initialZoom : this.minZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
_resizeRenderer(cb) {
|
||||||
|
if (config.size) {
|
||||||
|
this.width = config.size.width;
|
||||||
|
this.height = config.size.height;
|
||||||
|
} else {
|
||||||
|
this.width = config.output.columns >> 1 << 2;
|
||||||
|
this.height = config.output.rows * 4 - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minZoom = 4-Math.log(4096/this.width)/Math.LN2;
|
||||||
|
|
||||||
|
this.renderer.setSize(this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMousePosition(event) {
|
||||||
|
const projected = {
|
||||||
|
x: (event.x-0.5)*2,
|
||||||
|
y: (event.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);
|
||||||
|
|
||||||
|
this.mousePosition = utils.normalize(utils.tile2ll(center.x+(dx/size), center.y+(dy/size), z));
|
||||||
|
}
|
||||||
|
|
||||||
|
_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);
|
||||||
|
// TODO: handle .x/y for directed zoom
|
||||||
|
this.zoomBy(config.zoomStep * (event.button === 'up' ? 1 : -1));
|
||||||
|
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 'z', 'y':
|
||||||
|
this.zoomBy(-config.zoomStep);
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
this.moveBy(0, -8/Math.pow(2, this.zoom));
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
this.moveBy(0, 8/Math.pow(2, this.zoom));
|
||||||
|
break;
|
||||||
|
case 'up':
|
||||||
|
this.moveBy(6/Math.pow(2, this.zoom), 0);
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
this.moveBy(-6/Math.pow(2, this.zoom), 0);
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
config.useBraille = !config.useBraille;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
draw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw !== null) {
|
||||||
|
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)} `+
|
||||||
|
|
||||||
|
return `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `+
|
||||||
|
`zoom: ${utils.digits(this.zoom, 2)} `+
|
||||||
|
`mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user