CLGeocoder bug on CoreLocation SDK - swift

CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: coord.latitude, longitude: coord.longitude)) { [weak self] placeMarks, error in
      print("PLACEMARKSFIRST:")
      print(placeMarks?.first)
      self?.geoCodeCounter += 1
       
      guard let place = placeMarks?.first else {
        self?.selectionSubject.accept(nil)
        self?.geoCodeAddress()
        return
      } 
print("PLACEMARKSFIRST:")
      print(placeMarks?.first)
      self?.geoCodeCounter += 1
       
      guard let place = placeMarks?.first else {
        self?.selectionSubject.accept(nil)
        self?.geoCodeAddress()
        return
      }
      print("PLACE:")
      print(place)
      print(place.administrativeArea)
      print(place.subAdministrativeArea)
      print(place.locality)
      print(place.subLocality)
      print(place.thoroughfare)
      print(place.subThoroughfare)
      self?.geoAdministrativeArea = place.administrativeArea ?? ""
      self?.geoSubAdministrativeArea = place.subAdministrativeArea ?? ""
      self?.geoLocality = place.locality ?? ""
      self?.geoSubLocality = place.subLocality ?? ""
      self?.geoThoroughfare = place.thoroughfare ?? ""
      self?.geoSubThoroughfare = place.subThoroughfare ?? ""
DEBUG CODES:
PLACE: Darüşşafaka Cd. 5/2, Darüşşafaka Cd. 5/2, 34457 Sarıyer İstanbul, Türkiye # <+41.12570667,+29.02811721> +/- 100.00m, region CLCircularRegion (identifier:'<+41.12570100,+29.02817000> radius 70.68', center:<+41.12570100,+29.02817000>, radius:70.68m)
Optional("İstanbul")
Optional("Sarıyer")
Optional("Sarıyer")
Optional("Çamlıtepe")
Optional("Darüşşafaka Cd.")
Optional("5/2")
As you see above there is a conflict between the locality inside place and the response that place.locality returns. Locality must be "Darüşşafaka". How can I fix this?
Is it about a bug in CLGeocoder library?

Actually its not a bug even its not a mistake that you can fix.
You get what the locality value is at the location specified on the Apple maps database
If you see the doc you will see :
If the placemark location is Apple’s headquarters, for example, the value for this property would be the string “Cupertino”.
The locality value of Apple headquarters is Cupertino because it is expressed that way on maps.
As a result, if the locality value is defined as Sarıyer in the coordinates you entered, there is no way to change it.
You may need to check and manipulate other properties to get the value you want

Related

How to call async Firebase Geo Query in Swift

I am using the code given by Firebase for the Geo Query feature which I found from this post here: GeoHash Queries in Firebase with Swift
Right now, I am calling this function in the init of one of my classes, and I need this query to be completed before the rest of my code runs. I believe this is wait async/await comes into play but I am new to this concept and I am having trouble implementing this logic. In the code's current state, my attempt to print the number of Docs at the end will print 0, because that last print statement runs before my query completes. How do I make the code after my query wait until my query has completed?
Example format of the data in Firebase:
geohash:"9mupwu3mkc"
id:"13101C7F-D7FF-4141-BC5A-76602173C096"
lat:33.6863622
lng:-117.8264411
Address:"1 Civic Center Plaza, Irvine CA 92606"
Here is the code:
func getallDocs(radius: Double) {
// Find pickups within 50km of Basecamp
let center = CLLocationCoordinate2D(latitude: 33.9742268, longitude: -118.3947792)
let radiusInKilometers: Double = radius
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInKilometers)
let queries = queryBounds.compactMap { (any) -> Query? in
guard let bound = any as? GFGeoQueryBounds else { return nil }
return db.collection("pickups")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
print("\nDocs: Count \(documents.count)")
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let ownerAddress = document.data()["ownerAddress"] as? String ?? "no address"
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
print("ownerAddress: \(ownerAddress), distance: \(distance) \tlat: \(lat), \(lng)")
if distance <= radiusInKilometers {
matchingDocs.append(document)
}
}
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
print("Docs: \(matchingDocs.count)")
}

GMS reverseGeocodeCoordinate returns statusCode 400 error [duplicate]

This question already has an answer here:
Getting error com.google.HTTPStatus code 400 while resolving the address using GMSReverseGeocodeResponse
(1 answer)
Closed 4 years ago.
This has been function in my application for quite a while and only stopped working recently.
func reverseGeocodeCoordinate(coordinate: CLLocationCoordinate2D, onSuccess : #escaping LocationUpdateSuccess) {
let geocoder = GMSGeocoder()
geocoder.reverseGeocodeCoordinate(coordinate) { response, error in
if let address = response?.firstResult() {
if let lines = address.lines! as? [String] {
let title = lines.joined(separator: " ")
if (title.characters.count) > 3 {
print(title)
onSuccess(coordinate, title)
}
}
}
else
{
print("Error In Reverse GeoCoding \n \(error?.localizedDescription)")
}
}
}
For what it's worth, Here's what I'm sending as the parameter values
CLLocationCoordinate2D
- latitude : 31.51774603759463
- longitude : 74.340778893683378
Here's the exact text of the error when I make the request
The operation couldn’t be completed. (com.google.HTTPStatus error 400.)
Similarly
GMSPlacesClient().autocompleteQuery
wouldn't respond with results as it used to in the following function
func autoCompleteQuery(searchText: String) {
if searchText.characters.count != 0 {
self.tableView.alpha = 1
let countryFilter = GMSAutocompleteFilter()
countryFilter.country = "US"
var areaBounds : GMSCoordinateBounds? = nil
if let lastSavedLocation = MyLocationManager.sharedInstance.lastSavedLocation
{
let lat = lastSavedLocation.coordinate.latitude
let long = lastSavedLocation.coordinate.longitude
let offset = 200.0 / 1000.0;
let latMax = lat + offset;
let latMin = lat - offset;
let lngOffset = offset * cos(lat * M_PI / 200.0);
let lngMax = long + lngOffset;
let lngMin = long - lngOffset;
let initialLocation = CLLocationCoordinate2D(latitude: latMax, longitude: lngMax)
let otherLocation = CLLocationCoordinate2D(latitude: latMin, longitude: lngMin)
areaBounds = GMSCoordinateBounds(coordinate: initialLocation, coordinate: otherLocation)
}
placesClient.autocompleteQuery(searchText, bounds: areaBounds, filter: countryFilter, callback: { (result, error) -> Void in
self.nearbyPlacesList.removeAll()
if result == nil {
print("Error in autocompleteQuery -> \(error?.localizedDescription)")
return
}
for result in result!{
if let result = result as? GMSAutocompletePrediction {
self.nearbyPlacesList.append((id: result.placeID!, name: result.attributedFullText.string))
self.tableView.reloadData()
}
}
})
} else {
self.tableView.alpha = 0
}
}
with the following error message.
The operation couldn’t be completed. An internal error occurred in the Places API library. If you believe this error represents a bug, please file a report using the instructions on our community and support page (https://developers.google.com/places/ios-api/support).
I've looked into answers here, reinstalling the pods and what not.
I also matched the bundle identifier, lifted restrictions on API quotas, disabled and enabled the SDKs again from Google's Cloud console as shown below
but nothing worked. Any help would be greatly appreciated. Thanks
For anyone coming to this post sometime later, initialising the GMS Services and Places with right keys was the problem.
GMSServices.provideAPIKey(<API_KEY_GOES_HERE>)
GMSPlacesClient.provideAPIKey(<API_KEY_GOES_HERE>)
I was using an old key from a project which was no longer active on Google Cloud Console, hence returning a 400 Error. Hope this helps someone ;)

How to layout Firebase database

I want to know how to layout a Firebase database so I can easily get Data about coordinates. (Only started using Firebase yesterday so am very very new to it)
ViewController
var reference: DatabaseReference!
var handle: DatabaseHandle?
viewDidLoad
reference = Database.database().reference()
handle = ref.child("Places/America/Capital/Washington/Coordinates").observe(.value, with: { (snapshot) in
for child in snapshot.children {
let theSnap = child as! DataSnapshot
let theValue = theSnap.value as! Double
print(theValue)
}
This prints the coordinates perfectly. But is there any way I can get all the coordinates in my Database? Am I laying out the Database wrong or what syntax should I use in order to get all data that has latitude and longitude in it?
Image of Firebase Database
edit 1
Is there no easy way of doing a for-in loop instead of just copying the handle line and changing the necessary file path?
You are so very close.
The path you specified (the Firebase Reference) is for a specific set of coordinates at a coordinate node. You then iterate over the two child nodes within Coordinates which prints the latitude and longitude child nodes.
If you want to get the coords for all, you need to go up a few levels and use the same technique to iterate over the child nodes.
It's a bit hard to wrap your brain around at first so let me give you a leg up. Here's the complete code and the output. Note that I stored the coords as int's so adjust accordingly. I would probably suggest storing them as strings.
let placesRef = self.ref.child("Places")
placesRef.observeSingleEvent(of: .value, with: { snapshot in
for countryChild in snapshot.children { //iterate over each country
let countryChildSnap = countryChild as! DataSnapshot //cast the child to a snapshot
let countryName = countryChildSnap.key //get it's key, the country name
print("country: \(countryName)")
let capitalSnap = countryChildSnap.childSnapshot(forPath: "Capital") //get the nodes within the capital node
for stateChild in capitalSnap.children { //iterate over each state within the country
let stateChildSnap = stateChild as! DataSnapshot
let stateName = stateChildSnap.key
print(" state: \(stateName)")
let coordsSnap = stateChildSnap.childSnapshot(forPath: "Coordinates")
let lat = coordsSnap.childSnapshot(forPath: "latitude").value as! Int
let lon = coordsSnap.childSnapshot(forPath: "longitude").value as! Int
print(" latitude: \(lat) longitude: \(lon)")
}
}
})
and the output
country: America
state: Washington
latitude: 38 longitude: -77
country: France
state: Paris
latitude: 48 longitude: 2
country: Germany
state: Berlin
latitude: 52 longitude: 13
Note that I only want to read this once so I am using .observeSingleEvent. If you want to be notified of future changes, leverage .observe with .childAdded, .childChanged and .childRemoved.

Allocating the results of Reverse Geocoding to a global variable

I am using Swift 4 and Xcode 9.3.1. I'm including screenshots of the code.
I am new to mobile development/ programming in general and have been thinking about how to phrase this. So this is the best I can explain it:
I am building an app that gets the user's location in order to send assistance through to them. The app gets the user's coordinates, and displays a TextView with their address information. So pretty straight forward mapKit/coreLocation functionality. So far, so good: Getting the coordinates with startUpdatingLocation() works fine, and I've used Reverse Geocoder to get the street name & locality. But they-- meaning the decoded street and locality strings-- only print out if I call them within the closure, not outside it. I've understood (correctly or incorrectly?) that variables that need to be available for multiple functions within a class should to be declared globally at the top. However I can't figure out how to extract the information from this closure in order to use it elsewhere.
I've been googling and reading through questions in stackOverflow and I feel like something really simple is missing but can't figure out what. Things I've tried unsuccessfully so far:
1_ Defining global variables as empty strings at the beginning of the class
and using the variable names inside the closure where the geocoding reverse method happens, in an attempt to store the resulting strings, but when I try to print the variables outside the closure, the global variable is still and empty string ("").
[global variables declared at the top][1]
2_Defining an empty, global array of strings and appending the information from inside the closure to the global array. Still got an empty array outside the closure. (so same as 1)
3_Create a function --func decodedString()-- to return the data as a String, so I can use it by declaring
*let streetLocation : String = decodedString()*
However when I declare that function like this :
var street = ""
var locality = ""
// Get the street address from the coordinates
func deocodedString() -> String {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
self.street = placemark.name!
self.locality = placemark.locality!
let string = "\(self.street), \(self.locality)"
return string
}
}
}
I get an error of: Unexpected non-void return value in void function
unexpected no void return value in void function
Lastly, if I pass the information straight into a TextView within the closure by using the code below, my textView updates successfully-- but I can't format the strings, which I need to do in order to make them look like the design instructions I'm following (aka some bold text, some regular text, and some different sizes of text):
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
self.street = placemark.name!
self.locality = placemark.locality!
let string = "\(self.street), \(self.locality)"
self.addressTextView.text = string
}
}
So that's why I can't just pass it through with the textView.text = string approach.
I'd appreciate some help...I have been looking though StackOverFlow, youtube and other tutorial places but I can't figure out what I'm missing, or why my function declaration generates an error. I have already destroyed and reversed my code several times over last 24 hs without getting an independent string that I can apply formatting to before passing it into the textView and I'm at a loss as to how else to approach it.
When you call this function the reverseGeocodeLocation runs in the background thread. So if you want to return the address in this method you should use escaping closure.
func getaddress(_ position:CLLocationCoordinate2D,completion:#escaping (String)->()) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
let street = placemark.name!
let locality = placemark.locality!
let string = "\(street), \(locality)"
completion(string)
}
}
}
self.getaddress(position.target) { address in
print(address)
self.addressTextView.text = address
}
I had a problem with google geocoder to update the label on the map screen.
So I did this, first, create
swift file name: GoogleAPI just call it as you like.
class GoogleAPI {
static let sharedInstance = GoogleAPI()
private init() { }
var addressValue = ""
public func geocoding(lat: Double, long: Double) {
Alamofire.request("https://maps.googleapis.com/maps/api/geocode/json?latlng=\(lat),\(long)&key=YOUR_GOOGLE_KEY").responseJSON { (response) in
if response.result.isSuccess {
let dataJSON : JSON = JSON(response.result.value!)
self.geocoding(json: dataJSON)
} else {
print("Error \(response.result.error!)")
}
}
}
fileprivate func geocoding(json: JSON) {
let json = json["results"]
let address = json[1]["formatted_address"].stringValue
addressValue = address
print("pin address \(addressValue)")
}
}
This is an API call to Google to fetch all from a response and parse the only street.
After that go to your View Controller with a map where is the pin, map etc..
Set up a pin, marker to be draggable. [marker1.isDraggable = true]
Then add this function
mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker)
and add call from above like this :
func mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker) {
GoogleAPI.sharedInstance.geocoding(lat: marker.position.latitude, long: marker.position.longitude)
DispatchQueue.main.async {
self.txtSearch.text = GoogleAPI.sharedInstance.addressValue
}
}
txtSearch is my search text field.
yea I know, that can be done better, but no time. this is working.
Swift 4.2

How do i return coordinates after forward geocoding?

I am trying to see whether the user is within a certain distance of an address. I have successfully managed to get the users location, and convert the address with forward geocoding. I am left with two sets of coordinates. I am trying to make an if statement saying if they are within "a distance", print something!
Currently when i print the coordinates inside the placemark function i get the desired coordinates. When i call them to create eventLatitude and eventLongitude they become 0.0. I know this is a ascycronous problem, but i am unsure on who to resolve this. Can someone give me an example.
My code is below
before the viewdidload i have these variables
var placemarkLongitude = CLLocationDegrees()
var placemarkLatitude = CLLocationDegrees()
then inside the function i set these variables to the placemark coordinates
if let objects = objects {
for object in objects {
self.geocoder = CLGeocoder()
//get address from object
let COAddress = object.objectForKey("Address")as! String
let COCity = object.objectForKey("City")as! String
let COState = object.objectForKey("State")as! String
let COZipCode = object.objectForKey("ZipCode")as! String
let combinedAddress = "\(COAddress) \(COCity) \(COState) \(COZipCode)" //all parts of address
print(combinedAddress)
//make address a location
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
if(error != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0]
{
let placemark = placemarks![0]
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
print("Longitude: ", self.placemarkLongitude, " Latitude: ", self.placemarkLatitude)
}
})
// user location
let userLatitude = self.locationManager.location?.coordinate.latitude //THIS RETURNS A VALUE
let userLongitude = self.locationManager.location?.coordinate.longitude //THIS RETURNS A VALUE
print("User Location is ", userLatitude, ", " ,userLongitude)
let userLocation = CLLocation(latitude: userLatitude!, longitude: userLongitude!)
// event location
let eventLatitude = self.placemarkLatitude // THIS RETURNS 0.0
let eventLongitude = self.placemarkLatitude // THIS RETURNS 0.0
print("Event Location is ", eventLatitude, ", " ,eventLongitude)
let eventLocation = CLLocation(latitude: eventLatitude, longitude: eventLongitude)
//Measuring my distance to my buddy's (in km)
let distance = userLocation.distanceFromLocation(eventLocation) / 1000
//Display the result in km
print("The distance to event is ", distance)
if (distance < 100) {
print("yay")
}
}
}
You are correct about the asynchronous issue. Basically, you cannot do anything after this code:
// [A1]
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {
(placemarks, error) -> Void in
// [B] ... put everything _here_
})
// [A2] ... nothing _here_
The reason is that the stuff inside the curly braces (B) happens later than the stuff outside it (including the stuff afterward, A2). In other words, the code in my schematic above runs in the order A1, A2, B. But you are dependent on what happens inside the curly braces, so you need that dependent code to be inside the curly braces so that it executes in sequence with the results of the geocoding.
Of course this also means that the surrounding function cannot return a result, because it returns before the stuff in curly braces has even happened. The code in my schematic goes A1, A2, return! Only later does B happen. So clearly you cannot return anything that happens in B because it hasn't happened yet.
Just pass the coordinate values obtained from the completionHandler to any other method and do what you like to do.
{
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
// After this code pass the values like,
passingTheCoordinates(placemarkLatitude, placemarkLongitude)
}
func passingTheCoordinates(latitude:CLLocationDegrees, _ longitude:CLLocationDegrees){
}
Did not have enough reputation to reply your question but I also have this same problem today. I don't know much about your app design but for my case (which is stuck at the same place like you, same func, same problem, can't save to variable). My solution (maybe kinda temporally, does not good) is to save (placemark.location?.coordinate.latitude)! and (placemark.location?.coordinate.longitude)! to CoreData as Double.
This is how I implemented it. As I said before, since I don't know your app much so depend on your need, you might want something else.
LocationManager.sharedInstance.getReverseGeoCodedLocation(address: searchBar.text!, completionHandler: { (location:CLLocation?, placemark:CLPlacemark?, error:NSError?) in
if error != nil {
print((error?.localizedDescription)!)
return
}
if placemark == nil {
print("Location can't be fetched")
return
}
//Saving geo code to Core Data
newEntry.lat = (placemark?.location?.coordinate.latitude)!
newEntry.long = (placemark?.location?.coordinate.longitude)!
})
Credit to this repo for the LocationManager.swift file