Skip to content

Commit 49459ff

Browse files
authored
Merge pull request #822 from r-lidar/devel
Devel
2 parents c3ed6b4 + cd3df4a commit 49459ff

File tree

10 files changed

+219
-34
lines changed

10 files changed

+219
-34
lines changed

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Package: lidR
22
Type: Package
33
Title: Airborne LiDAR Data Manipulation and Visualization for Forestry
44
Applications
5-
Version: 4.2.0
5+
Version: 4.2.2
66
Authors@R: c(
77
person("Jean-Romain", "Roussel", email = "[email protected]", role = c("aut", "cre", "cph")),
88
person("David", "Auty", email = "", role = c("aut", "ctb"), comment = "Reviews the documentation"),
@@ -56,7 +56,7 @@ Suggests:
5656
knitr,
5757
rmarkdown
5858
RoxygenNote: 7.3.2
59-
LinkingTo: BH (>= 1.72.0),Rcpp,RcppArmadillo
59+
LinkingTo: BH (>= 1.72.0),Rcpp,RcppArmadillo,Rnanoflann
6060
Encoding: UTF-8
6161
ByteCompile: true
6262
VignetteBuilder: knitr

NEWS.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
If you are viewing this file on CRAN, please check [the latest news on GitHub](https://github.com/r-lidar/lidR/blob/master/NEWS.md) where the formatting is also better
22

3-
## lidR v4.2.0 (Release date: )
3+
## lidR v4.2.2 (Release date: 2025-06-02)
4+
5+
- Use `nanoflann` for knn computation
6+
7+
## lidR v4.2.1 (Release date: 2025-06-02)
8+
9+
- Fix compilation on MacOS
10+
11+
## lidR v4.2.0 (Release date: 2025-05-25)
412

513
**v4.2.0 brings new tools for terrestrial data (TLS, MLS):**
614

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ C_eigen_metrics <- function(las, k, r, coeffs, filter, ncpu) {
113113
.Call(`_lidR_C_eigen_metrics`, las, k, r, coeffs, filter, ncpu)
114114
}
115115

116+
C_fast_knn_eigen_decomposition <- function(las, k, coeffs, ncpu) {
117+
.Call(`_lidR_C_fast_knn_eigen_decomposition`, las, k, coeffs, ncpu)
118+
}
119+
116120
C_connected_component <- function(las, res) {
117121
.Call(`_lidR_C_connected_component`, las, res)
118122
}

R/st_crs.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ setGeneric("epsg<-", function(object, value) standardGeneric("epsg<-"))
269269
#' @rdname st_crs
270270
setMethod("epsg", "LASheader", function(object, ...)
271271
{
272-
if (use_wktcs(object)) warning("This LAS object stores the CRS as WKT. 0 returned; use 'wkt()' instead.", call. = FALSE)
272+
if (use_wktcs(object)) warning("This LAS object stores the CRS as WKT. CRS field might not be correctly populated, yielding uncertain results; use 'wkt()' instead.", call. = FALSE)
273273
return(rlas::header_get_epsg(as.list(object)))
274274
})
275275

src/LAS.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,35 @@
22
#include "Progress.h"
33
#include "myomp.h"
44
#include "SpatialIndex.h"
5+
#include "nanoflann.hpp"
56
#include <limits>
67

78
using namespace lidR;
89

10+
class DataFrameAdaptor
11+
{
12+
public:
13+
std::vector<Rcpp::NumericVector> coords;
14+
size_t dim;
15+
16+
DataFrameAdaptor(const Rcpp::DataFrame& df, std::vector<std::string> col_names)
17+
{
18+
dim = col_names.size();
19+
coords.reserve(dim);
20+
for (const auto& name : col_names)
21+
coords.push_back(df[name]);
22+
}
23+
24+
inline size_t kdtree_get_point_count() const { return coords[0].size(); }
25+
inline double kdtree_get_pt(const size_t idx, const size_t d) const {
26+
return coords[d][idx];
27+
}
28+
template <class BBOX> bool kdtree_get_bbox(BBOX&) const { return false; }
29+
};
30+
31+
using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, DataFrameAdaptor>, DataFrameAdaptor, 3>;
32+
33+
934
bool pnpoly(NumericMatrix polygon, double x, double y)
1035
{
1136
int nvert = polygon.nrow();
@@ -1547,6 +1572,112 @@ List LAS::point_metrics(unsigned int k, double r, DataFrame data, int nalloc, SE
15471572
}
15481573
#endif
15491574

1575+
DataFrame LAS::fast_knn_eigen_decomposition(int k, bool get_coef)
1576+
{
1577+
int n = npoints;
1578+
1579+
NumericVector eigen_largest(n);
1580+
NumericVector eigen_medium(n);
1581+
NumericVector eigen_smallest(n);
1582+
NumericVector coeff0;
1583+
NumericVector coeff1;
1584+
NumericVector coeff2;
1585+
NumericVector coeff3;
1586+
NumericVector coeff4;
1587+
NumericVector coeff5;
1588+
NumericVector coeff6;
1589+
NumericVector coeff7;
1590+
NumericVector coeff8;
1591+
1592+
if (get_coef)
1593+
{
1594+
coeff0 = NumericVector(n);
1595+
coeff1 = NumericVector(n);
1596+
coeff2 = NumericVector(n);
1597+
coeff3 = NumericVector(n);
1598+
coeff4 = NumericVector(n);
1599+
coeff5 = NumericVector(n);
1600+
coeff6 = NumericVector(n);
1601+
coeff7 = NumericVector(n);
1602+
coeff8 = NumericVector(n);
1603+
}
1604+
1605+
bool abort = false;
1606+
1607+
DataFrame df = as<DataFrame>(las.slot("data"));
1608+
DataFrameAdaptor adaptor(df, {"X", "Y", "Z"});
1609+
KDTree tree = KDTree(3, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
1610+
tree.buildIndex();
1611+
1612+
Progress pb(npoints, "Eigen decomposition: ");
1613+
1614+
#pragma omp parallel for num_threads(ncpu)
1615+
for (unsigned int i = 0 ; i < npoints ; i++)
1616+
{
1617+
if (abort) continue;
1618+
if (pb.check_interrupt()) abort = true;
1619+
pb.increment();
1620+
1621+
std::vector<uint32_t> indices(k);
1622+
std::vector<KDTree::DistanceType> dists(k);
1623+
double p[3] = { X[i], Y[i], Z[i] };
1624+
tree.knnSearch(p, k, indices.data(), dists.data());
1625+
1626+
1627+
arma::mat A(k,3);
1628+
arma::mat coeff; // Principle component matrix
1629+
arma::mat score;
1630+
arma::vec latent; // Eigenvalues in descending order
1631+
1632+
for (unsigned int j = 0 ; j < indices.size() ; j++)
1633+
{
1634+
A(j,0) = X[indices[j]];
1635+
A(j,1) = Y[indices[j]];
1636+
A(j,2) = Z[indices[j]];
1637+
}
1638+
1639+
arma::princomp(coeff, score, latent, A);
1640+
1641+
eigen_largest[i] = latent[0];
1642+
eigen_medium[i] = latent[1];
1643+
eigen_smallest[i] = latent[2];
1644+
1645+
if (get_coef)
1646+
{
1647+
coeff0[i] = coeff(0,0);
1648+
coeff1[i] = coeff(0,1);
1649+
coeff2[i] = coeff(0,2);
1650+
coeff3[i] = coeff(1,0);
1651+
coeff4[i] = coeff(1,1);
1652+
coeff5[i] = coeff(1,2);
1653+
coeff6[i] = coeff(2,0);
1654+
coeff7[i] = coeff(2,1);
1655+
coeff8[i] = coeff(2,2);
1656+
}
1657+
}
1658+
1659+
DataFrame out;
1660+
out.push_back(eigen_largest, "eigen_largest");
1661+
out.push_back(eigen_medium, "eigen_medium");
1662+
out.push_back(eigen_smallest, "eigen_smallest");
1663+
1664+
if (get_coef)
1665+
{
1666+
out.push_back(coeff0, "coeff00");
1667+
out.push_back(coeff1, "coeff01");
1668+
out.push_back(coeff2, "coeff02");
1669+
out.push_back(coeff3, "coeff10");
1670+
out.push_back(coeff4, "coeff11");
1671+
out.push_back(coeff5, "coeff12");
1672+
out.push_back(coeff6, "coeff20");
1673+
out.push_back(coeff7, "coeff21");
1674+
out.push_back(coeff8, "coeff22");
1675+
}
1676+
1677+
return out;
1678+
}
1679+
1680+
15501681
DataFrame LAS::eigen_decomposition(int k, double r, bool get_coef)
15511682
{
15521683
int n = std::count(skip.begin(), skip.end(), true);

src/LAS.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class LAS
5050
NumericVector knn_distance(unsigned int k);
5151
NumericVector interpolate_knnidw(NumericVector x, NumericVector y, int k, double p, double rmax);
5252
DataFrame eigen_decomposition(int k, double r, bool get_coef);
53+
DataFrame fast_knn_eigen_decomposition(int k, bool get_coef);
5354

5455
private:
5556
static bool coplanar (arma::vec& latent, arma::mat& coeff, NumericVector& th) { return latent[1] > th[0]*latent[2] && th[1]*latent[1] > latent[0]; }

src/RcppExports.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,19 @@ BEGIN_RCPP
385385
return rcpp_result_gen;
386386
END_RCPP
387387
}
388+
// C_fast_knn_eigen_decomposition
389+
DataFrame C_fast_knn_eigen_decomposition(S4 las, int k, bool coeffs, int ncpu);
390+
RcppExport SEXP _lidR_C_fast_knn_eigen_decomposition(SEXP lasSEXP, SEXP kSEXP, SEXP coeffsSEXP, SEXP ncpuSEXP) {
391+
BEGIN_RCPP
392+
Rcpp::RObject rcpp_result_gen;
393+
Rcpp::traits::input_parameter< S4 >::type las(lasSEXP);
394+
Rcpp::traits::input_parameter< int >::type k(kSEXP);
395+
Rcpp::traits::input_parameter< bool >::type coeffs(coeffsSEXP);
396+
Rcpp::traits::input_parameter< int >::type ncpu(ncpuSEXP);
397+
rcpp_result_gen = Rcpp::wrap(C_fast_knn_eigen_decomposition(las, k, coeffs, ncpu));
398+
return rcpp_result_gen;
399+
END_RCPP
400+
}
388401
// C_connected_component
389402
IntegerVector C_connected_component(S4 las, double res);
390403
RcppExport SEXP _lidR_C_connected_component(SEXP lasSEXP, SEXP resSEXP) {
@@ -690,6 +703,7 @@ static const R_CallMethodDef CallEntries[] = {
690703
{"_lidR_C_isolated_voxel", (DL_FUNC) &_lidR_C_isolated_voxel, 3},
691704
{"_lidR_C_check_gpstime", (DL_FUNC) &_lidR_C_check_gpstime, 2},
692705
{"_lidR_C_eigen_metrics", (DL_FUNC) &_lidR_C_eigen_metrics, 6},
706+
{"_lidR_C_fast_knn_eigen_decomposition", (DL_FUNC) &_lidR_C_fast_knn_eigen_decomposition, 4},
693707
{"_lidR_C_connected_component", (DL_FUNC) &_lidR_C_connected_component, 2},
694708
{"_lidR_C_voxel_id", (DL_FUNC) &_lidR_C_voxel_id, 2},
695709
{"_lidR_fast_table", (DL_FUNC) &_lidR_fast_table, 2},

src/RcppFunction.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ DataFrame C_eigen_metrics(S4 las, int k, double r, bool coeffs, LogicalVector fi
236236
return pt.eigen_decomposition(k, r, coeffs);
237237
}
238238

239+
//[[Rcpp::export(rng = false)]]
240+
DataFrame C_fast_knn_eigen_decomposition(S4 las, int k, bool coeffs, int ncpu)
241+
{
242+
LAS pt(las, ncpu);
243+
return pt.fast_knn_eigen_decomposition(k, coeffs);
244+
}
245+
239246

240247
#include "lidR/Grid3D.h"
241248

src/knn.cpp

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,46 @@
1-
#include "SpatialIndex.h"
1+
#include "nanoflann.hpp"
22
#include "Progress.h"
33

44
#include <Rcpp.h>
55
#include "myomp.h"
66

77
using namespace Rcpp;
8-
using namespace lidR;
8+
9+
class DataFrameAdaptor
10+
{
11+
public:
12+
std::vector<Rcpp::NumericVector> coords;
13+
size_t dim;
14+
15+
DataFrameAdaptor(const Rcpp::DataFrame& df, std::vector<std::string> col_names)
16+
{
17+
dim = col_names.size();
18+
coords.reserve(dim);
19+
for (const auto& name : col_names)
20+
coords.push_back(df[name]);
21+
}
22+
23+
inline size_t kdtree_get_point_count() const { return coords[0].size(); }
24+
inline double kdtree_get_pt(const size_t idx, const size_t d) const {
25+
return coords[d][idx];
26+
}
27+
template <class BBOX> bool kdtree_get_bbox(BBOX&) const { return false; }
28+
};
29+
30+
using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, DataFrameAdaptor>, DataFrameAdaptor, 3>;
931

1032
// [[Rcpp::export]]
1133
List cpp_knn(S4 data, int k, int ncpu)
1234
{
13-
SpatialIndex tree(data);
1435

15-
DataFrame tmp = as<DataFrame>(data.slot("data"));
16-
NumericVector X = tmp["X"];
17-
NumericVector Y = tmp["Y"];
18-
NumericVector Z = tmp["Z"];
36+
DataFrame df = as<DataFrame>(data.slot("data"));
37+
NumericVector X = df["X"];
38+
NumericVector Y = df["Y"];
39+
NumericVector Z = df["Z"];
40+
41+
DataFrameAdaptor adaptor(df, {"X", "Y", "Z"});
42+
KDTree tree = KDTree(3, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
43+
tree.buildIndex();
1944

2045
int npoints = X.size();
2146

@@ -36,19 +61,15 @@ List cpp_knn(S4 data, int k, int ncpu)
3661
if (pb.check_interrupt()) abort = true;
3762
pb.increment();
3863

39-
std::vector<PointXYZ> pts;
40-
PointXYZ p(X[i], Y[i], Z[i]);
41-
tree.knn(p, k, pts);
64+
std::vector<uint32_t> indices(k);
65+
std::vector<KDTree::DistanceType> dists(k);
66+
double p[3] = { X[i], Y[i], Z[i] };
67+
tree.knnSearch(p, k, indices.data(), dists.data());
4268

43-
for (unsigned int j = 1; j < pts.size(); j++)
69+
for (auto j = 1; j < indices.size(); j++)
4470
{
45-
double dx = X[i] - pts[j].x;
46-
double dy = Y[i] - pts[j].y;
47-
double dz = Z[i] - pts[j].z;
48-
double d = std::sqrt(dx*dx+dy*dy+dz*dz);
49-
50-
knn_idx(i,j-1) = pts[j].id+1;
51-
knn_dist(i,j-1) = d;
71+
knn_idx(i,j-1) = indices[j]+1;
72+
knn_dist(i,j-1) = std::sqrt(dists[j]);
5273
}
5374
}
5475

@@ -60,7 +81,10 @@ List cpp_knn(S4 data, int k, int ncpu)
6081
// [[Rcpp::export]]
6182
List cpp_knnx(S4 data, S4 query, int k, int ncpu)
6283
{
63-
SpatialIndex tree(data);
84+
DataFrame temp = as<DataFrame>(data.slot("data"));
85+
DataFrameAdaptor adaptor(temp, {"X", "Y", "Z"});
86+
KDTree tree = KDTree(3, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10));
87+
tree.buildIndex();
6488

6589
DataFrame tmp = as<DataFrame>(query.slot("data"));
6690
NumericVector X = tmp["X"];
@@ -84,19 +108,15 @@ List cpp_knnx(S4 data, S4 query, int k, int ncpu)
84108
if (pb.check_interrupt()) abort = true;
85109
pb.increment();
86110

87-
std::vector<PointXYZ> pts;
88-
PointXYZ p(X[i], Y[i], Z[i]);
89-
tree.knn(p, k, pts);
111+
std::vector<uint32_t> indices(k);
112+
std::vector<KDTree::DistanceType> dists(k);
113+
double p[3] = { X[i], Y[i], Z[i] };
114+
tree.knnSearch(p, k, indices.data(), dists.data());
90115

91-
for (unsigned int j = 0 ; j < pts.size(); j++)
116+
for (auto j = 0; j < indices.size(); j++)
92117
{
93-
double dx = X[i] - pts[j].x;
94-
double dy = Y[i] - pts[j].y;
95-
double dz = Z[i] - pts[j].z;
96-
double d = std::sqrt(dx*dx+dy*dy+dz*dz);
97-
98-
knn_idx(i,j) = pts[j].id+1;
99-
knn_dist(i,j) = d;
118+
knn_idx(i,j) = indices[j]+1;
119+
knn_dist(i,j) = std::sqrt(dists[j]);
100120
}
101121
}
102122

225 KB
Binary file not shown.

0 commit comments

Comments
 (0)