/src/gdal/alg/gdal_crs.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Mapinfo Image Warper |
4 | | * Purpose: Implementation of the GDALTransformer wrapper around CRS.C |
5 | | functions |
6 | | * to build a polynomial transformation based on ground control |
7 | | * points. |
8 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
9 | | * |
10 | | *************************************************************************** |
11 | | |
12 | | CRS.C - Center for Remote Sensing rectification routines |
13 | | |
14 | | Written By: Brian J. Buckley |
15 | | |
16 | | At: The Center for Remote Sensing |
17 | | Michigan State University |
18 | | 302 Berkey Hall |
19 | | East Lansing, MI 48824 |
20 | | (517)353-7195 |
21 | | |
22 | | Written: 12/19/91 |
23 | | |
24 | | Last Update: 12/26/91 Brian J. Buckley |
25 | | Last Update: 1/24/92 Brian J. Buckley |
26 | | Added printout of trnfile. Triggered by BDEBUG. |
27 | | Last Update: 1/27/92 Brian J. Buckley |
28 | | Fixed bug so that only the active control points were used. |
29 | | Last Update: 6/29/2011 C. F. Stallmann & R. van den Dool (South African |
30 | | National Space Agency) GCP refinement added |
31 | | |
32 | | Copyright (c) 1992, Michigan State University |
33 | | * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com> |
34 | | |
35 | | Permission is hereby granted, free of charge, to any person obtaining a |
36 | | copy of this software and associated documentation files (the "Software"), |
37 | | to deal in the Software without restriction, including without limitation |
38 | | the rights to use, copy, modify, merge, publish, distribute, sublicense, |
39 | | and/or sell copies of the Software, and to permit persons to whom the |
40 | | Software is furnished to do so, subject to the following conditions: |
41 | | |
42 | | The above copyright notice and this permission notice shall be included |
43 | | in all copies or substantial portions of the Software. |
44 | | |
45 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
46 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
47 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
48 | | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
49 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
50 | | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
51 | | DEALINGS IN THE SOFTWARE. |
52 | | |
53 | | ****************************************************************************/ |
54 | | |
55 | | #include "gdal_alg.h" |
56 | | #include "gdal_priv.h" |
57 | | #include "cpl_conv.h" |
58 | | #include "cpl_minixml.h" |
59 | | #include "cpl_string.h" |
60 | | #include "cpl_atomic_ops.h" |
61 | | |
62 | | #include <math.h> |
63 | | #include <stdlib.h> |
64 | | #include <string.h> |
65 | | |
66 | 0 | #define MAXORDER 3 |
67 | | |
68 | | namespace |
69 | | { |
70 | | struct Control_Points |
71 | | { |
72 | | int count; |
73 | | double *e1; |
74 | | double *n1; |
75 | | double *e2; |
76 | | double *n2; |
77 | | int *status; |
78 | | }; |
79 | | } // namespace |
80 | | |
81 | | struct GCPTransformInfo |
82 | | { |
83 | | GDALTransformerInfo sTI{}; |
84 | | |
85 | | double adfToGeoX[20]{}; |
86 | | double adfToGeoY[20]{}; |
87 | | |
88 | | double adfFromGeoX[20]{}; |
89 | | double adfFromGeoY[20]{}; |
90 | | double x1_mean{}; |
91 | | double y1_mean{}; |
92 | | double x2_mean{}; |
93 | | double y2_mean{}; |
94 | | int nOrder{}; |
95 | | int bReversed{}; |
96 | | |
97 | | std::vector<gdal::GCP> asGCPs{}; |
98 | | int bRefine{}; |
99 | | int nMinimumGcps{}; |
100 | | double dfTolerance{}; |
101 | | |
102 | | volatile int nRefCount{}; |
103 | | }; |
104 | | |
105 | | CPL_C_START |
106 | | CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg); |
107 | | void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree); |
108 | | CPL_C_END |
109 | | |
110 | | /* crs.c */ |
111 | | static int CRS_georef(double, double, double *, double *, double[], double[], |
112 | | int); |
113 | | static int CRS_compute_georef_equations(GCPTransformInfo *psInfo, |
114 | | struct Control_Points *, double[], |
115 | | double[], double[], double[], int); |
116 | | static int remove_outliers(GCPTransformInfo *); |
117 | | |
118 | 0 | #define MSUCCESS 1 /* SUCCESS */ |
119 | 0 | #define MNPTERR 0 /* NOT ENOUGH POINTS */ |
120 | 0 | #define MUNSOLVABLE -1 /* NOT SOLVABLE */ |
121 | 0 | #define MMEMERR -2 /* NOT ENOUGH MEMORY */ |
122 | 0 | #define MPARMERR -3 /* PARAMETER ERROR */ |
123 | 0 | #define MINTERR -4 /* INTERNAL ERROR */ |
124 | | |
125 | | static const char *const CRS_error_message[] = { |
126 | | "Failed to compute GCP transform: Not enough points available", |
127 | | "Failed to compute GCP transform: Transform is not solvable", |
128 | | "Failed to compute GCP transform: Not enough memory", |
129 | | "Failed to compute GCP transform: Parameter error", |
130 | | "Failed to compute GCP transform: Internal error"}; |
131 | | |
132 | | /************************************************************************/ |
133 | | /* GDALCreateSimilarGCPTransformer() */ |
134 | | /************************************************************************/ |
135 | | |
136 | | static void *GDALCreateSimilarGCPTransformer(void *hTransformArg, |
137 | | double dfRatioX, double dfRatioY) |
138 | 0 | { |
139 | 0 | GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(hTransformArg); |
140 | |
|
141 | 0 | VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGCPTransformer", |
142 | 0 | nullptr); |
143 | | |
144 | 0 | if (dfRatioX == 1.0 && dfRatioY == 1.0) |
145 | 0 | { |
146 | | /* We can just use a ref count, since using the source transformation */ |
147 | | /* is thread-safe */ |
148 | 0 | CPLAtomicInc(&(psInfo->nRefCount)); |
149 | 0 | } |
150 | 0 | else |
151 | 0 | { |
152 | 0 | auto newGCPs = psInfo->asGCPs; |
153 | 0 | for (auto &gcp : newGCPs) |
154 | 0 | { |
155 | 0 | gcp.Pixel() /= dfRatioX; |
156 | 0 | gcp.Line() /= dfRatioY; |
157 | 0 | } |
158 | | /* As remove_outliers modifies the provided GCPs we don't need to |
159 | | * reapply it */ |
160 | 0 | psInfo = static_cast<GCPTransformInfo *>(GDALCreateGCPTransformer( |
161 | 0 | static_cast<int>(newGCPs.size()), gdal::GCP::c_ptr(newGCPs), |
162 | 0 | psInfo->nOrder, psInfo->bReversed)); |
163 | 0 | } |
164 | |
|
165 | 0 | return psInfo; |
166 | 0 | } |
167 | | |
168 | | /************************************************************************/ |
169 | | /* GDALCreateGCPTransformer() */ |
170 | | /************************************************************************/ |
171 | | |
172 | | static void *GDALCreateGCPTransformerEx(int nGCPCount, |
173 | | const GDAL_GCP *pasGCPList, |
174 | | int nReqOrder, bool bReversed, |
175 | | bool bRefine, double dfTolerance, |
176 | | int nMinimumGcps) |
177 | | |
178 | 0 | { |
179 | | // If no minimumGcp parameter was passed, we use the default value |
180 | | // according to the model |
181 | 0 | if (bRefine && nMinimumGcps == -1) |
182 | 0 | { |
183 | 0 | nMinimumGcps = ((nReqOrder + 1) * (nReqOrder + 2)) / 2 + 1; |
184 | 0 | } |
185 | |
|
186 | 0 | GCPTransformInfo *psInfo = nullptr; |
187 | 0 | double *padfGeoX = nullptr; |
188 | 0 | double *padfGeoY = nullptr; |
189 | 0 | double *padfRasterX = nullptr; |
190 | 0 | double *padfRasterY = nullptr; |
191 | 0 | int *panStatus = nullptr; |
192 | 0 | int iGCP = 0; |
193 | 0 | int nCRSresult = 0; |
194 | 0 | struct Control_Points sPoints; |
195 | |
|
196 | 0 | double x1_sum = 0; |
197 | 0 | double y1_sum = 0; |
198 | 0 | double x2_sum = 0; |
199 | 0 | double y2_sum = 0; |
200 | |
|
201 | 0 | memset(&sPoints, 0, sizeof(sPoints)); |
202 | |
|
203 | 0 | if (nReqOrder == 0) |
204 | 0 | { |
205 | 0 | if (nGCPCount >= 10) |
206 | 0 | nReqOrder = 2; /*for now we avoid 3rd order since it is unstable*/ |
207 | 0 | else if (nGCPCount >= 6) |
208 | 0 | nReqOrder = 2; |
209 | 0 | else |
210 | 0 | nReqOrder = 1; |
211 | 0 | } |
212 | |
|
213 | 0 | psInfo = new GCPTransformInfo(); |
214 | 0 | psInfo->bReversed = bReversed; |
215 | 0 | psInfo->nOrder = nReqOrder; |
216 | 0 | psInfo->bRefine = bRefine; |
217 | 0 | psInfo->dfTolerance = dfTolerance; |
218 | 0 | psInfo->nMinimumGcps = nMinimumGcps; |
219 | |
|
220 | 0 | psInfo->nRefCount = 1; |
221 | |
|
222 | 0 | psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); |
223 | 0 | if (nGCPCount == 2 && nReqOrder == 1 && |
224 | 0 | psInfo->asGCPs[0].X() != psInfo->asGCPs[1].X() && |
225 | 0 | psInfo->asGCPs[0].Y() != psInfo->asGCPs[1].Y()) |
226 | 0 | { |
227 | | // Assumes that the 2 GCPs form opposite corners of a rectangle, |
228 | | // and synthetize a 3rd corner |
229 | 0 | gdal::GCP newGCP; |
230 | 0 | newGCP.X() = psInfo->asGCPs[1].X(); |
231 | 0 | newGCP.Y() = psInfo->asGCPs[0].Y(); |
232 | 0 | newGCP.Pixel() = psInfo->asGCPs[1].Pixel(); |
233 | 0 | newGCP.Line() = psInfo->asGCPs[0].Line(); |
234 | 0 | psInfo->asGCPs.emplace_back(std::move(newGCP)); |
235 | |
|
236 | 0 | nGCPCount = 3; |
237 | 0 | pasGCPList = gdal::GCP::c_ptr(psInfo->asGCPs); |
238 | 0 | } |
239 | |
|
240 | 0 | memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE, |
241 | 0 | strlen(GDAL_GTI2_SIGNATURE)); |
242 | 0 | psInfo->sTI.pszClassName = "GDALGCPTransformer"; |
243 | 0 | psInfo->sTI.pfnTransform = GDALGCPTransform; |
244 | 0 | psInfo->sTI.pfnCleanup = GDALDestroyGCPTransformer; |
245 | 0 | psInfo->sTI.pfnSerialize = GDALSerializeGCPTransformer; |
246 | 0 | psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGCPTransformer; |
247 | | |
248 | | /* -------------------------------------------------------------------- */ |
249 | | /* Compute the forward and reverse polynomials. */ |
250 | | /* -------------------------------------------------------------------- */ |
251 | |
|
252 | 0 | if (nGCPCount == 0) |
253 | 0 | { |
254 | 0 | nCRSresult = MNPTERR; |
255 | 0 | } |
256 | 0 | else if (bRefine) |
257 | 0 | { |
258 | 0 | nCRSresult = remove_outliers(psInfo); |
259 | 0 | } |
260 | 0 | else |
261 | 0 | { |
262 | | /* -------------------------------------------------------------------- |
263 | | */ |
264 | | /* Allocate and initialize the working points list. */ |
265 | | /* -------------------------------------------------------------------- |
266 | | */ |
267 | 0 | try |
268 | 0 | { |
269 | 0 | padfGeoX = new double[nGCPCount]; |
270 | 0 | padfGeoY = new double[nGCPCount]; |
271 | 0 | padfRasterX = new double[nGCPCount]; |
272 | 0 | padfRasterY = new double[nGCPCount]; |
273 | 0 | panStatus = new int[nGCPCount]; |
274 | 0 | for (iGCP = 0; iGCP < nGCPCount; iGCP++) |
275 | 0 | { |
276 | 0 | panStatus[iGCP] = 1; |
277 | 0 | padfGeoX[iGCP] = pasGCPList[iGCP].dfGCPX; |
278 | 0 | padfGeoY[iGCP] = pasGCPList[iGCP].dfGCPY; |
279 | 0 | padfRasterX[iGCP] = pasGCPList[iGCP].dfGCPPixel; |
280 | 0 | padfRasterY[iGCP] = pasGCPList[iGCP].dfGCPLine; |
281 | 0 | x1_sum += pasGCPList[iGCP].dfGCPPixel; |
282 | 0 | y1_sum += pasGCPList[iGCP].dfGCPLine; |
283 | 0 | x2_sum += pasGCPList[iGCP].dfGCPX; |
284 | 0 | y2_sum += pasGCPList[iGCP].dfGCPY; |
285 | 0 | } |
286 | 0 | psInfo->x1_mean = x1_sum / nGCPCount; |
287 | 0 | psInfo->y1_mean = y1_sum / nGCPCount; |
288 | 0 | psInfo->x2_mean = x2_sum / nGCPCount; |
289 | 0 | psInfo->y2_mean = y2_sum / nGCPCount; |
290 | |
|
291 | 0 | sPoints.count = nGCPCount; |
292 | 0 | sPoints.e1 = padfRasterX; |
293 | 0 | sPoints.n1 = padfRasterY; |
294 | 0 | sPoints.e2 = padfGeoX; |
295 | 0 | sPoints.n2 = padfGeoY; |
296 | 0 | sPoints.status = panStatus; |
297 | 0 | nCRSresult = CRS_compute_georef_equations( |
298 | 0 | psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY, |
299 | 0 | psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder); |
300 | 0 | } |
301 | 0 | catch (const std::exception &e) |
302 | 0 | { |
303 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what()); |
304 | 0 | nCRSresult = MINTERR; |
305 | 0 | } |
306 | 0 | delete[] padfGeoX; |
307 | 0 | delete[] padfGeoY; |
308 | 0 | delete[] padfRasterX; |
309 | 0 | delete[] padfRasterY; |
310 | 0 | delete[] panStatus; |
311 | 0 | } |
312 | |
|
313 | 0 | if (nCRSresult != 1) |
314 | 0 | { |
315 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", |
316 | 0 | CRS_error_message[-nCRSresult]); |
317 | 0 | GDALDestroyGCPTransformer(psInfo); |
318 | 0 | return nullptr; |
319 | 0 | } |
320 | 0 | else |
321 | 0 | { |
322 | 0 | return psInfo; |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | /** |
327 | | * Create GCP based polynomial transformer. |
328 | | * |
329 | | * Computes least squares fit polynomials from a provided set of GCPs, |
330 | | * and stores the coefficients for later transformation of points between |
331 | | * pixel/line and georeferenced coordinates. |
332 | | * |
333 | | * The return value should be used as a TransformArg in combination with |
334 | | * the transformation function GDALGCPTransform which fits the |
335 | | * GDALTransformerFunc signature. The returned transform argument should |
336 | | * be deallocated with GDALDestroyGCPTransformer when no longer needed. |
337 | | * |
338 | | * This function may fail (returning nullptr) if the provided set of GCPs |
339 | | * are inadequate for the requested order, the determinate is zero or they |
340 | | * are otherwise "ill conditioned". |
341 | | * |
342 | | * Note that 2nd order requires at least 6 GCPs, and 3rd order requires at |
343 | | * least 10 gcps. If nReqOrder is 0 the highest order possible (limited to 2) |
344 | | * with the provided gcp count will be used. |
345 | | * |
346 | | * @param nGCPCount the number of GCPs in pasGCPList. |
347 | | * @param pasGCPList an array of GCPs to be used as input. |
348 | | * @param nReqOrder the requested polynomial order. It should be 1, 2 or 3. |
349 | | * Using 3 is not recommended due to potential numeric instabilities issues. |
350 | | * @param bReversed set it to TRUE to compute the reversed transformation. |
351 | | * |
352 | | * @return the transform argument or nullptr if creation fails. |
353 | | */ |
354 | | void *GDALCreateGCPTransformer(int nGCPCount, const GDAL_GCP *pasGCPList, |
355 | | int nReqOrder, int bReversed) |
356 | | |
357 | 0 | { |
358 | 0 | return GDALCreateGCPTransformerEx(nGCPCount, pasGCPList, nReqOrder, |
359 | 0 | CPL_TO_BOOL(bReversed), false, -1, -1); |
360 | 0 | } |
361 | | |
362 | | /** Create GCP based polynomial transformer, with a tolerance threshold to |
363 | | * discard GCPs that transform badly. |
364 | | */ |
365 | | void *GDALCreateGCPRefineTransformer(int nGCPCount, const GDAL_GCP *pasGCPList, |
366 | | int nReqOrder, int bReversed, |
367 | | double dfTolerance, int nMinimumGcps) |
368 | | |
369 | 0 | { |
370 | 0 | return GDALCreateGCPTransformerEx(nGCPCount, pasGCPList, nReqOrder, |
371 | 0 | CPL_TO_BOOL(bReversed), true, dfTolerance, |
372 | 0 | nMinimumGcps); |
373 | 0 | } |
374 | | |
375 | | /************************************************************************/ |
376 | | /* GDALDestroyGCPTransformer() */ |
377 | | /************************************************************************/ |
378 | | |
379 | | /** |
380 | | * Destroy GCP transformer. |
381 | | * |
382 | | * This function is used to destroy information about a GCP based |
383 | | * polynomial transformation created with GDALCreateGCPTransformer(). |
384 | | * |
385 | | * @param pTransformArg the transform arg previously returned by |
386 | | * GDALCreateGCPTransformer(). |
387 | | */ |
388 | | |
389 | | void GDALDestroyGCPTransformer(void *pTransformArg) |
390 | | |
391 | 0 | { |
392 | 0 | if (pTransformArg == nullptr) |
393 | 0 | return; |
394 | | |
395 | 0 | GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg); |
396 | |
|
397 | 0 | if (CPLAtomicDec(&(psInfo->nRefCount)) == 0) |
398 | 0 | { |
399 | 0 | delete psInfo; |
400 | 0 | } |
401 | 0 | } |
402 | | |
403 | | /************************************************************************/ |
404 | | /* GDALGCPTransform() */ |
405 | | /************************************************************************/ |
406 | | |
407 | | /** |
408 | | * Transforms point based on GCP derived polynomial model. |
409 | | * |
410 | | * This function matches the GDALTransformerFunc signature, and can be |
411 | | * used to transform one or more points from pixel/line coordinates to |
412 | | * georeferenced coordinates (SrcToDst) or vice versa (DstToSrc). |
413 | | * |
414 | | * @param pTransformArg return value from GDALCreateGCPTransformer(). |
415 | | * @param bDstToSrc TRUE if transformation is from the destination |
416 | | * (georeferenced) coordinates to pixel/line or FALSE when transforming |
417 | | * from pixel/line to georeferenced coordinates. |
418 | | * @param nPointCount the number of values in the x, y and z arrays. |
419 | | * @param x array containing the X values to be transformed. |
420 | | * @param y array containing the Y values to be transformed. |
421 | | * @param z array containing the Z values to be transformed. |
422 | | * @param panSuccess array in which a flag indicating success (TRUE) or |
423 | | * failure (FALSE) of the transformation are placed. |
424 | | * |
425 | | * @return TRUE if all points have been successfully transformed. |
426 | | */ |
427 | | |
428 | | int GDALGCPTransform(void *pTransformArg, int bDstToSrc, int nPointCount, |
429 | | double *x, double *y, CPL_UNUSED double *z, |
430 | | int *panSuccess) |
431 | | |
432 | 0 | { |
433 | 0 | int i = 0; |
434 | 0 | GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg); |
435 | |
|
436 | 0 | if (psInfo->bReversed) |
437 | 0 | bDstToSrc = !bDstToSrc; |
438 | |
|
439 | 0 | int bRet = TRUE; |
440 | 0 | for (i = 0; i < nPointCount; i++) |
441 | 0 | { |
442 | 0 | if (x[i] == HUGE_VAL || y[i] == HUGE_VAL) |
443 | 0 | { |
444 | 0 | bRet = FALSE; |
445 | 0 | panSuccess[i] = FALSE; |
446 | 0 | continue; |
447 | 0 | } |
448 | | |
449 | 0 | if (bDstToSrc) |
450 | 0 | { |
451 | 0 | CRS_georef(x[i] - psInfo->x2_mean, y[i] - psInfo->y2_mean, x + i, |
452 | 0 | y + i, psInfo->adfFromGeoX, psInfo->adfFromGeoY, |
453 | 0 | psInfo->nOrder); |
454 | 0 | } |
455 | 0 | else |
456 | 0 | { |
457 | 0 | CRS_georef(x[i] - psInfo->x1_mean, y[i] - psInfo->y1_mean, x + i, |
458 | 0 | y + i, psInfo->adfToGeoX, psInfo->adfToGeoY, |
459 | 0 | psInfo->nOrder); |
460 | 0 | } |
461 | 0 | panSuccess[i] = TRUE; |
462 | 0 | } |
463 | |
|
464 | 0 | return bRet; |
465 | 0 | } |
466 | | |
467 | | /************************************************************************/ |
468 | | /* GDALSerializeGCPTransformer() */ |
469 | | /************************************************************************/ |
470 | | |
471 | | CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg) |
472 | | |
473 | 0 | { |
474 | 0 | CPLXMLNode *psTree = nullptr; |
475 | 0 | GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg); |
476 | |
|
477 | 0 | VALIDATE_POINTER1(pTransformArg, "GDALSerializeGCPTransformer", nullptr); |
478 | | |
479 | 0 | psTree = CPLCreateXMLNode(nullptr, CXT_Element, "GCPTransformer"); |
480 | | |
481 | | /* -------------------------------------------------------------------- */ |
482 | | /* Serialize Order and bReversed. */ |
483 | | /* -------------------------------------------------------------------- */ |
484 | 0 | CPLCreateXMLElementAndValue(psTree, "Order", |
485 | 0 | CPLSPrintf("%d", psInfo->nOrder)); |
486 | |
|
487 | 0 | CPLCreateXMLElementAndValue(psTree, "Reversed", |
488 | 0 | CPLSPrintf("%d", psInfo->bReversed)); |
489 | |
|
490 | 0 | if (psInfo->bRefine) |
491 | 0 | { |
492 | 0 | CPLCreateXMLElementAndValue(psTree, "Refine", |
493 | 0 | CPLSPrintf("%d", psInfo->bRefine)); |
494 | |
|
495 | 0 | CPLCreateXMLElementAndValue(psTree, "MinimumGcps", |
496 | 0 | CPLSPrintf("%d", psInfo->nMinimumGcps)); |
497 | |
|
498 | 0 | CPLCreateXMLElementAndValue(psTree, "Tolerance", |
499 | 0 | CPLSPrintf("%f", psInfo->dfTolerance)); |
500 | 0 | } |
501 | | |
502 | | /* -------------------------------------------------------------------- */ |
503 | | /* Attach GCP List. */ |
504 | | /* -------------------------------------------------------------------- */ |
505 | 0 | if (!psInfo->asGCPs.empty()) |
506 | 0 | { |
507 | 0 | if (psInfo->bRefine) |
508 | 0 | { |
509 | 0 | remove_outliers(psInfo); |
510 | 0 | } |
511 | |
|
512 | 0 | GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr); |
513 | 0 | } |
514 | |
|
515 | 0 | return psTree; |
516 | 0 | } |
517 | | |
518 | | /************************************************************************/ |
519 | | /* GDALDeserializeReprojectionTransformer() */ |
520 | | /************************************************************************/ |
521 | | |
522 | | void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) |
523 | | |
524 | 0 | { |
525 | 0 | std::vector<gdal::GCP> asGCPs; |
526 | 0 | void *pResult = nullptr; |
527 | 0 | int nReqOrder = 0; |
528 | 0 | int bReversed = 0; |
529 | 0 | int bRefine = 0; |
530 | 0 | int nMinimumGcps = 0; |
531 | 0 | double dfTolerance = 0.0; |
532 | | |
533 | | /* -------------------------------------------------------------------- */ |
534 | | /* Check for GCPs. */ |
535 | | /* -------------------------------------------------------------------- */ |
536 | 0 | CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); |
537 | |
|
538 | 0 | if (psGCPList != nullptr) |
539 | 0 | { |
540 | 0 | GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr); |
541 | 0 | } |
542 | | |
543 | | /* -------------------------------------------------------------------- */ |
544 | | /* Get other flags. */ |
545 | | /* -------------------------------------------------------------------- */ |
546 | 0 | nReqOrder = atoi(CPLGetXMLValue(psTree, "Order", "3")); |
547 | 0 | bReversed = atoi(CPLGetXMLValue(psTree, "Reversed", "0")); |
548 | 0 | bRefine = atoi(CPLGetXMLValue(psTree, "Refine", "0")); |
549 | 0 | nMinimumGcps = atoi(CPLGetXMLValue(psTree, "MinimumGcps", "6")); |
550 | 0 | dfTolerance = CPLAtof(CPLGetXMLValue(psTree, "Tolerance", "1.0")); |
551 | | |
552 | | /* -------------------------------------------------------------------- */ |
553 | | /* Generate transformation. */ |
554 | | /* -------------------------------------------------------------------- */ |
555 | 0 | if (bRefine) |
556 | 0 | { |
557 | 0 | pResult = GDALCreateGCPRefineTransformer( |
558 | 0 | static_cast<int>(asGCPs.size()), gdal::GCP::c_ptr(asGCPs), |
559 | 0 | nReqOrder, bReversed, dfTolerance, nMinimumGcps); |
560 | 0 | } |
561 | 0 | else |
562 | 0 | { |
563 | 0 | pResult = GDALCreateGCPTransformer(static_cast<int>(asGCPs.size()), |
564 | 0 | gdal::GCP::c_ptr(asGCPs), nReqOrder, |
565 | 0 | bReversed); |
566 | 0 | } |
567 | |
|
568 | 0 | return pResult; |
569 | 0 | } |
570 | | |
571 | | /************************************************************************/ |
572 | | /* ==================================================================== */ |
573 | | /* Everything below this point derived from the CRS.C from GRASS. */ |
574 | | /* ==================================================================== */ |
575 | | /************************************************************************/ |
576 | | |
577 | | /* STRUCTURE FOR USE INTERNALLY WITH THESE FUNCTIONS. THESE FUNCTIONS EXPECT |
578 | | SQUARE MATRICES SO ONLY ONE VARIABLE IS GIVEN (N) FOR THE MATRIX SIZE */ |
579 | | |
580 | | struct MATRIX |
581 | | { |
582 | | int n; /* SIZE OF THIS MATRIX (N x N) */ |
583 | | double *v; |
584 | | }; |
585 | | |
586 | | /* CALCULATE OFFSET INTO ARRAY BASED ON R/C */ |
587 | | |
588 | 0 | #define M(row, col) m->v[(((row)-1) * (m->n)) + (col)-1] |
589 | | |
590 | | /***************************************************************************/ |
591 | | /* |
592 | | FUNCTION PROTOTYPES FOR STATIC (INTERNAL) FUNCTIONS |
593 | | */ |
594 | | /***************************************************************************/ |
595 | | |
596 | | static int calccoef(struct Control_Points *, double, double, double *, double *, |
597 | | int); |
598 | | static int calcls(struct Control_Points *, struct MATRIX *, double, double, |
599 | | double *, double *, double *, double *); |
600 | | static int exactdet(struct Control_Points *, struct MATRIX *, double, double, |
601 | | double *, double *, double *, double *); |
602 | | static int solvemat(struct MATRIX *, double *, double *, double *, double *); |
603 | | static double term(int, double, double); |
604 | | |
605 | | /***************************************************************************/ |
606 | | /* |
607 | | TRANSFORM A SINGLE COORDINATE PAIR. |
608 | | */ |
609 | | /***************************************************************************/ |
610 | | |
611 | | static int |
612 | | CRS_georef(double e1, /* EASTINGS TO BE TRANSFORMED */ |
613 | | double n1, /* NORTHINGS TO BE TRANSFORMED */ |
614 | | double *e, /* EASTINGS TO BE TRANSFORMED */ |
615 | | double *n, /* NORTHINGS TO BE TRANSFORMED */ |
616 | | double E[], /* EASTING COEFFICIENTS */ |
617 | | double N[], /* NORTHING COEFFICIENTS */ |
618 | | int order /* ORDER OF TRANSFORMATION TO BE PERFORMED, MUST MATCH THE |
619 | | ORDER USED TO CALCULATE THE COEFFICIENTS */ |
620 | | ) |
621 | 0 | { |
622 | 0 | double e3 = 0.0; |
623 | 0 | double e2n = 0.0; |
624 | 0 | double en2 = 0.0; |
625 | 0 | double n3 = 0.0; |
626 | 0 | double e2 = 0.0; |
627 | 0 | double en = 0.0; |
628 | 0 | double n2 = 0.0; |
629 | |
|
630 | 0 | switch (order) |
631 | 0 | { |
632 | 0 | case 1: |
633 | |
|
634 | 0 | *e = E[0] + E[1] * e1 + E[2] * n1; |
635 | 0 | *n = N[0] + N[1] * e1 + N[2] * n1; |
636 | 0 | break; |
637 | | |
638 | 0 | case 2: |
639 | |
|
640 | 0 | e2 = e1 * e1; |
641 | 0 | n2 = n1 * n1; |
642 | 0 | en = e1 * n1; |
643 | |
|
644 | 0 | *e = E[0] + E[1] * e1 + E[2] * n1 + E[3] * e2 + E[4] * en + |
645 | 0 | E[5] * n2; |
646 | 0 | *n = N[0] + N[1] * e1 + N[2] * n1 + N[3] * e2 + N[4] * en + |
647 | 0 | N[5] * n2; |
648 | 0 | break; |
649 | | |
650 | 0 | case 3: |
651 | |
|
652 | 0 | e2 = e1 * e1; |
653 | 0 | en = e1 * n1; |
654 | 0 | n2 = n1 * n1; |
655 | 0 | e3 = e1 * e2; |
656 | 0 | e2n = e2 * n1; |
657 | 0 | en2 = e1 * n2; |
658 | 0 | n3 = n1 * n2; |
659 | |
|
660 | 0 | *e = E[0] + E[1] * e1 + E[2] * n1 + E[3] * e2 + E[4] * en + |
661 | 0 | E[5] * n2 + E[6] * e3 + E[7] * e2n + E[8] * en2 + E[9] * n3; |
662 | 0 | *n = N[0] + N[1] * e1 + N[2] * n1 + N[3] * e2 + N[4] * en + |
663 | 0 | N[5] * n2 + N[6] * e3 + N[7] * e2n + N[8] * en2 + N[9] * n3; |
664 | 0 | break; |
665 | | |
666 | 0 | default: |
667 | |
|
668 | 0 | return (MPARMERR); |
669 | 0 | } |
670 | | |
671 | 0 | return (MSUCCESS); |
672 | 0 | } |
673 | | |
674 | | /***************************************************************************/ |
675 | | /* |
676 | | COMPUTE THE GEOREFFERENCING COEFFICIENTS BASED ON A SET OF CONTROL POINTS |
677 | | */ |
678 | | /***************************************************************************/ |
679 | | |
680 | | static int CRS_compute_georef_equations(GCPTransformInfo *psInfo, |
681 | | struct Control_Points *cp, double E12[], |
682 | | double N12[], double E21[], |
683 | | double N21[], int order) |
684 | 0 | { |
685 | 0 | double *tempptr = nullptr; |
686 | 0 | int status = 0; |
687 | |
|
688 | 0 | if (order < 1 || order > MAXORDER) |
689 | 0 | return (MPARMERR); |
690 | | |
691 | | /* CALCULATE THE FORWARD TRANSFORMATION COEFFICIENTS */ |
692 | | |
693 | 0 | status = calccoef(cp, psInfo->x1_mean, psInfo->y1_mean, E12, N12, order); |
694 | 0 | if (status != MSUCCESS) |
695 | 0 | return (status); |
696 | | |
697 | | /* SWITCH THE 1 AND 2 EASTING AND NORTHING ARRAYS */ |
698 | | |
699 | 0 | tempptr = cp->e1; |
700 | 0 | cp->e1 = cp->e2; |
701 | 0 | cp->e2 = tempptr; |
702 | 0 | tempptr = cp->n1; |
703 | 0 | cp->n1 = cp->n2; |
704 | 0 | cp->n2 = tempptr; |
705 | | |
706 | | /* CALCULATE THE BACKWARD TRANSFORMATION COEFFICIENTS */ |
707 | |
|
708 | 0 | status = calccoef(cp, psInfo->x2_mean, psInfo->y2_mean, E21, N21, order); |
709 | | |
710 | | /* SWITCH THE 1 AND 2 EASTING AND NORTHING ARRAYS BACK */ |
711 | |
|
712 | 0 | tempptr = cp->e1; |
713 | 0 | cp->e1 = cp->e2; |
714 | 0 | cp->e2 = tempptr; |
715 | 0 | tempptr = cp->n1; |
716 | 0 | cp->n1 = cp->n2; |
717 | 0 | cp->n2 = tempptr; |
718 | |
|
719 | 0 | return (status); |
720 | 0 | } |
721 | | |
722 | | /***************************************************************************/ |
723 | | /* |
724 | | COMPUTE THE GEOREFFERENCING COEFFICIENTS BASED ON A SET OF CONTROL POINTS |
725 | | */ |
726 | | /***************************************************************************/ |
727 | | |
728 | | static int calccoef(struct Control_Points *cp, double x_mean, double y_mean, |
729 | | double E[], double N[], int order) |
730 | 0 | { |
731 | 0 | struct MATRIX m; |
732 | 0 | double *a = nullptr; |
733 | 0 | double *b = nullptr; |
734 | 0 | int numactive = 0; /* NUMBER OF ACTIVE CONTROL POINTS */ |
735 | 0 | int status = 0; |
736 | 0 | int i = 0; |
737 | |
|
738 | 0 | memset(&m, 0, sizeof(m)); |
739 | | |
740 | | /* CALCULATE THE NUMBER OF VALID CONTROL POINTS */ |
741 | |
|
742 | 0 | for (i = numactive = 0; i < cp->count; i++) |
743 | 0 | { |
744 | 0 | if (cp->status[i] > 0) |
745 | 0 | numactive++; |
746 | 0 | } |
747 | | |
748 | | /* CALCULATE THE MINIMUM NUMBER OF CONTROL POINTS NEEDED TO DETERMINE |
749 | | A TRANSFORMATION OF THIS ORDER */ |
750 | |
|
751 | 0 | m.n = ((order + 1) * (order + 2)) / 2; |
752 | |
|
753 | 0 | if (numactive < m.n) |
754 | 0 | return (MNPTERR); |
755 | | |
756 | | /* INITIALIZE MATRIX */ |
757 | | |
758 | 0 | m.v = static_cast<double *>( |
759 | 0 | VSICalloc(cpl::fits_on<int>(m.n * m.n), sizeof(double))); |
760 | 0 | if (m.v == nullptr) |
761 | 0 | { |
762 | 0 | return (MMEMERR); |
763 | 0 | } |
764 | 0 | a = static_cast<double *>(VSICalloc(m.n, sizeof(double))); |
765 | 0 | if (a == nullptr) |
766 | 0 | { |
767 | 0 | CPLFree(m.v); |
768 | 0 | return (MMEMERR); |
769 | 0 | } |
770 | 0 | b = static_cast<double *>(VSICalloc(m.n, sizeof(double))); |
771 | 0 | if (b == nullptr) |
772 | 0 | { |
773 | 0 | CPLFree(m.v); |
774 | 0 | CPLFree(a); |
775 | 0 | return (MMEMERR); |
776 | 0 | } |
777 | | |
778 | 0 | if (numactive == m.n) |
779 | 0 | status = exactdet(cp, &m, x_mean, y_mean, a, b, E, N); |
780 | 0 | else |
781 | 0 | status = calcls(cp, &m, x_mean, y_mean, a, b, E, N); |
782 | |
|
783 | 0 | CPLFree(m.v); |
784 | 0 | CPLFree(a); |
785 | 0 | CPLFree(b); |
786 | |
|
787 | 0 | return (status); |
788 | 0 | } |
789 | | |
790 | | /***************************************************************************/ |
791 | | /* |
792 | | CALCULATE THE TRANSFORMATION COEFFICIENTS WITH EXACTLY THE MINIMUM |
793 | | NUMBER OF CONTROL POINTS REQUIRED FOR THIS TRANSFORMATION. |
794 | | */ |
795 | | /***************************************************************************/ |
796 | | |
797 | | static int exactdet(struct Control_Points *cp, struct MATRIX *m, double x_mean, |
798 | | double y_mean, double a[], double b[], |
799 | | double E[], /* EASTING COEFFICIENTS */ |
800 | | double N[] /* NORTHING COEFFICIENTS */ |
801 | | ) |
802 | 0 | { |
803 | 0 | int currow = 1; |
804 | |
|
805 | 0 | for (int pntnow = 0; pntnow < cp->count; pntnow++) |
806 | 0 | { |
807 | 0 | if (cp->status[pntnow] > 0) |
808 | 0 | { |
809 | | /* POPULATE MATRIX M */ |
810 | |
|
811 | 0 | for (int j = 1; j <= m->n; j++) |
812 | 0 | { |
813 | 0 | M(currow, j) = |
814 | 0 | term(j, cp->e1[pntnow] - x_mean, cp->n1[pntnow] - y_mean); |
815 | 0 | } |
816 | | |
817 | | /* POPULATE MATRIX A AND B */ |
818 | |
|
819 | 0 | a[currow - 1] = cp->e2[pntnow]; |
820 | 0 | b[currow - 1] = cp->n2[pntnow]; |
821 | |
|
822 | 0 | currow++; |
823 | 0 | } |
824 | 0 | } |
825 | |
|
826 | 0 | if (currow - 1 != m->n) |
827 | 0 | return (MINTERR); |
828 | | |
829 | 0 | return (solvemat(m, a, b, E, N)); |
830 | 0 | } |
831 | | |
832 | | /***************************************************************************/ |
833 | | /* |
834 | | CALCULATE THE TRANSFORMATION COEFFICIENTS WITH MORE THAN THE MINIMUM |
835 | | NUMBER OF CONTROL POINTS REQUIRED FOR THIS TRANSFORMATION. THIS |
836 | | ROUTINE USES THE LEAST SQUARES METHOD TO COMPUTE THE COEFFICIENTS. |
837 | | */ |
838 | | /***************************************************************************/ |
839 | | |
840 | | static int calcls(struct Control_Points *cp, struct MATRIX *m, double x_mean, |
841 | | double y_mean, double a[], double b[], |
842 | | double E[], /* EASTING COEFFICIENTS */ |
843 | | double N[] /* NORTHING COEFFICIENTS */ |
844 | | ) |
845 | 0 | { |
846 | 0 | int numactive = 0; |
847 | | |
848 | | /* INITIALIZE THE UPPER HALF OF THE MATRIX AND THE TWO COLUMN VECTORS */ |
849 | |
|
850 | 0 | for (int i = 1; i <= m->n; i++) |
851 | 0 | { |
852 | 0 | for (int j = i; j <= m->n; j++) |
853 | 0 | M(i, j) = 0.0; |
854 | 0 | a[i - 1] = b[i - 1] = 0.0; |
855 | 0 | } |
856 | | |
857 | | /* SUM THE UPPER HALF OF THE MATRIX AND THE COLUMN VECTORS ACCORDING TO |
858 | | THE LEAST SQUARES METHOD OF SOLVING OVER DETERMINED SYSTEMS */ |
859 | |
|
860 | 0 | for (int n = 0; n < cp->count; n++) |
861 | 0 | { |
862 | 0 | if (cp->status[n] > 0) |
863 | 0 | { |
864 | 0 | numactive++; |
865 | 0 | for (int i = 1; i <= m->n; i++) |
866 | 0 | { |
867 | 0 | for (int j = i; j <= m->n; j++) |
868 | 0 | M(i, j) += term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean) * |
869 | 0 | term(j, cp->e1[n] - x_mean, cp->n1[n] - y_mean); |
870 | |
|
871 | 0 | a[i - 1] += |
872 | 0 | cp->e2[n] * term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean); |
873 | 0 | b[i - 1] += |
874 | 0 | cp->n2[n] * term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean); |
875 | 0 | } |
876 | 0 | } |
877 | 0 | } |
878 | |
|
879 | 0 | if (numactive <= m->n) |
880 | 0 | return (MINTERR); |
881 | | |
882 | | /* TRANSPOSE VALUES IN UPPER HALF OF M TO OTHER HALF */ |
883 | | |
884 | 0 | for (int i = 2; i <= m->n; i++) |
885 | 0 | { |
886 | 0 | for (int j = 1; j < i; j++) |
887 | 0 | M(i, j) = M(j, i); |
888 | 0 | } |
889 | |
|
890 | 0 | return (solvemat(m, a, b, E, N)); |
891 | 0 | } |
892 | | |
893 | | /***************************************************************************/ |
894 | | /* |
895 | | CALCULATE THE X/Y TERM BASED ON THE TERM NUMBER |
896 | | |
897 | | ORDER\TERM 1 2 3 4 5 6 7 8 9 10 |
898 | | 1 e0n0 e1n0 e0n1 |
899 | | 2 e0n0 e1n0 e0n1 e2n0 e1n1 e0n2 |
900 | | 3 e0n0 e1n0 e0n1 e2n0 e1n1 e0n2 e3n0 e2n1 e1n2 e0n3 |
901 | | */ |
902 | | /***************************************************************************/ |
903 | | |
904 | | static double term(int nTerm, double e, double n) |
905 | 0 | { |
906 | 0 | switch (nTerm) |
907 | 0 | { |
908 | 0 | case 1: |
909 | 0 | return (1.0); |
910 | 0 | case 2: |
911 | 0 | return (e); |
912 | 0 | case 3: |
913 | 0 | return (n); |
914 | 0 | case 4: |
915 | 0 | return ((e * e)); |
916 | 0 | case 5: |
917 | 0 | return ((e * n)); |
918 | 0 | case 6: |
919 | 0 | return ((n * n)); |
920 | 0 | case 7: |
921 | 0 | return ((e * e * e)); |
922 | 0 | case 8: |
923 | 0 | return ((e * e * n)); |
924 | 0 | case 9: |
925 | 0 | return ((e * n * n)); |
926 | 0 | case 10: |
927 | 0 | return ((n * n * n)); |
928 | 0 | } |
929 | 0 | return 0.0; |
930 | 0 | } |
931 | | |
932 | | /***************************************************************************/ |
933 | | /* |
934 | | SOLVE FOR THE 'E' AND 'N' COEFFICIENTS BY USING A SOMEWHAT MODIFIED |
935 | | GAUSSIAN ELIMINATION METHOD. |
936 | | |
937 | | | M11 M12 ... M1n | | E0 | | a0 | |
938 | | | M21 M22 ... M2n | | E1 | = | a1 | |
939 | | | . . . . | | . | | . | |
940 | | | Mn1 Mn2 ... Mnn | | En-1 | | an-1 | |
941 | | |
942 | | and |
943 | | |
944 | | | M11 M12 ... M1n | | N0 | | b0 | |
945 | | | M21 M22 ... M2n | | N1 | = | b1 | |
946 | | | . . . . | | . | | . | |
947 | | | Mn1 Mn2 ... Mnn | | Nn-1 | | bn-1 | |
948 | | */ |
949 | | /***************************************************************************/ |
950 | | |
951 | | static int solvemat(struct MATRIX *m, double a[], double b[], double E[], |
952 | | double N[]) |
953 | 0 | { |
954 | 0 | for (int i = 1; i <= m->n; i++) |
955 | 0 | { |
956 | 0 | int j = i; |
957 | | |
958 | | /* find row with largest magnitude value for pivot value */ |
959 | |
|
960 | 0 | double pivot = |
961 | 0 | M(i, j); /* ACTUAL VALUE OF THE LARGEST PIVOT CANDIDATE */ |
962 | 0 | int imark = i; |
963 | 0 | for (int i2 = i + 1; i2 <= m->n; i2++) |
964 | 0 | { |
965 | 0 | if (fabs(M(i2, j)) > fabs(pivot)) |
966 | 0 | { |
967 | 0 | pivot = M(i2, j); |
968 | 0 | imark = i2; |
969 | 0 | } |
970 | 0 | } |
971 | | |
972 | | /* if the pivot is very small then the points are nearly co-linear */ |
973 | | /* co-linear points result in an undefined matrix, and nearly */ |
974 | | /* co-linear points results in a solution with rounding error */ |
975 | |
|
976 | 0 | if (pivot == 0.0) |
977 | 0 | return (MUNSOLVABLE); |
978 | | |
979 | | /* if row with highest pivot is not the current row, switch them */ |
980 | | |
981 | 0 | if (imark != i) |
982 | 0 | { |
983 | 0 | for (int j2 = 1; j2 <= m->n; j2++) |
984 | 0 | { |
985 | 0 | std::swap(M(imark, j2), M(i, j2)); |
986 | 0 | } |
987 | |
|
988 | 0 | std::swap(a[imark - 1], a[i - 1]); |
989 | 0 | std::swap(b[imark - 1], b[i - 1]); |
990 | 0 | } |
991 | | |
992 | | /* compute zeros above and below the pivot, and compute |
993 | | values for the rest of the row as well */ |
994 | |
|
995 | 0 | for (int i2 = 1; i2 <= m->n; i2++) |
996 | 0 | { |
997 | 0 | if (i2 != i) |
998 | 0 | { |
999 | 0 | const double factor = M(i2, j) / pivot; |
1000 | 0 | for (int j2 = j; j2 <= m->n; j2++) |
1001 | 0 | M(i2, j2) -= factor * M(i, j2); |
1002 | 0 | a[i2 - 1] -= factor * a[i - 1]; |
1003 | 0 | b[i2 - 1] -= factor * b[i - 1]; |
1004 | 0 | } |
1005 | 0 | } |
1006 | 0 | } |
1007 | | |
1008 | | /* SINCE ALL OTHER VALUES IN THE MATRIX ARE ZERO NOW, CALCULATE THE |
1009 | | COEFFICIENTS BY DIVIDING THE COLUMN VECTORS BY THE DIAGONAL VALUES. */ |
1010 | | |
1011 | 0 | for (int i = 1; i <= m->n; i++) |
1012 | 0 | { |
1013 | 0 | E[i - 1] = a[i - 1] / M(i, i); |
1014 | 0 | N[i - 1] = b[i - 1] / M(i, i); |
1015 | 0 | } |
1016 | |
|
1017 | 0 | return (MSUCCESS); |
1018 | 0 | } |
1019 | | |
1020 | | /***************************************************************************/ |
1021 | | /* |
1022 | | DETECTS THE WORST OUTLIER IN THE GCP LIST AND RETURNS THE INDEX OF THE |
1023 | | OUTLIER. |
1024 | | |
1025 | | THE WORST OUTLIER IS CALCULATED BASED ON THE CONTROL POINTS, COEFFICIENTS |
1026 | | AND THE ALLOWED TOLERANCE: |
1027 | | |
1028 | | sampleAdj = a0 + a1*sample + a2*line + a3*line*sample |
1029 | | lineAdj = b0 + b1*sample + b2*line + b3*line*sample |
1030 | | |
1031 | | WHERE sampleAdj AND lineAdj ARE CORRELATED GCPS |
1032 | | |
1033 | | [residualSample] = [A1][sampleCoefficients] - [b1] |
1034 | | [residualLine] = [A2][lineCoefficients] - [b2] |
1035 | | |
1036 | | sampleResidual^2 = sum( [residualSample]^2 ) |
1037 | | lineResidual^2 = sum( [lineSample]^2 ) |
1038 | | |
1039 | | residuals(i) = squareRoot( residualSample(i)^2 + residualLine(i)^2 ) |
1040 | | |
1041 | | THE GCP WITH THE GREATEST DISTANCE residual(i) GREATER THAN THE TOLERANCE WILL |
1042 | | CONSIDERED THE WORST OUTLIER. |
1043 | | |
1044 | | IF NO OUTLIER CAN BE FOUND, -1 WILL BE RETURNED. |
1045 | | */ |
1046 | | /***************************************************************************/ |
1047 | | static int worst_outlier(struct Control_Points *cp, double x_mean, |
1048 | | double y_mean, int nOrder, double E[], double N[], |
1049 | | double dfTolerance) |
1050 | 0 | { |
1051 | | // double dfSampleResidual = 0.0; |
1052 | | // double dfLineResidual = 0.0; |
1053 | 0 | double *padfResiduals = |
1054 | 0 | static_cast<double *>(CPLCalloc(sizeof(double), cp->count)); |
1055 | |
|
1056 | 0 | for (int nI = 0; nI < cp->count; nI++) |
1057 | 0 | { |
1058 | 0 | double dfSampleRes = 0.0; |
1059 | 0 | double dfLineRes = 0.0; |
1060 | 0 | CRS_georef(cp->e1[nI] - x_mean, cp->n1[nI] - y_mean, &dfSampleRes, |
1061 | 0 | &dfLineRes, E, N, nOrder); |
1062 | 0 | dfSampleRes -= cp->e2[nI]; |
1063 | 0 | dfLineRes -= cp->n2[nI]; |
1064 | | // dfSampleResidual += dfSampleRes*dfSampleRes; |
1065 | | // dfLineResidual += dfLineRes*dfLineRes; |
1066 | |
|
1067 | 0 | padfResiduals[nI] = |
1068 | 0 | sqrt(dfSampleRes * dfSampleRes + dfLineRes * dfLineRes); |
1069 | 0 | } |
1070 | |
|
1071 | 0 | int nIndex = -1; |
1072 | 0 | double dfDifference = -1.0; |
1073 | 0 | for (int nI = 0; nI < cp->count; nI++) |
1074 | 0 | { |
1075 | 0 | double dfCurrentDifference = padfResiduals[nI]; |
1076 | 0 | if (fabs(dfCurrentDifference) < 1.19209290E-07F /*FLT_EPSILON*/) |
1077 | 0 | { |
1078 | 0 | dfCurrentDifference = 0.0; |
1079 | 0 | } |
1080 | 0 | if (dfCurrentDifference > dfDifference && |
1081 | 0 | dfCurrentDifference >= dfTolerance) |
1082 | 0 | { |
1083 | 0 | dfDifference = dfCurrentDifference; |
1084 | 0 | nIndex = nI; |
1085 | 0 | } |
1086 | 0 | } |
1087 | 0 | CPLFree(padfResiduals); |
1088 | 0 | return nIndex; |
1089 | 0 | } |
1090 | | |
1091 | | /***************************************************************************/ |
1092 | | /* |
1093 | | REMOVES THE WORST OUTLIERS ITERATIVELY UNTIL THE MINIMUM NUMBER OF GCPS |
1094 | | ARE REACHED OR NO OUTLIERS CAN BE DETECTED. |
1095 | | |
1096 | | 1. WE CALCULATE THE COEFFICIENTS FOR ALL THE GCPS. |
1097 | | 2. THE GCP LIST WILL BE SCANNED TO DETERMINE THE WORST OUTLIER USING |
1098 | | THE CALCULATED COEFFICIENTS. |
1099 | | 3. THE WORST OUTLIER WILL BE REMOVED FROM THE GCP LIST. |
1100 | | 4. THE COEFFICIENTS WILL BE RECALCULATED WITHOUT THE WORST OUTLIER. |
1101 | | 5. STEP 1 TO 4 ARE EXECUTED UNTIL THE MINIMUM NUMBER OF GCPS WERE REACHED |
1102 | | OR IF NO GCP IS CONSIDERED AN OUTLIER WITH THE PASSED TOLERANCE. |
1103 | | */ |
1104 | | /***************************************************************************/ |
1105 | | static int remove_outliers(GCPTransformInfo *psInfo) |
1106 | 0 | { |
1107 | 0 | double *padfGeoX = nullptr; |
1108 | 0 | double *padfGeoY = nullptr; |
1109 | 0 | double *padfRasterX = nullptr; |
1110 | 0 | double *padfRasterY = nullptr; |
1111 | 0 | int *panStatus = nullptr; |
1112 | 0 | int nCRSresult = 0; |
1113 | 0 | int nGCPCount = 0; |
1114 | 0 | int nMinimumGcps = 0; |
1115 | 0 | int nReqOrder = 0; |
1116 | 0 | double dfTolerance = 0; |
1117 | 0 | struct Control_Points sPoints; |
1118 | |
|
1119 | 0 | double x1_sum = 0; |
1120 | 0 | double y1_sum = 0; |
1121 | 0 | double x2_sum = 0; |
1122 | 0 | double y2_sum = 0; |
1123 | 0 | memset(&sPoints, 0, sizeof(sPoints)); |
1124 | |
|
1125 | 0 | nGCPCount = static_cast<int>(psInfo->asGCPs.size()); |
1126 | 0 | nMinimumGcps = psInfo->nMinimumGcps; |
1127 | 0 | nReqOrder = psInfo->nOrder; |
1128 | 0 | dfTolerance = psInfo->dfTolerance; |
1129 | |
|
1130 | 0 | try |
1131 | 0 | { |
1132 | 0 | padfGeoX = new double[nGCPCount]; |
1133 | 0 | padfGeoY = new double[nGCPCount]; |
1134 | 0 | padfRasterX = new double[nGCPCount]; |
1135 | 0 | padfRasterY = new double[nGCPCount]; |
1136 | 0 | panStatus = new int[nGCPCount]; |
1137 | |
|
1138 | 0 | for (int nI = 0; nI < nGCPCount; nI++) |
1139 | 0 | { |
1140 | 0 | panStatus[nI] = 1; |
1141 | 0 | padfGeoX[nI] = psInfo->asGCPs[nI].X(); |
1142 | 0 | padfGeoY[nI] = psInfo->asGCPs[nI].Y(); |
1143 | 0 | padfRasterX[nI] = psInfo->asGCPs[nI].Pixel(); |
1144 | 0 | padfRasterY[nI] = psInfo->asGCPs[nI].Line(); |
1145 | 0 | x1_sum += psInfo->asGCPs[nI].Pixel(); |
1146 | 0 | y1_sum += psInfo->asGCPs[nI].Line(); |
1147 | 0 | x2_sum += psInfo->asGCPs[nI].X(); |
1148 | 0 | y2_sum += psInfo->asGCPs[nI].Y(); |
1149 | 0 | } |
1150 | 0 | psInfo->x1_mean = x1_sum / nGCPCount; |
1151 | 0 | psInfo->y1_mean = y1_sum / nGCPCount; |
1152 | 0 | psInfo->x2_mean = x2_sum / nGCPCount; |
1153 | 0 | psInfo->y2_mean = y2_sum / nGCPCount; |
1154 | |
|
1155 | 0 | sPoints.count = nGCPCount; |
1156 | 0 | sPoints.e1 = padfRasterX; |
1157 | 0 | sPoints.n1 = padfRasterY; |
1158 | 0 | sPoints.e2 = padfGeoX; |
1159 | 0 | sPoints.n2 = padfGeoY; |
1160 | 0 | sPoints.status = panStatus; |
1161 | |
|
1162 | 0 | nCRSresult = CRS_compute_georef_equations( |
1163 | 0 | psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY, |
1164 | 0 | psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder); |
1165 | |
|
1166 | 0 | while (sPoints.count > nMinimumGcps) |
1167 | 0 | { |
1168 | 0 | int nIndex = worst_outlier( |
1169 | 0 | &sPoints, psInfo->x1_mean, psInfo->y1_mean, psInfo->nOrder, |
1170 | 0 | psInfo->adfToGeoX, psInfo->adfToGeoY, dfTolerance); |
1171 | | |
1172 | | // If no outliers were detected, stop the GCP elimination |
1173 | 0 | if (nIndex == -1) |
1174 | 0 | { |
1175 | 0 | break; |
1176 | 0 | } |
1177 | | |
1178 | 0 | for (int nI = nIndex; nI < sPoints.count - 1; nI++) |
1179 | 0 | { |
1180 | 0 | sPoints.e1[nI] = sPoints.e1[nI + 1]; |
1181 | 0 | sPoints.n1[nI] = sPoints.n1[nI + 1]; |
1182 | 0 | sPoints.e2[nI] = sPoints.e2[nI + 1]; |
1183 | 0 | sPoints.n2[nI] = sPoints.n2[nI + 1]; |
1184 | 0 | psInfo->asGCPs[nI].SetId(psInfo->asGCPs[nI + 1].Id()); |
1185 | 0 | psInfo->asGCPs[nI].SetInfo(psInfo->asGCPs[nI + 1].Info()); |
1186 | 0 | } |
1187 | |
|
1188 | 0 | sPoints.count = sPoints.count - 1; |
1189 | |
|
1190 | 0 | nCRSresult = CRS_compute_georef_equations( |
1191 | 0 | psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY, |
1192 | 0 | psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder); |
1193 | 0 | } |
1194 | |
|
1195 | 0 | for (int nI = 0; nI < sPoints.count; nI++) |
1196 | 0 | { |
1197 | 0 | psInfo->asGCPs[nI].X() = sPoints.e2[nI]; |
1198 | 0 | psInfo->asGCPs[nI].Y() = sPoints.n2[nI]; |
1199 | 0 | psInfo->asGCPs[nI].Pixel() = sPoints.e1[nI]; |
1200 | 0 | psInfo->asGCPs[nI].Line() = sPoints.n1[nI]; |
1201 | 0 | } |
1202 | 0 | psInfo->asGCPs.resize(sPoints.count); |
1203 | 0 | } |
1204 | 0 | catch (const std::exception &e) |
1205 | 0 | { |
1206 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what()); |
1207 | 0 | nCRSresult = MINTERR; |
1208 | 0 | } |
1209 | 0 | delete[] padfGeoX; |
1210 | 0 | delete[] padfGeoY; |
1211 | 0 | delete[] padfRasterX; |
1212 | 0 | delete[] padfRasterY; |
1213 | 0 | delete[] panStatus; |
1214 | |
|
1215 | 0 | return nCRSresult; |
1216 | 0 | } |