Skip to content

Locations

Locations represent ground positions or areas that satellites can access. Brahe provides two fundamental location types—points and polygons—with full GeoJSON interoperability and extensible metadata support.

All location types implement the AccessibleLocation trait, which provides a common interface for coordinate access, property management, and GeoJSON import/export. This design allows you to work with different location geometries through a unified API.

Coordinate Units

All coordinates are specified in geodetic longitude (λ), latitude (φ), and altitude (h) using the WGS84 reference frame. All units are in degrees (for λ and φ) and meters (for h) for consistency with the GeoJSON standard.

PointLocation

A PointLocation represents a single geodetic point on Earth's surface. This is the most common location type, used for ground stations, cities, or specific observation points.

Initialization from Coordinates

Create a point location from geodetic coordinates (longitude, latitude, altitude):

import brahe as bh

bh.initialize_eop()

# Create location (longitude, latitude, altitude in meters)
# San Francisco, CA
sf = bh.PointLocation(-122.4194, 37.7749, 0.0)

# Add an identifier for clarity
sf = sf.with_name("San Francisco")

print(f"Location: {sf.get_name()}")
print(f"Longitude: {sf.longitude(bh.AngleFormat.DEGREES):.4f} deg")
print(f"Latitude: {sf.latitude(bh.AngleFormat.DEGREES):.4f} deg")

# Expected output:
# Location: San Francisco
# Longitude: -122.4194 deg
# Latitude: 37.7749 deg
use brahe as bh;
use bh::utils::Identifiable;
use bh::AccessibleLocation;

fn main() {
    bh::initialize_eop().unwrap();

    // Create location (longitude, latitude, altitude in meters)
    // San Francisco, CA
    let sf = bh::PointLocation::new(
        -122.4194,  // longitude in degrees
        37.7749,    // latitude in degrees
        0.0         // altitude in meters
    ).with_name("San Francisco");

    let geodetic = sf.center_geodetic();
    println!("Location: {}", sf.get_name().unwrap_or_default());
    println!("Longitude: {:.4} deg", geodetic[0]);
    println!("Latitude: {:.4} deg", geodetic[1]);

    // Expected output:
    // Location: San Francisco
    // Longitude: -122.4194 deg
    // Latitude: 37.7749 deg
}

Coordinate Units

Python uses degrees for input convenience. Rust uses radians (SI standard). Both use meters for altitude.

Initialization from GeoJSON

Load locations from GeoJSON strings or files:

import brahe as bh
import json

bh.initialize_eop()

# GeoJSON Point feature
geojson_str = """
{
    "type": "Feature",
    "properties": {"name": "Svalbard Station"},
    "geometry": {
        "type": "Point",
        "coordinates": [15.4038, 78.2232, 458.0]
    }
}
"""

location = bh.PointLocation.from_geojson(json.loads(geojson_str))
print(f"Loaded: {location.get_name()}")
print(f"Longitude: {location.longitude(bh.AngleFormat.DEGREES):.4f} deg")
print(f"Latitude: {location.latitude(bh.AngleFormat.DEGREES):.4f} deg")
print(f"Altitude: {location.altitude():.1f} m")

# Expected output:
# Loaded: Svalbard Station
# Longitude: 15.4038 deg
# Latitude: 78.2232 deg
# Altitude: 458.0 m
use brahe as bh;
use bh::utils::Identifiable;
use bh::AccessibleLocation;

fn main() {
    bh::initialize_eop().unwrap();

    // GeoJSON Point feature
    let geojson = r#"
    {
        "type": "Feature",
        "properties": {"name": "Svalbard Station"},
        "geometry": {
            "type": "Point",
            "coordinates": [15.4038, 78.2232, 458.0]
        }
    }
    "#;

    // Parse JSON string first
    let json: serde_json::Value = serde_json::from_str(geojson).unwrap();
    let location = bh::PointLocation::from_geojson(&json).unwrap();
    let geodetic = location.center_geodetic();
    println!("Loaded: {}", location.get_name().unwrap_or_default());
    println!("Longitude: {:.4} deg", geodetic[0]);
    println!("Latitude: {:.4} deg", geodetic[1]);
    println!("Altitude: {:.1} m", geodetic[2]);

    // Expected output:
    // Loaded: Svalbard Station
    // Longitude: 15.4038 deg
    // Latitude: 78.2232 deg
    // Altitude: 458.0 m
}

Accessing Coordinates

Retrieve coordinates in different formats:

import brahe as bh

bh.initialize_eop()

location = bh.PointLocation(-122.4194, 37.7749, 0.0)

# Access in degrees
print(f"Longitude: {location.longitude(bh.AngleFormat.DEGREES)} deg")
print(f"Latitude: {location.latitude(bh.AngleFormat.DEGREES)} deg")
print(f"Altitude: {location.altitude()} m")

# Shorthand access (in degrees)
print(f"Lon (deg): {location.lon:.6f}")
print(f"Lat (deg): {location.lat:.6f}")

# Get geodetic array [lat, lon, alt] in radians and meters
geodetic = location.center_geodetic()
print(f"Geodetic: [{geodetic[0]:.6f}, {geodetic[1]:.6f}, {geodetic[2]:.1f}]")

# Get ECEF Cartesian position [x, y, z] in meters
ecef = location.center_ecef()
print(f"ECEF: [{ecef[0]:.1f}, {ecef[1]:.1f}, {ecef[2]:.1f}] m")

# Expected output:
# Longitude: -122.4194 deg
# Latitude: 37.7749 deg
# Altitude: 0.0 m
# Lon (deg): -122.419400
# Lat (deg): 37.774900
# Geodetic: [-122.419400, 37.774900, 0.0]
# ECEF: [-2706174.8, -4261059.5, 3885725.5] m
use brahe as bh;
use bh::AccessibleLocation;

fn main() {
    bh::initialize_eop().unwrap();

    let location = bh::PointLocation::new(
        -122.4194,
        37.7749,
        0.0
    );

    // Access geodetic coordinates (in degrees)
    let geodetic = location.center_geodetic();
    println!("Longitude: {:.4} deg", geodetic[0]);
    println!("Latitude: {:.4} deg", geodetic[1]);
    println!("Altitude: {:.1} m", geodetic[2]);


    // Get ECEF Cartesian position [x, y, z] in meters
    let ecef = location.center_ecef();
    println!("ECEF: [{:.1}, {:.1}, {:.1}] m", ecef[0], ecef[1], ecef[2]);

    // Expected output:
    // Longitude: -122.4194 deg
    // Latitude: 37.7749 deg
    // Altitude: 0.0 m
    // ECEF: [-2706174.8, -4261059.5, 3885725.5] m
}

PolygonLocation

A PolygonLocation represents a closed polygon area on Earth's surface. This is useful for imaging regions, coverage zones, or geographic areas of interest.

Initialization from Vertices

Create a polygon from a list of vertices:

import brahe as bh

bh.initialize_eop()

# Define polygon vertices (longitude, latitude, altitude)
# Simple rectangular region
vertices = [
    [-122.5, 37.7, 0.0],
    [-122.35, 37.7, 0.0],
    [-122.35, 37.8, 0.0],
    [-122.5, 37.8, 0.0],
    [-122.5, 37.7, 0.0],  # Close the polygon
]

polygon = bh.PolygonLocation(vertices).with_name("SF Region")

print(f"Name: {polygon.get_name()}")
print(f"Vertices: {polygon.num_vertices}")
print(
    f"Center: ({polygon.longitude(bh.AngleFormat.DEGREES):.4f}, {polygon.latitude(bh.AngleFormat.DEGREES):.4f})"
)

# Expected output:
# Name: SF Region
# Vertices: 4
# Center: (-122.4250, 37.7500)
use brahe as bh;
use bh::utils::Identifiable;
use bh::AccessibleLocation;
use nalgebra as na;

fn main() {
    bh::initialize_eop().unwrap();

    // Define polygon vertices (lon, lat, alt in degrees and meters)
    let vertices = vec![
        na::SVector::<f64, 3>::new(-122.5, 37.7, 0.0),
        na::SVector::<f64, 3>::new(-122.35, 37.7, 0.0),
        na::SVector::<f64, 3>::new(-122.35, 37.8, 0.0),
        na::SVector::<f64, 3>::new(-122.5, 37.8, 0.0),
        na::SVector::<f64, 3>::new(-122.5, 37.7, 0.0),
    ];

    let polygon = bh::PolygonLocation::new(vertices).unwrap()
        .with_name("SF Region");

    let center = polygon.center_geodetic();
    println!("Name: {}", polygon.get_name().unwrap_or_default());
    println!("Vertices: {}", polygon.num_vertices());
    println!("Center: ({:.4}, {:.4})", center[0], center[1]);

    // Expected output:
    // Name: SF Region
    // Vertices: 4
    // Center: (-122.4250, 37.7500)
}

Initialization from GeoJSON

Load polygon areas from GeoJSON:

import brahe as bh
import json

bh.initialize_eop()

# GeoJSON Polygon feature
geojson_str = """
{
    "type": "Feature",
    "properties": {"name": "Target Area"},
    "geometry": {
        "type": "Polygon",
        "coordinates": [[
            [-122.5, 37.7, 0],
            [-122.35, 37.7, 0],
            [-122.35, 37.8, 0],
            [-122.5, 37.8, 0],
            [-122.5, 37.7, 0]
        ]]
    }
}
"""

polygon = bh.PolygonLocation.from_geojson(json.loads(geojson_str))

print(f"Name: {polygon.get_name()}")
print(f"Vertices: {polygon.num_vertices}")
print(
    f"Center: ({polygon.longitude(bh.AngleFormat.DEGREES):.4f}, {polygon.latitude(bh.AngleFormat.DEGREES):.4f})"
)

# Expected output:
# Name: Target Area
# Vertices: 5
# Center: (-122.4250, 37.7500)
use brahe as bh;
use bh::utils::Identifiable;
use bh::AccessibleLocation;

fn main() {
    bh::initialize_eop().unwrap();

    let geojson = r#"
    {
        "type": "Feature",
        "properties": {"name": "Target Area"},
        "geometry": {
            "type": "Polygon",
            "coordinates": [[
                [-122.5, 37.7, 0],
                [-122.35, 37.7, 0],
                [-122.35, 37.8, 0],
                [-122.5, 37.8, 0],
                [-122.5, 37.7, 0]
            ]]
        }
    }
    "#;

    // Parse JSON string first
    let json: serde_json::Value = serde_json::from_str(geojson).unwrap();
    let polygon = bh::PolygonLocation::from_geojson(&json).unwrap();

    let center = polygon.center_geodetic();
    println!("Name: {}", polygon.get_name().unwrap_or_default());
    println!("Vertices: {}", polygon.num_vertices());
    println!("Center: ({:.4}, {:.4})", center[0], center[1]);

    // Expected output:
    // Name: Target Area
    // Vertices: 4
    // Center: (-122.4250, 37.7500)
}

Working with Properties

Both location types support custom properties for storing metadata:

import brahe as bh

bh.initialize_eop()

location = bh.PointLocation(-122.4194, 37.7749, 0.0)

# Add scalar properties
location.add_property("antenna_gain_db", 42.5)
location.add_property("frequency_mhz", 8450.0)

# Add string properties
location.add_property("operator", "NOAA")

# Add boolean flags
location.add_property("uplink_enabled", True)

# Retrieve properties
props = location.properties
gain = props.get("antenna_gain_db")
operator = props.get("operator")
uplink = props.get("uplink_enabled")

print(f"Antenna Gain: {gain}")
print(f"Operator: {operator}")
print(f"Uplink Enabled: {uplink}")

# Expected output:
# Antenna Gain: 42.5
# Operator: NOAA
# Uplink Enabled: True
use brahe as bh;
use bh::AccessibleLocation;
use serde_json::json;

fn main() {
    bh::initialize_eop().unwrap();

    let location = bh::PointLocation::new(-122.4194, 37.7749, 0.0)
        .add_property("antenna_gain_db", json!(42.5))
        .add_property("frequency_mhz", json!(8450.0))
        .add_property("operator", json!("NOAA"))
        .add_property("uplink_enabled", json!(true));

    // Access properties
    let props = location.properties();
    if let Some(gain) = props.get("antenna_gain_db") {
        println!("Antenna Gain: {}", gain);
    }
    if let Some(operator) = props.get("operator") {
        println!("Operator: {}", operator);
    }
    if let Some(uplink) = props.get("uplink_enabled") {
        println!("Uplink Enabled: {}", uplink);
    }

    // Expected output:
    // Antenna Gain: 42.5
    // Operator: "NOAA"
    // Uplink Enabled: true
}

Exporting to GeoJSON

Convert locations back to GeoJSON format:

import brahe as bh

bh.initialize_eop()

location = (
    bh.PointLocation(-122.4194, 37.7749, 0.0).with_name("San Francisco").with_id(1)
)

# Export to GeoJSON dict
geojson = location.to_geojson()
print("Exported GeoJSON:")
print(geojson)

# The output includes all properties and identifiers
# Can be loaded back with from_geojson()
reloaded = bh.PointLocation.from_geojson(geojson)
print(f"\nReloaded: {reloaded.get_name()} (ID: {reloaded.get_id()})")

# Expected output:
# Exported GeoJSON:
# {'geometry': {'coordinates': [-122.4194, 37.7749, 0.0], 'type': 'Point'}, 'properties': {'id': 1, 'name': 'San Francisco'}, 'type': 'Feature'}
#
# Reloaded: San Francisco (ID: 1)
use brahe as bh;
use bh::utils::Identifiable;
use bh::AccessibleLocation;

fn main() {
    bh::initialize_eop().unwrap();

    let location = bh::PointLocation::new(-122.4194, 37.7749, 0.0)
        .with_name("San Francisco")
        .with_id(1);

    // Export to GeoJSON
    let geojson = location.to_geojson();
    println!("Exported GeoJSON:");
    println!("{}", geojson);

    // The output includes all properties and identifiers
    // Can be loaded back with from_geojson()
    let reloaded = bh::PointLocation::from_geojson(&geojson).unwrap();
    println!("\nReloaded: {} (ID: {})",
        reloaded.get_name().unwrap_or_default(),
        reloaded.get_id().unwrap_or(0));

    // Expected output:
    // Exported GeoJSON:
    // {"geometry":{"coordinates":[-122.4194,37.7749,0.0],"type":"Point"},"properties":{"id":1,"name":"San Francisco"},"type":"Feature"}
    //
    // Reloaded: San Francisco (ID: 1)
}

See Also