I wanted to modify apple's earthquakes project to display a map together with the location magnitude and time. In the json file I can see the coordinates but for the life of me I cannot read them and use them as latitude and longitude for the map. I succeeded to display the map by using the address (title) but the format changes and there are too many possibilities to account for. The earthquake project can be downloaded at https://developer.apple.com/documentation/coredata/loading_and_displaying_a_large_data_feed I post the Quake.swift file below so you may have an idea of what I tried. I added a coordinates characteristic to their magnitude, place and time first as an array and then as a string but I always fail to read it and use it to display the map as latitude and longitude.
Thanks in advance for your help.
The json file is long so I post a few lines here to give you an idea of the format:
{"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"},
The Quake.swift file:
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
]
}
}
CodePudding user response:
The coordinates are not on the same level as properties
, they are in a sibling geometry
. The basic pattern is
{
"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]
}
}
]
}
You have to decode the coordinates in GeoJSON
by adding geometry
to FeatureCodingKeys
. And you have to extend the Core Data model to preserve the coordinates.
In the Core Data model add two properties
longitude - Double - non-optional, use scalar type latitude - Double - non-optional, use scalar type
In Quake.swift
import CoreLocation
In the class
Quake
add@NSManaged var latitude: CLLocationDegrees @NSManaged var longitude: CLLocationDegrees
and replace
update(from
withfunc 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 }
In the
Quake
extension addvar coordinate : CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: latitude, longitude: longitude) }
In GeoJSON extend
FeatureCodingKeys
private enum FeatureCodingKeys: String, CodingKey { case properties, geometry }
and replace the
while
loop withwhile !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) } }
Add the struct
struct QuakeGeometry: Decodable { let coordinates : [Double] }
In
QuakeProperties
addvar latitude : CLLocationDegrees = 0.0 var longitude : CLLocationDegrees = 0.0
and replace
dictionaryValue
withvar dictionaryValue: [String: Any] { [ "magnitude": magnitude, "place": place, "time": Date(timeIntervalSince1970: TimeInterval(time) / 1000), "code": code, "latitude": latitude, "longitude": longitude ] }
Finally in DetailView.swift
import MapKit
and replace
QuakeDetail
withstruct 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) } } } }