diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1c4ccbc6..e092d62e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ release. - Updated pixel2map documentation ### Fixed +- Fixed a bug in isisminer in which bad (e.g. self-intersecting) polygon geometries were not treated properly. Added pertinent unit tests to GisGeometry and Strategy classes. Issue: [5612](https://github.com/DOI-USGS/ISIS3/issues/5612) - Fixed a bug in kaguyasp2isis that doesn't work for data with a detached label. ## [8.3.0] - 2024-09-30 diff --git a/isis/src/base/apps/isisminer/isisminer.xml b/isis/src/base/apps/isisminer/isisminer.xml index bd7d22c827..d41dacfcf5 100644 --- a/isis/src/base/apps/isisminer/isisminer.xml +++ b/isis/src/base/apps/isisminer/isisminer.xml @@ -1,6 +1,7 @@ - + + Run a series of algorithms (Strategies) that perform various operations on input @@ -48,8 +49,8 @@ in well-known-text (WKT) or well-known-binary (WKB) as conversions are restricted to text representations of geometries (format requirements are the same as functions like - ST_GeomFromWKT and - ST_GeomFromWKB, + ST_GeomFromWKT and + ST_GeomFromWKB, respectively). Resources may also contain Assets. Assets are typically another list of Resources created from Strategies that are attached to a (parent) Resource. If Assets are represented as Resource lists, they can @@ -340,6 +341,40 @@ as these strings can be quite large. + + RepairInvalidGeometry + Optional + + There are times when invalid geometries will be passed into isisminer. + One of the most common invalid geometries encountered is self-intersecting + geometries. footprintinit will at times create self-intersecting geometries, + usually when it uses the shape model in projecting/intersecting to ground, + that are often read by isisminer as output from caminfo. + These types of invalid geometries can + be repaired by applying the GIS buffer algorithm with a width of 0. If an + invalid geometry occurs in isisminer, and RepairInvalidGeometry = True, the + buffer algorithm will be run to attempt to repair the geometry. The default + value of this keyword is true so that invalid geometries will be repaired. + See also InvalidGeometryAction. + + + + InvalidGeometryAction + Optional + + This parameter determines the action taken when an invalid geometry occurs + and is either not repaired (RepairInvalidGeometry = False) or could not be + sucessfully repaired (RepairInvalidGeometry = True). The options are: + continue, disable or error. For InvalidGeometryAction = continue, the state + of the invalid geometry is retained in the Resource (some GIS operations + still function) and it is not disabled and no error occurs - the issue is + ignored. If InvalidGeometryAction = disable, then the geometry is retained + in the Resource but the status is set to "discard". It can be re-enabled by + using the ResourceManager strategy. If InvalidGeometryAction = error, then + an error is thrown and isisminer terminates. The default is to + disable the Resource. + + GisSimplifyTolerance Optional @@ -990,6 +1025,23 @@ EndObject not already exist in the Resources. + + InitializersArgs + Optional + + Using this parameter, users can list existing keywords in a Resource or + from the global parameter pool in this keyword to create new variables + in the Initializers group. This works by substituting ordered numerical index + values specified as %1, %2, etc..., with a keyword value referenced by + the index in the InitializersArgs array keyword. The InitializersArgs keyword + should have the same number of keywords as %[index], where index is + numbered from 1 to n, specified in the keywords found in the + Initializers group. These initialzers should not involve equations as + they are not resovled in this step. + Note: This is the only way to create new keywords specificially in + isisminer. + + Equation Optional @@ -4655,14 +4707,14 @@ End feature. - Improved GisOverlap and StereoPair strategies. StereoAngle can now be - computed from the GIS centroid of the overlapping region, providing a - much higher degree of accuracy. Improved global variable pool management - whilst traversing through Strategy/Resource depths in the mining process. - Ensure Asset is cleared when Mode = Create is used in AssetSideBar strategy. + Improved GisOverlap and StereoPair strategies. StereoAngle can now be + computed from the GIS centroid of the overlapping region, providing a + much higher degree of accuracy. Improved global variable pool management + whilst traversing through Strategy/Resource depths in the mining process. + Ensure Asset is cleared when Mode = Create is used in AssetSideBar strategy. - Fixed bug in creation of GEOS SRTree when only one geometry occurs + Fixed bug in creation of GEOS SRTree when only one geometry occurs (requires two or more). Changed name of IsisMiner objects within the GisOverlap strategy (since we now have two of them) to CandidateMiner and OverlapMiner. Added feature to simplify geometry whilst preserving @@ -4674,21 +4726,21 @@ End Add ability to process the set of GisOverlap results for each Resource as they are matched. This is handled in the OverlapMiner Strategy object. - + Added more content to documentation and added an example demonstrating a real application of the isisminer application. - - + + Refactored the Resource class to contain all but the active status so that copies can be maintained in separate instances. Reworked the AssetSidebar strategy to take advantage of this work. - - + + Fixed bug in argument scanning when the number of arguments reached 10 or more as %1 was replacing %10, etc... Scan and replace i reverse order corrected this problem. Enhanced the Calculator strategy to provide argument replacement in the Initializers group. - + Modified CsvReader strategy to have the provided header length determine the number of keywords created/Resource. This allows the jigsaw residual @@ -4699,6 +4751,14 @@ End tests to gtest format. Moved some data from isis data area to "isis/tests/data/isisminer". + + Originally implemented in UofA OSIRIS-REx code by Kris Becker, 2018-07-31. + Corrected problems with invalid geometries causeing isisminer to abort. + Added two new parameters, RepairInvalidGeometry and InvalidGeometryAction, + to allow user more control over how invalid geometries are managed. Updated + documentation. Ken Edmundson added unit tests to GisGeometry and Strategy + classes to address these changes. + diff --git a/isis/src/base/apps/isisminer/tsts/badgeomfix/Makefile b/isis/src/base/apps/isisminer/tsts/badgeomfix/Makefile new file mode 100644 index 0000000000..0d937ac5fa --- /dev/null +++ b/isis/src/base/apps/isisminer/tsts/badgeomfix/Makefile @@ -0,0 +1,10 @@ +APPNAME = isisminer + +include $(ISISROOT)/make/isismake.tsts + +commands: + $(LS) $(INPUT)/*.pvl >& $(OUTPUT)/badgeomfix_data.lis; + $(APPNAME) config=$(INPUT)/badgeomfix.conf \ + parameters="fromlist:$(OUTPUT)/badgeomfix_data.lis@tocsv:$(OUTPUT)/badgeomfix.csv" > /dev/null + $(RM) $(OUTPUT)/badgeomfix_data.lis; + diff --git a/isis/src/base/apps/isisminer/tsts/badgeomfix/input/20190328T204026S3500_pol_iofL2pan.pvl b/isis/src/base/apps/isisminer/tsts/badgeomfix/input/20190328T204026S3500_pol_iofL2pan.pvl new file mode 100755 index 0000000000..2f6b1fdec2 --- /dev/null +++ b/isis/src/base/apps/isisminer/tsts/badgeomfix/input/20190328T204026S3500_pol_iofL2pan.pvl @@ -0,0 +1,632 @@ +Object = Caminfo + Object = Parameters + Program = caminfo + IsisVersion = "3.5.2.0 stable | 2018-04-06" + RunDate = 2018-06-20T00:13:01 + IsisId = OsirisRex/PolyCam/3/0607077597.03118 + From = 20190328T204026S3500_pol_iofL2pan.cub + Lines = 1024 + Samples = 1024 + Bands = 1 + End_Object + + Object = Camstats + MinimumLatitude = -89.977543273115 + MaximumLatitude = -11.850340336005 + MinimumLongitude = 9.52423066877119e-05 + MaximumLongitude = 359.93420656349 + MinimumResolution = 0.04427636192466 + MaximumResolution = 0.048998194928801 + MinimumPhase = 46.232501646477 + MaximumPhase = 47.348685898802 + MinimumEmission = 47.304393766337 + MaximumEmission = 132.54482385131 + MinimumIncidence = 82.70316362766 + MaximumIncidence = 148.20782054406 + LocalTimeMinimum = 0.0026302868152612 + LocalTimeMaximum = 23.998897797146 + End_Object + + Object = IsisLabel + Object = IsisCube + Object = Core + StartByte = 65537 + Format = Tile + TileSamples = 512 + TileLines = 512 + + Group = Dimensions + Samples = 1024 + Lines = 1024 + Bands = 1 + End_Group + + Group = Pixels + Type = Real + ByteOrder = Lsb + Base = 0.0 + Multiplier = 1.0 + End_Group + End_Object + + Group = Instrument + MissionName = OSIRIS-REx + MissionPhase = None + SpacecraftName = OSIRIS-REX + InstrumentId = PolyCam + TargetName = Bennu + StartTime = "2019 MAR 28 20:40:26.350" + ExposureDuration = 20 + SpacecraftClockStartCount = 3/0607077597.03118 + FocusPosition = 17551 + SourceProductId = 20190328T204026S3500_pol_iofL2pan + End_Group + + Group = BandBin + FilterName = pan + End_Group + + Group = Kernels + NaifFrameCode = -64360 + LeapSecond = $base/kernels/lsk/naif0012.tls + TargetAttitudeShape = ($osirisrex/kernels/pck/bennu.tpc, + $osirisrex/kernels/pck/pck00010.tpc) + TargetPosition = (Table, $osirisrex/kernels/tspk/de424.bsp, + $osirisrex/kernels/tspk/sb-101955-76.bsp) + InstrumentPointing = (Table, + $osirisrex/kernels/ck/orx_f_20190328T1810- + _20190328T2309_DS_BBD_SOPG_v01a_fb4b_1908- + 7a.bc, $osirisrex/kernels/fk/orx_v09.tf) + Instrument = $osirisrex/kernels/ik/orx_ocams_v06.ti + SpacecraftClock = $osirisrex/kernels/sclk/ORX_SCLKSCET.0002- + 7.tsc + InstrumentPosition = (Table, + $osirisrex/kernels/spk/orx_190220_190608_- + DS_MPRevB.bsp) + InstrumentAddendum = $osirisrex/kernels/iak/orex_ocams_addendu- + m_v04.ti + ShapeModel = $osirisrex/kernels/dems/bennu512.bds + InstrumentPositionQuality = Reconstructed + InstrumentPointingQuality = Reconstructed + CameraVersion = 1 + RayTraceEngine = embree + OnError = continue + Tolerance = 1.79769313486232e+308 + End_Group + End_Object + + Object = Label + Bytes = 65536 + End_Object + + Object = Table + Name = InstrumentPointing + StartByte = 4265899 + Bytes = 64 + Records = 1 + ByteOrder = Lsb + TimeDependentFrames = (-64000, 1) + ConstantFrames = (-64360, -64000) + ConstantRotation = (0.99999974040951, 7.11200727811218e-04, + -1.15647874696325e-04, -7.11305783186799e-04, + 0.99999933213616, -9.10917843429873e-04, + 1.14999952026068e-04, 9.10999867966352e-04, + 0.99999957842704) + CkTableStartTime = 607077698.48425 + CkTableEndTime = 607077698.48425 + CkTableOriginalSize = 1 + FrameTypeCode = 3 + Description = "Created by spiceinit" + Kernels = ($osirisrex/kernels/ck/orx_f_20190328T1810_201903- + 28T2309_DS_BBD_SOPG_v01a_fb4b_19087a.bc, + $osirisrex/kernels/fk/orx_v09.tf) + + Group = Field + Name = J2000Q0 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q1 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q2 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q3 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV1 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV2 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV3 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = ET + Type = Double + Size = 1 + End_Group + End_Object + + Object = Table + Name = InstrumentPosition + StartByte = 4265963 + Bytes = 56 + Records = 1 + ByteOrder = Lsb + CacheType = Linear + SpkTableStartTime = 607077698.48425 + SpkTableEndTime = 607077698.48425 + SpkTableOriginalSize = 1.0 + Description = "Created by spiceinit" + Kernels = $osirisrex/kernels/spk/orx_190220_190608_DS_MPRe- + vB.bsp + + Group = Field + Name = J2000X + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Y + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Z + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000XV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000YV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000ZV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = ET + Type = Double + Size = 1 + End_Group + End_Object + + Object = Table + Name = BodyRotation + StartByte = 4266019 + Bytes = 64 + Records = 1 + ByteOrder = Lsb + TimeDependentFrames = (10106, 1) + CkTableStartTime = 607077698.48425 + CkTableEndTime = 607077698.48425 + CkTableOriginalSize = 1 + FrameTypeCode = 2 + PoleRa = (86.6388, 0.0, 0.0) + PoleDec = (-65.1086, 0.0, 0.0) + PrimeMeridian = (89.6456, 2010.489449468, 0.0) + Description = "Created by spiceinit" + Kernels = ($osirisrex/kernels/tspk/de424.bsp, + $osirisrex/kernels/tspk/sb-101955-76.bsp, + $osirisrex/kernels/pck/bennu.tpc, + $osirisrex/kernels/pck/pck00010.tpc) + SolarLongitude = 135.41509366604 + + Group = Field + Name = J2000Q0 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q1 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q2 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Q3 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV1 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV2 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = AV3 + Type = Double + Size = 1 + End_Group + + Group = Field + Name = ET + Type = Double + Size = 1 + End_Group + End_Object + + Object = Table + Name = SunPosition + StartByte = 4266083 + Bytes = 56 + Records = 1 + ByteOrder = Lsb + CacheType = Linear + SpkTableStartTime = 607077698.48425 + SpkTableEndTime = 607077698.48425 + SpkTableOriginalSize = 1.0 + Description = "Created by spiceinit" + Kernels = ($osirisrex/kernels/tspk/de424.bsp, + $osirisrex/kernels/tspk/sb-101955-76.bsp) + + Group = Field + Name = J2000X + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Y + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000Z + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000XV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000YV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = J2000ZV + Type = Double + Size = 1 + End_Group + + Group = Field + Name = ET + Type = Double + Size = 1 + End_Group + End_Object + + Object = Table + Name = CameraStatistics + StartByte = 4268281 + Bytes = 1155 + Records = 15 + ByteOrder = Lsb + + Group = Field + Name = Name + Type = Text + Size = 45 + End_Group + + Group = Field + Name = Minimum + Type = Double + Size = 1 + End_Group + + Group = Field + Name = Maximum + Type = Double + Size = 1 + End_Group + + Group = Field + Name = Average + Type = Double + Size = 1 + End_Group + + Group = Field + Name = StandardDeviation + Type = Double + Size = 1 + End_Group + End_Object + + Object = History + Name = IsisCube + StartByte = 4276887 + Bytes = 3492 + End_Object + + Object = OriginalLabel + Name = IsisCube + StartByte = 4260457 + Bytes = 5442 + End_Object + + Object = NaifKeywords + BODY2101955_RADII = (0.2825, 0.2675, 0.254) + BODY_FRAME_CODE = 10106 + INS-64360_FOCAL_LENGTH = 628.7 + INS-64360_PIXEL_SIZE = 8.5 + CLOCK_ET_-64_3/0607077597.03118_COMPUTED = 46b43ca1a217c241 + INS-64360_TRANSX = (0.0, 0.0, 0.0085) + INS-64360_TRANSY = (0.0, 0.0085, 0.0) + INS-64360_ITRANSS = (0.0, 0.0, 117.64705882353) + INS-64360_ITRANSL = (0.0, 117.64705882353, 0.0) + INS-64360_CCD_CENTER = (511.5, 511.5) + End_Object + + Object = Polygon + Name = Footprint + StartByte = 4272163 + Bytes = 4724 + End_Object + End_Object + + Object = Statistics + MeanValue = 3.56102547970055e-04 + StandardDeviation = 4.07298920685332e-04 + MinimumValue = 2.65281014435459e-05 + MaximumValue = 0.0039145527407527 + PercentHIS = 0.0 + PercentHRS = 0.0 + PercentLIS = 0.0 + PercentLRS = 0.0 + PercentNull = 0.0 + TotalPixels = 1048576.0 + End_Object + + Object = Geometry + BandsUsed = 1 + ReferenceBand = 1 + OriginalBand = 1 + Target = Bennu + StartTime = 2019-03-28T20:40:29.2986046 + EndTime = 2019-03-28T20:40:29.2986046 + CenterLine = 512.0 + CenterSample = 512.0 + CenterLatitude = -85.806182958016 + CenterLongitude = 127.47342286573 + CenterRadius = 239.9357352421 + RightAscension = 167.82820817684 + Declination = -33.564227478217 + UpperLeftLongitude = 244.10368900916 + UpperLeftLatitude = -81.390001009948 + LowerLeftLongitude = 46.477188843357 + LowerLeftLatitude = -80.176943257946 + LowerRightLongitude = 104.70786485926 + LowerRightLatitude = -77.445765768177 + UpperRightLongitude = 199.15926088876 + UpperRightLatitude = -73.228038077218 + PhaseAngle = 46.791311751759 + EmissionAngle = 55.059285079676 + IncidenceAngle = 90.610483637642 + NorthAzimuth = 249.47051869331 + OffNadir = 3.2366160532891 + SolarLongitude = 135.41509366604 + LocalTime = 14.815562022303 + TargetCenterDistance = 3.4452210459306 + SlantDistance = 3.3023081977663 + SampleResolution = 0.044647080771455 + LineResolution = 0.044647080771455 + PixelResolution = 0.044647080771455 + MeanGroundResolution = 0.044851487260922 + SubSolarAzimuth = 211.70465369529 + SubSolarGroundAzimuth = 317.85901102683 + SubSolarLatitude = 3.4331191111682 + SubSolarLongitude = 85.23999253118 + SubSpacecraftAzimuth = 166.41021496018 + SubSpacecraftGroundAzimuth = 284.87226975325 + SubSpacecraftLatitude = -36.990831381959 + SubSpacecraftLongitude = 55.443606192391 + ParallaxX = -0.36736412580346 + ParallaxY = -1.3833499203943 + ShadowX = 69.589064886379 + ShadowY = 62.969032367659 + HasLongitudeBoundary = TRUE + HasNorthPole = FALSE + HasSouthPole = FALSE + End_Object + + Object = Polygon + CentroidLine = 895.72585973477 + CentroidSample = 644.84590594138 + CentroidLatitude = -81.190332040708 + CentroidLongitude = 93.04921550612 + CentroidRadius = 237.24166349057 + SurfaceArea = 0.0011505490491134 + GlobalCoverage = 0.162672 + SampleIncrement = 25 + LineIncrement = 25 + GisFootprint = "MULTIPOLYGON (((45.2132126883270828 + -81.9710739323089825, 46.3900697482582771 + -82.1732702631075966, 48.3807343157982785 + -82.3670511637212712, 50.0930656454000953 + -82.5947074359128095, 51.1756734310785077 + -82.6916671035287578, 51.8772808116133106 + -82.7518749766135926, 54.9309393622864874 + -82.8653176657448256, 57.2770339224418663 + -82.9915589476465101, 59.5310618697406113 + -83.2361311258595009, 61.2595500632626297 + -83.2898729605686441, 63.7017320012995327 + -83.5021341230045095, 63.0849056130083667 + -83.4735239071116268, 67.0354940959035446 + -83.6108592008742875, 69.8416105902251871 + -83.7360048133778321, 71.1080558583340405 + -83.7651631090500643, 74.5062844787984488 + -83.8179980041393549, 75.3594077804066842 + -83.7725123726611685, 80.5872214656606474 + -83.8783588867185443, 82.6308450435663104 + -83.9653005954941847, 84.3841814985218832 + -83.9724490858453407, 87.9888080308176228 + -83.9693748372311006, 91.3732068287094847 + -83.8278900155197277, 94.3238516125568509 + -83.7716727857661141, 97.7252866476106448 + -83.7712336251120178, 97.7824068348349300 + -83.8388622500535803, 101.0453824236701337 + -83.6851030866638013, 101.8186172701891081 + -83.7295826719524712, 105.2087299183673110 + -83.6100211569625174, 108.6598590574971439 + -83.3248610498735331, 111.5175291674420066 + -83.1675290751693836, 113.1200992490602317 + -83.1472175883431817, 115.8329034098292567 + -82.9756247561356872, 116.2311021136059566 + -83.0117756389327610, 117.9832631377363015 + -82.8810739184366270, 120.2455211013516134 + -82.6946456741336391, 122.8108054887181027 + -82.3004864810328058, 124.3337072232068294 + -82.3002994651425155, 126.1584821539486398 + -81.9858046796555868, 126.6262724004373297 + -82.0417442536875399, 129.3092605050725297 + -81.6582238785159547, 129.4971208818184607 + -81.6732001900426212, 131.8753249468513502 + -81.3240683047375938, 132.1440546312125548 + -81.2817741781780256, 134.3353229965744902 + -80.8505249764275504, 135.6934000498298190 + -80.5480541080396648, 135.8622082524397285 + -80.5752126083310856, 136.8171614381602978 + -80.4405028401881310, 136.7545488423376412 + -80.4376460102974988, 134.6318603268028937 + -80.3208267019499260, 131.9801649528900214 + -80.2693908986012019, 129.6012562699511648 + -80.0491529582131562, 127.8824594381253661 + -79.9666889831492824, 125.9854935842498946 + -79.7966379161561292, 123.1441888973893555 + -79.8197098114049339, 120.7162104163665362 + -79.5935840103888239, 119.4375847276845946 + -79.2555771267408460, 117.7847742805561637 + -79.2178162123147018, 116.0030324051906945 + -79.0461863538199765, 115.0659997288763634 + -78.8051858790117450, 112.3390892318091829 + -78.4997554916940032, 110.2191560926207217 + -78.3815333750022347, 108.5239888835026392 + -78.2830304157776027, 107.3793751857314760 + -77.9376443459565422, 106.0005478549929592 + -77.8527301603652688, 105.1539743933198423 + -77.5184188585909055, 104.7497773698118806 + -77.4355821187713502, 103.5988811499352238 + -77.6259865114883780, 102.6376847019217990 + -77.7954387819884516, 101.8432243941200284 + -77.9452726878277957, 100.8852377364070492 + -78.3019529542752650, 99.9337234657647286 + -78.3157979884764330, 98.8657365671700319 + -78.4777134495422075, 97.6678699936425829 + -78.6667544528437759, 95.7847401634583946 + -78.8030720203295800, 95.1198295734679391 + -78.9516530643774672, 93.9319606881578295 + -79.1462615773065039, 92.1719250266311008 + -79.2500824261785368, 91.5224933744262046 + -79.3312127588866218, 89.9982833519602963 + -79.5044324330666115, 88.2631902385483613 + -79.6045893136236913, 87.3493923625903506 + -79.6656444431007031, 85.8232404352163769 + -79.8375851889514365, 84.0055895809480830 + -79.9428930820308068, 82.8691890305008627 + -80.0326216125934735, 81.3601094915756846 + -80.1142920066747166, 79.5155688970176158 + -80.1943407863012396, 78.2483423374156786 + -80.3083003960452118, 76.9464454785041170 + -80.3137194313287779, 74.7730281815756115 + -80.3071101205093356, 73.4216248118050316 + -80.3358441782179398, 70.9140452475345455 + -80.4653116945363820, 69.7305356828333487 + -80.4143427381125235, 68.6343747585147668 + -80.4501757822241501, 67.4453381083687873 + -80.5230273806155594, 65.6332422676785399 + -80.4245330567297856, 63.2968324219685385 + -80.4811272457823463, 62.1139495298728974 + -80.3605937383087365, 60.0428349095074765 + -80.4164480057085029, 59.2134612778791976 + -80.3925804140070426, 57.1490312844103912 + -80.3317744845099639, 55.4533483881949962 + -80.3625590728819645, 53.6622769715622852 + -80.3754730171858540, 51.8849536717882245 + -80.3800960621792910, 51.6461391758912356 + -80.3572139759018711, 49.3874071093724041 + -80.2353823950590197, 47.9936934918719871 + -80.2160953404420667, 46.5101265972988642 + -80.1799074343788192, 46.1331970664866802 + -80.4429025998344827, 45.5909752722565216 + -80.8070946290784065, 44.7548063270353538 + -81.2687564596081131, 45.2132126883270828 + -81.9710739323089825)))" + + Group = Mapping + TargetName = Bennu + EquatorialRadius = 282.5 + PolarRadius = 254.0 + LatitudeType = Planetocentric + LongitudeDirection = PositiveEast + LongitudeDomain = 180 + MinimumLatitude = -88.627936545651 + MaximumLatitude = -73.206145083129 + MinimumLongitude = -179.8585766105 + MaximumLongitude = 179.94426591676 + PixelResolution = 0.044276055740542 + ProjectionName = Sinusoidal + CenterLongitude = 0.042844653128697 + End_Group + End_Object +End_Object +End diff --git a/isis/src/base/apps/isisminer/tsts/badgeomfix/input/badgeomfix.conf b/isis/src/base/apps/isisminer/tsts/badgeomfix/input/badgeomfix.conf new file mode 100644 index 0000000000..613e26edab --- /dev/null +++ b/isis/src/base/apps/isisminer/tsts/badgeomfix/input/badgeomfix.conf @@ -0,0 +1,89 @@ +############################################################################### +# +# @author 2017-06-24 Kris Becker +# +############################################################################### +Object = IsisMiner + + Name = MakeDatabase + RequiredParameters = (fromlist, tocsv) + + Object = Strategy + Name = LoadCaminfoPvls + Type = PvlReader + # Debug = true + RepairInvalidGeometry = true + InvalidGeometryAction = error + + FromList = "%1" + FromListArgs = "FromList" + + # Resource targets are for reading multiple rows from the DB. The fields + # from each row are created as Resources. + Identity = "%1" + IdentityArgs = "SourceProductId" + + # Include/exclude conditions + Excludes = Mapping + + # Can specify a GIS keyword that will be converted on input + GisGeometryRef = GisFootprint + GisType = WKT + # Tolerance of 0.0004 ~= 0.005 m/p at equator + GisSimplifyTolerance = 0.05 + GisGeometryPointsKey = GisPoints + RemoveGisKeywordAfterImport = True + EndObject + + Object = Strategy + Name = SetSerialNumber + Type = Calculator + + InitializersArgs = (IsisId, CkTableStartTime) + Group = Initializers + SerialNumber = %1 + ObservationTime = %2 + EndGroup + + Group = Equations_DNE + ObservationTime = CkTableStartTime + EndGroup + + EndObject + + + Object = Strategy + # Write result of StereoPair strategy for each Resource that has a + # StereoPairs asset. + Name = WritePairs + Type = CsvWriter + Description = "Write CSV file of caminfo data" + + SkipEmptyLists = True + CsvFileArgs = ("ToCSV" ) + CsvFile = "%1" + Keywords = (From, SourceProductId, SerialNumber, Target, + MeanValue, MinimumValue, MaximumValue, StandardDeviation, + Lines, Samples, InstrumentId, FilterName, MissionPhase, + StartTime, EndTime, ObservationTime, ExposureDuration, + FocusPosition, CenterLongitude, CenterLatitude, CenterRadius, + PixelResolution, MeanGroundResolution, + HasNorthPole, HasSouthPole, HasLongitudeBoundary, + RightAscension, Declination, OffNadir, SolarLongitude, + LocalTime, CentroidLine, CentroidSample, + CentroidLatitude, CentroidLongitude, + CentroidRadius, SurfaceArea, GlobalCoverage, + IncidenceAngle, EmissionAngle, PhaseAngle, + SubSolarGroundAzimuth, SubSpacecraftGroundAzimuth, + SubSpacecraftLongitude, SubSpacecraftLatitude, + SubSolarLongitude, SubSolarLatitude, + TargetCenterDistance, SlantDistance, + ParallaxX, ParallaxY, ShadowX, ShadowY, + GisFootprint) + GisGeometryKey = GisFootprint + GisType = WKB + EndObject + + +EndObject +End diff --git a/isis/src/base/apps/isisminer/tsts/badgeomfix/truth/badgeomfix.csv b/isis/src/base/apps/isisminer/tsts/badgeomfix/truth/badgeomfix.csv new file mode 100644 index 0000000000..247a4d9c4c --- /dev/null +++ b/isis/src/base/apps/isisminer/tsts/badgeomfix/truth/badgeomfix.csv @@ -0,0 +1,2 @@ +From,SourceProductId,SerialNumber,Target,MeanValue,MinimumValue,MaximumValue,StandardDeviation,Lines,Samples,InstrumentId,FilterName,MissionPhase,StartTime,EndTime,ObservationTime,ExposureDuration,FocusPosition,CenterLongitude,CenterLatitude,CenterRadius,PixelResolution,MeanGroundResolution,HasNorthPole,HasSouthPole,HasLongitudeBoundary,RightAscension,Declination,OffNadir,SolarLongitude,LocalTime,CentroidLine,CentroidSample,CentroidLatitude,CentroidLongitude,CentroidRadius,SurfaceArea,GlobalCoverage,IncidenceAngle,EmissionAngle,PhaseAngle,SubSolarGroundAzimuth,SubSpacecraftGroundAzimuth,SubSpacecraftLongitude,SubSpacecraftLatitude,SubSolarLongitude,SubSolarLatitude,TargetCenterDistance,SlantDistance,ParallaxX,ParallaxY,ShadowX,ShadowY,GisFootprint +20190328T204026S3500_pol_iofL2pan.cub,20190328T204026S3500_pol_iofL2pan,OsirisRex/PolyCam/3/0607077597.03118,Bennu,3.56102547970055e-04,2.65281014435459e-05,0.0039145527407527,4.07298920685332e-04,1024,1024,PolyCam,pan,None,2019-03-28T20:40:29.2986046,2019-03-28T20:40:29.2986046,607077698.48425,20,17551,127.47342286573,-85.806182958016,239.9357352421,0.044647080771455,0.044851487260922,FALSE,FALSE,TRUE,167.82820817684,-33.564227478217,3.2366160532891,135.41509366604,14.815562022303,895.72585973477,644.84590594138,-81.190332040708,93.04921550612,237.24166349057,0.0011505490491134,0.162672,90.610483637642,55.059285079676,46.791311751759,317.85901102683,284.87226975325,55.443606192391,-36.990831381959,85.23999253118,3.4331191111682,3.4452210459306,3.3023081977663,-0.36736412580346,-1.3833499203943,69.589064886379,62.969032367659,0103000000010000003F00000081BAA98D4A9B4640F9504713267E54C022B7647E9D604640C2264B4E335154C0F41B0ED44B414740B7BD789A830B54C0067F608E96B148404A355281100F54C01C67732946F1494080196F7E531854C0CF4F067513934C4086270CCB3B1554C01ABB439D7B054E4028408915A71A54C06E19F0E5950E4F406B4BC2F7131754C0A95FD49AFEA54F406276EEC9CA1E54C0F387930A876850406CAEB28C2B1B54C0D0C6686B80DC504002AAD547792154C03BB0314B79B15240EDF634B1A71354C0675A42D7E48F5340EA969531BB1354C0F27FA6F8AF745540448AE8FE9AF553C0EA32713EA57B574057CC84595CC953C0567ACE2E39F257407CE92F8865B353C02D2BC961BE6A58404A5BDE1AACAA53C0E83D1120C2FB58408729C408369453C06AC32DBCA7385940BEE27B32539353C0D6FB7263F77559406F0104597F7C53C0DEA7385AFC2F5A401AB6D293E05B53C0C6D1DEF908805A403AC58521937653C05CE8DBAE47D85A40694A6E5D027C53C02187AB0889215B4070E29A2B1D9253C0F54252A3B3155C409E3475FEFB9F53C06844ED5639C45C409D635A2A88B353C0D347D4AE31005D4069CB9BB7F4C253C0C36EE7BD39725D40891B69B3F0CD53C09AA85F6301DC5D40493D2A605BD053C053D63664D62D5E401B03CA47FDE553C0F3AE11643AC95E40730C242076F453C05AB1AE53127F5F403A29991DFCF253C0232BE4825D7F604043D552B33D1154C06D8EBE2F261A6140CD19D332311C54C00B224D5530F66040019E8951132354C0A63C75189C84604071089096085254C035320D6AE82F604038F93FB6156B54C00291D5D814A85F40921817F0AB8254C0610D5592248A5F4054D7826C177F54C030878A755B155F40C7723F1B389354C08A5DB43CE4B35E40443BA62B3B9354C01E32239EB60F5E408F2D211375AC54C057FE8460CA0E5D40C4069CEEC0C054C0637C1A4A4EF55C405431D1A270BE54C0DBFA7B213B2A5B40DE3C0086CAD454C06645BBD45B4D5A406AC12D960AE754C0CBD9B03964745940E8F0847BB1EE54C07E5EAE8BE742594005E79DBAD8EB54C01BF21DF41272584010754AEBAFF554C0A5EDAF186B6E5840A05B47E45BF154C0112C1DFCB99457400985401663F154C091987AA148FF554044DEC13C0AFE54C04EF8FE6D96185540C62E179B3CFE54C094B17D8900D752407215BCD770F154C05ABA03F766A0524032FE4C145AF454C0013FAAF2DC75514002CAEEB31AEF54C077E9A0F03AC84F40224364425FDF54C0A1B1BCEF38A14E40C56851478DD254C02258D9D5F9C34D4097C8B9C51CCF54C09D62FAD875A34C40AB0CA9B375BF54C0CDA4D5BC4AF049401DCF38B81EB054C0D1F635CEED314740A75A28DC168B54C081BAA98D4A9B4640F9504713267E54C0 diff --git a/isis/src/base/objs/GisGeometry/GisGeometry.cpp b/isis/src/base/objs/GisGeometry/GisGeometry.cpp index ab1e870e6b..12a0bca550 100644 --- a/isis/src/base/objs/GisGeometry/GisGeometry.cpp +++ b/isis/src/base/objs/GisGeometry/GisGeometry.cpp @@ -63,7 +63,7 @@ namespace Isis { * This constructor will read the contents of the Polygon blob of an ISIS cube * file and create a geometry from its contents. * - * @param cube Cube object to create the geomtery from + * @param cube Cube object to create the geometry from */ GisGeometry::GisGeometry(Cube &cube) : m_type(IsisCube), m_geom(0), m_preparedGeom(0) { @@ -207,15 +207,25 @@ namespace Isis { * * First determines if it contains a geometry and then validates with the * GEOS toolkit. - * - * @return bool True if valid, false if invalid or non-existant - */ + * + * @return bool True if valid, false if invalid or non-existant + * + * @history 2018-07-29 Kris Becker - If the geometry is invalid, it throws an + * exception. Catch all exceptions and return proper + * status. + */ bool GisGeometry::isValid() const { if (!isDefined()) { return (false); } - - return (1 == GEOSisValid(this->m_geom)); + + int valid(0); + try { + valid = GEOSisValid(this->m_geom); + } catch (...) { + valid = 0; + } + return (1 == valid); } @@ -601,9 +611,35 @@ namespace Isis { double ratio = inCommon->area() / this->area(); return (ratio); } + - - /** +/** + * @brief Compute a buffer around an existing geometry + * + * Add a buffer around a geometry with the defined enlargement factor. It is + * common to use this as buffer(0) to fix geometries with self-intersections. + * These cases are often identified by calling isValid() and getting a false + * value returned. + * + * @author 2018-07-29 Kris Becker + * + * @param width Width to enlarge or shrink polygon + * @param quadsegs Number of segments to define a circle on corners + * + * @return GisGeometry* Pointer to new GisGeometry with buffer applied + */ + GisGeometry *GisGeometry::buffer(const double width,const int quadsegs) const { + // If there is no geometry, return a null geometry + if ( !isDefined()) { + return (new GisGeometry()); + } + + // Create the buffer around the geom + GEOSGeometry *geom = GEOSBuffer(m_geom, width, quadsegs); + return (new GisGeometry(geom)); + } + + /** * @brief Computes the envelope or bounding box of this geometry * * This method computes the envelope or bounding box of the geometry in this @@ -789,7 +825,7 @@ namespace Isis { /** * Reads Polygon from ISIS Cube and returns geometry from contents * - * @param cube ISIS Cube contaning a Polygon geometry object + * @param cube ISIS Cube containing a Polygon geometry object * @return GEOSGeometry Pointer to GEOS-C type geometry from Polygon BLOB */ GEOSGeometry *GisGeometry::fromCube(Cube &cube) const { diff --git a/isis/src/base/objs/GisGeometry/GisGeometry.h b/isis/src/base/objs/GisGeometry/GisGeometry.h index f8cad42e59..b3d58fa90e 100644 --- a/isis/src/base/objs/GisGeometry/GisGeometry.h +++ b/isis/src/base/objs/GisGeometry/GisGeometry.h @@ -23,8 +23,8 @@ namespace Isis { * @brief Encapsulation class provides support for GEOS-C API * * The Geometry Engine, Open Source (GEOS) software package, developed in C++ - * from a port of the Java Toplogy Suite (JTS) provides a simplied, generic C - * API using an opaque C pointer. This layer is to provide stable API from + * from a port of the Java Topology Suite (JTS) provides a simplified, generic C + * API using an opaque C pointer. This layer is to provide a stable API from * which to develop and maintain applications that are relatively immune from * changes to the underlying C++ implementation. * @@ -44,8 +44,13 @@ namespace Isis { * @history 2016-03-02 Ian Humphrey - Updated for coding standards compliance. Added to * jwbacker's original unit test to prepare for adding this class to * ISIS. Fixes #2398. - * @history 2016-03-04 Kris Becker - Completed the documentation and implmented the equals() + * @history 2016-03-04 Kris Becker - Completed the documentation and implemented the equals() * method. + * @history 2018-07-29 Kris Becker - Added buffer() method; isValid() was + * throwing an exception if the geometry was invalid + * (e.g., self-intersecting geometry), which is now + * trapped and a false condition is properly returned. + * */ class GisGeometry { @@ -99,6 +104,7 @@ namespace Isis { double intersectRatio(const GisGeometry &geom) const; + GisGeometry *buffer(const double width=0.0, const int quadsegs=16) const; GisGeometry *envelope( ) const; GisGeometry *convexHull( ) const; GisGeometry *simplify(const double &tolerance) const; diff --git a/isis/src/base/objs/GisGeometry/GisGeometry.truth b/isis/src/base/objs/GisGeometry/GisGeometry.truth index b300df2829..d7eaf80642 100644 --- a/isis/src/base/objs/GisGeometry/GisGeometry.truth +++ b/isis/src/base/objs/GisGeometry/GisGeometry.truth @@ -83,6 +83,26 @@ GisGeometry::Types: length? 0 points? 0 +"Construct Self-Intersecting Geometry from WKT GIS source" + isDefined? true + isValid? false + isValidReason? "Self-intersection[286.747135842881 51.2716857610475]" + isEmpty? true + type? "WKT" + area? 0 + length? 0 + points? 0 + +"Repaired Self-Intersecting Geometry from WKT GIS source" + isDefined? true + isValid? true + isValidReason? "Valid Geometry" + isEmpty? false + type? "GeosGis" + area? 26.2303 + length? 21.0533 + points? 5 + "Construct Copy Geometry from GisGeometry from Cube" isDefined? true isValid? true @@ -171,6 +191,15 @@ GisGeometry::Types: equals? "No" intersect ratio? "0.0" +"Source: Repaired Self-Intersecting WKT Geometry, Target: GeomGisIsisCube Geometry" + distance? "0.0" + intersects? "Yes" + contains? "No" + disjoint? "No" + overlaps? "Yes" + equals? "No" + intersect ratio? "0.014072849290423" + "Source: GisIsisCube Geometry, Target: WKT Geometry" distance? "0.0" intersects? "Yes" @@ -282,6 +311,16 @@ Simplified Geometry from Invalid Geometry is NULL. length? 8.23376 points? 187 +"Intersection Geometry of GeomCube and Repaired Self-Intersecting WKT Geometries" + isDefined? true + isValid? true + isValidReason? "Valid Geometry" + isEmpty? false + type? "GeosGis" + area? 26.2303 + length? 21.0533 + points? 5 + "Union Geometry of Invalid Geometry with WKT Geometry as target" isDefined? false isValid? false diff --git a/isis/src/base/objs/GisGeometry/unitTest.cpp b/isis/src/base/objs/GisGeometry/unitTest.cpp index 3c8f70d325..4a654e75cd 100644 --- a/isis/src/base/objs/GisGeometry/unitTest.cpp +++ b/isis/src/base/objs/GisGeometry/unitTest.cpp @@ -39,6 +39,11 @@ void printTypes(); * References #2398. * @history 2016-03-04 Ian Humphrey - Updated test and truthdata for equals() method. * References #2398. + * @history 2024-09-23 Ken Edmundson - Updated test and truthdata for 1) detecting + * self-intersecting geometries; 2) repairing such geometries + * with a buffer of size 0; and 3) overlap and intersection + * of repaired geometry with another. + * References #5612. * * * NOTE - distance(), intersects(), contains(), disjoin(), overlaps() methods @@ -93,7 +98,6 @@ int main() { printBasicInfo(geomGisWKB, "Construct Geometry from WKB GIS source"); - GisGeometry geomGisIsisCube("$ISISTESTDATA/isis/src/messenger/unitTestData/EW0213634118G.lev1.cub", GisGeometry::IsisCube); printBasicInfo(geomGisIsisCube, @@ -109,6 +113,19 @@ int main() { //??? geomDefault.setGeometry(geos); // SEGFAULT //??? printBasicInfo(geomDefault, "Set Default Geometry from GEOSGeometry"); + // polygon with self-intersecting geometry that lies + // within the boundaries of EW0211286081G.lev1.cub + QString wktSelfIntersect + = QString::fromStdString("POLYGON ((286.0 51.0, 291.5 53.0, 295.0 49.8, 289.5 47.0, 286.6 51.5, 286.0 51.0))"); + GisGeometry geomGisWKTSelfIntersect(wktSelfIntersect, GisGeometry::WKT); + printBasicInfo(geomGisWKTSelfIntersect, + "Construct Self-Intersecting Geometry from WKT GIS source"); + + // repair the self-intersecting geometry with buffer(0) + GisGeometry *repairedSelfIntersect = geomGisWKTSelfIntersect.buffer(0); + printBasicInfo(*repairedSelfIntersect, + "Repaired Self-Intersecting Geometry from WKT GIS source"); + GisGeometry geomCopy(geomCube); printBasicInfo(geomCopy, "Construct Copy Geometry from GisGeometry from Cube"); @@ -136,7 +153,10 @@ int main() { printTargetInfo(geomGisWKT, geomDefault, "Source: WKT Geometry, Target: Invalid Geometry"); - // overlaping geometries + // overlapping geometries + printTargetInfo(*repairedSelfIntersect, geomGisIsisCube, + "Source: Repaired Self-Intersecting WKT Geometry, Target: GeomGisIsisCube Geometry"); + printTargetInfo(geomGisIsisCube, geomGisWKT, "Source: GisIsisCube Geometry, Target: WKT Geometry"); @@ -186,11 +206,13 @@ int main() { printBasicInfo(*intersectionInvalidTargetGeometry, "Intersection Geometry of WKT Geometry with Invalid Geometry as target"); - GisGeometry *intersectionGeom = geomGisIsisCube.intersection(geomGisWKT); printBasicInfo(*intersectionGeom, "Intersection Geometry of GisIsisCube Geometry with WKT Geometry"); + GisGeometry *intersectCubeAndRepairedGeom = geomCube.intersection(*repairedSelfIntersect); + printBasicInfo(*intersectCubeAndRepairedGeom, + "Intersection Geometry of GeomCube and Repaired Self-Intersecting WKT Geometries"); // These two tests below should output empty geometries (union w/ invalid -> invalid) GisGeometry *unionInvalidSourceGeom = geomDefault.g_union(geomGisWKT); diff --git a/isis/src/base/objs/Strategy/Strategy.cpp b/isis/src/base/objs/Strategy/Strategy.cpp index f262ef1986..e5fe5cf066 100644 --- a/isis/src/base/objs/Strategy/Strategy.cpp +++ b/isis/src/base/objs/Strategy/Strategy.cpp @@ -725,7 +725,19 @@ namespace Isis { // Got a geometry. if ( !geom.isEmpty() ) { - + + // Get decision keys + bool repairGeom = toBool(keys.get("RepairInvalidGeometry", "true")); + + QString geomAction = keys.get("InvalidGeometryAction", "disable").toLower(); + if ( !QString("disable error continue").contains(geomAction) ) { + if ( isDebug() ) { + cout << " Invalid value for InvalidGeometryAction (" << geomAction + << ") - set to disable!\n"; + } + geomAction = "disable"; + } + // Process arguments Allows creation specialized geometry as well if ( keys.exists("GisGeometryArgs") ) { QStringList args = keys.allValues("GisGeometryArgs"); @@ -743,7 +755,65 @@ namespace Isis { if ( !geom.isEmpty() ) { QScopedPointer geosgeom(new GisGeometry(geom, GisGeometry::type(gisType))); - if ( geosgeom.isNull() || !geosgeom->isValid() ) return (false); + if ( geosgeom.isNull() ) { + if ( isDebug() ) { + cout << resource->name() << " geometry failed to construct\n"; + } + if ("continue" == geomAction) return (false); + if ( "disable" == geomAction ) { + resource->discard(); + return ( false ); + } + + // Throw an error + QString mess = resource->name() + " failed to construct geometry!"; + throw IException(IException::Programmer, mess, _FILEINFO_); + } + + // Check validity and take appropriate action + if ( !geosgeom->isValid() ) { + + QString geomError = geosgeom->isValidReason(); + if ( isDebug() ) { + cout << " Geometry error: " << geomError << "\n"; + } + + // Attempt repair if requested + if ( repairGeom ) { + if (isDebug()) { + cout << " " << resource->name() << " geometry is invalid..." + << "attempting buffer(0) to fix it!\n"; + } + geosgeom.reset( geosgeom->buffer(0) ); + if ( isDebug() ) { + if (geosgeom.isNull() || !geosgeom->isValid() ) { + cout << " Geometry could not be repaired!\n"; + } + else { + cout << " Geometry was successfully repaired!\n"; + } + } + } + + // Now check state and take final action regarding a failed + // geometry + + if (geosgeom.isNull() || !geosgeom->isValid() ) { + if ( isDebug() ) { + cout << " All efforts to convert geometry failed!\n"; + } + if ("continue" == geomAction) return (false); + if ( "disable" == geomAction ) { + resource->discard(); + return ( false ); + } + + // Throw an error + QString mess = resource->name() + " failed to construct geometry - Error: " + + geomError; + throw IException(IException::Programmer, mess, _FILEINFO_); + } + } npointsOrg = npoints = geosgeom->points(); QString gisTolerance = translateKeywordArgs("GisSimplifyTolerance", diff --git a/isis/src/base/objs/Strategy/Strategy.h b/isis/src/base/objs/Strategy/Strategy.h index 5e3e813999..ea48771812 100644 --- a/isis/src/base/objs/Strategy/Strategy.h +++ b/isis/src/base/objs/Strategy/Strategy.h @@ -15,6 +15,7 @@ find files of those names at the top level of this repository. **/ #include // SharedResource and ResourceList typedefs +#include "GisGeometry.h" #include "Resource.h" #include "PvlObject.h" @@ -55,7 +56,7 @@ namespace Isis { * @internal * @history 2012-07-15 Kris Becker - Original version. * @history 2015-03-18 Jeannie Backer - Brought class files closer to ISIS coding standards. - * @history 2015-04-07 Kristin Berry - Modified applytoIntserectedGeometry to deal + * @history 2015-04-07 Kristin Berry - Modified applytoIntersectedGeometry to deal * @history 2015-03-26 Jeannie Backer - Updated documentation. * @history 2015-03-28 Kris Becker - Added the composite(Resource, Resource) * method @@ -80,6 +81,12 @@ namespace Isis { * replacements in reverse order fixes this issue. * @history 2016-03-07 Tyler Wilson - Corrected documentation, and created a * unit test to test most of this classe's methods. + * @history 2018-07-29 Kris Becker - Fixed errors in importGeometry() where an + * exception was being thrown when checking for valid + * Geometry. It now checks for this case and runs + * buffer(0) to attempt to fix the geometry. This is + * typically a self-intersection error that can be + * repaired with a buffer(0) operator. */ class Strategy { diff --git a/isis/src/base/objs/Strategy/Strategy.truth b/isis/src/base/objs/Strategy/Strategy.truth index 073fd0ee9f..f8243aba70 100644 --- a/isis/src/base/objs/Strategy/Strategy.truth +++ b/isis/src/base/objs/Strategy/Strategy.truth @@ -478,11 +478,47 @@ Testing composite(...): importGeometry = -true +true +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% repairInvalidGeometry % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + + + +importPolygonGeometry (Repair self-intersection on) = true + +importPolygonGeometry (Repair self-intersection off) = false + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% invalidGeometryAction % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + + + + invalidGeometryAction = disable +invalidGeometryDisabled = true + + invalidGeometryAction = error +"**PROGRAMMER ERROR** Polygon 1 failed to construct geometry - Error: Self-intersection[16.6666666666667 0]." + + invalidGeometryAction = continue +importPolygonGeometry (Repair off; Action continue) = false +invalidGeometryDisabled = false + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/isis/src/base/objs/Strategy/unitTest.cpp b/isis/src/base/objs/Strategy/unitTest.cpp index 2b81444d8c..82c30da81c 100644 --- a/isis/src/base/objs/Strategy/unitTest.cpp +++ b/isis/src/base/objs/Strategy/unitTest.cpp @@ -771,11 +771,82 @@ void IsisMain() { //This call succeeds qDebug() << "importGeometry = "; - qDebug() << geoms.importGeometryA(line2,lines) << Qt::endl; + qDebug() << geoms.importGeometryA(line2,lines); qDebug() << Qt::endl; qDebug() << Qt::endl; + qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; + qDebug() << "% repairInvalidGeometry %"; + qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; + qDebug() << Qt::endl; + qDebug() << Qt::endl; + + SharedResource poly1 = SharedResource(new Resource("Polygon 1")); + + ResourceList polygons; + polygons.append(poly1); + + PvlObject PolygonGeometry("Geom Object"); + PolygonGeometry += PvlKeyword("Type","Intersect"); + PolygonGeometry += PvlKeyword("Name","H5"); + PolygonGeometry += PvlKeyword("GisType","WKT"); + PolygonGeometry += PvlKeyword("GisGeometry","MULTIPOLYGON(((0.0 0.0, 50.0 0.0, 50.0 50.0, 0.0 50.0, 20.0 -10.0, 0.0 0.0)))"); + PolygonGeometry += PvlKeyword("BoundingBox","True"); + PolygonGeometry += PvlKeyword("RepairInvalidGeometry","True"); + DerivedStrategy polygeomsRepair(PolygonGeometry,polygons); + + // The input MULTIPOLYGON is self-intersecting. This call succeeds because + // RepairInvalidGeometry is True and the repair is successful. + qDebug() << "importPolygonGeometry (Repair self-intersection on) =" + << polygeomsRepair.importGeometryA(poly1,polygons) << Qt::endl; + + // Rerun the same test with repair set to false + PolygonGeometry.addKeyword(PvlKeyword("RepairInvalidGeometry", "False"), Pvl::Replace); + DerivedStrategy polygeomsNoRepair(PolygonGeometry,polygons); + qDebug() << "importPolygonGeometry (Repair self-intersection off) =" + << polygeomsNoRepair.importGeometryA(poly1,polygons); + qDebug() << Qt::endl; + qDebug() << Qt::endl; + + + qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; + qDebug() << "% invalidGeometryAction %"; + qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; + qDebug() << Qt::endl; + qDebug() << Qt::endl; + + // confirm Resource from above invalid geometry call has been disabled + // (Default behavior) as a result of the importGeometry failure + qDebug() << " invalidGeometryAction = disable "; + qDebug() << "invalidGeometryDisabled =" << poly1->isDiscarded() << Qt::endl;; + + // Rerun the same test with InvalidGeometryAction set to error + PolygonGeometry.addKeyword(PvlKeyword("InvalidGeometryAction", "Error")); + DerivedStrategy polygeomsNoRepairActionError(PolygonGeometry,polygons); + qDebug() << " invalidGeometryAction = error "; + + try { + qDebug() << polygeomsNoRepairActionError.importGeometryA(poly1,polygons); + } + catch(IException &e){ + qDebug() << e.toString() << Qt::endl; + } + + // Reactivate polygon and rerun test with InvalidGeometryAction + // set to Continue. Here, the import geometry action fails, but + // because InvalidGeometryAction is set to Continue, the polygon + // resource is NOT disabled. + poly1->activate(); + PolygonGeometry.addKeyword(PvlKeyword("InvalidGeometryAction", "Continue"), Pvl::Replace); + DerivedStrategy polygeomsNoRepairActionContinue(PolygonGeometry,polygons); + qDebug() << " invalidGeometryAction = continue "; + qDebug() << "importPolygonGeometry (Repair off; Action continue) =" + << polygeomsNoRepairActionContinue.importGeometryA(poly1,polygons); + qDebug() << "invalidGeometryDisabled =" << poly1->isDiscarded() << Qt::endl; + qDebug() << Qt::endl; + + qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; qDebug() << "% getObjectList %"; qDebug() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << Qt::endl; diff --git a/isis/src/base/objs/Strategy/unitTest.xml b/isis/src/base/objs/Strategy/unitTest.xml index adddd40aeb..1b80950cd0 100644 --- a/isis/src/base/objs/Strategy/unitTest.xml +++ b/isis/src/base/objs/Strategy/unitTest.xml @@ -33,6 +33,11 @@ xsi:noNamespaceSchemaLocation="http://isis.astrogeology.usgs.gov/Schemas/Applica Added tests for processing Galileo NIMS qubs (which are saved in VAX floating point format). References #2368. + + Added additional unit tests for handling self-intersecting polygon geometry. + These cover 1) RepairInvalidGeometry set to true or false; and 2) InvalidGeometryAction + set to disable (Default), continue, or error. References #5612. +