How to populate longitude latitude from JSON file into array - ios

I need to find the nearest cities around my current location. Would you advice me how should I populate the coordinates into an array in my project and how to calculate the distance between my location and the nearest around me. I have to display the distance in (KM) and the city name ,so that the user can choose which city best fits for him.I also need to call the coordinates and the names in my code from a json file
My JSON File is:
{
"City A": {
"Position": {
"Longitude": 9.96233,
"Latitude": 49.80404
}
},
"City B": {
"Position": {
"Longitude": 6.11499,
"Latitude": 50.76891
}
},
"City C": {
"Position": {
"Longitude": 6.80592,
"Latitude": 51.53548
}
and my function so far:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userlocation:CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
let location = CLLocationCoordinate2D(latitude: userlocation.coordinate.latitude, longitude: userlocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.5, 0.5)
let region = MKCoordinateRegion(center: location, span: span)
Mapview.setRegion(region, animated: true)
let locations = ["42.6977,23.3219","43.6977,24.3219"]
let distanceMeters = userlocation.distanceFromLocation(CLLocation(latitude: 42.6977,longitude: 23.3219))
let distanceKilometers = distanceMeters / 1000.00
let roundedDistanceKilometers = String(Double(round(100 * distanceKilometers) / 100)) + " km"
// var distanceMeter = NSString(format: "\(distanceKilometers)km")

Assuming the JSON file is in the application bundle and is named cities.json first create a custom struct City
struct City {
let name : String
let location : CLLocation
}
Then in your controller declare an array cities
var cities = [City]()
To populate the array deserialize the JSON and extract the data
guard let url = NSBundle.mainBundle().URLForResource("cities", withExtension: "json"), jsonData = NSData(contentsOfURL: url) else { return }
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! [String:AnyObject]
for (cityName, position) in jsonObject {
let coordinates = position["Position"] as! [String:CLLocationDegrees]
let longitude = coordinates["Longitude"]!
let latitude = coordinates["Latitude"]!
let location = CLLocation(latitude: latitude,longitude: longitude)
let city = City(name: cityName, location: location)
cities.append(city)
// print(cities)
}
} catch let error as NSError {
print(error)
}
To find the nearest location you could sort the array by distance
let nearestCity = cities.sort({ userLocation.distanceFromLocation($0.location) < userLocation.distanceFromLocation($1.location) }).first

Related

Cannot convert value of type 'String?' to expected argument type 'CLLocationDegrees' (aka 'Double')

I try to display the distance between a post creator and the current user within a cell.
My error now is
Cannot convert value of type 'String?' to expected argument type 'CLLocationDegrees' (aka 'Double')
in the following code:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
let geoCoder = CLGeocoder()
let myLocation = CLLocation(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)
/*Here the error*/ let myBuddysLocation = CLLocation(latitude: job!.distance, longitude: job!.distance)
let distance = myLocation.distance(from: myBuddysLocation) / 1000
print(String(format: "The distance to my buddy is %.01fkm", distance))
}
}
This is my JobClass:
class Job {
var distance: String?
let ref: DatabaseReference!
init(distance: String? = nil) {
self.distance = distance
ref = Database.database().reference().child("jobs").childByAutoId()
}
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
distance = value["distance"] as? String
}
}
func save() {
let newPostKey = ref.key
let postDictionary = [
"distance" : self.distance!
] as [String : Any]
self.ref.setValue(postDictionary)
}
}
I hope someone knows how to solve it, if you wan't I can add more code if it helps // I'm new to coding
The error is self-explanatory: You are trying to set a String? instead of a CLLocationDegrees, but you can even use a Double type.
There are several ways to fix this:
1) You can change your class definition as it follows:
class Job {
// Change distance to Double and set a default value instead of optional
var distance: Double = 0.0
let ref: DatabaseReference!
// Change the init
init(distance: Double = 0.0) {
self.distance = distance
ref = Database.database().reference().child("jobs").childByAutoId()
}
//You have to change all your distance to Double
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
// So change this cast
distance = value["distance"] as Double
}
}
}
2) You can try a cast from String? to Double, but I don't recommend this.
Anyway, here it is how you can do it:
if let dist = Double(job!.distance){
let myBuddysLocation = CLLocation(latitude: dist, longitude: dist) -> Error Appears in this Line
let distance = myLocation.distance(from: myBuddysLocation) / 1000
print(String(format: "The distance to my buddy is %.01fkm", distance))
}
Actually in your function the only problem are there you passing the string value in place of double you need to change like this.
let myBuddysLocation = CLLocation(latitude: Double.init(job!.distance) ?? 0.0, longitude: Double.init(job!.distance) ?? 0.0)
You need to change your function like this
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
let geoCoder = CLGeocoder()
let myLocation = CLLocation(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)
let myBuddysLocation = CLLocation(latitude: Double.init(job!.distance) ?? 0.0, longitude: Double.init(job!.distance) ?? 0.0)
let distance = myLocation.distance(from: myBuddysLocation) / 1000
print(String(format: "The distance to my buddy is %.01fkm", distance))
}
}
It's normal because distance should be Double value. Changing it to: var distance: Double? should correct it.
Or parse the value to Double let myBuddysLocation = CLLocation(latitude: Double(distance)!, longitude: Double(distance)!)
I recommend you to avoid using ! because it could generate a fatal error if the string isn't a number, you can guard the value with guard or if let like this example:
if let latitude = Double(distance) {
let myBuddysLocation = CLLocation(latitude: latitude, longitude: latitude)
}

Show multiple locations on google Maps from Firestore Swift

I am trying to show the locations on google maps I am getting the longitutde and latitude from Firestore.
I created a struct in which I store latitude and longitude
struct Location {
var latitude: String = ""
var longitute: String = ""
}
And here is my firestore code to get the longitude and latitude
for document in snapshot!.documents {
self.location.append(Location(latitude: "\(document.data()["Latitude"] ?? "")", longitute: "\(document.data()["longitude"] ?? "")"))
print(self.location)
guard let long = document.data()["Latitude"] as? String else { return}
guard let lat = document.data()["longitude"] as? String else { return}
let markerStart = GMSMarker(position: CLLocationCoordinate2D(latitude: Double(long) ?? 0.0, longitude: Double(lat) ?? 0.0))
markerStart.map = self.mapView
}
I am getting locations in my console but when I converting it into Doubles trying to show it on google maps it is not working. Please help?
Document Value is ["userid": 24xDkrtBV6cJrBvRD3U0PmyBF3o2, "createddatetime": FIRTimestamp: seconds=1546584489 nanoseconds=461000000>, "user_role": sales man, "Latitude": 20.6108261, "longitude": 72.9269003, "batterypercentage": 66, "name": Keyur , "company_code": 001]
So you have this data:
["userid": 24xDkrtBV6cJrBvRD3U0PmyBF3o2, "createddatetime": FIRTimestamp: seconds=1546584489 nanoseconds=461000000>, "user_role": sales man, "Latitude": 20.6108261, "longitude": 72.9269003, "batterypercentage": 66, "name": Keyur , "company_code": 001]
Instead of this kind of code:
for document in snapshot!.documents {
self.location.append(Location(latitude: "\(document.data()["Latitude"] ?? "")", longitute: "\(document.data()["longitude"] ?? "")"))
print(self.location)
guard let long = document.data()["Latitude"] as? String else { return}
guard let lat = document.data()["longitude"] as? String else { return}
let markerStart = GMSMarker(position: CLLocationCoordinate2D(latitude: Double(long) ?? 0.0, longitude: Double(lat) ?? 0.0))
markerStart.map = self.mapView
}
We can better it like so,
for document in snapshot!.documents {
self.location.append(Location(latitude: "\(document.data()["Latitude"] ?? "")", longitute: "\(document.data()["longitude"] ?? "")"))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double,
let longitude = document.data()["Latitude"] as? Double else { return }
let markerStart = GMSMarker(position: CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
markerStart.map = self.mapView
}
The reason why the program does not reach the lines 72 73 74, is because of the guard let. It fails to convert to String your assumed Double latitude and longitude from your document.data(). Do it like my above code, and then you can improve it further as you want.

Loop through coordinates and find the closest shop to a point Swift 3

Idea :
App lets drivers see the closest shop/restaurants to customers.
What I have :
Coordinates saved as strings
let clientLat = "24.449384"
let clientLng = "56.343243"
a function to find all the shops in my local area
I tried to save all the coordinates of a shop in my local area and I succeeded:
var coordinates: [CLLocationCoordinate2D] = [CLLocationCoordinate2D]()
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
// need to sort coordinates
// need to find the closest
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.mapView.addAnnotation(annotation)
}
}
})
}
What I need:
I wish to loop through the coordinates and find the closest shop (kilometers) to the lat and long strings then put a pin on it.
UPDATE
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
let pointToCompare = CLLocation(latitude: 24.741721, longitude: 46.891440)
let storedCorrdinates = self.coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
self.coordinate = storedCorrdinates
}
let annotation = MKPointAnnotation()
annotation.coordinate = self.coordinate[0].coordinate
self.mapView.addAnnotation(annotation)
}
})
}
Thank you #brimstone
You can compare distances between coordinates by converting them to CLLocation types and then using the distance(from:) method. For example, take your coordinates array and map it to CLLocation, then sort that based on the distance from the point you are comparing them to.
let coordinates = [CLLocationCoordinate2D]()
let pointToCompare = CLLocation(latitude: <#yourLat#>, longitude: <#yourLong#>)
let sortedCoordinates = coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
Then, to set your annotation's coordinate to the nearest coordinate, just subscript the sortedCoordinates array.
annotation.coordinate = sortedCoordinates[0].coordinate
I would like to share my solution :)
1) In my case, I upload data from the API, so I need to create a model.
import MapKit
struct StoresMap: Codable {
let id: Int?
let title: String?
let latitude: Double?
let longitude: Double?
let schedule: String?
let phone: String?
let ukmStoreId: Int?
var distanceToUser: CLLocationDistance?
}
The last variable is not from API, but from myself to define distance for each store.
2) In ViewController I define:
func fetchStoresList() {
NetworkManager.downloadStoresListForMap(firstPartURL: backendURL) { (storesList) in
self.shopList = storesList
let initialLocation = self.locationManager.location!
for i in 0..<self.shopList.count {
self.shopList[i].distanceToUser = initialLocation.distance(from: CLLocation(latitude: self.shopList[i].latitude!, longitude: self.shopList[i].longitude!))
}
self.shopList.sort(by: { $0.distanceToUser! < $1.distanceToUser!})
print("Closest shop - ", self.shopList[0])
}
}
3) Don't forget to call the function in viewDidLoad() and import MapView framework :)

Can I get a store name/restaurant name with mapkit?(swift)

I have a mapview and I added a method to drop a pin on the location where the user had pressed. The callout shows the address of the location as shown on the image.
screenshot of my mapview with annotation pin and callout view.
And my code is as following:
func onTapGestureRecognized(sender: UILongPressGestureRecognizer) {
self.mapView.removeAnnotations(mapView.annotations)
let location = tapRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
let getLat: CLLocationDegrees = coordinate.latitude
let getLon: CLLocationDegrees = coordinate.longitude
let theLocation: CLLocation = CLLocation(latitude: getLat, longitude: getLon)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(theLocation, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
var theLocationName = ""
var theStreetNumber = ""
var theStreet = ""
var theCity = ""
var theZip = ""
var theCountry = ""
// Address dictionary
print(placeMark.addressDictionary as Any)
// Location name
if let locationName = placeMark.name{
theLocationName = locationName
}
if let streetNumber = placeMark.subThoroughfare{
theStreetNumber = streetNumber
}
// Street address
if let street = placeMark.thoroughfare {
theStreet = street
}
// City
if let city = placeMark.locality {
theCity = city
}
// Zip code
if let zip = placeMark.postalCode{
theZip = zip
}
// Country
if let country = placeMark.isoCountryCode{
theCountry = country
}
let annotation = MKPointAnnotation()
annotation.title = theLocationName
annotation.subtitle = theStreetNumber + " " + theStreet + ", " + theCity + ", " + theCountry + ", " + theZip
if let location = placeMark.location {
annotation.coordinate = location.coordinate
// Display the annotation
self.mapView.showAnnotations([annotation], animated: true)
}
})
}
As you can see, when I try to get the location name by calling the line (((( if let locationName = placeMark.name )))), I can only get the address: "5197 Yonge St", instead of the restaurant name : " Pho 88 Restaurant ".
Can anyone tell me where I did wrong? or is it simply cannot be achieved? Thanks!
I can't give you a complete answer, but I may be able to point you in the right direction. As far as I can see, you will only ever get a single entry returned for placemarks, but you can get a more complete list using MKLocalSearchRequest. the challenge is going to be how you match up the returned values to exactly which one you want - maybe you have to ask the user to select from a short list? Also, I think you need to specify which type of establishment you're searching for. Here's something you could include within your completion handler above
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurant" // or whatever you're searching for
request.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: getLat, longitude: getLon), span: self.mapView.region.span)
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
print("There are \(response.mapItems.count)")
for item in response.mapItems {
// You may be able to match the address to what the geoCode gives you
// or present the user with a list of options
print("\(item.name), \(item.placemark)")
}
}
When I was testing this, the addresses didn't always match up, even when zoomed in - so that geoCoder might give me 1-3 Some Street while the MKLocalSearchRequest returned a restaurant at 3 Some Street

How to populate an array with information from JSON File and calculate distance?

I have a JSON File here:
{
"People": [{
"A1": "New York",
"B1": "ShoppingMall1",
"C1": "43.0757",
"D1": "23.6172"
},
{
"A1": "London",
"B1": "ShoppingMall2",
"C1": "44.0757",
"D1": "24.6172"
}, {
"A1": "Paris",
"B1": "ShoppingMall3",
"C1": "45.0757",
"D1": "25.6172"
}, {
"A1": "Bern",
"B1": "ShoppingMall4",
"C1": "41.0757",
"D1": "21.6172"
}, {
"A1": "Sofia",
"B1": "ShoppingMall5",
"C1": "46.0757",
"D1": "26.6172"
}
]
}
and from this JSON File I have to take the names and the coordinates of the shopping malls and populate them into an array. This array I want to use in Table View Cells. The main idea is calculating the nearest shopping malls around the user's current location. Here I calculate the user's current location.
#IBAction func LocateMe(sender: AnyObject) {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userlocation: CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
let location = CLLocationCoordinate2D(latitude: userlocation.coordinate.latitude, longitude: userlocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.5, 0.5)
let region = MKCoordinateRegion(center: location, span: span)
}
let distanceMeters = userlocation.distanceFromLocation(CLLocation(latitude: ??,longitude: ??))
let distanceKilometers = distanceMeters / 1000.00
let roundedDistanceKilometers = String(Double(round(100 * distanceKilometers) / 100)) + " km"
But I do not know how to take all of the shopping malls coordinates and compare them.I also do not how to populate them into an array which I need to use for the Table View Cells.I am new in swift and I will be glad if someone can help me with that.
I had been working on your question and this are my results,
First of all I recommend you to use one JSON framework such as SwiftyJSON but I don't use any because I don't know if you want to, so
first we need to load our json using this code
let pathForPlist = NSBundle.mainBundle().pathForResource("JSON", ofType: "json")!
let JSONData = NSData(contentsOfFile: pathForPlist)
after we need to parse this data and convert to JSONObject
let JSONObject = try NSJSONSerialization.JSONObjectWithData(JSONData!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
and convert to properly Objects using an initializer from Dictionary, note that we use NSJSONReadingOptions.MutableContainers because our json is an array of dictionaries
this is the full code, note that I define a class for your data type named ObjectShop to help with the calculation later
import UIKit
import MapKit
class ObjectShop
{
var A1 = ""
var B1 = ""
var C1 = ""
var D1 = ""
init?(dict:[String:AnyObject])
{
A1 = dict["A1"] as! String
B1 = dict["B1"] as! String
C1 = dict["C1"] as! String
D1 = dict["D1"] as! String
}
func getCoordenate2D() -> CLLocationCoordinate2D
{
return CLLocationCoordinate2D(latitude: Double(self.C1)!, longitude: Double(self.D1)!)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let pathForPlist = NSBundle.mainBundle().pathForResource("JSON", ofType: "json")!
let JSONData = NSData(contentsOfFile: pathForPlist)
do
{
var objects = [ObjectShop]()
let JSONObject = try NSJSONSerialization.JSONObjectWithData(JSONData!, options: NSJSONReadingOptions.MutableContainers) as! [String:AnyObject]
print(JSONObject)
for dic in JSONObject["People"] as! [[String:AnyObject]] {
print(dic)
let objc = ObjectShop(dict: dic)
objects.append(objc!)
}
for object in objects {
print(object.getCoordenate2D())
}
}
catch _
{
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I hope this helps you, let me know if you have any question

Resources