扫地机如何高效的实现轨迹
1、如果有几万个轨迹点, 实时绘制,如何才能保证不卡顿呢?
(考虑分段绘制, 光栅化等)
@interface SXLaserPathLayer : UIView
@property (strong, nonatomic) NSArray <NSString *>*pathHidingType;
@property (strong, nonatomic) LCLaserPathModel *pathModel;
//@property (copy, nonatomic, nullable) NSDictionary *pathData;
@property (strong, nonatomic, readonly) NSArray<LCLaserPathPoint *> *pathPoints;
//地图配置
@property (nonatomic, weak, nullable) LCLaserMapConfig *mapConfig;
@property (nonatomic, assign) BOOL isCleaning; //是否正在清扫
- (void)clearLayer;
/**
* 路径是否可以绘制
*/
- (BOOL)canRender;
/**
* 触发路径绘制
*/
//- (void)renderWithOrigin:(CGPoint)origin;
//- (void)renderWithOrigin:(CGPoint)origin pointScaleUsingFunction:(LCPointConverter NS_NOESCAPE)converter;
- (void)renderWithOrigin:(CGPoint)origin
radius:(CGFloat)radius
offset:(CGPoint)offset;
- (void)renderWithOriginNew:(CGPoint)origin radius:(CGFloat)radius offset:(CGPoint)offset;
- (void)setZoomScale:(CGFloat)scale; //设置缩放因子
@end
#define pathLayerWidth 1.2
#define pathLayerFWidth 10
#define pathLayerNum 2000
@interface SXLaserPathSubLayer : CAShapeLayer
@property (assign, nonatomic) BOOL didAddSubLayer;
@property (copy, nonatomic) NSString *dataColor;
@property (strong, nonatomic) UIBezierPath *bezierPath;
@end
typedef NS_ENUM(NSUInteger, LCLaserPathStyle) {
LCLaserPathNone,
LCLaserPathSkip,
LCLaserPathType,
};
@implementation SXLaserPathSubLayer
- (void)setDataColor:(NSString *)dataColor {
_dataColor = dataColor;
self.strokeColor = [LCColorWithHex(dataColor) CGColor];
self.lineCap = kCALineCapRound;
self.lineJoin = kCALineJoinRound;
self.fillColor = nil;
}
- (void)setBezierPath:(UIBezierPath *)bezierPath {
_bezierPath = bezierPath;
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
}
@end
@interface LCLaserPathLayer ()
@property (strong, nonatomic) NSMutableDictionary<NSString *, LCLaserPathSubLayer *> *layerMap;
@property (strong, nonatomic) LCLaserPathSubLayer *pathSubLayer;
@property (strong, nonatomic) LCLaserPathSubLayer *pathFloorLayer;
@property (strong, nonatomic) NSMutableArray<LCLaserPathSubLayer *> *pathFloorLayers; // 改为数组存储多个floor layer
@property (assign, nonatomic) LCLaserPathStyle pathStyle;
@property (strong, nonatomic, readwrite) NSArray<LCLaserPathPoint *> *pathPoints;
@property (assign, nonatomic) BOOL isSmoothed;
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_queue_t renderQueue;
@property (assign, nonatomic) CGFloat scale;//当前视图缩放比例
@end
@implementation LCLaserPathLayer
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = false;
_layerMap = [NSMutableDictionary dictionary];
_pathFloorLayers = [NSMutableArray array];
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
_renderQueue = dispatch_queue_create("lc.sweeper.laser.path", attr);
_scale = 1.0; /// 初始缩放比例
_isCleaning = NO;
self.semaphore = dispatch_semaphore_create(1);
}
return self;
}
#pragma mark - Public
- (void)clearLayer {
[self cleanLayerMap];
[self cleanPathSubLayer];
}
- (void)cleanLayerMap {
for (NSString *type in self.layerMap) {
LCLaserPathSubLayer *layer = self.layerMap[type];
layer.path = nil;
[layer removeFromSuperlayer];
}
[self.layerMap removeAllObjects];
}
- (void)cleanPathSubLayer {
if(_pathSubLayer) {
_pathSubLayer.path = nil;
[_pathSubLayer removeFromSuperlayer];
_pathSubLayer = nil;
}
if(_pathFloorLayer) {
_pathFloorLayer.path = nil;
[_pathFloorLayer removeFromSuperlayer];
_pathFloorLayer = nil;
}
// 清理所有floor layers
for (LCLaserPathSubLayer *layer in _pathFloorLayers) {
layer.path = nil;
[layer removeFromSuperlayer];
}
[_pathFloorLayers removeAllObjects];
}
- (BOOL)canRender {
BOOL canDraw = false;
do {
if ([self pathWidth] <= 0) {
break;
}
if (_pathModel == nil) {
break;
}
if (_pathPoints == nil) {
break;
}
canDraw = true;
} while (0);
return canDraw;
}
- (void)renderWithOrigin:(CGPoint)origin radius:(CGFloat)radius offset:(CGPoint)offset {
// if (![self canRender]) {
// return;
// }
dispatch_async(self.renderQueue, ^{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[self cleanLayerMap];
#pragma mark - 会崩溃
NSDictionary *tmpPathColors = [[NSDictionary alloc] initWithDictionary:self.pathModel.typeColors copyItems:YES];
if (!_pathSubLayer) {
LCLaserPathSubLayer *layer = [[LCLaserPathSubLayer alloc] init];
layer.dataColor = tmpPathColors.allValues.firstObject;
layer.bezierPath = [UIBezierPath bezierPath];
_pathSubLayer = layer;
} else {
[_pathSubLayer.bezierPath removeAllPoints];
}
#pragma - 涂抹 -- buyi
if (!_pathFloorLayer) {
LCLaserPathSubLayer *layer = [[LCLaserPathSubLayer alloc] init];
layer.dataColor = tmpPathColors.allValues.firstObject;
layer.bezierPath = [UIBezierPath bezierPath];
// 必须设置的参数
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
// 关键:设置光栅化内容的边界和锚点
layer.contentsScale = [UIScreen mainScreen].scale;
layer.needsDisplayOnBoundsChange = YES;
// 优化绘制性能
layer.drawsAsynchronously = YES;
layer.allowsEdgeAntialiasing = YES;
// 防止系统自动清理缓存
if ([layer respondsToSelector:@selector(setContentsFormat:)]) {
// iOS 10+ 使用优化的内容格式
if (@available(iOS 10.0, *)) {
layer.contentsFormat = kCAContentsFormatRGBA8Uint;
}
}
_pathFloorLayer = layer;
} else {
[_pathFloorLayer.bezierPath removeAllPoints];
}
LCLaserPathPoint *nextPoint = nil;
LCLaserPathPoint *tempPoint = [LCLaserPathPoint new];
LCLaserPathPoint *tempNextPoint = [LCLaserPathPoint new];
NSUInteger idx = 0;
#ifdef DEBUG
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
#endif
#pragma mark - 会崩溃
NSInteger mergeFlag = 0;
NSArray<LCLaserPathPoint *> *points = [self.pathPoints copy];
for (LCLaserPathPoint *point in points) {
// 坐标转换 这里外面layer只进行了Scale缩放,所以这里也只进行缩放即可,(偏移量待返回数据时再计算?)
tempPoint.x = (point.x - offset.x) * radius;
tempPoint.y = (point.y - offset.y) * radius;
if ((points.count > 1) && (idx < (points.count - 1))) {
nextPoint = [points objectAtIndex:idx + 1];
if (nextPoint == nil) {
dispatch_semaphore_signal(self.semaphore);
return;
}
NSString *type = [NSString stringWithFormat:@"%@", point.typeNew];
NSString *typeNext = [NSString stringWithFormat:@"%@", nextPoint.typeNew];
if ([type isEqualToString:@"0"]) {
/// 不绘制
} else {
// 下一个路径点
// 坐标转换
tempNextPoint.x = (nextPoint.x - offset.x) * radius;
tempNextPoint.y = (nextPoint.y - offset.y) * radius;
if (![type isEqualToString:@"2"]) {
[_pathSubLayer.bezierPath moveToPoint: CGPointMake(tempPoint.x, tempPoint.y)];
if (![typeNext isEqualToString:@"0"]) {
[_pathSubLayer.bezierPath addLineToPoint: CGPointMake(tempNextPoint.x, tempNextPoint.y)];
}
}
if (![type isEqualToString:@"3"] ) {
[_pathFloorLayer.bezierPath moveToPoint: CGPointMake(tempPoint.x, tempPoint.y)];
if (![typeNext isEqualToString:@"0"]) {
[_pathFloorLayer.bezierPath addLineToPoint: CGPointMake(tempNextPoint.x, tempNextPoint.y)];
}
}
}
}
idx++;
}
#pragma - 清扫轨迹的颜色 和宽度 -- buyi
dispatch_async(dispatch_get_main_queue(), ^{
#pragma - 涂抹
if (!_pathFloorLayer.didAddSubLayer) {
[self.layer addSublayer:_pathFloorLayer];
_pathFloorLayer.didAddSubLayer = YES;
}
if(points.count > 1500 ) {
// _pathFloorLayer.shouldRasterize = YES;
_pathFloorLayer.lineWidth = pathLayerFWidth ;
} else {
// _pathFloorLayer.shouldRasterize = NO;
_pathFloorLayer.lineWidth = pathLayerFWidth;
}
// 创建具有 30% 不透明度的白色
UIColor *whiteWithOpacity = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
_pathFloorLayer.strokeColor = whiteWithOpacity.CGColor;
_pathFloorLayer.path = nil;
_pathFloorLayer.path = [_pathFloorLayer.bezierPath CGPath];
if (!_pathSubLayer.didAddSubLayer) {
[self.layer addSublayer:_pathSubLayer];
_pathSubLayer.didAddSubLayer = YES;
}
if(points.count > 1500 && points.count < 8000) {
_pathSubLayer.lineWidth = [self pathWidth];//[self pathWidth];
_pathSubLayer.shouldRasterize = NO;
} else if(points.count >= 8000) {
_pathSubLayer.lineWidth = [self pathWidth];//[self pathWidth];
_pathSubLayer.shouldRasterize = YES;
}else {
_pathSubLayer.lineWidth = 1.2;
_pathSubLayer.shouldRasterize = NO;
}
// 必须设置的参数
_pathSubLayer.rasterizationScale = [UIScreen mainScreen].scale;
// 关键:设置光栅化内容的边界和锚点
_pathSubLayer.contentsScale = [UIScreen mainScreen].scale;
_pathSubLayer.needsDisplayOnBoundsChange = YES;
// 优化绘制性能
_pathSubLayer.drawsAsynchronously = YES;
_pathSubLayer.allowsEdgeAntialiasing = YES;
// 防止系统自动清理缓存
if ([_pathSubLayer respondsToSelector:@selector(setContentsFormat:)]) {
// iOS 10+ 使用优化的内容格式
if (@available(iOS 10.0, *)) {
_pathSubLayer.contentsFormat = kCAContentsFormatRGBA8Uint;
}
}
// _pathSubLayer.allowsEdgeAntialiasing = YES;
// // 其他优化设置
// _pathSubLayer.drawsAsynchronously = YES; // 异步绘制,提高性能
// _pathSubLayer.rasterizationScale = [UIScreen mainScreen].scale;
_pathSubLayer.strokeColor = [UIColor whiteColor].CGColor;
_pathSubLayer.path = nil;
_pathSubLayer.path = [_pathSubLayer.bezierPath CGPath];
dispatch_semaphore_signal(self.semaphore);
#ifdef DEBUG
NSLog(@"轨迹绘制结束 path: %f ms count: %d", (CFAbsoluteTimeGetCurrent() - start) * 1000, points.count);
#endif
});
});
}
- (void)renderWithOriginNew:(CGPoint)origin radius:(CGFloat)radius offset:(CGPoint)offset {
dispatch_async(self.renderQueue, ^{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[self cleanLayerMap];
NSDictionary *tmpPathColors = [[NSDictionary alloc] initWithDictionary:self.pathModel.typeColors copyItems:YES];
if (!_pathSubLayer) {
LCLaserPathSubLayer *layer = [[LCLaserPathSubLayer alloc] init];
layer.dataColor = tmpPathColors.allValues.firstObject;
layer.bezierPath = [UIBezierPath bezierPath];
_pathSubLayer = layer;
// 设置细轨迹图层在宽轨迹之上
_pathSubLayer.zPosition = 1.0;
} else {
[_pathSubLayer.bezierPath removeAllPoints];
}
// 清理并重新创建floor layers
for (LCLaserPathSubLayer *layer in _pathFloorLayers) {
layer.path = nil;
[layer removeFromSuperlayer];
}
[_pathFloorLayers removeAllObjects];
LCLaserPathPoint *nextPoint = nil;
LCLaserPathPoint *tempPoint = [LCLaserPathPoint new];
LCLaserPathPoint *tempNextPoint = [LCLaserPathPoint new];
NSUInteger idx = 0;
#ifdef DEBUG
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
#endif
NSArray<LCLaserPathPoint *> *points = [self.pathPoints copy];
// 分段绘制宽轨迹 - 按一定点数分段
NSInteger segmentPointCount = pathLayerNum; // 每2000个点分为一段
NSInteger segmentCount = ceil((CGFloat)points.count / segmentPointCount);
// 创建分段layer
for (NSInteger i = 0; i < segmentCount; i++) {
LCLaserPathSubLayer *floorLayer = [[LCLaserPathSubLayer alloc] init];
floorLayer.dataColor = tmpPathColors.allValues.firstObject;
floorLayer.bezierPath = [UIBezierPath bezierPath];
floorLayer.lineWidth = pathLayerFWidth;//[self calculateFloorLineWidthForPointsCount:points.count];
floorLayer.shouldRasterize = YES;
// 创建具有 30% 不透明度的白色
UIColor *whiteWithOpacity = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
floorLayer.strokeColor = whiteWithOpacity.CGColor;
// 设置宽轨迹图层在细轨迹之下
floorLayer.zPosition = 0.0;
// floorLayer.compositingFilter = nil; // 默认就是正常混合模式
// // 2. 对于图层,可以设置 shouldRasterize 来优化渲染
// floorLayer.allowsEdgeAntialiasing = YES;
//
// // 其他优化设置
// floorLayer.drawsAsynchronously = YES; // 异步绘制,提高性能
// floorLayer.shouldRasterize = YES;
// floorLayer.rasterizationScale = [UIScreen mainScreen].scale;
// 必须设置的参数
floorLayer.shouldRasterize = YES;
floorLayer.rasterizationScale = [UIScreen mainScreen].scale;
// 关键:设置光栅化内容的边界和锚点
floorLayer.contentsScale = [UIScreen mainScreen].scale;
floorLayer.needsDisplayOnBoundsChange = YES;
// 优化绘制性能
floorLayer.drawsAsynchronously = YES;
floorLayer.allowsEdgeAntialiasing = YES;
// 防止系统自动清理缓存
if ([floorLayer respondsToSelector:@selector(setContentsFormat:)]) {
// iOS 10+ 使用优化的内容格式
if (@available(iOS 10.0, *)) {
floorLayer.contentsFormat = kCAContentsFormatRGBA8Uint;
}
}
[_pathFloorLayers addObject:floorLayer];
}
NSInteger currentSegment = 0;
LCLaserPathSubLayer *currentFloorLayer = _pathFloorLayers.firstObject;
for (LCLaserPathPoint *point in points) {
// 检查是否需要切换到下一个segment
if (idx > 0 && idx % segmentPointCount == 0 && currentSegment < segmentCount - 1) {
currentSegment++;
if (currentSegment < _pathFloorLayers.count) {
currentFloorLayer = _pathFloorLayers[currentSegment];
}
}
// 坐标转换
tempPoint.x = (point.x - offset.x) * radius;
tempPoint.y = (point.y - offset.y) * radius;
if ((points.count > 1) && (idx < (points.count - 1))) {
nextPoint = [points objectAtIndex:idx + 1];
if (nextPoint == nil) {
dispatch_semaphore_signal(self.semaphore);
return;
}
NSString *type = [NSString stringWithFormat:@"%@", point.typeNew];
NSString *typeNext = [NSString stringWithFormat:@"%@", nextPoint.typeNew];
if ([type isEqualToString:@"0"]) {
} else {
// 下一个路径点
// 坐标转换
tempNextPoint.x = (nextPoint.x - offset.x) * radius;
tempNextPoint.y = (nextPoint.y - offset.y) * radius;
if (![type isEqualToString:@"2"]) {
[_pathSubLayer.bezierPath moveToPoint: CGPointMake(tempPoint.x, tempPoint.y)];
if (![typeNext isEqualToString:@"0"]) {
[_pathSubLayer.bezierPath addLineToPoint: CGPointMake(tempNextPoint.x, tempNextPoint.y)];
}
}
if (![type isEqualToString:@"3"] ) {
[currentFloorLayer.bezierPath moveToPoint: CGPointMake(tempPoint.x, tempPoint.y)];
if (![typeNext isEqualToString:@"0"]) {
[currentFloorLayer.bezierPath addLineToPoint: CGPointMake(tempNextPoint.x, tempNextPoint.y)];
}
}
}
}
idx++;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 先添加宽轨迹图层(底层)
for (LCLaserPathSubLayer *floorLayer in self.pathFloorLayers) {
if (!floorLayer.didAddSubLayer) {
[self.layer addSublayer:floorLayer];
floorLayer.didAddSubLayer = YES;
}
floorLayer.path = [floorLayer.bezierPath CGPath];
}
// 然后添加细轨迹图层(上层)
if (!_pathSubLayer.didAddSubLayer) {
[self.layer addSublayer:_pathSubLayer];
_pathSubLayer.didAddSubLayer = YES;
}
if(points.count > 1500 && points.count < 10000) {
_pathSubLayer.lineWidth = [self pathWidth];//[self pathWidth];
_pathSubLayer.shouldRasterize = NO;
} else if(points.count >= 10000) {
_pathSubLayer.lineWidth = [self pathWidth];//[self pathWidth];
_pathSubLayer.shouldRasterize = YES;
}else {
_pathSubLayer.lineWidth = 1.2;
_pathSubLayer.shouldRasterize = NO;
}
// 必须设置的参数
// _pathSubLayer.shouldRasterize = YES;
_pathSubLayer.rasterizationScale = [UIScreen mainScreen].scale;
// 关键:设置光栅化内容的边界和锚点
_pathSubLayer.contentsScale = [UIScreen mainScreen].scale;
_pathSubLayer.needsDisplayOnBoundsChange = YES;
// 优化绘制性能
_pathSubLayer.drawsAsynchronously = YES;
_pathSubLayer.allowsEdgeAntialiasing = YES;
// 防止系统自动清理缓存
if ([_pathSubLayer respondsToSelector:@selector(setContentsFormat:)]) {
// iOS 10+ 使用优化的内容格式
if (@available(iOS 10.0, *)) {
_pathSubLayer.contentsFormat = kCAContentsFormatRGBA8Uint;
}
}
// _pathSubLayer.allowsEdgeAntialiasing = YES;
// // 其他优化设置
// _pathSubLayer.drawsAsynchronously = YES; // 异步绘制,提高性能
// _pathSubLayer.shouldRasterize = YES;
// _pathSubLayer.rasterizationScale = [UIScreen mainScreen].scale;
//
_pathSubLayer.strokeColor = [UIColor whiteColor].CGColor;
_pathSubLayer.path = [_pathSubLayer.bezierPath CGPath];
dispatch_semaphore_signal(self.semaphore);
#ifdef DEBUG
NSLog(@"轨迹绘制结束 path: %f ms count: %d", (CFAbsoluteTimeGetCurrent() - start) * 1000, points.count);
#endif
});
});
}
// 计算floor layer的线宽
- (CGFloat)calculateFloorLineWidthForPointsCount:(NSInteger)count {
if(count > 1000 && count < 6000) {
return pathLayerFWidth;
} else if(count > 6000) {
return pathLayerFWidth;
} else {
return pathLayerFWidth;
}
}
//缩放地图时,保持路径宽度为原始视觉宽度
- (void)setZoomScale:(CGFloat)scale {
if (scale >= 1.0) {
_scale = scale;
[CATransaction begin];
[CATransaction setDisableActions:true];
for (NSString *type in self.layerMap) {
LCLaserPathSubLayer *layer = self.layerMap[type];
// layer.lineWidth = [self pathWidth];
layer.lineWidth = pathLayerWidth;
}
[CATransaction commit];
}
}
- (CGFloat)pathWidth {
CGFloat width = self.mapConfig.kMinPathWidth;
CGFloat pixelScale = [[UIScreen mainScreen] scale];
CGFloat mapScale = 1;
if (self.mapConfig) {
mapScale = self.mapConfig.orignalScale * self.scale * pixelScale;
if (mapScale <= self.mapConfig.minZoomScale) {
width = self.mapConfig.kMinPathWidth;
} else if (mapScale >= self.mapConfig.maxZoomScale) {
width = self.mapConfig.kMaxPathWidth;
} else {
CGFloat scaleRange = self.mapConfig.maxZoomScale - self.mapConfig.minZoomScale;
CGFloat widthRange = self.mapConfig.kMaxPathWidth - self.mapConfig.kMinPathWidth;
width = mapScale / scaleRange * widthRange + self.mapConfig.kMinPathWidth;
}
//因为已经tranform缩放过,所以要除回去
width = width / self.scale * 0.8;
}
//NSLog(@"caffe0421 路径宽度: %f 像素宽度:%f", width, width * self.scale * pixelScale);
return width;
}
#pragma mark - Setter
- (void)setPathModel:(LCLaserPathModel *)pathModel
{
if (_pathModel) {
if (_pathModel.pathId != pathModel.pathId) {
[self clearLayer];
}
}
_pathModel = pathModel;
_pathPoints = _pathModel.pointArr;
}
@end
