diff --git a/existing_maps/small_test.json b/existing_maps/small_test.json index f5ae5b1..d4e6688 100644 --- a/existing_maps/small_test.json +++ b/existing_maps/small_test.json @@ -30,11 +30,13 @@ { "source": "s0", "target": "s1", + "nodes": [], "lines": ["l0"] }, { "source": "s1", "target": "s2", + "nodes": [], "lines": ["l0"] } ] diff --git a/src/algorithm/recalculate_map.rs b/src/algorithm/recalculate_map.rs index 4e3a123..a914fe8 100644 --- a/src/algorithm/recalculate_map.rs +++ b/src/algorithm/recalculate_map.rs @@ -47,11 +47,12 @@ pub enum Updater { /// Attempt to route the edges of the map, retrying with different, random, edge /// orders if it fails. -fn attempt_edge_routing( +async fn attempt_edge_routing( settings: AlgorithmSettings, map: &mut Map, occupied: &mut OccupiedNodes, mut edges: Vec, + midway_updater: Updater, ) -> Result<()> { let mut attempt = 0; let mut found = false; @@ -65,7 +66,9 @@ fn attempt_edge_routing( &mut alg_map, edges.clone(), occupied.clone(), - ); + midway_updater.clone(), + ) + .await; if let Err(e) = res { log_print( @@ -140,12 +143,12 @@ pub async fn recalculate_map( super::LogType::Debug, ); + unsettle_map(map); + if let Updater::Updater(updater) = midway_updater.clone() { updater(map.clone(), IDManager::to_data()).await; } - unsettle_map(map); - let edges = order_edges(map)?; log_print( @@ -158,7 +161,14 @@ pub async fn recalculate_map( updater(map.clone(), IDManager::to_data()).await; } - attempt_edge_routing(settings, map, &mut occupied, edges)?; + attempt_edge_routing( + settings, + map, + &mut occupied, + edges, + midway_updater.clone(), + ) + .await?; if let Updater::Updater(updater) = midway_updater.clone() { updater(map.clone(), IDManager::to_data()).await; diff --git a/src/algorithm/route_edges.rs b/src/algorithm/route_edges.rs index 36f99d0..32c2807 100644 --- a/src/algorithm/route_edges.rs +++ b/src/algorithm/route_edges.rs @@ -6,6 +6,7 @@ use super::{ log_print, occupation::OccupiedNodes, AlgorithmSettings, + Updater, }; use crate::{ models::{ @@ -14,7 +15,10 @@ use crate::{ Map, Station, }, - utils::Result, + utils::{ + IDManager, + Result, + }, Error, }; @@ -112,11 +116,12 @@ fn split_overlap( /// Route all the edges on the map (as given by the input list of edges) and /// return them. This is the Route Edges algorithm in the paper. #[allow(clippy::too_many_lines)] // mostly due to large calls like debug prints -pub fn route_edges( +pub async fn route_edges( settings: AlgorithmSettings, map: &mut Map, mut edges: Vec, mut occupied: OccupiedNodes, + midway_updater: Updater, ) -> Result { for edge in &mut edges { if edge.is_locked() { @@ -251,6 +256,10 @@ pub fn route_edges( end_station.add_cost(*cost); } map.add_edge(edge.clone()); + + if let Updater::Updater(updater) = midway_updater.clone() { + updater(map.clone(), IDManager::to_data()).await; + } } Ok(occupied) } @@ -259,11 +268,13 @@ pub fn route_edges( mod tests { use std::collections::HashMap; + use futures_test::test; + use super::*; use crate::models::Station; #[test] - fn test_get_node_set() { + async fn test_get_node_set() { let mut map = Map::new(); let station = Station::new((0, 0).into(), None); map.add_station(station.clone()); @@ -308,7 +319,7 @@ mod tests { } #[test] - fn test_split_overlap() { + async fn test_split_overlap() { let from = Station::new((0, 0).into(), None); let from_set = vec![ (GridNode::from((0, 0)), 0.0), @@ -356,7 +367,7 @@ mod tests { } #[test] - fn test_route_edges() { + async fn test_route_edges() { let mut map = Map::new(); let edges = vec![]; @@ -365,7 +376,9 @@ mod tests { &mut map, edges, HashMap::new(), + Updater::NoUpdates, ) + .await .unwrap(); assert_eq!(result, HashMap::new()); diff --git a/src/algorithm/utils.rs b/src/algorithm/utils.rs index caf05dc..659c7b0 100644 --- a/src/algorithm/utils.rs +++ b/src/algorithm/utils.rs @@ -21,11 +21,16 @@ use crate::models::{ /// for moving in the algorithm. pub fn unsettle_map(map: &mut Map) { for station in map.get_mut_stations() { - station.unsettle(); - station.set_cost(0.0); + if !station.is_locked() { + station.unsettle(); + station.set_cost(0.0); + } } for edge in map.get_mut_edges() { - edge.unsettle(); + if !edge.is_locked() { + edge.unsettle(); + edge.set_nodes(Vec::new()); + } } } diff --git a/src/components/atoms/button.rs b/src/components/atoms/button.rs index a813fc2..4f4a4c8 100644 --- a/src/components/atoms/button.rs +++ b/src/components/atoms/button.rs @@ -86,9 +86,9 @@ pub fn Button( class += " rounded"; if smaller { - class += " text-xs"; + class += " text-xs font-semibold"; } else if bigger { - class += " text-xl"; + class += " text-xl font-semibold"; } else { class += " text-sm font-semibold"; } diff --git a/src/components/atoms/button_group.rs b/src/components/atoms/button_group.rs index 98f97f3..65d9250 100644 --- a/src/components/atoms/button_group.rs +++ b/src/components/atoms/button_group.rs @@ -10,7 +10,8 @@ pub fn ButtonGroup( /// These will be transformed into [`super::Button`] elements. children: Vec, ) -> impl IntoView { - let class = "max-w-full flex align-center gap-px [&>*]:flex-1 \ + let class = "max-w-full flex align-center gap-px \ + [&>*]:flex-1 [&>*]:max-w-[50%] \ [&>*:not(:first-child):not(:last-child)]:ml-0 \ [&>*:not(:first-child):not(:last-child)]:rounded-none \ [&>*:not(:only-child):first-child]:rounded-r-none \ diff --git a/src/components/canvas/mouse_up.rs b/src/components/canvas/mouse_up.rs index 232a0d7..b7ae959 100644 --- a/src/components/canvas/mouse_up.rs +++ b/src/components/canvas/mouse_up.rs @@ -44,7 +44,7 @@ pub fn on_mouse_up(map_state: &mut MapState, ev: &UiEvent, shift_key: bool) { // Handle a click while having an operation selected if let Some(action_type) = map_state.get_selected_action() { match action_type { - ActionType::RemoveStation => { + ActionType::RemoveStation | ActionType::RemoveCheckpoint => { if let Some(station_id) = station_at_node { map.remove_station(station_id); } diff --git a/src/components/molecules/station_info_box.rs b/src/components/molecules/station_info_box.rs index 2d6be13..5584c0b 100644 --- a/src/components/molecules/station_info_box.rs +++ b/src/components/molecules/station_info_box.rs @@ -25,8 +25,17 @@ pub fn StationInfoBox() -> impl IntoView { map_state .get() .get_clicked_on_station() + .filter(|s| !s.is_checkpoint()) .is_some() }; + let checkpoint_was_clicked = move || { + map_state + .get() + .get_clicked_on_station() + .filter(|s| s.is_checkpoint()) + .is_some() + }; + let position = Signal::derive(move || { map_state .get() @@ -85,6 +94,7 @@ pub fn StationInfoBox() -> impl IntoView { }; view! { + <> impl IntoView { + + + <> + + + } } diff --git a/src/components/organisms/sidebar.rs b/src/components/organisms/sidebar.rs index 15eac3c..dbbb427 100644 --- a/src/components/organisms/sidebar.rs +++ b/src/components/organisms/sidebar.rs @@ -58,6 +58,12 @@ pub fn Sidebar() -> impl IntoView { }); }; + let add_checkpoint = move |_| { + map_state.update(|state| { + state.select_station(SelectedStation::new_checkpoint()); + }); + }; + let add_line = move |_| { map_state.update(|state| { let line = SelectedLine::new_line(state.get_mut_map()); @@ -68,6 +74,9 @@ pub fn Sidebar() -> impl IntoView { let remove_station = move |_| update_action(ActionType::RemoveStation); let remove_station_selected = action_selected(ActionType::RemoveStation); + let remove_checkpoint = move |_| update_action(ActionType::RemoveCheckpoint); + let remove_checkpoint_selected = action_selected(ActionType::RemoveCheckpoint); + let remove_line = move |_| update_action(ActionType::RemoveLine); let remove_line_selected = action_selected(ActionType::RemoveLine); @@ -159,6 +168,20 @@ pub fn Sidebar() -> impl IntoView { .danger(true) .build(), ]}/> + } } diff --git a/src/components/pages/home.rs b/src/components/pages/home.rs index a5368f5..39a929c 100644 --- a/src/components/pages/home.rs +++ b/src/components/pages/home.rs @@ -16,7 +16,7 @@ pub fn Home() -> impl IntoView {
-
+
diff --git a/src/components/state/map.rs b/src/components/state/map.rs index 387a922..55a5ff4 100644 --- a/src/components/state/map.rs +++ b/src/components/state/map.rs @@ -30,6 +30,10 @@ pub enum ActionType { /// /// [`Station`]: crate::models::Station RemoveStation, + /// User wants to remove a checkpoint [`Station`]. + /// + /// [`Station`]: crate::models::Station + RemoveCheckpoint, /// User wants to remove a [`Line`]. /// /// [`Line`]: crate::models::Line diff --git a/src/models/line.rs b/src/models/line.rs index 61e3ab5..5ca52aa 100644 --- a/src/models/line.rs +++ b/src/models/line.rs @@ -154,11 +154,12 @@ impl Line { } /// Remove a station from the line. - pub fn remove_station(&mut self, map: &mut Map, station: StationID) { + pub fn remove_station(&mut self, map: &mut Map, station: &Station) { + let station_id = station.get_id(); if let Some(index) = self .stations .iter() - .position(|s| s == &station) + .position(|s| s == &station_id) { self.stations .remove(index); @@ -166,7 +167,7 @@ impl Line { return; } - let mut ends = Vec::new(); + let mut ends: Vec<(StationID, Vec)> = Vec::new(); let edges = self .edges .clone(); @@ -175,14 +176,22 @@ impl Line { .get_edge(edge_id) .expect("invalid edge id in line"); - if edge.get_to() == station { - ends.push(edge.get_from()); + if edge.get_to() == station_id { + ends.push(( + edge.get_from(), + edge.get_nodes() + .to_vec(), + )); self.edges .retain(|e| *e != edge_id); map.removed_edge(edge_id, self.get_id()); - } else if edge.get_from() == station { - ends.push(edge.get_to()); + } else if edge.get_from() == station_id { + ends.push(( + edge.get_to(), + edge.get_nodes() + .to_vec(), + )); self.edges .retain(|e| *e != edge_id); @@ -194,10 +203,62 @@ impl Line { .into_iter() .combinations(2) { - self.add_edge( - map.get_edge_id_between(combinations[0], combinations[1]), - map, - ); + let edge_id = map.get_edge_id_between(combinations[0].0, combinations[1].0); + { + let mut nodes = Vec::new(); + + if !combinations[0] + .1 + .is_empty() + { + if station + .get_pos() + .get_neighbors() + .contains(&combinations[0].1[0]) + { + let mut temp = combinations[0] + .1 + .clone(); + temp.reverse(); + nodes.append(&mut temp); + } else { + nodes.append( + &mut combinations[0] + .1 + .clone(), + ); + } + } + nodes.push(station.get_pos()); + if !combinations[1] + .1 + .is_empty() + { + if station + .get_pos() + .get_neighbors() + .contains(&combinations[1].1[0]) + { + nodes.append( + &mut combinations[1] + .1 + .clone(), + ); + } else { + let mut temp = combinations[1] + .1 + .clone(); + temp.reverse(); + nodes.append(&mut temp); + } + } + + map.get_mut_edge(edge_id) + .unwrap() + .set_nodes(nodes); + } + + self.add_edge(edge_id, map); } } @@ -615,7 +676,11 @@ mod tests { Some(station2), ); - line.remove_station(&mut map, station3); + let removed_station = map + .get_station(station3) + .cloned() + .expect("invalid station id"); + line.remove_station(&mut map, &removed_station); assert_eq!( line.get_stations(), diff --git a/src/models/map.rs b/src/models/map.rs index d40849a..72fc24f 100644 --- a/src/models/map.rs +++ b/src/models/map.rs @@ -157,13 +157,20 @@ impl Map { /// Remove a station from the map. pub fn remove_station(&mut self, id: StationID) { + let Some(station) = self + .get_station(id) + .cloned() + else { + return; + }; + let lines: Vec<_> = self .lines .values() .cloned() .collect(); for mut line in lines { - line.remove_station(self, id); + line.remove_station(self, &station); self.add_line(line); } diff --git a/src/models/selected_station.rs b/src/models/selected_station.rs index 52a9690..3f8bb01 100644 --- a/src/models/selected_station.rs +++ b/src/models/selected_station.rs @@ -31,8 +31,7 @@ pub struct SelectedStation { impl SelectedStation { /// Select a station. - pub fn new(mut station: Station) -> Self { - station.set_is_ghost(true); + pub fn new(station: Station) -> Self { Self { moved_from: Some(station.get_pos()), station, @@ -42,8 +41,17 @@ impl SelectedStation { /// Select a newly created station. pub fn new_station() -> Self { - let mut station = Station::new((i32::MIN, i32::MIN).into(), None); - station.set_is_ghost(true); + let station = Station::new((i32::MIN, i32::MIN).into(), None); + Self { + station, + before_after: (Vec::new(), Vec::new()), + moved_from: None, + } + } + + /// Select a newly created checkpoint. + pub fn new_checkpoint() -> Self { + let station = Station::new_checkpoint((i32::MIN, i32::MIN).into(), None); Self { station, before_after: (Vec::new(), Vec::new()), @@ -89,9 +97,7 @@ impl SelectedStation { } /// Deselects the station and returns it. - pub fn deselect(mut self) -> Station { - self.station - .set_is_ghost(false); + pub fn deselect(self) -> Station { self.station } @@ -125,9 +131,11 @@ impl SelectedStation { state: CanvasState, all_selected: &[Self], ) { - let canvas_pos = self + let mut station = self .station - .get_canvas_pos(state); + .clone(); + station.unlock(); + let canvas_pos = station.get_canvas_pos(state); let mut selected_width = state.drawn_square_size() / 3.5; if selected_width < 2.5 { @@ -154,33 +162,15 @@ impl SelectedStation { return; } - // draw self-ghost - let mut width = state.drawn_square_size() / 10.0 + 1.0; - if width < 2.0 { - width = 2.0; - } - - canvas.set_line_width(width); - canvas.set_global_alpha(0.5); - canvas.set_stroke_style_str("black"); - canvas.begin_path(); - canvas - .arc( - canvas_pos.0, - canvas_pos.1, - state.drawn_square_size() / 3.0, - 0.0, - 2.0 * std::f64::consts::PI, - ) - .unwrap(); - canvas.stroke(); + // draw station + station.draw(canvas, state, 0.5); + // draw edges to adjacent stations let mut edge_width = state.drawn_square_size() / 10.0 + 0.5; if edge_width < 1.0 { edge_width = 1.0; } - // draw edges to adjacent stations canvas.set_line_width(edge_width); canvas.set_stroke_style_str("black"); canvas.begin_path(); @@ -205,13 +195,8 @@ impl SelectedStation { .expect("invalid id"); draw_edge( before.get_pos(), - self.station - .get_pos(), - &run_a_star( - before.get_pos(), - self.station - .get_pos(), - ), + station.get_pos(), + &run_a_star(before.get_pos(), station.get_pos()), canvas, state, 0.0, @@ -236,14 +221,9 @@ impl SelectedStation { }; draw_edge( - self.station - .get_pos(), + station.get_pos(), after.get_pos(), - &run_a_star( - self.station - .get_pos(), - after.get_pos(), - ), + &run_a_star(station.get_pos(), after.get_pos()), canvas, state, 0.0, diff --git a/src/models/station.rs b/src/models/station.rs index 6c77d59..501ccd7 100644 --- a/src/models/station.rs +++ b/src/models/station.rs @@ -58,8 +58,6 @@ pub struct Station { original_pos: GridNode, /// ID of the station. id: StationID, - /// If when drawn the station should be greyed out (like when moving). - is_ghost: bool, /// The station name. name: String, /// The edges that are connected to this station. @@ -69,6 +67,8 @@ pub struct Station { is_locked: bool, /// Marks the location of the station as settled in the algorithm. is_settled: bool, + /// If the station is not actually real, but just a checkpoint. + is_checkpoint: bool, /// The total cost of all the edges attached to the station, used in the /// local search algorithm. cost: f64, @@ -86,11 +86,31 @@ impl Station { pos, original_pos: pos, id: id.unwrap_or_else(IDManager::next_station_id), - is_ghost: false, name: String::new(), edges: Vec::new(), is_locked: false, is_settled: false, + is_checkpoint: false, + cost: 0.0, + } + } + + /// Create a new checkpoint [`Station`] at the given grid coordinate. + /// If id is None, the next sequential id is used. + pub fn new_checkpoint(pos: GridNode, id: Option) -> Self { + if let Some(new_id) = id { + IDManager::update_station_id(new_id); + } + + Self { + pos, + original_pos: pos, + id: id.unwrap_or_else(IDManager::next_station_id), + name: String::new(), + edges: Vec::new(), + is_locked: false, + is_settled: false, + is_checkpoint: true, cost: 0.0, } } @@ -107,11 +127,6 @@ impl Station { self.pos } - /// A setter for if the stations should be greyed out. - pub fn set_is_ghost(&mut self, ghost: bool) { - self.is_ghost = ghost; - } - /// A setter for the grid position of the station. pub fn set_pos(&mut self, pos: GridNode) { self.pos = pos; @@ -179,6 +194,12 @@ impl Station { self.is_locked } + /// Check if the station is a checkpoint. + #[inline] + pub fn is_checkpoint(&self) -> bool { + self.is_checkpoint + } + /// Get the cost of the station. #[inline] pub fn get_cost(&self) -> f64 { @@ -267,22 +288,37 @@ impl Station { if width < 2.0 { width = 2.0; } + let radius = state.drawn_square_size() / 3.0; canvas.set_line_width(width); canvas.set_global_alpha(1.0 * base_alpha); - canvas.set_stroke_style_str("black"); - canvas.begin_path(); - canvas - .arc( - canvas_pos.0, - canvas_pos.1, - state.drawn_square_size() / 3.0, - 0.0, - 2.0 * f64::consts::PI, - ) - .unwrap(); - canvas.stroke(); + + if self.is_checkpoint() { + canvas.begin_path(); + canvas.move_to(canvas_pos.0, canvas_pos.1 - radius); + canvas.line_to(canvas_pos.0 + radius, canvas_pos.1); + canvas.line_to(canvas_pos.0, canvas_pos.1 + radius); + canvas.line_to(canvas_pos.0 - radius, canvas_pos.1); + canvas.line_to(canvas_pos.0, canvas_pos.1 - radius); + canvas.line_to( + canvas_pos.0 + width, + canvas_pos.1 - radius + width, + ); + canvas.stroke(); + } else { + canvas.begin_path(); + canvas + .arc( + canvas_pos.0, + canvas_pos.1, + radius, + 0.0, + 2.0 * f64::consts::PI, + ) + .unwrap(); + canvas.stroke(); + } if self.is_locked() { let locked_label_pos = calc_label_pos(state, canvas_pos, None, None)[0]; // FIXME: Check for occupancy diff --git a/src/utils/graphml/decode.rs b/src/utils/graphml/decode.rs index 16be807..ca37382 100644 --- a/src/utils/graphml/decode.rs +++ b/src/utils/graphml/decode.rs @@ -139,7 +139,7 @@ fn normalize_stations(mut items: Vec, state: CanvasState) -> Result>>()?; - coords = normalize_coords(coords, state); + coords = normalize_coords(coords, state).0; for (item, (x, y)) in items .iter_mut() diff --git a/src/utils/graphml/graphml_map.rs b/src/utils/graphml/graphml_map.rs index a93c9b8..0a73ba2 100644 --- a/src/utils/graphml/graphml_map.rs +++ b/src/utils/graphml/graphml_map.rs @@ -37,6 +37,7 @@ pub(super) struct Edge { pub(super) target: String, pub(super) data: Vec, } +// TODO: add an optional list of nodes to the edge /// This GraphML object can either be a [`Node`] (station) or [`Edge`] (edge /// connecting two stations). diff --git a/src/utils/json/decode.rs b/src/utils/json/decode.rs index f6b7b13..5da4faa 100644 --- a/src/utils/json/decode.rs +++ b/src/utils/json/decode.rs @@ -15,9 +15,11 @@ use crate::{ }, utils::{ parsing::{ + normalize_coordinate, normalize_coords, parse_color, parse_id, + NormalizationSettings, }, Error, Result, @@ -27,13 +29,16 @@ use crate::{ /// JSON data sometimes has maps/stations located in weird places (like all x /// coordinates being negative or only difference being in the decimals), this /// normalizes them so they fit within the canvas as it currently is. -fn normalize_stations(mut stations: Vec, state: CanvasState) -> Vec { +fn normalize_stations( + mut stations: Vec, + state: CanvasState, +) -> (Vec, NormalizationSettings) { let coords = stations .iter() .map(|s| (s.x, s.y)) .collect(); - let normalized_coords = normalize_coords(coords, state); + let (normalized_coords, normalization_settings) = normalize_coords(coords, state); for (station, (x, y)) in stations .iter_mut() @@ -43,14 +48,16 @@ fn normalize_stations(mut stations: Vec, state: CanvasState) -> Vec station.y = y; } - stations + (stations, normalization_settings) } /// Translates the [`JSONMap`] to a [`Map`] pub fn json_to_map(mut graph: JSONMap, state: CanvasState) -> Result { let mut map = Map::new(); - graph.stations = normalize_stations(graph.stations, state); + let temp = normalize_stations(graph.stations, state); + graph.stations = temp.0; + let normalization_settings = temp.1; // Add stations for json_station in graph @@ -114,6 +121,18 @@ pub fn json_to_map(mut graph: JSONMap, state: CanvasState) -> Result { parse_id(&json_edge.target).into(), ); + let nodes = json_edge + .nodes + .iter() + .map(|node| { + let node = normalize_coordinate(node.x, node.y, normalization_settings); + GridNode::from_canvas_pos((node.0, node.1), state) + }) + .collect(); + map.get_mut_edge(edge_id) + .unwrap() + .set_nodes(nodes); + // Add edge to lines for line_id in &json_edge.lines { let mut line = map @@ -135,6 +154,7 @@ pub fn json_to_map(mut graph: JSONMap, state: CanvasState) -> Result { mod tests { use super::*; use crate::utils::json::json_models::{ + EdgeNode, JSONEdge, JSONLine, }; @@ -170,7 +190,7 @@ mod tests { ); assert_eq!( - result, + result.0, vec![ JSONStation { id: "1".to_string(), @@ -232,11 +252,16 @@ mod tests { source: "0".to_string(), target: "1".to_string(), lines: vec!["0".to_string()], + nodes: vec![EdgeNode { + x: 0.0, + y: 0.0, + }], }, JSONEdge { source: "1".to_string(), target: "s3".to_string(), lines: vec!["0".to_string()], + nodes: vec![], }, ], }, diff --git a/src/utils/json/encode.rs b/src/utils/json/encode.rs index d3ae7f1..78f2d88 100644 --- a/src/utils/json/encode.rs +++ b/src/utils/json/encode.rs @@ -1,6 +1,7 @@ //! Contains the functions used to dencode a [`Map`] into a [`JSONMap`]. use super::json_models::{ + EdgeNode, JSONEdge, JSONLine, JSONMap, @@ -71,7 +72,7 @@ fn encode_line(line: &Line) -> JSONLine { } /// Encodes an [`Edge`] as a [`JSONEdge`]. -fn encode_edge(edge: &Edge) -> JSONEdge { +fn encode_edge(edge: &Edge, state: CanvasState) -> JSONEdge { let source = "s".to_owned() + &u64::from(edge.get_from()).to_string(); let target = "s".to_owned() + &u64::from(edge.get_to()).to_string(); @@ -88,9 +89,19 @@ fn encode_edge(edge: &Edge) -> JSONEdge { lines.sort(); } + let mut nodes = Vec::new(); + for node in edge.get_nodes() { + let pos = node.to_canvas_pos(state); + nodes.push(EdgeNode { + x: pos.0, + y: pos.1, + }); + } + JSONEdge { source, target, + nodes, lines, } } @@ -103,6 +114,18 @@ pub fn map_to_json(graph: &Map, state: CanvasState) -> JSONMap { edges: Vec::new(), }; + let mut graph = graph.clone(); + let all_stations = graph + .get_stations() + .into_iter() + .cloned() + .collect::>(); + for station in all_stations { + if station.is_checkpoint() { + graph.remove_station(station.get_id()); + } + } + // Add stations json_map.stations = graph .get_stations() @@ -141,7 +164,7 @@ pub fn map_to_json(graph: &Map, state: CanvasState) -> JSONMap { json_map.edges = graph .get_edges() .into_iter() - .map(encode_edge) + .map(|e| encode_edge(e, state)) .collect(); // Sort edges for deterministic output diff --git a/src/utils/json/json_models.rs b/src/utils/json/json_models.rs index f34dabd..f88f5b9 100644 --- a/src/utils/json/json_models.rs +++ b/src/utils/json/json_models.rs @@ -9,11 +9,20 @@ use serde::{ Serialize, }; +/// Represents a node in an edge for the JSON file. +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct EdgeNode { + pub x: f64, + pub y: f64, +} + /// Represents a connection between two stations for the JSON file. #[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct JSONEdge { pub source: String, pub target: String, + #[serde(default)] + pub nodes: Vec, pub lines: Vec, } diff --git a/src/utils/parsing.rs b/src/utils/parsing.rs index 5c8b8a3..e9a6e2a 100644 --- a/src/utils/parsing.rs +++ b/src/utils/parsing.rs @@ -9,10 +9,35 @@ use std::hash::{ use super::Result; use crate::components::CanvasState; +/// Settings used to normalize coordinates. +#[derive(Debug, Clone, Copy)] +pub(super) struct NormalizationSettings { + /// The minimum x coordinate. + pub(super) min_x: f64, + /// The maximum x coordinate. + pub(super) max_x: f64, + /// The minimum y coordinate. + pub(super) min_y: f64, + /// The maximum y coordinate. + pub(super) max_y: f64, + /// The width of the canvas in the x-coordinate. + pub(super) size_x: f64, + /// The height of the canvas in the y-coordinate. + pub(super) size_y: f64, + /// The offset of the canvas. + pub(super) offset: (f64, f64), + /// The size of a square on the canvas. + pub(super) square_size: f64, +} + /// Saved data sometimes has maps/stations located in weird places (like all x /// coordinates being negative or only difference being in the decimals), this -/// normalizes them so they fit within the canvas as it currently is. -pub(super) fn normalize_coords(mut items: Vec<(f64, f64)>, state: CanvasState) -> Vec<(f64, f64)> { +/// normalizes them so they fit within the canvas as it currently is. Returns +/// the generated [`NormalizationSettings`] and the normalized coordinates. +pub(super) fn normalize_coords( + mut items: Vec<(f64, f64)>, + state: CanvasState, +) -> (Vec<(f64, f64)>, NormalizationSettings) { let square_size = state.drawn_square_size(); let size_x = f64::from( @@ -51,24 +76,50 @@ pub(super) fn normalize_coords(mut items: Vec<(f64, f64)>, state: CanvasState) - } } - for (x, y) in &mut items { - *x = (*x - min_x) / (max_x - min_x) * size_x - + f64::from( + let settings = NormalizationSettings { + min_x, + max_x, + min_y, + max_y, + size_x, + size_y, + square_size, + offset: ( + f64::from( state .get_offset() .0, - ) / square_size - + 2.0 * square_size; - *y = (*y - min_y) / (max_y - min_y) * size_y - + f64::from( + ), + f64::from( state .get_offset() .1, - ) / square_size - + 2.0 * square_size; + ), + ), + }; + + for (x, y) in &mut items { + (*x, *y) = normalize_coordinate(*x, *y, settings); } - items + (items, settings) +} + +/// Normalize the given coordinate using the given settings. +pub(super) fn normalize_coordinate(x: f64, y: f64, settings: NormalizationSettings) -> (f64, f64) { + let x = (x - settings.min_x) / (settings.max_x - settings.min_x) * settings.size_x + + (settings + .offset + .0 + / settings.square_size) + + 2.0 * settings.square_size; + let y = (y - settings.min_y) / (settings.max_y - settings.min_y) * settings.size_y + + (settings + .offset + .1 + / settings.square_size) + + 2.0 * settings.square_size; + (x, y) } /// Parse the given string into an u64 to create an ID from. @@ -114,7 +165,7 @@ mod tests { ); assert_eq!( - result, + result.0, vec![(10.0, 10.0), (50.0, 50.0), (90.0, 90.0)] ); }