iOS中的MapKit集成了google地图api的很多功能加上iOS的定位的功能,我们就可以实现将你运行的轨迹绘制到地图上面。这个功能非常有用,比如汽车的gprs追踪、人员追踪、快递追踪等等。这篇文章我们将使用Map Kit和iOS的定位功能,将你的运行轨迹绘制在地图上面。
实现
-(void) loadRoute
{
NSString* filePath = [[NSBundle mainBundle] pathForResource:@”route” ofType:@”csv”];
NSString* fileContents = [NSString stringWithContentsOfFile :filePath encoding:NSUTF8StringEncoding error:nil];
NSArray* pointStrings = [fileContents componentsSeparatedByCha ractersInSet:[NSCharacterSet whitespaceAndNewlineChar acterSet]];
// while we create the route points, we will also be calculating the bounding box of our route
// so we can easily zoom in on it.
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;
// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * pointStrings.count);
for(int idx =0; idx < pointStrings.count; idx++)
{
// break the string down even further to latitude and longitude fields.
NSString* currentPointString = [pointStrings objectAtIndex:idx];
NSArray* latLonArr = [currentPointString componentsSeparatedByCha ractersInSet:[NSCharacterSet characterSetWithCharacte rsInString:@","]];
CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];
// create our coordinate and add it to the correct spot in the array
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMa ke(latitude, longitude);
MKMapPoint point = MKMapPointForCoordinate(coordinate);
//
// adjust the bounding box
//
// if it is the first point, just use them, since we have nothing to compare to yet.
if (idx ==0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}
// create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:pointStrings.count];
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
// clear the memory allocated earlier for the points
free(pointArr);
}
将这个路径MKPolyline对象添加到地图上
[self.mapView addOverlay:self.routeLine];
显示在地图上:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay
{
MKOverlayView* overlayView = nil;
if(overlay == self.routeLine)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(nil == self.routeLineView)
{
self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];
self.routeLineView.fillColor = [UIColor redColor];
self.routeLineView.strokeColor = [UIColor redColor];
self.routeLineView.lineWidth =3;
}
overlayView = self.routeLineView;
}
return overlayView;
}
看下从文件中读取数据绘制的轨迹路径效果:
然后我们在从文件中读取位置的方法改成从用gprs等方法获取当前位置。
- (void)viewDidLoad {
[super viewDidLoad];
noUpdates =0;
locations = [[NSMutableArray alloc] init];
locationMgr = [[CLLocationManager alloc] init];
locationMgr.delegate= self;
locationMgr.desiredAccuracy =kCLLocationAccuracyBest;
locationMgr.distanceFilter =1.0f;
[locationMgr startUpdatingLocation];
}
上面的代码我定义了一个数组,用于保存运行轨迹的经纬度。
每次通知更新当前位置的时候,我们将当前位置的经纬度放到这个数组中,并重新绘制路径,代码如下:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{
noUpdates++;
[locations addObject: [NSString stringWithFormat:@"%f,%f",[newLocation coordinate].latitude, [newLocation coordinate].longitude]];
[self updateLocation];
if (self.routeLine!=nil) {
self.routeLine =nil;
}
if(self.routeLine!=nil)
[self.mapView removeOverlay:self.routeLine];
self.routeLine =nil;
// create the overlay
[self loadRoute];
// add the overlay to the map
if (nil != self.routeLine) {
[self.mapView addOverlay:self.routeLine];
}
// zoom in on the route.
[self zoomInOnRoute];
}
我们将前面从文件获取经纬度创建轨迹的代码修改成从这个数组中取值就行了:
// creates the route (MKPolyline) overlay
-(void) loadRoute
{
// while we create the route points, we will also be calculating the bounding box of our route
// so we can easily zoom in on it.
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;
// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * locations.count);
for(int idx =0; idx < locations.count; idx++)
{
// break the string down even further to latitude and longitude fields.
NSString* currentPointString = [locations objectAtIndex:idx];
NSArray* latLonArr = [currentPointString componentsSeparatedByCha ractersInSet:[NSCharacterSet characterSetWithCharacte rsInString:@","]];
CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMa ke(latitude, longitude);
MKMapPoint point = MKMapPointForCoordinate(coordinate);
if (idx ==0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:locations.count];
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
free(pointArr);
}
这样我们就将我们运行得轨迹绘制google地图上面了。
扩展:
总结:这篇文章我们介绍了一种常见的技术实现:在地图上绘制出你运行的轨迹。
作者:朱祁林
出处:http://zhuqil.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
☉限制:必须将iPhone的操作系统更新到ios3.0版本,开发使用的SDK也要是SDK 3.0才有內建Mapkit Framework。
☉效果画面:
☉步骤说明:
在 地图上每一个记号,都是一個MKAnnotationView,也就是UI。而每一個MKAnnotationView都需要有对应的资料 MKAnnotation,这是Protocal,也就是存储每個座坐标所需要用到的资料的地方。因此,我们要先建立一個使用MKAnnotation的类别。
依照iPhone开发者文件的说明。这个Protocal需要声明三个属性和一个初始化方法。三个属性分別是coordinate、title、subtitle,和一个方法initWithCoords。
下面是MKAnnotation类的代码 POI.h
#import
#import
#import
@interface POI : NSObject {
CLLocationCoordinate2D coordinate;
NSString *subtitle;
NSString *title;
}
@property (nonatomic,readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic,retain) NSString *subtitle;
@property (nonatomic,retain) NSString *title;
-(id) initWithCoords:(CLLocationCoordinate2D) coords;
@end
下面是MKAnnotation类的代码 POI.m
#import "POI.h"
@implementation POI
@synthesize coordinate,subtitle,title;
- (id) initWithCoords:(CLLocationCoordinate2D) coords{
self = [super init];
if (self != nil)
{
coordinate = coords;
}
return self;
}
- (void) dealloc {
[title release];
[subtitle release];
[super dealloc];
}
@end
声明了符合MKAnnotation Protocal的类别后,我们就要在Google Map上建立坐标点。在iPhone上先是Google Map的程序可以參考上一篇博文
接下來,
第一步: 我声明了一个函数createMapPoint创建坐标点。在这个用到了我们在前面声明类别POI(这个符合MKAnnotation Protocal的类别),我们实例化一个POI,接着将坐标点所需的经纬度,标题,子标题等信息都放进去。接着调用
[mapView addAnnotation:poi];
把我們所建立的POI加入地图(MKMapView)的Annotation集合中。放入集合的只是坐标点的资料,这個時候还沒有真正建立坐标点
以下是函数createMapPoint的代码:
#import "POI.h"
-(void*) createMapPoint:(MKMapView *)mapView coordinateX:(double)coorX coordinateY:(double)coorY Title:(NSString*)title Subtitle:(NSString*)subtitle{
if(mapView!=nil){
//set POI lat and lng
CLLocationCoordinate2D p1;
POI *poi; if(coorX && coorY){
p1.latitude=coorX;
p1.longitude = coorY;
poi = [[POI alloc] initWithCoords:p1];
if(title!=NULL)
poi.title=title;
if(subtitle!=NULL)
poi.subtitle=subtitle;
[mapView addAnnotation:poi];
[poi release];
}
}
return NULL;
}
第二部:参考MKMapView的说明文件可以看到viewForAnnotation这个方法,这是MKMapView实际建立坐标点的地方。MKMapView类别在渲染地图的时候会按照Annotation集合中的资料建立坐标点。
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id ) annotation{
//方法一:using default pin as a PlaceMarker to display on map MKPinAnnotationView *newAnnotation = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"annotation1"]; newAnnotation.pinColor = MKPinAnnotationColorGreen; newAnnotation.animatesDrop = YES; //canShowCallout: to display the callout view by touch the pin newAnnotation.canShowCallout=YES; UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclo sure]; [button addTarget:self action:@selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpIns ide]; newAnnotation.rightCalloutAccessoryVie w=button; return newAnnotation;
//方法二:using the image as a PlaceMarker to display on map }
Annotation 集合中有几个坐标点viewForAnnotation方法就会被执行几次。因此每次viewForAnnotation被执行,我们都要实例化一个MKAnnotationView。 MKMapView接收到MKAnnotationView的实例就会将它先是在地图上。在上面代码中使用了 MKPinAnnotationView是集成自MKAnnotationView,作用就是在地图上显示一个大头钉。你可以用
annotationView.pinColor = MKPinAnnotationColorGreen;
可以设置大头钉的颜色,不过只有红色、紫色、绿色三种。
newAnnotation.canShowCallout=YES;
设定在点大头钉的时候效果。 第10行到第12行动态建立了一個DetailDisclousue类型的按钮,替这个按钮设置了一个UIControlEventTouchUpIns
被注释的方法二 是直接使用MKAnnotationView建立坐标点,并且设置它的image属性。
- (void)checkButtonTapped:(id)sender event:(id)event{
UIAlertView *tmp= [[UIAlertView alloc] initWithTitle:@"hi!" message:@"test" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[tmp show];
[tmp release];
}
插入MapView,设置Delegate(一般为Controller),Annotations记录兴趣位置点(AnnotationView用来显示兴趣位置点),annotation是可选的,选中的annotation会显示callout,用来显示信息。
2、设置地图显示类型:
mapView.mapType = MKMapTypeStandard;
mapView.mapType = MKMapTypeSatellite;
mapView.mapType = MKMapTypeHybrid;
3、显示用户位置
设置为可以显示用户位置:
mapView.showsUserLocation = YES;
判断用户当前位置是否可见(只读属性):
userLocationVisible
得到用户位置坐标:当userLocationVisible为YES时
CLLocationCoordinate2D coords = mapView.userLocation.location.coordinate;
4、坐标范围
MKCoordinateRegion用来设置坐标显示范围。
包括两部分:Center(CLLocationCoordinate2D struct,包括latitude和longitude),坐标中心
和Span(MKCoordinateSpan struct,包括latitudeDelta和longitudeDelta),缩放级别
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWi
以上代码创建一个以center为中心,上下各1000米,左右各1000米得区域,但其是一个矩形,不符合MapView的横纵比例
MKCoordinateRegion adjustedRegion = [mapView regionThatFits:viewRegion];
以上代码创建出来一个符合MapView横纵比例的区域
[mapView setRegion:adjustedRegion animated:YES];
以上代码为:最终显示该区域
5、Delegate
使用MapView须符合MKMapViewDelegate协议
5.1、地图加载Delegate
当需要从Google服务器取得新地图时
mapViewWillStartLoadingM
当成功地取得地图后
mapViewDidFinishLoadingM
当取得地图失败后(建议至少要实现此方法)
mapViewDidFailLoadingMap
5.2、范围变化Delegate
当手势开始(拖拽,放大,缩小,双击)
mapView:regionWillChangeAnimated
当手势结束(拖拽,放大,缩小,双击)
mapView:regionDidChangeAnimated:
判断坐标是否在MapView显示范围内:
CLLocationDegrees leftDegrees = mapView.region.center.longitude –(mapView.region.span.longitudeDelta / 2.0);
CLLocationDegrees rightDegrees = mapView.region.center.longitude +(mapView.region.span.longitudeDelta / 2.0);
CLLocationDegrees bottomDegrees = mapView.region.center.latitude –(mapView.region.span.latitudeDelta / 2.0);
CLLocationDegrees topDegrees = self.region.center.latitude +(mapView.region.span.latitudeDelta / 2.0);
if (leftDegrees > rightDegrees) { // Int'l Date Line in View
leftDegrees = -180.0 - leftDegrees;
if (coords.longitude > 0) // coords to West of Date Line
coords.longitude = -180.0 - coords.longitude;
}
If (leftDegrees <= coords.longitude && coords.longitude <= rightDegrees && bottomDegrees <= coords.latitude && coords.latitude <= topDegrees) {
// 坐标在范围内
}
6、Annotation
Annotation包含两部分:Annotation Object和Annotation View
Annotation Object必须符合协议MKAnnotation,包括两个方法:title和subtitle,分别用于显示注释的标题和子标题。还有coordinate属性,返回CLLocationCoordinate2D,表示Annotation的位置
然后,需使用mapView:viewForAnnotation: 方法来返回MKAnnotationView或者MKAnnotationView的子类用来显示Annotation(注意:这里显示的不是选中Annotation后的弹出框)
你可以子类化MKAnnotationView,然后再drawRect:方法里面进行自己的绘制动作(这个方法很蠢)
你完全可以实例化一个MKAnnotationView,然后更改它的image属性,这样很简单。
7、添加移除Annotation
添加一个Annotation
[mapView addAnnotation:annotation];
添加一个Annotation数组
[mapView addAnnotations:[NSArray arrayWithObjects:annotation1, annotation2, nil]];
移除一个Annotation
removeAnnotation:
移除一个Annotation数组
removeAnnotations:
移除所有Annotation
[mapView removeAnnotations:mapView.annotations];
8、选中Annotation
一次只能有一个Annotation被选中,选中后会出现CallOut(浮动框)
简单的CallOut显示Title和SubTitle,但你也可以自定义一个UIView作为CallOut(与自定义的TableViewCell一样)
可通过代码选中Annotation:
selectAnnotation:animated:
或者取消选择:
deselectAnnotation:animated:
9、显示Annotation
通过mapView:viewForAnnotation: 方法显示Annotation,每在MapView中加入一个Annotation,就会调用此方法
示例(与tableView:cellForRowAtIndexPath: 很相似)
- (MKAnnotationView *) mapView:(MKMapView *)theMapView viewForAnnotation:(id ) annotation {
static NSString *placemarkIdentifier = @"my annotation identifier";
if ([annotation isKindOfClass:[MyAnnotation class]]) {
MKAnnotationView *annotationView = [theMapView dequeueReusableAnnotatio
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:placemarkIdentifier];
annotationView.image = [UIImage imageNamed:@"blood_orange.png"];
}
else
annotationView.annotation = annotation;
return annotationView;
}
return nil;
}
10、取得真实地址
示例:
初始化MKReverseGeocoder
MKReverseGeocoder *geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:coordinates];
geocoder.delegate = self;
[geocoder start];
如果无法处理坐标,则调用reverseGeocoder:didFailWithError: 方法
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error {
NSLog(@"Error resolving coordinates: %@", [error localizedDescription]);
geocoder.delegate = nil;
[geocoder autorelease];
}
如果成功,则调用reverseGeocoder:didFindPlacemark: 并把信息存储在MKPlacemark 中
didFindPlacemark:(MKPlacemark *)placemark {
NSString *streetAddress = placemark.thoroughfare;
NSString *city = placemark.locality;
NSString *state = placemark.administrativeArea;
NSString *zip = placemark.postalCode;
// Do something with information
geocoder.delegate = nil;
[geocoder autorelease];
}