svgeomin
v0.1.3
Published
Create compact map SVGs and GeoJson subsets
Maintainers
Readme
Key features
svgeomin has a strong focus on SVG application – most importantly creating lightweight SVG map assets. However, it also comes with some handy geoJson helpers to reduce a given data source to the actually needed geo features.
- topology aware Ramer-Douglas-Peucker polygon simplification for Geojson supsets and SVG
- removal of small sub features e.g islands or exclaves
- advanced SVG pathData minification (e.g relative commands)
- multiple projection modes: Web Mercator, Miller, Behrmann, Equi-rectangular
- property based filtering of geodata feautures for subset creation
- convert/revert SVG to geoJson
- add markers to SVG using common lon/lat coordinates
TOC
- Key features
- The challenges of geodata (why another library)
- Usage
- Options
- SVGEO object and API methods
- Topology aware simplification
- Demos
- Credits
The challenges of geodata (why another library)
Geojsons are most often massive – 10K+ of coordinates are rather the lower end of the scale. For reasonably sized SVG assets, geometry simplifications are rather mandatory. But when we apply these (Ramer Douglas Peucker, Visvalingam etc) for each feature (e.g country border) individually we often get gaps between polygon edges.
Advanced map/geodata or data visualization libraries – e.g d3 – have developed sophisticated solutions such as the TopoJson superset for specifying shared polygon edges to allow for predictable simplification results.
However, these libraries are more focused on map specific use cases and highly complex. Besides, they often provide only basic control over the SVG output – resulting in rather huge markup sizes.
To put it differently:
svgeomin might be interesting if …
- you just need a convenient way to create compact svg maps
- shrink a massive geojson to used features
not very suitable for you if …
- if you're already a d3 or map pro
- your focus is on interactive map applications
Usage
Svgeomin can be loaded as IIFE or ESM module. For testing you can require it via CDN e.g.
CDN
IIFE
<script src="https://cdn.jsdelivr.net/npm/potrace-plus@latest/dist/potrace-plus.min.js"></script>
ESM
import { svgFromGeo } from "https://cdn.jsdelivr.net/npm/svgeomin@latest/dist/svgeomin.esm.min.js";Todos
- Node.js is currently not supported as some helpers require DOM Parser API.
Basic example: render from src URL
Svgeomin allows multiple input formats:
- geojson URL (requires async function call)
- stringified geojson
- parsed geojson object
// ESM import – not needed for IIFE build
import { svgFromGeo } from "./dist/svgeomin.esm.js";
// static geojson asset
let geoDataUrl = 'geoData.geojson';
// needs async when loading from URL
(async () => {
// process
let svGeo = await svgFromGeo(geoDataUrl);
// render/append to HTML target element
let target = document.getElementById('svgeoWrap');
svGeo.render(target)
})();See basic.html.
With options: Filter features and simplify
You can filter geodata features by property names e.g to show only a selection of countries. Also, you can apply multiple options e.g for Ramer-Douglas-Peucker simplification.
// ESM import – not needed for IIFE build
import { svgFromGeo } from "./dist/svgeomin.esm.js";
// static geojson asset
let geoDataUrl = 'geoData.geojson';
// needs async when loading from URL
(async () => {
let options = {
scale: 10000,
// remove small areas e.g island
minArea: 1,
// filter to these features
features: ['germany', 'switzerland', 'austria'],
// filter properties
properties: ['name'],
}
// process
let svGeo = await svgFromGeo(geoDataUrl, options);
// render/append to HTML target element
let target = document.getElementById('svgeoWrap');
svGeo.render(target)
})();See demo/options.html.
Options
| parameter | type | description | default/values |
|--|--|--|--|
|features|array|features to filter|empty|
|properties|array|properties to include in filtered Geojson and SVG output|empty|
|exclude|array|exclude feature items by property values|empty|
|scale|number|scale to reasonable coordinate space to avoid floating points and tiny SVG viewBoxes|10000|
|simplify|number|threshold for RDP simplification ~in km|0|
|minArea|number|remove small feautes e.g islands or exclaves by km² threshold (sloppy area approximation) |0|
|split|number/Boolean|create path el for each sub poly e.g islands |0|
|meta|number/Boolean|add meta for original geodata reference in SVG |0|
|classPre|string|CSS classname prefix for SVG elements |'svgeomin'|
|css|string|append CSS <style> element to SVG |''|
|cssInline|string|main svg inline css |''|
|projection|string |changes projection method | 'mercator' (Web Mercator). mercator,miller, equirectangular (Plate carrée), behrmann |projection method. See wikipedia: List of map projections |
|markers|array| add map markers to SVG |empty|
|marker params|| ||
|lon|number| longitude |0|
|lat|number| latitude |0|
|icon|string| Add custom SVG icon: Accepts SVG markup or pathData strings |'' – inserts default marker icon|
|bb|array| controls alignment of custom icon: x, y, width, height |[0,0,24,24]|
|width|number| controls size of marker icon |24|
|styles|object/string| Add CSS properties for markers: CSS string or object. When using objects you need to camleCase property names (e.g strokeWidth) |''|
|meta|object| adds properties as data-attributes to marker element |0|
Topology aware simplification
When applying polygon simplification algorithms (e.g Ramer Douglas Peucker) on adjacent/neighboring polygons we often get gaps between shapes.
To prevent this we first analyze the topology of all filtered features to detect shared polygon arcs to ensure a consistent edge simplification.
SVGEO object and API methods
Once you parsed the geoJson via svgFromGeo() an object is created which allows further processing:
// init object
const SVGEO = await svgFromGeo(geoDataUrl, options);
// retrieve properties directly from object
let { svg, bb, x, y, bbGeo, scale, size } = SVGEO;
// render
SVGEO.render(svgeoWrap)
// get GeoJson
let geojsonOptions = {
// round to decimals
decimals:4,
// name for feature collection
name: 'svgeomin',
// properties to include
properties:[],
}
let geojson = SVGEO.getGeoJson(geojsonOptions)
/**
* get object or data
* URLs for download
*/
let urlOptions = {
// return svg or json
data='svg',
//round to decimals
decimals=3
// return dataURL or object URL
dataUrl=true,
// add width and height attributes for SVG
addDimensions=true
// add SVG xlink namespace for legacy apps
addXlink=false
}
let dataUrl = SVGEO.getUrl(urlOptions)
erm, but I still see tiny gaps?
- The aforementioned topology simplification takes for granted the GeoData itself doesn't have any gaps
- if you notice thin hairlines in SVG rendering: it is simply due to sub-pixel rendering. Anti-aliasing will inevitable produce tiny gaps due to pixel-grid fitting problems.
Quick fix
- Apply a thin stroke to your paths
- disable anti-aliasing via SVG
shape-renderingattribute:shape-rendering="crispEdges"should do the trick.
Demos
- SVG from GeoJson (basic) | codepen
- Apply options: filter and simplify | codepen
- filtered and add markers
- Get filtered and minified GeoJson (only selected features)
- SVG to GeoJson (reverse projection)
- Geosearch helper (find lon/lat, show geojson on map)
Credits
- Sample geojson was retrieved from nvkelso's natural-earth-vector
- Leaflet: Volodymyr Agafonkin and contributors. Leaflet is used in geojson sample rendering and geo search helper
- osm-search/nominatim – used in geo search helper
Recommendations (tools and documentations)
- geojson.io: A webapp to inspect and edit geojson data
- svg-path-editor: A webapp to inspect and edit SVG pathdata
- wikipedia: List of map projections
Related projects
- poly-simplify: Simplify/reduce polylines/polygon vertices in JS
- svg-path-simplify: Simplify SVG Bézier paths while maintaining their shape
