public override int Execute(string logfileName) { this.InitLogging(logfileName); this.LogMessage(this.Name + " QA test execution started."); int currentOid = -1; try { this.LogMessage(this.Name + " Parameters:"); for (int i = 0; i < this.ParameterCount; i++) this.LogMessage(this.get_ParameterText(i) + ": " + this.get_ParameterValue(i)); this.ClearErrors(); // Check the search radius int idx = this.FindParameter("search-radius"); DoubleParameterInfo theInfo = (DoubleParameterInfo)this._params[idx]; double theSearchRadius = (double)theInfo.ParamValue; if (theSearchRadius <= 0) { this.LogMessage("Zero or negative value passed as search radius for " + this.Name + ". Stopping."); return -1; } // Check the cluster tolerance idx = this.FindParameter("cluster-tolerance"); theInfo = (DoubleParameterInfo)this._params[idx]; double theCluster = (double)theInfo.ParamValue; if (theCluster >= theSearchRadius) { this.LogMessage("Cluster tolerance equal to or larger than search radius for " + this.Name + ". Stopping."); return -1; } // Convert them to metres idx = this.FindParameter("search-units"); UnitsParameterInfo theUnitsParam = (UnitsParameterInfo)this._params[idx]; esriUnits theUnits = theUnitsParam.ParamValueInUnits; IUnitConverter theConverter = new UnitConverterClass(); theSearchRadius = theConverter.ConvertUnits(theSearchRadius, theUnits, esriUnits.esriMeters); theCluster = theConverter.ConvertUnits(theCluster, theUnits, esriUnits.esriMeters); // Get the canDefer/canExcept params idx = this.FindParameter(ParameterInfo.PARAM_CANDEFER); bool canDefer = (bool)((ParameterInfo)this._params[idx]).ParamValue; idx = this.FindParameter(ParameterInfo.PARAM_CANEXCEPT); bool canExcept = (bool)((ParameterInfo)this._params[idx]).ParamValue; // Loop through the layers for (int i = 0; i < this.LayerCount - 1; i++) { IFeatureLayer theLayer1 = this.get_Layer(i); if (this.SupportsGeometryType(theLayer1.FeatureClass.ShapeType) == false) continue; // Index the points in theLayer1 IDataset theDataset1 = (IDataset)theLayer1.FeatureClass; IGeoDataset theGeoDS = (IGeoDataset)theDataset1; IEnvelope theAOI = theGeoDS.Extent; if (theAOI == null || theAOI.IsEmpty) continue; theAOI.Project(SpatialReferenceHelper.BCAlbersSpatialReference); double theCellsize = theAOI.Width / 100; this.LogMessage("Indexing features in " + theDataset1.Name + ". Cellsize (m): " + theCellsize); PointCollectionIndex theIndex = new PointCollectionIndex(theCellsize); IFeatureCursor theFCursor1 = null; if (this.ConstrainToSelection) { ICursor theCursor = null; IFeatureSelection theFSel = (IFeatureSelection)theLayer1; theFSel.SelectionSet.Search(null, true, out theCursor); theFCursor1 = (IFeatureCursor)theCursor; } else { theFCursor1 = theLayer1.Search(null, true); } IFeature theFeature1 = theFCursor1.NextFeature(); while (theFeature1 != null) { if (theFeature1.Shape != null && theFeature1.Shape.IsEmpty == false) { IPointCollection thePtColl = (IPointCollection)theFeature1.Shape; for (int k = 0; k < thePtColl.PointCount; k++) { theIndex.AddPoint(new IndexPoint(theFeature1.OID, k, thePtColl.get_Point(k))); } } theFeature1 = theFCursor1.NextFeature(); } // Release the cursor Marshal.ReleaseComObject(theFCursor1); theFCursor1 = null; if (theIndex.PointCount > 0) { // Use the index to analyze the other layers for (int j = i+1; j < this.LayerCount; j++) { IFeatureLayer theLayer2 = this.get_Layer(j); if (this.SupportsGeometryType(theLayer2.FeatureClass.ShapeType) == false) continue; IDataset theDataset2 = (IDataset)theLayer2.FeatureClass; IFeatureCursor theFCursor2 = null; if (this.ConstrainToSelection) { ICursor theCursor = null; IFeatureSelection theFSel = (IFeatureSelection)theLayer2; theFSel.SelectionSet.Search(null, true, out theCursor); theFCursor2 = (IFeatureCursor)theCursor; } else { theFCursor2 = theLayer2.Search(null, true); } IFeature theFeature2 = theFCursor2.NextFeature(); while (theFeature2 != null) { if (theFeature2.Shape != null && theFeature2.Shape.IsEmpty == false) { IGeometry theCopy = theFeature2.ShapeCopy; theCopy.Project(SpatialReferenceHelper.BCAlbersSpatialReference); IPointCollection thePtColl = (IPointCollection)theCopy; for (int k = 0; k < thePtColl.PointCount; k++) { // Find the closest point in the index. If it's less than // the search radius and larger than the cluster tolerance, // log as an error IPoint thePoint = thePtColl.get_Point(k); IndexPoint theIdxPoint = theIndex.get_ClosestPointWithinLimits( thePoint.X, thePoint.Y, theCluster, theSearchRadius); if (theIdxPoint != null) { // Build a straight line between the offending points IPolyline thePolyline = new PolylineClass(); thePolyline.Project(SpatialReferenceHelper.BCAlbersSpatialReference); ((IPointCollection)thePolyline).AddPoint(thePoint, ref this._missing, ref this._missing); IPoint theOtherPoint = new PointClass(); theOtherPoint.Project(SpatialReferenceHelper.BCAlbersSpatialReference); theOtherPoint.PutCoords(theIdxPoint.X, theIdxPoint.Y); ((IPointCollection)thePolyline).AddPoint(theOtherPoint, ref this._missing, ref this._missing); // Get the distance double dist = PointCollectionIndex.get_Distance(thePoint.X, thePoint.Y, theOtherPoint.X, theOtherPoint.Y); // Project to geographics for error reporting thePolyline.Project(SpatialReferenceHelper.GeographicReference); // Error point will be 1/2 way along line between points IPoint theErrorPoint = new PointClass(); thePolyline.QueryPoint(esriSegmentExtension.esriNoExtension, 0.5, true, theErrorPoint); DataQualityError theError = new DataQualityError(this.Name, canDefer, canExcept); theError.Location = theErrorPoint; // If it's in the smallest 10% of the search radius, mark high severity // 50%, medium if (dist <= theSearchRadius / 10) theError.Severity = 1; else if (dist <= theSearchRadius / 2) theError.Severity = 2; else theError.Severity = 3; theError.Description = "Points almost but not quite co-located"; ExtendedInfo theExtInfo = new ExtendedInfo(); theExtInfo.AddProperty("Feature class 1", theDataset1.Name); theExtInfo.AddProperty("Feature class 2", theDataset2.Name); if (theLayer1.FeatureClass.HasOID) theExtInfo.AddProperty("Feature ID 1", theIdxPoint.SourceFeatureID.ToString()); if (theLayer2.FeatureClass.HasOID) theExtInfo.AddProperty("Feature ID 2", theFeature2.OID.ToString()); theExtInfo.AddProperty("Distance", String.Format("{0:0.## metres}", dist)); theExtInfo.AddProperty("From point x", thePolyline.FromPoint.X.ToString()); theExtInfo.AddProperty("From point y", thePolyline.FromPoint.Y.ToString()); theExtInfo.AddProperty("To point x", thePolyline.ToPoint.X.ToString()); theExtInfo.AddProperty("To point y", thePolyline.ToPoint.Y.ToString()); theError.ExtendedData = theExtInfo.WriteXML(); //this.LogMessage(theError.ExtendedData); this._errors.Add(theError); } } } theFeature2 = theFCursor2.NextFeature(); } } } } this.LogMessage("Number of errors found: " + this.ErrorCount); this.LogMessage("Test " + this.Name + " successful."); } catch (Exception ex) { this.LogMessage("Exception caught: \n" + ex.Message + "\n" + ex.StackTrace.ToString()); this.LogMessage("id of the current polygon: " + currentOid); return -1; } finally { this.StopLogging(); } return this.ErrorCount; }
private void EvaluateEditInSee( IFeature workingFeature, IFeature pristineFeature, IPolygon seeBounds, string seeID, string pkWhereClause, double shiftLimit) { bool bWorkingFeatureOutside = false; bool bPristineFeatureOutside = false; bool bUpdateOutOfBounds = false; HashSet<IPoint> theOffendingVertices = null; IRelationalOperator theRelOp = (IRelationalOperator)seeBounds; IGeometry theWorkingShape = null; IGeometry thePristineShape = null; try { if (workingFeature != null) { theWorkingShape = this.ShapeCopy(workingFeature); if (theRelOp.Contains(theWorkingShape) == false) { bWorkingFeatureOutside = true; } } if (pristineFeature != null) { thePristineShape = this.ShapeCopy(pristineFeature); if (theRelOp.Contains(thePristineShape) == false) { bPristineFeatureOutside = true; } } bUpdateOutOfBounds = bWorkingFeatureOutside || bPristineFeatureOutside; if (bUpdateOutOfBounds && theWorkingShape != null && thePristineShape != null) { IGeometry theWorkingShapeOutsideSee = ((ITopologicalOperator)theWorkingShape).Difference(seeBounds); IGeometry thePristineShapeOutsideSee = ((ITopologicalOperator)thePristineShape).Difference(seeBounds); if (theWorkingShapeOutsideSee == null || theWorkingShapeOutsideSee.IsEmpty) { bUpdateOutOfBounds = false; } else if (((IRelationalOperator)theWorkingShapeOutsideSee).Equals(thePristineShapeOutsideSee)) { // shape unchanged bUpdateOutOfBounds = false; } else if (!((IRelationalOperator)theWorkingShapeOutsideSee).Equals(thePristineShapeOutsideSee)) { IGeometry pristineClone = pristineFeature.ShapeCopy; IGeometry workingClone = workingFeature.ShapeCopy; string theTxID = this.TxID; IQueryFilter theQF = new QueryFilterClass(); theQF.WhereClause = SEE_ID_FIELD_NAME + " = '" + theTxID + "'"; IFeatureCursor theFCursor = this.SEELayer.Search(theQF, false); IFeature theSee = theFCursor.NextFeature(); Marshal.ReleaseComObject(theFCursor); theFCursor = null; IGeometry seeClone = theSee.ShapeCopy; // simplify the clones (see SEE.cs line 92?) ITopologicalOperator2 simplifyPristineTo = (ITopologicalOperator2)pristineClone; simplifyPristineTo.IsKnownSimple_2 = false; simplifyPristineTo.Simplify(); ITopologicalOperator2 simplifyWorkingTo = (ITopologicalOperator2)workingClone; simplifyWorkingTo.IsKnownSimple_2 = false; simplifyWorkingTo.Simplify(); // clone the SEE. Reproject the SEE to the pristines projection ITopologicalOperator2 simplifySeeTo = (ITopologicalOperator2)seeClone; simplifySeeTo.IsKnownSimple_2 = false; simplifySeeTo.Simplify(); seeClone.Project(pristineClone.SpatialReference); ITopologicalOperator baseWorkingTo = (ITopologicalOperator)workingClone; IGeometry bufferWorkingShape = baseWorkingTo.Buffer(shiftLimit); ITopologicalOperator pristineTo = (ITopologicalOperator)pristineClone; IGeometry diffedGeom = pristineTo.Difference(bufferWorkingShape); diffedGeom.SpatialReference = workingClone.SpatialReference; ITopologicalOperator basePristineTo = (ITopologicalOperator)pristineClone; IGeometry bufferPristineShape = basePristineTo.Buffer(shiftLimit); ITopologicalOperator workingTo = (ITopologicalOperator)workingClone; IGeometry secondDiffedGeom = workingTo.Difference(bufferPristineShape); secondDiffedGeom.SpatialReference = pristineClone.SpatialReference; theOffendingVertices = new HashSet<IPoint>(); GetOffendingVertices(diffedGeom, seeClone, theOffendingVertices); GetOffendingVertices(secondDiffedGeom, seeClone, theOffendingVertices); if(theOffendingVertices.Count == 0) bUpdateOutOfBounds = false; while (Marshal.ReleaseComObject(baseWorkingTo) > 0) { } while (Marshal.ReleaseComObject(bufferWorkingShape) > 0) { } while (Marshal.ReleaseComObject(pristineTo) > 0) { } while (Marshal.ReleaseComObject(diffedGeom) > 0) { } while (Marshal.ReleaseComObject(basePristineTo) > 0) { } while (Marshal.ReleaseComObject(bufferPristineShape) > 0) { } while (Marshal.ReleaseComObject(workingTo) > 0) { } while (Marshal.ReleaseComObject(secondDiffedGeom) > 0) { } while (Marshal.ReleaseComObject(pristineClone) > 0) { } while (Marshal.ReleaseComObject(workingClone) > 0) { } diffedGeom = null; pristineTo = null; bufferWorkingShape = null; baseWorkingTo = null; secondDiffedGeom = null; workingTo = null; bufferPristineShape = null; basePristineTo = null; pristineClone = null; workingClone = null; } else { // TODO: This index is causing errors on rebuild geometries using // TODO: topology tools. Verify vertext and shape match, not using an index // Evaluate if this is a real edit or just vertex shifting // Build point collection index for the pristine shape // cellsize: average of thePristineShapeOutsideSee width and height divided by 10 PointCollectionIndex theIndex = new PointCollectionIndex(thePristineShapeOutsideSee.Envelope.Width + thePristineShapeOutsideSee.Envelope.Height / 2 / 10); IPointCollection thePtColl = (IPointCollection)thePristineShapeOutsideSee; for (int i = 0; i < thePtColl.PointCount; i++) theIndex.AddPoint(new IndexPoint(pristineFeature.OID, i, thePtColl.get_Point(i))); // Check each working vertex to see if there is a vertex within the shift limit // If not, this vertex needs flagging theOffendingVertices = new HashSet<IPoint>(); thePtColl = (IPointCollection)theWorkingShapeOutsideSee; for (int i = 0; i < thePtColl.PointCount; i++) { IPoint theWorkingPoint = thePtColl.get_Point(i); IndexPoint thePristinePoint = theIndex.get_ClosestPointWithinLimits(theWorkingPoint.X, theWorkingPoint.Y, -1, shiftLimit); if (thePristinePoint == null) { theOffendingVertices.Add(this.get_ErrorPoint(theWorkingPoint)); } } if (theOffendingVertices.Count == 0) bUpdateOutOfBounds = false; } while (Marshal.ReleaseComObject(theWorkingShapeOutsideSee) > 0){} while (Marshal.ReleaseComObject(thePristineShapeOutsideSee) > 0) { } theWorkingShapeOutsideSee = null; thePristineShapeOutsideSee = null; } } catch (Exception ex) { throw ex; } if (bUpdateOutOfBounds) { // This needs to be flagged. The only exceptions are: // - a spatial update where the difference between the working and pristine shapes falls within the SEE // - an attribute-only update if (theOffendingVertices == null) { theOffendingVertices = new HashSet<IPoint>(); if (theWorkingShape != null) theOffendingVertices.Add(this.get_ErrorPoint(theWorkingShape)); else theOffendingVertices.Add(this.get_ErrorPoint(thePristineShape)); } foreach (IPoint theErrorPoint in theOffendingVertices) { theErrorPoint.Project(SpatialReferenceHelper.GeographicReference); DataQualityError theError = new DataQualityError(this.Name, false, false); theError.Location = theErrorPoint; theError.Severity = 1; if (thePristineShape == null) theError.Description = "Inserted shape extends outside of the SEE area"; else if (theWorkingShape == null) theError.Description = "Deleted feature extends outside of the SEE area"; else theError.Description = "Edited shape extends outside of the SEE area"; ExtendedInfo theInfo = new ExtendedInfo(); string theSourceDSName = ""; if (workingFeature != null) { IDataset theSourceDS = (IDataset)workingFeature.Table; theSourceDSName = theSourceDS.Name; } else { IDataset theSourceDS = (IDataset)pristineFeature.Table; theSourceDSName = "P_" + theSourceDS.Name; } theInfo.AddProperty("Feature class", theSourceDSName); theInfo.AddProperty("Business ID: ", pkWhereClause); if (workingFeature != null) theInfo.AddProperty("Working Feature ID", workingFeature.OID.ToString()); theInfo.AddProperty("SEE ID", seeID.ToString()); theError.ExtendedData = theInfo.WriteXML(); //this.LogMessage(theError.ExtendedData); this._errors.Add(theError); } } }
public override int Execute(string logfileName) { this.InitLogging(logfileName); this.LogMessage(this.Name + " QA test execution started."); bool bProblemsRunning = false; int currentOid = -1; try { this.LogMessage(this.Name + " Parameters:"); for (int i = 0; i < this.ParameterCount; i++) this.LogMessage(this.get_ParameterText(i) + ": " + this.get_ParameterValue(i)); this.ClearErrors(); for (int layerIdx = 0; layerIdx < this.LayerCount; layerIdx++) { IFeatureLayer theFLayer = this.get_Layer(layerIdx); IFeatureClass theFClass = theFLayer.FeatureClass; IDataset theDataset = (IDataset)theFClass; if (!this.SupportsGeometryType(theFClass.ShapeType)) { this.LogMessage("Gap detector does not support the geometry type in featureclass " + theDataset.Name); bProblemsRunning = true; } else if (theFClass.FeatureDataset == null) { this.LogMessage("Featureclass " + theDataset.Name + " is not in a feature dataset"); bProblemsRunning = true; } else { this.LogMessage("Looking for topology with gap rule including featureclass " + theDataset.Name); ITopology theTopology = null; ArrayList theTRules = new ArrayList(); ITopologyRule theTopoRule = null; IEnumDataset theDatasetEnum = theFClass.FeatureDataset.Subsets; IDataset theSiblingDS = theDatasetEnum.Next(); while (theSiblingDS != null) { if (theSiblingDS.Type == esriDatasetType.esriDTTopology) { // Look for a gap rule featuring theFClass theTopology = (ITopology)theSiblingDS; ITopologyRuleContainer theTRContainer = (ITopologyRuleContainer)theTopology; IEnumRule theRules = theTRContainer.get_RulesByClass(theFClass.FeatureClassID); theTopoRule = (ITopologyRule)theRules.Next(); while (theTopoRule != null) { if (theTopoRule.TopologyRuleType == esriTopologyRuleType.esriTRTAreaNoGaps) theTRules.Add(theTopoRule); theTopoRule = (ITopologyRule)theRules.Next(); } } theSiblingDS = theDatasetEnum.Next(); } if (theTopology == null || theTRules.Count == 0) { this.LogMessage("Featureclass " + theDataset.Name + " is not part of a Gap topology rule"); bProblemsRunning = true; continue; } if (this.ValidateTopology(theTopology) == false) { this.LogMessage("The topology " + ((IDataset)theTopology).Name + " could not be validated"); bProblemsRunning = true; continue; } if (theTopology.State == esriTopologyState.esriTSAnalyzedWithErrors) { this.LogMessage("Indexing gap errors in featureclass " + theDataset.Name + " as it relates to Topology " + ((IDataset)theTopology).Name); IErrorFeatureContainer theTopoErrorContainer = (IErrorFeatureContainer)theTopology; int count = 0; int countIncrement = 500; DoubleParameterInfo theParam = (DoubleParameterInfo)this._params[this.FindParameter("ignore-gap-larger-than")]; double maxGap = (double)theParam.ParamValue; theParam = (DoubleParameterInfo)this._params[this.FindParameter("ignore-gap-smaller-than")]; double minGap = (double)theParam.ParamValue; // Convert gap limits to metres UnitsParameterInfo theUnits = (UnitsParameterInfo)this._params[this.FindParameter("gap-units")]; IUnitConverter theConverter = new UnitConverterClass(); maxGap = theConverter.ConvertUnits(maxGap, theUnits.ParamValueInUnits, esriUnits.esriMeters); minGap = theConverter.ConvertUnits(minGap, theUnits.ParamValueInUnits, esriUnits.esriMeters); for (int j = 0; j < theTRules.Count; j++) { theTopoRule = (ITopologyRule)theTRules[j]; this.LogMessage("Processing rule " + (j+1) + " of " + theTRules.Count); double cellsize = maxGap * 5; PointCollectionIndex theIndex = new PointCollectionIndex(cellsize); IGeoDataset theGeoDS = (IGeoDataset)theTopology; IEnumTopologyErrorFeature theTopoErrors = theTopoErrorContainer.get_ErrorFeatures(theGeoDS.SpatialReference, theTopoRule, null, true, false); ITopologyErrorFeature theTopoErrorFeature = theTopoErrors.Next(); while (theTopoErrorFeature != null) { currentOid = theTopoErrorFeature.ErrorID; IPointCollection thePtColl = (IPointCollection)((IFeature)theTopoErrorFeature).Shape; for (int i = 0; i < thePtColl.PointCount; i++) { // ErrorID the same as ObjectID on the error feature theIndex.AddPoint(new IndexPoint(theTopoErrorFeature.ErrorID, i, thePtColl.get_Point(i))); } if (++count % countIncrement == 0) { this.LogMessage("Indexed " + count + " topology errors..."); } theTopoErrorFeature = theTopoErrors.Next(); } this.LogMessage("Getting pairs of points within tolerances..."); ArrayList theResults = theIndex.PointPairsWithinLimits(minGap, maxGap); this.LogMessage(theResults.Count + " pairs of points found within gap tolerances. Checking."); // Get info from the params int index = this.FindParameter("angle-tolerance-degrees"); theParam = (DoubleParameterInfo)this._params[index]; double theLimitRadians = Math.PI * (double)theParam.ParamValue / 180; index = this.FindParameter("eliminate-pinches"); bool allowPinches = !(bool)((ParameterInfo)this._params[index]).ParamValue; ArrayList theList = this.ProcessPotentialGapErrors(theResults, theTopology, theFClass, theLimitRadians, allowPinches); if (theList != null) { index = this.FindParameter(ParameterInfo.PARAM_CANDEFER); bool canDefer = (bool)((ParameterInfo)this._params[index]).ParamValue; index = this.FindParameter(ParameterInfo.PARAM_CANEXCEPT); bool canExcept = (bool)((ParameterInfo)this._params[index]).ParamValue; theList = this.WeedErrors(theList, canDefer, canExcept, minGap, maxGap); } if (theList != null) { foreach (DataQualityError dqe in theList) this._errors.Add(dqe); } } } else { this.LogMessage("Topology has no errors"); } } } this.LogMessage("Number of errors found: " + this.ErrorCount); this.LogMessage("Test " + this.Name + " successful."); } catch (Exception ex) { this.LogMessage("Exception caught: \n" + ex.Message + "\n" + ex.StackTrace.ToString()); this.LogMessage("id of the current polygon: " + currentOid); return -1; } finally { this.StopLogging(); } if (bProblemsRunning) return -1; return this.ErrorCount; }