使用 OpenWeatherMap 将 Overlay 添加到 MKMapView (Swift)
Adding Overlay to MKMapView using OpenWeatherMap (Swift)
我不知道从哪里开始。
这是文档:
https://openweathermap.org/api/weathermaps
之后,我在网上搜索了我可以尝试的内容,但它给了我一个致命错误,并且永远不会超过那个。 (注意:我也不确定要为 z、x 和 y 值添加什么,所以除了我的 API 键之外,我还保留了它们,此处留空,但在我的代码中我只添加了 1/ 1/1) 我的尝试,插入 temp_new 来接收温度覆盖:
Service.shared.getInfoCompletionHandler(requestURL: "https://tile.openweathermap.org/map/temp_new/{z}/{x}/{y}.png?appid={myKey}") { data in
if let data = data{
var geoJson = [MKGeoJSONObject]()
do{
geoJson = try MKGeoJSONDecoder().decode(data)
}
catch{
fatalError("Could not 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 {
[unowned self] in
//set to global variable
self.overlays = overlays
}
}
}
我的想法是简单地提取叠加层,然后像这样将其添加到 MKMapView:
mapView.addOverlays(self.overlays)
如果相关,这是我在 Service.swift 中用于进行 API 调用的完成处理程序:
//Get Info API
func getInfoCompletionHandler(requestURL: String, completion: @escaping (Data?)->Void){
guard let url = URL(string: requestURL) else {return}
URLSession.shared.dataTask(with: url) { data, response, error in
if error == nil {
if let data = String(data: data!, encoding: .utf8), let response =
response{
print(data)
print(response)
}
} else{
if let error = error {
print(error)
}
}
completion(data)
}.resume()
我走在正确的轨道上吗?
感谢任何帮助,谢谢!
编辑:
玩过之后我注意到我可以使用以下代码简单地将数据解析为 imageData:
class ViewController: UIViewController {
//Properties
var imgData = Data()
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
//imageView frame
view.addSubview(imageView)
imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
imageView.center = view.center
imageView.contentMode = .scaleAspectFit
//image string
let imgString = "https://tile.openweathermap.org/map/temp_new/0/0/0.png?appid={myKey}"
//convert string to url object (needed to decode image data)
let imgUrl = URL(string: imgString)
//convert url to data
self.imgData = try! Data(contentsOf: imgUrl!)
//set to imageView
self.imageView.image = UIImage(data: self.imgData)
}
}
给我这个结果:
所以现在剩下的唯一问题是如何将此 imageView 添加为 mapView 上的叠加层?
好的,多亏了这个教程,我终于明白了:
https://www.raywenderlich.com/9956648-mapkit-tutorial-overlay-views#toc-anchor-003
我会尽力解释这里的所有内容,我从 Ray 的网站上复制了一堆代码,所以我不能 100% 理解所有内容。话虽如此,需要做的主要工作是为叠加层布置坐标。这是在自定义 class 中完成的。这里的想法是解析写在字典中的 plist 中的坐标数据。对于我的项目,这很容易,因为我只需设置地球的最大坐标 ((90, 180), (90, -180), (-90, -180), (-90, 180))。中间坐标仅在我将其设置为 (100, 0) 时起作用,不知道为什么,但解析 plist 的完整代码如下。
class WorldMap {
var boundary: [CLLocationCoordinate2D] = []
var midCoordinate = CLLocationCoordinate2D()
var overlayTopLeftCoordinate = CLLocationCoordinate2D()
var overlayTopRightCoordinate = CLLocationCoordinate2D()
var overlayBottomLeftCoordinate = CLLocationCoordinate2D()
var overlayBottomRightCoordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2D(
latitude: overlayBottomLeftCoordinate.latitude,
longitude: overlayTopRightCoordinate.longitude)
}
var overlayBoundingMapRect: MKMapRect {
let topLeft = MKMapPoint(overlayTopLeftCoordinate)
let topRight = MKMapPoint(overlayTopRightCoordinate)
let bottomLeft = MKMapPoint(overlayBottomLeftCoordinate)
return MKMapRect(
x: topLeft.x,
y: topLeft.y,
width: fabs(topLeft.x - topRight.x),
height: fabs(topLeft.y - bottomLeft.y))
}
init(filename: String){
guard
let properties = WorldMap.plist(filename) as? [String: Any]
else { return }
midCoordinate = WorldMap.parseCoord(dict: properties, fieldName: "midCoord")
overlayTopLeftCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayTopLeftCoord")
overlayTopRightCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayTopRightCoord")
overlayBottomLeftCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayBottomLeftCoord")
}
static func plist(_ plist: String) -> Any? {
guard let filePath = Bundle.main.path(forResource: plist, ofType: "plist"),
let data = FileManager.default.contents(atPath: filePath) else { return nil }
do {
return try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
} catch {
return nil
}
}
static func parseCoord(dict: [String: Any], fieldName: String) -> CLLocationCoordinate2D {
if let coord = dict[fieldName] as? String {
let point = NSCoder.cgPoint(for: coord)
return CLLocationCoordinate2D(
latitude: CLLocationDegrees(point.x),
longitude: CLLocationDegrees(point.y))
}
return CLLocationCoordinate2D()
}
之后我不得不让它符合NSObject(这个概念不是很清楚)
class MapOverlay: NSObject, MKOverlay{
let coordinate: CLLocationCoordinate2D
let boundingMapRect: MKMapRect
init(worldMap: WorldMap) {
boundingMapRect = worldMap.overlayBoundingMapRect
coordinate = worldMap.midCoordinate
}
}
然后创建了一个符合 MKOverlayRenderer 的class来给它指示如何绘制叠加层。
class MapOverlayView: MKOverlayRenderer{
let overlayImage: UIImage
init(overlay: MKOverlay, overlayImage: UIImage){
self.overlayImage = overlayImage
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let imageReference = overlayImage.cgImage else {return}
let rect = self.rect(for: overlay.boundingMapRect)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -rect.size.height)
context.draw(imageReference, in: rect)
}
}
接下来我只需要创建一个调用上述 classes:
的函数
func addOverlay() {
//its a good idea to remove any overlays first
//In my case I will add overlays for temperature and precipitation
let overlays = mapView.overlays
mapView.removeOverlays(overlays)
//get overlay and add it to the mapView
let worldMap = WorldMap(filename: "WorldCoordinates")
let overlay = MapOverlay(worldMap: worldMap)
mapView.addOverlay(overlay)
}
一旦完成,我只需按如下方式填写 MKOverlayRenderer 委托:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
var overlayType: String
//change API call depending on which overlay button has been pressed
if self.tempOverlaySelected == true{
overlayType = "temp_new"
} else{
overlayType = "precipitation_new"
}
//image string
let imgString = "https://tile.openweathermap.org/map/\(overlayType)/0/0/0.png?{myKey}"
//convert string to url object (needed to decode image data)
let imgUrl = URL(string: imgString)
//convert url to data and guard
guard let imageData = try? Data(contentsOf: imgUrl!) else {return MKOverlayRenderer()}
//set to imageView
self.mapOverlay.image = UIImage(data: imageData)
//return the map Overlay
if overlay is MapOverlay {
if let image = self.mapOverlay.image{
return MapOverlayView(overlay: overlay, overlayImage: image)
}
}
return MKOverlayRenderer()
}
我希望这对将来可能遇到此问题的任何人有所帮助。如果有人可以进一步帮助解释这些概念,因为它对我来说是新的,请随意!
我不知道从哪里开始。
这是文档: https://openweathermap.org/api/weathermaps
之后,我在网上搜索了我可以尝试的内容,但它给了我一个致命错误,并且永远不会超过那个。 (注意:我也不确定要为 z、x 和 y 值添加什么,所以除了我的 API 键之外,我还保留了它们,此处留空,但在我的代码中我只添加了 1/ 1/1) 我的尝试,插入 temp_new 来接收温度覆盖:
Service.shared.getInfoCompletionHandler(requestURL: "https://tile.openweathermap.org/map/temp_new/{z}/{x}/{y}.png?appid={myKey}") { data in
if let data = data{
var geoJson = [MKGeoJSONObject]()
do{
geoJson = try MKGeoJSONDecoder().decode(data)
}
catch{
fatalError("Could not 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 {
[unowned self] in
//set to global variable
self.overlays = overlays
}
}
}
我的想法是简单地提取叠加层,然后像这样将其添加到 MKMapView:
mapView.addOverlays(self.overlays)
如果相关,这是我在 Service.swift 中用于进行 API 调用的完成处理程序:
//Get Info API
func getInfoCompletionHandler(requestURL: String, completion: @escaping (Data?)->Void){
guard let url = URL(string: requestURL) else {return}
URLSession.shared.dataTask(with: url) { data, response, error in
if error == nil {
if let data = String(data: data!, encoding: .utf8), let response =
response{
print(data)
print(response)
}
} else{
if let error = error {
print(error)
}
}
completion(data)
}.resume()
我走在正确的轨道上吗?
感谢任何帮助,谢谢!
编辑:
玩过之后我注意到我可以使用以下代码简单地将数据解析为 imageData:
class ViewController: UIViewController {
//Properties
var imgData = Data()
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
//imageView frame
view.addSubview(imageView)
imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
imageView.center = view.center
imageView.contentMode = .scaleAspectFit
//image string
let imgString = "https://tile.openweathermap.org/map/temp_new/0/0/0.png?appid={myKey}"
//convert string to url object (needed to decode image data)
let imgUrl = URL(string: imgString)
//convert url to data
self.imgData = try! Data(contentsOf: imgUrl!)
//set to imageView
self.imageView.image = UIImage(data: self.imgData)
}
}
给我这个结果:
所以现在剩下的唯一问题是如何将此 imageView 添加为 mapView 上的叠加层?
好的,多亏了这个教程,我终于明白了:
https://www.raywenderlich.com/9956648-mapkit-tutorial-overlay-views#toc-anchor-003
我会尽力解释这里的所有内容,我从 Ray 的网站上复制了一堆代码,所以我不能 100% 理解所有内容。话虽如此,需要做的主要工作是为叠加层布置坐标。这是在自定义 class 中完成的。这里的想法是解析写在字典中的 plist 中的坐标数据。对于我的项目,这很容易,因为我只需设置地球的最大坐标 ((90, 180), (90, -180), (-90, -180), (-90, 180))。中间坐标仅在我将其设置为 (100, 0) 时起作用,不知道为什么,但解析 plist 的完整代码如下。
class WorldMap {
var boundary: [CLLocationCoordinate2D] = []
var midCoordinate = CLLocationCoordinate2D()
var overlayTopLeftCoordinate = CLLocationCoordinate2D()
var overlayTopRightCoordinate = CLLocationCoordinate2D()
var overlayBottomLeftCoordinate = CLLocationCoordinate2D()
var overlayBottomRightCoordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2D(
latitude: overlayBottomLeftCoordinate.latitude,
longitude: overlayTopRightCoordinate.longitude)
}
var overlayBoundingMapRect: MKMapRect {
let topLeft = MKMapPoint(overlayTopLeftCoordinate)
let topRight = MKMapPoint(overlayTopRightCoordinate)
let bottomLeft = MKMapPoint(overlayBottomLeftCoordinate)
return MKMapRect(
x: topLeft.x,
y: topLeft.y,
width: fabs(topLeft.x - topRight.x),
height: fabs(topLeft.y - bottomLeft.y))
}
init(filename: String){
guard
let properties = WorldMap.plist(filename) as? [String: Any]
else { return }
midCoordinate = WorldMap.parseCoord(dict: properties, fieldName: "midCoord")
overlayTopLeftCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayTopLeftCoord")
overlayTopRightCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayTopRightCoord")
overlayBottomLeftCoordinate = WorldMap.parseCoord(
dict: properties,
fieldName: "overlayBottomLeftCoord")
}
static func plist(_ plist: String) -> Any? {
guard let filePath = Bundle.main.path(forResource: plist, ofType: "plist"),
let data = FileManager.default.contents(atPath: filePath) else { return nil }
do {
return try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
} catch {
return nil
}
}
static func parseCoord(dict: [String: Any], fieldName: String) -> CLLocationCoordinate2D {
if let coord = dict[fieldName] as? String {
let point = NSCoder.cgPoint(for: coord)
return CLLocationCoordinate2D(
latitude: CLLocationDegrees(point.x),
longitude: CLLocationDegrees(point.y))
}
return CLLocationCoordinate2D()
}
之后我不得不让它符合NSObject(这个概念不是很清楚)
class MapOverlay: NSObject, MKOverlay{
let coordinate: CLLocationCoordinate2D
let boundingMapRect: MKMapRect
init(worldMap: WorldMap) {
boundingMapRect = worldMap.overlayBoundingMapRect
coordinate = worldMap.midCoordinate
}
}
然后创建了一个符合 MKOverlayRenderer 的class来给它指示如何绘制叠加层。
class MapOverlayView: MKOverlayRenderer{
let overlayImage: UIImage
init(overlay: MKOverlay, overlayImage: UIImage){
self.overlayImage = overlayImage
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let imageReference = overlayImage.cgImage else {return}
let rect = self.rect(for: overlay.boundingMapRect)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -rect.size.height)
context.draw(imageReference, in: rect)
}
}
接下来我只需要创建一个调用上述 classes:
的函数func addOverlay() {
//its a good idea to remove any overlays first
//In my case I will add overlays for temperature and precipitation
let overlays = mapView.overlays
mapView.removeOverlays(overlays)
//get overlay and add it to the mapView
let worldMap = WorldMap(filename: "WorldCoordinates")
let overlay = MapOverlay(worldMap: worldMap)
mapView.addOverlay(overlay)
}
一旦完成,我只需按如下方式填写 MKOverlayRenderer 委托:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
var overlayType: String
//change API call depending on which overlay button has been pressed
if self.tempOverlaySelected == true{
overlayType = "temp_new"
} else{
overlayType = "precipitation_new"
}
//image string
let imgString = "https://tile.openweathermap.org/map/\(overlayType)/0/0/0.png?{myKey}"
//convert string to url object (needed to decode image data)
let imgUrl = URL(string: imgString)
//convert url to data and guard
guard let imageData = try? Data(contentsOf: imgUrl!) else {return MKOverlayRenderer()}
//set to imageView
self.mapOverlay.image = UIImage(data: imageData)
//return the map Overlay
if overlay is MapOverlay {
if let image = self.mapOverlay.image{
return MapOverlayView(overlay: overlay, overlayImage: image)
}
}
return MKOverlayRenderer()
}
我希望这对将来可能遇到此问题的任何人有所帮助。如果有人可以进一步帮助解释这些概念,因为它对我来说是新的,请随意!