Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Convenience methods for distance expression #275

Closed
chloekraw opened this issue Apr 22, 2020 · 4 comments · Fixed by #295
Closed

Convenience methods for distance expression #275

chloekraw opened this issue Apr 22, 2020 · 4 comments · Fixed by #295
Assignees
Labels
enhancement New feature or request

Comments

@chloekraw
Copy link
Contributor

mapbox/mapbox-gl-native#16397 landed in core. @1ec5 what would you recommend for iOS bindings?

@chloekraw chloekraw added the enhancement New feature or request label Apr 22, 2020
@1ec5
Copy link
Contributor

1ec5 commented Apr 24, 2020

NSExpression has built-in distanceToLocation:fromLocation: and distanceFromLocation: functions, which are requested in mapbox/mapbox-gl-native#11786. But we won’t be able to hook into those functions for the time being, because they take arbitrary coordinates as input. mbgl doesn’t support an expression operator that evaluates to a feature’s centroid, and it might be misleading to conflate geographic coordinates with tile coordinates anyways. Additionally, the distance operator requires a third parameter indicating the unit.

Given the mbgl expression operator’s different syntax, we’ll need to install an aftermarket expression function called mgl_distanceFromGeometry:inUnit:. The second argument can be either a string or perhaps an NSUnitLength on iOS 10.0 and above:

MGLPolyline *routePolyline = [MGLPolyline polylineWithCoordinates:route.coordinates count:route.coordinateCount];
[NSExpression expressionWithFormat:@"2 * mgl_distanceFromGeometry:unit:(%@, %@)", routePolyline, NSUnitLength.meters];
let routePolyline = MGLPolyline(route.shape!)
NSExpression(format: "2 * mgl_distanceFromGeometry:unit:(%@, %@)", routePolyline, UnitLength.meters)

I would prefer to omit the unit argument from the NSExpression function. The unit argument is very unusual for Apple platforms, where APIs are supposed to traffic exclusively in SI base units. It would be very natural for developers get back a number that they can treat as a CLLocationDistance, which is a double expressed in meters, because so many other APIs on the system use the same type, including Core Location and our iOS navigation SDK. If the developer needs to express a distance in some other unit, they can either convert it using NSMeasurement or embed the conversion factor:

// Filter out POIs within a lane width (12 feet or 3.7 meters) of the route.
MGLPolyline *routePolyline = [MGLPolyline polylineWithCoordinates:route.coordinates count:route.coordinateCount];
[NSPredicate predicateWithFormat:@"mgl_distanceFromGeometry(%@) > 3.7", routePolyline];

// Filter out POIs near a maneuver point.
MBRouteStep *routeStep = routeProgress.currentLegProgress.currentStepProgress.step;
[NSPredicate predicateWithFormat:@"mgl_distanceFromGeometry(%@) > %@", routeStep.coordinate, @(MBRouteControllerManeuverZoneRadius)];

// Filter out seamarks in international waters.
NSMeasurement *eezMeasurement = [[NSMeasurement alloc] initWithDoubleValue:200 unit:NSUnitLength.nauticalMiles];
CLLocationDistance eez = [eezMeasurement measurementByConvertingToUnit:NSUnitLength.meters].doubleValue;
[NSPredicate predicateWithFormat:@"mgl_distanceFromGeometry(%@) > %@", maritimeBoundaryMultiPolyline, eez];

// Include radio stations within listening range.
double metersFromMiles = [NSUnitLength.miles.converter constant];
[NSPredicate predicateWithFormat:@"mgl_distanceFromGeometry(%@) < range * %@", userLocation, metersFromMiles];
// Filter out POIs within a lane width (12 feet or 3.7 meters) of the route.
let routePolyline = MGLPolyline(route.shape!)
NSExpression(format: "mgl_distanceFromGeometry(%@) > 3.7", routePolyline)

// Filter out POIs near a maneuver point.
let routeStep = routeProgress.currentLegProgress.currentStepProgress.step
NSExpression(format: "mgl_distanceFromGeometry(%@) > %@", routeStep.coordinate, RouteControllerManeuverZoneRadius)

// Filter out seamarks in international waters.
let eezMeasurement = Measurement<UnitLength>(value: 200, unit: .nauticalMiles)
let eez = eezMeasurement.converted(to: .meters).value
NSPredicate(format: "mgl_distanceFromGeometry(%@) > %@", maritimeBoundaryMultiPolyline, eez)

// Include radio stations within listening range.
let metersFromMiles = (UnitLength.miles.converter as! UnitConverterLinear).constant
NSPredicate(format: "mgl_distanceFromGeometry(%@) < range * %@", userLocation, metersFromMiles)

@1ec5
Copy link
Contributor

1ec5 commented Apr 27, 2020

I would prefer to omit the unit argument from the NSExpression function.

Oh, I missed that the argument had already been removed: mapbox/mapbox-gl-native#16434. So the function would be mgl_distanceFromGeometry rather than mgl_distanceFromGeometry:unit:.

@1ec5
Copy link
Contributor

1ec5 commented Apr 27, 2020

As a workaround until this function is implemented, developers can use +[NSExpression expressionWithMGLJSONObject:] or +[NSPredicate predicateWithMGLJSONObject:] like this:

// Filter out POIs within a lane width (12 feet or 3.7 meters) of the route.
MGLPolyline *routePolyline = [MGLPolyline polylineWithCoordinates:route.coordinates count:route.coordinateCount];
NSData *routePolylineData = [routePolyline geoJSONDataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *routePolylineJSONObject = [NSJSONSerialization JSONObjectWithData:routePolylineData options:0 error:NULL];
NSExpression *distanceExpression = [NSExpression expressionWithMGLJSONObject:@[@"distance", routePolylineJSONObject]];
layer.predicate = [NSPredicate predicateWithFormat:@"%@ > 3.7", distanceExpression];
// Filter out POIs within a lane width (12 feet or 3.7 meters) of the route.
let routePolyline = MGLPolyline(route.shape!)
let routePolylineData = try! routePolyline.geoJSONData(using: .utf8)
let routePolylineJSONObject = try! JSONSerialization.jsonObject(with: routePolylineData, options: [])
let distanceExpression = NSExpression(mglJSONObject: ["distance", routePolylineJSONObject])
layer.predicate = NSPredicate(format: "%@ > 3.7", distanceExpression)

@1ec5
Copy link
Contributor

1ec5 commented Apr 28, 2020

The convoluted workaround in #275 (comment) would be quite a bit simpler if we expose a private API: #283.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants
@1ec5 @chloekraw and others