This repository explains how to make topographic maps of planets and moons using open-source data from the USGS, IAU, and NASA. Software used includes Python 3.7.1
, GDAL 2.4.1
, Illustrator CC 2019
and Photoshop CC 2019
. If you have comments or suggestions for this tutorial, please let me know on my blog! You can also buy these topographic maps for Mars, Venus, Mercury, and the Moon.
Python dependencies: matplotlib
numpy
pandas
os
cartopy
json
osgeo
math
scipy
jupyter
. Dependencies can be installed with pip install -r requirements.txt
.
Software Carpentry has great tutorials for installing Python (scroll down and follow the directions in the Bash Shell and Python sections), getting starting with Jupyter Notebooks, and beginner-friendly Python programming. After you've installed Python using these tutorials, you can use Git Desktop and the instructions in this tutorial to download the code and data in this tutorial. For the code in this repository you will also need to install GDAL. The installation instructions in the Conda section are probably the most relevant if you've installed Python as described above.
You'll need software for editing raster and vector images (this article explains the difference). I use Adobe Photoshop and Illustrator, but you can also use the free open-source programs Gimp and Inkscape. There is no perfect software that fits everyone's needs, so you'll want to understand the pros and cons for the different raster and vector programs before choosing.
- Gathering and processing data
- Map design in Python
- Map design in Illustrator and Photoshop
- References
- License
Digital Elevation Models (DEMs) are data files that provide height information. For this project I'm using the United States Geologic Survey's DEMs for Mars, Mercury, Venus, and the Earth's Moon. Each pixel in these GeoTIFF
files describes the elevation at that specific location on the planet. GeoTIFF
files also have embedded geographic data that allows tools like GDAL
to correctly position the elevation data onto a globe.
To run the code in this tutorial, you will need to download one of the DEM files linked above. Note: Many software programs can't read this kind of file, so it's normal if the downloaded DEM looks strange in Preview or other default image applications.
A Quick Introduction: Before we get into the details of map projection transformations, here's a quick overview of what this actually means. To map a 3D object in 2D space, the surface must be transformed using a map projection. There are many different projections, and for the maps in the Atlas of Space series I used Eckert IV, Orthographic, and Plate Carrée projections. To visually compare these different map projections, you can use a Tissot's indicatrix - a set of circles of the same size plotted at different places on the globe. All map projections distort space, but you can see that the effects are quite different depending on the projection:
The choice of projection depends on the purpose of the map. For example, Eckert IV preserves area measurements, so I used Eckert IV in geologic maps to show how much area on the planet was made up of each geological formation. I used an Orthographic projection for these topographic maps to visualize what planets look like from outer space. And finally I used a Plate Carrée projection for constellation charts, because the perpendicular gridlines make it easy to read coordinates (which are essential for finding stars that rise and set throughout the night).
Changing the Map Projection of a DEM File: In this project I wanted to use an orthographic projection to show each hemisphere of the planet (North, South, East and West). However, the original DEM data file uses a Plate Carrée projection. To create a new orthographic map, I used the command line installation of GDAL
- short for Geospatial Data Abstraction Library.
The code below, typed into a bash
shell, uses the original intif
file to create a new file outtif
centered at a latitude of 90
degrees and longitude of 0
degrees in the ortho
(orthographic) projection.
gdalwarp -t_srs "+proj=ortho +lat_0=90 +lon_0=0" ./path_to_intif.tif ./path_to_outtif.tif
Next I downsample the DEM by decreasing the resolution of each pixel to 1500
x1500
meters using the average
method. It's useful to decrease file size to lower computation times, and it's much faster to downsample at this point than to scale images later on in the process.
gdalwarp -tr 1500 1500 -r average ./path_to_intif.tif ./path_to_outtif.tif
Next, I used the downsampled DEM to generate hillshade and slope maps for each hemisphere of the planet.
Hillshade maps show the shadows cast by a hypothetical light source suspended above the map. It’s hypothetical because in the real world, a single light source would cast different shadow angles at different places on a globe. The GDAL
hillshade calculation sets the light source angle to be the same everywhere. In this hillshade I set z
, or the vertical exaggeration, to 20. This multiplies the real elevation values by 20 to increase visual contrast and help the hillshade show up under all of the other map elements.
gdaldem hillshade -z 20 ./path_to_intif.tif ./path_to_hillshade.tif
Slope maps emphasize the steep parts of a map, and adds more information to the topographic hillshade (which emphasizes absolute height rather than steepness).
gdaldem slope ./path_to_intif.tif ./path_to_slope.tif
Although these GDAL
commands are fairly straightforward, some of them take a long time to run on my computer. I wanted to write a single script that would make all of my orthographic maps without having to sit and wait for each command to finish running.
The bash
file 1_ortho_dem.sh
generates all four orthographic plots for Mars, Mercury, Venus, and the Moon, and then downsamples the data and creates hillshades and slopes for each projection. This bash
script is a little more complex because you can specify the central longitude for the central map (for the Moon I decided to make maps of the near side and far side instead of the Eastern and Western hemispheres). I run bash
scripts directly through my bash
shell:
bash ./path_to/1_ortho_dem.sh
It's worth noting that hillshades and slopes can also be generated in Python using the osgeo
library. I haven't included this code because I'm less familiar with osgeo
, but this tutorial may be helpful if you prefer pure Python. You can also customize hillshade and slope in many ways not mentioned here, as well as make other kinds of generated maps by looking through the list of available GDAL commands.
The International Astronomical Union is responsible for naming features of extraterrestrial objects. You can download a CSV
file of all features from each planet from the IAU website (though the data for this project is already included in the ./data/planetary_features
folder).
To download nomenclature data, use the Advanced Search Function and select All Feature Types
from your Target
of only an Approval Status of Adopted by IAU
. In the Columns to Include
section, select Feature ID
, Feature Name
, Clean Feature Name
, Target
, Diameter
, Center Lat/Lon
, Feature Type
, and Feature Type Code
. You can also include Origin
if you'd like additional information about each feature, such as who it is named after. The Output Format
should be CSV
.
Next, I made five Python plots with the contour fills, contour lines, text labels, and two types of gridlines. I often split up data for plotting so I can easily apply section-specific effects in Photoshop or Illustrator. The plotting code for all of these maps are shared in 2_plot_maps.ipynb
.
For this project I wanted all of my maps to fit nicely inside the decorative border I designed in Photoshop. To do this in Python, I use matplotlib gridspec
to set up my figures so that each of my subplots occupy the exact pixel locations inside my decorative border.
There are two steps in this map where I smoothed or cleaned the original data. First, I needed to display the data at different scales for the smaller inset maps in the corners of the design. This is similar to what we see in apps like Google Maps: as you zoom out, the map removes smaller features like streets and buildings. Earth maps also use several different coastline shapes at different zoom levels.
Because I only had one DEM
file, I made these coarser shapes myself by applying a Gaussian filter. Smoothing map data is a legibility step that's fairly subjective. The arrow in the diagram below shows the smoothing level I picked for my own Mercury maps, but other people might pick smoother or finer detail levels.
I also filled in missing pixels in the original data using the information in the surrounding pixels. Again, this is a data cleaning step that other people might implement differently. The limit
and limit_small
parameters in the configuration file describe the maximum sized hole that can be filled in (extremely large areas of missing data are left empty).
Although the code is more or less the same for every map, I wanted to use different colors and input files for each planet. I organized all of these parameters inside the config.json
configuration file. Configuration files are helpful because it's very easy to add or remove new planets - I could easily make a topographic map of Earth or Pluto by adding a new entry. The parameters include topographic levels, color, input files, and the amount of smoothing to apply to each map:
"moon":{
"cmap": ["#e6dfcf", "#ef9f30", "#638b71", "#24293c"],
"levels": [-10000, 10000, 1000],
"levels_small": [-20000, 20000, 5000],
"file_large": "A:/ATLAS_OF_SPACE/image_outputs/ortho_DEMs/moon_lat30_lon0_downsampled.tif",
"file_small": [ "A:/ATLAS_OF_SPACE/image_outputs/ortho_DEMs/moon_lat90_lon0_downsampled.tif",
"A:/ATLAS_OF_SPACE/image_outputs/ortho_DEMs/moon_lat-90_lon0_downsampled.tif",
"A:/ATLAS_OF_SPACE/image_outputs/ortho_DEMs/moon_lat0_lon180_downsampled.tif"],
"save_head": "A:/ATLAS_OF_SPACE/image_outputs/topography/",
"labelfile": "./data/planetary_features/moon.csv",
"limit": 10,
"limit_small": 10,
"gauss": 10,
"gauss_small": 30,
"ortho": [0, 30]
}
For each planet I also wanted to make a cutaway diagram showing the interior layers. But when I started to plot this data, I ran into an issue where you couldn't actually see some of the layers because they were so thin. To solve this problem, I decided to use an adjusted visualization where every layer has a minimum visible thickness (I also added a disclaimer in the key that the figures were not to scale). This code is shared in 3_plot_cores.ipynb
.
In the diagram below, the left half shows the actual thickness of each layer, and the right half shows the adjusted version. For Mercury and the Moon there's actually no difference, but the effect is much stronger for the other planets with a very thin crust or atmosphere.
Each of the planet core diagrams also has a blurred image of the surface on the outside of the sphere. To make these I used the open-source stock images from Stellarium, which I transformed from a Plate Carrée to an Orthographic projection in 3_plot_cores.ipynb
. I also intentionally blurred these images in Photoshop, because a more detailed surface illustration would take the focus away from the cutaway core diagram layers.
I usually save figures as a PDF so I can edit the text and shapes in Illustrator. There are a couple standard commands I use to export Matplotlib figures so they're easy to edit:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.backends.backend_pdf as pdf
# Export text as editable text instead of shapes:
matplotlib.rcParams['pdf.fonttype'] = 42
# Preserve the vertical order of embedded images:
matplotlib.rcParams['image.composite_image'] = False
# Remove borders and ticks from subplots:
ax.axis('off')
# Remove padding and margins from the figure and all its subplots
plt.margins(0,0)
plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
plt.gca().xaxis.set_major_locator(plt.NullLocator())
plt.gca().yaxis.set_major_locator(plt.NullLocator())
# Save the Matplotlib figure as a PDF file:
pp = pdf.PdfPages('./savename.pdf', keep_empty=False)
pp.savefig(fig)
pp.close()
# If I don't need to edit vector paths I save the file as a
# PNG so I can import it directly into Photoshop:
plt.savefig('./savename.png', format='png', dpi=600, pad_inches=0, transparent=True)
After saving the figure, the PDF
file needs to be edited so that each object can be manipulated individually. In Illustrator, select everything in the file and then go to Object
--> Clipping Mask
--> Release
. At this point you can also delete the background and axis border objects, if you included them in the output file.
I export Python figures to Illustrator and Photoshop because several great design features are impossible or very time-consuming in Matplotlib. I'm linking tutorials here for the features I use most often - font alternates and ligatures, custom brushes, layering effects, blur effects, gradients along a path, variable width paths, type on a path, and selecting objects by characteristic.
I've included a small section of the map in the figures
folder as the Photoshop file topography_sample.psd
. The file is small enough to upload online, but since it still has the original layers you should be able to use it as a reference for layering effects.
I decided to annotate this map using text labels that followed the spherical contour lines of the planet. First I used Python to plot a series of latitude lines up and down the globe. I also made a Python output file that placed each text annotation next to a scatterpoint at the center of the feature. Finally I opened these files in Illustrator and manually combined each label with a nearby latitude line:
- Use the
Type on a Path
tool to copy and paste the text for each object onto an appropriate latitude vector. - Use
Character
->Set the baseline shift
to center the text vertically to the desired location.
To create a shadow effect around the text labels, first save the text as a transparent PNG
file and import it into Photoshop. Duplicate this annotation layer and go to Filter
--> Blur Gallery
--> Field Blur
. For shadow text I usually create two blur layers set to 20% opacity - one with a Blur
of 4px and the other 10px.
I wanted the maps in this series to look cohesive, so I made a palette of ~70 different colors and picked from these choices in every map. I also used the same two fonts (Redflowers and Moon) in all maps. You're welcome to use the color palette and font styling if you'd like.
To develop this set of colors, I started the project by designing 14 different color schemes. My initial idea was to have a unique color palette for every planet, but in the end I used the same collection of colors throughout all of the projects to make the maps look more cohesive.
Each color palette is shown in several different ways, because I wanted to design versatile color schemes that could work as discrete elements, or as pieces of a complex pattern, or as a gradient in topographic maps. I updated these three visualizations while designing to make sure each color scheme would work for each application.
For this project I wanted to combine large datasets with the hand-crafted design style of artists like William Morris or Alphonse Mucha. To organize my thoughts I collected a big folder of inspiration images from sources like the New York Public Library Digital Database:
When I started this project I initially wanted to design different border decorations for every topic. I sketched a collection of 18 different repeated patterns, each meant to go alongside a unique astronomy theme like planets, galaxies, space missions, or satellites.
But as the project continued I realized there was so much data that the detailed borders made the maps look too cluttered. In the end I removed all of the borders and designed just one scrollwork illustration to wrap around rounded map projections. In these round maps I thought the shift from detailed map to blank paper was a bit too abrupt, so this was a good compromise between data-heavy and illustrative design styles.
For this scrollwork design I started with a pencil sketch, and tried a couple different iterations of leafy scrolls before finally picking a less botanically inspired design. When I paint decorations like these in Photoshop, I begin each design as a solid white shape and then gradually break away pieces into detailed chunks. Next, I brush away pieces of each section with the brush eraser tool until the pieces look like a fully-shaded monochrome design. I wait to add color until the very last step, where I use many different colors and overlay layers for a richer effect.
I've included two different examples of these painted Photoshop illustrations in the figures
folder as scrollwork_sample.psd
and decorations_sample.psd
. These files still have the original layers, so you should be able to use it as a reference for layering, painting, and color effects.
- Astronomy. Andrew Fraknoi, David Morrison, Sidney C. Wolff et al. OpenStax 2016.
- Gazetteer of Planetary Nomenclature. International Astronomical Union (IAU) Working Group for Planetary System Nomenclature (WGPSN) 2019.
- LRO LOLA Elevation Model 118m (LDEM GDR). NASA PDS and Derived Products Annex. LOLA Science Team 2018.
- Mars HRSC MOLA Blended DEM Global 200m v2. NASA PDS and Derived Products Annex. USGS Astrogeology Science Center 2018.
- Magellan Global Topography 4641m. NASA PDS and Derived Products Annex. Magellan Team 2014.
- Mercury MESSENGER Global DEM 665m (64ppd) v2 Oct. 2016. NASA PDS and Derived Products Annex. USGS Astrogeology Science Center 2016.
- NASA Science Solar System Exploration. 2019.
- Stellarium 2019 version 0.19.0.
- Fonts: Moon by Jack Harvatt and RedFlower by Type & Studio.
- Advice: Thank you to Oliver Fraser, Henrik Hargitai, Jennifer Hsiao, Chloe Pursey, and Leah Willey for their helpful advice in making this map.
Code: All of the code in this repository is shared under the GPL-3.0 license.
Data: The data in this repository belongs to the original authors of the data. Please use the references section to look up the original version. In cases where I edited or revised the data, I impose no additional restrictions to the original license. Any data files I created myself are shared under the ODC Open Database License.
Artwork: The artwork included in this repository are shared under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.