I am still learning, so any help would be much appreciated. I am trying to parse a JSON file, I can't seem to make it work. Followed some online tutorials but I can't find one for this kind of JSON tree.
Here is the structure of the 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"
}
I am trying to get the coordinates from the geometry tree level, as well as the data from the properties level, that I will use to annotate a map, but until then I just need to print that information.
So far I have the following structs and func:
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)
}
}
}
}
The above code works if I comment the geometry and properties constants of Feature struct
UPDATE 180821:
Thanks to the comment of @vadian I found out about the geoJson decoder, therefore I am going to post my code that parse the geoJson and shows the polygon overlays on the map, if anyone needs it.
Now I am trying to find out a solution on how to show the annotations (check the decodeAnnotations()
function below) on the map when the user taps the overlay polygon. Would have been easier if there were pointannotations with a location. But I don't know how to show them if it is a polygon overlay. If anyone has any hint please let me know.
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)!)
}))
})
}
}
You got field types in your structs a bit incorrectly compared to the JSON format you provided. These structs work for me:
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
}