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

View file

@ -15,3 +15,4 @@ serde_json = "1.0.118"
serde_with = "3.8.1"
toml = "0.8.14"
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-26: handle change in screen def
- [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: 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
- add edit toggle button / keybinding
- draw node handles

View file

@ -1,4 +1,4 @@
use std::fs;
use std::{fs, sync::{Arc, Mutex}};
use anyhow::{anyhow, Result, Context};
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();
}
if let Some(building_class) = tags.get("building") {
println!("building_class = {building_class}");
if building_class == "apartments" {
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
/// 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");
// TODO: add cache
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")?;
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)
.context("Parsing static osm data")?;
let layer_to_update: &mut Layer = get_dynamic_data_layer_mut(layers)
let mut l = layers.lock()
.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")?;
json_osm_data.update_existing_indexed_data(
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> {
layers.iter_mut().find(|l| {
match layers.iter_mut().find(|l| {
match l.kind {
LayerKind::DynamicOSMData => true,
_ => false
}
})
}) {
Some(v) => Some(v),
None => None
}
}

View file

@ -6,13 +6,15 @@ mod test_data;
#[cfg(test)]
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 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 raylib::prelude::*;
use xdg::BaseDirectories;
use std::io::Read;
use osmpbf::{ElementReader, Element};
use rstar::{RTree, RTreeObject, AABB};
@ -73,6 +75,14 @@ struct Camera {
canvas_to_real_factor: f32
}
/// store all the app state (UI state)
#[derive(Debug)]
#[fully_pub]
struct AppState {
editing: bool,
loading: bool
}
#[derive(Debug)]
enum GetRasterError {
HttpError(ureq::Error),
@ -98,7 +108,7 @@ struct OSMSession {
#[derive(Debug, Serialize, Deserialize)]
struct Session {
osm_session: OSMSession
osm_session: Option<OSMSession>
}
// #[derive(Debug, Serialize, Deserialize)]
@ -174,16 +184,33 @@ fn get_raster_tile(
// .context("Requesting OSM local data")?;
// }
fn read_config() -> Result<Config> {
fn read_config(xdg_dirs: &BaseDirectories) -> Result<Config> {
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"))?;
let config: Config = toml::from_str(&config_content)
.map_err(|_e| anyhow!("Could not parse config file as toml"))?;
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)
{
@ -211,10 +238,26 @@ fn main() -> Result<()> {
.title("BobOSM")
.build();
let config = read_config()
.context("Read config")?;
let xdg_dirs = xdg::BaseDirectories::with_prefix("bobosm")
.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_osm_layer("./data/centre_vernon.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,
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 {
Started { start_pos: Vector2 },
@ -369,6 +412,11 @@ fn main() -> Result<()> {
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
if is_mouse_left_down {
}
@ -426,19 +474,8 @@ fn main() -> Result<()> {
let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30);
// 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 {
for way in tree.locate_in_envelope_intersecting(&current_bounds_aabb) {
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!("Center: {:?}", camera.center), 13, 45, 15, Color::WHITE);
if download_zone_asked {
// download current bbox and add and merge data, merge into current Layer
let res = load_osm_zone(&http_agent, &mut layers, &camera.bounds);
match res {
Ok(elements_count) => {
println!("Downloaded OSM zone {:?}, found {elements_count:?} elements", &camera.bounds);
},
Err(err) => {
println!("ERR: failed to download OSM zone, {err:?}")
}
}
if app_state.lock().unwrap().loading {
// loading indicator
// TODO: add a sprite
d.draw_circle_v(
Vector2::new(20., (screen_def.1-20) as f32),
10.0,
Color::WHITE
)
}
// 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(());
}
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;
}
})
}