refactor: camera

This commit is contained in:
Matthieu Bessat 2024-06-24 22:08:09 +02:00
parent b998c43dcc
commit e344fc8d42
5 changed files with 159 additions and 40 deletions

79
Cargo.lock generated
View file

@ -80,9 +80,12 @@ checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
name = "bobosm_raylib_rs"
version = "0.1.0"
dependencies = [
"anyhow",
"osmpbf",
"raylib",
"rstar",
"serde",
"toml",
"ureq",
]
@ -201,6 +204,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
@ -284,6 +293,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heapless"
version = "0.8.0"
@ -329,6 +344,16 @@ dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown 0.14.5",
]
[[package]]
name = "itertools"
version = "0.12.1"
@ -563,7 +588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642"
dependencies = [
"anyhow",
"indexmap",
"indexmap 1.9.3",
"log",
"protobuf",
"protobuf-support",
@ -794,6 +819,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -938,6 +972,40 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [
"indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tuple_utils"
version = "0.3.0"
@ -1110,6 +1178,15 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
dependencies = [
"memchr",
]
[[package]]
name = "zeroize"
version = "1.8.1"

View file

@ -4,7 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.86"
osmpbf = "0.3.3"
raylib = { version = "5.0.1", features = ["wayland"] }
rstar = "0.12.0"
serde = { version = "1.0.203", features = ["derive"] }
toml = "0.8.14"
ureq = { version = "2.9.7", features = ["json"] }

View file

@ -31,6 +31,9 @@
- Tags edit on `/tmp/tags.txt`, open a dynamic editor like vim on another windows and allow to edit tags this way
- Distances de deux points sur une sphère avec la formule de Haversine
- https://fr.wikipedia.org/wiki/Formule_de_haversine
## Tile
https://a.tile.openstreetmap.org/15/16511/11224.png

2
config.toml Normal file
View file

@ -0,0 +1,2 @@
[static_layers]
base_data_path = "./data/france_places_cities_towns.osm.pbf"

View file

@ -1,8 +1,12 @@
use std::time::Duration;
use std::{fs, time::Duration};
use anyhow::{Context, Result, anyhow};
use raylib::prelude::*;
use std::io::Read;
use osmpbf::{ElementReader, Element};
use rstar::{RTree, RTreeObject, AABB};
use serde::Deserialize;
use ureq::Agent;
fn deg2num(lat_deg: f64, lon_deg: f64, zoom: u32) -> (u32, u32) {
@ -69,6 +73,17 @@ enum GetRasterError {
CannotDecodeImage(String)
}
#[derive(Deserialize)]
struct StaticLayers {
base_data_path: String,
}
#[derive(Deserialize)]
struct Config {
static_layers: StaticLayers,
}
fn get_envelope_from_bounds(bounds: (Vector2, Vector2), margin_percent: f32) -> AABB<[f32; 2]> {
let center = bounds.1.lerp(bounds.0, 0.5);
@ -81,42 +96,56 @@ fn get_envelope_from_bounds(bounds: (Vector2, Vector2), margin_percent: f32) ->
)
}
fn get_raster_tile(http_agent: &Agent, geo_pos: Vector2, zoom_level: u32) -> Result<Image, GetRasterError> {
fn get_raster_tile(http_agent: &Agent, geo_pos: Vector2, zoom_level: u32) -> Result<Image> {
let projected_pos = deg2num(
geo_pos.y.into(),
geo_pos.x.into(),
zoom_level
);
let response = http_agent.get(
&format!("https://a.tile.openstreetmap.org/{}/{}/{}",
&format!("https://a.tile.openstreetmap.org/{}/{}/{}.png",
zoom_level,
projected_pos.0,
projected_pos.1
)
)
.call()
.map_err(|e| GetRasterError::HttpError(e))?;
.context("Requesting the tile")?;
let len: usize = response.header("Content-Length")
.unwrap()
.parse()
.map_err(|_e| GetRasterError::InvalidContentLength)?;
let img_bytes: Vec<u8> = Vec::with_capacity(len);
.map_err(|_e| anyhow!("Invalid content length"))?;
let mut img_bytes: Vec<u8> = Vec::with_capacity(len);
let _ = response.into_reader()
.take(10_000_000) // safety to limit overflow
.read_to_end(&mut img_bytes)
.context("Reading response body");
Image::load_image_from_mem(".png", &img_bytes)
.map_err(|e| GetRasterError::CannotDecodeImage(e))
.map_err(|e| anyhow!("Cannot decode PNG, {:?}", e))
}
fn get_osm_data() {
// curl -v https://api.openstreetmap.org/api/0.6/map.json -G -d "bbox=1.32495,49.16409,1.34585,49.18485" > map.json
}
fn read_config() -> Result<Config> {
let config_content =
fs::read_to_string("./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 main() -> Result<()> {
fn main() {
let http_agent: Agent = ureq::AgentBuilder::new()
.user_agent("bobosm/0.1")
.timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5))
.timeout_read(Duration::from_secs(6))
.timeout_write(Duration::from_secs(6))
.build();
let (mut rl, thread) = raylib::init()
@ -125,9 +154,11 @@ fn main() {
.title("BobOSM")
.build();
let config = read_config()
.context("Read config")?;
let reader = ElementReader::from_path("./data/france_places_cities_towns.osm.pbf")
.expect("To read cities");
let reader = ElementReader::from_path(config.static_layers.base_data_path)
.context("Read static layers")?;
enum MoveState {
Started { start_pos: Vector2 },
@ -136,12 +167,10 @@ fn main() {
let mut move_state = MoveState::Done;
let current_geo_pos: Vector2 = (0.0, 0.0).into();
let mut current_zoom: f32 = 10.0;
// first point is left bottom, second point is top right point
// let mut current_bounds: (Vector2, Vector2) = ((-4.0, -4.0).into(), (4.0, 4.0).into());
let mut current_bounds: (Vector2, Vector2) = ((-6., 37.0).into(), (12., 55.0).into());
// let mut current_bounds: (Vector2, Vector2) = ((-6., 37.0).into(), (12., 55.0).into());
let mut points: Vec<LabeledPoint> = vec![];
println!("==1");
@ -201,11 +230,11 @@ fn main() {
let mut tmp_state = 0;
let mut camera = Camera {
bounds: (Vector2::zero(), Vector2::zero()),
bounds: (Vector2::new(-6., 37.0), Vector2::new(12., 55.)),
canvas_to_real_factor: 1.0,
real_to_canvas_factor: 1.0,
center: Vector2::zero(),
zoom: 1.0,
zoom: 10.0,
map_zoom: 1.0
};
@ -224,7 +253,7 @@ fn main() {
camera.center = camera.bounds.1.lerp(camera.bounds.0, 0.5);
camera.real_to_canvas_factor = (rl.get_screen_width() as f32) / (camera.bounds.0.x - camera.bounds.1.x).abs();
camera.canvas_to_real_factor = 1.0/camera.canvas_to_real_factor;
camera.canvas_to_real_factor = 1.0/camera.real_to_canvas_factor;
// println!("real_to_canvas_factor = {real_to_canvas_factor:?}; canvas_to_real_factor: {canvas_to_real_factor:?}");
// Handle zoom
@ -236,7 +265,7 @@ fn main() {
mouse_pos.invert_for_canvas().scale_by(camera.canvas_to_real_factor);
// update the bounds
camera.bounds.0 = real_mouse_pos + (camera.bounds.0 - real_mouse_pos).scale_by(zoom_factor);
camera.bounds.0 = real_mouse_pos + (camera.bounds.0 - real_mouse_pos).scale_by(zoom_factor);
camera.bounds.1 = real_mouse_pos + (camera.bounds.1 - real_mouse_pos).scale_by(zoom_factor);
camera.zoom = 1.0/(camera.bounds.0.x - camera.bounds.1.x).abs();
camera.map_zoom = 4.680851064*camera.zoom;
@ -251,12 +280,12 @@ fn main() {
MoveState::Started { start_pos } => {
// end of vector move
let move_speed_factor = 0.01;
let move_speed = move_speed_factor/current_zoom;
let move_speed = move_speed_factor/camera.zoom;
mouse_move = rl.get_mouse_delta()
.scale_by(-move_speed)
.invert_for_canvas();
current_bounds.0 += mouse_move;
current_bounds.1 += mouse_move;
camera.bounds.0 += mouse_move;
camera.bounds.1 += mouse_move;
move_state = MoveState::Done;
},
MoveState::Done => {
@ -265,53 +294,57 @@ fn main() {
}
}
let mut d = rl.begin_drawing(&thread);
d.clear_background(Color::BLACK);
let mut tmp_texture: Option<Texture2D> = None;
if tmp_state == 0 {
match get_raster_tile(&http_agent, camera.center, current_zoom.ceil() as u32) {
match get_raster_tile(&http_agent, camera.center, camera.map_zoom.ceil() as u32) {
Ok(img) => {
let t = match d.load_texture_from_image(&thread, &img) {
Ok(t) => t,
tmp_texture = match rl.load_texture_from_image(&thread, &img) {
Ok(t) => Some(t),
Err(e) => {
println!("ERROR, failed to load texture");
return;
return Ok(());
}
};
let canvas_pos = (LabeledPoint { pos: camera.center, ..Default::default() }).to_canvas_pos(&camera);
d.draw_texture(t, canvas_pos.x.ceil() as i32, canvas_pos.y.ceil() as i32, Color::WHITE);
},
Err(e) => {
println!("ERROR, failed to load raster tile {e:?}");
return;
return Ok(());
}
};
tmp_state = 1;
}
d.draw_text(&format!("Zoom: {:?}, Bounds: {:?}", current_zoom, current_bounds), 13, 12, 15, Color::WHITE);
let mut d = rl.begin_drawing(&thread);
let canvas_pos = (LabeledPoint { pos: camera.center, ..Default::default() }).to_canvas_pos(&camera);
if let Some(t) = tmp_texture {
d.draw_texture(t, canvas_pos.x.ceil() as i32, canvas_pos.y.ceil() as i32, Color::WHITE);
}
d.clear_background(Color::BLACK);
d.draw_text(&format!("Zoom: {:?}, Bounds: {:?}", camera.zoom, camera.bounds), 13, 12, 15, Color::WHITE);
d.draw_text(&format!("Mouse: {:?}", real_mouse_pos), 13, 30, 15, Color::WHITE);
// Repère A: Canvas
// Repère B: Virtual canvas
let current_bounds_aabb = get_envelope_from_bounds(current_bounds, 0.30);
let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30);
// draw raster tiles
for point in tree.locate_in_envelope(&current_bounds_aabb) {
let canvas_pos = point.to_canvas_pos(&camera);
let size = point.importance as f32 * (((1.)/(1.+ (-(0.4 * current_zoom - 4.)).exp() )) + 0.2);
d.draw_circle_v(
let size = point.importance as f32 * (((1.)/(1.+ (-(0.4 * camera.zoom - 4.)).exp() )) + 0.2);
d.draw_poly_lines(
canvas_pos,
5,
10.0*size,
0.,
match point.importance {
2 => Color::GREEN,
3 => Color::RED,
_ => Color::WHITE,
_ => Color::new(255, 255, 255, 100)
}
);
if (current_zoom <= 0.7 && point.importance >= 3) || current_zoom >= 0.7 {
if (camera.zoom <= 0.9 && point.importance >= 3) || camera.zoom >= 0.9 {
d.draw_text(
&format!("{}", point.name),
canvas_pos.x.ceil() as i32,
@ -325,5 +358,6 @@ fn main() {
d.draw_circle_sector_lines(mouse_pos, 10.0, 0.0, 360.0, 1, Color::MAGENTA);
}
return Ok(());
}