Clipper库 坐标圆整和精度控制

坐标圆整造成的问题

在Clipper库中顶点(IntPoint)的坐标使用的是整数类型, 目的是为了保持数字的鲁棒性,所以用整数类型来存储坐标,而不是我们常见的浮点数类型(浮点存在不精确性)。然而坐标圆整会带来如下两个问题:

  • 参与计算的顶点位置和真实的顶点位置存在一定偏差。对于一些数值极小的或相差极小的顶点难以处理。

  • 造成微小的自交。

image-20221217110430264

上图中,三个黑点是黄色和紫色多边形区域的实际交点,三个红点是三个黑点经坐标圆整之后的位置,黑点与红点之间的虚线表示对应关系。经过圆整后,造成了一个微小的自交区域。

解决方案

对于第一个问题,官方给出的解决方案为适当的缩放。同时指出Clipper库能接受范围为±0x3fffffffffffffffff (4.6e+18)的整数坐标值,支持扩展到非常高的精度 。这说明我们可以放心得对极小数值或极小数值差进行一个较大倍数的缩放,不会造成算法上的问题。

我们可以简单写一个装饰器,装饰我们用于多边形处理的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import math

def numeric_scaling(digits: 'int > 0' = 7, *, pos: list): # 必需参数pos:指定缩放参数
def decorate(func):
def scaled(*_args):
__args = list(_args)
f = math.pow(10, digits) # 数值精度,默认为7位小数
for p in pos:
if isinstance(_args[p], int) or isinstance(_args[p], float):
__args[p] *= f
elif isinstance(_args[p], list):
__args[p] = [[tuple(map(lambda x: x * f, pt)) for pt in path] for path in _args[p]]
else:
pass
_result = func(*tuple(__args)) # _result is list of paths
_result = [[tuple(map(lambda x: x / f, pt)) for pt in path] for path in _result]
return _result

return scaled

return decorate

用例:

1
2
3
4
5
6
7
8
9
10
11
12
from pyclipper import *

@numeric_scaling(digits = 7, pos = [0, 1])
def offset(polys, delta, jt = JT_SQUARE): # 偏置函数,输人多边形:[[[pt1_x,pt2_y],...,[ptN_x,ptN_y]],[...],...]
pco = PyclipperOffset()
pco.AddPaths(polys, jt, ET_CLOSEDPOLYGON)
sln = pco.Execute(delta) # 偏置距离delta
return sln # 返回Polyline列表

if __name__ == '__main__':
polys = [[(0, 0), (100, 0), (100, 100), (0, 100)]]
print(offset(polys, -7.5)) # 向内偏置7.5

上面代码中 digits 为数值精度,其大小同样为输出结果中点坐标的小数位数。

对于第二个问题,官方给出两种解决方案:

  • 使用 CleanPolygons 函数去除。
  • ClipperStrictlySimple 属性设置为true