Galileo Map Engine

repository·main·Indexed 20 days ago

https://github.com/galileo-map/galileo

A high-performance, cross-platform general purpose map rendering engine written in Rust. It supports raster and vector tiles, feature layers with advanced styling, and specialized rendering for LiDAR (LAS) and 3D point clouds. The engine can be integrated into Android via Rust and provides a dedicated galileo-egui crate for integration into egui applications using the EguiMap widget and EguiMapState.

Tokens
38.5K
Snippets
119
Records
157
Agent score
70%

What's inside galileo

  1. Explore Galileo map examples

    main

    The galileo repository includes several examples demonstrating different mapping capabilities:

    Tile Layers

    • raster_tiles: Create a map with a single raster tile layer (e.g., OSM) and set initial position/zoom.
    • vector_tiles: Create a map with a vector tile layer (MapLibre), configure styling via a style file, and inspect objects on click.

    Feature Layers & Styling

    • feature_layers: Create maps without a base map using feature layers. Supports advanced styling via symbols based on properties, hover effects to change properties, and click interactions to show/hide features.
    • georust: Load features as geo-types geometries using the geo-zero crate and display them with pin images.
    • highlight_features: Demonstrate getting/updating features on hover and changing pin images based on feature state.
    • linestring: Render a LineString from a GeoJSON FeatureCollection as a Contour in a FeatureLayer.

    Specialized Rendering & Data

    • lambert: Render feature layers using the Lambert Equal Area projection with hover interactions.
    • many_points: Render approximately 3,000,000 3D points over a map.
    • las: Read and render approximately 19,000,000 points from a LAS (LiDAR) dataset. Requires loading the dataset manually as described in the example module docs; run in --release mode.
    • render_to_file: Run a map without a window, load a GeoJSON file into a feature layer, and render the output to a .png file.

    UI Integration

    • with_egui: A version of the raster tiles example that includes support for the egui library.
  2. Build the Galileo map Rust app for Android

    main

    To run the Galileo map example as a Rust app on Android, you must first install Android Studio with the NDK. The build process involves installing cargo-ndk, adding the necessary Rust Android targets, exporting your NDK location, and then using cargo ndk to build the binaries for multiple architectures. Once the build completes, the resulting libraries are placed in the ../app/src/main/jniLibs/ directory, and you can run the application via Android Studio.

    # Install Cargo NDK
    cargo install cargo-ndk
    
    # Install android targets
    rustup target add \
        aarch64-linux-android \
        armv7-linux-androideabi \
        x86_64-linux-android \
        i686-linux-android
    
    # Export NDK location. The location and version number on your system may differ from the bellow
    export ANDROID_NDK_HOME=~/Android/Sdk/ndk/26.1.10909125/
    
    # Build the app
    cargo ndk -t arm64-v8a -t armeabi-v7a -t x86 -t x86_64 -o ../app/src/main/jniLibs/ build
  3. How the two-step rendering process works with Canvas

    main

    Galileo uses a two-step process to render map layers to a Canvas (such as a screen or image). This separation allows expensive calculations like tessellation to be performed in background threads without dropping the frame rate.

    1. Preparation: Layers create RenderBundles containing the primitives they want to render. Expensive calculations are performed when primitives are added to the bundle.
    2. Packing: Once a RenderBundle is ready, it must be converted into a PackedBundle using Canvas::pack_bundle. This step moves the data into GPU buffers. Note: PackedBundles are immutable; if the source RenderBundle changes, you must recreate the packed bundle.
    3. Drawing: PackedBundles are rendered by calling Canvas::draw_bundles with a slice of BundleToDraw objects.

    To optimize performance, layers can cache RenderBundles and PackedBundles between redraws to skip the preparation and packing steps.

    // 1. Create bundle (logic handled by layer)
    let bundle = RenderBundle::new(); 
    
    // 2. Pack bundle (moves data to GPU)
    let packed = canvas.pack_bundle(&bundle);
    
    // 3. Draw bundle
    let to_draw = BundleToDraw::new(&packed, 1.0, Vector2::new(0.0, 0.0));
    canvas.draw_bundles(&[to_draw], RenderOptions::default());
  4. Understand MapLibre style property values

    main

    In MapLibre, every paint and layout property (like line-color or line-width) has a known output type. A property value can be represented in three ways in JSON:

    1. Literal: A plain JSON primitive of the correct type (e.g., "#ff0000" for a color, 2.0 for a number, or true for a boolean).
    2. Expression: A modern (post-v0.41.0) JSON array used to compute values from map state (like zoom level) or feature properties (e.g., ["interpolate", ["linear"], ["zoom"], 5, 1, 10, 4]).
    3. Function: A legacy (pre-v0.41.0) JSON object containing a "stops" key. These are deprecated but still common in existing styles.

    Use the MlStyleValue<T> type to handle these three forms generically for any output type T (typically f64, bool, or String).

    // Example JSON representations:
    // Literal: 2.0
    // Expression: ["interpolate", ["linear"], ["zoom"], 5, 1, 10, 4]
    // Function: {"stops": [[5, 1], [10, 4]]}
  5. Configure sprite sheets with Sprite

    main

    The Sprite enum defines how sprite sheets are referenced in a style. It supports two formats:

    1. Legacy/Backwards-compatible: A single URL string.
    2. Modern: An array of SpriteEntry objects, where each entry has a unique id and a url.

    When using SpriteEntry, if the id is set to "default", the prefix is omitted when referencing images from that sprite.

    // Example: Array of entries
    {
      "sprite": [
        {"id": "default", "url": "https://example.com/sprite"},
        {"id": "extra", "url": "https://example.com/extra-sprite"}
      ]
    }
    
    // Example: Single URL
    {
      "sprite": "https://example.com/sprite"
    }
  6. Understand TileSchema and Level of Detail (LOD)

    main

    A TileSchema defines how tile indices (X, Y, Z) are calculated from map positions and resolutions. It manages the relationship between projected coordinates and tile grid indices.

    Key concepts:

    • Origin: The projected coordinate point where tile index (0, 0) is located. The position of this point depends on the VerticalDirection.
    • Tile Bounds: The rectangle in projected coordinates containing all valid tiles. Tiles outside this range are not requested.
    • World Bounds: The rectangle representing the entire globe/projection. Used for calculating X-coordinate wrapping and logarithmic Z-level resolutions.
    • LOD (Level of Detail): A sorted set of resolutions for each Z-level. Each Z-level in the schema corresponds to a specific resolution value.
    • Wrapping: If wrap_x is enabled, tiles wrap around the X-axis based on the world_bounds, allowing for a horizontally infinite map effect.

    Warning on Deserialization: While TileSchema implements Serialize and Deserialize, it is highly recommended not to construct it via deserialization from external or long-term storage. Incorrectly defined parameters can lead to infinite iteration or panics during tile traversal. Instead, use the TileSchemaBuilder to ensure all parameters are validated.

  7. How Contours and ClosedContours work

    main

    In Galileo, a Contour is a sequence of points representing a path. Contours are categorized into two types:

    1. Open Contours: The first and last points are not connected (e.g., a road).
    2. Closed Contours: The first and last points are implicitly connected by a segment (e.g., a shoreline).

    Key Differences from OGC LineString

    Unlike the OGC LineString standard, a Galileo Contour does not require the first and last points to be identical to be considered "closed." In fact, to avoid duplication, the last point in the sequence should not be the same as the first.

    Abstractions

    • Contour trait: The base trait for both open and closed sequences of points.
    • ClosedContour trait: A specialized trait for geometries that must be closed (e.g., a Polygon). All types implementing ClosedContour automatically implement Contour with is_closed() returning true.
  8. How RasterTileLayerBuilder works

    main

    The RasterTileLayerBuilder follows a builder pattern to compose a RasterTileLayer from several components:

    1. Loader: Defines how tiles are fetched (REST via UrlSource or a Custom RasterTileLoader).
    2. Cache: Defines how tiles are persisted (None, File cache, or a Custom PersistentCacheController).
    3. Schema: Defines the coordinate system and zoom levels (TileSchema).
    4. Messenger: Handles asynchronous loading notifications.

    Key Logic Rules:

    • REST vs Custom: If using new_rest, the builder manages the relationship between the URL source and the cache. If using new_with_loader, the loader is responsible for its own caching and offline logic; the builder will return an error if you try to attach a cache controller to a custom loader.
    • Offline Mode: When offline_mode is true, the RestTileLoader is instructed to only look in the cache and skip network requests.
  9. Understand MapLibre style expressions with MlExpr

    main

    In MapLibre, expressions are used to compute style property values dynamically based on zoom level or feature properties. They are represented in JSON as arrays where the first element is an operator string and the subsequent elements are arguments.

    MlExpr is a Rust enum that provides a typed representation of these expressions. It supports:

    • Literals: Bare JSON primitives (numbers, strings, booleans, null, objects).
    • Variable Binding: Using let to bind names to values and var to reference them.
    • Ramps/Curves: step (piecewise-constant) and interpolate (continuous) expressions.
    • Lookup: get (feature properties), has (existence checks), at (array indexing), and in (membership).
    • Decision Logic: case (if-else), match (switch-case), and coalesce (first non-null).
    • Logical/Math: all, any, not, eq, ne, gt, add, mul, sin, cos, etc.
    • Feature Data: zoom, id, properties, geometry-type, and feature-state.

    When deserializing, any JSON array is parsed as an MlExpr. If the array contains an operator not explicitly handled, it is preserved as MlExpr::Unknown to prevent parsing failures when new operators are added to the MapLibre spec.

    // Example of the JSON structure an MlExpr represents:
    // ["interpolate", ["linear"], ["zoom"], 0, "red", 10, "blue"]
    // This would map to MlExpr::Interpolate with Linear interpolation.
  10. Use the Layer enum to represent MapLibre layers

    main

    The Layer enum is the central abstraction for all supported MapLibre rendering layer types. It is designed to be deserialized from MapLibre style JSON using serde. Each variant corresponds to a specific layer type (e.g., fill, line, symbol) and contains the specific configuration for that layer type.

    Supported layer types include:

    • background: A background fill layer.
    • fill: A filled polygon layer.
    • line: A stroked line layer.
    • symbol: An icon or text label layer.
    • raster: A raster tile layer.
    • circle: A circle layer.
    • fill-extrusion: An extruded polygon (3D) layer.
    • heatmap: A heatmap layer.
    • hillshade: A client-side hillshade layer.
    • sky: A sky/atmosphere dome layer.
    • slot: An insertion-point layer for imported styles.
    • clip: A clipping mask layer.
    // Example of deserializing a layer from JSON
    let json = r#"{
        "id": "Meadow",
        "type": "fill",
        "source": "maptiler_planet",
        "source-layer": "globallandcover",
        "maxzoom": 8,
        "layout": {"visibility": "visible"},
        "paint": {"fill-color": "hsl(75,51%,85%)", "fill-opacity": 0.5}
    }"#;
    let layer: Layer = serde_json::from_str(json).unwrap();
  11. Use the Geom enum for polymorphic geometry operations

    main

    The Geom<P> enum is a container that allows you to work with different geometry types (Point, MultiPoint, Contour, MultiContour, Polygon, MultiPolygon) through a single interface. It implements both the Geometry and CartesianGeometry2d traits, enabling you to perform operations like projection or point-in-polygon tests without knowing the specific underlying geometry type.

    Commonly used conversions include From<P>, From<Contour<P>>, From<Polygon<P>>, and From<MultiPolygon<P>> to wrap specific types into a Geom.

    // Example of wrapping different types into a Geom enum
    let point_geom: Geom<MyPoint> = MyPoint::new(0.0, 0.0).into();
    let poly_geom: Geom<MyPoint> = Polygon::new(...).into();
    
    // You can then use polymorphic methods on the Geom instance
    let bbox = poly_geom.bounding_rectangle();