From 196c624b4c3329ac881a84be53a09e1eddd18321 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:41:24 -0400 Subject: [PATCH] refactor: bootstrapping --- client/src/components/drawer/Routing.tsx | 17 +- server/algorithms/src/bootstrap/mod.rs | 43 +++ server/algorithms/src/bootstrap/radius.rs | 179 ++++++++++++ server/algorithms/src/bootstrap/s2.rs | 258 +++++++++++++++++ server/algorithms/src/bootstrapping.rs | 167 ----------- server/algorithms/src/clustering/mod.rs | 50 ++-- server/algorithms/src/clustering/s2.rs | 37 +++ server/algorithms/src/lib.rs | 2 +- server/algorithms/src/routing/basic.rs | 2 +- server/algorithms/src/routing/mod.rs | 4 +- server/algorithms/src/s2.rs | 320 ++-------------------- server/algorithms/src/stats.rs | 121 ++++---- server/api/src/public/v1/calculate.rs | 83 +++--- server/api/src/public/v1/s2.rs | 8 +- server/model/src/api/args.rs | 2 +- server/model/src/api/geometry.rs | 17 ++ server/model/src/api/mod.rs | 4 + 17 files changed, 726 insertions(+), 588 deletions(-) create mode 100644 server/algorithms/src/bootstrap/mod.rs create mode 100644 server/algorithms/src/bootstrap/radius.rs create mode 100644 server/algorithms/src/bootstrap/s2.rs delete mode 100644 server/algorithms/src/bootstrapping.rs create mode 100644 server/algorithms/src/clustering/s2.rs diff --git a/client/src/components/drawer/Routing.tsx b/client/src/components/drawer/Routing.tsx index d3aac8c8..023643fd 100644 --- a/client/src/components/drawer/Routing.tsx +++ b/client/src/components/drawer/Routing.tsx @@ -104,18 +104,11 @@ export default function RoutingTab() { - - - Routing - - - - + + Routing + + + diff --git a/server/algorithms/src/bootstrap/mod.rs b/server/algorithms/src/bootstrap/mod.rs new file mode 100644 index 00000000..6f49511c --- /dev/null +++ b/server/algorithms/src/bootstrap/mod.rs @@ -0,0 +1,43 @@ +use geojson::{Feature, FeatureCollection}; +use model::api::{ + args::{CalculationMode, SortBy}, + Precision, +}; + +use crate::stats::Stats; + +pub mod radius; +pub mod s2; + +pub fn main( + area: FeatureCollection, + calculation_mode: CalculationMode, + radius: Precision, + sort_by: SortBy, + s2_level: u8, + s2_size: u8, + route_split_level: u64, + stats: &mut Stats, +) -> Vec { + let mut features = vec![]; + + for feature in area.features { + match calculation_mode { + CalculationMode::Radius => { + let mut new_radius = radius::BootstrapRadius::new(&feature, radius); + new_radius.sort(&sort_by, route_split_level); + + *stats += &new_radius.stats; + features.push(new_radius.feature()); + } + CalculationMode::S2 => { + let mut new_s2 = s2::BootstrapS2::new(&feature, s2_level, s2_size); + new_s2.sort(&sort_by, route_split_level); + + *stats += &new_s2.stats; + features.push(new_s2.feature()); + } + } + } + features +} diff --git a/server/algorithms/src/bootstrap/radius.rs b/server/algorithms/src/bootstrap/radius.rs new file mode 100644 index 00000000..d7e59b93 --- /dev/null +++ b/server/algorithms/src/bootstrap/radius.rs @@ -0,0 +1,179 @@ +use std::time::Instant; + +use crate::{routing, stats::Stats}; + +use geo::{Contains, Extremes, HaversineDestination, HaversineDistance, Point, Polygon}; +use geojson::{Feature, Geometry, Value}; +use model::{ + api::{args::SortBy, single_vec::SingleVec, Precision, ToFeature, ToGeometryVec}, + db::sea_orm_active_enums::Type, +}; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; + +#[derive(Debug)] +pub struct BootstrapRadius<'a> { + feature: &'a Feature, + result: SingleVec, + radius: Precision, + pub stats: Stats, +} + +impl<'a> BootstrapRadius<'a> { + pub fn new(feature: &'a Feature, radius: Precision) -> Self { + let mut new_bootstrap = Self { + feature, + result: vec![], + radius, + stats: Stats::new("BootstrapRadius".to_string(), 0), + }; + + let time = Instant::now(); + new_bootstrap.result = new_bootstrap.run(); + new_bootstrap.stats.set_cluster_time(time); + new_bootstrap + .stats + .cluster_stats(radius, &vec![], &new_bootstrap.result); + + new_bootstrap + } + + pub fn sort(&mut self, sort_by: &SortBy, route_split_level: u64) { + let time = Instant::now(); + self.result = routing::main( + &vec![], + self.result.clone(), + sort_by, + route_split_level, + self.radius, + &mut self.stats, + ); + self.stats.set_route_time(time); + } + + pub fn result(self) -> SingleVec { + self.result + } + + pub fn feature(self) -> Feature { + let mut new_feature = self.result.to_feature(Some(Type::CirclePokemon)); + + if let Some(name) = self.feature.property("__name") { + new_feature.set_property("__name", name.clone()); + } + if let Some(geofence_id) = self.feature.property("__id") { + new_feature.set_property("__geofence_id", geofence_id.clone()); + } + new_feature.set_property("__mode", "CirclePokemon"); + new_feature + } + + fn run(&self) -> SingleVec { + self.flatten_circles() + .into_iter() + .map(|p| [p.y(), p.x()]) + .collect() + } + + fn flatten_circles(&self) -> Vec { + if let Some(geometry) = self.feature.geometry.clone() { + match geometry.value { + Value::MultiPolygon(_) => geometry + .to_geometry_vec() + .par_iter() + .flat_map(|geo| self.generate_circles(geo)) + .collect(), + _ => self.generate_circles(&geometry), + } + } else { + vec![] + } + } + + fn generate_circles(&self, geometry: &Geometry) -> Vec { + let mut circles: Vec = vec![]; + + let polygon = Polygon::::try_from(geometry).unwrap(); + let external_points = polygon.exterior().points().collect::>(); + let internal_points: Vec<_> = polygon + .interiors() + .into_iter() + .map(|interior| interior.points().collect::>()) + .collect(); + + let x_mod = 0.75_f64.sqrt(); + let y_mod = 0.568_f64.sqrt(); + + let extremes = polygon.extremes().unwrap(); + let max = Point::new(extremes.x_max.coord.x, extremes.y_max.coord.y); + let min = Point::new(extremes.x_min.coord.x, extremes.y_min.coord.y); + + let start = max.haversine_destination(90., self.radius * 1.5); + let end = min + .haversine_destination(270., self.radius * 1.5) + .haversine_destination(180., self.radius); + + let mut row = 0; + let mut bearing = 270.; + let mut current = max; + + while current.y() > end.y() { + while (bearing == 270. && current.x() > end.x()) + || (bearing == 90. && current.x() < start.x()) + { + if polygon.contains(¤t) + || point_line_distance(&external_points, ¤t) <= self.radius + || internal_points + .par_iter() + .any(|internal| point_line_distance(&internal, ¤t) <= self.radius) + { + circles.push(current); + } + current = current.haversine_destination(bearing, x_mod * self.radius * 2.) + } + current = current.haversine_destination(180., y_mod * self.radius * 2.); + + if row % 2 == 1 { + bearing = 270.; + } else { + bearing = 90.; + } + current = current.haversine_destination(bearing, x_mod * self.radius * 3.); + + row += 1; + } + circles + } +} + +fn dot(u: &Point, v: &Point) -> Precision { + u.x() * v.x() + u.y() * v.y() +} + +fn distance_to_segment(p: &Point, a: &Point, b: &Point) -> Precision { + let v = Point::new(b.x() - a.x(), b.y() - a.y()); + let w = Point::new(p.x() - a.x(), p.y() - a.y()); + let c1 = dot(&w, &v); + if c1 <= 0.0 { + return p.haversine_distance(&a); + } + let c2 = dot(&v, &v); + if c2 <= c1 { + return p.haversine_distance(&b); + } + let b2 = c1 / c2; + let pb = Point::new(a.x() + b2 * v.x(), a.y() + b2 * v.y()); + p.haversine_distance(&pb) +} + +fn point_line_distance(input: &Vec, point: &Point) -> Precision { + let mut distance = Precision::MAX; + for (i, line) in input.iter().enumerate() { + let next = if i == input.len() - 1 { + input[0] + } else { + input[i + 1] + }; + distance = distance.min(distance_to_segment(point, line, &next)); + } + distance +} diff --git a/server/algorithms/src/bootstrap/s2.rs b/server/algorithms/src/bootstrap/s2.rs new file mode 100644 index 00000000..a04dccce --- /dev/null +++ b/server/algorithms/src/bootstrap/s2.rs @@ -0,0 +1,258 @@ +use std::time::Instant; + +use crate::{ + routing, + s2::{get_region_cells, ToPointArray}, + stats::Stats, +}; + +use geo::{Intersects, MultiPolygon, Polygon, RemoveRepeatedPoints}; +use geojson::{Feature, Value}; +use hashbrown::HashSet; +use model::{ + api::{args::SortBy, point_array::PointArray, single_vec::SingleVec, Precision, ToFeature}, + db::sea_orm_active_enums::Type, +}; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use s2::{cell::Cell, cellid::CellID, cellunion::CellUnion}; + +#[derive(Debug)] +pub struct BootstrapS2<'a> { + feature: &'a Feature, + result: SingleVec, + level: u8, + size: u8, + pub stats: Stats, +} + +impl<'a> BootstrapS2<'a> { + pub fn new(feature: &'a Feature, level: u8, size: u8) -> Self { + let mut new_bootstrap = Self { + feature, + result: vec![], + level, + size, + stats: Stats::new("BootstrapS2".to_string(), 0), + }; + + let time = Instant::now(); + new_bootstrap.result = new_bootstrap.run(); + new_bootstrap.stats.set_cluster_time(time); + new_bootstrap + .stats + .cluster_stats(0., &vec![], &new_bootstrap.result); + + new_bootstrap + } + + pub fn sort(&mut self, sort_by: &SortBy, route_split_level: u64) { + let time = Instant::now(); + self.result = routing::main( + &vec![], + self.result.clone(), + sort_by, + route_split_level, + 0., + &mut self.stats, + ); + self.stats.set_route_time(time); + } + + pub fn result(self) -> SingleVec { + self.result + } + + pub fn feature(self) -> Feature { + let mut new_feature = self.result.to_feature(Some(Type::CirclePokemon)); + + if let Some(name) = self.feature.property("__name") { + new_feature.set_property("__name", name.clone()); + } + if let Some(geofence_id) = self.feature.property("__id") { + new_feature.set_property("__geofence_id", geofence_id.clone()); + } + new_feature.set_property("__mode", "CirclePokemon"); + new_feature + } + + fn run(&self) -> SingleVec { + let bbox = self.feature.bbox.as_ref().unwrap(); + let mut polygons: Vec = vec![]; + if let Some(geometry) = self.feature.geometry.as_ref() { + match geometry.value { + Value::Polygon(_) => match Polygon::::try_from(geometry) { + Ok(poly) => polygons.push(poly), + Err(_) => (), + }, + Value::MultiPolygon(_) => match MultiPolygon::::try_from(geometry) { + Ok(multi_poly) => multi_poly + .0 + .into_iter() + .for_each(|poly| polygons.push(poly)), + Err(_) => (), + }, + _ => (), + } + } + log::warn!("BBOX {:?}", bbox); + let cells = get_region_cells(bbox[1], bbox[3], bbox[0], bbox[2], self.level); + + log::info!("Cells {}", cells.0.len()); + let mut visited = HashSet::::new(); + + let mut multi_point = vec![]; + + let mut current = CellID::from(s2::latlng::LatLng::from_degrees( + (bbox[1] + bbox[3]) / 2., + (bbox[0] + bbox[2]) / 2., + )) + .parent(self.level as u64); + let mut direction = 0; + let mut direction_count = 2; + let mut current_count = 1; + let mut turn = false; + let mut first = true; + let mut second = false; + let mut repeat_check = 0; + let mut last_report = 0; + + if self.size == 1 { + multi_point = cells.0.into_iter().map(|cell| cell.point_array()).collect(); + } else { + while visited.len() < cells.0.len() { + let (valid, point) = self.crawl_cells(¤t, &mut visited, &cells, &polygons); + if valid { + multi_point.push(point); + } + for _ in 0..self.size { + current = current.edge_neighbors()[direction]; + } + if first { + first = false; + second = true; + direction += 1; + } else if second { + second = false; + direction += 1; + } else if direction_count == current_count { + if direction == 3 { + direction = 0 + } else { + direction += 1 + } + if turn { + turn = false; + direction_count += 1; + current_count = 1; + } else { + turn = true; + current_count = 1; + } + } else { + current_count += 1; + } + if last_report == visited.len() { + repeat_check += 1; + if repeat_check > 10_000 { + log::error!("Only {} cells out of {} were able to be checked, breaking after {} repeated iterations", last_report, cells.0.len(), repeat_check); + break; + } + } else { + last_report = visited.len(); + repeat_check = 0; + } + } + } + + let mut multi_point: geo::MultiPoint = multi_point + .into_iter() + .map(|p| geo::Coord { x: p[1], y: p[0] }) + .collect(); + multi_point.remove_repeated_points_mut(); + + multi_point.into_iter().map(|p| [p.y(), p.x()]).collect() + } + + fn crawl_cells( + &self, + cell_id: &CellID, + visited: &mut HashSet, + cell_union: &CellUnion, + polygons: &Vec, + ) -> (bool, PointArray) { + let mut new_cell_id = cell_id.clone(); + let mut center = [0., 0.]; + let mut count = 0; + let mut line_string = vec![]; + + for v in 0..self.size { + new_cell_id = new_cell_id.edge_neighbors()[1]; + let mut h_cell_id = new_cell_id.clone(); + for h in 0..self.size { + if cell_union.contains_cellid(&h_cell_id) { + visited.insert(h_cell_id.0); + count += 1; + } + if self.size % 2 == 0 { + if v == ((self.size / 2) - 1) && h == ((self.size / 2) - 1) { + center = h_cell_id.point_array(); + } + if v == (self.size / 2) && h == (self.size / 2) { + let second_center = h_cell_id.point_array(); + center = [ + (center[0] + second_center[0]) / 2., + (center[1] + second_center[1]) / 2., + ]; + } + } else if v == ((self.size - 1) / 2) && h == ((self.size - 1) / 2) { + center = h_cell_id.point_array(); + } + + if v == 0 && h == 0 { + let vertex = Cell::from(&h_cell_id).vertex(3); + line_string.push(geo::Coord { + x: vertex.longitude().deg(), + y: vertex.latitude().deg(), + }); + } else if v == 0 && h == (self.size - 1) { + let vertex = Cell::from(&h_cell_id).vertex(0); + line_string.push(geo::Coord { + x: vertex.longitude().deg(), + y: vertex.latitude().deg(), + }); + } else if v == (self.size - 1) && h == (self.size - 1) { + let vertex = Cell::from(&h_cell_id).vertex(1); + line_string.push(geo::Coord { + x: vertex.longitude().deg(), + y: vertex.latitude().deg(), + }); + } else if v == (self.size - 1) && h == 0 { + let vertex = Cell::from(&h_cell_id).vertex(2); + line_string.push(geo::Coord { + x: vertex.longitude().deg(), + y: vertex.latitude().deg(), + }); + } + h_cell_id = h_cell_id.edge_neighbors()[0]; + } + } + if line_string.len() == 4 { + line_string.swap(2, 3); + } + let local_poly = geo::Polygon::::new(geo::LineString::new(line_string.into()), vec![]); + let valid = if count > 0 { + if polygons + .par_iter() + .find_any(|polygon| polygon.intersects(&local_poly)) + .is_some() + { + true + } else { + false + } + } else { + false + }; + (valid, center) + } +} diff --git a/server/algorithms/src/bootstrapping.rs b/server/algorithms/src/bootstrapping.rs deleted file mode 100644 index 544216d9..00000000 --- a/server/algorithms/src/bootstrapping.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::stats::Stats; - -use super::*; - -use geo::{Contains, Extremes, HaversineDestination, HaversineDistance, Point, Polygon}; -use geojson::{Feature, Geometry, Value}; -use model::api::{single_vec::SingleVec, GetBbox, ToFeatureVec, ToSingleVec}; - -fn dot(u: &Point, v: &Point) -> f64 { - u.x() * v.x() + u.y() * v.y() -} - -fn distance_to_segment(p: &Point, a: &Point, b: &Point) -> f64 { - let v = Point::new(b.x() - a.x(), b.y() - a.y()); - let w = Point::new(p.x() - a.x(), p.y() - a.y()); - let c1 = dot(&w, &v); - if c1 <= 0.0 { - return p.haversine_distance(&a); - } - let c2 = dot(&v, &v); - if c2 <= c1 { - return p.haversine_distance(&b); - } - let b2 = c1 / c2; - let pb = Point::new(a.x() + b2 * v.x(), a.y() + b2 * v.y()); - p.haversine_distance(&pb) -} - -pub fn point_line_distance(input: &Vec, point: &Point) -> f64 { - let mut distance: f64 = std::f64::MAX; - for (i, line) in input.iter().enumerate() { - let next = if i == input.len() - 1 { - input[0] - } else { - input[i + 1] - }; - distance = distance.min(distance_to_segment(point, line, &next)); - } - distance -} - -fn flatten_circles(feature: Feature, radius: f64, stats: &mut Stats) -> Vec { - if feature.geometry.is_none() { - return vec![]; - } - let geometry = feature.geometry.unwrap(); - let circles = match geometry.value { - Value::MultiPolygon(_) => geometry - .to_feature_vec() - .into_iter() - .flat_map(|feat| { - if let Some(geo) = feat.geometry { - generate_circles(geo, radius) - } else { - vec![] - } - }) - .collect(), - _ => generate_circles(geometry, radius), - }; - stats.total_clusters += circles.len(); - circles -} - -pub fn as_vec(feature: Feature, radius: f64, stats: &mut Stats) -> SingleVec { - flatten_circles(feature, radius, stats) - .iter() - .map(|p| [p.y(), p.x()]) - .collect() -} - -pub fn as_geojson(feature: Feature, radius: f64, stats: &mut Stats) -> Feature { - // let mut multiline_feature: Vec>> = vec![]; - let mut multipoint_feature: Vec> = vec![]; - let circles = flatten_circles(feature.clone(), radius, stats); - - for (i, point) in circles.iter().enumerate() { - multipoint_feature.push(vec![point.x(), point.y()]); - let point2 = if i == circles.len() { - circles[i + 1] - } else { - circles[0] - }; - let distance = point.haversine_distance(&point2); - if distance > stats.longest_distance { - stats.longest_distance = distance; - } - stats.total_distance += distance; - } - let geo_collection = Geometry { - value: Value::MultiPoint(multipoint_feature), - bbox: None, - foreign_members: None, - }; - let mut new_feature = Feature { - bbox: None, - geometry: Some(geo_collection), - ..Feature::default() - }; - if let Some(name) = feature.property("__name") { - if let Some(name) = name.as_str() { - new_feature.set_property("__name", name); - } - } - if let Some(geofence_id) = feature.property("__id") { - if let Some(geofence_id) = geofence_id.as_str() { - new_feature.set_property("__geofence_id", geofence_id); - } - } - new_feature.set_property("__mode", "CirclePokemon"); - new_feature.bbox = feature.to_single_vec().get_bbox(); - new_feature -} - -fn generate_circles(geometry: Geometry, radius: f64) -> Vec { - let mut circles: Vec = vec![]; - - let polygon = Polygon::::try_from(geometry).unwrap(); - let external_points = polygon.exterior().points().collect::>(); - let internal_points = polygon - .interiors() - .iter() - .map(|interior| interior.points().collect::>()); - - let x_mod = 0.75_f64.sqrt(); - let y_mod = 0.568_f64.sqrt(); - - let extremes = polygon.extremes().unwrap(); - let max = Point::new(extremes.x_max.coord.x, extremes.y_max.coord.y); - let min = Point::new(extremes.x_min.coord.x, extremes.y_min.coord.y); - - let start = max.haversine_destination(90., radius * 1.5); - let end = min - .haversine_destination(270., radius * 1.5) - .haversine_destination(180., radius); - - let mut row = 0; - let mut bearing = 270.; - let mut current = max; - - while current.y() > end.y() { - while (bearing == 270. && current.x() > end.x()) - || (bearing == 90. && current.x() < start.x()) - { - if polygon.contains(¤t) - || point_line_distance(&external_points, ¤t) <= radius - || internal_points - .clone() - .any(|internal| point_line_distance(&internal, ¤t) <= radius) - { - circles.push(current); - } - current = current.haversine_destination(bearing, x_mod * radius * 2.) - } - current = current.haversine_destination(180., y_mod * radius * 2.); - - if row % 2 == 1 { - bearing = 270.; - } else { - bearing = 90.; - } - current = current.haversine_destination(bearing, x_mod * radius * 3.); - - row += 1; - } - circles -} diff --git a/server/algorithms/src/clustering/mod.rs b/server/algorithms/src/clustering/mod.rs index 6d957442..050847c5 100644 --- a/server/algorithms/src/clustering/mod.rs +++ b/server/algorithms/src/clustering/mod.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use std::{time::Instant, vec}; use crate::stats::Stats; @@ -6,10 +6,12 @@ use self::greedy::Greedy; use super::*; -use model::api::{cluster_mode::ClusterMode, single_vec::SingleVec}; +use geojson::FeatureCollection; +use model::api::{args::CalculationMode, cluster_mode::ClusterMode, single_vec::SingleVec}; mod fastest; mod greedy; +mod s2; pub fn main( data_points: &SingleVec, @@ -19,32 +21,42 @@ pub fn main( stats: &mut Stats, cluster_split_level: u64, max_clusters: usize, + calculation_mode: CalculationMode, + s2_level: u8, + s2_size: u8, + collection: FeatureCollection, ) -> SingleVec { if data_points.is_empty() { return vec![]; } let time = Instant::now(); - let clusters = match cluster_mode { - ClusterMode::Fastest => { - let clusters = fastest::main(&data_points, radius, min_points); - clusters - } - _ => { - let mut greedy = Greedy::default(); - greedy - .set_cluster_mode(cluster_mode) - .set_cluster_split_level(cluster_split_level) - .set_max_clusters(max_clusters) - .set_min_points(min_points) - .set_radius(radius); - - greedy.run(&data_points) - } + let clusters = match calculation_mode { + CalculationMode::S2 => collection + .into_iter() + .flat_map(|feature| s2::cluster(feature, data_points, s2_level, s2_size)) + .collect(), + _ => match cluster_mode { + ClusterMode::Fastest => { + let clusters = fastest::main(&data_points, radius, min_points); + clusters + } + _ => { + let mut greedy = Greedy::default(); + greedy + .set_cluster_mode(cluster_mode) + .set_cluster_split_level(cluster_split_level) + .set_max_clusters(max_clusters) + .set_min_points(min_points) + .set_radius(radius); + + greedy.run(&data_points) + } + }, }; stats.set_cluster_time(time); stats.cluster_stats(radius, &data_points, &clusters); - stats.set_score(min_points); + stats.set_score(); clusters } diff --git a/server/algorithms/src/clustering/s2.rs b/server/algorithms/src/clustering/s2.rs new file mode 100644 index 00000000..0a93069e --- /dev/null +++ b/server/algorithms/src/clustering/s2.rs @@ -0,0 +1,37 @@ +use geojson::Feature; +use hashbrown::HashSet; +use model::api::single_vec::SingleVec; +use s2::cellid::CellID; + +use crate::bootstrap; +use crate::s2::cell_coverage; + +pub fn cluster(feature: Feature, data: &SingleVec, level: u8, size: u8) -> SingleVec { + let bootstrap_cells = bootstrap::s2::BootstrapS2::new(&feature, level, size); + let all_cells = bootstrap_cells.result(); + + let valid_cells = data + .iter() + .map(|f| { + CellID::from(s2::latlng::LatLng::from_degrees(f[0], f[1])) + .parent(level as u64) + .0 + }) + .collect::>(); + + all_cells + .into_iter() + .filter_map(|point| { + if cell_coverage(point[0], point[1], size, level) + .lock() + .unwrap() + .iter() + .any(|c| valid_cells.contains(c)) + { + Some(point) + } else { + None + } + }) + .collect() +} diff --git a/server/algorithms/src/lib.rs b/server/algorithms/src/lib.rs index f7f6913b..9c3a5737 100644 --- a/server/algorithms/src/lib.rs +++ b/server/algorithms/src/lib.rs @@ -1,6 +1,6 @@ use model; -pub mod bootstrapping; +pub mod bootstrap; pub mod clustering; mod project; pub mod routing; diff --git a/server/algorithms/src/routing/basic.rs b/server/algorithms/src/routing/basic.rs index 6ae795dd..aac7a15b 100644 --- a/server/algorithms/src/routing/basic.rs +++ b/server/algorithms/src/routing/basic.rs @@ -81,7 +81,7 @@ fn s2cell(clusters: SingleVec) -> SingleVec { .collect() } -pub fn sort(points: &SingleVec, clusters: SingleVec, radius: f64, sort_by: SortBy) -> SingleVec { +pub fn sort(points: &SingleVec, clusters: SingleVec, radius: f64, sort_by: &SortBy) -> SingleVec { match sort_by { SortBy::Random => random(clusters), SortBy::GeoHash => geohash(clusters), diff --git a/server/algorithms/src/routing/mod.rs b/server/algorithms/src/routing/mod.rs index a7c5ae6c..a17e3914 100644 --- a/server/algorithms/src/routing/mod.rs +++ b/server/algorithms/src/routing/mod.rs @@ -11,13 +11,13 @@ pub mod tsp; pub fn main( data_points: &SingleVec, clusters: SingleVec, - sort_by: SortBy, + sort_by: &SortBy, route_split_level: u64, radius: f64, stats: &mut Stats, ) -> SingleVec { let route_time = Instant::now(); - let clusters = if sort_by == SortBy::TSP && !clusters.is_empty() { + let clusters = if sort_by == &SortBy::TSP && !clusters.is_empty() { let tour = tsp::multi(&clusters, route_split_level); let mut final_clusters = VecDeque::::new(); diff --git a/server/algorithms/src/s2.rs b/server/algorithms/src/s2.rs index c894eee2..5323f287 100644 --- a/server/algorithms/src/s2.rs +++ b/server/algorithms/src/s2.rs @@ -3,18 +3,16 @@ use std::{ sync::{Arc, Mutex}, }; -use geo::{HaversineDestination, Intersects, MultiPolygon, Polygon, RemoveRepeatedPoints}; -use geojson::{Feature, Geometry, Value}; +use geo::{HaversineDestination, Intersects}; use model::api::{point_array::PointArray, single_vec::SingleVec}; +use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use s2::{ cell::Cell, cellid::CellID, cellunion::CellUnion, latlng::LatLng, rect::Rect, region::RegionCoverer, }; use serde::Serialize; -use crate::stats::Stats; - -type Covered = Arc>>; +type Covered = Arc>>; #[derive(Debug, Clone, Serialize)] pub struct S2Response { @@ -32,6 +30,17 @@ trait ToGeoJson { fn point(&self) -> Vec; } +pub trait ToPointArray { + fn point_array(&self) -> PointArray; +} + +impl ToPointArray for CellID { + fn point_array(&self) -> PointArray { + let center = Cell::from(self).center(); + [center.latitude().deg(), center.longitude().deg()] + } +} + impl ToGeo for CellID { fn polygon(&self) -> geo::Polygon { let cell = Cell::from(self); @@ -83,13 +92,13 @@ pub fn get_region_cells( cell_size: u8, ) -> CellUnion { let region = Rect::from_degrees(min_lat, min_lon, max_lat, max_lon); - let coverer = RegionCoverer { + RegionCoverer { max_level: cell_size, min_level: cell_size, level_mod: 1, max_cells: 1000, - }; - coverer.covering(®ion) + } + .covering(®ion) } pub fn get_cells( @@ -142,7 +151,7 @@ fn get_polygon(id: &CellID) -> S2Response { pub fn get_polygons(cell_ids: Vec) -> Vec { cell_ids - .into_iter() + .into_par_iter() .filter_map(|id| match id.parse::() { Ok(id) => Some(get_polygon(&CellID(id))), Err(e) => { @@ -174,7 +183,7 @@ fn check_neighbors(lat: f64, lon: f64, level: u8, circle: &geo::Polygon, covered let center_cell = CellID::from(center).parent(level as u64); match covered.lock() { Ok(mut c) => { - c.insert(center_cell.0.to_string()); + c.insert(center_cell.0); } Err(e) => { log::error!("[S2] Error locking `covered` to insert: {}", e) @@ -184,7 +193,7 @@ fn check_neighbors(lat: f64, lon: f64, level: u8, circle: &geo::Polygon, covered let current_neighbors = center_cell.edge_neighbors(); current_neighbors.iter().for_each(|neighbor| { - let id = neighbor.0.to_string(); + let id = neighbor.0; match covered.lock() { Ok(c) => { if c.contains(&id) { @@ -235,230 +244,11 @@ fn check_neighbors(lat: f64, lon: f64, level: u8, circle: &geo::Polygon, covered } } -fn crawl_cells( - cell_id: &CellID, - visited: &mut HashSet, - cell_union: &CellUnion, - polygons: &Vec, - size: u8, - // features: &mut Vec, -) -> (bool, Vec) { - let mut new_cell_id = cell_id.clone(); - let mut center = vec![]; - let mut count = 0; - let mut line_string = vec![]; - - for v in 0..size { - new_cell_id = new_cell_id.edge_neighbors()[1]; - let mut h_cell_id = new_cell_id.clone(); - for h in 0..size { - if cell_union.contains_cellid(&h_cell_id) { - visited.insert(h_cell_id.0); - count += 1; - } - if size % 2 == 0 { - if v == ((size / 2) - 1) && h == ((size / 2) - 1) { - center = h_cell_id.point(); - } - if v == (size / 2) && h == (size / 2) { - let second_center = h_cell_id.point(); - center = vec![ - (center[0] + second_center[0]) / 2., - (center[1] + second_center[1]) / 2., - ]; - } - } else if v == ((size - 1) / 2) && h == ((size - 1) / 2) { - center = h_cell_id.point(); - } - - if v == 0 && h == 0 { - let vertex = Cell::from(&h_cell_id).vertex(3); - line_string.push(geo::Coord { - x: vertex.longitude().deg(), - y: vertex.latitude().deg(), - }); - } else if v == 0 && h == (size - 1) { - let vertex = Cell::from(&h_cell_id).vertex(0); - line_string.push(geo::Coord { - x: vertex.longitude().deg(), - y: vertex.latitude().deg(), - }); - } else if v == (size - 1) && h == (size - 1) { - let vertex = Cell::from(&h_cell_id).vertex(1); - line_string.push(geo::Coord { - x: vertex.longitude().deg(), - y: vertex.latitude().deg(), - }); - } else if v == (size - 1) && h == 0 { - let vertex = Cell::from(&h_cell_id).vertex(2); - line_string.push(geo::Coord { - x: vertex.longitude().deg(), - y: vertex.latitude().deg(), - }); - } - h_cell_id = h_cell_id.edge_neighbors()[0]; - } - } - if line_string.len() == 4 { - line_string.swap(2, 3); - } - let local_poly = geo::Polygon::::new(geo::LineString::new(line_string.into()), vec![]); - // features.push(Feature { - // bbox: None, - // geometry: Some(Geometry::from(&local_poly)), - // id: None, - // properties: None, - // foreign_members: None, - // }); - let valid = if count > 0 { - if polygons - .iter() - .find(|polygon| polygon.intersects(&local_poly)) - .is_some() - { - true - } else { - false - } - } else { - false - }; - (valid, center) -} - -pub fn bootstrap(feature: &Feature, level: u8, size: u8, stats: &mut Stats) -> Feature { - let bbox = feature.bbox.as_ref().unwrap(); - let mut polygons: Vec = vec![]; - if let Some(geometry) = feature.geometry.as_ref() { - match geometry.value { - Value::Polygon(_) => match Polygon::::try_from(geometry) { - Ok(poly) => polygons.push(poly), - Err(_) => {} - }, - Value::MultiPolygon(_) => match MultiPolygon::::try_from(geometry) { - Ok(multi_poly) => multi_poly - .0 - .into_iter() - .for_each(|poly| polygons.push(poly)), - Err(_) => {} - }, - _ => {} - } - } - let region = Rect::from_degrees(bbox[1], bbox[0], bbox[3], bbox[2]); - let cells = RegionCoverer { - max_level: level, - min_level: level, - level_mod: 10, - max_cells: 5, - } - .covering(®ion); - - let mut visited = HashSet::::new(); - - let mut multi_point = vec![]; - - let center = [(bbox[0] + bbox[2]) / 2.0, (bbox[1] + bbox[3]) / 2.0]; - let mut current = - CellID::from(s2::latlng::LatLng::from_degrees(center[1], center[0])).parent(level as u64); - let mut direction = 0; - let mut direction_count = 2; - let mut current_count = 1; - let mut turn = false; - let mut first = true; - let mut second = false; - let mut repeat_check = 0; - let mut last_report = 0; - // let mut features = vec![]; - - if size == 1 { - multi_point = cells.0.into_iter().map(|cell| cell.point()).collect(); - } else { - while visited.len() < cells.0.len() { - let (valid, point) = crawl_cells( - ¤t, - &mut visited, - &cells, - &polygons, - size, - // &mut features, - ); - if valid { - multi_point.push(point); - } - for _ in 0..size { - current = current.edge_neighbors()[direction]; - } - if first { - first = false; - second = true; - direction += 1; - } else if second { - second = false; - direction += 1; - } else if direction_count == current_count { - if direction == 3 { - direction = 0 - } else { - direction += 1 - } - if turn { - turn = false; - direction_count += 1; - current_count = 1; - } else { - turn = true; - current_count = 1; - } - } else { - current_count += 1; - } - if last_report == visited.len() { - repeat_check += 1; - if repeat_check > 10000 { - log::error!("Only {} cells out of {} were able to be checked, breaking after {} repeated iterations", last_report, cells.0.len(), repeat_check); - break; - } - } else { - last_report = visited.len(); - repeat_check = 0; - } - } - } - - // match debug_string( - // "geojson.json", - // &serde_json::to_string_pretty(&FeatureCollection { - // features, - // bbox: None, - // foreign_members: None, - // }) - // .unwrap(), - // ) { - // Ok(_) => {} - // Err(e) => log::error!("Error writing geojson: {}", e), - // } - - stats.total_clusters += multi_point.len(); - stats.distance_stats(&multi_point.iter().map(|p| [p[0], p[1]]).collect()); - - let mut multi_point: geo::MultiPoint = multi_point - .iter() - .map(|p| geo::Coord { x: p[0], y: p[1] }) - .collect(); - multi_point.remove_repeated_points_mut(); - - Feature { - geometry: Some(Geometry::from(&multi_point)), - ..Default::default() - } -} - pub fn cell_coverage(lat: f64, lon: f64, size: u8, level: u8) -> Covered { let covered = Arc::new(Mutex::new(HashSet::new())); let mut center = CellID::from(s2::latlng::LatLng::from_degrees(lat, lon)).parent(level as u64); if size == 1 { - covered.lock().unwrap().insert(center.0.to_string()); + covered.lock().unwrap().insert(center.0); } else { for i in 0..((size / 2) + 1) { if i != 0 { @@ -472,7 +262,7 @@ pub fn cell_coverage(lat: f64, lon: f64, size: u8, level: u8) -> Covered { center = center.edge_neighbors()[1]; let mut h_cell_id = center.clone(); for _ in 0..size { - covered.lock().unwrap().insert(h_cell_id.0.to_string()); + covered.lock().unwrap().insert(h_cell_id.0); h_cell_id = h_cell_id.edge_neighbors()[0]; } } @@ -480,72 +270,6 @@ pub fn cell_coverage(lat: f64, lon: f64, size: u8, level: u8) -> Covered { covered } -// pub fn unwrap_feature(feature: Feature, level: u8) -> HashSet { -// if let Some(geometry) = feature.geometry { -// match geometry.value { -// Value::MultiPoint(mp) => { -// let mut cells = HashSet::new(); -// for point in mp { -// let cell_id = -// CellID::from(s2::latlng::LatLng::from_degrees(point[1], point[0])) -// .parent(level as u64); -// cells.insert(cell_id.0); -// } -// cells -// } -// _ => HashSet::new(), -// } -// } else { -// HashSet::new() -// } -// } - -pub fn cluster( - feature: Feature, - data: &SingleVec, - level: u8, - size: u8, - stats: &mut Stats, -) -> SingleVec { - let all_cells = bootstrap(&feature, level, size, stats); - let valid_cells = data - .iter() - .map(|f| { - CellID::from(s2::latlng::LatLng::from_degrees(f[0], f[1])) - .parent(level as u64) - .0 - }) - .collect::>(); - - stats.total_clusters = 0; - let points = if let Some(geometry) = all_cells.geometry { - match geometry.value { - Value::MultiPoint(mp) => mp - .into_iter() - .filter_map(|point| { - if cell_coverage(point[1], point[0], size, level) - .lock() - .unwrap() - .iter() - .any(|c| valid_cells.contains(&c.parse::().unwrap())) - { - stats.total_clusters += 1; - Some([point[1], point[0]]) - } else { - None - } - }) - .collect(), - _ => vec![], - } - } else { - vec![] - }; - stats.distance_stats(&points); - stats.points_covered = data.len(); - points -} - pub fn from_array_to_cell_id(point: &PointArray, parent_level: u64) -> CellID { CellID::from(LatLng::from_degrees(point[0], point[1])).parent(parent_level) } diff --git a/server/algorithms/src/stats.rs b/server/algorithms/src/stats.rs index 7d01ec75..52ac03e1 100644 --- a/server/algorithms/src/stats.rs +++ b/server/algorithms/src/stats.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use std::{ops::AddAssign, time::Instant}; use geo::{HaversineDistance, Point}; use hashbrown::HashSet; @@ -11,6 +11,10 @@ const WIDTH: &str = "=========================================================== #[derive(Debug, Clone)] pub struct Stats { + stats_start_time: Option, + label: String, + min_points: usize, + pub best_clusters: SingleVec, pub best_cluster_point_count: usize, pub cluster_time: Precision, @@ -22,13 +26,11 @@ pub struct Stats { pub total_distance: Precision, pub longest_distance: Precision, pub mygod_score: usize, - stats_start_time: Option, - label: String, } impl Stats { - pub fn new(label: String) -> Self { - Stats { + pub fn new(label: String, min_points: usize) -> Self { + Self { best_clusters: vec![], best_cluster_point_count: 0, cluster_time: 0., @@ -42,16 +44,17 @@ impl Stats { mygod_score: 0, stats_start_time: None, label, + min_points, } } - pub fn get_score(&self, min_points: usize) -> usize { - self.total_clusters * min_points + (self.total_points - self.points_covered) + pub fn get_score(&self) -> usize { + self.total_clusters * self.min_points + (self.total_points - self.points_covered) } - pub fn set_score(&mut self, min_points: usize) { + pub fn set_score(&mut self) { self.start_timer(); - self.mygod_score = self.get_score(min_points); + self.mygod_score = self.get_score(); self.stop_timer(); } @@ -128,17 +131,17 @@ impl Stats { ) } - pub fn distance_stats(&mut self, points: &SingleVec) { + pub fn distance_stats(&mut self, clusters: &SingleVec) { self.start_timer(); - log::info!("generating distance stats for {} points", points.len()); + log::info!("generating distance stats for {} points", clusters.len()); self.total_distance = 0.; self.longest_distance = 0.; - for (i, point) in points.iter().enumerate() { + for (i, point) in clusters.iter().enumerate() { let point = Point::new(point[1], point[0]); - let point2 = if i == points.len() - 1 { - Point::new(points[0][1], points[0][0]) + let point2 = if i == clusters.len() - 1 { + Point::new(clusters[0][1], clusters[0][0]) } else { - Point::new(points[i + 1][1], points[i + 1][0]) + Point::new(clusters[i + 1][1], clusters[i + 1][0]) }; let distance = point.haversine_distance(&point2); self.total_distance += distance; @@ -179,41 +182,45 @@ impl Stats { self.start_timer(); log::info!("starting coverage check for {} points", points.len()); self.total_points = points.len(); + self.total_clusters = clusters.len(); + + if points.is_empty() { + } else { + let tree = rtree::spawn(radius, points); + let clusters: Vec = clusters + .into_iter() + .map(|c| point::Point::new(radius, 20, *c)) + .collect(); + let clusters: Vec> = cluster_info(&tree, &clusters); + let mut points_covered: HashSet<&point::Point> = HashSet::new(); + let mut best_clusters = SingleVec::new(); + let mut best = 0; - let tree = rtree::spawn(radius, points); - let clusters: Vec = clusters - .into_iter() - .map(|c| point::Point::new(radius, 20, *c)) - .collect(); - let clusters: Vec> = cluster_info(&tree, &clusters); - let mut points_covered: HashSet<&point::Point> = HashSet::new(); - let mut best_clusters = SingleVec::new(); - let mut best = 0; - - for cluster in clusters.iter() { - if cluster.all.len() > best { - best_clusters.clear(); - best = cluster.all.len(); - best_clusters.push(cluster.point.center); - } else if cluster.all.len() == best { - best_clusters.push(cluster.point.center); + for cluster in clusters.iter() { + if cluster.all.len() > best { + best_clusters.clear(); + best = cluster.all.len(); + best_clusters.push(cluster.point.center); + } else if cluster.all.len() == best { + best_clusters.push(cluster.point.center); + } + if let Some(point) = tree.locate_at_point(&cluster.point.center) { + points_covered.insert(point); + } + points_covered.extend(&cluster.all); } - if let Some(point) = tree.locate_at_point(&cluster.point.center) { - points_covered.insert(point); + + self.best_cluster_point_count = best; + self.best_clusters = best_clusters; + self.points_covered = points_covered.len(); + + if self.points_covered > self.total_points { + log::warn!( + "points covered ({}) is greater than total points ({}), please report this to the developers", + self.points_covered, + self.total_points + ); } - points_covered.extend(&cluster.all); - } - self.total_clusters = clusters.len(); - self.best_cluster_point_count = best; - self.best_clusters = best_clusters; - self.points_covered = points_covered.len(); - - if self.points_covered > self.total_points { - log::warn!( - "points covered ({}) is greater than total points ({}), please report this to the developers", - self.points_covered, - self.total_points - ); } log::info!( "coverage check complete in {:.4}s", @@ -243,3 +250,23 @@ impl Serialize for Stats { state.end() } } + +impl<'a> AddAssign<&'a Self> for Stats { + fn add_assign(&mut self, rhs: &'a Self) { + if self.best_cluster_point_count < rhs.best_cluster_point_count { + self.best_clusters = rhs.best_clusters.clone(); + self.best_cluster_point_count = rhs.best_cluster_point_count; + } else if self.best_cluster_point_count == rhs.best_cluster_point_count { + self.best_clusters.extend(rhs.best_clusters.clone()); + } + self.cluster_time += rhs.cluster_time; + self.route_time += rhs.route_time; + self.stats_time += rhs.stats_time; + self.total_points += rhs.total_points; + self.points_covered += rhs.points_covered; + self.total_clusters += rhs.total_clusters; + self.total_distance += rhs.total_distance; + self.longest_distance += rhs.longest_distance; + self.set_score(); + } +} diff --git a/server/api/src/public/v1/calculate.rs b/server/api/src/public/v1/calculate.rs index 1ee725a6..31afc3cd 100644 --- a/server/api/src/public/v1/calculate.rs +++ b/server/api/src/public/v1/calculate.rs @@ -5,9 +5,8 @@ use super::*; use std::time::Instant; use algorithms::{ - bootstrapping, clustering, + self, clustering, routing::{self, tsp}, - s2, stats::Stats, }; use geo::{ChamberlainDuquetteArea, MultiPolygon, Polygon}; @@ -15,8 +14,8 @@ use geo::{ChamberlainDuquetteArea, MultiPolygon, Polygon}; use geojson::Value; use model::{ api::{ - args::{Args, ArgsUnwrapped, CalculationMode, SortBy}, - FeatureHelpers, GeoFormats, Precision, ToCollection, ToFeature, ToSingleVec, + args::{Args, ArgsUnwrapped, SortBy}, + FeatureHelpers, GeoFormats, ToCollection, ToFeature, ToSingleVec, }, db::{area, geofence, instance, route, sea_orm_active_enums::Type}, KojiDb, ScannerType, @@ -40,6 +39,8 @@ async fn bootstrap( s2_level, s2_size, parent, + sort_by, + route_split_level, .. } = payload.into_inner().init(Some("bootstrap")); @@ -53,24 +54,27 @@ async fn bootstrap( .await .map_err(actix_web::error::ErrorInternalServerError)?; - let mut stats = Stats::new(format!("Bootstrap | {:?}", calculation_mode)); + let mut stats = Stats::new(format!("Bootstrap | {:?}", calculation_mode), 1); let time = Instant::now(); - let mut features: Vec = area - .into_iter() - .map(|sub_area| match calculation_mode { - CalculationMode::Radius => bootstrapping::as_geojson(sub_area, radius, &mut stats), - CalculationMode::S2 => s2::bootstrap(&sub_area, s2_level, s2_size, &mut stats), - }) - .collect(); + let mut features: Vec = algorithms::bootstrap::main( + area, + calculation_mode, + radius, + sort_by, + s2_level, + s2_size, + route_split_level, + &mut stats, + ); if parent.is_some() { let mut condensed = vec![]; features .into_iter() .for_each(|feat| match feat.geometry.unwrap().value { - geojson::Value::MultiPoint(mut points) => condensed.append(&mut points), + geojson::Value::MultiPoint(points) => condensed.extend(points), _ => {} }); features = vec![Feature { @@ -82,7 +86,7 @@ async fn bootstrap( ..Default::default() }] } - stats.cluster_time = time.elapsed().as_secs_f32() as Precision; + stats.set_cluster_time(time); let instance = if let Some(parent) = parent { let model = geofence::Query::get_one(&conn.koji, parent.to_string()) @@ -186,7 +190,10 @@ async fn cluster( sort_by }; - let mut stats = Stats::new(format!("{:?} | {:?}", cluster_mode, calculation_mode)); + let mut stats = Stats::new( + format!("{:?} | {:?}", cluster_mode, calculation_mode), + min_points, + ); let enum_type = if category == "gym" || category == "fort" { if conn.scanner_type == ScannerType::Unown { Type::CircleRaid @@ -207,13 +214,13 @@ async fn cluster( .await .map_err(actix_web::error::ErrorInternalServerError)?; - let data_points = if !data_points.is_empty() { - data_points - } else { + let data_points = if data_points.is_empty() { utils::points_from_area(&area, &category, &conn, last_seen, tth) .await .map_err(actix_web::error::ErrorInternalServerError)? .to_single_vec() + } else { + data_points }; log::debug!( @@ -222,25 +229,23 @@ async fn cluster( data_points.len() ); - let clusters = match calculation_mode { - CalculationMode::Radius => clustering::main( - &data_points, - cluster_mode, - radius, - min_points, - &mut stats, - cluster_split_level, - max_clusters, - ), - CalculationMode::S2 => area - .into_iter() - .flat_map(|feature| s2::cluster(feature, &data_points, s2_level, s2_size, &mut stats)) - .collect(), - }; + let clusters = clustering::main( + &data_points, + cluster_mode, + radius, + min_points, + &mut stats, + cluster_split_level, + max_clusters, + calculation_mode, + s2_level, + s2_size, + area, + ); let clusters = routing::main( &data_points, clusters, - sort_by, + &sort_by, route_split_level, radius, &mut stats, @@ -312,7 +317,7 @@ async fn reroute(payload: web::Json) -> Result { mode, .. } = payload.into_inner().init(Some("reroute")); - let mut stats = Stats::new(String::from("Reroute")); + let mut stats = Stats::new(String::from("Reroute"), 1); // For legacy compatibility let data_points = if clusters.is_empty() { @@ -358,10 +363,10 @@ async fn route_stats(payload: web::Json) -> Result { return Ok(HttpResponse::BadRequest() .json(Response::send_error("no_clusters_or_data_points_found"))); } - let mut stats = Stats::new(format!("Route Stats | {:?}", mode)); + let mut stats = Stats::new(format!("Route Stats | {:?}", mode), min_points); stats.cluster_stats(radius, &data_points, &clusters); stats.distance_stats(&clusters); - stats.set_score(min_points); + stats.set_score(); let feature = clusters.to_feature(Some(mode.clone())).remove_last_coord(); let feature = feature.to_collection(Some(instance.clone()), Some(mode)); @@ -414,12 +419,12 @@ async fn route_stats_category( .json(Response::send_error("no_clusters_or_data_points_found"))); } - let mut stats = Stats::new(format!("Route Stats | {:?}", mode)); + let mut stats = Stats::new(format!("Route Stats | {:?}", mode), min_points); stats.cluster_stats(radius, &data_points, &clusters); stats.distance_stats(&clusters); + stats.set_score(); - stats.set_score(min_points); let feature = clusters.to_feature(Some(mode.clone())).remove_last_coord(); let feature = feature.to_collection(Some(instance.clone()), Some(mode)); diff --git a/server/api/src/public/v1/s2.rs b/server/api/src/public/v1/s2.rs index ee7fea69..4fa65922 100644 --- a/server/api/src/public/v1/s2.rs +++ b/server/api/src/public/v1/s2.rs @@ -51,9 +51,15 @@ async fn cell_coverage(payload: web::Json) -> Result>(); Ok(HttpResponse::Ok().json(Response { - data: Some(json!(result)), + data: Some(json!(locked)), message: "Success".to_string(), status: "ok".to_string(), stats: None, diff --git a/server/model/src/api/args.rs b/server/model/src/api/args.rs index bff485f8..544e6b19 100644 --- a/server/model/src/api/args.rs +++ b/server/model/src/api/args.rs @@ -537,7 +537,7 @@ impl Args { let save_to_db = save_to_db.unwrap_or(false); let save_to_scanner = save_to_scanner.unwrap_or(false); let simplify = simplify.unwrap_or(false); - let sort_by = sort_by.unwrap_or(SortBy::GeoHash); + let sort_by = sort_by.unwrap_or(SortBy::None); let tth = tth.unwrap_or(SpawnpointTth::All); let mode = get_enum(mode); let route_split_level = validate_s2_cell(route_split_level, "route_split_level"); diff --git a/server/model/src/api/geometry.rs b/server/model/src/api/geometry.rs index 861f46ac..af33f684 100644 --- a/server/model/src/api/geometry.rs +++ b/server/model/src/api/geometry.rs @@ -195,6 +195,23 @@ impl ToFeatureVec for Geometry { } } +impl ToGeometryVec for Geometry { + fn to_geometry_vec(self) -> Vec { + match self.value { + Value::MultiPolygon(val) => val + .into_iter() + .map(|polygon| Geometry { + bbox: self.bbox.clone(), + value: Value::Polygon(polygon), + foreign_members: None, + }) + .collect(), + Value::GeometryCollection(val) => val, + _ => vec![self], + } + } +} + impl ToCollection for Vec { fn to_collection(self, _name: Option, enum_type: Option) -> FeatureCollection { FeatureCollection { diff --git a/server/model/src/api/mod.rs b/server/model/src/api/mod.rs index 6fe58ce6..39c074b7 100644 --- a/server/model/src/api/mod.rs +++ b/server/model/src/api/mod.rs @@ -106,6 +106,10 @@ pub trait ToGeometry { fn to_geometry(self) -> Geometry; } +pub trait ToGeometryVec { + fn to_geometry_vec(self) -> Vec; +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] pub enum GeoFormats {