Realm filter CLLocation - ios

I have warpper class for CLLoaction
Wrapper
class Location: Object {
dynamic var long: Double = 0
dynamic var lat: Double = 0
}
I have to filter stored Location for those which are in 1km radius based on my current location. I thought NSPredicate with block would do the job, but realm doesn't support it. So my question is how other way I can achieve it?
Of course I could do something like this:
let locations = realm.objects(Location)
var locationsInRadius = [Location]()
for l in locations {
let location = CLLocation(latitude: l.lat, longitude: l.long)
if (location.distanceFromLocation(currentLocation) < radius){
locationsInRadius.append(l)
}
}
But it feels wrong according to whole realm concept of filters.

You can't search for objects by distance, but you can search by using a bounding box. Simply add latitude and longitude fields to your object, then:
Get current location
Create a bounding box around that location
Filter your objects by bounding box
In code, that could like this:
// 0. This example uses MapKit to calculate the bounding box
import MapKit
// 1. Plenty of answers for this one...
let currentLocation = CLLocationCoordinate2DMake(37.7749295, -122.4194155)
// 2. Create the bounding box with, 1km radius
let region = MKCoordinateRegionMakeWithDistance(currentLocation, 1000, 1000)
let northWestCorner = CLLocationCoordinate2DMake(
currentLocation.latitude + (region.span.latitudeDelta),
currentLocation.longitude - (region.span.longitudeDelta)
)
let southEastCorner = CLLocationCoordinate2DMake(
currentLocation.latitude - (region.span.latitudeDelta),
currentLocation.longitude + (region.span.longitudeDelta)
)
// 3. Filter your objects
let predicate = NSPredicate(format: "lat BETWEEN {%f, %f} AND lon BETWEEN {%f, %f}",
northWestCorner.latitude,
southEastCorner.latitude,
northWestCorner.longitude,
southEastCorner.longitude
)
let nearbyLocations = realm.objects(MyLocation).filter(predicate)
Note that you can still store your CLLocation object for other purposes, but you won't need it for the search.
Also note that as this searches a box rather than what you wanted, a circle with a 1km radius, this can return results of greater than 1km. If that's not ok, you would need to reduce the radius or make a fancier predicate.

For some reason, using a single predicate (lat BETWEEN {%f, %f} AND lon BETWEEN {%f, %f}) doesn't work with the current version of Realm. I'm using this nice lib: https://github.com/mhergon/RealmGeoQueries
This is how the predicate is built inside and it works fine: https://github.com/mhergon/RealmGeoQueries/blob/master/GeoQueries.swift:
let topLeftPredicate = NSPredicate(format: "%K <= %f AND %K >= %f", latitudeKey, box.topLeft.latitude, longitudeKey, box.topLeft.longitude)
let bottomRightPredicate = NSPredicate(format: "%K >= %f AND %K <= %f", latitudeKey, box.bottomRight.latitude, longitudeKey, box.bottomRight.longitude)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [topLeftPredicate, bottomRightPredicate])
Basically, here is how to use it:
let results = try! Realm()
.findNearby(YourModelClass.self, origin: location.coordinate, radius: 500, sortAscending: nil)
You can also change the default "lat" and "lng" keys by passing 2 extra parameters with your own naming (latitudeKey and longitudeKey).
Thanks to https://github.com/mhergon for providing this lib.

Related

how to detect difference between 2 points on the map and consider the zoom?

I am using a map with some annotations, I want to group the nearest annotations in annotation group to not overlap in the design in case of user zoom out, but my problem is to how to know the distance between annotations and this distance should change when zooming in and out.
the distance should be between points with x and y formate note meters
So my question is to how to catch the difference between 2 points on the map and consider the zoom
// convert location to cLLocation
let cLLocation1 = CLLocation(latitude: post1Location?.lat ?? 0, longitude: post1Location?.lng ?? 0)
let cLLocation2 = CLLocation(latitude: post2Location?.lat ?? 0, longitude: post2Location?.lng ?? 0)
// this is return the dinsactence in metres but i don't need that
let distance = cLLocation1.distance(from: cLLocation2)
let annotaionPoint1 = MKMapPoint(cLLocation1.coordinate)
let annotaionPoint2 = MKMapPoint(cLLocation2.coordinate)
let xDistance = max(annotaionPoint1.x, annotaionPoint2.x) - min(annotaionPoint1.x, annotaionPoint2.x)
let yDistance = max(annotaionPoint1.y, annotaionPoint2.y) - min(annotaionPoint1.y, annotaionPoint2.y)
this is working but zoom in and zoom out no effect so I need zoom to make change
if min(xDistance, yDistance) <= 32 {
/// action
}

How to calculate CLLocation distance between 2 CLLocation points using as a path an array of CLLocations and not a straight line

I have 2 CLLocation coordinates and I wanna know the distance between them using as a path an array of coordinates that go from point A to B. I know how to calculate the distance from point A to B but the problem is that it seems to be measured in a straight line rather than following a given route. Any idea on how to do this efficiently?
It's very simple if you have all CLLocations. Just one line code can do it. For example:
var locations : [CLLocation] = [CLLocation.init(latitude: CLLocationDegrees(10.00000), longitude: CLLocationDegrees(100.00000)),
CLLocation.init(latitude: CLLocationDegrees(10.00001), longitude: CLLocationDegrees(100.00001)),
CLLocation.init(latitude: CLLocationDegrees(10.00002), longitude: CLLocationDegrees(100.00002)),
CLLocation.init(latitude: CLLocationDegrees(10.00003), longitude: CLLocationDegrees(100.00003)),
]
let totalDistance = locations.dropFirst().reduce((locations.first!, 0.0)) { ($1 , $0.1 + $0.0.distance(from: $1)) }.1
print(totalDistance)
I made E.Coms answer more readable:
guard let firstLocation = locations.first else { return }
let distance = locations.reduce((location: firstLocation, distance: 0.0)) { partialResult, nextLocation in
return (nextLocation, partialResult.distance + partialResult.location.distance(from: nextLocation))
}.distance

How to find ArcGIS iOS AGSPoint xy coordinates with latitude and longitude?

I am creating a wayfinding app that will display directions based on the device's current location and desired destination. I am trying to add stop points. How would I find the x and y values of the AGSPoint given the latitude and longitude of the location? I found the following code after doing some research, but the compiler tells me that * is not a prefix unary operator
func findAGSPointXY(longitude:Double, latitude:Double) -> (){
let mercatorX = longitude * 0.017453292519943295 * 6378137.0;
let a = latitude * 0.017453292519943295;
let mercatorY = 3189068.5 * log((1.0 + sin(a))/(1.0 - sin(a)));
AGSPoint *graphicPoint = [AGSPoint pointWithX:mercatorX y:mercatorY spatialReference:[AGSSpatialReference wgs84SpatialReference]];
}
It looks like you're mixing objective-c and swift, there are objc pointers (*), objc methods call ([]) and swift const (let) in the same snippet. I assume you're using swift.
The ArcGIS SDK can reproject geometries, you don't have to compute theses values by hand.
//Create an AGSPoint from your parameters, specifying them as WGS 84.
let wgsPoint = AGSPoint(x: longitude, y: latitude, spatialReference: AGSSpatialReference.wgs84())
//Preparing a Geometry Engine
let geometryEngine = AGSGeometryEngine.defaultGeometryEngine()
//Get a projected (in web mercator) geometry (points, lines, polygons and whatever inherit AGSGeometry).
let projGeometry = geometryEngine.projectGeometry(wgsPoint, toSpatialReference: AGSSpatialReference.webMercatorSpatialReference())
//Cast it back to AGSPoint, might have some optional to manage
let projPoint = projGeometry as AGSPoint // Might be some optional to manage there
//Here you can use projPoint.x and projPoint.y

Creating a constant from properities returned from arrays

Basically I have two arrays one containing latitudes and the other containing longitudes (the real arrays are much longer obviously).
i.e:
latArray = [50.456782, 57.678654]
longArray = [14.578002, 17.890652]
I need to go trough these arrays with a for loop and create an object that would have respective latitude and longitude depending in order of the array. However I don't really know how to go about it.
i.e.
firstPlace.latitude = 50.456782
firstPlace.longitude = 14.578002
secondPlace.latitude = 57.678654
secondPlace.longitude = 17.890652
I need these as separate variables, because I need to pass them into MKAnnotation so that they can be later represented in an MKAnnotationView as various locations on the map.
Hopefully this is clear enough, any help is appreciated.
Thanks.
It would likely be better to use CLLocationCoordinate2D from the CoreLocation framework to represent a coordinate location.
This sounds like a good place to use the zip command.
Here is an example:
let latArray = [50.456782, 57.678654]
let longArray = [14.578002, 17.890652]
let coordinates = zip(latArray, longArray).map { lat, lon in
CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
print(coordinates)
Output (tidied slightly):
[CLLocationCoordinate2D(latitude: 50.456781999999997, longitude: 14.578002), CLLocationCoordinate2D(latitude: 57.678654000000002, longitude: 17.890651999999999)]
MKAnnotation uses a CLLocationCoordinate2D for it's coordinate property anyway, so this should be easier. You could even create your MKAnnotations in the map function if you wanted.
Example of using with a struct:
let latArray = [50.456782, 57.678654]
let longArray = [14.578002, 17.890652]
struct Place {
var name: String
var coordinate: CLLocationCoordinate2D
}
let places = zip(latArray, longArray).map { lat, lon in
Place(name: "Some place",
coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
}
print(places)

Where is MapKit Step polyline?

I am trying to print coordinates of all route steps, similar to Google Maps SDK's "legs".
But it tells me that I cannot use polyline property to obtain a coordinate?
Try this:
for step in self.route!.steps as [MKRouteStep] {
otherwise it treats step as AnyObject (which doesn't have a polyline property defined so you get that compiler error).
By the way, note that polyline.coordinate just gives the average center of the polyline or one endpoint. A polyline can have more than one line segment.
If you need to get all the line segments and coordinates along the polyline, see latitude and longitude points from MKPolyline (Objective-C).
Here's one possible translation to Swift (with help from this answer):
for step in route!.steps as [MKRouteStep] {
let pointCount = step.polyline.pointCount
var cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.alloc(pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for var c=0; c < pointCount; c++ {
let coord = cArray[c]
println("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.dealloc(pointCount)
}
As the first linked answer warns, you may get hundreds or thousands of coordinates per step depending on the route.
Swift 4.1, as of July 2018, based on the other answer.
let pointCount = step.polyline.pointCount
let cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for c in 0..<pointCount {
let coord = cArray[c]
print("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.deallocate()

Resources