[iOS] GeoJSON

RudinP·2024년 6월 27일
0

Study

목록 보기
237/258

GeoJSON을 추가, 파싱, 지도에 마커 추가 방법을 알아본다.

GeoJSON

  • 위치정보를 가진 JSON
  • 지형지물, 거리, 고속도로 등의 LineString, 지역을 표시하는 다각형 등이 저장되어 있다.

예시

  • features: 위치 데이터의 배열
  • type: feature가 대부분 저장됨
  • geometry: 좌표가 저장됨.
    • point
    • lineString
    • Polygon
  • property: 원하는 키와 값을 자유롭게 저장 가능
{
  "type": "FeatureCollection", 
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [102.0, 0.5]
      },
      "properties": {
        "prop0": "value0"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
        ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": 0.0
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
            [100.0, 1.0], [100.0, 0.0]
          ]
        ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": { "this": "that" }
      }
    }
  ]
}

GeoJSON 제작

geojson.io 에서 제작할 수 있다.

GeoJSON 파싱

  • 우리가 직접 타입을 만들지 않아도 된다.
  • 기본으로 제공되는 타입을 그대로 사용한다.

1. url 객체 생성

2. dataTask 요청

3. MKGeoJSONDecoder로 결과 Decode

4. 사용하는 부분에서 결과값 받기

  • MKGeoJSONObject 타입
func fetchMap() async throws -> [MKGeoJSONObject] {
        guard let url = URL(string: "https:kxapi.azurewebsites.net/geojson?apiKey=Vlma2ThXYk968FCuLLEy") else {
            throw "invalid url"
        }
        
        //dataTask 요청. response 사용하지 않으므로 생략(_)
        let (data, _) = try await URLSession.shared.data(from: url)
        //GeoJSONDecoder. 사용법 동일.
        let decoder = MKGeoJSONDecoder()
        let results = try decoder.decode(data)
        
        return results
    }
override func viewDidLoad() {
        super.viewDidLoad()
        ...
        
        Task{
            geoJsonObjects = try await fetchMap()
            print(geoJsonObjects)
        }
    }

  • 오브젝트의 feature에 접근하기 위해서는 MKGeoJSONFeature로 타입 캐스팅해야한다.

5. 타입캐스팅하기

Task{
            geoJsonObjects = try await fetchMap()
            
            for obj in geoJsonObjects {
                guard let feature = obj as? MKGeoJSONFeature else {continue}
                print(feature.geometry)
            }
        }

  • 하나의 좌표는 MKPointAnnotation으로, 구역은 MKPolygon으로 파싱된다.
  • GeoJSONDecoder가 파일을 파싱한 다음 맵뷰에 표시하기 적절한 형태로 디코딩 해주기 때문이다.

6. Properties 디코딩하기

  • properties에는 키-값 으로 사용자가 정의한 데이터가 들어가있는데, 이부분은 별도의 json으로 인식하면 된다.
  • 따라서 이 속성이 필요하면 JSONDecoding이 필요하다

Property: Codable struct 생성

  • 속성은 사용자가 정의한대로 구성하면 된다.

JSONDecode

        Task{
            geoJsonObjects = try await fetchMap()
            
            for obj in geoJsonObjects {
                guard let feature = obj as? MKGeoJSONFeature else {continue}
                let jsonDecoder = JSONDecoder()
                guard let pdata = feature.properties,
                      let properties = try? jsonDecoder.decode(Property.self, from: pdata) else {continue}
                guard let category = Category(rawValue: properties.category) else {continue}
                //포인트면 위치 마킹
                if let pointAnnotation = feature.geometry.first as? MKPointAnnotation {
                    if category == .attraction {
                        //지도에 마킹
                        mapView.addAnnotation(pointAnnotation)
                    }
                }
            }
        }

7. 지도에 마킹하기

  • 원하는 아이콘으로 마킹할 수 있다.

8. 포인트 이름 및 설명 변경

if category == .attraction {
	pointAnnotation.title = properties.name
    pointAnnotation.subtitle = properties.desc
    //지도에 마킹
    mapView.addAnnotation(pointAnnotation)
}

9. 지도 어노테이션(마크) 설정하기 MKMapViewDelegate

extension ViewController: MKMapViewDelegate{
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        //사용자의 현재 위치를 표시하는 경우 기본뷰를 표시하도록 nil 리턴
        guard !(annotation is MKUserLocation) else {return nil}
        //어노테이션이 포인트 어노테이션이면 마커 뷰를 표시
        if let pointAnnotation = annotation as? MKPointAnnotation {
            let marker = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation)
            //pointannotation에는 title, subtitle, 좌표 세 가지가 저장된다.
            //따라서 우리가 설정한 카테고리에 접근하려면 커스텀 어노테이션을 만들고 어노테이션에 프로퍼티를 함께 저장해야 함.
            return marker
        }
        
        return nil
    }
}

10. 커스텀 어노테이션 설정

커스텀 파일 생성

import Foundation
import MapKit
import CoreLocation

class EverlandAnnotation: NSObject, MKAnnotation{
    @objc dynamic var coordinate: CLLocationCoordinate2D
    let properties: Property
    
    init(coordinate: CLLocationCoordinate2D, properties: Property) {
        self.coordinate = coordinate
        self.properties = properties
    }
    
    var title: String?{
        return properties.name
    }
    
    var subtitle: String? {
        return properties.desc
    }
    
    var category: Category?{
        return Category(rawValue:  properties.category)
    }
    
    var image: UIImage? {
        guard let category else {return nil}
        
        return UIImage(named: category.rawValue)
    }
}

11. 맵뷰에서 어노테이션 추가하는 부분을 커스텀 어노테이션 추가로 변경

Task{
            geoJsonObjects = try await fetchMap()
            
            for obj in geoJsonObjects {
                guard let feature = obj as? MKGeoJSONFeature else {continue}
                let jsonDecoder = JSONDecoder()
                guard let pdata = feature.properties,
                      let properties = try? jsonDecoder.decode(Property.self, from: pdata) else {continue}
                guard let category = Category(rawValue: properties.category) else {continue}
                //포인트면 위치 마킹
                if let pointAnnotation = feature.geometry.first as? MKPointAnnotation {
                    if category == .attraction {
                        //이부분 변경
                        let annotation = EverlandAnnotation(coordinate: pointAnnotation.coordinate, properties: properties)
                        //지도에 마킹
                        mapView.addAnnotation(annotation)
                    }
                }
            }
        }

12. 어노테이션 추가 부분에서 타입캐스팅 변경

13. 그리프이미지 변경으로 어노테이션 이미지 변경

extension ViewController: MKMapViewDelegate{
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        //사용자의 현재 위치를 표시하는 경우 기본뷰를 표시하도록 nil 리턴
        guard !(annotation is MKUserLocation) else {return nil}
        //어노테이션이 포인트 어노테이션이면 마커 뷰를 표시
        if let everlandAnnotation = annotation as? EverlandAnnotation {
            let marker = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation) as! MKMarkerAnnotationView
            //마커의 그리프이미지 변경
            marker.glyphImage = everlandAnnotation.image
            //pointannotation에는 title, subtitle, 좌표 세 가지가 저장된다.
            //따라서 우리가 설정한 카테고리에 접근하려면 커스텀 어노테이션을 만들고 어노테이션에 프로퍼티를 함께 저장해야 함.
            return marker
        }
        
        return nil
    }
}

참고

.glyphTintColor는 그리프이미지 자체의 색을,
.markerTintColor은 마커의 배경색을 변경하는데 사용한다.

profile
iOS 개발자가 되기 위한 스터디룸...

0개의 댓글