List<RelatedCurve> GetParallelCurveMatchFeatures(IFeatureClass FeatureClass, IFeature inFeature, IPolycurve inPolycurve, string WhereClause)
            //double AngleToleranceTangentCompareInDegrees, double OrthogonalSearchDistance,
            //                                        out int outFoundLinesCount, out int outFoundParallelCurvesCount, ref List<RelatedCurve> CurveInfoFromNeighbours)
        {
            List<RelatedCurve> CurveInfoFromNeighbours = new List<RelatedCurve>();

            ILine inGeomChord = (ILine)new Line();
            inGeomChord.PutCoords(inPolycurve.FromPoint, inPolycurve.ToPoint);
            IVector3D inGeomVector = (IVector3D)new Vector3D();
            inGeomVector.PolarSet(inGeomChord.Angle, 0, 1);

            //generate line segments that are perpendicular to the in feature at half way

            IGeometryBag queryGeomBag = (IGeometryBag)new GeometryBag();
            IGeometryCollection queryGeomPartCollection = (IGeometryCollection)queryGeomBag;

            IGeometry queryMultiPartPolyLine = (IGeometry)new Polyline(); //qi
            IGeoDataset pGeoDS = (IGeoDataset)FeatureClass;
            ISpatialReference spatialRef = pGeoDS.SpatialReference;
            queryMultiPartPolyLine.SpatialReference = spatialRef;

            IGeometryCollection queryGeomCollection = queryMultiPartPolyLine as IGeometryCollection;

            ILine pNormalLine = (ILine)new Line(); //new
            for (int i = -1; i < 2; i = i + 2)
            {
                double dOffset = CurveByInferenceSettings.Instance.OrthogonalSearchDistance * i;

                inPolycurve.QueryNormal(esriSegmentExtension.esriNoExtension, 0.5, true, dOffset, pNormalLine);
                ILine pThisLine = (ILine)new Line();

                pThisLine.PutCoords(pNormalLine.FromPoint, pNormalLine.ToPoint);
                queryGeomPartCollection.AddGeometry(pThisLine);

                //Although each line is connected to the other, create a new path for each line 
                //this allows for future changes in case the input needs to be altered to separate paths.

                ISegmentCollection newPath = (ISegmentCollection)new Path();
                object obj = Type.Missing;
                newPath.AddSegment((ISegment)pThisLine, ref obj, ref obj);
                //The spatial reference associated with geometryCollection will be assigned to all incoming paths and segments.
                queryGeomCollection.AddGeometry(newPath as IGeometry, ref obj, ref obj);
            }

            //search for records that intersect these perpendicular geometries
            IQueryFilter filter = new SpatialFilter();
            filter.SubFields = String.Format("{0}, {1}, {2}, {3}", FeatureClass.OIDFieldName, CurveByInferenceSettings.Instance.RadiusFieldName, CurveByInferenceSettings.Instance.CenterpointIDFieldName, FeatureClass.ShapeFieldName);
            ISpatialFilter spatialFilter = (ISpatialFilter)filter;
            
            //Can't add any additional filtering on centerpointid and radius field because the straight lines are needed
            spatialFilter.WhereClause = WhereClause;
            spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
            spatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;

            spatialFilter.Geometry = queryGeomBag;

            IFeatureCursor pFeatCursLines = null;
            try
            {
                pFeatCursLines = FeatureClass.Search(spatialFilter, false);
            }
            catch (Exception ex)
            {
                messageBox.Show(ex.Message);
                return CurveInfoFromNeighbours;
            }

            int idxCenterPointID = pFeatCursLines.Fields.FindField(CurveByInferenceSettings.Instance.CenterpointIDFieldName);
            int idxRadius = pFeatCursLines.Fields.FindField(CurveByInferenceSettings.Instance.RadiusFieldName);

            IPoint midpoint = new Point();
            inPolycurve.QueryPoint(esriSegmentExtension.esriNoExtension, 0.5, true, midpoint);
            double lengthFiler = inPolycurve.Length * 3;
            double closestStraighLine = Double.MaxValue;
            IFeature pFeat = null;
            while ((pFeat = pFeatCursLines.NextFeature()) != null)
            {
                if (inFeature.OID == pFeat.OID)
                    continue;

                IGeometry pFoundLineGeom = pFeat.ShapeCopy;
                IPolyline pFoundPolyline = pFoundLineGeom as IPolyline;

                ITopologicalOperator6 pTopoOp6 = (ITopologicalOperator6)queryMultiPartPolyLine;
                IGeometry intersectionPoint = pTopoOp6.IntersectEx(pFoundLineGeom, false, esriGeometryDimension.esriGeometry0Dimension);
                if (intersectionPoint == null || intersectionPoint.IsEmpty)
                {
                    // there isn't actually an intersection between the features
                    Marshal.ReleaseComObject(pFeat);
                    continue;
                }

                //distance from intersection of tangent line and found feature to the start point of the tangent line
                 
                double distanceToLine = ((IProximityOperator)intersectionPoint).ReturnDistance(midpoint);

                //if the feature has no radius attribute, skip.
                double dRadius = pFeat.get_Value(idxRadius) is DBNull ? 0 : (double)pFeat.get_Value(idxRadius);
                int? centerpointID = pFeat.get_Value(idxCenterPointID) is DBNull ? null : (int?)pFeat.get_Value(idxCenterPointID);
                if (dRadius == 0 || centerpointID == null)
                {//null centrpointID so skip.
                    if (closestStraighLine > distanceToLine && pFoundPolyline.Length > lengthFiler)
                    {
                        closestStraighLine = distanceToLine;
                        CurveInfoFromNeighbours.RemoveAll(w=>w.DistanceToLine > closestStraighLine);
                    }
                    Marshal.ReleaseComObject(pFeat);
                    continue;
                }

                //out past a straight line
                if (closestStraighLine < distanceToLine)
                    continue;

                ISegmentCollection pFoundLineGeomSegs = pFoundLineGeom as ISegmentCollection;
                bool bHasCurves = false;
                pFoundLineGeomSegs.HasNonLinearSegments(ref bHasCurves);
                if (!bHasCurves)
                {
                    Marshal.ReleaseComObject(pFeat);
                    continue;
                }

                IPointCollection5 PtColl = (IPointCollection5)intersectionPoint;
                if (PtColl.PointCount > 1)
                {
                    // the intersection isn't a point
                    Marshal.ReleaseComObject(pFeat);
                    continue;
                }
                IPolycurve pPolyCurve4Tangent = pFoundLineGeom as IPolycurve;

                for (int j = 0; j < PtColl.PointCount; j++)
                {
                    IPoint p = PtColl.get_Point(j);
                    IPoint outPoint = (IPoint)new Point();
                    double dDistanceAlong = 0;
                    double dDistanceFromCurve = 0;
                    bool bOffsetRight = true;

                    //work out if the point is to the left or right of the original 
                    inPolycurve.QueryPointAndDistance(esriSegmentExtension.esriNoExtension, p, false, outPoint,
                      ref dDistanceAlong, ref dDistanceFromCurve, ref bOffsetRight);

                    ILine pTangent = (ILine)new Line();
                    dDistanceAlong = 0;
                    dDistanceFromCurve = 0;
                    bool bOnRight = true;

                    pPolyCurve4Tangent.QueryPointAndDistance(esriSegmentExtension.esriNoExtension, p, false, outPoint,
                      ref dDistanceAlong, ref dDistanceFromCurve, ref bOnRight);
                    pPolyCurve4Tangent.QueryTangent(esriSegmentExtension.esriNoExtension, dDistanceAlong, false, 100, pTangent);

                    //compare the tangent bearing with the normal to check for orthogonality
                    IVector3D vecTangent = (IVector3D)new Vector3D();
                    vecTangent.PolarSet(pTangent.Angle, 0, 1);

                    IVector3D vecNormal = (IVector3D)new Vector3D();
                    vecNormal.PolarSet(pNormalLine.Angle, 0, 1);

                    ILine pHitDistanceForRadiusDifference = (ILine)new Line();
                    pHitDistanceForRadiusDifference.PutCoords(pNormalLine.FromPoint, outPoint);
                    double dRadiusDiff = pHitDistanceForRadiusDifference.Length;

                    double dDotProd = vecTangent.DotProduct(vecNormal);
                    double dAngleCheck = Math.Acos(dDotProd) * 180 / Math.PI; //in degrees
                    dAngleCheck = Math.Abs(dAngleCheck - 90);

                    if (dAngleCheck < CurveByInferenceSettings.Instance.AngleToleranceTangentCompareInDegrees)
                    {
                        //work out concavity orientation with respect to the original line using the radius sign and dot product
                        dDotProd = inGeomVector.DotProduct(vecTangent);
                        double dTangentCheck = Math.Acos(dDotProd) * 180 / Math.PI; // in degrees
                        //dTangentCheck at this point should be close to 0 or 180 degrees.
                        
                        bool bIsConvex = ((dTangentCheck < 90 && dRadius < 0 && !bOffsetRight) ||
                                          (dTangentCheck > 90 && dRadius > 0 && !bOffsetRight) ||
                                          (dTangentCheck < 90 && dRadius > 0 && bOffsetRight) ||
                                          (dTangentCheck > 90 && dRadius < 0 && bOffsetRight));

                        double dUnitSignChange = 1;

                        if (!bIsConvex)
                            dUnitSignChange = -1;

                        double dDerivedRadius = (Math.Abs(dRadius)) + dRadiusDiff * dUnitSignChange;

                        dUnitSignChange = 1;
                        //now compute inferred left/right for candidate
                        if (bIsConvex && !bOffsetRight)
                            dUnitSignChange = -1;

                        if (!bIsConvex && bOffsetRight)
                            dUnitSignChange = -1;

                        dDerivedRadius = dDerivedRadius * dUnitSignChange;

                        //string sHarvestedCurveInfo = pFeat.OID.ToString() + "," + dDerivedRadius.ToString("#.000") + "," + centerpointID.ToString() + "," + dRadiusDiff.ToString("#.000");
                        CurveInfoFromNeighbours.Add(new RelatedCurve(pFeat.OID, dDerivedRadius, centerpointID.Value, distanceToLine, RelativeOrientation.Parallel));
                    }
                }

                Marshal.ReleaseComObject(pFeat);
            }
            Marshal.FinalReleaseComObject(pFeatCursLines);

            return CurveInfoFromNeighbours;
        }
        public bool HasParallelCurveMatchFeatures(IFeatureClass FeatureClass, IPolycurve inPolycurve, string WhereClause,
      double AngleToleranceTangentCompareInDegrees, double OrthogonalSearchDistance,
       out int outFoundLinesCount, out int outFoundParallelCurvesCount, ref List<string> CurveInfoFromNeighbours)
        {
            outFoundLinesCount = 0;
              outFoundParallelCurvesCount = 0;

              ILine pOriginalChord = new Line();
              pOriginalChord.PutCoords(inPolycurve.FromPoint, inPolycurve.ToPoint);
              IVector3D vecOriginalSelected = new Vector3DClass();
              vecOriginalSelected.PolarSet(pOriginalChord.Angle, 0, 1);

              int idxRadius = FeatureClass.FindField("RADIUS");
              if (idxRadius == -1)
            return false;

              int idxCenterPointID = FeatureClass.FindField("CENTERPOINTID");
              if (idxCenterPointID == -1)
            return false;

              object val = null;

              IGeometryBag pGeomBag = new GeometryBagClass();
              IGeometryCollection pGeomColl = (IGeometryCollection)pGeomBag;

              IGeometry MultiPartPolyLine = new PolylineClass(); //qi
              IGeoDataset pGeoDS = (IGeoDataset)FeatureClass;
              ISpatialReference spatialRef = pGeoDS.SpatialReference;
              MultiPartPolyLine.SpatialReference = spatialRef;

              IGeometryCollection geometryCollection2 = MultiPartPolyLine as IGeometryCollection;

              ILine pNormalLine = new Line(); //new
              for (int i = -1; i < 2; i = i + 2)
              {
            double dOffset = OrthogonalSearchDistance * i;

            inPolycurve.QueryNormal(esriSegmentExtension.esriNoExtension, 0.5, true, dOffset, pNormalLine);
            ILine pThisLine = new Line();

            pThisLine.PutCoords(pNormalLine.FromPoint, pNormalLine.ToPoint);
            pGeomColl.AddGeometry(pThisLine);

            //Although each line is connected to the other, create a new path for each line
            //this allows for future changes in case the input needs to be altered to separate paths.

            ISegmentCollection newPath = new PathClass();
            object obj = Type.Missing;
            newPath.AddSegment((ISegment)pThisLine, ref obj, ref obj);
            //The spatial reference associated with geometryCollection will be assigned to all incoming paths and segments.
            geometryCollection2.AddGeometry(newPath as IGeometry, ref obj, ref obj);
              }

              ISpatialFilter pSpatFilt = new SpatialFilter();
              pSpatFilt.WhereClause = WhereClause;
              pSpatFilt.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
              pSpatFilt.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;

              pSpatFilt.Geometry = pGeomBag;

              IFeatureCursor pFeatCursLines = null;
              try
              {
            pFeatCursLines = FeatureClass.Search(pSpatFilt, false);
              }
              catch (Exception ex)
              {
            MessageBox.Show(ex.Message);
            return false;
              }

              IFeature pFeat = pFeatCursLines.NextFeature();
              while (pFeat != null)
              {
            IGeometry pFoundLineGeom = pFeat.ShapeCopy;

            //if the feature has no radius attribute, skip.
            double dRadius = 0;
            int iCtrPoint = -1;
            val = pFeat.get_Value(idxRadius);
            if (val == DBNull.Value)
              dRadius = 0;
            else
              dRadius = (double)val;

            if (dRadius == 0)
            {//null or zero radius so skip.
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }

            val = pFeat.get_Value(idxCenterPointID);
            if (val == DBNull.Value)
            {//null centrpointID so skip.
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }

            iCtrPoint = (int)val;

            ITopologicalOperator6 pTopoOp6 = (ITopologicalOperator6)MultiPartPolyLine;
            IGeometry pResultGeom = pTopoOp6.IntersectEx(pFoundLineGeom, false, esriGeometryDimension.esriGeometry0Dimension);
            if (pResultGeom == null)
            {
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }
            if (pResultGeom.IsEmpty)
            {
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }

            ISegmentCollection pFoundLineGeomSegs = pFoundLineGeom as ISegmentCollection;
            bool bHasCurves = false;
            pFoundLineGeomSegs.HasNonLinearSegments(ref bHasCurves);
            if (!bHasCurves)
            {
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }

            IPointCollection5 PtColl = (IPointCollection5)pResultGeom;

            if (PtColl.PointCount > 1)
            {
              Marshal.ReleaseComObject(pFeat);
              pFeat = pFeatCursLines.NextFeature();
              continue;
            }
            IPolycurve pPolyCurve4Tangent = pFoundLineGeom as IPolycurve;

            for (int j = 0; j < PtColl.PointCount; j++)
            {
              IPoint p = PtColl.get_Point(j);
              IPoint outPoint = new Point();
              double dDistanceAlong = 0;
              double dDistanceFromCurve = 0;
              bool bOffsetRight = true;

              //work out if the point is to the left or right of the original
              inPolycurve.QueryPointAndDistance(esriSegmentExtension.esriNoExtension, p, false, outPoint,
            ref dDistanceAlong, ref dDistanceFromCurve, ref bOffsetRight);

              ILine pTangent = new Line();
              dDistanceAlong = 0;
              dDistanceFromCurve = 0;
              bool bOnRight = true;

              pPolyCurve4Tangent.QueryPointAndDistance(esriSegmentExtension.esriNoExtension, p, false, outPoint,
            ref dDistanceAlong, ref dDistanceFromCurve, ref bOnRight);
              pPolyCurve4Tangent.QueryTangent(esriSegmentExtension.esriNoExtension, dDistanceAlong, false, 100, pTangent);

              //compare the tangent bearing with the normal to check for orthogonality
              IVector3D vecTangent = new Vector3DClass();
              vecTangent.PolarSet(pTangent.Angle, 0, 1);

              IVector3D vecNormal = new Vector3DClass();
              vecNormal.PolarSet(pNormalLine.Angle, 0, 1);

              ILine pHitDistanceForRadiusDifference = new Line();
              pHitDistanceForRadiusDifference.PutCoords(pNormalLine.FromPoint, outPoint);
              double dRadiusDiff = pHitDistanceForRadiusDifference.Length;

              double dDotProd = vecTangent.DotProduct(vecNormal);
              double dAngleCheck = Math.Acos(dDotProd) * 180 / Math.PI; //in degrees
              dAngleCheck = Math.Abs(dAngleCheck - 90);

              if (dAngleCheck < AngleToleranceTangentCompareInDegrees)
              {
            //work out concavity orientation with respect to the original line using the radius sign and dot product
            dDotProd = vecOriginalSelected.DotProduct(vecTangent);
            double dTangentCheck = Math.Acos(dDotProd) * 180 / Math.PI; // in degrees
            //dTangentCheck at this point should be close to 0 or 180 degrees.
            outFoundLinesCount++;

            bool bIsConvex = ((dTangentCheck < 90 && dRadius < 0 && !bOffsetRight) ||
                              (dTangentCheck > 90 && dRadius > 0 && !bOffsetRight) ||
                              (dTangentCheck < 90 && dRadius > 0 && bOffsetRight) ||
                              (dTangentCheck > 90 && dRadius < 0 && bOffsetRight));

            double dUnitSignChange = 1;

            if (!bIsConvex)
              dUnitSignChange = -1;

            double dDerivedRadius = (Math.Abs(dRadius)) + dRadiusDiff * dUnitSignChange;

            dUnitSignChange = 1;
            //now compute inferred left/right for candidate
            if (bIsConvex && !bOffsetRight)
              dUnitSignChange = -1;

            if (!bIsConvex && bOffsetRight)
              dUnitSignChange = -1;

            dDerivedRadius = dDerivedRadius * dUnitSignChange;

            string sHarvestedCurveInfo = pFeat.OID.ToString() + "," + dDerivedRadius.ToString("#.000") + "," +
              iCtrPoint.ToString() + "," + dRadiusDiff.ToString("#.000");
            CurveInfoFromNeighbours.Add(sHarvestedCurveInfo);
              }
            }

            Marshal.ReleaseComObject(pFeat);
            pFeat = pFeatCursLines.NextFeature();
              }
              Marshal.FinalReleaseComObject(pFeatCursLines);

              bool bHasParallelCurveFeaturesNearby = (outFoundLinesCount > 0);

              return bHasParallelCurveFeaturesNearby;
        }