How to Compare Cgpoints in Swift

How to compare CGPoints In swift?

CGPoint already implements the Equatable protocol, so you can compare using the == operator:

if a == b {
}

Comparing two CGPoints for equality: returning not equal for two objects that output same point?

Unfortunately, what you see in the console is not what your real value is.

import UIKit

var x = CGPoint(x:175.0,y:70.0)
var y = CGPoint(x:175.0,y:70.00000000000001)

print("\(x.equalTo(y)), \(x == y),\(x),\(y)")

The problem is, the console only allows for 10-16 but in reality your CGFloat can go even lower than that because on 64bit architecture, CGFloat is Double.

This means you have to cast your CGPoint values to a Float if you want to get equality that will appear on the console, so you need to do something like:

if Float(boxA.x) == Float(boxB.x) && Float(boxA.y) == Float(boxB.y)
{
//We have equality
}

Now I like to take it one step further.

In most cases, we are using CGPoint to determine points on the scene. Rarely do we ever want to be dealing with 1/2 points, they make our lives just confusing.

So instead of Float, I like to cast to Int. This will guarantee if two points are lying on the same CGPoint in scene space

if Int(boxA.x) == Int(boxB.x) && Int(boxA.y) == Int(boxB.y)
{
//We have equality
}

Is there a way to compare CGPoints?

CGPointEqualToPoint: Returns whether two points are equal.

bool CGPointEqualToPoint (
CGPoint point1,
CGPoint point2
);

From: https://developer.apple.com/documentation/coregraphics/cgpointequaltopoint

How to compare two points in Swift

To handle the Y position

Bool onPreviousLine = (emoji_pt[i].y < cursor.y)

To handle the X position (lineHeight being a constant depending on your case and the size of images, you could probably get away with some low constant value (e.g. 1.0)).

Bool onTheSameLine = abs(Double(emoji_pt[i].y - cursor.y)) < Double(lineHeight / 2)
Bool beforeX = (emoji_pt[i].x < cursor.x)

Together

if onPreviousLine || (onTheSameLine && beforeX) {
continue
}

Swift - Compare colors at CGPoint

have the UIImage extension return a UIColor. use this method to compare each pixel of the two images. if both pixels match, add the color to an array of arrays.

extension UIImage {
func getPixelColor(pos: CGPoint) -> UIColor {

let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4

let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

return UIColor(red: r, green: g, blue: b, alpha: a)
}
}

func findMatchingPixels(aImage: UIImage, _ bImage: UIImage) -> [[UIColor?]] {
guard aImage.size == bImage.size else { fatalError("images must be the same size") }

var matchingColors: [[UIColor?]] = []
for y in 0..<Int(aImage.size.height) {
var currentRow = [UIColor?]()
for x in 0..<Int(aImage.size.width) {
let aColor = aImage.getPixelColor(CGPoint(x: x, y: y))
let colorsMatch = bImage.getPixelColor(CGPoint(x: x, y: y)) == aColor
currentRow.append(colorsMatch ? aColor : nil)
}
matchingColors.append(currentRow)
}
return matchingColors
}

used like this:

let matchingPixels = findMatchingPixels(UIImage(named: "imageA.png")!, UIImage(named: "imageB.png")!)
if let colorForOrigin = matchingPixels[0][0] {
print("the images have the same color, it is: \(colorForOrigin)")
} else {
print("the images do not have the same color at (0,0)")
}

for simplicity i made findMatchingPixels() require the images be the same size, but it wouldn't take much to allow different sized images.

UPDATE

if you want ONLY the pixels that match, i'd return a tuple like this:

func findMatchingPixels(aImage: UIImage, _ bImage: UIImage) -> [(CGPoint, UIColor)] {
guard aImage.size == bImage.size else { fatalError("images must be the same size") }

var matchingColors = [(CGPoint, UIColor)]()
for y in 0..<Int(aImage.size.height) {
for x in 0..<Int(aImage.size.width) {
let aColor = aImage.getPixelColor(CGPoint(x: x, y: y))
guard bImage.getPixelColor(CGPoint(x: x, y: y)) == aColor else { continue }

matchingColors.append((CGPoint(x: x, y: y), aColor))
}
}
return matchingColors
}

Find points between two CGPoints

This is by no means the only solution, but here is one approach I would take.

1. Retrieve valid points from the array

We want to get only the valid points from the array which falls between the start and end points. So to visualize it, I assumed this:

A region is defined as follows in the iOS coordinate system with the top left being 0,0 but this should also work in other coordinate systems where the bottom left is 0,0 for example:

CGPoint defining a region, nearest between 2 points, shortest path neighbours heap sort

So to define a valid region, here are the rules:

  • the start x and start y will be <= end x and end y.
  • Start and end can be a straight line
  • Start and end can be the same point
  • But end cannot be below or to the left of start

I assume that

  • the start x and start y will be <= end x and end y.
  • Start and end can be a straight line
  • Start and end can be the same point
  • But end cannot be below or to the left of start

So in order to support that, I added to your CGPoint extension to check if the point exists in the region

extension CGPoint
{
func distance(to point: CGPoint) -> CGFloat
{
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2))
}


/// Checks if the current point exists in a region. The x and y coordinate of
/// `regionStart` has to be less than or equal to `regionEnd` for a
/// valid check to occur.
/// - Parameters:
/// - regionStart: The top left of the region
/// - regionEnd: The bottom right of the region
/// - Returns: True if the current point falls within the region
func doesExistInRegion(regionStart: CGPoint, regionEnd: CGPoint) -> Bool
{
// Check if we have an invalid region
if regionStart.x > regionEnd.x || regionStart.y > regionEnd.y
{
return false
}

// Check if the current point is outside the region
if x < regionStart.x ||
y < regionStart.y ||
x > regionEnd.x ||
y > regionEnd.y
{
return false
}

// The point is within the region
return true
}
}

Then I extract only the valid points using the extension like this:

let pointsArray = [(10.0, 10.0), (70.0, 10.0), (10.0, 200.0), (70.0, 200.0), (73.0, 10.0), (133.0, 10.0), (73.0, 200.0), (133.0, 200.0), (135.5, 10.0), (195.5, 10.0), (135.5, 200.0), (195.5, 200.0), (198.5, 10.0), (258.5, 10.0), (198.5, 200.0), (258.5, 200.0), (261.5, 10.0), (321.5, 10.0), (261.5, 200.0), (321.5, 200.0), (324.0, 10.0), (384.0, 10.0), (324.0, 200.0), (384.0, 200.0), (387.0, 10.0), (447.0, 10.0), (387.0, 200.0), (447.0, 200.0), (450.0, 10.0), (510.0, 10.0), (450.0, 200.0), (510.0, 200.0), (512.5, 10.0), (572.5, 10.0), (512.5, 200.0), (572.5, 200.0), (575.5, 10.0), (635.5, 10.0), (575.5, 200.0), (635.5, 200.0), (638.5, 10.0), (698.5, 10.0), (638.5, 200.0), (698.5, 200.0), (701.0, 10.0), (761.0, 10.0), (701.0, 200.0), (761.0, 200.0), (764.0, 10.0), (824.0, 10.0), (764.0, 200.0), (824.0, 200.0), (10.0, 390.0), (70.0, 390.0), (73.0, 390.0), (133.0, 390.0), (135.5, 390.0), (195.5, 390.0), (198.5, 390.0), (258.5, 390.0), (261.5, 390.0), (321.5, 390.0), (324.0, 390.0), (384.0, 390.0), (387.0, 390.0), (447.0, 390.0), (450.0, 390.0), (510.0, 390.0), (512.5, 390.0), (572.5, 390.0), (575.5, 390.0), (635.5, 390.0), (638.5, 390.0), (698.5, 390.0), (701.0, 390.0), (761.0, 390.0), (764.0, 390.0), (824.0, 390.0), (10.0, 580.0), (70.0, 580.0), (73.0, 580.0), (133.0, 580.0), (135.5, 580.0), (195.5, 580.0), (198.5, 580.0), (258.5, 580.0)]

let startPoint = CGPoint(x: 80, y: 20)
let endPoint = CGPoint(x: 170, y: 440)

let validPoints = extractValidPoints()

private func extractValidPoints() -> [CGPoint]
{
var validPoints: [CGPoint] = []

for point in pointsArray
{
let coordinate = CGPoint(x: point.0, y: point.1)

if coordinate.doesExistInRegion(regionStart: startPoint, regionEnd: endPoint)
{
validPoints.append(coordinate)
}
}

return validPoints
}

2. Find shortest distance between pairs

From your above array, I got 4 valid coordinates within the region and they are stored in the validPoints array:

(133.0, 200.0)
(135.5, 200.0)
(133.0, 390.0)
(135.5, 390.0)

Now we can loop through these points to get the distance. First I created a convenience struct to organize things better

struct PointPair: Comparable, Hashable
{
private(set) var startPoint = CGPoint.zero
private(set) var endPoint = CGPoint.zero
private(set) var distance = CGFloat.zero

init(withStartPoint start: CGPoint, andEndPoint end: CGPoint)
{
startPoint = start
endPoint = end
distance = startPoint.distance(to: endPoint)

// Just for convenience
display()
}

func display()
{
print("Distance (\(startPoint.x), \(startPoint.y)) and (\(endPoint.x), \(endPoint.y)): \(distance)")
}

// Needed to implement this so that we conform to Comparable and
// can compare 2 points
static func < (lhs: PointPair, rhs: PointPair) -> Bool
{
return lhs.distance < rhs.distance
}

// Need to implement this to conform to Hashable so we can insert a PointPair
// into dictionaries and data strcutures that work with Hashable types
func hash(into hasher: inout Hasher)
{
hasher.combine(startPoint.x)
hasher.combine(startPoint.y)
hasher.combine(endPoint.x)
hasher.combine(endPoint.y)
}
}

Now I can loop through the validPoints array and check pairs like this:

if let nearestPoint = retrieveClosestPairUsingSort(fromPoints: validPoints)
{
print("The nearest pair using sort O(n log n) is")
print(nearestPoint.display())
}

private func retrieveClosestPairUsingSort(fromPoints points: [CGPoint]) -> PointPair?
{
var pairs: [PointPair] = []

// Loop through all the points
for index in 0 ..< points.count
{
for secondIndex in index + 1 ..< points.count
{
let pointPair = PointPair(withStartPoint: points[index],
andEndPoint: points[secondIndex])

pairs.append(pointPair)
}
}

return pairs.sorted().first
}

The output for this is as follows:

Distance (133.0, 200.0) and (135.5, 200.0): 2.5
Distance (133.0, 200.0) and (133.0, 390.0): 190.0
Distance (133.0, 200.0) and (135.5, 390.0): 190.01644665659865
Distance (135.5, 200.0) and (133.0, 390.0): 190.01644665659865
Distance (135.5, 200.0) and (135.5, 390.0): 190.0
Distance (133.0, 390.0) and (135.5, 390.0): 2.5
The nearest pair using sort O(n log n) is
Distance (133.0, 200.0) and (135.5, 200.0): 2.5

3. Taking it one step further

If you have huge array of filtered points, you can consider putting the coordinates into a min heap to retrieve the closest pair in O(log n) - I have an implementation of a heap here

if let nearestPoint = retrieveClosestPairUsingHeap(fromPoints: validPoints)
{
print("The nearest pair using heap O(n) is")
print(nearestPoint.display())
}

private func retrieveClosestPairUsingHeap(fromPoints points: [CGPoint]) -> PointPair?
{
// Instantiate a min heap so the root will be the closest pair
var heap = Heap<PointPair>(withProperty: .min)

// Loop through all the points
for index in 0 ..< points.count
{
for secondIndex in index + 1 ..< points.count
{
let pointPair = PointPair(withStartPoint: points[index],
andEndPoint: points[secondIndex])

heap.insert(pointPair)
}
}

return heap.peek()
}

This also gives the same output:

Distance (133.0, 200.0) and (135.5, 200.0): 2.5
Distance (133.0, 200.0) and (133.0, 390.0): 190.0
Distance (133.0, 200.0) and (135.5, 390.0): 190.01644665659865
Distance (135.5, 200.0) and (133.0, 390.0): 190.01644665659865
Distance (135.5, 200.0) and (135.5, 390.0): 190.0
Distance (133.0, 390.0) and (135.5, 390.0): 2.5
The nearest pair using heap O(n) is
Distance (133.0, 390.0) and (135.5, 390.0): 2.5

I have created a sample with all of this code working together as a simple console application to test out - you can grab that from here.

I hope this answers your question.

Mathematical operations on CGPoint in Swift

It is not strictly meaningful to add two CGPoints. It's not completely wrong; it's just not meaningful because points are coordinates, not offsets. "Chicago + New York" is not a new location.

Generally you would offset a CGPoint using a CGVector, CGSize/NSSize, or a UIOffset. Those also don't have a + operator, but it would make more sense to add the operator to CGPoint+CGVector rather than two CGPoints.

But you're free to add it the way you did if it's particularly convenient.

iOS - Check [CGPoint] make a straight line

One way to do this is to compute the Line of Best Fit for your points to determine if they lie on a line.

func pointsFormALine(_ points: [CGPoint]) -> Bool {

// helper function to test if CGFloat is close enough to zero
// to be considered zero
func isZero(_ f: CGFloat) -> Bool {
let epsilon: CGFloat = 0.00001

return abs(f) < epsilon
}

// variables for computing linear regression
var sumXX: CGFloat = 0 // sum of X^2
var sumXY: CGFloat = 0 // sum of X * Y
var sumX: CGFloat = 0 // sum of X
var sumY: CGFloat = 0 // sum of Y

for point in points {
sumXX += point.x * point.x
sumXY += point.x * point.y
sumX += point.x
sumY += point.y
}

// n is the number of points
let n = CGFloat(points.count)

// compute numerator and denominator of the slope
let num = n * sumXY - sumX * sumY
let den = n * sumXX - sumX * sumX

// is the line vertical or horizontal?
if isZero(num) || isZero(den) {
return true
}

// calculate slope of line
let m = num / den

// calculate the y-intercept
let b = (sumY - m * sumX) / n

print("y = \(m)x + \(b)")

// check fit by summing the squares of the errors
var error: CGFloat = 0
for point in points {
// apply equation of line y = mx + b to compute predicted y
let predictedY = m * point.x + b
error += pow(predictedY - point.y, 2)
}

return isZero(error)
}

Test:

pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 4), CGPoint(x: 5, y: 10)])  // true
pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 1, y: 4), CGPoint(x: 1, y: 10)]) // true
pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 2), CGPoint(x: 5, y: 2)]) // true
pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 1), CGPoint(x: 2, y: 2)]) // false

Extract a String to array of CGPoints in Swift

One of the better efficient way is using to json serialization. Json can not serialize your string because its not in to correct format so you can insert "[" and "]" for serialize it like below

 var coordinates = "[1.3123,3.2131],[2.3123,4.213]"
coordinates.insert("[", at: coordinates.startIndex)
coordinates.insert("]", at: coordinates.endIndex)

Now , our data is ready for serialization.Then the easiest part is appending poinst to CGPoint array

 var points : [CGPoint] = []
if let data = coordinates.data(using: .utf8),
let jsonArray = try? JSONSerialization.jsonObject(with: data) as? [[Double]] {
points.append(contentsOf: jsonArray.map{CGPoint(x: $0[0], y: $0[1]) })
print(points)

}

You can easily reach ur datas

print(points[0].x) // 1.3123
print(points[1].y) // 4.213


Related Topics



Leave a reply



Submit