OTKGlobeEngineA 3D map engine for TAK-style applications

Embedding OTKGlobeEngine

The engine never owns a window. A host app creates a GPU surface with its own toolkit, creates the engine once for that device, and calls a frame function whenever the surface needs drawing. otk_needs_redraw tells the host when tiles are still arriving, so a host can render on demand instead of continuously.

Packages:

Two APIs are available:

The graphics backend is chosen at build time: Direct3D 11 on Windows, Metal on macOS and iOS, OpenGL 4.1 on Linux, OpenGL ES 3 on Android.

Kernel only (no GPU)

Servers, converters and other headless programs link the otk_kernel target (add the repository with add_subdirectory or FetchContent, optionally with -DOTK_ENGINE=OFF). Everything in it works without a window or graphics driver:

#include "otk/elevation.hpp"
#include "otk/feature_query.hpp"
#include "otk/formats.hpp"
#include "otk/importer.hpp"

otk::ImportPlan plan = otk::planImport("survey.kmz");
otk::ImportResult data = otk::importData(plan.path);

otk::FeatureQuery near;
near.useRadius = true;
near.center = {47.0359, -122.9049, 0};
near.radiusMeters = 500;
for (std::size_t i : otk::queryFeatures(data.data.features, near)) { /* ... */ }

otk::ElevationService elevation;
elevation.setCacheDirectory(otk::defaultCacheDirectory());
elevation.setSources(otk::defaultElevation());
double msl = elevation.heightMsl(46.8529, -121.7604);
otk::LineOfSight los = elevation.lineOfSight({46.85, -121.76, 0}, 2.0, {46.90, -121.70, 0}, 2.0);

Android (GLSurfaceView, Kotlin + JNI)

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
    engine = Native.create()
}
override fun onDrawFrame(gl: GL10?) {
    Native.frame(engine, width, height, density)
}
JNIEXPORT jlong JNICALL Java_app_Native_create(JNIEnv* env, jclass) {
    JavaVM* vm;
    env->GetJavaVM(&vm);
    otk_set_android_vm(vm);
    otk_engine* e = otk_create_gl(1, 1);
    otk_set_cache_directory(e, "/data/data/app/files");
    return reinterpret_cast<jlong>(e);
}
JNIEXPORT void JNICALL Java_app_Native_frame(JNIEnv*, jclass, jlong e, jint w, jint h, jfloat dpi) {
    otk_frame_gl(reinterpret_cast<otk_engine*>(e), w, h, 0, dpi);
}

otk_set_android_vm is required: tiles are fetched through the platform HttpURLConnection, so no TLS library ships in the APK. scripts/android_apk.py builds a standalone NativeActivity viewer APK without Gradle.

iOS, iPadOS and macOS (MTKView, Swift)

let engine = otk_create_metal(Unmanaged.passUnretained(view.device!).toOpaque(), 1)
view.depthStencilPixelFormat = .depth32Float_stencil8
view.colorPixelFormat = .bgra8Unorm

func draw(in view: MTKView) {
    guard let drawable = view.currentDrawable, let depth = view.depthStencilTexture else { return }
    otk_frame_metal(engine, Int32(view.drawableSize.width), Int32(view.drawableSize.height),
                    Unmanaged.passUnretained(drawable).toOpaque(),
                    Unmanaged.passUnretained(depth).toOpaque(), nil, Float(view.contentScaleFactor))
}

Windows (Direct3D 11)

otk_engine* e = otk_create_d3d11(device, context, 1);
otk_frame_d3d11(e, width, height, backbufferRtv, nullptr, depthDsv, dpiScale);
swapChain->Present(1, 0);

The back buffer must be DXGI_FORMAT_B8G8R8A8_UNORM and the depth view DXGI_FORMAT_D24_UNORM_S8_UINT.

C# (.NET P/Invoke)

[DllImport("otkglobeengine_c")] static extern IntPtr otk_create_d3d11(IntPtr device, IntPtr context, int samples);
[DllImport("otkglobeengine_c")] static extern void otk_frame_d3d11(IntPtr e, int w, int h, IntPtr rtv, IntPtr resolve, IntPtr dsv, float dpi);
[DllImport("otkglobeengine_c")] static extern int otk_load_features(IntPtr e, [MarshalAs(UnmanagedType.LPUTF8Str)] string path);

Qt (QOpenGLWidget on Linux)

void initializeGL() override { engine = otk_create_gl(0, 1); }
void paintGL() override {
    otk_frame_gl(engine, width() * devicePixelRatio(), height() * devicePixelRatio(), defaultFramebufferObject(), devicePixelRatio());
    if (otk_needs_redraw(engine)) update();
}

Common calls

otk_view v = {47.04, -122.90, 20.0, 0.0, 45.0};
otk_set_view(e, &v);
otk_add_layer(e, OTK_SOURCE_MBTILES, "Offline", "/maps/area.mbtiles", 0, 18, 1.0f);
otk_load_mobac(e, "/maps/source.xml");
otk_add_elevation_directory(e, "/dted");
otk_load_features(e, "/data/overlay.kmz");
otk_open_result opened;
if (otk_open(e, "/data/anything.pmtiles", &opened) && opened.has_region) {
    otk_view_for_region(e, opened.west, opened.south, opened.east, opened.north, &v);
    otk_fly_to(e, &v, 2.0);
}
otk_set_grid(e, 1);
otk_set_labels(e, 0);
char mgrs[32];
otk_to_mgrs(47.04, -122.90, 5, mgrs, sizeof mgrs);