Get nearby places from Google API sorted by distance - ios

I am currently stuck on this functionality where user need nearby places results on the basis of distance.
For e.g :-
If i search "Kochi" and i am in India
Then kochi is in India as well as in Japan
So result should be like
1. Kochi, India
2. Kochi, Japan
This is just an example, user can also search landmarks, city, streets etc.. But i want all results to be sorted by distance.Closer results will display first and then far places. But not able to get results according to requirement.
Similar functionality is done on android and they are using it by passing radius (Like 500 km from current location)
What i have tried so far :-
Using GMSPlacesClient.autocompleteQuery and passing bounds in it for current location
GMSPlacesClient().autocompleteQuery(txtLocation.text!, bounds: bounds, filter: filter, callback: {(results, error) -> Void in
if let error = error {
print("Autocomplete error \(error)")
return
}
if let results = results {
}
})
Using GooglePlaces.placeAutocomplete
GooglePlaces.placeAutocomplete(forInput: self.txtLocation.text!, offset: 0, locationCoordinate: nil, radius: nil, language: nil, types: [GooglePlaces.PlaceType.Regions], components: nil, completion: { (response, error) in
// To do
})
I also used this url (https://maps.googleapis.com/maps/api/place/autocomplete/json?input=pan&location=30.704316,76.712106&radius=50000&components=country:IN) for Google API but for these kind of alternatives i have to do custom parsing.

I am playing around with the same GoogleMaps API. I do the same as you requested but through a different way code is attached, I do this on a button press for 'Search' you can either set the bounds to coordinates or the map view frame. In the below it is currently using the users current location for the bounds and it then works its way out from that I think this works near enough perfectly:
let autoCompleteController = GMSAutocompleteViewController()
autoCompleteController.delegate = self as! GMSAutocompleteViewControllerDelegate
// Set bounds to map viewable region
let visibleRegion = googleMaps.projection.visibleRegion()
let bounds = GMSCoordinateBounds(coordinate: visibleRegion.farLeft, coordinate: visibleRegion.nearRight)
// New Bounds now set to User Location so choose closest destination to user.
let predictBounds = GMSCoordinateBounds(coordinate: userLocation.coordinate, coordinate: userLocation.coordinate)
autoCompleteController.autocompleteBounds = predictBounds
// Set autocomplete filter to no filter to include all types of destinations.
let addressFilter = GMSAutocompleteFilter()
addressFilter.type = .noFilter
autoCompleteController.autocompleteFilter = addressFilter
// Change text color
UISearchBar.appearance().setTextColor(color: UIColor.black)
self.present(autoCompleteController, animated: true, completion: nil)
I am having a problem with Google Directions and getting the calculated distance and miles if you have any code for this that may be of help for me!

Related

Mapbox iOS - Show multiple callouts

I want to show multiple annotations at once in Mapbox (note: NOT MKMap). There doesn't seem to be a method to show multiple annotations, although it can add multiples.
e.g.
mapView.addAnnotations(<annotationsArray>)
Even though there is a method as follows, it does not show the annotations.
mapView.showAnnotations(<annotationsArray>, animated: true)
What I want it to show multiple instances as shown in the image below. i.e. I want to show several "Hello World" annotations in the map, for each point the user added.
Is there a similar method in Mapbox to show multiple annotations in Swift?
Use this to display multiple annotations:
func setAnnotations() {
var index = 0
for placeModel in self.placesList! {
let marker = MyMGLPointAnnotation()
marker.willUseImage = true
marker.id = placeModel.id
marker.type = placeModel.type
if let img = placeModel.images.first {
marker.imgURL = img.thumbnail
}
marker.coordinate = CLLocationCoordinate2D(latitude: placeModel.location?.latitude ?? 0, longitude: placeModel.location?.longitude ?? 0)
marker.title = placeModel.name.withoutHtmlTags
marker.subtitle = placeModel.descr.htmlToString
//DispatchQueue.main.async {
self.mapView.setCenter(CLLocationCoordinate2D(latitude: latitude, longitude: longitude), zoomLevel: 12, animated: false)
self.mapView.addAnnotation(marker)
}
}
You can display multiple instance of the annotations but for the selection you can tap one by one and it will show title like the image you shared.
Edit: There is way you can create your custom xib load as annotation to show the title.
Check: https://github.com/mapbox/ios-sdk-examples/blob/0e2c8ce878de500f36c4168f7a1e62041c8adbdf/Examples/Swift/AnnotationViewsAndImagesExample.swift
https://docs.mapbox.com/ios/maps/examples/annotation-view-image/

MapBox - detect zoomLevel changes

How can I simply detect zoom level changes? Is it possible?
I simply need to hide my annotation views when zoom level is not enough.
regionDidChange:animated: is not intended to use for me. Any other way?
I need to hide my labels here:
and show them here:
This is what I currently do with my labels:
class CardAnnotation: MGLPointAnnotation {
var card: Card
init(card: Card) {
self.card = card
super.init()
let coordinates = card.border.map { $0.coordinate }
let sumLatitudes = coordinates.map { $0.latitude }.reduce(0, +)
let sumLongitudes = coordinates.map { $0.longitude }.reduce(0, +)
let averageLatitude = sumLatitudes / Double(coordinates.count)
let averageLongitude = sumLongitudes / Double(coordinates.count)
coordinate = CLLocationCoordinate2D(latitude: averageLatitude, longitude: averageLongitude)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var annotations = [CardAnnotation]()
mapView.addAnnotations(annotations)
Of the two main ways to add overlays to an MGLMapView, the runtime styling API is better suited for text labels and also for varying the appearance based on the zoom level. While you’re at it, you might as well create the polygons using the same API too.
Start by creating polygon features for the areas you want shaded in:
var cards: [MGLPolygonFeature] = []
var coordinates: [CLLocationCoordinate2D] = […]
let card = MGLPolygonFeature(coordinates: &coordinates, count: UInt(coordinates.count))
card.attributes = ["address": 123]
// …
cards.append(card)
Within any method that runs after the map finishes loading, such as MGLMapViewDelegate.mapView(_:didFinishLoading:), add a shape source containing these features to the current style:
let cardSource = MGLShapeSource(identifier: "cards", features: cards, options: [:])
mapView.style?.addSource(cardSource)
With the shape source in place, create a style layer that renders the polygon features as mauve fills:
let fillLayer = MGLFillStyleLayer(identifier: "card-fills", source: cardSource)
fillLayer.fillColor = NSExpression(forConstantValue: #colorLiteral(red: 0.9098039216, green: 0.8235294118, blue: 0.9647058824, alpha: 1))
mapView.style?.addLayer(fillLayer)
Then create another style layer that renders labels at each polygon feature’s centroid. (MGLSymbolStyleLayer automatically calculates the centroids, accounting for irregularly shaped polygons.)
// Same source as the fillLayer.
let labelLayer = MGLSymbolStyleLayer(identifier: "card-labels", source: cardSource)
// Each feature’s address is an integer, but text has to be a string.
labelLayer.text = NSExpression(format: "CAST(address, 'NSString')")
// Smoothly interpolate from transparent at z16 to opaque at z17.
labelLayer.textOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %#)",
[16: 0, 17: 1])
mapView.style?.addLayer(labelLayer)
As you customize these style layers, pay particular attention to the options on MGLSymbolStyleLayer that control whether nearby symbols are automatically hidden due to collision. You may find that the automatic collision detection makes it unnecessary to specify the textOpacity property.
When you create the source, one of the options you can pass into the MGLShapeSource initializer is MGLShapeSourceOption.clustered. However, in order to use that option, you’d have to create MGLPointFeatures, not MGLPolygonFeatures. Fortunately, MGLPolygonFeature has a coordinate property that lets you find the centroid without manual calculations:
var cardCentroids: [MGLPointFeature] = []
var coordinates: [CLLocationCoordinate2D] = […]
let card = MGLPolygonFeature(coordinates: &coordinates, count: UInt(coordinates.count))
let cardCentroid = MGLPointFeature()
cardCentroid.coordinate = card.coordinate
cardCentroid.attributes = ["address": 123]
cardCentroids.append(cardCentroid)
// …
let cardCentroidSource = MGLShapeSource(identifier: "card-centroids", features: cardCentroids, options: [.clustered: true])
mapView.style?.addSource(cardCentroidSource)
This clustered source can only be used with MGLSymbolStyleLayer or MGLCircleStyleLayer, not MGLFillStyleLayer. This example shows how to work with clustered points in more detail.
One option is to add the labels as a MGLSymbolStyleLayer, then determine the textOpacity based on zoom level.
If you are using the current version of the Maps SDK for iOS, you could try something like:
symbols.textOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %#)", [16.9: 0, 17: 1])
The dynamically styled interactive points example shows one approach to this.
Is the problem that when you zoom out, your annotations are too close together? If so, it is better to group them together than to hide them entirely. See Decluttering a Map with MapKit Annotation Clustering.

Ordering Geofire results on Distance

I've been experimenting with Geofire for iOS but can't seem to find any way of returning the distance from the search position in a circle query. The GFQueryResultBlock only returns the key and position. Am I right in assuming that I have to calculate the distance myself?
Let's say I am making a nearby restaurant app with Firebase, and want to display the 20 closest restaurants to the user, ordered by how close they are. I could create a circle query and then increase the search radius via a loop until I find 20 restaurants. Then calculate the distance for each one and sort them before displaying them to the user. Is this a reasonable approach, given that a large amount of work is being done in the app to structure the data (calculating distance & sorting)?
I've noticed that javascript Geofire queries return distance from the center, but I guess the iOS and android versions are different from this.
When you are querying from Geofire, relevant results are automatically ordered by ascending order. So in order to get the distance , Im just using the distanceFromLocation function:
Here my code:
func getGeoFirePlaces(){
let geofireRef = FIRDatabase.database().reference().child("testForGeofire")
let geoFire = GeoFire(firebaseRef: geofireRef)
//geoFireRef is pointing to a firebase reference where I previously set all places' location
let userPosition = CLLocation(latitude: 32.0776067, longitude: 34.78912)
let circleQuery = geoFire.queryAtLocation(userPosition, withRadius: 2)
circleQuery.observeEventType(.KeyEntered, withBlock: { (key: String!, location: CLLocation!) in
print("Key '\(key)' entered the search area and is at location '\(location)'")
//getting distance of each Place return with the callBack
let distanceFromUser = userPosition.distanceFromLocation(location)
print(distanceFromUser)
})
}
Hope this help!

How To Tell If User Is At A Specific Location?

Essentially what I need to do is find out if a user is at a specific place (IE at a venue). And if the user is, allow access to a specific ViewController.
I've been looking high and low for an answer to this problem online and surprisingly I haven't found anything. I will say I'm pretty new to iOS development.
I don't need anything as complex as geofencing like in the Ray Wenderlich tutorial, and I don't need to run it in the background. I also don't need to know if they entered or left. Just whether or not they are within that area or not when the user clicks a button.
I've gotten as far as being able to get the users location using CoreLocation, but I'm confused as to how I will go about identifying if the user is at the specific location. Ideally, I will want a radius of about 5 miles (It's a big location).
if you have the user's location as well as the venue's location you can do the following:
let radius: Double = 5 // miles
let userLocation = CLLocation(latitude: 51.499336, longitude: -0.187390)
let venueLocation = CLLocation(latitude: 51.500909, longitude: -0.177366)
let distanceInMeters = userLocation.distanceFromLocation(venueLocation)
let distanceInMiles = distanceInMeters * 0.00062137
if distanceInMiles < radius {
// user is near the venue
}
If you have the latitude and longitude of the venue, just create a CLLocation object for that and see how far the user is from that location.
// get the current user location, then...
let MinDistance = 100.0 // meters
let distance = venueLocation.distanceFromLocation(userLocation)
if distance < MinDistance {
// I'm close enough to the venue!
}

Google AutoComplete Query in Swift

I am using a google maps auto complete query as you seen in the below I determine the bounds of autocomplete around my location but I am still getting a places so far from my location. I also check the bounds.southwest and bounds.northeast, they are also true but places are not arsounds here.
var northRegion = CLLocationCoordinate2DMake((locationManager.location?.coordinate.latitude)!*1.0001, (locationManager.location?.coordinate.longitude)!*1.01)
var southRegion = CLLocationCoordinate2DMake((locationManager.location?.coordinate.latitude)!*0.99, (locationManager.location?.coordinate.longitude)!*0.9999)
var bounds = GMSCoordinateBounds(coordinate: northRegion, coordinate: southRegion)
print(locationManager.location?.coordinate.latitude)
print(locationManager.location?.coordinate.longitude)
print(bounds.northEast)
print(bounds.southWest)
var filter = GMSAutocompleteFilter()
filter.type = GMSPlacesAutocompleteTypeFilter.Geocode
var yourLat = locationManager.location?.coordinate.latitude
var yourLon = locationManager.location?.coordinate.longitude
placesClient = GMSPlacesClient()
placesClient?.autocompleteQuery("[YOUR TEXTFIELD'S TEXT HERE]", bounds: bounds, filter: filter, callback: { (result, error) -> Void in
for item in result! {
print(item.attributedFullText)
}
})
For example, my location is Istanbul and when I wrote this, I am getting Cape Town datas.
OK I found this in GMSPlacesClient
* #param bounds The bounds used to bias the results. This is not a hard restrict - places may still * be returned outside of these bounds. This parameter may be nil.
So autocomplete query can not filter the places in bounds way.
Maybe a little late to answer but you can try using filter.country = countryCode, I use that to limit the result to just my country.

Resources