├── .travis.yml ├── .gitignore ├── example ├── berlin.js └── berlin.svg ├── map.js ├── projection.js ├── .editorconfig ├── test.js ├── docs ├── demo.js └── index.html ├── license.md ├── package.json ├── index.js └── readme.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - 'lts/*' 5 | - '6' 6 | script: 7 | - npm run build 8 | - npm test 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | .nvm-version 5 | node_modules 6 | npm-debug.log 7 | 8 | /package-lock.json 9 | 10 | data.json 11 | meta.json 12 | -------------------------------------------------------------------------------- /example/berlin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const toHTML = require('virtual-dom-stringify') 4 | 5 | const map = require('..') 6 | 7 | process.stdout.write(toHTML(map(13.34, 52.54))) 8 | -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const data = require('./data.json') 4 | const toVdom = require('vdom-as-json/fromJson') 5 | 6 | const map = toVdom(data) 7 | 8 | module.exports = map 9 | -------------------------------------------------------------------------------- /projection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mercator = require('projections/mercator') 4 | 5 | const projection = ([lon, lat]) => { 6 | const {x, y} = mercator({lon, lat}, {latLimit: 80}) 7 | return [ 8 | +(x * 100).toFixed(3), 9 | +(y * 100).toFixed(3) 10 | ] 11 | } 12 | 13 | module.exports = projection 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | # Use tabs in JavaScript and JSON. 11 | [**.{js, json}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | # Use spaces in YAML. 16 | [**.{yml,yaml}] 17 | indent_style = spaces 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const toHTML = require('virtual-dom-stringify') 6 | const disparity = require('disparity') 7 | 8 | const map = require('.') 9 | const fixture = fs.readFileSync(path.join(__dirname, 'example/berlin.svg'), 'utf8') 10 | 11 | const assertEqualString = (a, b) => { 12 | if (a === b) return 13 | process.stdout.write(disparity.chars(a, b) + '\n') 14 | process.exit(1) 15 | } 16 | 17 | 18 | 19 | const generated = toHTML(map(13.34, 52.54)) 20 | 21 | assertEqualString(generated, fixture) 22 | -------------------------------------------------------------------------------- /docs/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const stringify = require('virtual-dom-stringify') 4 | const map = require('..') 5 | 6 | 7 | 8 | const lat = document.querySelector('#lat') 9 | const lon = document.querySelector('#lon') 10 | const demo = document.querySelector('#demo') 11 | 12 | const render = () => { 13 | const data = 'data:image/svg+xml,' + 14 | encodeURIComponent(stringify(map(+lon.value, +lat.value, {ocean: 'transparent'}))) 15 | demo.innerHTML = `` 16 | } 17 | 18 | lat.addEventListener('change', render) 19 | lon.addEventListener('change', render) 20 | setTimeout(render, 0) 21 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Jannis R 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-world-map", 3 | "description": "Show a location on a world map.", 4 | "version": "1.0.1", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js", 8 | "projection.js", 9 | "map.js", 10 | "data.json", 11 | "meta.json" 12 | ], 13 | "keywords": [ 14 | "svg", 15 | "world", 16 | "map", 17 | "location", 18 | "pin", 19 | "dataviz", 20 | "vector" 21 | ], 22 | "author": "Jannis R ", 23 | "homepage": "http://derhuerst.github.io/svg-world-map/", 24 | "repository": "derhuerst/svg-world-map", 25 | "bugs": "https://github.com/derhuerst/svg-world-map/issues", 26 | "license": "ISC", 27 | "engines": { 28 | "node": ">=6" 29 | }, 30 | "dependencies": { 31 | "projections": "^1.0.0", 32 | "virtual-dom": "^2.1.1", 33 | "vdom-as-json": "^1.0.9" 34 | }, 35 | "devDependencies": { 36 | "@turf/bbox": "^6.0.0", 37 | "@turf/simplify": "^5.0.4", 38 | "browserify": "^16.0.0", 39 | "disparity": "^2", 40 | "es2020": "^1.1.9", 41 | "geojson-svgify": "^0.5", 42 | "node-fetch": "^2.0.0", 43 | "uglify-es": "^3.0.25", 44 | "virtual-dom-stringify": "^3.0.1", 45 | "virtual-hyperscript-svg": "^2" 46 | }, 47 | "scripts": { 48 | "build": "node build.js", 49 | "test": "node test.js", 50 | "demo:bundle": "browserify -g es2020 docs/demo.js > docs/bundle.js", 51 | "demo:minify": "uglifyjs -mc --screw-ie8 -- docs/bundle.js > docs/bundle.min.js", 52 | "demo": "npm run demo:bundle && npm run demo:minify" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const svg = require('virtual-dom/virtual-hyperscript/svg') 4 | const h = require('virtual-dom/h') 5 | const meta = require('./meta.json') 6 | const map = require('./map') 7 | const projection = require('./projection') 8 | 9 | 10 | 11 | const pinRatio = 5 / 8 12 | const pin = svg('symbol', {id: 'pin', viewBox: '0 0 5 8'}, [ 13 | svg('ellipse', { 14 | rx: '.5', ry: '.3', 15 | cy: '7.7', cx: '2.5', 16 | fill: '#555' 17 | }), 18 | svg('path', { 19 | d: 'M2.5 7.7 L 4.665 3.75 A 2.5 2.5 0 1 0 0.335 3.75 L 2.5 7.7', 20 | fill: '#cd4646' 21 | }), 22 | svg('circle', {r: '.9', cy: '2.5', cx: '2.5', fill: 'black'}) 23 | ]) 24 | 25 | 26 | 27 | const defaults = { 28 | ocean: '#476F9C', // color of the ocean 29 | land: 'white', // color of the land 30 | mapWidth: 500, // width of the `` 31 | pin, // virtual dom node with the pin 32 | pinHeight: 8 // relative to map viewBox 33 | } 34 | 35 | const render = (lon, lat, opt = {}) => { 36 | opt = Object.assign({}, defaults, opt) 37 | 38 | const mapWidth = opt.mapWidth 39 | const mapHeight = mapWidth * meta.height / meta.width 40 | 41 | const pinHeight = opt.pinHeight 42 | const pinWidth = pinRatio * pinHeight 43 | let [pinX, pinY] = projection([lon, lat]) 44 | pinY -= pinHeight 45 | pinX -= pinWidth / 2 46 | 47 | return svg('svg', { 48 | xmlns: 'http://www.w3.org/2000/svg', 49 | 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 50 | width: mapWidth + '', height: mapHeight + '', 51 | viewBox: [meta.left, meta.top, meta.width, meta.height].join(',') 52 | }, [].concat( 53 | h('style', {}, ` 54 | .country { 55 | fill: ${opt.land}; 56 | stroke: #555; 57 | stroke-width: .1; 58 | } 59 | `), 60 | svg('rect', { 61 | width: mapWidth + '', height: mapHeight + '', 62 | fill: opt.ocean 63 | }), 64 | map, 65 | svg('defs', {}, [opt.pin]), 66 | svg('use', { 67 | 'xlink:href': '#pin', href: '#pin', 68 | x: pinX + '', y: pinY + '', 69 | width: pinWidth + '', height: pinHeight + '' 70 | }) 71 | )) 72 | } 73 | 74 | module.exports = render 75 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [svg-world-map](https://derhuerst.github.io/svg-world-map/) 2 | 3 | **Render a world map with a pin at a specific location.** Fiddle with it on [the website](https://derhuerst.github.io/svg-world-map/). 4 | 5 | ![map with pin at Berlin](example/berlin.svg) 6 | 7 | [![npm version](https://img.shields.io/npm/v/svg-world-map.svg)](https://www.npmjs.com/package/svg-world-map) 8 | [![build status](https://img.shields.io/travis/derhuerst/svg-world-map.svg)](https://travis-ci.org/derhuerst/svg-world-map) 9 | [![dependency status](https://img.shields.io/david/derhuerst/svg-world-map.svg)](https://david-dm.org/derhuerst/svg-world-map) 10 | [![dev dependency status](https://img.shields.io/david/dev/derhuerst/svg-world-map.svg)](https://david-dm.org/derhuerst/svg-world-map#info=devDependencies) 11 | ![ISC-licensed](https://img.shields.io/github/license/derhuerst/svg-world-map.svg) 12 | [![chat on gitter](https://badges.gitter.im/derhuerst.svg)](https://gitter.im/derhuerst) 13 | [![support me on Patreon](https://img.shields.io/badge/support%20me-on%20patreon-fa7664.svg)](https://patreon.com/derhuerst) 14 | 15 | `svg-world-map` returns a [virtual-dom](https://github.com/Matt-Esch/virtual-dom#dom-model) `` node. You can either stringify it into a file or embed it into your Frontend stack. 16 | 17 | Note that because shapes of all countries are quite a lot of data, this module weighs **roughly `43k`** when [browserified](http://browserify.org), [minified](https://github.com/mishoo/UglifyJS2#uglifyjs-2) and gzipped. 18 | 19 | The data is from [world.geo.json](https://github.com/johan/world.geo.json). 20 | 21 | 22 | ## Installing 23 | 24 | ```shell 25 | npm install svg-world-map 26 | ``` 27 | 28 | 29 | ## Usage 30 | 31 | ```js 32 | const map = require('svg-world-map') 33 | const stringify = require('virtual-dom-stringify') 34 | 35 | const myMap = map(81.8, 28.4) // Nepal 36 | 37 | process.stdout.write(stringify(myMap)) 38 | ``` 39 | 40 | 41 | ## API 42 | 43 | ``` 44 | map(longitude, latitude, [opt]) 45 | ``` 46 | 47 | `opt` is optional and has the following default values: 48 | 49 | ```js 50 | const defaults = { 51 | ocean: '#8df', // color of the ocean 52 | land: 'white', // color of the land 53 | mapWidth: 500, // width of the `` 54 | pin, // virtual dom node with the pin 55 | pinHeight: 8 // relative to map viewBox 56 | } 57 | ``` 58 | 59 | 60 | ## See also 61 | 62 | - [`svg-patterns`](https://github.com/derhuerst/svg-patterns) – Create SVG patterns programmatically to visualize data. 63 | - [`svg-radar-chart`](https://github.com/derhuerst/svg-radar-chart) – A reusable radar chart in SVG. 64 | 65 | 66 | ## Contributing 67 | 68 | `npm test` is a regression test: It compares the generated output, to a `example/berlin.svg`, which has been manually checked by me. If you introduce a change that changes the output, *check it manually* and commit it as `example/berlin.svg`. 69 | 70 | If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/derhuerst/svg-world-map/issues). 71 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | svg-world-map 6 | 7 | 8 | 9 | 10 | 86 | 87 |

svg-world-map

88 |

Show a location on a world map. 43k minified & gzipped.

89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | 102 | 106 |
107 |
108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /example/berlin.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------