台灣圖霸 | Map8 Platform
歡迎使用 台灣圖霸 | Map8 Platform 地圖平台
- 歡迎試用我們的 台灣圖霸 (Map8 Platform) 地圖平台 API!! 請點此申請試用~
- 然後也歡迎您到我們的 官方 API Explorer 試用看看~
有任何技術疑難,或其他疑問,都歡迎您 跟我們聯絡 喔!!!
初始化地圖
- 範例說明:
- 設定地圖中心點
- 顯示使用者位置
- 旋轉地圖
- 2D 轉 3D 俯瞰視角
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
}
}
攝影機視角移動地圖
- 範例說明:
- 點擊任一點以攝影機動畫移動地圖
- 顯示地圖標記 ( Marker )
- 點擊 Marker 顯示座標
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
}
}
拖曳地圖標記
- 範例說明:
- 顯示地圖標記 ( Marker )
- 長按 Marker 可拖曳制地圖任一點
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()
}
}
}
自定義地圖標記圖示
- 範例說明:
- 顯示地圖標記 ( 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)
}
}
限制地圖可滑區域
- 範例說明:
- 設定東北與西南的經緯度,限制使用者在此座標內滑動地圖
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
}
}
繪製多邊形圖層
- 範例說明:
- 給予指定經緯度座標點,畫出 Polygon Annotation 顯示在地圖上
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)
}
}
繪製圓形圖層
- 範例說明:
- 給予指定中心點經緯度,畫出圓形圖層顯示在地圖上
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)
}
}
多個圓形圖層疊加
- 範例說明:
- 畫出複數個圓形圖層顯示在地圖上
- 圓形可依地圖俯瞰視角與縮放變動
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)
}
}
}
路徑線段動畫展示
- 範例說明:
- 載入自定義的 geojson 檔案
- 繪出路徑線段,並以動畫方式呈現
- 範例 geojson
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
}
}
建立群聚標示
- 範例說明:
- 載入自定義的 geojson 檔案
- 在地圖上建立群聚標示
- 範例 geojson
- 範例圖示
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
}
}
台灣圖霸感謝您的支持與愛護!
有任何疑問,或是指教,都非常歡迎您找我們詢問。
非常感謝!
https://map8.zone