feat: download OSM zone in background thread

This commit is contained in:
Matthieu Bessat 2024-06-27 09:45:52 +02:00
parent 58c0783e75
commit 3d50948ff0
5 changed files with 129 additions and 43 deletions

7
Cargo.lock generated
View file

@ -135,6 +135,7 @@ dependencies = [
"serde_with", "serde_with",
"toml", "toml",
"ureq", "ureq",
"xdg",
] ]
[[package]] [[package]]
@ -1824,6 +1825,12 @@ dependencies = [
"tap", "tap",
] ]
[[package]]
name = "xdg"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.1" version = "1.8.1"

View file

@ -15,3 +15,4 @@ serde_json = "1.0.118"
serde_with = "3.8.1" serde_with = "3.8.1"
toml = "0.8.14" toml = "0.8.14"
ureq = { version = "2.9.7", features = ["json"] } ureq = { version = "2.9.7", features = ["json"] }
xdg = "2.5.2"

View file

@ -6,8 +6,11 @@
- [x] 2024-06-25: Afficher les quartiers d'Aubevoye, afficher les rues - [x] 2024-06-25: Afficher les quartiers d'Aubevoye, afficher les rues
- [x] 2024-06-26: handle change in screen def - [x] 2024-06-26: handle change in screen def
- [x] 2024-06-26: download and merge osm data in current bbox - [x] 2024-06-26: download and merge osm data in current bbox
- [ ] 2024-06-27: basic name display - [x] 2024-06-27: basic name display
- [ ] 2024-06-27: OSM download in a separated thread - [ ] 2024-06-27: OSM download in a separated thread
- [ ] 2024-06-27: Show basic POI
- [ ] 2024-06-27: basic edit of POI
- [ ] 2024-06-27: oauth2 authorize and OSM session management
- [ ] 2024-06-27: basic edit of existing way - [ ] 2024-06-27: basic edit of existing way
- add edit toggle button / keybinding - add edit toggle button / keybinding
- draw node handles - draw node handles

View file

@ -1,4 +1,4 @@
use std::fs; use std::{fs, sync::{Arc, Mutex}};
use anyhow::{anyhow, Result, Context}; use anyhow::{anyhow, Result, Context};
use fully_pub::fully_pub; use fully_pub::fully_pub;
@ -123,7 +123,6 @@ pub fn build_displayed_ways(indexed_data: &IndexedData) -> Result<RTree<Displaye
osm_name = name.to_string(); osm_name = name.to_string();
} }
if let Some(building_class) = tags.get("building") { if let Some(building_class) = tags.get("building") {
println!("building_class = {building_class}");
if building_class == "apartments" { if building_class == "apartments" {
final_name = osm_name; final_name = osm_name;
} }
@ -212,7 +211,7 @@ pub fn load_static_cities_layer(path: &str) -> Result<Layer> {
/// download the bbox JSON data via 0.6 OSM API /// download the bbox JSON data via 0.6 OSM API
/// find the DynamicOSMData layer, and update it and merge the data into it /// find the DynamicOSMData layer, and update it and merge the data into it
pub fn load_osm_zone(http_agent: &Agent, layers: &mut Vec<Layer>, bounds: &(Vector2, Vector2)) -> Result<usize> { pub fn load_osm_zone(http_agent: &Agent, layers: Arc<Mutex<Vec<Layer>>>, bounds: (Vector2, Vector2)) -> Result<usize> {
println!("Initiated download of OSM zone"); println!("Initiated download of OSM zone");
// TODO: add cache // TODO: add cache
let response = http_agent.get("https://api.openstreetmap.org/api/0.6/map.json") let response = http_agent.get("https://api.openstreetmap.org/api/0.6/map.json")
@ -221,14 +220,15 @@ pub fn load_osm_zone(http_agent: &Agent, layers: &mut Vec<Layer>, bounds: &(Vect
.context("Requesting OSM local data")?; .context("Requesting OSM local data")?;
let data_json = response.into_string().context("Decoding response data")?; let data_json = response.into_string().context("Decoding response data")?;
load_osm_data_from_json(layers, &data_json) load_osm_data_from_json(layers, data_json)
} }
pub fn load_osm_data_from_json(layers: &mut Vec<Layer>, data_json: &str) -> Result<usize> { pub fn load_osm_data_from_json(layers: Arc<Mutex<Vec<Layer>>>, data_json: String) -> Result<usize> {
let json_osm_data: data::Data = serde_json::from_str(&data_json) let json_osm_data: data::Data = serde_json::from_str(&data_json)
.context("Parsing static osm data")?; .context("Parsing static osm data")?;
let mut l = layers.lock()
let layer_to_update: &mut Layer = get_dynamic_data_layer_mut(layers) .map_err(|_e| anyhow!("Acquire mutation lock for layers"))?;
let layer_to_update: &mut Layer = get_dynamic_data_layer_mut(&mut l)
.context("Did not find dynamic OSMData")?; .context("Did not find dynamic OSMData")?;
json_osm_data.update_existing_indexed_data( json_osm_data.update_existing_indexed_data(
layer_to_update.data_source.as_mut().expect("Data source on dynamic OSMData") layer_to_update.data_source.as_mut().expect("Data source on dynamic OSMData")
@ -252,10 +252,13 @@ fn get_dynamic_data_layer(layers: &Vec<Layer>) -> Option<&Layer> {
} }
fn get_dynamic_data_layer_mut(layers: &mut Vec<Layer>) -> Option<&mut Layer> { fn get_dynamic_data_layer_mut(layers: &mut Vec<Layer>) -> Option<&mut Layer> {
layers.iter_mut().find(|l| { match layers.iter_mut().find(|l| {
match l.kind { match l.kind {
LayerKind::DynamicOSMData => true, LayerKind::DynamicOSMData => true,
_ => false _ => false
} }
}) }) {
Some(v) => Some(v),
None => None
}
} }

View file

@ -6,13 +6,15 @@ mod test_data;
#[cfg(test)] #[cfg(test)]
mod test_layers; mod test_layers;
use std::{collections::HashMap, fs, time::Duration}; use std::{collections::HashMap, fs, sync::{Arc, Mutex}, thread::{self, JoinHandle}, time::Duration};
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use data::{bbox_center, compute_way_bbox, IndexedData}; use data::{bbox_center, compute_way_bbox, IndexedData};
use fully_pub::fully_pub;
use layers::{load_osm_data_from_json, load_osm_zone, load_static_cities_layer, load_static_osm_layer, Layer, LayerKind}; use layers::{load_osm_data_from_json, load_osm_zone, load_static_cities_layer, load_static_osm_layer, Layer, LayerKind};
use raylib::prelude::*; use raylib::prelude::*;
use xdg::BaseDirectories;
use std::io::Read; use std::io::Read;
use osmpbf::{ElementReader, Element}; use osmpbf::{ElementReader, Element};
use rstar::{RTree, RTreeObject, AABB}; use rstar::{RTree, RTreeObject, AABB};
@ -73,6 +75,14 @@ struct Camera {
canvas_to_real_factor: f32 canvas_to_real_factor: f32
} }
/// store all the app state (UI state)
#[derive(Debug)]
#[fully_pub]
struct AppState {
editing: bool,
loading: bool
}
#[derive(Debug)] #[derive(Debug)]
enum GetRasterError { enum GetRasterError {
HttpError(ureq::Error), HttpError(ureq::Error),
@ -98,7 +108,7 @@ struct OSMSession {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Session { struct Session {
osm_session: OSMSession osm_session: Option<OSMSession>
} }
// #[derive(Debug, Serialize, Deserialize)] // #[derive(Debug, Serialize, Deserialize)]
@ -174,16 +184,33 @@ fn get_raster_tile(
// .context("Requesting OSM local data")?; // .context("Requesting OSM local data")?;
// } // }
fn read_config() -> Result<Config> { fn read_config(xdg_dirs: &BaseDirectories) -> Result<Config> {
let config_content = let config_content =
fs::read_to_string("./config.toml") fs::read_to_string(xdg_dirs.get_config_file("config.toml"))
.map_err(|_e| anyhow!("Could not read config file"))?; .map_err(|_e| anyhow!("Could not read config file"))?;
let config: Config = toml::from_str(&config_content) let config: Config = toml::from_str(&config_content)
.map_err(|_e| anyhow!("Could not parse config file as toml"))?; .map_err(|_e| anyhow!("Could not parse config file as toml"))?;
Ok(config) Ok(config)
} }
fn read_session(xdg_dirs: &BaseDirectories) -> Result<Session> {
// find session file
let config_content =
fs::read_to_string(xdg_dirs.get_data_file("session.json"))
.map_err(|_e| anyhow!("Could not read session JSON file"))?;
let session: Session = serde_json::from_str(&config_content)
.map_err(|_e| anyhow!("Could not deserialize session file"))?;
Ok(session)
}
fn write_session(xdg_dirs: &BaseDirectories, session: &Session) -> Result<()> {
let session_path = xdg_dirs.place_data_file("session.json").context("Could not create or find session file")?;
fs::write(
session_path,
serde_json::to_string(session).context("Could not serialize session")?
).context("Could not write session")?;
Ok(())
}
fn compute_bounds_from_center_zoom(screen_def: (i32, i32), target_center: Vector2, target_zoom: f32) -> (Vector2, Vector2) fn compute_bounds_from_center_zoom(screen_def: (i32, i32), target_center: Vector2, target_zoom: f32) -> (Vector2, Vector2)
{ {
@ -211,10 +238,26 @@ fn main() -> Result<()> {
.title("BobOSM") .title("BobOSM")
.build(); .build();
let config = read_config() let xdg_dirs = xdg::BaseDirectories::with_prefix("bobosm")
.context("Read config")?; .context("Locating XDG dirs")?;
let mut layers: Vec<Layer> = vec![ let config = read_config(&xdg_dirs)
.context("Read user config")?;
if xdg_dirs.find_data_file("session.json").is_none() {
write_session(&xdg_dirs, &(Session {
osm_session: None
})).context("Writing initial session")?;
}
let mut session = read_session(&xdg_dirs)
.context("Read user session")?;
dbg!(session);
let app_state = Arc::new(Mutex::new(AppState {
editing: false,
loading: false
}));
let layers: Arc<Mutex<Vec<Layer>>> = Arc::new(Mutex::new(vec![
load_static_cities_layer(&config.static_layers.base_data_path).context("Loading static cities layer")?, load_static_cities_layer(&config.static_layers.base_data_path).context("Loading static cities layer")?,
// load_static_osm_layer("./data/centre_vernon.json").context("Loading static osm data")?, // load_static_osm_layer("./data/centre_vernon.json").context("Loading static osm data")?,
// load_static_osm_layer("./data/ouest_aubevoye.json").context("Loading static osm data")?, // load_static_osm_layer("./data/ouest_aubevoye.json").context("Loading static osm data")?,
@ -227,9 +270,9 @@ fn main() -> Result<()> {
editable: true, editable: true,
name: "Dynamic data".to_string(), name: "Dynamic data".to_string(),
} }
]; ]));
let _ = load_osm_data_from_json(&mut layers, &fs::read_to_string("./data/ouest_aubevoye.json")?); let _ = load_osm_data_from_json(layers.clone(), fs::read_to_string("./data/ouest_aubevoye.json")?);
enum MoveState { enum MoveState {
Started { start_pos: Vector2 }, Started { start_pos: Vector2 },
@ -369,6 +412,11 @@ fn main() -> Result<()> {
camera.map_zoom = 4.680851064*camera.zoom; camera.map_zoom = 4.680851064*camera.zoom;
if download_zone_asked {
// download current bbox and add and merge data, merge into current Layer
spawn_download_thread(app_state.clone(), http_agent.clone(), layers.clone(), camera.bounds.clone());
}
// handle input // handle input
if is_mouse_left_down { if is_mouse_left_down {
} }
@ -426,19 +474,8 @@ fn main() -> Result<()> {
let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30); let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30);
// draw raster tiles // draw raster tiles
let cross_width = 6;
d.draw_line_v(
Vector2::new(((screen_def.0/2) - cross_width) as f32, (screen_def.1/2) as f32),
Vector2::new(((screen_def.0/2) + cross_width) as f32, (screen_def.1/2) as f32),
Color::WHITE
);
d.draw_line_v(
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) - cross_width) as f32),
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) + cross_width) as f32),
Color::WHITE
);
for layer in &layers { for layer in layers.lock().unwrap().iter() {
if let Some(tree) = &layer.ways_tree { if let Some(tree) = &layer.ways_tree {
for way in tree.locate_in_envelope_intersecting(&current_bounds_aabb) { for way in tree.locate_in_envelope_intersecting(&current_bounds_aabb) {
let mut last_p: Option<Vector2> = None; let mut last_p: Option<Vector2> = None;
@ -501,20 +538,55 @@ fn main() -> Result<()> {
d.draw_text(&format!("Mouse: {:?}", real_mouse_pos), 13, 30, 15, Color::WHITE); d.draw_text(&format!("Mouse: {:?}", real_mouse_pos), 13, 30, 15, Color::WHITE);
d.draw_text(&format!("Center: {:?}", camera.center), 13, 45, 15, Color::WHITE); d.draw_text(&format!("Center: {:?}", camera.center), 13, 45, 15, Color::WHITE);
if download_zone_asked { if app_state.lock().unwrap().loading {
// download current bbox and add and merge data, merge into current Layer // loading indicator
let res = load_osm_zone(&http_agent, &mut layers, &camera.bounds); // TODO: add a sprite
match res { d.draw_circle_v(
Ok(elements_count) => { Vector2::new(20., (screen_def.1-20) as f32),
println!("Downloaded OSM zone {:?}, found {elements_count:?} elements", &camera.bounds); 10.0,
}, Color::WHITE
Err(err) => { )
println!("ERR: failed to download OSM zone, {err:?}")
}
}
} }
// draw target cross (center fixed cursor)
let cross_width = 6;
d.draw_line_v(
Vector2::new(((screen_def.0/2) - cross_width) as f32, (screen_def.1/2) as f32),
Vector2::new(((screen_def.0/2) + cross_width) as f32, (screen_def.1/2) as f32),
Color::WHITE
);
d.draw_line_v(
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) - cross_width) as f32),
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) + cross_width) as f32),
Color::WHITE
);
} }
return Ok(()); return Ok(());
} }
pub fn spawn_download_thread(
app_state: Arc<Mutex<AppState>>,
http_agent: Agent,
layers: Arc<Mutex<Vec<Layer>>>,
bounds: (Vector2, Vector2)
) -> JoinHandle<()> {
thread::spawn(move || {
{
let mut app_state_mut = app_state.lock().unwrap();
app_state_mut.loading = true;
}
let res = load_osm_zone(&http_agent, layers, bounds);
match res {
Ok(elements_count) => {
println!("Downloaded OSM zone {:?}, found {elements_count:?} elements", bounds);
},
Err(err) => {
println!("ERR: failed to download OSM zone, {err:?}")
}
}
{
let mut app_state_mut = app_state.lock().unwrap();
app_state_mut.loading = false;
}
})
}