美文网首页iOS 深度好文MKMapKit
MapKit框架详细解析(十八) —— 基于MapKit使用In

MapKit框架详细解析(十八) —— 基于MapKit使用In

作者: 刀客传奇 | 来源:发表于2020-10-12 17:54 被阅读0次

    版本记录

    版本号 时间
    V1.0 2020.10.12 星期一

    前言

    MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
    1. MapKit框架详细解析(一) —— 基本概览(一)
    2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
    3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
    4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
    5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
    6. MapKit框架详细解析(六) —— 添加自定义图块(一)
    7. MapKit框架详细解析(七) —— 添加自定义图块(二)
    8. MapKit框架详细解析(八) —— 添加自定义图块(三)
    9. MapKit框架详细解析(九) —— 地图特定区域放大和创建自定义地图annotations(一)
    10. MapKit框架详细解析(十) —— 地图特定区域放大和创建自定义地图annotations(二)
    11. MapKit框架详细解析(十一) —— 自定义MapKit Tiles(一)
    12. MapKit框架详细解析(十二) —— 自定义MapKit Tiles(二)
    13. MapKit框架详细解析(十三) —— MapKit Overlay Views(一)
    14. MapKit框架详细解析(十四) —— MapKit Overlay Views(二)
    15. MapKit框架详细解析(十五) —— 基于MapKit和Core Location的Routing(一)
    16. MapKit框架详细解析(十六) —— 基于MapKit和Core Location的Routing(二)
    17. MapKit框架详细解析(十七) —— 基于MapKit使用Indoor Maps来绘制建筑物内部的地图的简单示例(一)

    源码

    1. Swift

    首先看下工程组织结构

    下面就是sb中的内容

    接着就是源码了

    1. LabelAnnotation.swift
    
    import MapKit
    import UIKit
    
    class LabelAnnotation: MKAnnotationView {
      var label: UILabel
      var point: UIView
    
      override var annotation: MKAnnotation? {
        didSet {
          if let title = annotation?.title {
            label.text = title
          } else {
            label.text = nil
          }
        }
      }
    
      override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        label = UILabel(frame: .zero)
        point = UIView(frame: .zero)
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    
        label.font = UIFont.preferredFont(forTextStyle: .caption1)
        addSubview(label)
    
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
          label.leadingAnchor.constraint(equalTo: leadingAnchor),
          label.trailingAnchor.constraint(equalTo: trailingAnchor),
          label.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    
        let radius: CGFloat = 5.0
        point.layer.cornerRadius = radius
        point.layer.borderWidth = 1.0
        point.layer.borderColor = UIColor(named: "AnnotationBorder")?.cgColor
        addSubview(point)
    
        point.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
          point.widthAnchor.constraint(equalToConstant: radius * 2),
          point.heightAnchor.constraint(equalToConstant: radius * 2),
          point.topAnchor.constraint(equalTo: topAnchor),
          point.centerXAnchor.constraint(equalTo: label.centerXAnchor),
          point.bottomAnchor.constraint(equalTo: label.topAnchor)
        ])
    
        centerOffset = CGPoint(x: 0, y: label.font.lineHeight / 2 )
        calloutOffset = CGPoint(x: 0, y: -radius)
        canShowCallout = true
        translatesAutoresizingMaskIntoConstraints = false
      }
    
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
    
      override var backgroundColor: UIColor? {
        get {
          return point.backgroundColor
        }
        set {
          point.backgroundColor = newValue
        }
      }
    }
    
    2. PointAnnotation.swift
    
    import Foundation
    import MapKit
    
    class PointAnnotation: MKAnnotationView {
      override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    
        frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: 10, height: 10)
        layer.cornerRadius = 5
        layer.borderWidth = 1.0
        layer.borderColor = UIColor(named: "AnnotationBorder")?.cgColor
        canShowCallout = true
      }
    
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
    }
    
    3. IMDFError.swift
    
    import Foundation
    
    enum IMDFError: Error {
      case invalidType
      case invalidData
    }
    
    4. Archive.swift
    
    import Foundation
    
    struct Archive {
      let directory: URL
    
      init(directory: URL) {
        self.directory = directory
      }
    
      enum File {
        case address
        case amenity
        case anchor
        case building
        case detail
        case fixture
        case footprint
        case geofence
        case kiosk
        case level
        case manifest
        case occupant
        case opening
        case relationship
        case section
        case unit
        case venue
    
        var filename: String {
          return "\(self).geojson"
        }
      }
    
      func fileURL(for file: File) -> URL {
        return directory.appendingPathComponent(file.filename)
      }
    }
    
    5. IMDFDecoder.swift
    
    import Foundation
    import MapKit
    
    class IMDFDecoder {
      private let geoJSONDecoder = MKGeoJSONDecoder()
      func decode(_ imdfDirectory: URL) throws -> Venue {
        let archive = Archive(directory: imdfDirectory)
    
        // Decode all the features that need to be rendered.
        let venues = try decodeFeatures(Venue.self, from: .venue, in: archive)
        let levels = try decodeFeatures(Level.self, from: .level, in: archive)
        let units = try decodeFeatures(Unit.self, from: .unit, in: archive)
        let openings = try decodeFeatures(Opening.self, from: .opening, in: archive)
        let amenities = try decodeFeatures(Amenity.self, from: .amenity, in: archive)
    
        // Associate levels to venues.
        if venues.isEmpty {
          throw IMDFError.invalidData
        }
        let venue = venues[0]
        venue.levelsByOrdinal = Dictionary(grouping: levels) { level in
          level.properties.ordinal
        }
    
        // Associate Units and Opening to levels.
        let unitsByLevel = Dictionary(grouping: units) { unit in
          unit.properties.levelId
        }
    
        let openingsByLevel = Dictionary(grouping: openings) { opening in
          opening.properties.levelId
        }
    
        // Associate each Level with its corresponding Units and Openings.
        for level in levels {
          if let unitsInLevel = unitsByLevel[level.id] {
            level.units = unitsInLevel
          }
          if let openingsInLevel = openingsByLevel[level.id] {
            level.openings = openingsInLevel
          }
        }
    
        // Associate Amenities to the Unit in which they reside.
        let unitsById = units.reduce(into: [UUID: Unit]()) {
          $0[$1.id] = $1
        }
    
        for amenity in amenities {
          guard let pointGeometry = amenity.geometry[0] as? MKPointAnnotation else {
            throw IMDFError.invalidData
          }
    
          if let name = amenity.properties.name?.bestLocalizedValue {
            amenity.title = name
            amenity.subtitle = amenity.properties.category.capitalized
          } else {
            amenity.title = amenity.properties.category.capitalized
          }
    
          for unitID in amenity.properties.unitIds {
            let unit = unitsById[unitID]
            unit?.amenities.append(amenity)
          }
    
          amenity.coordinate = pointGeometry.coordinate
        }
    
        // Occupants (and certain other IMDF features) do not directly contain geometry. Instead, they reference a separate `Anchor` which
        // contains geometry. Occupants should be associated with Units.
        try decodeOccupants(units: units, in: archive)
    
        return venue
      }
    
      private func decodeOccupants(units: [Unit], in archive: Archive) throws {
        let occupants = try decodeFeatures(Occupant.self, from: .occupant, in: archive)
        let anchors = try decodeFeatures(Anchor.self, from: .anchor, in: archive)
        let unitsById = units.reduce(into: [UUID: Unit]()) {
          $0[$1.id] = $1
        }
        let anchorsById = anchors.reduce(into: [UUID: Anchor]()) {
          $0[$1.id] = $1
        }
    
        // Resolve the occupants location based on the referenced Anchor, and associate them
        // with their corresponding Unit.
        for occupant in occupants {
          guard let anchor = anchorsById[occupant.properties.anchorId] else {
            throw IMDFError.invalidData
          }
    
          guard let pointGeometry = anchor.geometry[0] as? MKPointAnnotation else {
            throw IMDFError.invalidData
          }
          occupant.coordinate = pointGeometry.coordinate
    
          if let name = occupant.properties.name.bestLocalizedValue {
            occupant.title = name
            occupant.subtitle = occupant.properties.category.capitalized
          } else {
            occupant.title = occupant.properties.category.capitalized
          }
    
          guard let unit = unitsById[anchor.properties.unitId] else {
            continue
          }
    
          // Associate occupants to units.
          unit.occupants.append(occupant)
          occupant.unit = unit
        }
      }
    
      private func decodeFeatures<T: IMDFDecodableFeature>(_ type: T.Type, from file: Archive.File, in archive: Archive) throws -> [T] {
        let fileURL = archive.fileURL(for: file)
        let data = try Data(contentsOf: fileURL)
        let geoJSONFeatures = try geoJSONDecoder.decode(data)
        guard let features = geoJSONFeatures as? [MKGeoJSONFeature] else {
          throw IMDFError.invalidType
        }
    
        let imdfFeatures = try features.map { try type.init(feature: $0) }
        return imdfFeatures
      }
    }
    
    6. StylableFeatures.swift
    
    import Foundation
    import MapKit
    
    protocol StylableFeature {
      var geometry: [MKShape & MKGeoJSONObject] { get }
      func configure(overlayRenderer: MKOverlayPathRenderer)
      func configure(annotationView: MKAnnotationView)
    }
    
    extension StylableFeature {
      func configure(overlayRenderer: MKOverlayPathRenderer) {}
      func configure(annotationView: MKAnnotationView) {}
    }
    
    7. MapViewController.swift
    
    import UIKit
    import MapKit
    
    class MapViewController: UIViewController {
      @IBOutlet weak var mapView: MKMapView!
    
      let locationManager = CLLocationManager()
      let decoder = IMDFDecoder()
      var venue: Venue?
    
      private var levels: [Level] = []
      private var currentLevelFeatures: [StylableFeature] = []
      private var currentLevelOverlays: [MKOverlay] = []
      private var currentLevelAnnotations: [MKAnnotation] = []
    
      override func viewDidLoad() {
        super.viewDidLoad()
        setupMapView()
        loadRazeHQIndoorMapData()
        showDefaultMapRect()
        showFeatures(for: 1)
        startListeningForLocation()
      }
    
      func setupMapView() {
        mapView.delegate = self
        mapView.register(PointAnnotation.self, forAnnotationViewWithReuseIdentifier: "PointAnnotationView")
        mapView.register(LabelAnnotation.self, forAnnotationViewWithReuseIdentifier: "LabelAnnotationView")
        mapView.pointOfInterestFilter = .none
      }
    
      func loadRazeHQIndoorMapData() {
        guard let resourceURL = Bundle.main.resourceURL else { return }
    
        let imdfDirectory = resourceURL.appendingPathComponent("Data")
        do {
          venue = try decoder.decode(imdfDirectory)
        } catch let error {
          print(error)
        }
      }
    
      private func showFeatures(for ordinal: Int) {
        guard venue != nil else {
          return
        }
    
        // 1
        currentLevelFeatures.removeAll()
        mapView.removeOverlays(currentLevelOverlays)
        mapView.removeAnnotations(currentLevelAnnotations)
        currentLevelAnnotations.removeAll()
        currentLevelOverlays.removeAll()
    
        // 2
        if let levels = venue?.levelsByOrdinal[ordinal] {
          for level in levels {
            currentLevelFeatures.append(level)
            currentLevelFeatures += level.units
            currentLevelFeatures += level.openings
    
            let occupants = level.units.flatMap { unit in
              unit.occupants
            }
    
            let amenities = level.units.flatMap { unit in
              unit.amenities
            }
    
            currentLevelAnnotations += occupants
            currentLevelAnnotations += amenities
          }
        }
    
        // 3
        let currentLevelGeometry = currentLevelFeatures.flatMap { feature in
          feature.geometry
        }
    
        currentLevelOverlays = currentLevelGeometry.compactMap { mkOverlay in
          mkOverlay as? MKOverlay
        }
    
        mapView.addOverlays(currentLevelOverlays)
        mapView.addAnnotations(currentLevelAnnotations)
      }
    
      func showDefaultMapRect() {
        guard
          let venue = venue,
          let venueOverlay = venue.geometry[0] as? MKOverlay
          else { return }
    
        mapView.setVisibleMapRect(
          venueOverlay.boundingMapRect,
          edgePadding: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20),
          animated: false)
      }
    
      func startListeningForLocation() {
        locationManager.requestWhenInUseAuthorization()
      }
    
      @IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
        showFeatures(for: sender.selectedSegmentIndex)
      }
    }
    
    // MARK: - MKMapViewDelegate
    
    extension MapViewController: MKMapViewDelegate {
      func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        guard
          let shape = overlay as? (MKShape & MKGeoJSONObject),
          let feature = currentLevelFeatures.first( where: { $0.geometry.contains( where: { $0 == shape }) })
          else { return MKOverlayRenderer(overlay: overlay) }
    
        let renderer: MKOverlayPathRenderer
        switch overlay {
        case is MKMultiPolygon:
          renderer = MKMultiPolygonRenderer(overlay: overlay)
        case is MKPolygon:
          renderer = MKPolygonRenderer(overlay: overlay)
        case is MKMultiPolyline:
          renderer = MKMultiPolylineRenderer(overlay: overlay)
        case is MKPolyline:
          renderer = MKPolylineRenderer(overlay: overlay)
        default:
          return MKOverlayRenderer(overlay: overlay)
        }
    
        // Configure the overlay renderer's display properties in feature-specific ways.
        feature.configure(overlayRenderer: renderer)
    
        return renderer
      }
    
      func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if let stylableFeature = annotation as? StylableFeature {
          if stylableFeature is Occupant {
            let annotationView = mapView.dequeueReusableAnnotationView(
              withIdentifier: "LabelAnnotationView",
              for: annotation)
            stylableFeature.configure(annotationView: annotationView)
            return annotationView
          } else {
            let annotationView = mapView.dequeueReusableAnnotationView(
              withIdentifier: "PointAnnotationView",
              for: annotation)
            stylableFeature.configure(annotationView: annotationView)
            return annotationView
          }
        }
    
        return nil
      }
    
      func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        guard
          let venue = venue,
          let location = userLocation.location
          else { return }
    
        // Display location only if the user is inside this venue.
        var isUserInsideVenue = false
        let userMapPoint = MKMapPoint(location.coordinate)
        for geometry in venue.geometry {
          guard let overlay = geometry as? MKOverlay else {
            continue
          }
    
          if overlay.boundingMapRect.contains(userMapPoint) {
            isUserInsideVenue = true
            break
          }
        }
    
        guard isUserInsideVenue else {
          return
        }
    
        // If the device knows which level the user is physically on, automatically switch to that level.
        if let ordinal = location.floor?.level {
          showFeatures(for: ordinal)
        }
      }
    }
    

    后记

    本篇主要讲述了基于MapKit使用Indoor Maps来绘制建筑物内部的地图的简单示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:MapKit框架详细解析(十八) —— 基于MapKit使用In

        本文链接:https://www.haomeiwen.com/subject/nqejpktx.html