MKMapView のメモ

# 3種類の座標系

* 緯度経度
 * CLLocationCoordinate2D
 * 緯度経度で表される座標系、原点はアフリカ大陸西方沖
* マップ座標系
 * MK〜
 * メルカトル地図の座標系、原点は左上
* Core Graphics 座標系
 * CG〜
 * UIView における座標系、原点は左上

## 緯度経度

* CLLocationCoordinate2D
* 緯度経度値をもつ構造体. __WGS84__（米国で採用されている世界測地系）で定義される座標系
* latitude: 緯度. CLLocationDegrees (double) 型
* longitude: 経度. CLLocationDegrees (double) 型
* __『無効な値』__を表す場合、lat/lon それぞれが __NaN__ となる模様. __isnan()__ 関数で判定できる
* 緯度経度なので、本初子午線と赤道を境にマイナス値を取る場合もあり得る
* 日本はだいたい、{35, 135} 前後くらいになる
* {0, 0} はアフリカ大陸西方沖、赤道と子午線が交差する点
 * 拡大すると妙な島がぽつんと浮かんでいる。おそらく聖地（Google Maps では存在しない？）
 ![CF5h3JNUgAAXXDL.png](https://qiita-image-store.s3.amazonaws.com/0/3603/e7b9b2c8-d645-16da-b5a9-9617c5020f89.png)
 ![CF5h3GrUIAA-Szz.png](https://qiita-image-store.s3.amazonaws.com/0/3603/f2809a01-344b-126d-9f58-a5b49db6ee4d.png)

## マップ座標系

他と区別するためにマップ座標系と勝手に呼称する。

* MKMapRect
* MKMapPoint
* MKMapSize

MapView のあの見た目はメルカトル図法になる。原点は左上、北米大陸の左上の日付変更線上が {0, 0} になる。この場合の地図は西洋を中心に描かれた世界地図である。

MKMapPoint は普通マイナス値にはならないが、何かしらのエラーの際には __{-1, -1}__ となり得る（後述）

## とりあえず Apple の資料と MKGeometry.h は眺めるべし
* [ADC - 位置情報対応プログラミングガイド.pdf](https://developer.apple.com/jp/devcenter/ios/library/documentation/LocationAwarenessPG.pdf)
* MapKit.framework / MKGeometry.h


# 座標系変換・衝突判定

## MKMapRect を NSString に変換

```objectivec:文字列としてログ出力
NSLog(MKStringFromMapRect(mapRect));
```

## MKMapView の表示範囲を MKMapRect で取得

```objectivec:地図の表示範囲を取得
MKMapRect visibleRect = mapView.visibleMapRect;
```

## MKMapPoint から CLLocationCoordinate2D に変換

```objectivec:メルカトル図法の点から緯度経度に変換
MKMapPoint p = MKMapPointMake(100.0, 200.0);
CLLocationCoordinate2D coordinate = MKCoordinateForMapPoint;
```

## CLLocationCoordinate2D から MKMapPoint に変換

```objectivec:緯度経度からメルカトル図法の点に変換
CLLocationCoordinate2D coordinate = CLLocationCoordinate2D(35.0, 135.0);
MKMapPoint p = MKMapPointForCoordinate(coordinate);
```

### 注意

例えば以下のようにあり得ない緯度経度を指定すると、MKMapPoint には {-1, -1} が返ってくる。この座標は画面上には存在しない。

```objectivec:こんな場所はありません
CLLocationCoordinate2D coordinate = CLLocationCoordinate2D(999999, 999999);
MKMapPoint p = MKMapPointForCoordinate(coordinate);
```

## MKMapPoint と MKMapRect の衝突判定

マップ座標がマップ矩形の内側にあるかを判定する方法。

```objectivec:点が画面内にあるか
MKMapRect mapRect = mapView.visibleMapRect; // 表示している画面矩形
MKMapPoint point = MKMapPointMake(100, 200.0); // メルカトル図法上での点

// 点が画面内にあれば YES が返る
BOOL isInMapRect = MKMapRectContainsPoint(mapRect, point);
```

## CLLocationCoordinate2D と MKCircle の衝突判定

緯度経度と MKCircle との衝突判定を行う方法。ユーザーの座標が半径〜mの円の内側にあるかを判定する。

```objc:ユーザーがヨドバシカメラ梅田の近所にいるかを判定
CLLocationCoordinate2D yodobashiUmedaPoint = CLLocationCoordinate2DMake(34.704172, 135.496324); // 中心緯度経度
CLLocationDistance yodobashiUmedaRadius = 100.0; // 半径100m
MKCircle *yodobashiUmedaArea = [MKCircle circleWithCenterCoordinate:yodobashiUmedaPoint radius: yodobashiUmedaRadius];

// ユーザーの座標
CLLocationCoordinate2D userCoordinate = mapView.userLocation.coordinate;
MKMapPoint userLocationMapPoint = MKMapPointForCoordinate(userCoordinate);

// パスをオフスクリーン描画
MKCircleRenderer *yodobashiUmedaRenderer = [[MKCircleRenderer alloc] initWithCircle: yodobashiUmedaArea];
[yodobashiUmedaRenderer createPath];
// ユーザーの座標を CGPoint に変換
CGPoint userLocationPoint = [yodobashiUmedaRenderer pointForMapPoint:userLocationMapPoint];

// ユーザーの座標がパスに含まれるかを判定
if (CGPathContainsPoint(yodobashiUmedaRenderer.path, NULL, userLocationPoint, NO)) {
	// 近くにいるよ
}
else {
	// いないよ
}
```


# 2点間の距離をメートル単位で計算

```
MKMapPoint point1 = MKMapPointForCoordinate(coordinate1);
MKMapPoint point2 = MKMapPointForCoordinate(coordinate2);
CLLocationDistance distance = MKMetersBetweenMapPoints(point1, point2);
```

CLLocationDistance はメートル単位。