In Google Maps iOS how to turn Marker as per turns on road like in Uber's Trip screen - ios

I am working on an iOS application that works like Uber. When the device moves I am moving the car marker as well. But am not able to turn it as per turns on road like Uber does. How can I make it possible?
Also how can I keep this car marker only on road?

You can calculate bearing then rotate marker based on the calculated bearing.
Use this function to calculate bearing:
+(float)getBearing:(CLLocationCoordinate2D)locations1 andSecond:(CLLocationCoordinate2D)locattion2
{
float fLat = degreesToRadians(locations1.latitude);
float fLng = degreesToRadians(locations1.longitude);
float tLat = degreesToRadians(locattion2.latitude);
float tLng = degreesToRadians(locattion2.longitude);
float degree = radiansToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));
if (degree >= 0) {
return degree;
} else {
return 360+degree;
}
}
Then rotate the marker itself using rotation property
marker.rotation = CALCULATED_BEARING - 180;

Take a look at rotating the marker alongside the update or the moving of locations with regards to the bearing. Not really sure how you can achieve with keeping the marker on the road since you are relying on the gps to provide exact location where you will place your marker.
Take a look at this SO as a sample.

Related

Find distance of location to route in Google Maps SDK

I´m developing an iPhone app, and I need some help with this case:
I need to check, if user leave google maps route (GMSPolyline) and if distance from user location to nearest point of route is more than 40 meters -- I need to rebuild route.
I can't find the right algorithm to detect if distance from user to route is more than 40 meters.
I've tried to use this method to find projection of user location (converted to CGPoint by CGPointMake) on route :
+ (CGPoint)projectionOfPoint:(CGPoint)origPoint toSegmentP1:(CGPoint)p1 p2:(CGPoint)p2 {
// for case line is parallel to x axis
if (p2.y == p1.y) {
return CGPointMake(origPoint.x, p1.y);
// for case line is parallel to y axis
} else if (p2.x == p1.x) {
return CGPointMake(p1.x, origPoint.y);
}
// line from segment
CGFloat kKoefLine1 = (p2.x - p1.x)/(p2.y - p1.y);
CGFloat bKoefLine1 = p1.y - kKoefLine1*p1.x;
// perpendicular line
CGFloat kKoefLine2 = -1/kKoefLine1;
CGFloat bKoefLine2 = origPoint.y - kKoefLine2*origPoint.x;
// cross point
CGFloat krossX = (bKoefLine2 - bKoefLine1)/(kKoefLine1 - kKoefLine2);
CGFloat krossY = kKoefLine2*krossX + bKoefLine2;
return CGPointMake(krossX, krossY);}
Then I calculate distance from returned projection (converted to CLLocation) and user location, but it doesn't works.
P.S.: I will be thankful if solution would be written on swift.
There is a GMSGeometryIsLocationOnPath function in the GMSGeometryUtils module in the Google Maps SDK.
You should be able to use that to calculate what you need.
Pseudocode (not tested):
let currentLocation: CLLocationCoordinate2D = ...
let routePath: GMSPath = routePolyline.path
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPath(currentLocation, routePath, geodesic, tolerance)
for swift 5.0 and based on #Arthur answer I wrote follwoing function
func isInRoute(posLL: CLLocationCoordinate2D, path: GMSPath) -> Bool
{
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPathTolerance(posLL, path, geodesic, tolerance)
return within40Meters
}
While I don't recall much about the GMS SDK off the top of my head, before I give you an answer, I will say that nobody on here will write your code for you. That's your job and should be done on your time. You haven't given any background as to how far you've gotten in terms of calculating routes, whether or not you've figured out how to calculate distance at all, etc.
With that being said, routes on Google Maps are comprised of "legs", which denote a path to take before a turn is made in efforts to reach the end destination. By querying your "route" dictionary, you can extract an array of dictionaries where each element (which is a dictionary) contains metadata about a "leg". You can then loop through that array, go through each dictionary and extract the "distance" value, and sum them to a single "distance" var.
You can recalculate this as often as needed and use a conditional to check whether or not the leg distance sum is < 40M, else rebuild.
link to an article that should help (I didn't have the time to go through the entire thing for you, so do your due diligence and research) here.

How to implement MKAnnotationViews that rotate with the map

I have an app with some annotations on it, and up until now they are just symbols that look good with the default behavior (e.g. like numbers and letters). Their directions are fixed in orientation with the device which is appropriate.
Now however I need some annotations that need to be fixed in orientation to the actual map, so if the map rotates, then the annotation symbols need to rotate with it (like an arrow indicating the flow of a river for example).
I don't want them to scale with the map like an overlay but I do want them to rotate with the map.
I need a solution that primarily works when the user manually rotates the map with their fingers, and also when it rotates due to be in tracking with heading mode.
On Google Maps (at least on android) this is very easy with a simple MarkerOptions.flat(true)
I am sure it won't be too much more difficult on ios, I hope.
Thanks in advance!
Here's what I used for something similar.
- (void)rotateAnnotationView:(MKAnnotationView *)annotationView toHeading:(double)heading
{
// Convert mapHeading to 360 degree scale.
CGFloat mapHeading = self.mapView.camera.heading;
if (mapHeading < 0) {
mapHeading = fabs(mapHeading);
} else if (mapHeading > 0) {
mapHeading = 360 - mapHeading;
}
CGFloat offsetHeading = (heading + mapHeading);
while (offsetHeading > 360.0) {
offsetHeading -= 360.0;
}
CGFloat headingInRadians = offsetHeading * M_PI / 180;
annotationView.layer.affineTransform = CGAffineTransformMakeRotation(headingInRadians);
}
And then, this is called in regionDidChange etc.
Unfortunately, this solution doesn't rotate while the user is rotating the map, but it corrects itself afterwards to make sure it has the proper heading. I wrap some of the affineTransform into an animation block to make it look nice.
Hopefully this can help, or maybe help get you pointed in the right direction.

ios: predict if two annotation views would overlap each other

I'm developing some MKMapView logic. There are many annotations on my map view. I need to compose few locations in one if those annotation views would overlap each other, and display Annotation view with changes. So I should predict that case, and I need to determinate that only from annotations' location properties and current MKMapView's zoom.
- (BOOL)shouldAlert:(CoordinatesAlert *)alert1 beInGroupWithAlert:(CoordinatesAlert *)alert2 {
// somehow calculate current map view zoom
CGFloat zoom = ...
if ([alert1.location distanceFromLocation:alert2.location] / zoom < kSomeCoeficient) {
return YES;
}
return NO;
}
Also I worried about this solution because I need to reload annotations and those views on every zoom change.
How can I do this in better way? Is there any default solution for annotation views clustering?
All of exist libraries do not suit for my needs, they all works with areas but I need to group annotations only if they would be overlapped. Also I do not need so many annotations, so i do not need performance optimization.
So, I've found some solution, it is better then nothing.
- (BOOL)isViewForLocation:(CLLocation *)location1 overlappedByViewForLocation:(CLLocation *)location2 {
const CLLocationDegrees deltaLatitude = ABS(location1.coordinate.latitude - location1.coordinate.latitude);
const CLLocationDegrees deltaLongitude = ABS(location2.coordinate.longitude - location2.coordinate.longitude);
const CLLocationDegrees mapAreaLatitude = self.mapView.region.span.latitudeDelta;
const CLLocationDegrees mapAreaLongitude = self.mapView.region.span.longitudeDelta;
return (deltaLatitude / mapAreaLatitude) < (kAnnotationTapArea.size.height / self.mapView.frame.size.height)
&& (deltaLongitude / mapAreaLongitude) < (kAnnotationTapArea.size.width / self.mapView.frame.size.width);
}

How to calculate GMSCamera zoom

I am developing iOS application including GoogleMaps.
And implementation process of GoogleMaps SDK for iOS was completed yet.
But I want to know how to calculate GMSCamera zoom
for showing whole route on Map.
In Apple Map, we use span, maybe.
But GoogleMaps SDK doesn't have span.
Please give me advice.
Actually in the current version of the SDK you can use fitBounds:
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithPath:yourPath];
//There are several useful init methods for the GMSCoordinateBounds!
GMSCameraUpdate *update = [GMSCameraUpdate fitBounds:bounds];
[mapView_ moveCamera:update];
This way you let the math to Google, and I'm pretty sure they are really good at it.
Here is a function that may help as this calculates the zoomLevel from an MKMapView that is "Google Compatible"
- (NSInteger)getZoomLevel
{
MKMapView *map = (MKMapView *)self.mapView;
CLLocationDegrees longitudeDelta = map.region.span.longitudeDelta;
CGFloat mapWidthInPixels = map.bounds.size.width;
double zoomScale = longitudeDelta * 85445659.44705395 * M_PI / (180.0 * mapWidthInPixels);
double zoomer = 20 - log2(zoomScale);
if ( zoomer < 0 ) zoomer = 0;
return (NSInteger)zoomer;
}
If you can grab the coordinates and pass those in instead you should be ok.
Lee's method is right, but the math is not right, based on google maps ios sdk document on zoom
Increasing the zoom level by 1 doubles the width of the world on the
screen. Hence at zoom level N, the width of the world is approximately
256 * 2^N, i.e., at zoom level 2, the whole world is approximately 1024
points wide. Note that the zoom level need not be an integer. The
range of zoom levels permitted by the map depends on a number of
factors including location, map type and screen size.
The math to calculate the zoom should be
zoom = log2(360 * mapView.bounds.size.width/ longitudeDelta) - 8;
I posted an answer to a similar question here:
How to setRegion with google maps sdk for iOS?
It uses a similar approach to Lee's answer: Convert the lat/lon to pixels, calculate a scale, then a zoom level.
Swift:
//Create a path
let path = GMSMutablePath()
//for each point you need, add it to your path
let position = CLLocationCoordinate2DMake(latitude, longitude)
path.add(position)
//Update your mapView with path
let mapBounds = GMSCoordinateBounds(path: path)
let cameraUpdate = GMSCameraUpdate.fit(mapBounds)
mapView.moveCamera(cameraUpdate)
Jing, your math is correct for longitudes, but for latitudes it has to be a bit more tricky. Mercator projection handles latitudes and longitudes differently. I have posted the correct code here: https://stackoverflow.com/a/16217785/2291425
Use GMSCoordinateBounds. You can fit the map using different parameters like cooodinates, bounds etc.
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithPath:path];
https://developers.google.com/maps/documentation/ios/reference/interface_g_m_s_coordinate_bounds#a583bf55d8dd8cd10eec3473688f9d788

Best practice for using lat/long within a UIView (not MKMapView)

Basically i have a list of POI's (name,lat,long) and i want to draw them on the UIView, relative to my current lat/long. I'm looking for some best practice for mapping these POI (lat/long) to a UIView.
I don't want to use MKMapView (no need for displaying map-data).
I was reading:
http://developer.apple.com/library/ios/#documentation/general/conceptual/Devpedia-CocoaApp/CoordinateSystem.html
But I'm clueless how i get from a CLLocation to a (x,y) on my UIView. I only want to draw those POI's around my current location. So, for example if my screen would represent a 20 by 30 KM region, how do i map my POI's to their corresponding (x,y) coordinates?
Thanks.
What you're doing is a little strange, but you can convert latitude/longitude to a CGPoint-like struct called an MKMapPoint. An MKMapPoint has an x and y value which correspond to points on a map. Imagine if you laid out a flat map of the world, and 0,0 was the top left. MKMapPoint is a point on that map using that coordinate system.
Use the function MKMapPointForCoordinate() to convert a CLLocationCoordinate2D to an MKMapPoint
MKMapPoint myMapPoint = MKMapPointForCoordinate(myLocationCoordinate);
When you get the list of points, you'll have to do something like finding the max and min x and y values, then fitting all the points into your view using those values, otherwise you'll end up with a load of very close points in one place in your view.
My guess is that, for a 20KM by 30KM region, you can consider the earth to be flat and there fore linearly extrapolate the coordinates. I am sure you can google and find out as to how much distance is a difference in 0.00001 in latitude and longitude.
So if you have 20Km to be represented on X axis, and your current location is 30.1234567 in latitude, and 0.0000001 is 1 km then you can put your coordinate in the center of the screen and 30.1234557 as the left most X coordinate and so on.
I am not trying to provide an answer here, but just trying to think out loud, because I wanted to do some thing similar as well and did it as an Internet based app (without display though), where given two coordinates, I had to find the distance between them.
There are many (many) different approaches to modelling the planet and translating 3D coordinates onto a 2D surface, and the errors introduced by the various methods vary depending on what part of the globe you are. This question seems to cover most of what you are after though:
Converting Longitude & Latitude to X Y on a map with Calibration points
I think its best way (correctly work for Mercator projection map):
extension UIView
{
func addLocation(coordinate: CLLocationCoordinate2D)
{
// max MKMapPoint values
let maxY = Double(267995781)
let maxX = Double(268435456)
let mapPoint = MKMapPointForCoordinate(coordinate)
let normalizatePointX = CGFloat(mapPoint.x / maxX)
let normalizatePointY = CGFloat(mapPoint.y / maxY)
let pointView = UIView(frame: CGRectMake(0, 0, 5, 5))
pointView.center = CGPointMake(normalizatePointX * frame.width, normalizatePointY * frame.height)
pointView.backgroundColor = UIColor.blueColor()
addSubview(pointView)
}
}
My simple project for adding coordinate on UIView: https://github.com/Glechik/MapCoordinateDrawer

Resources