use super::{intersect, ulps_eq_c};
use core::fmt;
use num_traits::{Float, Zero};
use std::cmp;
use std::convert::identity;
use std::fmt::Debug;
use std::marker::PhantomData;
#[derive(Clone, Copy)]
pub struct SiteEventKey<T>
where
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
pub pos: geo::Coordinate<T>,
}
impl<T> SiteEventKey<T>
where
T: Float + approx::UlpsEq + Copy + geo::CoordFloat,
T::Epsilon: Copy,
{
pub fn new(x: T, y: T) -> Self {
Self {
pos: geo::Coordinate { x, y },
}
}
}
impl<T> Debug for SiteEventKey<T>
where
T: Float + approx::UlpsEq + Copy + geo::CoordFloat,
T::Epsilon: Copy,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("")
.field(&self.pos.x)
.field(&self.pos.y)
.finish()
}
}
impl<T> PartialOrd for SiteEventKey<T>
where
T: Float + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
if approx::ulps_eq!(&self.pos.y, &other.pos.y) {
if approx::ulps_eq!(&self.pos.x, &other.pos.x) {
Some(cmp::Ordering::Equal)
} else {
self.pos.x.partial_cmp(&other.pos.x)
}
} else {
self.pos.y.partial_cmp(&other.pos.y)
}
}
}
impl<T> PartialEq for SiteEventKey<T>
where
T: Float + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
fn eq(&self, other: &Self) -> bool {
ulps_eq_c(&self.pos, &other.pos)
}
}
struct MinMax<T>
where
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
best_left: Option<T>,
slope: MinMaxSlope<T>,
best_right: Option<T>,
}
impl<T> MinMax<T>
where
T: Sized + Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
fn new() -> Self {
Self {
best_left: None,
best_right: None,
slope: MinMaxSlope::new(),
}
}
fn update(&mut self, pivot_x: T, candidate_x: T, candidate_slope: T, candidate_index: usize) {
if candidate_x < pivot_x {
if let Some(current_min) = self.best_left {
if approx::ulps_eq!(¤t_min, &candidate_x) {
self.slope
.update_left(false, candidate_slope, candidate_index);
} else if current_min < candidate_x {
self.slope
.update_left(true, candidate_slope, candidate_index);
self.best_left = Some(candidate_x);
} else {
}
} else {
self.best_left = Some(candidate_x);
self.slope
.update_left(false, candidate_slope, candidate_index);
}
} else if candidate_x > pivot_x {
if let Some(current_max) = self.best_right {
if approx::ulps_eq!(¤t_max, &candidate_x) {
self.slope
.update_right(false, candidate_slope, candidate_index);
} else if current_max > candidate_x {
self.slope
.update_right(true, candidate_slope, candidate_index);
self.best_right = Some(candidate_x);
} else {
}
} else {
self.best_right = Some(candidate_x);
self.slope
.update_right(false, candidate_slope, candidate_index);
}
}
}
fn clear(&mut self) {
self.best_left = None;
self.best_right = None;
self.slope.clear();
}
}
struct MinMaxSlope<T>
where
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
best_left: Option<T>, candidates_left: Vec<usize>,
best_right: Option<T>, candidates_right: Vec<usize>,
}
impl<T> MinMaxSlope<T>
where
T: Sized + Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
fn new() -> Self {
Self {
best_left: None,
candidates_left: Vec::<usize>::new(),
best_right: None,
candidates_right: Vec::<usize>::new(),
}
}
fn update_both(&mut self, candidate_index: usize, lines: &[geo::Line<T>]) {
let line = lines[candidate_index];
let candidate_slope = if approx::ulps_eq!(&line.end.y, &line.start.y) {
T::infinity()
} else {
(line.end.x - line.start.x) / (line.end.y - line.start.y)
};
self.update_left(false, candidate_slope, candidate_index);
self.update_right(false, candidate_slope, candidate_index);
}
fn update_left(&mut self, clear: bool, candidate_slope: T, candidate_index: usize) {
if clear {
self.candidates_left.clear();
self.best_left = None;
}
if let Some(current_slope) = self.best_left {
if approx::ulps_eq!(¤t_slope, &candidate_slope) {
self.candidates_left.push(candidate_index);
} else if candidate_slope < current_slope {
self.candidates_left.clear();
self.candidates_left.push(candidate_index);
self.best_left = Some(candidate_slope);
} else {
}
} else {
self.best_left = Some(candidate_slope);
self.candidates_left.push(candidate_index);
}
}
fn update_right(&mut self, clear: bool, candidate_slope: T, candidate_index: usize) {
if clear {
self.candidates_right.clear();
self.best_right = None;
}
if let Some(current_slope) = self.best_right {
if approx::ulps_eq!(¤t_slope, &candidate_slope) {
self.candidates_right.push(candidate_index);
} else if candidate_slope > current_slope {
self.candidates_right.clear();
self.candidates_right.push(candidate_index);
self.best_right = Some(candidate_slope);
} else {
}
} else {
self.best_right = Some(candidate_slope);
self.candidates_right.push(candidate_index);
}
}
fn clear(&mut self) {
self.best_left = None;
self.candidates_left.clear();
self.best_right = None;
self.candidates_right.clear();
}
}
pub struct SiteEvent<T>
where
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
drop: Option<Vec<usize>>,
add: Option<Vec<usize>>,
intersection: Option<Vec<usize>>,
#[doc(hidden)]
pd: PhantomData<T>,
}
impl<T> SiteEvent<T>
where
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
pub(crate) fn with_intersection(i: &[usize]) -> Self {
Self {
drop: None,
add: None,
intersection: Some(i.to_vec()),
pd: PhantomData,
}
}
pub(crate) fn with_drop(l: &[usize]) -> Self {
Self {
drop: Some(l.to_vec()),
add: None,
intersection: None,
pd: PhantomData,
}
}
pub(crate) fn with_add(l: &[usize]) -> Self {
Self {
drop: None,
add: Some(l.to_vec()),
intersection: None,
pd: PhantomData,
}
}
pub fn get_intersections(&self) -> &Option<Vec<usize>> {
&self.intersection
}
}
fn sweepline_intersection<T>(sweepline: geo::Coordinate<T>, other: &geo::Line<T>) -> Option<(T, T)>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
let y1 = other.start.y;
let y2 = other.end.y;
let x1 = other.start.x;
let x2 = other.end.x;
if approx::ulps_eq!(&y1, &y2) {
if sweepline.x < x2 {
return Some((x2, T::zero()));
} else {
return None;
}
}
if approx::ulps_eq!(&x1, &x2) {
return Some((x1, T::infinity()));
}
let slope: T = (y2 - y1) / (x2 - x1);
if slope.is_nan() {
panic!("D==0 should not happen!");
}
let d = y1 - slope * x1;
Some(((sweepline.y - d) / slope, slope))
}
pub struct AlgorithmData<T>
where
T: Float + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
sweepline_pos: geo::Coordinate<T>,
stop_at_first_intersection: bool,
pub ignore_end_point_intersections: bool,
site_events: Option<rb_tree::RBMap<SiteEventKey<T>, SiteEvent<T>>>,
active_lines: Option<ahash::AHashSet<usize>>,
result: Option<rb_tree::RBMap<SiteEventKey<T>, Vec<usize>>>,
intersection_calls: usize,
neighbour_priority: Option<MinMax<T>>,
connected_priority: Option<MinMaxSlope<T>>,
lines: Vec<geo::Line<T>>,
}
impl<T> Default for AlgorithmData<T>
where
T: Float + num_traits::ToPrimitive + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
fn default() -> Self {
Self {
sweepline_pos: geo::Coordinate {
x: -T::max_value(),
y: -T::max_value(),
},
stop_at_first_intersection: false,
ignore_end_point_intersections: false,
site_events: Some(rb_tree::RBMap::new()),
lines: Vec::<geo::Line<T>>::new(),
result: Some(rb_tree::RBMap::new()),
active_lines: Some(ahash::AHashSet::default()),
intersection_calls: 0,
neighbour_priority: Some(MinMax::new()),
connected_priority: Some(MinMaxSlope::new()),
}
}
}
impl<T> AlgorithmData<T>
where
T: Float + num_traits::ToPrimitive + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
pub fn get_sweepline_pos(&self) -> &geo::Coordinate<T> {
&self.sweepline_pos
}
pub fn get_lines(&self) -> &Vec<geo::Line<T>> {
&self.lines
}
pub fn get_results(&self) -> &Option<rb_tree::RBMap<SiteEventKey<T>, Vec<usize>>> {
&self.result
}
#[allow(clippy::type_complexity)]
pub fn take_results<'a>(
&mut self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
super::IntersectError,
>
where
T: 'a,
{
if let Some(rv) = self.result.take() {
Ok(Box::new(rv.into_iter().map(|x| (x.0.pos, x.1))))
} else {
Err(super::IntersectError::ResultsAlreadyTaken(
"Results already taken from structure".to_string(),
))
}
}
pub fn get_site_events(&self) -> &Option<rb_tree::RBMap<SiteEventKey<T>, SiteEvent<T>>> {
&self.site_events
}
pub fn get_active_lines(&self) -> &Option<ahash::AHashSet<usize>> {
&self.active_lines
}
pub fn get_intersection_calls(&self) -> usize {
self.intersection_calls
}
pub fn with_stop_at_first_intersection(
&mut self,
value: bool,
) -> Result<&mut Self, super::IntersectError> {
self.stop_at_first_intersection = value;
Ok(self)
}
pub fn with_ignore_end_point_intersections(
&mut self,
value: bool,
) -> Result<&mut Self, super::IntersectError> {
self.ignore_end_point_intersections = value;
Ok(self)
}
pub fn with_lines<I>(&mut self, input_iter: I) -> Result<&mut Self, super::IntersectError>
where
I: Iterator<Item = geo::Line<T>>,
{
let mut site_events = self.site_events.take().unwrap();
for (index, mut aline) in input_iter.enumerate() {
if !(aline.start.x.is_finite()
&& aline.start.y.is_finite()
&& aline.end.x.is_finite()
&& aline.end.y.is_finite())
{
return Err(super::IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
if !(SiteEventKey { pos: aline.start }).lt(&(SiteEventKey { pos: aline.end })) {
std::mem::swap(&mut aline.start, &mut aline.end);
};
self.lines.push(aline);
let key_start = SiteEventKey { pos: aline.start };
let key_end = SiteEventKey { pos: aline.end };
if let Some(mut event) = site_events.get_mut(&key_start) {
let mut lower = event.add.take().map_or(Vec::<usize>::new(), identity);
lower.push(index);
event.add = Some(lower);
} else {
let event = SiteEvent::<T>::with_add(&[index]);
let _ = site_events.insert(key_start, event);
}
if let Some(mut event) = site_events.get_mut(&key_end) {
let mut upper = event.drop.take().map_or(Vec::<usize>::new(), identity);
upper.push(index);
event.drop = Some(upper);
} else {
let event = SiteEvent::<T>::with_drop(&[index]);
let _ = site_events.insert(key_end, event);
}
}
self.site_events = Some(site_events);
#[cfg(feature = "console_trace")]
self.debug();
Ok(self)
}
pub fn with_ref_lines<'a, I>(
&mut self,
input_iter: I,
) -> Result<&mut Self, super::IntersectError>
where
T: 'a,
I: Iterator<Item = &'a geo::Line<T>>,
{
let mut site_events = self.site_events.take().unwrap();
for (index, aline) in input_iter.enumerate() {
if !(aline.start.x.is_finite()
&& aline.start.y.is_finite()
&& aline.end.x.is_finite()
&& aline.end.y.is_finite())
{
return Err(super::IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
let aline =
if (SiteEventKey { pos: aline.start }).lt(&(SiteEventKey { pos: aline.end })) {
geo::Line {
start: aline.start,
end: aline.end,
}
} else {
geo::Line {
start: aline.end,
end: aline.start,
}
};
self.lines.push(aline);
let key_start = SiteEventKey { pos: aline.start };
let key_end = SiteEventKey { pos: aline.end };
if let Some(mut event) = site_events.get_mut(&key_start) {
let mut lower = event.add.take().map_or(Vec::<usize>::new(), identity);
lower.push(index);
event.add = Some(lower);
} else {
let event = SiteEvent::<T>::with_add(&[index]);
let _ = site_events.insert(key_start, event);
}
if let Some(mut event) = site_events.get_mut(&key_end) {
let mut upper = event.drop.take().map_or(Vec::<usize>::new(), identity);
upper.push(index);
event.drop = Some(upper);
} else {
let event = SiteEvent::<T>::with_drop(&[index]);
let _ = site_events.insert(key_end, event);
}
}
self.site_events = Some(site_events);
#[cfg(feature = "console_trace")]
self.debug();
Ok(self)
}
fn add_intersection_event(
&self,
site_events: &mut rb_tree::RBMap<SiteEventKey<T>, SiteEvent<T>>,
position: &SiteEventKey<T>,
intersecting_lines: &[usize],
) {
if let Some(event) = site_events.get_mut(position) {
let mut intersections_added = 0;
for new_intersection in intersecting_lines.iter() {
let i_line = self.lines[*new_intersection];
if ulps_eq_c(&position.pos, &i_line.start) || ulps_eq_c(&position.pos, &i_line.end)
{
continue;
}
if let Some(prev_intersections) = &mut event.intersection {
prev_intersections.push(*new_intersection);
} else {
let new_vec = vec![*new_intersection];
event.intersection = Some(new_vec);
}
intersections_added += 1;
}
if intersections_added > 0 {
let mut intersections = event.intersection.take().unwrap();
intersections.sort_unstable();
intersections.dedup();
event.intersection = Some(intersections);
}
} else {
let event = SiteEvent::with_intersection(intersecting_lines);
let _ = site_events.insert(*position, event);
}
}
#[allow(clippy::type_complexity)]
pub fn compute<'a>(
&mut self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
super::IntersectError,
>
where
T: 'a,
{
if self.stop_at_first_intersection && self.result.as_ref().map_or(false, |x| !x.is_empty())
{
return self.take_results();
}
let mut active_lines = self.active_lines.take().unwrap();
let mut site_events = self.site_events.take().unwrap();
let mut result = self.result.take().unwrap();
let mut neighbour_priority = self.neighbour_priority.take().unwrap();
let mut connected_priority = self.connected_priority.take().unwrap();
loop {
if let Some((key, event)) = site_events.pop_pair() {
self.handle_event(
&key,
&event,
&mut active_lines,
&mut neighbour_priority,
&mut connected_priority,
&mut site_events,
&mut result,
);
} else {
self.sweepline_pos = geo::Coordinate {
x: T::max_value(),
y: T::max_value(),
};
break;
}
}
self.site_events = Some(site_events);
self.active_lines = Some(active_lines);
self.result = Some(result);
self.neighbour_priority = Some(neighbour_priority);
self.connected_priority = Some(connected_priority);
self.take_results()
}
pub fn compute_iterative(&mut self) -> Result<bool, super::IntersectError> {
if self.stop_at_first_intersection && self.result.as_ref().map_or(false, |x| !x.is_empty())
{
return Ok(true);
}
let mut active_lines = self.active_lines.take().unwrap();
let mut site_events = self.site_events.take().unwrap();
let mut result = self.result.take().unwrap();
let mut neighbour_priority = self.neighbour_priority.take().unwrap();
let mut connected_priority = self.connected_priority.take().unwrap();
let algorithm_is_done: bool;
algorithm_is_done = if let Some((key, event)) = site_events.pop_pair() {
self.handle_event(
&key,
&event,
&mut active_lines,
&mut neighbour_priority,
&mut connected_priority,
&mut site_events,
&mut result,
);
false
} else {
self.sweepline_pos = geo::Coordinate {
x: T::max_value(),
y: T::max_value(),
};
true
};
self.site_events = Some(site_events);
self.active_lines = Some(active_lines);
self.result = Some(result);
self.neighbour_priority = Some(neighbour_priority);
self.connected_priority = Some(connected_priority);
Ok(algorithm_is_done)
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn handle_event(
&mut self,
key: &SiteEventKey<T>,
event: &SiteEvent<T>,
active_lines: &mut ahash::AHashSet<usize>,
neighbour_priority: &mut MinMax<T>,
connected_priority: &mut MinMaxSlope<T>,
site_events: &mut rb_tree::RBMap<SiteEventKey<T>, SiteEvent<T>>,
result: &mut rb_tree::RBMap<SiteEventKey<T>, Vec<usize>>,
) {
self.sweepline_pos = key.pos;
let removed_active_lines = event.drop.iter().flatten().count();
let added_active_lines = event.add.iter().flatten().count();
let intersections_found = event.intersection.iter().flatten().count();
#[cfg(feature = "console_trace")]
println!("*************************************");
#[cfg(feature = "console_trace")]
print!(
"handle_event() sweepline=({:?},{:?})",
self.sweepline_pos.x, self.sweepline_pos.y,
);
#[cfg(feature = "console_trace")]
print!(
", drop={:?}",
event.drop.iter().flatten().collect::<Vec<&usize>>()
);
#[cfg(feature = "console_trace")]
print!(
", add={:?}",
event.add.iter().flatten().collect::<Vec<&usize>>()
);
#[cfg(feature = "console_trace")]
print!(
", intersection={:?}",
event.intersection.iter().flatten().collect::<Vec<&usize>>()
);
#[cfg(feature = "console_trace")]
println!(
", active:{:?}",
active_lines.iter().collect::<Vec<&usize>>()
);
if self.ignore_end_point_intersections {
if intersections_found > 0 {
if removed_active_lines > 0 {
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.drop.iter().flatten(),
);
}
if added_active_lines > 0 {
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.add.iter().flatten(),
);
}
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.intersection.iter().flatten(),
);
}
} else if removed_active_lines + added_active_lines + intersections_found > 1 {
if removed_active_lines > 0 {
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.drop.iter().flatten(),
);
}
if added_active_lines > 0 {
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.add.iter().flatten(),
);
}
if intersections_found > 0 {
self.report_intersections_to_result(
result,
&self.sweepline_pos.clone(),
event.intersection.iter().flatten(),
);
}
}
for line_index in event.drop.iter().flatten() {
let _ = active_lines.remove(line_index);
}
neighbour_priority.clear();
'active_lines: for line_index in active_lines.iter() {
for i in event.intersection.iter().flatten() {
if i == line_index {
continue 'active_lines;
}
}
if let Some((intersection_x, intersection_slope)) =
sweepline_intersection(self.sweepline_pos, self.lines.get(*line_index).unwrap())
{
neighbour_priority.update(
self.sweepline_pos.x,
intersection_x,
intersection_slope,
*line_index,
);
}
}
for l in event.add.iter().flatten() {
let _ = active_lines.insert(*l);
}
if intersections_found + added_active_lines == 0 {
#[cfg(feature = "console_trace")]
println!(
"neighbours left: {:?}",
neighbour_priority.slope.candidates_left
);
#[cfg(feature = "console_trace")]
println!(
"neighbours right: {:?}",
neighbour_priority.slope.candidates_right
);
if !neighbour_priority.slope.candidates_left.is_empty()
&& !neighbour_priority.slope.candidates_right.is_empty()
{
self.find_new_events(
&neighbour_priority.slope.candidates_left,
&neighbour_priority.slope.candidates_right,
site_events,
);
}
} else {
connected_priority.clear();
for l in event.add.iter().flatten() {
connected_priority.update_both(*l, &self.lines);
}
for l in event.intersection.iter().flatten() {
connected_priority.update_both(*l, &self.lines);
}
#[cfg(feature = "console_trace")]
println!(
"left connected_priority candidates {:?}",
connected_priority.candidates_left
);
#[cfg(feature = "console_trace")]
println!(
"right connected_priority candidates {:?}",
connected_priority.candidates_right
);
if !neighbour_priority.slope.candidates_left.is_empty() {
#[cfg(feature = "console_trace")]
println!(
"left neighbour_priority candidates {:?}",
neighbour_priority.slope.candidates_left
);
self.find_new_events(
&neighbour_priority.slope.candidates_left,
&connected_priority.candidates_left,
site_events,
);
}
if !neighbour_priority.slope.candidates_right.is_empty() {
#[cfg(feature = "console_trace")]
println!(
"right neighbour_priority candidates {:?}",
neighbour_priority.slope.candidates_right
);
self.find_new_events(
&neighbour_priority.slope.candidates_right,
&connected_priority.candidates_right,
site_events,
);
}
}
#[cfg(any(test, example))]
println!("Post active lines: {:?}", active_lines);
#[cfg(feature = "console_trace")]
println!();
}
fn find_new_events(
&mut self,
left: &[usize],
right: &[usize],
site_events: &mut rb_tree::RBMap<SiteEventKey<T>, SiteEvent<T>>,
) {
for left_i in left.iter() {
for right_i in right.iter() {
let left_l = &self.lines[*left_i];
let right_l = &self.lines[*right_i];
if ulps_eq_c(&left_l.end, &right_l.end) {
continue;
}
#[cfg(feature = "console_trace")]
print!("testing intersection between {} and {}: ", left_i, right_i);
self.intersection_calls += 1;
if let Some(intersection_p) = intersect(left_l, right_l) {
let intersection_p = intersection_p.single();
if intersection_p.y >= self.sweepline_pos.y
&& !(intersection_p.y == self.sweepline_pos.y
&& intersection_p.x < self.sweepline_pos.x)
&& !ulps_eq_c(&intersection_p, &self.sweepline_pos)
{
#[cfg(feature = "console_trace")]
println!(
"Lines {:?} and {:?} intersects at {:?}",
left_i, right_i, intersection_p
);
self.add_intersection_event(
site_events,
&SiteEventKey {
pos: intersection_p,
},
&[*left_i, *right_i],
)
}
} else {
#[cfg(feature = "console_trace")]
println!(" no intersection");
}
}
}
}
fn report_intersections_to_result<'a, I>(
&mut self,
result: &mut rb_tree::RBMap<SiteEventKey<T>, Vec<usize>>,
pos: &geo::Coordinate<T>,
intersecting_lines: I,
) where
I: Iterator<Item = &'a usize>,
{
let key = SiteEventKey { pos: *pos };
let value = if let Some(value) = result.get_mut(&key) {
value
} else {
let _ = result.insert(key, Vec::default());
result.get_mut(&key).unwrap()
};
for line in intersecting_lines {
value.push(*line);
#[cfg(feature = "console_trace")]
println!("Reported an intersection {:?} for line #{}", pos, line);
}
value.sort_unstable();
}
#[cfg(feature = "console_trace")]
fn debug(&self) {
println!("Stored item in this order:");
for k in self.site_events.as_ref().unwrap().iter().map(|kv| *kv.0) {
print!("{:?}, ", k);
}
println!();
}
}