MapViewでピンを立てて線で結ぶにはどうするの?
散歩した軌跡をマップに表示したいと思い、MapViewでどう実現するのかを調べた。
そこで得た成果として『山手線の各駅にピンを立てて線で結ぶ』コードを書いたてみた。
XcodeのPlaygroundで動かしました。
ピンの立て方
アノテーションが正しい呼び方。(PinAnnotation
はDeprecated)
let annotation = MKPointAnnotation()
let (latitude, longitude) = ピンを立てる座標(経度、緯度)
annotation.title = title
annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
mapView.addAnnotation(annotation)
座標データMKPointAnnotation
を用意して、mapView.addAnnotation()
呼ぶだけ。
今回は使っていないが、複数の座標にまとめてピンを立てるなら、座標データを配列にしてmapView.addAnnotations()
を呼ぶ。
let annotations: [MKPointAnnotation] = [
座標データを配列にする
]
mapView.addAnnotations(annotations)
ピンの色などは後ほど説明するデリゲートメソッドで指定する。
座標間の結び方
線を引いていく順に座標を配列にしてpolyline
を作成し、mapView.addOverlay()
を呼ぶ。
let coordinates: [CLLocationCoordinate2D] = [
線で結びたい座標データをCLLocationCoordinate2Dの配列にする
]
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline, level: .aboveRoads)
線の色や太さなどは、次に説明するデリゲートメソッドで指定する。
MapViewデリゲートメソッド
class MapDelegate: NSObject, MKMapViewDelegate {
// ピンの色やイメージを指定する
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "annotation"
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.markerTintColor = .blue
// annotationView.glyphImage = 独自バルーンのイメージ
return annotationView
}
}
//線の色や太さを指定する
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = .orange
renderer.lineWidth = 3.0
return renderer
}
}
縮尺の指定
これを指定しないと、冒頭動画の初めのように日本列島全体が見渡せるかなり高所から見たマップが表示されます。
方法は、見せたい複数の座標点をカバーする範囲をMKCoordinateRegion
で指定して、mapView.setRegion()
を呼びます。
線で結ぶときに作ったPolyline
からboundingMapRect
プロパティで得られる領域を引数とします。
let coordinates: [CLLocationCoordinate2D] = [
線で結びたい座標データをCLLocationCoordinate2Dの配列にする
]
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline, level: .aboveRoads)
+ var region = MKCoordinateRegion(polyline.boundingMapRect)
+ region.span.latitudeDelta *= 1.2
+ region.span.longitudeDelta *= 1.2
+ mapView.setRegion(region, animated: true)
マップの上下左右に余裕ができるように1.2
倍しています。
『山手線の各駅にピンを立てて線で結ぶ』コード
最後に、冒頭の動画で使ったコード全体を載せておきます。
タイマーを使って、3秒ごとに駅にピンを立て線で結びます。
縮尺(MKCoordinateRegion)は、駅にピンを立てるたびに領域にunionで付け加えています。
各駅のデータは、コードの末尾に配列で定義しています。
import PlaygroundSupport
import MapKit
let mapView = MKMapView()
mapView.frame = CGRect(x: 0, y: 0, width: 600, height: 600)
PlaygroundPage.current.liveView = mapView
PlaygroundPage.current.needsIndefiniteExecution = true
let mapDelegate = MapDelegate()
mapView.delegate = mapDelegate
var stations = 0
var boundingMapRect = MKMapRect()
let timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { timer in
if stations > YamanoteLineCoordinates.count {
timer.invalidate()
} else {
let annotation = MKPointAnnotation()
let (title, latitude, longitude) = YamanoteLineCoordinates[stations % YamanoteLineCoordinates.count]
annotation.title = title
annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
if stations < YamanoteLineCoordinates.count {
mapView.addAnnotation(annotation)
}
if stations > 0 {
let (_, latitude, longitude) = YamanoteLineCoordinates[stations - 1]
let coordinate2 = [
CLLocationCoordinate2D(latitude: latitude, longitude: longitude),
annotation.coordinate
]
let polyline = MKPolyline(coordinates: coordinate2, count: coordinate2.count)
mapView.addOverlay(polyline, level: .aboveRoads)
boundingMapRect.isEmpty
boundingMapRect = boundingMapRect.isEmpty ? polyline.boundingMapRect : boundingMapRect.union(polyline.boundingMapRect)
var region = MKCoordinateRegion(boundingMapRect)
region.span.latitudeDelta = region.span.latitudeDelta * 1.2
region.span.longitudeDelta = region.span.longitudeDelta * 1.2
mapView.setRegion(region, animated: true)
}
}
stations += 1
}
class MapDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = .orange
renderer.lineWidth = 3.0
return renderer
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "annotation"
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.markerTintColor = .blue
return annotationView
}
}
}
var YamanoteLineCoordinates: [(String, Double, Double)] {
[ //(駅名, 緯度, 経度),
("東京", 35.681382, 139.766084),
("有楽町", 35.675069, 139.763328),
("新橋", 35.665498, 139.75964),
("浜松町", 35.655646, 139.756749),
("田町", 35.645736, 139.747575),
("高輪ゲートウェイ", 35.6355, 139.7407),
("品川", 35.630152, 139.74044),
("大崎", 35.6197, 139.728553),
("五反田", 35.626446, 139.723444),
("目黒", 35.633998, 139.715828),
("恵比寿", 35.64669, 139.710106),
("渋谷", 35.658517, 139.701334),
("原宿", 35.670168, 139.702687),
("代々木", 35.683061, 139.702042),
("新宿", 35.690921, 139.700258),
("新大久保", 35.701306, 139.700044),
("高田馬場", 35.712285, 139.703782),
("目白", 35.721204, 139.706587),
("池袋", 35.728926, 139.71038),
("大塚", 35.731401, 139.728662),
("巣鴨", 35.733492, 139.739345),
("駒込", 35.736489, 139.746875),
("田端", 35.738062, 139.76086),
("西日暮里", 35.732135, 139.766787),
("日暮里", 35.727772, 139.770987),
("鶯谷", 35.720495, 139.778837),
("上野", 35.713768, 139.777254),
("御徒町", 35.707438, 139.774632),
("秋葉原", 35.698683, 139.774219),
("神田", 35.69169, 139.770883),
] }
- Xcode 14.3
- Swift 5.8
- macOS 13.3.1(a)
おしまい
点の集合でも軌跡は表現できるが、やはり線で結びたいと思い調べて実装しました。(ピンを立てずに線を引くだけで軌跡を表現)
以上です