#import "MBXViewController.h" #import "MBXAppDelegate.h" #import "MBXCustomCalloutView.h" #import "MBXOfflinePacksTableViewController.h" #import "MBXAnnotationView.h" #import "MBXUserLocationAnnotationView.h" #import "MBXEmbeddedMapViewController.h" #import #import static const CLLocationCoordinate2D WorldTourDestinations[] = { { .latitude = 38.9131982, .longitude = -77.0325453144239 }, { .latitude = 37.7757368, .longitude = -122.4135302 }, { .latitude = 12.9810816, .longitude = 77.6368034 }, { .latitude = -13.15589555, .longitude = -74.2178961777998 }, }; static NSString * const MBXViewControllerAnnotationViewReuseIdentifer = @"MBXViewControllerAnnotationViewReuseIdentifer"; typedef NS_ENUM(NSInteger, MBXSettingsSections) { MBXSettingsCoreRendering = 0, MBXSettingsAnnotations, MBXSettingsRuntimeStyling, MBXSettingsMiscellaneous, }; typedef NS_ENUM(NSInteger, MBXSettingsCoreRenderingRows) { MBXSettingsCoreRenderingResetPosition = 0, MBXSettingsCoreRenderingTileBoundaries, MBXSettingsCoreRenderingTileInfo, MBXSettingsCoreRenderingTimestamps, MBXSettingsCoreRenderingCollisionBoxes, MBXSettingsCoreRenderingOverdrawVisualization, MBXSettingsCoreRenderingToggleTwoMaps, }; typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { MBXSettingsAnnotations100Views = 0, MBXSettingsAnnotations1000Views, MBXSettingsAnnotations10000Views, MBXSettingsAnnotations100Sprites, MBXSettingsAnnotations1000Sprites, MBXSettingsAnnotations10000Sprites, MBXSettingsAnnotationAnimation, MBXSettingsAnnotationsTestShapes, MBXSettingsAnnotationsCustomCallout, MBXSettingsAnnotationsQueryAnnotations, MBXSettingsAnnotationsRemoveAnnotations, }; typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingBuildingExtrusions = 0, MBXSettingsRuntimeStylingWater, MBXSettingsRuntimeStylingRoads, MBXSettingsRuntimeStylingRaster, MBXSettingsRuntimeStylingShape, MBXSettingsRuntimeStylingSymbols, MBXSettingsRuntimeStylingBuildings, MBXSettingsRuntimeStylingFerry, MBXSettingsRuntimeStylingParks, MBXSettingsRuntimeStylingFilteredFill, MBXSettingsRuntimeStylingFilteredLines, MBXSettingsRuntimeStylingNumericFilteredFill, MBXSettingsRuntimeStylingStyleQuery, MBXSettingsRuntimeStylingFeatureSource, MBXSettingsRuntimeStylingPointCollection, MBXSettingsRuntimeStylingUpdateShapeSourceData, MBXSettingsRuntimeStylingUpdateShapeSourceURL, MBXSettingsRuntimeStylingUpdateShapeSourceFeatures, MBXSettingsRuntimeStylingVectorSource, MBXSettingsRuntimeStylingRasterSource, MBXSettingsRuntimeStylingImageSource, MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { MBXSettingsMiscellaneousShowReuseQueueStats = 0, MBXSettingsMiscellaneousWorldTour, MBXSettingsMiscellaneousCustomUserDot, MBXSettingsMiscellaneousShowZoomLevel, MBXSettingsMiscellaneousScrollView, MBXSettingsMiscellaneousPrintLogFile, MBXSettingsMiscellaneousDeleteLogFile, }; @interface MBXDroppedPinAnnotation : MGLPointAnnotation @end @implementation MBXDroppedPinAnnotation @end @interface MBXCustomCalloutAnnotation : MGLPointAnnotation @property (nonatomic, assign) BOOL anchoredToAnnotation; @property (nonatomic, assign) BOOL dismissesAutomatically; @end @implementation MBXCustomCalloutAnnotation @end @interface MBXSpriteBackedAnnotation : MGLPointAnnotation @end @implementation MBXSpriteBackedAnnotation @end @interface MBXViewController () @property (nonatomic) IBOutlet MGLMapView *mapView; @property (weak, nonatomic) IBOutlet UILabel *hudLabel; @property (nonatomic) NSInteger styleIndex; @property (nonatomic) BOOL debugLoggingEnabled; @property (nonatomic) BOOL customUserLocationAnnnotationEnabled; @property (nonatomic) BOOL usingLocaleBasedCountryLabels; @property (nonatomic) BOOL reuseQueueStatsEnabled; @property (nonatomic) BOOL showZoomLevelEnabled; @end @interface MGLMapView (MBXViewController) @property (nonatomic) BOOL usingLocaleBasedCountryLabels; @property (nonatomic) NSDictionary *annotationViewReuseQueueByIdentifier; @end @implementation MBXViewController { BOOL _isTouringWorld; } #pragma mark - Setup & Teardown + (void)initialize { if (self == [MBXViewController class]) { [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"MBXUserTrackingMode": @(MGLUserTrackingModeNone), @"MBXShowsUserLocation": @NO, @"MBXDebug": @NO, }]; } } - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil]; [self restoreState:nil]; self.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]; self.mapView.scaleBar.hidden = NO; self.hudLabel.hidden = YES; if ([MGLAccountManager accessToken].length) { self.styleIndex = -1; [self cycleStyles:self]; } else { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Access Token" message:@"Enter your Mapbox access token to load Mapbox-hosted tiles and styles:" preferredStyle:UIAlertControllerStyleAlert]; [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { textField.keyboardType = UIKeyboardTypeURL; textField.autocorrectionType = UITextAutocorrectionTypeNo; textField.autocapitalizationType = UITextAutocapitalizationTypeNone; }]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; UIAlertAction *OKAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { UITextField *textField = alertController.textFields.firstObject; NSString *accessToken = textField.text; [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:MBXMapboxAccessTokenDefaultsKey]; [MGLAccountManager setAccessToken:accessToken]; self.styleIndex = -1; [self cycleStyles:self]; [self.mapView reloadStyle:self]; }]; [alertController addAction:OKAction]; if ([alertController respondsToSelector:@selector(setPreferredAction:)]) { alertController.preferredAction = OKAction; } [self presentViewController:alertController animated:YES completion:nil]; } } - (void)saveState:(__unused NSNotification *)notification { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSData *archivedCamera = [NSKeyedArchiver archivedDataWithRootObject:self.mapView.camera]; [defaults setObject:archivedCamera forKey:@"MBXCamera"]; [defaults setInteger:self.mapView.userTrackingMode forKey:@"MBXUserTrackingMode"]; [defaults setBool:self.mapView.showsUserLocation forKey:@"MBXShowsUserLocation"]; [defaults setInteger:self.mapView.debugMask forKey:@"MBXDebugMask"]; [defaults synchronize]; } - (void)restoreState:(__unused NSNotification *)notification { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSData *archivedCamera = [defaults objectForKey:@"MBXCamera"]; MGLMapCamera *camera = archivedCamera ? [NSKeyedUnarchiver unarchiveObjectWithData:archivedCamera] : nil; if (camera) { self.mapView.camera = camera; } NSInteger uncheckedTrackingMode = [defaults integerForKey:@"MBXUserTrackingMode"]; if (uncheckedTrackingMode >= 0 && (NSUInteger)uncheckedTrackingMode >= MGLUserTrackingModeNone && (NSUInteger)uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse) { self.mapView.userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode; } self.mapView.showsUserLocation = [defaults boolForKey:@"MBXShowsUserLocation"]; NSInteger uncheckedDebugMask = [defaults integerForKey:@"MBXDebugMask"]; if (uncheckedDebugMask >= 0) { self.mapView.debugMask = (MGLMapDebugMaskOptions)uncheckedDebugMask; } } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(__unused id)sender { if ([segue.identifier isEqualToString:@"ShowOfflinePacks"]) { MBXOfflinePacksTableViewController *controller = [segue destinationViewController]; controller.mapView = self.mapView; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self saveState:nil]; } #pragma mark - Debugging Interface - (IBAction)showSettings:(__unused id)sender { UITableViewController *settingsViewController = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped]; settingsViewController.tableView.delegate = self; settingsViewController.tableView.dataSource = self; settingsViewController.title = @"Debugging"; settingsViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissSettings:)]; UINavigationController *wrapper = [[UINavigationController alloc] initWithRootViewController:settingsViewController]; wrapper.navigationBar.tintColor = self.navigationController.navigationBar.tintColor; [self.navigationController presentViewController:wrapper animated:YES completion:nil]; } - (void)dismissSettings:(__unused id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } - (NSArray *)settingsSectionTitles { return @[ @"Core Rendering", @"Annotations", @"Runtime Styling", @"Miscellaneous" ]; } - (NSArray *)settingsTitlesForSection:(NSInteger)section { NSMutableArray *settingsTitles = [NSMutableArray array]; MGLMapDebugMaskOptions debugMask = self.mapView.debugMask; switch (section) { case MBXSettingsCoreRendering: [settingsTitles addObjectsFromArray:@[ @"Reset Position", [NSString stringWithFormat:@"%@ Tile Boundaries", (debugMask & MGLMapDebugTileBoundariesMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Tile Info", (debugMask & MGLMapDebugTileInfoMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Tile Timestamps", (debugMask & MGLMapDebugTimestampsMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Collision Boxes", (debugMask & MGLMapDebugCollisionBoxesMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Overdraw Visualization", (debugMask & MGLMapDebugOverdrawVisualizationMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], ]]; break; case MBXSettingsAnnotations: [settingsTitles addObjectsFromArray:@[ @"Add 100 Views", @"Add 1,000 Views", @"Add 10,000 Views", @"Add 100 Sprites", @"Add 1,000 Sprites", @"Add 10,000 Sprites", @"Animate an Annotation View", @"Add Test Shapes", @"Add Point With Custom Callout", @"Query Annotations", @"Remove Annotations", ]]; break; case MBXSettingsRuntimeStyling: [settingsTitles addObjectsFromArray:@[ @"Add Building Extrusions", @"Style Water With Function", @"Style Roads With Function", @"Add Raster & Apply Function", @"Add Shapes & Apply Fill", @"Style Symbol Color", @"Style Building Fill Color", @"Style Ferry Line Color", @"Remove Parks", @"Style Fill With Filter", @"Style Lines With Filter", @"Style Fill With Numeric Filter", @"Query and Style Features", @"Style Feature", @"Style Dynamic Point Collection", @"Update Shape Source: Data", @"Update Shape Source: URL", @"Update Shape Source: Features", @"Style Vector Source", @"Style Raster Source", @"Style Image Source", [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", ]]; break; case MBXSettingsMiscellaneous: [settingsTitles addObjectsFromArray:@[ [NSString stringWithFormat:@"%@ Reuse Queue Stats", (_reuseQueueStatsEnabled ? @"Hide" :@"Show")], @"Start World Tour", [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], [NSString stringWithFormat:@"%@ Zoom Level", (_showZoomLevelEnabled ? @"Hide" :@"Show")], @"Embedded Map View", ]]; if (self.debugLoggingEnabled) { [settingsTitles addObjectsFromArray:@[ @"Print Telemetry Logfile", @"Delete Telemetry Logfile", ]]; }; break; default: NSAssert(NO, @"All settings sections should be implemented"); break; } return settingsTitles; } - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath { switch (indexPath.section) { case MBXSettingsCoreRendering: switch (indexPath.row) { case MBXSettingsCoreRenderingResetPosition: [self.mapView resetPosition]; break; case MBXSettingsCoreRenderingTileBoundaries: self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask; break; case MBXSettingsCoreRenderingTileInfo: self.mapView.debugMask ^= MGLMapDebugTileInfoMask; break; case MBXSettingsCoreRenderingTimestamps: self.mapView.debugMask ^= MGLMapDebugTimestampsMask; break; case MBXSettingsCoreRenderingCollisionBoxes: self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask; break; case MBXSettingsCoreRenderingOverdrawVisualization: self.mapView.debugMask ^= MGLMapDebugOverdrawVisualizationMask; break; case MBXSettingsCoreRenderingToggleTwoMaps: if ([self.view viewWithTag:2] == nil) { MGLMapView *secondMapView = [[MGLMapView alloc] initWithFrame: CGRectMake(0, self.view.bounds.size.height / 2, self.view.bounds.size.width, self.view.bounds.size.height / 2)]; secondMapView.translatesAutoresizingMaskIntoConstraints = false; secondMapView.tag = 2; for (NSLayoutConstraint *constraint in self.view.constraints) { if ((constraint.firstItem == self.mapView && constraint.firstAttribute == NSLayoutAttributeBottom) || (constraint.secondItem == self.mapView && constraint.secondAttribute == NSLayoutAttributeBottom)) { [self.view removeConstraint:constraint]; break; } } [self.view addSubview:secondMapView]; [self.view addConstraints:@[ [NSLayoutConstraint constraintWithItem:self.mapView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0], [NSLayoutConstraint constraintWithItem:secondMapView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0], [NSLayoutConstraint constraintWithItem:secondMapView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0], [NSLayoutConstraint constraintWithItem:secondMapView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0], [NSLayoutConstraint constraintWithItem:secondMapView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.bottomLayoutGuide attribute:NSLayoutAttributeTop multiplier:1 constant:0], ]]; } else { NSMutableArray *constraintsToRemove = [NSMutableArray array]; MGLMapView *secondMapView = (MGLMapView *)[self.view viewWithTag:2]; for (NSLayoutConstraint *constraint in self.view.constraints) { if (constraint.firstItem == secondMapView || constraint.secondItem == secondMapView) { [constraintsToRemove addObject:constraint]; } } [self.view removeConstraints:constraintsToRemove]; [secondMapView removeFromSuperview]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.bottomLayoutGuide attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; } break; default: NSAssert(NO, @"All core rendering setting rows should be implemented"); break; } break; case MBXSettingsAnnotations: switch (indexPath.row) { case MBXSettingsAnnotations100Views: [self parseFeaturesAddingCount:100 usingViews:YES]; break; case MBXSettingsAnnotations1000Views: [self parseFeaturesAddingCount:1000 usingViews:YES]; break; case MBXSettingsAnnotations10000Views: [self parseFeaturesAddingCount:10000 usingViews:YES]; break; case MBXSettingsAnnotations100Sprites: [self parseFeaturesAddingCount:100 usingViews:NO]; break; case MBXSettingsAnnotations1000Sprites: [self parseFeaturesAddingCount:1000 usingViews:NO]; break; case MBXSettingsAnnotations10000Sprites: [self parseFeaturesAddingCount:10000 usingViews:NO]; break; case MBXSettingsAnnotationAnimation: [self animateAnnotationView]; break; case MBXSettingsAnnotationsTestShapes: [self addTestShapes]; break; case MBXSettingsAnnotationsCustomCallout: [self addAnnotationWithCustomCallout]; break; case MBXSettingsAnnotationsQueryAnnotations: [self testQueryPointAnnotations]; break; case MBXSettingsAnnotationsRemoveAnnotations: [self.mapView removeAnnotations:self.mapView.annotations]; break; default: NSAssert(NO, @"All annotations setting rows should be implemented"); break; } break; case MBXSettingsRuntimeStyling: switch (indexPath.row) { case MBXSettingsRuntimeStylingBuildingExtrusions: [self styleBuildingExtrusions]; break; case MBXSettingsRuntimeStylingWater: [self styleWaterLayer]; break; case MBXSettingsRuntimeStylingRoads: [self styleRoadLayer]; break; case MBXSettingsRuntimeStylingRaster: [self styleRasterLayer]; break; case MBXSettingsRuntimeStylingShape: [self styleShapeSource]; break; case MBXSettingsRuntimeStylingSymbols: [self styleSymbolLayer]; break; case MBXSettingsRuntimeStylingBuildings: [self styleBuildingLayer]; break; case MBXSettingsRuntimeStylingFerry: [self styleFerryLayer]; break; case MBXSettingsRuntimeStylingParks: [self removeParkLayer]; break; case MBXSettingsRuntimeStylingFilteredFill: [self styleFilteredFill]; break; case MBXSettingsRuntimeStylingFilteredLines: [self styleFilteredLines]; break; case MBXSettingsRuntimeStylingNumericFilteredFill: [self styleNumericFilteredFills]; break; case MBXSettingsRuntimeStylingStyleQuery: [self styleQuery]; break; case MBXSettingsRuntimeStylingFeatureSource: [self styleFeature]; break; case MBXSettingsRuntimeStylingPointCollection: [self styleDynamicPointCollection]; break; case MBXSettingsRuntimeStylingUpdateShapeSourceURL: [self updateShapeSourceURL]; break; case MBXSettingsRuntimeStylingUpdateShapeSourceData: [self updateShapeSourceData]; break; case MBXSettingsRuntimeStylingUpdateShapeSourceFeatures: [self updateShapeSourceFeatures]; break; case MBXSettingsRuntimeStylingVectorSource: [self styleVectorSource]; break; case MBXSettingsRuntimeStylingRasterSource: [self styleRasterSource]; break; case MBXSettingsRuntimeStylingImageSource: [self styleImageSource]; break; case MBXSettingsRuntimeStylingCountryLabels: [self styleCountryLabelsLanguage]; break; case MBXSettingsRuntimeStylingRouteLine: [self styleRouteLine]; break; case MBXSettingsRuntimeStylingDDSPolygon: [self stylePolygonWithDDS]; break; default: NSAssert(NO, @"All runtime styling setting rows should be implemented"); break; } break; case MBXSettingsMiscellaneous: switch (indexPath.row) { case MBXSettingsMiscellaneousWorldTour: [self startWorldTour]; break; case MBXSettingsMiscellaneousCustomUserDot: [self toggleCustomUserDot]; break; case MBXSettingsMiscellaneousPrintLogFile: [self printTelemetryLogFile]; break; case MBXSettingsMiscellaneousDeleteLogFile: [self deleteTelemetryLogFile]; break; case MBXSettingsMiscellaneousShowReuseQueueStats: { self.reuseQueueStatsEnabled = !self.reuseQueueStatsEnabled; self.hudLabel.hidden = !self.reuseQueueStatsEnabled; self.showZoomLevelEnabled = NO; break; } case MBXSettingsMiscellaneousShowZoomLevel: { self.showZoomLevelEnabled = !self.showZoomLevelEnabled; self.hudLabel.hidden = !self.showZoomLevelEnabled; self.reuseQueueStatsEnabled = NO; break; } case MBXSettingsMiscellaneousScrollView: { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; MBXEmbeddedMapViewController *embeddedMapViewController = (MBXEmbeddedMapViewController *)[storyboard instantiateViewControllerWithIdentifier:@"MBXEmbeddedMapViewController"]; [self.navigationController pushViewController:embeddedMapViewController animated:YES]; break; } default: NSAssert(NO, @"All miscellaneous setting rows should be implemented"); break; } break; default: NSAssert(NO, @"All settings sections should be implemented"); break; } } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[self settingsSectionTitles] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[self settingsTitlesForSection:section] count]; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; { return [[self settingsSectionTitles] objectAtIndex:section]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; cell.textLabel.text = [[self settingsTitlesForSection:indexPath.section] objectAtIndex:indexPath.row]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; [self dismissViewControllerAnimated:YES completion:^ { [self performActionForSettingAtIndexPath:indexPath]; }]; } #pragma mark - Debugging Actions - (void)parseFeaturesAddingCount:(NSUInteger)featuresCount usingViews:(BOOL)useViews { [self.mapView removeAnnotations:self.mapView.annotations]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]]; id features = [NSJSONSerialization JSONObjectWithData:featuresData options:0 error:nil]; if ([features isKindOfClass:[NSDictionary class]]) { NSMutableArray *annotations = [NSMutableArray array]; for (NSDictionary *feature in features[@"features"]) { CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([feature[@"geometry"][@"coordinates"][1] doubleValue], [feature[@"geometry"][@"coordinates"][0] doubleValue]); NSString *title = feature[@"properties"][@"NAME"]; MGLPointAnnotation *annotation = (useViews ? [MGLPointAnnotation new] : [MBXSpriteBackedAnnotation new]); annotation.coordinate = coordinate; annotation.title = title; [annotations addObject:annotation]; if (annotations.count == featuresCount) break; } dispatch_async(dispatch_get_main_queue(), ^ { [self.mapView addAnnotations:annotations]; [self.mapView showAnnotations:annotations animated:YES]; }); } }); } - (void)animateAnnotationView { MGLPointAnnotation *annot = [[MGLPointAnnotation alloc] init]; annot.coordinate = self.mapView.centerCoordinate; [self.mapView addAnnotation:annot]; // Move the annotation to a point that is offscreen. CGPoint point = CGPointMake(self.view.frame.origin.x - 200, CGRectGetMidY(self.view.frame)); CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.view]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:10 animations:^{ annot.coordinate = coord; }]; }); }; - (void)addTestShapes { // Pacific Northwest triangle // CLLocationCoordinate2D triangleCoordinates[3] = { CLLocationCoordinate2DMake(44, -122), CLLocationCoordinate2DMake(46, -122), CLLocationCoordinate2DMake(46, -121) }; MGLPolygon *triangle = [MGLPolygon polygonWithCoordinates:triangleCoordinates count:3]; [self.mapView addAnnotation:triangle]; // West coast polyline // CLLocationCoordinate2D lineCoordinates[4] = { CLLocationCoordinate2DMake(47.6025, -122.3327), CLLocationCoordinate2DMake(45.5189, -122.6726), CLLocationCoordinate2DMake(37.7790, -122.4177), CLLocationCoordinate2DMake(34.0532, -118.2349) }; MGLPolyline *line = [MGLPolyline polylineWithCoordinates:lineCoordinates count:4]; [self.mapView addAnnotation:line]; // Orcas Island, WA hike polyline // NSDictionary *hike = [NSJSONSerialization JSONObjectWithData: [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]] options:0 error:nil]; NSArray *hikeCoordinatePairs = hike[@"features"][0][@"geometry"][@"coordinates"]; CLLocationCoordinate2D *polylineCoordinates = (CLLocationCoordinate2D *)malloc([hikeCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); for (NSUInteger i = 0; i < [hikeCoordinatePairs count]; i++) { polylineCoordinates[i] = CLLocationCoordinate2DMake([hikeCoordinatePairs[i][1] doubleValue], [hikeCoordinatePairs[i][0] doubleValue]); } MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:polylineCoordinates count:[hikeCoordinatePairs count]]; [self.mapView addAnnotation:polyline]; free(polylineCoordinates); // PA/NJ/DE polygons // NSDictionary *threestates = [NSJSONSerialization JSONObjectWithData: [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"threestates" ofType:@"geojson"]] options:0 error:nil]; for (NSDictionary *feature in threestates[@"features"]) { NSArray *stateCoordinatePairs = feature[@"geometry"][@"coordinates"]; while ([stateCoordinatePairs count] == 1) stateCoordinatePairs = stateCoordinatePairs[0]; CLLocationCoordinate2D *polygonCoordinates = (CLLocationCoordinate2D *)malloc([stateCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); for (NSUInteger i = 0; i < [stateCoordinatePairs count]; i++) { polygonCoordinates[i] = CLLocationCoordinate2DMake([stateCoordinatePairs[i][1] doubleValue], [stateCoordinatePairs[i][0] doubleValue]); } MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]]; [self.mapView addAnnotation:polygon]; free(polygonCoordinates); } // Null Island polygon with an interior hole // CLLocationCoordinate2D innerCoordinates[] = { CLLocationCoordinate2DMake(-5, -5), CLLocationCoordinate2DMake(-5, 5), CLLocationCoordinate2DMake(5, 5), CLLocationCoordinate2DMake(5, -5), }; MGLPolygon *innerPolygon = [MGLPolygon polygonWithCoordinates:innerCoordinates count:sizeof(innerCoordinates) / sizeof(innerCoordinates[0])]; CLLocationCoordinate2D outerCoordinates[] = { CLLocationCoordinate2DMake(-10, -10), CLLocationCoordinate2DMake(-10, 10), CLLocationCoordinate2DMake(10, 10), CLLocationCoordinate2DMake(10, -10), }; MGLPolygon *outerPolygon = [MGLPolygon polygonWithCoordinates:outerCoordinates count:sizeof(outerCoordinates) / sizeof(outerCoordinates[0]) interiorPolygons:@[innerPolygon]]; [self.mapView addAnnotation:outerPolygon]; } - (void)addAnnotationWithCustomCallout { [self.mapView removeAnnotations:self.mapView.annotations]; MBXCustomCalloutAnnotation *firstAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; firstAnnotation.coordinate = CLLocationCoordinate2DMake(48.8533940, 2.3775439); firstAnnotation.title = @"Open anchored to annotation"; firstAnnotation.anchoredToAnnotation = YES; firstAnnotation.dismissesAutomatically = NO; MBXCustomCalloutAnnotation *secondAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; secondAnnotation.coordinate = CLLocationCoordinate2DMake(48.8543940, 2.3775439); secondAnnotation.title = @"Open not anchored to annotation"; secondAnnotation.anchoredToAnnotation = NO; secondAnnotation.dismissesAutomatically = NO; MBXCustomCalloutAnnotation *thirdAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; thirdAnnotation.coordinate = CLLocationCoordinate2DMake(48.8553940, 2.3775439); thirdAnnotation.title = @"Dismisses automatically"; thirdAnnotation.anchoredToAnnotation = YES; thirdAnnotation.dismissesAutomatically = YES; NSArray *annotations = @[firstAnnotation, secondAnnotation, thirdAnnotation]; [self.mapView addAnnotations:annotations]; [self.mapView showAnnotations:annotations animated:YES]; } - (void)styleBuildingExtrusions { MGLSource* source = [self.mapView.style sourceWithIdentifier:@"composite"]; if (source) { MGLFillExtrusionStyleLayer* layer = [[MGLFillExtrusionStyleLayer alloc] initWithIdentifier:@"extrudedBuildings" source:source]; layer.sourceLayerIdentifier = @"building"; layer.predicate = [NSPredicate predicateWithFormat:@"extrude == 'true' AND height > 0"]; layer.fillExtrusionBase = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"min_height" options:nil]; layer.fillExtrusionHeight = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"height" options:nil]; // Set the fill color to that of the existing building footprint layer, if it exists. MGLFillStyleLayer* buildingLayer = (MGLFillStyleLayer*)[self.mapView.style layerWithIdentifier:@"building"]; if (buildingLayer) { if (buildingLayer.fillColor) { layer.fillExtrusionColor = buildingLayer.fillColor; } else { layer.fillExtrusionColor = [MGLStyleValue valueWithRawValue:[UIColor whiteColor]]; } layer.fillExtrusionOpacity = [MGLStyleValue valueWithRawValue:@0.75]; } MGLStyleLayer* labelLayer = [self.mapView.style layerWithIdentifier:@"waterway-label"]; if (labelLayer) { [self.mapView.style insertLayer:layer belowLayer:labelLayer]; } else { [self.mapView.style addLayer:layer]; } } } - (void)styleWaterLayer { MGLFillStyleLayer *waterLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"water"]; NSDictionary *waterColorStops = @{@6.0f: [MGLStyleValue valueWithRawValue:[UIColor yellowColor]], @8.0f: [MGLStyleValue valueWithRawValue:[UIColor blueColor]], @10.0f: [MGLStyleValue valueWithRawValue:[UIColor redColor]], @12.0f: [MGLStyleValue valueWithRawValue:[UIColor greenColor]], @14.0f: [MGLStyleValue valueWithRawValue:[UIColor blueColor]]}; MGLStyleValue *waterColorFunction = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:waterColorStops options: nil]; waterLayer.fillColor = waterColorFunction; NSDictionary *fillAntialiasedStops = @{@11: [MGLStyleValue valueWithRawValue:@YES], @12: [MGLStyleValue valueWithRawValue:@NO], @13: [MGLStyleValue valueWithRawValue:@YES], @14: [MGLStyleValue valueWithRawValue:@NO], @15: [MGLStyleValue valueWithRawValue:@YES]}; MGLStyleValue *fillAntialiasedFunction = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:fillAntialiasedStops options:nil]; waterLayer.fillAntialiased = fillAntialiasedFunction; } - (void)styleRoadLayer { MGLLineStyleLayer *roadLayer = (MGLLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"road-primary"]; roadLayer.lineColor = [MGLStyleValue valueWithRawValue:[UIColor blackColor]]; NSDictionary *lineWidthStops = @{@5: [MGLStyleValue valueWithRawValue:@5], @10: [MGLStyleValue valueWithRawValue:@15], @15: [MGLStyleValue valueWithRawValue:@30]}; MGLStyleValue *lineWidthFunction = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:lineWidthStops options:nil]; roadLayer.lineWidth = lineWidthFunction; roadLayer.lineGapWidth = lineWidthFunction; NSDictionary *roadLineColorStops = @{@10: [MGLStyleValue valueWithRawValue:[UIColor purpleColor]], @13: [MGLStyleValue valueWithRawValue:[UIColor yellowColor]], @16: [MGLStyleValue valueWithRawValue:[UIColor cyanColor]]}; MGLStyleValue *roadLineColor = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:roadLineColorStops options: nil]; roadLayer.lineColor = roadLineColor; roadLayer.visible = YES; roadLayer.maximumZoomLevel = 15; roadLayer.minimumZoomLevel = 13; } - (void)styleRasterLayer { NSURL *rasterURL = [NSURL URLWithString:@"mapbox://mapbox.satellite"]; MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"my-raster-source" configurationURL:rasterURL tileSize:512]; [self.mapView.style addSource:rasterSource]; MGLRasterStyleLayer *rasterLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"my-raster-layer" source:rasterSource]; MGLStyleValue *opacityFunction = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{@20.0f: [MGLStyleValue valueWithRawValue:@1.0f], @5.0f: [MGLStyleValue valueWithRawValue:@0.0f]} options:nil]; rasterLayer.rasterOpacity = opacityFunction; [self.mapView.style addLayer:rasterLayer]; } - (void)styleShapeSource { NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"ams" URL:geoJSONURL options:nil]; [self.mapView.style addSource:source]; MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"test" source:source]; fillLayer.fillColor = [MGLStyleValue valueWithRawValue:[UIColor purpleColor]]; [self.mapView.style addLayer:fillLayer]; } - (void)styleSymbolLayer { MGLSymbolStyleLayer *stateLayer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:@"state-label-lg"]; stateLayer.textColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; } - (void)styleBuildingLayer { MGLTransition transition = { 5, 1 }; self.mapView.style.transition = transition; MGLFillStyleLayer *buildingLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"]; buildingLayer.fillColor = [MGLStyleValue valueWithRawValue:[UIColor purpleColor]]; } - (void)styleFerryLayer { MGLLineStyleLayer *ferryLineLayer = (MGLLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"ferry"]; ferryLineLayer.lineColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; } - (void)removeParkLayer { MGLFillStyleLayer *parkLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"park"]; [self.mapView.style removeLayer:parkLayer]; } - (void)styleFilteredFill { // set style and focus on Texas [self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"fill_filter_style" ofType:@"json"]]]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(31, -100) zoomLevel:3 animated:NO]; // after slight delay, fill in Texas (atypical use; we want to clearly see the change for test purposes) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ { MGLFillStyleLayer *statesLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"states"]; // filter statesLayer.predicate = [NSPredicate predicateWithFormat:@"name == 'Texas'"]; // paint properties statesLayer.fillColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; statesLayer.fillOpacity = [MGLStyleValue valueWithRawValue:@0.25]; }); } - (void)styleFilteredLines { // set style and focus on lower 48 [self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"line_filter_style" ofType:@"json"]]]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; // after slight delay, change styling for all Washington-named counties (atypical use; we want to clearly see the change for test purposes) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ { MGLLineStyleLayer *countiesLayer = (MGLLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"counties"]; // filter countiesLayer.predicate = [NSPredicate predicateWithFormat:@"NAME10 == 'Washington'"]; // paint properties countiesLayer.lineColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; countiesLayer.lineOpacity = [MGLStyleValue valueWithRawValue:@0.75]; countiesLayer.lineWidth = [MGLStyleValue valueWithRawValue:@5]; }); } - (void)styleNumericFilteredFills { // set style and focus on lower 48 [self.mapView setStyleURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"numeric_filter_style" ofType:@"json"]]]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; // after slight delay, change styling for regions 200-299 (atypical use; we want to clearly see the change for test purposes) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ { MGLFillStyleLayer *regionsLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"regions"]; // filter (testing both inline and format strings) regionsLayer.predicate = [NSPredicate predicateWithFormat:@"HRRNUM >= %@ AND HRRNUM < 300", @(200)]; // paint properties regionsLayer.fillColor = [MGLStyleValue valueWithRawValue:[UIColor blueColor]]; regionsLayer.fillOpacity = [MGLStyleValue valueWithRawValue:@0.5]; }); } - (void)styleQuery { CGRect queryRect = CGRectInset(self.mapView.bounds, 100, 200); NSArray *visibleFeatures = [self.mapView visibleFeaturesInRect:queryRect]; NSString *querySourceID = @"query-source-id"; NSString *queryLayerID = @"query-layer-id"; // RTE if you don't remove the layer first // RTE if you pass a nill layer to remove layer MGLStyleLayer *layer = [self.mapView.style layerWithIdentifier:queryLayerID]; if (layer) { [self.mapView.style removeLayer:layer]; } // RTE if you pass a nill source to remove source MGLSource *source = [self.mapView.style sourceWithIdentifier:querySourceID]; if (source) { [self.mapView.style removeSource:source]; } dispatch_async(dispatch_get_main_queue(), ^{ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:querySourceID features:visibleFeatures options:nil]; [self.mapView.style addSource:source]; MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:queryLayerID source:source]; fillLayer.fillColor = [MGLConstantStyleValue valueWithRawValue:[UIColor blueColor]]; fillLayer.fillOpacity = [MGLConstantStyleValue valueWithRawValue:@0.5]; [self.mapView.style addLayer:fillLayer]; }); } - (void)styleFeature { self.mapView.zoomLevel = 10; self.mapView.centerCoordinate = CLLocationCoordinate2DMake(51.068585180672635, -114.06074523925781); CLLocationCoordinate2D leafCoords[] = { {50.9683733218221,-114.07035827636719}, {51.02325750523972,-114.06967163085938}, {51.009434536947786,-114.14245605468749}, {51.030599281184124,-114.12597656249999}, {51.060386316691016,-114.21043395996094}, {51.063838646941576,-114.17816162109375}, {51.08152779888779,-114.19876098632812}, {51.08066507029602,-114.16854858398438}, {51.09662294502995,-114.17472839355469}, {51.07764539352731,-114.114990234375}, {51.13670896949613,-114.12391662597656}, {51.13369295212583,-114.09576416015624}, {51.17546878815025,-114.07585144042969}, {51.140155605265896,-114.04632568359375}, {51.15049396880196,-114.01542663574219}, {51.088860342359965,-114.00924682617186}, {51.12205789681453,-113.94813537597656}, {51.106539930027225,-113.94882202148438}, {51.117747873223344,-113.92616271972656}, {51.10093493903458,-113.92616271972656}, {51.10697105503078,-113.90625}, {51.09144802136697,-113.9117431640625}, {51.04916446529361,-113.97010803222655}, {51.045279344649146,-113.9398956298828}, {51.022825599852496,-114.06211853027344}, {51.045279344649146,-113.9398956298828}, {51.022825599852496,-114.06211853027344}, {51.022825599852496,-114.06280517578125}, {50.968805734317804,-114.06280517578125}, {50.9683733218221,-114.07035827636719}, }; NSUInteger coordsCount = sizeof(leafCoords) / sizeof(leafCoords[0]); MGLPolygonFeature *feature = [MGLPolygonFeature polygonWithCoordinates:leafCoords count:coordsCount]; feature.identifier = @"leaf-feature"; feature.attributes = @{@"color": @"red"}; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"leaf-source" shape:feature options:nil]; [self.mapView.style addSource:source]; MGLFillStyleLayer *layer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"leaf-fill-layer" source:source]; layer.predicate = [NSPredicate predicateWithFormat:@"color = 'red'"]; MGLStyleValue *fillColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; layer.fillColor = fillColor; [self.mapView.style addLayer:layer]; NSString *geoJSON = @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; MGLShape *shape = [MGLShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; MGLShapeSource *pointSource = [[MGLShapeSource alloc] initWithIdentifier:@"leaf-point-source" shape:shape options:nil]; [self.mapView.style addSource:pointSource]; MGLCircleStyleLayer *circleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"leaf-circle-layer" source:pointSource]; circleLayer.circleColor = [MGLStyleValue valueWithRawValue:[UIColor greenColor]]; circleLayer.predicate = [NSPredicate predicateWithFormat:@"color = 'green'"]; [self.mapView.style addLayer:circleLayer]; CLLocationCoordinate2D squareCoords[] = { {51.056070541830934, -114.0274429321289}, {51.07937094724242, -114.0274429321289}, {51.07937094724242, -113.98761749267578}, {51.05607054183093, -113.98761749267578}, {51.056070541830934, -114.0274429321289}, }; MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:squareCoords count:sizeof(squareCoords)/sizeof(squareCoords[0])]; MGLShapeSource *plainShapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"leaf-plain-shape-source" shape:polygon options:nil]; [self.mapView.style addSource:plainShapeSource]; MGLFillStyleLayer *plainFillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"leaf-plain-fill-layer" source:plainShapeSource]; plainFillLayer.fillColor = [MGLStyleValue valueWithRawValue:[UIColor yellowColor]]; [self.mapView.style addLayer:plainFillLayer]; } - (void)updateShapeSourceData { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.329795743702064, -107.75390625) zoomLevel:11 animated:NO]; NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; MGLShape *shape = [MGLShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"mutable-data-source-id" shape:shape options:nil]; [self.mapView.style addSource:source]; MGLLineStyleLayer *layer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-id" source:source]; [self.mapView.style addLayer:layer]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-109.34814453125,37.64903402157866]]}}]}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; MGLShape *shape = [MGLShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; source.shape = shape; }); } - (void)updateShapeSourceURL { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(48.668731, -122.857151) zoomLevel:11 animated:NO]; NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"polyline" ofType:@"geojson"]; NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"mutable-data-source-url-id" URL:geoJSONURL options:nil]; [self.mapView.style addSource:source]; MGLLineStyleLayer *layer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-url-id" source:source]; [self.mapView.style addLayer:layer]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(41.563986787078704, -75.04843935793578) zoomLevel:8 animated:NO]; NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"threestates" ofType:@"geojson"]; NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; source.URL = geoJSONURL; }); } - (void)updateShapeSourceFeatures { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-41.1520, 288.6592) zoomLevel:10 animated:NO]; CLLocationCoordinate2D smallBox[] = { {-41.14763798539186, 288.68019104003906}, {-41.140915920129665, 288.68019104003906}, {-41.140915920129665, 288.6887741088867}, {-41.14763798539186, 288.6887741088867}, {-41.14763798539186, 288.68019104003906} }; CLLocationCoordinate2D largeBox[] = { {-41.17710352162799, 288.67298126220703}, {-41.13962313627545, 288.67298126220703}, {-41.13962313627545, 288.7261962890625}, {-41.17710352162799, 288.7261962890625}, {-41.17710352162799, 288.67298126220703} }; MGLPolygonFeature *smallBoxFeature = [MGLPolygonFeature polygonWithCoordinates:smallBox count:sizeof(smallBox)/sizeof(smallBox[0])]; MGLPolygonFeature *largeBoxFeature = [MGLPolygonFeature polygonWithCoordinates:largeBox count:sizeof(largeBox)/sizeof(largeBox[0])]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"mutable-data-source-features-id" shape:smallBoxFeature options:nil]; [self.mapView.style addSource:source]; MGLFillStyleLayer *layer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-features-id" source:source]; MGLStyleValue *fillColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]]; layer.fillColor = fillColor; [self.mapView.style addLayer:layer]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ source.shape = largeBoxFeature; }); } - (void)styleDynamicPointCollection { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO]; CLLocationCoordinate2D coordinates[] = { {37.00145594210082, -109.04960632324219}, {37.00173012609867, -109.0404224395752}, {36.99453246847359, -109.04960632324219}, {36.99508088541243, -109.04007911682129}, }; MGLPointCollectionFeature *feature = [MGLPointCollectionFeature pointCollectionWithCoordinates:coordinates count:4]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"wiggle-source" shape:feature options:nil]; [self.mapView.style addSource:source]; MGLCircleStyleLayer *layer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"wiggle-layer" source:source]; [self.mapView.style addLayer:layer]; } - (void)styleVectorSource { NSURL *url = [[NSURL alloc] initWithString:@"mapbox://mapbox.mapbox-terrain-v2"]; MGLVectorSource *vectorSource = [[MGLVectorSource alloc] initWithIdentifier:@"style-vector-source-id" configurationURL:url]; [self.mapView.style addSource:vectorSource]; MGLBackgroundStyleLayer *backgroundLayer = [[MGLBackgroundStyleLayer alloc] initWithIdentifier:@"style-vector-background-layer-id"]; backgroundLayer.backgroundColor = [MGLStyleValue valueWithRawValue:[UIColor blackColor]]; [self.mapView.style addLayer:backgroundLayer]; MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"style-vector-line-layer-id" source:vectorSource]; lineLayer.sourceLayerIdentifier = @"contour"; NSUInteger lineJoinValue = MGLLineJoinRound; lineLayer.lineJoin = [MGLStyleValue valueWithRawValue:[NSValue value:&lineJoinValue withObjCType:@encode(MGLLineJoin)]]; NSUInteger lineCapValue = MGLLineCapRound; lineLayer.lineCap = [MGLStyleValue valueWithRawValue:[NSValue value:&lineCapValue withObjCType:@encode(MGLLineCap)]]; lineLayer.lineColor = [MGLStyleValue valueWithRawValue:[UIColor greenColor]]; [self.mapView.style addLayer:lineLayer]; } - (void)styleRasterSource { NSArray *tileURLTemplates = @[@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}.jpg"]; MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:tileURLTemplates options:@{ MGLTileSourceOptionTileSize: @256, }]; [self.mapView.style addSource:rasterSource]; MGLRasterStyleLayer *rasterLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"style-raster-layer-id" source:rasterSource]; [self.mapView.style addLayer:rasterLayer]; } - (void)styleImageSource { MGLCoordinateQuad coordinateQuad = { { 46.437, -80.425 }, { 37.936, -80.425 }, { 37.936, -71.516 }, { 46.437, -71.516 } }; MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"style-image-source-id" coordinateQuad:coordinateQuad URL:[NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar0.gif"]]; [self.mapView.style addSource:imageSource]; MGLRasterStyleLayer *rasterLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"style-raster-image-layer-id" source:imageSource]; [self.mapView.style addLayer:rasterLayer]; [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateAnimatedImageSource:) userInfo:imageSource repeats:YES]; } - (void)updateAnimatedImageSource:(NSTimer *)timer { static int radarSuffix = 0; MGLImageSource *imageSource = (MGLImageSource *)timer.userInfo; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/mapbox-gl-js/assets/radar%d.gif", radarSuffix++]]; [imageSource setValue:url forKey:@"URL"]; if (radarSuffix > 3) { radarSuffix = 0; } } -(void)styleCountryLabelsLanguage { NSArray *labelLayers = @[ @"country-label-lg", @"country-label-md", @"country-label-sm", ]; [self styleLabelLanguageForLayersNamed:labelLayers]; } - (void)styleRouteLine { CLLocationCoordinate2D coords[] = { { 43.84455590478528, 10.504238605499268 }, { 43.84385562343126, 10.504125952720642 }, { 43.84388657526694, 10.503299832344055 }, { 43.84332557075269, 10.503235459327698 }, { 43.843441641085036, 10.502264499664307 }, { 43.84396395478592, 10.50242006778717 }, { 43.84406067904351, 10.501744151115416 }, { 43.84422317544319, 10.501792430877686 } }; NSInteger count = sizeof(coords) / sizeof(coords[0]); [self.mapView setCenterCoordinate:coords[0] zoomLevel:16 animated:YES]; MGLPolylineFeature *routeLine = [MGLPolylineFeature polylineWithCoordinates:coords count:count]; MGLShapeSource *routeSource = [[MGLShapeSource alloc] initWithIdentifier:@"style-route-source" shape:routeLine options:nil]; [self.mapView.style addSource:routeSource]; MGLLineStyleLayer *baseRouteLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource]; baseRouteLayer.lineColor = [MGLConstantStyleValue valueWithRawValue:[UIColor orangeColor]]; baseRouteLayer.lineWidth = [MGLConstantStyleValue valueWithRawValue:@20]; baseRouteLayer.lineOpacity = [MGLConstantStyleValue valueWithRawValue:@0.5]; baseRouteLayer.lineCap = [MGLConstantStyleValue valueWithRawValue:[NSValue valueWithMGLLineCap:MGLLineCapRound]]; baseRouteLayer.lineJoin = [MGLConstantStyleValue valueWithRawValue:[NSValue valueWithMGLLineJoin:MGLLineJoinRound]]; [self.mapView.style addLayer:baseRouteLayer]; MGLLineStyleLayer *routeLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" source:routeSource]; routeLayer.lineColor = [MGLConstantStyleValue valueWithRawValue:[UIColor whiteColor]]; routeLayer.lineWidth = [MGLConstantStyleValue valueWithRawValue:@15]; routeLayer.lineOpacity = [MGLConstantStyleValue valueWithRawValue:@0.8]; routeLayer.lineCap = [MGLConstantStyleValue valueWithRawValue:[NSValue valueWithMGLLineCap:MGLLineCapRound]]; routeLayer.lineJoin = [MGLConstantStyleValue valueWithRawValue:[NSValue valueWithMGLLineJoin:MGLLineJoinRound]]; [self.mapView.style addLayer:routeLayer]; } - (void)stylePolygonWithDDS { CLLocationCoordinate2D leftCoords[] = { {37.73081027834234, -122.49412536621094}, {37.7566013348511, -122.49412536621094}, {37.7566013348511, -122.46253967285156}, {37.73081027834234, -122.46253967285156}, {37.73081027834234, -122.49412536621094}, }; CLLocationCoordinate2D rightCoords[] = { {37.73135334055843, -122.44640350341795}, {37.75741564287944, -122.44640350341795}, {37.75741564287944, -122.41310119628906}, {37.73135334055843, -122.41310119628906}, {37.73135334055843, -122.44640350341795}, }; MGLPolygonFeature *leftFeature = [MGLPolygonFeature polygonWithCoordinates:leftCoords count:5]; leftFeature.attributes = @{@"fill": @(YES)}; MGLPolygonFeature *rightFeature = [MGLPolygonFeature polygonWithCoordinates:rightCoords count:5]; rightFeature.attributes = @{@"opacity": @(0.5)}; MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"shape-source" features:@[leftFeature, rightFeature] options:nil]; [self.mapView.style addSource:shapeSource]; // source, categorical function that sets any feature with a "fill" attribute value of true to red color and anything without to green MGLFillStyleLayer *fillStyleLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:shapeSource]; NSDictionary *stops = @{@(YES): [MGLStyleValue valueWithRawValue:[UIColor greenColor]]}; NSDictionary *fillColorOptions = @{MGLStyleFunctionOptionDefaultValue: [MGLStyleValue valueWithRawValue:[UIColor redColor]]}; fillStyleLayer.fillColor = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeCategorical sourceStops:stops attributeName:@"fill" options:fillColorOptions]; // source, identity function that sets any feature with an "opacity" attribute to use that value and anything without to 1.0 NSDictionary *fillOpacityOptions = @{MGLStyleFunctionOptionDefaultValue: [MGLStyleValue valueWithRawValue:@(1.0)]}; fillStyleLayer.fillOpacity = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"opacity" options:fillOpacityOptions]; [self.mapView.style addLayer:fillStyleLayer]; } - (void)styleLabelLanguageForLayersNamed:(NSArray *)layers { _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; NSString *bestLanguageForUser = [NSString stringWithFormat:@"{name_%@}", [self bestLanguageForUser]]; NSString *language = _usingLocaleBasedCountryLabels ? bestLanguageForUser : @"{name}"; for (NSString *layerName in layers) { MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:layerName]; if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { MGLConstantStyleValue *label = (MGLConstantStyleValue *)layer.text; if ([label.rawValue hasPrefix:@"{name"]) { layer.text = [MGLStyleValue valueWithRawValue:language]; } } else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; NSMutableDictionary *stops = function.stops.mutableCopy; [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue *stop, BOOL *done) { if ([stop.rawValue hasPrefix:@"{name"]) { stops[zoomLevel] = [MGLStyleValue valueWithRawValue:language]; } }]; function.stops = stops; layer.text = function; } } else { NSLog(@"%@ is not a symbol style layer", layerName); } } } - (NSString *)bestLanguageForUser { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview NSArray *supportedLanguages = @[ @"ar", @"en", @"es", @"fr", @"de", @"pt", @"ru", @"zh", @"zh-Hans" ]; NSArray *preferredLanguages = [NSBundle preferredLocalizationsFromArray:supportedLanguages forPreferences:[NSLocale preferredLanguages]]; NSString *mostSpecificLanguage; for (NSString *language in preferredLanguages) { if (language.length > mostSpecificLanguage.length) { mostSpecificLanguage = language; } } return mostSpecificLanguage ?: @"en"; } - (IBAction)startWorldTour { _isTouringWorld = YES; [self.mapView removeAnnotations:self.mapView.annotations]; NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]); NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations]; for (NSUInteger i = 0; i < numberOfAnnotations; i++) { MBXDroppedPinAnnotation *annotation = [[MBXDroppedPinAnnotation alloc] init]; annotation.coordinate = WorldTourDestinations[i]; [annotations addObject:annotation]; } [self.mapView addAnnotations:annotations]; [self continueWorldTourWithRemainingAnnotations:annotations]; } - (void)continueWorldTourWithRemainingAnnotations:(NS_MUTABLE_ARRAY_OF(MGLPointAnnotation *) *)annotations { MGLPointAnnotation *nextAnnotation = annotations.firstObject; if (!nextAnnotation || !_isTouringWorld) { _isTouringWorld = NO; return; } [annotations removeObjectAtIndex:0]; MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate fromDistance:10 pitch:arc4random_uniform(60) heading:arc4random_uniform(360)]; __weak MBXViewController *weakSelf = self; [self.mapView flyToCamera:camera completionHandler:^{ MBXViewController *strongSelf = weakSelf; [strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:) withObject:annotations afterDelay:2]; }]; } - (void)toggleCustomUserDot { _customUserLocationAnnnotationEnabled = !_customUserLocationAnnnotationEnabled; self.mapView.showsUserLocation = NO; self.mapView.userTrackingMode = MGLUserTrackingModeFollow; } - (void)testQueryPointAnnotations { NSNumber *visibleAnnotationCount = @(self.mapView.visibleAnnotations.count); NSString *message; if ([visibleAnnotationCount integerValue] == 1) { message = [NSString stringWithFormat:@"There is %@ visible annotation.", visibleAnnotationCount]; } else { message = [NSString stringWithFormat:@"There are %@ visible annotations.", visibleAnnotationCount]; } UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Visible Annotations" message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } - (void)printTelemetryLogFile { NSString *fileContents = [NSString stringWithContentsOfFile:[self telemetryDebugLogFilePath] encoding:NSUTF8StringEncoding error:nil]; NSLog(@"%@", fileContents); } - (void)deleteTelemetryLogFile { NSString *filePath = [self telemetryDebugLogFilePath]; if ([[NSFileManager defaultManager] isDeletableFileAtPath:filePath]) { NSError *error; BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; if (success) { NSLog(@"Deleted telemetry log."); } else { NSLog(@"Error deleting telemetry log: %@", error.localizedDescription); } } } - (NSString *)telemetryDebugLogFilePath { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"telemetry_log-%@.json", [dateFormatter stringFromDate:[NSDate date]]]]; return filePath; } #pragma mark - User Actions - (IBAction)handleLongPress:(UILongPressGestureRecognizer *)longPress { if (longPress.state == UIGestureRecognizerStateBegan) { CGPoint point = [longPress locationInView:longPress.view]; NSArray *features = [self.mapView visibleFeaturesAtPoint:point]; NSString *title; for (id feature in features) { if (!title) { title = [feature attributeForKey:@"name_en"] ?: [feature attributeForKey:@"name"]; } } MBXDroppedPinAnnotation *pin = [[MBXDroppedPinAnnotation alloc] init]; pin.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; pin.title = title ?: @"Dropped Pin"; pin.subtitle = [[[MGLCoordinateFormatter alloc] init] stringFromCoordinate:pin.coordinate]; // Calling `addAnnotation:` on mapView is not required since `selectAnnotation:animated` has the side effect of adding the annotation if required [self.mapView selectAnnotation:pin animated:YES]; } } - (IBAction)cycleStyles:(__unused id)sender { static NSArray *styleNames; static NSArray *styleURLs; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ styleNames = @[ @"Streets", @"Outdoors", @"Light", @"Dark", @"Satellite", @"Satellite Streets", @"Traffic Day", @"Traffic Night", ]; styleURLs = @[ [MGLStyle streetsStyleURL], [MGLStyle outdoorsStyleURL], [MGLStyle lightStyleURL], [MGLStyle darkStyleURL], [MGLStyle satelliteStyleURL], [MGLStyle satelliteStreetsStyleURL], [MGLStyle trafficDayStyleURL], [MGLStyle trafficNightStyleURL], ]; NSAssert(styleNames.count == styleURLs.count, @"Style names and URLs don’t match."); // Make sure defaultStyleURLs is up-to-date. unsigned numMethods = 0; Method *methods = class_copyMethodList(object_getClass([MGLStyle class]), &numMethods); unsigned numStyleURLMethods = 0; for (NSUInteger i = 0; i < numMethods; i++) { Method method = methods[i]; if (method_getNumberOfArguments(method) == 3 /* _cmd, self, version */) { SEL selector = method_getName(method); NSString *name = @(sel_getName(selector)); if ([name hasSuffix:@"StyleURLWithVersion:"]) { numStyleURLMethods += 1; } } } NSAssert(numStyleURLMethods == styleNames.count, @"MGLStyle provides %u default styles but iosapp only knows about %lu of them.", numStyleURLMethods, (unsigned long)styleNames.count); }); self.styleIndex = (self.styleIndex + 1) % styleNames.count; self.mapView.styleURL = styleURLs[self.styleIndex]; UIButton *titleButton = (UIButton *)self.navigationItem.titleView; [titleButton setTitle:styleNames[self.styleIndex] forState:UIControlStateNormal]; } - (IBAction)locateUser:(id)sender { MGLUserTrackingMode nextMode; NSString *nextAccessibilityValue; switch (self.mapView.userTrackingMode) { case MGLUserTrackingModeNone: nextMode = MGLUserTrackingModeFollow; nextAccessibilityValue = @"Follow location"; break; case MGLUserTrackingModeFollow: nextMode = MGLUserTrackingModeFollowWithHeading; nextAccessibilityValue = @"Follow location and heading"; break; case MGLUserTrackingModeFollowWithHeading: nextMode = MGLUserTrackingModeFollowWithCourse; nextAccessibilityValue = @"Follow course"; break; case MGLUserTrackingModeFollowWithCourse: nextMode = MGLUserTrackingModeNone; nextAccessibilityValue = @"Off"; break; } self.mapView.userTrackingMode = nextMode; [sender setAccessibilityValue:nextAccessibilityValue]; } #pragma mark - Map Delegate - (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id)annotation { if (annotation == mapView.userLocation) { if (_customUserLocationAnnnotationEnabled) { MBXUserLocationAnnotationView *annotationView = [[MBXUserLocationAnnotationView alloc] initWithFrame:CGRectZero]; annotationView.frame = CGRectMake(0, 0, annotationView.intrinsicContentSize.width, annotationView.intrinsicContentSize.height); return annotationView; } return nil; } // Use GL backed pins for dropped pin annotations if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || [annotation isKindOfClass:[MBXSpriteBackedAnnotation class]]) { return nil; } MBXAnnotationView *annotationView = (MBXAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; if (!annotationView) { annotationView = [[MBXAnnotationView alloc] initWithReuseIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; annotationView.frame = CGRectMake(0, 0, 10, 10); annotationView.backgroundColor = [UIColor whiteColor]; // Note that having two long press gesture recognizers on overlapping // views (`self.view` & `annotationView`) will cause weird behaviour. // Comment out the pin dropping functionality in the handleLongPress: // method in this class to make draggable annotation views play nice. annotationView.draggable = YES; // Uncomment to force annotation view to maintain a constant size when // the map is tilted. By default, annotation views will shrink and grow // as they move towards and away from the horizon. Relatedly, annotations // backed by GL sprites currently ONLY scale with viewing distance. // annotationView.scalesWithViewingDistance = NO; } else { // orange indicates that the annotation view was reused annotationView.backgroundColor = [UIColor orangeColor]; } return annotationView; } - (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotation:(id __nonnull)annotation { if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) { return nil; // use default marker } NSAssert([annotation isKindOfClass:[MBXSpriteBackedAnnotation class]], @"Annotations should be sprite-backed."); NSString *title = [(MGLPointAnnotation *)annotation title]; if (!title.length) return nil; NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; if ( ! annotationImage) { UIColor *color; // make every tenth annotation blue if ([lastTwoCharacters hasSuffix:@"0"]) { color = [UIColor blueColor]; } else { color = [UIColor redColor]; } UIImage *image = [self imageWithText:lastTwoCharacters backgroundColor:color]; annotationImage = [MGLAnnotationImage annotationImageWithImage:image reuseIdentifier:lastTwoCharacters]; // don't allow touches on blue annotations if ([color isEqual:[UIColor blueColor]]) annotationImage.enabled = NO; } return annotationImage; } - (UIImage *)imageWithText:(NSString *)text backgroundColor:(UIColor *)color { CGRect rect = CGRectMake(0, 0, 20, 15); UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(ctx, [[color colorWithAlphaComponent:0.75] CGColor]); CGContextFillRect(ctx, rect); CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); CGContextStrokeRectWithWidth(ctx, rect, 2); NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12], NSForegroundColorAttributeName: [UIColor whiteColor], }]; CGSize stringSize = drawString.size; CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2, (rect.size.height - stringSize.height) / 2, stringSize.width, stringSize.height); [drawString drawInRect:stringRect]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id )annotation { return YES; } - (CGFloat)mapView:(__unused MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation { return ([annotation isKindOfClass:[MGLPolygon class]] ? 0.5 : 1.0); } - (UIColor *)mapView:(__unused MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation { UIColor *color = [annotation isKindOfClass:[MGLPolyline class]] ? [UIColor greenColor] : [UIColor blackColor]; return [color colorWithAlphaComponent:0.9]; } - (UIColor *)mapView:(__unused MGLMapView *)mapView fillColorForPolygonAnnotation:(__unused MGLPolygon *)annotation { UIColor *color = annotation.pointCount > 3 ? [UIColor greenColor] : [UIColor redColor]; return [color colorWithAlphaComponent:0.5]; } - (void)mapView:(__unused MGLMapView *)mapView didChangeUserTrackingMode:(MGLUserTrackingMode)mode animated:(__unused BOOL)animated { UIImage *newButtonImage; NSString *newButtonTitle; switch (mode) { case MGLUserTrackingModeNone: newButtonImage = [UIImage imageNamed:@"TrackingLocationOffMask.png"]; break; case MGLUserTrackingModeFollow: newButtonImage = [UIImage imageNamed:@"TrackingLocationMask.png"]; break; case MGLUserTrackingModeFollowWithHeading: newButtonImage = [UIImage imageNamed:@"TrackingHeadingMask.png"]; break; case MGLUserTrackingModeFollowWithCourse: newButtonImage = nil; newButtonTitle = @"Course"; break; } self.navigationItem.rightBarButtonItem.title = newButtonTitle; [UIView animateWithDuration:0.25 animations:^{ self.navigationItem.rightBarButtonItem.image = newButtonImage; }]; } - (nullable id )mapView:(__unused MGLMapView *)mapView calloutViewForAnnotation:(id)annotation { if ([annotation respondsToSelector:@selector(title)] && [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) { MBXCustomCalloutAnnotation *customAnnotation = (MBXCustomCalloutAnnotation *)annotation; MBXCustomCalloutView *calloutView = [[MBXCustomCalloutView alloc] init]; calloutView.representedObject = annotation; calloutView.anchoredToAnnotation = customAnnotation.anchoredToAnnotation; calloutView.dismissesAutomatically = customAnnotation.dismissesAutomatically; return calloutView; } return nil; } - (UIView *)mapView:(__unused MGLMapView *)mapView leftCalloutAccessoryViewForAnnotation:(__unused id)annotation { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.frame = CGRectZero; [button setTitle:@"Left" forState:UIControlStateNormal]; [button sizeToFit]; return button; } - (UIView *)mapView:(__unused MGLMapView *)mapView rightCalloutAccessoryViewForAnnotation:(__unused id)annotation { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.frame = CGRectZero; [button setTitle:@"Right" forState:UIControlStateNormal]; [button sizeToFit]; return button; } - (void)mapView:(MGLMapView *)mapView tapOnCalloutForAnnotation:(id )annotation { if ( ! [annotation isKindOfClass:[MGLPointAnnotation class]]) { return; } MGLPointAnnotation *point = (MGLPointAnnotation *)annotation; point.coordinate = [self.mapView convertPoint:self.mapView.center toCoordinateFromView:self.mapView]; } - (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { // Default Mapbox styles use {name_en} as their label language, which means // that a device with an English-language locale is already effectively // using locale-based country labels. _usingLocaleBasedCountryLabels = [[self bestLanguageForUser] isEqualToString:@"en"]; } - (void)mapViewRegionIsChanging:(MGLMapView *)mapView { if (self.reuseQueueStatsEnabled) { NSUInteger queuedAnnotations = 0; for (NSArray *queue in self.mapView.annotationViewReuseQueueByIdentifier.allValues) { queuedAnnotations += queue.count; } self.hudLabel.text = [NSString stringWithFormat:@" Visible: %ld Queued: %ld", (unsigned long)mapView.visibleAnnotations.count, (unsigned long)queuedAnnotations]; } else if (self.showZoomLevelEnabled) { self.hudLabel.text = [NSString stringWithFormat:@" Zoom: %.2f", self.mapView.zoomLevel]; } } - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { if (self.showZoomLevelEnabled) { self.hudLabel.text = [NSString stringWithFormat:@" Zoom: %.2f", self.mapView.zoomLevel]; } } @end