Check if array has same Latitude Longitude swift - ios

I have a class which has those data
class Events {
var name: String!
var latitude: Double!
var longitude: Double!
}
And I fill it out with data from json.
So some Events have the same lat and lon but they are not on continuous, i mean its not that event3 is the same as event4 etc.
So I'm trying to show them on a map
Filling out this array
var events = [Events]()
And in this for loop i'm making the pins.
for events in events {
let annotation = MKPointAnnotation()
annotation.title = events.name
annotation.coordinate = CLLocationCoordinate2D(latitude: events.latitude, longitude: events.longitude)
mapView.addAnnotation(annotation)
}
How can I make a quick search before deploying the pins, to see if a pin has the same lat and lon with another pin, to add some digits just to show them both close?
Thanks a lot!

Use Set to find unique instances. In order to use Set your base element, Events in this case must be Hashable and by implication Equatable:
class Events : Hashable {
var name: String!
var latitude: Double!
var longitude: Double!
// implement Hashable
var hashValue: Int {
return latitude.hashValue | longitude.hashValue
}
// Implement Equatable
static func ==(lhs:Events, rhs:Events) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
Then your main loop is a direct extension of what you already have, note that this collapses all matches down to a single point and changes the name to indicate how many matches there are:
// Use a Set to filter out duplicates
for event in Set<Events>(events) {
let annotation = MKPointAnnotation()
// Count number of occurrences of each item in the original array
let count = events.filter { $0 == event }.count
// Append (count) to the title if it's not 1
annotation.title = count > 1 ? "\(event.name) (\(count))" : event.name
// add to the map
}
If, instead, you want to move the points so that they don't stack up, then you want something like, where we build up the set of occupied locations as we go and mutate the points to move them a little.
func placeEvents(events:[Events], mapView:MKMapView) {
var placed = Set<Events>()
for event in events {
if placed.contains(event) {
// collision: mutate the location of event as needed,
}
// Add the mutated point to occupied points
placed.formUnion([event])
// Add the point to the map here
}
}
If the values aren't expected to be exactly the same, but only within, eg., .0001 of each other, then you could use the following for hashValue and ==
fileprivate let tolerance = 1.0 / 0.0001
private var tolerantLat : Long { return Long(tolerance * latitude) }
private var tolerantLon : Long { return Long(tolerance * longitude) }
var hashValue : Int {
return tolerantLat.hashValue | tolerantLon.hashValue
}
static func ==(lhs:Events, rhs:Events) -> Bool {
return lhs.tolerantLat == rhs.tolerantLat && lhs.tolerantLon == rhs.tolerantLon
}

Related

Convert MKAnnotation array to set

I have a situation where I have map annotations as [MKAnnotation] array in swift. Now I need to convert this into a set for some operation. How can I do this in swift? Basically I need to add only the non existent annotations on the map while updating the map view.
You can find out if an annotation exists on the map by using mapView.view(for:) as mentioned here:
if (self.mapView.view(for: annotation) != nil) {
print("pin already on mapview")
}
"Basically I need to add only the non existent annotations on the map while updating the map view."
We first need to define what makes two annotations equal (in your scenario). Once that is clear you an override the isEqual method. You can then add annotations to a Set.
Here is an example :
class MyAnnotation : NSObject,MKAnnotation{
var coordinate: CLLocationCoordinate2D
var title: String?
convenience init(coord : CLLocationCoordinate2D, title: String) {
self.init()
self.coordinate = coord
self.title = title
}
private override init() {
self.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
}
override func isEqual(_ object: Any?) -> Bool {
if let annot = object as? MyAnnotation{
// Add your defintion of equality here. i.e what determines if two Annotations are equal.
return annot.coordinate.latitude == coordinate.latitude && annot.coordinate.longitude == coordinate.longitude && annot.title == title
}
return false
}
}
In the code above two instances of MyAnnotation are considered equal when you they have the same coordinates and the same title.
let ann1 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
let ann2 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), title: "Annot B")
let ann3 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
var annSet = Set<MyAnnotation>()
annSet.insert(ann1)
annSet.insert(ann2)
annSet.insert(ann3)
print(annSet.count) // Output : 2 (ann1 & ann3 are equal)

Sorting cells by distance

So I'm trying to sort the cells by distance calculated. I have a struct
struct Sal {
var name:String
var coordinatesLat: CLLocationDegrees
var coordinatesLong: CLLocationDegrees
//var distance?
}
in scene A
func distanceFromLocation(location: CLLocation) -> CLLocationDistance {
// Find lat and long coordinates of current location
let getLat = self.location.latitude
let getLon = self.location.longitude
let currentLocation = CLLocation(latitude: getLat, longitude: getLon)
// Calculate distance from current location to sal
let distance = currentLocation.distanceFromLocation(location)
return distance
}
scene B
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! SalCustomCell
let name = sals[indexPath.row].name
cell.name.text = name
// Calculate distance between current location and sal
let salLocation = CLLocation(latitude: sals[indexPath.row].coordinatesLat, longitude: sals[indexPath.row].coordinatesLong)
// Display distance in cell.
let distanceM = viewController.distanceFromLocation(salLocation)
let distanceKm = round(distanceM/1000)
cell.distance.text = String(distanceKm) + "km"
return cell
}
I call the functions from scene A to display the distance in Km in scene B. I'm trying to sort them by distance displayed in cell but i'm not to sure on how i would sort them by. If it was name i could just do
sals.sortInPlace ({ $0.name < $1.name })
but I can't do
sals.sortInPlace ({ $0.distance < $1.distance })
because it's not apart of the struct. Do I need to make it apart of the struct? If so how?
I also tried to create an empty array and append the distance into the array to sort that way
var distance = [Double]()
self.distance.append(distanceKm) // inside cellForRowAtIndexPath
distance.sortInPlace ({ $0 < $1 })
But this didn't work out
Rewrite Sal so that it contains your distance calculation function, like this:
struct Sal {
var name:String
var coordinatesLat: CLLocationDegrees
var coordinatesLong: CLLocationDegrees
func distanceFromLocation(location: CLLocation) -> CLLocationDistance {
// ...
}
}
Now your array of Sal has a Sal feature that you can sort on.

Local variable won't copy to global variable (swift)

I need to copy a local variable tuple (sortedMarkers) from the function closestMarker() into the global variable sortedMarkerGlobal, however it doesn't seem to be working thus far. I want to use sortedMarkersGlobal in another function but it's count is zero (empty).
class MapViewController: UIViewController {
#IBOutlet weak var mapView: GMSMapView!
var sortedMarkerGlobal: [(lat: String, long: String)] = []
var nextParkCount: Int = 1 // Used to iterate over sortedMarkers with nextPark IBAction button
let locationManager = CLLocationManager()
var lastLocation: CLLocation? = nil // Create internal value to store location from didUpdateLocation to use in func showDirection()
var isAnimating: Bool = false
var dropDownViewIsDisplayed: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
....
}
func closestMarker(userLat: Double, userLong: Double)-> [(lat: String, long: String)] {
/*
var lengthRow = firstRow.count
while (sortedMarkers.count != lengthRow) { // There are markers that haven't been added to sortedMarkers
// Haversine formula
// add nearest marker to sortedMarkers
// Remove recently added marker from ParseViewControler() type tuple
// Recurse until all markers are in sortedMarkers
}
*/
let markerToTest = ParseViewController()
let firstRow = markerToTest.returnParse()
let lat2 = degreesToRadians(userLat) // Nearest lat in radians
let long2 = degreesToRadians(userLong) // Nearest long in radians
let R = (6371).doubleValue // Radius of earth in km
var closestLat: Double? = nil // Used to store the latitude of the closest marker
var closestLong: Double? = nil // Used to store the longitude of the closest marker
//
var indexToDelete: Int = 0 // Used to delete the marker which has been added to sortedMarkers tuple
let lengthRow = firstRow.count
var latLongs: (String, String)
var sortedMarkers: [(lat: String, long: String)] = []
var dynamicRow = firstRow // Tuple that has markers deleted from it
while (sortedMarkers.count != lengthRow) { // Store markers from closest to furtherst distance from user
var shortestDistance = 100.00E100
for (var i = 0; i < dynamicRow.count; i++) {
let testMarker = dynamicRow[i]
let testLat = (testMarker.lat as NSString)
let testLong = (testMarker.long as NSString)
let doubleLat = Double(testLat as String)
let doubleLong = Double(testLong as String)
let lat1 = degreesToRadians(doubleLat!)
let long1 = degreesToRadians(doubleLong!)
let dLat = lat2 - lat1
let dLong = long2 - long1
let a = ((sin((dLat)/2)) * (sin((dLat)/2))) + (cos(lat1)*cos(lat2)*((sin((dLong)/2)) * (sin((dLong)/2))))
let b = sqrt(a)
let d = (2*R) * (asin(b)) // Haversine formula
if (d < shortestDistance) {
closestLat = (doubleLat)
closestLong = (doubleLong)
shortestDistance = d
indexToDelete = i// Keep updating index of closest marker to later be removed
}
}
latLongs = (String(closestLat!), String(closestLong!)) // Each time for loop will find closest marker ~ NOT WORKING ~
sortedMarkers.append(latLongs)
dynamicRow.removeAtIndex(indexToDelete) // Remove marker that has just been added
}
sortedMarkerGlobal = sortedMarkers
return sortedMarkers
}
sortedMarkerGlobal is not a global variable. currently it is an instance variable like any of the other variables you have defined (locationManager, lastLocation, isAnimating, etc).
if you want it to be a global variable you should define it outside of your class MapViewController.
another option would be to make it a class variable of MapViewController by defining it as:
class MapViewController: UIViewController {
static var sortedMarkerGlobal: [(lat: String, long: String)] = []
}
Well I tried to test your code in playground and it worked for me
import UIKit
class Test {
var global:[(lat: String, long: String)] = []
func test1() {
var sortedMarkers: [(lat: String, long: String)] = []
var latLongs: (String, String)
latLongs = ("123", "123")
sortedMarkers.append(latLongs)
global = sortedMarkers
}
}
let test:Test = Test()
test.test1();
print("global \(test.global)")
And the result is global [("123", "123")] so I guess it should also work for you, the problem might be somewhere else.
I would suggest that you just use a closures instead of saving to a global variable. This lets you pass in the parameters and return your result based on that. You can save that result in the function that called it and do what ever you want from there.

Efficiently finding NSManagedObject (Polygon) that a given point is within

I'm trying to find if a given point (CLLocationCoordinate2D) is within a Polygon NSManagedObject.
My Polygon object is defined as:
public class Polygon: NSManagedObject {
#NSManaged var points: NSOrderedSet?
#NSManaged var centroid: Point?
#NSManaged var computed : NSNumber!
}
And the Point object:
public class Point: NSManagedObject {
#NSManaged var longitude: NSNumber!
#NSManaged var latitude: NSNumber!
}
My current method uses this for creating the predicate for Polygon objects:
public static func nearbyPredicate(offset offset: Double, nearLocation location: CLLocationCoordinate2D) -> NSPredicate {
let maxLat = location.latitude + offset
let minLat = location.latitude - offset
let maxLong = location.longitude - offset
let minLong = location.longitude + offset
return NSPredicate(format: "(centroid.latitude <= %#) && (centroid.latitude >= %#) && (centroid.longitude >= %#) && (centroid.longitude <= %#) && (computed == false)", argumentArray: [maxLat, minLat, maxLong, minLong])
}
Where offset is an arbitrary search 'radius'. The computed property is a boolean that I set to true once I have tried to detect if the Polygon contains the given point so that subsequent fetch calls exclude the object (as seen in the predicate). For detection, I first fetch Polygons with that predicate above, then use this:
let location : CLLocationCoordinate2D // ... point to test
for poly in polysFetched {
poly.computed = NSNumber(bool: true)
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: (poly.points!.array as! [Point]).first!.longitude.doubleValue, y: (poly.points!.array as! [Point]).first!.latitude.doubleValue)) //set initial point
for pt in poly.points!.array as! [Point] {
path.addLineToPoint(CGPoint(x: pt.longitude.doubleValue, y: pt.latitude.doubleValue))
}
if (CGPathContainsPoint(path.CGPath, nil, CGPoint(x: location.longitude, y: location.latitude), false)) {
print("Poly \(poly) matches location \(location)")
return
}
}
This can be computationally heavy. The polygons I'm testing against can be quite large and oddly shaped.
Is there anyway to make this process more efficient using Core Data? Is there a way to offset the computation of calculating the containment of a point in an NSFetchRequest or in other ways?
I ended up resolving this in a few ways and sped up the process significantly. Since the Point NSManagedObject only existed for the purpose of fetching them via the Polygon relationships and computing the path (UIBezierPath) for the Polygon, I just computed the path during the creation of the Polygon object and stored it as a property. I also changed the Centriod relationship into the two properties c_lat, c_long and removed the Point class altogether! Indexing the c_lat and c_long properties sped up fetch requests too.
The new Polygon class is:
public class Polygon: NSManagedObject {
#NSManaged internal var pathData : NSData!
#NSManaged internal var c_lat : NSNumber!
#NSManaged internal var c_long : NSNumber!
#NSManaged var computed : NSNumber!
var path : UIBezierPath {
get {
return NSKeyedUnarchiver.unarchiveObjectWithData(pathData) as! UIBezierPath
}
set {
pathData = NSKeyedArchiver.archivedDataWithRootObject(newValue)
}
}
var centroid : CLLocationCoordinate2D {
get {
return CLLocationCoordinate2D(latitude: c_lat.doubleValue, longitude: c_long.doubleValue)
}
set {
c_lat = newValue.latitude
c_long = newValue.longitude
}
}
func containsLocation(location : CLLocationCoordinate2D) -> Bool {
return self.path.containsPoint(CGPoint(x: location.latitude, y: location.longitude))
}
}
Code Different's comment on my original post suggested the Wikipedia page for detecting points in polygons, but conveniently that is abstracted away from me with UIBezierPath and there exists a function CGPathContainsPoint that takes a boolean as its last argument to choose between the two algorithms for detecting points in polygons mentioned in the Wikipedia article.
For those encountering the same or similar issues that I did, I recommend inlining relationships and using computed properties to access non-primitive types of data you'd like to store in Core Data.

How can I get the index of an object in an array with a property value that matches a specific criteria?

Maybe this question has already been answered before or the answer is so obvious but I've been searching for days and cannot find an answer to this problem. I would appreciate any help on this as I'm at my wits' end.
class marker {
var latitude = Double()
var longitude = Double()
var coordinate = CLLocationCoordinate2DMake(latitude, longitude)
}
let someCoordinate = CLLocationCoordinate2DMake(someLatitude, someLongitude)
Now let's say that I have an array named "markers" of 10 marker objects with all properties already initialized. How can I find the index of the marker item whose coordinate value matches the value of someCoordinate?
EDIT
While trying out the suggestion of iosdk below, I found out that I was trying to compare the marker.coordinate property which is a CLLocationCoordinate2D. That did not work as I expected. Instead I tried this and it worked:
let markerIndex = indexOfMarker(markers) { $0.position.latitude == latitude && $0.position.longitude == longitude }
struct SomeStruct {
let a, b: Int
}
let one = SomeStruct(a: 1, b: 1)
let two = SomeStruct(a: 2, b: 2)
let three = SomeStruct(a: 3, b: 3)
let ar = [one, two, three]
let ans = ar.indexOf { $0.a == 2 } // 1?
Or, on Swift 1, the indexOf function can be:
func indexOfOld<S : SequenceType>(seq: S, predicate: S.Generator.Element -> Bool) -> Int? {
for (index, value) in enumerate(seq) {
if predicate(value) {
return index
}
}
return nil
}
And you'd replace the last line above with:
let ans = indexOfOld(ar) { $0.a == 2 } // 1?

Resources