diff options
author | Fabian Guerra <fabian.guerra@mapbox.com> | 2017-08-09 18:22:40 -0400 |
---|---|---|
committer | Fabian Guerra <fabian.guerra@mapbox.com> | 2017-09-01 20:01:04 -0400 |
commit | 5164788449dd111a6e9e60da919538b4716f8ef2 (patch) | |
tree | d18358c63869a88e960b3b9000cafe2e0f9a1399 | |
parent | 87348234d16915176fab313f0d3f2dae246216d9 (diff) | |
download | qtlocation-mapboxgl-5164788449dd111a6e9e60da919538b4716f8ef2.tar.gz |
[ios, macos] Add shapes annotations select/deselect delegates.
-rw-r--r-- | include/mbgl/map/map.hpp | 2 | ||||
-rw-r--r-- | platform/ios/app/MBXViewController.m | 10 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 122 | ||||
-rw-r--r-- | platform/ios/src/MGLMapViewDelegate.h | 21 | ||||
-rw-r--r-- | platform/ios/test/MGLMapViewDelegateIntegrationTests.swift | 4 | ||||
-rw-r--r-- | platform/macos/src/MGLMapView.mm | 113 | ||||
-rw-r--r-- | platform/macos/src/MGLMapViewDelegate.h | 21 | ||||
-rw-r--r-- | platform/macos/test/MGLMapViewDelegateIntegrationTests.swift | 4 | ||||
-rw-r--r-- | src/mbgl/map/map.cpp | 9 |
9 files changed, 303 insertions, 3 deletions
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index bbeeeac6cc..2881abc9fe 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -197,6 +197,7 @@ public: 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); @@ -213,6 +214,7 @@ public: private: class Impl; const std::unique_ptr<Impl> impl; + AnnotationIDs queryAnnotations(const ScreenBox&, const RenderedQueryOptions& options = {}); }; } // namespace mbgl diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 992b7d587a..ac492928e0 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -1809,4 +1809,14 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { } } +- (void)mapView:(MGLMapView *)mapView didSelectShapeAnnotation:(nonnull MGLShape *)shapeAnnotation +{ + NSLog(@"Did Select: %f, %f", shapeAnnotation.coordinate.latitude, shapeAnnotation.coordinate.longitude); +} + +- (void)mapView:(MGLMapView *)mapView didDeselectShapeAnnotation:(nonnull MGLShape *)shapeAnnotation +{ + NSLog(@"Did deselect: %f, %f", shapeAnnotation.coordinate.latitude, shapeAnnotation.coordinate.longitude); +} + @end diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 320c4bcda8..5135cf97d7 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -152,6 +152,11 @@ typedef std::unordered_map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotation /// Mapping from an annotation object to an annotation tag. typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap; +/// Mapping from a shape annotation object to shape layer id. +typedef std::map<id<MGLAnnotation>, std::string> MGLShapeAnnotationObjectLayerIDMap; + +static NSString *const MGLLayerIDShapeAnnotation = @"com.mapbox.annotations.shape."; + mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { if ( ! function) @@ -285,9 +290,11 @@ public: MGLAnnotationTagContextMap _annotationContextsByAnnotationTag; MGLAnnotationObjectTagMap _annotationTagsByAnnotation; - + MGLShapeAnnotationObjectLayerIDMap _shapeAnnotationLayerIDsByAnnotation; + /// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to `MGLAnnotationTagNotFound`. MGLAnnotationTag _selectedAnnotationTag; + MGLShape *_selectedShapeAnnotation; BOOL _userLocationAnnotationIsSelected; /// Size of the rectangle formed by unioning the maximum slop area around every annotation image and annotation image view. @@ -468,6 +475,7 @@ public: _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; _annotationContextsByAnnotationTag = {}; _annotationTagsByAnnotation = {}; + _shapeAnnotationLayerIDsByAnnotation = {}; _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary]; _selectedAnnotationTag = MGLAnnotationTagNotFound; _annotationsNearbyLastTap = {}; @@ -1483,12 +1491,20 @@ public: id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:singleTap persistingResults:YES]; if(annotation) { + [self deselectShapeAnnotation:_selectedShapeAnnotation]; [self selectAnnotation:annotation animated:YES]; } else { [self deselectAnnotation:self.selectedAnnotation animated:YES]; + MGLShape *shapeAnnotation = [self shapeAnnotationForGestureRecognizer:singleTap]; + if (shapeAnnotation) { + [self selectShapeAnnotation:shapeAnnotation]; + } else { + [self deselectShapeAnnotation:_selectedShapeAnnotation]; + } } + } /** @@ -1861,8 +1877,12 @@ public: if(!self.selectedAnnotation) { id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; - if(!annotation) { - return NO; + if(!annotation && !_selectedShapeAnnotation) { + MGLShape *shapeAnnotation = [self shapeAnnotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer]; + if (!shapeAnnotation) { + return NO; + } + } } } @@ -3226,6 +3246,8 @@ public: context.annotation = annotation; _annotationContextsByAnnotationTag[annotationTag] = context; _annotationTagsByAnnotation[annotation] = annotationTag; + NSString *layerID = [NSString stringWithFormat:@"%@%u", MGLLayerIDShapeAnnotation, annotationTag]; + _shapeAnnotationLayerIDsByAnnotation[annotation] = layerID.UTF8String; [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; } @@ -3524,6 +3546,7 @@ public: _annotationContextsByAnnotationTag.erase(annotationTag); _annotationTagsByAnnotation.erase(annotation); + _shapeAnnotationLayerIDsByAnnotation.erase(annotation); if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MGLMultiPoint class]]) { @@ -4150,6 +4173,99 @@ public: } } +#pragma mark - Shape Annotation + +- (void)selectShapeAnnotation:(MGLShape *)shapeAnnotation +{ + if (!shapeAnnotation) return; + + if (shapeAnnotation == _selectedShapeAnnotation) return; + + [self deselectShapeAnnotation:_selectedShapeAnnotation]; + + _selectedShapeAnnotation = shapeAnnotation; + + if ([self.delegate respondsToSelector:@selector(mapView:didSelectShapeAnnotation:)]) + { + [self.delegate mapView:self didSelectShapeAnnotation:shapeAnnotation]; + } +} + +- (void)deselectShapeAnnotation:(MGLShape *)shapeAnnotation +{ + if (!shapeAnnotation) return; + + if (_selectedShapeAnnotation == shapeAnnotation) + { + if ([self.delegate respondsToSelector:@selector(mapView:didDeselectShapeAnnotation:)]) + { + [self.delegate mapView:self didDeselectShapeAnnotation:shapeAnnotation]; + } + _selectedShapeAnnotation = nil; + } + +} + +- (MGLShape*)shapeAnnotationForGestureRecognizer:(UITapGestureRecognizer*)singleTap +{ + CGPoint tapPoint = [singleTap locationInView:self]; + + MGLAnnotationTag hitAnnotationTag = [self shapeAnnotationTagAtPoint:tapPoint]; + + if (hitAnnotationTag != MGLAnnotationTagNotFound) { + id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag]; + NSAssert(annotation, @"Cannot select nonexistent annotation with tag %u", hitAnnotationTag); + if ([annotation isKindOfClass:[MGLShape class]]) { + return (MGLShape *)annotation; + } + } + + return nil; +} + +- (MGLAnnotationTag)shapeAnnotationTagAtPoint:(CGPoint)point +{ + CGRect queryRect = CGRectInset({ point, CGSizeZero }, + -_unionedAnnotationRepresentationSize.width, + -_unionedAnnotationRepresentationSize.height); + queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest, + -MGLAnnotationImagePaddingForHitTest); + + std::vector<MGLAnnotationTag> nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect]; + + MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; + + // Choose the first nearby annotation. + if (nearbyAnnotations.size()) + { + hitAnnotationTag = nearbyAnnotations.front(); + } + return hitAnnotationTag; +} + +- (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 (_shapeAnnotationLayerIDsByAnnotation.size()) { + __block std::vector<std::string> layerIDs; + layerIDs.reserve(_shapeAnnotationLayerIDsByAnnotation.size()); + for (const auto &annotation : _shapeAnnotationLayerIDsByAnnotation) { + layerIDs.push_back(annotation.second); + } + optionalLayerIDs = layerIDs; + nearbyAnnotations = _mbglMap->queryShapeAnnotations(screenBox, { optionalLayerIDs }); + } + + return nearbyAnnotations; +} + #pragma mark - User Location - - (void)validateLocationServices diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 096711fcbb..6360ede329 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -382,6 +382,27 @@ NS_ASSUME_NONNULL_BEGIN - (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation; /** + Tells the delegate that one of its shape annotations was selected. + + You can use this method to track changes in the selection state of annotations. + + @param mapView The map view containing the annotation. + @param shapeAnnotation The shape annotation that was selected. + */ +- (void)mapView:(MGLMapView *)mapView didSelectShapeAnnotation:(MGLShape *)shapeAnnotation; + +/** + Tells the delegate that one of its shape annotations was deselected. + + You can use this method to track changes in the selection state of annotations. + + + @param mapView The map view containing the annotation. + @param shapeAnnotation The shape annotation that was deselected. + */ +- (void)mapView:(MGLMapView *)mapView didDeselectShapeAnnotation:(MGLShape *)shapeAnnotation; + +/** Tells the delegate that one of its annotation views was selected. You can use this method to track changes in the selection state of annotation diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 50f101e86b..dad63c7d1d 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -78,5 +78,9 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, annotation: MGLAnnotation, calloutAccessoryControlTapped control: UIControl) {} func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool { return false } + + func mapView(_ mapView: MGLMapView, didSelectShapeAnnotation shapeAnnotation: MGLShape) {} + + func mapView(_ mapView: MGLMapView, didDeselectShapeAnnotation shapeAnnotation: MGLShape) {} } diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 1908a46cf9..c99ec95f6e 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -105,6 +105,11 @@ typedef std::unordered_map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotation /// Mapping from an annotation object to an annotation tag. typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap; +/// Mapping from a shape annotation object to shape layer id. +typedef std::map<id<MGLAnnotation>, std::string> MGLShapeAnnotationObjectLayerIDMap; + +static NSString *const MGLLayerIDShapeAnnotation = @"com.mapbox.annotations.shape."; + /// Returns an NSImage for the default marker image. NSImage *MGLDefaultMarkerImage() { NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MGLDefaultStyleMarkerSymbolName @@ -168,6 +173,9 @@ public: MGLAnnotationObjectTagMap _annotationTagsByAnnotation; MGLAnnotationTag _selectedAnnotationTag; MGLAnnotationTag _lastSelectedAnnotationTag; + + MGLShapeAnnotationObjectLayerIDMap _shapeAnnotationLayerIDsByAnnotation; + MGLShape *_selectedShapeAnnotation; /// Size of the rectangle formed by unioning the maximum slop area around every annotation image. NSSize _unionedAnnotationImageSize; std::vector<MGLAnnotationTag> _annotationsNearbyLastClick; @@ -293,9 +301,11 @@ public: _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; _annotationContextsByAnnotationTag = {}; _annotationTagsByAnnotation = {}; + _shapeAnnotationLayerIDsByAnnotation = {}; _selectedAnnotationTag = MGLAnnotationTagNotFound; _lastSelectedAnnotationTag = MGLAnnotationTagNotFound; _annotationsNearbyLastClick = {}; + // Jump to Null Island initially. self.automaticallyAdjustsContentInsets = YES; @@ -1484,10 +1494,17 @@ public: if (hitAnnotationTag != _selectedAnnotationTag) { id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag]; NSAssert(annotation, @"Cannot select nonexistent annotation with tag %u", hitAnnotationTag); + [self deselectShapeAnnotation:_selectedShapeAnnotation]; [self selectAnnotation:annotation]; } } else { [self deselectAnnotation:self.selectedAnnotation]; + MGLShape *shapeAnnotation = [self shapeAnnotationForGestureRecognizer:gestureRecognizer]; + if (shapeAnnotation) { + [self selectShapeAnnotation:shapeAnnotation]; + } else { + [self deselectShapeAnnotation:_selectedShapeAnnotation]; + } } } @@ -1869,6 +1886,8 @@ public: context.annotation = annotation; _annotationContextsByAnnotationTag[annotationTag] = context; _annotationTagsByAnnotation[annotation] = annotationTag; + NSString *layerID = [NSString stringWithFormat:@"%@%u", MGLLayerIDShapeAnnotation, annotationTag]; + _shapeAnnotationLayerIDsByAnnotation[annotation] = layerID.UTF8String; [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; } else if (![annotation isKindOfClass:[MGLMultiPolyline class]] @@ -1996,6 +2015,7 @@ public: _annotationContextsByAnnotationTag.erase(annotationTag); _annotationTagsByAnnotation.erase(annotation); + _shapeAnnotationLayerIDsByAnnotation.erase(annotation); if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MGLMultiPoint class]]) { @@ -2365,6 +2385,99 @@ public: } } +#pragma mark - Shape Annotation + +- (void)selectShapeAnnotation:(MGLShape *)shapeAnnotation +{ + if (!shapeAnnotation) return; + + if (shapeAnnotation == _selectedShapeAnnotation) return; + + [self deselectShapeAnnotation:_selectedShapeAnnotation]; + + _selectedShapeAnnotation = shapeAnnotation; + + if ([self.delegate respondsToSelector:@selector(mapView:didSelectShapeAnnotation:)]) + { + [self.delegate mapView:self didSelectShapeAnnotation:shapeAnnotation]; + } +} + +- (void)deselectShapeAnnotation:(MGLShape *)shapeAnnotation +{ + if (!shapeAnnotation) return; + + if (_selectedShapeAnnotation == shapeAnnotation) + { + if ([self.delegate respondsToSelector:@selector(mapView:didDeselectShapeAnnotation:)]) + { + [self.delegate mapView:self didDeselectShapeAnnotation:shapeAnnotation]; + } + _selectedShapeAnnotation = nil; + } + +} + +- (MGLShape*)shapeAnnotationForGestureRecognizer:(NSClickGestureRecognizer*)singleTap +{ + NSPoint tapPoint = [singleTap locationInView:self]; + + MGLAnnotationTag hitAnnotationTag = [self shapeAnnotationTagAtPoint:tapPoint]; + + if (hitAnnotationTag != MGLAnnotationTagNotFound) { + id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag]; + NSAssert(annotation, @"Cannot select nonexistent annotation with tag %u", hitAnnotationTag); + if ([annotation isKindOfClass:[MGLShape class]]) { + return (MGLShape *)annotation; + } + } + + return nil; +} + +- (MGLAnnotationTag)shapeAnnotationTagAtPoint:(NSPoint)point +{ + NSRect queryRect = NSInsetRect({ point, CGSizeZero }, + -_unionedAnnotationImageSize.width, + -_unionedAnnotationImageSize.height); + queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest, + -MGLAnnotationImagePaddingForHitTest); + + std::vector<MGLAnnotationTag> nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect]; + + MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; + + // Choose the first nearby annotation. + if (nearbyAnnotations.size()) + { + hitAnnotationTag = nearbyAnnotations.front(); + } + return hitAnnotationTag; +} + +- (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 (_shapeAnnotationLayerIDsByAnnotation.size()) { + __block std::vector<std::string> layerIDs; + layerIDs.reserve(_shapeAnnotationLayerIDsByAnnotation.size()); + for (const auto &annotation : _shapeAnnotationLayerIDsByAnnotation) { + layerIDs.push_back(annotation.second); + } + optionalLayerIDs = layerIDs; + nearbyAnnotations = _mbglMap->queryShapeAnnotations(screenBox, { optionalLayerIDs }); + } + + return nearbyAnnotations; +} + #pragma mark MGLMultiPointDelegate methods - (double)alphaForShapeAnnotation:(MGLShape *)annotation { diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h index dae5b40286..a156b7f158 100644 --- a/platform/macos/src/MGLMapViewDelegate.h +++ b/platform/macos/src/MGLMapViewDelegate.h @@ -263,6 +263,27 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation; +/** + Tells the delegate that one of its shape annotations was selected. + + You can use this method to track changes in the selection state of annotations. + + @param mapView The map view containing the annotation. + @param shapeAnnotation The shape annotation that was selected. + */ +- (void)mapView:(MGLMapView *)mapView didSelectShapeAnnotation:(MGLShape *)shapeAnnotation; + +/** + Tells the delegate that one of its shape annotations was deselected. + + You can use this method to track changes in the selection state of annotations. + + + @param mapView The map view containing the annotation. + @param shapeAnnotation The shape annotation that was deselected. + */ +- (void)mapView:(MGLMapView *)mapView didDeselectShapeAnnotation:(MGLShape *)shapeAnnotation; + #pragma mark Managing Callout Popovers /** diff --git a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift index 3f82e7c61a..ad5cda7713 100644 --- a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift @@ -52,5 +52,9 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, fillColorForPolygonAnnotation annotation: MGLPolygon) -> NSColor { return .black } func mapView(_ mapView: MGLMapView, calloutViewControllerFor annotation: MGLAnnotation) -> NSViewController? { return nil } + + func mapView(_ mapView: MGLMapView, didSelectShapeAnnotation shapeAnnotation: MGLShape) {} + + func mapView(_ mapView: MGLMapView, didDeselectShapeAnnotation shapeAnnotation: MGLShape) {} } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 35457f3a5b..084b825c02 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -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) { |