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,
|