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

Commit

Permalink
[osx] Observe annotation coordinates
Browse files Browse the repository at this point in the history
MGLMapView observes changes to the coordinate property of each MGLAnnotation added to it. Changing the coordinate property in a KVO-compliant way causes the annotation to be relocated and its callout view, if present, to be dismissed. To avoid observing the same annotation twice yet also avoid expensive lookups when adding or removing annotations, MGLMapView indexes added point annotations in an NSMutableSet.
  • Loading branch information
1ec5 committed Apr 19, 2016
1 parent 3976448 commit f8c52d5
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 35 deletions.
4 changes: 1 addition & 3 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2697,11 +2697,9 @@ - (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:image centeredAtCoordinate:annotation.coordinate];
CGRect annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
Expand Down
4 changes: 4 additions & 0 deletions platform/osx/osx.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA839E9E1CC2E3400062CAFB /* MapDocument.xib */; };
DA839EA21CC2E3400062CAFB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA839EA11CC2E3400062CAFB /* Assets.xcassets */; };
DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA839EA31CC2E3400062CAFB /* MainMenu.xib */; };
DAC2ABC51CC6D343006D18C4 /* MGLAnnotationImage_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */; };
DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = DAE6C2E11CC304F900DB3429 /* Credits.rtf */; };
DAE6C2ED1CC3050F00DB3429 /* DroppedPinAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2E41CC3050F00DB3429 /* DroppedPinAnnotation.m */; };
DAE6C2EE1CC3050F00DB3429 /* LocationCoordinate2DTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C2E61CC3050F00DB3429 /* LocationCoordinate2DTransformer.m */; };
Expand Down Expand Up @@ -143,6 +144,7 @@
DA839EA11CC2E3400062CAFB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DA839EA41CC2E3400062CAFB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
DA839EA61CC2E3400062CAFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLAnnotationImage_Private.h; path = src/MGLAnnotationImage_Private.h; sourceTree = SOURCE_ROOT; };
DAE6C2E11CC304F900DB3429 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
DAE6C2E31CC3050F00DB3429 /* DroppedPinAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DroppedPinAnnotation.h; sourceTree = "<group>"; };
DAE6C2E41CC3050F00DB3429 /* DroppedPinAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DroppedPinAnnotation.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -413,6 +415,7 @@
isa = PBXGroup;
children = (
DAE6C39F1CC31E9400DB3429 /* MGLAnnotationImage.h */,
DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */,
DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */,
DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */,
DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */,
Expand Down Expand Up @@ -462,6 +465,7 @@
DAE6C35E1CC31E0400DB3429 /* MGLMultiPoint.h in Headers */,
DAE6C3971CC31E2A00DB3429 /* NSBundle+MGLAdditions.h in Headers */,
DAE6C3631CC31E0400DB3429 /* MGLPointAnnotation.h in Headers */,
DAC2ABC51CC6D343006D18C4 /* MGLAnnotationImage_Private.h in Headers */,
DAE6C35F1CC31E0400DB3429 /* MGLOfflinePack.h in Headers */,
DAE6C39C1CC31E2A00DB3429 /* NSString+MGLAdditions.h in Headers */,
DAE6C3861CC31E2A00DB3429 /* MGLGeometry_Private.h in Headers */,
Expand Down
3 changes: 2 additions & 1 deletion platform/osx/src/MGLAnnotationImage.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#import "MGLAnnotationImage.h"
#import "MGLAnnotationImage_Private.h"

@interface MGLAnnotationImage ()

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

@end

Expand Down
8 changes: 8 additions & 0 deletions platform/osx/src/MGLAnnotationImage_Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import <Mapbox/Mapbox.h>

@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;

@end
100 changes: 69 additions & 31 deletions platform/osx/src/MGLMapView.mm
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#import "MGLMapView_Private.h"
#import "MGLAnnotationImage_Private.h"
#import "MGLAttributionButton.h"
#import "MGLCompassCell.h"
#import "MGLOpenGLLayer.h"
#import "MGLStyle.h"

#import "../../darwin/src/MGLGeometry_Private.h"
#import "../../darwin/src/MGLMultiPoint_Private.h"
#import "../../darwin/src/MGLOfflineStorage_Private.h"
#import "MGLGeometry_Private.h"
#import "MGLMultiPoint_Private.h"
#import "MGLOfflineStorage_Private.h"

#import "MGLAccountManager.h"
#import "MGLMapCamera.h"
Expand All @@ -31,10 +32,10 @@
#import <map>
#import <unordered_set>

#import "../../darwin/src/NSBundle+MGLAdditions.h"
#import "../../darwin/src/NSProcessInfo+MGLAdditions.h"
#import "../../darwin/src/NSException+MGLAdditions.h"
#import "../../darwin/src/NSString+MGLAdditions.h"
#import "NSBundle+MGLAdditions.h"
#import "NSProcessInfo+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
#import "NSString+MGLAdditions.h"

#import <QuartzCore/QuartzCore.h>

Expand Down Expand Up @@ -130,9 +131,8 @@
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;
};

@interface MGLMapView () <NSPopoverDelegate, MGLMultiPointDelegate>
Expand Down Expand Up @@ -165,6 +165,7 @@ @implementation MGLMapView {
BOOL _didHideCursorDuringGesture;

MGLAnnotationContextMap _annotationContextsByAnnotationTag;
NS_MUTABLE_SET_OF(id <MGLAnnotation>) *_pointAnnotations;
MGLAnnotationTag _selectedAnnotationTag;
MGLAnnotationTag _lastSelectedAnnotationTag;
/// Size of the rectangle formed by unioning the maximum slop area around every annotation image.
Expand Down Expand Up @@ -279,6 +280,7 @@ - (void)commonInit {
// Set up annotation management and selection state.
_annotationImagesByIdentifier = [NSMutableDictionary dictionary];
_annotationContextsByAnnotationTag = {};
_pointAnnotations = [NSMutableSet set];
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastClick = {};
Expand Down Expand Up @@ -464,6 +466,18 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object
if ([keyPath isEqualToString:@"contentLayoutRect"] ||
[keyPath isEqualToString:@"titlebarAppearsTransparent"]) {
[self adjustContentInsets];
} else if ([keyPath isEqualToString:@"coordinate"] &&
[object conformsToProtocol:@protocol(MGLAnnotation)]) {
id <MGLAnnotation> annotation = object;
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
if (annotationTag != MGLAnnotationTagNotFound) {
const mbgl::LatLng latLng = MGLLatLngFromLocationCoordinate2D(annotation.coordinate);
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
_mbglMap->updatePointAnnotation(annotationTag, { latLng, annotationImage.styleIconIdentifier.UTF8String ?: "" });
if (annotationTag == _selectedAnnotationTag) {
[self deselectAnnotation:annotation];
}
}
}
}

Expand Down Expand Up @@ -1539,6 +1553,7 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {

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

for (id <MGLAnnotation> annotation in annotations) {
NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
Expand All @@ -1555,25 +1570,27 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
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.
NSImage *image = MGLDefaultMarkerImage();
NSRect alignmentRect = image.alignmentRect;
alignmentRect.origin.y = NSMidY(alignmentRect);
alignmentRect.size.height /= 2;
image.alignmentRect = alignmentRect;
annotationImage = [MGLAnnotationImage annotationImageWithImage:image
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];
}
[annotationImages addObject:annotationImage];

points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String ?: "");

NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : "");
if ( ! [_pointAnnotations containsObject:annotation] && [annotation isKindOfClass:[NSObject class]]) {
[(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:NULL];
[_pointAnnotations addObject:annotation];
}

// Opt into potentially expensive tooltip tracking areas.
if (annotation.toolTip.length) {
Expand All @@ -1587,9 +1604,12 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
std::vector<MGLAnnotationTag> pointAnnotationTags = _mbglMap->addPointAnnotations(points);

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 @@ -1610,9 +1630,25 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
[self updateAnnotationTrackingAreas];
}

/// Initializes and returns 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 {
NSImage *image = MGLDefaultMarkerImage();
NSRect alignmentRect = image.alignmentRect;
alignmentRect.origin.y = NSMidY(alignmentRect);
alignmentRect.size.height /= 2;
image.alignmentRect = alignmentRect;
return [MGLAnnotationImage annotationImageWithImage:image
reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
}

/// Sends the raw pixel data of the annotation image’s image to mbgl and
/// calculates state needed for hit testing later.
- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage {
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;

NSImage *image = annotationImage.image;
NSSize size = image.size;
if (size.width == 0 || size.height == 0 || !image.valid) {
Expand All @@ -1634,8 +1670,7 @@ - (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage {
std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(std::move(cPremultipliedImage),
(float)(rep.pixelsWide / size.width));
NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
_mbglMap->addAnnotationIcon(symbolName.UTF8String, cSpriteImage);
_mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);

// Create a slop area with a “radius” equal to the annotation image’s entire
// size, allowing the eventual click to be on any point within this image.
Expand Down Expand Up @@ -1678,6 +1713,11 @@ - (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
}

_annotationContextsByAnnotationTag.erase(annotationTag);

if ([annotation isKindOfClass:[NSObject class]]) {
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate"];
}
[_pointAnnotations removeObject:annotation];
}

[self willChangeValueForKey:@"annotations"];
Expand All @@ -1688,11 +1728,6 @@ - (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
}

- (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 @@ -1950,6 +1985,9 @@ - (NSRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
return NSZeroRect;
}
NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
if (!image) {
image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image;
}
if (!image) {
return NSZeroRect;
}
Expand All @@ -1974,7 +2012,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

0 comments on commit f8c52d5

Please sign in to comment.