iOS MapKit - 点击了哪个叠加层

iOS MapKit - Which overlay was tapped

我正在尝试构建一个在地图上显示某些区域 (polygons/overlays) 的应用程序,当点击多边形时,我想在控制台上打印 print("Polygon \(zone_id ) 已被窃听")。多边形是从 GeoJSON 文件呈现的,我们还可以在属性特征中找到 zone_id。到目前为止,我在地图上渲染了叠加层,但我卡住了,我很感激从这里得到的指导。

我将粘贴我目前拥有的代码以及 GeoJSON 文件的片段。

import SwiftUI
import CoreLocation

struct Home: View {
   
  @StateObject var mapData = MapViewModel()
   
  var body: some View {
    ZStack{
       
      MapView()
        .environmentObject(mapData)
        .ignoresSafeArea(.all, edges: .all)
         
    }
  }
}

struct Home_Previews: PreviewProvider {
  static var previews: some View {
    Home()
  }
}
import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
   
  @EnvironmentObject var mapData: MapViewModel
   
  @State var restrictions: [MKOverlay] = []
   
  func makeCoordinator() -> Coordinator {
    return MapView.Coordinator()
  }
   
  func makeUIView(context: Context) -> MKMapView {
     
    let view = mapData.mapView
     
    view.showsUserLocation = true
    view.delegate = context.coordinator
     
    mapData.showRestrictedZones { (restrictions) in
      self.restrictions = restrictions
      view.addOverlays(self.restrictions)
    }
    return view
  }
   
  func updateUIView(_ uiView: MKMapView, context: Context) {
     
  }
   
  class Coordinator: NSObject, MKMapViewDelegate {
     
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
      if let polygon = overlay as? MKPolygon {
        let renderer = MKPolygonRenderer(polygon: polygon)
        renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
        renderer.strokeColor = .purple.withAlphaComponent(0.7)
         
        return renderer
      }
      return MKOverlayRenderer(overlay: overlay)
    }
  }
}
import SwiftUI
import MapKit

// All Map Data Goes here...

class MapViewModel: NSObject, ObservableObject {
   
  @Published var mapView = MKMapView()
   
   
  
  // Decode GeoJSON from the server
  func showRestrictedZones(completion: @escaping ([MKOverlay]) -> ()) {
    guard let url = URL(string: "https://flightplan.romatsa.ro/init/static/zone_restrictionate_uav.json") else {
      fatalError("Unable to get geoJSON") }
     
    downloadData(fromURL: url) { (returnedData) in
      if let data = returnedData {
        var geoJson = [MKGeoJSONObject]()
        do {
          geoJson = try MKGeoJSONDecoder().decode(data)
        } catch {
          fatalError("Unable to decode GeoJSON")
        }
        var overlays = [MKOverlay]()
        for item in geoJson {
          if let feature = item as? MKGeoJSONFeature {
            for geo in feature.geometry {
              if let polygon = geo as? MKPolygon {
                overlays.append(polygon)
                 
              }
            }
          }
        }
        DispatchQueue.main.async {
          completion(overlays)
        }
      }
    }
  }
   
  func downloadData( fromURL url: URL, completion: @escaping (_ data: Data?) -> ()) {
    URLSession.shared.dataTask(with: url) { (data, response, error) in
      guard
        let data = data,
        error == nil,
        let response = response as? HTTPURLResponse,
        response.statusCode >= 200 && response.statusCode < 300 else {
        print("Error downloading data.")
        completion(nil)
        return
      }
      completion(data)
    }
    .resume()
  }
}

这里是 GeoJSON 结构的片段

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "id": "zone_restrictionate_uav.2120",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [
                            24.98812963,
                            44.10877275
                        ],
                        [
                            24.98806588,
                            44.1070722
                        ],
                        [
                            24.98537796,
                            44.10717296
                        ],
                        [
                            24.9854417,
                            44.10887351
                        ],
                        [
                            24.98812963,
                            44.10877275
                        ]
                    ]
                ]
            },
            "geometry_name": "the_geom",
            "properties": {
                "zone_id": "RZ 2120",
                "human_readable_definition": "POLYGON: 440631.5819N 0245917.2667E- 440625.4599N 0245917.0372E- 440625.8227N 0245907.3606E- 440631.9446N 0245907.5901E- 440631.5819N 0245917.2667E",
                "wkt": "POLYGON((24.9881296281 44.1087727458,24.9880658794 44.1070721972,24.9853779561 44.1071729596,24.9854417047 44.1088735083,24.9881296281 44.1087727458))",
                "lower_lim": "GND",
                "upper_lim": "120m AGL",
                "contact": "mailto: aerofoto@mapn.ro",
                "status": "RESTRICTED"
            }
        },
        {
            "type": "Feature",
            "id": "zone_restrictionate_uav.2121",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [
                            26.44760265,
                            44.13453494
                        ],
                        [
                            26.43440247,
                            44.11089351
                        ],
                        [
                            26.38955244,
                            44.1359355
                        ],
                        [
                            26.40275262,
                            44.15957693
                        ],
                        [
                            26.44760265,
                            44.13453494
                        ]
                    ]
                ]
            },
            "geometry_name": "the_geom",
            "properties": {
                "zone_id": "RZ 2121",
                "human_readable_definition": "POLYGON: 440804.3258N 0262651.3695E- 440639.2166N 0262603.8489E- 440809.3678N 0262322.3888E- 440934.4769N 0262409.9094E- 440804.3258N 0262651.3695E",
                "wkt": "POLYGON((26.4476026463 44.1345349397,26.4344024672 44.1108935127,26.3895524427 44.1359355018,26.4027526218 44.1595769288,26.4476026463 44.1345349397))",
                "lower_lim": "GND",
                "upper_lim": "120m AGL",
                "contact": "mailto: aerofoto@mapn.ro",
                "status": "RESTRICTED"
            }
        }
    ],
    "totalFeatures": 339,
    "numberMatched": 339,
    "numberReturned": 339,
    "timeStamp": "2021-08-15T11:39:26.055Z"
}

好的,如果其他人需要的话,我找到了解决方案。

  1. 我向我的地图视图实例添加了一个 UITapGestureRecognizer。
  2. 我使用了 MKMapView 的 convert(_:toCoordinateFrom:) 将触摸点转换为地图坐标
  3. 我从该坐标创建了一个 MKMapPoint 并检查了 MKPolygon 渲染器路径是否包含该点
  4. 对于 MKPolygon,毕竟是 MKShape,我使用 .title 属性 分配从 GeoJSON 解析的 zone_id 值。

所以我能够确定哪个多边形被点击了。

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    
    var tapCoordinates: Binding<CLLocationCoordinate2D>
    var polygonID: Binding<String>
    @EnvironmentObject var mapData: MapViewModel
    @State var restrictions: [MKOverlay] = []
    @State var restrictionsData: [RestrictionInfo] = []
    
    func makeCoordinator() -> Coordinator {
        return MapView.Coordinator(self, tapCoordinatesBinding: tapCoordinates, polygonTitle: polygonID)
    }
    
    func makeUIView(context: Context) -> MKMapView {
        
        mapData.mapView.delegate = context.coordinator
        
        let view = mapData.mapView
        
        
        view.showsUserLocation = true
        view.delegate = context.coordinator
        
        mapData.showRestrictedZones { (restriction) in
            self.restrictions = restriction
            view.addOverlays(self.restrictions)
        }
        return view
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        
    }
    
    class Coordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
        var parent: MapView
        var polygonTitle: Binding<String>
        var gRecognizer = UITapGestureRecognizer()
        
        var tapCoordinatesBinding: Binding<CLLocationCoordinate2D>
        var coordinate = CLLocationCoordinate2D()

        
        init(_ parent: MapView, tapCoordinatesBinding: Binding<CLLocationCoordinate2D>, polygonTitle: Binding<String>) {
            self.parent = parent
            self.tapCoordinatesBinding = tapCoordinatesBinding
            self.polygonTitle = polygonTitle
            super.init()
            self.gRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
            self.gRecognizer.delegate = self
            self.parent.mapData.mapView.addGestureRecognizer(gRecognizer)
        }
        
        
        @objc func tapHandler(_ gesture: UITapGestureRecognizer) {
            // position on the screen, CGPoint
            let location = gRecognizer.location(in: self.parent.mapData.mapView)
            // position on the map, CLLocationCoordinate2D
            coordinate = self.parent.mapData.mapView.convert(location, toCoordinateFrom: self.parent.mapData.mapView)
            tapCoordinatesBinding.wrappedValue = coordinate
            
            for overlay: MKOverlay in self.parent.mapData.mapView.overlays {
                if let polygon = overlay as? MKPolygon {
                    let renderer = MKPolygonRenderer(polygon: polygon)
                    let mapPoint = MKMapPoint(coordinate)
                    let rendererPoint = renderer.point(for: mapPoint)
                    if renderer.path.contains(rendererPoint) {
                        print("Tap inside polygon")
                        print("Polygon \(polygon.title ?? "no value") has been tapped")
                        polygonTitle.wrappedValue = polygon.title!
                    }
                }
            }
        }
        
        
        func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
            if let polygon = overlay as? MKPolygon {
                let renderer = MKPolygonRenderer(polygon: polygon)
                renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
                renderer.strokeColor = .purple.withAlphaComponent(0.7)
                renderer.lineWidth = 2
                
                return renderer
            }
            return MKOverlayRenderer(overlay: overlay)
        }
    }
}
import SwiftUI
import MapKit

// All Map Data Goes here...

class MapViewModel: NSObject, ObservableObject {
    
    @Published var mapView = MKMapView()
    
    
  
    func restrictionsInfo(completion: @escaping ([RestrictionInfo]) -> ()) {
        guard let url = URL(string: "https://flightplan.romatsa.ro/init/static/zone_restrictionate_uav.json") else {
            fatalError("Unable to get geoJSON") }
        
        downloadData(fromURL: url) { (returnedData) in
            if let data = returnedData {
                var geoJson = [MKGeoJSONObject]()
                do {
                    geoJson = try MKGeoJSONDecoder().decode(data)
                } catch {
                    fatalError("Unable to decode GeoJSON")
                }
                
                
                var restrictionsInfo = [RestrictionInfo]()
                for item in geoJson {
                    if let feature = item as? MKGeoJSONFeature {
                        let propData = feature.properties!
                        for geo in feature.geometry {
                            if geo is MKPolygon {
                                let polygonInfo = try? JSONDecoder.init().decode(RestrictionInfo.self, from: propData)
                                restrictionsInfo.append(polygonInfo!)
                            }
                        }
                    }
                }
                DispatchQueue.main.async {
                    completion(restrictionsInfo)
                }
            }
        }
    }
    
    
    // Decode GeoJSON from the server
    func showRestrictedZones(completion: @escaping ([MKOverlay]) -> ()) {
        guard let url = URL(string: "https://flightplan.romatsa.ro/init/static/zone_restrictionate_uav.json") else {
            fatalError("Unable to get geoJSON") }
        
        downloadData(fromURL: url) { (returnedData) in
            if let data = returnedData {
                var geoJson = [MKGeoJSONObject]()
                do {
                    geoJson = try MKGeoJSONDecoder().decode(data)
                } catch {
                    fatalError("Unable to decode GeoJSON")
                }

                var overlays = [MKOverlay]()
                for item in geoJson {
                    if let feature = item as? MKGeoJSONFeature {
                        let propData = feature.properties!
                        for geo in feature.geometry {
                            if let polygon = geo as? MKPolygon {
                                let polygonInfo = try? JSONDecoder.init().decode(RestrictionInfo.self, from: propData)
                                polygon.title = polygonInfo?.zone_id
                                overlays.append(polygon)
                            }
                        }
                    }
                }
                DispatchQueue.main.async {
                    completion(overlays)
                }
            }
        }
    }
    
    func downloadData( fromURL url: URL, completion: @escaping (_ data: Data?) -> ()) {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard
                let data = data,
                error == nil,
                let response = response as? HTTPURLResponse,
                response.statusCode >= 200 && response.statusCode < 300 else {
                print("Error downloading data.")
                completion(nil)
                return
            }
            completion(data)
        }
        .resume()
    }
}