Skip to content

Instantly share code, notes, and snippets.

@slattery
Last active March 12, 2020 13:59
Show Gist options
  • Save slattery/0c99d7f23e7d0ed913a5506b005eb6a3 to your computer and use it in GitHub Desktop.
Save slattery/0c99d7f23e7d0ed913a5506b005eb6a3 to your computer and use it in GitHub Desktop.
world map svgs from natural earth

Bash script to generate world map svgs, with a given grouping highlighted by color. Since this is for groupings and not detail, the script removes Antarctica, subs in French Guiana as a unit removed from France, and uses the map-units version of France.

We loop over uniques for properties: CONTINENT SUBREGION REGION_WB REGION_UN

GeoJSON sources via: natural-earth-vector

Clues and Facilities: Mike Bostock's command line cartography series. You'll need to install the nodejs packages including:

npm install -g d3-geo-projection
npm install -g topojson
npm install -g ndjson-cli

You'll need to have jq installed, too.

Warnings: The script creates subdirs in the cwd per grouping, and the colors and stroke widths are hardcoded. Also generally non-elegant and ham-handed scripting compared to the tools we use...

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
# curl -sSL -O https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson
# curl -sSL -O https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_map_units.geojson
# remove ATA, sorry! and France for future swap
ndjson-split 'd.features' < ne_50m_admin_0_countries.geojson \
| ndjson-filter 'd.properties.GEOUNIT !== "Antarctica" && d.properties.GEOUNIT !== "France"' \
> ne_50m_admin_0_countries_xata_fra_guf.ndjson
# grab GUF and FRA from map_units
ndjson-split 'd.features' < ne_50m_admin_0_map_units.geojson \
| ndjson-filter 'd.properties.GEOUNIT == "French Guiana" || d.properties.GEOUNIT == "France"' \
>> ne_50m_admin_0_countries_xata_fra_guf.ndjson
# replace pesky ampersands so we don't trip up on svg ids
cat ne_50m_admin_0_countries_xata_fra_guf.ndjson \
| jq -c '.properties.REGION_WB |= sub("&"; "and"; "g")' \
> ne_50m_admin_0_countries_edited.ndjson
# wrap the stack in a FeatureCollection
ndjson-reduce \
< ne_50m_admin_0_countries_edited.ndjson \
| ndjson-map '{type: "FeatureCollection", features: d}' \
> ne_50m_admin_0_countries_edited.geo.json
# (re?)project to natural earth proj, shift a bit and make sure the size will match the svg output
geoproject 'd3.geoNaturalEarth().rotate([-9, 0]).fitSize([960, 480], d)' ne_50m_admin_0_countries_edited.geo.json \
> naturalearth_50m_admin_0_countries_edited.geo.json
# create groupings in topojson, then loop each to highlight each group in the svg
# we'll have three patterns: all flat, boundaries in highlight, all boundaries
for token in CONTINENT SUBREGION REGION_WB REGION_UN
do
echo "processing ${token}"
lowertoken=$(echo "$token" | tr '[:upper:]' '[:lower:]' | tr -d '_')
mkdir -p ./$lowertoken
geo2topo countries=naturalearth_50m_admin_0_countries_edited.geo.json | topomerge -k "d.properties.${token}" $lowertoken=countries > naturalearth_50m_admin_0_$lowertoken.topo.json
toposimplify -p 1 -f < naturalearth_50m_admin_0_$lowertoken.topo.json > naturalearth_simpler_50m_admin_0_$lowertoken.topo.json
cat naturalearth_simpler_50m_admin_0_$lowertoken.topo.json \
| jq ".objects | .${lowertoken}.geometries[].id" | sort | uniq | tr -d '"' \
| while read line
do
lowerline=$(echo "$line" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr '-' '_')
echo "looping ${line} for ${lowertoken}"
# broadest brush, only group fills no internals
topo2geo $lowertoken=- < naturalearth_simpler_50m_admin_0_$lowertoken.topo.json | ndjson-map "z = (obj) => { if (obj.${token} == \"${line}\") { obj.fill = '#00356b'; obj.stroke = '#00356b'; obj['stroke-width'] = '.5'; } else { obj.fill = '#c9c9c9'; obj.stroke = '#c9c9c9'; obj['stroke-width'] = '.5';} return obj }, d.features.forEach(f => f.properties = z(f.properties)), d" | ndjson-split 'd.features' | geo2svg -n --stroke none -p 1 -w 960 -h 480 > ./$lowertoken/$lowertoken.$lowerline.groupflats.svg
# use contrast only on countries within the group
topo2geo countries=- < naturalearth_simpler_50m_admin_0_$lowertoken.topo.json | ndjson-map "z = (obj) => { if (obj.${token} == \"${line}\") { obj.fill = '#00356b'; obj.stroke = '#c9c9c9'; obj['stroke-width'] = '.5'; } else { obj.fill = '#c9c9c9'; obj.stroke = '#c9c9c9'; obj['stroke-width'] = '.5';} return obj }, d.features.forEach(f => f.properties = z(f.properties)), d" | ndjson-split 'd.features' | geo2svg -n --stroke none -p 1 -w 960 -h 480 > ./$lowertoken/$lowertoken.$lowerline.groupdetail.svg
# show all country boundaries
topo2geo countries=- < naturalearth_simpler_50m_admin_0_$lowertoken.topo.json | ndjson-map "z = (obj) => { if (obj.${token} == \"${line}\") { obj.fill = '#00356b'; obj.stroke = '#c9c9c9'; obj['stroke-width'] = '.5'; } else { obj.fill = '#c9c9c9'; obj.stroke = '#00356b'; obj['stroke-width'] = '.5';} return obj }, d.features.forEach(f => f.properties = z(f.properties)), d" | ndjson-split 'd.features' | geo2svg -n --stroke none -p 1 -w 960 -h 480 > ./$lowertoken/$lowertoken.$lowerline.worlddetail.svg
done
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment