Spatial Query REST SOE
SpatialQueryREST\SpatialQueryREST.cs
// Copyright 2012 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections.Specialized;

using System.Runtime.InteropServices;

using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Server;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SOESupport;


//TODO: sign the project (project properties > signing tab > sign the assembly)
//      this is strongly suggested if the dll will be registered using regasm.exe <your>.dll /codebase


namespace SpatialQueryREST
{
    [ComVisible(true)]
    [Guid("0d9be731-0094-45ee-873a-db2c10c773b3")]
    [ClassInterface(ClassInterfaceType.None)]
    [ServerObjectExtension("MapServer",
        AllCapabilities = "",
        DefaultCapabilities = "",
        Description = ".NET Spatial Query REST SOE Sample",
        DisplayName = ".NET Spatial Query REST SOE",
        Properties = "FieldName=PRIMARY_;LayerName=veg;FilePath=NotSet",
        SupportsREST = true,
        SupportsSOAP = false)]
    public class SpatialQueryREST : IServerObjectExtension, IObjectConstruct, IRESTRequestHandler
    {
        private string soe_name;

        private IPropertySet configProps;
        private IServerObjectHelper serverObjectHelper;
        private ServerLogger logger;
        private IRESTRequestHandler reqHandler;
        private IFeatureClass m_fcToQuery;
        private string m_mapLayerNameToQuery;
        private string m_mapFieldToQuery;

        public SpatialQueryREST()
        {
            soe_name = this.GetType().Name;
            logger = new ServerLogger();
            reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler;
        }

        #region IServerObjectExtension Members

        public void Init(IServerObjectHelper pSOH)
        {
            serverObjectHelper = pSOH;
        }


        public void Shutdown()
        {
            logger.LogMessage(ServerLogger.msgType.infoStandard, "Shutdown", 8000, "Custom message: Shutting down the SOE");
            soe_name = null;
            m_fcToQuery = null;
            m_mapFieldToQuery = null;
            serverObjectHelper = null;
            logger = null;
        }

        #endregion

        #region IObjectConstruct Members

        public void Construct(IPropertySet props)
        {
            configProps = props;
            // Read the properties.

            if (props.GetProperty("FieldName") != null)
            {
                m_mapFieldToQuery = props.GetProperty("FieldName") as string;
            }
            else
            {
                throw new ArgumentNullException();
            }
            if (props.GetProperty("LayerName") != null)
            {
                m_mapLayerNameToQuery = props.GetProperty("LayerName") as string;
            }
            else
            {
                throw new ArgumentNullException();
            }
            try
            {
                // Get the feature layer to be queried.
                // Since the layer is a property of the SOE, this only has to be done once.
                IMapServer3 mapServer = (IMapServer3)serverObjectHelper.ServerObject;
                string mapName = mapServer.DefaultMapName;
                IMapLayerInfo layerInfo;
                IMapLayerInfos layerInfos = mapServer.GetServerInfo(mapName).MapLayerInfos;
                // Find the index position of the map layer to query.
                int c = layerInfos.Count;
                int layerIndex = 0;
                for (int i = 0; i < c; i++)
                {
                    layerInfo = layerInfos.get_Element(i);
                    if (layerInfo.Name == m_mapLayerNameToQuery)
                    {
                        layerIndex = i;
                        break;
                    }
                }
                // Use IMapServerDataAccess to get the data
                IMapServerDataAccess dataAccess = (IMapServerDataAccess)mapServer;
                // Get access to the source feature class.
                m_fcToQuery = (IFeatureClass)dataAccess.GetDataSource(mapName, layerIndex);
                if (m_fcToQuery == null)
                {
                    logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Layer name not found.");
                    return;
                }
                // Make sure the layer contains the field specified by the SOE's configuration.
                if (m_fcToQuery.FindField(m_mapFieldToQuery) == -1)
                {
                    logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Field not found in layer.");
                }
            }
            catch
            {
                logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Could not get the feature layer.");
            }
        }

        #endregion

        #region IRESTRequestHandler Members

        public string GetSchema()
        {
            return reqHandler.GetSchema();
        }

        public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties)
        {
            return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties);
        }

        #endregion

        private RestResource CreateRestSchema()
        {
            RestResource rootRes = new RestResource(soe_name, false, RootResHandler);
            RestOperation spatialQueryOper = new RestOperation("SpatialQuery",
                                                      new string[] { "location", "distance" },
                                                      new string[] { "json" },
                                                      SpatialQueryOperationHandler);
            rootRes.operations.Add(spatialQueryOper);
            return rootRes;
        }

        private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
        {
            responseProperties = null;

            JsonObject result = new JsonObject();

            return Encoding.UTF8.GetBytes(result.ToJson());
        }

        private byte[] SpatialQueryOperationHandler(NameValueCollection boundVariables,
                                                  JsonObject operationInput,
                                                      string outputFormat,
                                                      string requestProperties,
                                                  out string responseProperties)
        {
            responseProperties = null; 

            // Deserialize the location.
            JsonObject jsonPoint;
            if (!operationInput.TryGetJsonObject("location", out jsonPoint))
                throw new ArgumentNullException("location");
            IPoint location = Conversion.ToGeometry(jsonPoint,
                esriGeometryType.esriGeometryPoint) as IPoint;
            if (location == null)
                throw new ArgumentException("SpatialQueryREST: invalid location", "location");
            // Deserialize the distance.
            double? distance;
            if (!operationInput.TryGetAsDouble("distance", out distance) || !distance.HasValue)
                throw new ArgumentException("SpatialQueryREST: invalid distance", "distance");
            byte[] result = QueryPoint(location, distance.Value);
            return result;
        }

        private byte[] QueryPoint(ESRI.ArcGIS.Geometry.IPoint location, double distance)
        {
            if (distance <= 0.0)
                throw new ArgumentOutOfRangeException("distance");
            // Buffer the point.
            ITopologicalOperator topologicalOperator = (ESRI.ArcGIS.Geometry.ITopologicalOperator)location;
            IGeometry queryGeometry = topologicalOperator.Buffer(distance);
            // Query the feature class.
            ISpatialFilter spatialFilter = new ESRI.ArcGIS.Geodatabase.SpatialFilter();
            spatialFilter.Geometry = queryGeometry;
            spatialFilter.SpatialRel = ESRI.ArcGIS.Geodatabase.esriSpatialRelEnum.esriSpatialRelIntersects;
            spatialFilter.GeometryField = m_fcToQuery.ShapeFieldName;
            IFeatureCursor resultsFeatureCursor = m_fcToQuery.Search(spatialFilter, true);
            // Loop through the features, clip each geometry to the buffer
            // and total areas by attribute value.
            topologicalOperator = (ESRI.ArcGIS.Geometry.ITopologicalOperator)queryGeometry;
            int classFieldIndex = m_fcToQuery.FindField(m_mapFieldToQuery);
            // System.Collections.Specialized.ListDictionary summaryStatsDictionary = new System.Collections.Specialized.ListDictionary();
            Dictionary<string, double> summaryStatsDictionary = new Dictionary<string, double>();
            // Initialize a list to hold JSON geometries.
            List<JsonObject> jsonGeometries = new List<JsonObject>();

            IFeature resultsFeature = null;
            while ((resultsFeature = resultsFeatureCursor.NextFeature()) != null)
            {
                // Clip the geometry.
                IPolygon clippedResultsGeometry = (IPolygon)topologicalOperator.Intersect(resultsFeature.Shape,
                    ESRI.ArcGIS.Geometry.esriGeometryDimension.esriGeometry2Dimension);
                clippedResultsGeometry.Densify(0, 0); // Densify to maintain curved appearance when converted to JSON. 
                // Convert the geometry to JSON and add it to the list.
                JsonObject jsonClippedResultsGeometry = Conversion.ToJsonObject(clippedResultsGeometry);
                jsonGeometries.Add(jsonClippedResultsGeometry);
                // Get statistics.
                IArea area = (IArea)clippedResultsGeometry;
                string resultsClass = resultsFeature.get_Value(classFieldIndex) as string;
                // If the class is already in the dictionary, add the current feature's area to the existing entry.
                if (summaryStatsDictionary.ContainsKey(resultsClass))
                    summaryStatsDictionary[resultsClass] = (double)summaryStatsDictionary[resultsClass] + area.Area;
                else
                    summaryStatsDictionary[resultsClass] = area.Area;
            }
            // Use a helper method to get a JSON array of area records.
            JsonObject[] areaResultJson = CreateJsonRecords(summaryStatsDictionary) as JsonObject[];
            // Create a JSON object of the geometry results and the area records.
            JsonObject resultJsonObject = new JsonObject();
            resultJsonObject.AddArray("geometries", jsonGeometries.ToArray());
            resultJsonObject.AddArray("records", areaResultJson);
            // Get byte array of json and return results.
            byte[] result = Encoding.UTF8.GetBytes(resultJsonObject.ToJson());
            return result;
        }
        // Helper method to read the items in a dictionary and make a JSON object from them.
        private JsonObject[] CreateJsonRecords(Dictionary<string, double> inListDictionary)
        {
            JsonObject[] jsonRecordsArray = new JsonObject[inListDictionary.Count];
            int i = 0;
            // Loop through dictionary.
            foreach (KeyValuePair<string, double> kvp in inListDictionary)
            {
                // Get the current key and value.
                string currentKey = kvp.Key.ToString();
                string currentValue = kvp.Value.ToString();
                // Add the key and value to a JSON object.
                JsonObject currentKeyValue = new JsonObject();
                currentKeyValue.AddString(m_mapLayerNameToQuery, currentKey);
                currentKeyValue.AddString("value", currentValue);
                // Add the record object to an array.
                jsonRecordsArray.SetValue(currentKeyValue, i);
                i++;
            }

            return jsonRecordsArray;
        }


    }
}