[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:
Fabian Guerra Soto
2017-10-18 15:18:54 -04:00
committed by GitHub
parent 24fc48982a
commit b7b8baef9d
12 changed files with 167 additions and 66 deletions

View File

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

View File

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

View File

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

View File

@@ -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)
{

View File

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

View File

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

View File

@@ -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_) {

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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