File failed to load: https://isis.astrogeology.usgs.gov/6.0.0/Object/assets/jax/output/NativeMML/config.js
Isis 3 Programmer Reference
UniversalGroundMap.cpp
1 
6 /* SPDX-License-Identifier: CC0-1.0 */
7 #include "UniversalGroundMap.h"
8 
9 #include <QPointF>
10 
11 #include "Camera.h"
12 #include "CameraFactory.h"
13 #include "ImagePolygon.h"
14 #include "Latitude.h"
15 #include "Longitude.h"
16 #include "Latitude.h"
17 #include "Longitude.h"
18 #include "PolygonTools.h"
19 #include "Projection.h"
20 #include "RingPlaneProjection.h"
21 #include "TProjection.h"
22 #include "ProjectionFactory.h"
23 #include "SurfacePoint.h"
24 #include "Target.h"
25 
26 namespace Isis {
34  p_camera = NULL;
35  p_projection = NULL;
36 
37  Pvl &pvl = *cube.label();
38  try {
39  if(priority == CameraFirst)
41  else
43  }
44  catch (IException &firstError) {
45  p_camera = NULL;
46  p_projection = NULL;
47 
48  try {
49  if(priority == CameraFirst)
51  else
53  }
54  catch (IException &secondError) {
55  p_projection = NULL;
56  QString msg = "Could not create camera or projection for [" +
57  cube.fileName() + "]";
58  IException realError(IException::Unknown, msg, _FILEINFO_);
59  realError.append(firstError);
60  realError.append(secondError);
61  throw realError;
62  }
63  }
64  }
65 
72  void UniversalGroundMap::SetBand(const int band) {
73  if (p_camera != NULL)
74  p_camera->SetBand(band);
75  }
76 
77 
78 
81  if (p_camera != NULL) {
82  delete p_camera;
83  p_camera = NULL;
84  }
85 
86  if (p_projection != NULL) {
87  delete p_projection;
88  p_projection = NULL;
89  }
90  }
91 
102  bool UniversalGroundMap::SetUniversalGround(double lat, double lon) {
103  if (p_camera != NULL) {
104  if (p_camera->SetUniversalGround(lat, lon)) { // This should work for rings (radius,azimuth)
105  return p_camera->InCube();
106  }
107  else {
108  return false;
109  }
110  }
111  else {
112  return p_projection->SetUniversalGround(lat, lon); // This should work for rings (radius,azimuth)
113  }
114  }
115 
116 
128  if(p_camera != NULL) {
129  if(p_camera->SetGround(lat, lon)) { // This should work for rings (radius,azimuth)
130  return p_camera->InCube();
131  }
132  else {
133  return false;
134  }
135  }
136  else {
137  double universalLat = lat.degrees();
138  double universalLon = lon.degrees();
139  return p_projection->SetUniversalGround(universalLat, universalLon); // This should work for rings (radius,azimuth)
140  }
141  }
142 
143 
155  if(p_camera != NULL) {
156  if(p_camera->SetGround(lat, lon)) { // This should work for rings (radius,azimuth)
157  return p_camera->InCube();
158  }
159  else {
160  return false;
161  }
162  }
163  else {
164  double universalLat = lat.degrees();
165  double universalLon = lon.degrees();
166  return p_projection->SetUnboundUniversalGround(universalLat, universalLon);
167  }
168  }
169 
170 
181  if (p_camera != NULL) {
182  if (p_camera->SetGround(sp)) {
183  return p_camera->InCube();
184  }
185  else {
186  return false;
187  }
188  }
189  else {
191  sp.GetLongitude().degrees()); // This should work for rings (radius,azimuth)
192  }
193  }
194 
200  double UniversalGroundMap::Sample() const {
201  if (p_camera != NULL) {
202  return p_camera->Sample();
203  }
204  else {
205  return p_projection->WorldX();
206  }
207  }
208 
214  double UniversalGroundMap::Line() const {
215  if (p_camera != NULL) {
216  return p_camera->Line();
217  }
218  else {
219  return p_projection->WorldY();
220  }
221  }
222 
233  bool UniversalGroundMap::SetImage(double sample, double line) {
234  if (p_camera != NULL) {
235  return p_camera->SetImage(sample, line);
236  }
237  else {
238  return p_projection->SetWorld(sample, line);
239  }
240  }
241 
248  if (p_camera != NULL) {
249  return p_camera->UniversalLatitude();
250  }
251  else {
252  // Is this a triaxial projection or ring projection. If ring return Radius as latitude
254  if (projType == Projection::Triaxial) {
255  TProjection *tproj = (TProjection *) p_projection;
256  return tproj->UniversalLatitude();
257  }
258  else {
260  return rproj->RingRadius();
261  }
262  }
263  }
264 
271  if (p_camera != NULL) {
272  return p_camera->UniversalLongitude();
273  }
274  else {
275  // Is this a triaxial projection or ring projection. If ring return ring longitude as
276  // longitude
278  if (projType == Projection::Triaxial) {
279  TProjection *tproj = (TProjection *) p_projection;
280  return tproj->UniversalLongitude();
281  }
282  else {
284  return rproj->RingLongitude();
285  }
286  }
287  }
288 
295  if (p_camera != NULL) {
296  return p_camera->PixelResolution();
297  }
298  else {
299  return p_projection->Resolution();
300  }
301  }
302 
303 
321  Latitude &maxLat, Longitude &minLon, Longitude &maxLon,
322  bool allowEstimation) {
323  // Do we need a RingRange method?
324  // For now just return false
325  if (HasCamera())
326  if (p_camera->target()->shape()->name() == "Plane") return false;
327  if (HasProjection())
328  if (p_projection->projectionType() == Projection::RingPlane) return false;
329 
330  minLat = Latitude();
331  maxLat = Latitude();
332  minLon = Longitude();
333  maxLon = Longitude();
334 
335  // If we have a footprint, use it
336  try {
337  if (cube) {
338  ImagePolygon poly = cube->readFootprint();
339  geos::geom::MultiPolygon *footprint = PolygonTools::MakeMultiPolygon(
340  poly.Polys()->clone());
341 
342  geos::geom::Geometry *envelope = footprint->getEnvelope();
343  geos::geom::CoordinateSequence *coords = envelope->getCoordinates();
344 
345  for (unsigned int i = 0; i < coords->getSize(); i++) {
346  const geos::geom::Coordinate &coord = coords->getAt(i);
347 
348  Latitude coordLat(coord.y, Angle::Degrees);
349  Longitude coordLon(coord.x, Angle::Degrees);
350 
351  if (!minLat.isValid() || minLat > coordLat)
352  minLat = coordLat;
353  if (!maxLat.isValid() || maxLat < coordLat)
354  maxLat = coordLat;
355 
356  if (!minLon.isValid() || minLon > coordLon)
357  minLon = coordLon;
358  if (!maxLon.isValid() || maxLon < coordLon)
359  maxLon = coordLon;
360  }
361 
362  delete coords;
363  coords = NULL;
364 
365  delete envelope;
366  envelope = NULL;
367 
368  delete footprint;
369  footprint = NULL;
370  }
371  }
372  catch (IException &) {
373  }
374 
375  if (!minLat.isValid() || !maxLat.isValid() ||
376  !minLon.isValid() || !maxLon.isValid()) {
377  if (HasCamera()) {
378  // Footprint failed, ask the camera
379  PvlGroup mappingGrp("Mapping");
380  mappingGrp += PvlKeyword("LatitudeType", "Planetocentric");
381  mappingGrp += PvlKeyword("LongitudeDomain", "360");
382  mappingGrp += PvlKeyword("LongitudeDirection", "PositiveEast");
383 
384  Pvl mappingPvl;
385  mappingPvl += mappingGrp;
386  double minLatDouble;
387  double maxLatDouble;
388  double minLonDouble;
389  double maxLonDouble;
391  minLatDouble, maxLatDouble,
392  minLonDouble, maxLonDouble, mappingPvl);
393  minLat = Latitude(minLatDouble, Angle::Degrees);
394  maxLat = Latitude(maxLatDouble, Angle::Degrees);
395  minLon = Longitude(minLonDouble, Angle::Degrees);
396  maxLon = Longitude(maxLonDouble, Angle::Degrees);
397  }
398  else if (HasProjection()) {
399  // Footprint failed, look in the mapping group
400  PvlGroup mappingGrp = p_projection->Mapping();
401  if (mappingGrp.hasKeyword("MinimumLatitude") &&
402  mappingGrp.hasKeyword("MaximumLatitude") &&
403  mappingGrp.hasKeyword("MinimumLongitude") &&
404  mappingGrp.hasKeyword("MaximumLongitude")) {
405 
406  minLat = Latitude(mappingGrp["MinimumLatitude"],
407  mappingGrp, Angle::Degrees);
408  maxLat = Latitude(mappingGrp["MaximumLatitude"],
409  mappingGrp, Angle::Degrees);
410  minLon = Longitude(mappingGrp["MinimumLongitude"],
411  mappingGrp, Angle::Degrees);
412  maxLon = Longitude(mappingGrp["MaximumLongitude"],
413  mappingGrp, Angle::Degrees);
414 
415  }
416  else if (allowEstimation && cube) {
417  // Footprint and mapping failed... no lat/lon range of any kind is
418  // available. Let's test points in the image to try to make our own
419  // extent.
420  QList<QPointF> imagePoints;
421 
422  // Reset to TProjection
423  TProjection *tproj = (TProjection *) p_projection;
424 
425  /*
426  * This is where we're testing:
427  *
428  * |---------------|
429  * |***************|
430  * |** * **|
431  * |* * * * *|
432  * |* * * * *|
433  * |***************|
434  * |* * * * *|
435  * |* * * * *|
436  * |** * **|
437  * |***************|
438  * |---------------|
439  *
440  * We'll test at the edges, a plus (+) and an (X) to help DEMs work.
441  */
442 
443  int sampleCount = cube->sampleCount();
444  int lineCount = cube->lineCount();
445 
446  int stepsPerLength = 20; //number of steps per length
447  double aspectRatio = (double)lineCount / (double)sampleCount;
448  double xStepSize = sampleCount / stepsPerLength;
449  double yStepSize = xStepSize * aspectRatio;
450 
451  if (lineCount > sampleCount) {
452  aspectRatio = (double)sampleCount / (double)lineCount;
453  yStepSize = lineCount / stepsPerLength;
454  xStepSize = yStepSize * aspectRatio;
455  }
456 
457  double yWalked = 0.5;
458 
459  //3 vertical lines
460  for (int i = 0; i < 3; i++) {
461  double xValue = 0.5 + ( i * (sampleCount / 2) );
462 
463  while (yWalked <= lineCount) {
464  imagePoints.append( QPointF(xValue, yWalked) );
465  yWalked += yStepSize;
466  }
467 
468  yWalked = 0.5;
469  }
470 
471  double xWalked = 0.5;
472 
473  //3 horizontal lines
474  for (int i = 0; i < 3; i++) {
475  double yValue = 0.5 + ( i * (lineCount / 2) );
476 
477  while (xWalked <= sampleCount) {
478  imagePoints.append( QPointF(xWalked, yValue) );
479  xWalked += xStepSize;
480  }
481 
482  xWalked = 0.5;
483  }
484 
485  double xDiagonalWalked = 0.5;
486  double yDiagonalWalked = 0.5;
487  xStepSize = sampleCount / stepsPerLength;
488  yStepSize = lineCount / stepsPerLength;
489 
490  //Top-Down Diagonal
491  while ( (xDiagonalWalked <= sampleCount) && (yDiagonalWalked <= lineCount) ) {
492  imagePoints.append( QPointF(xDiagonalWalked, yDiagonalWalked) );
493  xDiagonalWalked += xStepSize;
494  yDiagonalWalked += yStepSize;
495  }
496 
497  xDiagonalWalked = 0.5;
498 
499  //Bottom-Up Diagonal
500  while ( (xDiagonalWalked <= sampleCount) && (yDiagonalWalked >= 0) ) {
501  imagePoints.append( QPointF(xDiagonalWalked, yDiagonalWalked) );
502  xDiagonalWalked += xStepSize;
503  yDiagonalWalked -= yStepSize;
504  }
505 
506  foreach (QPointF imagePoint, imagePoints) {
507  if (tproj->SetWorld(imagePoint.x(), imagePoint.y())) {
508  Latitude latResult(tproj->UniversalLatitude(),
510  Longitude lonResult(tproj->UniversalLongitude(),
512  if (minLat.isValid())
513  minLat = qMin(minLat, latResult);
514  else
515  minLat = latResult;
516 
517  if (maxLat.isValid())
518  maxLat = qMax(maxLat, latResult);
519  else
520  maxLat = latResult;
521 
522  if (minLon.isValid())
523  minLon = qMin(minLon, lonResult);
524  else
525  minLon = lonResult;
526 
527  if (maxLon.isValid())
528  maxLon = qMax(maxLon, lonResult);
529  else
530  maxLon = lonResult;
531  }
532  }
533  }
534  }
535  }
536 
537  return (minLat.isValid() && maxLat.isValid() &&
538  minLon.isValid() && maxLon.isValid() &&
539  minLat < maxLat && minLon < maxLon);
540  }
541 }
Isis::Camera::SetBand
virtual void SetBand(const int band)
Virtual method that sets the band number.
Definition: Camera.cpp:2680
Isis::Angle::Degrees
@ Degrees
Degrees are generally considered more human readable, 0-360 is one circle, however most math does not...
Definition: Angle.h:56
Isis::CameraFactory::Create
static Camera * Create(Cube &cube)
Creates a Camera object using Pvl Specifications.
Definition: CameraFactory.cpp:45
Isis::Target::shape
ShapeModel * shape() const
Return the shape.
Definition: Target.cpp:655
Isis::Cube::fileName
virtual QString fileName() const
Returns the opened cube's filename.
Definition: Cube.cpp:1563
Isis::UniversalGroundMap::Sample
double Sample() const
Returns the current line value of the camera model or projection.
Definition: UniversalGroundMap.cpp:200
Isis::UniversalGroundMap::UniversalLatitude
double UniversalLatitude() const
Returns the universal latitude of the camera model or projection.
Definition: UniversalGroundMap.cpp:247
Isis::PvlKeyword
A single keyword-value pair.
Definition: PvlKeyword.h:82
Isis::UniversalGroundMap::~UniversalGroundMap
~UniversalGroundMap()
Destroys the UniversalGroundMap object.
Definition: UniversalGroundMap.cpp:80
QList< QPointF >
Isis::Projection::Resolution
double Resolution() const
This method returns the resolution for mapping world coordinates into projection coordinates.
Definition: Projection.cpp:675
Isis::Latitude
This class is designed to encapsulate the concept of a Latitude.
Definition: Latitude.h:51
Isis::TProjection::UniversalLatitude
virtual double UniversalLatitude()
This returns a universal latitude (planetocentric).
Definition: TProjection.cpp:908
Isis::Camera::SetImage
virtual bool SetImage(const double sample, const double line)
Sets the sample/line values of the image to get the lat/lon values.
Definition: Camera.cpp:154
Isis::UniversalGroundMap::SetGround
bool SetGround(Latitude lat, Longitude lon)
Returns whether the lat/lon position was set successfully in the camera model or projection.
Definition: UniversalGroundMap.cpp:127
Isis::Camera::Sample
virtual double Sample() const
Returns the current sample number.
Definition: Camera.cpp:2690
Isis::IException::Unknown
@ Unknown
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:118
Isis::Cube::readFootprint
ImagePolygon readFootprint() const
Read the footprint polygon for the Cube.
Definition: Cube.cpp:866
Isis::RingPlaneProjection::RingLongitude
double RingLongitude() const
This returns a ring longitude with correct ring longitude direction and domain as specified in the la...
Definition: RingPlaneProjection.cpp:530
Isis::ShapeModel::name
QString name() const
Gets the shape name.
Definition: ShapeModel.cpp:543
Isis::Projection::projectionType
ProjectionType projectionType() const
Returns an enum value for the projection type.
Definition: Projection.cpp:198
Isis::PvlContainer::hasKeyword
bool hasKeyword(const QString &name) const
Check to see if a keyword exists.
Definition: PvlContainer.cpp:159
Isis::Pvl
Container for cube-like labels.
Definition: Pvl.h:119
Isis::SurfacePoint::GetLatitude
Latitude GetLatitude() const
Return the body-fixed latitude for the surface point.
Definition: SurfacePoint.cpp:1665
Isis::ImagePolygon
Create cube polygons, read/write polygons to blobs.
Definition: ImagePolygon.h:153
Isis::Projection::SetUniversalGround
virtual bool SetUniversalGround(const double coord1, const double coord2)
This method is used to set the lat/lon or radius/azimuth (i.e.
Definition: Projection.cpp:417
Isis::Camera::SetGround
virtual bool SetGround(Latitude latitude, Longitude longitude)
Sets the lat/lon values to get the sample/line values.
Definition: Camera.cpp:401
Isis::UniversalGroundMap::p_projection
Isis::Projection * p_projection
The projection (if the image is projected)
Definition: UniversalGroundMap.h:143
Isis::IException::append
void append(const IException &exceptionSource)
Appends the given exception (and its list of previous exceptions) to this exception's causational exc...
Definition: IException.cpp:409
Isis::Longitude
This class is designed to encapsulate the concept of a Longitude.
Definition: Longitude.h:40
Isis::Spice::target
virtual Target * target() const
Returns a pointer to the target object.
Definition: Spice.cpp:1368
Isis::Projection::WorldY
virtual double WorldY() const
This returns the world Y coordinate provided SetGround, SetCoordinate, SetUniversalGround,...
Definition: Projection.cpp:544
Isis::Camera::InCube
bool InCube()
This returns true if the current Sample() or Line() value is outside of the cube (meaning the point m...
Definition: Camera.cpp:2619
Isis::RingPlaneProjection
Base class for Map Projections of plane shapes.
Definition: RingPlaneProjection.h:147
Isis::PvlGroup
Contains multiple PvlContainers.
Definition: PvlGroup.h:41
Isis::Sensor::UniversalLongitude
virtual double UniversalLongitude() const
Returns the positive east, 0-360 domain longitude, in degrees, at the surface intersection point in t...
Definition: Sensor.cpp:233
Isis::Cube::lineCount
int lineCount() const
Definition: Cube.cpp:1734
Isis::UniversalGroundMap::HasCamera
bool HasCamera()
Returns whether the ground map has a camera or not.
Definition: UniversalGroundMap.h:126
Isis::RingPlaneProjection::RingRadius
double RingRadius() const
This returns a radius.
Definition: RingPlaneProjection.cpp:506
Isis::TProjection
Base class for Map TProjections.
Definition: TProjection.h:166
Isis::Camera::SetUniversalGround
virtual bool SetUniversalGround(const double latitude, const double longitude)
Sets the lat/lon values to get the sample/line values.
Definition: Camera.cpp:380
Isis::Projection::RingPlane
@ RingPlane
These projections are used to map ring planes.
Definition: Projection.h:168
Isis::Cube::sampleCount
int sampleCount() const
Definition: Cube.cpp:1807
Isis::UniversalGroundMap::CameraFirst
@ CameraFirst
This is the default because cameras are projection-aware.
Definition: UniversalGroundMap.h:80
Isis::Projection::WorldX
virtual double WorldX() const
This returns the world X coordinate provided SetGround, SetCoordinate, SetUniversalGround,...
Definition: Projection.cpp:524
Isis::UniversalGroundMap::HasProjection
bool HasProjection()
Returns whether the ground map has a projection or not.
Definition: UniversalGroundMap.h:115
Isis::Cube
IO Handler for Isis Cubes.
Definition: Cube.h:167
Isis::IException
Isis exception class.
Definition: IException.h:91
Isis::Projection::SetWorld
virtual bool SetWorld(const double x, const double y)
This method is used to set a world coordinate.
Definition: Projection.cpp:497
Isis::UniversalGroundMap::CameraPriority
CameraPriority
This enum is used to define whether to use a camera or projection primarily, and which to fall back o...
Definition: UniversalGroundMap.h:75
Isis::SurfacePoint::GetLongitude
Longitude GetLongitude() const
Return the body-fixed longitude for the surface point.
Definition: SurfacePoint.cpp:1685
Isis::ProjectionFactory::CreateFromCube
static Isis::Projection * CreateFromCube(Isis::Cube &cube)
This method is a helper method.
Definition: ProjectionFactory.cpp:1069
Isis::UniversalGroundMap::UniversalGroundMap
UniversalGroundMap(Cube &cube, CameraPriority priority=CameraFirst)
Constructs a UniversalGroundMap object from a cube.
Definition: UniversalGroundMap.cpp:33
Isis::UniversalGroundMap::Resolution
double Resolution() const
Returns the resolution of the camera model or projection.
Definition: UniversalGroundMap.cpp:294
Isis::PolygonTools::MakeMultiPolygon
static geos::geom::MultiPolygon * MakeMultiPolygon(const geos::geom::Geometry *geom)
Make a geos::geom::MultiPolygon out of the components of the argument.
Definition: PolygonTools.cpp:1369
Isis::Angle::isValid
bool isValid() const
This indicates whether we have a legitimate angle stored or are in an unset, or invalid,...
Definition: Angle.cpp:95
Isis::Camera::PixelResolution
virtual double PixelResolution()
Returns the pixel resolution at the current position in meters/pixel.
Definition: Camera.cpp:670
Isis::Cube::label
Pvl * label() const
Returns a pointer to the IsisLabel object associated with the cube.
Definition: Cube.cpp:1701
Isis::TProjection::UniversalLongitude
virtual double UniversalLongitude()
This returns a universal longitude (positive east in 0 to 360 domain).
Definition: TProjection.cpp:922
Isis::Camera::GroundRange
bool GroundRange(double &minlat, double &maxlat, double &minlon, double &maxlon, Pvl &pvl)
Computes the Ground Range.
Definition: Camera.cpp:1182
Isis::UniversalGroundMap::SetUnboundGround
bool SetUnboundGround(Latitude lat, Longitude lon)
Returns whether the lat/lon position was set successfully in the camera model or projection.
Definition: UniversalGroundMap.cpp:154
Isis::Angle::degrees
double degrees() const
Get the angle in units of Degrees.
Definition: Angle.h:232
Isis::UniversalGroundMap::SetUniversalGround
bool SetUniversalGround(double lat, double lon)
Returns whether the lat/lon position was set successfully in the camera model or projection.
Definition: UniversalGroundMap.cpp:102
Isis::Projection::Triaxial
@ Triaxial
These projections are used to map triaxial and irregular-shaped bodies.
Definition: Projection.h:166
Isis::UniversalGroundMap::SetImage
bool SetImage(double sample, double line)
Returns whether the sample/line postion was set successfully in the camera model or projection.
Definition: UniversalGroundMap.cpp:233
Isis::Camera::Line
virtual double Line() const
Returns the current line number.
Definition: Camera.cpp:2710
Isis::UniversalGroundMap::GroundRange
bool GroundRange(Cube *cube, Latitude &minLat, Latitude &maxLat, Longitude &minLon, Longitude &maxLon, bool allowEstimation=true)
Find the lat/lon range of the image.
Definition: UniversalGroundMap.cpp:320
Isis::UniversalGroundMap::Line
double Line() const
Returns the current line value of the camera model or projection.
Definition: UniversalGroundMap.cpp:214
Isis::Projection::ProjectionType
ProjectionType
This enum defines the subclasses of Projection supported in Isis.
Definition: Projection.h:166
Isis::SurfacePoint
This class defines a body-fixed surface point.
Definition: SurfacePoint.h:132
Isis::UniversalGroundMap::p_camera
Isis::Camera * p_camera
The camera (if the image has a camera)
Definition: UniversalGroundMap.h:138
Isis::ImagePolygon::Polys
geos::geom::MultiPolygon * Polys()
Return a geos Multipolygon.
Definition: ImagePolygon.h:210
Isis::UniversalGroundMap::UniversalLongitude
double UniversalLongitude() const
Returns the universal longitude of the camera model or projection.
Definition: UniversalGroundMap.cpp:270
Isis
This is free and unencumbered software released into the public domain.
Definition: Apollo.h:16
Isis::Projection::SetUnboundUniversalGround
virtual bool SetUnboundUniversalGround(const double coord1, const double coord2)
This method is used to set the lat/lon or radius/azimuth (i.e.
Definition: Projection.cpp:446
Isis::UniversalGroundMap::SetBand
void SetBand(const int band)
Set the image band number.
Definition: UniversalGroundMap.cpp:72
Isis::Sensor::UniversalLatitude
virtual double UniversalLatitude() const
Returns the planetocentric latitude, in degrees, at the surface intersection point in the body fixed ...
Definition: Sensor.cpp:210

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the USGS Astrogeology Discussion Board
To report a bug, or suggest a feature go to: ISIS Github
File Modified: 07/13/2023 15:17:25