Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
Annotation image deletion
Browse files Browse the repository at this point in the history
Added an API for deleting unused annotation images’ images. When you nil out the image of an MGLAnnotationImage, MGLMapView deletes the sprite from the style and recreates any annotation associated with the MGLAnnotationImage instance; the MGLAnnotationImage’s falls back to SDK’s default annotation image.

In iosapp, deselecting an annotation resets its image to the default; deselecting it again restores the image.

ref #3185
  • Loading branch information
1ec5 committed Feb 5, 2016
1 parent fa21051 commit e0199a7
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 70 deletions.
2 changes: 1 addition & 1 deletion include/mbgl/ios/MGLAnnotationImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Getting and Setting Attributes

/** The image to be displayed for the annotation. */
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong, nullable) UIImage *image;

/**
The string that identifies that this annotation image is reusable. (read-only)
Expand Down
93 changes: 56 additions & 37 deletions ios/app/MBXViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ - (void)showSettings
@"Add Test Shapes",
@"Start World Tour",
@"Add Custom Callout Point",
@"Reset Annotation Images",
@"Remove Annotations",
(_isShowingCustomStyleLayer
? @"Hide Custom Style Layer"
Expand Down Expand Up @@ -559,52 +560,70 @@ - (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotati
if (!title.length) return nil;
NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2];

UIColor *color;
MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters];

// make every tenth annotation blue
if ([lastTwoCharacters hasSuffix:@"0"]) {
color = [UIColor blueColor];
} else {
color = [UIColor redColor];
}

MGLAnnotationImage *image = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters];

if ( ! image)
if ( ! annotationImage)
{
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:lastTwoCharacters 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];

image = [MGLAnnotationImage annotationImageWithImage:UIGraphicsGetImageFromCurrentImageContext() reuseIdentifier:lastTwoCharacters];
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]]) image.enabled = NO;

UIGraphicsEndImageContext();
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;
}

- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id<MGLAnnotation>)annotation {
NSString *title = [(MGLPointAnnotation *)annotation title];
if ( ! title.length)
{
return;
}
NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2];
MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters];
annotationImage.image = annotationImage.image ? nil : [self imageWithText:lastTwoCharacters backgroundColor:[UIColor grayColor]];
}

- (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id <MGLAnnotation>)annotation
{
return YES;
Expand Down
2 changes: 2 additions & 0 deletions platform/ios/src/MGLAnnotationImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
@interface MGLAnnotationImage ()

@property (nonatomic, strong) NSString *reuseIdentifier;
@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;

@property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate;

@end
Expand Down
3 changes: 3 additions & 0 deletions platform/ios/src/MGLAnnotationImage_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ NS_ASSUME_NONNULL_BEGIN

@interface MGLAnnotationImage (Private)

/// Unique identifier of the sprite image used by the style to represent the receiver’s `image`.
@property (nonatomic, strong, nullable) NSString *styleIconIdentifier;

@property (nonatomic, weak) id<MGLAnnotationImageDelegate> delegate;

@end
Expand Down
142 changes: 110 additions & 32 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,8 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
class MGLAnnotationContext {
public:
id <MGLAnnotation> annotation;
/// mbgl-given identifier for the annotation image used by this annotation.
/// Based on the annotation image’s reusable identifier.
NSString *symbolIdentifier;
/// The annotation’s image’s reuse identifier.
NSString *imageReuseIdentifier;
};

#pragma mark - Private -
Expand Down Expand Up @@ -2379,12 +2378,18 @@ - (void)addAnnotation:(id <MGLAnnotation>)annotation
}

- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations
{
[self addAnnotations:annotations withAnnotationImage:nil];
}

- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations withAnnotationImage:(MGLAnnotationImage *)explicitAnnotationImage
{
if ( ! annotations) return;
[self willChangeValueForKey:@"annotations"];

std::vector<mbgl::PointAnnotation> points;
std::vector<mbgl::ShapeAnnotation> shapes;
NSMutableArray *annotationImages = [NSMutableArray arrayWithCapacity:annotations.count];

BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];

Expand All @@ -2398,31 +2403,32 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations
}
else
{
MGLAnnotationImage *annotationImage = delegateImplementsImageForPoint ? [self.delegate mapView:self imageForAnnotation:annotation] : nil;
MGLAnnotationImage *annotationImage = explicitAnnotationImage;
if ( ! annotationImage && delegateImplementsImageForPoint)
{
annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
}
if ( ! annotationImage)
{
annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
}
if ( ! annotationImage)
{
// Create a default annotation image that depicts a round pin
// rising from the center, with a shadow slightly below center.
// The alignment rect therefore excludes the bottom half.
UIImage *defaultAnnotationImage = [MGLMapView resourceImageNamed:MGLDefaultStyleMarkerSymbolName];
defaultAnnotationImage = [defaultAnnotationImage imageWithAlignmentRectInsets:
UIEdgeInsetsMake(0, 0, defaultAnnotationImage.size.height / 2, 0)];
annotationImage = [MGLAnnotationImage annotationImageWithImage:defaultAnnotationImage
reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
annotationImage = self.defaultAnnotationImage;
}

NSString *symbolName = annotationImage.styleIconIdentifier;
if ( ! symbolName)
{
symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
annotationImage.styleIconIdentifier = symbolName;
}

if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier])
{
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
[self installAnnotationImage:annotationImage];
annotationImage.delegate = self;
}

NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
[annotationImages addObject:annotationImage];

points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : "");
}
Expand All @@ -2434,9 +2440,12 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations

for (size_t i = 0; i < pointAnnotationTags.size(); ++i)
{
MGLAnnotationImage *annotationImage = annotationImages[i];
annotationImage.styleIconIdentifier = @(points[i].icon.c_str());

MGLAnnotationContext context;
context.annotation = annotations[i];
context.symbolIdentifier = @(points[i].icon.c_str());
context.imageReuseIdentifier = annotationImage.reuseIdentifier;
_annotationContextsByAnnotationTag[pointAnnotationTags[i]] = context;
}
}
Expand All @@ -2456,6 +2465,20 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations
[self didChangeValueForKey:@"annotations"];
}

/// Initialize and return a default annotation image that depicts a round pin
/// rising from the center, with a shadow slightly below center. The alignment
/// rect therefore excludes the bottom half.
- (MGLAnnotationImage *)defaultAnnotationImage
{
UIImage *image = [MGLMapView resourceImageNamed:MGLDefaultStyleMarkerSymbolName];
image = [image imageWithAlignmentRectInsets:
UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)];
MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image
reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
return annotationImage;
}

- (double)alphaForShapeAnnotation:(MGLShape *)annotation
{
if (_delegateHasAlphasForShapeAnnotations)
Expand Down Expand Up @@ -2492,6 +2515,10 @@ - (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation

- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage
{
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
annotationImage.delegate = self;

// retrieve pixels
CGImageRef image = annotationImage.image.CGImage;
size_t width = CGImageGetWidth(image);
Expand All @@ -2512,8 +2539,7 @@ - (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage
float(annotationImage.image.scale));

// sprite upload
NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
_mbglMap->addAnnotationIcon(symbolName.UTF8String, cSpriteImage);
_mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);

// Create a slop area with a “radius” equal in size to the annotation
// image’s alignment rect, allowing the eventual tap to be on any point
Expand Down Expand Up @@ -2601,12 +2627,6 @@ - (void)removeOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays

- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier
{
// This prefix is used to avoid collisions with style-defined sprites in
// mbgl, but reusable identifiers are never prefixed.
if ([identifier hasPrefix:MGLAnnotationSpritePrefix])
{
identifier = [identifier substringFromIndex:MGLAnnotationSpritePrefix.length];
}
return self.annotationImagesByIdentifier[identifier];
}

Expand Down Expand Up @@ -2641,6 +2661,9 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);

MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
UIImage *fallbackImage = fallbackAnnotationImage.image;

// Filter out any annotation whose image is unselectable or for which
// hit testing fails.
std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag)
Expand All @@ -2654,10 +2677,11 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
return true;
}

UIImage *image = annotationImage.image ? annotationImage.image : fallbackImage;

// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
CGRect annotationRect = [self frameOfImage:annotationImage.image
centeredAtCoordinate:annotation.coordinate];
CGRect annotationRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});
}
Expand Down Expand Up @@ -2895,6 +2919,10 @@ - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
}
UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
if ( ! image)
{
image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image;
}
if ( ! image)
{
return CGRectZero;
}
Expand Down Expand Up @@ -2923,7 +2951,7 @@ - (MGLAnnotationImage *)imageOfAnnotationWithTag:(MGLAnnotationTag)annotationTag
return nil;
}

NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).symbolIdentifier;
NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;

return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
Expand Down Expand Up @@ -2988,10 +3016,60 @@ - (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations edgePaddi

- (void)annotationImageNeedsRedisplay:(MGLAnnotationImage *)annotationImage
{
// remove sprite
NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
_mbglMap->removeAnnotationIcon(symbolName.UTF8String);
[self installAnnotationImage:annotationImage];
NSString *reuseIdentifier = annotationImage.reuseIdentifier;
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
NSString *fallbackReuseIdentifier = MGLDefaultStyleMarkerSymbolName;
NSString *fallbackIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier];

// Remove the old icon from the style.
if ( ! [iconIdentifier isEqualToString:fallbackIconIdentifier]) {
_mbglMap->removeAnnotationIcon(iconIdentifier.UTF8String);
}

if (annotationImage.image)
{
// Add the new icon to the style.
annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
[self installAnnotationImage:annotationImage];

if ([iconIdentifier isEqualToString:fallbackIconIdentifier])
{
// Remove any annotations associated with the annotation image.
NSMutableArray *annotationsToRecreate = [NSMutableArray array];
for (auto &pair : _annotationContextsByAnnotationTag)
{
if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier])
{
[annotationsToRecreate addObject:pair.second.annotation];
}
}
[self removeAnnotations:annotationsToRecreate];

// Recreate the annotations with the new icon.
[self addAnnotations:annotationsToRecreate withAnnotationImage:annotationImage];
}
}
else
{
// Remove any annotations associated with the annotation image.
NSMutableArray *annotationsToRecreate = [NSMutableArray array];
for (auto &pair : _annotationContextsByAnnotationTag)
{
if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier])
{
[annotationsToRecreate addObject:pair.second.annotation];
}
}
[self removeAnnotations:annotationsToRecreate];

// Recreate the annotations, falling back to the default icon.
annotationImage.styleIconIdentifier = fallbackIconIdentifier;
if ( ! [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName])
{
[self installAnnotationImage:self.defaultAnnotationImage];
}
[self addAnnotations:annotationsToRecreate withAnnotationImage:annotationImage];
}
_mbglMap->update(mbgl::Update::Annotations);
}

Expand Down

0 comments on commit e0199a7

Please sign in to comment.