What is the best way to interpolate points between coordinates in google path? Is this approach correct? - ios

Am trying to plot Polyline on Googlemap to show the position of driver/delivery boy to user like in Uber.
I use google directions API get overview Polyline and draw it on map. Now I get the driver location from our own server and in order to update the user location on map I iterate through the co-ordinates in GMSPath which I obtained by decoding the over-view Polyline as
if let jsonArray = jsonResult["routes"].array, jsonArray.count > 0 {
for json in jsonArray {
if let polyline = json["overview_polyline"]["points"].string {
self.possibleOverViewPolylines.append(polyline)
}
}
}
self.currentPolyline = self.possibleOverViewPolylines[0]
self.path = GMSMutablePath.init(fromEncodedPath: self.currentPolyline)
self.polyline = GMSPolyline(path: self.path)
Google usually returns multiple routes when sent alternative=true so I cache all the overview_polyline and use the first one as current over line.
Now from reading and trial-error I have figured out that there can be error in lat-long of driver captured and it might range from 5-50meters. So once I get the driver location I iterate through entire coordinates in path to find the closest point in map and snap driver to that location
var overallDistance: CLLocationDistance = 50
for index in 0 ..< strongSelf.path.count() {
let coordinate = strongSelf.path.coordinate(at: UInt(index))
let distance = location.distance(to: coordinate)
if distance < overallDistance {
foundIndex = Int(index)
overallDistance = distance
}
}
if overallDistance >= 50 {
debugPrint("\(location)")
evaluateAlternativeRoutes()
}
else {
updatepolyline(location: strongSelf.path.coordinate(at: UInt(foundIndex)))
}
update Polyline as
self?.polyline.map = nil
while strongSelf.path.coordinate(at: UInt(0)).latitude != location.latitude && strongSelf.path.coordinate(at: UInt(0)).longitude != location.longitude {
self?.path.removeCoordinate(at: 0)
}
if strongSelf.path.coordinate(at: 0).latitude == location.latitude && strongSelf.path.coordinate(at: UInt(0)).longitude == location.longitude {
self?.path.removeCoordinate(at: 0)
}
self?.polyline = GMSPolyline(path: strongSelf.path)
Finally alternative routes are evaluated as
var overallDistance: CLLocationDistance = 50
var foundIndex = -1
for (polylineIndex,polyline) in strongSelf.possibleOverViewPolylines.enumerated() {
if let path = GMSMutablePath.init(fromEncodedPath: polyline) {
for index in 0 ..< path.count() {
let coordinate = path.coordinate(at: UInt(index))
let distance = location.distance(to: coordinate)
if distance < overallDistance {
foundIndex = polylineIndex
overallDistance = distance
}
}
}
}
if foundIndex != -1 {
self?.path = GMSMutablePath.init(fromEncodedPath: strongSelf.possibleOverViewPolylines[foundIndex])
}
else {
//make routes API call again
}
If none of the alternative routes available matches the driver location, driver might be taking a whole different route so I make routes API call again with driver location
Why So many optimisation?
Google's routes APIs are costly and calling google routes API unnecessarily will add financial burden as well as screw up the whole user experience, hence want to make most of the calculations locally
But above code works sub-optimally :( It works but not great :|
Issues with this approach
Issue 1:
Method assumes the possible error rate in drivers location is 50m max, and when I evaluate all the distance to points in path are checked against this 50, but unfortunately coordinates in google paths are not equally distributed, on a lengthy straight road, distance between 2 subsequent points in path's coordinates can be up to 200m. I tested it with
for i in 0 ..< self.path.count() {
if i == 0 {
debugPrint(self.path.coordinate(at: i))
}
else {
debugPrint("distance between \(self.path.coordinate(at: (i - 1))) and \(self.path.coordinate(at: (i))) is \(self.path.coordinate(at: (i - 1)).distance(to: self.path.coordinate(at: (i))))")
}
}
So logic of comparing drivers location to all points in path with 50m as cap logic fails there.
Solution I can think of
If I can interpolate the points between any two coordinates in google path with a regular interval of 50m, and raise the cap to 100m (50m distance between two points in path, 50m for error in lat-long) that should give fairly better chance for me to reduce number of API calls
What I have tried?
I tried solving it with linear interpolation using
No need to tell the result is disastrous, because equation assumes a Cartesian plane and earth is not flat :|
So finally What the heck are you asking?
Is it a correct approach to interpolate the points between two coordinates of google path to achieve what am trying to achieve?
If yes, what is the better interpolation algorithm I should be using? Clearly linear doesn't make much sense :(
Please help, thanks in advance

Google itself has provided various methods for interpolating within GMSGeometryUtils module. I think what you need for interpolating could be the:
https://developers.google.com/maps/documentation/ios-sdk/reference/group___geometry_utils.html#gad0c5870bd9d182d22310f84a77888124
GMSGeometryInterpolate uses shortest path between 'from' and 'to' coordinate that you provide at a given fraction, and GMSPath indeed connects shortest paths between each sub-sequential coordinates you provide so it should be sufficient.

Related

How to check if geo point lies close to a set of other geo points

I have a list of geo-points and radiuses around them
P1 = { lat1, lon1, rad1 }
P2 = { lat2, lon2, rad2 }
...
I can reorder this list and change representation in any way before hand.
My tasks is: given another point
Q = { latQ, lonQ }`
I need to find if Q lies close enough to at least one of Ps, i.e. distance
d(Q, P[i]) < P[i].rad
I can just iterate over each point, calculate distance and compare it with .rad, but there must be a more efficient way.

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 get the area of the polygons from wiki mapia api

I'm Using wiki mapia api to get the geo information.
Wiki Mapia
http://api.wikimapia.org/?key=example&function=place.getnearest&lat=12.9605459&lon=77.5649618&count=50&format=json&category=15417.
this api returning, location name lat,lng,min lat lng, max lat lng , polygon.
Like that i need polygon area. anyone used this api kindly suggest me how to get the area parameter .
Without using the api, and only using the points returned by the api you may apply the following algorithm (specified here in pseudocode):
function polygonArea(X, Y, numPoints)
{
area = 0; // Accumulates area
j = numPoints-1; // The last vertex is the previous one to first
for (i=0; i<numPoints; i++)
{
area = area + (X[j]+X[i]) * (Y[j]-Y[i]);
j = i; //j is previous vertex to i
}
return area/2;
}

UIBezierPath Percent of Length at Point

I'm building an app that features some graphical manipulation. I'm storing shapes as UIBezierPaths, and I want to allow users to touch points along the line to create saved locations. Using the wonderful answer to this question, and more specifically, this project, I'm able to place a point on a line knowing the percentage of its length the point rests on. This is half of my problem.
I want a way to take a point on a path, and derive the percent of its length.
My math-fu is extremely weak. I've studied bezier curves but I simply don't have the math to understand it.
I would humbly submit that "go back and learn geometry and trigonometry" is a correct answer, but sadly one I don't have time for at present. What I need is a way to fill in this method:
- (CGFloat)percentOfLengthAtPoint:(CGPoint)point onPath:(UIBezierPath*)path
Any help appreciated!
I have working code that solves my problem. I'm not particularly proud of it; the overall technique is essentially a brute-force attack on a UIBezierPath, which is kind of funny if you think about it. (Please don't think about it).
As I mentioned, I have access to a method that allows me to get a point from a given percentage of a line. I have taken advantage of that power to find the closest percentage to the given point by running through 1000 percentage values. To wit:
Start with a CGPoint that represents where on the line the user touched.
let pointA = // the incoming CGPoint
Run through the 0-1 range in the thousands. This is the set of percentages we're going to brute-force and see if we have a match. For each, we run pointAtPercentOfLength, from the linked project above.
var pointArray:[[String:Any]] = []
for (var i:Int = 0; i <= 1000; i++) {
let value = CGFloat(round((CGFloat(i) / CGFloat(1000)) * 1000) / 1000)
let testPoint = path.pointAtPercentOfLength(value)
let pointB = CGPoint(x: floor(testPoint.x), y: floor(testPoint.y))
pointArray.append(["point" : pointB, "percent" : value])
}
That was the hard part. Now we take the returning values and calculate the distance between each point and the touched point. Closest one is our winner.
// sort the damned array by distance so we find the closest
var distanceArray:[[String:Any]] = []
for point in pointArray {
distanceArray.append([
"distance" : self.distanceFrom(point["point"] as! CGPoint, point2: pointA),
"point" : point["point"],
"percent" : point["percent"] as! CGFloat
])
}
Here's the sorting function if you're interested:
func distanceFrom(point1:CGPoint, point2:CGPoint) -> CGFloat {
let xDist = (point2.x - point1.x);
let yDist = (point2.y - point1.y);
return sqrt((xDist * xDist) + (yDist * yDist));
}
Finally, I sort the array by the distance of the values, and pick out the winner as our closest percent.
let ordered = distanceArray.sort { return CGFloat($0["distance"] as! CGFloat) < CGFloat($1["distance"] as! CGFloat) }
ordered is a little dictionary that includes percent, the correct value for a percentage of a line's length.
This is not pretty code, I know. I know. But it gets the job done and doesn't appear to be computationally expensive.
As a postscript, I should point to what appears to be a proper resource for doing this. During my research I read this beautiful article by David Rönnqvist, which included an equation for calculating the percentage distance along a path:
start⋅(1-t)3 + 3⋅c1⋅t(1-t)2 + 3⋅c2⋅t2(1-t) + end⋅t3
I was just about to try implementing that before my final solution occurred to me. Math, man. I can't even brain it. But if you're more ambitious than I, and wish to override my 30 lines of code with a five-line alternative, everyone would appreciate it!
I think your approach is sound, but you could do this far more efficiently.
Instead of creating an two arrays of dicts (with a thousand elements each) and then sorting the array - just use a while loop to move from 0.0 to 1.0, calculate the distance to the touch point and keep track of the minimum distance.
For example:
var t:CGFloat = 0.0
let step:CGFloat = 0.001
var minDistance:CGFloat = -1.0
var minPoint:CGPoint = CGPointZero
var minT:CGFloat = -1;
while (t<1.0) {
let point = pointAtPercentOfLength(t)
let distance:CGFloat = self.distanceFrom(point, point2: pointA)
if (minDistance == -1.0 || distance < minDistance) {
minDistance = distance
minPoint = point
minT = t
}
t += step
}
print("minDistance: \(minDistance) minPoint: \(minPoint.x) \(minPoint.y) t\(minT)\n")

Where is MapKit Step polyline?

I am trying to print coordinates of all route steps, similar to Google Maps SDK's "legs".
But it tells me that I cannot use polyline property to obtain a coordinate?
Try this:
for step in self.route!.steps as [MKRouteStep] {
otherwise it treats step as AnyObject (which doesn't have a polyline property defined so you get that compiler error).
By the way, note that polyline.coordinate just gives the average center of the polyline or one endpoint. A polyline can have more than one line segment.
If you need to get all the line segments and coordinates along the polyline, see latitude and longitude points from MKPolyline (Objective-C).
Here's one possible translation to Swift (with help from this answer):
for step in route!.steps as [MKRouteStep] {
let pointCount = step.polyline.pointCount
var cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.alloc(pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for var c=0; c < pointCount; c++ {
let coord = cArray[c]
println("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.dealloc(pointCount)
}
As the first linked answer warns, you may get hundreds or thousands of coordinates per step depending on the route.
Swift 4.1, as of July 2018, based on the other answer.
let pointCount = step.polyline.pointCount
let cArray = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: pointCount)
step.polyline.getCoordinates(cArray, range: NSMakeRange(0, pointCount))
for c in 0..<pointCount {
let coord = cArray[c]
print("step coordinate[\(c)] = \(coord.latitude),\(coord.longitude)")
}
cArray.deallocate()

Resources