0% found this document useful (0 votes)
4 views28 pages

GIS Renderer Plugin Design Document

The GIS Renderer Plugin is a real-time geospatial rendering and navigation engine designed to dynamically stream and render real-world geographic data in interactive applications. It utilizes a fixed-size tile streaming model for efficient memory usage and integrates Godot for rendering and user input, while Rust handles the core GIS functionalities. The document outlines the system architecture, core design principles, and the interaction between Godot and Rust through a strict FFI boundary.

Uploaded by

agrim00001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views28 pages

GIS Renderer Plugin Design Document

The GIS Renderer Plugin is a real-time geospatial rendering and navigation engine designed to dynamically stream and render real-world geographic data in interactive applications. It utilizes a fixed-size tile streaming model for efficient memory usage and integrates Godot for rendering and user input, while Rust handles the core GIS functionalities. The document outlines the system architecture, core design principles, and the interaction between Godot and Rust through a strict FFI boundary.

Uploaded by

agrim00001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

📐 GIS Renderer Plugin

Developer Design Document (v0.1)

0. Introduction & Project Overview


The GIS Renderer Plugin is a real-time geospatial rendering and
navigation engine that streams, renders, and augments real-world
geographic data inside interactive applications.

It is designed as a spatial runtime, not a traditional map viewer.

The system allows applications to load and interact with real-world


geography dynamically. Geographic data is streamed on demand and
rendered as raster imagery, vector map data, or elevation-based
terrain. All rendering modes share the same coordinate system and
spatial logic, ensuring consistency across zoom levels, projections,
and navigation states.

To support large geographic areas, the system uses a fixed-size


tile streaming model. Only a bounded set of tiles is kept in
memory at any time. As the camera moves, tiles entering the view are
fetched and tiles leaving the view are evicted in a deterministic way.
This keeps memory usage stable regardless of the size of the
mapped area.

Navigation is built into the core runtime rather than layered on top.
Routes are fetched from provider-agnostic APIs and parsed into
structured route data. A live Journey system tracks progress along
the route using the current user location, updating the active path,
remaining distance, estimated time, and navigation instructions
continuously.

All spatial computation—coordinate conversion, routing logic,


geocoding, and tile addressing—is handled inside the GIS core.
Latitude and longitude remain the authoritative coordinate space
throughout the system. Conversion into engine-specific world
coordinates happens only at the point of rendering to avoid precision
loss and inconsistencies.

The architecture is engine-independent and provider-


independent. The current integration targets:

Godot as the rendering and input layer


Rust as the GIS runtime responsible for streaming, navigation,
geocoding, and spatial computation

Rust is used to provide predictable memory behavior, safe


concurrency for asynchronous data fetching, and accurate
geographic calculations. Godot is responsible only for rendering, user
input, and presentation. Communication between the two is handled
through a strict FFI boundary with clear ownership and lifetime rules.

This document is a technical design specification. It describes


the system as it exists today—tile streaming, quadtree management,
navigation journeys, overlays, and asynchronous data pipelines—and
formalizes the design to support continued development and
extension.

Alright.
I’ll treat this as a proper internal engineering design doc, not
marketing fluff, not README-level, and not UE-specific.
Platform-agnostic, Godot + Rust target, but cleanly abstracted
so the renderer could be embedded elsewhere later.

I’ll also implicitly reflect the actual system you already


built (tile streaming, quadtree, navigation, overlay, async APIs)
instead of reinventing it.

1. Purpose & Scope


The GIS Renderer Plugin is a real-time geospatial rendering and
navigation engine that streams, renders, and augments real-world
geographic data inside an interactive 2D/3D scene.

It is designed as a core spatial runtime, not a map viewer.

Primary goals:

Render real-world geography (raster, vector, elevation)


interactively
Maintain constant memory usage via tile streaming
Provide navigation + geocoding as first-class primitives
Support AR-style world overlays
Remain provider-agnostic and engine-agnostic
Move heavy logic into Rust (deterministic, safe, testable)

Target integration (current):

Godot Engine (frontend / renderer)


Rust (core GIS + data + navigation engine)

2. High-Level Architecture
1 ┌──────────────────────────────┐
2 │ Host Engine (Godot) │
3 │ - Camera / Viewport │
4 │ - Mesh & Material Binding │
5 │ - Input (pan / zoom / AR) │
6 └───────────────▲──────────────┘
7 │ FFI / Bindings
8 ┌───────────────┴──────────────┐
9 │ GIS Renderer Core (Rust) │
10 │ │
11 │ ┌──────── Tile System ────┐ │
12 │ │ Quadtree / Tile IDs │ │
13 │ │ Streaming Window ││
14 │ │ Cache & Eviction ││
15 │ └────────────────────────┘ │
16 │ │
17 │ ┌──── Rendering Models ───┐ │
18 │ │ Raster Tiles │
19 │ │ Vector Tiles (MVT) │
20 │ │ Height / DEM Tiles │
21 │ └────────────────────────┘ │
22 │ │
23 │ ┌──── Navigation Engine ──┐ │
24 │ │ Route Fetching │
25 │ │ Path Flattening │
26 │ │ Journey State │
27 │ │ Instructions │
28 │ └────────────────────────┘ │
29 │ │
30 │ ┌──── Geo Intelligence ───┐ │
31 │ │ Lat/Lon ↔ Tile │
32 │ │ Geocoding / Reverse │
33 │ │ POI / Overlay Data │
34 │ └────────────────────────┘ │
35 └──────────────────────────────┘

3. Core Design Principles


3.1 Constant-Memory Streaming
World is infinite; memory is not.
Only a fixed window of tiles exists in memory.
Default strategy:
4×4 atlas grid
3×3 active viewport
Panning shifts the window; tiles are evicted and fetched
deterministically.

3.2 Data-Driven Everything


No static markers.
No baked paths.
Navigation, markers, overlays all react to live state:
user location
zoom level
provider data
journey progress

3.3 Precision First

All geographic math is double-precision.


Rendering coordinates are derived last.
No lossy projection early in the pipeline.

4. Tile System
4.1 Tile Identity

1 struct TileId {
2 zoom: u8,
3 x: u32,
4 y: u32,
5 }

Slippy-map compatible
Hashable
Parent/child relationships (quadtree)

4.2 Quadtree
Responsibilities:

Logical tile hierarchy


Cache ownership
Parent/child resolution
LOD transitions (future)

Each node:

1 struct TileNode<T> {
2 id: TileId,
3 resource: Option<T>,
4 fetched: bool,
5 children: [Option<NodeRef>; 4],
6 }

4.3 Tile Streaming Window


Inputs:

Center tile
Grid dimensions
Camera offset (fractional)

Outputs:

Deterministic list of visible TileIds


Fetch / evict decisions

This logic already exists in your system and must remain


deterministic to avoid jitter, flicker, or tile drift.

5. Rendering Modes
5.1 Static Raster Tiles
PNG / JPG
Satellite / imagery
Decoded in Rust
Uploaded as engine textures

Fallback:
Solid color tile for missing data

5.2 Vector Tiles


MVT (Mapbox Vector Tile)
Parsed in Rust
Geometry flattened into:
lines (roads)
polygons (buildings)
points (POIs)
Renderer decides:
mesh generation
styling
labels

5.3 Height / DEM Tiles


Height encoded (PNG / GeoTIFF / quantized mesh)
Converted into height arrays
Mesh generated per tile
Stitching & skirts handled at tile boundaries

6. Coordinate Conversion Engine


This is foundational and already a major solved pain point.

6.1 Supported Spaces


Lat / Lon (WGS84)
Tile Space (X/Y/Z)
Local Tile Space (fractional offsets)
Engine World Space

6.2 Core Functions


1 latlon_to_tile(lat, lon, zoom) -> TileId
2 tile_to_latlon(tile, offset) -> LatLon
3 local_to_gis(local_point, camera_state) -> LatLon
4 gis_to_local(latlon, camera_state) -> Vec2

6.3 Invariants
Center tile must always map to viewport center.
Camera offsets must be normalized against grid bounds.
Odd/even grid parity must be preserved.

Your previous debug-trace heavy work stays — it’s essential.

7. Navigation System
7.1 Route Fetching
Provider-agnostic API layer
URL built dynamically:
origin
destination
travel mode

Rust owns:

HTTP
JSON parsing
error handling

7.2 Route Data Model

1 struct GeoCoordinate {
2 lat: f64,
3 lon: f64,
4 }
5
6 struct RouteStep {
7 instruction: String,
8 distance_m: f64,
9 duration_s: f64,
10 geometry: Vec<GeoCoordinate>,
11 }
12
13 struct Route {
14 steps: Vec<RouteStep>,
15 geometry: Vec<GeoCoordinate>, // flattened
16 }

7.3 Journey System


A Journey is a live navigation session.

1 struct Journey {
2 route: Route,
3 current_index: usize,
4 remaining_distance: f64,
5 remaining_duration: f64,
6 }

Advances based on user location


Emits:
current instruction
remaining path
ETA updates

This replaces static path rendering entirely.

8. Overlay & Marker System


8.1 Marker Model

1 struct Marker {
2 lat: f64,
3 lon: f64,
4 kind: MarkerType,
5 }

Markers:

Stored in GIS space


Reprojected every frame
Auto-hidden when out of relevance

8.2 Path Rendering


Spline / polyline generated from route geometry
Clipped to local area bounds
Truncated, never “disappears”
Preserves visual continuity

(Your spline clipping solution is correct and intentional.)

9. Geocoding & World Intelligence


9.1 Forward / Reverse Geocoding
Provider API or local DB
Rust returns structured results
Engine decides presentation

9.2 Custom World Data


User-defined POIs
Metadata overlays
Reviews, ratings, tags
Used heavily in AR mode

10. AR Overlay Mode (Conceptual)


GIS world is anchor truth
Device pose is relative
Overlays projected from GIS → camera
Same marker + journey systems reused

No separate AR logic branch.

11. Error Handling & Logging


Centralized logging (already built)
Severity-aware
Engine-filterable
Safe in release builds

This is non-negotiable for a system this complex.

12. Why Rust + Godot (Explicit Rationale)


Rust:

Deterministic memory
Safe async
Testable math
No engine lifecycle footguns

Godot:

Lightweight renderer
Easy FFI
Mobile & desktop friendly
AR support without vendor lock-in

13. Non-Goals (Explicit)


Not a UI map SDK
Not a static map exporter
Not provider-locked
Not GoDot-only
Not “just tiles”

🔌 Godot ↔ Rust FFI ABI Design


0. Core Philosophy (non-negotiable)
1. Rust owns all logic and state
2. Godot owns all rendering objects
3. No shared mutable memory

4. No callbacks from Rust into Godot


5. Godot pulls snapshots; Rust never pushes

If you violate any of these, the project will rot.

1. ABI Shape (C-compatible, stable)


We expose a flat C ABI from Rust.

No Rust structs exposed directly


No lifetimes across boundary
No async across boundary
No Godot types in Rust
No Rust types in Godot

Language boundary

1 Godot (GDExtension / C++)


2 ↓
3 C ABI
4 ↓
5 Rust

2. Opaque Handles (Ownership Model)


Rust side

1 #[repr(C)]
2 pub struct RuntimeHandle {
3 ptr: *mut Runtime,
4 }

Godot never dereferences this


Godot treats it as an opaque token
Rust guarantees validity until destroyed

Godot side

1 typedef void* GIS_Runtime;

3. Lifecycle API (Minimal & Complete)


3.1 Create / Destroy

1 GIS_Runtime gis_runtime_create();
2 void gis_runtime_destroy(GIS_Runtime runtime);

Rules

create allocates all long-lived Rust state


destroy stops async tasks, drains queues, frees memory
Godot must call destroy exactly once
4. Configuration Phase (Before Start)
No hot mutation of core config.

1 void gis_runtime_set_provider(
2 GIS_Runtime runtime,
3 const char* provider_name,
4 const char* api_key
5 );
6
7 void gis_runtime_set_viewport(
8 GIS_Runtime runtime,
9 int grid_x,
10 int grid_y
11 );

Rules:

Strings are copied immediately


Caller retains ownership of strings
Configuration must happen before first update

5. Update Loop Contract (Heart of the System)


5.1 Godot → Rust (Push minimal state)
Called once per frame.

1 void gis_runtime_update(
2 GIS_Runtime runtime,
3 double delta_time,
4 double camera_lat,
5 double camera_lon,
6 double zoom,
7 double heading_deg
8 );
Rust responsibilities:

advance streaming window


advance journey state
poll async channels
update internal world state
❗ No rendering happens here

6. Snapshot Model (Pull, Don’t Push)


Godot pulls immutable snapshots every frame.

6.1 Tile Snapshot

1 typedef struct {
2 int zoom;
3 int x;
4 int y;
5 int kind; // raster / vector / height
6 const void* data;
7 int data_len;
8 } GIS_TileSnapshot;

1 int gis_runtime_get_visible_tiles(
2 GIS_Runtime runtime,
3 const GIS_TileSnapshot** out_tiles
4 );

Rules:

Rust owns memory


Snapshot valid until next update
Godot must copy data if it wants to keep it

6.2 Navigation Snapshot


1 typedef struct {
2 double lat;
3 double lon;
4 } GIS_Point;
5
6 typedef struct {
7 GIS_Point* points;
8 int count;
9 } GIS_Path;

1 bool gis_runtime_get_active_path(
2 GIS_Runtime runtime,
3 GIS_Path* out_path
4 );

Geometry already flattened


Ordered for spline / polyline rendering
Valid for one frame only

6.3 Instruction Snapshot

1 typedef struct {
2 const char* text;
3 double remaining_distance_m;
4 double remaining_time_s;
5 } GIS_NavInstruction;

1 bool gis_runtime_get_current_instruction(
2 GIS_Runtime runtime,
3 GIS_NavInstruction* out_instruction
4 );

Strings:

UTF-8
Rust-owned
Valid until next update()

7. Marker / Overlay Snapshots


1 typedef struct {
2 double lat;
3 double lon;
4 int kind;
5 } GIS_Marker;

1 int gis_runtime_get_visible_markers(
2 GIS_Runtime runtime,
3 const GIS_Marker** out_markers
4 );

Markers:

Always stored in GIS space


Godot projects them into world/AR

8. Memory Rules (READ THIS TWICE)


8.1 Allocation Rules

Resource Owner
Runtime Rust
Tile data Rust
Route geometry Rust
Strings Rust
Meshes Godot
Textures Godot

8.2 Lifetime Rules


All snapshot pointers:
invalidated on next update()
Godot must copy if persistence is needed
Rust never frees memory mid-frame

8.3 Threading Rules

All ABI calls are single-threaded


Rust async tasks communicate via channels
Godot thread never blocks on Rust async

9. Error Handling Strategy


No panics across FFI.

1 int gis_runtime_get_last_error(
2 GIS_Runtime runtime,
3 const char** out_message
4 );

Errors are sticky until queried


Godot can log or surface them
Rust continues running unless fatal

10. Minimal Godot Frame Pseudocode


1 void _process(double dt) {
2 gis_runtime_update(
3 runtime,
4 dt,
5 camera_lat,
6 camera_lon,
7 zoom,
8 heading
9 );
10
11 // tiles
12 const GIS_TileSnapshot* tiles;
13 int count = gis_runtime_get_visible_tiles(runtime, &tiles);
14 render_tiles(tiles, count);
15
16 // navigation
17 GIS_Path path;
18 if (gis_runtime_get_active_path(runtime, &path)) {
19 render_path(path);
20 }
21
22 // instruction
23 GIS_NavInstruction instr;
24 if (gis_runtime_get_current_instruction(runtime, &instr)) {
25 ui_show([Link]);
26 }
27 }

No callbacks.
No surprises.
No UB.

11. Why This ABI Will Survive


Godot can crash → Rust still clean
Rust can restart runtime → Godot unaffected
Async never leaks
No lifetime ambiguity
No engine lock-in

This is exactly how engines like Unity DOTS, Mapbox Native, and
game physics runtimes do it internally.

🦀 Rust Module Layout


1. Crate Topology
This should not be one monolithic crate.
1 gis/
2 ├── crates/
3 │ ├── gis-core/ # Pure GIS math + types (NO async, NO IO)
4 │ ├── gis-tiles/ # Tile streaming, quadtree, cache
5 │ ├── gis-net/ # HTTP, providers, async fetchers
6 │ ├── gis-nav/ # Routing, journeys, navigation logic
7 │ ├── gis-geo/ # Geocoding, POIs, world intelligence
8 │ ├── gis-runtime/ # Orchestrator / facade used by engines
9 │ ├── gis-ffi/ # Godot <-> Rust boundary
10 │ └── gis-log/ # Logging & diagnostics
11 │
12 └── [Link] # Workspace

Rule:

Anything that touches the network → async → gis-net


Anything that touches the engine → gis-ffi
Anything that does math → gis-core
Anything that coordinates systems → gis-runtime

2. Core Crates (Deep Dive)


2.1 gis-core (Zero-dependency foundation)
NO async. NO IO. NO engine.

1 gis-core/
2 ├── [Link]
3 ├── geo/
4 │ ├── [Link]
5 │ ├── tile_id.rs
6 │ ├── [Link]
7 │ └── [Link]
8 ├── math/
9 │ ├── [Link]
10 │ └── [Link]
11 └── traits/
12 ├── [Link]
13 └── render_model.rs

Responsibilities

LatLon , TileId , GeoBounds


Mercator math
Tile ↔ lat/lon conversions
Traits only, no implementations

1 pub struct LatLon {


2 pub lat: f64,
3 pub lon: f64,
4 }
5
6 pub struct TileId {
7 pub zoom: u8,
8 pub x: u32,
9 pub y: u32,
10 }

This crate must be:

deterministic
testable
engine-agnostic

2.2 gis-tiles (Streaming + quadtree)

1 gis-tiles/
2 ├── [Link]
3 ├── quadtree/
4 │ ├── [Link]
5 │ ├── [Link]
6 │ └── [Link]
7 ├── cache/
8 │ ├── [Link]
9 │ └── [Link]
10 ├── streaming/
11 │ ├── [Link]
12 │ └── [Link]
13 └── resources/
14 ├── [Link]
15 ├── [Link]
16 └── [Link]

Responsibilities

Tile window calculation (3×3, 4×4, etc.)


Cache eviction
Tile lifecycle state:
requested
in-flight
ready
evicted

1 pub enum TileState {


2 Empty,
3 Fetching,
4 Ready,
5 }

❗ Important

gis-tiles does not fetch data.


It only asks for tiles.
Fetching is injected.

2.3 gis-net (Async, providers, HTTP)

1 gis-net/
2 ├── [Link]
3 ├── http/
4 │ ├── [Link]
5 │ └── [Link]
6 ├── providers/
7 │ ├── [Link]
8 │ ├── [Link]
9 │ └── [Link]
10 ├── fetchers/
11 │ ├── [Link]
12 │ ├── [Link]
13 │ ├── [Link]
14 │ └── [Link]
15 └── decode/
16 ├── [Link]
17 ├── [Link]
18 └── [Link]

Responsibilities

Async HTTP
Provider-specific URL building
Decoding network payloads → raw data models

1 #[async_trait]
2 pub trait TileProvider {
3 async fn fetch_raster(&self, id: TileId) -> Result<RasterTile>;
4 }

Uses:

tokio
reqwest
serde
Nothing engine-facing.

2.4 gis-nav (Navigation engine)


1 gis-nav/
2 ├── [Link]
3 ├── route/
4 │ ├── [Link]
5 │ ├── [Link]
6 │ └── [Link]
7 ├── journey/
8 │ ├── [Link]
9 │ └── [Link]
10 └── instructions/
11 └── [Link]

Responsibilities

Route parsing
Geometry flattening
Journey state machine

1 pub struct Journey {


2 route: Route,
3 cursor: usize,
4 }

No rendering.
No HTTP.
Pure logic + async entry points.

2.5 gis-geo (Geocoding & POIs)

1 gis-geo/
2 ├── [Link]
3 ├── geocode/
4 │ ├── [Link]
5 │ └── [Link]
6 ├── poi/
7 │ ├── [Link]
8 │ └── [Link]
9 └── overlay/
10 └── [Link]

Supports:

External geocoding APIs


Local POI DBs
AR overlay metadata

3. gis-runtime (The brain)


This is the only crate the engine talks to.

1 gis-runtime/
2 ├── [Link]
3 ├── state/
4 │ ├── [Link]
5 │ ├── [Link]
6 │ └── [Link]
7 ├── systems/
8 │ ├── tile_system.rs
9 │ ├── nav_system.rs
10 │ └── overlay_system.rs
11 └── [Link]

Responsibilities
Hold long-lived state
Glue tiles + nav + geo together
Emit plain data outputs for renderer

1 pub struct Runtime {


2 tile_system: TileSystem,
3 nav_system: NavSystem,
4 }

This is where:
tile requests are scheduled
async results are applied
world state advances

4. gis-ffi (Godot boundary)


1 gis-ffi/
2 ├── [Link]
3 ├── api/
4 │ ├── [Link]
5 │ ├── [Link]
6 │ └── [Link]
7 ├── types/
8 │ ├── [Link]
9 │ └── [Link]
10 └── [Link]

Rules
No internal structs leak
Engine sees opaque handles
Rust owns memory

1 #[repr(C)]
2 pub struct RuntimeHandle(*mut Runtime);

Godot:

calls update(dt)
pulls snapshot data
never touches internals

5. Ownership Boundaries (Critical)


5.1 What Rust Owns
Tile cache
Route data
Navigation state
Geo math
Async tasks

5.2 What Engine Owns


Meshes
Materials
Textures
UI widgets

5.3 Data Flow Direction

1 Network → Rust → Plain Data → Engine → GPU

Never:

1 Engine → Rust → GPU

6. Async Model (Very Important)


6.1 Single Tokio Runtime
Created once (inside gis-runtime )
Shared via handles

6.2 Async Boundaries

Layer Async?
gis-core ❌
gis-tiles ❌
gis-net ✅
Layer Async?
gis-nav ⚠️ (fetch only)
gis-runtime ⚠️ (orchestration)
gis-ffi ❌

6.3 Pattern Used

Command → async task → channel → apply on tick

1 // async task
2 tokio::spawn(async move {
3 let tile = provider.fetch_raster(id).await?;
4 [Link](Event::TileReady(tile));
5 });

Runtime:

drains channel during update()


applies changes deterministically

No async inside render loop.

7. Why This Layout Will Scale


You can:
swap providers
swap engines
add offline routing
add LOD tiles
without touching core math
without rewriting FFI
without async bleeding into rendering

You might also like