Porównaj commity

...

161 Commity

Autor SHA1 Wiadomość Data
dependabot[bot] 4fe9a60a0c
Bump jsdom from 16.4.0 to 16.7.0 (#118)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 16.4.0 to 16.7.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/16.4.0...16.7.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:14:17 +01:00
dependabot[bot] 1ac31f62e9
Bump ansi-regex from 4.1.0 to 4.1.1 (#125)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:13:15 +01:00
dependabot[bot] 84f29b8e8b
Bump minimatch from 3.0.4 to 3.1.2 (#126)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:12:38 +01:00
dependabot[bot] dcc2508761
Bump json-schema and jsprim (#127)
Bumps [json-schema](https://github.com/kriszyp/json-schema) and [jsprim](https://github.com/joyent/node-jsprim). These dependencies needed to be updated together.

Updates `json-schema` from 0.2.3 to 0.4.0
- [Release notes](https://github.com/kriszyp/json-schema/releases)
- [Commits](https://github.com/kriszyp/json-schema/compare/v0.2.3...v0.4.0)

Updates `jsprim` from 1.4.1 to 1.4.2
- [Release notes](https://github.com/joyent/node-jsprim/releases)
- [Changelog](https://github.com/TritonDataCenter/node-jsprim/blob/v1.4.2/CHANGES.md)
- [Commits](https://github.com/joyent/node-jsprim/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: json-schema
  dependency-type: indirect
- dependency-name: jsprim
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:12:16 +01:00
dependabot[bot] bae6fcfc93
Bump node-fetch from 2.6.1 to 2.6.7 (#119)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 13:13:13 +01:00
dependabot[bot] e39cd8783b
Bump decode-uri-component from 0.2.0 to 0.2.2 (#122)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 13:12:49 +01:00
dependabot[bot] c093199c65
Bump json5 from 2.1.3 to 2.2.3 (#124)
Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 13:12:24 +01:00
dependabot[bot] edeb0ebb2d
Bump qs from 6.5.2 to 6.5.3 (#123)
Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 13:12:10 +01:00
Christian Paul 76f541db46
Test on Node 14, 16 and 18 2022-04-29 20:03:20 +02:00
dependabot[bot] 2315a3515c
Bump path-parse from 1.0.6 to 1.0.7 (#109)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-31 23:28:17 +02:00
dependabot[bot] 7f0c04cb49
Bump hosted-git-info from 2.8.8 to 2.8.9 (#102)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-14 03:59:48 +02:00
dependabot[bot] c58da4e86f
Bump ws from 7.3.1 to 7.4.6 (#103)
Bumps [ws](https://github.com/websockets/ws) from 7.3.1 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.3.1...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-14 03:59:00 +02:00
dependabot[bot] f2a694f26d
Bump glob-parent from 5.1.1 to 5.1.2 (#104)
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-14 03:58:08 +02:00
Michael Straßburger 9f0fa46f92
Merge pull request #101 from rastapasta/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-05-08 15:06:18 +02:00
Michael Straßburger da9d4d05a6
Merge pull request #99 from rastapasta/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-05-08 15:06:03 +02:00
dependabot[bot] 64c53f6d01
Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 20:49:46 +00:00
dependabot[bot] dafe5cf4cb
Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-30 10:02:56 +00:00
dependabot[bot] 9be22c8a1a
Bump node-notifier from 8.0.0 to 8.0.1 (#96)
Bumps [node-notifier](https://github.com/mikaelbr/node-notifier) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/mikaelbr/node-notifier/releases)
- [Changelog](https://github.com/mikaelbr/node-notifier/blob/v8.0.1/CHANGELOG.md)
- [Commits](https://github.com/mikaelbr/node-notifier/compare/v8.0.0...v8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-22 14:08:22 +01:00
Christian Paul b70e64f378 Upgrade dependencies 2020-09-09 00:40:47 +02:00
Christian Paul 247122f684
Remove bluebird (#94) 2020-09-09 00:36:37 +02:00
Christian Paul a7e7621705
Replace userhome with env-paths (#91)
* Replace userhome with env-paths

* Download the correct package
2020-08-17 12:02:07 +02:00
Christian Paul 2d88b6f16d Merge branch 'master' of github.com:rastapasta/mapscii 2020-08-12 02:08:04 +02:00
Christian Paul 0ce9b27a04 Upgrade dependencies 2020-08-12 02:07:55 +02:00
Christian Paul df269c10d9
Fix TileSource test to be a test (#90) 2020-08-12 02:06:01 +02:00
Christian Paul 26f8ebe1f5 Add lennonhill to the AUTHORS file 2020-08-12 01:59:51 +02:00
Christian Paul 5c8ded81dc Change the unit of width and length options to full characters 2020-08-12 01:58:25 +02:00
Christian Paul 9eb2e5ef25 Regenerate package-lock.json 2020-08-12 01:50:26 +02:00
Christian Paul 9bee8cca19 Fix the initial center to be the c-base 2020-08-12 01:46:20 +02:00
Thorkil Værge 34d628d1d1
Add preliminary support for parsing of command line options (#86)
* Add preliminary support for parsing of command line options

* Remove redundant options for width and height

* Add parser for command line arguments. Type validation outstanding.

* Update TODO list

* Use config for argv's default values, simplify argument management

Co-authored-by: LH <@>
2020-08-12 01:44:39 +02:00
dependabot[bot] 219161e13e
Bump lodash from 4.17.15 to 4.17.19 (#88)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-16 13:53:43 +02:00
Christian Paul 393f867c84
Raise the minimum NodeJS version to 10 (#84) 2020-05-28 12:18:00 +02:00
Christian Paul 8475501605 Upgrade dev dependencies: jest and eslint 2020-05-21 14:47:59 +02:00
dependabot[bot] 739176e817
Bump acorn from 6.4.0 to 6.4.1 (#82)
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-03-16 01:34:48 +01:00
Michael Straßburger 7103ecc0b7 re-adding jest, making the CI happy again 2020-02-19 10:23:55 +01:00
Michael Straßburger 223bb9b644 🔖 bumping 0.3.1 2020-02-18 21:42:14 +01:00
Michael Straßburger c1cfe3ecfe ⬆️ upgrading dependencies 2020-02-18 21:41:39 +01:00
Michael Straßburger 3b85c6d513 🔖 bumping 0.3.0, adding @quincylvania to the authors 2019-10-14 17:01:50 +02:00
Quincy Morgan a872c0ee6d Zoom toward the mouse pointer instead of the map center (#75) 2019-10-13 17:59:03 +02:00
Michael Straßburger dcda97e305 📝 adding travis badge to readme 2019-10-11 15:46:47 +02:00
Michael Straßburger 4011c877bb
Merge pull request #74 from rastapasta/eslint
CI: Add linting with ESLint
2019-10-11 15:44:01 +02:00
Michael Straßburger b76e626d5e 🔖 bumping 0.2.0 2019-10-11 00:51:36 +02:00
Christian Paul b2e491d9ea
Run the linter on CI 2019-10-10 21:05:24 +02:00
Christian Paul b07786c547
Also test on NodeJS 12 2019-10-10 20:54:32 +02:00
Christian Paul 348b84f71f
Make ESLint pass the current codebase 2019-10-10 20:54:16 +02:00
Christian Paul 184ce100d7
Upgrade dependencies 2019-10-10 19:50:50 +02:00
0xflotus 54235a81b7 fixed optimized (#69) 2019-07-12 00:15:09 +02:00
Christian Paul 95ad703199
Merge pull request #70 from jestin/master
Add vim keys as alternative to arrow key navigation
2019-07-11 22:37:48 +02:00
Jestin Stoffel fccacad3a2 Add vim keys as alternative to arrow key navigation 2019-07-08 10:50:19 -05:00
Christian Paul 9b5769a2ec
Merge pull request #68 from rastapasta/upgrade-dependencies
Upgrade various dependencies
2019-06-16 22:30:06 -07:00
Christian Paul 421d71255f Merge branch 'master' into upgrade-dependencies 2019-06-16 22:18:01 -07:00
Christian Paul 7d4cd331ae Upgrade various dependencies 2019-06-16 22:12:23 -07:00
Christian Paul eac7cb4d15
Merge pull request #67 from Self-Perfection/patch-1
Fix typo in README
2019-05-13 15:55:15 -07:00
Christian Paul 22532ba03b
Merge pull request #61 from rastapasta/draw-is-never-null
Fix issue where every button press causes a rerender
2019-04-12 20:49:00 -07:00
Christian Paul 0b0384c3f3
Merge pull request #63 from rastapasta/authors
Start a dedicated AUTHORS file for copyright purposes
2019-04-12 20:48:41 -07:00
Christian Paul e7ff94fc1c
Merge pull request #62 from rastapasta/safe-upgrades
Upgrade dependencies
2019-03-24 18:28:17 -07:00
Christian Paul ff75d2de50
Merge pull request #59 from rastapasta/mapbox-mbtiles
The NPM package mbtiles got deprecated in favour of @mapbox/mbtiles
2019-03-24 18:27:56 -07:00
Christian Paul 6c86050b3d
Merge pull request #64 from rastapasta/drop-node6
Remove support for Node 6
2019-03-24 18:27:24 -07:00
Christian Paul ad4103c8df Don't use async within describe 2019-03-24 18:18:48 -07:00
Christian Paul 7b322af22f Remove tests / support for Node 6 2019-03-24 18:14:19 -07:00
Christian Paul d98aced647
Merge pull request #57 from rastapasta/create-folder-error
Throw an error if creating a folder fails.
2019-03-24 18:05:01 -07:00
Christian Paul 044c24f6c8
Merge pull request #56 from rastapasta/promises
Load the style file in Mapscii to keep the Renderer simpler
2019-03-24 18:00:57 -07:00
Christian Paul 66a605254c Fix mistake where _createFolder wouldn't throw an error 2019-03-24 17:59:46 -07:00
Christian Paul 3dcebfa42e Start a dedicated AUTHORS file for copyright purposes 2019-03-24 17:36:30 -07:00
Christian Paul fea38767d3 Upgrade string-width to 3.1.0. The latest version supporting Node 6. 2019-03-24 17:14:43 -07:00
Christian Paul eb1f81536e Upgrade dependencies which are unlikely to break Mapscii 2019-03-24 17:06:24 -07:00
Christian Paul f4c002cadd Fix issue where every button press causes a rerender 2019-03-24 17:02:30 -07:00
Christian Paul 8a4d82dead Update title in the README 2018-11-19 01:07:26 -08:00
Christian Paul 4546a99a55 The NPM package mbtiles got deprecated in favour of @mapbox/mbtiles 2018-11-19 00:58:09 -08:00
Christian Paul cf4731682e
Merge pull request #58 from rastapasta/unhandled-mapscii-init
Unhandled mapscii init
2018-11-19 00:41:06 -08:00
Christian Paul 0ba7a7201d Load the style file in Mapscii.js 2018-11-19 00:35:10 -08:00
Christian Paul 4b97538235 Fix MBTiles typo MBtils 2018-11-19 00:29:22 -08:00
Christian Paul ca789ba9dd Throw an error if creating a Folder fails. 2018-11-19 00:24:51 -08:00
Christian Paul 926a6d0bcc Use async to simplify Mapscii.init() 2018-11-19 00:18:05 -08:00
Christian Paul cdb0f45c75 Handle errors from Mapscii.init() 2018-11-19 00:17:12 -08:00
Christian Paul b320618a7e
Merge pull request #55 from rastapasta/update-packages
Update dependencies
2018-11-18 19:32:25 -08:00
Christian Paul e47fe83f38
Merge pull request #54 from rastapasta/unit-tests
Add unit tests for utils.normalize
2018-11-18 19:32:04 -08:00
Christian Paul 14628a80c2
Merge pull request #51 from rastapasta/issue-templates
Add GitHub issue templates to the repo
2018-11-18 19:30:58 -08:00
Christian Paul 2a914dc983 Update dependencies 2018-11-18 19:28:18 -08:00
Christian Paul 62440a98b8 Add tests for utils.normailze 2018-11-18 19:22:39 -08:00
Christian Paul 9ef0d88d55 Improve utils.hex2rgb tests 2018-11-18 19:21:56 -08:00
Christian Paul baceee6ad6 Stylize name as MapSCII 2018-11-18 18:47:24 -08:00
Christian Paul 1480258a73 Add issue templates as seen on styleguidist/react-styleguidist
Co-Authored-By: Artem Sapegin <asapegin@wayfair.com>
2018-10-25 17:21:17 -07:00
Christian Paul fb624c5784 Merge branch 'master' of github.com:rastapasta/mapscii 2018-10-25 17:12:22 -07:00
Christian Paul 5e095f473a git push origin masterMerge branch 'jaller94-mouse-footer-activate' 2018-10-25 17:11:37 -07:00
Christian Paul 7430ca09fa Merge branch 'mouse-footer-activate' of https://github.com/jaller94/mapscii into jaller94-mouse-footer-activate 2018-10-25 17:11:21 -07:00
Christian Paul 5a384f6e25
Merge pull request #49 from rastapasta/travis-ci
Add Travis CI configuration
2018-10-25 16:44:08 -07:00
Christian Paul 0642d2e533 Add Travis CI configuration 2018-10-18 20:01:08 -07:00
Christian Paul 712898aa97
Merge pull request #46 from rastapasta/color-bug
Bug fix: utils.hex2rgb does not fail on invalid hex strings
2018-10-18 19:58:29 -07:00
Christian Paul c03fb7fd64
Merge pull request #47 from rastapasta/jest
Add Jest as a development dependency for unit tests
2018-10-18 19:58:15 -07:00
Christian Paul 23d5b4db16 Add Jest as a development dependency for unit tests 2018-10-18 00:45:47 -07:00
Christian Paul 24737f3db9
Merge pull request #43 from jaller94/buffer
Only support maintained LTS versions of Node
2018-10-18 00:42:26 -07:00
Christian Paul ac273b2409 Add tests for utils.hex2rgb 2018-10-18 00:38:44 -07:00
Christian Paul 46dd1a9853 Fix: invert test and fix error message 2018-10-18 00:38:10 -07:00
Christian Paul 25ce95a8b3 Fix: test the variable color 2018-10-17 22:41:01 -07:00
Christian Paul 9c66bb5418 Lower required NodeJS version to 6.14.0 to support the maintained LTS 6 2018-10-17 09:26:42 -07:00
Christian Paul 76d1995e95 Update dependencies 2018-10-17 09:16:24 -07:00
Christian Paul 60abd91bba Require the latest LTS of Node 2018-10-17 09:11:34 -07:00
Christian Paul 2bf82b8f46 Replace deprecated new Buffer(size) with Buffer.alloc() 2018-10-17 09:08:14 -07:00
Christian Paul 7a313c8a7c
Merge pull request #36 from jaller94/decaffeinate
Decaffeinate Mapscii
2018-10-16 23:46:08 -07:00
Christian Paul a76679b8ac Merge remote-tracking branch 'upstream/master' into decaffeinate 2018-10-16 23:04:56 -07:00
Christian Paul b704e21c4b
Merge pull request #21 from ZhukovAlexander/patch-1
Fix a typo
2018-10-16 22:04:42 -07:00
Christian Paul 02dc03de96
Merge pull request #37 from elopio/patch-2
Add the instructions to install the snap
2018-10-16 21:58:50 -07:00
Christian Paul c6d76ec41c
Merge pull request #28 from elopio/snapcraft
Add the packaging metadata to build the mapscii snap
2018-10-16 21:51:57 -07:00
Alexander Meshcheryakov 8440715e44
Fix typo in README 2018-07-15 00:26:07 +03:00
Leo Arias 43e589cbd1
Add the instructions to install the snap 2018-02-12 19:32:18 -06:00
Christian Paul be9b703720 Update package-lock and README.md (removing CoffeeScript) 2017-12-23 02:18:55 -08:00
Christian Paul 57497ecea7 Fix breaking bug introduced in 0353effa67 2017-12-23 02:07:30 -08:00
Christian Paul 7d6aa54d33 Remove CoffeeScript dependency 2017-12-23 02:03:24 -08:00
Christian Paul a784a55759 Fix zoom out on US keyboards 2017-12-23 01:56:47 -08:00
Christian Paul f3efbae8ba Decaffeinate remaining methods of the Renderer class 2017-12-23 01:33:57 -08:00
Christian Paul bfd5c54fa4 Decaffeinate remaining methods of the Tile class 2017-12-23 01:27:43 -08:00
Christian Paul 0353effa67 More code clean up in Renderer 2017-12-23 00:56:56 -08:00
Christian Paul 0c27a79ad6 Add ESLint configuration 2017-12-23 00:17:51 -08:00
Christian Paul 069de8c227 Remove unused parameter in Styler 2017-12-23 00:16:01 -08:00
Christian Paul 1f7245644e ESLint Disable no-constant-condition in Canvas 2017-12-23 00:15:40 -08:00
Christian Paul f6485d9830 After ESLint in config 2017-12-23 00:15:08 -08:00
Christian Paul d692c9317c After ESLint in BrailleBuffer 2017-12-23 00:08:46 -08:00
Christian Paul ba09e8bd82 After ESLint in Mapscii 2017-12-23 00:08:14 -08:00
Christian Paul eee880e237 After ESLint in Renderer 2017-12-23 00:08:01 -08:00
Christian Paul 8673da8ecb After ESLint in Styler 2017-12-23 00:07:42 -08:00
Christian Paul 804b55076d After ESLint in utils 2017-12-23 00:07:34 -08:00
Christian Paul 088b9a3b71 After ESLint in TileSource 2017-12-23 00:07:23 -08:00
Christian Paul 1369140af4 After ESLint in LabelBuffer 2017-12-23 00:07:12 -08:00
Christian Paul 09142e7a8f Decaffeinate Canvas 2017-12-23 00:06:47 -08:00
Christian Paul 4122e8826b 'use strict' in main.js 2017-12-22 22:40:38 -08:00
Christian Paul bf8ebfd3df Clean up Renderer (still uses bluejay) 2017-12-22 22:40:02 -08:00
Christian Paul f236f95383 Dirty translation of Tile.js 2017-12-22 22:35:26 -08:00
Christian Paul 283c245a88 Non-perfect translation of Renderer.js 2017-12-22 21:33:10 -08:00
Christian Paul 29b5b3ccb1 Decaffeinate TileSource 2017-12-21 02:18:34 -08:00
Christian Paul d27e086758 Decaffeinate BrailleBuffer class 2017-11-24 02:01:05 -08:00
Christian Paul 9d9ef31f7b Make Styler strict 2017-11-24 01:42:09 -08:00
Christian Paul fd40b57f51 Shorten the code by using Array.prototype.find in the Styler. 2017-11-24 00:01:05 -08:00
Christian Paul 554ae593be Unpolished translation of Styler.coffee 2017-11-23 23:43:18 -08:00
Christian Paul 2d22e33ff1 Decaffeinate Mapscii class 2017-11-04 18:32:27 -07:00
Christian Paul 6033f0be3c Decaffeinate utils 2017-11-04 18:18:58 -07:00
Christian Paul 48367926c2 Decaffeinate config 2017-11-04 17:10:50 -07:00
Christian Paul ac784977d9 Decaffeinate LabelBuffer 2017-11-02 01:22:48 -07:00
Michael Straßburger 1251e3f257 ⬆️ updating dependencies 2017-10-25 22:18:09 +02:00
Michael Straßburger 45071fd931 🔖 bumping 0.1.6 2017-10-25 20:04:45 +02:00
Michael Straßburger b65da50d05 👷 optimizing select order of node installations 2017-10-25 20:03:04 +02:00
Leo Arias 490a598d84 Add the packaging metadata to build the mapscii snap 2017-08-09 00:16:54 +00:00
Michael Straßburger 45315d4ccf ⬆️ upgrading to term-mouse 0.2.0 (closes #24) 2017-06-17 00:15:16 +02:00
Alexander Zhukov 5fcf553427 🔬 fix a typo 2017-05-14 18:26:21 +03:00
Michael Straßburger cb42c21812 🔖 bumping 0.1.5 2017-05-14 14:47:49 +02:00
Michael Straßburger 60dfdb8135 📝 adding string-width to readme 2017-05-14 14:47:19 +02:00
Michael Straßburger 5873af5c96 Merge pull request #20 from rastapasta/ascii
Braille-less ASCII mode, using ▀ ▄ █ ▌▐ ■
2017-05-12 23:17:48 +02:00
Michael Straßburger 724e605317 🎨 convert braille idx to bitstring to allow ascii selection 2017-05-12 23:11:42 +02:00
Michael Straßburger 7edde3b30f 🎨 moving poiMarker to config, improving asciiMap 2017-05-12 19:58:39 +02:00
Michael Straßburger 7e255edc53 🎨 adding braille-free mode, activate with 'c' 2017-05-12 18:48:38 +02:00
Michael Straßburger 82c05d6932 Merge pull request #19 from rastapasta/stringwidth
Support for wide characters (Chinese/Japanese/...), cache, speed and general optimization
2017-05-10 14:40:56 +02:00
Michael Straßburger 46692ec1bb adding string-width 2017-05-10 14:40:38 +02:00
Michael Straßburger 03f191edb0 📝 adding reference to PuTTY 2017-05-10 14:35:42 +02:00
Michael Straßburger 7fc618d6d2 📝 adding OSM license to readme 2017-05-10 14:32:45 +02:00
Michael Straßburger 3888293f0e 🎨 welcome message, callbacks, latlng normalization 2017-05-10 14:26:04 +02:00
Michael Straßburger ca6a4e27ae 🏃 moving label selection logic to tile loader 2017-05-10 14:24:11 +02:00
Michael Straßburger 3dffcde3b5 only keep 16 tiles in memory 2017-05-10 14:22:07 +02:00
Michael Straßburger 0e5ce02e79 🎨 support label characters with width>1 2017-05-10 14:16:36 +02:00
Michael Straßburger 0bdb01f706 🎨 adding hint for -> telnet mapscii.me 2017-04-30 01:12:51 +02:00
Michael Straßburger e79d91186b 📝 kudos to @manuelroth 2017-04-29 06:46:54 +02:00
Michael Straßburger 0b6d24126b 📝 kudos to @mourner 2017-04-29 03:49:34 +02:00
Michael Straßburger cd5176d23b 📝 kudos to @lukasmartinelli 2017-04-29 03:44:51 +02:00
36 zmienionych plików z 7483 dodań i 1326 usunięć

36
.eslintrc.js 100644
Wyświetl plik

@ -0,0 +1,36 @@
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"
]
}
};

Wyświetl plik

@ -0,0 +1,26 @@
---
name: "\U0001F41B Bug report"
about: Something isnt 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?
-->

Wyświetl plik

@ -0,0 +1,22 @@
---
name: "\U0001F680 Feature request"
about: I have a suggestion (and might want to implement myself)
---
<!-- Consider opening a pull request instead: its 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 youve considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

Wyświetl plik

@ -0,0 +1,14 @@
---
name: "\U0001F914 Support question"
about: I have a question or dont 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).

Wyświetl plik

@ -0,0 +1,15 @@
---
name: "\U0001F984 Support MapSCIIs 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 isnt 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 (were always happy to review your texts and slides).

9
.travis.yml 100644
Wyświetl plik

@ -0,0 +1,9 @@
language: node_js
node_js:
- "14"
- "16"
- "18"
script:
- npm run-script lint
- npm test

8
AUTHORS 100644
Wyświetl plik

@ -0,0 +1,8 @@
# 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)

Wyświetl plik

@ -1,6 +1,7 @@
MIT License
Copyright (c) 2016 Michael Straßburger
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

Wyświetl plik

@ -1,24 +1,42 @@
# MapSCII - The Whole World In Your Console.
# MapSCII - The Whole World In Your Console. [![Build Status](https://travis-ci.com/rastapasta/mapscii.svg?branch=master)](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">![asciicast](https://cloud.githubusercontent.com/assets/1259904/25480718/497a64e2-2b4a-11e7-9cf0-ed52ee0b89c0.png)</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 [OpenStreetMap](https://en.wikipedia.org/wiki/OpenStreetMap) based one
* 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 optimizied algorithms for a smooth experience
* 100% pure Coffee-/JavaScript! :sunglasses:
* Highly optimized algorithms for a smooth experience
* 100% pure JavaScript! :sunglasses:
## How to install
## How to run it locally
If you haven't already got Node.js >= version 4.5, then [go get it](http://nodejs.org/).
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
@ -26,6 +44,14 @@ 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.
@ -38,6 +64,7 @@ mapscii
* 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
@ -50,6 +77,7 @@ If your terminal supports mouse events you can drag the map and use your scroll
* [`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
@ -59,31 +87,27 @@ If your terminal supports mouse events you can drag the map and use your scroll
#### 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
* [`breseham`](https://github.com/madbence/node-bresenham) for line point calculations
* [`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
* [`bluebird`](https://github.com/petkaantonov/bluebird) for all the asynchronous [Promise](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise) magic
* [`node-fetch`](https://github.com/bitinn/node-fetch) for HTTP requests
* [`userhome`](https://github.com/shama/userhome) to determine where to persist downloaded tiles
* [`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
* [ ] TileSource
* [ ] Style
* [ ] center position
* [ ] zoom
* [-] startup parameters
* [X] TileSource
* [X] Style
* [X] center position
* [X] zoom
* [ ] demo mode?
* [ ] mouse control
* [ ] get hover lat/lng
* [ ] accurate mouse drag&drop with instant update
* [ ] hover POIs/labels
* [ ] hover maybe even polygons/-lines?
* [ ] zoom into mouse pos
* Styler
* [ ] respect zoom based style ranges
@ -97,12 +121,23 @@ If your terminal supports mouse events you can drag the map and use your scroll
* TileSource
* [ ] implement single vector-tile handling
## License
#### The MIT License (MIT)
Copyright (c) 2017 Michael Straßburger
## Special thanks
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:
* [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), ..)
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
## Licenses
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.
### 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)

Wyświetl plik

@ -1,6 +1,6 @@
#!/bin/sh
':' //; # Based on https://github.com/MrRio/vtop/blob/master/bin/vtop.js
':' //; export TERM=xterm-256color
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"
':' //; exec "$(command -v node || command -v nodejs)" "$0" "$@"
'use strict'
require('../main.js');

82
main.js
Wyświetl plik

@ -1,5 +1,5 @@
/*#
mapscii - Terminal Map Viewer
MapSCII - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Discover the planet in your console!
@ -7,9 +7,81 @@
TODO: params parsing and so on
#*/
require('coffee-script/register');
'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;
mapscii = new Mapscii();
mapscii.init();
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);
});

5358
package-lock.json wygenerowano 100644

Plik diff jest za duży Load Diff

Wyświetl plik

@ -1,11 +1,12 @@
{
"name": "mapscii",
"version": "0.1.4",
"description": "Map+Ascii -> MapSCII! Console Map Viewer.",
"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": "echo \"Error: no test specified\" && exit 1"
"test": "jest"
},
"repository": {
"type": "git",
@ -15,7 +16,7 @@
"mapscii": "./bin/mapscii.sh"
},
"engines": {
"node": ">=4.5.0"
"node": ">=10"
},
"keywords": [
"map",
@ -29,18 +30,23 @@
"author": "Michael Straßburger <codepoet@cpan.org>",
"license": "MIT",
"dependencies": {
"bluebird": "^3.4.6",
"@mapbox/vector-tile": "^1.3.1",
"bresenham": "0.0.4",
"coffee-script": "^1.10.0",
"earcut": "^2.1.1",
"earcut": "^2.2.2",
"env-paths": "^2.2.0",
"keypress": "^0.2.1",
"node-fetch": "^1.6.3",
"pbf": "^3.0.0",
"rbush": "^2.0.1",
"simplify-js": "^1.2.1",
"term-mouse": "^0.1.1",
"userhome": "^1.0.0",
"vector-tile": "^1.3.0",
"x256": "0.0.2"
"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"
}
}

Wyświetl plik

@ -0,0 +1,19 @@
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

Wyświetl plik

@ -1,105 +0,0 @@
###
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Simple pixel to barille character mapper
Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)
* added color support
* added support for filled polygons
* added text label support
* general optimizations
-> more bit shifting/operations, less Math.floors
Will either be merged into node-drawille or become an own module at some point
###
module.exports = class BrailleBuffer
characterMap: [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]
pixelBuffer: null
charBuffer: null
foregroundBuffer: null
backgroundBuffer: null
globalBackground: null
termReset: "\x1B[39;49m"
constructor: (@width, @height) ->
size = @width*@height/8
@pixelBuffer = new Buffer size
@foregroundBuffer = new Buffer size
@backgroundBuffer = new Buffer size
@clear()
clear: ->
@pixelBuffer.fill 0
@charBuffer = []
@foregroundBuffer.fill 0
@backgroundBuffer.fill 0
setGlobalBackground: (@globalBackground) ->
setBackground: (x, y, color) ->
return unless 0 <= x < @width and 0 <= y < @height
idx = @_project x, y
@backgroundBuffer[idx] = color
setPixel: (x, y, color) ->
@_locate x, y, (idx, mask) =>
@pixelBuffer[idx] |= mask
@foregroundBuffer[idx] = color
unsetPixel: (x, y) ->
@_locate x, y, (idx, mask) =>
@pixelBuffer[idx] &= ~mask
_project: (x, y) ->
(x>>1) + (@width>>1)*(y>>2)
_locate: (x, y, cb) ->
return unless 0 <= x < @width and 0 <= y < @height
idx = @_project x, y
mask = @characterMap[y&3][x&1]
cb idx, mask
_termColor: (foreground, background) ->
background = background or @globalBackground
if foreground and background
"\x1B[38;5;#{foreground};48;5;#{background}m"
else if foreground
"\x1B[49;38;5;#{foreground}m"
else if background
"\x1B[39;48;5;#{background}m"
else
@termReset
frame: ->
output = []
currentColor = null
delimeter = "\n"
for idx in [0...@pixelBuffer.length]
output.push delimeter if idx and (idx % (@width/2)) is 0
if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx]
output.push currentColor = colorCode
output.push if @charBuffer[idx]
@charBuffer[idx]
else
String.fromCharCode 0x2800+@pixelBuffer[idx]
output.push @termReset+delimeter
output.join ''
setChar: (char, x, y, color) ->
return unless 0 <= x < @width and 0 <= y < @height
idx = @_project x, y
@charBuffer[idx] = char
@foregroundBuffer[idx] = color
writeText: (text, x, y, color, center = true) ->
x -= text.length/2+1 if center
@setChar text.charAt(i), x+i*2, y, color for i in [0...text.length]

Wyświetl plik

@ -0,0 +1,211 @@
/*
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;

Wyświetl plik

@ -1,161 +0,0 @@
###
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
###
bresenham = require 'bresenham'
simplify = require 'simplify-js'
earcut = require 'earcut'
BrailleBuffer = require './BrailleBuffer'
utils = require './utils'
module.exports = class Canvas
stack: []
constructor: (@width, @height) ->
@buffer = new BrailleBuffer @width, @height
frame: ->
@buffer.frame()
clear: ->
@buffer.clear()
text: (text, x, y, color, center = false) ->
@buffer.writeText text, x, y, color, center
line: (from, to, color, width = 1) ->
@_line from.x, from.y, to.x, to.y, color, width
polyline: (points, color, width = 1) ->
for i in [1...points.length]
@_line points[i-1].x, points[i-1].y, points[i].x, points[i].y, width, color
setBackground: (color) ->
@buffer.setGlobalBackground color
background: (x, y, color) ->
@buffer.setBackground x, y, color
polygon: (rings, color) ->
vertices = []
holes = []
for ring in rings
if vertices.length
continue if ring.length < 3
holes.push vertices.length/2
else
return false if ring.length < 3
for point in ring
vertices.push point.x
vertices.push point.y
try
triangles = earcut vertices, holes
catch e
return false
for i in [0...triangles.length] by 3
pa = @_polygonExtract vertices, triangles[i]
pb = @_polygonExtract vertices, triangles[i+1]
pc = @_polygonExtract vertices, triangles[i+2]
@_filledTriangle pa, pb, pc, color
true
_polygonExtract: (vertices, pointId) ->
[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
unless width = Math.max 0, width-1
return bresenham x0, y0, x1, y1,
(x, y) => @buffer.setPixel x, y, color
dx = Math.abs x1-x0
sx = if x0 < x1 then 1 else -1
dy = Math.abs y1-y0
sy = if y0 < y1 then 1 else -1
err = dx-dy
ed = if dx+dy is 0 then 1 else Math.sqrt dx*dx+dy*dy
width = (width+1)/2
loop
@buffer.setPixel x0, y0, color
e2 = err
x2 = x0
if 2*e2 >= -dx
e2 += dy
y2 = y0
while e2 < ed*width && (y1 != y2 || dx > dy)
@buffer.setPixel x0, y2 += sy, color
e2 += dx
break if x0 is x1
e2 = err
err -= dy
x0 += sx
if 2*e2 <= dy
e2 = dx-e2
while e2 < ed*width && (x1 != x2 || dx < dy)
@buffer.setPixel x2 += sx, y0, color
e2 += dy
break if y0 is y1
err += dx
y0 += sy
_filledRectangle: (x, y, width, height, color) ->
pointA = [x, y]
pointB = [x+width, y]
pointC = [x, y+height]
pointD = [x+width, y+height]
@_filledTriangle pointA, pointB, pointC, color
@_filledTriangle pointC, pointB, pointD, color
_bresenham: (pointA, pointB) ->
bresenham pointA[0], pointA[1],
pointB[0], pointB[1]
# Draws a filled triangle
_filledTriangle: (pointA, pointB, pointC, color) ->
a = @_bresenham pointB, pointC
b = @_bresenham pointA, pointC
c = @_bresenham pointA, pointB
points = a.concat(b).concat(c)
.filter (point) => 0 <= point.y < @height
.sort (a, b) -> if a.y is b.y then a.x - b.x else a.y-b.y
for i in [0...points.length]
point = points[i]
next = points[i*1+1]
if point.y is next?.y
left = Math.max 0, point.x
right = Math.min @width-1, next.x
if left >= 0 and right <= @width
@buffer.setPixel x, point.y, color for x in [left..right]
else
@buffer.setPixel point.x, point.y, color
break unless next

202
src/Canvas.js 100644
Wyświetl plik

@ -0,0 +1,202 @@
/*
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;

Wyświetl plik

@ -1,45 +0,0 @@
###
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
###
rbush = require 'rbush'
module.exports = class LabelBuffer
tree: null
margin: 5
constructor: (@width, @height) ->
@tree = rbush()
clear: ->
@tree.clear()
project: (x, y) ->
[Math.floor(x/2), Math.floor(y/4)]
writeIfPossible: (text, x, y, feature, margin = @margin) ->
point = @project x, y
if @_hasSpace text, point[0], point[1]
data = @_calculateArea text, point[0], point[1], margin
data.feature = feature
@tree.insert data
else
false
featuresAt: (x, y) ->
@tree.search minX: x, maxX: x, minY: y, maxY: y
_hasSpace: (text, x, y) ->
not @tree.collides @_calculateArea text, x, y
_calculateArea: (text, x, y, margin = 0) ->
minX: x-margin
minY: y-margin/2
maxX: x+margin+text.length
maxY: y+margin/2

57
src/LabelBuffer.js 100644
Wyświetl plik

@ -0,0 +1,57 @@
/*
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,
};
}
};

Wyświetl plik

@ -1,228 +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()
_initTileSource: ->
@tileSource = new TileSource()
@tileSource.init config.source
_initKeyboard: ->
keypress config.input
config.input.setRawMode true
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.tile2ll center.x+(dx/size), center.y+(dy/size), z
_onClick: (event) ->
@_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) ->
# 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) ->
# check if the pressed key is configured
draw = switch key?.name
when "q"
process.exit 0
when "w" then @zoomy = 1
when "s" then @zoomy = -1
when "a" then @zoomBy config.zoomStep
when "z" then @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
else
null
if draw isnt null
@_draw()
else
# display debug info for unhandled keys
@notify JSON.stringify key
_draw: ->
@renderer
.draw @center, @zoom
.then (frame) =>
@_write frame
@notify @_getFooter()
.catch =>
@notify "renderer is busy"
.then =>
if @zoomy
if (@zoomy > 0 and @zoom < config.maxZoom) or (@zoomy < 0 and @zoom > @minZoom)
@zoom += @zoomy * config.zoomStep
else
@zoomy *= -1
setImmediate => @_draw()
_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) ->
@_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) ->
lon += 360 if lon < -180
lon -= 360 if lon > 180
lat = 85.0511 if lat > 85.0511
lat = -85.0511 if lat < -85.0511
@center.lat = lat
@center.lon = lon

313
src/Mapscii.js 100644
Wyświetl plik

@ -0,0 +1,313 @@
/*
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;

Wyświetl plik

@ -1,273 +0,0 @@
###
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
The Console Vector Tile renderer - bäm!
###
Promise = require 'bluebird'
x256 = require 'x256'
simplify = require 'simplify-js'
Canvas = require './Canvas'
LabelBuffer = require './LabelBuffer'
Styler = require './Styler'
Tile = require './Tile'
utils = require './utils'
config = require './config'
module.exports = class Renderer
terminal:
CLEAR: "\x1B[2J"
MOVE: "\x1B[?6h"
isDrawing: false
lastDrawAt: 0
labelBuffer: null
tileSource: null
tilePadding: 64
constructor: (@output, @tileSource) ->
@labelBuffer = new LabelBuffer()
loadStyleFile: (file) ->
@styler = new Styler file
@tileSource.useStyler @styler
setSize: (@width, @height) ->
@canvas = new Canvas @width, @height
draw: (center, zoom) ->
return Promise.reject() if @isDrawing
@isDrawing = true
@labelBuffer.clear()
@_seen = {}
if color = @styler.styleById['background']?.paint['background-color']
@canvas.setBackground x256 utils.hex2rgb color
@canvas.clear()
Promise
.resolve @_visibleTiles center, zoom
.map (tile) => @_getTile tile
.map (tile) => @_getTileFeatures tile, zoom
.then (tiles) => @_renderTiles tiles
.then => @_getFrame()
.catch (e) ->
console.log e
.finally (frame) =>
@isDrawing = false
@lastDrawAt = Date.now()
frame
_visibleTiles: (center, zoom) ->
z = utils.baseZoom zoom
center = utils.ll2tile center.lon, center.lat, z
tiles = []
tileSize = utils.tilesizeAtZoom zoom
for y in [Math.floor(center.y)-1..Math.floor(center.y)+1]
for x in [Math.floor(center.x)-1..Math.floor(center.x)+1]
tile = x: x, y: y, z: z
position =
x: @width/2-(center.x-tile.x)*tileSize
y: @height/2-(center.y-tile.y)*tileSize
gridSize = Math.pow 2, z
tile.x %= gridSize
if tile.x < 0
tile.x = if z is 0 then 0 else tile.x+gridSize
if tile.y < 0 or
tile.y >= gridSize or
position.x+tileSize < 0 or
position.y+tileSize < 0 or
position.x>@width or
position.y>@height
continue
tiles.push xyz: tile, zoom: zoom, position: position, size: tileSize
tiles
_getTile: (tile) ->
@tileSource
.getTile tile.xyz.z, tile.xyz.x, tile.xyz.y
.then (data) =>
tile.data = data
tile
_getTileFeatures: (tile, zoom) ->
position = tile.position
layers = {}
for layerId in @_generateDrawOrder zoom
continue unless layer = tile.data.layers?[layerId]
scale = layer.extent / utils.tilesizeAtZoom zoom
layers[layerId] =
scale: scale
features: layer.tree.search
minX: -position.x*scale
minY: -position.y*scale
maxX: (@width-position.x)*scale
maxY: (@height-position.y)*scale
tile.layers = layers
tile
_renderTiles: (tiles) ->
drawn = {}
labels = []
for layerId in @_generateDrawOrder tiles[0].xyz.z
for tile in tiles
continue unless layer = tile.layers[layerId]
for feature in layer.features
# 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
@_drawFeature tile, feature, layer.scale
labels.sort (a, b) ->
if a.feature.properties.localrank
a.feature.properties.localrank-b.feature.properties.localrank
else
a.feature.properties.scalerank-b.feature.properties.scalerank
for label in labels
@_drawFeature label.tile, label.feature, label.scale
_getFrame: ->
frame = ""
frame += @terminal.CLEAR unless @lastDrawAt
frame += @terminal.MOVE
frame += @canvas.frame()
frame
featuresAt: (x, y) ->
@labelBuffer.featuresAt x, y
_drawFeature: (tile, feature, scale) ->
if feature.style.minzoom and tile.zoom < feature.style.minzoom
return false
else if feature.style.maxzoom and tile.zoom > feature.style.maxzoom
return false
switch feature.style.type
when "line"
width = feature.style.paint['line-width']
# TODO: apply the correct zoom based value
width = width.stops[0][1] if width instanceof Object
points = @_scaleAndReduce tile, feature, feature.points, scale
@canvas.polyline points, feature.color, width if points.length
when "fill"
points = (@_scaleAndReduce tile, feature, p, scale, false for p in feature.points)
@canvas.polygon points, feature.color
when "symbol"
text = feature.properties["name_"+config.language] or
feature.properties["name_en"] or
feature.properties["name"] or
feature.properties.house_num or
genericSymbol = ""
return false if @_seen[text] and not genericSymbol
placed = false
for point in @_scaleAndReduce tile, feature, feature.points, scale
x = point.x - text.length
margin = config.layers[feature.layer]?.margin or config.labelMargin
if @labelBuffer.writeIfPossible text, x, point.y, feature, margin
@canvas.text text, x, point.y, feature.color
placed = true
break
else if config.layers[feature.layer]?.cluster and
@labelBuffer.writeIfPossible "", point.x, point.y, feature, 3
@canvas.text "", point.x, point.y, feature.color
placed = true
break
@_seen[text] = true if placed
true
_scaleAndReduce: (tile, feature, points, scale, filter = true) ->
lastX = lastY = outside = null
scaled = []
minX = minY = -@tilePadding
maxX = @width+@tilePadding
maxY = @height+@tilePadding
for point in points
x = Math.floor tile.position.x+(point.x/scale)
y = Math.floor tile.position.y+(point.y/scale)
continue if lastX is x and lastY is y
lastY = y
lastX = x
if filter
if x < minX or x > maxX or y < minY or y > maxY
continue if outside
outside = true
else
if outside
outside = null
scaled.push x: lastX, y: lastY
scaled.push x: x, y: y
if feature.style.type isnt "symbol"
if scaled.length < 2
return []
if config.simplifyPolylines
simplify scaled, .5, true
else
scaled
else
scaled
_generateDrawOrder: (zoom) ->
if zoom < 2
[
"admin"
"water"
"country_label"
"marine_label"
]
else
[
"landuse"
"water"
"marine_label"
"building"
"road"
"admin"
"country_label"
"state_label"
"water_label"
"place_label"
"rail_station_label"
"poi_label"
"road_label"
"housenum_label"
]

335
src/Renderer.js 100644
Wyświetl plik

@ -0,0 +1,335 @@
/*
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;

Wyświetl plik

@ -1,112 +0,0 @@
###
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.
###
fs = require 'fs'
module.exports = class Styler
styleById: {}
styleByLayer: {}
constructor: (file) ->
json = JSON.parse fs.readFileSync(file).toString()
@styleName = json.name
@_replaceConstants json.constants, json.layers if json.constants
for style in json.layers
if style.ref and @styleById[style.ref]
for ref in ['type', 'source-layer', 'minzoom', 'maxzoom', 'filter']
if @styleById[style.ref][ref] and not style[ref]
style[ref] = @styleById[style.ref][ref]
style.appliesTo = @_compileFilter style.filter
@styleByLayer[style['source-layer']] ?= []
@styleByLayer[style['source-layer']].push style
@styleById[style.id] = style
getStyleFor: (layer, feature, zoom) ->
return false unless @styleByLayer[layer]
for style in @styleByLayer[layer]
if style.appliesTo feature
return style
return false
_replaceConstants: (constants, tree) ->
for id, node of tree
switch typeof node
when 'object'
continue if node.constructor.name.match /Stream/
@_replaceConstants constants, node
when 'string'
if node.charAt(0) is '@'
tree[id] = constants[node]
null
_compileFilter: (filter) ->
switch filter?[0]
when "all"
filters = (@_compileFilter subFilter for subFilter in filter[1..])
(feature) ->
return false for appliesTo in filters when not appliesTo feature
true
when "any"
filters = (@_compileFilter subFilter for subFilter in filter[1..])
(feature) ->
return true for appliesTo in filters when appliesTo feature
false
when "none"
filters = (@_compileFilter subFilter for subFilter in filter[1..])
(feature) ->
return false for appliesTo in filters when appliesTo feature
true
when "=="
(feature) -> feature.properties[filter[1]] is filter[2]
when "!="
(feature) -> feature.properties[filter[1]] isnt filter[2]
when "in"
(feature) ->
return true for value in filter[2..] when feature.properties[filter[1]] is value
false
when "!in"
(feature) ->
return false for value in filter[2..] when feature.properties[filter[1]] is value
true
when "has"
(feature) -> !!feature.properties[filter[1]]
when "!has"
(feature) -> !feature.properties[filter[1]]
when ">"
(feature) -> feature.properties[filter[1]] > filter[2]
when ">="
(feature) -> feature.properties[filter[1]] >= filter[2]
when "<"
(feature) -> feature.properties[filter[1]] < filter[2]
when "<="
(feature) -> feature.properties[filter[1]] <= filter[2]
else
-> true

133
src/Styler.js 100644
Wyświetl plik

@ -0,0 +1,133 @@
/*
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;

Wyświetl plik

@ -1,140 +0,0 @@
###
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Handling of and access to single VectorTiles
###
VectorTile = require('vector-tile').VectorTile
Protobuf = require 'pbf'
Promise = require 'bluebird'
zlib = require 'zlib'
rbush = require 'rbush'
x256 = require 'x256'
earcut = require 'earcut'
utils = require "./utils"
class Tile
layers: {}
constructor: (@styler) ->
load: (buffer) ->
@_unzipIfNeeded buffer
.then (buffer) => @_loadTile buffer
.then => @_loadLayers()
.then => this
_loadTile: (buffer) ->
@tile = new VectorTile new Protobuf buffer
_unzipIfNeeded: (buffer) ->
new Promise (resolve, reject) =>
if @_isGzipped buffer
zlib.gunzip buffer, (err, data) ->
return reject err if err
resolve data
else
resolve buffer
_isGzipped: (buffer) ->
buffer.slice(0,2).indexOf(Buffer.from([0x1f, 0x8b])) is 0
_loadLayers: () ->
layers = {}
colorCache = {}
for name, layer of @tile.layers
nodes = []
#continue if name is "water"
for i in [0...layer.length]
# TODO: caching of similar attributes to avoid looking up the style each time
#continue if @styler and not @styler.getStyleFor layer, feature
feature = layer.feature i
feature.properties.$type = type = [undefined, "Point", "LineString", "Polygon"][feature.type]
if @styler
style = @styler.getStyleFor name, feature
continue unless style
color =
style.paint['line-color'] or
style.paint['fill-color'] or
style.paint['text-color']
# TODO: style zoom stops handling
if color instanceof Object
color = color.stops[0][1]
colorCode = colorCache[color] or 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
geometries = feature.loadGeometry() #@_reduceGeometry feature, 8
if style.type is "fill"
nodes.push @_addBoundaries true,
id: feature.id
layer: name
style: style
properties: feature.properties
points: geometries
color: colorCode
else
for points in geometries
nodes.push @_addBoundaries false,
id: feature.id
layer: name
style: style
properties: feature.properties
points: points
color: colorCode
tree = rbush 18
tree.load nodes
layers[name] =
extent: layer.extent
tree: tree
@layers = layers
_addBoundaries: (deep, data) ->
minX = Infinity
maxX = -Infinity
minY = Infinity
maxY = -Infinity
for p in (if deep then data.points[0] else data.points)
minX = p.x if p.x < minX
maxX = p.x if p.x > maxX
minY = p.y if p.y < minY
maxY = p.y if p.y > maxY
data.minX = minX
data.maxX = maxX
data.minY = minY
data.maxY = maxY
data
_reduceGeometry: (feature, factor) ->
for points, i in feature.loadGeometry()
reduced = []
last = null
for point in points
p =
x: Math.floor point.x/factor
y: Math.floor point.y/factor
if last and last.x is p.x and last.y is p.y
continue
reduced.push last = p
reduced
module.exports = Tile

167
src/Tile.js 100644
Wyświetl plik

@ -0,0 +1,167 @@
/*
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;

Wyświetl plik

@ -1,123 +0,0 @@
###
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Source for VectorTiles - supports
* remote TileServer
* local MBTiles and VectorTiles
###
Promise = require 'bluebird'
userhome = require 'userhome'
fetch = require 'node-fetch'
fs = require 'fs'
Tile = require './Tile'
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 mbtiles
MBTiles = try
require 'mbtiles'
catch
null
module.exports = class TileSource
cache: {}
modes:
MBTiles: 1
VectorTile: 2
HTTP: 3
mode: null
mbtiles: null
styler: null
init: (@source) ->
if @source.startsWith "http"
@_initPersistence() if config.persistDownloadedTiles
@mode = @modes.HTTP
else if @source.endsWith ".mbtiles"
unless MBTiles
throw new Error "MBTiles support must be installed with following command: 'npm install -g mbtiles'"
@mode = @modes.MBTiles
@loadMBtils source
else
throw new Error "source type isn't supported yet"
loadMBtils: (source) ->
new Promise (resolve, reject) =>
new MBTiles source, (err, @mbtiles) =>
if err then reject err
else resolve()
useStyler: (@styler) ->
getTile: (z, x, y) ->
unless @mode
throw new Error "no TileSource defined"
z = Math.max 0, Math.floor z
if cached = @cache[[z,x,y].join("-")]
return Promise.resolve cached
switch @mode
when @modes.MBTiles then @_getMBTile z, x, y
when @modes.HTTP then @_getHTTP z, x, y
_getHTTP: (z, x, y) ->
promise =
if config.persistDownloadedTiles and tile = @_getPersited z, x, y
Promise.resolve tile
else
fetch @source+[z,x,y].join("/")+".pbf"
.then (res) => res.buffer()
.then (buffer) =>
@_persistTile z, x, y, buffer if config.persistDownloadedTiles
buffer
promise
.then (buffer) =>
@_createTile z, x, y, buffer
_getMBTile: (z, x, y) ->
new Promise (resolve, reject) =>
@mbtiles.getTile z, x, y, (err, buffer) =>
return reject err if err
resolve @_createTile z, x, y, buffer
_createTile: (z, x, y, buffer) ->
tile = @cache[[z,x,y].join("-")] = new Tile @styler
tile.load buffer
_initPersistence: ->
try
@_createFolder userhome ".mapscii"
@_createFolder userhome ".mapscii", "cache"
catch error
config.persistDownloadedTiles = false
return
_persistTile: (z, x, y, buffer) ->
zoom = z.toString()
@_createFolder userhome ".mapscii", "cache", zoom
fs.writeFile userhome(".mapscii", "cache", zoom, "#{x}-#{y}.pbf"), buffer, -> null
_getPersited: (z, x, y) ->
try
fs.readFileSync userhome ".mapscii", "cache", z.toString(), "#{x}-#{y}.pbf"
catch error
false
_createFolder: (path) ->
try
fs.mkdirSync path
true
catch e
e.code is "EEXIST"

178
src/TileSource.js 100644
Wyświetl plik

@ -0,0 +1,178 @@
/*
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 MapSCIIs 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;

Wyświetl plik

@ -0,0 +1,12 @@
'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);
});
});
});

Wyświetl plik

@ -1,35 +0,0 @@
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
simplifyPolylines: false
# 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

59
src/config.js 100644
Wyświetl plik

@ -0,0 +1,59 @@
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: '◉',
};

Wyświetl plik

@ -1,59 +0,0 @@
###
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
methods used all around
###
config = require './config'
constants =
RADIUS: 6378137
utils =
clamp: (num, min, max) ->
if num <= min then min else if num >= max then max else num
baseZoom: (zoom) ->
Math.min config.tileRange, Math.max 0, Math.floor zoom
tilesizeAtZoom: (zoom) ->
config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom))
deg2rad: (angle) ->
# (angle / 180) * Math.PI
angle * 0.017453292519943295
ll2tile: (lon, lat, zoom) ->
x: (lon+180)/360*Math.pow(2, zoom)
y: (1-Math.log(Math.tan(lat*Math.PI/180)+1/Math.cos(lat*Math.PI/180))/Math.PI)/2*Math.pow(2, zoom)
z: zoom
tile2ll: (x, y, zoom) ->
n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom)
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) ->
(Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom))
hex2rgb: (color) ->
return [255, 0, 0] unless color?.match
unless color.match /^#[a-fA-F0-9]{3,6}$/
throw new Error "#{color} isn\'t a supported hex color"
color = color.substr 1
decimal = parseInt color, 16
if color.length is 3
rgb = [decimal>>8, (decimal>>4)&15, decimal&15]
rgb.map (c) => c + (c<<4)
else
[(decimal>>16)&255, (decimal>>8)&255, decimal&255]
digits: (number, digits) ->
Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)
module.exports = utils

103
src/utils.js 100644
Wyświetl plik

@ -0,0 +1,103 @@
/*
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;

47
src/utils.spec.js 100644
Wyświetl plik

@ -0,0 +1,47 @@
'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);
});
});
});