Spatial Metrics

Analyze the geographic characteristics of events.

SpatialMetrics

The SpatialMetrics struct provides geographic measurements for a set of events.

use spatial_narrative::analysis::SpatialMetrics;
use spatial_narrative::core::{Event, Location, Timestamp};

let events = vec![
    Event::new(Location::new(40.7128, -74.0060), Timestamp::now(), "NYC"),
    Event::new(Location::new(34.0522, -118.2437), Timestamp::now(), "LA"),
    Event::new(Location::new(41.8781, -87.6298), Timestamp::now(), "Chicago"),
];

let metrics = SpatialMetrics::from_events(&events);

Available Metrics

MetricTypeDescription
total_distancef64Sum of distances between consecutive events (meters)
max_extentf64Maximum distance between any two events (meters)
centroid(f64, f64)Geographic center (lat, lon)
boundsGeoBoundsBounding box containing all events
dispersionf64Standard distance from centroid (meters)

Example Output

println!("Metrics for {} events:", events.len());
println!("  Total distance: {:.1} km", metrics.total_distance / 1000.0);
println!("  Maximum extent: {:.1} km", metrics.max_extent / 1000.0);
println!("  Centroid: ({:.4}°, {:.4}°)", metrics.centroid.0, metrics.centroid.1);
println!("  Dispersion: {:.1} km", metrics.dispersion / 1000.0);
println!("  Bounds: {:.2}°N to {:.2}°N, {:.2}°W to {:.2}°W",
    metrics.bounds.min_lat, metrics.bounds.max_lat,
    metrics.bounds.min_lon.abs(), metrics.bounds.max_lon.abs());

Distance Functions

Haversine Distance

Calculate great-circle distance between two points:

use spatial_narrative::analysis::haversine_distance;
use spatial_narrative::core::Location;

let nyc = Location::new(40.7128, -74.0060);
let london = Location::new(51.5074, -0.1278);

let distance_m = haversine_distance(&nyc, &london);
println!("NYC to London: {:.0} km", distance_m / 1000.0);  // ~5570 km

Bearing

Calculate initial bearing between two points:

use spatial_narrative::analysis::bearing;

let from = Location::new(40.7128, -74.0060);  // NYC
let to = Location::new(51.5074, -0.1278);     // London

let degrees = bearing(&from, &to);
println!("Bearing: {:.1}°", degrees);  // ~51° (northeast)

Destination Point

Calculate destination given start, bearing, and distance:

use spatial_narrative::analysis::destination_point;

let start = Location::new(40.7128, -74.0060);
let bearing_deg = 45.0;   // Northeast
let distance_m = 100_000.0;  // 100 km

let dest = destination_point(&start, bearing_deg, distance_m);
println!("Destination: ({:.4}, {:.4})", dest.lat, dest.lon);

Density Mapping

Generate a density map of event locations:

use spatial_narrative::analysis::{density_map, DensityCell};
use spatial_narrative::core::GeoBounds;

// Define grid
let bounds = GeoBounds::new(34.0, -118.5, 41.0, -73.5);
let lat_cells = 50;
let lon_cells = 50;

// Generate density map
let cells = density_map(&events, &bounds, lat_cells, lon_cells);

// Find hotspots
let max_density = cells.iter().map(|c| c.count).max().unwrap_or(0);
let hotspots: Vec<_> = cells.iter()
    .filter(|c| c.count > max_density / 2)
    .collect();

println!("Found {} hotspot cells", hotspots.len());

DensityCell

Each cell in the density map contains:

FieldTypeDescription
lat_idxusizeRow index
lon_idxusizeColumn index
countusizeNumber of events in cell
center(f64, f64)Cell center coordinates

Use Cases

Analyzing Geographic Spread

let metrics = SpatialMetrics::from_events(&events);

if metrics.max_extent > 1_000_000.0 {
    println!("Events span over 1000 km - continental scale");
} else if metrics.max_extent > 100_000.0 {
    println!("Events span over 100 km - regional scale");
} else {
    println!("Events are localized - city scale");
}

Finding the Geographic Center

let metrics = SpatialMetrics::from_events(&events);
let center = Location::new(metrics.centroid.0, metrics.centroid.1);

// Find events nearest to center
for event in &events {
    let dist = haversine_distance(&event.location, &center);
    if dist < 10_000.0 {  // Within 10km of center
        println!("Near center: {}", event.text);
    }
}