Finding RestRoom In Seoul Subway Restroom 1호선부터 8호선 역사 내 화장실 위치를 찾기 위한 앱입니다.
https://apple-document.tistory.com/156 에서 자세한 내용을 확인할 수 있습니다.
- 반드시 API Key를 info.plist에 추가하거나 앱을 시작할 때 API Key를 사용해야 지도를 띄울 수 있습니다.
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
target 'DiseaseTrackerMap' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for DiseaseTrackerMap
pod 'NMapsMap'
end좌표가 포함되어 있는 csv 파일을 불러옵니다. 하지만 csv를 xcode로 불러왔을때 정확하게 모두 가져오지 않는다는 것을 확인하였습니다. (outlier 존재)
if let path = Bundle.main.path(forResource: self.CSV_ASSET_NAME, ofType: "csv") {
let contents = try String(contentsOfFile: path, encoding: .utf8)
let lines = contents.components(separatedBy: .newlines)원하는 데이터를 keyTagMap 프로퍼티에 담아 addAll을 사용하여 클러스터링을 진행할 수 있습니다.
@Published var keyTagMap = [ItemKey: ItemData]()
self.clusterer?.addAll(self.keyTagMap)
클러스터 마커의 이름또한 정할 수 있고 크기, 마커를 탭했을 때 동작도 정의할 수 있습니다.
func updateClusterMarker(_ info: NMCClusterMarkerInfo, _ marker: NMFMarker) {
marker.captionText = String(info.size)
marker.captionTextSize = 16
marker.touchHandler = { (overlay: NMFOverlay) -> Bool in
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: info.position.lat, lng: info.position.lng))
cameraUpdate.animation = .easeIn
self.view.mapView.moveCamera(cameraUpdate)
return true
}
if info.size < 3 && info.size > 1 {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_LOW_DENSITY
} else if info.size < 10 {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_MEDIUM_DENSITY
} else {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_HIGH_DENSITY
}
}일반 마커를 탭하였을 때 특정 뷰를 띄우기 위해 코드를 만들었습니다.
func updateLeafMarker(_ info: NMCLeafMarkerInfo, _ marker: NMFMarker) {
let tag = info.tag as? ItemData
marker.captionText = tag?.toiletName ?? ""
marker.iconImage = NMF_MARKER_IMAGE_GREEN
marker.touchHandler = { [weak self] (overlay: NMFOverlay) -> Bool in
let cameraUpdate = NMFCameraUpdate(scrollTo: info.position)
self?.view.mapView.moveCamera(cameraUpdate)
self?.tappedMarkerInfo = info
self?.tappedMarkerKey = info.key as? ItemKey
self?.tappedMarkerTag = info.tag as? ItemData
return true
}
}탭한 마커의 정보를 나타내기 위해서 코드를 만들었으며 반복되는 코드의 길이를 줄이기 위해서 extension에 HStackTextView를 만들었습니다.
if let tag = coordinator.tappedMarkerTag, let key = coordinator.tappedMarkerKey {
HStackTextView(text: "위치:\(key.position.lat), \(key.position.lng)")
HStackTextView(text: "거리:\(Double(Int(key.distance ?? 0.0))/1000)km")
HStackTextView(text: "\(tag.line)호선")
HStackTextView(text: "화장실이름: " + tag.toiletName)
HStackTextView(text: "화장실 상세 위치: " + tag.toiletDetailedLocation)
HStackTextView(text: "게이트 내외부: " + tag.toiletDetailedLocationInGate)
HStackTextView(text: "운영시간: " + tag.openHours)
HStackPhoneCallVeiw(text: "전화번호: ", phoneNumber: tag.phoneNumber)
HStackTextView(text: "리모델링: " + tag.remodelingYearMonth)
HStackTextView(text: "기저귀교환대설치유무-남자화장실: " + transformXY(value: tag.diaperChangingTableInstallationMaleToilet))
HStackTextView(text: "기저귀교환대설치유무-여자화장실: " + transformXY(value: tag.diaperChangingTableInstallationFemaleToilet))
HStackTextView(text: "기저귀교환대설치유무-남자장애인화장실: " + transformXY(value: tag.diaperChangingTableInstallationMaleDisabledToilet))
HStackTextView(text: "기저귀교환대설치유무-여자장인화장실: " + transformXY(value: tag.diaperChangingTableInstallationFemaleDisabledToilet))
}func HStackTextView(text: String) -> some View {
HStack {
Text(text)
Spacer()
}
.padding(3)
}현 위치의 좌표와 목적지의 좌표 사이의 거리를 수직으로 뻗었을 때의 거리를 의미하며 실제 경로의 거리와 차이가 날 수 있습니다.
func distance(to coordinate1: NMGLatLng, coordinate2: NMGLatLng) -> Double {
let earthRadius: Double = 6371 // 지구의 반지름 (단위: km)
// 라디안 단위로 변환
let lat1Rad = coordinate1.lat * .pi / 180
let lon1Rad = coordinate1.lng * .pi / 180
let lat2Rad = coordinate2.lat * .pi / 180
let lon2Rad = coordinate2.lng * .pi / 180
// 위도와 경도의 차이
let latDiff = lat2Rad - lat1Rad
let lonDiff = lon2Rad - lon1Rad
// 위도와 경도의 차이에 대한 Haversine 공식
let a = sin(latDiff / 2) * sin(latDiff / 2) +
cos(lat1Rad) * cos(lat2Rad) *
sin(lonDiff / 2) * sin(lonDiff / 2)
let c = 2 * atan2(sqrt(a), sqrt(1 - a))
// 거리 계산
let distance = earthRadius * c
return distance
}func NaverMap(lat: Double, lng: Double) {
// URL Scheme을 사용하여 네이버맵 앱을 열고 자동차 경로를 생성합니다.
guard let url = URL(string: "nmap://route/car?dlat=\(lat)&dlng=\(lng)&appname=kr.co.kepco.ElectricCar") else { return }
// 앱 스토어 URL을 설정합니다.
guard let appStoreURL = URL(string: "http://itunes.apple.com/app/id311867728?mt=8") else { return }
if UIApplication.shared.canOpenURL(url) {
// 네이버맵 앱이 설치되어 있는 경우 앱을 엽니다.
UIApplication.shared.open(url)
} else {
// 네이버맵 앱이 설치되어 있지 않은 경우 앱 스토어로 이동합니다.
UIApplication.shared.open(appStoreURL)
}
}func KaKaoMap(lat: Double, lng: Double) {
// URL Scheme을 사용하여 kakaomap 앱 열고 경로 생성합니다
guard let url = URL(string: "kakaomap://route?ep=\(lat),\(lng)&by=CAR") else { return }
// Kakaomap 앱의 App Store URL 생성
guard let appStoreUrl = URL(string: "itms-apps://itunes.apple.com/app/id304608425") else { return }
let urlString = "kakaomap://open"
// Kakaomap 앱이 설치되어 있는지 확인하고 URL 열기
if let appUrl = URL(string: urlString) {
if UIApplication.shared.canOpenURL(appUrl) {
UIApplication.shared.open(url)
} else {
// Kakaomap 앱이 설치되어 있지 않은 경우 App Store URL 열기
print("안깔려있는데")
UIApplication.shared.open(appStoreUrl)
}
}
}func TMap(lat:Double, lng:Double) {
// URL Scheme을 사용하여 티맵 앱을 열고 자동차 경로를 생성합니다.
let urlStr = "tmap://route?rGoName=목적지&rGoX=\(lng)&rGoY=\(lat)"
// URL 문자열을 인코딩하여 올바른 형식으로 변환합니다.
guard let encodedStr = urlStr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
// 인코딩된 URL 문자열을 URL 객체로 변환합니다.
guard let url = URL(string: encodedStr) else { return }
// TMap 앱이 설치되어 있는지 확인합니다.
if UIApplication.shared.canOpenURL(url) {
// TMap 앱을 엽니다.
UIApplication.shared.open(url)
} else {
// TMap 앱이 설치되어 있지 않은 경우 앱 스토어로 이동합니다.
guard let appStoreURL = URL(string: "http://itunes.apple.com/app/id431589174") else { return }
UIApplication.shared.open(appStoreURL)
}
}

