use seattle geodata to find the neighborhood/territory #2

Merged
ammaratef45 merged 2 commits from ammar into main 2025-11-19 04:46:32 +00:00
6 changed files with 449 additions and 11 deletions

289
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -11,6 +17,15 @@ dependencies = [
"libc",
]
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -41,6 +56,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
@ -92,6 +113,31 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "csv"
version = "1.4.0"
@ -124,6 +170,22 @@ dependencies = [
"syn",
]
[[package]]
name = "earcutr"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01"
dependencies = [
"itertools",
"num-traits",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
@ -161,12 +223,24 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "float_next_after"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -230,6 +304,59 @@ dependencies = [
"pin-utils",
]
[[package]]
name = "geo"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc1a1678e54befc9b4bcab6cd43b8e7f834ae8ea121118b0fd8c42747675b4a"
dependencies = [
"earcutr",
"float_next_after",
"geo-types",
"geographiclib-rs",
"i_overlay",
"log",
"num-traits",
"robust",
"rstar",
"spade",
]
[[package]]
name = "geo-types"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a4dcd69d35b2c87a7c83bce9af69fd65c9d68d3833a0ded568983928f3fc99"
dependencies = [
"approx",
"num-traits",
"rayon",
"rstar",
"serde",
]
[[package]]
name = "geographiclib-rs"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841"
dependencies = [
"libm",
]
[[package]]
name = "geojson"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e26f3c45b36fccc9cf2805e61d4da6bc4bbd5a3a9589b01afa3a40eff703bd79"
dependencies = [
"geo-types",
"log",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@ -272,12 +399,42 @@ dependencies = [
"tracing",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]]
name = "http"
version = "1.3.1"
@ -398,6 +555,49 @@ dependencies = [
"windows-registry",
]
[[package]]
name = "i_float"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "010025c2c532c8d82e42d0b8bb5184afa449fa6f06c709ea9adcb16c49ae405b"
dependencies = [
"libm",
]
[[package]]
name = "i_key_sort"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27"
[[package]]
name = "i_overlay"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcccbd4e4274e0f80697f5fbc6540fdac533cce02f2081b328e68629cce24f9"
dependencies = [
"i_float",
"i_key_sort",
"i_shape",
"i_tree",
"rayon",
]
[[package]]
name = "i_shape"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea154b742f7d43dae2897fcd5ead86bc7b5eefcedd305a7ebf9f69d44d61082"
dependencies = [
"i_float",
]
[[package]]
name = "i_tree"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
@ -531,7 +731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.16.0",
]
[[package]]
@ -550,6 +750,15 @@ dependencies = [
"serde",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -572,6 +781,12 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@ -606,6 +821,8 @@ version = "0.1.0"
dependencies = [
"chrono",
"csv",
"geo",
"geojson",
"reqwest",
"serde",
"tempfile",
@ -660,6 +877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -792,6 +1010,26 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
@ -855,6 +1093,23 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "robust"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
[[package]]
name = "rstar"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
dependencies = [
"heapless",
"num-traits",
"smallvec",
]
[[package]]
name = "rustix"
version = "1.1.2"
@ -1043,6 +1298,18 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "spade"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990"
dependencies = [
"hashbrown 0.15.5",
"num-traits",
"robust",
"smallvec",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -1120,6 +1387,26 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.2"

View File

@ -6,6 +6,8 @@ edition = "2024"
[dependencies]
chrono = "0.4.42"
csv = "1.3"
geo = "0.31.0"
geojson = "0.24.2"
reqwest = "0.12.24"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.48.0", features = ["full"] }

File diff suppressed because one or more lines are too long

49
src/geo_utils.rs Normal file
View File

@ -0,0 +1,49 @@
use geo as geo_types;
use std::convert::TryFrom;
use std::fs;
use geo::algorithm::contains::Contains;
use geojson::{FeatureCollection, GeoJson, Geometry, Value};
pub fn get_territory_for(point: geo_types::Point) -> String {
let txt: String = fs::read_to_string("Neighborhood_Map_Atlas_Neighborhoods.geojson").expect("file should be present");
let geojson_str = txt;
let geojson: GeoJson = geojson_str.parse::<GeoJson>().unwrap();
let feature_collection: FeatureCollection = FeatureCollection::try_from(geojson).unwrap();
for feature in feature_collection {
let name = feature.property("S_HOOD").unwrap_or_default();
let name = name.as_str().unwrap_or("unknown");
let geometry: Geometry = feature.clone().geometry.expect("Feature has no geometry");
match geometry.value {
Value::Polygon(coords) => {
// Outer ring
let exterior = coords[0]
.iter()
.map(|coord| (coord[0], coord[1]))
.collect::<Vec<(f64, f64)>>();
let polygon = geo_types::Polygon::new(exterior.into(), vec![]);
if polygon.contains(&point) {
return String::from(name);
}
}
Value::MultiPolygon(coords) => {
let multipoly = coords.iter().map(|poly_coords| {
let exterior = poly_coords[0]
.iter()
.map(|coord| (coord[0], coord[1]))
.collect::<Vec<(f64, f64)>>()
.try_into()
.expect("Invalid exterior ring");
geo_types::Polygon::new(exterior, vec![])
}).collect::<Vec<geo_types::Polygon<f64>>>();
let multipolygon = geo_types::MultiPolygon::new(multipoly);
if multipolygon.contains(&point) {
return String::from(name);
}
}
_ => panic!("Geometry is not supported"),
};
}
String::from("Outside")
}

View File

@ -1,6 +1,7 @@
pub mod osm;
mod data;
mod csv_io;
mod geo_utils;
use crate::osm::scores;
use std::error::Error;

View File

@ -1,19 +1,11 @@
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
@ -54,6 +46,8 @@ pub mod scores {
use reqwest::Client;
use std::{collections::HashMap, error::Error};
use xml::{attribute::OwnedAttribute, reader::{EventReader, XmlEvent}};
use geo as geo_types;
use crate::geo_utils;
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%SZ";
pub struct Team {
pub players: Vec<String>
@ -127,11 +121,15 @@ pub mod scores {
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;
let min_lat: f64 = min_lat.parse().unwrap_or_default();
let min_lon: f64 = min_lon.parse().unwrap_or_default();
// 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);
let point = geo_types::Point::new(min_lon, min_lat);
let territory = geo_utils::get_territory_for(point);
println!("{territory}");
result.entry(territory).and_modify(|val| *val += score).or_insert(score);
}
result
}