491 lines
16 KiB
C++
491 lines
16 KiB
C++
/********************************************************
|
|
* ██████╗ ██████╗████████╗██╗
|
|
* ██╔════╝ ██╔════╝╚══██╔══╝██║
|
|
* ██║ ███╗██║ ██║ ██║
|
|
* ██║ ██║██║ ██║ ██║
|
|
* ╚██████╔╝╚██████╗ ██║ ███████╗
|
|
* ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝
|
|
* Geophysical Computational Tools & Library (GCTL)
|
|
*
|
|
* Copyright (c) 2023 Yi Zhang (yizhang-geo@zju.edu.cn)
|
|
*
|
|
* GCTL is distributed under a dual licensing scheme. You can redistribute
|
|
* it and/or modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, either version 2
|
|
* of the License, or (at your option) any later version. You should have
|
|
* received a copy of the GNU Lesser General Public License along with this
|
|
* program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* If the terms and conditions of the LGPL v.2. would prevent you from using
|
|
* the GCTL, please consider the option to obtain a commercial license for a
|
|
* fee. These licenses are offered by the GCTL's original author. As a rule,
|
|
* licenses are provided "as-is", unlimited in time for a one time fee. Please
|
|
* send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget
|
|
* to include some description of your company and the realm of its activities.
|
|
* Also add information on how to contact you by electronic and paper mail.
|
|
******************************************************/
|
|
|
|
#ifndef _BOXSORT2D_H
|
|
#define _BOXSORT2D_H
|
|
|
|
#include "../core.h"
|
|
#include "heap_sort.h"
|
|
|
|
namespace gctl
|
|
{
|
|
template <typename A>
|
|
struct cargo
|
|
{
|
|
double x, y;
|
|
A* item;
|
|
};
|
|
|
|
template <typename B>
|
|
struct box_sort_pair
|
|
{
|
|
int id;
|
|
double near_dist;
|
|
std::vector<cargo<B>> *box_ptr;
|
|
};
|
|
|
|
/**
|
|
* @brief This class implements the template class boxes2d, which preforms the 2D distance based sorting of objects.
|
|
*
|
|
* @tparam T template type
|
|
*/
|
|
template <typename T>
|
|
class boxes2d
|
|
{
|
|
public:
|
|
boxes2d();
|
|
boxes2d(const array<double> &xs, const array<double> &ys, const array<T> &items, unsigned int x_bnum, unsigned int y_bnum);
|
|
virtual ~boxes2d();
|
|
void init(const array<double> &xs, const array<double> &ys, const array<T> &items, unsigned int x_bnum, unsigned int y_bnum);
|
|
void get_by_index(unsigned int xid, unsigned int yid, std::vector<T*> &cargo_list);
|
|
void get_by_matrix(unsigned int xid, unsigned int yid, unsigned int extend_row, unsigned int extend_col, std::vector<T*> &cargo_list);
|
|
void get_by_radius(double inx, double iny, double inrad, std::vector<T*> &cargo_list, bool on_boundary = false);
|
|
void get_by_number(double inx, double iny, unsigned int target_num, std::vector<T*> &cargo_list);
|
|
|
|
protected:
|
|
int xbsize, ybsize, item_size;
|
|
double xmin, xmax, ymin, ymax, dx, dy;
|
|
array<std::vector<cargo<T>>> boxes;
|
|
bool initialized;
|
|
|
|
// 以下为get_by_number函数所需变量
|
|
array<box_sort_pair<T>> box_pairs;
|
|
heap_sort<box_sort_pair<T>> boxes_sorter;
|
|
array<double> LocalDist;
|
|
bool box_pairs_initalized;
|
|
};
|
|
|
|
template <typename T>
|
|
boxes2d<T>::boxes2d()
|
|
{
|
|
initialized = false;
|
|
box_pairs_initalized = false;
|
|
}
|
|
|
|
template <typename T>
|
|
boxes2d<T>::boxes2d(const array<double> &xs, const array<double> &ys, const array<T> &items, unsigned int x_bnum, unsigned int y_bnum) : boxes2d()
|
|
{
|
|
init(xs, ys, items, x_bnum, y_bnum);
|
|
}
|
|
|
|
template <typename T>
|
|
boxes2d<T>::~boxes2d()
|
|
{
|
|
if (!boxes.empty())
|
|
{
|
|
for (int i = 0; i < boxes.size(); ++i)
|
|
{
|
|
boxes[i].clear();
|
|
std::vector<cargo<T>>().swap(boxes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void boxes2d<T>::init(const array<double> &xs, const array<double> &ys, const array<T> &items, unsigned int x_bnum, unsigned int y_bnum)
|
|
{
|
|
if (xs.empty() || ys.empty() || items.empty())
|
|
{
|
|
throw domain_error("Empty arrays. From boxes2d<T>::init(...)");
|
|
}
|
|
|
|
if (xs.size() != ys.size() || xs.size() != items.size())
|
|
{
|
|
throw invalid_argument("Arrays' sizes do not match. From boxes2d<T>::init(...)");
|
|
}
|
|
|
|
if (x_bnum <= 0 || y_bnum <= 0)
|
|
{
|
|
throw invalid_argument("Invalid boxes dimensions. From boxes2d<T>::init(...)");
|
|
}
|
|
|
|
xbsize = x_bnum+2;
|
|
ybsize = y_bnum+2;
|
|
item_size = items.size();
|
|
boxes.resize(xbsize*ybsize);
|
|
|
|
xmin = GCTL_BDL_MAX; xmax = GCTL_BDL_MIN;
|
|
ymin = GCTL_BDL_MAX; ymax = GCTL_BDL_MIN;
|
|
for (int i = 0; i < item_size; ++i)
|
|
{
|
|
xmin = std::min(xmin, xs[i]);
|
|
xmax = std::max(xmax, xs[i]);
|
|
ymin = std::min(ymin, ys[i]);
|
|
ymax = std::max(ymax, ys[i]);
|
|
}
|
|
|
|
dx = (xmax-xmin)/x_bnum;
|
|
dy = (ymax-ymin)/y_bnum;
|
|
xmin -= dx; xmax += dx;
|
|
ymin -= dy; ymax += dy;
|
|
|
|
int M, N;
|
|
cargo<T> tmp_cargo;
|
|
for (int i = 0; i < item_size; ++i)
|
|
{
|
|
N = floor((xs[i]-xmin)/dx);
|
|
M = floor((ys[i]-ymin)/dy);
|
|
|
|
tmp_cargo.x = xs[i];
|
|
tmp_cargo.y = ys[i];
|
|
tmp_cargo.item = items.get(i);
|
|
boxes[N + M*xbsize].push_back(tmp_cargo);
|
|
}
|
|
|
|
initialized = true;
|
|
return;
|
|
}
|
|
|
|
template <typename T>
|
|
void boxes2d<T>::get_by_index(unsigned int xid, unsigned int yid, std::vector<T*> &cargo_list)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
throw runtime_error("The boxes2d object is not initialized. From boxes2d<T>::get_by_index(...)");
|
|
}
|
|
|
|
if (xid >= xbsize-2 || yid >= ybsize-2)
|
|
{
|
|
throw out_of_range("Invalid index. From boxes2d<T>::get_by_index(...)");
|
|
}
|
|
|
|
if (!cargo_list.empty()) cargo_list.clear();
|
|
|
|
int box_id = xid+1 + (yid+1)*xbsize;
|
|
if (boxes[box_id].empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
cargo_list.resize(boxes[box_id].size());
|
|
for (int i = 0; i < cargo_list.size(); ++i)
|
|
{
|
|
cargo_list[i] = boxes[box_id][i].item;
|
|
}
|
|
return;
|
|
}
|
|
|
|
template <typename T>
|
|
void boxes2d<T>::get_by_matrix(unsigned int xid, unsigned int yid, unsigned int extend_row, unsigned int extend_col, std::vector<T*> &cargo_list)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
throw runtime_error("The boxes2d object is not initialized. From boxes2d<T>::get_by_matrix(...)");
|
|
}
|
|
|
|
if (xid >= xbsize-2 || yid >= ybsize-2)
|
|
{
|
|
throw out_of_range("Invalid index. From boxes2d<T>::get_by_matrix(...)");
|
|
}
|
|
|
|
if (!cargo_list.empty()) cargo_list.clear();
|
|
|
|
int start_row = yid - extend_row;
|
|
int end_row = yid + extend_row;
|
|
int start_col = xid - extend_col;
|
|
int end_col = xid + extend_col;
|
|
|
|
if (start_row < 0) start_row = 0;
|
|
if (end_row > ybsize-1) end_row = ybsize-1;
|
|
if (start_col < 0) start_col = 0;
|
|
if (end_col > xbsize-1) end_col = xbsize-1;
|
|
|
|
int curr_id;
|
|
for (int i = start_row; i <= end_row; ++i)
|
|
{
|
|
for (int j = start_col; j <= end_col; ++j)
|
|
{
|
|
curr_id = j+xbsize*i;
|
|
for (int k = 0; k < boxes[curr_id].size(); ++k)
|
|
{
|
|
cargo_list.push_back(boxes[curr_id][k].item);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
template <typename T>
|
|
void boxes2d<T>::get_by_radius(double inx, double iny, double inrad, std::vector<T*> &cargo_list, bool on_boundary)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
throw runtime_error("The boxes2d object is not initialized. From boxes2d<T>::get_by_radius(...)");
|
|
}
|
|
|
|
if (inrad <= 0)
|
|
{
|
|
throw invalid_argument("Invalid search radius. From boxes2d<T>::get_by_radius(...)");
|
|
}
|
|
|
|
if (inx+inrad <= xmin+dx || inx-inrad >= xmax-dx || iny+inrad <= ymin+dy || iny-inrad >= ymax-dy)
|
|
{
|
|
throw invalid_argument("Invalid inquire coordinates. From boxes2d<T>::get_by_radius(...)");
|
|
}
|
|
|
|
if (!cargo_list.empty()) cargo_list.clear();
|
|
|
|
// locate the reference box
|
|
int M = floor((iny - ymin)/dy);
|
|
int N = floor((inx - xmin)/dx);
|
|
|
|
// determine the matrix size includes in the circle
|
|
int half_size = 0;
|
|
bool covered = false;
|
|
double right_x, left_x, down_y, up_y;
|
|
do
|
|
{
|
|
right_x = (N+1+half_size)*dx;
|
|
left_x = (N-half_size)*dx;
|
|
up_y = (M+1+half_size)*dy;
|
|
down_y = (M-half_size)*dy;
|
|
|
|
if (inx+inrad <= right_x && inx-inrad >= left_x &&
|
|
iny+inrad <= up_y && iny-inrad >= down_y)
|
|
{
|
|
covered = true;
|
|
}
|
|
else half_size++;
|
|
}
|
|
while (!covered);
|
|
|
|
int start_row = M - half_size;
|
|
int end_row = M + half_size;
|
|
int start_col = N - half_size;
|
|
int end_col = N + half_size;
|
|
|
|
if (start_row < 0) start_row = 0;
|
|
if (end_row > ybsize-1) end_row = ybsize-1;
|
|
if (start_col < 0) start_col = 0;
|
|
if (end_col > xbsize-1) end_col = xbsize-1;
|
|
|
|
int curr_id;
|
|
double dist;
|
|
for (int i = start_row; i <= end_row; ++i)
|
|
{
|
|
for (int j = start_col; j <= end_col; ++j)
|
|
{
|
|
curr_id = j+xbsize*i;
|
|
for (int k = 0; k < boxes[curr_id].size(); ++k)
|
|
{
|
|
dist = sqrt((boxes[curr_id][k].x - inx)*(boxes[curr_id][k].x - inx)
|
|
+ (boxes[curr_id][k].y - iny)*(boxes[curr_id][k].y - iny));
|
|
|
|
if (dist < inrad)
|
|
{
|
|
cargo_list.push_back(boxes[curr_id][k].item);
|
|
}
|
|
else if (on_boundary && dist == inrad)
|
|
{
|
|
cargo_list.push_back(boxes[curr_id][k].item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
template <typename T>
|
|
void boxes2d<T>::get_by_number(double inx, double iny, unsigned int target_num, std::vector<T*> &cargo_list)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
throw runtime_error("The boxes2d object is not initialized. From boxes2d<T>::get_by_number(...)");
|
|
}
|
|
|
|
if (target_num <= 0)
|
|
{
|
|
throw invalid_argument("Invalid target number. From boxes2d<T>::get_by_number(...)");
|
|
}
|
|
|
|
if (!cargo_list.empty()) cargo_list.clear();
|
|
cargo_list.resize(target_num, nullptr);
|
|
|
|
int count = 0;
|
|
if (target_num >= item_size)
|
|
{
|
|
for (int i = 0; i < xbsize*ybsize; ++i)
|
|
{
|
|
for (int j = 0; j < boxes[i].size(); ++j)
|
|
{
|
|
cargo_list[count] = boxes[i][j].item;
|
|
count++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// sort all boxes by the closest possible distance
|
|
double near_x, near_y;
|
|
if (!box_pairs_initalized)
|
|
{
|
|
box_pairs.resize(xbsize*ybsize);
|
|
for (int i = 0; i < ybsize; ++i)
|
|
{
|
|
for (int j = 0; j < xbsize; ++j)
|
|
{
|
|
if (inx >= xmin+j*dx && inx <= xmin+(j+1)*dx)
|
|
{
|
|
near_x = 0.0;
|
|
}
|
|
else if (inx < xmin+j*dx)
|
|
{
|
|
near_x = xmin+j*dx-inx;
|
|
}
|
|
else
|
|
{
|
|
near_x = inx-xmin-(j+1)*dx;
|
|
}
|
|
|
|
if (iny >= ymin+i*dy && iny <= ymin+(i+1)*dy)
|
|
{
|
|
near_y = 0.0;
|
|
}
|
|
else if (iny < ymin+i*dy)
|
|
{
|
|
near_y = ymin+i*dy-iny;
|
|
}
|
|
else
|
|
{
|
|
near_y = iny-ymin-(i+1)*dy;
|
|
}
|
|
|
|
box_pairs[j+i*xbsize].id = j+i*xbsize;
|
|
box_pairs[j+i*xbsize].near_dist = sqrt(near_x*near_x + near_y*near_y);
|
|
box_pairs[j+i*xbsize].box_ptr = boxes.get(j+i*xbsize);
|
|
}
|
|
}
|
|
|
|
box_pairs_initalized = true;
|
|
}
|
|
else
|
|
{
|
|
int M, N;
|
|
for (int i = 0; i < box_pairs.size(); ++i)
|
|
{
|
|
M = box_pairs[i].id/xbsize;
|
|
N = box_pairs[i].id%xbsize;
|
|
|
|
if (inx >= xmin+N*dx && inx <= xmin+(N+1)*dx)
|
|
{
|
|
near_x = 0.0;
|
|
}
|
|
else if (inx < xmin+N*dx)
|
|
{
|
|
near_x = xmin+N*dx-inx;
|
|
}
|
|
else
|
|
{
|
|
near_x = inx-xmin-(N+1)*dx;
|
|
}
|
|
|
|
if (iny >= ymin+M*dy && iny <= ymin+(M+1)*dy)
|
|
{
|
|
near_y = 0.0;
|
|
}
|
|
else if (iny < ymin+M*dy)
|
|
{
|
|
near_y = ymin+M*dy-iny;
|
|
}
|
|
else
|
|
{
|
|
near_y = iny-ymin-(M+1)*dy;
|
|
}
|
|
|
|
box_pairs[i].near_dist = sqrt(near_x*near_x + near_y*near_y);
|
|
}
|
|
}
|
|
|
|
// Sort boxes' pointers by the closest possible distance
|
|
boxes_sorter.execute(box_pairs, [](array<box_sort_pair<T>> &a, int l_id, int r_id)->bool{
|
|
if (a[l_id].near_dist < a[r_id].near_dist) return true;
|
|
else return false;
|
|
});
|
|
|
|
double dist, tmp_dist, maxi_dist = GCTL_BDL_MIN, maxi_record_dist = GCTL_BDL_MAX;
|
|
T *tmp_item, *curr_item;
|
|
|
|
LocalDist.resize(target_num);
|
|
|
|
count = 0;
|
|
for (int i = 0; i < box_pairs.size(); ++i)
|
|
{
|
|
if (!box_pairs[i].box_ptr->empty() && box_pairs[i].near_dist < maxi_record_dist)
|
|
{
|
|
for (int j = 0; j < box_pairs[i].box_ptr->size(); ++j)
|
|
{
|
|
dist = sqrt((box_pairs[i].box_ptr->at(j).x - inx)*(box_pairs[i].box_ptr->at(j).x - inx)
|
|
+ (box_pairs[i].box_ptr->at(j).y - iny)*(box_pairs[i].box_ptr->at(j).y - iny));
|
|
|
|
curr_item = box_pairs[i].box_ptr->at(j).item;
|
|
|
|
if (count < target_num)
|
|
{
|
|
LocalDist[count] = dist;
|
|
cargo_list[count] = curr_item;
|
|
|
|
if (dist > maxi_dist)
|
|
{
|
|
maxi_dist = dist;
|
|
}
|
|
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
maxi_record_dist = maxi_dist;
|
|
|
|
// 这比排序还要快
|
|
if (dist < maxi_record_dist)
|
|
{
|
|
for (int k = 0; k < target_num; ++k)
|
|
{
|
|
if (dist < LocalDist[k])
|
|
{
|
|
tmp_dist = LocalDist[k]; LocalDist[k] = dist; dist = tmp_dist;
|
|
tmp_item = cargo_list[k]; cargo_list[k] = curr_item; curr_item = tmp_item;
|
|
}
|
|
}
|
|
|
|
maxi_dist = GCTL_BDL_MIN;
|
|
for (int k = 0; k < target_num; ++k)
|
|
{
|
|
if (LocalDist[k] > maxi_dist)
|
|
{
|
|
maxi_dist = LocalDist[k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
#endif // _BOXSORT2D_H
|