feat: changeset generation and upload
This commit is contained in:
parent
d1d563fd52
commit
ad0c988869
189
Cargo.lock
generated
189
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
5
docs/DEV.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Dev tips
|
||||
|
||||
Use the live test server
|
||||
|
||||
https://master.apis.dev.openstreetmap.org/#map=16/49.1679/1.3327
|
15
docs/curl.md
15
docs/curl.md
|
@ -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
|
||||
|
||||
|
|
BIN
docs/dev_screenshots/2024-06-29_france_cities.png
Normal file
BIN
docs/dev_screenshots/2024-06-29_france_cities.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
|
@ -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
1
out
Normal file
|
@ -0,0 +1 @@
|
|||
{"access_token":"eqn8EG_A7VSeBGEaO5XeKGThlTkCWMbbuHwF_PNfX7E","scope":"read_prefs write_api","token_type":"Bearer"}
|
147
src/changeset.rs
147
src/changeset.rs
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ struct Node {
|
|||
id: i64,
|
||||
lat: f32,
|
||||
lon: f32,
|
||||
tags: Tags
|
||||
tags: Tags,
|
||||
version: i64
|
||||
}
|
||||
|
||||
impl Node {
|
||||
|
|
|
@ -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")?
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -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);
|
||||
|
|
|
@ -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
1
tmp/changes.osc
Normal 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>
|
Loading…
Reference in a new issue