从 json 文件获取经纬度以创建地图

Getting latitude and longitude from a json file to create a map

我想修改 apple 的地震项目以显示地图以及位置震级和时间。在 json 文件中,我可以看到坐标,但对于我来说,我无法读取它们并将它们用作地图的纬度和经度。我成功地使用地址(标题)显示了地图,但格式发生了变化,而且有太多的可能性需要考虑。 地震工程可以在https://developer.apple.com/documentation/coredata/loading_and_displaying_a_large_data_feed下载 我 post 下面是 Quake.swift 文件,因此您可能对我的尝试有所了解。我首先将坐标特征添加到它们的大小、位置和时间中,作为一个数组,然后作为一个字符串,但我总是无法读取它并使用它来将地图显示为纬度和经度。

在此先感谢您的帮助。

json 文件很长,所以我 post 在这里写几行让您了解格式:

{"type":"FeatureCollection","metadata":{"generated":1648109722000,"url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.geojson","title":"USGS All Earthquakes, Past Month","status":200,"api":"1.10.3","count":9406},"features":[{"type":"Feature","properties":{"mag":4.5,"place":"south of the Fiji Islands","time":1648106910967,"updated":1648108178040,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/us7000gwsr","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us7000gwsr.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"reviewed","tsunami":0,"sig":312,"net":"us","code":"7000gwsr","ids":",us7000gwsr,","sources":",us,","types":",origin,phase-data,","nst":null,"dmin":5.374,"rms":1.03,"gap":102,"magType":"mb","type":"earthquake","title":"M 4.5 - south of the Fiji Islands"},"geometry":{"type":"Point","coordinates":[179.1712,-24.5374,534.35]},"id":"us7000gwsr"},
{"type":"Feature","properties":{"mag":1.95000005,"place":"2 km NE of Pāhala, Hawaii","time":1648106708550,"updated":1648106923140,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/hv72960677","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/hv72960677.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"automatic","tsunami":0,"sig":59,"net":"hv","code":"72960677","ids":",hv72960677,","sources":",hv,","types":",origin,phase-data,","nst":33,"dmin":null,"rms":0.109999999,"gap":136,"magType":"md","type":"earthquake","title":"M 2.0 - 2 km NE of Pāhala, Hawaii"},"geometry":{"type":"Point","coordinates":[-155.463333129883,19.2151660919189,34.9500007629395]},"id":"hv72960677"},
{"type":"Feature","properties":{"mag":1.75,"place":"4km SE of Calabasas, CA","time":1648106545420,"updated":1648109717670,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/ci39976447","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/ci39976447.geojson","felt":7,"cdi":3.1,"mmi":null,"alert":null,"status":"automatic","tsunami":0,"sig":49,"net":"ci","code":"39976447","ids":",ci39976447,","sources":",ci,","types":",dyfi,nearby-cities,origin,phase-data,scitech-link,","nst":33,"dmin":0.04554,"rms":0.27,"gap":56,"magType":"ml","type":"earthquake","title":"M 1.8 - 4km SE of Calabasas, CA"},"geometry":{"type":"Point","coordinates":[-118.61,34.1285,2.92]},"id":"ci39976447"},

Quake.swift 文件:

import CoreData
import SwiftUI
import OSLog

// MARK: - Core Data

/// Managed object subclass for the Quake entity.
class Quake: NSManagedObject, Identifiable {
    
    // The characteristics of a quake.
    @NSManaged var magnitude: Float
    @NSManaged var place: String
    @NSManaged var time: Date
    @NSManaged var coordinates: String
    
    // A unique identifier used to avoid duplicates in the persistent store.
    // Constrain the Quake entity on this attribute in the data model editor.
    @NSManaged var code: String
    
    /// Updates a Quake instance with the values from a QuakeProperties.
    func update(from quakeProperties: QuakeProperties) throws {
        let dictionary = quakeProperties.dictionaryValue
        guard let newCode = dictionary["code"] as? String,
              let newMagnitude = dictionary["magnitude"] as? Float,
              let newPlace = dictionary["place"] as? String,
              let newTime = dictionary["time"] as? Date,
              let newCoordinates = dictionary["coordinates"] as? String
        else {
            throw QuakeError.missingData
        }
        
        code = newCode
        magnitude = newMagnitude
        place = newPlace
        time = newTime
        coordinates = newCoordinates
    }
}

// MARK: - SwiftUI

extension Quake {
    
    /// The color which corresponds with the quake's magnitude.
    var color: Color {
        switch magnitude {
        case 0..<1:
            return .green
        case 1..<2:
            return .yellow
        case 2..<3:
            return .orange
        case 3..<5:
            return .red
        case 5..<Float.greatestFiniteMagnitude:
            return .init(red: 0.8, green: 0.2, blue: 0.7)
        default:
            return .gray
        }
    }
    
    /// An earthquake for use with canvas previews.
    static var preview: Quake {
        let quakes = Quake.makePreviews(count: 1)
        return quakes[0]
    }
    
    @discardableResult
    static func makePreviews(count: Int) -> [Quake] {
        var quakes = [Quake]()
        let viewContext = QuakesProvider.preview.container.viewContext
        for index in 0..<count {
            let quake = Quake(context: viewContext)
            quake.code = UUID().uuidString
            quake.time = Date().addingTimeInterval(Double(index) * -300)
            quake.magnitude = .random(in: -1.1...10.0)
            quake.place = "15km SSW of Cupertino, CA"
            quake.coordinates = "-117.7153333,35.8655,7.59"
            quakes.append(quake)
        }
        return quakes
    }
}

// MARK: - Codable

/// creating or updating Quake instances.
struct GeoJSON: Decodable {
    
    private enum RootCodingKeys: String, CodingKey {
        case features
    }
    
    private enum FeatureCodingKeys: String, CodingKey {
        case properties
    }
    
    private(set) var quakePropertiesList = [QuakeProperties]()
    
    init(from decoder: Decoder) throws {
        let rootContainer = try decoder.container(keyedBy: RootCodingKeys.self)
        var featuresContainer = try rootContainer.nestedUnkeyedContainer(forKey: .features)
        
        while !featuresContainer.isAtEnd {
            let propertiesContainer = try featuresContainer.nestedContainer(keyedBy: FeatureCodingKeys.self)
            
            // Decodes a single quake from the data, and appends it to the array, ignoring invalid data.
            if let properties = try? propertiesContainer.decode(QuakeProperties.self, forKey: .properties) {
                quakePropertiesList.append(properties)
            }
        }
    }
}

/// A struct encapsulating the properties of a Quake.
struct QuakeProperties: Decodable {
    
    // MARK: Codable
    
    private enum CodingKeys: String, CodingKey {
        case magnitude = "mag"
        case place
        case time
        case code
        case coordinates
    }
    
    let magnitude: Float   // 1.9
    let place: String      // "21km ENE of Honaunau-Napoopoo, Hawaii"
    let time: Double       // 1539187727610
    let code: String       // "70643082"
    let coordinates: String // [-117.7153333,35.8655,7.59]
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let rawMagnitude = try? values.decode(Float.self, forKey: .magnitude)
        let rawPlace = try? values.decode(String.self, forKey: .place)
        let rawTime = try? values.decode(Double.self, forKey: .time)
        let rawCode = try? values.decode(String.self, forKey: .code)
        let rawCoordinates = try? values.decode(String.self, forKey: .coordinates)
        
        // Ignore earthquakes with missing data.
        guard let magntiude = rawMagnitude,
              let place = rawPlace,
              let time = rawTime,
              let code = rawCode,
              let coordinates = rawCoordinates
        else {
            let values = "code = \(rawCode?.description ?? "nil"), "
            + "mag = \(rawMagnitude?.description ?? "nil"), "
            + "place = \(rawPlace?.description ?? "nil"), "
            + "time = \(rawTime?.description ?? "nil"), "
            + "coordinates = \(rawCoordinates?.description ?? "nil")"
            
            let logger = Logger(subsystem: "com.example.apple-samplecode.Earthquakes", category: "parsing")
            logger.debug("Ignored: \(values)")
            
            throw QuakeError.missingData
        }
        
        self.magnitude = magntiude
        self.place = place
        self.time = time
        self.code = code
        self.coordinates = coordinates
    }
    
    // The keys must have the same name as the attributes of the Quake entity.
    var dictionaryValue: [String: Any] {
        [
            "magnitude": magnitude,
            "place": place,
            "time": Date(timeIntervalSince1970: TimeInterval(time) / 1000),
            "code": code,
            "coordinates": coordinates
        ]
    }
}

坐标与 properties 不在同一层级,它们在兄弟 geometry 中。基本模式是

 {
    "features": [
          {
       "properties": {
         "mag":1.9,
         "place":"21km ENE of Honaunau-Napoopoo, Hawaii",
         "time":1539187727610,"updated":1539187924350,
         "code":"70643082"
       },
       "geometry" : {
         "coordinates": [-122.8096695,38.8364983,1.96]
       }
     }
   ]
 }

您必须通过将 geometry 添加到 FeatureCodingKeys 来解码 GeoJSON 中的坐标。而且您必须扩展核心数据模型以保留坐标。


  • 在核心数据模型中添加两​​个属性

    longitude - Double - non-optional, use scalar type
    latitude - Double - non-optional, use scalar type
    
  • 在Quake.swift

    import CoreLocation
    
  • 在classQuake中添加

    @NSManaged var latitude: CLLocationDegrees
    @NSManaged var longitude: CLLocationDegrees
    
  • 并将update(from替换为

    func update(from quakeProperties: QuakeProperties) throws {
        let dictionary = quakeProperties.dictionaryValue
        guard let newCode = dictionary["code"] as? String,
              let newMagnitude = dictionary["magnitude"] as? Float,
              let newPlace = dictionary["place"] as? String,
              let newTime = dictionary["time"] as? Date,
              let newLatitude = dictionary["latitude"] as? CLLocationDegrees,
              let newLongitude = dictionary["longitude"] as? CLLocationDegrees
        else {
            throw QuakeError.missingData
        }
    
        code = newCode
        magnitude = newMagnitude
        place = newPlace
        time = newTime
        latitude = newLatitude
        longitude = newLongitude
    }
    
  • Quake扩展中添加

    var coordinate : CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
    
  • 在 GeoJSON 中扩展 FeatureCodingKeys

    private enum FeatureCodingKeys: String, CodingKey {
       case properties, geometry
    }
    
  • 并将while循环替换为

    while !featuresContainer.isAtEnd {
        let propertiesContainer = try featuresContainer.nestedContainer(keyedBy: FeatureCodingKeys.self)
        // Decodes a single quake from the data, and appends it to the array, ignoring invalid data.
        if var properties = try? propertiesContainer.decode(QuakeProperties.self, forKey: .properties),
           let geometry = try? propertiesContainer.decode(QuakeGeometry.self, forKey: .geometry) {
            let coordinates = geometry.coordinates
            properties.longitude = coordinates[0]
            properties.latitude = coordinates[1]
            quakePropertiesList.append(properties)
        }
    }
    
  • 添加结构

    struct QuakeGeometry: Decodable {
        let coordinates : [Double]
    }
    
  • QuakeProperties中添加

    var latitude : CLLocationDegrees = 0.0
    var longitude : CLLocationDegrees = 0.0
    
  • 并将dictionaryValue替换为

    var dictionaryValue: [String: Any] {
        [
            "magnitude": magnitude,
            "place": place,
            "time": Date(timeIntervalSince1970: TimeInterval(time) / 1000),
            "code": code,
            "latitude": latitude,
            "longitude": longitude
        ]
    }
    
  • 终于在DetailView.swift

    import MapKit
    
  • 并将QuakeDetail替换为

    struct QuakeDetail: View {
        var quake: Quake
    
        @State private var region : MKCoordinateRegion
    
        init(quake : Quake) {
            self.quake = quake
            _region = State(wrappedValue: MKCoordinateRegion(center: quake.coordinate,
                                                               span: MKCoordinateSpan(latitudeDelta: 0.3, longitudeDelta: 0.3)))
        }
    
        var body: some View {
            VStack {
                QuakeMagnitude(quake: quake)
                Text(quake.place)
                    .font(.title3)
                    .bold()
                Text("\(quake.time.formatted())")
                    .foregroundStyle(Color.secondary)
                Text("\(quake.latitude) - \(quake.longitude)")
    
                Map(coordinateRegion: $region, annotationItems: [quake]) { item in
                    MapMarker(coordinate: item.coordinate, tint: .red)
                }
            }
        }
    }