Skip to content

Commit 1445092

Browse files
committed
Add Closest pair in 2D
1 parent bc3792e commit 1445092

File tree

6 files changed

+321
-1
lines changed

6 files changed

+321
-1
lines changed

AGU/ClosestPair2D.cpp

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#include "main.h"
2+
3+
class CP2DWrap
4+
{
5+
public:
6+
cv::Mat pane;
7+
std::vector<cv::Point> points;
8+
std::vector<Line> shortestDistances;
9+
};
10+
11+
int showWithDelay(CP2DWrap &dataWrap, int delay = 0) {
12+
cv::imshow("Closest pair 2D", dataWrap.pane);
13+
return cv::waitKey(delay);
14+
}
15+
void drawPoints(cv::Mat &pane, std::vector<cv::Point> &points, int startIndex, int endIndex, int pointType = 0, bool clearPane = false, bool showIndex = false) {
16+
if (clearPane) {
17+
pane = CL_BACKGROUND;
18+
}
19+
cv::Scalar color = pointType == 0 ? CL_GREENBLUE : CL_BLUE_DARK;
20+
int radius = pointType == 0 ? 5 : 3;
21+
for (int i = startIndex; i <= endIndex; i++)
22+
{
23+
cv::circle(pane, points[i], radius, color, -1);
24+
if (showIndex) {
25+
std::stringstream ss;
26+
ss << (i + 1);
27+
cv::putText(pane, ss.str(), cv::Point(points[i].x , points[i].y - 8), CV_FONT_HERSHEY_SIMPLEX, 0.4f, CL_WHITE, 1);
28+
}
29+
}
30+
}
31+
void drawPoints(CP2DWrap &dataWrap, int startIndex, int endIndex, int pointType = 0, bool clearPane = false, bool showIndex = false) {
32+
drawPoints(dataWrap.pane, dataWrap.points, startIndex, endIndex, pointType, clearPane, showIndex);
33+
}
34+
void drawAllPoints(CP2DWrap &dataWrap, int pointType = 1, bool clearPane = true, bool showIndex = false) {
35+
drawPoints(dataWrap, 0, dataWrap.points.size() - 1, pointType, clearPane, showIndex);
36+
}
37+
bool sortPointsByX(const cv::Point a, const cv::Point b) { return (a.x < b.x); }
38+
bool sortPointsByY(const cv::Point a, const cv::Point b) { return (a.y < b.y); }
39+
void drawBoundingBox(CP2DWrap &dataWrap, int startIndex, int endIndex) {
40+
int minX = startIndex == 0 ? dataWrap.points[startIndex].x - 25 :
41+
MIDDLE_NUM(dataWrap.points[startIndex].x, dataWrap.points[startIndex - 1].x);
42+
int maxX = endIndex == dataWrap.points.size() - 1 ? dataWrap.points[endIndex].x + 25 :
43+
MIDDLE_NUM(dataWrap.points[endIndex].x, dataWrap.points[endIndex + 1].x);
44+
cv::rectangle(dataWrap.pane, cv::Point(minX, 25), cv::Point(maxX, dataWrap.pane.rows - 25), CL_GRAY);
45+
}
46+
void drawStoredDistances(CP2DWrap &dataWrap) {
47+
for (int i = 0; i < dataWrap.shortestDistances.size(); i++)
48+
{
49+
doubleArrowedLine(dataWrap.pane, dataWrap.shortestDistances[i], CL_GRAY, 2, 7);
50+
}
51+
}
52+
void drawStripBase(CP2DWrap &dataWrap, std::vector<cv::Point> &points, int middleX, float stripSize, int startIndex, int endIndex, bool showIndex = false) {
53+
drawAllPoints(dataWrap);
54+
drawBoundingBox(dataWrap, startIndex, endIndex);
55+
56+
cv::line(dataWrap.pane, cv::Point(middleX, 15), cv::Point(middleX, dataWrap.pane.rows - 25), CL_RED_DARK);
57+
int leftX = std::max<int>((int)(middleX - stripSize), dataWrap.points[startIndex].x);
58+
cv::line(dataWrap.pane, cv::Point(leftX, 15), cv::Point(leftX, dataWrap.pane.rows - 25), CL_GRAY);
59+
int rightX = std::min<int>((int)(middleX + stripSize), dataWrap.points[endIndex].x);
60+
cv::line(dataWrap.pane, cv::Point(rightX, 15), cv::Point(rightX, dataWrap.pane.rows - 25), CL_GRAY);
61+
62+
drawPoints(dataWrap.pane, points, 0, points.size() - 1, 0, false, showIndex);
63+
drawStoredDistances(dataWrap);
64+
}
65+
void drawBase(CP2DWrap &dataWrap, int startIndex, int endIndex) {
66+
drawAllPoints(dataWrap);
67+
drawBoundingBox(dataWrap, startIndex, endIndex);
68+
drawPoints(dataWrap, startIndex, endIndex);
69+
drawStoredDistances(dataWrap);
70+
}
71+
72+
/*
73+
http://www.geeksforgeeks.org/closest-pair-of-points/
74+
*/
75+
76+
Line getShortestDistanceBruteForce(CP2DWrap &dataWrap, int startIndex, int endIndex) {
77+
float smallestDistance = FLT_MAX;
78+
int p1i = 0, p2i = 0;
79+
for (int i = startIndex; i <= endIndex; i++)
80+
{
81+
for (int j = i + 1; j <= endIndex; j++)
82+
{
83+
float dist = getLineLength(dataWrap.points[i], dataWrap.points[j]);
84+
85+
drawBase(dataWrap, startIndex, endIndex);
86+
doubleArrowedLine(dataWrap.pane, dataWrap.points[i], dataWrap.points[j], CL_PURPLE_DARK, 2, 7);
87+
88+
if (dist < smallestDistance) {
89+
smallestDistance = dist;
90+
p1i = i;
91+
p2i = j;
92+
}
93+
94+
doubleArrowedLine(dataWrap.pane, dataWrap.points[p1i], dataWrap.points[p2i], CL_YELLOW, 2, 7);
95+
showWithDelay(dataWrap, 500);
96+
}
97+
}
98+
dataWrap.shortestDistances.push_back(Line(dataWrap.points[p1i], dataWrap.points[p2i]));
99+
return Line(dataWrap.points[p1i], dataWrap.points[p2i]);
100+
}
101+
102+
Line getShortestDistanceWithStrip(CP2DWrap &dataWrap, std::vector<cv::Point> &stripPoints, Line closest, int middleX, float stripSize, int startIndex, int endIndex) {
103+
drawStripBase(dataWrap, stripPoints, middleX, stripSize, startIndex, endIndex, true);
104+
doubleArrowedLine(dataWrap.pane, closest, CL_YELLOW, 2, 7);
105+
showWithDelay(dataWrap, 500);
106+
if (stripPoints.size() < 2)
107+
{
108+
return closest;
109+
}
110+
111+
std::sort(stripPoints.begin(), stripPoints.end(), sortPointsByY);
112+
113+
drawStripBase(dataWrap, stripPoints, middleX, stripSize, startIndex, endIndex, true);
114+
doubleArrowedLine(dataWrap.pane, closest, CL_YELLOW, 2, 7);
115+
showWithDelay(dataWrap, 500);
116+
117+
float closestDistance = getLineLength(closest);
118+
for (int i = 0; i < stripPoints.size() - 1; i++)
119+
{
120+
for (int j = i+1; j < stripPoints.size() &&
121+
(stripPoints[j].y - stripPoints[i].y < closestDistance); j++)
122+
{
123+
drawStripBase(dataWrap, stripPoints, middleX, stripSize, startIndex, endIndex);
124+
doubleArrowedLine(dataWrap.pane, stripPoints[i], stripPoints[j], CL_PURPLE_DARK, 2, 7);
125+
126+
if (getLineLength(stripPoints[i], stripPoints[j]) < closestDistance )
127+
{
128+
closestDistance = getLineLength(stripPoints[i], stripPoints[j]);
129+
closest = Line(stripPoints[i], stripPoints[j]);
130+
}
131+
132+
doubleArrowedLine(dataWrap.pane, closest, CL_YELLOW, 2, 7);
133+
showWithDelay(dataWrap, 500);
134+
}
135+
}
136+
return closest;
137+
}
138+
139+
Line getShortestDistance(CP2DWrap &dataWrap, int startIndex, int endIndex) {
140+
drawBase(dataWrap, startIndex, endIndex);
141+
showWithDelay(dataWrap, 500);
142+
143+
if (endIndex - startIndex < 3)
144+
{
145+
return getShortestDistanceBruteForce(dataWrap, startIndex, endIndex);
146+
}
147+
148+
int middleIndex = ((float)(endIndex - startIndex) / 2.0f) + startIndex;
149+
Line closestLeft = getShortestDistance(dataWrap, startIndex, middleIndex);
150+
Line closestRight = getShortestDistance(dataWrap, middleIndex + 1, endIndex);
151+
152+
drawBase(dataWrap, startIndex, endIndex);
153+
showWithDelay(dataWrap, 500);
154+
155+
dataWrap.shortestDistances.pop_back();
156+
dataWrap.shortestDistances.pop_back();
157+
158+
float fromLeftDistance = getLineLength(closestLeft),
159+
fromRightDistance = getLineLength(closestRight);
160+
161+
Line closestBoth = (fromLeftDistance < fromRightDistance) ? closestLeft : closestRight;
162+
float closestBothDistance = getLineLength(closestBoth);
163+
164+
drawBase(dataWrap, startIndex, endIndex);
165+
doubleArrowedLine(dataWrap.pane, closestBoth, CL_YELLOW, 2, 7);
166+
showWithDelay(dataWrap, 500);
167+
168+
int middleX = MIDDLE_NUM(dataWrap.points[middleIndex].x, dataWrap.points[middleIndex + 1].x);
169+
std::vector<cv::Point> stripLinePoints;
170+
for (int i = startIndex; i <= endIndex; i++)
171+
{
172+
if (abs(dataWrap.points[i].x - middleX) < closestBothDistance)
173+
{
174+
stripLinePoints.push_back(dataWrap.points[i]);
175+
}
176+
}
177+
float stripHalfWidth = (float)(dataWrap.points[endIndex].x - dataWrap.points[startIndex].x) / 2.0f;
178+
if (closestBothDistance < stripHalfWidth)
179+
{
180+
stripHalfWidth = closestBothDistance;
181+
}
182+
Line finalClosest = getShortestDistanceWithStrip(dataWrap, stripLinePoints, closestBoth, middleX, stripHalfWidth, startIndex, endIndex);
183+
184+
dataWrap.shortestDistances.push_back(finalClosest);
185+
return finalClosest;
186+
}
187+
188+
void runClosestPair2D() {
189+
CP2DWrap dataWrap;
190+
dataWrap.pane = cv::Mat(500, 1100, CV_8UC3);
191+
dataWrap.points = std::vector<cv::Point>(50);
192+
193+
for (int i = 0; i < dataWrap.points.size(); i++)
194+
{
195+
dataWrap.points[i] = cv::Point2i(getRandom(0, dataWrap.pane.cols - 100) + 50, getRandom(0, dataWrap.pane.rows - 100) + 50);
196+
}
197+
198+
drawAllPoints(dataWrap, 0, true, true);
199+
showWithDelay(dataWrap, 5000);
200+
std::sort(dataWrap.points.begin(), dataWrap.points.end(), sortPointsByX);
201+
drawAllPoints(dataWrap, 0, true, true);
202+
showWithDelay(dataWrap, 1000);
203+
204+
Line shortestDistance = getShortestDistance(dataWrap, 0, dataWrap.points.size() - 1);
205+
206+
drawAllPoints(dataWrap, 1);
207+
std::vector<cv::Point> finalPoints(2);
208+
finalPoints[0] = shortestDistance.first;
209+
finalPoints[1] = shortestDistance.second;
210+
drawPoints(dataWrap.pane, finalPoints, 0, 1);
211+
doubleArrowedLine(dataWrap.pane, shortestDistance, CL_YELLOW, 2, 7);
212+
showWithDelay(dataWrap, 0);
213+
}

AGU/TimeMeasuring.h

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
#include <unordered_map>
3+
#include <chrono>
4+
class TimeMeasuring
5+
{
6+
private:
7+
bool started = false;
8+
std::chrono::steady_clock::time_point start;
9+
std::unordered_map<std::string, std::chrono::steady_clock::time_point> breakpoints;
10+
public:
11+
TimeMeasuring(bool start = false) {
12+
this->breakpoints = std::unordered_map<std::string, std::chrono::steady_clock::time_point>();
13+
if (start)
14+
{
15+
this->startMeasuring();
16+
}
17+
}
18+
19+
bool startMeasuring() {
20+
if (!this->started)
21+
{
22+
this->start = std::chrono::steady_clock::now();
23+
this->started = true;
24+
return true;
25+
}
26+
return false;
27+
}
28+
29+
bool insertBreakpoint(std::string name) {
30+
if (this->breakpoints.count(name) == 0)
31+
{
32+
this->breakpoints[name] = std::chrono::steady_clock::now();
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
long long int getTimeFromBeginning(bool microSeconds = false) {
39+
if (!this->started)
40+
{
41+
return -1;
42+
}
43+
if (microSeconds)
44+
{
45+
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - this->start).count();
46+
}
47+
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - this->start).count();
48+
}
49+
50+
long long int getTimeFromBreakpoint(std::string breakpoint, bool microSeconds = false) {
51+
if (!this->started || this->breakpoints.count(breakpoint) == 0)
52+
{
53+
return -1;
54+
}
55+
if (microSeconds)
56+
{
57+
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - this->breakpoints[breakpoint]).count();
58+
}
59+
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - this->breakpoints[breakpoint]).count();
60+
}
61+
};
62+

AGU/main.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
void runConvexHull();
44
void runPointInsidePolygonCheck();
55
void runClosestPair1D();
6+
void runClosestPair2D();
67

78
int main(){
89
srand(time(NULL));
910

11+
runClosestPair2D();
12+
return 0;
13+
1014
std::cout << "Insert index of algorithm to run:\n";
1115
std::cout << "\t1. Point in Polygon test\n";
1216
std::cout << "\t2. Convex hull - Jarvis march\n";
1317
std::cout << "\t3. Find closest pair in 1D\n";
18+
std::cout << "\t4. Find closest pair in 2D\n";
1419

1520
int algo;
1621
std::cin >> algo;
@@ -26,6 +31,9 @@ int main(){
2631
case 3:
2732
runClosestPair1D();
2833
break;
34+
case 4:
35+
runClosestPair2D();
36+
break;
2937
default:
3038
std::cout << "Invalid algorithm";
3139
}

AGU/utils.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ float getRandom(float to) {
1111
cv::Point getLineMiddle(cv::Point p1, cv::Point p2) {
1212
return cv::Point(std::min(p1.x, p2.x) + (abs(p1.x - p2.x) / 2), std::min(p1.y, p2.y) + (abs(p1.y - p2.y) / 2));
1313
}
14+
float getLineLength(const cv::Point p1, const cv::Point p2) {
15+
return sqrt(SQR(p1.x - p2.x) + SQR(p1.y - p2.y));
16+
}
17+
float getLineLength(const Line line) {
18+
return getLineLength(line.first, line.second);
19+
}
1420
int getPointSideToLine(cv::Point lineStart, cv::Point lineEnd, cv::Point testPoint) {
1521
// https://math.stackexchange.com/questions/274712/calculate-on-which-side-of-a-straight-line-is-a-given-point-located
1622
int d = (testPoint.x - lineStart.x)*(lineEnd.y - lineStart.y) -
@@ -58,4 +64,27 @@ Vec2f getPerpendicularVector(cv::Point p1, cv::Point p2, bool toRight) {
5864
}
5965
Vec2f getPerpendicularVector(Vec2f vec, bool toRight) {
6066
return Vec2f((toRight ? -vec.y : vec.y), (toRight ? vec.x : -vec.x));
67+
}
68+
Vec2f rotateVector(const Vec2f vec, float deg) {
69+
return Vec2f(
70+
vec.x * cos(DEG2RAD(deg)) - vec.y * sin(DEG2RAD(deg)),
71+
vec.y * cos(DEG2RAD(deg)) + vec.x * sin(DEG2RAD(deg))
72+
);
73+
}
74+
75+
void doubleArrowedLine(cv::Mat &mat, cv::Point p1, cv::Point p2, cv::Scalar color, int thickness, int arrowSize) {
76+
cv::line(mat, p1, p2, color, thickness);
77+
78+
Vec2f arrowVec1 = rotateVector(getNormalizedVector(p1, p2), 45);
79+
Vec2f arrowVec2 = getPerpendicularVector(arrowVec1, false);
80+
81+
cv::line(mat, p1, cv::Point(p1.x + arrowVec1.x * arrowSize, p1.y + arrowVec1.y * arrowSize), color, thickness);
82+
cv::line(mat, p1, cv::Point(p1.x + arrowVec2.x * arrowSize, p1.y + arrowVec2.y * arrowSize), color, thickness);
83+
84+
cv::line(mat, p2, cv::Point(p2.x - arrowVec1.x * arrowSize, p2.y - arrowVec1.y * arrowSize), color, thickness);
85+
cv::line(mat, p2, cv::Point(p2.x - arrowVec2.x * arrowSize, p2.y - arrowVec2.y * arrowSize), color, thickness);
86+
87+
}
88+
void doubleArrowedLine(cv::Mat &mat, Line line, cv::Scalar color, int thickness, int arrowSize) {
89+
doubleArrowedLine(mat, line.first, line.second, color, thickness, arrowSize);
6190
}

AGU/utils.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#define DEG2RAD(deg) ((deg) * M_PI / 180.0f)
55
#define INT_FLOOR(x) ((int)std::floor(x))
66
#define INT_CEIL(x) ((int)std::ceil(x))
7+
#define MIDDLE_NUM(a, b) ( (float)((b)-(a))/2.0f + (a) )
78

89
#define CL_BACKGROUND cv::Scalar(55, 55, 55)
910
#define CL_WHITE cv::Scalar(255, 255, 255)
@@ -29,6 +30,8 @@ float getRandom(float from, float to);
2930
float getRandom(float to = 1.0f);
3031

3132
cv::Point getLineMiddle(const cv::Point p1, const cv::Point p2);
33+
float getLineLength(const cv::Point p1, const cv::Point p2);
34+
float getLineLength(const Line line);
3235
int getPointSideToLine(const cv::Point lineStart, const cv::Point lineEnd, const cv::Point testPoint);
3336
bool isPointInsideTriangle(const cv::Point p1, const cv::Point p2, const cv::Point p3, const cv::Point searchPoint);
3437

@@ -38,4 +41,8 @@ Vec2f getVector(const cv::Point p1, const cv::Point p2);
3841
Vec2f getNormalizedVector(const Vec2f vec);
3942
Vec2f getNormalizedVector(const cv::Point p1, const cv::Point p2);
4043
Vec2f getPerpendicularVector(const cv::Point p1, const cv::Point p2, const bool toRight = true);
41-
Vec2f getPerpendicularVector(const Vec2f vec, const bool toRight = true);
44+
Vec2f getPerpendicularVector(const Vec2f vec, const bool toRight = true);
45+
Vec2f rotateVector(const Vec2f vec, float deg);
46+
47+
void doubleArrowedLine(cv::Mat &mat, cv::Point p1, cv::Point p2, const cv::Scalar color, int thickness = 1, int arrowSize = 5);
48+
void doubleArrowedLine(cv::Mat &mat, Line line, const cv::Scalar color, int thickness = 1, int arrowSize = 5);

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Visualization of algorithms studied in Technical university of Ostrava in lectur
55
- Point in Polygon test
66
- Convex hull - Jarvis march
77
- Closest pair of points in 1D
8+
- Closest pair of points in 2D
89

910
All algorithms are visualised and uploaded on [YouTube](https://www.youtube.com/playlist?list=PLkX8LaR_NiJBx38USppREzcDAvbfybqhO&disable_polymer=true).
1011

0 commit comments

Comments
 (0)