I need to find nearest point from given CLLocationCoordinate2D on array of GMSPolyline. I can convert this to GMSPath if that's better. Is there any ready method (or any repository) for such calculations? I have some problems with implementation. I wonder how to create an algorithm:
1. for all polylines
1.1. find smallest distance between polyline and touch point, save CLLocationCoordinate2D
2. for all distances from point 1.1.
2.1. find the shortest one, it's CLLocationCoordinate2D is our point
Now the question is how to achieve point 1.1..?
Basing on SOF shortest distance question, I wrote such code:
- (void)findNearestLineSegmentToCoordinate:(CLLocationCoordinate2D)coordinate {
GMSPolyline *bestPolyline;
double bestDistance = DBL_MAX;
CGPoint originPoint = CGPointMake(coordinate.longitude, coordinate.latitude);
for (GMSPolyline *polyline in self.polylines) {
polyline.strokeColor = [UIColor redColor]; // TMP
if (polyline.path.count < 2) { // we need at least 2 points: start and end
return;
}
for (NSInteger index = 0; index < polyline.path.count - 1; index++) {
CLLocationCoordinate2D startCoordinate = [polyline.path coordinateAtIndex:index];
CGPoint startPoint = CGPointMake(startCoordinate.longitude, startCoordinate.latitude);
CLLocationCoordinate2D endCoordinate = [polyline.path coordinateAtIndex:(index + 1)];
CGPoint endPoint = CGPointMake(endCoordinate.longitude, endCoordinate.latitude);
double distance = [self distanceToPoint:originPoint fromLineSegmentBetween:startPoint and:endPoint];
if (distance < bestDistance) {
bestDistance = distance;
bestPolyline = polyline;
}
}
}
bestPolyline.map = nil;
bestPolyline.strokeColor = [UIColor greenColor]; // TMP
bestPolyline.map = self.aView.mapView;
}
Still, the problem is with exact point. Any algorithm? I'll post answer here when found.
Ok, I've managed to write it. Method nearestPointToPoint:onLineSegmentPointA:pointB:distance: allows you both to find closest coordinate and distance between selected point and segment line (so line with start and end).
- (CLLocationCoordinate2D)nearestPolylineLocationToCoordinate:(CLLocationCoordinate2D)coordinate {
GMSPolyline *bestPolyline;
double bestDistance = DBL_MAX;
CGPoint bestPoint;
CGPoint originPoint = CGPointMake(coordinate.longitude, coordinate.latitude);
for (GMSPolyline *polyline in self.polylines) {
if (polyline.path.count < 2) { // we need at least 2 points: start and end
return kCLLocationCoordinate2DInvalid;
}
for (NSInteger index = 0; index < polyline.path.count - 1; index++) {
CLLocationCoordinate2D startCoordinate = [polyline.path coordinateAtIndex:index];
CGPoint startPoint = CGPointMake(startCoordinate.longitude, startCoordinate.latitude);
CLLocationCoordinate2D endCoordinate = [polyline.path coordinateAtIndex:(index + 1)];
CGPoint endPoint = CGPointMake(endCoordinate.longitude, endCoordinate.latitude);
double distance;
CGPoint point = [self nearestPointToPoint:originPoint onLineSegmentPointA:startPoint pointB:endPoint distance:&distance];
if (distance < bestDistance) {
bestDistance = distance;
bestPolyline = polyline;
bestPoint = point;
}
}
}
return CLLocationCoordinate2DMake(bestPoint.y, bestPoint.x);
}
Method nearestPolylineLocationToCoordinate: will browse through all polylines (you just need to supply polylines array == self.polylines) and find the best one.
// taken and modified from: http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
- (CGPoint)nearestPointToPoint:(CGPoint)origin onLineSegmentPointA:(CGPoint)pointA pointB:(CGPoint)pointB distance:(double *)distance {
CGPoint dAP = CGPointMake(origin.x - pointA.x, origin.y - pointA.y);
CGPoint dAB = CGPointMake(pointB.x - pointA.x, pointB.y - pointA.y);
CGFloat dot = dAP.x * dAB.x + dAP.y * dAB.y;
CGFloat squareLength = dAB.x * dAB.x + dAB.y * dAB.y;
CGFloat param = dot / squareLength;
CGPoint nearestPoint;
if (param < 0 || (pointA.x == pointB.x && pointA.y == pointB.y)) {
nearestPoint.x = pointA.x;
nearestPoint.y = pointA.y;
} else if (param > 1) {
nearestPoint.x = pointB.x;
nearestPoint.y = pointB.y;
} else {
nearestPoint.x = pointA.x + param * dAB.x;
nearestPoint.y = pointA.y + param * dAB.y;
}
CGFloat dx = origin.x - nearestPoint.x;
CGFloat dy = origin.y - nearestPoint.y;
*distance = sqrtf(dx * dx + dy * dy);
return nearestPoint;
}
You can use it eg in:
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
marker.position = [self nearestPolylineLocationToCoordinate:marker.position];
}
#Vive's nearestPointToPoint() to Swift 5
func nearestPointToPoint(_ origin: CGPoint, _ pointA: CGPoint, _ pointB: CGPoint) -> (CGPoint, Double) {
let dAP = CGPoint(x: origin.x - pointA.x, y: origin.y - pointA.y)
let dAB = CGPoint(x: pointB.x - pointA.x, y: pointB.y - pointA.y)
let dot = dAP.x * dAB.x + dAP.y * dAB.y
let squareLength = dAB.x * dAB.x + dAB.y * dAB.y
let param = dot / squareLength
// abnormal value at near latitude 180
// var nearestPoint = CGPoint()
// if param < 0 || (pointA.x == pointB.x && pointA.y == pointB.y) {
// nearestPoint.x = pointA.x
// nearestPoint.y = pointA.y
// } else if param > 1 {
// nearestPoint.x = pointB.x
// nearestPoint.y = pointB.y
// } else {
// nearestPoint.x = pointA.x + param * dAB.x
// nearestPoint.y = pointA.y + param * dAB.y
// }
let nearestPoint = CGPoint(x: pointA.x + param * dAB.x,
y: pointA.y + param * dAB.y)
let dx = origin.x - nearestPoint.x
let dy = origin.y - nearestPoint.y
let distance = sqrtf(Float(dx * dx + dy * dy))
return (nearestPoint, Double(distance))
}
Result is
me: (53, -1), pointA: (52, -2), pointB(52, 0) => (52, -1)
me: (53, 0), pointA: (52, -1), pointB(52, 1) => (52, 0)
me: (53, 1), pointA: (52, 0), pointB(52, 2) => (52, 1)
me: (-1, -77), pointA: (0, -78), pointB(-2, -78) => (-1, -78)
me: (0, -77), pointA: (-1, -78), pointB(1, -78) => (0, -78)
me: (1, -77), pointA: (2, -78), pointB(0, -78) => (1, -78)
me: (1, 179), pointA: (0, 178), pointB(0, 180) => (0, 179)
me: (1, 180), pointA: (0, 179), pointB(0, -179) => (0, 180)
me: (1, -179), pointA: (0, 180), pointB(0, -178) => (0, -179)
But some error at latitude 180. I can't fix it.
me: (0, 180), pointA: (0, 179), pointB(1, 180) => (0.5, 179.5)
me: (0, -179), pointA: (0, 179), pointB(1, -179) => (0.99, -178.99) // abnormal
Related
I have an Array of CGPoints and I would like to find those points, which build a shape. Please see the attached image:
The red circles just mark the points I have.
How can the area with the question mark be found?
Thanks.
You are going to have to start with your first line segment and check for intersections. Obviously if the first two line segments intersect then they are the same line and your shape is just a line, so ignore that case. As you continue down your line segments once you find a segment pair that intersect then you have your shape.
Check line segment 2 against line segment 1. Then check line segment 3 against line segment 2, then against line segment 1. Then check 4 against 3, then 2, then 1, etc... If you find that line segment 7 intersects with line segment 3, delete the first point of line segment 3 and se it to the intersection point you found. Then delete the last point of line segment 7 and set it to the intersection point you found. There you have your shape.
Here is an example method to find the intersection of 2 line segments (written in C#, but it's straight math so it should be very easy to convert to any language you would like). Taken from here:
// Determines if the lines AB and CD intersect.
static bool LinesIntersect(PointF A, PointF B, PointF C, PointF D)
{
PointF CmP = new PointF(C.X - A.X, C.Y - A.Y);
PointF r = new PointF(B.X - A.X, B.Y - A.Y);
PointF s = new PointF(D.X - C.X, D.Y - C.Y);
float CmPxr = CmP.X * r.Y - CmP.Y * r.X;
float CmPxs = CmP.X * s.Y - CmP.Y * s.X;
float rxs = r.X * s.Y - r.Y * s.X;
if (CmPxr == 0f)
{
// Lines are collinear, and so intersect if they have any overlap
return ((C.X - A.X < 0f) != (C.X - B.X < 0f))
|| ((C.Y - A.Y < 0f) != (C.Y - B.Y < 0f));
}
if (rxs == 0f)
return false; // Lines are parallel.
float rxsr = 1f / rxs;
float t = CmPxs * rxsr;
float u = CmPxr * rxsr;
return (t >= 0f) && (t <= 1f) && (u >= 0f) && (u <= 1f);
}
I've figured out the solution.
This function returns a polygon for each area that gets closed by intersecting lines.
func intersectionOfLineFrom(p1: CGPoint, to p2: CGPoint, withLineFrom p3: CGPoint, to p4: CGPoint) -> NSValue? {
let d: CGFloat = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x)
if d == 0 {
return nil
}
// parallel lines
let u: CGFloat = ((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d
let v: CGFloat = ((p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x)) / d
if u < 0.0 || u > 1.0 {
return nil
}
// intersection point not between p1 and p2
if v < 0.0 || v > 1.0 {
return nil
}
// intersection point not between p3 and p4
var intersection: CGPoint = CGPointZero
intersection.x = p1.x + u * (p2.x - p1.x)
intersection.y = p1.y + u * (p2.y - p1.y)
return NSValue(CGPoint: intersection)
}
func intersectedPolygons(points: [CGPoint]) -> [[CGPoint]] {
var removeIndexBelow : Int = 0
var removeIndexAbove : Int = 0
var resultArrays : [[CGPoint]] = [[CGPoint]]()
for i in 1..<points.count {
let firstLineStart = points[i-1] as CGPoint
let firstLineEnd = points[i] as CGPoint
for var j = points.count-1; j > i+1; j-- {
let lastLineStart = points[j-1] as CGPoint
let lastLineEnd = points[j] as CGPoint
if let intersect: NSValue = self.intersectionOfLineFrom(firstLineStart, to: firstLineEnd, withLineFrom: lastLineStart, to: lastLineEnd){
var pointsCopy = points
let intersection = intersect.CGPointValue()
pointsCopy[i-1] = intersection
pointsCopy[j] = intersection
removeIndexBelow = i
removeIndexAbove = j
let fullPoly = Array(pointsCopy[removeIndexBelow-1..<removeIndexAbove])
resultArrays.append(fullPoly)
break;
}
}
}
return resultArrays
}
I'm try to implement drag and drop for some UIImageViews within a UIView. It is currently working, with the exception that you can drag the image views out of the parent and off the screen. I know I have to check to see if the views go outside the parent bounds.. but im struggling to achieve it! Does anyone have any experience with this? For the Windows Phone client, all that is needed is the following line;
var dragBehavior = new MouseDragElementBehavior {ConstrainToParentBounds = true};
The current gesture code I have is here;
private UIPanGestureRecognizer CreateGesture(UIImageView imageView)
{
float dx = 0;
float dy = 0;
var panGesture = new UIPanGestureRecognizer((pg) =>
{
if ((pg.State == UIGestureRecognizerState.Began || pg.State == UIGestureRecognizerState.Changed) && (pg.NumberOfTouches == 1))
{
var p0 = pg.LocationInView(View);
if (dx == 0)
dx = (float)p0.X - (float)imageView.Center.X;
if (dy == 0)
dy = (float)p0.Y - (float)imageView.Center.Y;
float newX = (float)p0.X - dx;
float newY = (float)p0.Y - dy;
var p1 = new PointF(newX, newY);
imageView.Center = p1;
}
else if (pg.State == UIGestureRecognizerState.Ended)
{
dx = 0;
dy = 0;
}
});
return panGesture;
}
I resolved this with the following code;
var panGesture = new UIPanGestureRecognizer((pg) =>
{
if ((pg.State == UIGestureRecognizerState.Began || pg.State == UIGestureRecognizerState.Changed) && (pg.NumberOfTouches == 1))
{
var p0 = pg.LocationInView(View);
if (dx == 0)
dx = (float)p0.X - (float)imageView.Center.X;
if (dy == 0)
dy = (float)p0.Y - (float)imageView.Center.Y;
float newX = (float)p0.X - dx;
float newY = (float)p0.Y - dy;
var p1 = new PointF(newX, newY);
// If too far right...
if (p1.X > imageView.Superview.Bounds.Size.Width - (imageView.Bounds.Size.Width / 2))
p1.X = (float) imageView.Superview.Bounds.Size.Width - (float)imageView.Bounds.Size.Width / 2;
else if (p1.X < 0 + (imageView.Bounds.Size.Width / 2)) // If too far left...
p1.X = 0 + (float)(imageView.Bounds.Size.Width / 2);
// If too far down...
if (p1.Y > imageView.Superview.Bounds.Size.Height - (imageView.Bounds.Size.Height / 2))
p1.Y = (float)imageView.Superview.Bounds.Size.Height - (float)imageView.Bounds.Size.Height / 2;
else if (p1.Y < 0 + (imageView.Bounds.Size.Height / 2)) // If too far up...
p1.Y = 0 + (float)(imageView.Bounds.Size.Height / 2);
imageView.Center = p1;
}
else if (pg.State == UIGestureRecognizerState.Ended)
{
// reset offsets when dragging ends so that they will be recalculated for next touch and drag that occurs
dx = 0;
dy = 0;
}
});
This means excluding the area(s) of any interiorPolygons.
Once one has the centroid of the outer points polygon, how does one (i.e., in the form of an Objective-C example) adjust the centroid by the subtractive interiorPolygons? Or is there a more elegant way to compute the centroid in one go?
If you help get the code working, it will be open sourced (WIP here).
Might be helpful:
http://www.ecourses.ou.edu/cgi-bin/eBook.cgi?topic=st&chap_sec=07.2&page=case_sol
https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
Thinking about it today, it makes qualitative sense that adding each interior centroid weighted by area to the exterior centroid would arrive at something sensible. (A square with an interior polygon (hole) on the left side would displace the centroid right, directly proportional to the area of the hole.)
Not to scale:
- (MKMapPoint)calculateCentroid
{
switch (self.pointCount) {
case 0: return MKMapPointMake(0.0,
0.0);
case 1: return MKMapPointMake(self.points[0].x,
self.points[0].y);
case 2: return MKMapPointMake((self.points[0].x + self.points[1].x) / 2.0,
(self.points[0].y + self.points[1].y) / 2.0);
}
// onward implies pointCount >= 3
MKMapPoint centroid;
MKMapPoint *previousPoint = &(self.points[self.pointCount-1]); // for i=0, wrap around to the last point
for (NSUInteger i = 0; i < self.pointCount; ++i) {
MKMapPoint *point = &(self.points[i]);
double delta = (previousPoint->x * point->y) - (point->x * previousPoint->y); // x[i-1]*y[i] + x[i]*y[i-1]
centroid.x += (previousPoint->x + point->x) * delta; // (x[i-1] + x[i]) / delta
centroid.y += (previousPoint->y + point->y) * delta; // (y[i-1] + y[i]) / delta
previousPoint = point;
}
centroid.x /= 6.0 * self.area;
centroid.y /= 6.0 * self.area;
// interiorPolygons are holes (subtractive geometry model)
for (MKPolygon *interiorPoly in self.interiorPolygons) {
if (interiorPoly.area == 0.0) {
continue; // avoid div-by-zero
}
centroid.x += interiorPoly.centroid.x / interiorPoly.area;
centroid.y += interiorPoly.centroid.y / interiorPoly.area;
}
return centroid;
}
in Swift 5
private func centroidForCoordinates(_ coords: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D? {
guard let firstCoordinate = coordinates.first else {
return nil
}
guard coords.count > 1 else {
return firstCoordinate
}
var minX = firstCoordinate.longitude
var maxX = firstCoordinate.longitude
var minY = firstCoordinate.latitude
var maxY = firstCoordinate.latitude
for i in 1..<coords.count {
let current = coords[i]
if minX > current.longitude {
minX = current.longitude
} else if maxX < current.longitude {
maxX = current.longitude
} else if minY > current.latitude {
minY = current.latitude
} else if maxY < current.latitude {
maxY = current.latitude
}
}
let centerX = minX + ((maxX - minX) / 2)
let centerY = minY + ((maxY - minY) / 2)
return CLLocationCoordinate2D(latitude: centerY, longitude: centerX)
}
I'm trying to find a rotated rectangle UIView's four corners' coordinates.
I think one way I can do is to use recognizer.rotation, find the rotated angle then calculate the origins. But that requires some geometry calculation.
- (IBAction)handlePan:(UIRotationGestureRecognizer*)recognizer {
NSLog(#"Rotation in degrees since last change: %f", [recognizer rotation] * (180 / M_PI));
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
NSLog(#"%#",recognizer);
recognizer.rotation = 0;
NSLog(#"bound is %f and %f, frame is %f and %f, %f and %f.",recognizer.view.bounds.size.width,recognizer.view.bounds.size.height, recognizer.view.frame.size.width,recognizer.view.frame.size.height, recognizer.view.frame.origin.x, recognizer.view.frame.origin.y);
}
I'm just wondering if there are any other easier ways to get the coordinates?
Thanks!
EDIT:
Looks like we have a great answer here(see answer below). I have managed to calculate the corners through a stupid way -- using rotation angle and geometry. It works but not easy and light. I'm sharing my code here just in case some one may want to use it(Even though I doubt it.)
float r = 100;
NSLog(#"radius is %f.",r);
float AAngle = M_PI/3+self.rotatedAngle;
float AY = recognizer.view.center.y - sin(AAngle)*r;
float AX = recognizer.view.center.x - cos(AAngle)*r;
self.pointPADA = CGPointMake(AX, AY);
NSLog(#"View Center is (%f,%f)",recognizer.view.center.x,recognizer.view.center.y);
NSLog(#"Point A has coordinate (%f,%f)",self.pointPADA.x,self.pointPADA.y);
float BAngle = M_PI/3-self.rotatedAngle;
float BY = recognizer.view.center.y - sin(BAngle)*r;
float BX = recognizer.view.center.x + cos(BAngle)*r;
self.pointPADB = CGPointMake(BX, BY);
NSLog(#"Point B has coordinate (%f,%f)",BX,BY);
float CY = recognizer.view.center.y + sin(AAngle)*r;
float CX = recognizer.view.center.x + cos(AAngle)*r;
self.pointPADC = CGPointMake(CX, CY);
NSLog(#"Point C has coordinate (%f,%f)",CX,CY);
float DY = recognizer.view.center.y + sin(BAngle)*r;
float DX = recognizer.view.center.x - cos(BAngle)*r;
self.pointPADD = CGPointMake(DX, DY);
NSLog(#"Point D has coordinate (%f,%f)",DX,DY);
Here's my solution though I wonder if there's a more succinct way:
CGPoint originalCenter = CGPointApplyAffineTransform(theView.center,
CGAffineTransformInvert(theView.transform));
CGPoint topLeft = originalCenter;
topLeft.x -= theView.bounds.size.width / 2;
topLeft.y -= theView.bounds.size.height / 2;
topLeft = CGPointApplyAffineTransform(topLeft, theView.transform);
CGPoint topRight = originalCenter;
topRight.x += theView.bounds.size.width / 2;
topRight.y -= theView.bounds.size.height / 2;
topRight = CGPointApplyAffineTransform(topRight, theView.transform);
CGPoint bottomLeft = originalCenter;
bottomLeft.x -= theView.bounds.size.width / 2;
bottomLeft.y += theView.bounds.size.height / 2;
bottomLeft = CGPointApplyAffineTransform(bottomLeft, theView.transform);
CGPoint bottomRight = originalCenter;
bottomRight.x += theView.bounds.size.width / 2;
bottomRight.y += theView.bounds.size.height / 2;
bottomRight = CGPointApplyAffineTransform(bottomRight, theView.transform);
Checked answer in Swift
struct ViewCorners {
private(set) var topLeft: CGPoint!
private(set) var topRight: CGPoint!
private(set) var bottomLeft: CGPoint!
private(set) var bottomRight: CGPoint!
private let originalCenter: CGPoint
private let transformedView: UIView
private func pointWith(multipliedWidth: CGFloat, multipliedHeight: CGFloat) -> CGPoint {
var x = originalCenter.x
x += transformedView.bounds.width / 2 * multipliedWidth
var y = originalCenter.y
y += transformedView.bounds.height / 2 * multipliedHeight
var result = CGPoint(x: x, y: y).applying(transformedView.transform)
result.x += transformedView.transform.tx
result.y += transformedView.transform.ty
return result
}
init(view: UIView) {
transformedView = view
originalCenter = view.center.applying(view.transform.inverted())
topLeft = pointWith(multipliedWidth:-1, multipliedHeight:-1)
topRight = pointWith(multipliedWidth: 1, multipliedHeight:-1)
bottomLeft = pointWith(multipliedWidth:-1, multipliedHeight: 1)
bottomRight = pointWith(multipliedWidth: 1, multipliedHeight: 1)
}
}
Then create struct instance and take transformed rect corners new points.
let view = UIView(frame: CGRect(x: 40, y: 20, width: 100, height: 50))
view.transform = .init(rotationAngle: .pi / 4)
let corners = ViewCorners(view: view)
print(corners.topLeft,
corners.topRight,
corners.bottomLeft,
corners.bottomRight,
separator: "\n")
Using Swift 4, We can get the bounds of rotated view by simple way.
let transformedBounds = view.bounds.applying(view.transform)
I am trying to figure out how to keep an image within the frame width and height. Right now it just wraps around. I would preferably like to create something that stays within the frame and bounces around inside.
-(void) moveButterfly {
bfly.center = CGPointMake(bfly.center.x + bfly_vx, bfly.center.y + bfly_vy);
if(bfly.center.x > frameWidth)
{
bfly.center = CGPointMake(0, bfly.center.y + bfly_vy);
}
else if (bfly.center.x < 0)
{
bfly.center = CGPointMake(frameWidth, bfly.center.y + bfly_vy);
}
if(bfly.center.y > frameHeight)
{
bfly.center = CGPointMake(bfly.center.x + bfly_vx, 0);
}
else if (bfly.center.y < 0)
{
bfly.center = CGPointMake(bfly.center.x + bfly_vx, frameHeight);
}
}
-(void)moveButterfly{
static int dx = 1;
static int dy = 1;
if (bfly.frame.origin.x >= self.view.bounds.size.width - bfly.bounds.size.width) {
dx = -dx;
}
if (bfly.frame.origin.y >= self.view.bounds.size.height - bfly.bounds.size.height) {
dy = -dy;
}
if (bfly.frame.origin.x <= 0) {
dx = -dx;
}
if (bfly.frame.origin.y <= 0) {
dy = -dy;
}
CGPoint point = bfly.center;
point.x += dx;
point.y += dy;
bfly.center = point;
}
Keep Calling this function using NSTimer at the rate you want to update position. Here dx and dy is the velocity at which butterfly will move.