✨ Added ability to view Columbus CSV GPS tracks
This commit is contained in:
parent
012353d016
commit
6921d28add
6 changed files with 248 additions and 6 deletions
106
Cargo.lock
generated
106
Cargo.lock
generated
|
@ -11,6 +11,21 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "any_spawner"
|
||||
version = "0.2.0"
|
||||
|
@ -137,6 +152,20 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codee"
|
||||
version = "0.3.0"
|
||||
|
@ -231,12 +260,39 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
|
@ -552,6 +608,29 @@ dependencies = [
|
|||
"throw_error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
|
@ -1034,6 +1113,15 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
|
@ -1070,13 +1158,16 @@ checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd"
|
|||
name = "osm_track_importer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"csv",
|
||||
"leptos",
|
||||
"leptos-leaflet",
|
||||
"leptos_meta",
|
||||
"leptos_router",
|
||||
"log",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
|
@ -1989,6 +2080,21 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
|
|
@ -24,6 +24,9 @@ web-sys = { version = "0.3", features = [
|
|||
"HtmlInputElement",
|
||||
"FileList",
|
||||
] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
csv = "1.3.1"
|
||||
chrono = "0.4.40"
|
||||
|
||||
# utils
|
||||
# strum = { version = "0.25", features = ["derive", "strum_macros"] }
|
||||
|
|
112
src/gps/gps_track.rs
Normal file
112
src/gps/gps_track.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
|
||||
use csv::ReaderBuilder;
|
||||
use leptos::prelude::*;
|
||||
use leptos_leaflet::prelude::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn to_polyline(track: &Option<GpsTrack>) -> impl IntoView {
|
||||
if let Some(track) = track {
|
||||
// Extract a vector of (latitude, longitude) tuples from the track points.
|
||||
let positions_vec: Vec<(f64, f64)> = track
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| (point.latitude, point.longitude))
|
||||
.collect();
|
||||
|
||||
// Return a Polyline view using the positions helper.
|
||||
view! { <Polyline positions=positions(&positions_vec) /> }.into_any()
|
||||
} else {
|
||||
view! {}.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GpsTrack {
|
||||
pub points: Vec<GpsTrackPoint>,
|
||||
}
|
||||
|
||||
impl From<&str> for GpsTrack {
|
||||
fn from(value: &str) -> Self {
|
||||
let mut reader = ReaderBuilder::new()
|
||||
.delimiter(b',')
|
||||
.from_reader(value.as_bytes());
|
||||
|
||||
Self {
|
||||
points: reader.deserialize().map(|v| v.unwrap()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GpsTrackPoint {
|
||||
pub index: u32,
|
||||
pub tag: char,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub height: i32,
|
||||
pub speed: f32,
|
||||
pub heading: u32,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GpsTrackPoint {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
struct Helper {
|
||||
index: u32,
|
||||
tag: char,
|
||||
date: String, // e.g., "250301" (ddMMyy)
|
||||
time: String, // e.g., "101102" (HHMMSS)
|
||||
#[serde(rename = "LATITUDE N/S")]
|
||||
latitude: String, // e.g., "51.9787868N"
|
||||
#[serde(rename = "LONGITUDE E/W")]
|
||||
longitude: String, // e.g., "9.2337406E"
|
||||
height: i32,
|
||||
speed: f32,
|
||||
heading: u32,
|
||||
}
|
||||
|
||||
let helper = Helper::deserialize(deserializer)?;
|
||||
|
||||
// Combine DATE and TIME into one string, then parse.
|
||||
let datetime_str = format!("{} {}", helper.date, helper.time);
|
||||
let naive = NaiveDateTime::parse_from_str(&datetime_str, "%y%m%d %H%M%S")
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
// Use the TimeZone trait's method from_utc_datetime.
|
||||
let timestamp = Utc.from_utc_datetime(&naive);
|
||||
|
||||
fn parse_coord<E>(coord: &str) -> Result<f64, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if coord.len() < 2 {
|
||||
return Err(serde::de::Error::custom("Coordinate string too short"));
|
||||
}
|
||||
let (number_part, direction) = coord.split_at(coord.len() - 1);
|
||||
let value: f64 = number_part.parse().map_err(serde::de::Error::custom)?;
|
||||
match direction {
|
||||
"N" | "E" => Ok(value),
|
||||
"S" | "W" => Ok(-value),
|
||||
_ => Err(serde::de::Error::custom("Invalid coordinate direction")),
|
||||
}
|
||||
}
|
||||
|
||||
let latitude = parse_coord::<D::Error>(&helper.latitude)?;
|
||||
let longitude = parse_coord::<D::Error>(&helper.longitude)?;
|
||||
|
||||
Ok(GpsTrackPoint {
|
||||
index: helper.index,
|
||||
tag: helper.tag,
|
||||
timestamp,
|
||||
latitude,
|
||||
longitude,
|
||||
height: helper.height,
|
||||
speed: helper.speed,
|
||||
heading: helper.heading,
|
||||
})
|
||||
}
|
||||
}
|
1
src/gps/mod.rs
Normal file
1
src/gps/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod gps_track;
|
|
@ -4,6 +4,7 @@ use leptos_router::{components::*, path};
|
|||
|
||||
// Modules
|
||||
mod components;
|
||||
mod gps;
|
||||
mod pages;
|
||||
|
||||
// Top-Level pages
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::components::file_select::FileSelect;
|
||||
use crate::gps::gps_track::to_polyline;
|
||||
use crate::{components::file_select::FileSelect, gps::gps_track::GpsTrack};
|
||||
use leptos::logging::log;
|
||||
use leptos::prelude::*;
|
||||
use leptos::wasm_bindgen::JsCast;
|
||||
use leptos_leaflet::leaflet::*;
|
||||
use leptos_leaflet::prelude::*;
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use web_sys::{File, FileReader};
|
||||
|
@ -9,14 +11,15 @@ use web_sys::{File, FileReader};
|
|||
/// Default Home Page
|
||||
#[component]
|
||||
pub fn Home() -> impl IntoView {
|
||||
let file_contents = RwSignal::new(None);
|
||||
let track = RwSignal::new(None);
|
||||
let map: RwSignal<Option<leptos_leaflet::leaflet::Map>, _> = RwSignal::new_local(None);
|
||||
|
||||
// Function to handle file selection
|
||||
let on_file_select = move |file: File| {
|
||||
let reader = FileReader::new().expect("Failed to create FileReader");
|
||||
|
||||
// Clone the signal to move into the closure
|
||||
let file_contents_clone = file_contents.clone();
|
||||
let track_clone = track.clone();
|
||||
|
||||
// Define the callback to handle the 'load' event
|
||||
let onload = Closure::wrap(Box::new(move |event: web_sys::Event| {
|
||||
|
@ -24,7 +27,7 @@ pub fn Home() -> impl IntoView {
|
|||
if let Ok(result) = reader.result() {
|
||||
if let Some(text) = result.as_string() {
|
||||
log!("File content: {}", text);
|
||||
file_contents_clone.set(Some(text));
|
||||
track_clone.set(Some(GpsTrack::from(text.as_str())));
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
@ -39,6 +42,20 @@ pub fn Home() -> impl IntoView {
|
|||
onload.forget();
|
||||
};
|
||||
|
||||
Effect::watch(
|
||||
move || track.get(),
|
||||
move |track, _, _| {
|
||||
map.get().as_ref().unwrap().set_view(
|
||||
&LatLng::new(
|
||||
track.as_ref().unwrap().points[0].latitude,
|
||||
track.as_ref().unwrap().points[0].longitude,
|
||||
),
|
||||
35.0,
|
||||
)
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
view! {
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
view! {
|
||||
|
@ -69,15 +86,17 @@ pub fn Home() -> impl IntoView {
|
|||
"Select Trace File"
|
||||
</FileSelect>
|
||||
<MapContainer
|
||||
center=Position::new(0.0, 0.0)
|
||||
zoom=3.0
|
||||
class="flex-grow w-full"
|
||||
center=Position::new(51.505, -0.09)
|
||||
zoom=13.0
|
||||
map=map.write_only()
|
||||
set_view=true
|
||||
>
|
||||
<TileLayer
|
||||
url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution="© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"
|
||||
/>
|
||||
{{ move || to_polyline(&track.get()) }}
|
||||
</MapContainer>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue