Distanceinmeters Problems with Arrays and Sort

distanceInMeters Problems with arrays and sort

Use of unresolved identifier 'mall'

=> There is nothing named "mall" in the scope of the function locationManager didUpdateLocations. Meaning: locationManager didUpdateLocations has no clue what happens inside of tableView cellForRowAt indexPath and vice versa.


You could solve this problem by applying following steps to your code:

  1. Move the distance calculation from

    locationManager didUpdateLocations
    to

    tableView cellForRowAt indexPath
  2. add a property to your class that stores the current position:

    var currentPosition: CLLocation? = nil

  3. Store the position you receive in locationManager didUpdateLocations to the currentPosition property

  4. Add self.tableView.reloadData() after you received and assigned the position
  5. Use the mall and the currentPosition in cellForRowAt to calculate the distance and update the distanceLabel
  6. Keep in mind that currentPosition can be nil and the distance can not be calculated => make label empty or add something like "unknown distance" to it

Sort by distance Swift 3

Sort the malls first, and then use it as TableView's datasource, TableView will display the cells sorted as the order in Array sorted malls:

    let sortedMalls = malls.sorted { (mallOne, mallTwo) -> Bool in
return mallOne.distance < mallTwo.distance
}

Also a one liner:

let sortedMalls = malls.sorted { $0.mallOne.distance < $1.mallTwo.distance }

Use sortedMalls for:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

How to make all objects have different distanceInMeters?

You have two big problems in your cellForRowAt method.

  1. coords is an empty CLLocation, not the user's current location. You should store the user's current location in a property and use that property instead of the coords local variable.
  2. You use malls[0] instead of mall to create mallLocate. This is why all 100 objects appear to have the same coordinates.

Append array data into Struct variable

There's a lot of stuff going on here, so I'll walk you through the solution.

First of all, rest is a poor variable name. When I read it first, I thought this was "the rest", like the remainder of something, and was looking for where the "main" data is. Keystrokes don't cost you $, you can afford to just type out restaurants.

Secondly, you create an empty array, and manually append all these restaurants to it. Instead, you can just create an array from an array literal, that contains all the restaurants directly. That omits the need to have separate

To answer your direct question, you could use zip to iterate the rest and distance together, but the issue is that the rest1, rest2, ... variables, which were doomed to fail. What happens when you copy/paste a bunch of lines, and forget to change one of them, accidentally writing rest.append(rest6); rest.append(rest6), forgetting rest7?

Thirdly, your findDistance(from:long:) function takes two Doubles (a latitude and a longitude), and uses them to construct a CLLocation. But when you look where this function is used, you already have a CLLocation, which you decompose into two Doubles, only for findDistance(from:long:) to immediately reglue them back together into a CLLocation. Instead, just make findDistance take a CLLocation directly.

Fourthly, the Restaurants datatype is miss named. It's not a plurality of restaurants. It models a single restaurant. Name it accordingly: Restaurant

Applying these improvements (and also fixing a bunch of indenting along the way), we get:

struct Restaurant {
var name: String
var lat: Double
var long: Double
var distance: String?
}

let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]

var distances = [Double]()
for location in restaurants {
distance.append(findDistance(to: location))
}

func findDistance(to destination: CLLocation) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)

let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}

The direct problem

Here I'll illustrate the process I went through to address the direct question. However, don't use any of these. All these are shitty designs trying to work around underlying flaws in design. I show them in order to demonstrate what successive refinement looks like.

Now, to address the direct question:

First attempt: using zip(_:_:)

A first solution approach might be to use zip to iterate both restaurants and distances in parallel, and then mutating each restaurant from each `distance. Something like this:

for (restaurant, distance) in zip(restaurants, distances) {
restaurant.distance = distance
}

However, this won't work. Since Restaurant is a value type, the restaurant variable in the loop is a copy of that in the array. Setting its distance mutates the copy, but doesn't effect the original in the array.

Second attempt: manual indexing

We can work around this, albeit in a much less pretty way, by looping over the indices:

for i in restaurants.indices {
restaurants[i] = distances[i]
}

Third attempt: skip the distances array.

The second attempt works, but if the only purpose of distances is for us to fill it with a bunch of values, only to immediately use them and discard them, why do we both having the array at all?

for i in restaurants.indices {
restaurant.location = findDistance(to: location)
}

This is still not great, however. The Restaurant data type suffers from two stage initialization, which is a code smell. First we initialize it with nil location, then we set it to a real location. Why bother? Why don't we just set the location directly?

let distance = findDistance(to: location)
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: distance),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: distance),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: distance),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: distance),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: distance),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]

But this is still not good design...

Fixing the flaws with findDistance(to:) and Restaurant.distance

What does findDistance(to:) even really do? Internally, it gets a distance from some hard-coded, unnamed location CLLocation(latitude: 31.461512, longitude: 74.272024). When I say someRestaurant.distance, I would expect a distance to me. If it were a distance from some reference point A, I would expect the API to instead be spelt something like someResaurant.distanceFromNorthPole, or something to that effect.

Furthermore, why is it the Restaurant's job to store its distance to something else? What if I want restaurant.distanceToSouthPole, restaurant.distanceToEquator? The API would get pretty bloated, and a restaurant type ends up doing way too much. And what if I restaurant.distanceToMe? I can move, how is a pre-computed, stored value going to keep up with me as I move?

The solution is not to store a distance at all. Instead, provide an API that users of this data type can use to calculate distances to any point of their own choosing.


struct Restaurant {
var name: String
var lat: Double
var long: Double

func distance(from other: CLLocation) -> Double {
let selfLocation = CLLocation(latitude: self.lat, longitude: self.long)
return selfLocation.distance(from: other)
}
}

let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]

let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!

// and now, whenever you need the distance to pointA:

for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")

}

And surprise, this is still not the best we can make this!

Don't store doubles for lats and longs

That's what CLLocation is for. Notice that almost all uses of lat and long require first boxing it into a CLLocation. So let's just store that directly, rather than separating individual components and passing them around independently. This prevents bugs like useLocation(lat: self.lat, long: self.long /* oops! */).

struct Restaurant {
var name: String
var location: CLLocation

func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}

However, if you do this, the initializer now requires a CLLocation instead of two separate lat/long Doubles. This is better for interacting with location APIs (where CLLocation is the "common currency" type for exchanging location info), but it's more cumbersome for hard-coded locations like your restaurants, because all of your initializer calls get bloated with a bunch of calls to CLLocation.init(latitude:longitude:):

let restaurants = [
Restaurant(name: "English Tea House", CLLocation(latitude: 31.461812, longitude: 74.272524)),
Restaurant(name: "Cafe Barbera", CLLocation(latitude: 31.474536, longitude: 74.268103)),
Restaurant(name: "Butler's Chocolate", CLLocation(latitude: 31.467505), longitude: 74.251908)),
Restaurant(name: "Freddy's Cafe", CLLocation(latitude: 31.461312, longitude: 74.272124)),
Restaurant(name: "Arcadian Cafe", CLLocation(latitude: 31.464536, longitude: 74.268603)),
Restaurant(name: "Big Moes", CLLocation(latitude: 31.467305, longitude: 74.256908)),
]

To remedy this, we can tuck the CLLocation.init(latitude:longitude:) away into a little initializer for convenience. I do so in an extension of Restaurant rather than directly in the initial declaration of Restaurant, because doing so preserves the compiler-generated initializer (called the "member-wise initializer"), which would otherwise be replaced:

extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}

Which allows us to regain the previous nice syntax:

let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]

Mutability

Restaurant names and locations are unlikely to change for the lifetime of an instance of your app, so there's no need to keep them mutable. So lets fix that:

struct Restaurant {
var name: String
var location: CLLocation

func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}

And Finally...

We've come to the final stage. A well named Restaurant that doesn't suffer from the need for two-stage initialization, that provides up-to-date distance data for any points a user might like, and that isn't vulnerable to copy paste errors thanks to lat and long being glued together into a CLLocation.

struct Restaurant {
var name: String
var location: CLLocation

func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}

extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}

let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]

let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!

// and now, whenever you need the distance to pointA you can do this (for example):

for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")

}

How to find out distance between coordinates?

CLLocation has a distanceFromLocation method so given two CLLocations:

CLLocationDistance distanceInMeters = [location1 distanceFromLocation:location2];

or in Swift 4:

//: Playground - noun: a place where people can play

import CoreLocation

let coordinate₀ = CLLocation(latitude: 5.0, longitude: 5.0)
let coordinate₁ = CLLocation(latitude: 5.0, longitude: 3.0)

let distanceInMeters = coordinate₀.distance(from: coordinate₁) // result is in meters

you get here distance in meter so 1 miles = 1609 meter

if(distanceInMeters <= 1609)
{
// under 1 mile
}
else
{
// out of 1 mile
}

Function to calculate distance between two coordinates

What you're using is called the haversine formula, which calculates the distance between two points on a sphere as the crow flies. The Google Maps link you provided shows the distance as 2.2 km because it's not a straight line.

Wolfram Alpha is a great resource for doing geographic calculations, and also shows a distance of 1.652 km between these two points.

Drive distance vs. straight line distance (red line mine).

If you're looking for straight-line distance (as the crow files), your function is working correctly. If what you want is driving distance (or biking distance or public transportation distance or walking distance), you'll have to use a mapping API (Google or Bing being the most popular) to get the appropriate route, which will include the distance.

Incidentally, the Google Maps API provides a packaged method for spherical distance, in its google.maps.geometry.spherical namespace (look for computeDistanceBetween). It's probably better than rolling your own (for starters, it uses a more precise value for the Earth's radius).

For the picky among us, when I say "straight-line distance", I'm referring to a "straight line on a sphere", which is actually a curved line (i.e. the great-circle distance), of course.



Related Topics



Leave a reply



Submit