まずは、地図画面で地点と範囲を指定
早速ジオフェンシングの機能について紹介したいのだが、ジオフェンシングを使うためには、ユーザにジオフェンシングを作動させる領域を指定させなければならない。そこで、ジオフェンシングの解説に入る前に、まずはiOSの地図画面で任意の点と領域を表示するためのアノテーション・オーバーレイ機能を説明する。
iOSで地図の表示を行うには、MapKitフレームワークに含まれる、MKMapViewを用いる。MKMapViewはUIViewから派生するクラスであるが、他のUIView派生クラスと異なり、プログラマが直接的に描画を行うことができない。これは、MKMapViewの座標系が緯度・経度をベースとして表示するよう設計されているためである。
では、地図上にアプリ固有の図形や文字列を表示したい場合、どうすればよいのか。
MKAnnotation、MKOverlayというプロトコルを実装したクラスを定義し、そのインスタンスをMKMapViewに追加することで行う。
MKAnnotationは、地図上の特定の一点を「アノテーション」として表現する際に、その座標をMKMapViewに伝える場合に実装するプロトコルである。プロパティとして、以下を実装していることが必須である。
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate
なお、CLLocationCoordinate2Dは実世界の緯度・経度の座標を持つ構造体である。
一方のMKOverlayは、地図上の特定の領域図形を地図に重ね合わせて表示したい場合に、その形状と位置をMKMapViewに伝えるために実装するプロトコルで、プロパティとして、以下を実装する必要がある。
@property (nonatomic, readonly) MKMapRect boundingMapRect
こちらのMKMapRect型は、緯度経度座標系をメルカトル図法に基づく平面座標に射影した座標系に従うものなので、注意が必要である。
ただし、MapKitフレームワークでは、緯度経度と距離からboundingMapRectプロパティへの変換がサポートされたクラスが用意されており、多くの場合、それらを使用すれば間に合うため、座標系の変換を意識して実装する必要があるケースは少ないだろう。
これらのクラスには、円形の領域を表現するMKCircleクラス、多角形の領域を表現するMKPolygonなどがある。
では、実際の図形の詳細、もしくは文字列、イメージなどはどのように指定するのだろうか。
MkMapViewでは、mapView:viewForAnnotation:もしくはmapView:viewForOverlay:デリゲートメソッドが用意されている。これらのデリゲートメソッドが適切なタイミングでMKMapViewから呼び出されるので、プログラマは自分が描画したい内容を実装したUIViewサブクラスを返せば、MKMapView側で地図上の正確な位置にビューを配置し、描画を行ってくれる。
つまり、MKMapViewは、「View」と名が付いているものの、地図の情報もそれ自身で保持しているのである。いわば、MVCパターンにおけるModelの大部分を内包したオールインワンのクラスなのだ。アプリケーションプログラマは、MKMapViewが内包するモデルに自分が追加したい座標とその種類を追加することで、アプリ独自の図形を描画すると考えれば良いだろう。
なお、MapKitフレームワークには、MKMapViewに表示するためのコンビニエントクラスとして、標準地図アプリでおなじみの「ピン」を所定の位置に表示してくれるMKPinViewや、MKCircleクラスに対応して地図上に円を描画するMKCircleViewなども用意されている。
アノテーションとオーバーレイの実装例
それでは実際に地図上のアノテーションとオーバーレイを表示させてみよう。
今回の例では、東京スカイツリーの地点(35.71014,139.81085)に、ピンを表示してみる。下記の例ではMKMapViewのインスタンスが、ビューコントローラのmapViewプロパティとひも付いており、MKMapViewのデリゲートメソッドの呼び出し先オブジェクトがビューコントローラを指すようにコーディングがなされているものとする。
まず、viewDidLoadメソッドの処理で、東京スカイツリーの座標にアノテーションを追加する。(リスト1)
なお、下記のコードでは、あらかじめ、MKMapView#setRegion:animated:メソッドを用いて、同座標を中心とする1km=1000mの範囲を表示するよう設定している。
リスト1
- (void)viewDidLoad
{
[super viewDidLoad];
// 東京スカイツリー
CLLocationCoordinate2D coordinate
= CLLocationCoordinate2DMake(35.71014, 139.81085);
// 地図の表示範囲を東京スカイツリーを中心とした
// 1kmの領域に設定
MKCoordinateRegion region
= MKCoordinateRegionMakeWithDistance( coordinate, 1000.0, 1000.0 );
[self.mapView setRegion:region animated:YES];
// ピンを追加
MKPointAnnotation* annotaion = [[MKPointAnnotation alloc] init];
annotaion.coordinate = coordinate;
[self.mapView addAnnotation:annotaion];
}
次に、MKMapViewのaddAnnotaionメソッドを呼び出すと、MKMapViewからmapView:viewForAnnotation:が呼ばれるので、MKPinAnnotationViewのインスタンスを作成して返す。上述したように、MKPinAnnotationViewは標準の地図アプリのような「ピン」を地図上に表示するクラスである。(リスト2)
リスト2
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
MKAnnotationView* annotationView;
// 再利用可能なオブジェクトが存在していないか確認
annotationView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"pin"];
// 再利用可能なオブジェクトが存在していなければ新たに作成する
if( ! annotationView ){
annotationView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:@"pin"];
}
return annotationView;
}
図1のように、東京スカイツリーの位置にピンが表示される。
次に、東京スカイツリーを中心とした半径500mの円を表示してみよう。
まず、viewDidLoad:で(リスト1)の処理に追加する形で、下記のように、MKCircleオブジェクトを作成してMKMapViewに追加する。(リスト2)
リスト2
- (void)viewDidLoad
{
....
// 500mの範囲円を追加
MKCircle* circle = [MKCircle circleWithCenterCoordinate: coordinate radius: 500.0];
}
アノテーションと同様、mapView:viewForOverlay:メソッドが呼ばれるので、MKMapView上で円形を描画するMKCircleViewを作成して返す。(リスト3)
リスト3
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay
{
MKCircle* circle = overlay;
MKCircleView* circleOverlayView = [[MKCircleView alloc] initWithCircle:circle];
circleOverlayView.strokeColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];
circleOverlayView.lineWidth = 4.;
circleOverlayView.fillColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.35];
return circleOverlayView;
}
MKCircleViewは、地図上に描かれる円弧の線の太さ、色、塗りつぶし色などをカスタマイズすることができる。
以上で地図上の任意の点と、その範囲をユーザに示すことができるようになった(図2)。ボタンやスライダーなどのUI部品を組み合わせることで、任意の点と範囲をジオフェンシング機能に設定することができるだろう。