diff --git a/client/src/assets/constants.ts b/client/src/assets/constants.ts index a8347c65..d10b1051 100644 --- a/client/src/assets/constants.ts +++ b/client/src/assets/constants.ts @@ -200,4 +200,10 @@ export const CLUSTERING_MODES = [ // 'Best', ] as const -export const SORT_BY = ['GeoHash', 'ClusterCount', 'Random'] as const +export const SORT_BY = [ + 'GeoHash', + 'S2Cell', + 'TSP', + 'ClusterCount', + 'Random', +] as const diff --git a/client/src/components/drawer/Routing.tsx b/client/src/components/drawer/Routing.tsx index 6cfbccca..83451778 100644 --- a/client/src/components/drawer/Routing.tsx +++ b/client/src/components/drawer/Routing.tsx @@ -33,6 +33,8 @@ export default function RoutingTab() { const category = usePersist((s) => s.category) const cluster_mode = usePersist((s) => s.cluster_mode) const calculation_mode = usePersist((s) => s.calculation_mode) + const sort_by = usePersist((s) => s.sort_by) + const scannerType = useStatic((s) => s.scannerType) const updateButton = useStatic((s) => s.updateButton) const isEditing = useStatic((s) => @@ -101,7 +103,10 @@ export default function RoutingTab() { - + + + Routing + - - - - Routing - + + diff --git a/server/algorithms/src/clustering/mod.rs b/server/algorithms/src/clustering/mod.rs index 40674706..5ba4ceb7 100644 --- a/server/algorithms/src/clustering/mod.rs +++ b/server/algorithms/src/clustering/mod.rs @@ -6,27 +6,21 @@ use self::greedy::Greedy; use super::*; -use model::{ - api::{args::SortBy, cluster_mode::ClusterMode, single_vec::SingleVec, ToSingleVec}, - db::GenericData, -}; +use model::api::{cluster_mode::ClusterMode, single_vec::SingleVec}; mod fastest; mod greedy; pub fn main( - data_points: Vec, + data_points: &SingleVec, cluster_mode: ClusterMode, radius: f64, min_points: usize, stats: &mut Stats, - sort_by: SortBy, cluster_split_level: u64, max_clusters: usize, ) -> SingleVec { let time = Instant::now(); - let data_points = data_points.to_single_vec(); - let clusters = match cluster_mode { ClusterMode::Fastest => { let clusters = fastest::cluster(&data_points, radius, min_points); diff --git a/server/algorithms/src/routing/basic.rs b/server/algorithms/src/routing/basic.rs new file mode 100644 index 00000000..6ae795dd --- /dev/null +++ b/server/algorithms/src/routing/basic.rs @@ -0,0 +1,92 @@ +use geo::Coord; +use geohash::{decode, encode}; +use model::api::{args::SortBy, single_vec::SingleVec}; +use rand::rngs::mock::StepRng; +use rayon::slice::ParallelSliceMut; +use s2::{cell::Cell, latlng::LatLng}; +use shuffle::{irs::Irs, shuffler::Shuffler}; + +use crate::rtree::{self, cluster::Cluster, point}; + +fn random(clusters: SingleVec) -> SingleVec { + let mut clusters = clusters; + let mut rng = StepRng::new(2, 13); + let mut irs = Irs::default(); + match irs.shuffle(&mut clusters, &mut rng) { + Ok(_) => {} + Err(e) => { + log::warn!("Error while shuffling: {}", e); + } + } + clusters +} + +pub fn cluster_count(points: &SingleVec, clusters: SingleVec, radius: f64) -> SingleVec { + let tree = rtree::spawn(radius, points); + let clusters: Vec = clusters + .into_iter() + .map(|c| point::Point::new(radius, 20, c)) + .collect(); + + let mut clusters: Vec> = rtree::cluster_info(&tree, &clusters); + + clusters.par_sort_by(|a, b| b.all.len().cmp(&a.all.len())); + + clusters.into_iter().map(|c| c.point.center).collect() +} + +fn geohash(clusters: SingleVec) -> SingleVec { + let mut points: Vec = clusters + .into_iter() + .filter_map(|p| match encode(Coord { x: p[1], y: p[0] }, 12) { + Ok(geohash) => Some(geohash), + Err(e) => { + log::warn!("Error while encoding geohash: {}", e); + None + } + }) + .collect(); + + points.par_sort(); + + points + .into_iter() + .map(|p| { + let coord = decode(&p); + match coord { + Ok(coord) => [coord.0.y, coord.0.x], + Err(e) => { + log::warn!("Error while decoding geohash: {}", e); + [0., 0.] + } + } + }) + .collect() +} + +fn s2cell(clusters: SingleVec) -> SingleVec { + let mut points: Vec = clusters + .into_iter() + .map(|p| LatLng::from_degrees(p[0], p[1]).into()) + .collect(); + + points.par_sort_by(|a, b| a.id.cmp(&b.id)); + + points + .into_iter() + .map(|p| { + let center = p.center(); + [center.latitude().deg(), center.longitude().deg()] + }) + .collect() +} + +pub fn sort(points: &SingleVec, clusters: SingleVec, radius: f64, sort_by: SortBy) -> SingleVec { + match sort_by { + SortBy::Random => random(clusters), + SortBy::GeoHash => geohash(clusters), + SortBy::S2Cell => s2cell(clusters), + SortBy::ClusterCount => cluster_count(&points, clusters, radius), + _ => clusters, + } +} diff --git a/server/algorithms/src/routing/mod.rs b/server/algorithms/src/routing/mod.rs index 88bc3b01..9b3e4a86 100644 --- a/server/algorithms/src/routing/mod.rs +++ b/server/algorithms/src/routing/mod.rs @@ -1,2 +1,3 @@ +pub mod basic; pub mod tsp; // pub mod vrp; diff --git a/server/algorithms/src/rtree/mod.rs b/server/algorithms/src/rtree/mod.rs index c3589208..9d5ba715 100644 --- a/server/algorithms/src/rtree/mod.rs +++ b/server/algorithms/src/rtree/mod.rs @@ -4,6 +4,7 @@ pub mod point; use model::api::single_vec::SingleVec; use point::Point; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use rstar::RTree; pub fn spawn(radius: f64, points: &SingleVec) -> RTree { @@ -13,3 +14,16 @@ pub fn spawn(radius: f64, points: &SingleVec) -> RTree { .collect::>(); RTree::bulk_load(points) } + +pub fn cluster_info<'a>( + point_tree: &'a RTree, + clusters: &'a Vec, +) -> Vec> { + clusters + .par_iter() + .map(|cluster| { + let points = point_tree.locate_all_at_point(&cluster.center).into_iter(); + cluster::Cluster::new(cluster, points, vec![].into_iter()) + }) + .collect::>() +} diff --git a/server/algorithms/src/s2.rs b/server/algorithms/src/s2.rs index d61164b6..c894eee2 100644 --- a/server/algorithms/src/s2.rs +++ b/server/algorithms/src/s2.rs @@ -5,10 +5,7 @@ use std::{ use geo::{HaversineDestination, Intersects, MultiPolygon, Polygon, RemoveRepeatedPoints}; use geojson::{Feature, Geometry, Value}; -use model::{ - api::{point_array::PointArray, single_vec::SingleVec}, - db::GenericData, -}; +use model::api::{point_array::PointArray, single_vec::SingleVec}; use s2::{ cell::Cell, cellid::CellID, cellunion::CellUnion, latlng::LatLng, rect::Rect, region::RegionCoverer, @@ -17,8 +14,6 @@ use serde::Serialize; use crate::stats::Stats; -// use crate::utils::debug_string; - type Covered = Arc>>; #[derive(Debug, Clone, Serialize)] @@ -507,7 +502,7 @@ pub fn cell_coverage(lat: f64, lon: f64, size: u8, level: u8) -> Covered { pub fn cluster( feature: Feature, - data: &Vec, + data: &SingleVec, level: u8, size: u8, stats: &mut Stats, @@ -516,7 +511,7 @@ pub fn cluster( let valid_cells = data .iter() .map(|f| { - CellID::from(s2::latlng::LatLng::from_degrees(f.p[0], f.p[1])) + CellID::from(s2::latlng::LatLng::from_degrees(f[0], f[1])) .parent(level as u64) .0 }) diff --git a/server/algorithms/src/stats.rs b/server/algorithms/src/stats.rs index 6482d2e9..0f226fdd 100644 --- a/server/algorithms/src/stats.rs +++ b/server/algorithms/src/stats.rs @@ -3,10 +3,9 @@ use std::time::Instant; use geo::{HaversineDistance, Point}; use hashbrown::HashSet; use model::api::{single_vec::SingleVec, Precision}; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use serde::{ser::SerializeStruct, Serialize}; -use crate::rtree::{self, cluster::Cluster, point}; +use crate::rtree::{self, cluster::Cluster, cluster_info, point}; const WIDTH: &str = "======================================================================="; @@ -186,13 +185,7 @@ impl Stats { .into_iter() .map(|c| point::Point::new(radius, 20, *c)) .collect(); - let clusters: Vec> = clusters - .par_iter() - .map(|cluster| { - let points = tree.locate_all_at_point(&cluster.center).into_iter(); - Cluster::new(cluster, points, vec![].into_iter()) - }) - .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; diff --git a/server/api/src/public/v1/calculate.rs b/server/api/src/public/v1/calculate.rs index 6d63fa38..55119189 100644 --- a/server/api/src/public/v1/calculate.rs +++ b/server/api/src/public/v1/calculate.rs @@ -4,13 +4,18 @@ use super::*; use std::{collections::VecDeque, time::Instant}; -use algorithms::{bootstrapping, clustering, routing::tsp, s2, stats::Stats}; +use algorithms::{ + bootstrapping, clustering, + routing::{basic, tsp}, + s2, + stats::Stats, +}; use geo::{ChamberlainDuquetteArea, MultiPolygon, Polygon}; use geojson::Value; use model::{ api::{ - args::{Args, ArgsUnwrapped, CalculationMode}, + args::{Args, ArgsUnwrapped, CalculationMode, SortBy}, point_array::PointArray, FeatureHelpers, GeoFormats, Precision, ToCollection, ToFeature, ToSingleVec, }, @@ -220,7 +225,9 @@ async fn cluster( utils::points_from_area(&area, &category, &conn, last_seen, tth) .await .map_err(actix_web::error::ErrorInternalServerError)? - }; + } + .to_single_vec(); + log::debug!( "[{}] Found Data Points: {}", mode.to_uppercase(), @@ -229,12 +236,11 @@ async fn cluster( let mut clusters = match calculation_mode { CalculationMode::Radius => clustering::main( - data_points, + &data_points, cluster_mode, radius, min_points, &mut stats, - sort_by, cluster_split_level, max_clusters, ), @@ -244,11 +250,10 @@ async fn cluster( .collect(), }; - if mode.eq("route") && !clusters.is_empty() { + let route_time = Instant::now(); + clusters = if (mode.eq("route") || sort_by == SortBy::TSP) && !clusters.is_empty() { log::info!("Cluster Length: {}", clusters.len()); - let route_time = Instant::now(); let tour = tsp::multi(&clusters, route_split_level); - stats.set_route_time(route_time); log::info!("Tour Length {}", tour.len()); let mut final_clusters = VecDeque::::new(); @@ -267,8 +272,11 @@ async fn cluster( } final_clusters.rotate_left(rotate_count); - clusters = final_clusters.into(); - } + final_clusters.into() + } else { + basic::sort(&data_points, clusters, radius, sort_by) + }; + stats.set_route_time(route_time); stats.distance_stats(&clusters); let mut feature = clusters diff --git a/server/model/src/api/args.rs b/server/model/src/api/args.rs index af5f20cd..ff0d994f 100644 --- a/server/model/src/api/args.rs +++ b/server/model/src/api/args.rs @@ -162,8 +162,25 @@ pub enum SortBy { GeoHash, ClusterCount, Random, + S2Cell, + TSP, } +impl PartialEq for SortBy { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (SortBy::GeoHash, SortBy::GeoHash) => true, + (SortBy::ClusterCount, SortBy::ClusterCount) => true, + (SortBy::Random, SortBy::Random) => true, + (SortBy::S2Cell, SortBy::S2Cell) => true, + (SortBy::TSP, SortBy::TSP) => true, + _ => false, + } + } +} + +impl Eq for SortBy {} + #[derive(Debug, Serialize, Deserialize, Clone)] pub enum SpawnpointTth { All,