wip: get popup

This commit is contained in:
2026-05-23 21:29:50 -07:00
parent eb4a2e04d6
commit d5bf7183c6
5 changed files with 32 additions and 129 deletions

View File

@ -1,5 +1,6 @@
use crate::geo_utils::GeoUtils;
use serde::{Deserialize, Serialize};
use geojson::{Feature};
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -27,11 +28,16 @@ pub struct Game {
impl Game {
fn build_territories(&mut self, geoutils: &GeoUtils) {
let territory_names = geoutils.get_all_territories();
for name in territory_names {
let territory_json = geoutils.get_feature_collection_copy();
for feature in &territory_json.features {
let feat: Feature = feature.clone();
let name = feature.property("S_HOOD").unwrap_or_default();
let name = name.as_str().unwrap_or("unknown");
let t: Territory = Territory {
territory_name: name,
territory_name: String::from(name),
claiming_team: None,
claiming_score: 0,
territory_feature:feat,
};
self.territories.push(t);
}
@ -73,6 +79,7 @@ pub struct Territory {
pub territory_name: String,
pub claiming_team: Option<Team>,
pub claiming_score: i32,
pub territory_feature: Feature,
}
impl Team {

View File

@ -1,9 +1,9 @@
use crate::Team;
use geo as geo_types;
use geo::algorithm::contains::Contains;
use geojson::{FeatureCollection, GeoJson, Geometry, Value};
use std::convert::TryFrom;
use std::fs;
use crate::data::{Game, Territory};
pub struct GeoUtils {
feature_collection: FeatureCollection,
@ -42,58 +42,28 @@ impl GeoUtils {
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);
// TODO: Make the territories field of Game struct to be a hashmap
let this_territory : Territory = game.territories.clone().iter().find(|t| t.territory_name == *name)
.expect("somehow the territory wasn't found").clone();
match this_territory.claiming_team {
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
}
// pub fn get_number_neighborhoods(self, teams : Vec<Team>) -> HashMap<Team, i32> {
// // new dicts of {team_name: number_claimed_neighborhoods}
// let team_to_score = HashMap::new();
// 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<Team>, 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.clone()), *score);
// } else if *score > 0 && *score == max_team.1 {
// // Tie doesn't count
// max_team = (None, 0);
// }
// }
// match max_team.0 {
// None => println!("No team has claimed this neighborhood: {name}"),
// Some(c) => {
// team_to_score.entry(c).and_modify(|val| *val+=1).or_insert(1);
// }
// }
// }
// team_to_score
// }
pub fn get_territory_for(self, point: geo_types::Point) -> String {
let feature_collection: FeatureCollection = self.get_feature_collection_copy();
for feature in feature_collection {

View File

@ -139,7 +139,6 @@ async fn get_territories(
if let Ok(Responses::GamesResult(games)) = games_actor.send(Messages::GetGames).await
&& let Some(game) = games.iter().find(|g| g.code == *game_code)
{
println!("{0:?}", game);
return HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&game.territories).unwrap());
@ -183,9 +182,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());

View File

@ -36,8 +36,13 @@
function onEachFeature(feature, layer) {
// bind a popup to each geojson element
// does this feature have a property named popupContent?
if (feature.properties) {
layer.bindPopup(feature.properties.S_HOOD);
const popup = document.createElement("p");
popup.textContent += feature.properties.S_HOOD;
popup.textContent += "\nTeam: " + feature.properties.claiming_team;
popup.textContent += "\nScore: " + feature.properties.claiming_score;
layer.bindPopup(popup);
}
}
@ -48,7 +53,9 @@
fetch('/geojson/{{game_code}}')
.then(res => res.json())
.then(data => L.geoJSON(data, {style: style, onEachFeature : onEachFeature}).addTo(map));
.then(data => {
L.geoJSON(data, {style: style, onEachFeature : onEachFeature}).addTo(map)
});
// </map>
// <legend>
const endpoint = "/legend-data/{{game_code}}";

View File

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ game_code }}</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<style>
#map { height: 90vh; }
.legend {
list-style: none;
padding: 0;
width: 200px;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.color-box {
width: 20px;
height: 20px;
margin-right: 10px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="map"></div>
<ul id="legend" class="legend"></ul>
<script>
// <map>
function style(feature) {
return { color: feature.properties.color};
}
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 = "";
data.forEach(item => {
const li = document.createElement("li");
li.className = "legend-item";
const colorBox = document.createElement("span");
colorBox.className = "color-box";
colorBox.style.backgroundColor = item.color;
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);
});
</script>
</body>
</html>