NAV Navbar

台灣圖霸 | Map8 Platform

歡迎使用 Logo 28x28 台灣圖霸 | Map8 Platform 地圖平台

有任何技術疑難,或其他疑問,都歡迎您 跟我們聯絡 喔!!!

初始化地圖

Example

import UIKit
import Map8

class SimpleMap: UIViewController, MGLMapViewDelegate {

    var maxBounds: MGLCoordinateBounds!

    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885), zoomLevel: 16, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        mapView.delegate = self
        mapView.showsUserLocation = true
        mapView.showsUserHeadingIndicator = true

        // Taiwan's max bounds
        let northeast = CLLocationCoordinate2D(latitude: 33.4, longitude:138.45858)
        let southwest = CLLocationCoordinate2D(latitude: 15, longitude: 105)
        maxBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)
    }

    func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
        let cameraPosition = MGLMapCamera(lookingAtCenter: mapView.userLocation?.coordinate ?? CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885),
                                                 altitude: mapView.camera.altitude,
                                                 pitch: 50,
                                                 heading: 0)
        mapView.camera = cameraPosition
    }

    // Restrict panning area
    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        let currentCamera = mapView.camera
        let newCameraCenter = newCamera.centerCoordinate
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds
        mapView.camera = currentCamera
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds)

        return inside && intersects
    }
}

Simple map view

攝影機視角移動地圖

Example

import UIKit
import Map8

class CameraAnimation: UIViewController, MGLMapViewDelegate {

    var mapView: MGLMapView!
    var maxBounds: MGLCoordinateBounds!

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)

        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885), zoomLevel: 16, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        mapView.delegate = self
        mapView.showsUserLocation = true
        mapView.showsUserHeadingIndicator = true

        // Taiwan's max bounds
        let northeast = CLLocationCoordinate2D(latitude: 33.4, longitude:138.45858)
        let southwest = CLLocationCoordinate2D(latitude: 15, longitude: 105)
        maxBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)

        // Add a single tap gesture recognizer. This gesture requires the built-in MGLMapView tap gestures (such as those for zoom and annotation selection) to fail.
        let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
        for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
            singleTap.require(toFail: recognizer)
        }
        mapView.addGestureRecognizer(singleTap)

        // Convert `mapView.centerCoordinate` (CLLocationCoordinate2D) to screen location (CGPoint).
        let centerScreenPoint: CGPoint = mapView.convert(mapView.centerCoordinate, toPointTo: nil)
        print("Screen center: \(centerScreenPoint) = \(mapView.center)")
    }

    func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
        let cameraPosition = MGLMapCamera(lookingAtCenter: mapView.userLocation?.coordinate ?? CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885),
                                                 altitude: mapView.camera.altitude,
                                                 pitch: 50,
                                                 heading: 0)
        mapView.camera = cameraPosition
    }

    // Restrict panning area
    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        let currentCamera = mapView.camera
        let newCameraCenter = newCamera.centerCoordinate
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds
        mapView.camera = currentCamera
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds)

        return inside && intersects
    }

    @objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) {
        // Convert tap location (CGPoint) to geographic coordinate (CLLocationCoordinate2D).
        let tapPoint: CGPoint = sender.location(in: mapView)
        let tapCoordinate: CLLocationCoordinate2D = mapView.convert(tapPoint, toCoordinateFrom: nil)
        print("You tapped at: \(tapCoordinate.latitude), \(tapCoordinate.longitude)")

        // Wait for the map to load before initiating the first camera movement.

        // Create a camera that rotates around the same center point, rotating 180°.
        // `fromDistance:` is meters above mean sea level that an eye would have to be in order to see what the map view is showing.
        let camera = MGLMapCamera(lookingAtCenter: tapCoordinate, altitude: 4500, pitch: 15, heading: 0)

        // Animate the camera movement over 2 seconds.
        mapView.setCamera(camera, withDuration: 2, animationTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))

        // Remove any existing annotation(s) from the map.
        if mapView.annotations?.count != nil, let existingAnnotations = mapView.annotations {
            mapView.removeAnnotations(existingAnnotations)
        }

        let point = MGLPointAnnotation()
        point.coordinate = tapCoordinate
        point.title = "座標"
        point.subtitle = "\(point.coordinate.latitude), \(point.coordinate.longitude)"

        // Add marker `point` to the map.
        mapView.addAnnotation(point)
    }

    // Allow callout view to appear when an annotation is tapped.
    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        return true
    }
}

Camera animation

拖曳地圖標記

Example


import UIKit
import Map8

class DragMarker: UIViewController, MGLMapViewDelegate {

    var maxBounds: MGLCoordinateBounds!

    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885), zoomLevel: 16, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        mapView.delegate = self
        mapView.showsUserLocation = true
        mapView.showsUserHeadingIndicator = true

        // Taiwan's max bounds
        let northeast = CLLocationCoordinate2D(latitude: 33.4, longitude:138.45858)
        let southwest = CLLocationCoordinate2D(latitude: 15, longitude: 105)
        maxBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)

        // Declare the marker `hello` and set its coordinates, title, and subtitle.
        let point = MGLPointAnnotation()
        point.coordinate = CLLocationCoordinate2D(latitude: 25.04753, longitude: 121.55045)
        point.title = "To drag this annotation, first tap and hold."

        mapView.addAnnotation(point)
    }

    func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
        let cameraPosition = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885),
                                                 altitude: mapView.camera.altitude,
                                                 pitch: 50,
                                                 heading: 0)
        mapView.camera = cameraPosition
    }

    // Restrict panning area
    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        let currentCamera = mapView.camera
        let newCameraCenter = newCamera.centerCoordinate
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds
        mapView.camera = currentCamera
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds)

        return inside && intersects
    }

    // This delegate method is where you tell the map to load a view for a specific annotation. To load a static MGLAnnotationImage, you would use `-mapView:imageForAnnotation:`.
    func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
        // This example is only concerned with point annotations.
        guard annotation is MGLPointAnnotation else {
            return nil
        }

        // For better performance, always try to reuse existing annotations. To use multiple different annotation views, change the reuse identifier for each.
        if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "draggablePoint") {
            return annotationView
        } else {
            return DraggableAnnotationView(reuseIdentifier: "draggablePoint", size: 50)
        }
    }

    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        return true
    }
}

// MGLAnnotationView subclass
class DraggableAnnotationView: MGLAnnotationView {
    init(reuseIdentifier: String, size: CGFloat) {
        super.init(reuseIdentifier: reuseIdentifier)

        // `isDraggable` is a property of MGLAnnotationView, disabled by default.
        isDraggable = true

        // This property prevents the annotation from changing size when the map is tilted.
        scalesWithViewingDistance = false

        // Begin setting up the view.
        frame = CGRect(x: 0, y: 0, width: 30, height: 48)

        let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 48))
        imageViewBackground.image = UIImage(named: "red_marker.png")
        imageViewBackground.contentMode = .scaleAspectFill

        addSubview(imageViewBackground)
        contentMode = .scaleAspectFill
    }

    // These two initializers are forced upon us by Swift.
    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Custom handler for changes in the annotation’s drag state.
    override func setDragState(_ dragState: MGLAnnotationViewDragState, animated: Bool) {
        super.setDragState(dragState, animated: animated)

        switch dragState {
        case .starting:
            print("Starting", terminator: "")
            startDragging()
        case .dragging:
            print(".", terminator: "")
            case .ending, .canceling:
        print("Ending")
            endDragging()
            case .none:
        break
        @unknown default:
            fatalError("Unknown drag state")
        }
    }

    // When the user interacts with an annotation, animate opacity and scale changes.
    func startDragging() {
        UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
            self.layer.opacity = 0.8
            self.transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
        }, completion: nil)

        // Initialize haptic feedback generator and give the user a light thud.
        if #available(iOS 10.0, *) {
            let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
            hapticFeedback.impactOccurred()
        }
    }

    func endDragging() {
        transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
        UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
            self.layer.opacity = 1
            self.transform = CGAffineTransform.identity.scaledBy(x: 1, y: 1)
        }, completion: nil)

        // Give the user more haptic feedback when they drop the annotation.
        if #available(iOS 10.0, *) {
            let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
            hapticFeedback.impactOccurred()
        }
    }
}

Drag marker

自定義地圖標記圖示

Example

import UIKit
import Map8

class CustomMarker: UIViewController, MGLMapViewDelegate {

    var maxBounds: MGLCoordinateBounds!

    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.03625, longitude: 121.54885), zoomLevel: 14, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        mapView.delegate = self

        // Taiwan's max bounds
        let northeast = CLLocationCoordinate2D(latitude: 33.4, longitude:138.45858)
        let southwest = CLLocationCoordinate2D(latitude: 15, longitude: 105)
        maxBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)

        // Create new point annotations with specified coordinates and titles.
        let pointA = MyCustomPointAnnotation()
        pointA.coordinate = CLLocationCoordinate2D(latitude: 25.0366550, longitude: 121.551039)
        pointA.title = "Red Marker"
        pointA.willUseImage = true

        let pointB = MyCustomPointAnnotation()
        pointB.coordinate = CLLocationCoordinate2D(latitude: 25.0394935, longitude: 121.544044)
        pointB.title = "Blue Marker"
        pointB.willUseImage = true

        let pointC = MyCustomPointAnnotation()
        pointC.title = "Yellow Marker"
        pointC.coordinate = CLLocationCoordinate2D(latitude: 25.032708, longitude: 121.5445160)
        pointC.willUseImage = true

        let pointD = MyCustomPointAnnotation()
        pointD.title = "Camera"
        pointD.coordinate = CLLocationCoordinate2D(latitude: 25.0402128, longitude: 121.5538072)
        pointD.willUseImage = true

        let pointE = MyCustomPointAnnotation()
        pointE.title = "Light House"
        pointE.coordinate = CLLocationCoordinate2D(latitude: 25.0336999, longitude: 121.5530562)
        pointE.willUseImage = true

        let pointF = MyCustomPointAnnotation()
        pointF.title = "Point"
        pointF.coordinate = CLLocationCoordinate2D(latitude: 25.040504, longitude: 121.5486359)

        // Fill an array with four point annotations.
        let myPlaces = [pointA, pointB, pointC, pointD, pointE, pointF]

        // Add all annotations to the map all at once, instead of individually.
        mapView.addAnnotations(myPlaces)
    }

    // Restrict panning area
    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        let currentCamera = mapView.camera
        let newCameraCenter = newCamera.centerCoordinate
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds
        mapView.camera = currentCamera
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds)

        return inside && intersects
    }

    // This delegate method is where you tell the map to load a view for a specific annotation based on the willUseImage property of the custom subclass.
    func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {

        if let castAnnotation = annotation as? MyCustomPointAnnotation {
            if (castAnnotation.willUseImage) {
                return nil
            }
        }

        // Assign a reuse identifier to be used by both of the annotation views, taking advantage of their similarities.
        let reuseIdentifier = "reusableDotView"

        // For better performance, always try to reuse existing annotations.
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)

        // If there’s no reusable annotation view available, initialize a new one.
        if annotationView == nil {
            annotationView = MGLAnnotationView(reuseIdentifier: reuseIdentifier)
            annotationView?.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
            annotationView?.layer.cornerRadius = (annotationView?.frame.size.width)! / 2
            annotationView?.layer.borderWidth = 4.0
            annotationView?.layer.borderColor = UIColor.white.cgColor
            annotationView!.backgroundColor = UIColor(red: 0.03, green: 0.80, blue: 0.69, alpha: 1.0)
        }

        return annotationView
    }

    // This delegate method is where you tell the map to load an image for a specific annotation based on the willUseImage property of the custom subclass.
    func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {

        if let castAnnotation = annotation as? MyCustomPointAnnotation {
            if (!castAnnotation.willUseImage) {
                return nil
            }
        }

        // For better performance, always try to reuse existing annotations.
        var annotationImage: MGLAnnotationImage!
        var markerIcon: UIImage!

        // If there is no reusable annotation image available, initialize a new one.
        if (mapView.dequeueReusableAnnotationImage(withIdentifier: "red_marker") == nil && annotation.title == "Red Marker") {
            markerIcon = UIImage(named: "red_marker")!
            annotationImage = MGLAnnotationImage(image: markerIcon, reuseIdentifier: "red_marker")
            annotationImage.image = annotationImage.image?.scaleImage(scaleSize: 0.5)
        }

        if (mapView.dequeueReusableAnnotationImage(withIdentifier: "blue_marker") == nil && annotation.title == "Blue Marker") {
            markerIcon = UIImage(named: "blue_marker")!
            annotationImage = MGLAnnotationImage(image: markerIcon, reuseIdentifier: "blue_marker")
            annotationImage.image = annotationImage.image?.scaleImage(scaleSize: 0.5)
        }

        if (mapView.dequeueReusableAnnotationImage(withIdentifier: "yellow_marker") == nil && annotation.title == "Yellow Marker") {
            markerIcon = UIImage(named: "yellow_marker")!
            annotationImage = MGLAnnotationImage(image: markerIcon, reuseIdentifier: "yellow_marker")
            annotationImage.image = annotationImage.image?.scaleImage(scaleSize: 0.5)
        }

        if (mapView.dequeueReusableAnnotationImage(withIdentifier: "camera") == nil && annotation.title == "Camera") {
            markerIcon = UIImage(named: "attraction")!
            annotationImage = MGLAnnotationImage(image: markerIcon, reuseIdentifier: "camera")
            annotationImage.image = annotationImage.image?.scaleImage(scaleSize: 0.5)
        }

        if (mapView.dequeueReusableAnnotationImage(withIdentifier: "lighthouse") == nil && annotation.title == "Light House") {
            markerIcon = UIImage(named: "lighthouse")!
            annotationImage = MGLAnnotationImage(image: markerIcon, reuseIdentifier: "lighthouse")
            annotationImage.image = annotationImage.image?.scaleImage(scaleSize: 0.5)
        }

        return annotationImage
    }

    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        // Always allow callouts to popup when annotations are tapped.
        return true
    }

}

// MGLPointAnnotation subclass
class MyCustomPointAnnotation: MGLPointAnnotation {
    var willUseImage: Bool = false
}

extension UIImage {
    /**
    *  重設圖片大小
    */
    func reSizeImage(reSize:CGSize)->UIImage {
        UIGraphicsBeginImageContextWithOptions(reSize,false,UIScreen.main.scale);
        self.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: reSize.width, height: reSize.height)));
        let reSizeImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!;
        UIGraphicsEndImageContext();
        return reSizeImage;
    }

    /**
    *  等比例缩放
    */
    func scaleImage(scaleSize:CGFloat)->UIImage {
        let reSize = CGSize(width: self.size.width * scaleSize, height: self.size.height * scaleSize)
        return reSizeImage(reSize: reSize)
    }
}

Custom marker

限制地圖可滑區域

Example

import UIKit
import Map8

class RestrictArea: UIViewController, MGLMapViewDelegate {

    private var customBounds: MGLCoordinateBounds!

    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.04753, longitude: 121.55045), zoomLevel: 14, direction: 0, animated: false)
        mapView.delegate = self
        view.addSubview(mapView)

        let northeast = CLLocationCoordinate2D(latitude: 25.088269, longitude: 121.614291)
        let southwest = CLLocationCoordinate2D(latitude: 25.014706, longitude: 121.482946)
        customBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)
    }

    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        // Get the current camera to restore it after.
        let currentCamera = mapView.camera

        // From the new camera obtain the center to test if it’s inside the boundaries.
        let newCameraCenter = newCamera.centerCoordinate

        // Set the map’s visible bounds to newCamera.
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds

        // Revert the camera.
        mapView.camera = currentCamera

        // Test if the newCameraCenter and newVisibleCoordinates are inside self.colorado.
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, self.customBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, self.customBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, self.customBounds)

        return inside && intersects
    }
}

Restrict area

繪製多邊形圖層

Example

import UIKit
import Map8

class PolygonAnnotation: UIViewController, MGLMapViewDelegate {

    var mapView: MGLMapView!

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.04753, longitude: 121.55045), zoomLevel: 12, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        // Set the map view's delegate
        mapView.delegate = self

        // Allow the map view to display the user's location
        mapView.showsUserLocation = true
    }

    override func viewDidAppear(_ animated: Bool) {
        // Draw the polygon after the map has initialized
        drawShape()
    }

    func drawShape() {
        // Create a coordinates array to hold all of the coordinates for our shape.
        var coordinates = [
            CLLocationCoordinate2D(latitude: 25.075310, longitude: 121.549365),
            CLLocationCoordinate2D(latitude: 25.059519, longitude: 121.525185),
            CLLocationCoordinate2D(latitude: 25.036776, longitude: 121.538199),
            CLLocationCoordinate2D(latitude: 25.037825, longitude: 121.558884),
            CLLocationCoordinate2D(latitude: 25.056986, longitude: 121.565167)
        ]

        let shape = MGLPolygon(coordinates: &coordinates, count: UInt(coordinates.count))

        mapView.addAnnotation(shape)
    }

    func mapView(_ mapView: MGLMapView, alphaForShapeAnnotation annotation: MGLShape) -> CGFloat {
        return 0.5
    }

    func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
        return .white
    }

    func mapView(_ mapView: MGLMapView, fillColorForPolygonAnnotation annotation: MGLPolygon) -> UIColor {
        return UIColor(red: 59/255, green: 178/255, blue: 208/255, alpha: 1)
    }

}

Polygon

繪製圓形圖層

Example

import UIKit
import Map8

class AddCirclePolygon: UIViewController, MGLMapViewDelegate {

    var mapView: MGLMapView!

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.037682, longitude: 121.548785), zoomLevel: 14, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        // Set the map view's delegate
        mapView.delegate = self

        // Allow the map view to display the user's location
        mapView.showsUserLocation = true
    }

    // Wait until the map is loaded before adding to the map.
    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        drawCirclePolygon()
    }

    func drawCirclePolygon() {
        var features = [MGLPointFeature]()
        let center = MGLPointFeature();
        center.coordinate = CLLocationCoordinate2D(latitude: 25.037682, longitude: 121.548785)
        features.append(center)
        let circleSource = MGLShapeSource(identifier: "circles", features: features, options: nil)
        mapView.style?.addSource(circleSource)

        let circleLayer = MGLCircleStyleLayer(identifier: "circles", source: circleSource)
        circleLayer.circleColor = NSExpression(forConstantValue: UIColor.red)
        circleLayer.circleRadius = NSExpression(forConstantValue: 100)
        circleLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
        circleLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.black)
        circleLayer.circleOpacity = NSExpression(forConstantValue: 0.3)

        mapView.style?.addLayer(circleLayer)
    }

}

Draw circle

多個圓形圖層疊加

Example

import UIKit
import Map8

class AddMultiCirclesLayer: UIViewController, MGLMapViewDelegate {

    var mapView: MGLMapView!

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.04624, longitude: 121.52415), zoomLevel: 14, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        // Set the map view's delegate
        mapView.delegate = self
    }

    func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
        let cameraPosition = MGLMapCamera(lookingAtCenter: mapView.userLocation?.coordinate ?? CLLocationCoordinate2D(latitude: 25.04624, longitude: 121.52415),
                                          altitude: mapView.camera.altitude,
                                          pitch: 42,
                                          heading: 0)
        mapView.camera = cameraPosition

        polygonCircleForCoordinate(coordinate: CLLocationCoordinate2D(latitude: 25.04624, longitude: 121.52415), withMeterRadius: 500, withTitle: "circle1")
        polygonCircleForCoordinate(coordinate: CLLocationCoordinate2D(latitude: 25.05003, longitude: 121.52942), withMeterRadius: 300, withTitle: "circle2")
        polygonCircleForCoordinate(coordinate: CLLocationCoordinate2D(latitude: 25.04996, longitude: 121.50792), withMeterRadius: 1500, withTitle: "circle3")
    }

    // This circle layer can change with pitch and zoom level
    func polygonCircleForCoordinate(coordinate: CLLocationCoordinate2D, withMeterRadius: Double, withTitle: String) {
        let degreesBetweenPoints = 8.0
        //45 sides
        let numberOfPoints = floor(360.0 / degreesBetweenPoints)
        let distRadians: Double = withMeterRadius / 6371000.0
        // earth radius in meters
        let centerLatRadians: Double = coordinate.latitude * Double.pi / 180
        let centerLonRadians: Double = coordinate.longitude * Double.pi / 180
        var coordinates = [CLLocationCoordinate2D]()
        //array to hold all the points
        for index in 0 ..< Int(numberOfPoints) {
            let degrees: Double = Double(index) * Double(degreesBetweenPoints)
            let degreeRadians: Double = degrees * Double.pi / 180
            let pointLatRadians: Double = asin(sin(centerLatRadians) * cos(distRadians) + cos(centerLatRadians) * sin(distRadians) * cos(degreeRadians))
            let pointLonRadians: Double = centerLonRadians + atan2(sin(degreeRadians) * sin(distRadians) * cos(centerLatRadians), cos(distRadians) - sin(centerLatRadians) * sin(pointLatRadians))
            let pointLat: Double = pointLatRadians * 180 / Double.pi
            let pointLon: Double = pointLonRadians * 180 / Double.pi
            let point: CLLocationCoordinate2D = CLLocationCoordinate2DMake(pointLat, pointLon)
            coordinates.append(point)
        }

        let polygon = MGLPolygon(coordinates: &coordinates, count: UInt(coordinates.count))
        polygon.title = withTitle

        self.mapView.addAnnotation(polygon)
    }

    func mapView(_ mapView: MGLMapView, alphaForShapeAnnotation annotation: MGLShape) -> CGFloat {
        return 0.5
    }
    func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
        return .white
    }

    func mapView(_ mapView: MGLMapView, fillColorForPolygonAnnotation annotation: MGLPolygon) -> UIColor {
        if (annotation.title == "circle2") {
            return UIColor(red: 133/255, green: 91/255, blue: 50/255, alpha: 1)
        }
        else if (annotation.title == "circle3") {
            return UIColor(red: 208/255, green: 16/255, blue: 76/255, alpha: 1)
        }
        else {
            return UIColor(red: 59/255, green: 178/255, blue: 208/255, alpha: 1)
        }
    }
}

Multi circle layer

路徑線段動畫展示

Example

import UIKit
import Map8

class LineAnimation: UIViewController, MGLMapViewDelegate {

    // Struct for decode geojson format
    struct Geodata: Codable {
        let type: String
        let features: [Feature]
    }

    struct Feature: Codable {
        let type: String
        let properties: Properties
        let geometry: Geometry
    }

    struct Geometry: Codable {
        let type: String
        let coordinates: [[Double]]
    }

    struct Properties: Codable {
        let name: String?
    }

    var mapView: MGLMapView!
    var timer: Timer?
    var polylineSource: MGLShapeSource?
    var currentIndex = 1
    var allCoordinates: [CLLocationCoordinate2D]!

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 25.037682, longitude: 121.548785), zoomLevel: 14, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        // Set the map view's delegate
        mapView.delegate = self

        // Allow the map view to display the user's location
        mapView.showsUserLocation = true
    }

    // Wait until the map is loaded before adding to the map.
    func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
        loadGeosjon();
    }

    func loadGeosjon() {
        DispatchQueue.global().async {
            // Get the path for example.geojson in the app’s bundle  tbiMapbox.
            guard let jsonUrl = Bundle.main.url(forResource: "map", withExtension: "geojson") else { return }
            guard let jsonData = try? Data(contentsOf: jsonUrl) else { return }
            DispatchQueue.main.async {
                do {
                    let geoData = try JSONDecoder().decode(Geodata.self, from: jsonData)
                    for feature in geoData.features {
                        let coordData = feature.geometry.coordinates.map{CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0])}
                        self.allCoordinates = coordData
                        self.addPolyline(to: self.mapView.style!)
                        self.animatePolyline()
                    }
                } catch {
                    print("GeoJSON parsing failed")
                }
            }
        }
    }

    func addPolyline(to style: MGLStyle) {
        // Add an empty MGLShapeSource, we’ll keep a reference to this and add points to this later.
        let source = MGLShapeSource(identifier: "polyline", shape: nil, options: nil)
        style.addSource(source)
        polylineSource = source

        // Add a layer to style our polyline.
        let layer = MGLLineStyleLayer(identifier: "polyline", source: source)
        layer.lineJoin = NSExpression(forConstantValue: "round")
        layer.lineCap = NSExpression(forConstantValue: "round")
        layer.lineColor = NSExpression(forConstantValue: UIColor.red)

        // The line width should gradually increase based on the zoom level.
        layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
                                       [14: 5, 18: 20])
        style.addLayer(layer)
    }

    func animatePolyline() {
        currentIndex = 1

        // Start a timer that will simulate adding points to our polyline. This could also represent coordinates being added to our polyline from another source, such as a CLLocationManagerDelegate.
        timer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
    }

    @objc func tick() {
        if currentIndex > allCoordinates.count {
            timer?.invalidate()
            timer = nil
            return
        }

        // Create a subarray of locations up to the current index.
        let coordinates = Array(allCoordinates[0..<currentIndex])

        // Update our MGLShapeSource with the current locations.
        updatePolylineWithCoordinates(coordinates: coordinates)

        currentIndex += 1
    }

    func updatePolylineWithCoordinates(coordinates: [CLLocationCoordinate2D]) {
        var mutableCoordinates = coordinates

        let polyline = MGLPolylineFeature(coordinates: &mutableCoordinates, count: UInt(mutableCoordinates.count))

        // Updating the MGLShapeSource’s shape will have the map redraw our polyline with the current coordinates.
        polylineSource?.shape = polyline
    }
}

Line animate

建立群聚標示

Example

import UIKit
import Map8

class ShowCluster: UIViewController, MGLMapViewDelegate {

    var mapView: MGLMapView!
    var maxBounds: MGLCoordinateBounds!
    var icon: UIImage!
    var popup: UIView?

    enum CustomError: Error {
        case castingError(String)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: NSURL(string: "https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json") as URL?)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 24.963355, longitude: 121.216776), zoomLevel: 16, animated: false)
        mapView.maximumZoomLevel = 19.99
        view.addSubview(mapView)

        mapView.delegate = self
        mapView.showsUserLocation = true
        mapView.showsUserHeadingIndicator = true

        // Taiwan's max bounds
        let northeast = CLLocationCoordinate2D(latitude: 33.4, longitude:138.45858)
        let southwest = CLLocationCoordinate2D(latitude: 15, longitude: 105)
        maxBounds = MGLCoordinateBounds(sw: southwest, ne: northeast)

        // Add a double tap gesture recognizer. This gesture is used for double
        // tapping on clusters and then zooming in so the cluster expands to its
        // children.
        let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTapCluster(sender:)))
        doubleTap.numberOfTapsRequired = 2
        doubleTap.delegate = self

        // It's important that this new double tap fails before the map view's
        // built-in gesture can be recognized. This is to prevent the map's gesture from
        // overriding this new gesture (and then not detecting a cluster that had been
        // tapped on).
        for recognizer in mapView.gestureRecognizers!
            where (recognizer as? UITapGestureRecognizer)?.numberOfTapsRequired == 2 {
            recognizer.require(toFail: doubleTap)
        }
        mapView.addGestureRecognizer(doubleTap)

        // Add a single tap gesture recognizer. This gesture requires the built-in
        // MGLMapView tap gestures (such as those for zoom and annotation selection)
        // to fail (this order differs from the double tap above).
        let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
        for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
            singleTap.require(toFail: recognizer)
        }
        mapView.addGestureRecognizer(singleTap)

        icon = UIImage(named: "port")
    }

    // Restrict panning area
    func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
        let currentCamera = mapView.camera
        let newCameraCenter = newCamera.centerCoordinate
        mapView.camera = newCamera
        let newVisibleCoordinates = mapView.visibleCoordinateBounds
        mapView.camera = currentCamera
        let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds)
        let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds)

        return inside && intersects
    }

    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        let cameraPosition = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 24.963355, longitude: 121.216776),
                                                 altitude: mapView.camera.altitude,
                                                 pitch: 50,
                                                 heading: 0)
        mapView.camera = cameraPosition

        let url = URL(fileURLWithPath: Bundle.main.path(forResource: "housesale", ofType: "geojson")!)

        let source = MGLShapeSource(identifier: "clusteredPorts",
                                    url: url,
                                    options: [.clustered: true,
                                              .maximumZoomLevelForClustering: 14,
                                              .clusterRadius: icon.size.width])
        style.addSource(source)

        // Use a template image so that we can tint it with the `iconColor` runtime styling property.
        style.setImage(icon.withRenderingMode(.alwaysTemplate), forName: "icon")

        // Show unclustered features as icons. The `cluster` attribute is built into clustering-enabled
        // source features.
        let ports = MGLSymbolStyleLayer(identifier: "ports", source: source)
        ports.iconImageName = NSExpression(forConstantValue: "icon")
        ports.iconColor = NSExpression(forConstantValue: UIColor.darkGray.withAlphaComponent(0.9))
        ports.predicate = NSPredicate(format: "cluster != YES")
        ports.iconAllowsOverlap = NSExpression(forConstantValue: true)
        ports.iconScale = NSExpression(forConstantValue: 0.5)
        style.addLayer(ports)

        // Color clustered features based on clustered point counts.
        let stops = [
            20: UIColor.lightGray,
            50: UIColor.orange,
            100: UIColor.red,
            200: UIColor.purple
        ]

        // Cluster radius based on clustered point counts.
        let stops_radius = [
            20: NSNumber(20),
            50: NSNumber(30),
            100: NSNumber(40),
            200: NSNumber(50)
        ]

        // Show clustered features as circles. The `point_count` attribute is built into
        // clustering-enabled source features.
        let circlesLayer = MGLCircleStyleLayer(identifier: "clusteredPorts", source: source)
        circlesLayer.circleRadius = NSExpression(format: "mgl_step:from:stops:(point_count, %@, %@)", NSNumber(20), stops_radius)
        circlesLayer.circleOpacity = NSExpression(forConstantValue: 0.75)
        circlesLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.white.withAlphaComponent(0.75))
        circlesLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
        circlesLayer.circleColor = NSExpression(format: "mgl_step:from:stops:(point_count, %@, %@)", UIColor.lightGray, stops)
        circlesLayer.predicate = NSPredicate(format: "cluster == YES")
        style.addLayer(circlesLayer)

        // Label cluster circles with a layer of text indicating feature count. The value for
        // `point_count` is an integer. In order to use that value for the
        // `MGLSymbolStyleLayer.text` property, cast it as a string.
        let numbersLayer = MGLSymbolStyleLayer(identifier: "clusteredPortsNumbers", source: source)
        numbersLayer.textColor = NSExpression(forConstantValue: UIColor.white)
        numbersLayer.textFontSize = NSExpression(forConstantValue: NSNumber(value: Double(icon.size.width) / 2))
        // *** IMPORTANT, 必須要指定字型,Cluster 才能運作
        numbersLayer.textFontNames = NSExpression(forConstantValue: ["Noto Sans Regular"])
        numbersLayer.iconAllowsOverlap = NSExpression(forConstantValue: true)
        numbersLayer.text = NSExpression(format: "CAST(point_count, 'NSString')")

        numbersLayer.predicate = NSPredicate(format: "cluster == YES")
        style.addLayer(numbersLayer)
    }

    func mapViewRegionIsChanging(_ mapView: MGLMapView) {
        showPopup(false, animated: false)
    }

    private func firstCluster(with gestureRecognizer: UIGestureRecognizer) -> MGLPointFeatureCluster? {
        let point = gestureRecognizer.location(in: gestureRecognizer.view)
        let width = icon.size.width
        let rect = CGRect(x: point.x - width / 2, y: point.y - width / 2, width: width, height: width)

        // This example shows how to check if a feature is a cluster by
        // checking for that the feature is a `MGLPointFeatureCluster`. Alternatively, you could
        // also check for conformance with `MGLCluster` instead.
        let features = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts", "ports"])
        let clusters = features.compactMap { $0 as? MGLPointFeatureCluster }

        // Pick the first cluster, ideally selecting the one nearest nearest one to
        // the touch point.
        return clusters.first
    }

    @objc func handleDoubleTapCluster(sender: UITapGestureRecognizer) {

        guard let source = mapView.style?.source(withIdentifier: "clusteredPorts") as? MGLShapeSource else {
            return
        }

        guard sender.state == .ended else {
            return
        }

        showPopup(false, animated: false)

        guard let cluster = firstCluster(with: sender) else {
            return
        }

        let zoom = source.zoomLevel(forExpanding: cluster)

        if zoom > 0 {
            mapView.setCenter(cluster.coordinate, zoomLevel: zoom, animated: true)
        }
    }

    @objc func handleMapTap(sender: UITapGestureRecognizer) {

        guard let source = mapView.style?.source(withIdentifier: "clusteredPorts") as? MGLShapeSource else {
            return
        }

        guard sender.state == .ended else {
            return
        }

        showPopup(false, animated: false)

        let point = sender.location(in: sender.view)
        let width = icon.size.width
        let rect = CGRect(x: point.x - width / 2, y: point.y - width / 2, width: width, height: width)

        let features = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts", "ports"])

        // Pick the first feature (which may be a port or a cluster), ideally selecting
        // the one nearest nearest one to the touch point.
        guard let feature = features.first else {
            return
        }

        let description: String
        let color: UIColor

        if let cluster = feature as? MGLPointFeatureCluster {
            // Tapped on a cluster.
            let children = source.children(of: cluster)
            description = "Cluster #\(cluster.clusterIdentifier)\n\(children.count) children"
            color = .blue
        } else if let featureName = feature.attribute(forKey: "name") as? String?,
            // Tapped on a port.
            let portName = featureName {
            description = portName
            color = .black
        } else {
            // Tapped on a port that is missing a name.
            description = "No port name"
            color = .red
        }

        popup = popup(at: feature.coordinate, with: description, textColor: color)

        showPopup(true, animated: true)
    }

    // Convenience method to create a reusable popup view.
    private func popup(at coordinate: CLLocationCoordinate2D, with description: String, textColor: UIColor) -> UIView {
        let popup = UILabel()

        popup.backgroundColor     = UIColor.white.withAlphaComponent(0.9)
        popup.layer.cornerRadius  = 4
        popup.layer.masksToBounds = true
        popup.textAlignment       = .center
        popup.lineBreakMode       = .byTruncatingTail
        popup.numberOfLines       = 0
        popup.font                = .systemFont(ofSize: 16)
        popup.textColor           = textColor
        popup.alpha               = 0
        popup.text                = description

        popup.sizeToFit()

        // Expand the popup.
        popup.bounds = popup.bounds.insetBy(dx: -10, dy: -10)
        let point = mapView.convert(coordinate, toPointTo: mapView)
        popup.center = CGPoint(x: point.x, y: point.y - 50)

        return popup
    }

    func showPopup(_ shouldShow: Bool, animated: Bool) {
        guard let popup = self.popup else {
            return
        }

        if shouldShow {
            view.addSubview(popup)
        }

        let alpha: CGFloat = (shouldShow ? 1 : 0)

        let animation = {
            popup.alpha = alpha
        }

        let completion = { (_: Bool) in
            if !shouldShow {
                popup.removeFromSuperview()
            }
        }

        if animated {
            UIView.animate(withDuration: 0.25, animations: animation, completion: completion)
        } else {
            animation()
            completion(true)
        }
    }
}

extension ShowCluster: UIGestureRecognizerDelegate {

    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        // This will only get called for the custom double tap gesture,
        // that should always be recognized simultaneously.
        return true
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        // This will only get called for the custom double tap gesture.
        return firstCluster(with: gestureRecognizer) != nil
    }
}

Cluster


台灣圖霸感謝您的支持與愛護!

有任何疑問,或是指教,都非常歡迎您找我們詢問。

非常感謝!



https://map8.zone