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

Commit

Permalink
[ios, macos] Add selection support to MGLMultiPoint annotations.
Browse files Browse the repository at this point in the history
  • Loading branch information
fabian-guerra committed Sep 22, 2017
1 parent efe9928 commit 7408d21
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 72 deletions.
2 changes: 2 additions & 0 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class Map : private util::noncopyable {
std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options = {});

AnnotationIDs queryPointAnnotations(const ScreenBox&);
AnnotationIDs queryShapeAnnotations(const ScreenBox&, const RenderedQueryOptions& options = {});

// Memory
void setSourceTileCacheSize(size_t);
Expand All @@ -213,6 +214,7 @@ class Map : private util::noncopyable {
private:
class Impl;
const std::unique_ptr<Impl> impl;
AnnotationIDs queryAnnotations(const ScreenBox&, const RenderedQueryOptions& options = {});
};

} // namespace mbgl
1 change: 1 addition & 0 deletions platform/ios/app/MBXViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ - (void)addTestShapes
}

MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]];
polygon.title = feature[@"properties"][@"NAME"];

[self.mapView addAnnotation:polygon];

Expand Down
146 changes: 97 additions & 49 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
/// Mapping from an annotation object to an annotation tag.
typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap;

static NSString *const MGLShapeAnnotationLayerIdentifierPrefix = @"com.mapbox.annotations.shape.";

mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function)
{
if ( ! function)
Expand Down Expand Up @@ -3133,6 +3135,11 @@ - (void)removeStyleClass:(NSString *)styleClass
}

std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect];

if (!annotationTags.size()) {
annotationTags = [self shapeAnnotationTagsInRect:rect];
}

if (annotationTags.size())
{
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()];
Expand Down Expand Up @@ -3622,61 +3629,71 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
BOOL queryingShapeAnnotations = NO;

if (!nearbyAnnotations.size()) {
nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect];
queryingShapeAnnotations = YES;
}

if (nearbyAnnotations.size())
{
// Assume that the user is fat-fingering an annotation.
CGRect hitRect = CGRectInset({ point, CGSizeZero },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);

if (!queryingShapeAnnotations) {
// Filter out any annotation whose image or view is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(),
[&](const MGLAnnotationTag annotationTag)
{
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
if ( ! annotation)
{
return true;
}

MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
CGRect annotationRect;

MGLAnnotationView *annotationView = annotationContext.annotationView;

if (annotationView)
{
if ( ! annotationView.enabled)
{
return true;
}

CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
}
else
{
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if ( ! annotationImage.enabled)
{
return true;
}

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

annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
}

// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});

nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}

// Filter out any annotation whose image or view is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(),
[&](const MGLAnnotationTag annotationTag)
{
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
if ( ! annotation)
{
return true;
}

MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
CGRect annotationRect;

MGLAnnotationView *annotationView = annotationContext.annotationView;
if (annotationView)
{
if ( ! annotationView.enabled)
{
return true;
}

CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
}
else
{
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if ( ! annotationImage.enabled)
{
return true;
}

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

annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
}

// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});

nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}

MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
Expand Down Expand Up @@ -3759,6 +3776,32 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)
});
}

- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(CGRect)rect
{
mbgl::ScreenBox screenBox = {
{ CGRectGetMinX(rect), CGRectGetMinY(rect) },
{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
};

std::vector<MGLAnnotationTag> nearbyAnnotations;

mbgl::optional<std::vector<std::string>> optionalLayerIDs;

if (_annotationTagsByAnnotation.size()) {
__block std::vector<std::string> layerIDs;
layerIDs.reserve(_annotationTagsByAnnotation.size());
NSString *layerID;
for (const auto &annotation : _annotationTagsByAnnotation) {
layerID = [NSString stringWithFormat:@"%@%u", MGLShapeAnnotationLayerIdentifierPrefix, annotation.second];
layerIDs.push_back(layerID.UTF8String);
}
optionalLayerIDs = layerIDs;
return nearbyAnnotations = _mbglMap->queryShapeAnnotations(screenBox, { optionalLayerIDs });
}

return nearbyAnnotations;
}

- (id <MGLAnnotation>)selectedAnnotation
{
if (_userLocationAnnotationIsSelected)
Expand Down Expand Up @@ -3810,8 +3853,6 @@ - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
{
if ( ! annotation) return;

if ([annotation isKindOfClass:[MGLMultiPoint class]]) return;

if (annotation == self.selectedAnnotation) return;

if (annotation != self.userLocation)
Expand Down Expand Up @@ -3960,6 +4001,13 @@ - (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
{
return CGRectZero;
}

if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
CLLocationCoordinate2D origin = annotation.coordinate;
CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest);

}
UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
if ( ! image)
{
Expand Down
92 changes: 69 additions & 23 deletions platform/macos/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
/// Mapping from an annotation object to an annotation tag.
typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap;

static NSString *const MGLShapeAnnotationLayerIdentifierPrefix = @"com.mapbox.annotations.shape.";

/// Returns an NSImage for the default marker image.
NSImage *MGLDefaultMarkerImage() {
NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MGLDefaultStyleMarkerSymbolName
Expand Down Expand Up @@ -1787,6 +1789,11 @@ - (IBAction)giveFeedback:(id)sender {
}

std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect];

if (!annotationTags.size()) {
annotationTags = [self shapeAnnotationTagsInRect:rect];
}

if (annotationTags.size())
{
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()];
Expand Down Expand Up @@ -2046,35 +2053,43 @@ - (MGLAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)
queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
BOOL queryingShapeAnnotations = NO;

if (!nearbyAnnotations.size()) {
nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect];
queryingShapeAnnotations = YES;
}

if (nearbyAnnotations.size()) {
// Assume that the user is fat-fingering an annotation.
NSRect hitRect = NSInsetRect({ point, NSZeroSize },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);

// Filter out any annotation whose image is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) {
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby click");
if (!annotation) {
return true;
}

MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if (!annotationImage.selectable) {
return true;
}

// Filter out the annotation if the fattened finger didn’t land on a
// translucent or opaque pixel in the image.
NSRect annotationRect = [self frameOfImage:annotationImage.image
centeredAtCoordinate:annotation.coordinate];
return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect
context:nil hints:nil flipped:NO];
});
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));

if (!queryingShapeAnnotations) {
// Filter out any annotation whose image is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) {
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby click");
if (!annotation) {
return true;
}

MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if (!annotationImage.selectable) {
return true;
}

// Filter out the annotation if the fattened finger didn’t land on a
// translucent or opaque pixel in the image.
NSRect annotationRect = [self frameOfImage:annotationImage.image
centeredAtCoordinate:annotation.coordinate];
return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect
context:nil hints:nil flipped:NO];
});
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}
}

MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
Expand Down Expand Up @@ -2145,6 +2160,30 @@ - (MGLAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)
});
}

- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(NSRect)rect {
mbgl::ScreenBox screenBox = {
{ NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) },
{ NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) },
};

std::vector<MGLAnnotationTag> nearbyAnnotations;
mbgl::optional<std::vector<std::string>> optionalLayerIDs;
if (_annotationTagsByAnnotation.size()) {
__block std::vector<std::string> layerIDs;
layerIDs.reserve(_annotationTagsByAnnotation.size());
NSString *layerID;
for (const auto &annotation : _annotationTagsByAnnotation) {
layerID = [NSString stringWithFormat:@"%@%u", MGLShapeAnnotationLayerIdentifierPrefix, annotation.second];
layerIDs.push_back(layerID.UTF8String);
}
optionalLayerIDs = layerIDs;
return nearbyAnnotations = _mbglMap->queryShapeAnnotations(screenBox, { optionalLayerIDs });
}

return nearbyAnnotations;

}

- (id <MGLAnnotation>)selectedAnnotation {
if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) ||
_selectedAnnotationTag == MGLAnnotationTagNotFound) {
Expand Down Expand Up @@ -2310,6 +2349,13 @@ - (NSRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annota
if (!annotation) {
return NSZeroRect;
}
if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
CLLocationCoordinate2D origin = annotation.coordinate;
CGPoint originPoint = [self convertCoordinate:origin toPointToView:self];
return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest);

}

NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image;
if (!image) {
image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image;
Expand Down
9 changes: 9 additions & 0 deletions src/mbgl/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,15 @@ std::vector<Feature> Map::querySourceFeatures(const std::string& sourceID, const
AnnotationIDs Map::queryPointAnnotations(const ScreenBox& box) {
RenderedQueryOptions options;
options.layerIDs = {{ AnnotationManager::PointLayerID }};
return queryAnnotations(box, options);

}

AnnotationIDs Map::queryShapeAnnotations(const ScreenBox& box, const RenderedQueryOptions& options) {
return queryAnnotations(box, options);
}

AnnotationIDs Map::queryAnnotations(const ScreenBox& box, const RenderedQueryOptions& options) {
auto features = queryRenderedFeatures(box, options);
std::set<AnnotationID> set;
for (auto &feature : features) {
Expand Down

0 comments on commit 7408d21

Please sign in to comment.