Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I was wondering if I could use a Server object Extension (SOE) to modify the image data that is returned by a Map Service. By that I mean, for each incoming http request (and based on the authenticated user), set a filter on the Server Object's map layer(s) that will be reflected in the map image. Is this possible and would there be performance issues? Also would there be performance issues of doing a database query inside an SOE?

share|improve this question
    
+1 I'm not aware of way to do this, but I'd sure upvote it as an enhancement. –  Kirk Kuykendall Jun 9 '11 at 17:30
    
Have you built an SOE before? I'm building my first one now and I'm experimenting to see if and how I can affect the map image that is returned. –  enamrik Jun 11 '11 at 1:14
    
Yes I've built a number of SOE's, but I haven't noticed any sort of hook that would allow you to override the image drawing pipeline the way you describe. It might be worth wiring IActiveViewEvents.ViewRefreshed when IServerObjectExtent.Init gets called to see what happens when a web client requests an image. I've never tried this though. –  Kirk Kuykendall Jun 12 '11 at 23:21
    
Also new changes at 10.1 would likely break this, even if it did work at 10.0. –  Kirk Kuykendall Jun 12 '11 at 23:23
    
It's really hard to believe such a hook doesn't exist considering ArcGIS has a price tag that only a large enterprise (most likely with really detailed security requirements) can afford. I'll try what you said and continue experimenting. It would be funny if I ended up going with an open source solution for security sake. –  enamrik Jun 14 '11 at 13:31

1 Answer 1

What about changing the image after the fact? Basically get the byte[], and use a 3rd party library to change pixel values based on user roles? Might not be exactly what your looking for, but this has been my approach to modifying image data in the past. My favorite library to do this with is Aforge.NET. From a security perspective, maybe this isn't your best solution, but for general image manipulation of a dynamic map export its great.

Below is one such extension I wrote which combines channels from 3 different layer map exports into a single image. The SOE takes {{channel (e.g. R,G,B,A)}} : {{arcgis service layer index}} as arguments. Here is the sample code which was written for ArcGIS Server 10.1 (.NET).

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

using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.EnterpriseServices;

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

using AForge.Imaging.Filters;
using AForge.Imaging;
namespace GetCompositeSuitabilityMapRESTSOE101
{
    [ComVisible(true)]
    [Guid("a6ceb521-9b3a-47a6-be27-dea0f55985fe")]
    [ClassInterface(ClassInterfaceType.None)]
    [ServerObjectExtension("MapServer",
        AllCapabilities = "",
        DefaultCapabilities = "",
        Description = @"Support PNG and JPG formats. Returns a single export map from 3 separate map exports using input json: {""xmin"", ""ymin"", ""xmax"", ""ymax"", ""height"", ""width"", ""r"", ""g"", ""b"", ""compression""}",
        DisplayName = "Export Merged Map",
        Properties = "",
        SupportsREST = true,
        SupportsSOAP = false)]
    public class GetCompositeSuitabilityMapRESTSOE101 : IServerObjectExtension, IObjectConstruct, IRESTRequestHandler
    {
       private string soe_name;

        private IPropertySet configProps;
        private IServerObjectHelper serverObjectHelper;
        private ServerLogger logger;
        private IRESTRequestHandler reqHandler;

        public GetCompositeSuitabilityMapRESTSOE101()
        {
            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()
        {
        }

        #endregion

        #region IObjectConstruct Members

        public void Construct(IPropertySet props)
        {
            configProps = props;
        }

        #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 sampleOper = new RestOperation("getCompositeSuitabilityMap",
                                                      new string[] { "xmin", "ymin", "xmax", "ymax", "height", "width", "r", "g", "b", "a", "compression" },
                                                      new string[] { "png", "jpg" },
                                                      GetCompositeSuitabilityMap);

            rootRes.operations.Add(sampleOper);

            return rootRes;
        }

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

            JsonObject result = new JsonObject();
            result.AddString("hello", "world");

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

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

            //extent
            double? xmin, ymin, xmax, ymax;
            long? width, height, r_layer_id, g_layer_id, b_layer_id, a_layer_id, compression;

            if (!operationInput.TryGetAsDouble("xmin", out xmin))
                throw new ArgumentNullException("xmin");

            if (!operationInput.TryGetAsDouble("ymin", out ymin))
                throw new ArgumentNullException("ymin");

            if (!operationInput.TryGetAsDouble("xmax", out xmax))
                throw new ArgumentNullException("xmax");

            if (!operationInput.TryGetAsDouble("ymax", out ymax))
                throw new ArgumentNullException("ymax");

            IEnvelope envelope = new EnvelopeClass() as IEnvelope;
            envelope.PutCoords(xmin.Value, ymin.Value, xmax.Value, ymax.Value);

            if (envelope == null)
                throw new ArgumentException("envelope is null", "envelope");

            //height and width
            if (!operationInput.TryGetAsLong("width", out width))
                throw new ArgumentException("GetCompositeSuitabilityMap: invalid input width", "width");

            if (!operationInput.TryGetAsLong("height", out height))
                throw new ArgumentException("GetCompositeSuitabilityMap: invalid input height", "height");

            //channel values
            if (!operationInput.TryGetAsLong("r", out r_layer_id))
                throw new ArgumentException("GetCompositeSuitabilityMap: red channel index is required", "r");

            if (!operationInput.TryGetAsLong("g", out g_layer_id))
                g_layer_id = -1;

            if (!operationInput.TryGetAsLong("b", out b_layer_id))
                b_layer_id = -1;

            if (!operationInput.TryGetAsLong("a", out a_layer_id))
                a_layer_id = -1;

            if (!operationInput.TryGetAsLong("compression", out compression))
                throw new ArgumentException("GetCompositeSuitabilityMap: invalid input compression", "compression");

            byte[] result = this.GetMap(envelope, 
                                        (int)width.Value, 
                                        (int)height.Value, 
                                        (int)r_layer_id.Value, 
                                        (int)g_layer_id.Value, 
                                        (int)b_layer_id.Value,
                                        (int)a_layer_id.Value,
                                        outputFormat,
                                        (int)compression.Value);

            responseProperties = "{\"Content-Type\" : \"image/" + outputFormat + "\"}";

            return result;
        }

        private byte[] GetMap(IEnvelope extent, int width, int height, int r, int g, int b, int a, string outputFormat, int compression=75)
        {
            IMapServer3 mapServer = serverObjectHelper.ServerObject as IMapServer3;
            if (mapServer == null)
                throw new Exception("Unable to access the map server.");

            IMapServerInfo mapServerInfo = mapServer.GetServerInfo(mapServer.DefaultMapName);
            IMapDescription mapDesc = mapServerInfo.DefaultMapDescription;

            //Set the Extent for the output image
            IMapExtent mapExtent = new MapExtentClass();
            mapExtent.Extent = extent;
            mapDesc.MapArea = (IMapArea)mapExtent;

            IImageDisplay pImgDisp = new ImageDisplayClass();
            pImgDisp.DeviceResolution = 96;
            pImgDisp.Height = height;
            pImgDisp.Width = width;

            IImageType pImgType = new ImageTypeClass();
            pImgType.ReturnType = esriImageReturnType.esriImageReturnMimeData;

            switch (outputFormat)
            {
                case "png":
                    pImgType.Format = esriImageFormat.esriImagePNG24;
                    break;
                case "jpg":
                    pImgType.Format = esriImageFormat.esriImageJPG;
                    break;
                default:
                    //if no valid format parameter exists the output will be png
                    pImgType.Format = esriImageFormat.esriImagePNG24;
                    break;
            }

            IImageDescription pImageDesc = new ImageDescriptionClass();
            pImageDesc.Display = pImgDisp;
            pImageDesc.Type = pImgType;

            //Get Images ================================================================================================
            Bitmap red, green = null, blue = null, alpha = null;
            ILayerDescriptions layerDescriptions = mapDesc.LayerDescriptions;

            //RED
            for (int i = 0; i < layerDescriptions.Count; i++)
            {
                ILayerDescription layer = layerDescriptions.get_Element(i);
                if (i == r)
                {
                    layer.Visible = true;
                }
                else
                {
                    layer.Visible = false;
                }
            }

            byte[] red_bytes = mapServer.ExportMapImage(mapDesc, pImageDesc).MimeData;
            MemoryStream red_ms = new MemoryStream(red_bytes, 0, red_bytes.Length);
            red = (Bitmap)System.Drawing.Image.FromStream(red_ms);

            //GREEN
            if (g > -1)
            {
                for (int i = 0; i < layerDescriptions.Count; i++)
                {
                    ILayerDescription layer = layerDescriptions.get_Element(i);
                    if (i == g)
                    {
                        layer.Visible = true;
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }

                byte[] green_bytes = mapServer.ExportMapImage(mapDesc, pImageDesc).MimeData;
                MemoryStream green_ms = new MemoryStream(green_bytes, 0, green_bytes.Length);
                green = (Bitmap)System.Drawing.Image.FromStream(green_ms);
            }

            //BLUE
            if (b > -1)
            {
                for (int i = 0; i < layerDescriptions.Count; i++)
                {
                    ILayerDescription layer = layerDescriptions.get_Element(i);
                    if (i == b)
                    {
                        layer.Visible = true;
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }

                byte[] blue_bytes = mapServer.ExportMapImage(mapDesc, pImageDesc).MimeData;
                MemoryStream blue_ms = new MemoryStream(blue_bytes, 0, blue_bytes.Length);
                blue = (Bitmap)System.Drawing.Image.FromStream(blue_ms);
            }

            //ALPHA
            if (a > -1)
            {
                for (int i = 0; i < layerDescriptions.Count; i++)
                {
                    ILayerDescription layer = layerDescriptions.get_Element(i);
                    if (i == a)
                    {
                        layer.Visible = true;
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }

                byte[] alpha_bytes = mapServer.ExportMapImage(mapDesc, pImageDesc).MimeData;
                MemoryStream alpha_ms = new MemoryStream(alpha_bytes, 0, alpha_bytes.Length);
                alpha = (Bitmap)System.Drawing.Image.FromStream(alpha_ms);
            }

            Bitmap mergedBitmap = MergeChannels(red, green, blue, alpha);

            byte[] result = null;
            switch (outputFormat)
            {
                case "png":
                    result = this.imageToPNG((System.Drawing.Image)mergedBitmap);
                    break;
                case "jpg":
                    result = this.bitmapToJPG(mergedBitmap, (System.Int64)compression);
                    break;
                default:
                    //if format not set it will default to just turning the result into a byte array uncompressioned for PNG
                    result = this.imageToPNG((System.Drawing.Image)mergedBitmap);
                    break;
            }

            return result;
        }

        //Channel Merging using AForge .NET Library
        static Bitmap MergeChannels(Bitmap red_channel_image,
                                    Bitmap green_channel_image=null,
                                    Bitmap blue_channel_image=null,
                                    Bitmap alpha_channel_image=null)
        {

            if (green_channel_image != null)
            {
                ExtractChannel greenFilter = new ExtractChannel(RGB.G);
                Bitmap gBitmap = greenFilter.Apply(green_channel_image);
                ReplaceChannel greenReplace = new ReplaceChannel(RGB.G, gBitmap);
                greenReplace.ApplyInPlace(red_channel_image);
            }

            if (blue_channel_image != null)
            {
                ExtractChannel blueFilter = new ExtractChannel(RGB.B);
                Bitmap bBitmap = blueFilter.Apply(blue_channel_image);
                ReplaceChannel blueReplace = new ReplaceChannel(RGB.B, bBitmap);
                blueReplace.ApplyInPlace(red_channel_image);
            }

            if (alpha_channel_image != null)
            {
                ExtractChannel alphaFilter = new ExtractChannel(RGB.R);
                Bitmap aBitmap = alphaFilter.Apply(alpha_channel_image);
                ReplaceChannel alphaReplace = new ReplaceChannel(RGB.A, aBitmap);
                alphaReplace.ApplyInPlace(red_channel_image);
            }

            return red_channel_image;
        }

        //Logic for PNG Encoding ============================================================================
        public byte[] imageToPNG(System.Drawing.Image image)
        {
            MemoryStream ms = new MemoryStream();
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            return ms.ToArray();
        }

        //Logic for Jpeg Encoding ============================================================================
        private byte[] bitmapToJPG(Bitmap bitmap, System.Int64 compression=75L)
        {
            byte[] result = null;

            //Get Encoder and Set Encoding Parameters
            ImageCodecInfo jgpEncoder = this.GetEncoder(ImageFormat.Jpeg);
            System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
            EncoderParameters myEncoderParameters = new EncoderParameters(1);
            EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, compression);
            myEncoderParameters.Param[0] = myEncoderParameter;

            //Write JPG to memory
            MemoryStream result_ms = new MemoryStream();
            bitmap.Save(result_ms, jgpEncoder, myEncoderParameters);
            result = new byte[result_ms.Length];
            result_ms.Position = 0;
            result_ms.Read(result, 0, Convert.ToInt32(result_ms.Length));

            return result;
        }

        private ImageCodecInfo GetEncoder(ImageFormat format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }
    }
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.