Engauge Digitizer  2
CallbackAxisPointsAbstract.cpp
1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "CallbackAxisPointsAbstract.h"
8 #include "EngaugeAssert.h"
9 #include "Logger.h"
10 #include "Point.h"
11 #include <qmath.h>
12 #include "QtToString.h"
13 #include "Transformation.h"
14 
16  DocumentAxesPointsRequired documentAxesPointsRequired) :
17  m_modelCoords (modelCoords),
18  m_isError (false),
19  m_documentAxesPointsRequired (documentAxesPointsRequired)
20 {
21 }
22 
24  const QString pointIdentifierOverride,
25  const QPointF &posScreenOverride,
26  const QPointF &posGraphOverride,
27  DocumentAxesPointsRequired documentAxesPointsRequired) :
28  m_modelCoords (modelCoords),
29  m_pointIdentifierOverride (pointIdentifierOverride),
30  m_posScreenOverride (posScreenOverride),
31  m_posGraphOverride (posGraphOverride),
32  m_isError (false),
33  m_documentAxesPointsRequired (documentAxesPointsRequired)
34 {
35 }
36 
37 bool CallbackAxisPointsAbstract::anyPointsRepeatPair (const CoordPairVector &vector) const
38 {
39  for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
40  for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
41 
42  if ((vector.at(pointLeft).x() == vector.at(pointRight).x()) &&
43  (vector.at(pointLeft).y() == vector.at(pointRight).y())) {
44 
45  // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
46  return true;
47  }
48  }
49  }
50 
51  // No columns repeat
52  return false;
53 }
54 
55 bool CallbackAxisPointsAbstract::anyPointsRepeatSingle (const CoordSingleVector &vector) const
56 {
57  for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
58  for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
59 
60  if (vector.at(pointLeft) == vector.at(pointRight)) {
61 
62  // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
63  return true;
64  }
65  }
66  }
67 
68  // No columns repeat
69  return false;
70 }
71 
73  const Point &point)
74 {
75  QPointF posScreen = point.posScreen ();
76  QPointF posGraph = point.posGraph ();
77 
78  if (m_pointIdentifierOverride == point.identifier ()) {
79 
80  // Override the old point coordinates with its new (if all tests are passed) coordinates
81  posScreen = m_posScreenOverride;
82  posGraph = m_posGraphOverride;
83  }
84 
85  // Try to compute transform
86  if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
87  return callbackRequire3AxisPoints (posScreen,
88  posGraph);
89  } else {
90  return callbackRequire4AxisPoints (point.isXOnly(),
91  posScreen,
92  posGraph);
93  }
94 }
95 
96 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire3AxisPoints (const QPointF &posScreen,
97  const QPointF &posGraph)
98 {
100 
101  // Update range variables
102  int numberPoints = m_screenInputs.count();
103  if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
104  if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
105  if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
106  if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
107 
108  if (numberPoints < 3) {
109 
110  // Append new point
111  m_screenInputs.push_back (posScreen);
112  m_graphOutputs.push_back (posGraph);
113  numberPoints = m_screenInputs.count();
114 
115  if (numberPoints == 3) {
116  loadTransforms3 ();
117  }
118 
119  // Error checking
120  if (anyPointsRepeatPair (m_screenInputs)) {
121 
122  m_isError = true;
123  m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
125 
126  } else if (anyPointsRepeatPair (m_graphOutputs)) {
127 
128  m_isError = true;
129  m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
131 
132  } else if ((numberPoints == 3) && threePointsAreCollinear (m_screenInputsTransform)) {
133 
134  m_isError = true;
135  m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
137 
138  } else if ((numberPoints == 3) && threePointsAreCollinear (m_graphOutputsTransform)) {
139 
140  m_isError = true;
141  m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
143 
144  }
145  }
146 
147  if (m_screenInputs.count() > 2) {
148 
149  // There are enough axis points so quit
151 
152  }
153 
154  return rtn;
155 }
156 
157 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire4AxisPoints (bool isXOnly,
158  const QPointF &posScreen,
159  const QPointF &posGraph)
160 {
162 
163  // Update range variables
164  int numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
165  if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
166  if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
167  if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
168  if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
169 
170  if (numberPoints < 4) {
171 
172  // Append the new point
173  if (isXOnly) {
174 
175  m_screenInputsX.push_back (posScreen);
176  m_graphOutputsX.push_back (posGraph.x());
177 
178  } else {
179 
180  m_screenInputsY.push_back (posScreen);
181  m_graphOutputsY.push_back (posGraph.y());
182 
183  }
184 
185  numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
186  if (numberPoints == 4) {
187  loadTransforms4 ();
188  }
189  }
190 
191  if (m_screenInputsX.count() > 2) {
192 
193  m_isError = true;
194  m_errorMessage = QObject::tr ("Too many x axis points. There should only be two");
196 
197  } else if (m_screenInputsY.count() > 2) {
198 
199  m_isError = true;
200  m_errorMessage = QObject::tr ("Too many y axis points. There should only be two");
202 
203  } else {
204 
205  if ((m_screenInputsX.count() == 2) &&
206  (m_screenInputsY.count() == 2)) {
207 
208  // Done, although an error may intrude
210  }
211 
212  // Error checking
213  if (anyPointsRepeatPair (m_screenInputsX) ||
214  anyPointsRepeatPair (m_screenInputsY)) {
215 
216  m_isError = true;
217  m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
219 
220  } else if (anyPointsRepeatSingle (m_graphOutputsX) ||
221  anyPointsRepeatSingle (m_graphOutputsY)) {
222 
223  m_isError = true;
224  m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
226 
227  } else if ((numberPoints == 4) && threePointsAreCollinear (m_screenInputsTransform)) {
228 
229  m_isError = true;
230  m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
232 
233  } else if ((numberPoints == 4) && threePointsAreCollinear (m_graphOutputsTransform)) {
234 
235  m_isError = true;
236  m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
238 
239  }
240  }
241 
242  return rtn;
243 }
244 
246 {
247  return m_documentAxesPointsRequired;
248 }
249 
250 void CallbackAxisPointsAbstract::loadTransforms3 ()
251 {
252  // Screen coordinates
253  m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), m_screenInputs.at(2).x(),
254  m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), m_screenInputs.at(2).y(),
255  1.0 , 1.0 , 1.0 );
256 
257  // Graph coordinates
258  m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), m_graphOutputs.at(2).x(),
259  m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), m_graphOutputs.at(2).y(),
260  1.0 , 1.0 , 1.0 );
261 }
262 
263 void CallbackAxisPointsAbstract::loadTransforms4 ()
264 {
265  double x1Screen = m_screenInputsX.at(0).x();
266  double y1Screen = m_screenInputsX.at(0).y();
267  double x2Screen = m_screenInputsX.at(1).x();
268  double y2Screen = m_screenInputsX.at(1).y();
269  double x3Screen = m_screenInputsY.at(0).x();
270  double y3Screen = m_screenInputsY.at(0).y();
271  double x4Screen = m_screenInputsY.at(1).x();
272  double y4Screen = m_screenInputsY.at(1).y();
273 
274  // Each of the four axes points has only one coordinate
275  double x1Graph = m_graphOutputsX.at(0);
276  double x2Graph = m_graphOutputsX.at(1);
277  double y3Graph = m_graphOutputsY.at(0);
278  double y4Graph = m_graphOutputsY.at(1);
279 
280  // Intersect the two lines of the two axes. The lines are defined parametrically for the screen coordinates, with
281  // points 1 and 2 on the x axis and points 3 and 4 on the y axis, as:
282  // x = (1 - sx) * x1 + sx * x2
283  // y = (1 - sx) * y1 + sx * y2
284  // x = (1 - sy) * x3 + sy * x4
285  // y = (1 - sy) * y3 + sy * y4
286  // Intersection of the 2 lines is at (x,y). Solving for sx and sy using Cramer's rule where Ax=b
287  // (x1 - x3) (x1 - x2 x4 - x3) (sx)
288  // (y1 - y3) = (y1 - y2 y4 - y3) (sy)
289  double A00 = x1Screen - x2Screen;
290  double A01 = x4Screen - x3Screen;
291  double A10 = y1Screen - y2Screen;
292  double A11 = y4Screen - y3Screen;
293  double b0 = x1Screen - x3Screen;
294  double b1 = y1Screen - y3Screen;
295  double numeratorx = (b0 * A11 - A01 * b1);
296  double numeratory = (A00 * b1 - b0 * A10);
297  double denominator = (A00 * A11 - A01 * A10);
298  double sx = numeratorx / denominator;
299  double sy = numeratory / denominator;
300 
301  // Intersection point. For the graph coordinates, the initial implementation assumes cartesian coordinates
302  double xIntScreen = (1.0 - sx) * x1Screen + sx * x2Screen;
303  double yIntScreen = (1.0 - sy) * y3Screen + sy * y4Screen;
304  double xIntGraph, yIntGraph;
305  if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR) {
306  xIntGraph = (1.0 - sx) * x1Graph + sx * x2Graph;
307  } else {
308  xIntGraph = qExp ((1.0 - sx) * qLn (x1Graph) + sx * qLn (x2Graph));
309  }
310  if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR) {
311  yIntGraph = (1.0 - sy) * y3Graph + sy * y4Graph;
312  } else {
313  yIntGraph = qExp ((1.0 - sy) * qLn (y3Graph) + sy * qLn (y4Graph));
314  }
315 
316  // Distances of 4 axis points from interception
317  double distance1 = qSqrt ((x1Screen - xIntScreen) * (x1Screen - xIntScreen) +
318  (y1Screen - yIntScreen) * (y1Screen - yIntScreen));
319  double distance2 = qSqrt ((x2Screen - xIntScreen) * (x2Screen - xIntScreen) +
320  (y2Screen - yIntScreen) * (y2Screen - yIntScreen));
321  double distance3 = qSqrt ((x3Screen - xIntScreen) * (x3Screen - xIntScreen) +
322  (y3Screen - yIntScreen) * (y3Screen - yIntScreen));
323  double distance4 = qSqrt ((x4Screen - xIntScreen) * (x4Screen - xIntScreen) +
324  (y4Screen - yIntScreen) * (y4Screen - yIntScreen));
325 
326  // We now have too many data points with both x and y coordinates:
327  // (xInt,yInt) (xInt,y3) (xInt,y4) (x1,yInt) (x2,yInt)
328  // so we pick just 3, making sure that those 3 are widely separated
329  // (xInt,yInt) (x axis point furthest from xInt,yInt) (y axis point furthest from xInt,yInt)
330  double xFurthestXAxisScreen, yFurthestXAxisScreen, xFurthestYAxisScreen, yFurthestYAxisScreen;
331  double xFurthestXAxisGraph, yFurthestXAxisGraph, xFurthestYAxisGraph, yFurthestYAxisGraph;
332  if (distance1 < distance2) {
333  xFurthestXAxisScreen = x2Screen;
334  yFurthestXAxisScreen = y2Screen;
335  xFurthestXAxisGraph = x2Graph;
336  yFurthestXAxisGraph = yIntGraph;
337  } else {
338  xFurthestXAxisScreen = x1Screen;
339  yFurthestXAxisScreen = y1Screen;
340  xFurthestXAxisGraph = x1Graph;
341  yFurthestXAxisGraph = yIntGraph;
342  }
343  if (distance3 < distance4) {
344  xFurthestYAxisScreen = x4Screen;
345  yFurthestYAxisScreen = y4Screen;
346  xFurthestYAxisGraph = xIntGraph;
347  yFurthestYAxisGraph = y4Graph;
348  } else {
349  xFurthestYAxisScreen = x3Screen;
350  yFurthestYAxisScreen = y3Screen;
351  xFurthestYAxisGraph = xIntGraph;
352  yFurthestYAxisGraph = y3Graph;
353  }
354 
355  // Screen coordinates
356  m_screenInputsTransform = QTransform (xIntScreen, xFurthestXAxisScreen, xFurthestYAxisScreen,
357  yIntScreen, yFurthestXAxisScreen, yFurthestYAxisScreen,
358  1.0 , 1.0 , 1.0 );
359 
360  // Graph coordinates
361  m_graphOutputsTransform = QTransform (xIntGraph, xFurthestXAxisGraph, xFurthestYAxisGraph,
362  yIntGraph, yFurthestXAxisGraph, yFurthestYAxisGraph,
363  1.0 , 1.0 , 1.0 );
364 }
365 
367 {
368  return m_graphOutputsTransform;
369 }
370 
372 {
373  return m_screenInputsTransform;
374 }
375 
377 {
378  if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
379  return m_screenInputs.count();
380  } else {
381  return m_screenInputsX.count() + m_screenInputsY.count();
382  }
383 }
384 
385 bool CallbackAxisPointsAbstract::threePointsAreCollinear (const QTransform &transform)
386 {
387  return (transform.determinant() == 0);
388 }
QPointF posGraph(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Accessor for graph position. Skip check if copying one instance to another.
Definition: Point.cpp:383
QTransform matrixGraph() const
Returns graph coordinates matrix after transformIsDefined has already indicated success.
DocumentAxesPointsRequired documentAxesPointsRequired() const
Number of axes points required for the transformation.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:23
QPointF posScreen() const
Accessor for screen position.
Definition: Point.cpp:392
CallbackSearchReturn
Return values for search callback methods.
QString identifier() const
Unique identifier for a specific Point.
Definition: Point.cpp:256
Continue normal execution of the search.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
bool isXOnly() const
In DOCUMENT_AXES_POINTS_REQUIRED_4 modes, this is true/false if y/x coordinate is undefined...
Definition: Point.cpp:274
Model for DlgSettingsCoords and CmdSettingsCoords.
CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords, DocumentAxesPointsRequired documentAxesPointsRequired)
Constructor for when all of the existing axis points are to be processed as is.
unsigned int numberAxisPoints() const
Number of axis points which is less than 3 if the axes curve is incomplete.
Immediately terminate the current search.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
QTransform matrixScreen() const
Returns screen coordinates matrix after transformIsDefined has already indicated success.