首页 > 解决方案 > 沿 3d 线插入点

问题描述

尽管我在R中概述了问题,但欢迎 使用RPython回答。

假设我们在 x,y,z 中有一组点,它们定义了时间 t 向量上的离散路径(或一组连接的线段)。这些路径在 z 中不是单调的。

path <- data.frame(x = c(245, 233, 270, 400, 380),
                   y = c(245, 270, 138, 225, 300),
                   z = c(0, 1.2, 5, 3, 9),
                   t = 1:5)


plot3D::scatter3D(path$x, path$y, path$z, type = "b", bty = "g", 
                  phi = 0, col = "red", pch = 20, ticktype = "detailed")

在此处输入图像描述

如何在 z 中以任意分辨率沿路径进行插值?

例如,假设我想在 z 中保留 1 个单位的分辨率。沿 z 的点为 0、1.2、5、3、9。因此,满足此约束的一种可能解决方案是在 z 中的 1、2、3、4、4、4、5、6、7、8 处进行插值方向,产生下图中的蓝点(标签表示 z 位置):

在此处输入图像描述

最终,我想要蓝点的坐标。我们可以通过对每对点 z 依次求解 3d 中的线方程,然后沿每个线段进行插值来强制求解。但是,我想确保我没有遗漏一些现有的实现或巧妙的技巧。

标签: pythonrline

解决方案


我过去purrr::map只是将它们全部捆绑成一个小标题,但您可以轻松地独立完成它们。唯一的问题是你的xory不是单调的z。然后你需要做第四个变量来指示行顺序,从那里开始变得更加复杂。

library(dplyr)
library(purrr)
library(plotly)

path3d <- data.frame(
  x = c(245, 233, 270, 400, 380),
  y = c(245, 270, 138, 225, 300),
  z = c(0, 1.2, 3, 5, 9)
  )

path3d_interp <- list(
  x = approxfun(path3d$z, path3d$x),
  y = approxfun(path3d$z, path3d$y)
) %>% 
  map(~.x(1:8)) %>% as_tibble() %>% 
  mutate(z = 1:8)
      x     y     z
  <dbl> <dbl> <int>
1  235   266.     1
2  249.  211.     2
3  270   138      3
4  335   182.     4
5  400   225      5
6  395   244.     6
7  390   262.     7
8  385   281.     8
plot_ly(path3d) %>% 
  add_paths(x = ~x, y = ~y, z = ~z) %>% 
  add_markers(
    x = ~x, y = ~y, z = ~z,
    data = path3d_interp
    )

在此处输入图像描述


为非单调更新z

我想不出一种特别优雅的方法来预测x,并且y确切地说,是否每个所需的解决方案都不完全一致z。我建议的总体大纲是根据您的 time 预测的每个变量创建一个funX, funY, and 。然后使用一个非常高分辨率的新值向量并将其子集为. 您永远无法准确找到您正在寻找的值,但您可以将它们近似为所需的任意精度:funZttfunZ(new_t_values)

path3d <- data.frame(
  x = c(245, 233, 270, 400, 380),
  y = c(245, 270, 138, 225, 300),
  z = c(0, 1.2, 5, 3, 9),
  t = 1:5
  )

只是为了对这里发生的事情有一个很好的了解t

library(ggplot2)
ggplot(path3d) + 
  geom_path(aes(t, x), color = "blue") +
  geom_path(aes(t, y), color = "red") +
  geom_path(aes(t, z*50), color = "orange") +
  labs(y = "x, y, z*50")

在此处输入图像描述

path3d这是在( xyz和)的每一列上循环,并t为每个变量创建一个线性插值函数t作为预测变量。

path3d_interp_funs <- 
  map(path3d, ~approxfun(path3d$t, .x))

全方位检查t

现在我们可以在整个范围内制作高分辨率矢量t,这里有 100 万个元素。您可以根据您的精度要求和您的记忆力允许尽可能多地增加它。

new_t_values <- seq(min(path3d$t), max(path3d$t), length.out = 1e6)
 1.000000 1.000004 1.000008 1.000012 1.000016 1.000020 ...

生成的完整路径z

现在我们看看范围内z每个可能的值是什么t

z_candidates <- path3d_interp_funs$z(new_t_values)
0.000000e+00 4.800005e-06 9.600010e-06 1.440001e-05 1.920002e-05 2.400002e-05 ...

测试z最接近您期望的位置z

z所以现在我们取( )的每个期望值1:8,并询问向量的哪个元素与z_candidates它的绝对偏差最小。这将返回我们可以用于子集的索引new_t_values

t_indices <- map_dbl(1:8, ~which.min(abs(z_candidates-.x)))
208334 302632 750000 434211 500001 875000 916667 958333

健全性检查:这些选择t的值是否会产生z您想要的 s?

path3d_interp_funs$z(new_t_values[t_indices])
0.9999994 1.9999958 3.0000020 3.9999986 4.9999960 5.9999970 7.0000060 7.9999910

生成您想要的数据:

因此,让我们遍历每个近似函数,以我们新选择的值评估每个函数t

path3d_interp <- 
  path3d_interp_funs %>%
  map(~.x(new_t_values[t_indices])) %>% 
  as_tibble()
# A tibble: 8 x 4
      x     y     z     t
  <dbl> <dbl> <dbl> <dbl>
1  235.  266. 1.000  1.83
2  241.  242. 2.00   2.21
3  400.  225. 3.00   4.00
4  260.  173. 4.00   2.74
5  270.  138. 5.00   3.00
6  390.  262. 6.00   4.50
7  387.  275. 7.00   4.67
8  383.  287. 8.00   4.83

可视化您的结果

您可以检查以确保这些点确实落在正确的路径上:

ggplot(path3d) + 
  geom_path(aes(t, x), color = "blue") +
  geom_path(aes(t, y), color = "red") +
  geom_path(aes(t, z*50), color = "orange") +
  geom_point(data = path3d_interp, aes(t, x), color = "blue") +
  geom_point(data = path3d_interp, aes(t, y), color = "red") +
  geom_point(data = path3d_interp, aes(t, z*50), color = "orange") +
  geom_text(data = path3d_interp, aes(t, z*50, label = round(z))) +
  labs(y = "x, y, z*50")

在此处输入图像描述

并以 3D 形式查看它们:

plot_ly(path3d) %>% 
  add_paths(x = ~x, y = ~y, z = ~z) %>% 
  add_markers(
    x = ~x, y = ~y, z = ~z,
    data = path3d_interp
    )  %>% 
  add_text(
    x = ~x, y = ~y, z = ~z, text = ~round(z),
    data = path3d_interp
  )

在此处输入图像描述


再次更新:

冒着过于冗长的风险,我想起了这个uniroot函数,它在这个反解方面做得很好:

t_solutions <- map(1:8,
  ~uniroot(
    function(x) path3d_interp_funs$z(x) - .x, 
    interval = range(path3d$t)
  )
) %>% map_dbl("root") 
  1.833333 2.210526 2.473684 2.736842 4.333333 4.500000 4.666667 4.833333

但是,您可能会注意到这些解决方案与以前的方法不同!

在此处输入图像描述

uniroot找到了更接近区间极值的解,而不是在函数改变方向的局部区域。但这带来了一个问题,即t每个所需值可能有多个z值。所以一个更强大的解决方案:

root_finder <- function(f, zero, range, cuts) {

  endpts <- seq(range[1], range[2], length.out = cuts+1)

  range_list <- map2(endpts[-(cuts+1)], endpts[-1], c)

  safe_root <- possibly(uniroot, otherwise = NULL)
  f0 <- function(x) f(x) - zero

  map(range_list, ~safe_root(f0, interval = .x, maxiter = 100)$root) %>% 
    compact() %>% 
    unlist() %>% 
    unique()

}

这个函数需要一个函数,一个新的期望“零”uniroot来求解,一个值范围来测试函数,以及将该范围划分为多少个桶。然后它在每个桶中测试一个解决方案,NULL如果没有找到则返回。然后它会抛出NULLs 并删除任何重复项(例如,如果解决方案恰好位于存储桶的边界)。

root_finder(path3d_interp_funs$z, zero = 4, range = range(path3d$t), cuts = 10)
2.736842 3.500000 4.166667

然后你可以遍历所有你想要z的值来找到t满足它的值。

t_solutions <- map(
    1:8, 
    ~root_finder(path3d_interp_funs$z, zero = .x, range = range(path3d$t), cuts = 100)
  ) %>% unlist()
1.833333 2.210526 2.473684 4.000000 2.736842 3.500000 4.166667 3.000000 4.333333 4.500000 4.666667 4.833333

然后再次将这些t值传递给您之前创建的每个函数,您可以为所有这些函数创建一个数据框。

map(path3d_interp_funs, ~.x(t_solutions)) %>% 
  as_tibble()
       x     y     z     t
   <dbl> <dbl> <dbl> <dbl>
 1  235   266.  1.    1.83
 2  241.  242.  2.    2.21
 3  251.  207.  3.    2.47
 4  400   225   3     4   
 5  260.  173.  4     2.74
 6  335   182.  4     3.5 
 7  397.  238.  4.    4.17
 8  270   138   5     3   
 9  393.  250.  5.00  4.33
10  390   262.  6     4.5 
11  387.  275   7.    4.67
12  383.  288.  8.00  4.83

推荐阅读