diff options
Diffstat (limited to 'platform/macos/app/MapDocument.m')
-rw-r--r-- | platform/macos/app/MapDocument.m | 1507 |
1 files changed, 0 insertions, 1507 deletions
diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m deleted file mode 100644 index 1ab8b690b9..0000000000 --- a/platform/macos/app/MapDocument.m +++ /dev/null @@ -1,1507 +0,0 @@ -#import "MapDocument.h" - -#import "AppDelegate.h" -#import "LimeGreenStyleLayer.h" -#import "DroppedPinAnnotation.h" -#import "MGLMapsnapshotter.h" - -#import "MGLStyle+MBXAdditions.h" -#import "MGLVectorTileSource_Private.h" - -#import <Mapbox/Mapbox.h> - -static NSString * const MGLDroppedPinAnnotationImageIdentifier = @"dropped"; - -static const CLLocationCoordinate2D WorldTourDestinations[] = { - { .latitude = 38.8999418, .longitude = -77.033996 }, - { .latitude = 37.7884307, .longitude = -122.3998631 }, - { .latitude = 52.5003103, .longitude = 13.4197763 }, - { .latitude = 60.1712627, .longitude = 24.9378866 }, - { .latitude = 53.8948782, .longitude = 27.5558476 }, -}; - -NSArray<id <MGLAnnotation>> *MBXFlattenedShapes(NSArray<id <MGLAnnotation>> *shapes) { - NSMutableArray *flattenedShapes = [NSMutableArray arrayWithCapacity:shapes.count]; - for (id <MGLAnnotation> shape in shapes) { - NSArray *subshapes; - if ([shape isKindOfClass:[MGLMultiPolyline class]]) { - subshapes = [(MGLMultiPolyline *)shape polylines]; - } else if ([shape isKindOfClass:[MGLMultiPolygon class]]) { - subshapes = [(MGLMultiPolygon *)shape polygons]; - } else if ([shape isKindOfClass:[MGLPointCollection class]]) { - NSUInteger pointCount = [(MGLPointCollection *)shape pointCount]; - CLLocationCoordinate2D *coordinates = [(MGLPointCollection *)shape coordinates]; - NSMutableArray *pointAnnotations = [NSMutableArray arrayWithCapacity:pointCount]; - for (NSUInteger i = 0; i < pointCount; i++) { - MGLPointAnnotation *pointAnnotation = [[MGLPointAnnotation alloc] init]; - pointAnnotation.coordinate = coordinates[i]; - [pointAnnotations addObject:pointAnnotation]; - } - subshapes = pointAnnotations; - } else if ([shape isKindOfClass:[MGLShapeCollection class]]) { - subshapes = MBXFlattenedShapes([(MGLShapeCollection *)shape shapes]); - } - - if (subshapes) { - [flattenedShapes addObjectsFromArray:subshapes]; - } else { - [flattenedShapes addObject:shape]; - } - } - return flattenedShapes; -} - -@interface MGLVectorTileSource (MBXAdditions) - -@property (nonatomic, readonly, getter=isMapboxTerrain) BOOL mapboxTerrain; - -@end - -@implementation MGLVectorTileSource (MBXAdditions) - -- (BOOL)isMapboxTerrain { - NSURL *url = self.configurationURL; - if (![url.scheme isEqualToString:@"mapbox"]) { - return NO; - } - NSArray *identifiers = [url.host componentsSeparatedByString:@","]; - return [identifiers containsObject:@"mapbox.mapbox-terrain-v2"] || [identifiers containsObject:@"mapbox.mapbox-terrain-v1"]; -} - -@end - -@interface MapDocument () <NSWindowDelegate, NSSharingServicePickerDelegate, NSMenuDelegate, NSSplitViewDelegate, MGLMapViewDelegate, MGLComputedShapeSourceDataSource> - -@property (weak) IBOutlet NSArrayController *styleLayersArrayController; -@property (weak) IBOutlet NSTableView *styleLayersTableView; -@property (weak) IBOutlet NSMenu *mapViewContextMenu; -@property (weak) IBOutlet NSSplitView *splitView; -@property (weak) IBOutlet NSWindow *addOfflinePackWindow; -@property (weak) IBOutlet NSTextField *offlinePackNameField; -@property (weak) IBOutlet NSTextField *minimumOfflinePackZoomLevelField; -@property (weak) IBOutlet NSNumberFormatter *minimumOfflinePackZoomLevelFormatter; -@property (weak) IBOutlet NSTextField *maximumOfflinePackZoomLevelField; -@property (weak) IBOutlet NSNumberFormatter *maximumOfflinePackZoomLevelFormatter; -@property (weak) IBOutlet NSButton *includesIdeographicGlyphsBox; - -@end - -@implementation MapDocument { - /// Style URL inherited from an existing document at the time this document - /// was created. - NSURL *_inheritedStyleURL; - - NSPoint _mouseLocationForMapViewContextMenu; - NSUInteger _droppedPinCounter; - NSNumberFormatter *_spellOutNumberFormatter; - - BOOL _isLocalizingLabels; - BOOL _showsToolTipsOnDroppedPins; - BOOL _randomizesCursorsOnDroppedPins; - BOOL _isTouringWorld; - BOOL _isShowingPolygonAndPolylineAnnotations; - BOOL _isShowingAnimatedAnnotation; - - MGLMapSnapshotter *_snapshotter; -} - -#pragma mark Lifecycle - -- (NSString *)windowNibName { - return @"MapDocument"; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)windowControllerWillLoadNib:(NSWindowController *)windowController { - NSDocument *currentDocument = [NSDocumentController sharedDocumentController].currentDocument; - if ([currentDocument isKindOfClass:[MapDocument class]]) { - _inheritedStyleURL = [(MapDocument *)currentDocument mapView].styleURL; - } -} - -- (void)windowControllerDidLoadNib:(NSWindowController *)controller { - [super windowControllerDidLoadNib:controller]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(userDefaultsDidChange:) - name:NSUserDefaultsDidChangeNotification - object:nil]; - - _spellOutNumberFormatter = [[NSNumberFormatter alloc] init]; - - NSPressGestureRecognizer *pressGestureRecognizer = [[NSPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)]; - [self.mapView addGestureRecognizer:pressGestureRecognizer]; - - [self.splitView setPosition:0 ofDividerAtIndex:0]; - - [self applyPendingState]; -} - -- (NSWindow *)window { - return self.windowControllers.firstObject.window; -} - -- (void)userDefaultsDidChange:(NSNotification *)notification { - NSUserDefaults *userDefaults = notification.object; - NSString *accessToken = [userDefaults stringForKey:MGLMapboxAccessTokenDefaultsKey]; - if (![accessToken isEqualToString:[MGLAccountManager accessToken]]) { - [MGLAccountManager setAccessToken:accessToken]; - [self reload:self]; - } -} - -#pragma mark NSWindowDelegate methods - -- (void)window:(NSWindow *)window willEncodeRestorableState:(NSCoder *)state { - [state encodeObject:self.mapView.styleURL forKey:@"MBXMapViewStyleURL"]; - [state encodeBool:_isLocalizingLabels forKey:@"MBXLocalizeLabels"]; -} - -- (void)window:(NSWindow *)window didDecodeRestorableState:(NSCoder *)state { - self.mapView.styleURL = [state decodeObjectForKey:@"MBXMapViewStyleURL"]; - _isLocalizingLabels = [state decodeBoolForKey:@"MBXLocalizeLabels"]; -} - -#pragma mark Services - -- (IBAction)showShareMenu:(id)sender { - NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[self.shareURL]]; - picker.delegate = self; - [picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge]; -} - -- (NSURL *)shareURL { - NSArray *components = self.mapView.styleURL.pathComponents; - MGLMapCamera *camera = self.mapView.camera; - return [NSURL URLWithString: - [NSString stringWithFormat:@"https://api.mapbox.com/styles/v1/%@/%@.html?access_token=%@#%.2f/%.5f/%.5f/%.f/%.f", - components[1], components[2], [MGLAccountManager accessToken], - self.mapView.zoomLevel, camera.centerCoordinate.latitude, camera.centerCoordinate.longitude, - camera.heading, camera.pitch]]; -} - -#pragma mark File methods - -- (IBAction)import:(id)sender { - NSOpenPanel *panel = [NSOpenPanel openPanel]; - panel.allowedFileTypes = @[@"public.json", @"json", @"geojson"]; - panel.allowsMultipleSelection = YES; - - __weak __typeof__(self) weakSelf = self; - [panel beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse result) { - if (result != NSFileHandlingPanelOKButton) { - return; - } - - for (NSURL *url in panel.URLs) { - [weakSelf importFromURL:url]; - } - }]; -} - -/** - Adds the contents of the GeoJSON file at the given URL to the map. - - GeoJSON features are styled according to - [simplestyle-spec](https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0/). - */ -- (void)importFromURL:(NSURL *)url { - MGLStyle *style = self.mapView.style; - if (!style) { - return; - } - - MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:[NSUUID UUID].UUIDString URL:url options:nil]; - [self.mapView.style addSource:source]; - - NSString *pointIdentifier = [NSString stringWithFormat:@"%@ marker", source.identifier]; - MGLSymbolStyleLayer *pointLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:pointIdentifier source:source]; - pointLayer.iconImageName = - [NSExpression expressionWithFormat:@"mgl_join({%K, '-', CAST(TERNARY(%K = 'small', 11, 15), 'NSString')})", - @"marker-symbol", @"marker-size"]; - pointLayer.iconScale = [NSExpression expressionForConstantValue:@1]; - pointLayer.iconColor = [NSExpression expressionWithFormat:@"CAST(mgl_coalesce({%K, '#7e7e7e'}), 'NSColor')", - @"marker-color"]; - pointLayer.iconAllowsOverlap = [NSExpression expressionForConstantValue:@YES]; - [style addLayer:pointLayer]; - - NSString *fillIdentifier = [NSString stringWithFormat:@"%@ fill", source.identifier]; - MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:fillIdentifier source:source]; - fillLayer.predicate = [NSPredicate predicateWithFormat:@"fill != nil OR %K != nil", @"fill-opacity"]; - fillLayer.fillColor = [NSExpression expressionWithFormat:@"CAST(mgl_coalesce({fill, '#555555'}), 'NSColor')"]; - fillLayer.fillOpacity = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, 0.5})", @"fill-opacity"]; - [style addLayer:fillLayer]; - - NSString *lineIdentifier = [NSString stringWithFormat:@"%@ stroke", source.identifier]; - MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:lineIdentifier source:source]; - lineLayer.lineColor = [NSExpression expressionWithFormat:@"CAST(mgl_coalesce({stroke, '#555555'}), 'NSColor')"]; - lineLayer.lineOpacity = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, 1.0})", @"stroke-opacity"]; - lineLayer.lineWidth = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, 2})", @"stroke-width"]; - lineLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - lineLayer.lineJoin = [NSExpression expressionForConstantValue:@"bevel"]; - [style addLayer:lineLayer]; -} - -- (IBAction)takeSnapshot:(id)sender { - MGLMapCamera *camera = self.mapView.camera; - - MGLMapSnapshotOptions *options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:self.mapView.styleURL camera:camera size:self.mapView.bounds.size]; - options.zoomLevel = self.mapView.zoomLevel; - - // Create and start the snapshotter - __weak __typeof__(self) weakSelf = self; - _snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; - [_snapshotter startWithCompletionHandler:^(MGLMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (error) { - NSLog(@"Could not load snapshot: %@", error.localizedDescription); - } else { - // Set the default name for the file and show the panel. - NSSavePanel *panel = [NSSavePanel savePanel]; - panel.nameFieldStringValue = [strongSelf.mapView.styleURL.lastPathComponent.stringByDeletingPathExtension stringByAppendingPathExtension:@"png"]; - panel.allowedFileTypes = [@[(NSString *)kUTTypePNG] arrayByAddingObjectsFromArray:[NSBitmapImageRep imageUnfilteredTypes]]; - - [panel beginSheetModalForWindow:strongSelf.window completionHandler:^(NSInteger result) { - if (result == NSFileHandlingPanelOKButton) { - // Write the contents in the new format. - NSURL *fileURL = panel.URL; - - NSBitmapImageRep *bitmapRep; - for (NSImageRep *imageRep in snapshot.image.representations) { - if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { - bitmapRep = (NSBitmapImageRep *)imageRep; - break; // stop on first bitmap rep we find - } - } - - if (!bitmapRep) { - bitmapRep = [NSBitmapImageRep imageRepWithData:snapshot.image.TIFFRepresentation]; - } - - CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileURL.pathExtension, NULL /* inConformingToUTI */); - NSBitmapImageFileType fileType = NSTIFFFileType; - if (UTTypeConformsTo(uti, kUTTypePNG)) { - fileType = NSPNGFileType; - } else if (UTTypeConformsTo(uti, kUTTypeGIF)) { - fileType = NSGIFFileType; - } else if (UTTypeConformsTo(uti, kUTTypeJPEG2000)) { - fileType = NSJPEG2000FileType; - } else if (UTTypeConformsTo(uti, kUTTypeJPEG)) { - fileType = NSJPEGFileType; - } else if (UTTypeConformsTo(uti, kUTTypeBMP)) { - fileType = NSBitmapImageFileTypeBMP; - } - - NSData *imageData = [bitmapRep representationUsingType:fileType properties:@{}]; - [imageData writeToURL:fileURL atomically:NO]; - } - }]; - - } - - strongSelf->_snapshotter = nil; - }]; -} - -#pragma mark View methods - -- (IBAction)showStyle:(id)sender { - NSInteger tag = -1; - if ([sender isKindOfClass:[NSMenuItem class]]) { - tag = [sender tag]; - } else if ([sender isKindOfClass:[NSPopUpButton class]]) { - tag = [sender selectedTag]; - } - NSURL *styleURL; - switch (tag) { - case 1: - styleURL = [MGLStyle streetsStyleURL]; - break; - case 2: - styleURL = [MGLStyle outdoorsStyleURL]; - break; - case 3: - styleURL = [MGLStyle lightStyleURL]; - break; - case 4: - styleURL = [MGLStyle darkStyleURL]; - break; - case 5: - styleURL = [MGLStyle satelliteStyleURL]; - break; - case 6: - styleURL = [MGLStyle satelliteStreetsStyleURL]; - break; - default: - NSAssert(NO, @"Cannot set style from control with tag %li", (long)tag); - break; - } - [self.undoManager removeAllActionsWithTarget:self]; - self.mapView.styleURL = styleURL; - [self.window.toolbar validateVisibleItems]; -} - -- (IBAction)chooseCustomStyle:(id)sender { - NSAlert *alert = [[NSAlert alloc] init]; - alert.messageText = @"Apply custom style"; - alert.informativeText = @"Enter the URL to a JSON file that conforms to the Mapbox Style Specification, such as a style designed in Mapbox Studio:"; - NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect]; - [textField sizeToFit]; - NSRect textFieldFrame = textField.frame; - textFieldFrame.size.width = 300; - textField.frame = textFieldFrame; - NSURL *savedURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"MBXCustomStyleURL"]; - if (savedURL) { - textField.stringValue = savedURL.absoluteString; - } - alert.accessoryView = textField; - [alert addButtonWithTitle:@"Apply"]; - [alert addButtonWithTitle:@"Cancel"]; - if ([alert runModal] == NSAlertFirstButtonReturn) { - [self.undoManager removeAllActionsWithTarget:self]; - self.mapView.styleURL = [NSURL URLWithString:textField.stringValue]; - [[NSUserDefaults standardUserDefaults] setURL:self.mapView.styleURL forKey:@"MBXCustomStyleURL"]; - [self.window.toolbar validateVisibleItems]; - } -} - -- (IBAction)zoomIn:(id)sender { - [self.mapView setZoomLevel:round(self.mapView.zoomLevel) + 1 animated:YES]; -} - -- (IBAction)zoomOut:(id)sender { - [self.mapView setZoomLevel:round(self.mapView.zoomLevel) - 1 animated:YES]; -} - -- (IBAction)snapToNorth:(id)sender { - [self.mapView setDirection:0 animated:YES]; -} - -- (IBAction)reload:(id)sender { - [self.undoManager removeAllActionsWithTarget:self]; - [self.mapView reloadStyle:sender]; -} - -/** - Show or hide the Layers sidebar. - */ -- (IBAction)toggleLayers:(id)sender { - BOOL isShown = ![self.splitView isSubviewCollapsed:self.splitView.arrangedSubviews.firstObject]; - [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { - context.allowsImplicitAnimation = YES; - [self.splitView setPosition:isShown ? 0 : 100 ofDividerAtIndex:0]; - [self.window.toolbar validateVisibleItems]; - } completionHandler:nil]; -} - -/** - Show or hide the selected layers. - */ -- (IBAction)toggleStyleLayers:(id)sender { - NSInteger clickedRow = self.styleLayersTableView.clickedRow; - NSIndexSet *indices = self.styleLayersTableView.selectedRowIndexes; - if (clickedRow >= 0 && ![indices containsIndex:clickedRow]) { - indices = [NSIndexSet indexSetWithIndex:clickedRow]; - } - [self toggleStyleLayersAtArrangedObjectIndexes:indices]; -} - -- (void)toggleStyleLayersAtArrangedObjectIndexes:(NSIndexSet *)indices { - NSArray<MGLStyleLayer *> *layers = [self.mapView.style.reversedLayers objectsAtIndexes:indices]; - BOOL isVisible = layers.firstObject.visible; - [self.undoManager registerUndoWithTarget:self handler:^(MapDocument * _Nonnull target) { - [target toggleStyleLayersAtArrangedObjectIndexes:indices]; - }]; - - if (!self.undoManager.undoing) { - NSString *actionName; - if (indices.count == 1) { - actionName = [NSString stringWithFormat:@"%@ Layer “%@”", isVisible ? @"Hide" : @"Show", layers.firstObject.identifier]; - } else { - actionName = [NSString stringWithFormat:@"%@ %@ Layers", isVisible ? @"Hide" : @"Show", - [NSNumberFormatter localizedStringFromNumber:@(indices.count) - numberStyle:NSNumberFormatterDecimalStyle]]; - } - [self.undoManager setActionIsDiscardable:YES]; - [self.undoManager setActionName:actionName]; - } - - for (MGLStyleLayer *layer in layers) { - layer.visible = !isVisible; - } - - NSIndexSet *columnIndices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]; - [self.styleLayersTableView reloadDataForRowIndexes:indices columnIndexes:columnIndices]; -} - -- (IBAction)deleteStyleLayers:(id)sender { - NSInteger clickedRow = self.styleLayersTableView.clickedRow; - NSIndexSet *indices = self.styleLayersTableView.selectedRowIndexes; - if (clickedRow >= 0 && ![indices containsIndex:clickedRow]) { - indices = [NSIndexSet indexSetWithIndex:clickedRow]; - } - [self deleteStyleLayersAtArrangedObjectIndexes:indices]; -} - -- (void)insertStyleLayers:(NSArray<MGLStyleLayer *> *)layers atArrangedObjectIndexes:(NSIndexSet *)indices { - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self deleteStyleLayersAtArrangedObjectIndexes:indices]; - }]; - - if (!self.undoManager.undoing) { - NSString *actionName; - if (indices.count == 1) { - actionName = [NSString stringWithFormat:@"Add Layer “%@”", layers.firstObject.identifier]; - } else { - actionName = [NSString stringWithFormat:@"Add %@ Layers", - [NSNumberFormatter localizedStringFromNumber:@(indices.count) numberStyle:NSNumberFormatterDecimalStyle]]; - } - [self.undoManager setActionName:actionName]; - } - - [self.styleLayersArrayController insertObjects:layers atArrangedObjectIndexes:indices]; -} - -- (void)deleteStyleLayersAtArrangedObjectIndexes:(NSIndexSet *)indices { - NSArray<MGLStyleLayer *> *layers = [self.mapView.style.reversedLayers objectsAtIndexes:indices]; - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self insertStyleLayers:layers atArrangedObjectIndexes:indices]; - }]; - - if (!self.undoManager.undoing) { - NSString *actionName; - if (indices.count == 1) { - actionName = [NSString stringWithFormat:@"Delete Layer “%@”", layers.firstObject.identifier]; - } else { - actionName = [NSString stringWithFormat:@"Delete %@ Layers", - [NSNumberFormatter localizedStringFromNumber:@(indices.count) numberStyle:NSNumberFormatterDecimalStyle]]; - } - [self.undoManager setActionName:actionName]; - } - - [self.styleLayersArrayController removeObjectsAtArrangedObjectIndexes:indices]; -} - -- (IBAction)setLabelLanguage:(NSMenuItem *)sender { - _isLocalizingLabels = sender.tag; - [self reload:sender]; -} - -- (void)updateLabels { - [self.mapView.style localizeLabelsIntoLocale:_isLocalizingLabels ? nil : [NSLocale localeWithLocaleIdentifier:@"mul"]]; -} - -- (void)applyPendingState { - if (_inheritedStyleURL) { - self.mapView.styleURL = _inheritedStyleURL; - _inheritedStyleURL = nil; - } - - AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate; - if (appDelegate.pendingStyleURL) { - self.mapView.styleURL = appDelegate.pendingStyleURL; - } - if (appDelegate.pendingCamera) { - if (appDelegate.pendingZoomLevel >= 0) { - self.mapView.zoomLevel = appDelegate.pendingZoomLevel; - appDelegate.pendingCamera.altitude = self.mapView.camera.altitude; - } - self.mapView.camera = appDelegate.pendingCamera; - appDelegate.pendingZoomLevel = -1; - appDelegate.pendingCamera = nil; - } - if (!MGLCoordinateBoundsIsEmpty(appDelegate.pendingVisibleCoordinateBounds)) { - self.mapView.visibleCoordinateBounds = appDelegate.pendingVisibleCoordinateBounds; - appDelegate.pendingVisibleCoordinateBounds = (MGLCoordinateBounds){ { 0, 0 }, { 0, 0 } }; - } - if (appDelegate.pendingDebugMask) { - self.mapView.debugMask = appDelegate.pendingDebugMask; - } - if (appDelegate.pendingMinimumZoomLevel >= 0) { - self.mapView.zoomLevel = MAX(appDelegate.pendingMinimumZoomLevel, self.mapView.zoomLevel); - appDelegate.pendingMaximumZoomLevel = -1; - } - if (appDelegate.pendingMaximumZoomLevel >= 0) { - self.mapView.zoomLevel = MIN(appDelegate.pendingMaximumZoomLevel, self.mapView.zoomLevel); - appDelegate.pendingMaximumZoomLevel = -1; - } - - // Temporarily set the display name to the default center coordinate instead - // of “Untitled” until the binding kicks in. - NSValue *coordinateValue = [NSValue valueWithMGLCoordinate:self.mapView.centerCoordinate]; - NSString *coordinateString = [[NSValueTransformer valueTransformerForName:@"LocationCoordinate2DTransformer"] - transformedValue:coordinateValue]; - - - self.displayName = [NSString stringWithFormat:@"%@ @ %f", coordinateString, _mapView.zoomLevel]; -} - -#pragma mark Debug methods - -- (IBAction)toggleTileBoundaries:(id)sender { - self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask; -} - -- (IBAction)toggleTileInfo:(id)sender { - self.mapView.debugMask ^= MGLMapDebugTileInfoMask; -} - -- (IBAction)toggleTileTimestamps:(id)sender { - self.mapView.debugMask ^= MGLMapDebugTimestampsMask; -} - -- (IBAction)toggleCollisionBoxes:(id)sender { - self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask; -} - -- (IBAction)toggleOverdrawVisualization:(id)sender { - self.mapView.debugMask ^= MGLMapDebugOverdrawVisualizationMask; -} - -- (IBAction)showColorBuffer:(id)sender { - self.mapView.debugMask &= ~MGLMapDebugStencilBufferMask; - self.mapView.debugMask &= ~MGLMapDebugDepthBufferMask; -} - -- (IBAction)showStencilBuffer:(id)sender { - self.mapView.debugMask &= ~MGLMapDebugDepthBufferMask; - self.mapView.debugMask |= MGLMapDebugStencilBufferMask; -} - -- (IBAction)showDepthBuffer:(id)sender { - self.mapView.debugMask &= ~MGLMapDebugStencilBufferMask; - self.mapView.debugMask |= MGLMapDebugDepthBufferMask; -} - -- (IBAction)toggleShowsToolTipsOnDroppedPins:(id)sender { - _showsToolTipsOnDroppedPins = !_showsToolTipsOnDroppedPins; -} - -- (IBAction)toggleRandomizesCursorsOnDroppedPins:(id)sender { - _randomizesCursorsOnDroppedPins = !_randomizesCursorsOnDroppedPins; -} - -- (IBAction)dropManyPins:(id)sender { - [self removeAllAnnotations:sender]; - - NSRect bounds = self.mapView.bounds; - NSMutableArray *annotations = [NSMutableArray array]; - for (CGFloat x = NSMinX(bounds); x < NSMaxX(bounds); x += arc4random_uniform(50)) { - for (CGFloat y = NSMaxY(bounds); y >= NSMinY(bounds); y -= arc4random_uniform(100)) { - [annotations addObject:[self pinAtPoint:NSMakePoint(x, y)]]; - } - } - - [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 - target:self - selector:@selector(dropOneOfManyPins:) - userInfo:annotations - repeats:YES]; -} - -- (void)dropOneOfManyPins:(NSTimer *)timer { - NSMutableArray *annotations = timer.userInfo; - NSUInteger numberOfAnnotationsToAdd = 50; - if (annotations.count < numberOfAnnotationsToAdd) { - numberOfAnnotationsToAdd = annotations.count; - } - NSArray *annotationsToAdd = [annotations subarrayWithRange: - NSMakeRange(0, numberOfAnnotationsToAdd)]; - [self.mapView addAnnotations:annotationsToAdd]; - [annotations removeObjectsInRange:NSMakeRange(0, numberOfAnnotationsToAdd)]; - if (!annotations.count) { - [timer invalidate]; - } -} - -- (IBAction)showAllAnnotations:(id)sender { - [self.mapView showAnnotations:self.mapView.annotations animated:YES]; -} - -- (IBAction)removeAllAnnotations:(id)sender { - [self.mapView removeAnnotations:self.mapView.annotations]; - _isShowingPolygonAndPolylineAnnotations = NO; - _isShowingAnimatedAnnotation = NO; -} - -- (IBAction)startWorldTour:(id)sender { - _isTouringWorld = YES; - - [self removeAllAnnotations:sender]; - NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]); - NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations]; - for (NSUInteger i = 0; i < numberOfAnnotations; i++) { - MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; - annotation.coordinate = WorldTourDestinations[i]; - [annotations addObject:annotation]; - } - [self.mapView addAnnotations:annotations]; - [self continueWorldTourWithRemainingAnnotations:annotations]; -} - -- (void)continueWorldTourWithRemainingAnnotations:(NSMutableArray<MGLPointAnnotation *> *)annotations { - MGLPointAnnotation *nextAnnotation = annotations.firstObject; - if (!nextAnnotation || !_isTouringWorld) { - _isTouringWorld = NO; - return; - } - - [annotations removeObjectAtIndex:0]; - MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate - acrossDistance:0 - pitch:arc4random_uniform(60) - heading:arc4random_uniform(360)]; - __weak MapDocument *weakSelf = self; - [self.mapView flyToCamera:camera completionHandler:^{ - MapDocument *strongSelf = weakSelf; - [strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:) - withObject:annotations - afterDelay:2]; - }]; -} - -- (IBAction)stopWorldTour:(id)sender { - _isTouringWorld = NO; - // Any programmatic viewpoint change cancels outstanding animations. - self.mapView.camera = self.mapView.camera; -} - -- (IBAction)drawPolygonAndPolyLineAnnotations:(id)sender { - - if (_isShowingPolygonAndPolylineAnnotations) { - [self removeAllAnnotations:sender]; - return; - } - - _isShowingPolygonAndPolylineAnnotations = YES; - - // 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 line - 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]; -} - -- (IBAction)drawAnimatedAnnotation:(id)sender { - DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init]; - [self.mapView addAnnotation:annotation]; - - _isShowingAnimatedAnnotation = YES; - - [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 - target:self - selector:@selector(updateAnimatedAnnotation:) - userInfo:annotation - repeats:YES]; -} - - -- (id<MGLAnnotation>)randomOffscreenPointAnnotation { - - NSPredicate *pointAnnotationPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { - return [evaluatedObject isKindOfClass:[MGLPointAnnotation class]]; - }]; - - NSArray *annotations = [self.mapView.annotations filteredArrayUsingPredicate:pointAnnotationPredicate]; - - if (annotations.count == 0) { - return nil; - } - - // NOTE: self.mapView.visibleAnnotations occasionally returns nil - see - // https://github.com/mapbox/mapbox-gl-native/issues/11296 - NSArray *visibleAnnotations = [self.mapView.visibleAnnotations filteredArrayUsingPredicate:pointAnnotationPredicate]; - - NSLog(@"Number of visible point annotations = %ld", visibleAnnotations.count); - - if (visibleAnnotations.count == annotations.count) { - return nil; - } - - NSMutableArray *invisibleAnnotations = [annotations mutableCopy]; - - if (visibleAnnotations.count > 0) { - [invisibleAnnotations removeObjectsInArray:visibleAnnotations]; - } - - // Now pick a random offscreen annotation. - uint32_t index = arc4random_uniform((uint32_t)invisibleAnnotations.count); - return invisibleAnnotations[index]; -} - -- (IBAction)selectOffscreenPointAnnotation:(id)sender { - id<MGLAnnotation> annotation = [self randomOffscreenPointAnnotation]; - if (annotation) { - [self.mapView selectAnnotation:annotation]; - - // Alternative method to select the annotation. These two should do the same thing. - // self.mapView.selectedAnnotations = @[annotation]; - NSAssert(self.mapView.selectedAnnotations.firstObject, @"The annotation was not selected"); - } -} - -- (void)updateAnimatedAnnotation:(NSTimer *)timer { - DroppedPinAnnotation *annotation = timer.userInfo; - double angle = timer.fireDate.timeIntervalSinceReferenceDate; - annotation.coordinate = CLLocationCoordinate2DMake( - sin(angle) * 20, - cos(angle) * 20); -} - -- (IBAction) addAnimatedImageSource:(id)sender { - - MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:@"southeast_0"]; - - MGLCoordinateBounds bounds = { {22.551103322318994, -90.24006072802854}, {36.928147474567794, -75.1441643681673} }; - MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"animated-radar-source" coordinateQuad:MGLCoordinateQuadFromCoordinateBounds(bounds) image:image]; - [self.mapView.style addSource:imageSource]; - - MGLRasterStyleLayer * imageLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"animated-radar-layer" source:imageSource]; - [self.mapView.style addLayer:imageLayer]; - - [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; - - MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:[NSString stringWithFormat:@"southeast_%d", radarSuffix++]]; - [imageSource setValue:image forKey:@"image"]; - - if(radarSuffix > 3) { - radarSuffix = 0 ; - } -} - -- (IBAction)insertCustomStyleLayer:(id)sender { - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self removeCustomStyleLayer:sender]; - }]; - - if (!self.undoManager.isUndoing) { - [self.undoManager setActionName:@"Add Lime Green Layer"]; - } - - LimeGreenStyleLayer *layer = [[LimeGreenStyleLayer alloc] initWithIdentifier:@"mbx-custom"]; - MGLStyleLayer *houseNumberLayer = [self.mapView.style layerWithIdentifier:@"housenum-label"]; - if (houseNumberLayer) { - [self.mapView.style insertLayer:layer belowLayer:houseNumberLayer]; - } else { - [self.mapView.style addLayer:layer]; - } -} - -- (IBAction)removeCustomStyleLayer:(id)sender { - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self insertCustomStyleLayer:sender]; - }]; - - if (!self.undoManager.isUndoing) { - [self.undoManager setActionName:@"Delete Lime Green Layer"]; - } - - MGLStyleLayer *layer = [self.mapView.style layerWithIdentifier:@"mbx-custom"]; - [self.mapView.style removeLayer:layer]; -} - -- (IBAction)insertGraticuleLayer:(id)sender { - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self removeGraticuleLayer:sender]; - }]; - - if (!self.undoManager.isUndoing) { - [self.undoManager setActionName:@"Add Graticule"]; - } - - NSDictionary *sourceOptions = @{ - MGLShapeSourceOptionMaximumZoomLevel:@14, - MGLShapeSourceOptionWrapsCoordinates: @YES, - MGLShapeSourceOptionClipsCoordinates: @YES, - }; - MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule" - options:sourceOptions]; - - source.dataSource = self; - [self.mapView.style addSource:source]; - MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"graticule.lines" - source:source]; - [self.mapView.style addLayer:lineLayer]; - MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"graticule.labels" - source:source]; - labelLayer.text = [NSExpression expressionWithFormat:@"value"]; - [self.mapView.style addLayer:labelLayer]; -} - -- (IBAction)removeGraticuleLayer:(id)sender { - [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { - [self insertGraticuleLayer:sender]; - }]; - - if (!self.undoManager.isUndoing) { - [self.undoManager setActionName:@"Delete Graticule"]; - } - - MGLStyleLayer *layer = [self.mapView.style layerWithIdentifier:@"graticule.lines"]; - [self.mapView.style removeLayer:layer]; - - layer = [self.mapView.style layerWithIdentifier:@"graticule.labels"]; - [self.mapView.style removeLayer:layer]; - - MGLSource *source = [self.mapView.style sourceWithIdentifier:@"graticule"]; - [self.mapView.style removeSource:source]; -} - -- (IBAction)enhanceTerrain:(id)sender { - // Find all the identifiers of Mapbox Terrain sources used in the style. - NSMutableSet *terrainSourceIdentifiers = [NSMutableSet set]; - for (MGLVectorTileSource *source in self.mapView.style.sources) { - if (![source isKindOfClass:[MGLVectorTileSource class]]) { - continue; - } - - if (source.mapboxTerrain) { - [terrainSourceIdentifiers addObject:source.identifier]; - } - } - - // Find and remove all the style layers using those sources. - NSUInteger hillshadeIndex = NSNotFound; - NSEnumerator *layerEnumerator = self.mapView.style.layers.objectEnumerator; - MGLVectorStyleLayer *layer; - for (NSUInteger i = 0; (layer = layerEnumerator.nextObject); i++) { - if (![layer isKindOfClass:[MGLVectorStyleLayer class]]) { - continue; - } - - if ([terrainSourceIdentifiers containsObject:layer.sourceIdentifier] - && [layer.sourceLayerIdentifier isEqualToString:@"hillshade"]) { - hillshadeIndex = i; - [self.mapView.style removeLayer:layer]; - } - } - - if (hillshadeIndex == NSNotFound) { - return; - } - - // Add a Mapbox Terrain-RGB source. - NSURL *terrainRGBURL = [NSURL URLWithString:@"mapbox://mapbox.terrain-rgb"]; - MGLRasterDEMSource *terrainRGBSource = [[MGLRasterDEMSource alloc] initWithIdentifier:@"terrain" configurationURL:terrainRGBURL]; - [self.mapView.style addSource:terrainRGBSource]; - - // Insert a hillshade layer where the Mapbox Terrain–based layers were. - MGLHillshadeStyleLayer *hillshadeLayer = [[MGLHillshadeStyleLayer alloc] initWithIdentifier:@"hillshade" source:terrainRGBSource]; - [self.mapView.style insertLayer:hillshadeLayer atIndex:hillshadeIndex]; -} - -#pragma mark Offline packs - -- (IBAction)addOfflinePack:(id)sender { - self.offlinePackNameField.stringValue = @""; - self.offlinePackNameField.placeholderString = MGLStringFromCoordinateBounds(self.mapView.visibleCoordinateBounds); - self.minimumOfflinePackZoomLevelField.doubleValue = floor(self.mapView.zoomLevel); - self.maximumOfflinePackZoomLevelField.doubleValue = ceil(self.mapView.maximumZoomLevel); - self.minimumOfflinePackZoomLevelFormatter.minimum = @(floor(self.mapView.minimumZoomLevel)); - self.maximumOfflinePackZoomLevelFormatter.minimum = @(floor(self.mapView.minimumZoomLevel)); - self.minimumOfflinePackZoomLevelFormatter.maximum = @(ceil(self.mapView.maximumZoomLevel)); - self.maximumOfflinePackZoomLevelFormatter.maximum = @(ceil(self.mapView.maximumZoomLevel)); - - id ideographicFontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographicFontFamilyName"]; - self.includesIdeographicGlyphsBox.state = ([ideographicFontFamilyName isKindOfClass:[NSNumber class]] && ![ideographicFontFamilyName boolValue]) ? NSOffState : NSOnState; - [self.addOfflinePackWindow makeFirstResponder:self.offlinePackNameField]; - - __weak __typeof__(self) weakSelf = self; - [self.window beginSheet:self.addOfflinePackWindow completionHandler:^(NSModalResponse returnCode) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf || returnCode != NSModalResponseOK) { - return; - } - - id <MGLOfflineRegion> region = - [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:strongSelf.mapView.styleURL - bounds:strongSelf.mapView.visibleCoordinateBounds - fromZoomLevel:strongSelf.minimumOfflinePackZoomLevelField.integerValue - toZoomLevel:strongSelf.maximumOfflinePackZoomLevelField.integerValue]; - region.includesIdeographicGlyphs = strongSelf.includesIdeographicGlyphsBox.state == NSOnState; - NSString *name = strongSelf.offlinePackNameField.stringValue; - if (!name.length) { - name = strongSelf.offlinePackNameField.placeholderString; - } - NSData *context = [[NSValueTransformer valueTransformerForName:@"OfflinePackNameValueTransformer"] reverseTransformedValue:name]; - [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) { - if (error) { - [[NSAlert alertWithError:error] runModal]; - } else { - [(AppDelegate *)NSApp.delegate watchOfflinePack:pack]; - [pack resume]; - } - }]; - }]; -} - -- (IBAction)confirmAddingOfflinePack:(id)sender { - [self.window endSheet:self.addOfflinePackWindow returnCode:[sender tag] ? NSModalResponseOK : NSModalResponseCancel]; -} - -#pragma mark Mouse events - -- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer { - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - NSPoint location = [gestureRecognizer locationInView:self.mapView]; - if (!NSPointInRect([gestureRecognizer locationInView:self.mapView.compass], self.mapView.compass.bounds) - && !NSPointInRect([gestureRecognizer locationInView:self.mapView.zoomControls], self.mapView.zoomControls.bounds) - && !NSPointInRect([gestureRecognizer locationInView:self.mapView.attributionView], self.mapView.attributionView.bounds)) { - [self dropPinAtPoint:location]; - } - } -} - -- (IBAction)manipulateStyle:(id)sender { - MGLTransition transition = { .duration = 5, .delay = 1 }; - self.mapView.style.transition = transition; - - MGLStyleLayer *waterLayer = [self.mapView.style layerWithIdentifier:@"water"]; - NSExpression *colorExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{ - @0.0: [NSColor redColor], - @10.0: [NSColor yellowColor], - @20.0: [NSColor blackColor], - }]; - - if ([waterLayer respondsToSelector:@selector(fillColor)]) { - [waterLayer setValue:colorExpression forKey:@"fillColor"]; - } else if ([waterLayer respondsToSelector:@selector(lineColor)]) { - [waterLayer setValue:colorExpression forKey:@"lineColor"]; - } - - 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]; - - MGLCircleStyleLayer *circleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"test" source:source]; - circleLayer.circleColor = [NSExpression expressionForConstantValue:[NSColor greenColor]]; - circleLayer.circleRadius = [NSExpression expressionForConstantValue:@40]; -// fillLayer.predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"type", @"park"]; - [self.mapView.style addLayer:circleLayer]; - - MGLSource *streetsSource = [self.mapView.style sourceWithIdentifier:@"composite"]; - if (streetsSource) { - NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; - [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; - - MGLSymbolStyleLayer *theaterLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"theaters" source:streetsSource]; - theaterLayer.sourceLayerIdentifier = @"poi_label"; - theaterLayer.predicate = [NSPredicate predicateWithFormat:@"maki == 'theatre'"]; - theaterLayer.iconImageName = [NSExpression expressionForConstantValue:NSImageNameIChatTheaterTemplate]; - theaterLayer.iconScale = [NSExpression expressionForConstantValue:@2]; - theaterLayer.iconColor = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{ - @16.0: [NSColor redColor], - @18.0: [NSColor yellowColor], - @20.0: [NSColor blackColor], - }]; - [self.mapView.style addLayer:theaterLayer]; - } - - NSURL *imageURL = [NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar.gif"]; - MGLCoordinateQuad quad = { {46.437, -80.425}, - {37.936, -80.425}, - {37.936, -71.516}, - {46.437, -71.516} }; - MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"radar-source" coordinateQuad:quad URL:imageURL]; - [self.mapView.style addSource:imageSource]; - - MGLRasterStyleLayer * imageLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"radar-layer" source:imageSource]; - [self.mapView.style addLayer:imageLayer]; -} - -- (IBAction)dropPin:(NSMenuItem *)sender { - [self dropPinAtPoint:_mouseLocationForMapViewContextMenu]; -} - -- (void)dropPinAtPoint:(NSPoint)point { - DroppedPinAnnotation *annotation = [self pinAtPoint:point]; - [self.mapView addAnnotation:annotation]; - [self.mapView selectAnnotation:annotation]; -} - -- (DroppedPinAnnotation *)pinAtPoint:(NSPoint)point { - NSArray *features = [self.mapView visibleFeaturesAtPoint:point]; - NSString *title; - NSString *description; - for (id <MGLFeature> feature in features) { - if (!title) { - title = [feature attributeForKey:@"title"] ?: [feature attributeForKey:@"name_en"] ?: [feature attributeForKey:@"name"]; - - // simplestyle-spec defines a “description” attribute in HTML format. - NSString *featureDescription = [feature attributeForKey:@"description"]; - if (featureDescription) { - // Convert HTML to plain text, because the default popover is - // bound to an NSString-typed property. - NSData *data = [featureDescription dataUsingEncoding:NSUTF8StringEncoding]; - description = [[NSAttributedString alloc] initWithHTML:data options:@{} documentAttributes:nil].string; - } - - if (title) { - break; - } - } - } - - DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init]; - annotation.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; - annotation.title = title ?: @"Dropped Pin"; - annotation.note = description; - _spellOutNumberFormatter.numberStyle = NSNumberFormatterSpellOutStyle; - if (_showsToolTipsOnDroppedPins) { - NSString *formattedNumber = [_spellOutNumberFormatter stringFromNumber:@(++_droppedPinCounter)]; - annotation.toolTip = formattedNumber; - } - return annotation; -} - -- (IBAction)removePin:(NSMenuItem *)sender { - [self removePinAtPoint:_mouseLocationForMapViewContextMenu]; -} - -- (void)removePinAtPoint:(NSPoint)point { - [self.mapView removeAnnotation:[self.mapView annotationAtPoint:point]]; -} - -- (IBAction)selectFeatures:(id)sender { - [self selectFeaturesAtPoint:_mouseLocationForMapViewContextMenu]; -} - -- (void)selectFeaturesAtPoint:(NSPoint)point { - NSArray *features = [self.mapView visibleFeaturesAtPoint:point]; - NSArray *flattenedFeatures = MBXFlattenedShapes(features); - [self.mapView addAnnotations:flattenedFeatures]; -} - -#pragma mark User interface validation - -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - if (menuItem.action == @selector(showStyle:)) { - NSURL *styleURL = self.mapView.styleURL; - NSCellStateValue state; - switch (menuItem.tag) { - case 1: - state = [styleURL isEqual:[MGLStyle streetsStyleURL]]; - break; - case 2: - state = [styleURL isEqual:[MGLStyle outdoorsStyleURL]]; - break; - case 3: - state = [styleURL isEqual:[MGLStyle lightStyleURL]]; - break; - case 4: - state = [styleURL isEqual:[MGLStyle darkStyleURL]]; - break; - case 5: - state = [styleURL isEqual:[MGLStyle satelliteStyleURL]]; - break; - case 6: - state = [styleURL isEqual:[MGLStyle satelliteStreetsStyleURL]]; - break; - default: - return NO; - } - menuItem.state = state; - return YES; - } - if (menuItem.action == @selector(chooseCustomStyle:)) { - menuItem.state = self.indexOfStyleInToolbarItem == NSNotFound; - return YES; - } - if (menuItem.action == @selector(zoomIn:)) { - return self.mapView.zoomLevel < self.mapView.maximumZoomLevel; - } - if (menuItem.action == @selector(zoomOut:)) { - return self.mapView.zoomLevel > self.mapView.minimumZoomLevel; - } - if (menuItem.action == @selector(snapToNorth:)) { - return self.mapView.direction != 0; - } - if (menuItem.action == @selector(reload:)) { - return YES; - } - if (menuItem.action == @selector(toggleLayers:)) { - BOOL isShown = ![self.splitView isSubviewCollapsed:self.splitView.arrangedSubviews.firstObject]; - menuItem.title = isShown ? @"Hide Layers" : @"Show Layers"; - return YES; - } - if (menuItem.action == @selector(toggleStyleLayers:)) { - NSInteger row = self.styleLayersTableView.clickedRow; - if (row == -1) { - row = self.styleLayersTableView.selectedRow; - } - if (row == -1) { - menuItem.title = @"Show"; - } else { - BOOL isVisible = self.mapView.style.reversedLayers[row].visible; - menuItem.title = isVisible ? @"Hide" : @"Show"; - } - return row != -1; - } - if (menuItem.action == @selector(deleteStyleLayers:)) { - return self.styleLayersTableView.clickedRow >= 0 || self.styleLayersTableView.selectedRow >= 0; - } - if (menuItem.action == @selector(setLabelLanguage:)) { - menuItem.state = menuItem.tag == _isLocalizingLabels ? NSOnState: NSOffState; - if (menuItem.tag) { - NSLocale *locale = [NSLocale localeWithLocaleIdentifier:[NSBundle mainBundle].developmentLocalization]; - NSString *preferredLanguage = [MGLVectorTileSource preferredMapboxStreetsLanguage] ?: @"en"; - menuItem.title = [locale displayNameForKey:NSLocaleIdentifier value:preferredLanguage]; - } - return YES; - } - if (menuItem.action == @selector(manipulateStyle:)) { - return YES; - } - if (menuItem.action == @selector(dropPin:)) { - id <MGLAnnotation> annotationUnderCursor = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu]; - menuItem.hidden = annotationUnderCursor != nil; - return YES; - } - if (menuItem.action == @selector(removePin:)) { - id <MGLAnnotation> annotationUnderCursor = [self.mapView annotationAtPoint:_mouseLocationForMapViewContextMenu]; - menuItem.hidden = annotationUnderCursor == nil; - return YES; - } - if (menuItem.action == @selector(selectFeatures:)) { - return YES; - } - if (menuItem.action == @selector(toggleTileBoundaries:)) { - BOOL isShown = self.mapView.debugMask & MGLMapDebugTileBoundariesMask; - menuItem.title = isShown ? @"Hide Tile Boundaries" : @"Show Tile Boundaries"; - return YES; - } - if (menuItem.action == @selector(toggleTileInfo:)) { - BOOL isShown = self.mapView.debugMask & MGLMapDebugTileInfoMask; - menuItem.title = isShown ? @"Hide Tile Info" : @"Show Tile Info"; - return YES; - } - if (menuItem.action == @selector(toggleTileTimestamps:)) { - BOOL isShown = self.mapView.debugMask & MGLMapDebugTimestampsMask; - menuItem.title = isShown ? @"Hide Tile Timestamps" : @"Show Tile Timestamps"; - return YES; - } - if (menuItem.action == @selector(toggleCollisionBoxes:)) { - BOOL isShown = self.mapView.debugMask & MGLMapDebugCollisionBoxesMask; - menuItem.title = isShown ? @"Hide Collision Boxes" : @"Show Collision Boxes"; - return YES; - } - if (menuItem.action == @selector(toggleOverdrawVisualization:)) { - BOOL isShown = self.mapView.debugMask & MGLMapDebugOverdrawVisualizationMask; - menuItem.title = isShown ? @"Hide Overdraw Visualization" : @"Show Overdraw Visualization"; - return YES; - } - if (menuItem.action == @selector(showColorBuffer:)) { - BOOL enabled = self.mapView.debugMask & (MGLMapDebugStencilBufferMask | MGLMapDebugDepthBufferMask); - menuItem.state = enabled ? NSOffState : NSOnState; - return YES; - } - if (menuItem.action == @selector(showStencilBuffer:)) { - BOOL enabled = self.mapView.debugMask & MGLMapDebugStencilBufferMask; - menuItem.state = enabled ? NSOnState : NSOffState; - return YES; - } - if (menuItem.action == @selector(showDepthBuffer:)) { - BOOL enabled = self.mapView.debugMask & MGLMapDebugDepthBufferMask; - menuItem.state = enabled ? NSOnState : NSOffState; - return YES; - } - if (menuItem.action == @selector(toggleShowsToolTipsOnDroppedPins:)) { - BOOL isShown = _showsToolTipsOnDroppedPins; - menuItem.title = isShown ? @"Hide Tooltips on Dropped Pins" : @"Show Tooltips on Dropped Pins"; - return YES; - } - if (menuItem.action == @selector(toggleRandomizesCursorsOnDroppedPins:)) { - BOOL isRandom = _randomizesCursorsOnDroppedPins; - menuItem.title = isRandom ? @"Use Default Cursor for Dropped Pins" : @"Use Random Cursors for Dropped Pins"; - return _showsToolTipsOnDroppedPins; - } - if (menuItem.action == @selector(dropManyPins:)) { - return YES; - } - if (menuItem.action == @selector(drawPolygonAndPolyLineAnnotations:)) { - return !_isShowingPolygonAndPolylineAnnotations; - } - if (menuItem.action == @selector(drawAnimatedAnnotation:)) { - return !_isShowingAnimatedAnnotation; - } - if (menuItem.action == @selector(addAnimatedImageSource:)) { - return YES; - } - if (menuItem.action == @selector(insertCustomStyleLayer:)) { - return ![self.mapView.style layerWithIdentifier:@"mbx-custom"]; - } - if (menuItem.action == @selector(insertGraticuleLayer:)) { - return ![self.mapView.style sourceWithIdentifier:@"graticule"]; - } - if (menuItem.action == @selector(selectOffscreenPointAnnotation:)) { - return YES; - } - if (menuItem.action == @selector(showAllAnnotations:) || menuItem.action == @selector(removeAllAnnotations:)) { - return self.mapView.annotations.count > 0; - } - if (menuItem.action == @selector(enhanceTerrain:)) { - return YES; - } - if (menuItem.action == @selector(startWorldTour:)) { - return !_isTouringWorld; - } - if (menuItem.action == @selector(stopWorldTour:)) { - return _isTouringWorld; - } - if (menuItem.action == @selector(addOfflinePack:)) { - NSURL *styleURL = self.mapView.styleURL; - return !styleURL.isFileURL; - } - if (menuItem.action == @selector(giveFeedback:)) { - return YES; - } - if (menuItem.action == @selector(import:)) { - return YES; - } - if (menuItem.action == @selector(takeSnapshot:)) { - return !(_snapshotter && [_snapshotter isLoading]); - } - return NO; -} - -- (NSUInteger)indexOfStyleInToolbarItem { - if (![MGLAccountManager accessToken]) { - return NSNotFound; - } - - NSArray *styleURLs = @[ - [MGLStyle streetsStyleURL], - [MGLStyle outdoorsStyleURL], - [MGLStyle lightStyleURL], - [MGLStyle darkStyleURL], - [MGLStyle satelliteStyleURL], - [MGLStyle satelliteStreetsStyleURL], - ]; - return [styleURLs indexOfObject:self.mapView.styleURL]; -} - -- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem { - if (!self.mapView) { - return NO; - } - - SEL action = toolbarItem.action; - if (action == @selector(showShareMenu:)) { - [(NSButton *)toolbarItem.view sendActionOn:NSLeftMouseDownMask]; - if (![MGLAccountManager accessToken]) { - return NO; - } - NSURL *styleURL = self.mapView.styleURL; - return ([styleURL.scheme isEqualToString:@"mapbox"] - && [styleURL.pathComponents.firstObject isEqualToString:@"styles"]); - } - if (action == @selector(showStyle:)) { - NSPopUpButton *popUpButton = (NSPopUpButton *)toolbarItem.view; - NSInteger index = self.indexOfStyleInToolbarItem; - if (index == NSNotFound) { - index = -1; - } - [popUpButton selectItemAtIndex:index]; - if (index == -1) { - NSString *name = self.mapView.style.name; - popUpButton.title = name ?: @"Custom"; - } - } - if (action == @selector(toggleLayers:)) { - BOOL isShown = ![self.splitView isSubviewCollapsed:self.splitView.arrangedSubviews.firstObject]; - [(NSButton *)toolbarItem.view setState:isShown ? NSOnState : NSOffState]; - } - return NO; -} - -#pragma mark NSSharingServicePickerDelegate methods - -- (NSArray<NSSharingService *> *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray<NSSharingService *> *)proposedServices { - NSURL *shareURL = self.shareURL; - NSURL *browserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:shareURL]; - NSImage *browserIcon = [[NSWorkspace sharedWorkspace] iconForFile:browserURL.path]; - NSString *browserName = [[NSFileManager defaultManager] displayNameAtPath:browserURL.path]; - NSString *browserServiceName = [NSString stringWithFormat:@"Open in %@", browserName]; - - NSSharingService *browserService = [[NSSharingService alloc] initWithTitle:browserServiceName - image:browserIcon - alternateImage:nil - handler:^{ - [[NSWorkspace sharedWorkspace] openURL:self.shareURL]; - }]; - - NSMutableArray *sharingServices = [proposedServices mutableCopy]; - [sharingServices insertObject:browserService atIndex:0]; - return sharingServices; -} - -#pragma mark NSMenuDelegate methods - -- (void)menuWillOpen:(NSMenu *)menu { - if (menu == self.mapViewContextMenu) { - _mouseLocationForMapViewContextMenu = [self.window.contentView convertPoint:self.window.mouseLocationOutsideOfEventStream - toView:self.mapView]; - } -} - -#pragma mark NSSplitViewDelegate methods - -- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview { - return subview != self.mapView; -} - -- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex { - return YES; -} - -#pragma mark MGLMapViewDelegate methods - -- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { - [self updateLabels]; -} - -- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation { - return YES; -} - -- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation { - MGLAnnotationImage *annotationImage = [self.mapView dequeueReusableAnnotationImageWithIdentifier:MGLDroppedPinAnnotationImageIdentifier]; - if (!annotationImage) { - NSString *imagePath = [[NSBundle bundleForClass:[MGLMapView class]] - pathForResource:@"default_marker" ofType:@"pdf"]; - NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath]; - NSRect alignmentRect = image.alignmentRect; - alignmentRect.origin.y = NSMidY(alignmentRect); - alignmentRect.size.height /= 2; - image.alignmentRect = alignmentRect; - annotationImage = [MGLAnnotationImage annotationImageWithImage:image - reuseIdentifier:MGLDroppedPinAnnotationImageIdentifier]; - } - if (_randomizesCursorsOnDroppedPins) { - NSArray *cursors = @[ - [NSCursor IBeamCursor], - [NSCursor crosshairCursor], - [NSCursor pointingHandCursor], - [NSCursor disappearingItemCursor], - [NSCursor IBeamCursorForVerticalLayout], - [NSCursor operationNotAllowedCursor], - [NSCursor dragLinkCursor], - [NSCursor dragCopyCursor], - [NSCursor contextualMenuCursor], - ]; - annotationImage.cursor = cursors[arc4random_uniform((uint32_t)cursors.count) % cursors.count]; - } else { - annotationImage.cursor = nil; - } - return annotationImage; -} - -- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation { - if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) { - DroppedPinAnnotation *droppedPin = (DroppedPinAnnotation *)annotation; - [droppedPin resume]; - } -} - -- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation { - if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) { - DroppedPinAnnotation *droppedPin = (DroppedPinAnnotation *)annotation; - [droppedPin pause]; - } -} - -- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation { - return 0.8; -} - -#pragma mark - MGLComputedShapeSourceDataSource -- (NSArray<id <MGLFeature>>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { - double gridSpacing; - if(zoom >= 13) { - gridSpacing = 0.01; - } else if(zoom >= 11) { - gridSpacing = 0.05; - } else if(zoom == 10) { - gridSpacing = .1; - } else if(zoom == 9) { - gridSpacing = 0.25; - } else if(zoom == 8) { - gridSpacing = 0.5; - } else if (zoom >= 6) { - gridSpacing = 1; - } else if(zoom == 5) { - gridSpacing = 2; - } else if(zoom >= 4) { - gridSpacing = 5; - } else if(zoom == 2) { - gridSpacing = 10; - } else { - gridSpacing = 20; - } - - NSMutableArray <id <MGLFeature>> * features = [NSMutableArray array]; - CLLocationCoordinate2D coords[2]; - - for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { - coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); - coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); - MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; - feature.attributes = @{@"value": @(y)}; - [features addObject:feature]; - } - - for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { - coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); - coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); - MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; - feature.attributes = @{@"value": @(x)}; - [features addObject:feature]; - } - - return features; -} - -@end - -@interface ValidatedToolbarItem : NSToolbarItem - -@end - -@implementation ValidatedToolbarItem - -- (void)validate { - [(MapDocument *)self.toolbar.delegate validateToolbarItem:self]; -} - -@end |