Пример #1
0
        /// <summary>
        /// Summarise the features of an input feature class that fall within an input polygon.
        /// Input features can be points, lines, or polygons. A value field and a category field can be provided.
        /// If a category field is provided then this will be used like a GROUP BY clause in SQL and return will be broken down by
        /// category, in addition to the overall totals.
        /// If a value field is provided then it will be totalled (by category if provided), e.g. population in a polygon.
        /// But as some line / polygon features may be only partially contained we need to decide how to handle the values on those.
        /// Currently we just scale based on the proportion of the original feature that is included but there would be
        /// alternatives: count all or nothing based on inclusion of majority of feature, or centre of feature. Not yet implemented.
        /// </summary>
        /// <param name="pInputPolygon">
        /// The IPolygon which will be used to clip and summarise the features from the second parameter
        /// </param>
        /// <param name="pInputFeatures">
        /// The features which will be clipped and summarised. IFeatureClass that must be of ShapeType
        /// esriGeometryPoint, esriGeometryPolyline or esriGeometryPolygon
        /// </param>
        /// <param name="pCategoryFieldNum">
        /// ID (integer) of a field in the feature class containing values by which the results should be grouped. Can be
        /// any field type but integer, string, etc are recommended. Value of -1 means no summation by category will occur.
        /// Set to -1 to not do this summary
        /// </param>
        /// <param name="pValueFieldNum">
        /// ID (integer of a field in the feature class containing values by which the results should be totalled. For example
        /// population of counties, value of land parcels. Done in addition to totalling area / length / count.
        /// Set to -1 to not do this summary
        /// </param>
        /// <param name="pMeasureFieldNum">
        /// ID (integer) of a field in the feature class containing pre-calculated values for area (polygons) or length (lines).
        /// Can be used to speed calculation of these properties. They will be calculated manually for features partially
        /// within the input polygon.
        /// </param>
        /// <returns>
        /// FeatureExtractionResult object containing:
        /// total feature count,
        /// total feature length / area (for lines / polygons),
        /// total feature value (from value field if provided),
        /// plus each of the above broken down by category if a category field is provided
        /// category results are each a dictionary of key=category value (as string), value=int or double
        /// </returns>
        internal static FeatureExtractionResult SummariseFeatures(IPolygon pInputPolygon,
                                                                  ExtractionLayerConfig pExtractionLayerConfig)
        //IFeatureClass pInputFeatures,int pCategoryFieldNum, int pValueFieldNum, int pMeasureFieldNum)
        {
            // use cast rather than as to make sure it blows up if the geodataset isn't
            // a feature class
            IFeatureClass    pInputFeatures = (IFeatureClass)pExtractionLayerConfig.LayerDataset;
            esriGeometryType tFCType        = pInputFeatures.ShapeType;
            // set up variables to build results
            Dictionary <string, int>    tCategoryCounts = new Dictionary <string, int>();
            Dictionary <string, double> tCategoryTotals = new Dictionary <string, double>(),
                                        tCategoryMeasures = new Dictionary <string, double>();
            double tTotalMeasure = 0, tTotalValue = 0;
            int    tTotalCount = 0;
            // variables to control search
            bool hasCategories = pExtractionLayerConfig.HasCategories;
            bool hasMeasures = pExtractionLayerConfig.HasMeasures;
            bool hasPreCalcMeasures = hasMeasures && (pExtractionLayerConfig.MeasureField != -1);
            bool hasValues = false;

            if (pExtractionLayerConfig.HasValues && pExtractionLayerConfig.ValueField != -1)
            {
                // only numeric fields will be totalled
                //esriFieldType tValueFieldType = pInputFeatures.Fields.get_Field(pValueFieldNum).Type;
                esriFieldType tValueFieldType = pInputFeatures.Fields.get_Field(pExtractionLayerConfig.ValueField)
                                                .Type;
                if (tValueFieldType == esriFieldType.esriFieldTypeDouble ||
                    tValueFieldType == esriFieldType.esriFieldTypeInteger ||
                    tValueFieldType == esriFieldType.esriFieldTypeSingle ||
                    tValueFieldType == esriFieldType.esriFieldTypeSmallInteger)
                {
                    hasValues = true;
                }
            }
            // use a spatial filter to do the geographic selection
            ISpatialFilter tSpatialFilter = new SpatialFilterClass();

            tSpatialFilter.Geometry = pInputPolygon as IGeometry;
            // first we will select all features wholly within the polygon. We don't need to do anything special with these
            // just use them as is. This applies for points, lines and polys. It is the only thing required for points.
            tSpatialFilter.SpatialRel    = esriSpatialRelEnum.esriSpatialRelContains;
            tSpatialFilter.GeometryField = "SHAPE";
            // safe to use a recycling cursor: we are not maintaining a reference to features across multiple calls to NextFeature
            IFeatureCursor tFeatureCursor = pInputFeatures.Search(tSpatialFilter, true);
            IFeature       tThisFeature = tFeatureCursor.NextFeature();

            try
            {
                while (tThisFeature != null)
                {
                    tTotalCount += 1;
                    double tMeasure = 0;
                    if (hasPreCalcMeasures)
                    {
                        try
                        {
                            //tMeasure = (double)tThisFeature.get_Value(pMeasureFieldNum);
                            tMeasure = (double)tThisFeature.get_Value(pExtractionLayerConfig.MeasureField);
                        }
                        catch
                        {
                            hasPreCalcMeasures = false;
                            if (tFCType == esriGeometryType.esriGeometryPolyline)
                            {
                                IPolyline tFeatureAsLine = tThisFeature.Shape as IPolyline;
                                tMeasure = tFeatureAsLine.Length;
                            }
                            else if (tFCType == esriGeometryType.esriGeometryPolygon)
                            {
                                IArea tFeatureAsArea = tThisFeature.Shape as IArea;
                                tMeasure = tFeatureAsArea.Area;
                            }
                        }
                    }
                    else
                    {
                        if (tFCType == esriGeometryType.esriGeometryPolyline)
                        {
                            IPolyline tFeatureAsLine = tThisFeature.Shape as IPolyline;
                            tMeasure = tFeatureAsLine.Length;
                        }
                        else if (tFCType == esriGeometryType.esriGeometryPolygon)
                        {
                            IArea tFeatureAsArea = tThisFeature.Shape as IArea;
                            tMeasure = tFeatureAsArea.Area;
                        }
                    }
                    tTotalMeasure += tMeasure;
                    if (hasCategories)
                    {
                        // get the category / class of this featue
                        string tCategory = tThisFeature.get_Value(pExtractionLayerConfig.CategoryField).ToString();
                        // placeholders for the dictionary lookups (out variables)
                        int    tCurrentCategoryCount;
                        double tCurrentCategoryMeasure;
                        // add 1 to the appropriate category count in the category counts dictionary
                        if (tCategoryCounts.TryGetValue(tCategory, out tCurrentCategoryCount))
                        {
                            tCategoryCounts[tCategory] = tCurrentCategoryCount + 1;
                        }
                        else
                        {
                            tCategoryCounts[tCategory] = 1;
                        }
                        if (tCategoryMeasures.TryGetValue(tCategory, out tCurrentCategoryMeasure))
                        {
                            tCategoryMeasures[tCategory] = tCurrentCategoryMeasure + tMeasure;
                        }
                        else
                        {
                            tCategoryMeasures[tCategory] = tMeasure;
                        }
                        if (hasValues)
                        {
                            // i.e. look up the value from another field, other than just the feature's length/area and count
                            double tCurrentCategoryTotal;
                            //double tCurrentArcValue = (double)tThisFeature.get_Value(pValueFieldNum);
                            double tCurrentArcValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                            tTotalValue += tCurrentArcValue;
                            if (tCategoryTotals.TryGetValue(tCategory, out tCurrentCategoryTotal))
                            {
                                tCategoryTotals[tCategory] = tCurrentCategoryTotal + tCurrentArcValue;
                            }
                            else
                            {
                                tCategoryTotals[tCategory] = tCurrentArcValue;
                            }
                        }
                    }
                    else if (hasValues)
                    {
                        //double tCurrentArcValue = (double)tThisFeature.get_Value(pValueFieldNum);
                        double tCurrentArcValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                        tTotalValue += tCurrentArcValue;
                    }
                    tThisFeature = tFeatureCursor.NextFeature();
                }
                // now for lines and polygons we need to process the features that are partially inside the polygon.
                // this process would work on all the features but there is no point doing expensive intersections where we
                // don't need to, so we did the wholly-contained features separately
                // we need to find the features where there is an intersection between the polygon boundary and the feature's
                // interior. Could use shape description language but this is covered by available spatialrelenum values
                bool doPartialFeatures = ((tFCType == esriGeometryType.esriGeometryPolyline ||
                                           tFCType == esriGeometryType.esriGeometryPolygon)); // a point is either in or out!
                // not yet implemented: control how partially-intersecting features are handled
                //&& pFeatureIntersectionMode != IntersectingFeatureSelectionMode.SelectNoPartialFeatures);
                if (doPartialFeatures)
                {
                    if (tFCType == esriGeometryType.esriGeometryPolyline)
                    {
                        // "A polyline and a polygon cross if they share a polyline or a point (for vertical line) in common on the
                        // interior of the polygon which is not equivalent to the entire polyline."
                        tSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelCrosses;
                    }
                    else if (tFCType == esriGeometryType.esriGeometryPolygon)
                    {
                        // "Two geometries overlap if the region of their intersection is of the same dimension as the geometries involved
                        // and is not equivalent to either of the geometries."
                        tSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelOverlaps;
                    }
                    // no longer safe to use recycling cursor as we're going to tinker with the features returned
                    tFeatureCursor = pInputFeatures.Search(tSpatialFilter, false);
                    tThisFeature   = tFeatureCursor.NextFeature();
                    ITopologicalOperator tInputAsTopoOp = pInputPolygon as ITopologicalOperator;
                    int tNumberCrossingBoundary         = 0;
                    while (tThisFeature != null)
                    {
                        // return (so track) the number of partially-included features separately from the overall total
                        tNumberCrossingBoundary += 1;
                        tTotalCount             += 1;
                        // either the length or area of the intersected feature:
                        double tMeasure = 0;
                        // either the length or area of entire original intersected feature
                        // (to get proportion that's included):
                        double tOriginalMeasure = 0;
                        // do the
                        IGeometry tFeatureGeometry = tThisFeature.ShapeCopy;
                        if (tFCType == esriGeometryType.esriGeometryPolyline)
                        {
                            IPolyline tArcEntire = tFeatureGeometry as IPolyline;
                            tOriginalMeasure = tArcEntire.Length;
                            IPolyline tArcInside = tInputAsTopoOp.Intersect(tFeatureGeometry, esriGeometryDimension.esriGeometry1Dimension) as IPolyline;
                            tMeasure = tArcInside.Length;
                        }
                        else
                        {
                            IArea tAreaEntire = tFeatureGeometry as IArea;
                            tOriginalMeasure = tAreaEntire.Area;
                            IArea tAreaInside = tInputAsTopoOp.Intersect(tFeatureGeometry, esriGeometryDimension.esriGeometry2Dimension) as IArea;
                            tMeasure = tAreaInside.Area;
                        }
                        tTotalMeasure += tMeasure;
                        if (hasCategories)
                        {
                            //string tCategory = tThisFeature.get_Value(pCategoryFieldNum).ToString();
                            string tCategory = tThisFeature.get_Value(pExtractionLayerConfig.CategoryField).ToString();
                            int    tCurrentCategoryCount;
                            double tCurrentCategoryMeasure;
                            if (tCategoryCounts.TryGetValue(tCategory, out tCurrentCategoryCount))
                            {
                                tCategoryCounts[tCategory] = tCurrentCategoryCount + 1;
                            }
                            else
                            {
                                tCategoryCounts[tCategory] = 1;
                            }
                            if (tCategoryMeasures.TryGetValue(tCategory, out tCurrentCategoryMeasure))
                            {
                                tCategoryMeasures[tCategory] = tCurrentCategoryMeasure + tMeasure;
                            }
                            else
                            {
                                tCategoryMeasures[tCategory] = tMeasure;
                            }
                            if (hasValues)
                            {
                                // how should we handle a value field in an intersected feature? we can't, for certain,
                                // as we don't know what they mean. We'll just assume that it scales proportionally with the
                                // proportion of the original feature's length / area that is included.
                                // The raster equivalent is to count all or none based on cell centre, so maybe we should count
                                // all or none based on centroid??
                                double tCurrentCategoryTotal;
                                //double tCurrentFeatureValue = (double)tThisFeature.get_Value(pValueFieldNum);
                                double tCurrentFeatureValue = (double)tThisFeature.get_Value(
                                    pExtractionLayerConfig.ValueField);
                                double tScaledFeatureValue = (tMeasure / tOriginalMeasure) * tCurrentFeatureValue;
                                tTotalValue += tScaledFeatureValue;
                                if (tCategoryTotals.TryGetValue(tCategory, out tCurrentCategoryTotal))
                                {
                                    tCategoryTotals[tCategory] = tCurrentCategoryTotal + tScaledFeatureValue;
                                }
                                else
                                {
                                    tCategoryTotals[tCategory] = tScaledFeatureValue;
                                }
                            }
                        }
                        else if (hasValues)
                        {
                            //double tCurrentFeatureValue = (double)tThisFeature.get_Value(pValueFieldNum);
                            double tCurrentFeatureValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                            double tScaledFeatureValue  = (tMeasure / tOriginalMeasure) * tCurrentFeatureValue;
                            tTotalValue += tScaledFeatureValue;
                        }
                        tThisFeature = tFeatureCursor.NextFeature();
                    }
                }
                double?outMeasure;
                double?outValue;
                if (hasMeasures)
                {
                    outMeasure = tTotalMeasure;
                }
                else
                {
                    outMeasure = null;
                }
                if (hasValues)
                {
                    outValue = tTotalValue;
                }
                else
                {
                    outValue = null;
                }
                FeatureExtractionResult tResult = new FeatureExtractionResult(
                    pExtractionLayerConfig.ParamName,
                    tTotalCount,
                    outMeasure,
                    outValue,
                    tCategoryCounts,
                    tCategoryMeasures,
                    tCategoryTotals,
                    pInputFeatures.ShapeType
                    );
                return(tResult);
            }
            catch (Exception ex)
            {
                logger.LogMessage(ServerLogger.msgType.debug, "process features", 99, "error summarising features in " +
                                  pInputFeatures.AliasName + " Detail: " + ex.StackTrace + " " + ex.Message);
                return(new FeatureExtractionResult("An error occurred with extraction from " + pInputFeatures.AliasName, pExtractionLayerConfig.ParamName));
            }
        }
        //IFeatureClass pInputFeatures,int pCategoryFieldNum, int pValueFieldNum, int pMeasureFieldNum)
        /// <summary>
        /// Summarise the features of an input feature class that fall within an input polygon.
        /// Input features can be points, lines, or polygons. A value field and a category field can be provided.
        /// If a category field is provided then this will be used like a GROUP BY clause in SQL and return will be broken down by
        /// category, in addition to the overall totals.
        /// If a value field is provided then it will be totalled (by category if provided), e.g. population in a polygon.
        /// But as some line / polygon features may be only partially contained we need to decide how to handle the values on those.
        /// Currently we just scale based on the proportion of the original feature that is included but there would be
        /// alternatives: count all or nothing based on inclusion of majority of feature, or centre of feature. Not yet implemented.
        /// </summary>
        /// <param name="pInputPolygon">
        /// The IPolygon which will be used to clip and summarise the features from the second parameter
        /// </param>
        /// <param name="pInputFeatures">
        /// The features which will be clipped and summarised. IFeatureClass that must be of ShapeType 
        /// esriGeometryPoint, esriGeometryPolyline or esriGeometryPolygon
        /// </param>
        /// <param name="pCategoryFieldNum">
        /// ID (integer) of a field in the feature class containing values by which the results should be grouped. Can be 
        /// any field type but integer, string, etc are recommended. Value of -1 means no summation by category will occur.
        /// Set to -1 to not do this summary
        /// </param>
        /// <param name="pValueFieldNum">
        /// ID (integer of a field in the feature class containing values by which the results should be totalled. For example
        /// population of counties, value of land parcels. Done in addition to totalling area / length / count.
        /// Set to -1 to not do this summary
        /// </param>
        /// <param name="pMeasureFieldNum">
        /// ID (integer) of a field in the feature class containing pre-calculated values for area (polygons) or length (lines). 
        /// Can be used to speed calculation of these properties. They will be calculated manually for features partially 
        /// within the input polygon.
        /// </param>
        /// <returns>
        /// FeatureExtractionResult object containing:
        /// total feature count,
        /// total feature length / area (for lines / polygons),
        /// total feature value (from value field if provided),
        /// plus each of the above broken down by category if a category field is provided
        /// category results are each a dictionary of key=category value (as string), value=int or double
        /// </returns>
        internal static FeatureExtractionResult SummariseFeatures(IPolygon pInputPolygon, 
            ExtractionLayerConfig pExtractionLayerConfig)
        {
            // use cast rather than as to make sure it blows up if the geodataset isn't
            // a feature class
            IFeatureClass pInputFeatures = (IFeatureClass)pExtractionLayerConfig.LayerDataset;
            esriGeometryType tFCType = pInputFeatures.ShapeType;
            // set up variables to build results
            Dictionary<string, int> tCategoryCounts = new Dictionary<string, int>();
            Dictionary<string, double> tCategoryTotals = new Dictionary<string, double>(),
                tCategoryMeasures = new Dictionary<string, double>();
            double tTotalMeasure = 0, tTotalValue = 0;
            int tTotalCount = 0;
            // variables to control search
            bool hasCategories = pExtractionLayerConfig.HasCategories;
            bool hasMeasures = pExtractionLayerConfig.HasMeasures;
            bool hasPreCalcMeasures = hasMeasures && (pExtractionLayerConfig.MeasureField != -1);
            bool hasValues = false;
            if (pExtractionLayerConfig.HasValues && pExtractionLayerConfig.ValueField != -1)
            {
                // only numeric fields will be totalled
                //esriFieldType tValueFieldType = pInputFeatures.Fields.get_Field(pValueFieldNum).Type;
                esriFieldType tValueFieldType = pInputFeatures.Fields.get_Field(pExtractionLayerConfig.ValueField)
                    .Type;
                if (tValueFieldType == esriFieldType.esriFieldTypeDouble ||
                    tValueFieldType == esriFieldType.esriFieldTypeInteger ||
                    tValueFieldType == esriFieldType.esriFieldTypeSingle ||
                    tValueFieldType == esriFieldType.esriFieldTypeSmallInteger)
                {
                    hasValues = true;
                }
            }
            // use a spatial filter to do the geographic selection
            ISpatialFilter tSpatialFilter = new SpatialFilterClass();
            tSpatialFilter.Geometry = pInputPolygon as IGeometry;
            // first we will select all features wholly within the polygon. We don't need to do anything special with these
            // just use them as is. This applies for points, lines and polys. It is the only thing required for points.
            tSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;
            tSpatialFilter.GeometryField = "SHAPE";
            // safe to use a recycling cursor: we are not maintaining a reference to features across multiple calls to NextFeature
            IFeatureCursor tFeatureCursor = pInputFeatures.Search(tSpatialFilter, true);
            IFeature tThisFeature = tFeatureCursor.NextFeature();
            try
            {
                while (tThisFeature != null)
                {
                    tTotalCount += 1;
                    double tMeasure = 0;
                    if (hasPreCalcMeasures)
                    {
                        try
                        {
                            //tMeasure = (double)tThisFeature.get_Value(pMeasureFieldNum);
                            tMeasure = (double)tThisFeature.get_Value(pExtractionLayerConfig.MeasureField);
                        }
                        catch
                        {
                            hasPreCalcMeasures = false;
                            if (tFCType == esriGeometryType.esriGeometryPolyline)
                            {
                                IPolyline tFeatureAsLine = tThisFeature.Shape as IPolyline;
                                tMeasure = tFeatureAsLine.Length;
                            }
                            else if (tFCType == esriGeometryType.esriGeometryPolygon)
                            {
                                IArea tFeatureAsArea = tThisFeature.Shape as IArea;
                                tMeasure = tFeatureAsArea.Area;
                            }
                        }
                    }
                    else
                    {
                        if (tFCType == esriGeometryType.esriGeometryPolyline)
                        {
                            IPolyline tFeatureAsLine = tThisFeature.Shape as IPolyline;
                            tMeasure = tFeatureAsLine.Length;
                        }
                        else if (tFCType == esriGeometryType.esriGeometryPolygon)
                        {
                            IArea tFeatureAsArea = tThisFeature.Shape as IArea;
                            tMeasure = tFeatureAsArea.Area;
                        }
                    }
                    tTotalMeasure += tMeasure;
                    if (hasCategories)
                    {
                        // get the category / class of this featue
                        string tCategory = tThisFeature.get_Value(pExtractionLayerConfig.CategoryField).ToString();
                        // placeholders for the dictionary lookups (out variables)
                        int tCurrentCategoryCount;
                        double tCurrentCategoryMeasure;
                        // add 1 to the appropriate category count in the category counts dictionary
                        if (tCategoryCounts.TryGetValue(tCategory, out tCurrentCategoryCount))
                        {
                            tCategoryCounts[tCategory] = tCurrentCategoryCount + 1;
                        }
                        else
                        {
                            tCategoryCounts[tCategory] = 1;
                        }
                        if (tCategoryMeasures.TryGetValue(tCategory, out tCurrentCategoryMeasure))
                        {
                            tCategoryMeasures[tCategory] = tCurrentCategoryMeasure + tMeasure;
                        }
                        else
                        {
                            tCategoryMeasures[tCategory] = tMeasure;
                        }
                        if (hasValues)
                        {
                            // i.e. look up the value from another field, other than just the feature's length/area and count
                            double tCurrentCategoryTotal;
                            //double tCurrentArcValue = (double)tThisFeature.get_Value(pValueFieldNum);
                            double tCurrentArcValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                            tTotalValue += tCurrentArcValue;
                            if (tCategoryTotals.TryGetValue(tCategory, out tCurrentCategoryTotal))
                            {
                                tCategoryTotals[tCategory] = tCurrentCategoryTotal + tCurrentArcValue;
                            }
                            else
                            {
                                tCategoryTotals[tCategory] = tCurrentArcValue;
                            }
                        }
                    }
                    else if (hasValues)
                    {
                        //double tCurrentArcValue = (double)tThisFeature.get_Value(pValueFieldNum);
                        double tCurrentArcValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                        tTotalValue += tCurrentArcValue;
                    }
                    tThisFeature = tFeatureCursor.NextFeature();
                }
                // now for lines and polygons we need to process the features that are partially inside the polygon.
                // this process would work on all the features but there is no point doing expensive intersections where we
                // don't need to, so we did the wholly-contained features separately
                // we need to find the features where there is an intersection between the polygon boundary and the feature's
                // interior. Could use shape description language but this is covered by available spatialrelenum values
                bool doPartialFeatures = ((tFCType == esriGeometryType.esriGeometryPolyline ||
                                          tFCType == esriGeometryType.esriGeometryPolygon) ); // a point is either in or out!
                                         // not yet implemented: control how partially-intersecting features are handled
                                         //&& pFeatureIntersectionMode != IntersectingFeatureSelectionMode.SelectNoPartialFeatures);
                if (doPartialFeatures){
                    if (tFCType == esriGeometryType.esriGeometryPolyline)
                    {
                        // "A polyline and a polygon cross if they share a polyline or a point (for vertical line) in common on the
                        // interior of the polygon which is not equivalent to the entire polyline."
                        tSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelCrosses;
                    }
                    else if (tFCType == esriGeometryType.esriGeometryPolygon)
                    {
                        // "Two geometries overlap if the region of their intersection is of the same dimension as the geometries involved
                        // and is not equivalent to either of the geometries."
                        tSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelOverlaps;
                    }
                    // no longer safe to use recycling cursor as we're going to tinker with the features returned
                    tFeatureCursor = pInputFeatures.Search(tSpatialFilter, false);
                    tThisFeature = tFeatureCursor.NextFeature();
                    ITopologicalOperator tInputAsTopoOp = pInputPolygon as ITopologicalOperator;
                    int tNumberCrossingBoundary = 0;
                    while (tThisFeature != null)
                    {
                        // return (so track) the number of partially-included features separately from the overall total
                        tNumberCrossingBoundary += 1;
                        tTotalCount += 1;
                        // either the length or area of the intersected feature:
                        double tMeasure = 0;
                        // either the length or area of entire original intersected feature
                        // (to get proportion that's included):
                        double tOriginalMeasure = 0;
                        // do the
                        IGeometry tFeatureGeometry = tThisFeature.ShapeCopy;
                        if (tFCType == esriGeometryType.esriGeometryPolyline)
                        {
                            IPolyline tArcEntire = tFeatureGeometry as IPolyline;
                            tOriginalMeasure = tArcEntire.Length;
                            IPolyline tArcInside = tInputAsTopoOp.Intersect(tFeatureGeometry, esriGeometryDimension.esriGeometry1Dimension) as IPolyline;
                            tMeasure = tArcInside.Length;
                        }
                        else
                        {
                            IArea tAreaEntire = tFeatureGeometry as IArea;
                            tOriginalMeasure = tAreaEntire.Area;
                            IArea tAreaInside = tInputAsTopoOp.Intersect(tFeatureGeometry, esriGeometryDimension.esriGeometry2Dimension) as IArea;
                            tMeasure = tAreaInside.Area;
                        }
                        tTotalMeasure += tMeasure;
                        if (hasCategories)
                        {
                            //string tCategory = tThisFeature.get_Value(pCategoryFieldNum).ToString();
                            string tCategory = tThisFeature.get_Value(pExtractionLayerConfig.CategoryField).ToString();
                            int tCurrentCategoryCount;
                            double tCurrentCategoryMeasure;
                            if (tCategoryCounts.TryGetValue(tCategory, out tCurrentCategoryCount))
                            {
                                tCategoryCounts[tCategory] = tCurrentCategoryCount + 1;
                            }
                            else
                            {
                                tCategoryCounts[tCategory] = 1;
                            }
                            if (tCategoryMeasures.TryGetValue(tCategory, out tCurrentCategoryMeasure))
                            {
                                tCategoryMeasures[tCategory] = tCurrentCategoryMeasure + tMeasure;
                            }
                            else
                            {
                                tCategoryMeasures[tCategory] = tMeasure;
                            }
                            if (hasValues)
                            {
                                // how should we handle a value field in an intersected feature? we can't, for certain,
                                // as we don't know what they mean. We'll just assume that it scales proportionally with the
                                // proportion of the original feature's length / area that is included.
                                // The raster equivalent is to count all or none based on cell centre, so maybe we should count
                                // all or none based on centroid??
                                double tCurrentCategoryTotal;
                                //double tCurrentFeatureValue = (double)tThisFeature.get_Value(pValueFieldNum);
                                double tCurrentFeatureValue = (double)tThisFeature.get_Value(
                                    pExtractionLayerConfig.ValueField);
                                double tScaledFeatureValue = (tMeasure / tOriginalMeasure) * tCurrentFeatureValue;
                                tTotalValue += tScaledFeatureValue;
                                if (tCategoryTotals.TryGetValue(tCategory,out tCurrentCategoryTotal))
                                {
                                    tCategoryTotals[tCategory] = tCurrentCategoryTotal + tScaledFeatureValue;
                                }
                                else
                                {
                                    tCategoryTotals[tCategory] = tScaledFeatureValue;
                                }
                            }
                        }
                        else if (hasValues)
                        {
                            //double tCurrentFeatureValue = (double)tThisFeature.get_Value(pValueFieldNum);
                            double tCurrentFeatureValue = (double)tThisFeature.get_Value(pExtractionLayerConfig.ValueField);
                            double tScaledFeatureValue = (tMeasure / tOriginalMeasure)*tCurrentFeatureValue;
                            tTotalValue += tScaledFeatureValue;
                        }
                    tThisFeature = tFeatureCursor.NextFeature();
                    }
                    }
                double? outMeasure;
                double? outValue;
                if (hasMeasures) { outMeasure = tTotalMeasure; }
                else { outMeasure = null; }
                if (hasValues) { outValue = tTotalValue; }
                else { outValue = null; }
                FeatureExtractionResult tResult = new FeatureExtractionResult(
                    pExtractionLayerConfig.ParamName,
                    tTotalCount,
                    outMeasure,
                    outValue,
                    tCategoryCounts,
                    tCategoryMeasures,
                    tCategoryTotals,
                    pInputFeatures.ShapeType
                );
                return tResult;

            }
            catch (Exception ex)
            {
                logger.LogMessage(ServerLogger.msgType.debug, "process features", 99, "error summarising features in " +
                           pInputFeatures.AliasName+" Detail: " + ex.StackTrace + " " + ex.Message);
                return new FeatureExtractionResult("An error occurred with extraction from "+pInputFeatures.AliasName,pExtractionLayerConfig.ParamName);
            }
        }