-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add MGLCircle (with radius expressed in physical units) #2167
Comments
ref #1740 |
@incanus is there a roadmap plan for when MGLCircle will be implemented? Trying to update from the old "RMMapView" based SDK and the lack of replacement for an RMCircle is blocking me. |
Thanks for the ping on this @cowgp. It hasn't been on the roadmap, but thinking this was relatively straightforward, I took a look today in The Cocoa side was pretty easy, but as it turns out, even though we have support for circles in the renderer and styles as of 0451aca, we don't have support for circle annotations in that API. It's an interesting bind, since circles seems like shapes (have alpha & color) but behave like points (have a single coordinate and a scalar Stay tuned — will try to push this through soon. |
@cowgp Sorry, I'm gonna have to say we're going to push this out a ways right now — too many higher priorities. If it was as easy as I had initially guessed, with pure Cocoa API, it would have been easy to rationalize, but increasing the surface area of our annotations API right now is not ideal given the other things we're integrating them with. Stay tuned; I'd love to get this in, but it's not a priority roadmap right now. |
@incanus - Thanks so much for looking into it, I appreciate the effort. I do hope it can remain a priority, seems like an important feature from my perspective. That said, totally understand that this update is all new underpinnings and thus there's a lot to build up. |
@cowgp Depending on if your needs are truly at runtime, you could possibly use the existing support in the style spec for rendering circles in the meantime? mapbox/mapbox-gl-js#1446 shows an example of how this looks in the style. You could also consider mutating the style JSON and custom setting it at runtime as a workaround. |
@incanus - I looked into the style spec you linked, but no it doesn't seem to be a viable solve for our needs. We have ~45k active users each with one or more geofences that they can set/modify at will. This is accomplished in the existing deprecated SDK by adding an annotation at the center point of the geofence and then in the layerForAnnotation delegate method returning an RMCircle with the correct radius in meters for the fence. It seems to me a clear case where an MGLCircle is necessary to have in the new SDK. |
What I meant about mutating the style, though, could be a workaround here. The idea would be that you'd have a parsed representation of the style JSON (as, say, an Admittedly more work than just having |
Is this still not available with the new release of mapbox studio? |
Nope, no further movement here just yet. Workaround is to code circle primitives into your style and/or add them to the style JSON at runtime for now. |
@incanus can you please provide an example of this workaround? |
Ah, yes, this would require something like the GeoJSON source in #2161 in order to modify runtime data for the circle layer(s). In that case, I would recommend instead adding |
I take that back — this could also be accomplished with the existing annotations API, with the slight downside that unlike a true |
Of course the other main hack here is that circle radii are only valid for a single zoom since they don't scale as the map does. You could combat this by pre-generating circles of varying radii for given integer zoom levels and only allowing external control to zoom to each, or else disallowing map zoom once you've added a particular image. Either way you'd be using |
@incanus - Thanks for showing a functional work around - helpful for sure. The performance hit when zooming is significant - and disabling zoom is not an option for me. That loops me back to your original suggestion, I haven't tried it yet, but I was about to - mutating the style that is. You've made subsequent suggestions here and a POC workaround that did not head down that path. Is there anything wrong with still pursuing circles in the style JSON? |
@cowgp It should work, the trick is updating your vector tile source data rapidly enough. You would put |
@incanus - I've initially implemented my work around in a similar manner to the sample you provided, but I've hit a snag and am now banging my head. using a standard MGLPointAnnotation and providing the circle as a UIImage does a fine job of simply rendering a circle... our use case is a radial geofence, which unfortunately means that the circle needs to adjust it's size correctly when the zoom level on the map changes. the head banging comes in that I have tried implementing both: The trick is that to get a new image displayed for the annotation, you have to somehow get - Do you have a better trick for forcing |
@cowgp Can you model this using Polygon annotation? A polygon with 50 or so edges will very closely match a circle. |
@mb12 - the polygon is a much much better solve than rendering circles as images and struggling with the zoom. I went with 45 sides (evenly divisible into 360) and it's reasonably performant and it correctly scales when the map is zoomed. Thanks a bunch for the suggestion. For anyone else following along, below is my method, and what seems to be the best work around for current lack of MGLCircle. - (MGLPolygon*)polygonCircleForCoordinate:(CLLocationCoordinate2D)coordinate withMeterRadius:(double)meterRadius
{
NSUInteger degreesBetweenPoints = 8; //45 sides
NSUInteger numberOfPoints = floor(360 / degreesBetweenPoints);
double distRadians = meterRadius / 6371000.0; // earth radius in meters
double centerLatRadians = coordinate.latitude * M_PI / 180;
double centerLonRadians = coordinate.longitude * M_PI / 180;
CLLocationCoordinate2D coordinates[numberOfPoints]; //array to hold all the points
for (NSUInteger index = 0; index < numberOfPoints; index++) {
double degrees = index * degreesBetweenPoints;
double degreeRadians = degrees * M_PI / 180;
double pointLatRadians = asin( sin(centerLatRadians) * cos(distRadians) + cos(centerLatRadians) * sin(distRadians) * cos(degreeRadians));
double pointLonRadians = centerLonRadians + atan2( sin(degreeRadians) * sin(distRadians) * cos(centerLatRadians),
cos(distRadians) - sin(centerLatRadians) * sin(pointLatRadians) );
double pointLat = pointLatRadians * 180 / M_PI;
double pointLon = pointLonRadians * 180 / M_PI;
CLLocationCoordinate2D point = CLLocationCoordinate2DMake(pointLat, pointLon);
coordinates[index] = point;
}
MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfPoints];
return polygon;
} The delegate methods for stroke color and fill color are then called for you to set your colors: It is unfortunate however that supplying a color with less than 1 alpha value for the fill does weird things with the GL blend mode. your only option is to return the alpha in the delegate method: |
Hello - Nadia from Mapbox support pointed us towards #2167 (comment) as a workaround for our need for this feature. I'm definitely putting in a feature request for this. Here are a couple of iOS apps that do this: It is implemented as a feature in Citymapper (approximate walking isochrone) using Google Maps and in Tile app which using Apple Maps. |
Thanks to @Ruben2112, here is a Kotlin conversion from your code:
Add to mapboxMap:
|
This issue is specifically about supporting georeferenced circles in the iOS SDK (and macOS SDK). #4312 tracks the same thing for the Android SDK. |
Am I correct in thinking there's still no parity with MKCircle in MapBox? Seems like a glaring omission to me. Edit: one of the proposed solutions (#2167 (comment)) doesn't even compile anymore, and crashes when changed to use NSExpression. |
#2167 (comment) Actually works. I just changed |
This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions. |
This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions. |
Any news on this front? (trying to get the bot not to close it again) |
Given it’s 3 years old, would be nice to have some sort of update on the issue for sure. |
@jlubeck @KaneCheshire just wanted to assure you that this is on our list - and you should start seeing some movement on this issue soon. |
Thanks for the update @julianrex |
Great to hear that @julianrex ! |
@jlubeck @KaneCheshire - After some internal discussions we are currently planning a major refactor to how shapes are generated to be added to a map. The current plan is to wait until that work is done before proceeding to address this issue. I know this is not ideal, but we believe the upcoming refactoring we’re working on will make adding all sorts of shapes to a map much easier for everyone in the long run. We’ll be sure to post back here as soon as we have more updates. If you're looking for a workaround right now, one possibility would be to implement this with turf-swift. There are limitations to this approach though - this is not a true circle, but rather a an |
Thanks for the heads up @captainbarbosa |
How's the refactoring going? This issue will be 4 years old in just over a month :/ |
All, thanks for following this ticket. Our refactor is going well, and we are nearing a go/no-go decision on merging #14534 sometime this year after we resolve the remaining considerations. Please upvote this comment if #14534 would work for your use case and solve your needs. Per its description, the PR implements a new |
Any news on this issue? Looks like it was on good track back in August... And Github is returning a 500 error on the pull request so I can't see if there is progress there... Weird thing is that is the only PR that is returning an error, the others work just fine Thanks! |
Inspired by @incanus's answer in 2017, I came up with another solution, which doesn't requires usage of Code below is written in Swift 5, using Mapbox-iOS-SDK 6.1.0, The performance seems ok on a simulator. // Code below only covers 1 annotation use case for demo purpose, make changes if you need to support multiple annotations
class MyMapBoxViewController: UIViewController, MGLMapViewDelegate {
/// Use this stored property to track my circle
private var circleAnnotationView: MGLAnnotationView?
private let radiusMeters: Double = 200
private let divisor: Double = 2
...
/// Return a clear image instead of default pin image, so the annotation will be invisible.
/// if you do not want to hide annotation, comment this method out
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
let clearImage = mapView.dequeueReusableAnnotationImage(withIdentifier: "clear") ??
.init(image: UIImage(color: .clear)!, reuseIdentifier: "clear")
return clearImage
}
/// Return a circular `MGLAnnotationView` for the annotation, use ViewController's stored property `circleAnnotationView` to track it
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// use lng, lat to create a unique reuse identifier
let reuseIdentifier = "\(annotation.coordinate.longitude)-\(annotation.coordinate.latitude)"
// reuse or create a circle view around the annotation
circleAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) ??
CustomAnnotationView(reuseIdentifier: reuseIdentifier)
var radiusPoints = radiusMeters / mapView.metersPerPoint(atLatitude: annotation.coordinate.latitude)
radiusPoints /= divisor // divide by (2) here so circle doesn't fill the entire screen, please choose your own divisor
circleAnnotationView?.bounds = .init(origin: .zero, size: .init(width: radiusPoints, height: radiusPoints))
circleAnnotationView?.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
return circleAnnotationView
}
/// When map zooms, rescale `circleAnnotationView` based on zoom level
func mapViewRegionIsChanging(_ mapView: MGLMapView) {
guard circleAnnotationView != nil else { return }
var radiusPoints = radiusMeters / mapView.metersPerPoint(atLatitude: mapView.centerCoordinate.latitude)
radiusPoints /= divisor // divide by (2) here so circle doesn't fill the entire screen, please choose your own divisor
circleAnnotationView?.bounds = .init(origin: .zero, size: .init(width: radiusPoints, height: radiusPoints))
}
} Some subclass and extension used in code above class CustomAnnotationView: MGLAnnotationView {
override func layoutSubviews() {
super.layoutSubviews()
// Use CALayer’s corner radius to turn this view into a circle.
layer.cornerRadius = bounds.width / 2
layer.borderWidth = 2
layer.borderColor = UIColor.textBlue.cgColor
}
}
extension UIImage {
/// Use color to init an `UIImage`
convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
} |
Now that v8 has landed in 0451aca we have a
Circle
layer type.The text was updated successfully, but these errors were encountered: