台灣圖霸 | Map8 Platform
歡迎使用 台灣圖霸 | Map8 Platform 地圖平台
- 歡迎試用我們的 台灣圖霸 (Map8 Platform) 地圖平台 API!! 請點此申請試用~
- 然後也歡迎您到我們的 官方 API Explorer 試用看看~
有任何技術疑難,或其他疑問,都歡迎您 跟我們聯絡 喔!!!
初始化地圖
- 範例說明:
- 設定地圖中心點
- 顯示使用者位置
- 旋轉地圖
- 2D 轉 3D 俯瞰視角
Example
#import "SimpleMapView.h"
@import Map8;
@interface SimpleMapView () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
}
@end
@implementation SimpleMapView
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.03625, 121.54885)
zoomLevel:16
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
mapView.showsUserLocation = YES;
mapView.showsUserHeadingIndicator = YES;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.04753, 121.55045);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
@end
攝影機視角移動地圖
- 範例說明:
- 點擊任一點以攝影機動畫移動地圖
- 顯示地圖標記 ( Marker )
- 點擊 Marker 顯示座標
Example
#import "CameraAnimation.h"
@import Map8;
@interface CameraAnimation () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
}
@end
@implementation CameraAnimation
- (void)viewDidLoad {
[super viewDidLoad];
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.03625, 121.54885)
zoomLevel:16
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
mapView.showsUserLocation = YES;
mapView.showsUserHeadingIndicator = YES;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)];
for (UIGestureRecognizer *recognizer in mapView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[singleTap requireGestureRecognizerToFail:recognizer];
}
}
[mapView addGestureRecognizer:singleTap];
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.04753, 121.55045);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (void)handleMapTap:(UITapGestureRecognizer*)sender {
// Convert tap location (CGPoint) to geographic coordinate (CLLocationCoordinate2D).
CGPoint tapPoint = [sender locationInView:mapView];
CLLocationCoordinate2D tapCoordinate = [mapView convertPoint:tapPoint toCoordinateFromView:nil];
NSLog(@"You tapped at: %.5f, %.5f", tapCoordinate.latitude, tapCoordinate.longitude);
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:tapCoordinate altitude:4500 pitch:15 heading:0];
// Animate the camera movement over 5 seconds.
[mapView setCamera:camera withDuration:2 animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
// Remove any existing annotation(s) from the map.
if (mapView.annotations.count != 0) {
NSArray *existAnnorations = mapView.annotations;
[mapView removeAnnotations:existAnnorations];
}
// Add marker `point` to the map.
MGLPointAnnotation *point = [MGLPointAnnotation new];
point.coordinate = tapCoordinate;
point.title = @"座標";
point.subtitle = [NSString stringWithFormat:@"%.5f, %.5f", point.coordinate.latitude, point.coordinate.longitude];
[mapView addAnnotation:point];
}
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
return YES;
}
@end
拖曳地圖標記
- 範例說明:
- 顯示地圖標記 ( Marker )
- 長按 Marker 可拖曳制地圖任一點
Example
#import "DragMarker.h"
@import Map8;
// MGLAnnotationView subclass
@interface DraggableAnnotationView : MGLAnnotationView
- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier size:(CGFloat)size;
@end
@interface DragMarker () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
}
@end
@implementation DragMarker
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.03625, 121.54885)
zoomLevel:16
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
MGLPointAnnotation *point = [MGLPointAnnotation new];
point.coordinate = CLLocationCoordinate2DMake(25.03625, 121.54885);
point.title = @"To drag this annotation, first tap and hold.";
[mapView addAnnotation:point];
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.03625, 121.54885);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL 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:`.
- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id <MGLAnnotation>)annotation {
// This example is only concerned with point annotations.
if (![annotation isKindOfClass:[MGLPointAnnotation class]]) {
return nil;
}
// For better performance, always try to reuse existing annotations. To use multiple different annotation views, change the reuse identifier for each.
DraggableAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"draggablePoint"];
// If there’s no reusable annotation view available, initialize a new one.
if (!annotationView) {
annotationView = [[DraggableAnnotationView alloc] initWithReuseIdentifier:@"draggablePoint" size:50];
}
return annotationView;
}
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
return YES;
}
@end
// Private interface for DraggableAnnotationView
@interface DraggableAnnotationView ()
@property (nonatomic, nullable) UIImpactFeedbackGenerator *hapticFeedback;
@end
@implementation DraggableAnnotationView
- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier size:(CGFloat)size {
self = [self initWithReuseIdentifier:reuseIdentifier];
if (self)
{
// `draggable` is a property of MGLAnnotationView, disabled by default.
self.draggable = true;
// This property prevents the annotation from changing size when the map is tilted.
self.scalesWithViewingDistance = false;
// Begin setting up the view.
self.frame = CGRectMake(0, 0, 30, 48);
UIImageView *icon = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 48)];
[icon setImage:[UIImage imageNamed:@"red_marker.png"]];
[icon setContentMode:UIViewContentModeScaleAspectFill];
[self addSubview:icon];
self.contentMode = UIViewContentModeScaleAspectFill;
}
return self;
}
- (void)setDragState:(MGLAnnotationViewDragState)dragState animated:(BOOL)animated {
[super setDragState:dragState animated:animated];
switch (dragState) {
case MGLAnnotationViewDragStateStarting:
printf("Starting");
[self startDragging];
break;
case MGLAnnotationViewDragStateDragging:
printf(".");
break;
case MGLAnnotationViewDragStateEnding:
case MGLAnnotationViewDragStateCanceling:
printf("Ending\n");
[self endDragging];
break;
case MGLAnnotationViewDragStateNone:
return;
}
}
// When the user interacts with an annotation, animate opacity and scale changes.
- (void)startDragging {
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:0 animations:^{
self.layer.opacity = 0.8f;
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
} completion:nil];
// Initialize haptic feedback generator and give the user a light thud.
if (@available(iOS 10.0, *)) {
self.hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[self.hapticFeedback impactOccurred];
// Keep the generator prepared, as the drop feedback event will probably happen quite soon.
[self.hapticFeedback prepare];
}
}
- (void)endDragging {
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:0 animations:^{
self.layer.opacity = 1;
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
} completion:nil];
// Give the user more haptic feedback when they drop the annotation, then release the current generator.
if (@available(iOS 10.0, *)) {
[self.hapticFeedback impactOccurred];
self.hapticFeedback = nil;
}
}
@end
自定義地圖標記圖示
- 範例說明:
- 顯示地圖標記 ( Marker )
- 使用自定義圖示
- 圖示下載
Example
#import "CustomMarker.h"
@import Map8;
// MGLPointAnnotation subclass
@interface MyCustomPointAnnotation : MGLPointAnnotation
@property (nonatomic, assign) BOOL willUseImage;
@end
@implementation MyCustomPointAnnotation
@end
@interface CustomMarker () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
}
@end
@implementation CustomMarker
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.03625, 121.54885)
zoomLevel:16
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
MyCustomPointAnnotation *pointA = [[MyCustomPointAnnotation alloc] init];
pointA.coordinate = CLLocationCoordinate2DMake(25.0366550, 121.551039);
pointA.title = @"Red Marker";
pointA.willUseImage = YES;
MyCustomPointAnnotation *pointB = [[MyCustomPointAnnotation alloc] init];
pointB.coordinate = CLLocationCoordinate2DMake(25.0394935, 121.544044);
pointB.title = @"Blue Marker";
pointB.willUseImage = YES;
MyCustomPointAnnotation *pointC = [[MyCustomPointAnnotation alloc] init];
pointC.coordinate = CLLocationCoordinate2DMake(25.032708, 121.5445160);
pointC.title = @"Yellow Marker";
pointC.willUseImage = YES;
MyCustomPointAnnotation *pointD = [[MyCustomPointAnnotation alloc] init];
pointD.coordinate = CLLocationCoordinate2DMake(25.0402128, 121.5538072);
pointD.title = @"Camera";
pointD.willUseImage = YES;
MyCustomPointAnnotation *pointE = [[MyCustomPointAnnotation alloc] init];
pointE.coordinate = CLLocationCoordinate2DMake(25.0336999, 121.5530562);
pointE.title = @"Light House";
pointE.willUseImage = YES;
MyCustomPointAnnotation *pointF = [[MyCustomPointAnnotation alloc] init];
pointF.coordinate = CLLocationCoordinate2DMake(25.040504, 121.5486359);
pointF.title = @"Point";
pointF.willUseImage = NO;
[mapView addAnnotations:@[pointA, pointB, pointC, pointD, pointE, pointF]];
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.03625, 121.54885);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id <MGLAnnotation>)annotation {
MyCustomPointAnnotation *castAnnotation = (MyCustomPointAnnotation*) annotation;
if (castAnnotation.willUseImage) {
return nil;
}
NSString *reuseIdentifier = @"reusableDotView";
MGLAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier];
if (annotationView == nil) {
annotationView = [[MGLAnnotationView alloc] initWithReuseIdentifier:reuseIdentifier];
annotationView.frame = CGRectMake(0, 0, 30, 30);
annotationView.layer.cornerRadius = annotationView.frame.size.width / 2;
annotationView.layer.borderWidth = 4.0;
annotationView.layer.borderColor = [[UIColor whiteColor] CGColor];
annotationView.backgroundColor = [UIColor colorWithRed:0.03 green:0.80 blue:0.69 alpha:1.0];
}
return annotationView;
}
- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation {
MyCustomPointAnnotation *castAnnotation = (MyCustomPointAnnotation*) annotation;
if (!castAnnotation.willUseImage) {
return nil;
}
MGLAnnotationImage *annotationImage;
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"red_marker"] == nil && [annotation.title isEqualToString:@"Red Marker"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"red_marker"] reuseIdentifier:@"red_marker"];
}
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"blue_marker"] == nil && [annotation.title isEqualToString:@"Blue Marker"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"blue_marker"] reuseIdentifier:@"blue_marker"];
}
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"blue_marker"] == nil && [annotation.title isEqualToString:@"Blue Marker"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"blue_marker"] reuseIdentifier:@"blue_marker"];
}
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"yellow_marker"] == nil && [annotation.title isEqualToString:@"Yellow Marker"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"yellow_marker"] reuseIdentifier:@"yellow_marker"];
}
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"camera"] == nil && [annotation.title isEqualToString:@"Camera"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"attraction"] reuseIdentifier:@"camera"];
}
if ([mapView dequeueReusableAnnotationViewWithIdentifier:@"lighthouse"] == nil && [annotation.title isEqualToString:@"Light House"]) {
annotationImage = [MGLAnnotationImage annotationImageWithImage:[UIImage imageNamed:@"lighthouse"] reuseIdentifier:@"lighthouse"];
}
return annotationImage;
}
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
return YES;
}
@end
限制地圖可滑區域
- 範例說明:
- 設定東北與西南的經緯度,限制使用者在此座標內滑動地圖
Example
#import "RestrictArea.h"
@import Map8;
@interface RestrictArea () <MGLMapViewDelegate>
{
MGLCoordinateBounds customBounds;
}
@end
@implementation RestrictArea
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.04753, 121.55045) zoomLevel:14 animated:NO];
[self.view addSubview:mapView];
mapView.delegate = self;
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(25.088269, 121.614291);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(25.014706, 121.482946);
customBounds = MGLCoordinateBoundsMake(southwest, northeast);
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, customBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, customBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, customBounds);
return inside && intersects;
}
@end
繪製多邊形圖層
- 範例說明:
- 給予指定經緯度座標點,畫出 Polygon Annotation 顯示在地圖上
Example
#import "PolygonAnnotation.h"
@import Map8;
@interface PolygonAnnotation () <MGLMapViewDelegate>
{
MGLMapView *mapView;
}
@end
@implementation PolygonAnnotation
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.05488, 121.55097) zoomLevel:14 animated:NO];
[self.view addSubview:mapView];
mapView.delegate = self;
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
[self drawShape];
}
- (void)drawShape {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(25.051779, 121.544111),
CLLocationCoordinate2DMake(25.057678, 121.548695),
CLLocationCoordinate2DMake(25.058218, 121.555254),
CLLocationCoordinate2DMake(25.054389, 121.556052),
CLLocationCoordinate2DMake(25.053385, 121.556656),
CLLocationCoordinate2DMake(25.051451, 121.557468),
CLLocationCoordinate2DMake(25.051779, 121.544111)
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
// Create our shape with the formatted coordinates array
MGLPolygon *shape = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates];
// Add the shape to the map
[mapView addAnnotation:shape];
}
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation {
return 0.5;
}
- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation {
return UIColor.whiteColor;
}
- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation {
return [UIColor colorWithRed:59.0/255.0 green:178.0/255.0 blue:208.0/255.0 alpha:1.0];
}
@end
繪製圓形圖層
- 範例說明:
- 給予指定中心點經緯度,畫出圓形圖層顯示在地圖上
Example
#import "AddCirclePolygon.h"
@import Map8;
@interface AddCirclePolygon () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
}
@end
@implementation AddCirclePolygon
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.037682, 121.548785)
zoomLevel:14
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.037682, 121.548785);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
[self drawCirclePolygon:CLLocationCoordinate2DMake(25.037682, 121.548785)];
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (void)drawCirclePolygon:(CLLocationCoordinate2D)center {
MGLPointFeature *feature = [[MGLPointFeature alloc] init];
feature.coordinate = center;
NSMutableArray *features = [NSMutableArray array];
[features addObject:feature];
MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"circles" features:features options:nil];
[mapView.style addSource:source];
MGLCircleStyleLayer *circleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"circle" source:source];
circleLayer.circleColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
circleLayer.circleRadius = [NSExpression expressionForConstantValue:@(100)];
circleLayer.circleStrokeWidth = [NSExpression expressionForConstantValue:@(2)];
circleLayer.circleStrokeColor = [NSExpression expressionForConstantValue:[UIColor blackColor]];
circleLayer.circleOpacity = [NSExpression expressionForConstantValue:@(0.3)];
[mapView.style addLayer:circleLayer];
}
@end
多個圓形圖層疊加
- 範例說明:
- 畫出複數個圓形圖層顯示在地圖上
- 圓形可依地圖俯瞰視角與縮放變動
Example
#import "AddMultiCirclesLayer.h"
@import Map8;
@interface AddMultiCirclesLayer () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
}
@end
@implementation AddMultiCirclesLayer
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.04624, 121.52415)
zoomLevel:14
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.04624, 121.52415);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
[self polygonCircleForCoordinate:CLLocationCoordinate2DMake(25.04624, 121.52415) andMeterRaius:500 andTitle:@"circle1"];
[self polygonCircleForCoordinate:CLLocationCoordinate2DMake(25.05003, 121.52942) andMeterRaius:300 andTitle:@"circle2"];
[self polygonCircleForCoordinate:CLLocationCoordinate2DMake(25.04996, 121.50792) andMeterRaius:1000 andTitle:@"circle3"];
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (void)polygonCircleForCoordinate:(CLLocationCoordinate2D)center andMeterRaius:(double)radius andTitle:(NSString*)title {
double degreesBetweenPoints = 8.0;
//45 sides
int numberOfPoints = floor(360.0 / degreesBetweenPoints);
double distRadians = radius / 6371000.0;
// earth radius in meters
double centerLatRadians = center.latitude * M_PI / 180;
double centerLonRadians = center.longitude * M_PI / 180;
CLLocationCoordinate2D coordinates[numberOfPoints];
//array to hold all the points
for (int i = 0; i < numberOfPoints; i++) {
double degrees = (double)i * degreesBetweenPoints;
double degreeRadians = degrees * M_PI / 180;
double pointLatRadians = asin(sin(centerLatRadians) * cos(distRadians) + cos(centerLatRadians) * sin(distRadians) * cos(degreeRadians));
double pointLonRadians = centerLonRadians + atan2(sin(degreeRadians) * sin(distRadians) * cos(centerLatRadians), cos(distRadians) - sin(centerLatRadians) * sin(pointLatRadians));
double pointLat = pointLatRadians * 180 / M_PI;
double pointLon = pointLonRadians * 180 / M_PI;
CLLocationCoordinate2D point = CLLocationCoordinate2DMake(pointLat, pointLon);
coordinates[i] = point;
}
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates];
polygon.title = title;
[mapView addAnnotation:polygon];
}
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation {
return 0.5;
}
- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation {
return UIColor.whiteColor;
}
- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation {
if ([annotation.title isEqualToString:@"circle2"]) {
return [UIColor colorWithRed:133.0/255.0 green:91.0/255.0 blue:50.0/255.0 alpha:1.0];
}
else if ([annotation.title isEqualToString:@"circle3"]) {
return [UIColor colorWithRed:208.0/255.0 green:16.0/255.0 blue:76.0/255.0 alpha:1.0];
}
else {
return [UIColor colorWithRed:59.0/255.0 green:178.0/255.0 blue:208.0/255.0 alpha:1.0];
}
}
@end
路徑線段動畫展示
- 範例說明:
- 載入自定義的 geojson 檔案
- 繪出路徑線段,並以動畫方式呈現
- 範例 geojson
Example
#import "LineAnimation.h"
@import Map8;
@interface LineAnimation () <MGLMapViewDelegate>
{
MGLCoordinateBounds maxBounds;
}
@property (nonatomic) MGLMapView *mapView;
@property (nonatomic, strong) NSMutableArray<CLLocation *> *locations;
@property (nonatomic) MGLShapeSource *polylineSource;
@property (nonatomic) NSInteger currentIndex;
@end
@implementation LineAnimation
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(25.037682, 121.548785)
zoomLevel:15
animated:NO];
[self.mapView setMaximumZoomLevel:19.9];
[self.view addSubview:self.mapView];
self.mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, northeast);
}
- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(25.037682, 121.548785);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
[self loadGeoJSON];
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (void)loadGeoJSON {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *path = [[NSBundle mainBundle] pathForResource:@"map" ofType:@"geojson"];
NSData *jsonData = [NSData dataWithContentsOfFile:path];
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
NSDictionary *geoJSON = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
NSArray *features = [geoJSON objectForKey:@"features"];
NSDictionary *feature = [features objectAtIndex:0];
NSArray *coordinates = [[feature objectForKey:@"geometry"] objectForKey:@"coordinates"];
self.locations = [NSMutableArray array];
for (NSArray<NSNumber *> *c in coordinates) {
[self.locations addObject:[[CLLocation alloc] initWithLatitude:[c[1] doubleValue] longitude:[c[0] doubleValue]]];
}
[self addPolylineToStyle:self.mapView.style];
[self animatePolyline];
});
});
}
- (void)addPolylineToStyle:(MGLStyle *)style {
// Add an empty MGLShapeSource, we’ll keep a reference to this and add points to this later.
MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"polyline" features:@[] options:nil];
[style addSource:source];
self.polylineSource = source;
// Add a layer to style our polyline.
MGLLineStyleLayer *layer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"polyline" source:source];
layer.lineJoin = [NSExpression expressionForConstantValue:@"round"];
layer.lineCap = layer.lineJoin = [NSExpression expressionForConstantValue:@"round"];
layer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]];
// The line width should gradually increase based on the zoom level.
layer.lineWidth = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@14: @5, @18: @20}];
[self.mapView.style addLayer:layer];
}
- (void)animatePolyline {
self.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.
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
}
- (void)tick:(NSTimer*)timer {
if (self.currentIndex > self.locations.count) {
[timer invalidate];
return;
}
// Create a subarray of locations up to the current index.
NSArray *currentLocations = [self.locations subarrayWithRange:NSMakeRange(0, _currentIndex)];
// Update our MGLShapeSource with the current locations.
[self updatePolylineWithLocations:currentLocations];
self.currentIndex++;
}
- (void)updatePolylineWithLocations:(NSArray<CLLocation *> *)locations {
CLLocationCoordinate2D coordinates[locations.count];
for (NSUInteger i = 0; i < locations.count; i++) {
coordinates[i] = locations[i].coordinate;
}
MGLPolylineFeature *polyline = [MGLPolylineFeature polylineWithCoordinates:coordinates count:locations.count];
// Updating the MGLShapeSource’s shape will have the map redraw our polyline with the current coordinates.
self.polylineSource.shape = polyline;
}
@end
建立群聚標示
- 範例說明:
- 載入自定義的 geojson 檔案
- 在地圖上建立群聚標示
- 範例 geojson
- 範例圖示
Example
#import "ShowCluster.h"
@import Map8;
@interface ShowCluster () <MGLMapViewDelegate, UIGestureRecognizerDelegate>
{
MGLCoordinateBounds maxBounds;
MGLMapView *mapView;
UIImage *icon;
UIView *popup;
}
@end
@implementation ShowCluster
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *styleURL = [[NSURL alloc] initWithString:@"https://api.map8.zone/styles/go-life-maps-tw-style-std/style.json"];
mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:styleURL];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Set the map’s center coordinate and zoom level.
[mapView setCenterCoordinate:CLLocationCoordinate2DMake(24.963355, 121.216776)
zoomLevel:16
animated:NO];
[mapView setMaximumZoomLevel:19.9];
[self.view addSubview:mapView];
mapView.delegate = self;
// Taiwan's max bounds
CLLocationCoordinate2D northeast = CLLocationCoordinate2DMake(33.4, 138.45858);
CLLocationCoordinate2D southwest = CLLocationCoordinate2DMake(15, 105);
maxBounds = MGLCoordinateBoundsMake(southwest, 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.
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapCluster:)];
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 (UIGestureRecognizer *recognizer in mapView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]] &&
((UITapGestureRecognizer*)recognizer).numberOfTapsRequired == 2) {
[recognizer requireGestureRecognizerToFail: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).
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)];
for (UIGestureRecognizer *recognizer in mapView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[singleTap requireGestureRecognizerToFail:recognizer];
}
}
[mapView addGestureRecognizer:singleTap];
icon = [UIImage imageNamed:@"port"];
}
- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {
MGLMapCamera *currentCamera = mapView.camera;
CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate;
mapView.camera = newCamera;
MGLCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds;
mapView.camera = currentCamera;
BOOL inside = MGLCoordinateInCoordinateBounds(newCameraCenter, maxBounds);
BOOL intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, maxBounds) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, maxBounds);
return inside && intersects;
}
- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
CLLocationCoordinate2D currentLocation = mapView.userLocation.location != nil ? mapView.userLocation.coordinate : CLLocationCoordinate2DMake(24.963355, 121.216776);
MGLMapCamera *cameraPosition = [MGLMapCamera cameraLookingAtCenterCoordinate:currentLocation altitude:mapView.camera.altitude pitch:50.0 heading:0];
mapView.camera = cameraPosition;
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"housesale" ofType:@"geojson"]];
MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"clusteredPorts"
URL:url options:@{
MGLShapeSourceOptionClustered: @(YES),
MGLShapeSourceOptionClusterRadius: @(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 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forName:@"icon"];
// Show unclustered features as icons. The `cluster` attribute is built into clustering-enabled
// source features.
MGLSymbolStyleLayer *ports = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"ports" source:source];
ports.iconImageName = [NSExpression expressionForConstantValue:@"icon"];
ports.iconAllowsOverlap = [NSExpression expressionForConstantValue:@(YES)];
ports.iconColor = [NSExpression expressionForConstantValue:[[UIColor darkGrayColor] colorWithAlphaComponent:0.9]];
ports.iconScale = [NSExpression expressionForConstantValue:@(0.5)];
ports.predicate = [NSPredicate predicateWithFormat:@"cluster != YES"];
[style addLayer:ports];
// Color clustered features based on clustered point counts.
NSDictionary *stops = @{ @20: [UIColor lightGrayColor],
@50: [UIColor orangeColor],
@100: [UIColor redColor],
@200: [UIColor purpleColor] };
NSDictionary *stops_radius = @{ @20: @(20),
@50: @(30),
@100: @(40),
@200: @(50) };
// Show clustered features as circles. The `point_count` attribute is built into
// clustering-enabled source features.
MGLCircleStyleLayer *circlesLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"clusteredPorts" source:source];
circlesLayer.circleRadius = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(point_count, %@, %@)", @(20), stops_radius];
circlesLayer.circleOpacity = [NSExpression expressionForConstantValue:@0.75];
circlesLayer.circleStrokeColor = [NSExpression expressionForConstantValue:[[UIColor whiteColor] colorWithAlphaComponent:0.75]];
circlesLayer.circleStrokeWidth = [NSExpression expressionForConstantValue:@2];
circlesLayer.circleColor = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(point_count, %@, %@)",
[UIColor lightGrayColor], stops];
circlesLayer.predicate = [NSPredicate predicateWithFormat:@"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.
MGLSymbolStyleLayer *numbersLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"clusteredPortsNumbers" source:source];
numbersLayer.textColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]];
numbersLayer.textFontSize = [NSExpression expressionForConstantValue:@(icon.size.width / 2)];
numbersLayer.iconAllowsOverlap = [NSExpression expressionForConstantValue:@(YES)];
numbersLayer.text = [NSExpression expressionWithFormat:@"CAST(point_count, 'NSString')"];
// *** IMPORTANT, 必須要指定字型,Cluster 才能運作
numbersLayer.textFontNames = [NSExpression expressionForConstantValue:@[@"Noto Sans Regular"]];
numbersLayer.predicate = [NSPredicate predicateWithFormat:@"cluster == YES"];
[style addLayer:numbersLayer];
}
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView {
[self showPopup:NO animated:NO];
}
- (MGLPointFeatureCluster *)firstClusterWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
CGPoint point = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat width = icon.size.width;
CGRect rect = CGRectMake(point.x - width / 2, point.y - width / 2, width, 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.
NSArray<id<MGLFeature>> *features = [mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObjects:@"clusteredPorts", @"ports", nil]];
NSPredicate *clusterPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [evaluatedObject isKindOfClass:[MGLPointFeatureCluster class]];
}];
NSArray *clusters = [features filteredArrayUsingPredicate:clusterPredicate];
// Pick the first cluster, ideally selecting the one nearest nearest one to
// the touch point.
return (MGLPointFeatureCluster *)clusters.firstObject;
}
- (IBAction)handleDoubleTapCluster:(UITapGestureRecognizer *)sender {
MGLSource *source = [mapView.style sourceWithIdentifier:@"clusteredPorts"];
if (![source isKindOfClass:[MGLShapeSource class]]) {
return;
}
if (sender.state != UIGestureRecognizerStateEnded) {
return;
}
[self showPopup:NO animated:NO];
MGLPointFeatureCluster *cluster = [self firstClusterWithGestureRecognizer:sender];
if (!cluster) {
return;
}
double zoom = [(MGLShapeSource *)source zoomLevelForExpandingCluster:cluster];
if (zoom > 0.0) {
[mapView setCenterCoordinate:cluster.coordinate
zoomLevel:zoom
animated:YES];
}
}
- (IBAction)handleMapTap:(UITapGestureRecognizer *)tap {
MGLSource *source = [mapView.style sourceWithIdentifier:@"clusteredPorts"];
if (![source isKindOfClass:[MGLShapeSource class]]) {
return;
}
if (tap.state != UIGestureRecognizerStateEnded) {
return;
}
[self showPopup:NO animated:NO];
CGPoint point = [tap locationInView:tap.view];
CGFloat width = icon.size.width;
CGRect rect = CGRectMake(point.x - width / 2, point.y - width / 2, width, width);
NSArray<id<MGLFeature>> *features = [mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObjects:@"clusteredPorts", @"ports", nil]];
// Pick the first feature (which may be a port or a cluster), ideally selecting
// the one nearest nearest one to the touch point.
id<MGLFeature> feature = features.firstObject;
if (!feature) {
return;
}
NSString *description = @"No port name";
UIColor *color = UIColor.redColor;
if ([feature isKindOfClass:[MGLPointFeatureCluster class]]) {
// Tapped on a cluster.
MGLPointFeatureCluster *cluster = (MGLPointFeatureCluster *)feature;
NSArray *children = [(MGLShapeSource*)source childrenOfCluster:cluster];
description = [NSString stringWithFormat:@"Cluster #%zd\n%zd children",
cluster.clusterIdentifier,
children.count];
color = UIColor.blueColor;
} else {
// Tapped on a port.
id name = [feature attributeForKey:@"name"];
if ([name isKindOfClass:[NSString class]]) {
description = (NSString *)name;
color = UIColor.blackColor;
}
}
popup = [self popupAtCoordinate:feature.coordinate
withDescription:description
textColor:color];
[self showPopup:YES animated:YES];
}
- (UIView *)popupAtCoordinate:(CLLocationCoordinate2D)coordinate withDescription:(NSString *)description textColor:(UIColor *)textColor {
UILabel *popup = [[UILabel alloc] init];
popup.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
popup.layer.cornerRadius = 4;
popup.layer.masksToBounds = YES;
popup.textAlignment = NSTextAlignmentCenter;
popup.lineBreakMode = NSLineBreakByTruncatingTail;
popup.numberOfLines = 0;
popup.font = [UIFont systemFontOfSize:16];
popup.textColor = textColor;
popup.alpha = 0;
popup.text = description;
[popup sizeToFit];
// Expand the popup.
popup.bounds = CGRectInset(popup.bounds, -10, -10);
CGPoint point = [mapView convertCoordinate:coordinate toPointToView:mapView];
popup.center = CGPointMake(point.x, point.y - 50);
return popup;
}
- (void)showPopup:(BOOL)shouldShow animated:(BOOL)animated {
if (!popup) {
return;
}
UIView *thePopup = popup;
if (shouldShow) {
[self.view addSubview:thePopup];
}
CGFloat alpha = (shouldShow ? 1 : 0);
dispatch_block_t animation = ^{
thePopup.alpha = alpha;
};
void (^completion)(BOOL) = ^(BOOL finished){
if (!shouldShow) {
[thePopup removeFromSuperview];
}
};
if (animated) {
[UIView animateWithDuration:0.25
animations:animation
completion:completion];
} else {
animation();
completion(YES);
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// This will only get called for the custom double tap gesture,
// that should always be recognized simultaneously.
return YES;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// This will only get called for the custom double tap gesture.
return [self firstClusterWithGestureRecognizer:gestureRecognizer] != nil;
}
@end
台灣圖霸感謝您的支持與愛護!
有任何疑問,或是指教,都非常歡迎您找我們詢問。
非常感謝!
https://map8.zone