3D Terrain Arrives in GLMap SDK 2.0
Tilt the camera on most map SDKs and the world stays flat. The view leans, labels reorient, the perspective looks 3D — and the ground underneath is still a sheet of paper. For mountains, ridges, coastlines, and any app that cares where the high ground is, that flat sheet is the long-standing gap.
GLMap SDK 2.0 closes it. The elevation data is on the device. The mesh is on the GPU. The camera lifts off and the planet stands up underneath it.
This post is what shipped, what we left out on purpose, and how to turn it on in your app.
What ships
Global land coverage, offline. Every continent — roughly 149 million km² of land — backed by SRTM 30 m with fallbacks where SRTM does not reach. Once a region's elevation tiles are downloaded, no network calls. Same offline-first contract as the rest of GLMap.
Five properties. That is the entire customer-facing API surface for terrain on GLMapView:
altitudeScale— the headline.0disables terrain entirely.1renders full elevation. Animatable.drawHillshades— cartographic relief shading with an Imhof-inspired palette, computed on the GPU from the elevation tiles. Not an optical sun simulation — a palette in the Swiss cartographic tradition.drawSlopes— gradient overlay for steepness visualization.drawElevationLines— contour lines that follow the actual mesh, not a separate dataset.rotateHillshadesLight— rotates the shading direction with the camera bearing, so the relief reads correctly as the map turns.
altitudeScale interpolates smoothly through GLMapAnimation; the four boolean toggles snap on and off. The pattern is the GLMap one you already know:
mapView.drawHillshades = true
mapView.animate { _ in
mapView.altitudeScale = 1.0
}
Turn on the visual layers, animate the ground up.
iOS and Android, day one. Metal on iOS; OpenGL ES 3.0 and Vulkan on Android. Same five properties on both. No new build flag, no separate SKU.
How it actually works
The terrain renderer is real geometry, not a shader trick.
Each elevation tile arrives as a BPG-compressed grayscale image inside an .eletar archive — decoded on device into a 254×254 uint16 height grid with a 4-pixel overlap so neighbor tiles stitch cleanly. The renderer tessellates each tile into a 128-division grid and lifts every vertex by the sampled elevation. That gives the rest of the SDK real surface to interact with: tracks drape on the ground, markers sit at their actual elevation, labels collide against real height, a tap on a mountain peak returns the point on the peak (not the sea-level projection below it), and contour lines follow the mesh instead of a flat projection.
Frustum culling keeps the budget honest — tiles outside the view draw nothing. A multi-level LOD pyramid is wired in for a later release; in 2.0, every visible tile renders at the full 128 divisions, which keeps the behavior predictable while we calibrate the LOD bands against real-world routes.
Hillshade and slope are baked once per tile on the CPU and uploaded as 256×256 textures. The terrain fragment shader composites them with the contour overlay in a single pass, with a separate uniform per channel. That single-pass composition is what makes each visualization an independent toggle — customers turn on what fits their app and ignore the rest.
The work spans two graphics APIs. Metal on iOS, Vulkan and OpenGL ES 3.0 on Android. The Vulkan path needed a few device-specific paths along the way, which is the cost of supporting Android well.
What we left out — on purpose
A 3D terrain feature can balloon quickly. We resisted.
- No TIN meshes. A triangulated irregular network is prettier on cliffs but harder to stream, animate, and reason about. The regular grid wins on simplicity and behaves predictably under LOD transitions.
- No physically-based sun shading. Hillshading is cartographic — an Imhof-inspired palette, not an optical light simulation.
rotateHillshadesLightkeeps that relief legible as the camera turns. A true sun path with time-of-day shadows is a different feature, not a corner we cut. - No upscaled elevation textures. Tiles are sized to the 30 m source data, not inflated beyond what the DEM actually resolves.
- No water-surface effects, no atmospheric scattering. Terrain is elevation. The current release does the first cleanly. The rest are separate problems.
Each of those is the kind of feature that looks great in a screenshot and quietly degrades the SDK if it ships before it earns its place.
What this means if you build on GLMap
Integration effort is small. If your app already uses GLMapView, you add the elevation data set to your download list and set altitudeScale to something non-zero. Tracks, markers, vector objects, labels, and contours drape on the resulting mesh automatically — no per-layer setup. Hillshade, slope, and contour overlays are independent toggles, all on the same view object, same API on iOS and Android.
Offline behavior is what you would expect from GLMap. Elevation is a separate optional data set in the same GLMapManager flow as map and navigation tiles — opt in for the regions you need, the tiles cache on device, and from then on the terrain renders entirely offline.
The Chamonix demo in SwiftDemo shows the pattern end to end; the Android side mirrors it method-for-method.
What's next
We have been climbing this hill for a while too:
- Flat 2D maps with vector tiles — the foundation.
- Pitch and yaw on a flat surface — the camera leans, the ground stays a sheet.
- 3D Terrain — what lands in SDK 2.0. Real elevation, real mesh, hillshade and slope and contours on top.
- Wider visualization layers — denser contour styling, custom shading ramps, exposure-aware lighting.
- Routes draping on the mesh, full per-vertex vector-tile draping for roads and water, depth-aware raster overlays — pulling the last few layers down onto the surface.
Each step has to earn its place before the next one ships. SDK 2.0 is the first release that puts real elevation geometry under the camera. The next ones compose on top of it — not replace it.
If you are building an outdoor, off-road, navigation, GIS, or visualization app on GLMap and want a closer look — write us at [email protected].
