首页 > 解决方案 > Swift 5 解析多级 JSON

问题描述

我还在学习,所以任何帮助将不胜感激。我正在尝试解析 JSON 文件,但似乎无法使其工作。遵循了一些在线教程,但我找不到这种 json 树的教程。

这是json的结构:

{
    "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"
}

我正在尝试从几何树级别获取坐标,以及从属性级别获取数据,我将使用它们来注释地图。但在那之前我只需要打印这些信息。

到目前为止,我有以下结构和函数:

struct Notam: Decodable {
    let type: String
    let features: [Feature]
    let totalFeatures: Int
    let numberMatched: Int
    let numberReturned: Int
    let timeStamp: String
}

struct Feature: Decodable {
    let type: String
    let id: String
    let geometry: [Geometry]
    let geometry_name: String
    let properties: [Properties]   
}

struct Geometry: Decodable {
    let type: String
    let coordinates: [[Double]]
}

struct Properties: Decodable {
    let zone_id: String
    let wkt: String
    let contact: String
}
import SwiftUI

struct ContentView: View {  
    var body: some View {
        Text("Hello World")
            .onAppear(perform: readFile)
    }
    
    private func readFile() {
        if let url = URL(string: "https://www.yeltsy.com/wp-content/uploads/2021/08/restrictions.json"),
           let data = try? Data(contentsOf: url) {
            let decoder = JSONDecoder()
            
            if let jsonData = try? decoder.decode(Notam.self, from: data) {
                print(jsonData.totalFeatures)
            }
        }
    }
}

如果我注释 Feature 结构的几何和属性常量,上面的代码就可以工作

更新 180821

感谢@vadian 的评论,我发现了geoJson 解码器,因此如果有人需要,我将发布解析geoJson 并在地图上显示多边形叠加层的代码。

现在,我正在尝试找出当用户点击覆盖多边形时如何在地图上显示注释(检查下面的 decodeAnnotations() 函数)的解决方案。如果有带有位置的点注释会更容易。但我不知道如果它是一个多边形叠加层如何显示它们。如果有人有任何提示,请告诉我。

import SwiftUI
import MapKit
import CoreLocation

// All Map Data Goes here...

class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    @Published var mapView = MKMapView()
    
    @Published var region: MKCoordinateRegion!
    
    // Based on Location it Will set up
    
    // Alert
    @Published var permissionDenied = false
    
    @Published var mapType: MKMapType = .standard

    
    func updateMapType() {
        if mapType == .standard {
            mapType = .hybrid
            mapView.mapType = mapType
        }
        else {
            mapType = .standard
            mapView.mapType = mapType
        }
    }
    
    // Focus Location...
    func focusLocation() {
        guard let _ = region else {return}
        
        mapView.setRegion(region, animated: true)
        mapView.setVisibleMapRect(mapView.visibleMapRect, animated: true)
    }
    
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // Checking User Permission for Location Sharing
        
        switch manager.authorizationStatus {
        case .denied:
            // If request denied, alert the user ...
            permissionDenied.toggle()
        case .notDetermined:
            //If not determined, Request permission
            manager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse:
            manager.requestLocation()
            manager.startUpdatingLocation()
        default:
            ()
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // Getting user Region
        
        guard let location = locations.last else {return}

        self.region = MKCoordinateRegion(center: location.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
        
        // Updating map
        self.mapView.setRegion(self.region, animated: true)
        
        //Smooth Animation..
        self.mapView.setVisibleMapRect(self.mapView.visibleMapRect, animated: true)
        manager.stopUpdatingLocation()
        
    }
    
    // Decode GeoJSON from the server
        func showRestrictedZones(completion: @escaping ([MKOverlay]) -> ()) {
        guard let url = URL(string: "https://www.yeltsy.com/wp-content/uploads/2021/08/restrictions.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 decodeAnnotations() {
        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 jsonData = returnedData {
                do {
                    let result = try JSONDecoder().decode(FeatureCollection.self, from: jsonData)
                    for feature in result.features {
                        print(feature.properties.contact)
                    }
                } catch { print("Error while parsing: \(error)") }
            }
        }
    }

    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()
    }
}
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 CoreLocation

struct Home: View {
    
    @StateObject var mapData = MapViewModel()
    // Location Manager..
    @State var locationManager = CLLocationManager()
    
    var body: some View {
        ZStack{
             MapView()
                .environmentObject(mapData)
                .ignoresSafeArea(.all, edges: .all)
            
            VStack {
                Spacer()
                
                VStack{
                    
                    Button(action: mapData.focusLocation, label: {
                        Image(systemName: "location.fill")
                            .font(.title2)
                            .padding(10)
                            .background(Color.white)
                            
                    })
                    
                    Divider()
                        .frame(width: 20)
                    
                    Button(action: mapData.updateMapType, label: {
                        Image(systemName: mapData.mapType == .standard ? "network" : "map.fill")
                            .font(.title2)
                            .padding(10)
                            .background(Color.white)
                            
                    })
                }
                .background(Color.white)
                .cornerRadius(10)
                .frame(maxWidth: .infinity, alignment: .trailing)
                .shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
                .padding()
            }
        }
        .onAppear(perform: {
            locationManager.delegate = mapData
            locationManager.requestWhenInUseAuthorization()
        })
        .alert(isPresented: $mapData.permissionDenied, content: {
            Alert(title: Text("Permission Denied"), message: Text("Please Enable Permission In App Settings"), dismissButton: .default(Text("Go To Settings"), action: {
                // Redirecting user to the settings...
                 UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
            }))
        })
    }
}

标签: iosjsonswiftapi

解决方案


与您提供的 JSON 格式相比,您的结构中的字段类型有点不正确。这些结构对我有用:

struct Notam: Decodable {
    let type: String
    let features: [Feature]
    let totalFeatures: Int
    let numberMatched: Int
    let numberReturned: Int
    let timeStamp: String
}

struct Feature: Decodable {
    let type: String
    let id: String
    let geometry: Geometry
    let geometry_name: String
    let properties: Properties
}

struct Geometry: Decodable {
    let type: String
    let coordinates: [[[Double]]]
}

struct Properties: Decodable {
    let zone_id: String
    let wkt: String
    let contact: String
}

推荐阅读