You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
869 lines
33 KiB
869 lines
33 KiB
![]()
2 years ago
|
//
|
||
|
// TrackMapView.m
|
||
|
// LekangGuard
|
||
|
//
|
||
|
// Created by ecell on 2022/12/15.
|
||
|
//
|
||
|
|
||
|
#import "TrackMapView.h"
|
||
|
#import "TracingPoint.h"
|
||
|
#import "CustomAnnotationView.h"
|
||
|
#import "Util.h"
|
||
|
#import "MKPointAnnotation+NewTemp.h"
|
||
|
//高德
|
||
|
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||
|
#import <AMapSearchKit/AMapSearchKit.h>
|
||
|
|
||
|
#define kAnimationDuration 3
|
||
|
#define kSpeed 50 // 每秒多少像素
|
||
|
|
||
|
//关键帧旋转动画
|
||
|
#define RotationAnimationKey @"transform.rotation.z"
|
||
|
//关键帧位移动画
|
||
|
#define PositionAnimationKey @"position"
|
||
|
//关键帧划线动画
|
||
|
#define StrokeEndAnimationKey @"strokeEnd"
|
||
|
|
||
|
@interface TrackMapView ()<CAAnimationDelegate,MKMapViewDelegate,AMapSearchDelegate>
|
||
|
|
||
|
|
||
|
@property (nonatomic ,strong) UIView *topView;
|
||
|
|
||
|
@property(strong ,nonatomic) MKMapView *mapView;
|
||
|
|
||
|
//动画持续的时间 (不超过5秒) 时速 每秒 320
|
||
|
@property(nonatomic,assign) NSTimeInterval durationTime;
|
||
|
// 路线动画
|
||
|
@property(nonatomic,strong) CAAnimation *shapeLayerAnimation;
|
||
|
// 头部光点动画
|
||
|
@property(nonatomic,strong) CAKeyframeAnimation *headLayerAnimation;
|
||
|
|
||
|
@property(nonatomic,assign) NSInteger currentIndex ;
|
||
|
//路径点的指针数组
|
||
|
@property(nonatomic,assign) CGPoint *pathPoints;
|
||
|
//路径点的指针数组
|
||
|
@property(nonatomic,strong) NSMutableArray <TracingPoint *>*pathPointsModels;
|
||
|
// angele数组 每个点的指向
|
||
|
@property(nonatomic,strong) NSMutableArray <NSNumber *>*angeleValues;
|
||
|
|
||
|
@property(nonatomic,assign) double SumJuli;
|
||
|
@property(nonatomic,strong) MKPolyline *routeLine;
|
||
|
@property(nonatomic,strong) MKPolylineRenderer *routeLineView;
|
||
|
|
||
|
@property(nonatomic,strong) NSMutableArray<MKPointAnnotation*>* pointAnnotationArr;
|
||
|
//
|
||
|
@property(nonatomic,strong) NSArray *animations;
|
||
|
@property(nonatomic,strong) NSArray *juliArr;
|
||
|
/**
|
||
|
轨迹回放的layer
|
||
|
*/
|
||
|
@property(nonatomic,strong) CAShapeLayer * shapeLayer;
|
||
|
/**
|
||
|
头部的光点
|
||
|
*/
|
||
|
//@property(nonatomic,strong) CAShapeLayer * headView;
|
||
|
@property(nonatomic,strong) UIImageView * headView;
|
||
|
|
||
|
@property(nonatomic,strong) NSArray<UIBezierPath*>* beziePaths;
|
||
|
/**
|
||
|
轨迹回放图层
|
||
|
*/
|
||
|
@property(nonatomic,strong) UIView *trackView;
|
||
|
|
||
|
@property (nonatomic, strong) AMapSearchAPI *searchAPI;
|
||
|
|
||
|
///
|
||
|
@property (nonatomic ,weak) UIButton *startAndStopBtn;
|
||
|
|
||
|
|
||
|
@property (nonatomic ,assign) UIImageView *mapImg;
|
||
|
|
||
|
@property (nonatomic ,weak) UILabel *locationTypeLabel;
|
||
|
|
||
|
@property (nonatomic ,weak) UILabel *locationAddressLabel;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation TrackMapView
|
||
|
|
||
|
- (instancetype)initWithFrame:(CGRect)frame
|
||
|
{
|
||
|
self = [super initWithFrame:frame];
|
||
|
if (self)
|
||
|
{
|
||
|
self.frame = frame;
|
||
|
self.backgroundColor = KKWhiteColorColor;
|
||
|
[self viewDidLoads];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (UIView *)topView
|
||
|
{
|
||
|
if (!_topView)
|
||
|
{
|
||
|
_topView = [UIView new];
|
||
|
_topView.hidden = YES;
|
||
|
_topView.backgroundColor = KKWhiteColorColor;
|
||
|
_topView.frame = CGRectMake(0, -Adapted(55), SCREEN_WIDTH, Adapted(55));
|
||
|
|
||
|
UIImageView *mapImg = [UICommon ui_imageView:CGRectZero fileName:@"icon_base_station"];
|
||
|
self.mapImg = mapImg;
|
||
|
[_topView addSubview:mapImg];
|
||
|
[mapImg mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
|
make.left.equalTo(_topView).offset(15);
|
||
|
make.bottom.equalTo(_topView.mas_centerY);
|
||
|
make.size.mas_equalTo(CGSizeMake(AdaptedHeight(self.mapImg.image.size.width), AdaptedHeight(self.mapImg.image.size.height)));
|
||
|
}];
|
||
|
|
||
|
UILabel *locationTypeLabel = [UICommon ui_label:CGRectZero lines:0 align:NSTextAlignmentCenter font:FontADA_(11) textColor:KKGrey121 text:@"LBS" Radius:0];
|
||
|
self.locationTypeLabel = locationTypeLabel;
|
||
|
[_topView addSubview:locationTypeLabel];
|
||
|
[locationTypeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
|
make.centerX.equalTo(mapImg);
|
||
|
make.top.equalTo(mapImg.mas_bottom).offset(3);
|
||
|
}];
|
||
|
|
||
|
UILabel *locationAddressLabel = [UICommon ui_label:CGRectZero lines:0 align:NSTextAlignmentLeft font:FontADA_(13) textColor:KKTextBlackColor text:@"" Radius:0];
|
||
|
self.locationAddressLabel = locationAddressLabel;
|
||
|
[_topView addSubview:locationAddressLabel];
|
||
|
[locationAddressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
|
make.centerY.equalTo(_topView);
|
||
|
make.left.equalTo(mapImg.mas_right).offset(10);
|
||
|
make.right.equalTo(_topView.mas_right).inset(15);
|
||
|
}];
|
||
|
|
||
|
}
|
||
|
return _topView;
|
||
|
}
|
||
|
|
||
|
- (void)topViewShowAndHidden:(NSInteger)isShow
|
||
|
{
|
||
|
self.topView.hidden = isShow == 1 ? NO : YES;
|
||
|
[UIView animateWithDuration:0.5 animations:^{
|
||
|
self.topView.frame = CGRectMake(0,isShow == 1 ? 0 : -Adapted(55), SCREEN_WIDTH, Adapted(55));
|
||
|
} completion:^(BOOL finished) {
|
||
|
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)viewDidLoads
|
||
|
{
|
||
|
[self addSubview:self.mapView];
|
||
|
[self addSubview:self.topView];
|
||
|
self.searchAPI = [[AMapSearchAPI alloc] init];
|
||
|
self.searchAPI.delegate = self;
|
||
|
|
||
|
UIButton *startAndStopBtn = [UICommon ui_buttonSimple:CGRectZero font:Font_(0) normalColor:KKWhiteColorColor normalText:@"" click:^(UIButton *btn) {
|
||
|
btn.selected = !btn.selected;
|
||
|
[self startAndStopBtnAction:btn];
|
||
|
}];
|
||
|
[startAndStopBtn setImage:ImageName_(@"icon_start") forState:UIControlStateNormal];
|
||
|
[startAndStopBtn setImage:ImageName_(@"icon_stop") forState:UIControlStateSelected];
|
||
|
startAndStopBtn.backgroundColor = KKWhiteColorColor;
|
||
|
startAndStopBtn.layer.cornerRadius = 5;
|
||
|
startAndStopBtn.layer.masksToBounds = YES;
|
||
|
self.startAndStopBtn = startAndStopBtn;
|
||
|
[self addSubview:startAndStopBtn];
|
||
|
[startAndStopBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
|
make.right.equalTo(self.mas_right).inset(20);
|
||
|
make.top.equalTo(self).offset(Adapted(55)+30);
|
||
|
make.size.mas_equalTo(CGSizeMake(Adapted(48), Adapted(48)));
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
|
||
|
//MARK: 重新加载地图轨迹数据
|
||
|
- (void)loadWithInfoTrack:(NSArray *)mapPoints
|
||
|
{
|
||
|
self.startAndStopBtn.selected = NO;
|
||
|
[self startAndStopBtnAction:self.startAndStopBtn];
|
||
|
[self.mapView removeAnnotations:self.pointAnnotationArr];
|
||
|
[self.mapView removeOverlay:self.routeLine];
|
||
|
self.trackView = nil;
|
||
|
self.routeLine = nil;
|
||
|
self.routeLineView = nil;
|
||
|
self.pointAnnotationArr = nil;
|
||
|
if(self.headView){
|
||
|
[self.headView.layer removeAllAnimations];
|
||
|
self.headView = nil;
|
||
|
}
|
||
|
if(self.pathPoints)
|
||
|
{
|
||
|
free(self.pathPoints);
|
||
|
self.pathPoints = nil;
|
||
|
}
|
||
|
|
||
|
[self addLocate:mapPoints];
|
||
|
if(mapPoints.count > 1)
|
||
|
{
|
||
|
self.startAndStopBtn.selected = YES;
|
||
|
[self startAndStopBtnAction:self.startAndStopBtn];
|
||
|
}
|
||
|
//默认选中 起点
|
||
|
CustomAnnotationView *tempView = [CustomAnnotationView new];
|
||
|
tempView.annotation = self.pointAnnotationArr.lastObject;
|
||
|
[self mapView:self.mapView didSelectAnnotationView:tempView];
|
||
|
}
|
||
|
|
||
|
|
||
|
-(void)startAndStopBtnAction:(UIButton *)sender
|
||
|
{
|
||
|
if(sender.isSelected)
|
||
|
{
|
||
|
[self.mapView removeAnnotations:self.pointAnnotationArr];
|
||
|
[self.mapView removeOverlay:self.routeLine];
|
||
|
self.currentIndex = 0;
|
||
|
//经纬度的区域坐标
|
||
|
if(self.routeLine)
|
||
|
{
|
||
|
MKMapRect mapShowRect = [self.routeLine boundingMapRect];
|
||
|
NSLog(@"停止按钮:mapShowRect X: %f Y:%f Width: %f, Height: %f", mapShowRect.origin.x,mapShowRect.origin.y,mapShowRect.size.width,mapShowRect.size.height);
|
||
|
//显示区域
|
||
|
[self.mapView setVisibleMapRect:mapShowRect edgePadding:UIEdgeInsetsMake(50, 50, 50, 50) animated:NO];
|
||
|
}
|
||
|
[self initShapeLayerWithPath:[self pathForPoints:self.pathPoints count:self.pointAnnotationArr.count]];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// [self.mapView removeAnnotations:self.pointAnnotationArr];
|
||
|
// [self.mapView removeOverlay:self.routeLine];
|
||
|
|
||
|
[self.trackView removeFromSuperview];
|
||
|
self.trackView = nil;
|
||
|
self.currentIndex = 0;
|
||
|
[self.mapView addAnnotations:self.pointAnnotationArr];
|
||
|
}
|
||
|
[sender setSelected:!sender.isSelected];
|
||
|
self.mapView.userInteractionEnabled = sender.isSelected;
|
||
|
}
|
||
|
#pragma mark - 构造shapeLayer
|
||
|
- (void)initShapeLayerWithPath:(CGPathRef)path
|
||
|
{
|
||
|
if(_shapeLayer)
|
||
|
{
|
||
|
[self.shapeLayer removeAllAnimations];
|
||
|
[self.shapeLayer removeFromSuperlayer];
|
||
|
[self.headView.layer removeAllAnimations];
|
||
|
[self.headView.layer removeFromSuperlayer];
|
||
|
[self.headView removeFromSuperview];
|
||
|
self.shapeLayer = nil;
|
||
|
self.headView = nil;
|
||
|
|
||
|
}
|
||
|
self.shapeLayer = [[CAShapeLayer alloc] init];
|
||
|
self.shapeLayer.frame = self.trackView.bounds;
|
||
|
self.shapeLayer.frame = self.mapView.bounds;
|
||
|
self.shapeLayer.strokeColor = KKMainColor.CGColor;
|
||
|
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
|
||
|
self.shapeLayer.lineJoin = kCALineJoinRound;
|
||
|
self.shapeLayer.lineCap = kCALineCapRound;
|
||
|
self.shapeLayer.lineWidth = 3;
|
||
|
self.shapeLayer.path = path;
|
||
|
self.shapeLayer.lineDashPhase = 10;
|
||
|
NSArray *arr = @[@(10),@(10)];
|
||
|
self.shapeLayer.lineDashPattern = arr;
|
||
|
|
||
|
self.headView = [[UIImageView alloc] init];
|
||
|
self.headView.frame = CGRectMake(-20, -20, 10, 10);
|
||
|
self.headView.backgroundColor = [UIColor whiteColor];
|
||
|
self.headView.layer.cornerRadius = 5;
|
||
|
self.headView.layer.masksToBounds = YES;
|
||
|
// self.headView.image = [UIImage imageNamed:@"icon_orientierungspunkt"];
|
||
|
|
||
|
if(self.angeleValues.count > 1)
|
||
|
{
|
||
|
self.headView.transform = CGAffineTransformMakeRotation(self.angeleValues[self.angeleValues.count-1].doubleValue);
|
||
|
}
|
||
|
[self.trackView.layer addSublayer:self.shapeLayer];
|
||
|
[self.trackView.layer addSublayer: self.headView.layer];
|
||
|
|
||
|
self.shapeLayerAnimation = [self constructShapeLayerAnimation];
|
||
|
self.shapeLayerAnimation.delegate = self;
|
||
|
self.shapeLayerAnimation.removedOnCompletion = NO;
|
||
|
self.shapeLayerAnimation.fillMode = kCAFillModeForwards;
|
||
|
[self.shapeLayer addAnimation:self.shapeLayerAnimation forKey:@"shape"];
|
||
|
|
||
|
self.animations = [self constructHeadLayerAnimationWithBeziePaths:self.beziePaths];
|
||
|
if(self.animations.count != 0)
|
||
|
{
|
||
|
self.headLayerAnimation = self.animations[self.currentIndex];
|
||
|
self.headLayerAnimation.removedOnCompletion = NO;
|
||
|
self.headLayerAnimation.delegate = self;
|
||
|
[self.headView.layer addAnimation:self.headLayerAnimation forKey:[NSString stringWithFormat:@"headPosition%ld",(long)self.currentIndex]];
|
||
|
// [self.mapView addAnnotation:self.pointAnnotationArr.lastObject];
|
||
|
|
||
|
[self addTrackViewPoint:self.pathPoints[0] Type:0 Angle:0];
|
||
|
}
|
||
|
}
|
||
|
//MARK: 动画的代理方法 ----------CAAnimationDelegate------------
|
||
|
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
|
||
|
{
|
||
|
if([anim isKindOfClass:[CABasicAnimation class]])
|
||
|
{
|
||
|
if(self.routeLine)
|
||
|
{
|
||
|
[self.mapView addOverlay:self.routeLine];
|
||
|
}
|
||
|
}
|
||
|
if([anim isKindOfClass:[CAKeyframeAnimation class]])
|
||
|
{
|
||
|
self.currentIndex++;
|
||
|
if (self.currentIndex <self.pointAnnotationArr.count)
|
||
|
{
|
||
|
MKPointAnnotation * tempAnn = (MKPointAnnotation *)[[self.pointAnnotationArr reverseObjectEnumerator] allObjects][self.currentIndex];
|
||
|
|
||
|
CustomAnnotationView * tempView = [CustomAnnotationView new];
|
||
|
tempView.annotation = tempAnn;
|
||
|
[self mapView:self.mapView didSelectAnnotationView:tempView];
|
||
|
}
|
||
|
if(self.currentIndex >= self.animations.count){
|
||
|
self.currentIndex = 0;
|
||
|
if(self.pathPoints){
|
||
|
[self addTrackViewPoint:self.pathPoints[self.pointAnnotationArr.count-1] Type:2 Angle:0];
|
||
|
[self.headView.layer removeFromSuperlayer];
|
||
|
if(!self.startAndStopBtn.isSelected)
|
||
|
{
|
||
|
[self startAndStopBtnAction:self.startAndStopBtn];
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
CAKeyframeAnimation *nextAnim = self.animations[self.currentIndex];
|
||
|
nextAnim.removedOnCompletion = NO;
|
||
|
nextAnim.delegate = self;
|
||
|
// [self.headView addAnimation:nextAnim forKey:[NSString stringWithFormat:@"headPosition%d",currentIndex]];
|
||
|
[self.headView.layer addAnimation:nextAnim forKey:@"headPosition"];
|
||
|
if(self.pathPoints){
|
||
|
CGPoint currentPoint = self.pathPoints[self.currentIndex];
|
||
|
if (self.currentIndex == 0){
|
||
|
//第一
|
||
|
[self addTrackViewPoint:currentPoint Type:0 Angle:0];
|
||
|
double angle = self.angeleValues[self.angeleValues.count-1].doubleValue;
|
||
|
self.headView.transform = CGAffineTransformMakeRotation(angle);
|
||
|
}else if(self.currentIndex == self.pointAnnotationArr.count-1){
|
||
|
//最后一个
|
||
|
[self addTrackViewPoint:currentPoint Type:2 Angle:0];
|
||
|
}else{
|
||
|
NSInteger tempIndex = self.angeleValues.count-self.currentIndex-1;
|
||
|
if(tempIndex < 0){
|
||
|
tempIndex = 0;
|
||
|
}
|
||
|
double angle = self.angeleValues[tempIndex].doubleValue;
|
||
|
// if(self.currentIndex == 1){
|
||
|
// self.headView.transform = CGAffineTransformMakeRotation(angle);
|
||
|
// }
|
||
|
[self addTrackViewPoint:currentPoint Type:1 Angle:angle ];
|
||
|
self.headView.transform = CGAffineTransformMakeRotation(angle);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//MARK: 添加 在TrackView 上的点
|
||
|
/**
|
||
|
添加 在TrackView 上的点
|
||
|
|
||
|
@param centerPoint 点的坐标
|
||
|
@param type 类型 起点 0 平常 1 终点 2
|
||
|
@param angle 图标旋转的 弧度
|
||
|
*/
|
||
|
-(void)addTrackViewPoint:(CGPoint)centerPoint Type:(int)type Angle:(double)angle{
|
||
|
if(!_trackView){
|
||
|
return;
|
||
|
}
|
||
|
CGFloat Width = 12;
|
||
|
CGFloat height = 12;
|
||
|
|
||
|
__block UIImageView *view = [UIImageView new];
|
||
|
view.contentMode = UIViewContentModeScaleAspectFit;
|
||
|
switch (type) {
|
||
|
case 0:
|
||
|
view.frame = CGRectMake(0, 0, 30, 30);
|
||
|
view.image = [UIImage imageNamed:@"icon_start_point"];
|
||
|
view.center = CGPointMake(centerPoint.x, centerPoint.y-15);
|
||
|
break;
|
||
|
case 1:
|
||
|
view.frame = CGRectMake(0, 0, Width,height);
|
||
|
view.image = [UIImage imageNamed:@"icon_orientierungspunkt"];
|
||
|
// xLog(@"radian: %f", angle);
|
||
|
view.transform = CGAffineTransformRotate(view.transform, angle);
|
||
|
view.center = centerPoint;
|
||
|
break;
|
||
|
case 2:
|
||
|
view.frame = CGRectMake(0, 0, 30, 30);
|
||
|
view.image = [UIImage imageNamed:@"icon_end_point_1"];
|
||
|
view.center = CGPointMake(centerPoint.x, centerPoint.y-15);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
[_trackView addSubview:view];
|
||
|
|
||
|
|
||
|
//自定义动画
|
||
|
// view.transform = CGAffineTransformScale(view.transform, 0.5, 0.5);
|
||
|
// // 弹簧动画,参数分别为:时长,延时,弹性(越小弹性越大),初始速度
|
||
|
// [UIView animateWithDuration:1 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:0.3 options:UIViewAnimationOptionLayoutSubviews animations:^{
|
||
|
// //因为先缩放了所以得 2倍 0.5*2 = 1
|
||
|
// view.transform = CGAffineTransformScale(view.transform, 2, 2);
|
||
|
// } completion:^(BOOL finished) {
|
||
|
//
|
||
|
// }];
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - 经纬度转屏幕坐标, 调用者负责释放内存!
|
||
|
- (CGPoint *)pointsForCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count{
|
||
|
if (coordinates == NULL || count <= 1)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* 申请屏幕坐标存储空间. */
|
||
|
CGPoint *points = (CGPoint *)malloc(count * sizeof(CGPoint));
|
||
|
|
||
|
/* 经纬度转换为屏幕坐标. */
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
points[i] = [self.mapView convertCoordinate:coordinates[i] toPointToView:self.mapView];
|
||
|
}
|
||
|
return points;
|
||
|
}
|
||
|
|
||
|
#pragma mark - 构建path, 调用者负责释放内存!
|
||
|
- (CGPathRef)pathForPoints:(CGPoint *)points count:(NSUInteger)count{
|
||
|
if (points == NULL || count <= 1){
|
||
|
return NULL;
|
||
|
}
|
||
|
UIBezierPath *layerPathBezier = [UIBezierPath bezierPath];
|
||
|
[layerPathBezier moveToPoint:points[0]];
|
||
|
|
||
|
//贝塞尔路线数组
|
||
|
NSMutableArray *mbeziePaths = [NSMutableArray array];
|
||
|
//上一个点
|
||
|
CGPoint lastPoint = points[0];
|
||
|
double sumJuli = 0;
|
||
|
NSMutableArray *juliArr = [NSMutableArray array];
|
||
|
for (int i = 1 ; i < count; i++) {
|
||
|
UIBezierPath *beziePath = [UIBezierPath bezierPath];
|
||
|
[beziePath moveToPoint:lastPoint];
|
||
|
[beziePath addLineToPoint:points[i]];
|
||
|
[layerPathBezier addLineToPoint:points[i]];
|
||
|
CGPoint nowPiont = points[i];
|
||
|
CGFloat xDist = lastPoint.x - nowPiont.x;
|
||
|
CGFloat yDist = lastPoint.y - nowPiont.y;
|
||
|
double juli = sqrt((xDist*xDist)+(yDist*yDist));
|
||
|
[juliArr addObject:@(juli)];
|
||
|
//计算总的距离(相对在屏幕上的距离)
|
||
|
sumJuli += juli;
|
||
|
lastPoint = points[i];
|
||
|
[mbeziePaths addObject:beziePath];
|
||
|
}
|
||
|
|
||
|
self.juliArr = [juliArr copy];
|
||
|
self.SumJuli = sumJuli;
|
||
|
self.durationTime = sumJuli/(kSpeed); //每秒 320/2 dp 距离
|
||
|
// if(self.durationTime > 5.00){
|
||
|
// self.durationTime = 5.00;
|
||
|
// }
|
||
|
self.beziePaths = [mbeziePaths copy];
|
||
|
return layerPathBezier.CGPath;
|
||
|
}
|
||
|
|
||
|
#pragma mark - 构建shapeLayer的basicAnimation
|
||
|
- (CAAnimation *)constructShapeLayerAnimation{
|
||
|
CABasicAnimation *theStrokeAnimation = [CABasicAnimation animationWithKeyPath:StrokeEndAnimationKey]; //画线
|
||
|
theStrokeAnimation.duration = self.durationTime;
|
||
|
theStrokeAnimation.fromValue = @0.f;
|
||
|
theStrokeAnimation.toValue = @1.f;
|
||
|
theStrokeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||
|
return theStrokeAnimation;
|
||
|
}
|
||
|
#pragma mark - 构建动画轨迹头部发光点 关键帧动画
|
||
|
- (NSArray *)constructHeadLayerAnimationWithBeziePaths:(NSArray<UIBezierPath*>*)beziePaths{
|
||
|
if(self.juliArr.count != beziePaths.count){
|
||
|
return [NSArray array];
|
||
|
}
|
||
|
NSMutableArray *arr = [NSMutableArray array];
|
||
|
CAAnimationGroup *group = [CAAnimationGroup animation];
|
||
|
for (int i=0; i<beziePaths.count; i++) {
|
||
|
CAKeyframeAnimation *headKeyAnimation = [CAKeyframeAnimation animationWithKeyPath:PositionAnimationKey]; //位置
|
||
|
|
||
|
NSNumber *julinumber = self.juliArr[i];
|
||
|
double juli = julinumber.doubleValue;
|
||
|
double duration = juli/self.SumJuli* self.durationTime;
|
||
|
headKeyAnimation.duration = duration;
|
||
|
headKeyAnimation.path = beziePaths[i].CGPath;
|
||
|
// headKeyAnimation.values = [arr copy];
|
||
|
headKeyAnimation.calculationMode = kCAAnimationPaced ;
|
||
|
headKeyAnimation.fillMode = kCAFillModeForwards ;
|
||
|
// headKeyAnimation.rotationMode = kCAAnimationRotateAutoReverse;
|
||
|
// headKeyAnimation.rotationMode = kCAAnimationRotateAuto;
|
||
|
[arr addObject:headKeyAnimation];
|
||
|
}
|
||
|
|
||
|
group.animations = arr;
|
||
|
|
||
|
|
||
|
return arr;
|
||
|
}
|
||
|
|
||
|
- (void)addLocate:(NSArray<TrackInfoModel *>*)arr
|
||
|
{
|
||
|
kWeakSelf(self)
|
||
|
NSMutableArray *pointArr = [NSMutableArray array];
|
||
|
self.pathPointsModels = [NSMutableArray array];
|
||
|
self.angeleValues = [NSMutableArray array];
|
||
|
|
||
|
TracingPoint * firstTp = [[TracingPoint alloc] init];
|
||
|
TrackInfoModel *firstLocate = arr.firstObject;
|
||
|
firstTp.coordinate = CLLocationCoordinate2DMake([firstLocate.lat doubleValue],
|
||
|
[firstLocate.lon doubleValue]);
|
||
|
|
||
|
//
|
||
|
firstTp.course = [self.pathPointsModels firstObject].course;
|
||
|
[self.pathPointsModels addObject:firstTp];
|
||
|
[self.angeleValues addObject:@(0)];
|
||
|
for (int i = 1; i<arr.count; i++)
|
||
|
{
|
||
|
//此点坐标
|
||
|
TrackInfoModel *tempLocate = arr[i];
|
||
|
CLLocationCoordinate2D tempCoordinate = CLLocationCoordinate2DMake([tempLocate.lat doubleValue],
|
||
|
[tempLocate.lon doubleValue]);
|
||
|
//上一个点的坐标 (arr.length > 2)
|
||
|
TrackInfoModel *tempLocate2 = arr[i-1];
|
||
|
CLLocationCoordinate2D tempCoordinate2 = CLLocationCoordinate2DMake([tempLocate2.lat doubleValue],
|
||
|
[tempLocate2.lon doubleValue]);
|
||
|
TracingPoint * tp = [[TracingPoint alloc] init];
|
||
|
//此点的坐标
|
||
|
tp.coordinate = tempCoordinate;
|
||
|
//此点相对于上个点的偏移角
|
||
|
tp.course = [Util calculateCourseFromCoordinate:tempCoordinate to:tempCoordinate2];
|
||
|
// 角度转换
|
||
|
double radian = [Util fixNewDirection: tp.course basedOnOldDirection:0];
|
||
|
//计算弧度 角度*弧度值 DegToRad
|
||
|
[self.angeleValues addObject:@(radian*DegToRad)];
|
||
|
[self.pathPointsModels addObject:tp];
|
||
|
}
|
||
|
|
||
|
// self.angeleValues = [NSMutableArray array];
|
||
|
// NSArray *tempArr = [self.pathPointsModels copy];
|
||
|
// for (int i = 0; i < tempArr.count; i++) {
|
||
|
// TracingPoint *tp = tempArr[i];
|
||
|
// //angle
|
||
|
// double currDir = [Util fixNewDirection:tp.course basedOnOldDirection:0];
|
||
|
// //DegToRad 1弧度对应多少度
|
||
|
// [self.angeleValues addObject:@(currDir * DegToRad)];
|
||
|
// }
|
||
|
|
||
|
|
||
|
|
||
|
[arr enumerateObjectsUsingBlock:^(TrackInfoModel * _Nonnull locate, NSUInteger idx, BOOL * _Nonnull stop) {
|
||
|
MKPointAnnotation *pointAnnotation = [[MKPointAnnotation alloc] init];
|
||
|
pointAnnotation.tempLocate = locate;
|
||
|
pointAnnotation.tracingPoint = self.pathPointsModels[idx];
|
||
|
// xLog(@"idx: %d, tp.course:%f ", idx, pointAnnotation.tracingPoint.course);
|
||
|
CLLocationCoordinate2D tempCoordinate = CLLocationCoordinate2DMake([locate.lat doubleValue],
|
||
|
[locate.lon doubleValue]);
|
||
|
pointAnnotation.coordinate = tempCoordinate;
|
||
|
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[locate.updateTime doubleValue]];
|
||
|
NSString *dateStr = [UICommon getDateFormatWithStr:@"M月d日 HH:mm"
|
||
|
date:date];
|
||
|
if (locate.locationType) {
|
||
|
pointAnnotation.title = [NSString stringWithFormat:@"定位类型:%@ %@",locate.locationType,dateStr];
|
||
|
}else{
|
||
|
pointAnnotation.title = [NSString stringWithFormat:@"%@",dateStr];
|
||
|
}
|
||
|
pointAnnotation.subtitle = locate.addr;
|
||
|
|
||
|
CLLocation * pointLocation = [[CLLocation alloc] initWithLatitude:[locate.lat doubleValue] longitude:[locate.lon doubleValue]];
|
||
|
|
||
|
[pointArr addObject:pointLocation];
|
||
|
|
||
|
// [weakSelf setMapZoom:pointAnnotation.coordinate];
|
||
|
|
||
|
[weakself.pointAnnotationArr addObject:pointAnnotation];
|
||
|
// [weakSelf.mapView addAnnotation:pointAnnotation];
|
||
|
}];
|
||
|
if(arr.count > 1){
|
||
|
[self drawLineWithLocationArray:[pointArr copy]];
|
||
|
[self.startAndStopBtn setHidden:NO];
|
||
|
}else{
|
||
|
[self.startAndStopBtn setHidden:YES];
|
||
|
if(self.pointAnnotationArr && self.pointAnnotationArr.count > 0){
|
||
|
[self.mapView addAnnotations:self.pointAnnotationArr];
|
||
|
self.mapView.userInteractionEnabled = YES;
|
||
|
[self.mapView showAnnotations:self.pointAnnotationArr animated:YES];
|
||
|
[self.mapView selectAnnotation:self.pointAnnotationArr[0] animated:YES];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
#pragma mark - 画线
|
||
|
- (void)drawLineWithLocationArray:(NSArray *)locationArray
|
||
|
{
|
||
|
NSInteger pointCount = [locationArray count];
|
||
|
CLLocationCoordinate2D *coordinateArray = (CLLocationCoordinate2D *)malloc(pointCount * sizeof(CLLocationCoordinate2D));
|
||
|
|
||
|
for (int i = 0; i < pointCount; ++i) {
|
||
|
CLLocation *location = [locationArray objectAtIndex:i];
|
||
|
coordinateArray[(pointCount-1)-i] = [location coordinate];
|
||
|
}
|
||
|
|
||
|
self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:pointCount];
|
||
|
|
||
|
|
||
|
//经纬度的区域坐标
|
||
|
MKMapRect mapShowRect = [self.routeLine boundingMapRect];
|
||
|
NSLog(@"mapShowRect X: %f Y:%f Width: %f, Height: %f", mapShowRect.origin.x,mapShowRect.origin.y,mapShowRect.size.width,mapShowRect.size.height);
|
||
|
//显示区域
|
||
|
[self.mapView setVisibleMapRect:mapShowRect edgePadding:UIEdgeInsetsMake(50, 50, 50, 50) animated:NO];
|
||
|
|
||
|
|
||
|
//把路径点变成在 屏幕上的坐标点
|
||
|
self.pathPoints = [self pointsForCoordinates:coordinateArray count:pointCount];
|
||
|
|
||
|
free(coordinateArray);
|
||
|
coordinateArray = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - 地图控件代理方法
|
||
|
|
||
|
#pragma mark 显示大头针时调用,注意方法中的annotation参数是即将显示的大头针对象
|
||
|
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
|
||
|
//由于当前位置的标注也是一个大头针,所以此时需要判断,此代理方法返回nil使用默认大头针视图
|
||
|
static NSString *key1=@"AnnotationKey1";
|
||
|
CustomAnnotationView *annotationView = (CustomAnnotationView*)[_mapView dequeueReusableAnnotationViewWithIdentifier:key1];
|
||
|
MKPointAnnotation *pointAnnotation = (MKPointAnnotation*)annotation;
|
||
|
//如果缓存池中不存在则新建
|
||
|
if (!annotationView) {
|
||
|
annotationView=[[CustomAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1];
|
||
|
annotationView.canShowCallout=NO;//不允许交互点击
|
||
|
}else{
|
||
|
//重新设置此类大头针视图的大头针模型(因为有可能是从缓存池中取出来的,位置是放到缓存池时的位置)
|
||
|
annotationView.annotation = annotation;
|
||
|
}
|
||
|
|
||
|
if([annotation isEqual:self.pointAnnotationArr.firstObject]&&self.pointAnnotationArr.count>1){
|
||
|
//修改大头针视图 设置终点
|
||
|
[annotationView setImageWithString:@"icon_end_point_1"];
|
||
|
//设置大小
|
||
|
annotationView.frame = CGRectMake(0, 0, 30, 30);
|
||
|
//设置中心偏移量
|
||
|
annotationView.centerOffset = CGPointMake(0, -15);
|
||
|
|
||
|
annotationView.calloutOffset=CGPointMake(0, 1);//定义详情视图偏移量
|
||
|
return annotationView;
|
||
|
}
|
||
|
if([annotation isEqual:self.pointAnnotationArr.lastObject]){
|
||
|
//修改大头针视图 设置起点
|
||
|
[annotationView setImageWithString:@"icon_start_point"];
|
||
|
|
||
|
//设置大小
|
||
|
annotationView.frame = CGRectMake(0, 0, 30, 30);
|
||
|
//设置中心偏移量
|
||
|
annotationView.centerOffset = CGPointMake(0, -15);
|
||
|
|
||
|
annotationView.calloutOffset=CGPointMake(0, 1);//定义详情视图偏移量
|
||
|
return annotationView;
|
||
|
}
|
||
|
[annotationView setImageWithString:@"icon_orientierungspunkt"];
|
||
|
double radian = [Util fixNewDirection:pointAnnotation.tracingPoint.course basedOnOldDirection:0];
|
||
|
// xLog(@"radian %f", radian*DegToRad);
|
||
|
[annotationView setRadian:radian*DegToRad];
|
||
|
//设置大小
|
||
|
annotationView.frame = CGRectMake(0, 0, 12, 12);
|
||
|
//设置中心偏移量
|
||
|
annotationView.centerOffset = CGPointMake(3, -3);
|
||
|
|
||
|
annotationView.calloutOffset=CGPointMake(0, 0);//定义详情视图偏移量
|
||
|
|
||
|
return annotationView;
|
||
|
|
||
|
}
|
||
|
|
||
|
//MARK: 点击地图 中的 标点 触发的方法
|
||
|
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
|
||
|
{
|
||
|
if([view isKindOfClass:[CustomAnnotationView class]])
|
||
|
{
|
||
|
if([view.annotation isKindOfClass:[MKPointAnnotation class]])
|
||
|
{
|
||
|
MKPointAnnotation *pointAnnotation = (MKPointAnnotation*)view.annotation;
|
||
|
self.curLocate = pointAnnotation.tempLocate;
|
||
|
if(!self.curLocate)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}else{
|
||
|
return;
|
||
|
}
|
||
|
//自定义动画
|
||
|
view.transform = CGAffineTransformScale(view.transform, 0.5, 0.5);
|
||
|
// 弹簧动画,参数分别为:时长,延时,弹性(越小弹性越大),初始速度
|
||
|
[UIView animateWithDuration:1 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:0.3 options:UIViewAnimationOptionLayoutSubviews animations:^{
|
||
|
view.transform = CGAffineTransformScale(view.transform, 2, 2);
|
||
|
} completion:^(BOOL finished) {
|
||
|
|
||
|
}];
|
||
|
|
||
|
// 设备点
|
||
|
self.locationTypeLabel.text = self.curLocate.locationType;
|
||
|
if ([self.curLocate.locationType isEqualToString:@"LBS"])
|
||
|
self.mapImg.image = ImageName_(@"icon_home_base_station");
|
||
|
else if ([self.curLocate.locationType isEqualToString:@"GPS"])
|
||
|
self.mapImg.image = ImageName_(@"icon_histroy_g");
|
||
|
else if ([self.curLocate.locationType isEqualToString:@"WIFI"])
|
||
|
self.mapImg.image = ImageName_(@"icon_home_wifi");
|
||
|
[self.mapImg mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||
|
make.left.equalTo(self.topView).offset(15);
|
||
|
make.bottom.equalTo(self.topView.mas_centerY);
|
||
|
make.size.mas_equalTo(CGSizeMake(AdaptedHeight(self.mapImg.image.size.width), AdaptedHeight(self.mapImg.image.size.height)));
|
||
|
}];
|
||
|
|
||
|
if(self.curLocate.addr.length > 0){
|
||
|
NSString *dateStr = self.curLocate.updateTime;
|
||
|
NSMutableAttributedString *att = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@",self.curLocate.addr,self.curLocate.updateTime]];
|
||
|
[att addAttribute:NSForegroundColorAttributeName
|
||
|
value:KKMainColor
|
||
|
range:NSMakeRange(att.length-dateStr.length,dateStr.length)];
|
||
|
self.locationAddressLabel.attributedText = att;
|
||
|
}else{
|
||
|
AMapReGeocodeSearchRequest *regeo = [[AMapReGeocodeSearchRequest alloc] init];
|
||
|
|
||
|
regeo.location = [AMapGeoPoint locationWithLatitude:[self.curLocate.lat floatValue] longitude:[self.curLocate.lon floatValue]];
|
||
|
[self.searchAPI AMapReGoecodeSearch:regeo];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
|
||
|
{
|
||
|
if ([overlay isKindOfClass:[MKCircle class]])
|
||
|
{
|
||
|
MKCircleRenderer *circle = [[MKCircleRenderer alloc] initWithOverlay:overlay];
|
||
|
circle.strokeColor = [UIColor clearColor];
|
||
|
|
||
|
circle.fillColor = [[UIColor blueColor] colorWithAlphaComponent:0.1];
|
||
|
|
||
|
// circle.lineWidth = 1;
|
||
|
return circle;
|
||
|
}
|
||
|
if (overlay == self.routeLine) {
|
||
|
if(nil == self.routeLineView) {
|
||
|
self.routeLineView = [[MKPolylineRenderer alloc] initWithPolyline:self.routeLine] ;
|
||
|
//虚线===
|
||
|
//self.routeLineView.lineDashPhase = 10;
|
||
|
//NSArray* array = [NSArray arrayWithObjects:[NSNumber numberWithInt:15] , [NSNumber numberWithInt:15],nil]; // 2
|
||
|
//self.routeLineView.lineDashPattern = array; // 3
|
||
|
//====
|
||
|
self.routeLineView.fillColor = KKMainColor;
|
||
|
self.routeLineView.strokeColor = KKMainColor;
|
||
|
self.routeLineView.lineWidth = 4;
|
||
|
}
|
||
|
return self.routeLineView;
|
||
|
}
|
||
|
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
#pragma mark ====高德地图位置解析回调====
|
||
|
|
||
|
-(void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
|
||
|
{
|
||
|
if (response.regeocode != nil)
|
||
|
{
|
||
|
NSString *name = response.regeocode.formattedAddress;
|
||
|
NSString *dateStr = self.curLocate.updateTime;
|
||
|
NSMutableAttributedString *att = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@",name,dateStr]];
|
||
|
[att addAttribute:NSForegroundColorAttributeName
|
||
|
value:KKMainColor
|
||
|
range:NSMakeRange(att.length-dateStr.length,dateStr.length)];
|
||
|
self.locationAddressLabel.attributedText = att;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)didReceiveMemoryWarning {
|
||
|
//[super didReceiveMemoryWarning];
|
||
|
// Dispose of any resources that can be recreated.
|
||
|
switch (self.mapView.mapType) {
|
||
|
case MKMapTypeHybrid:
|
||
|
{
|
||
|
self.mapView.mapType = MKMapTypeStandard;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case MKMapTypeStandard:
|
||
|
{
|
||
|
self.mapView.mapType = MKMapTypeHybrid;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
self.mapView.mapType = MKMapTypeStandard;
|
||
|
}
|
||
|
|
||
|
-(void)dealloc{
|
||
|
NSLog(@"销毁 历史轨迹展示页面");
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
- (MKMapView *)mapView
|
||
|
{
|
||
|
if (!_mapView)
|
||
|
{
|
||
|
CGFloat yy = iPhoneX_NavHeight+Adapted(50)+0.5;
|
||
|
_mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT-yy)];
|
||
|
MKCoordinateSpan span = MKCoordinateSpanMake(0.021251, 0.016093);
|
||
|
CLLocationCoordinate2D center = self.mapView.region.center;
|
||
|
[_mapView setRegion:MKCoordinateRegionMake(center, span) animated:YES];
|
||
|
_mapView.rotateEnabled = NO;
|
||
|
_mapView.userInteractionEnabled = NO;
|
||
|
_mapView.delegate = self;
|
||
|
}
|
||
|
return _mapView;
|
||
|
}
|
||
|
|
||
|
|
||
|
-(NSMutableArray *)pointAnnotationArr
|
||
|
{
|
||
|
if(!_pointAnnotationArr){
|
||
|
_pointAnnotationArr = [NSMutableArray array];
|
||
|
}
|
||
|
return _pointAnnotationArr;
|
||
|
}
|
||
|
|
||
|
-(UIView *)trackView
|
||
|
{
|
||
|
if(!_trackView){
|
||
|
_trackView = [[UIView alloc] initWithFrame: self.mapView.bounds];
|
||
|
_trackView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.1];
|
||
|
[self.mapView addSubview:_trackView];
|
||
|
}
|
||
|
return _trackView;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
// Only override drawRect: if you perform custom drawing.
|
||
|
// An empty implementation adversely affects performance during animation.
|
||
|
- (void)drawRect:(CGRect)rect {
|
||
|
// Drawing code
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
@end
|