gctl/lib/math/boxsort_sph.h
2025-04-23 12:39:44 +08:00

437 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 _BOXSORT_SPH_H
#define _BOXSORT_SPH_H
#include "heap_sort.h"
namespace gctl
{
template <typename A>
struct cargo_sph
{
double lon, lat;
A* item;
};
template <typename B>
struct box_sort_pair_sph
{
int id;
double near_deg;
std::vector<cargo_sph<B> > *box_ptr;
};
/**
* @brief This class implements the template class boxes_sph, which preforms the spherical degree based sorting of objects.
*
* @tparam T template type
*/
template <typename T>
class boxes_sph
{
public:
boxes_sph();
boxes_sph(const array<double> &lons, const array<double> &lats, const array<T> &items, size_t lon_bnum, size_t lat_bnum);
virtual ~boxes_sph();
void init(const array<double> &lons, const array<double> &lats, const array<T> &items, size_t lon_bnum, size_t lat_bnum);
void get_by_angle(double inlon, double inlat, double indeg, std::vector<T*> &cargo_list, bool on_boundary = false);
void get_by_number(double inlon, double inlat, size_t target_num, std::vector<T*> &cargo_list);
double spherical_angle(double lon1, double lat1, double lon2, double lat2);
protected:
int lon_bsize_, lat_bsize_, item_size_;
double lon_min_, lon_max_, lat_min_, lat_max_, dlon_, dlat_;
array<std::vector<cargo_sph<T> > > boxes_;
bool initialized_;
// 以下为get_by_number函数所需变量
array<box_sort_pair_sph<T> > box_pairs_;
heap_sort<box_sort_pair_sph<T> > boxes_sorter_;
array<double> LocalDeg_;
bool box_pairs_initalized_;
};
template <typename T>
boxes_sph<T>::boxes_sph()
{
initialized_ = false;
box_pairs_initalized_ = false;
}
template <typename T>
boxes_sph<T>::boxes_sph(const array<double> &lons, const array<double> &lats, const array<T> &items, size_t lon_bnum, size_t lat_bnum) : boxes_sph()
{
init(lons, lats, items, lon_bnum, lat_bnum);
}
template <typename T>
boxes_sph<T>::~boxes_sph()
{
if (!boxes_.empty())
{
for (int i = 0; i < boxes_.size(); ++i)
{
boxes_[i].clear();
std::vector<cargo_sph<T>>().swap(boxes_[i]);
}
}
}
template <typename T>
void boxes_sph<T>::init(const array<double> &lons, const array<double> &lats, const array<T> &items, size_t lon_bnum, size_t lat_bnum)
{
if (lons.empty() || lats.empty() || items.empty())
{
throw domain_error("Empty inputs. From boxes_sph<T>::init(...)");
}
if (lons.size() != lats.size() || lons.size() != items.size())
{
throw invalid_argument("Unequal array sizes. From boxes_sph<T>::init(...)");
}
if (lon_bnum == 0 || lat_bnum == 0)
{
throw invalid_argument("Invalid box dimensions. From boxes_sph<T>::init(...)");
}
lon_bsize_ = lon_bnum;
lat_bsize_ = lat_bnum;
item_size_ = items.size();
boxes_.resize(lon_bsize_*lat_bsize_);
lon_min_ = GCTL_BDL_MAX; lon_max_ = GCTL_BDL_MIN;
lat_min_ = GCTL_BDL_MAX; lat_max_ = GCTL_BDL_MIN;
for (size_t i = 0; i < item_size_; i++)
{
lon_min_ = std::min(lon_min_, lons[i]);
lon_max_ = std::max(lon_max_, lons[i]);
lat_min_ = std::min(lat_min_, lats[i]);
lat_max_ = std::max(lat_max_, lats[i]);
}
lon_min_ = std::max(lon_min_, -180.0);
lon_max_ = std::min(lon_max_, 180.0);
lat_min_ = std::max(lat_min_, -90.0);
lat_max_ = std::min(lat_max_, 90.0);
dlon_ = (lon_max_ - lon_min_)/lon_bsize_;
dlat_ = (lat_max_ - lat_min_)/lat_bsize_;
int M, N;
double lon, lat;
cargo_sph<T> tmp_cargo;
for (size_t i = 0; i < item_size_; i++)
{
lon = std::max(-180.0, std::min(lons[i], 180.0));
lat = std::max(-90.0, std::min(lats[i], 90.0));
N = floor((lon - lon_min_)/dlon_);
M = floor((lat - lat_min_)/dlat_);
if (N == lon_bsize_) N--;
if (M == lat_bsize_) M--;
if (M >= 0 && M < lat_bsize_ && N >= 0 && N < lon_bsize_)
{
tmp_cargo.lon = lon;
tmp_cargo.lat = lat;
tmp_cargo.item = items.get(i);
boxes_[N + M*lon_bsize_].push_back(tmp_cargo);
}
}
initialized_ = true;
return;
}
template <typename T>
void boxes_sph<T>::get_by_angle(double inlon, double inlat, double indeg, std::vector<T*> &cargo_list, bool on_boundary)
{
if (!initialized_)
{
throw runtime_error("The boxes_sph object is not initialized_. From boxes_sph<T>::get_by_angle(...)");
}
if (!cargo_list.empty()) cargo_list.clear();
// sort all boxes_ by the closest possible angle
size_t idx;
double cor_lon, cor_lat;
if (!box_pairs_initalized_)
{
box_pairs_.resize(lon_bsize_*lat_bsize_);
for (size_t i = 0; i < lat_bsize_; i++)
{
for (size_t j = 0; j < lon_bsize_; j++)
{
idx = j+i*lon_bsize_;
box_pairs_[idx].id = idx;
box_pairs_[idx].near_deg = 180.0;
box_pairs_[idx].box_ptr = boxes_.get(idx);
cor_lon = lon_min_ + j*dlon_;
cor_lat = lat_min_ + i*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (j+1)*dlon_;
cor_lat = lat_min_ + i*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (j+1)*dlon_;
cor_lat = lat_min_ + (i+1)*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + j*dlon_;
cor_lat = lat_min_ + (i+1)*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
}
}
box_pairs_initalized_ = true;
}
else
{
int M, N;
for (size_t i = 0; i < box_pairs_.size(); i++)
{
M = box_pairs_[i].id/lon_bsize_;
N = box_pairs_[i].id%lon_bsize_;
cor_lon = lon_min_ + N*dlon_;
cor_lat = lat_min_ + M*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (N+1)*dlon_;
cor_lat = lat_min_ + M*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (N+1)*dlon_;
cor_lat = lat_min_ + (M+1)*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + N*dlon_;
cor_lat = lat_min_ + (M+1)*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
}
}
// 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_deg < a[r_id].near_deg) return true;
else return false;
});
double deg;
for (size_t i = 0; i < box_pairs_.size(); i++)
{
if (box_pairs_[i].near_deg < indeg || (on_boundary && box_pairs_[i].near_deg == indeg))
{
for (size_t j = 0; j < box_pairs_[i].box_ptr->size(); j++)
{
deg = spherical_angle(inlon, inlat, box_pairs_[i].box_ptr->at(j).lon, box_pairs_[i].box_ptr->at(j).lat);
if (deg < indeg || (on_boundary && deg == indeg))
{
cargo_list.push_back(box_pairs_[i].box_ptr->at(j).item);
}
}
}
break; // break at the first failed inquire
}
return;
}
template <typename T>
void boxes_sph<T>::get_by_number(double inlon, double inlat, size_t target_num, std::vector<T*> &cargo_list)
{
if (!initialized_)
{
throw runtime_error("The boxes_sph object is not initialized_. From boxes_sph<T>::get_by_number(...)");
}
if (target_num <= 0)
{
throw invalid_argument("Invalid target number. From boxes_sph<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 < lon_bsize_*lat_bsize_; ++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 angle
size_t idx;
double cor_lon, cor_lat;
if (!box_pairs_initalized_)
{
box_pairs_.resize(lon_bsize_*lat_bsize_);
for (size_t i = 0; i < lat_bsize_; i++)
{
for (size_t j = 0; j < lon_bsize_; j++)
{
idx = j+i*lon_bsize_;
box_pairs_[idx].id = idx;
box_pairs_[idx].near_deg = 180.0;
box_pairs_[idx].box_ptr = boxes_.get(idx);
cor_lon = lon_min_ + j*dlon_;
cor_lat = lat_min_ + i*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (j+1)*dlon_;
cor_lat = lat_min_ + i*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (j+1)*dlon_;
cor_lat = lat_min_ + (i+1)*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + j*dlon_;
cor_lat = lat_min_ + (i+1)*dlat_;
box_pairs_[idx].near_deg = std::min(box_pairs_[idx].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
}
}
box_pairs_initalized_ = true;
}
else
{
int M, N;
for (size_t i = 0; i < box_pairs_.size(); i++)
{
M = box_pairs_[i].id/lon_bsize_;
N = box_pairs_[i].id%lon_bsize_;
cor_lon = lon_min_ + N*dlon_;
cor_lat = lat_min_ + M*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (N+1)*dlon_;
cor_lat = lat_min_ + M*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + (N+1)*dlon_;
cor_lat = lat_min_ + (M+1)*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
cor_lon = lon_min_ + N*dlon_;
cor_lat = lat_min_ + (M+1)*dlat_;
box_pairs_[i].near_deg = std::min(box_pairs_[i].near_deg, spherical_angle(inlon, inlat, cor_lon, cor_lat));
}
}
// Sort boxes_' pointers by the closest possible distance
boxes_sorter_.execute(box_pairs_, [](array<box_sort_pair_sph<T>> &a, int l_id, int r_id)->bool{
if (a[l_id].near_deg < a[r_id].near_deg) return true;
else return false;
});
double deg, tmp_deg, maxi_deg = GCTL_BDL_MIN, maxi_record_deg = GCTL_BDL_MAX;
T *tmp_item, *curr_item;
LocalDeg_.resize(target_num);
count = 0;
for (size_t i = 0; i < box_pairs_.size(); i++)
{
if (!box_pairs_[i].box_ptr->empty() && box_pairs_[i].near_deg < maxi_record_deg)
{
for (size_t j = 0; j < box_pairs_[i].box_ptr->size(); j++)
{
deg = spherical_angle(inlon, inlat, box_pairs_[i].box_ptr->at(j).lon, box_pairs_[i].box_ptr->at(j).lat);
curr_item = box_pairs_[i].box_ptr->at(j).item;
if (count < target_num)
{
LocalDeg_[count] = deg;
cargo_list[count] = curr_item;
count++;
maxi_deg = std::max(maxi_deg, deg);
continue;
}
maxi_record_deg = maxi_deg;
// 这比排序还要快
if (deg < maxi_record_deg)
{
for (size_t k = 0; k < target_num; k++)
{
if (deg < LocalDeg_[k])
{
tmp_deg = LocalDeg_[k]; LocalDeg_[k] = deg; deg = tmp_deg;
tmp_item = cargo_list[k]; cargo_list[k] = curr_item; curr_item = tmp_item;
break;
}
}
maxi_deg = GCTL_BDL_MIN;
for (size_t k = 0; k < target_num; k++)
{
maxi_deg = std::max(maxi_deg, LocalDeg_[k]);
}
}
}
}
}
return;
}
template <typename T>
double boxes_sph<T>::spherical_angle(double lon1, double lat1, double lon2, double lat2)
{
double x1, y1, z1, x2, y2, z2;
x1 = cos(GCTL_Pi*lat1/180.0)*cos(GCTL_Pi*lon1/180.0);
y1 = cos(GCTL_Pi*lat1/180.0)*sin(GCTL_Pi*lon1/180.0);
z1 = sin(GCTL_Pi*lat1/180.0);
x2 = cos(GCTL_Pi*lat2/180.0)*cos(GCTL_Pi*lon2/180.0);
y2 = cos(GCTL_Pi*lat2/180.0)*sin(GCTL_Pi*lon2/180.0);
z2 = sin(GCTL_Pi*lat2/180.0);
return acos(std::max(-1.0, std::min(1.0, (x1*x2 + y1*y2 + z1*z2))))*180.0/GCTL_Pi;
}
}
#endif // _BOXSORT_SPH_H