使用 MKAnnotations 来自 GeoJSON URL 端点的数据未出现在地图上
Data from GeoJSON URL end point not appearing on Map using MKAnnotations
我正在创建我的第一个 IOS 应用程序,我不是开发人员,而且我真的很喜欢地图注释。
我正在尝试从 GeoJSON URL 端点获取火灾数据,并使用 URLSession 和自定义 MKAnnotationView 以及自定义火针将火灾显示为地图上的注释。
问题是来自 URL 端点的 GeoJSON Fire 数据的注释没有出现在地图上,尽管 URL 会话正在返回数据。但是,如果我手动创建单个 Fire 注释,它会使用自定义图钉正确显示在地图上。
任何帮助将不胜感激,我花了几天时间试图解决这个问题:(
这是ViewController.Swift文件
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!
var locationManager:CLLocationManager!
var lat = Double()
var lon = Double()
var fires: [Fire] = []
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.register(FireMarkerView.self,forAnnotationViewWithReuseIdentifier:
MKMapViewDefaultAnnotationViewReuseIdentifier)
if let url = URL(string: "https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Active_Fires/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") {
URLSession.shared.dataTask(with: url) {data, response, error in
if let data = data {
do {
let features = try MKGeoJSONDecoder().decode(data)
.compactMap { [=11=] as? MKGeoJSONFeature }
let validWorks = features.compactMap(Fire.init)
self.fires.append(contentsOf: validWorks)
print([self.fires])
}
catch let error {
print(error)
}
}
}.resume()
}
//This code works an annotation appears correctly on map
/* let fire = Fire(
title: "Ford Fire",
incidentShortDescription: "Hwy 35",
incidentTypeCategory: "WF",
coordinate: CLLocationCoordinate2D(latitude: 37.7993, longitude: -122.1947))
mapView.addAnnotation(fire)*/
mapView.addAnnotations(fires)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways , .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .notDetermined , .denied , .restricted:
locationManager.requestWhenInUseAuthorization()
break
default:
break
}
switch manager.accuracyAuthorization {
case .fullAccuracy:
break
case .reducedAccuracy:
break
default:
break
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
//locationManager.startUpdatingHeading()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let userLocation = locations.first! as CLLocation
lat = userLocation.coordinate.latitude
lon = userLocation.coordinate.longitude
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 400000, longitudinalMeters: 400000)
self.mapView.setRegion(region, animated: true)
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// Need a solution for this.
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}
这是模型Class,Fire.swift
import Foundation
import MapKit
class Fire: NSObject, MKAnnotation {
let title: String?
let incidentShortDescription: String?
let incidentTypeCategory: String?
let coordinate: CLLocationCoordinate2D
init(
title: String?,
incidentShortDescription: String?,
incidentTypeCategory: String?,
coordinate: CLLocationCoordinate2D
) {
self.title = title
self.incidentShortDescription = incidentShortDescription
self.incidentTypeCategory = incidentTypeCategory
self.coordinate = coordinate
super.init()
}
init?(feature: MKGeoJSONFeature) {
// 1
guard
let point = feature.geometry.first as? MKPointAnnotation,
let propertiesData = feature.properties,
let json = try? JSONSerialization.jsonObject(with: propertiesData),
let properties = json as? [String: Any]
else {
return nil
}
// 3
title = properties ["IncidentName"] as? String
incidentShortDescription = properties["IncidentShortDescription"] as? String
incidentTypeCategory = properties["IncidentTypeCategory"] as? String
coordinate = point.coordinate
super.init()
}
var subtitle: String? {
return (incidentTypeCategory)
}
var image: UIImage {
guard let name = incidentTypeCategory else {
return #imageLiteral(resourceName: "RedFlame")
}
switch name {
case "RX":
return #imageLiteral(resourceName: "YellowFlame")
default:
return #imageLiteral(resourceName: "RedFlame")
}
}
这里是自定义 MKAnnotation Class:FileMarkerView.swift
import Foundation
import MapKit
class FireMarkerView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let fire = newValue as? Fire else {
return
}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
image = fire.image
}
}
}
URLSession.shared.dataTask
是一个 异步 任务,这意味着它会在未来某个不确定的时间调用其回调函数。在其回调({ }
) 之外执行的代码将在数据任务实际完成之前被调用。现在,您正在该回调之外设置注释。
为了解决这个问题,你需要在那个回调函数的里面设置注解。所以,如果你有 print([self.fires])
,你可以这样做:
DispatchQueue.main.async {
self.mapView.addAnnotations(self.fires)
}
DispatchQueue.main.async
是为了确保在主线程上调用对 UI 的更新(URL 任务可能 return 在不同的线程上) .
我正在创建我的第一个 IOS 应用程序,我不是开发人员,而且我真的很喜欢地图注释。
我正在尝试从 GeoJSON URL 端点获取火灾数据,并使用 URLSession 和自定义 MKAnnotationView 以及自定义火针将火灾显示为地图上的注释。
问题是来自 URL 端点的 GeoJSON Fire 数据的注释没有出现在地图上,尽管 URL 会话正在返回数据。但是,如果我手动创建单个 Fire 注释,它会使用自定义图钉正确显示在地图上。
任何帮助将不胜感激,我花了几天时间试图解决这个问题:(
这是ViewController.Swift文件
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!
var locationManager:CLLocationManager!
var lat = Double()
var lon = Double()
var fires: [Fire] = []
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.register(FireMarkerView.self,forAnnotationViewWithReuseIdentifier:
MKMapViewDefaultAnnotationViewReuseIdentifier)
if let url = URL(string: "https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Active_Fires/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") {
URLSession.shared.dataTask(with: url) {data, response, error in
if let data = data {
do {
let features = try MKGeoJSONDecoder().decode(data)
.compactMap { [=11=] as? MKGeoJSONFeature }
let validWorks = features.compactMap(Fire.init)
self.fires.append(contentsOf: validWorks)
print([self.fires])
}
catch let error {
print(error)
}
}
}.resume()
}
//This code works an annotation appears correctly on map
/* let fire = Fire(
title: "Ford Fire",
incidentShortDescription: "Hwy 35",
incidentTypeCategory: "WF",
coordinate: CLLocationCoordinate2D(latitude: 37.7993, longitude: -122.1947))
mapView.addAnnotation(fire)*/
mapView.addAnnotations(fires)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways , .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .notDetermined , .denied , .restricted:
locationManager.requestWhenInUseAuthorization()
break
default:
break
}
switch manager.accuracyAuthorization {
case .fullAccuracy:
break
case .reducedAccuracy:
break
default:
break
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
//locationManager.startUpdatingHeading()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let userLocation = locations.first! as CLLocation
lat = userLocation.coordinate.latitude
lon = userLocation.coordinate.longitude
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 400000, longitudinalMeters: 400000)
self.mapView.setRegion(region, animated: true)
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// Need a solution for this.
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}
这是模型Class,Fire.swift
import Foundation
import MapKit
class Fire: NSObject, MKAnnotation {
let title: String?
let incidentShortDescription: String?
let incidentTypeCategory: String?
let coordinate: CLLocationCoordinate2D
init(
title: String?,
incidentShortDescription: String?,
incidentTypeCategory: String?,
coordinate: CLLocationCoordinate2D
) {
self.title = title
self.incidentShortDescription = incidentShortDescription
self.incidentTypeCategory = incidentTypeCategory
self.coordinate = coordinate
super.init()
}
init?(feature: MKGeoJSONFeature) {
// 1
guard
let point = feature.geometry.first as? MKPointAnnotation,
let propertiesData = feature.properties,
let json = try? JSONSerialization.jsonObject(with: propertiesData),
let properties = json as? [String: Any]
else {
return nil
}
// 3
title = properties ["IncidentName"] as? String
incidentShortDescription = properties["IncidentShortDescription"] as? String
incidentTypeCategory = properties["IncidentTypeCategory"] as? String
coordinate = point.coordinate
super.init()
}
var subtitle: String? {
return (incidentTypeCategory)
}
var image: UIImage {
guard let name = incidentTypeCategory else {
return #imageLiteral(resourceName: "RedFlame")
}
switch name {
case "RX":
return #imageLiteral(resourceName: "YellowFlame")
default:
return #imageLiteral(resourceName: "RedFlame")
}
}
这里是自定义 MKAnnotation Class:FileMarkerView.swift
import Foundation
import MapKit
class FireMarkerView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let fire = newValue as? Fire else {
return
}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
image = fire.image
}
}
}
URLSession.shared.dataTask
是一个 异步 任务,这意味着它会在未来某个不确定的时间调用其回调函数。在其回调({ }
) 之外执行的代码将在数据任务实际完成之前被调用。现在,您正在该回调之外设置注释。
为了解决这个问题,你需要在那个回调函数的里面设置注解。所以,如果你有 print([self.fires])
,你可以这样做:
DispatchQueue.main.async {
self.mapView.addAnnotations(self.fires)
}
DispatchQueue.main.async
是为了确保在主线程上调用对 UI 的更新(URL 任务可能 return 在不同的线程上) .