Isis 3 Programmer Reference
EquatorialCylindricalShape.cpp
1
6/* SPDX-License-Identifier: CC0-1.0 */
7#include "EquatorialCylindricalShape.h"
8
9#include <QDebug>
10
11#include <algorithm>
12#include <cfloat>
13#include <string>
14#include <iomanip>
15#include <cmath>
16#include <vector>
17
18#include <SpiceUsr.h>
19#include <SpiceZfc.h>
20#include <SpiceZmc.h>
21
22#include "Cube.h"
23#include "IException.h"
24// #include "Geometry3D.h"
25#include "Latitude.h"
26// #include "LinearAlgebra.h"
27#include "Longitude.h"
28#include "NaifStatus.h"
29#include "SpecialPixel.h"
30#include "SurfacePoint.h"
31#include "Table.h"
32
33using namespace std;
34
35#define MAX(x,y) (((x) > (y)) ? (x) : (y))
36
37namespace Isis {
44 DemShape(target, pvl) {
45 setName("EquatorialCylindricalShape");
46
47 m_minRadius = NULL;
48 m_maxRadius = NULL;
49
50 // Read in the min/max radius of the DEM file and the Scale of the DEM
51 // file in pixels/degree
52 if (!demCube()->hasTable("ShapeModelStatistics")) {
53 QString msg = "The input cube references a ShapeModel that has "
54 "not been updated for the new ray tracing algorithm. All DEM "
55 "files must now be padded at the poles and contain a "
56 "ShapeModelStatistics table defining their minimum and maximum "
57 "radii values. The demprep program should be used to prepare the "
58 "DEM before you can run this program. There is more information "
59 "available in the documentation of the demprep program.";
60 throw IException(IException::User, msg, _FILEINFO_);
61 }
62
63 // Table table("ShapeModelStatistics", demCubeFile(), *demCube()->label()));
64 Table table("ShapeModelStatistics", demCube()->fileName(), *demCube()->label());
65
66 // Find minimum and maximum radius
67 m_minRadius = new Distance(table[0]["MinimumRadius"], Distance::Kilometers);
68 m_maxRadius = new Distance(table[0]["MaximumRadius"], Distance::Kilometers);
69 }
70
71
83
84
95 bool EquatorialCylindricalShape::intersectSurface(vector<double> observerBodyFixedPos,
96 vector<double> observerLookVectorToTarget) {
97
98 // try to intersect the surface using the DemShape method
99 // if this is successful, return
100 //
101 // otherwise, (i.e. DemShape::intersectSurface() fails) we will attempt to
102 // intersect using the following iterative method...
103 if (!DemShape::intersectSurface(observerBodyFixedPos, observerLookVectorToTarget)) {
104
105 SpiceDouble a = targetRadii()[0].kilometers();
106
107 double plen = 0.0;
108 SpiceDouble plat, plon, pradius;
109 SpiceDouble pB[3];
110 double maxRadiusMetersSquared = m_maxRadius->kilometers() * m_maxRadius->kilometers();
111
112 // Separate iteration algorithms are used for different projections -
113 // use this iteration for equatorial cylindrical type projections that
114 // failed to find an intersection with the DemShape method.
115 int maxit = 100;
116 int it = 0;
117 bool done = false;
118
119 // Normalize the look vector
120 SpiceDouble ulookB[3];
121 ulookB[0] = observerLookVectorToTarget[0];
122 ulookB[1] = observerLookVectorToTarget[1];
123 ulookB[2] = observerLookVectorToTarget[2];
124 vhat_c(ulookB, ulookB);
125
126 // Calculate the limb viewing angle to see if the line of sight is
127 // pointing away from the planet
128 SpiceDouble observer[3];
129 observer[0] = observerBodyFixedPos[0];
130 observer[1] = observerBodyFixedPos[1];
131 observer[2] = observerBodyFixedPos[2];
132
133 SpiceDouble negobserver[3];
134 vminus_c(observer, negobserver);
135
136 double psi0 = vsep_c(negobserver, ulookB); // find separation angle between -obsPos and look
137
138 // If psi0 is greater than 90 degrees, then reject data as looking
139 // away from the planet and no proper tangent point exists in the
140 // direction that the spacecraft is looking
141 if (psi0 > PI / 2.0) {
142 setHasIntersection(false);
143 return hasIntersection();
144 }
145
146
147 #if 0
148 // JWB - The commented code here is an alternate way of doing this...
149 // which method has less expensive computing???
150 // I believe the alternate one may be (no need for cosine after naif routines)
151 //
152 //
153 // find the vector to the point found by orthogonally projecting the observer position vector
154 // onto the line containing the look direction vector
155 SpiceDouble perp[3];
156 vperp_c(&observer, &ulookB, perp);
157 SpiceDouble v[3];
158 vsub_c(&observer, &perp, v);
159 double dot = vdot_c(ulookB, v);
160 qDebug() << "dot = " << dot;
161 if (dot > 0.0) {
162 setHasIntersection(false);
163 return hasIntersection();
164 }
165
166 // TODO: When added to ISIS library, see LinearAlgebra/Geometry3D equivalent methods:
167 // vperp=perpendicular{find max and normalize vectors, 2 dot products}
168 // vsep=Geometry3D::separationAngle{find max and normalize vectors, dot product}
169
170 // replace calculation of psi0 and intersection check using psi0
171 // replace calculation of tvec, observerdist, cospsi0
172 // tvec below is equivalent to perp found above
173 // observerdist*cospsi0 is equivalent to vnorm_c(v) [pretty sure about this equivalence... need to verify]
174
175 #endif
176
177 // Calculate the vector to the surface point
178 SpiceDouble tvec[3];
179 double observerdist = vnorm_c(observer);
180 double cospsi0 = cos(psi0);
181 tvec[0] = observer[0] + observerdist * cospsi0 * ulookB[0];
182 tvec[1] = observer[1] + observerdist * cospsi0 * ulookB[1];
183 tvec[2] = observer[2] + observerdist * cospsi0 * ulookB[2];
184
185 double tlen = vnorm_c(tvec);
186
187 // Calculate distance along look vector to first and last test point
188 double radiusDiff = maxRadiusMetersSquared - tlen * tlen;
189
190 // Make sure radiusDiff makes sense
191 if (radiusDiff >= 0.0) {
192 radiusDiff = sqrt(radiusDiff);
193 }
194 else {
195 setHasIntersection(false);
196 return hasIntersection();
197 }
198
199 double d0 = observerdist * cospsi0 - radiusDiff;
200 double dm = observerdist * cospsi0 + radiusDiff;
201
202 // Set the properties at the first test observation point
203 double d = d0;
204 SpiceDouble g1[3];
205 g1[0] = observer[0] + d0 * ulookB[0];
206 g1[1] = observer[1] + d0 * ulookB[1];
207 g1[2] = observer[2] + d0 * ulookB[2];
208
209 double g1len = vnorm_c(g1);
210 SpiceDouble g1lat, g1lon, g1radius;
211 reclat_c(g1, &g1radius, &g1lon, &g1lat);
212 g1lat *= RAD2DEG;
213 g1lon *= RAD2DEG;
214
215 if (g1lon < 0.0) g1lon += 360.0;
216
217 SpiceDouble negg1[3];
218 vminus_c(g1, negg1);
219
220 double psi1 = vsep_c(negg1, ulookB);
221
222 // Set dalpha to be half the grid spacing for nyquist sampling
223 //double dalpha = (PI/180.0)/(2.0*p_demScale);
224 double cmin = cos((90.0 - 1.0 / (2.0 * demScale())) * DEG2RAD);
225 double dalpha = MAX(cos(g1lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
226
227 // Previous Sensor version used local version of this method with lat and lon doubles. Steven said
228 // it didn't make a significant difference in speed.
229 double r1 = (localRadius(Latitude(g1lat, Angle::Degrees),
230 Longitude(g1lon, Angle::Degrees))).kilometers();
231
232 if (Isis::IsSpecial(r1)) {
233 setHasIntersection(false);
234 return hasIntersection();
235 }
236
237 // Set the tolerance to a fraction of the equatorial radius, a
238 double tolerance = 3E-8 * a;
239
240 // Main iteration loop
241 // Loop from g1 to gm stepping by angles of dalpha until intersection is found
242 while (!done) {
243
244 if (d > dm) {
245 setHasIntersection(false);
246 return hasIntersection();
247 }
248
249 it = 0;
250
251 // Calculate the angle between the look vector and the planet radius at the current
252 // test point
253 double psi2 = psi1 + dalpha;
254
255 // Calculate the step size
256 double dd = g1len * sin(dalpha) / sin(PI - psi2);
257
258 // JAA: If we are moving along the vector at a smaller increment than the pixel
259 // tolerance we will be in an infinite loop. The infinite loop is elimnated by
260 // this test. Now the algorithm produces a jagged limb in phocube. This may
261 // be a function of the very low resolution of the Vesta DEM and could
262 // improve in the future
263 if (dd < tolerance) {
264 setHasIntersection(false);
265 return hasIntersection();
266 }
267
268 // Calculate the vector to the current test point from the planet center
269 d = d + dd;
270
271 SpiceDouble g2[3];
272 g2[0] = observer[0] + d * ulookB[0];
273 g2[1] = observer[1] + d * ulookB[1];
274 g2[2] = observer[2] + d * ulookB[2];
275
276 double g2len = vnorm_c(g2);
277
278 // Determine lat,lon,radius at this point
279 SpiceDouble g2lat, g2lon, g2radius;
280 reclat_c(g2, &g2radius, &g2lon, &g2lat);
281
282 g2lat *= RAD2DEG;
283 g2lon *= RAD2DEG;
284
285 if (g2lon < 0.0) g2lon += 360.0;
286
287 // Previous Sensor version used local version of this method with lat and lon doubles to save
288 // According to Steven the savings was negligible.
289 double r2 = (localRadius(Latitude(g2lat, Angle::Degrees),
290 Longitude(g2lon, Angle::Degrees))).kilometers();
291
292
293 if (Isis::IsSpecial(r2)) {
294 setHasIntersection(false);
295 return hasIntersection();
296 }
297
298 // Test for intersection
299 if (r2 > g2len) {
300 // An intersection has occurred. Interpolate between g1 and g2 to get the
301 // lat,lon of the intersect point.
302
303 // If g1 and g2 straddle a hill, then we may need to iterate a few times
304 // until we are on the linear segment.
305 while (it < maxit && !done) {
306 // Calculate the fractional distance "v" to move along the look vector
307 // to the intersection point. Check to see if there was a convergence
308 // of the solution and the tolerance was too small to detect it.
309 double palt;
310 if ((g2len * r1 / r2 - g1len) == 0.0) {
311 setHasIntersection(true);
312 plen = pradius;
313 palt = 0.0;
314 done = true;
315 }
316 else {
317 double v = (r1 - g1len) / (g2len * r1 / r2 - g1len);
318 pB[0] = g1[0] + v * dd * ulookB[0];
319 pB[1] = g1[1] + v * dd * ulookB[1];
320 pB[2] = g1[2] + v * dd * ulookB[2];
321 plen = vnorm_c(pB);
322 reclat_c(pB, &pradius, &plon, &plat);
323 plat *= RAD2DEG;
324 plon *= RAD2DEG;
325 if (plon < 0.0) plon += 360.0;
326
327 if (plon > 360.0) plon -= 360.0;
328
329 // Previous Sensor version used local version of this method with lat and lon doubles. ..Why Jeff???
330 pradius = (localRadius(Latitude(plat, Angle::Degrees),
331 Longitude(plon, Angle::Degrees))).kilometers();
332
333 if (Isis::IsSpecial(pradius)) {
334 setHasIntersection(false);
335 return hasIntersection();
336 }
337 palt = plen - pradius;
338
339 // The altitude relative to surface is +ve at the observation point,
340 // so reset g1=p and r1=pradius
341 if (palt > tolerance) {
342 it = it + 1;
343 g1[0] = pB[0];
344 g1[1] = pB[1];
345 g1[2] = pB[2];
346 g1len = plen;
347 r1 = pradius;
348 dd = dd * (1.0 - v);
349
350 // The altitude relative to surface -ve at the observation point,
351 // so reset g2=p and r2=pradius
352 }
353 else if (palt < -tolerance) {
354 it = it + 1;
355 g2[0] = pB[0];
356 g2[1] = pB[1];
357 g2[2] = pB[2];
358 g2len = plen;
359 r2 = pradius;
360 dd = dd * v;
361
362 // We are within the tolerance, so the solution has converged
363 }
364 else {
365 setHasIntersection(true);
366 plen = pradius;
367 palt = 0.0;
368 done = true;
369 }
370 }
371 if (!done && it >= maxit) {
372 setHasIntersection(false);
373 return hasIntersection();
374 }
375 }
376 }
377
378 g1[0] = g2[0];
379 g1[1] = g2[1];
380 g1[2] = g2[2];
381 g1len = g2len;
382 r1 = r2;
383 psi1 = psi2;
384
385 // TODO: Examine how dalpha is computed at the limb for Vesta. It
386 // appears that eventually it gets really small and causes the loop
387 // to be neverending. Of course this could be happening for other
388 // limb images and we just have never tested the code. For example,
389 // Messenger with a DEM could cause the problem. As a result JAA
390 // put in a test (above) for dd being smaller than the pixel
391 // convergence tolerance. If so the loop exits without an
392 // intersection
393 dalpha = MAX(cos(g2lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
394 } // end while
395
396 SpiceDouble intersectionPoint[3];
397 SpiceBoolean found;
398
400 surfpt_c(&observerBodyFixedPos[0], &observerLookVectorToTarget[0], plen, plen, plen,
401 intersectionPoint, &found);
403
404 surfaceIntersection()->FromNaifArray(intersectionPoint);
405
406 if (!found) {
407 setHasIntersection(false);
408 return hasIntersection();
409 }
410 else {
411 return hasIntersection();
412 }
413
414 }
415
416 setHasIntersection(true);
417 return hasIntersection();
418 }
419 // Do nothing since the DEM intersection was already successful
420}
@ Degrees
Degrees are generally considered more human readable, 0-360 is one circle, however most math does not...
Definition Angle.h:56
Define shapes and provide utilities for targets stored as ISIS maps.
Definition DemShape.h:52
Distance localRadius(const Latitude &lat, const Longitude &lon)
Gets the radius from the DEM, if we have one.
Definition DemShape.cpp:264
double demScale()
Return the scale of the DEM shape, in pixels per degree.
Definition DemShape.cpp:295
Cube * demCube()
Returns the cube defining the shape model.
Definition DemShape.cpp:342
bool intersectSurface(std::vector< double > observerPos, std::vector< double > lookDirection)
Find the intersection point with the DEM.
Definition DemShape.cpp:143
Distance measurement, usually in meters.
Definition Distance.h:34
double kilometers() const
Get the distance in kilometers.
Definition Distance.cpp:106
@ Kilometers
The distance is being specified in kilometers.
Definition Distance.h:45
Distance * m_minRadius
Minimum radius value in DEM file.
~EquatorialCylindricalShape()
Destructor for ISIS Equatorial Cylindrical shape model.
Distance * m_maxRadius
Maximum radius value in DEM file.
EquatorialCylindricalShape(Target *target, Pvl &pvl)
Initialize the ISIS Equatorial Cylindrical shape model.
bool intersectSurface(std::vector< double > observerPos, std::vector< double > lookDirection)
Finds the surface intersection point.
Isis exception class.
Definition IException.h:91
@ User
A type of error that could only have occurred due to a mistake on the user's part (e....
Definition IException.h:126
This class is designed to encapsulate the concept of a Latitude.
Definition Latitude.h:51
This class is designed to encapsulate the concept of a Longitude.
Definition Longitude.h:40
static void CheckErrors(bool resetNaif=true)
This method looks for any naif errors that might have occurred.
Container for cube-like labels.
Definition Pvl.h:119
bool hasIntersection()
Returns intersection status.
void setHasIntersection(bool b)
Sets the flag to indicate whether this ShapeModel has an intersection.
virtual SurfacePoint * surfaceIntersection() const
Returns the surface intersection for this ShapeModel.
std::vector< Distance > targetRadii() const
Returns the radii of the body in km.
void setName(QString name)
Sets the shape name.
Class for storing Table blobs information.
Definition Table.h:61
This class is used to create and store valid Isis targets.
Definition Target.h:63
const double E
Sets some basic constants for use in ISIS programming.
Definition Constants.h:39
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16
const double DEG2RAD
Multiplier for converting from degrees to radians.
Definition Constants.h:43
bool IsSpecial(const double d)
Returns if the input pixel is special.
const double RAD2DEG
Multiplier for converting from radians to degrees.
Definition Constants.h:44
const double PI
The mathematical constant PI.
Definition Constants.h:40
Namespace for the standard library.