use crate::{GpsPoint, RouteGroup, RouteSignature, init_logging};
use log::info;
#[uniffi::export(callback_interface)]
pub trait FetchProgressCallback: Send + Sync {
fn on_progress(&self, completed: u32, total: u32);
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct ActivitySportType {
pub activity_id: String,
pub sport_type: String,
}
#[uniffi::export]
pub fn default_scale_presets() -> Vec<crate::ScalePreset> {
crate::ScalePreset::default_presets()
}
#[uniffi::export]
pub fn ffi_detect_sections_multiscale(
activity_ids: Vec<String>,
all_coords: Vec<f64>,
offsets: Vec<u32>,
sport_types: Vec<ActivitySportType>,
groups: Vec<RouteGroup>,
config: crate::SectionConfig,
) -> crate::MultiScaleSectionResult {
init_logging();
info!(
"[RouteMatcherRust] detect_sections_multiscale: {} activities, {} coords, {} scales",
activity_ids.len(),
all_coords.len() / 2,
config.scale_presets.len()
);
let start = std::time::Instant::now();
let mut tracks: Vec<(String, Vec<GpsPoint>)> = Vec::with_capacity(activity_ids.len());
for (i, activity_id) in activity_ids.iter().enumerate() {
let start_offset = offsets[i] as usize;
let end_offset = offsets
.get(i + 1)
.map(|&o| o as usize)
.unwrap_or(all_coords.len() / 2);
let mut points = Vec::with_capacity(end_offset - start_offset);
for j in start_offset..end_offset {
let coord_idx = j * 2;
if coord_idx + 1 < all_coords.len() {
points.push(GpsPoint::new(
all_coords[coord_idx],
all_coords[coord_idx + 1],
));
}
}
if !points.is_empty() {
tracks.push((activity_id.clone(), points));
}
}
info!(
"[RouteMatcherRust] Converted to {} tracks with full GPS data",
tracks.len()
);
let sport_map: std::collections::HashMap<String, String> = sport_types
.into_iter()
.map(|st| (st.activity_id, st.sport_type))
.collect();
let result = crate::sections::detect_sections_multiscale(&tracks, &sport_map, &groups, &config);
let filtered_sections: Vec<_> = result
.sections
.into_iter()
.filter(|s| s.polyline.len() >= 2)
.collect();
let filtered_potentials: Vec<_> = result
.potentials
.into_iter()
.filter(|p| p.polyline.len() >= 2)
.collect();
let elapsed = start.elapsed();
info!(
"[RouteMatcherRust] Multi-scale detection: {} sections, {} potentials in {:?}",
filtered_sections.len(),
filtered_potentials.len(),
elapsed
);
crate::MultiScaleSectionResult {
sections: filtered_sections,
potentials: filtered_potentials,
stats: result.stats,
}
}
#[uniffi::export]
pub fn ffi_generate_heatmap(
signatures: Vec<RouteSignature>,
activity_data: Vec<crate::ActivityHeatmapData>,
config: crate::HeatmapConfig,
) -> crate::HeatmapResult {
init_logging();
info!(
"[RouteMatcherRust] generate_heatmap: {} signatures, {}m cells",
signatures.len(),
config.cell_size_meters
);
let start = std::time::Instant::now();
let data_map: std::collections::HashMap<String, crate::ActivityHeatmapData> = activity_data
.into_iter()
.map(|d| (d.activity_id.clone(), d))
.collect();
let result = crate::generate_heatmap(&signatures, &data_map, &config);
let elapsed = start.elapsed();
info!(
"[RouteMatcherRust] Heatmap generated: {} cells, {} routes, {} activities in {:?}",
result.cells.len(),
result.total_routes,
result.total_activities,
elapsed
);
result
}
#[cfg(feature = "http")]
#[derive(Debug, Clone, uniffi::Record)]
pub struct FfiActivityMapResult {
pub activity_id: String,
pub bounds: Vec<f64>,
pub latlngs: Vec<f64>,
pub success: bool,
pub error: Option<String>,
}
#[cfg(feature = "http")]
#[uniffi::export]
pub fn fetch_activity_maps(
auth_header: String,
activity_ids: Vec<String>,
) -> Vec<FfiActivityMapResult> {
init_logging();
info!(
"[RouteMatcherRust] fetch_activity_maps called for {} activities",
activity_ids.len()
);
let results = crate::http::fetch_activity_maps_sync(auth_header, activity_ids, None);
results
.into_iter()
.map(|r| FfiActivityMapResult {
activity_id: r.activity_id,
bounds: r
.bounds
.map_or(vec![], |b| vec![b.ne[0], b.ne[1], b.sw[0], b.sw[1]]),
latlngs: r.latlngs.map_or(vec![], |coords| {
coords.into_iter().flat_map(|p| vec![p[0], p[1]]).collect()
}),
success: r.success,
error: r.error,
})
.collect()
}
#[cfg(feature = "http")]
#[uniffi::export]
pub fn fetch_activity_maps_with_progress(
auth_header: String,
activity_ids: Vec<String>,
callback: Box<dyn FetchProgressCallback>,
) -> Vec<FfiActivityMapResult> {
use std::sync::Arc;
init_logging();
info!(
"[RouteMatcherRust] fetch_activity_maps_with_progress called for {} activities",
activity_ids.len()
);
let callback = Arc::new(callback);
let progress_callback: crate::http::ProgressCallback = Arc::new(move |completed, total| {
callback.on_progress(completed, total);
});
let results =
crate::http::fetch_activity_maps_sync(auth_header, activity_ids, Some(progress_callback));
results
.into_iter()
.map(|r| FfiActivityMapResult {
activity_id: r.activity_id,
bounds: r
.bounds
.map_or(vec![], |b| vec![b.ne[0], b.ne[1], b.sw[0], b.sw[1]]),
latlngs: r.latlngs.map_or(vec![], |coords| {
coords.into_iter().flat_map(|p| vec![p[0], p[1]]).collect()
}),
success: r.success,
error: r.error,
})
.collect()
}