[ios, macos] Add selection support to MGLMultiPoint annotations. (#9984)
* [ios, macos] Add selection support to MGLMultiPoint annotations. * [ios, macos] Update changelogs.
This commit is contained in:
committed by
GitHub
parent
24fc48982a
commit
b7b8baef9d
@@ -40,6 +40,8 @@ public:
|
||||
std::vector<Feature> queryRenderedFeatures(const ScreenBox& box, const RenderedQueryOptions& options = {}) const;
|
||||
std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options = {}) const;
|
||||
AnnotationIDs queryPointAnnotations(const ScreenBox& box) const;
|
||||
AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const;
|
||||
AnnotationIDs getAnnotationIDs(const std::vector<Feature>&) const;
|
||||
|
||||
// Debug
|
||||
void dumpDebugLogs();
|
||||
|
||||
@@ -30,6 +30,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
|
||||
* Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617))
|
||||
* Selecting an annotation no longer sets the user tracking mode to `MGLUserTrackingModeNone`. ([#10094](https://github.com/mapbox/mapbox-gl-native/pull/10094))
|
||||
* Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107))
|
||||
* Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984))
|
||||
|
||||
### Other changes
|
||||
|
||||
|
||||
@@ -827,6 +827,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
}
|
||||
|
||||
MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]];
|
||||
polygon.title = feature[@"properties"][@"NAME"];
|
||||
|
||||
[self.mapView addAnnotation:polygon];
|
||||
|
||||
|
||||
@@ -3257,6 +3257,12 @@ public:
|
||||
}
|
||||
|
||||
std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect];
|
||||
std::vector<MGLAnnotationTag> shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect];
|
||||
|
||||
if (shapeAnnotationTags.size()) {
|
||||
annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end());
|
||||
}
|
||||
|
||||
if (annotationTags.size())
|
||||
{
|
||||
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()];
|
||||
@@ -3762,6 +3768,12 @@ public:
|
||||
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())
|
||||
{
|
||||
@@ -3769,54 +3781,56 @@ public:
|
||||
CGRect hitRect = CGRectInset({ point, CGSizeZero },
|
||||
-MGLAnnotationImagePaddingForHitTest,
|
||||
-MGLAnnotationImagePaddingForHitTest);
|
||||
|
||||
// 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)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
|
||||
CGRect annotationRect;
|
||||
|
||||
MGLAnnotationView *annotationView = annotationContext.annotationView;
|
||||
|
||||
if (annotationView)
|
||||
{
|
||||
return true;
|
||||
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];
|
||||
}
|
||||
|
||||
MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
|
||||
UIImage *fallbackImage = fallbackAnnotationImage.image;
|
||||
// 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));
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -3899,6 +3913,14 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(CGRect)rect
|
||||
{
|
||||
return _rendererFrontend->getRenderer()->queryShapeAnnotations({
|
||||
{ CGRectGetMinX(rect), CGRectGetMinY(rect) },
|
||||
{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
|
||||
});
|
||||
}
|
||||
|
||||
- (id <MGLAnnotation>)selectedAnnotation
|
||||
{
|
||||
if (_userLocationAnnotationIsSelected)
|
||||
@@ -3950,8 +3972,6 @@ public:
|
||||
{
|
||||
if ( ! annotation) return;
|
||||
|
||||
if ([annotation isKindOfClass:[MGLMultiPoint class]]) return;
|
||||
|
||||
if (annotation == self.selectedAnnotation) return;
|
||||
|
||||
[self deselectAnnotation:self.selectedAnnotation animated:NO];
|
||||
@@ -4095,6 +4115,13 @@ public:
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
* Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835))
|
||||
* Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617))
|
||||
* Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107))
|
||||
* Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984))
|
||||
|
||||
### Other changes
|
||||
|
||||
|
||||
@@ -1791,6 +1791,12 @@ public:
|
||||
}
|
||||
|
||||
std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect];
|
||||
std::vector<MGLAnnotationTag> shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect];
|
||||
|
||||
if (shapeAnnotationTags.size()) {
|
||||
annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end());
|
||||
}
|
||||
|
||||
if (annotationTags.size())
|
||||
{
|
||||
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()];
|
||||
@@ -2050,35 +2056,43 @@ public:
|
||||
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;
|
||||
@@ -2149,6 +2163,14 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(NSRect)rect {
|
||||
// Cocoa origin is at the lower-left corner.
|
||||
return _rendererFrontend->getRenderer()->queryShapeAnnotations({
|
||||
{ NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) },
|
||||
{ NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) },
|
||||
});
|
||||
}
|
||||
|
||||
- (id <MGLAnnotation>)selectedAnnotation {
|
||||
if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) ||
|
||||
_selectedAnnotationTag == MGLAnnotationTagNotFound) {
|
||||
@@ -2314,6 +2336,13 @@ public:
|
||||
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;
|
||||
|
||||
@@ -18,6 +18,7 @@ using namespace style;
|
||||
|
||||
const std::string AnnotationManager::SourceID = "com.mapbox.annotations";
|
||||
const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points";
|
||||
const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape.";
|
||||
|
||||
AnnotationManager::AnnotationManager(Style& style_)
|
||||
: style(style_) {
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
|
||||
static const std::string SourceID;
|
||||
static const std::string PointLayerID;
|
||||
static const std::string ShapeLayerID;
|
||||
|
||||
private:
|
||||
void add(const AnnotationID&, const SymbolAnnotation&, const uint8_t);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <mbgl/annotation/shape_annotation_impl.hpp>
|
||||
#include <mbgl/annotation/annotation_tile.hpp>
|
||||
#include <mbgl/annotation/annotation_manager.hpp>
|
||||
#include <mbgl/tile/tile_id.hpp>
|
||||
#include <mbgl/math/wrap.hpp>
|
||||
#include <mbgl/math/clamp.hpp>
|
||||
@@ -15,7 +16,7 @@ namespace geojsonvt = mapbox::geojsonvt;
|
||||
ShapeAnnotationImpl::ShapeAnnotationImpl(const AnnotationID id_, const uint8_t maxZoom_)
|
||||
: id(id_),
|
||||
maxZoom(maxZoom_),
|
||||
layerID("com.mapbox.annotations.shape." + util::toString(id)) {
|
||||
layerID(AnnotationManager::ShapeLayerID + util::toString(id)) {
|
||||
}
|
||||
|
||||
void ShapeAnnotationImpl::updateTileData(const CanonicalTileID& tileID, AnnotationTileData& data) {
|
||||
|
||||
@@ -57,6 +57,21 @@ AnnotationIDs Renderer::queryPointAnnotations(const ScreenBox& box) const {
|
||||
RenderedQueryOptions options;
|
||||
options.layerIDs = {{ AnnotationManager::PointLayerID }};
|
||||
auto features = queryRenderedFeatures(box, options);
|
||||
return getAnnotationIDs(features);
|
||||
}
|
||||
|
||||
AnnotationIDs Renderer::queryShapeAnnotations(const ScreenBox& box) const {
|
||||
auto features = impl->queryShapeAnnotations({
|
||||
box.min,
|
||||
{box.max.x, box.min.y},
|
||||
box.max,
|
||||
{box.min.x, box.max.y},
|
||||
box.min
|
||||
});
|
||||
return getAnnotationIDs(features);
|
||||
}
|
||||
|
||||
AnnotationIDs Renderer::getAnnotationIDs(const std::vector<Feature>& features) const {
|
||||
std::set<AnnotationID> set;
|
||||
for (auto &feature : features) {
|
||||
assert(feature.id);
|
||||
|
||||
@@ -629,6 +629,10 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin
|
||||
}
|
||||
}
|
||||
|
||||
return queryRenderedFeatures(geometry, options, layers);
|
||||
}
|
||||
|
||||
std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::vector<const RenderLayer*>& layers) const {
|
||||
std::unordered_set<std::string> sourceIDs;
|
||||
for (const RenderLayer* layer : layers) {
|
||||
sourceIDs.emplace(layer->baseImpl->source);
|
||||
@@ -663,6 +667,21 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Feature> Renderer::Impl::queryShapeAnnotations(const ScreenLineString& geometry) const {
|
||||
std::vector<const RenderLayer*> shapeAnnotationLayers;
|
||||
RenderedQueryOptions options;
|
||||
for (const auto& layerImpl : *layerImpls) {
|
||||
if (std::mismatch(layerImpl->id.begin(), layerImpl->id.end(),
|
||||
AnnotationManager::ShapeLayerID.begin(), AnnotationManager::ShapeLayerID.end()).second == AnnotationManager::ShapeLayerID.end()) {
|
||||
if (const RenderLayer* layer = getRenderLayer(layerImpl->id)) {
|
||||
shapeAnnotationLayers.emplace_back(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queryRenderedFeatures(geometry, options, shapeAnnotationLayers);
|
||||
}
|
||||
|
||||
std::vector<Feature> Renderer::Impl::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const {
|
||||
const RenderSource* source = getRenderSource(sourceID);
|
||||
if (!source) return {};
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
|
||||
std::vector<Feature> queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&) const;
|
||||
std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const;
|
||||
std::vector<Feature> queryShapeAnnotations(const ScreenLineString&) const;
|
||||
|
||||
void onLowMemory();
|
||||
void dumDebugLogs();
|
||||
@@ -61,6 +62,8 @@ private:
|
||||
|
||||
RenderLayer* getRenderLayer(const std::string& id);
|
||||
const RenderLayer* getRenderLayer(const std::string& id) const;
|
||||
|
||||
std::vector<Feature> queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&, const std::vector<const RenderLayer*>&) const;
|
||||
|
||||
// GlyphManagerObserver implementation.
|
||||
void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) override;
|
||||
|
||||
Reference in New Issue
Block a user