create modules for osm operations

This commit is contained in:
2025-11-16 14:11:06 -08:00
parent cf76570fca
commit bf2fbbc134
2 changed files with 133 additions and 98 deletions

View File

@ -1,108 +1,17 @@
use std::{collections::HashMap, error::Error};
pub mod osm;
use crate::osm::scores;
use std::error::Error;
use chrono::NaiveDateTime;
use xml::{attribute::OwnedAttribute, reader::{EventReader, XmlEvent}};
use reqwest::Client;
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%SZ";
#[tokio::main]
async fn main()-> Result<(), Box<dyn Error>> {
let client = Client::builder()
.user_agent("MapBattle/0.1")
.build()?;
let user = "ammaratef45";
let start_date = NaiveDateTime::parse_from_str("2025-11-13T11:55:07Z", DATE_FORMAT)?;
let end_date = NaiveDateTime::parse_from_str("2025-11-13T15:55:07Z", DATE_FORMAT)?;
let body = get_changesets(String::from(user), &client).await?;
let reader = EventReader::from_str(&body);
let changesets = extract_changesets(reader);
let changesets = time_bound(changesets, start_date, end_date);
let points = to_points(changesets, &client).await;
println!("{points:?}");
Ok(())
let scores_input = scores::Input {
start_time: start_date, end_time: end_date, teams: vec![scores::Team{players: vec![String::from(user)]}]
};
scores::calculate(scores_input).await
}
async fn to_points(changesets: Vec<Vec<OwnedAttribute>>, client: &Client) -> HashMap<String, i32> {
let mut result: HashMap<String, i32> = HashMap::new();
for changeset in changesets {
let min_lat = changeset.clone().into_iter().find(|att| att.name.local_name.eq("min_lat")).unwrap().value;
let min_lon = changeset.clone().into_iter().find(|att| att.name.local_name.eq("min_lon")).unwrap().value;
// TODO get the points from the changes_count
let score = changeset.into_iter().find(|att| att.name.local_name.eq("changes_count")).unwrap().value;
let score:i32 = score.parse().expect("changes count returned as not a string");
let terriroty = get_territory(min_lat, min_lon, client).await.expect("failed to get territory");
result.entry(terriroty).and_modify(|val| *val += score).or_insert(score);
}
result
}
fn time_bound(input: Vec<Vec<OwnedAttribute>>, start_date: NaiveDateTime, end_date: NaiveDateTime) -> Vec<Vec<OwnedAttribute>> {
input.into_iter().filter(|list| {
match list.iter().find(|att| att.name.local_name.eq("created_at")) {
Some(created_at) => {
match NaiveDateTime::parse_from_str(&created_at.value, DATE_FORMAT) {
Ok(date) => date > start_date && date < end_date,
Err(_) => {
todo!()
}
}
}
None => false
}
}).collect()
}
fn extract_changesets(reader: EventReader<&[u8]>) -> Vec<Vec<OwnedAttribute>> {
reader.into_iter().filter(|event| match event {
Ok(XmlEvent::StartElement { name, .. }) => {
name.local_name.eq("changeset")
},
Err(_) => todo!(),
_ => false
}).map(|event| match event {
Ok(XmlEvent::StartElement { name:_, attributes, .. }) => attributes,
_ => panic!("We should have already filtered out all of these")
}).collect()
}
async fn get_changesets(user: String, client: &Client) -> Result<String, Box<dyn Error>> {
let endpoint = format!("https://api.openstreetmap.org/api/0.6/changesets?display_name={user}");
get_data(endpoint, client).await
}
async fn get_territory(lat: String, lon: String, client: &Client) -> Result<String, Box<dyn Error>> {
let endpoint = format!("https://nominatim.openstreetmap.org/reverse??format=json&lon={lon}&lat={lat}");
let resp = get_data(endpoint, client).await?;
let reader = EventReader::from_str(&resp);
Ok(extract_tag_value(reader, String::from("suburb")).unwrap())
}
async fn get_data(endpoint: String, client: &Client) -> Result<String, Box<dyn Error>> {
Ok(client.get(endpoint).send().await?.text().await?)
}
fn extract_tag_value(reader: EventReader<&[u8]>, tag: String) -> Option<String> {
let mut inside_tag = false;
for e in reader {
match e {
Ok(XmlEvent::StartElement { name, .. }) => {
if name.local_name == tag {
inside_tag = true;
}
}
Ok(XmlEvent::Characters(data)) => {
if inside_tag {
return Some(data);
}
}
Ok(XmlEvent::EndElement { name }) => {
if name.local_name == tag {
inside_tag = false;
}
}
_ => {}
}
}
None
}

126
src/osm.rs Normal file
View File

@ -0,0 +1,126 @@
mod network {
use reqwest::Client;
use std::error::Error;
use xml::reader::EventReader;
async fn get_data(endpoint: String, client: &Client) -> Result<String, Box<dyn Error>> {
Ok(client.get(endpoint).send().await?.text().await?)
}
pub async fn get_territory(lat: String, lon: String, client: &Client) -> Result<String, Box<dyn Error>> {
let endpoint = format!("https://nominatim.openstreetmap.org/reverse??format=json&lon={lon}&lat={lat}");
let resp = get_data(endpoint, client).await?;
let reader = EventReader::from_str(&resp);
Ok(crate::osm::xml_utils::extract_tag_value_from_reader(reader, String::from("suburb")).unwrap())
}
pub async fn get_changesets(user: String, client: &Client) -> Result<String, Box<dyn Error>> {
let endpoint = format!("https://api.openstreetmap.org/api/0.6/changesets?display_name={user}");
get_data(endpoint, client).await
}
}
mod xml_utils {
use xml::reader::{EventReader, XmlEvent};
pub fn extract_tag_value_from_reader(reader: EventReader<&[u8]>, tag: String) -> Option<String> {
let mut inside_tag = false;
for e in reader {
match e {
Ok(XmlEvent::StartElement { name, .. }) => {
if name.local_name == tag {
inside_tag = true;
}
}
Ok(XmlEvent::Characters(data)) => {
if inside_tag {
return Some(data);
}
}
Ok(XmlEvent::EndElement { name }) => {
if name.local_name == tag {
inside_tag = false;
}
}
_ => {}
}
}
None
}
}
pub mod scores {
use crate::osm::network;
use chrono::NaiveDateTime;
use reqwest::Client;
use std::{collections::HashMap, error::Error};
use xml::{attribute::OwnedAttribute, reader::{EventReader, XmlEvent}};
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%SZ";
pub struct Team {
pub players: Vec<String>
}
pub struct Input {
pub start_time: NaiveDateTime,
pub end_time: NaiveDateTime,
pub teams: Vec<Team>
}
pub async fn calculate(input: Input)-> Result<(), Box<dyn Error>> {
let client = Client::builder()
.user_agent("MapBattle/0.1")
.build()?;
for team in input.teams {
for player in team.players {
let raw_changesets = network::get_changesets(player.clone(), &client).await?;
let reader = EventReader::from_str(&raw_changesets);
let changesets = extract_changesets_from_reader(reader);
let changesets = time_bound(changesets, input.start_time, input.end_time);
let points = to_points(changesets, &client).await;
println!("Points for {player}: {points:?}");
}
}
Ok(())
}
fn time_bound(input: Vec<Vec<OwnedAttribute>>, start_date: NaiveDateTime, end_date: NaiveDateTime) -> Vec<Vec<OwnedAttribute>> {
input.into_iter().filter(|list| {
match list.iter().find(|att| att.name.local_name.eq("created_at")) {
Some(created_at) => {
match NaiveDateTime::parse_from_str(&created_at.value, DATE_FORMAT) {
Ok(date) => date > start_date && date < end_date,
Err(_) => {
todo!()
}
}
}
None => false
}
}).collect()
}
async fn to_points(changesets: Vec<Vec<OwnedAttribute>>, client: &Client) -> HashMap<String, i32> {
let mut result: HashMap<String, i32> = HashMap::new();
for changeset in changesets {
let min_lat = changeset.clone().into_iter().find(|att| att.name.local_name.eq("min_lat")).unwrap().value;
let min_lon = changeset.clone().into_iter().find(|att| att.name.local_name.eq("min_lon")).unwrap().value;
// TODO get the points from the changes_count
let score = changeset.into_iter().find(|att| att.name.local_name.eq("changes_count")).unwrap().value;
let score:i32 = score.parse().expect("changes count returned as not a string");
let terriroty = network::get_territory(min_lat, min_lon, client).await.expect("failed to get territory");
result.entry(terriroty).and_modify(|val| *val += score).or_insert(score);
}
result
}
fn extract_changesets_from_reader(reader: EventReader<&[u8]>) -> Vec<Vec<OwnedAttribute>> {
reader.into_iter().filter(|event| match event {
Ok(XmlEvent::StartElement { name, .. }) => {
name.local_name.eq("changeset")
},
Err(_) => todo!(),
_ => false
}).map(|event| match event {
Ok(XmlEvent::StartElement { name:_, attributes, .. }) => attributes,
_ => panic!("We should have already filtered out all of these")
}).collect()
}
}