A primer on foregoing Cesium’s native labels in favor of using custom HTML labels to enhance user experience, and our developer experience getting to the current implementation.
As the amount of user-defined data grew in our application, the ability to discern contextual relevance became increasingly important. In a map-centric application especially, making available that discernible data quickly and intuitively can prove challenging. That is, geospatial visualizations—by nature—need ancillary and tangential experiences to provide context both beyond the globe and in minutiae.
In choosing Cesium (for many reasons, the greatest of which being 3D and terrain support), our initial needs were met for displaying small amounts of data per map object by, namely, Cesium’s standard label element. Through several style iterations and continuous experience improvements, however, the standard labels began to feel out of place.
Our color palette and typography had been honed for affordance and hierarchy, as well as to accommodate the environment in which the application would be used (e.g., light levels and screen size). Simply put, we needed more control over label-like elements in order to provide a similar, justifiable experience.
Standard Cesium labels
The current form of our custom HTML labels
Thinking inside the bounds of Cesium, we explored using
replacements. The difficulty faced with billboards is that they expect
rasterized data, such as images, and our desire was to use markup to be truly
flexible. By using SVG’s
were able to render markup as an SVG, which in turn could be encoded as the
source for an image.
We worked through several iterations of this method, at times utilizing raw SVG
and base64 encoding before landing on the output of
as the image source.
The example below shows creating an image for use; a later example will show how it relates to Cesium.
Outcome & Refinement
For more static conditions, this method worked well. Performance concerns were
always present for more dynamic labels, though, because for any data change, the
image would need to be completely redrawn and rendered. For the most part,
browser inconsistencies were manageable. For instance, we temporarily used
promises to handle setting
image.src to account for instantiation time
variance between Chrome and Firefox. The breaking point came when IE11 was
deemed the primary browser for client use. URI-encoded SVGs are supported by
IE11, but the crux of this solution—the
foreignObject tag—is not.
In exploration unrelated to labels, we found that appending HTML and positioning via Cesium utilities worked surprisingly well. Aside from having to handle positioning (which could be abstracted) the benefits were incredible: updatable elements (no destroying), entirely customizable, sharper rendering, and unrestricted browser support.
What is not shown in the above example is handling changes in label positioning.
As mentioned, in using this approach we need to handle updating label positions
outside of Cesium’s normal methods. To do this, we utilize a registry of point
coordinates that gets compared to the previous registered set on a timer.
what we currently use to on-loop discern position changes, which in turn runs
updateDomPosition function from the example. We’ll discuss implications of
this method later in the post.
As with all HTML-in-JS cases, the more complex the labels become, the more we
needed logical templates. Handlebars offered all we
needed, simply passing the output of Handlebars’ compilation to the element’s
Outro & Notes
The experiential value afforded to users with the introduction of HTML labels has been positively noted. We are able to provide several different contextual labels depending on interaction type (i.e., hover vs. click; shift-click vs. left-click) that would not have truly been possible otherwise.
- The manner in which we chose to handle position updates is still under
consideration and evaluation. Using a timer works well in all cases we’ve
encountered, including when hundreds of points are rendered on the map. Our
reservations lie with using
onTick, which would cease to run if the clock was paused.
- We currently use absolute positioning via CSS for label placement, but it has been noted that CSS translate transforms could provide better performance for large change batches.
- In this particular application, we actually allow for using multiple mapping
libraries (Leaflet being the other, currently).
cesiumShapeHelperare abstraction APIs that help us normalize similar methods in each library. This is very much beyond the scope of this post, so suffice to know that
cesiumAdapter.getViewersimply returns the current Cesium Viewer instance, and
cesiumShapeHelper.coordinateAsCartesiansimply returns the provided coordinates as Cartesian3 coordinates.
- All CSS class names adhere to the SUIT CSS naming convention. We use an internal CSS framework and a UI guide system that we hope to write about in a future post.