I have a route (MKPolyline derived from an MKRoute retrieved from Apple's MKDirections API) and a bunch of points of interest (array of MKAnnotations) that are close to the route.
I would like to know how to select the next POI the user is going to meet following the route, in order to present it through a UI.
Two different approaches come to mind, but none of them is really adequate:
The first one would be to mark the POIs as checked each time you get close enough to them, and simply display the first unmarked POI in the array (we'll assume that they are correctly ordered). The problem is that if for a reason or another one of the POIs is not checked, then the app will forever display it instead of displaying the actual next POI coming. This situation can arise e.g. if the user followed a slightly different route than the one suggested, that didn't come close enough to the POI to get it checked; or the user starts the navigation after the first POI; etc.
The second one one would be to select the POI closest to the user (probably also with a marking system to avoid presenting the POI you just checked). But this would only work for routes straight enough: sometimes in mountain zones or other sinuous routes you can get closer to a point that you will actually cross later. I expect this situation to happen actually quite often.
Any idea?
When I had to implement a turn-by-turn in one of our apps I used what you describe as first bullet. To figure out if user diverged from original polyline I calculated distance between current position and line segments each time I read a new position. Once I detected I was NOT following the path, I re-calculated the route while showing a "Recalculating..." message for user.
This is my code
- (BOOL)isCoordinate:(CLLocationCoordinate2D)coordinate closeToPolyline:(MKPolyline *)polyline {
CLLocationCoordinate2D polylineCoordinates[polyline.pointCount];
[polyline getCoordinates:polylineCoordinates range:NSMakeRange(0, polyline.pointCount)];
for (int i = 0; i < polyline.pointCount - 1; i++) {
CLLocationCoordinate2D a = polylineCoordinates[i];
CLLocationCoordinate2D b = polylineCoordinates[i + 1];
double distance = [self distanceToPoint:MKMapPointForCoordinate(coordinate) fromLineSegmentBetween:MKMapPointForCoordinate(a) and:MKMapPointForCoordinate(b)];
if (distance < 25) {
return YES;
}
}
return NO;
}
- (double)distanceToPoint:(MKMapPoint)p fromLineSegmentBetween:(MKMapPoint)l1 and:(MKMapPoint)l2 {
double A = p.x - l1.x;
double B = p.y - l1.y;
double C = l2.x - l1.x;
double D = l2.y - l1.y;
double dot = A * C + B * D;
double len_sq = C * C + D * D;
double param = dot / len_sq;
double xx, yy;
if (param < 0 || (l1.x == l2.x && l1.y == l2.y)) {
xx = l1.x;
yy = l1.y;
}
else if (param > 1) {
xx = l2.x;
yy = l2.y;
}
else {
xx = l1.x + param * C;
yy = l1.y + param * D;
}
return MKMetersBetweenMapPoints(p, MKMapPointMake(xx, yy));
}
Then I call - (BOOL)isCoordinate:(CLLocationCoordinate2D)coordinate closeToPolyline:(MKPolyline *)polyline { with coordinate being users current location and polyline being path from your MKDirections.
In my case I wouldn't allow more than 25 meters off but it might depend on your lat/lng precision.
Maybe it will help you or someone.
Use the following pseudometric, which I'll call route distance. Intuitively, it works like highway location markers. Assume that the route polyline does not touch or cross itself (is simple). For each point q on some segment pr of the polyline where p comes first, the location of that point in our 1D coordinate system is the Euclidean (spherical?) distance from p to q, plus the lengths of all segments that come before pr. The route distance between two such points is the absolute value of the difference of their locations in our 1D coordinate system. Extend route distance to points off of the route by treating such points as the closest point on the route (the answers to this question about computing point-to-segment distance should be helpful for computing the closest route point).
Present the closest POI to the user by route distance (check it off when this distance is sufficiently small).
Related
I'm building a small application that takes the user's device's lat/long position as well as the heading (true north) and tells you what sort of points of interests are in front of the user. I am getting all my points of interests from google maps api.
I thought the easiest way to do this would be a dot product calculation between my forward vector and the AB vector however I have some false positives.
Here is my code:
func isFront(_ p1 : Point, _ p2 : Point, _ p1Heading : Double) -> Bool {
let forward = Point(cos(p1Heading), sin(p1Heading))
let AB = Point(p2.x - p1.x, p2.y - p1.y)
let lenAB = (AB.x * AB.x + AB.y * AB.y).squareRoot()
let normalAB = Point(AB.x / lenAB, AB.y / lenAB)
let dot = (normalAB.x * forward.x + normalAB.y * forward.y)
return (dot > 0)
}
So something that is coming back as true but I think shouldnt is:
My location(lat/lng): 42.359291, -71.059638 heading: 173.89306640625
Point of interest (lat/lng): 42.359980, -71.060303
Is this a good approach in figuring out if things are in front or should I look into doing something different?
Yes dot product is a good approach for this but I do not see any dot product in your equations/code I see only messed up gibberish there... If I get it right:
p1 is device position
forward is forward direction
p2 is tested point
then it should be:
let forward = Point(cos(p1Heading), sin(p1Heading))
let dot = ((p2.x-p1.x)*forward.x)+((p2.y-p1.y)*forward.y)
return (dot>0.0)
As you can see no lengths are required. Also no normal (even if your normal is not a normal but some gibberish). I think you should read some book/tutorial on vector math basic and check how things are computed here the 2D stuff you mess up:
dot(a,b) = a.x*b.x + a.y*b.y
normal(a) = Point(a.y,-a.x)
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.
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.
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;
}
This is language-agnostic question, more about model of my game.
I have a snake game with elements, but I move the elements smoothly, they don't just move 1 block each time, but instead they move some amount of pixels every frame.
I have an update loop that calculates the positions of the element, but I am stuck on correct calculations.
I have heading for each element:
typedef NS_ENUM(int, kElementHeading)
{
kElementHeadingNorth = 1,
kElementHeadingSouth,
kElementHeadingEast,
kElementHeadingWest
};
I also have velocity (x, y) that determines in what direction snake is going. I have problem with snake movement, because my elements are in wrong positions. I managed to localize the thing for 2 elements, but my solution fails on more elements.
First solution I tried is to save point of rotation where the head changes direction. This worked, but due to different circumstances element can move different amount of pixels each turn. Often the element would skip the point. I tried increasing the zone where it should rotate, but it adds up error. I tried fixing this error, but element would still separate from snake (quite often).
On the second try I decided to keep the snake head in center of the screen and move the world around it. It worked good for 2 elements, as I just smoothly move the next element to desired position relatively to head. But this fails badly on more elements. If you make fast turns they start dancing and not following the path.
Third thing that I tried is leaving a path for other elements to follow. But that didn't work because I intend to keep my snake on center of the screen and technically it never moves to create a path.
I'm looking to replicate the movement pattern like in Nimble Quest (or any snake).
How should I implement snake elements moving to have no errors?
Here is my code for the first method, problem with it is that often the elements would fall off. The code is pretty self-explanatory. Rotation points are the places where to change direction.
CFTimeInterval delta = self.lastTime - currentTime;
CGPoint currentPosition = self.playerSnake.head.sprite.position;
CGPoint velocity = self.playerSnake.velocity;
self.playerSnake.head.sprite.position = CGPointMake(currentPosition.x + velocity.x * delta * CONSTANTSPEEDFACTOR , currentPosition.y + velocity.y * delta * CONSTANTSPEEDFACTOR);
for (SnakeElement *element in self.playerSnake.elements) {
CGPoint currentPositionE = element.sprite.position;
CGPoint velocityE = element.velocity;
element.sprite.position = CGPointMake(currentPositionE.x + velocityE.x * delta * CONSTANTSPEEDFACTOR , currentPositionE.y + velocityE.y * delta * CONSTANTSPEEDFACTOR);
}
BOOL markToDelete = NO;
NSDictionary *deleteDictionary;
for (NSDictionary *dict in self.playerSnake.rotationPoints) {
CGPoint positionCoordinate = CGPointFromString(dict[#"position"]);
CGPoint velocityNew = CGPointFromString(dict[#"velocity"]);
double newAngle = [dict[#"angle"] doubleValue];
for (SnakeElement *element in self.playerSnake.elements) {
int xDifference = element.sprite.position.x - positionCoordinate.x;
int yDifference = element.sprite.position.y - positionCoordinate.y;
if ((xDifference > -2 && xDifference < 2) && (yDifference > -2 && yDifference < 2) ) {
element.velocity = velocityNew;
element.sprite.position = CGPointMake(element.sprite.position.x + xDifference, element.sprite.position.y + yDifference);
SKAction *action = [SKAction rotateToAngle:newAngle duration:0.2 shortestUnitArc:YES];
[element.sprite runAction:action];
if ([element isEqual:[self.playerSnake.elements lastObject]]) {
markToDelete = YES;
deleteDictionary = dict;
}
}
}
}
[self.playerSnake.rotationPoints removeObject:deleteDictionary];
If I try increase the catch zone for the turning point, the elements tend to fall off more often then when it is 1 or 2 pixels wide. I'm not sure why this happens.
This is what I was suggesting you do in the comments in terms of handling your turning on points :
1.. calculate the distance that the element should move that frame based on speed and your elapsed time since last frame. (delta)
2.. calculate distance from element's current position to the turn point. This is the beforeDistance I spoke of in the comments
3.. calculate the distance the element should move towards the NEW target turning point AFTER the turn
afterDistance = distanceToMoveThisFrame - beforeDistance
4.. Calculate the new position for your element, starting at the current turning point towards the next target turning point of the element using afterDistance
If you follow this logic, you will NEVER overshoot or undershoot the turning point.