Common Web Mapping Application
Common_WebMappingApp_CSharp\Measure.ascx.cs
// Copyright 2010 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.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using ESRI.ArcGIS.ADF.Web;
using ESRI.ArcGIS.ADF.Web.UI.WebControls;
using ESRI.ArcGIS.ADF.Web.DataSources;
using ESRI.ArcGIS.ADF.Web.Geometry;

namespace WebMapApp
{

    public enum MapUnit
    {
        Resource_Default, Degrees, Feet, Meters
    }

    public enum MeasureUnit
    {
        Feet, Kilometers, Meters, Miles
    }

    public enum AreaUnit
    {
        Acres, Sq_Feet, Sq_Kilometers, Sq_Meters, Sq_Miles, Hectares
    }

    public partial class Measure : System.Web.UI.UserControl, IPostBackEventHandler, ICallbackEventHandler
    {
        MapResourceManager _resourceManger;
        public string CallbackInvocation = "";
        private string _mapBuddyId = "Map1";
        private string _id;
        private Map _map;
        private MapUnit _fallbackMapUnit = MapUnit.Degrees; // fallback value used if resource value is not available
        private MapUnit _mapUnits;
        private MapUnit _startMapUnits = MapUnit.Degrees;
        private MeasureUnit _measureUnits = MeasureUnit.Miles;
        private AreaUnit _areaUnits = AreaUnit.Sq_Miles;
        private double _numberDecimals = 4;

        protected void Page_Load(object sender, EventArgs e)
        {
            _id = this.ClientID;
            Page page = this.Page;
            // find the map control
            if (_mapBuddyId == null || _mapBuddyId.Length == 0) _mapBuddyId = "Map1";
            _map = page.FindControl(_mapBuddyId) as Map;
            // find the map resource manager
            _resourceManger = page.FindControl(_map.MapResourceManager) as MapResourceManager;
            CallbackInvocation = CallbackFunctionString;

        }
        protected internal virtual string CallbackFunctionString
        {
            get
            {
                if(ScriptManager.GetCurrent(Page) != null)
                  return String.Format("__esriDoPostBack('{0}','{1}', argument, ESRI.ADF.System.ProcessMSAjaxCallbackResult, context)", this.UniqueID, this.ClientID);
                else
                    return Page.ClientScript.GetCallbackEventReference(this, "argument", "ESRI.ADF.System.processCallbackResult", "context", "postBackError", false);
            }
        }
        protected void Page_PreRender(object sender, EventArgs e)
        {
            MeasureDisplayPanel.InnerHtml = WriteInitialContents();
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);
            if(sm != null)
                sm.RegisterAsyncPostBackControl(this);
        }




        #region Action Methods

        IMapFunctionality _mapFunctionality;
        private IMapFunctionality MapFunctionality
        {
            get
            {
                if (_mapFunctionality == null)
                {
                    // use the primary resouce, if defined
                    string primeResource = _map.PrimaryMapResource;
                    IGISResource resource = null;
                    if (primeResource != null && primeResource.Length > 0)
                        resource = _resourceManger.GetResource(primeResource);
                    if (resource == null)
                    {
                        for (int i = 0; i < _resourceManger.ResourceItems.Count; i++)
                        {
                            resource = _resourceManger.GetResource(i);
                            if (resource != null)
                                break;
                        }
                    }
                    if (resource != null) _mapFunctionality = (IMapFunctionality)resource.CreateFunctionality(typeof(IMapFunctionality), "mapFunctionality");
                }
                return _mapFunctionality;
            }
        }

        private string ProcessMeasureRequest(NameValueCollection queryString)
        {
            object o = Session["MeasureMapUnits"];
            if (o != null)
                _mapUnits = (MapUnit)Enum.Parse(typeof(MapUnit), o.ToString());
            else if (_startMapUnits == MapUnit.Resource_Default)
                _mapUnits = GetResourceDefaultMapUnit();
            else
                _mapUnits = _startMapUnits;
            bool forceRefresh = queryString["refresh"] == "true";
            string eventArg = queryString["EventArg"];
            string vectorAction = queryString["VectorMode"];
            string[] coordPairs, xys;
            string coordString = queryString["coords"];
            if (coordString == null && coordString.Length == 0)
                coordString = "";
            coordPairs = coordString.Split(char.Parse("|"));
            string mapUnitString = queryString["MapUnits"];
            if (mapUnitString != null && mapUnitString.Length > 0)
                _mapUnits = (MapUnit)Enum.Parse(typeof(MapUnit), mapUnitString);
            Session["MeasureMapUnits"] = _mapUnits;
            string measureUnitString = queryString["MeasureUnits"];
            if (measureUnitString != null && measureUnitString.Length > 0)
                _measureUnits = (MeasureUnit)Enum.Parse(typeof(MeasureUnit), measureUnitString);
            string areaUnitstring = queryString["AreaUnits"];
            if (areaUnitstring != null && areaUnitstring.Length > 0)
                _areaUnits = (AreaUnit)Enum.Parse(typeof(AreaUnit), areaUnitstring);
            string response = null;
            PointCollection points = new PointCollection();
            PointCollection dPoints = new PointCollection();
            ArrayList distances = new ArrayList();
            double totalDistance = 0;
            double segmentDistance = 0;
            double area = 0;
            double perimeter = 0;
            double roundFactor = Math.Pow(10, _numberDecimals);
            double xD, yD, tempDist, tempDist2, tempArea, x1, x2, y1, y2;

            if (vectorAction == "measure")
            {
                if (coordPairs != null && coordPairs.Length > 1)
                {
                    for (int cp = 0; cp < coordPairs.Length; cp++)
                    {
                        xys = coordPairs[cp].Split(char.Parse(":"));
                        points.Add(new Point(Convert.ToDouble(xys[0], System.Globalization.CultureInfo.InvariantCulture), Convert.ToDouble(xys[1], System.Globalization.CultureInfo.InvariantCulture)));
                        if (cp > 0)
                        {
                            // check for duplicate points from double click.... Firefox will send coords for both clicks, causing segmentDistance to be zero.
                            if (points[cp - 1].X != points[cp].X || points[cp - 1].Y != points[cp].Y)
                            {
                                if (_mapUnits == MapUnit.Degrees)
                                {
                                    // use great circle formula
                                    tempDist = DegreeToFeetDistance(points[cp - 1].X, points[cp - 1].Y, points[cp].X, points[cp].Y);
                                    y1 = DegreeToFeetDistance(points[cp].X, points[cp].Y, points[cp].X, 0);
                                    x1 = DegreeToFeetDistance(points[cp].X, points[cp].Y, 0, points[cp].Y);
                                    dPoints.Add(new Point(x1, y1));
                                    segmentDistance = ConvertUnits(tempDist, MapUnit.Feet, _measureUnits);
                                }
                                else
                                {
                                    // get third side of triangle for distance
                                    xD = Math.Abs(points[cp].X - points[cp - 1].X);
                                    yD = Math.Abs(points[cp].Y - points[cp - 1].Y);
                                    tempDist = Math.Sqrt(Math.Pow(xD, 2) + Math.Pow(yD, 2));
                                    segmentDistance = ConvertUnits(tempDist, _mapUnits, _measureUnits);

                                }


                                distances.Add(segmentDistance);
                                totalDistance += segmentDistance;
                                segmentDistance = Math.Round(segmentDistance * roundFactor) / roundFactor;
                                totalDistance = Math.Round(totalDistance * roundFactor) / roundFactor;
                            }
                        }
                        else
                        {
                            if (_mapUnits == MapUnit.Degrees)
                            {
                                y1 = DegreeToFeetDistance(points[cp].X, points[cp].Y, points[cp].X, 0);
                                x1 = DegreeToFeetDistance(points[cp].X, points[cp].Y, 0, points[cp].Y);
                                dPoints.Add(new Point(x1, y1));
                            }
                        }
                    }
                }
                if (eventArg == "polygon")
                {
                    if (points.Count > 2 || forceRefresh)
                    {
                        if (_mapUnits == MapUnit.Degrees)
                        {
                            tempDist = DegreeToFeetDistance(points[points.Count - 1].X, points[points.Count - 1].Y, points[0].X, points[0].Y);
                            tempDist2 = ConvertUnits(tempDist, MapUnit.Feet, _measureUnits);
                            distances.Add(tempDist2);
                            dPoints.Add(dPoints[0]);
                        }
                        else
                        {
                            xD = Math.Abs(points[points.Count - 1].X - points[0].X);
                            yD = Math.Abs(points[points.Count - 1].Y - points[0].Y);
                            tempDist = Math.Sqrt(Math.Pow(xD, 2) + Math.Pow(yD, 2));
                            tempDist2 = ConvertUnits(tempDist, _mapUnits, _measureUnits);
                            distances.Add(tempDist2);
                        }
                        points.Add(points[0]);
                        perimeter = totalDistance + tempDist2;
                        // add area calculation
                        tempArea = 0;
                        MapUnit mUnits = _mapUnits;
                        double xDiff, yDiff;
                        if (_mapUnits == MapUnit.Degrees)
                        {
                            points = dPoints;
                            mUnits = MapUnit.Feet;
                        }
                        for (int j = 0; j < points.Count - 1; j++)
                        {
                            x1 = Convert.ToDouble(points[j].X, System.Globalization.CultureInfo.InvariantCulture);
                            x2 = Convert.ToDouble(points[j + 1].X, System.Globalization.CultureInfo.InvariantCulture);
                            y1 = Convert.ToDouble(points[j].Y, System.Globalization.CultureInfo.InvariantCulture);
                            y2 = Convert.ToDouble(points[j + 1].Y, System.Globalization.CultureInfo.InvariantCulture);
                            xDiff = x2 - x1;
                            yDiff = y2 - y1;
                            tempArea += x1 * yDiff - y1 * xDiff;
                        }
                        tempArea = Math.Abs(tempArea) / 2;
                        area = ConvertAreaUnits(tempArea, mUnits, _areaUnits);
                        perimeter = Math.Round(perimeter * roundFactor) / roundFactor;
                        area = Math.Round(area * roundFactor) / roundFactor;
                    }
                    else
                        response = String.Format( "<table cellspacing='0' ><tr><td>Perimeter: </td><td align='right' id=\"tdperimiter\"> 0</td><td >{0}</td></tr><tr><td>Area:</td><td align='right' id=\"tdarea\">0 </td><td>{1}</td></tr></table>", WriteMeasureUnitDropDown(), WriteAreaUnitDropDown());
                }
                else if (eventArg == "polyline")
                {
                    if (points.Count < 3 || forceRefresh)
                        response = String.Format("<table cellspacing='0' ><tr><td>Segment: </td><td align='right' id=\"tdsegment\">{0} </td><td>{1}</td></tr><tr><td>Total Length:</td><td align='right' id=\"tdtotaldistance\">{2} </td><td>{3}</td></tr></table>", String.Format("{0}",segmentDistance), _measureUnits, String.Format("{0}",totalDistance), WriteMeasureUnitDropDown());
                }
                else if (eventArg == "point" && coordPairs.Length > 0)
                {
                    xys = coordPairs[0].Split(char.Parse(":"));
                    response = String.Format("<table cellspacing='0' ><tr><td>X Coordinate:</td><td align='right' dir='ltr'>{0}</td></tr><tr><td>Y Coordinate:</td><td align='right' dir='ltr'>{1}</td></tr></table>", (Math.Round(Convert.ToDouble(xys[0], System.Globalization.CultureInfo.InvariantCulture) * roundFactor) / roundFactor).ToString(), (Math.Round(Convert.ToDouble(xys[1], System.Globalization.CultureInfo.InvariantCulture) * roundFactor) / roundFactor).ToString());
                }
            }
            CallbackResultCollection coll = new CallbackResultCollection();
            coll.Add(new CallbackResult("", "", "invoke", "measureComplete",
                new object[] { response, _id, String.Format("{0}", area), String.Format("{0}", perimeter), String.Format("{0}", segmentDistance), String.Format("{0}", totalDistance) }));
            return coll.ToString();
        }

        private string CheckFormMeasureUnits(string unit)
        {
            string response = "";
            if (unit == _measureUnits.ToString())
                response = "selected=\"selected\"";
            return response;
        }

        private string CheckFormAreaUnits(string unit)
        {
            string response = "";
            if (unit == _areaUnits.ToString())
                response = "selected=\"selected\"";
            return response;
        }

        private string WriteMeasureUnitDropDown()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            sb.Append("<select id=\"MeasureUnits2\" onchange=\"changeMeasureUnits()\" style=\"font: normal 7pt Verdana; width: 100px;\">");
            Array mArray = Enum.GetValues(typeof(MeasureUnit));
            foreach (MeasureUnit mu in mArray)
            {
                sb.AppendFormat("<option value=\"{0}\" {1}>{0}</option>", mu, CheckFormMeasureUnits(mu.ToString()));

            }
            sb.Append("</select>");

            return sb.ToString();
        }

        private string WriteAreaUnitDropDown()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            sb.Append("<select id=\"AreaUnits2\" onchange=\"changeAreaUnits()\" style=\"font: normal 7pt Verdana; width: 100px;\">");
            Array aArray = Enum.GetValues(typeof(AreaUnit));
            foreach (AreaUnit au in aArray)
            {
                sb.AppendFormat("<option value=\"{0}\" {1}>{0}</option>", au, CheckFormAreaUnits(au.ToString()));

            }
            sb.Append("</select>");

            return sb.ToString();
        }

        private string WriteInitialContents()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            // set up tool buttons
            sb.Append("<table cellpadding='0' cellspacing='0' ><tr>\n");
            sb.Append("<td id='MeasureToolbarButton_point' style='border: solid White 1px; background-color: White;' onmouseover='this.style.cursor=\"pointer\"; this.style.borderColor=\"Black\";' onmouseout='checkMeasureToolbarBorder(this, \"point\")' onmousedown='setMeasureToolbarTool(\"point\")'><img id='ToolbarImage_point' src='images/measure-point.png' align='middle' alt='Point - Coordinates' title='Point - Coordinates' style='padding: 0px 0px 0px 0px' /></td>\n");
            sb.Append("<td id='MeasureToolbarButton_polyline' style='border: solid Black 1px; background-color: #EEEEEE;' onmouseover='this.style.cursor=\"pointer\";this.style.borderColor=\"Black\";' onmouseout='checkMeasureToolbarBorder(this, \"polyline\")' onmousedown='setMeasureToolbarTool(\"polyline\")'><img id='ToolbarImage_polyline' src='images/measure-line.png' align='middle' alt='Line - Distance' title='Line - Distance' style='padding: 0px 0px 0px 0px' /></td>\n");
            sb.Append("<td id='MeasureToolbarButton_polygon' style='border: solid White 1px; background-color: White;' onmouseover='this.style.cursor=\"pointer\";this.style.borderColor=\"Black\";' onmouseout='checkMeasureToolbarBorder(this, \"polygon\")' onmousedown='setMeasureToolbarTool(\"polygon\")'><img id='ToolbarImage_polygon' src='images/measure-poly.png' align='middle' alt='Polygon - Area' title='Polygon - Area' style='padding: 0px 0px 0px 0px' />  </td>\n");
            sb.Append("</tr></table>\n");
            sb.AppendFormat("<input id='MeasureUnits' type='hidden' value='{0}'/>\n", MeasureUnits);
            sb.AppendFormat(" <input id='AreaUnits' type='hidden' value='{0}'/>\n", AreaUnits);
            // create display area
            sb.Append("<table id='MeasureToolbarTable' cellspacing='2' cellpadding='1' style=' width: 100%;font: normal 7pt Verdana; '>\n");
            sb.Append("<tr><td style='background-color: #ffffff' id='MeasureDisplay' colspan='2'  valign='top'>\n");
            sb.Append("Click on the map and draw a line. Double-click to end line.\n");
            sb.Append("</td></tr>\n");
            sb.Append("</table>\n");

            return sb.ToString();
        }

        #endregion

        #region Conversion Methods

        private double ConvertUnits(double distance, MapUnit fromUnits, MeasureUnit toUnits)
        {
            double mDistance = distance;
            if (fromUnits == MapUnit.Feet)
            {
                if (toUnits == MeasureUnit.Miles)
                {
                    mDistance = distance / 5280;
                }
                else if (toUnits == MeasureUnit.Meters)
                {
                    mDistance = distance * 0.304800609601;
                }
                else if (toUnits == MeasureUnit.Kilometers)
                {
                    mDistance = distance * 0.0003048;
                }
            }
            else
            {
                if (toUnits == MeasureUnit.Miles)
                {
                    mDistance = distance * 0.0006213700922;
                }
                else if (toUnits == MeasureUnit.Feet)
                {
                    mDistance = distance * 3.280839895;
                }
                else if (toUnits == MeasureUnit.Kilometers)
                {
                    mDistance = distance / 1000;
                }
            }
            return mDistance;
        }

        private double ConvertAreaUnits(double area, MapUnit baseUnits, AreaUnit toUnits)
        {
            double mArea = area;
            if (baseUnits == MapUnit.Feet)
            {
                if (toUnits == AreaUnit.Acres)
                    mArea = area * 0.000022956;
                else if (toUnits == AreaUnit.Sq_Meters)
                    mArea = area * 0.09290304;
                else if (toUnits == AreaUnit.Sq_Miles)
                    mArea = area * 0.00000003587;
                else if (toUnits == AreaUnit.Sq_Kilometers)
                    mArea = area * 0.09290304 / 1000000;
                else if (toUnits == AreaUnit.Hectares)
                    mArea = area * 0.09290304 * 0.0001;
            }
            else if (baseUnits == MapUnit.Meters)
            {
                if (toUnits == AreaUnit.Acres)
                    mArea = area * 0.0002471054;
                else if (toUnits == AreaUnit.Sq_Miles)
                    mArea = area * 0.0000003861003;
                else if (toUnits == AreaUnit.Sq_Kilometers)
                    mArea = area * 1.0e-6;
                else if (toUnits == AreaUnit.Sq_Feet)
                    mArea = area * 10.76391042;
                else if (toUnits == AreaUnit.Hectares)
                    mArea = area * 0.0001;
            }

            return mArea;
        }

        private double DegreeToFeetDistance(double x1, double y1, double x2, double y2)
        {
            // use great circle formula
            double Lat1 = DegToRad(y1);
            double Lat2 = DegToRad(y2);
            double Lon1 = DegToRad(x1);
            double Lon2 = DegToRad(x2);
            double LonDist = Lon1 - Lon2;
            double LatDist = Lat1 - Lat2;
            double x = Math.Pow(Math.Sin(LatDist / 2), 2) + Math.Cos(Lat1) * Math.Cos(Lat2) * Math.Pow(Math.Sin(LonDist / 2), 2);
            x = 2 * Math.Asin(Math.Min(1, Math.Sqrt(x)));
            x = (3963 - 13 * Math.Sin((Lat1 + Lat2) / 2)) * x;
            // in miles... convert to feet and use that as base
            return (x * 5280);
        }

        private double DegToRad(double degrees)
        {
            return degrees * Math.PI / 180.0;
        }

        private MapUnit GetResourceDefaultMapUnit()
        {
            MapUnit mUnit = MapUnit.Degrees;
            try
            {
                Units mu = MapFunctionality.Units;
                if (mu == Units.DecimalDegrees)
                    mUnit = MapUnit.Degrees;
                else if (mu == Units.Feet)
                    mUnit = MapUnit.Feet;
                else if (mu == Units.Meters)
                    mUnit = MapUnit.Meters;
            }
            catch
            {
                // cannot get units from resource... default to fallback value set in declaration
                mUnit = _fallbackMapUnit;
            }
            return mUnit;

        }

        /// <summary>
        /// Whether map has at least one resource that supports returning map units
        /// </summary>
        /// <returns></returns>
        public bool CanGetUnits()
        {
            foreach (ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality mapFunc in _map.GetFunctionalities())
            {
                try
                {
                    Units units = mapFunc.Units;
                    return true;
                }
                catch
                {
                }
            }
            return false;
        }


        #endregion
        #region IPostBackEventHandler Members

        public void RaisePostBackEvent(string eventArgument)
        {
            string strResult = processPostbackEvent(eventArgument);
            if (!String.IsNullOrEmpty(strResult))
                ScriptManager.GetCurrent(this.Page).RegisterDataItem(this, strResult, false);
        }


        private string processPostbackEvent(string requestString)
        {
            // break out the responseString into a querystring
            Array keyValuePairs = requestString.Split("&".ToCharArray());
            NameValueCollection queryString = new NameValueCollection();
            Page page = this.Page;
            Map map = page.FindControl(this._mapBuddyId) as Map;
            string[] keyValue;
            string response = "";
            if (keyValuePairs.Length > 0)
            {
                for (int i = 0; i < keyValuePairs.Length; i++)
                {
                    keyValue = keyValuePairs.GetValue(i).ToString().Split("=".ToCharArray());
                    queryString.Add(keyValue[0], keyValue[1]);
                }
            }
            else
            {
                keyValue = requestString.Split("=".ToCharArray());
                if (keyValue.Length > 0)
                    queryString.Add(keyValue[0], keyValue[1]);
            }
            // isolate control type and mode
            string controlType = queryString["ControlType"];
            string eventArg = queryString["EventArg"];
            if (controlType == null) controlType = "Map";


            switch (controlType)
            {
                case "Map":
                    // request is for the map control
                    string vectorMode = queryString["VectorMode"];
                    if (vectorMode != null && vectorMode == "measure")
                    {
                        response = ProcessMeasureRequest(queryString);
                    }
                    break;
                default:
                    //
                    break;
            }
            return response;
        }

        #endregion


        #region Properties

        public string Id
        {
            get { return _id; }
            set { _id = value; }
        }

        private MapResourceManager MapResourceManager
        {
            get { return _resourceManger; }
            set { _resourceManger = value; }
        }

        /// <summary>
        /// Id of Buddy MapControl
        /// </summary>
        public string MapBuddyId
        {
            get { return _mapBuddyId; }
            set { _mapBuddyId = value; }
        }


        /// <summary>
        /// Unit used resource. Resource_Default will return value from resource, if available. Other values will force calculations to use that unit.
        /// </summary>
        public MapUnit MapUnits
        {
            get { return _startMapUnits; }
            set { _startMapUnits = value; }
        }

        /// <summary>
        ///  Unit used in display of linear measurements.
        /// </summary>
        public MeasureUnit MeasureUnits
        {
            get { return _measureUnits; }
            set { _measureUnits = value; }
        }

        /// <summary>
        ///  Area Units - Unit used in display of area measurements.
        /// </summary>
        public AreaUnit AreaUnits
        {
            get { return _areaUnits; }
            set { _areaUnits = value; }
        }

        // Number of Decimals - Number of decimal digits displayed in measurements.
        public double NumberDecimals
        {
            get { return _numberDecimals; }
            set { _numberDecimals = value; }
        }


        public override bool Visible
        {
            get { return base.Visible; }
        }

        public override bool EnableTheming
        {
            get
            {
                return base.EnableTheming;
            }
        }

        public override bool EnableViewState
        {
            get
            {
                return base.EnableViewState;
            }
        }

        #endregion


        #region ICallbackEventHandler Members
        private string callbackArg;
        public string GetCallbackResult()
        {
            return processPostbackEvent(callbackArg);
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            callbackArg = eventArgument;
        }

        #endregion
    }
}