Space Domain 3D Visualization Part 1, Mars
AlphaPixel often gets called upon to work on unusual 3D applications. We’ve worked with data from the bottom of the ocean, to near space, the surface of the Moon, Mars, the asteroids and beyond. One of our programmers once wrote a massively parallel physics engine capable of simultaneously solving ten thousand inter-reacting gravitational objects, running purely on CPUs of the early twenty-teens. We have simulated both Mars and Lunar spacecraft trajectories for visualization and Vision Navigation, as well as near-Earth Space Domain Awareness work.
While NDAs and confidentiality mean we often can’t actually show our clients’ projects, we recently had an interesting side project that used the same processes, so we can talk all about that project.
The task: Display spatial data in 3D on an interactive globe of Mars, running online, with no locally-installed software beyond a WebGL-capable browser.
In Part 1 of this blog series, we will create a basic interactive 3D globe of Mars, and add some data to it.
I’ll try to document every step and tool used.
CesiumJS for Mars Data Visualization
We would be fools if we didn’t immediately turn towards CesiumJS. It’s a high-performance WebGL-browser-based 3d globe rendering toolkit based on Javascript. It’s developed and maintained by the Cesium company, led by Patrick Cozzi. Cesium has integrations and implementations in Unreal, Unity, Javascript/WebGL, NVidia Omniverse, and Open 3D Engine (O3DE) and has a backend data service called Cesium Ion.
For this demo, I’m going to use the Cesium Sandcastle, because it allows anyone to get started without having to provision their own webserver or install the Cesium content itself. The Javascript code we build here is equally applicable to a self-hosted CesiumJS instance. Also, I’m going to host a demonstration data file on our own AlphaPixel webserver, so that I can address SSL/CORS issues (we’ll talk about this later).
So, navigate to https://sandcastle.cesium.com/
It will start with the following Javascript in the left side:
const viewer = new Cesium.Viewer("cesiumContainer");
This creates a default viewer with a default Earth configuration, some supplied data layers and some overlaid UI controls. This is great for visualizing Earth, but we need to undo a lot of things and change and add others.
We’ll start by adding some new code at the top. Delete everything in the Javascript editor on the left and paste in the following:
// ref https://cesium.com/learn/ion-sdk/ref-doc/Ellipsoid.html // ref https://github.com/CesiumGS/cesium/blob/1.120/packages/engine/Source/Core/Ellipsoid.js#L232 // order of parameters is semi-major, semi-major, semi-minor // For Earth it is new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793) // For Mars ref https://tharsis.gsfc.nasa.gov/geodesy.html //var ellipsoidMars = new Cesium.Ellipsoid(3398627,3393760,3376200); // this breaks due to Ceisum bug var ellipsoidMars = new Cesium.Ellipsoid(3376200,3376200,3376200); var mapProjectionMars = new Cesium.GeographicProjection(ellipsoidMars); var globeMars = new Cesium.Globe(ellipsoidMars); ellipsoidMars.material = new Cesium.Color.DARKRED.withAlpha(0.3);
Let’s talk through what we’re doing so far.
The first part creates an Ellipsoid. Mathematically, an ellipsoid is a squished/stretched sphere. Wikipedia explains it as “An ellipsoid is a surface that can be obtained from a sphere by deforming it by means of directional scalings, or more generally, of an affine transformation.” So, it’s a sphere that’s scaled in different directions. Now, why do we need an ellipsoid and what is it used for?
What’s an Ellipsoid?
Ellipsoids by Ag2gaeh via Wikipedia CC BY-SA 4.0
It turns out that most planetary objects aren’t perfectly round. Isaac Newton proposed that rotating self-gravitating fluid-like bodies (apparently, long-term a rocky planet like Earth is fluid enough to be considered fluid) will take the shape of a sphere that has been squashed on its axis of rotation. This is called an Oblate Spheroid. The inverse, a stretched sphere like a football is a Prolate Spheroid. However, the name “The National Prolate Spheroid League” didn’t test well in marketing focus groups.
Earth is roughly a spheroid that is 6356752.314 meters in its North-South direction (called the semi-minor axis, because it’s the smaller dimension or b) and 6378137.0 meters across at the equator (called the semi-major axis or a) according to the WGS84 ellipsoid definition. The squashed-ness of an oblate spheroid is called its flattening (sensibly) and roughly expresses its length as a percentage decrease of the longer, semi-minor axis. Earth’s flattening according to WGS84 is 0.0033528106647475. Because people and computers often struggle with really small decimal numbers, it’s actually usually recorded as INVERSE flattening, which is 1 divided by flattening, which makes it a slightly more comfortable number, 298.257223563.
You can work the numbers yourself. WGS-84 says inverse flattening is 298.257223563. 1/298.257223563 is 0.0033528106647475 so Earth is 0.33528106647475 PERCENT shorter North-South than across the equator. 0.0033528106647475 times the equator semi-major distance of 6378137.0 equals 21,384.68, and that’s the difference, in meters, between the semi-major and semi-minor axes: 21,384.68.
6378137.0 – 6356752.314 = 21,384.686.
Ok, so that’s Earth. For Earth, the Cesium ellipsoid object construction looks like
Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793)
This creates an ellipsoid definition with a semi-minor axes (the last parameter) of 6356752.3142451793 and the other two axes are 6378137.0.
Mars is different.
What is Mars’ Spheroid?
Mars is also a spheroid that is oblate-ish, but the two semi-minor axes are slightly different because Mars is a bit lopsided, 3398627 and 3393760 meters respectively. However, as of version 1.12, Cesium has a bug that can’t handle the two semi-major axes differing. So, we’ll leave the proper values comments out and just define a simple sphere using 3376200 meters, the average of the three actual axes. This seems to work and is close enough for non-altitude/geodesy-related tasks.
So our code does the following:
var ellipsoidMars = new Cesium.Ellipsoid(3376200,3376200,3376200); var mapProjectionMars = new Cesium.GeographicProjection(ellipsoidMars); var globeMars = new Cesium.Globe(ellipsoidMars); ellipsoidMars.material = new Cesium.Color.DARKRED.withAlpha(0.3);
This creates the ellipsoid to tell CesiumJS how to shape the zero-meter elevation base shape it will draw imagery (and possibly terrain) relative to.
Then we create a geographic map projection based on that ellipsoid, which is a necessary component for the next step. Finally, with the map projection, we create a Cesium Globe, which is kind of the heart of visualizing a virtual globe. As an afterthought, we’ll create a base material color of a dark red.
Next, we’ll set some options in an options object and use it to create a Cesium Viewer using them.
var optsMars = { mapProjection: mapProjectionMars, globe: globeMars, baseLayerPicker: false }; var viewer = new Cesium.Viewer('cesiumContainer', optsMars);
At this point you can click the Run (F8) button and the code should operate.
Next, we’ll configure a bunch of settings viewer.scene.skyAtmosphere.show = false; viewer.scene.fog.enabled = false; viewer.scene.globe.showGroundAtmosphere = false; viewer.scene.moon.show=false; viewer.bottomContainer.style.display = "NONE"; //capitalized viewer.fullscreenButton.container.style.display = "none"; //not capitalized viewer.homeButton.container.style.display="none"; viewer.navigationHelpButton.container.style.display="none"; viewer.timeline.container.style.display="none";
This disables the rendering of atmospheres (Mars doesn’t have an atmosphere visible from space) and fog. It turns of rendering of Earth’s moon, which wouldn’t be orbiting Mars. It also turns off some unused UI elements on the screen to declutter things.
Now let’s make it look like Mars!
var imageryLayers = viewer.imageryLayers; imageryLayers.removeAll(); imageryLayers.addImageryProvider(new Cesium.WebMapServiceImageryProvider({ url : 'https://planetarymaps.usgs.gov/cgi-bin/mapserv?map=/maps/mars/mars_simp_cyl.map&service=WMS', layers : 'MDIM21_color', parameters : { transparent : false, format : 'image/png' }, tilingScheme: new Cesium.GeographicTilingScheme({ ellipsoid: ellipsoidMars }), tileWidth: 512, tileHeight: 512 }));
This accesses the imageryLayers object that’s part of the viewer. First, we’ll remove all existing layers, because Cesium starts with an Earth imagery layer by default. Next, we will create an ImageryProvider of the WebMapServiceImageryProvider subclass. This is an object that knows how to fetch imagery over the Internet from a Web Map Service, and I have preconfigured it to use the USGS’ Planetary Maps server, requesting the ‘MDIM21_color’ layer.
If you run the code now, it should look like this:
That’s already pretty great, actually.
Wanna start your viewpoint over a specific location, like Olympus Mons ( Central Latitude/Longitude (deg): 23, 135 W, ref https://nssdc.gsfc.nasa.gov/imgcat/html/object_page/mgs_85298.html )? Just add at the bottom:
var center = Cesium.Cartesian3.fromDegrees(-135.0, 23.0);
viewer.camera.flyTo({destination: center, });
Here’s some more details about how to control the camera in CesiumJS:
https://cesium.com/learn/cesiumjs-learn/cesiumjs-camera/#fly-to-a-location
Ok, so the final introductory task most people want to know how to do is how to drape an image of their own (usually some kind of data they want to overlay). Here’s where it gets a little tricky. See, CesiumJS runs entirely in the user’s browser. And because the Internet is a scary and dangerous place, there’s actually a LOT of dangerous things that browsers used to be allowed to do that we later learned were a terrible idea, and we later restricted. One of those disallowed things was allowing a web page on one server to willy-nilly direct the browser to fetch data from somewhere else and incorporate it into the current webpage. Accessing files on the user’s local computer was always considered sketchy, but later it was realized that malicious web pages could try poking around on the user’s own local network, messing with other computers and IoT devices like webcams. Another attack was to try to make the user’s browser access public Internet sites in hopes that the user would have privileged accounts there that a malicious script could shoulder-surf into as the user.
So, a browser policy named CORS (Cross Origin Resource Sharing) was devised. This pretty much prohibits a web page from accessing resources from third-party servers other than the server that provided the webpage, unless the third-party server explicitly says it’s ok. In our current situation, the CesiumJS viewer itself is being served from the sandcastle.cesium.com server, so attempting to include any image data residing on any other server (or your local hard drive!).
If you install CesiumJS on your own webserver, this won’t be an issue – it will be able to fetch other data from the same server. However, we wanted to let you do this tutorial with no installation necessary, just using the Cesium Sandcastle server. To allow Sand Castle to access data on a third-party webserver, a CORS directive needs to be added to the server config declaring either “sandcastle.cesium.com” as an allowed partner, or simply making access to some data wide-open using a wildcard “*”. This will let any webpage on any server anywhere fetch resources from your server, which could be slightly dangerous if not thought out. Here is a website instructing how to add a wildcard CORS directive to almost any webserver. That doesn’t include OpenLiteSpeed, our server stack of choice, so here’s a document explaining how to add CORS on OpenLiteSpeed.
To facilitate this tutorial, we have provisioned a special webserver just to serve up the supporting data files, and configured it with wide-open CORS directives. At the URL
https://datastore.alphapixel.com/spatial-data/mars/tes_ruffdust.png
You can see this image.
It’s 5760×2800 and it came from https://www.mars.asu.edu/data/tes_ruffdust/ but the asu.edu webserver doesn’t have permissive CORS directives, so we can’t use it in-situ from the ASU server.
The explanation of what this data is is right here: https://www.mars.asu.edu/data/tes_ruffdust/
I’m not smart enough to understand exactly what “dust cover” is, and neither were a few Mars scientists I casually asked, but it doesn’t matter. Per the paper it’s “a measure of spectrally-obscuring surface dust on Mars that is independent of thermal inertia and albedo values.” What is important is it is spatial Mars data with referencing. You notice I didn’t say GEOspatial and GEOreferencing, because technically the prefix GEO is derived from the Greek word γαια meaning Earth. For Mars the accepted prefix would be aerospatial or aeroreferencing but nobody understands that. Wikipedia can tell you the prefixes for all solar bodies.
But anyway, we have the data and its referencing coordinates, so let’s stick it into CesiumJS.
const ruffdust_imagery = Cesium.ImageryLayer.fromProviderAsync( Cesium.SingleTileImageryProvider.fromUrl( "https://datastore.alphapixel.com/spatial-data/mars/tes_ruffdust.png", // from https://www.mars.asu.edu/data/tes_ruffdust/ { rectangle: Cesium.Rectangle.fromDegrees( -180.0, -90.0, 180.0, 90.0 ), } ) ); imageryLayers.add(ruffdust_imagery);
When you reload, it may take a minute (this is a 30MB image file and it’s not tiled in any way, so Cesium has to download and process the whole thing in one bite which takes a minute). You’ll see something like this:
Ok, that IS pretty great now.
For our final trick before we wrap part 1, let’s add a toggle control to let us switch on and off the dust map so we can see how it correlates with the visual appearance layer underneath it. Add the following lines:
Sandcastle.addToolbarButton("Image Layer", function () { ruffdust_imagery.show = !ruffdust_imagery.show; });
Now you should have this new button in the top left:
Clicking it will make the ruffdust layer appear and disappear!
Here is the final, functional code:
// ref https://cesium.com/learn/ion-sdk/ref-doc/Ellipsoid.html // ref https://github.com/CesiumGS/cesium/blob/1.120/packages/engine/Source/Core/Ellipsoid.js#L232 // order of parameters is semi-major, semi-major, semi-minor // For Earth it is new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793) // For Mars ref https://tharsis.gsfc.nasa.gov/geodesy.html //var ellipsoidMars = new Cesium.Ellipsoid(3398627,3393760,3376200); // this breaks due to Ceisum bug var ellipsoidMars = new Cesium.Ellipsoid(3376200,3376200,3376200); var mapProjectionMars = new Cesium.GeographicProjection(ellipsoidMars); var globeMars = new Cesium.Globe(ellipsoidMars); ellipsoidMars.material = new Cesium.Color.DARKRED.withAlpha(0.3); var optsMars = { mapProjection: mapProjectionMars, globe: globeMars, baseLayerPicker: false }; var viewer = new Cesium.Viewer('cesiumContainer', optsMars); viewer.scene.skyAtmosphere.show = false; viewer.scene.fog.enabled = false; viewer.scene.globe.showGroundAtmosphere = false; viewer.scene.moon.show=false; viewer.bottomContainer.style.display = "NONE"; //capitalized viewer.fullscreenButton.container.style.display = "none"; //not capitalized viewer.homeButton.container.style.display="none"; viewer.navigationHelpButton.container.style.display="none"; viewer.timeline.container.style.display="none"; var imageryLayers = viewer.imageryLayers; imageryLayers.removeAll(); imageryLayers.addImageryProvider(new Cesium.WebMapServiceImageryProvider({ url : 'https://planetarymaps.usgs.gov/cgi-bin/mapserv?map=/maps/mars/mars_simp_cyl.map&service=WMS', layers : 'MDIM21_color', parameters : { transparent : false, format : 'image/png' }, tilingScheme: new Cesium.GeographicTilingScheme({ ellipsoid: ellipsoidMars }), tileWidth: 512, tileHeight: 512 })); var center = Cesium.Cartesian3.fromDegrees(-135.0, 23.0); viewer.camera.flyTo({destination: center, }); const ruffdust_imagery = Cesium.ImageryLayer.fromProviderAsync( Cesium.SingleTileImageryProvider.fromUrl( "https://datastore.alphapixel.com/spatial-data/mars/tes_ruffdust.png", // from https://www.mars.asu.edu/data/tes_ruffdust/ { rectangle: Cesium.Rectangle.fromDegrees( -180.0, -90.0, 180.0, 90.0 ), } ) ); imageryLayers.add(ruffdust_imagery); Sandcastle.addToolbarButton("Image Layer", function () { ruffdust_imagery.show = !ruffdust_imagery.show; });
A copy of the CesiumJS Mars with Dust Layer Overlay code is saved in Cesium Sandcastle if you want to just jump straight to the finish.
In later blog posts we will add more data and controls to this visualization. Also, we might do some Moon and Earth Space Domain Visualization, especially now that Cesium Ion has a Moon data layer.
It’s also worth knowing that Cesium is available in an Unreal Engine plugin which we have used in several projects and recommend highly. The Cesium Ion platform is also an excellent source for excellent data of the Earth (and now moon!) including 3D Tilesets and buildings, and can be used in CesiumJS, Cesium for Unreal and even one of our longtime favorite tools, osgEarth.
AlphaPixel solves difficult problems every day for clients around the world. We develop computer graphics software from embedded and safety-critical driver level, to VR/AR/xR, plenoptic/holographic displays, avionics and mobile devices, workstations, clusters and cloud. We’ve been in the computer graphics field for 35+ years, working on early cutting-edge computers and even pioneering novel algorithms, techniques, software and standards. We’ve contributed to numerous patents, papers and projects. Our work is in open and closed-source bases worldwide.
People come to us to solve their difficult problems when they can’t or don’t want to solve them themselves. If you have a difficult problem you’re facing and you’re not confident about solving it yourself, give us a call. We Solve Your Difficult Problems.