diff --git a/src/main.rs b/src/main.rs index 237b668..1225a8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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> { - 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>, client: &Client) -> HashMap { - let mut result: HashMap = 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>, start_date: NaiveDateTime, end_date: NaiveDateTime) -> Vec> { - 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> { - 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> { - 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> { - 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> { - Ok(client.get(endpoint).send().await?.text().await?) -} - -fn extract_tag_value(reader: EventReader<&[u8]>, tag: String) -> Option { - 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 -} - diff --git a/src/osm.rs b/src/osm.rs new file mode 100644 index 0000000..5d7cec6 --- /dev/null +++ b/src/osm.rs @@ -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> { + Ok(client.get(endpoint).send().await?.text().await?) + } + + pub async fn get_territory(lat: String, lon: String, client: &Client) -> Result> { + 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> { + 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 { + 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 + } + pub struct Input { + pub start_time: NaiveDateTime, + pub end_time: NaiveDateTime, + pub teams: Vec + } + pub async fn calculate(input: Input)-> Result<(), Box> { + 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>, start_date: NaiveDateTime, end_date: NaiveDateTime) -> Vec> { + 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>, client: &Client) -> HashMap { + let mut result: HashMap = 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> { + 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() + } + +} \ No newline at end of file