【SDU Chart Team - Core】通用坐标系转换

坐标系转换

原始思路

在上周的博客中记录了一种针对当前所定义的一些坐标系间如何进行转换的一个思路:https://blog.csdn.net/cyk0620/article/details/123902973。然而这个解决方案并不具有通用性,并且在转换中,甚至忽略了某些特殊情况间的转换。

问题描述

条件:现有七种坐标系:画布坐标系、组件坐标系及其相对坐标系、点坐标系及其相对坐标系、向量坐标系及其相对坐标系。每个坐标系有明确的几何意义。每个点除了坐标外还拥有一个坐标系。

问题描述:实现一个函数,参数为在某坐标系下的一个点、某个坐标系,返回在后者坐标系下的与前者在几何意义上等价的点。

原解决方案

按照对坐标系几何意义的分析,我们将其分为两种类型:点相关与点无关。其划分标准为坐标系的组成是否有点的参照。
原思路
我们认为没有点的参照的,可以作为基本转换;有点参照的,需要递归转换。于是我们分别讨论了两种情况下的解决方法,并最终使用外部函数递归的手段解决了后者相互转换。

但是这个方案对“点无关”坐标系的定义并不严谨,包括转换思路忽略了部分情况。如组件坐标系到组件坐标系的转换,并没有被考虑在内,实际上组件坐标系是以组件为参照的。

上述思路的最大问题就是只考虑了点作为参照,没有考虑存在其他参照的情况,以至于并不能实现绝对的相互转换。

当然这种讨论的思想从正确性上并没有绝对的问题,并且在解决的运算复杂度上有一定的优势,原因在于参照相同时,我们可以进行直接的转换。加入我们将这种讨论过程补全,最终需要实现 p 2 + ∑ i p ∣ P ∣ i 2 p^2+\sum_i^p|P|_i^2 p2+ipPi2方法,其中 P P P表示参照类型相同的坐标系的集合。

局限性

  1. 坐标系转换与坐标系脱钩

    这是很显然的问题,因为每定义一个新的坐标系,就要重新进行讨论并重新向转换过程中添加新的方法。换言之,我们将坐标系和它们转换的概念进行了分离,使得它们并不同时存在。也就是说,坐标系转换和坐标系并非同时实现的。那么,转换就失去了通用性。

  2. 实现代价高

    加入要实现新的转换,则我们需要对已经编写好的内容添加 p p p个方法,以适应各参照类型下的转换。这样的代价是越来越大的,而且整体规模也是 ∑ i p ∣ P ∣ i 2 \sum_i^p|P|_i^2 ipPi2接近 N 2 N^2 N2,这导致后期很难维护。

通用坐标系转换

问题描述

在原问题上,要求坐标系转换的实现与坐标系的实现同时进行,并且实现尽可能简单。

分析

这是坐标系的类图:
原坐标系UML
其中点、组件为参照,分别被某些坐标系组合;点又组合了坐标系。原思路中,我们着重解决了“点 - 坐标系 - 点”的循环组合,但没有覆盖“组件 - 坐标系”的组合。

实际上,通过此图,我们可以看到坐标系的转换结构与组合结构是同构。即,坐标系转化,实际上是沿着组合结构向上或向下移动。抓住这个特性,我们就能将坐标系转换与坐标系本身绑定,因而解决提出的第一个问题。

两种基本转换

对于第二个问题,我们需要回答具体应该实现怎样的移动过程。

如果仍然按照讨论的思路,向上和向下,我们都需要考虑 p p p种转换,代表不同的参照。为了消除坐标系转换为不同参照时给实现带来的复杂性,我们定义一个基本参照。我们将画布坐标系定义为唯一参照,所有坐标系实现两种基本转换:从画布坐标系转换为该坐标系;从该坐标系转换为画布坐标系。

这样处理以后,对于任意坐标系,我们只需要考虑两个转换方向,以及同一个转换对象。当然,代价就是将 p 2 p^2 p2的成本分摊到了运行时:对于同类型参照下的坐标转换,仍然需要先转换为画布坐标系,再转换为目标,这相比原思路的运算过程更复杂了。

此时坐标系类之间的关系转变为:
坐标系UML

实例1:普通转换

(组件尚未编写定义,故暂不展示)

  • 画布坐标系到画布坐标系

    Point2D from_canvas(const Point2D &p) const override {
       if (p.get_coordinate_system().get_type() != "CAN") {
            throw coordinate_system_mismatch("Point is not in canvas coordinate system");
        }
        return p;
    }
    Point2D to_canvas(const Point2D &p) const override {
        if (p.get_coordinate_system() != *this) {
            throw coordinate_system_mismatch("Point is not in this coordinate system");
        }
        return p;
    }
    

    实际上就只需要实现上述两个方法。并且由于已是画布坐标系,对于这个坐标系而言运算是自反的,我们只需要返回输入的点即可。当然,为保证过程正确,需要先进行检查,要求输入的点满足对应的坐标系。

实例2:组合关系递归

  • 点坐标系到画布坐标系/画布坐标系到点坐标系

    Point2D from_canvas(const Point2D &p) const override {
       if (p.get_coordinate_system().get_type() != "CAN") {
            throw coordinate_system_mismatch("Point is not in canvas coordinate system");
        }
        const Point2D &pf = _origin.get_coordinate_system().from_canvas(p);
        return Point2D(pf.get_x() - _origin.get_x(), pf.get_y() - _origin.get_y(), *this);
    }
    Point2D to_canvas(const Point2D &p) const override {
        if (p.get_coordinate_system() != *this) {
            throw coordinate_system_mismatch("Point is not in this coordinate system");
        }
        const Point2D &pf = Point2D(p.get_x() + _origin.get_x(), p.get_y() + _origin.get_y(), _origin.get_coordinate_system());
        return _origin.get_coordinate_system().to_canvas(pf);
    }
    

    也是实现上述两个方法。这里涉及递归实现,为的是满足“点 - 坐标系 - 点”的递归结构。

关于绝对/相对坐标系转换的优化

如果是坐标系的参照相同,只是绝对和相对的区别,那么它们之间存在直接的转换关系。
在这里插入图片描述
如果此时仍然使用上述思路,则会产生大量冗余转换。为了优化此过程,对于存在绝对与相对之分的几种坐标系提供了它们之间的直接转换接口。例如:

  • 点坐标系到点相对坐标系
    Point2D to_relative(const Point2D &p) const {
       if (p.get_coordinate_system() != *this) {
            throw coordinate_system_mismatch("Point is not in this coordinate system");
        }
        double dx = _vertex.get_x() - _origin.get_x(), dy = _vertex.get_y() - _origin.get_y();
        return Point2D((dx == 0) ? 0 : p.get_x() / dx, (dy == 0) ? 0 : p.get_y() / dy, PointRelativeCoordinateSystem(_origin, _vertex));
    }
    
  • 点相对坐标系到点坐标系
    Point2D to_absolute(const Point2D &p) const {
       if (p.get_coordinate_system() != *this) {
            throw coordinate_system_mismatch("Point is not in this coordinate system");
        }
        double dx = _vertex.get_x() - _origin.get_x(), dy = _vertex.get_y() - _origin.get_y();
        return Point2D(p.get_x() * dx, p.get_y() * dy, PointCoordinateSystem(_origin, _vertex));
    }
    

异常处理

在坐标系转换等过程中,经常需要判断坐标系是否符合预期。如不符合预期,则需要抛出报错。考虑增加自定义异常:coordinate_system_mismatch

/**
* Coordinate system mismatch exception.
*/
class coordinate_system_mismatch: public std::logic_error
{
private:
    std::string _message;
public:
    coordinate_system_mismatch();
    coordinate_system_mismatch(const std::string &str);
    virtual char const* what() const noexcept override;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值