关于高德地图的使用
最近在做一款iOS APP,用到了地图功能,出于多方考虑,使用了高德地图,来实现,实时轨迹、轨迹循迹、在地图上显示自定义大头针,轨迹平滑处理、加载高德地图瓦片功能等,在这里做一下总结。
地图初始化
- 首先,根据高德开发文档,将SDK导入到项目中,这里不做赘述
- 导入成功后,在.h文件中引入头文件:
- import AMapFoundationKit/AMapFoundationKit.h
- import MAMapKit/MAMapKit.h
- import AMapLocationKit/AMapLocationManager.h
- 初始化地图:
设置属性
// 高德地图
@property (nonatomic, strong) MAMapView *mapView;
// 定位管理者
@property (nonatomic, strong) AMapLocationManager *locationManager;
// 地图瓦片
@property (nonatomic, strong) MATileOverlay *tileOverlay
懒加载地图
pragma mark -----懒加载
// 定位管理者
- (AMapLocationManager *)locationManager {
if (_locationManager == nil) {
AMapLocationManager *locationManager = [[AMapLocationManager alloc] init];
locationManager.delegate = self;
// 定位的最小更新距离
locationManager.distanceFilter = 50;
// 指定定位是否会被系统自动暂停。默认为NO。
locationManager.pausesLocationUpdatesAutomatically = NO;
// 是否允许后台定位。默认为NO。
locationManager.allowsBackgroundLocationUpdates = YES;
// 连续定位是否返回逆地理信息,默认NO。
locationManager.locatingWithReGeocode = YES;
locationManager.desiredAccuracy = 10;
_locationManager = locationManager;
}
return _locationManager;
}
// MapView
-(MAMapView *)mapView{
if (!_mapView) {
// 1、地图
MAMapView *mapView = [[MAMapView alloc] initWithFrame:self.view.bounds];
mapView.delegate = self;
// 是否显示用户位置(定位蓝点)
mapView.showsUserLocation = YES;
mapView.userTrackingMode = MAUserTrackingModeFollow;
// 是否显示室内地图, 默认NO
mapView.showsIndoorMap = YES;
// 地图类型
mapView.mapType = MAMapTypeStandard;
// 地图logo(高德地图字样)
// mapView.logoCenter
// 指南针
mapView.showsCompass = NO;
mapView.compassOrigin = CGPointMake(kScreenWidth, 200);
// 比例尺
mapView.showsScale= YES; //设置成NO表示不显示比例尺;YES表示显示比例尺
if (kIsIphoneX) {
mapView.scaleOrigin= CGPointMake(_mapView.scaleOrigin.x,kScreenHeight-300); //设置比例尺位置
}else{
mapView.scaleOrigin= CGPointMake(_mapView.scaleOrigin.x,kScreenHeight-53); //设置比例尺位置
}
// 是否支持缩放, 默认YES
mapView.zoomEnabled = YES;
// 缩放级别(默认3-19,有室内地图时为3-20)
mapView.zoomLevel = 17;
mapView.minZoomLevel = kTileOverlayRemoteMinZoom;
mapView.maxZoomLevel = kTileOverlayRemoteMaxZoom;
// 是否支持平移, 默认YES
mapView.scrollEnabled = YES;
// 是否支持旋转, 默认YES
mapView.rotateEnabled = YES;
// 是否支持倾斜, 默认YES(用户可以在地图上放置两个手指,移动它们一起向下或向上去增加或减小倾斜角。)
mapView.rotateCameraEnabled = YES;
_mapView = mapView;
}
_mapView.delegate = self;
return _mapView;
}
然后将将地图添加到View上
// 高德地图
[self.view addSubview:self.mapView];
// 开启持续定位
[self.locationManager startUpdatingLocation];
这里因为要实时绘制行走的路线轨迹,所以使用持续定位,到这里,地图已经显示出来了
使用高德地图加载谷歌地图瓦片(地形图和卫星图)
设置默认 地图
// Google地形瓦片地图URL
define kTileOverlayGoogleTopography @"http://mt2.google.cn/vt/lyrs=p&scale=2&hl=zh-CN&gl=cn&x={x}&y={y}&z={z}"
// Google卫星瓦片地图URL
define kTileOverlayGoogleSatellite @"http://mt2.google.cn/vt/lyrs=y&scale=2&hl=zh-CN&gl=cn&x={x}&y={y}&z={z}"
// 瓦片最小缩放级别
define kTileOverlayRemoteMinZoom 3
// 瓦片最大缩放级别
define kTileOverlayRemoteMaxZoom 19
pragma mark - 构建Google瓦片
- (MATileOverlay *)constructGoogleTileOverlay:(NSString *)url {
MATileOverlay *tileOverlay = [[MATileOverlay alloc] initWithURLTemplate:url];
tileOverlay.minimumZ = kTileOverlayRemoteMinZoom;
tileOverlay.maximumZ = kTileOverlayRemoteMaxZoom;
tileOverlay.boundingMapRect = MAMapRectWorld;
return tileOverlay;
}
/**
* @brief 根据overlay生成对应的Renderer
* @param mapView 地图View
* @param overlay 指定的overlay
* @return 生成的覆盖物Renderer
*/
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay {
if ([overlay isKindOfClass:[MATileOverlay class]]) { // 瓦片
MATileOverlayRenderer *render = [[MATileOverlayRenderer alloc] initWithTileOverlay:overlay];
return render;
}
return nil;
}
- (void)setMapStyp{
NSInteger mapType = [[NSUserDefaults standardUserDefaults] integerForKey:@"mapType"];
if (mapType == 0) {
[self.mapView removeOverlays:self.mapView.overlays];
}else if (mapType == 1){
// 移除所有瓦片
[self.mapView removeOverlays:self.mapView.overlays];
// 构建Google地形瓦片
[self.mapView addOverlay:[self constructGoogleTileOverlay:kTileOverlayGoogleTopography]];
}else if (mapType == 2){
[self.mapView removeOverlays:self.mapView.overlays];
// 构建Google卫星瓦片
[self.mapView addOverlay:[self constructGoogleTileOverlay:kTileOverlayGoogleSatellite]];
}
}
实时轨迹画线
首先在地图的代理中,采集画轨迹所需要的坐标点
pragma mark - 位置更新之后
- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location reGeocode:(AMapLocationReGeocode *)reGeocode {
// 底部显示坐标
self.locationLabel.text = [NSString stringWithFormat:@"%lf %lf",location.coordinate.longitude,location.coordinate.latitude];
// 底部显示的海拔
self.altitudeLabel.text = [NSString stringWithFormat:@"%.fM",location.altitude];
// 如果点了暂停,不在往数组里添加点
if (self.isPause) return;
// 如果还在初始化中,不在往数组里添加点
if (self.showSetUpView) return;
if (self.locationMutableArray.count>0) {
// 从位置数组中取出最后一个点的位置
NSMutableDictionary *getPointDict = self.locationMutableArray.lastObject;
// 计算当前位置和上一个点之间的时间间隔
NSString *fromDateStr = getPointDict[@"time"];
NSString *toDateStr = [self.dateFormatter stringFromDate:location.timestamp];
NSInteger secondsInterval = [self caculateDateWithFromDateStr:fromDateStr toDateStr:toDateStr];
// 如果时间间隔小于2秒,不记录
if (secondsInterval<1) return;
// 计算当前位置和上一个点之间的距离
NSString *latitudeStr = getPointDict[@"latitude"];
NSString *longitudeStr = getPointDict[@"longitude"];
// 拿到当前位置数据
CLLocationCoordinate2D endCoordinate;
endCoordinate.latitude = location.coordinate.latitude;
endCoordinate.longitude = location.coordinate.longitude;
double meters = [self distanceBetweenOrderBy:latitudeStr.doubleValue :location.coordinate.latitude :longitudeStr.doubleValue :location.coordinate.longitude];
// 大于所少米可以记录
if (self.travalType != 3) {
if (meters<self.travalTypeM) return;
}
// 速度大于200公里/时
if ((meters/secondsInterval)>55.555) return;
// 计算行走的米数
[self caculateTrackDistanceWithUserLocation:location];
// 将点数据添加到数组中
NSString *pointDate = [self.dateFormatter stringFromDate:location.timestamp];
NSMutableDictionary *pointDict = [NSMutableDictionary dictionary];
pointDict[@"longitude"] = [NSString stringWithFormat:@"%f",location.coordinate.longitude];
pointDict[@"latitude"] = [NSString stringWithFormat:@"%f",location.coordinate.latitude];
pointDict[@"altitude"] = [NSString stringWithFormat:@"%.f",location.altitude];
pointDict[@"time"] = pointDate;
[self.locationMutableArray addObject:pointDict];
NSString *altitude = [NSString stringWithFormat:@"%.f",location.altitude];
[self.altitudeArray addObject:altitude];
// 画线
[self drawLine];
}else{
// 将点数据添加到数组中
NSString *pointDate = [self.dateFormatter stringFromDate:location.timestamp];
NSMutableDictionary *pointDict = [NSMutableDictionary dictionary];
pointDict[@"longitude"] = [NSString stringWithFormat:@"%f",location.coordinate.longitude];
pointDict[@"latitude"] = [NSString stringWithFormat:@"%f",location.coordinate.latitude];
pointDict[@"altitude"] = [NSString stringWithFormat:@"%.f",location.altitude];
pointDict[@"time"] = pointDate;
[self.locationMutableArray addObject:pointDict];
// 添加起点坐标View
self.startAnnotation.coordinate = location.coordinate;
[self.mapView addAnnotation:self.startAnnotation];
}
}
/// 画线
- (void)drawLine{
// 先移除
[self.mapView removeOverlay:self.smoothedTrace];
MASmoothPathTool *tool = [[MASmoothPathTool alloc] init];
tool.intensity = 4;
tool.threshHold = 0.25;
tool.noiseThreshhold = 10;
NSMutableArray *tempArray = [NSMutableArray array];
/// 将自己的点Model转换成MALonLatPoint
for (NSMutableDictionary *pointDict in self.locationMutableArray) {
MALonLatPoint *smoothPoint = [[MALonLatPoint alloc]init];
smoothPoint.lat = [pointDict[@"latitude"] floatValue];
smoothPoint.lon = [pointDict[@"longitude"] floatValue];
[tempArray addObject:smoothPoint];
}
// 平滑处理之后的坐标点
NSArray *smoothedTraceArray = [tool pathOptimize:tempArray];
CLLocationCoordinate2D *pCoords = malloc(sizeof(CLLocationCoordinate2D) * smoothedTraceArray.count);
if(!pCoords) {
return;
}
for(int i = 0; i < smoothedTraceArray.count; ++i) {
MALonLatPoint *p = [smoothedTraceArray objectAtIndex:i];
CLLocationCoordinate2D *pCur = pCoords + i;
pCur->latitude = p.lat;
pCur->longitude = p.lon;
}
self.smoothedTrace = [MAPolyline polylineWithCoordinates:pCoords count:tempArray.count];
[self.mapView addOverlay:self.smoothedTrace];
if(pCoords) {
free(pCoords);
}
}
// 计算两个日期的差值
- (NSInteger)caculateDateWithFromDateStr:(NSString*)fromDateStr toDateStr:(NSString*)toDateStr{
NSDate *fromDate = [self.dateFormatter dateFromString:fromDateStr];
NSDate *toDate = [self.dateFormatter dateFromString:toDateStr];
// 创建日历类
NSCalendar *calendar = [NSCalendar currentCalendar];
// 设置比较的级别:秒
NSCalendarUnit unit = NSCalendarUnitSecond;
//比较的结果是NSDateComponents类对象
NSDateComponents *delta = [calendar components:unit fromDate:fromDate toDate:toDate options:0];
return delta.second;
}
/// 计算两个经纬度之间的距离
-(double)distanceBetweenOrderBy:(double) lat1 :(double) lat2 :(double) lng1 :(double) lng2{
CLLocation *curLocation = [[CLLocation alloc] initWithLatitude:lat1 longitude:lng1];
CLLocation *otherLocation = [[CLLocation alloc] initWithLatitude:lat2 longitude:lng2];
double distance = [curLocation distanceFromLocation:otherLocation];
return distance;
}
这里计算时间间隔和两个坐标点之间的距离,是为了过滤一些其他不必要的点,iOS采集坐标点跟安卓不一致,是在位置更新之后,会频繁的给出很多坐标点,并没有什么规律,这样还可以优化坐标点飘点的问题,因为高德在iOS上采用的是混合定位,采集方式不可修改,所以只能通过其他的方式去做筛选。尽可能的将异常的点排除掉。
在地图上添加自定义的大头针,以及标注图
/// 自定义用户位置样式
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {
/* 自定义userLocation对应的annotationView. */
if ([annotation isKindOfClass:[MAUserLocation class]])
{
static NSString *userLocationStyleReuseIndetifier = @"userLocationStyleReuseIndetifier";
MAAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:userLocationStyleReuseIndetifier];
if (annotationView == nil){
annotationView = [[LocationAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:userLocationStyleReuseIndetifier];
annotationView.canShowCallout = YES;
}
_locationAnnotationView = (LocationAnnotationView *)annotationView;
[_locationAnnotationView updateImage:[UIImage imageNamed:@"userPosition"]];
return annotationView;
}
if ([annotation isKindOfClass:[MAPointAnnotation class]]){
static NSString *customReuseIndetifier = @"customReuseIndetifier";
FYCustomAnnotationView *annotationView = (FYCustomAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:customReuseIndetifier];
if (annotationView == nil){
annotationView = [[FYCustomAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:customReuseIndetifier];
annotationView.canShowCallout = NO;
annotationView.draggable = YES;
annotationView.calloutOffset = CGPointMake(0, -5);
}
__weak typeof(annotationView) weakAnnotationView = annotationView;
annotationView.clickImage = ^{
};
annotationView.delegate = self;
if (annotation == self.startAnnotation||annotation == self.trackStartAnnotation) {
annotationView.image = [UIImage imageNamed:@"qi"];
}else if (annotation == self.trackEndAnnotation){
annotationView.image = [UIImage imageNamed:@"zhong"];
}else{
annotationView.image = [UIImage imageNamed:@"paizhaodian"];
}
return annotationView;
}
return nil;
}
在设置自定义的标注图的时候首先要继承MAAnnotationView
在.h文件中
#import <MAMapKit/MAMapKit.h>
#import "FYCustomCalloutView.h"
NS_ASSUME_NONNULL_BEGIN
@class FYCustomAnnotationView;
@protocol FYCustomAnnotationViewDelegate <NSObject>
- (void)didClickDeleteAnnotationViewWith:(FYCustomAnnotationView*)annotationView;
@end
@interface FYCustomAnnotationView : MAAnnotationView
@property (nonatomic, strong) FYCustomCalloutView *calloutView;
@property (nonatomic, copy) void(^clickImage)(void);
@property (nonatomic, copy) void(^clickDeleteImageAction)(void);
@property (nonatomic, weak) id<FYCustomAnnotationViewDelegate>delegate;
@end
NS_ASSUME_NONNULL_END
在.m文件中
#import "FYCustomAnnotationView.h"
@implementation FYCustomAnnotationView
- (void)setSelected:(BOOL)selected animated:(BOOL)animated{
if (self.selected == selected){
return;
}
if (selected){
if (self.calloutView == nil){
self.calloutView = [FYCustomCalloutView viewFromXib];
self.calloutView.alpha = 0;
self.calloutView.frame = CGRectMake(0, 0, 100, 105);
self.calloutView.center = CGPointMake(CGRectGetWidth(self.bounds) / 2.f + self.calloutOffset.x, -CGRectGetHeight(self.calloutView.bounds) / 2.f + self.calloutOffset.y);
self.calloutView.userInteractionEnabled = YES;
self.calloutView.photoImgView.userInteractionEnabled = YES;
}
[self addSubview:self.calloutView];
[UIView animateWithDuration:0.3 animations:^{
self.calloutView.alpha = 1;
}];
}else{
[self.calloutView removeFromSuperview];
}
[super setSelected:selected animated:animated];
}
/**
重写这个方法,可以让整个在父控件之外的气泡点击也有反应;
*/
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 如果一个子视图的区域超出父视图的bounds,那么正常情况下对子视图在父视图之外区域的触摸操作是不会被识别的,因为父视图的pointInside:withEvent:方法会返回NO,这样的话就不会继续向下遍历子视图了;
// 返回一个view来响应事件
CGPoint point0 = [self convertPoint:point toView:self.calloutView.deleteButton];
if ([self.calloutView.deleteButton pointInside:point0 withEvent:event]) {
if (self.delegate && [self.delegate respondsToSelector:@selector(didClickDeleteAnnotationViewWith:)]) {
[self.delegate didClickDeleteAnnotationViewWith:self];
}
return self.calloutView.deleteButton;
}
CGPoint newPoint = [self convertPoint:point toView:self.calloutView];
if ([self.calloutView pointInside:newPoint withEvent:event]) {
if (self.clickImage) {
self.clickImage();
}
return self.calloutView;
}
return [super hitTest:point withEvent:event];
}
@end
标注图calloutView
.h文件中
@interface FYCustomCalloutView : UIView
@property (weak, nonatomic) IBOutlet UIImageView *photoImgView;
@property (weak, nonatomic) IBOutlet UIButton *deleteButton;
@end
.m文件中不需要做其他的设置,然后添加坐标点就可以了
// 初始化地图上的照片
MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
// 创建位置数据
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([imageModel[@"startLatitude"] floatValue],[imageModel[@"startLongitude"] floatValue]);
pointAnnotation.coordinate = coordinate;
// 添加到地图上
[self.mapView addAnnotation:pointAnnotation];
这样就可以添加了,另外添加起点和终点也是同样