NAV Navbar

台灣圖霸 | Map8 Platform

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

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

初始化地圖

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

Simple map view

攝影機視角移動地圖

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

Camera animation

拖曳地圖標記

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

Drag 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

Custom marker

限制地圖可滑區域

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

Restrict area

繪製多邊形圖層

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

Polygon

繪製圓形圖層

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

Draw circle

多個圓形圖層疊加

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

Multi circle layer

路徑線段動畫展示

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

Line animate

建立群聚標示

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

Cluster


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

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

非常感謝!



https://map8.zone