我遇到了收到警告的问题[VKDefault] Triangulator failed to fully triangulate polygon MapKit
放大和缩小地图上的特定区域时。
我正在从 json 数据创建多边形,该数据可以包含 MKPolygon 或 MKMultiPolygon。 200 多个多边形中只有几个存在问题。
对我来说,数据看起来不错。我尝试删除坐标数组中的重复项(如果它们包含任何重复项),但没有成功。也许我做得不对,因为 MKMultiPolygon 是从 [[[[Double]]]] 创建的
这是该项目的链接 -https://www.dropbox.com/s/zwy08upcne11dyy/VectorMaps%203.zip?dl=0 https://www.dropbox.com/s/zwy08upcne11dyy/VectorMaps%203.zip?dl=0
这只是一个原型,所以请不要强迫我强行展开。
这就是带有警告的区域的样子。
我在 ViewController 中添加了我遇到问题的区域列表。
解决警告的额外奖励VectorMaps[1611:116104] [VKDefault] Style Z is requested for an invisible rect
struct SubRegion: Decodable {
var id: Int
var name: String
var geometry: Geometry
enum CodingKeys: String, CodingKey {
case id, name, geometry = "geojson_geometry"
}
}
enum PolygonType: String {
case multiPolygon = "MultiPolygon"
case polygon = "Polygon"
}
struct Geometry: Decodable {
var type: String
var coordinates: Array<Any>
var convexHullGeometry: ConvexGeometry?
enum CodingKeys: String, CodingKey {
case type, coordinates, convexHullGeometry = "convex_hull_geojson_geometry"
}
enum ParseError: Error {
case notRecognizedType(Any)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
switch type {
case "Polygon":
coordinates = try container.decode([[[Double]]].self, forKey: .coordinates)
case "MultiPolygon":
coordinates = try container.decode([[[[Double]]]].self, forKey: .coordinates)
default:
throw ParseError.notRecognizedType(type)
}
}
}
struct ConvexGeometry: Codable {
var coordinates: [[[Double]]]
}
class ViewController: UIViewController {
var activeMultiRenders = [MKMultiPolygonRenderer]()
var activeRenders = [MKPolygonRenderer]()
var mapView: MKMapView!
let pinView = LocationPinView()
var showMarker = false
var showAnnotation = false
var this = MKPointAnnotation()
typealias Polygon = [[[Double]]]
typealias MultiPolygon = [[[[Double]]]]
let staticMarkerButton: UIButton = {
let button = UIButton()
button.setTitle("Show static marker", for: .normal)
button.addTarget(self, action: #selector(addMarker), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
button.setTitleColor(.blue, for: .normal)
button.backgroundColor = .white
button.layer.cornerRadius = 5
return button
}()
let mapButton: UIButton = {
let button = UIButton()
button.setTitle("Radius Map", for: .normal)
button.addTarget(self, action: #selector(visitRadius), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
button.setTitleColor(.blue, for: .normal)
button.backgroundColor = .white
button.layer.cornerRadius = 5
return button
}()
let showLabelButton: UIButton = {
let button = UIButton()
button.setTitle("Show labels", for: .normal)
button.addTarget(self, action: #selector(addAnnotations), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
button.setTitleColor(.blue, for: .normal)
button.backgroundColor = .white
button.layer.cornerRadius = 5
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView(frame: self.view.frame)
mapView.isRotateEnabled = false
mapView.mapType = .standard
addPolygonsToMap()
mapView.delegate = self
self.view.addSubview(mapView)
let tap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
self.mapView.addGestureRecognizer(tap)
self.view.addSubview(pinView)
pinView.frame = CGRect(x: mapView.center.x, y: mapView.center.y - pinView.frame.height,
width: pinView.frame.width, height: pinView.frame.height)
pinView.isHidden = true
view.addSubview(staticMarkerButton)
staticMarkerButton.translatesAutoresizingMaskIntoConstraints = false
staticMarkerButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
staticMarkerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(mapButton)
mapButton.translatesAutoresizingMaskIntoConstraints = false
mapButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
mapButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(showLabelButton)
showLabelButton.translatesAutoresizingMaskIntoConstraints = false
showLabelButton.leadingAnchor.constraint(equalTo: mapButton.trailingAnchor, constant: 10).isActive = true
showLabelButton.topAnchor.constraint(equalTo: mapButton.topAnchor).isActive = true
}
func addPolygonsToMap() {
let fileURL = Bundle.main.url(forResource: "subregion", withExtension: "json")!
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: fileURL)
let subregions = try decoder.decode([SubRegion].self, from: data)
for each in subregions {
let geometry = each.geometry
// if !badData.contains(each.name) {
// continue
// }
// Type is either a Polygon or MultiPolygon
let geometryType = each.geometry.type
if geometryType == PolygonType.polygon.rawValue {
// Cast the coordinates to a Polygon type-alias.
let coords = geometry.coordinates as! Polygon
let lCoords = getCoordinatesFromPolygonArray(coords)
// Create a MKPolygon with locationCoordinates
let polygon = MKPolygon(coordinates: lCoords, count: lCoords.count)
// Add the MKPolygon to the map
mapView.addOverlay(polygon)
} else if geometryType == PolygonType.multiPolygon.rawValue {
// Cast the coordinates to a MultiPolygon type-alias.
let coords = geometry.coordinates as! MultiPolygon
var polygons:[MKPolygon] = []
coords.forEach { polygon in
let lCoords = getCoordinatesFromPolygonArray(polygon)
// Create a MKPolygon with locationCoordinates
let polygon = MKPolygon(coordinates: lCoords, count: lCoords.count)
polygons.append(polygon)
}
let multiPolygon = MKMultiPolygon(polygons)
mapView.addOverlay(multiPolygon)
}
}
} catch let error {
print("Error", error)
}
}
let badData = ["Central Coast", "Dapto and Port Kembla", "Wellington", "South West Perth", "East Arnhem", "Charters Towers", "Ormeau", "Murray River", "Wodonga", "Bruny Island", "Katherine", "Far North", "Tweed Valley", "Richmond Valley", "South East Coast", "Gympie", "Meander Valley"]
private func getCoordinatesFromPolygonArray(_ polygon: Polygon) -> [CLLocationCoordinate2D] {
var locationCoordinates = [CLLocationCoordinate2D]()
if let coordinates = polygon.first?.map({ CLLocationCoordinate2D(latitude: $0[1] , longitude: $0[0]) }) {
locationCoordinates.append(contentsOf: coordinates)
}
return locationCoordinates
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.strokeColor = UIColor.blue.withAlphaComponent(0.4)
return renderer
} else if let multiPolygon = overlay as? MKMultiPolygon {
let renderer = MKMultiPolygonRenderer(multiPolygon: multiPolygon)
renderer.strokeColor = UIColor.blue.withAlphaComponent(0.4)
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
@objc func mapTapped(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: self.mapView)
let coordinate = self.mapView.convert(point, toCoordinateFrom: nil)
let mappoint = MKMapPoint(coordinate)
for renderer in self.mapView.polygonRenderers {
let tapPoint = renderer.point(for: mappoint)
if renderer.path.contains(tapPoint) && activeRendersContains(renderer) {
renderer.strokeColor = UIColor.blue.withAlphaComponent(0.4)
renderer.fillColor = UIColor.clear
removeActive(renderer)
} else if renderer.path.contains(tapPoint) {
renderer.fillColor = UIColor.blue.withAlphaComponent(0.4)
renderer.strokeColor = UIColor.blue
appendActive(renderer)
}
}
}
func activeRendersContains(_ renderer: MKOverlayPathRenderer) -> Bool {
if let multiRenderer = renderer as? MKMultiPolygonRenderer {
return activeMultiRenders.contains(multiRenderer)
} else if let polyRenderer = renderer as? MKPolygonRenderer {
return activeRenders.contains(polyRenderer)
} else {
return false
}
}
func appendActive(_ renderer: MKOverlayPathRenderer) {
if let multiRenderer = renderer as? MKMultiPolygonRenderer {
activeMultiRenders.append(multiRenderer)
} else if let polyRenderer = renderer as? MKPolygonRenderer {
activeRenders.append(polyRenderer)
}
}
func removeActive(_ renderer: MKOverlayPathRenderer) {
if let multiRenderer = renderer as? MKMultiPolygonRenderer {
activeMultiRenders.removeAll(where: {$0 == multiRenderer})
} else if let polyRenderer = renderer as? MKPolygonRenderer {
activeRenders.removeAll(where: {$0 == polyRenderer})
}
}
@objc func addMarker() {
showMarker.toggle()
if showMarker {
staticMarkerButton.setTitle("Hide marker", for: .normal)
pinView.isHidden = false
} else {
staticMarkerButton.setTitle("Show marker", for: .normal)
pinView.isHidden = true
}
}
@objc func visitRadius() {
let radiusVC = RadiusViewController()
radiusVC.modalPresentationStyle = .fullScreen
present(radiusVC, animated: false, completion: nil)
}
@objc func addAnnotations() {
showAnnotation.toggle()
if showAnnotation {
// Gets the coordinates and titles of each polygon renderer.
let details = mapView.activePointDetailsFor(activeRenders)
// Creates and adds annotations to the map with given details.
mapView.addAnnotationsWith(details)
} else {
mapView.removeAllAnnotations()
}
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
pinView.pinPosition = .up
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if showMarker {
pinView.pinPosition = .down
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = LabelAnnotationView(annotation: annotation, reuseIdentifier: annotation.title!)
return annotationView
}
}
extension MKMapView {
var polygonRenderers: [MKOverlayPathRenderer] {
var renders = [MKOverlayPathRenderer]()
for overlay in self.overlays {
if let polygon = overlay as? MKPolygon {
guard let renderer = self.renderer(for: polygon) as? MKPolygonRenderer else { continue }
renders.append(renderer)
} else if let multiPolygon = overlay as? MKMultiPolygon {
guard let renderer = self.renderer(for: multiPolygon) as? MKMultiPolygonRenderer else { continue }
renders.append(renderer)
}
}
return renders
}
typealias PointDetails = (coordinate: CLLocationCoordinate2D ,title: String)
func activePointDetailsFor(_ renders: [MKPolygonRenderer]) -> [PointDetails] {
var details = [PointDetails]()
for each in renders {
let title: String = each.polygon.title ?? ""
let coordinate = each.polygon.coordinate
let detail = (coordinate: coordinate ,title: title)
details.append(detail)
}
return details
}
func addAnnotationsWith(_ details: [PointDetails]) {
for each in details {
let annotation = MKPointAnnotation()
annotation.coordinate = each.coordinate
annotation.title = each.title
self.addAnnotation(annotation)
}
}
func removeAllAnnotations() {
let annotations = self.annotations
self.removeAnnotations(annotations)
}
}