mirror of
https://github.com/seemueller-io/yachtpit.git
synced 2025-09-08 22:46:45 +00:00
Add GPS service and nautical base city data
- Implement `GpsService` with methods for position updates and enabling/disabling GPS. - Introduce test data for nautical base cities with key attributes like population, coordinates, and images. - Update dependencies in `bun.lock` with required packages such as `geojson`.
This commit is contained in:
@@ -60,7 +60,7 @@ webbrowser = { version = "1", features = ["hardened"] }
|
||||
systems = { path = "../systems" }
|
||||
components = { path = "../components" }
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3.53", features = ["Document", "Element", "HtmlElement", "Window"] }
|
||||
web-sys = { version = "0.3.53", features = ["Document", "Element", "HtmlElement", "Window", "Navigator", "Geolocation", "Position", "PositionOptions", "PositionError", "Coordinates"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -78,6 +78,7 @@ winit = "0.30"
|
||||
bevy_webview_wry = { version = "0.4", default-features = false, features = ["api"] }
|
||||
bevy_flurx = "0.11"
|
||||
bevy_flurx_ipc = "0.4.0"
|
||||
# GPS support for native platforms - placeholder for future real GPS implementation
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { version = "1.0", features = ["rt"] }
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
mod core;
|
||||
mod ui;
|
||||
mod services;
|
||||
|
||||
use bevy::app::App;
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -11,6 +12,7 @@ use bevy::prelude::*;
|
||||
use crate::core::{ActionsPlugin, SystemManagerPlugin};
|
||||
use crate::core::system_manager::SystemManager;
|
||||
use crate::ui::{LoadingPlugin, MenuPlugin, GpsMapPlugin};
|
||||
use crate::services::GpsServicePlugin;
|
||||
use systems::{PlayerPlugin, setup_instrument_cluster, get_vessel_systems};
|
||||
|
||||
// See https://bevy-cheatbook.github.io/programming/states.html
|
||||
@@ -41,11 +43,12 @@ impl Plugin for GamePlugin {
|
||||
LoadingPlugin,
|
||||
MenuPlugin,
|
||||
GpsMapPlugin,
|
||||
GpsServicePlugin,
|
||||
ActionsPlugin,
|
||||
SystemManagerPlugin,
|
||||
PlayerPlugin,
|
||||
))
|
||||
|
||||
|
||||
.add_systems(OnEnter(GameState::Playing), (setup_instrument_cluster, initialize_vessel_systems));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
211
crates/yachtpit/src/services/gps_service.rs
Normal file
211
crates/yachtpit/src/services/gps_service.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GpsData {
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub altitude: Option<f64>,
|
||||
pub accuracy: Option<f64>,
|
||||
pub heading: Option<f64>,
|
||||
pub speed: Option<f64>,
|
||||
pub timestamp: f64,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct GpsService {
|
||||
pub current_position: Option<GpsData>,
|
||||
pub is_enabled: bool,
|
||||
pub last_update: f64,
|
||||
}
|
||||
|
||||
impl GpsService {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_position: None,
|
||||
is_enabled: false,
|
||||
last_update: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
self.is_enabled = true;
|
||||
info!("GPS service enabled");
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) {
|
||||
self.is_enabled = false;
|
||||
info!("GPS service disabled");
|
||||
}
|
||||
|
||||
pub fn update_position(&mut self, gps_data: GpsData) {
|
||||
self.current_position = Some(gps_data.clone());
|
||||
self.last_update = gps_data.timestamp;
|
||||
info!("GPS position updated: lat={:.6}, lon={:.6}", gps_data.latitude, gps_data.longitude);
|
||||
}
|
||||
|
||||
pub fn get_current_position(&self) -> Option<&GpsData> {
|
||||
self.current_position.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// Native GPS implementation - Mock implementation for demonstration
|
||||
// TODO: Replace with real GPS hardware access (e.g., using gpsd, CoreLocation, etc.)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_native_gps_tracking(mut gps_service: ResMut<GpsService>, time: Res<Time>) {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
if !gps_service.is_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock GPS data that simulates realistic movement
|
||||
// In a real implementation, this would read from GPS hardware
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs_f64();
|
||||
|
||||
// Only update every 2 seconds to simulate realistic GPS update rate
|
||||
if timestamp - gps_service.last_update < 2.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Simulate GPS coordinates around Monaco with realistic movement
|
||||
let base_lat = 43.7384;
|
||||
let base_lon = 7.4246;
|
||||
let time_factor = time.elapsed_secs() * 0.1;
|
||||
|
||||
// Simulate a boat moving in a realistic pattern
|
||||
let lat_offset = (time_factor.sin() * 0.002) as f64;
|
||||
let lon_offset = (time_factor.cos() * 0.003) as f64;
|
||||
|
||||
let gps_data = GpsData {
|
||||
latitude: base_lat + lat_offset,
|
||||
longitude: base_lon + lon_offset,
|
||||
altitude: Some(0.0), // Sea level
|
||||
accuracy: Some(3.0), // 3 meter accuracy
|
||||
heading: Some(((time_factor * 20.0) % 360.0) as f64),
|
||||
speed: Some(5.2), // 5.2 knots
|
||||
timestamp,
|
||||
};
|
||||
|
||||
gps_service.update_position(gps_data);
|
||||
}
|
||||
|
||||
// Web GPS implementation using geolocation API
|
||||
// For web platforms, we'll use a simplified approach that requests position periodically
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start_web_gps_tracking(mut gps_service: ResMut<GpsService>, time: Res<Time>) {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
if !gps_service.is_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs_f64();
|
||||
|
||||
// Only try to get GPS every 5 seconds to avoid overwhelming the browser
|
||||
if timestamp - gps_service.last_update < 5.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// For now, use mock data for web as well
|
||||
// TODO: Implement proper web geolocation API integration using channels or events
|
||||
let time_factor = time.elapsed_secs() * 0.1;
|
||||
let base_lat = 43.7384;
|
||||
let base_lon = 7.4246;
|
||||
|
||||
let lat_offset = (time_factor.sin() * 0.001) as f64;
|
||||
let lon_offset = (time_factor.cos() * 0.002) as f64;
|
||||
|
||||
let gps_data = GpsData {
|
||||
latitude: base_lat + lat_offset,
|
||||
longitude: base_lon + lon_offset,
|
||||
altitude: Some(0.0),
|
||||
accuracy: Some(5.0), // Slightly less accurate on web
|
||||
heading: Some(((time_factor * 15.0) % 360.0) as f64),
|
||||
speed: Some(4.8), // Slightly different speed for web
|
||||
timestamp,
|
||||
};
|
||||
|
||||
gps_service.update_position(gps_data);
|
||||
info!("Web GPS position updated: lat={:.6}, lon={:.6}", gps_data.latitude, gps_data.longitude);
|
||||
}
|
||||
|
||||
pub struct GpsServicePlugin;
|
||||
|
||||
impl Plugin for GpsServicePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<GpsService>()
|
||||
.add_systems(Update, (
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
start_native_gps_tracking,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
start_web_gps_tracking,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[test]
|
||||
fn test_gps_service_initialization() {
|
||||
let mut gps_service = GpsService::new();
|
||||
assert!(!gps_service.is_enabled);
|
||||
assert!(gps_service.current_position.is_none());
|
||||
|
||||
gps_service.enable();
|
||||
assert!(gps_service.is_enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gps_data_update() {
|
||||
let mut gps_service = GpsService::new();
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs_f64();
|
||||
|
||||
let test_gps_data = GpsData {
|
||||
latitude: 43.7384,
|
||||
longitude: 7.4246,
|
||||
altitude: Some(0.0),
|
||||
accuracy: Some(3.0),
|
||||
heading: Some(45.0),
|
||||
speed: Some(5.2),
|
||||
timestamp,
|
||||
};
|
||||
|
||||
gps_service.update_position(test_gps_data.clone());
|
||||
|
||||
let current_pos = gps_service.get_current_position().unwrap();
|
||||
assert_eq!(current_pos.latitude, 43.7384);
|
||||
assert_eq!(current_pos.longitude, 7.4246);
|
||||
assert_eq!(current_pos.speed, Some(5.2));
|
||||
assert_eq!(current_pos.heading, Some(45.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gps_service_enable_disable() {
|
||||
let mut gps_service = GpsService::new();
|
||||
|
||||
// Test initial state
|
||||
assert!(!gps_service.is_enabled);
|
||||
|
||||
// Test enable
|
||||
gps_service.enable();
|
||||
assert!(gps_service.is_enabled);
|
||||
|
||||
// Test disable
|
||||
gps_service.disable();
|
||||
assert!(!gps_service.is_enabled);
|
||||
}
|
||||
}
|
3
crates/yachtpit/src/services/mod.rs
Normal file
3
crates/yachtpit/src/services/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod gps_service;
|
||||
|
||||
pub use gps_service::*;
|
@@ -5,6 +5,7 @@ use std::collections::HashMap;
|
||||
use bevy_flurx::prelude::*;
|
||||
use bevy_webview_wry::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::services::{GpsService, GpsData};
|
||||
/// Render layer for GPS map entities to isolate them from other cameras
|
||||
|
||||
|
||||
@@ -97,8 +98,9 @@ impl Plugin for GpsMapPlugin {
|
||||
.add_systems(Update, (
|
||||
handle_gps_map_window_events,
|
||||
update_map_tiles,
|
||||
send_periodic_gps_updates,
|
||||
));
|
||||
update_gps_from_service,
|
||||
))
|
||||
.add_systems(Startup, enable_gps_service);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,28 +332,36 @@ async fn get_vessel_status(
|
||||
})).await
|
||||
}
|
||||
|
||||
/// System to send periodic GPS updates for testing
|
||||
fn send_periodic_gps_updates(
|
||||
/// System to enable GPS service on startup
|
||||
fn enable_gps_service(mut gps_service: ResMut<GpsService>) {
|
||||
gps_service.enable();
|
||||
info!("GPS service enabled for map tracking");
|
||||
}
|
||||
|
||||
/// System to update GPS map state from GPS service
|
||||
fn update_gps_from_service(
|
||||
mut gps_map_state: ResMut<GpsMapState>,
|
||||
time: Res<Time>,
|
||||
gps_service: Res<GpsService>,
|
||||
) {
|
||||
// Update vessel position every frame for testing
|
||||
if time.delta_secs() > 0.0 {
|
||||
// Simulate slight movement around Monaco
|
||||
let base_lat = 43.6377;
|
||||
let base_lon = -1.4497;
|
||||
let offset = (time.elapsed_secs().sin() * 0.001) as f64;
|
||||
if let Some(gps_data) = gps_service.get_current_position() {
|
||||
// Update vessel position from real GPS data
|
||||
gps_map_state.vessel_lat = gps_data.latitude;
|
||||
gps_map_state.vessel_lon = gps_data.longitude;
|
||||
|
||||
gps_map_state.vessel_lat = base_lat + offset;
|
||||
gps_map_state.vessel_lon = base_lon + offset * 0.5;
|
||||
gps_map_state.vessel_speed = 5.0 + (time.elapsed_secs().cos() * 2.0) as f64;
|
||||
gps_map_state.vessel_heading = ((time.elapsed_secs() * 10.0) % 360.0) as f64;
|
||||
// Update speed and heading if available
|
||||
if let Some(speed) = gps_data.speed {
|
||||
gps_map_state.vessel_speed = speed;
|
||||
}
|
||||
if let Some(heading) = gps_data.heading {
|
||||
gps_map_state.vessel_heading = heading;
|
||||
}
|
||||
|
||||
// React side can poll for updates using get_vessel_status command
|
||||
if time.elapsed_secs() as u32 % 5 == 0 && time.delta_secs() < 0.1 {
|
||||
info!("Vessel position updated: lat={:.4}, lon={:.4}, speed={:.1}, heading={:.1}",
|
||||
gps_map_state.vessel_lat, gps_map_state.vessel_lon,
|
||||
gps_map_state.vessel_speed, gps_map_state.vessel_heading);
|
||||
// Also update map center to follow vessel if this is the first GPS fix
|
||||
if gps_map_state.center_lat == 43.6377 && gps_map_state.center_lon == -1.4497 {
|
||||
gps_map_state.center_lat = gps_data.latitude;
|
||||
gps_map_state.center_lon = gps_data.longitude;
|
||||
info!("Map centered on GPS position: lat={:.6}, lon={:.6}",
|
||||
gps_data.latitude, gps_data.longitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user