道格拉斯-普克算法(Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法、迭代适应点算法、分裂与合并算法)是将曲线近似表示为一系列点,并减少点的数量的一种算法。该算法的原始类型分别由乌尔斯·拉默(Urs Ramer)于1972年以及大卫·道格拉斯(David Douglas)和托马斯·普克(Thomas Peucker)于1973年提出,并在之后的数十年中由其他学者予以完善。
经典的Douglas-Peucker算法描述如下:
(1)在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;
(2)得到曲线上离该直线段距离最大的点C,计算其与AB的距离d;
(3)比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕。
(4)如果距离大于阈值,则用C将曲线分为两段AC和BC,并分别对两段取信进行1~3的处理。
(5)当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似。
简单的说就是如果矢量曲线出现大量的点的时候,这种算法可以有较好的保真度,能够在保持原来波形的情况下,压缩大量的数据,比如说如果有30w个坐标(x, y),y全部是0的话,道格拉斯算法可以直接压缩成2个点,实际中我试了下,30000多个点,压缩后大概不到一千,大大节省了资源。
这里我要记录一下我根据算法,编写的oc版本的道格拉斯抽稀算法,因为网络上有c语言版本的,有java版本的,就是没有oc版本的。
这个算法应用范围,主要是地图划线,比如步行,跑步,骑行等轨迹记录的优化,因为通常我们使用这些轨迹划线用到的是高德地图,或者百度地图的实时定位每一段时间获取用户当前的经纬度然后完成地图划线,但是通常我们画出来的先是不规则,不圆滑的折线。而道格拉斯算法的作用就是,通过算法计算使你所画的轨迹,圆滑,连续。
.h文件中:
@interface DouglasPeucker : NSObject
- (NSArray*)douglasAlgorithm:(NSArray *)coordinateList threshold:(CGFloat)threshold;
@end
.h中我们给外部一个接口,接口参数coordinateList是你在外部获取足够的点时就要去调用这个接口,我在项目是设置的当高德地图给你返回了5个点的时候就去进行一次纠偏。threshold是临界值,项目里面我给的是1,因为地图中单位是1米,所以这个1代表的是1米。
.m文件中:
/**
* 道格拉斯算法,处理protoArray序列
* 先将首末两点加入calcArray序列中,然后在calcArray序列找出距离首末点连线距离的最大距离值dmax并与阈值进行比较,
* 若大于阈值则将这个点加入calcArray序列,重新遍历calcArray序列。否则将两点间的所有点(protoArray)移除
* @return 返回经过道格拉斯算法后得到的点的序列
*/
- (NSArray*)douglasAlgorithm:(NSArray <LatLngEntity *>*)coordinateList threshold:(CGFloat)threshold{
// 将首末两点加入到序列中
NSMutableArray *points = [NSMutableArray array];
NSMutableArray *list = [NSMutableArray arrayWithArray:coordinateList];
[points addObject:list[0]];
[points addObject:coordinateList[coordinateList.count - 1]];
for (NSInteger i = 0; i<points.count - 1; i++) {
NSUInteger start = (NSUInteger)[list indexOfObject:points[i]];
NSUInteger end = (NSUInteger)[list indexOfObject:points[i+1]];
if ((end - start) == 1) {
continue;
}
NSString *value = [self getMaxDistance:list startIndex:start endIndex:end threshold:threshold];
NSString *dist = [value componentsSeparatedByString:@","][0];
CGFloat maxDist = [dist floatValue];
//大于限差 将点加入到points数组
if (maxDist >= threshold) {
NSInteger position = [[value componentsSeparatedByString:@","][1] integerValue];
[points insertObject:list[position] atIndex:i+1];
// 将循环变量i初始化,即重新遍历points序列
i = -1;
}else {
/**
* 将两点间的点全部删除
*/
NSInteger removePosition = start + 1;
for (NSInteger j = 0; j < end - start - 1; j++) {
[list removeObjectAtIndex:removePosition];
}
}
}
return points;
}
/**
* 根据给定的始末点,计算出距离始末点直线的最远距离和点在coordinateList列表中的位置
* @param startIndex 遍历coordinateList起始点
* @param endIndex 遍历coordinateList终点
* @return maxDistance + "," + position 返回最大距离+"," + 在coordinateList中位置
*/
- (NSString *)getMaxDistance:(NSArray <LatLngEntity *>*)coordinateList startIndex:(NSInteger)startIndex endIndex:(NSInteger)endIndex threshold:(CGFloat)threshold{
CGFloat maxDistance = -1;
NSInteger position = -1;
CGFloat distance = [self getDistance:coordinateList[startIndex] lastEntity:coordinateList[endIndex]];
for(NSInteger i = startIndex; i < endIndex; i++){
CGFloat firstSide = [self getDistance:coordinateList[startIndex] lastEntity:coordinateList[i]];
if(firstSide < threshold){
continue;
}
CGFloat lastSide = [self getDistance:coordinateList[endIndex] lastEntity:coordinateList[i]];
if(lastSide < threshold){
continue;
}
// 使用海伦公式求距离
CGFloat p = (distance + firstSide + lastSide) / 2.0;
CGFloat dis = sqrt(p * (p - distance) * (p - firstSide) * (p - lastSide)) / distance * 2;
if(dis > maxDistance){
maxDistance = dis;
position = i;
}
}
NSString *strMaxDistance = [NSString stringWithFormat:@"%f,%ld", maxDistance,position];
return strMaxDistance;
}
// 两点间距离公式
- (CGFloat)getDistance:(LatLngEntity*)firstEntity lastEntity:(LatLngEntity*)lastEntity{
CLLocation *firstLocation = [[CLLocation alloc] initWithLatitude:[firstEntity getLatitude] longitude:[firstEntity getLangitude]];
CLLocation *lastLocation = [[CLLocation alloc] initWithLatitude:[lastEntity getLatitude] longitude:[lastEntity getLangitude]];
CGFloat distance = [firstLocation distanceFromLocation:lastLocation];
return distance;
}
LatLngEntity是储存点的点经纬度的Model。