add territories struct #6
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1487,6 +1487,7 @@ version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
@ -16,7 +16,7 @@ geojson = "0.24.2"
|
||||
r2d2 = "0.8.10"
|
||||
r2d2_sqlite = "0.31.0"
|
||||
reqwest = "0.12.24"
|
||||
rusqlite = "0.37.0"
|
||||
rusqlite = {version="0.37.0", features=["bundled"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
tera = "1.20.1"
|
||||
|
||||
58
src/data.rs
58
src/data.rs
@ -1,5 +1,7 @@
|
||||
use crate::geo_utils::GeoUtils;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Player {
|
||||
pub username: String,
|
||||
@ -10,7 +12,7 @@ pub struct Team {
|
||||
pub name: String,
|
||||
pub color: String,
|
||||
pub players: Vec<Player>,
|
||||
pub scores: HashMap<String, i32>,
|
||||
pub scores: HashMap<String, i32>, // neighborhood to number of valid changes
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
@ -19,6 +21,60 @@ pub struct Game {
|
||||
pub teams: Vec<Team>,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
pub territories: HashMap<String, Territory>, // territory name/id to struct
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn build_territories(&mut self, geoutils: &GeoUtils) {
|
||||
let territory_json = geoutils.get_feature_collection_copy();
|
||||
for feature in &territory_json.features {
|
||||
let name = feature.property("S_HOOD").unwrap_or_default();
|
||||
let name = name.as_str().unwrap_or("unknown");
|
||||
let t: Territory = Territory {
|
||||
territory_name: String::from(name),
|
||||
claiming_team: None,
|
||||
claiming_score: 0,
|
||||
};
|
||||
self.territories.insert(name.to_string(), t);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(code: String, teams: Vec<Team>, start_time: String, end_time: String) -> Self {
|
||||
let geoutils = GeoUtils::new();
|
||||
let mut game = Game {
|
||||
code: code,
|
||||
teams: teams,
|
||||
start_time: start_time,
|
||||
end_time: end_time,
|
||||
territories: HashMap::new(),
|
||||
};
|
||||
game.build_territories(&geoutils);
|
||||
game
|
||||
}
|
||||
|
||||
pub fn update_territories(&mut self) {
|
||||
for (_, territory) in &mut self.territories {
|
||||
let mut max_team: (Option<Team>, i32) = (None, 0);
|
||||
for team in &self.teams {
|
||||
let score = team.scores.get(&territory.territory_name).unwrap_or(&0);
|
||||
if *score > max_team.1 {
|
||||
max_team = (Some(team.clone()), *score);
|
||||
} else if *score > 0 && *score == max_team.1 {
|
||||
// Tie doesn't count
|
||||
max_team = (None, max_team.1);
|
||||
}
|
||||
}
|
||||
territory.claiming_team = max_team.0;
|
||||
territory.claiming_score = max_team.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Territory {
|
||||
pub territory_name: String,
|
||||
pub claiming_team: Option<Team>,
|
||||
pub claiming_score: i32,
|
||||
}
|
||||
|
||||
impl Team {
|
||||
|
||||
@ -66,12 +66,7 @@ fn get_all_games(conn: Connection, all_teams: HashMap<String, Team>) -> GamesRes
|
||||
.map(|part| all_teams.get(&format!("{game_name}_{part}")).unwrap())
|
||||
.map(|part| part.to_owned())
|
||||
.collect();
|
||||
Game {
|
||||
code: game_name,
|
||||
teams,
|
||||
start_time: games_row.get(2)?,
|
||||
end_time: games_row.get(3)?,
|
||||
}
|
||||
Game::new(game_name, teams, games_row.get(2)?, games_row.get(3)?)
|
||||
})
|
||||
})
|
||||
.and_then(Iterator::collect)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::Team;
|
||||
use crate::data::{Game, Territory};
|
||||
use geo as geo_types;
|
||||
use geo::algorithm::contains::Contains;
|
||||
use geojson::{FeatureCollection, GeoJson, Geometry, Value};
|
||||
@ -29,31 +29,30 @@ impl GeoUtils {
|
||||
FeatureCollection::try_from(geojson).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_feature_collection_copy(self) -> FeatureCollection {
|
||||
pub fn get_feature_collection_copy(&self) -> FeatureCollection {
|
||||
self.feature_collection.clone()
|
||||
}
|
||||
|
||||
pub fn get_colored_collection_copy(self, teams: Vec<Team>) -> FeatureCollection {
|
||||
pub fn get_colored_collection_copy(self, game: &Game) -> FeatureCollection {
|
||||
let mut copy = self.feature_collection.clone();
|
||||
for feature in &mut copy.features {
|
||||
let name = feature.property("S_HOOD").unwrap_or_default();
|
||||
let name = name.as_str().unwrap_or("unknown");
|
||||
let mut max_team: (Option<String>, i32) = (None, 0);
|
||||
for team in &teams {
|
||||
let score = team.scores.get(name).unwrap_or(&0);
|
||||
if *score > max_team.1 {
|
||||
max_team = (Some(team.color.clone()), *score);
|
||||
} else if *score > 0 && *score == max_team.1 {
|
||||
// Tie doesn't count
|
||||
max_team = (None, 0);
|
||||
}
|
||||
}
|
||||
match max_team.0 {
|
||||
None => feature.set_property("color", "#888888ff"),
|
||||
Some(c) => {
|
||||
feature.set_property("color", c);
|
||||
let this_territory: Territory = game
|
||||
.territories
|
||||
.get(name)
|
||||
.expect("Whaaa no territory found!")
|
||||
.clone();
|
||||
match this_territory.claiming_team.clone() {
|
||||
Some(team) => {
|
||||
feature.set_property("color", team.color.clone());
|
||||
feature.set_property("claiming_team", team.name.clone());
|
||||
}
|
||||
None => {
|
||||
feature.set_property("color", "#888888ff");
|
||||
}
|
||||
}
|
||||
feature.set_property("claiming_score", this_territory.claiming_score);
|
||||
}
|
||||
copy
|
||||
}
|
||||
|
||||
24
src/main.rs
24
src/main.rs
@ -131,6 +131,23 @@ async fn legend_data(
|
||||
.body("{}")
|
||||
}
|
||||
|
||||
#[get("/territories/{game_code}")]
|
||||
async fn get_territories(
|
||||
game_code: web::Path<String>,
|
||||
games_actor: web::Data<Addr<GamesActor>>,
|
||||
) -> impl Responder {
|
||||
if let Ok(Responses::GamesResult(games)) = games_actor.send(Messages::GetGames).await
|
||||
&& let Some(game) = games.iter().find(|g| g.code == *game_code)
|
||||
{
|
||||
return HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(serde_json::to_string(&game.territories).unwrap());
|
||||
}
|
||||
HttpResponse::InternalServerError()
|
||||
.content_type("application/json")
|
||||
.body("{}")
|
||||
}
|
||||
|
||||
#[get("/geojson/{game_code}")]
|
||||
async fn geojson_endpoint(
|
||||
game_code: web::Path<String>,
|
||||
@ -139,9 +156,8 @@ async fn geojson_endpoint(
|
||||
if let Ok(Responses::GamesResult(games)) = games_actor.send(Messages::GetGames).await
|
||||
&& let Some(game) = games.iter().find(|g| g.code == *game_code)
|
||||
{
|
||||
let teams = &game.teams;
|
||||
let utils = geo_utils::GeoUtils::new();
|
||||
let feature_collection = utils.get_colored_collection_copy(teams.to_vec());
|
||||
let feature_collection = utils.get_colored_collection_copy(&game);
|
||||
return HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(serde_json::to_string(&feature_collection).unwrap());
|
||||
@ -198,7 +214,7 @@ async fn main() -> std::io::Result<()> {
|
||||
let games = db::games(&pool)
|
||||
.await
|
||||
.expect("retirieving games from db should work fine");
|
||||
let state = Arc::new(Mutex::new(games));
|
||||
let state = Arc::new(Mutex::new(games.clone()));
|
||||
let actor_addr = GamesActor {
|
||||
games: state.clone(),
|
||||
}
|
||||
@ -215,6 +231,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(admin)
|
||||
.service(admin_query)
|
||||
.service(list_of_games)
|
||||
.service(get_territories)
|
||||
})
|
||||
.bind(("0.0.0.0", 8080))?
|
||||
.run()
|
||||
@ -266,6 +283,7 @@ async fn update_scores(games: Vec<Game>) -> Result<Vec<Game>, Box<dyn Error>> {
|
||||
team.add_scores(&territory_scores);
|
||||
}
|
||||
}
|
||||
neogame.update_territories();
|
||||
result.push(neogame);
|
||||
}
|
||||
Ok(result)
|
||||
|
||||
@ -28,52 +28,70 @@
|
||||
<div id="map"></div>
|
||||
<ul id="legend" class="legend"></ul>
|
||||
<script>
|
||||
// <map>
|
||||
function style(feature) {
|
||||
return { color: feature.properties.color};
|
||||
// <map>
|
||||
function style(feature) {
|
||||
return { color: feature.properties.color};
|
||||
};
|
||||
|
||||
function onEachFeature(feature, layer) {
|
||||
// bind a popup to each geojson element
|
||||
// does this feature have a property named popupContent?
|
||||
|
||||
if (feature.properties) {
|
||||
var popup = document.createElement("div");
|
||||
popup.innerHTML += feature.properties.S_HOOD;
|
||||
popup.innerHTML += "<br>Team: " + feature.properties.claiming_team;
|
||||
popup.innerHTML += "<br>Score: " + feature.properties.claiming_score;
|
||||
layer.bindPopup(popup);
|
||||
}
|
||||
var map = L.map('map').setView([47.6062, -122.3321], 12);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
fetch('/geojson/{{game_code}}')
|
||||
.then(res => res.json())
|
||||
.then(data => L.geoJSON(data, {style: style}).addTo(map));
|
||||
// </map>
|
||||
// <legend>
|
||||
const endpoint = "/legend-data/{{game_code}}";
|
||||
fetch(endpoint)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const legend = document.getElementById("legend");
|
||||
// Clear existing content if any
|
||||
legend.innerHTML = "";
|
||||
var map = L.map('map').setView([47.6062, -122.3321], 12);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
}).addTo(map);
|
||||
|
||||
data.forEach(item => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "legend-item";
|
||||
fetch('/geojson/{{game_code}}')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
L.geoJSON(data, {style: style, onEachFeature : onEachFeature}).addTo(map)
|
||||
});
|
||||
// </map>
|
||||
// <legend>
|
||||
const endpoint = "/legend-data/{{game_code}}";
|
||||
fetch(endpoint)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const legend = document.getElementById("legend");
|
||||
// Clear existing content if any
|
||||
legend.innerHTML = "";
|
||||
|
||||
const colorBox = document.createElement("span");
|
||||
colorBox.className = "color-box";
|
||||
colorBox.style.backgroundColor = item.color;
|
||||
data.forEach(item => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "legend-item";
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = item.name;
|
||||
const colorBox = document.createElement("span");
|
||||
colorBox.className = "color-box";
|
||||
colorBox.style.backgroundColor = item.color;
|
||||
|
||||
li.appendChild(colorBox);
|
||||
li.appendChild(label);
|
||||
legend.appendChild(li);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fetching legend data failed:", error);
|
||||
const label = document.createElement("span");
|
||||
label.textContent = item.name;
|
||||
|
||||
li.appendChild(colorBox);
|
||||
li.appendChild(label);
|
||||
legend.appendChild(li);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fetching legend data failed:", error);
|
||||
});
|
||||
const territories_endpoint = "/territories/{game_code}"
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user