diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 63ca0ea1f69..a763e99d55d 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -72,6 +72,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, + MBXSettingsRuntimeStylingCustomLatLonGrid, }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @@ -105,7 +106,8 @@ @implementation MBXSpriteBackedAnnotation @interface MBXViewController () + MGLMapViewDelegate, + MGLComputedShapeSourceDataSource> @property (nonatomic) IBOutlet MGLMapView *mapView; @@ -342,6 +344,7 @@ - (void)dismissSettings:(__unused id)sender [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", + @"Add Custom Lat/Lon Grid", ]]; break; case MBXSettingsMiscellaneous: @@ -581,6 +584,9 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsRuntimeStylingDDSPolygon: [self stylePolygonWithDDS]; break; + case MBXSettingsRuntimeStylingCustomLatLonGrid: + [self addLatLonGrid]; + break; default: NSAssert(NO, @"All runtime styling setting rows should be implemented"); break; @@ -1330,6 +1336,21 @@ - (void)stylePolygonWithDDS { [self.mapView.style addLayer:fillStyleLayer]; } +- (void)addLatLonGrid +{ + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"latlon" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"latlonlines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"latlonlabels" + source:source]; + labelLayer.text = [MGLStyleValue valueWithRawValue:@"{value}"]; + [self.mapView.style addLayer:labelLayer]; +} + - (void)styleLabelLanguageForLayersNamed:(NSArray *)layers { _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; @@ -1811,4 +1832,52 @@ - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { } } +#pragma mark - MGLComputedShapeSourceDataSource + +- (NSArray>*)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 > * 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 diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 941bed21368..4f524df4d40 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -544,6 +544,12 @@ + + + + + + diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 39055d74478..a2e55a9e55e 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -49,7 +49,7 @@ return flattenedShapes; } -@interface MapDocument () +@interface MapDocument () @property (weak) IBOutlet NSArrayController *styleLayersArrayController; @property (weak) IBOutlet NSTableView *styleLayersTableView; @@ -639,6 +639,47 @@ - (IBAction)removeCustomStyleLayer:(id)sender { [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 Layer"]; + } + + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + 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 = [MGLStyleValue valueWithRawValue:@"{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 Layer"]; + } + + 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]; +} + #pragma mark Offline packs - (IBAction)addOfflinePack:(id)sender { @@ -926,6 +967,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { 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(showAllAnnotations:) || menuItem.action == @selector(removeAllAnnotations:)) { return self.mapView.annotations.count > 0; } @@ -1095,6 +1139,53 @@ - (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)ann return 0.8; } +#pragma mark - MGLComputedShapeSourceDataSource +- (NSArray>*)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 > * 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