从 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)
}
}
}
}
我想修改 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
在class
Quake
中添加@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) } } } }