Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Maps and Location

In this chapter, you will:

  • Embed interactive maps with annotations and styles
  • Track and display the user’s real-time location
  • Build a complete location-aware feature with reactive state
  • Understand cross-platform differences in map rendering

Picture a ride-sharing app that follows your car in real time, or a travel guide that drops pins on every restaurant nearby. Maps are one of those features that instantly make an app feel polished and professional. WaterUI gives you native map rendering through the waterui-map crate and cross-platform location access via waterkit-location, all with a declarative, reactive API.

Feature flag: Maps live behind the map feature on waterui. Enable it in Cargo.toml (waterui = { version = "...", features = ["map"] }) so the prelude re-export waterui::map is available.

Crate Overview

CratePurpose
waterui-mapThe Map view component, Coordinate, Region, Annotation, map styles
waterkit-locationCross-platform Location::get(), permission handling, coordinate data

The map crate re-exports the location crate for convenience:

use waterui_map::location; // waterkit-location
use waterui_map::Location; // waterkit_location::Location

Coordinates and Regions

Before you can display a map, you need to tell it where to look. That starts with two types: Coordinate and Region.

Coordinate

A geographic point on the globe:

use waterui_map::Coordinate;

let san_francisco = Coordinate::new(37.7749, -122.4194);
let tokyo = Coordinate::new(35.6762, 139.6503);

Coordinate has two fields:

FieldTypeRange
latitudef64-90.0 to 90.0
longitudef64-180.0 to 180.0

You can convert from a waterkit_location::Location directly:

use waterui_map::Coordinate;
use waterkit_location::Location;

// From a Location reference
let coord = Coordinate::from_location(&location);

// Or via Into
let coord: Coordinate = location.into();

Region

A Region describes the visible area of the map – a center coordinate plus a span in degrees:

use waterui_map::{Coordinate, Region};

// Explicit span
let bay_area = Region::new(
    Coordinate::new(37.7749, -122.4194),
    0.5,  // latitude span in degrees
    0.5,  // longitude span in degrees
);

// Default zoom from a coordinate
let zoomed_in = Region::from_coordinate(Coordinate::new(37.7749, -122.4194));
// Uses 0.05 degree span in both directions
FieldDescription
centerThe Coordinate at the center of the visible region
latitude_deltaNorth-to-south span in degrees (smaller = more zoomed in)
longitude_deltaEast-to-west span in degrees

Region implements From<Coordinate>, so you can pass a bare coordinate anywhere a region is expected and get a sensible default zoom.


Displaying a Map

Now let’s put a map on screen. You will find it is just as straightforward as placing any other view.

Basic Map

use waterui_map::{Map, Coordinate, Region};

fn city_map() -> impl View {
    let region = Region::new(
        Coordinate::new(48.8566, 2.3522), // Paris
        0.1,
        0.1,
    );
    Map::new(region)
}

Map::new accepts any Into<Computed<Region>>, meaning you can pass:

  • A static Region value
  • A reactive Computed<Region> signal that updates the visible area over time

Centering on a Coordinate

If you only have a coordinate and want the default zoom:

use waterui_map::{Map, Coordinate};

fn pin_map() -> impl View {
    Map::centered_on(Coordinate::new(35.6762, 139.6503))
}

Map::centered_on also accepts Computed<Coordinate> for reactive updates.

Centering on a Location

When working directly with the waterkit-location crate:

use waterui_map::Map;
use waterkit_location::Location;

fn location_map(location: Computed<Location>) -> impl View {
    Map::centered_on_location(location)
}

Annotations (Map Markers)

A map without markers is just a pretty picture. Add pins with Annotation:

use waterui_map::{Map, Coordinate, Region, Annotation};

fn annotated_map() -> impl View {
    let sf = Coordinate::new(37.7749, -122.4194);
    let la = Coordinate::new(34.0522, -118.2437);

    Map::new(Region::new(Coordinate::new(36.0, -120.0), 5.0, 5.0))
        .annotations(vec![
            Annotation::new(sf, "San Francisco"),
            Annotation::new(la, "Los Angeles")
                .subtitle("City of Angels"),
        ])
}

Annotation API

MethodDescription
Annotation::new(coordinate, title)Create with a position and title
.subtitle(text)Add optional subtitle text

Each annotation has these fields:

FieldTypeDescription
coordinateCoordinateWhere the pin is placed
titleStrPrimary label shown on the annotation
subtitleOption<Str>Secondary label (optional)

Reactive Annotations

Since annotations accepts Into<Computed<Vec<Annotation>>>, you can drive the marker list from a reactive signal. This is perfect for search results, live tracking, or any data that changes over time:

use waterui::prelude::*;
use waterui_map::{Map, Coordinate, Region, Annotation};

fn dynamic_markers() -> impl View {
    let markers = Binding::container(vec![
        Annotation::new(Coordinate::new(37.7749, -122.4194), "Start"),
    ]);

    Map::new(Region::default())
        .annotations(markers.into_computed())
}

Map Styles

Choose between three display modes to match the feel of your app:

use waterui_map::{Map, Region, MapStyle};

fn satellite_view() -> impl View {
    Map::new(Region::default())
        .style(MapStyle::Satellite)
}
StyleDescription
MapStyle::StandardRoad map with labels (default)
MapStyle::SatelliteSatellite imagery
MapStyle::HybridSatellite imagery with road overlays

User Location

Showing the User’s Position

Display the familiar blue dot indicating the user’s current location:

use waterui_map::{Map, Region};

fn location_enabled_map() -> impl View {
    Map::new(Region::default())
        .shows_user_location(true)
}

Note: This requires location permission. Use Location::ask_permission() or let the platform prompt the user automatically.

Following the User

follows_location both centers the map on a reactive Location stream and enables the user-location indicator. The map moves as the user moves – great for navigation or fitness tracking:

use waterui_map::Map;
use waterkit_location::Location;

fn tracking_map(location: Computed<Location>) -> impl View {
    Map::new(Region::default())
        .follows_location(location)
}

This is equivalent to calling shows_user_location(true) plus binding the region to the location signal.


Map Interaction Controls

Fine-tune the map’s interactive behavior. For example, you might want a non-interactive overview map in a list cell:

use waterui_map::{Map, Region};

fn static_overview() -> impl View {
    Map::new(Region::default())
        .is_interactive(false)  // Disable pan and zoom
        .shows_compass(false)   // Hide the compass
        .shows_scale(true)      // Show the scale bar
}
MethodDefaultDescription
is_interactive(bool)trueEnable or disable pan/zoom gestures
shows_compass(bool)trueShow the compass indicator
shows_scale(bool)trueShow the distance scale bar

Getting the User’s Location

The waterkit-location crate provides a cross-platform API for accessing device location:

use waterkit_location::{Location, LocationError};

async fn where_am_i() -> Result<(), LocationError> {
    let location = Location::get().await?;

    tracing::info!("Lat: {}", location.latitude());
    tracing::info!("Lon: {}", location.longitude());

    if let Some(alt) = location.altitude() {
        tracing::info!("Altitude: {} meters", alt);
    }

    tracing::info!("Accuracy: {:?} meters", location.horizontal_accuracy());
    tracing::info!("Time: {:?}", location.timestamp());

    Ok(())
}

Location Accessors

MethodReturn TypeDescription
latitude()f64Latitude in degrees
longitude()f64Longitude in degrees
altitude()Option<f64>Altitude in meters above sea level
horizontal_accuracy()Option<f64>Horizontal accuracy in meters
vertical_accuracy()Option<f64>Vertical accuracy in meters
timestamp()TimestampWhen the location was recorded

Error Handling

Location::get() returns Result<Location, LocationError>:

ErrorDescription
PermissionDeniedThe user denied location access
ServiceDisabledLocation services are turned off on the device
TimeoutThe location request timed out
NotAvailableLocation data could not be determined
Unknown(String)An unexpected platform error

Permissions

Location::get() calls Location::ask_permission() automatically. If you want to check permission status before presenting the map, call it explicitly:

use waterkit_location::Location;

async fn ensure_location_access() {
    if let Err(e) = Location::ask_permission().await {
        tracing::warn!("Location permission not granted: {e}");
    }
}

Convenience Function

A free function map() is available as a shorthand for Map::new():

use waterui_map::{map, Region};

fn quick_map() -> impl View {
    map(Region::default())
}

Complete Example

Here is a complete example that fetches the user’s location and displays a map centered on it with an annotation:

use waterui::prelude::*;
use waterui_map::{Annotation, Coordinate, Map, MapStyle, Region};
use waterkit_location::Location;

fn location_app() -> impl View {
    let location_binding: Binding<Option<Location>> = Binding::container(None);

    // Reactive map region and annotations derived from the same source signal.
    let region = location_binding.map(|opt| {
        opt.map(|loc| Region::from_coordinate(Coordinate::from(loc)))
            .unwrap_or_default()
    });

    let annotations = location_binding.map(|opt| {
        opt.map(|loc| vec![
            Annotation::new(Coordinate::from(loc), "You are here"),
        ])
        .unwrap_or_default()
    });

    // Fetch the location once when the map appears; the task is cancelled with the view.
    let loc = location_binding.clone();
    Map::new(region)
        .annotations(annotations)
        .style(MapStyle::Standard)
        .shows_user_location(true)
        .shows_compass(true)
        .task(async move {
            match Location::get().await {
                Ok(location) => loc.set(Some(location)),
                Err(e) => tracing::error!("Location error: {e}"),
            }
        })
}

Platform Considerations

FeatureAppleAndroidDesktop
Map renderingMKMapView (MapKit)Platform map viewWIP
Standard/Satellite/HybridAll supportedAll supported
User location dotNativeNative
AnnotationsNative pinsNative markers
Location accessCoreLocationFusedLocationProviderGeoClue (Linux), WinRT (Windows)

The Map component uses the configurable! macro with StretchAxis::Both, meaning it expands to fill available space in both directions by default. Use layout modifiers like .frame() to constrain its size when needed.


What’s Next

You have maps and location covered. Next up: WebView, where you will embed web content directly into your app – complete with JavaScript bridges, cookie management, and navigation controls.