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.
Overview¶
All locations in Brahe share common capabilities:
- Geographic coordinates: Geodetic (lat/lon/alt) and ECEF (Earth-fixed Cartesian)
- Identification: Name, numeric ID, and UUID support
- Properties: Extensible metadata dictionary for custom data
- GeoJSON: Import/export compatibility with GIS systems
- Type safety: Strong typing with the
AccessibleLocationtrait
PointLocation¶
Point locations represent discrete positions on Earth's surface, such as:
- Ground stations and tracking sites
- Imaging targets and waypoints
Creating Point Locations¶
import brahe as bh
# Simple creation with lon, lat, alt
svalbard = bh.PointLocation(15.4, 78.2, 0.0)
# With identification
svalbard = bh.PointLocation(15.4, 78.2, 0.0).with_name("Svalbard")
# With multiple identifiers
station = bh.PointLocation(-117.2, 34.1, 500.0) \
.with_name("Goldstone") \
.with_id(42) \
.with_new_uuid()
# With custom properties
loc = bh.PointLocation(15.4, 78.2, 0.0) \
.with_name("Svalbard") \
.add_property("country", "Norway") \
.add_property("operator", "KSAT") \
.add_property("frequency_band", "X-band")
Coordinate Systems¶
Point locations maintain coordinates in both geodetic and ECEF systems:
loc = bh.PointLocation(15.4, 78.2, 500.0)
# Geodetic coordinates (degrees, meters)
lon = loc.lon() # 15.4 degrees
lat = loc.lat() # 78.2 degrees
alt = loc.alt() # 500.0 meters
# ECEF coordinates (meters)
ecef = loc.center_ecef() # Vector3 [x, y, z] in meters
# Access with angle format conversion
lon_deg = loc.longitude(bh.AngleFormat.DEGREES)
lon_rad = loc.longitude(bh.AngleFormat.RADIANS)
Coordinate Conventions: - Longitude: -180° to +180° (negative = West, positive = East) - Latitude: -90° to +90° (negative = South, positive = North) - Altitude: Height above WGS84 ellipsoid (meters) - ECEF: Earth-fixed Cartesian coordinates (meters)
GeoJSON Integration¶
Point locations seamlessly convert to/from GeoJSON Feature format:
import brahe as bh
import json
# Create from GeoJSON
geojson = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [15.4, 78.2, 500.0] # [lon, lat, alt]
},
"properties": {
"name": "Svalbard",
"country": "Norway"
}
}
loc = bh.PointLocation.from_geojson(geojson)
# Export to GeoJSON
geojson = loc.to_geojson()
print(json.dumps(geojson, indent=2))
GeoJSON format details:
- Coordinates: [longitude, latitude, altitude] (note lon/lat order!)
- Altitude is optional (defaults to 0.0)
- Properties include identity fields (name, id, uuid) plus custom metadata
- Fully compatible with QGIS, GeoPandas, and other GIS tools
Use Cases¶
Ground Stations:
# Ground station network
stations = [
bh.PointLocation(15.4, 78.2, 0.0).with_name("Svalbard"),
bh.PointLocation(-64.5, -31.5, 0.0).with_name("Malargue"),
bh.PointLocation(-117.2, 34.1, 0.0).with_name("Goldstone"),
]
for station in stations:
station.add_property("elevation_mask_deg", 5.0)
station.add_property("max_data_rate_mbps", 300.0)
Imaging Targets:
# Points of interest
targets = [
bh.PointLocation(2.3, 48.9, 0.0)
.with_name("Paris")
.add_property("priority", "high")
.add_property("min_off_nadir_deg", 5.0)
.add_property("max_off_nadir_deg", 30.0),
bh.PointLocation(139.7, 35.7, 0.0)
.with_name("Tokyo")
.add_property("priority", "medium")
.add_property("cloud_tolerance_pct", 20.0),
]
Grid Tessellation:
# Create global grid
import numpy as np
grid_points = []
for lat in np.arange(-90, 90, 10):
for lon in np.arange(-180, 180, 10):
point = bh.PointLocation(lon, lat, 0.0) \
.with_name(f"Grid_{lat}_{lon}") \
.add_property("grid_cell_id", f"{lat}_{lon}")
grid_points.append(point)
print(f"Created {len(grid_points)} grid points")
PolygonLocation¶
Polygon locations represent areas on Earth's surface, such as:
- Areas of interest (countries, regions, sea zones)
- Imaging swaths and coverage footprints
Creating Polygon Locations¶
import brahe as bh
from nalgebra import Vector3 # From brahe Rust bindings
import numpy as np
# Define vertices [lon, lat, alt]
vertices = [
Vector3(10.0, 50.0, 0.0),
Vector3(11.0, 50.0, 0.0),
Vector3(11.0, 51.0, 0.0),
Vector3(10.0, 51.0, 0.0),
Vector3(10.0, 50.0, 0.0), # Closed polygon (first == last)
]
# Create polygon
aoi = bh.PolygonLocation(vertices).with_name("AOI-1")
# Auto-closure: First/last vertex don't need to match
vertices = [
Vector3(10.0, 50.0, 0.0),
Vector3(11.0, 50.0, 0.0),
Vector3(11.0, 51.0, 0.0),
Vector3(10.0, 51.0, 0.0),
# Last vertex automatically added to close polygon
]
aoi = bh.PolygonLocation(vertices) # Auto-closed
# With properties
region = bh.PolygonLocation(vertices) \
.with_name("Europe-Central") \
.add_property("population_millions", 82) \
.add_property("min_cloud_free_pct", 80)
Polygon Properties¶
Polygons compute their centroid automatically:
poly = bh.PolygonLocation(vertices)
# Centroid coordinates
center_lon = poly.lon # Average longitude
center_lat = poly.lat # Average latitude
center_alt = poly.alt # Average altitude
# Vertex access
verts = poly.vertices # All vertices (including closure)
num_unique = poly.num_vertices # Excluding closure vertex
# ECEF center
ecef_center = poly.center_ecef
GeoJSON for Polygons¶
# Create from GeoJSON
geojson = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[ # Note: nested array for outer ring
[10.0, 50.0, 0.0],
[11.0, 50.0, 0.0],
[11.0, 51.0, 0.0],
[10.0, 51.0, 0.0],
[10.0, 50.0, 0.0]
]]
},
"properties": {
"name": "AOI-1",
"region": "Europe"
}
}
poly = bh.PolygonLocation.from_geojson(geojson)
# Export to GeoJSON
geojson = poly.to_geojson()
Polygon GeoJSON notes:
- Coordinates are nested: [[[lon, lat, alt], ...]] (outer ring)
- First and last vertex must match (closed)
- All vertices must be unique (except closure)
Polygon Validity
Polygons must not contain holes.
Use Cases¶
Coverage Analysis:
# Define coverage regions
regions = [
bh.PolygonLocation([
Vector3(-10.0, 35.0, 0.0),
Vector3(40.0, 35.0, 0.0),
Vector3(40.0, 70.0, 0.0),
Vector3(-10.0, 70.0, 0.0),
]).with_name("Europe"),
bh.PolygonLocation([
Vector3(-125.0, 25.0, 0.0),
Vector3(-65.0, 25.0, 0.0),
Vector3(-65.0, 50.0, 0.0),
Vector3(-125.0, 50.0, 0.0),
]).with_name("North-America"),
]
Sensor Footprint:
# Imaging sensor footprint (simplified rectangular approximation)
def create_footprint(center_lon, center_lat, width_deg, height_deg):
"""Create rectangular footprint around center point"""
half_w = width_deg / 2.0
half_h = height_deg / 2.0
vertices = [
Vector3(center_lon - half_w, center_lat - half_h, 0.0),
Vector3(center_lon + half_w, center_lat - half_h, 0.0),
Vector3(center_lon + half_w, center_lat + half_h, 0.0),
Vector3(center_lon - half_w, center_lat + half_h, 0.0),
]
return bh.PolygonLocation(vertices)
# Create footprints
footprint1 = create_footprint(15.0, 50.0, 2.0, 1.5) \
.with_name("Footprint-1") \
.add_property("swath_width_km", 100.0)
Extensible Properties¶
Both location types support arbitrary metadata through a properties dictionary:
Adding Properties¶
loc = bh.PointLocation(15.4, 78.2, 0.0)
# Builder pattern
loc = loc.add_property("country", "Norway") \
.add_property("elevation_mask_deg", 5.0) \
.add_property("operational_hours", [8, 18])
# Direct access
props = loc.properties()
country = props.get("country")
Common Property Patterns¶
Ground Station Metadata:
station = bh.PointLocation(lon, lat, alt) \
.with_name("Station-1") \
.add_property("operator", "ESA") \
.add_property("dish_diameter_m", 15.0) \
.add_property("frequency_bands", ["X", "Ka"]) \
.add_property("max_data_rate_mbps", 800.0) \
.add_property("elevation_mask_deg", 5.0) \
.add_property("operational_24_7", True)
Imaging Target Constraints:
target = bh.PointLocation(lon, lat, 0.0) \
.with_name("Target-Alpha") \
.add_property("priority", 10) \
.add_property("min_off_nadir_deg", 0.0) \
.add_property("max_off_nadir_deg", 30.0) \
.add_property("preferred_look_direction", "RIGHT") \
.add_property("min_sun_elevation_deg", 10.0) \
.add_property("max_cloud_cover_pct", 20.0)
Identification and Traceability¶
All locations implement the Identifiable trait for tracking and association:
Identity Methods¶
# Name-based identification
loc = bh.PointLocation(lon, lat, alt).with_name("Station-1")
assert loc.get_name() == "Station-1"
# Numeric ID
loc = loc.with_id(42)
assert loc.get_id() == 42
# UUID for global uniqueness
import uuid
my_uuid = uuid.uuid4()
loc = loc.with_uuid(my_uuid)
assert loc.get_uuid() == my_uuid
# Or generate new UUID
loc = loc.with_new_uuid()
assert loc.get_uuid() is not None
# Combined identity
loc = loc.with_identity(
name="Station-1",
uuid=my_uuid,
id=42
)
Linking Locations to Access Windows¶
Access windows preserve location and propagator identifiers:
windows = bh.location_accesses(locations, propagators, start, end, constraint)
for window in windows:
# Identifiers stored in window
loc_id = window.location_id
prop_id = window.propagator_id
# Find original location/propagator by ID
matching_loc = next(l for l in locations if l.get_id() == loc_id)
matching_prop = next(p for p in propagators if p.get_id() == prop_id)
print(f"Access: {matching_prop.get_name()} -> {matching_loc.get_name()}")
Common Patterns¶
Loading Locations from GeoJSON¶
import json
import brahe as bh
# Load GeoJSON FeatureCollection
with open("ground_stations.geojson") as f:
data = json.load(f)
locations = []
for feature in data["features"]:
loc = bh.PointLocation.from_geojson(feature)
locations.append(loc)
print(f"Loaded {len(locations)} locations")
Exporting Results to GeoJSON¶
# Collect access results
windows = bh.location_accesses(locations, propagators, start, end, constraint)
# Create GeoJSON with access statistics
features = []
for loc in locations:
# Find all windows for this location
loc_windows = [w for w in windows if w.location_id == loc.get_id()]
# Add access statistics to properties
loc = loc.add_property("total_passes", len(loc_windows))
loc = loc.add_property("total_duration_sec", sum(w.duration for w in loc_windows))
features.append(loc.to_geojson())
# Export FeatureCollection
geojson = {
"type": "FeatureCollection",
"features": features
}
with open("access_results.geojson", "w") as f:
json.dump(geojson, f, indent=2)
Custom Property Computation¶
# Compute derived properties
for loc in locations:
# Geographic region classification
if loc.lat() > 60:
region = "polar"
elif abs(loc.lat()) < 23.5:
region = "tropical"
else:
region = "temperate"
loc = loc.add_property("climate_region", region)
# Sun-synchronous orbit access potential
if 96.0 <= abs(loc.lat()) <= 100.0:
loc = loc.add_property("sso_compatible", True)
See Also¶
- Constraints - Defining access criteria for locations
- Computation - Access algorithms and property computation
- API Reference: Locations
- Example: Predicting Ground Contacts