We explicitly don't support selecting non-point annotations in iOS (820d2da203b184fbe369f3e5beb3d68a51c8a1f1), so polylines and polygons aren't particularly interactive yet.
Related: #1504
/cc @incanus @1ec5
I'm unsure where Android stands on annotation selection — @ljbade @bleege?
General Android annotation selection has been on my plate over in https://github.com/mapbox/mapbox-gl-native/issues/1969 but is incomplete.
As for shapes, I don't think MapKit even supports this, or if it does, I know for a fact it doesn't support callouts on them. We hit this with the raster SDK and it was due to difficult callout placement algorithms. But we could try here?
This would be nice for Leaflet parity.
The way this is done in JS is with
featuresAt APIEDIT: AFIK
You don't necessarily need to support callouts.
Perhaps just a way to select/hit test polygons.
We definitely need callout support as well as selection.
+1 for callout support as well.
Would it be acceptable to require the developer to indicate where on a polyline/polygon the callout should attach? That would take the guesswork off of the framework. We could support some simple options like top/bottom/left/right/centroid and leave it at that.
The problem is when you get into polylines like this — where do you attach the callout?

Prior art: https://github.com/mapbox/mapbox-ios-sdk-legacy/issues/285
Definiteyly, that's a lot better than not being able to do it at all!
@incanus The callout for line can be attached to a 'point on the line' that passed the hit-test for tap.
The callout for line can be attached to a 'point on the line' that passed the hit-test for tap.
Aye, tapped-point would be the most intuitive solution for polyline callouts. Polygons, seems like centroid would be best?
@friedbunny Please leave an option to set any point or tapped point on the polygon as well. Centroid won't always work in our use case.
The callout for line can be attached to a 'point on the line' that passed the hit-test for tap.
Just a note that this may require we allow callout arrows in directions other than just down.
Centroid won't always work in our use case.
Callout arrows aside, this is trickier without full support for featuresAt per https://github.com/mapbox/mapbox-gl-native/issues/352.
As to the default callout anchor point strategy, another consideration is that the shape may extend beyond the viewport, meaning that an obvious anchor point like the centroid could fall outside the viewport. We’d have to crop the shape to the viewport before computing an anchor point.
As an alternative to adjusting the callout anchor point of a shape, once #5502/#5165 lands, you could also display your own callout in response to a shape annotation getting selected. As part of #4392, we could perhaps expose an API for displaying a popup independently of a particular annotation; you’d use that API to create a popup that you could position anywhere you want on the shape.
The anchor placement issue illustrated in https://github.com/mapbox/mapbox-gl-native/issues/2082#issuecomment-140134203 would be addressed by a port of @mourner's Polylabel library to C++. The additional consideration in https://github.com/mapbox/mapbox-gl-native/issues/2082#issuecomment-229487695 still applies, but mbgl probably already has a solution for clipping polygons that could be reused for this problem.
While we wait for a port of Polylabel, plan B would be to anchor the callout as close as possible to the tap location. This would require porting some minimal Turf functionally from JavaScript. Then once Polylabel is ready, we could switch to that implementation without any API changes.
For people like me, precisely a client of mine who needs a solution now and can't wait for this feature to be implemented:
I've created a workaround to make the MGLPolyLine interactive until this feature is implemented in the core. While this works smooth for a few lines on the map, I don't recommend it to use with hundreds of thousands of lines.
The implementation is simple: I add invisible MGLAnnotationPoints along the MGLPolyLine by recursively calculating the distance between two points, check if its higher than a THRESHOLD and if yes, finding the middle point between these points and repeat the process.
private func split(_ from: CLLocationCoordinate2D, _ to: CLLocationCoordinate2D) {
if distance(from, to) > 200 { // THRESHOLD is 200m
let middle = mid(from, to)
add(coordinate: middle)
split(from, middle)
split(middle, to)
}
add(coordinate: from)
add(coordinate: to)
}
private func distance(_ from: CLLocationCoordinate2D, _ to: CLLocationCoordinate2D) -> Double {
let fromLoc = CLLocation(latitude: from.latitude, longitude: from.longitude)
let toLoc = CLLocation(latitude: to.latitude, longitude: to.longitude)
return fromLoc.distance(from: toLoc)
}
private func mid(_ from: CLLocationCoordinate2D, _ to: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
let latitude = (from.latitude + to.latitude) / 2
let longitude = (from.longitude + to.longitude) / 2
return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
private func add(coordinate: CLLocationCoordinate2D) {
DispatchQueue.main.async {
// Unowned reference to self to prevent retain cycle
[unowned self] in
let point = MGLPointAnnotation()
point.coordinate = coordinate
point.title = "Marker selected"
point.subtitle = "\(coordinate.latitude) / \(coordinate.longitude)"
self.mapView.addAnnotation(point)
}
}
You can find the implementation here.
Again: I don't recommend using this when you have a lot of lines to display but because my client needs this feature _now_, I'm fine with this implementation hoping for a better and more efficient solution from the Mapbox team :relaxed:

Is selection of polygons going to be supported at some point? (any updates?) Thanks.
If not, any workarounds without filling my polygons with points?
Given the large amount of work still needed to get v3.4.0 out the door, we’re not optimistic that this feature would make it into that release.
Fortunately, it is already possible to implement shape selection on your own in v3.3.x: install your own tap gesture recognizer; in its action, call -[MGLMapView visibleFeaturesAt:inStyleLayersWithIdentifiers:] (MGLMapView.visibleFeatures(at:styleLayerIdentifiers:)) to get the polyline at the tap event. You’ll have to implement and position a bubble-like view yourself. Internally, the SDK uses SMCalloutView; you could pull in your own copy or use a similar library to avoid having to reinvent the wheel.
As you’ve noted, the workaround in https://github.com/mapbox/mapbox-gl-native/issues/2082#issuecomment-249979397 is less robust but has the benefit of using the built-in annotation callout view functionality.
@inigo333 You can initialize a UITapGestureRecognizer to recognize if there was a tap inside your polygon. Example here
The anchor placement issue illustrated in https://github.com/mapbox/mapbox-gl-native/issues/2082#issuecomment-140134203 would be addressed by a port of @mourner's Polylabel library to C++.
Polylabel was integrated into mbgl in #7070.
I am implementing the workaround, since I need support for tapping polylines representing route sections. For simplicity the whole route is one section at the moment. I cannot find any ways to associate the MGLPolylineFeature returned from MGLMapView.visibleFeatures(at:styleLayerIdentifiers: to the original MGLPolylineFeature I add to the source let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: UInt(routeCoordinates.count))
self.routeSource?.shape = polyline
The returned polyline feature has no identifiers, attributes and I do not know how to get the coordinates out of the UnsafeMutablePointer: features.first?.coordinates It appears to be a completely new object (which is understandable), which does not preserve or double any of the information the original object had (which poses a problem.)
The only workaround I see at the moment is to put each section on a different layer and use the layerIdentifiers to distinguish them, but that is very ugly :( there could be hundreds of sections - users are not given restrictions, so this would have to be coordinated dynamically.
Help would be very much appreciated!
The returned polyline feature has no identifiers, attributes and I do not know how to get the coordinates out of the UnsafeMutablePointer:
features.first?.coordinates
Comparing the returned feature’s coordinates is currently the only way to match feature querying results with a feature you’re interested in. See this document for guidance on working with UnsafeMutablePointer in Swift.
Howdy! I've seen several references to being able to get an event at click/touch MGLPolyLine where you might release it at 3.4.1. It's currently 3.4.2. Can you give a current estimate of when you think that feature will be realized? I appreciate all of the effort and any direction you can lend on this would be helpful. Thanks!
@iFarmSupport I obviously can't speak on behalf of the developers, but in case you need a temporary workaround, mine is just that.
I did get the coordinates in the polyline returned from MGLMapView.visibleFeatures(at:styleLayerIdentifiers:. Unfortunately they lose precision compared to original line added:
Original polyline:
latitude: 55.864750000000001, longitude: -4.2521399999999998
latitude: 55.864820000000002, longitude: -4.2527099999999995
latitude: 55.864960000000004, longitude: -4.2528199999999998
latitude: 55.865080000000006, longitude: -4.2538900000000002
latitude: 55.864700000000006, longitude: -4.2540300000000002
latitude: 55.864320000000006, longitude: -4.2541500000000001
latitude: 55.864190000000008, longitude: -4.25305
latitude: 55.863740000000007, longitude: -4.2528800000000002
latitude: 55.863430000000008, longitude: -4.2529900000000005
latitude: 55.863070000000008, longitude: -4.2531400000000001
latitude: 55.86272000000001, longitude: -4.2532700000000006
latitude: 55.862390000000012, longitude: -4.253400000000001
latitude: 55.862300000000012, longitude: -4.2526400000000013
latitude: 55.862310000000015, longitude: -4.252600000000001
latitude: 55.862300000000012, longitude: -4.2524900000000008
latitude: 55.86234000000001, longitude: -4.2524700000000006
latitude: 55.862320000000011, longitude: -4.2523400000000002
latitude: 55.862320000000011, longitude: -4.2523200000000001
latitude: 55.862310000000008, longitude: -4.2523
latitude: 55.862320000000011, longitude: -4.2522900000000003
Returned polyline:
latitude: 55.864750112256786, longitude: -4.252140149474144
latitude: 55.864820100139724, longitude: -4.2527101188898087
latitude: 55.864960075527279, longitude: -4.2528200894594193
latitude: 55.865080107783143, longitude: -4.2538902908563614
latitude: 55.864700067080378, longitude: -4.2540297657251358
latitude: 55.864320022659314, longitude: -4.2541497945785522
latitude: 55.864189828368893, longitude: -4.2530500888824463
latitude: 55.863740165541458, longitude: -4.2528797686100006
latitude: 55.863430102116212, longitude: -4.2529897391796112
latitude: 55.863069988939515, longitude: -4.2531399428844452
latitude: 55.862390016920983, longitude: -4.2534001171588898
latitude: 55.862300080605081, longitude: -4.2526397109031677
latitude: 55.862309864482171, longitude: -4.2526001483201981
latitude: 55.862300080605081, longitude: -4.2524901777505875
latitude: 55.862339968703935, longitude: -4.2524700611829758
latitude: 55.862320024659624, longitude: -4.2523399740457535
latitude: 55.862320024659624, longitude: -4.2523198574781418
latitude: 55.862309864482171, longitude: -4.2522997409105301
latitude: 55.862320024659624, longitude: -4.2522896826267242
Depending on your use case, it is definitely possible to match the returned line with original. For my use case, I found a simpler solution though. I keep references to all the polylines the user can tap on (it is never too many) and then check for intersection of bounds.
// poly is the area around the point where the user tapped converted to map coordinates
let poly = MGLPolygon(coordinates: &polygonCoords, count: UInt(polygonCoords.count))
var found: RouteSection? = nil
//TODO: Could be improved by looking for streets closer to the centre of the tap?
for route in routeSections {
if(poly.intersects(route.line.overlayBounds)) {
found = route
break
}
}
if let selectedRouteSection = found {
if let selectedIndex = routeSections.index(of: selectedRouteSection) {
//if the user did select something, display it
self.selectedFeaturesSource?.shape = selectedRouteSection.line
self.navigationView.detailsOnSelectedSection(selectedRouteSectionIndex: selectedIndex)
}
} else {
// the tap was not on any of the routes, so we deselect everything
self.selectedFeaturesSource?.shape = nil
}
The side effect of this is that bounds refers to whole shape, rather than line. (True marks where tap is detected)

The above is just an example. My use case breaks the route into small sections, so user experience is acceptable. I am waiting for the proper functionality as well. I needed more than a pop-up to appear, since I have more data relating to each polyline, so for now this works :)
Removing android label, this is going to be integrated as part of #3720, which is aimed at next release.
Hi
I have my own CustomPolygonFeature subclass
class CustomPolygonFeature: MGLPolygonFeature {
var fillColor: UIColor?
var strokeColor:UIColor?
var name:String?
}
I am adding these polygons on top of mapView using .add(Overlay:) method
I have added a tapGesture on the mapView. When I click on tap I am checking the type of Feature I clicked on.
let spot = gesture.location(in: mapView)
let features = mapView.visibleFeatures(at: spot)
features.map{
print(type(of: $0))
}
I am expecting the result to be
Print:CustomPolygonFeature
Actual result:
Prints: MGLPolygonFeature
Is there anything wrong?
@CodeKunal, this is an inherent limitation of the feature querying APIs and runtime styling in general: #6178. In #9540, we amended the documentation to note this limitation.
This feature was implemented for in #9984, which is in iOS map SDK v3.7.0 and will be in macOS SDK v0.6.0 shortly.
Most helpful comment
Definiteyly, that's a lot better than not being able to do it at all!