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:
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
Saving Coredata to a Web Server with Swift 3.0
Solving System of Equations in Swift
How to Make a Segue to Second Item of Tab Bar
Error "No Such Module" When Installed Framework with Pod in Swift 3
How to Switch to Swift 4.0 in Xcode 9.3
How to 'Addtarget' to Uilabel in Swift
Codable Enum with Multiple Keys and Associated Values
Color Keying Video with Gpuimage on a Scnplane in Arkit
Loading Multiple Google Interstitial Ads Makes App Crash
Appdelegate Segue Alternative Pass Data
Nssortdescriptor Sorting Using Nsdate in Swift
Convert String to Staticstring
Changing Tab Bar Color (Swift)
Create a Loading Image/ Activity Indicator, Until the Image Is Shown in the Screen in Swift
Tvos Remote Notification Replacement