feat: changeset generation and upload

This commit is contained in:
Matthieu Bessat 2024-06-29 13:27:25 +02:00
parent d1d563fd52
commit ad0c988869
14 changed files with 497 additions and 88 deletions

189
Cargo.lock generated
View file

@ -126,6 +126,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"fully_pub",
"native-tls",
"osm-types",
"osmpbf",
"quick-xml",
@ -272,6 +273,16 @@ dependencies = [
"cc",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
@ -423,6 +434,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -728,6 +754,23 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915"
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -760,6 +803,50 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "osm-types"
version = "0.1.5"
@ -815,6 +902,12 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1202,6 +1295,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
dependencies = [
"base64",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.7.0"
@ -1225,6 +1341,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -1237,6 +1362,29 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.203"
@ -1344,6 +1492,17 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socks"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
dependencies = [
"byteorder",
"libc",
"winapi",
]
[[package]]
name = "specs"
version = "0.16.1"
@ -1616,10 +1775,12 @@ dependencies = [
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"rustls-webpki",
"serde",
"serde_json",
"socks",
"url",
"webpki-roots",
]
@ -1641,6 +1802,12 @@ version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
@ -1728,6 +1895,28 @@ dependencies = [
"rustix",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"

View file

@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.86"
fully_pub = "0.1.4"
native-tls = "0.2.12"
osm-types = "0.1.5"
osmpbf = "0.3.3"
quick-xml = { version = "0.34.0", features = ["serialize"] }
@ -16,5 +17,5 @@ serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.118"
serde_with = "3.8.1"
toml = "0.8.14"
ureq = { version = "2.9.7", features = ["json"] }
ureq = { version = "2.9.7", features = ["json", "native-certs", "socks-proxy"] }
xdg = "2.5.2"

5
docs/DEV.md Normal file
View file

@ -0,0 +1,5 @@
# Dev tips
Use the live test server
https://master.apis.dev.openstreetmap.org/#map=16/49.1679/1.3327

View file

@ -1 +1,16 @@
# Manual changeset upload with curl
PUT /api/0.6/changeset/create
get an id
then
POST /api/0.6/changeset/370826/upload
increment the version number and but the changeset id you get
<osmChange version="0.6" generator="iD"><create/><modify><node id="4350831726" lon="1.3169075" lat="49.140221" version="1" changeset="370827"><tag k="amenity" v="bank"/><tag k="name" v="test name"/></node></modify><delete if-unused="true"/></osmChange>
PUT /api/0.6/changeset/370826/close

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -9,4 +9,5 @@ oauth2c \
--callback-tls-key "$(pwd)/tmp/domain.key" \
--auth-method "client_secret_basic" \
--response-types "code" --response-mode "query" \
"https://www.openstreetmap.org"
"https://master.apis.dev.openstreetmap.org" \
| jq '{ osm_session: . }' > session.json

1
out Normal file
View file

@ -0,0 +1 @@
{"access_token":"eqn8EG_A7VSeBGEaO5XeKGThlTkCWMbbuHwF_PNfX7E","scope":"read_prefs write_api","token_type":"Bearer"}

View file

@ -1,3 +1,4 @@
use fully_pub::fully_pub;
use std::sync::{Arc, Mutex};
use anyhow::{Context, Result, anyhow};
use serde::{Serialize};
@ -5,10 +6,11 @@ use serde::{Serialize};
use quick_xml::se::{to_string_with_root, Serializer};
use xdg::BaseDirectories;
use crate::layers::Layer;
use crate::{changeset, data::Element, layers::{get_dynamic_data_layer, Layer}, osm_api::OSMApiClient, USER_AGENT};
#[derive(Debug, Serialize)]
struct TagChanges {
#[fully_pub]
struct Tag {
#[serde(rename = "@k")]
key: String,
@ -19,17 +21,17 @@ struct TagChanges {
#[derive(Debug, Serialize)]
struct NodeChanges {
#[serde(rename = "@id")]
id: String,
id: i64,
#[serde(rename = "@changeset")]
changeset: String,
changeset: i64,
#[serde(rename = "@version")]
version: String,
version: i64,
#[serde(rename = "@lat")]
lat: f32,
#[serde(rename = "@lon")]
lon: f32,
#[serde(rename = "tag")]
tags: Option<Vec<TagChanges>>
tags: Option<Vec<Tag>>
}
#[derive(Debug, Serialize)]
@ -39,48 +41,131 @@ struct ModifyChanges {
}
#[derive(Debug, Serialize)]
struct Changeset {
#[fully_pub]
struct ChangesetContent {
#[serde(rename = "@version")]
version: String,
#[serde(rename = "@generator")]
generator: String,
modify: Option<ModifyChanges>
}
fn build_osm_change_xml(changeset: &Changeset) -> Result<String> {
#[derive(Debug, Serialize)]
#[fully_pub]
struct ChangesetMetaInner {
#[serde(rename = "tag")]
tags: Vec<Tag>
}
#[derive(Debug, Serialize)]
#[fully_pub]
struct ChangesetMeta {
#[serde(rename= "changeset")]
osm: ChangesetMetaInner
}
/*
<osm>
<changeset>
<tag k="created_by" v="JOSM 1.61"/>
<tag k="comment" v="Just adding some streetnames"/>
...
</changeset>
...
</osm>
*/
fn build_osm_change_xml(changeset: &ChangesetContent) -> Result<String> {
Ok(to_string_with_root("osmChange", changeset)?)
}
// fn open_changeset(&OsmC) -> Result<i64>
// {
/// Will handle all the operations to collect the changes and create a changeset
// }
/// Will handle all the operations to collect the changes and create a changeset and push it
pub fn try_changeset(
xdg_dirs: &BaseDirectories,
layers: Arc<Mutex<Vec<Layer>>>
layers: Arc<Mutex<Vec<Layer>>>,
osm_client: &OSMApiClient
) -> Result<()> {
let changeset = Changeset {
version: "0.6".to_string(),
generator: "BobOSM".to_string(),
modify: Some(ModifyChanges {
nodes: vec![
NodeChanges {
id: "0".to_string(),
changeset: "0".to_string(),
version: "0".to_string(),
lat: 23.0,
lon: 32.0,
tags: Some(vec![
TagChanges {
key: "amenity".to_string(),
value: "restaurant".to_string()
}
])
let layers_ref = layers.lock().unwrap();
let dynamic_layer = match get_dynamic_data_layer(&layers_ref) {
Some(v) => v,
None => {
return Ok(())
}
};
let changeset_comment = String::from("Add source on POI");
let changeset_meta = changeset::ChangesetMeta {
osm: changeset::ChangesetMetaInner {
tags: vec![
Tag {
key: "comment".to_string(),
value: changeset_comment
},
Tag {
key: "created_by".to_string(),
value: USER_AGENT.to_string()
}
]
}
};
println!("changeset_meta: {:?}", changeset_meta);
// get the changeset id
let changeset_id = osm_client.open_changeset(changeset_meta)?;
println!("changeset_id: {}", changeset_id);
// build osmChange
let mut nodes_changes: Vec<NodeChanges> = vec![];
for modified_e_id in &dynamic_layer.modified_data {
let modified_e = dynamic_layer.data_source.as_ref().unwrap()
.elements.get(&modified_e_id).unwrap();
match modified_e {
Element::Node(node) => {
nodes_changes.push(NodeChanges {
id: node.id,
lat: node.lat,
lon: node.lon,
version: node.version,
changeset: changeset_id,
tags: node.tags.as_ref().map(
|x|
x.iter()
.map(|(k,v)| Tag {
key: k.to_string(),
value: v.to_string()
})
.collect()
)
})
},
_ => continue
}
}
let changeset_content = ChangesetContent {
version: "0.6".to_string(),
generator: USER_AGENT.to_string(),
modify: Some(ModifyChanges {
nodes: nodes_changes
})
};
// 1. collect changes and build a OsmChange
let xml = build_osm_change_xml(&changeset);
println!("{xml:?}");
println!("changeset_content: {changeset_content:?}");
println!("CHANGESET: Uploading changeset");
let _ = osm_client.upload_changes(changeset_id, changeset_content)
.context("Uploading changeset content")?;
println!("CHANGESET: Closing changeset");
osm_client.close_changeset(changeset_id)
.context("Closing changeset")?;
println!("CHANGESET: Changeset closed");
Ok(())
}

View file

@ -14,7 +14,8 @@ struct Node {
id: i64,
lat: f32,
lon: f32,
tags: Tags
tags: Tags,
version: i64
}
impl Node {

View file

@ -77,21 +77,23 @@ pub fn edit_tags(
for (layer_i, layer_selections) in app_state_ref.selected_elements_per_layer.iter().enumerate() {
for e_id in layer_selections {
let object_element = layers_ref.get(layer_i)
.ok_or(anyhow!("Could not find layer"))?
.data_source.as_ref()
let layer_mut_ref = layers_ref.get_mut(layer_i)
.ok_or(anyhow!("Could not find layer"))?;
let object_element = layer_mut_ref
.data_source.as_mut()
.ok_or(anyhow!("Could not access layer data source"))?
.elements
.get(&e_id)
.ok_or(anyhow!("Could not access selected object source"))?
.clone();
.get_mut(&e_id)
.ok_or(anyhow!("Could not access selected object source"))?;
match object_element {
crate::data::Element::Node(node) => {
let new_tags = edit_tags_external(&xdg_dirs, &node.tags);
let new_tags = edit_tags_external(&xdg_dirs, &node.tags.clone())
.context("Editing tags")?;
// mark the node as modified, using the node id in the indexed_data
println!("{:?}", new_tags);
let layer_mut_ref = layers_ref.get_mut(layer_i).unwrap();
layer_mut_ref.modified_data.insert(node.id.clone());
// then, update the tags into the index data
node.tags = new_tags;
println!("new tags {:?}", node.tags);
},
_ => ()
};
@ -115,7 +117,7 @@ pub fn edit_tags_external(xdg_dirs: &BaseDirectories, initial_tags: &Tags) -> Re
.arg("--command")
.arg("helix")
.arg("--config")
.arg("~/.config/helix/config.toml")
.arg("/home/mbess/.config/helix/config.toml")
.arg(tags_file_location.clone())
.spawn()
.context("Failed to start external editor")?

View file

@ -8,7 +8,7 @@ use raylib::math::Vector2;
use rstar::{RTree, RTreeObject, AABB};
use ureq::Agent;
use crate::{data::{self, compute_way_bbox, IndexedData}, ToSlice};
use crate::{data::{self, compute_way_bbox, IndexedData}, osm_api::OSMApiClient, ToSlice};
#[fully_pub]
/// represent the displayed point on screen
@ -118,10 +118,15 @@ pub fn build_displayed_pois(indexed_data: &IndexedData) -> Result<RTree<Displaye
}
}
if let Some(amenity_class) = tags.get("amenity") {
if ["restaurant", "bank"].contains(&amenity_class.as_str()) {
if ["cafe", "restaurant", "bank"].contains(&amenity_class.as_str()) {
importance = 3;
}
}
if let Some(power_class) = tags.get("power") {
if ["tower", "substation"].contains(&power_class.as_str()) {
importance = 1;
}
}
if importance >= 0 {
d_pois.push(DisplayedPOI {
@ -264,26 +269,18 @@ 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: Arc<Mutex<Vec<Layer>>>, bounds: (Vector2, Vector2)) -> Result<usize> {
pub fn load_osm_zone(osm_api_client: &OSMApiClient, 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")
.query("bbox", &format!("{},{},{},{}", bounds.0.x, bounds.0.y, bounds.1.x, bounds.1.y))
.call()
.context("Requesting OSM local data")?;
let data_json = response.into_string().context("Decoding response data")?;
load_osm_data_from_json(layers, data_json)
let data = osm_api_client.download_map_data(bounds)?;
load_osm_data(layers, data)
}
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")?;
pub fn load_osm_data(layers: Arc<Mutex<Vec<Layer>>>, osm_data: data::Data) -> Result<usize> {
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(
osm_data.update_existing_indexed_data(
layer_to_update.data_source.as_mut().expect("Data source on dynamic OSMData")
);
layer_to_update.pois_tree = Some(
@ -294,7 +291,7 @@ pub fn load_osm_data_from_json(layers: Arc<Mutex<Vec<Layer>>>, data_json: String
build_displayed_ways(&layer_to_update.data_source.as_ref().unwrap())
.context("Building displayed ways")?
);
let elements_count = json_osm_data.elements.len();
let elements_count = osm_data.elements.len();
Ok(elements_count)
}
@ -310,3 +307,15 @@ fn get_dynamic_data_layer_mut(layers: &mut Vec<Layer>) -> Option<&mut Layer> {
None => None
}
}
pub fn get_dynamic_data_layer(layers: &Vec<Layer>) -> Option<&Layer> {
match layers.iter().find(|l| {
match l.kind {
LayerKind::DynamicOSMData => true,
_ => false
}
}) {
Some(v) => Some(v),
None => None
}
}

View file

@ -2,20 +2,23 @@ mod data;
mod changeset;
mod layers;
mod editor;
mod osm_api;
#[cfg(test)]
mod test_data;
#[cfg(test)]
mod test_layers;
use std::{collections::{HashMap, HashSet}, fs, sync::{Arc, Mutex}, thread::{self, JoinHandle}, time::Duration};
use std::{collections::{HashMap, HashSet}, fs, process, sync::{Arc, Mutex}, thread::{self, JoinHandle}, time::Duration};
use anyhow::{Context, Result, anyhow};
use changeset::Tag;
use data::{bbox_center, IndexedData};
use editor::{edit_tags, get_objects_in_area};
use fully_pub::fully_pub;
use layers::{load_osm_data_from_json, load_osm_zone, load_static_cities_layer, Layer, LayerKind};
use layers::{load_osm_data, load_osm_zone, load_static_cities_layer, Layer, LayerKind};
use osm_api::{get_base_agent, OSMApiClient};
use raylib::prelude::*;
use xdg::BaseDirectories;
use std::io::Read;
@ -24,6 +27,8 @@ use serde::{Deserialize, Serialize};
use ureq::Agent;
use rand::Rng;
pub const USER_AGENT: &str = "BobOSM dev";
fn deg2num(lat_deg: f64, lon_deg: f64, zoom: u32) -> (u32, u32) {
// Web mercator projection
let lat_rad = lat_deg.to_radians();
@ -129,9 +134,11 @@ struct Config {
static_layers: StaticLayers,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct OSMSession {
token: String
access_token: String,
scope: String,
token_type: String
}
#[derive(Debug, Serialize, Deserialize)]
@ -253,21 +260,13 @@ fn compute_bounds_from_center_zoom(screen_def: (i32, i32), target_center: Vector
fn main() -> Result<()> {
let mut rng = rand::thread_rng();
let mut raster_cache: HashMap<(u32, u32, u32), Vec<u8>> = HashMap::new();
// open editor
// hx --config ~/.config/helix/config.toml
let http_agent: Agent = ureq::AgentBuilder::new()
.user_agent("bobosm/0.1")
.timeout_read(Duration::from_secs(6))
.timeout_write(Duration::from_secs(6))
.build();
//let mut raster_cache: HashMap<(u32, u32, u32), Vec<u8>> = HashMap::new();
let http_agent: Agent = get_base_agent().build();
let (mut rl, thread) = raylib::init()
.size(1000, 1000)
.vsync()
.title("BobOSM")
.title(USER_AGENT)
.build();
let xdg_dirs = xdg::BaseDirectories::with_prefix("bobosm")
@ -282,7 +281,8 @@ fn main() -> Result<()> {
}
let mut session = read_session(&xdg_dirs)
.context("Read user session")?;
dbg!(session);
println!("loaded session: {:?}", &session);
let osm_client = OSMApiClient::from_session(session);
let app_state = Arc::new(Mutex::new(AppState {
loading: false,
@ -306,7 +306,12 @@ fn main() -> Result<()> {
}
]));
let _ = load_osm_data_from_json(layers.clone(), fs::read_to_string("./data/ouest_aubevoye.json")?);
let _ = load_osm_data(
layers.clone(),
serde_json::from_str(
&fs::read_to_string("./data/ouest_aubevoye.json")?
).expect("Load static osm data")
);
enum MoveState {
Started { start_pos: Vector2 },
@ -324,8 +329,6 @@ fn main() -> Result<()> {
// 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());
// additional
// let points_repr: Vec<(f32, f32)> = vec![
// (0.0, 0.0), (1.0, 1.0), (0.5, 0.5), (-2.0, -2.0), (0.0, -1.0), (1.0, -0.5)
@ -352,6 +355,8 @@ fn main() -> Result<()> {
// let target_geo: Vector2 = Vector2::new(1.33, 49.175); // aubevoye
let target_geo: Vector2 = Vector2::new(1.3355, 49.1761); // aubevoye boulangerie
// let target_geo: Vector2 = Vector2::new(1.33, 49.159); // gaillon
// let target_geo: Vector2 = Vector2::new(4.86101, 45.74803); // lyon univ3 tabac
let target_zoom: f32 = 50.;
let mut camera = Camera {
// compute bounds from screen width and screen height
@ -416,7 +421,8 @@ fn main() -> Result<()> {
if rl.is_key_pressed(KeyboardKey::KEY_C) {
// changeset mode
// for now, generate Osm Change in local data dir
let _ = changeset::try_changeset(&xdg_dirs, layers.clone());
println!("try to create and push a changeset");
let _ = changeset::try_changeset(&xdg_dirs, layers.clone(), &osm_client);
}
if rl.is_key_pressed(KeyboardKey::KEY_E) {
// toggle edit mode
@ -471,7 +477,12 @@ fn main() -> Result<()> {
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());
spawn_download_thread(
app_state.clone(),
osm_client.clone(),
layers.clone(),
camera.bounds.clone()
);
}
// handle input
@ -717,7 +728,7 @@ fn main() -> Result<()> {
pub fn spawn_download_thread(
app_state: Arc<Mutex<AppState>>,
http_agent: Agent,
osm_client: OSMApiClient,
layers: Arc<Mutex<Vec<Layer>>>,
bounds: (Vector2, Vector2)
) -> JoinHandle<()> {
@ -726,7 +737,7 @@ pub fn spawn_download_thread(
let mut app_state_mut = app_state.lock().unwrap();
app_state_mut.loading = true;
}
let res = load_osm_zone(&http_agent, layers, bounds);
let res = load_osm_zone(&osm_client, layers, bounds);
match res {
Ok(elements_count) => {
println!("Downloaded OSM zone {:?}, found {elements_count:?} elements", bounds);

View file

@ -1,10 +1,98 @@
// all the functions to interact with the OSM API
// https://wiki.openstreetmap.org/wiki/API_v0.6
fn upload_changeset() {
// Create: PUT /api/0.6/changeset/create
// Diff upload: POST /api/0.6/changeset/#id/upload
// upload OsmChange file
// Close: PUT /api/0.6/changeset/#id/close
use std::{sync::Arc, time::Duration};
use fully_pub::fully_pub;
use anyhow::{Context, Result, anyhow};
use quick_xml::se::to_string_with_root;
use raylib::math::Vector2;
use ureq::{Agent, AgentBuilder};
use crate::{changeset::{ChangesetContent, ChangesetMeta}, data, OSMSession, Session};
#[derive(Debug, Clone)]
#[fully_pub]
struct OSMApiClient {
agent: Agent,
session: OSMSession
}
const BASE_URL: &str = "https://api.openstreetmap.org/api/0.6";
// const BASE_URL: &str = "https://master.apis.dev.openstreetmap.org/api/0.6";
fn get_path(path: &str) -> String {
BASE_URL.to_string() + path
}
pub fn get_base_agent() -> AgentBuilder {
ureq::AgentBuilder::new()
.user_agent("bobosm/0.1")
// .proxy(ureq::Proxy::new("socks5://localhost:8999").expect("Build proxy error"))
.timeout_read(Duration::from_secs(6))
.timeout_write(Duration::from_secs(6))
}
impl OSMApiClient {
pub fn download_map_data(&self, bounds: (Vector2, Vector2)) -> Result<data::Data> {
let response = self.agent.get(&get_path("/map.json"))
.query("bbox", &format!("{},{},{},{}", bounds.0.x, bounds.0.y, bounds.1.x, bounds.1.y))
.call()
.context("Requesting OSM local data")?;
let data_json = response.into_string().context("Decoding response data")?;
let osm_data: data::Data = serde_json::from_str(&data_json)
.context("Parsing static osm data")?;
Ok(osm_data)
}
/// Open Changeset and get a changeset id
pub fn open_changeset(&self, changeset_meta: ChangesetMeta) -> Result<i64> {
let body_xml = to_string_with_root("osm", &changeset_meta)?;
println!("{}", &body_xml);
println!("opening…");
let res = self.agent
.put(&get_path("/changeset/create"))
.set("Authorization", &format!("Bearer {}", self.session.access_token))
.set("Content-Type", "text/xml")
.send_string(&body_xml)?;
let changeset_id: i64 = res
.into_string()
.map_err(|_e| anyhow!("Could not decode response"))?
.parse()
.map_err(|_e| anyhow!("Could not parse changeset response as number"))?;
Ok(changeset_id)
}
pub fn upload_changes(&self, changeset_id: i64, changeset_content: ChangesetContent) -> Result<()> {
let body_xml = to_string_with_root("osmChange", &changeset_content)?;
println!("{}", &body_xml);
let res = self.agent
.post(&get_path(&format!("/changeset/{}/upload", changeset_id)))
.set("Authorization", &format!("Bearer {}", self.session.access_token))
.set("Content-Type", "text/xml")
.send_string(&body_xml)?;
let changeset_confirmation = res
.into_string()
.map_err(|_e| anyhow!("Could not decode response"))?;
println!("confirm: {:?}", changeset_confirmation);
Ok(())
}
pub fn close_changeset(&self, changeset_id: i64) -> Result<()> {
let _ = self.agent
.put(&get_path(&format!("/changeset/{}/close", changeset_id)))
.set("Authorization", &format!("Bearer {}", self.session.access_token))
.call()
.map_err(|_e| anyhow!("Http request failed"))?;
Ok(())
}
pub fn from_session(session: Session) -> OSMApiClient {
OSMApiClient {
session: session.osm_session.unwrap(),
agent: get_base_agent().build()
}
}
}

1
tmp/changes.osc Normal file
View file

@ -0,0 +1 @@
<osmChange version="0.6" generator="iD"><create><node id="-1" lon="1.3201818696975711" lat="49.1284288305609" version="0"><tag k="amenity" v="cafe"/></node></create><modify/><delete if-unused="true"/></osmChange>