【Swift】MapKitで経由地点を含めたルートディレクションの表示方法
この記事ではMapKitを使ってGoogle Mapのようにwaypoints(経由地点)を含めたルートをマップ上に絵画する方法を書いていきます。
Google Mapのように経由地点も含めて一括でルート計算出来れば楽なのですが、2019年9月時点では出発地点と目標地点のルートしか計算することが出来ません(私が知らないだけかもしれませんが)。
ですので、A、B、C地点がある場合、まず
②B → Cへのルートを計算。マップに絵画
上記のように、ルートを繋げて完成させていく形になります。
1. 準備
今回は皇居を回るようなルートを例に使っていきます。Google Mapでルート表示した際には下記の画像のようになります。
それぞれの地点の座標は以下です。
皇居外苑(B地点): 35.68026, 139.75801
国立劇場(C地点): 35.6818, 139.74326
九段下駅(D地点): 35.69555, 139.75074
2. ピンを立てる
まずはマップにピンを立てるところから始めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import UIKit import MapKit class MapViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! //座標の配列 let coordinatesArray = [ ["name":"東京駅", "lat":35.68124, "lon": 139.76672], ["name":"皇居外苑", "lat":35.68026, "lon": 139.75801], ["name":"国立劇場", "lat":35.6818, "lon": 139.74326], ["name":"九段下駅", "lat":35.69555, "lon": 139.75074] ] override func viewDidLoad() { super.viewDidLoad() self.mapView.delegate = self makeMap() } func makeMap(){ //マップの表示域を設定 let coordinate = CLLocationCoordinate2DMake(coordinatesArray[0]["lat"] as! CLLocationDegrees, coordinatesArray[0]["lon"] as! CLLocationDegrees) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: coordinate, span: span) self.mapView.setRegion(region, animated: true) for i in 0..<coordinatesArray.count { let annotation = MKPointAnnotation() annotation.title = coordinatesArray[i]["name"] as? String //ピンの吹き出しに名前が出るように annotation.coordinate = CLLocationCoordinate2DMake(coordinatesArray[i]["lat"] as! CLLocationDegrees, coordinatesArray[i]["lon"] as! CLLocationDegrees) self.mapView.addAnnotation(annotation) } } } extension MapViewController:MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { if annotation is MKUserLocation { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) pinView?.canShowCallout = true //吹き出しで情報を表示出来るように }else{ pinView?.annotation = annotation } return pinView } } |
配列の一番最初の座標の「東京駅」を中心に、それぞれの座標に赤いピンが立ちます。
3. ルート計算せずに線で繋げた場合
次に、それぞれのピンを地形に関係なく、ただ直線に結んで行きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
import UIKit import MapKit class MapViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! //座標の配列 let coordinatesArray = [ ["name":"東京駅", "lat":35.68124, "lon": 139.76672], ["name":"皇居外苑", "lat":35.68026, "lon": 139.75801], ["name":"国立劇場", "lat":35.6818, "lon": 139.74326], ["name":"九段下駅", "lat":35.69555, "lon": 139.75074] ] override func viewDidLoad() { super.viewDidLoad() self.mapView.delegate = self makeMap() } func makeMap(){ //マップの表示域を設定 let coordinate = CLLocationCoordinate2DMake(coordinatesArray[0]["lat"] as! CLLocationDegrees, coordinatesArray[0]["lon"] as! CLLocationDegrees) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: coordinate, span: span) self.mapView.setRegion(region, animated: true) var routeCoordinates: [CLLocationCoordinate2D] = [] for i in 0..<coordinatesArray.count { let annotation = MKPointAnnotation() let annotationCoordinate = CLLocationCoordinate2DMake(coordinatesArray[i]["lat"] as! CLLocationDegrees, coordinatesArray[i]["lon"] as! CLLocationDegrees) annotation.title = coordinatesArray[i]["name"] as? String //ピンの吹き出しに名前が出るように annotation.coordinate = annotationCoordinate routeCoordinates.append(annotationCoordinate) self.mapView.addAnnotation(annotation) } let polyLine = MKPolyline(coordinates: &routeCoordinates, count: routeCoordinates.count) self.mapView.addOverlay(polyLine) } } extension MapViewController:MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { if annotation is MKUserLocation { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) pinView?.canShowCallout = true //吹き出しで情報を表示出来るように }else{ pinView?.annotation = annotation } return pinView } //ピンを繋げている線の幅や色を調整 func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { let route: MKPolyline = overlay as! MKPolyline let routeRenderer = MKPolylineRenderer(polyline: route) routeRenderer.strokeColor = UIColor(red:1.00, green:0.35, blue:0.30, alpha:1.0) routeRenderer.lineWidth = 3.0 return routeRenderer } } |
人が通る道を考慮していないため、建物や水場の上に線が表示されています。
4. ルート計算した場合
次はちゃんと人が通れる道の上に線を引いていきしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import UIKit import MapKit class MapViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! //座標の配列 let coordinatesArray = [ ["name":"東京駅", "lat":35.68124, "lon": 139.76672], ["name":"皇居外苑", "lat":35.68026, "lon": 139.75801], ["name":"国立劇場", "lat":35.6818, "lon": 139.74326], ["name":"九段下駅", "lat":35.69555, "lon": 139.75074] ] override func viewDidLoad() { super.viewDidLoad() self.mapView.delegate = self makeMap() } func makeMap(){ //マップの表示域を設定 let coordinate = CLLocationCoordinate2DMake(coordinatesArray[0]["lat"] as! CLLocationDegrees, coordinatesArray[0]["lon"] as! CLLocationDegrees) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: coordinate, span: span) self.mapView.setRegion(region, animated: true) var routeCoordinates: [CLLocationCoordinate2D] = [] for i in 0..<coordinatesArray.count { let annotation = MKPointAnnotation() let annotationCoordinate = CLLocationCoordinate2DMake(coordinatesArray[i]["lat"] as! CLLocationDegrees, coordinatesArray[i]["lon"] as! CLLocationDegrees) annotation.title = coordinatesArray[i]["name"] as? String //ピンの吹き出しに名前が出るように annotation.coordinate = annotationCoordinate routeCoordinates.append(annotationCoordinate) self.mapView.addAnnotation(annotation) } var myRoute: MKRoute! let directionsRequest = MKDirections.Request() var placemarks = [MKMapItem]() //routeCoordinatesの配列からMKMapItemの配列にに変換 for item in routeCoordinates{ let placemark = MKPlacemark(coordinate: item, addressDictionary: nil) placemarks.append(MKMapItem(placemark: placemark)) } directionsRequest.transportType = .walking //移動手段は徒歩 for (k, item) in placemarks.enumerated(){ if k < (placemarks.count - 1){ directionsRequest.source = item //スタート地点 directionsRequest.destination = placemarks[k + 1] //目標地点 let direction = MKDirections(request: directionsRequest) direction.calculate(completionHandler: {(response, error) in if error == nil { myRoute = response?.routes[0] self.mapView.addOverlay(myRoute.polyline, level: .aboveRoads) //mapViewに絵画 } }) } } } } extension MapViewController:MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { if annotation is MKUserLocation { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) pinView?.canShowCallout = true //吹き出しで情報を表示出来るように }else{ pinView?.annotation = annotation } return pinView } //ピンを繋げている線の幅や色を調整 func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { let route: MKPolyline = overlay as! MKPolyline let routeRenderer = MKPolylineRenderer(polyline: route) routeRenderer.strokeColor = UIColor(red:1.00, green:0.35, blue:0.30, alpha:1.0) routeRenderer.lineWidth = 3.0 return routeRenderer } } |
先ほどのような直線的なルートではなく、道路上にルートが絵画されています。
記事冒頭に貼ったGoogle Mapと見比べてみると少々ルートは異なるみたいですが、概ね同じですね。
ちなみに、移動手段は
- Any (なんでも)
- Automobile(車)
- Transit(バスなどの交通機関)
- Walking(徒歩)
から選ぶことができます。
おまけ:ルートがマップに収まるように
一番最初に立てるピンがマップの中心地点になっているため、のちに立てるピンの位置によってはマップが収まりきらないことがあるかもしれません。
なので、ピンを立てた後にルートがマップ無いに収まるように調整しましょう。
func makeMap()に以下を追加します。
1 2 3 4 5 |
//ルートがマップに収まるように if let firstOverlay = self.mapView.overlays.first{ let rect = self.mapView.overlays.reduce(firstOverlay.boundingMapRect, {$0.union($1.boundingMapRect)}) self.mapView.setVisibleMapRect(rect, edgePadding: UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35), animated: true) } |
最終的なMapViewControllerは以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
import UIKit import MapKit class MapViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! //座標の配列 let coordinatesArray = [ ["name":"東京駅", "lat":35.68124, "lon": 139.76672], ["name":"皇居外苑", "lat":35.68026, "lon": 139.75801], ["name":"国立劇場", "lat":35.6818, "lon": 139.74326], ["name":"九段下駅", "lat":35.69555, "lon": 139.75074] ] override func viewDidLoad() { super.viewDidLoad() self.mapView.delegate = self makeMap() } func makeMap(){ //マップの表示域を設定 let coordinate = CLLocationCoordinate2DMake(coordinatesArray[0]["lat"] as! CLLocationDegrees, coordinatesArray[0]["lon"] as! CLLocationDegrees) let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) let region = MKCoordinateRegion(center: coordinate, span: span) self.mapView.setRegion(region, animated: true) var routeCoordinates: [CLLocationCoordinate2D] = [] for i in 0..<coordinatesArray.count { let annotation = MKPointAnnotation() let annotationCoordinate = CLLocationCoordinate2DMake(coordinatesArray[i]["lat"] as! CLLocationDegrees, coordinatesArray[i]["lon"] as! CLLocationDegrees) annotation.title = coordinatesArray[i]["name"] as? String //ピンの吹き出しに名前が出るように annotation.coordinate = annotationCoordinate routeCoordinates.append(annotationCoordinate) self.mapView.addAnnotation(annotation) } var myRoute: MKRoute! let directionsRequest = MKDirections.Request() var placemarks = [MKMapItem]() //routeCoordinatesの配列からMKMapItemの配列にに変換 for item in routeCoordinates{ let placemark = MKPlacemark(coordinate: item, addressDictionary: nil) placemarks.append(MKMapItem(placemark: placemark)) } directionsRequest.transportType = .walking //移動手段は徒歩 for (k, item) in placemarks.enumerated(){ if k < (placemarks.count - 1){ directionsRequest.source = item //スタート地点 directionsRequest.destination = placemarks[k + 1] //目標地点 let direction = MKDirections(request: directionsRequest) direction.calculate(completionHandler: {(response, error) in if error == nil { myRoute = response?.routes[0] self.mapView.addOverlay(myRoute.polyline, level: .aboveRoads) //mapViewに絵画 } }) } } //ルートがマップに収まるように if let firstOverlay = self.mapView.overlays.first{ let rect = self.mapView.overlays.reduce(firstOverlay.boundingMapRect, {$0.union($1.boundingMapRect)}) self.mapView.setVisibleMapRect(rect, edgePadding: UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35), animated: true) } } } extension MapViewController:MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { if annotation is MKUserLocation { return nil } let reuseId = "pin" var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) if pinView == nil { pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId) pinView?.canShowCallout = true //吹き出しで情報を表示出来るように }else{ pinView?.annotation = annotation } return pinView } //ピンを繋げている線の幅や色を調整 func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { let route: MKPolyline = overlay as! MKPolyline let routeRenderer = MKPolylineRenderer(polyline: route) routeRenderer.strokeColor = UIColor(red:1.00, green:0.35, blue:0.30, alpha:1.0) routeRenderer.lineWidth = 3.0 return routeRenderer } } |
参考URL