Query Demographics
DockWindow.cs
// Copyright 2011 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.
// 

// 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 at <your ArcGIS Explorer install location>/DeveloperKit/userestrictions.txt.
// 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using ESRI.ArcGISExplorer;
using ESRI.ArcGISExplorer.Application;
using ESRI.ArcGISExplorer.Mapping;
using ESRI.ArcGISExplorer.Geometry;
using ESRI.ArcGISExplorer.Data;
using ESRI.ArcGISExplorer.Threading;

namespace QueryDemographicsCS
{
    public partial class DockWindow : ESRI.ArcGISExplorer.Application.DockWindow
    {
        private MapDisplay _mapDisp = ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay;

        public DockWindow()
        {
            InitializeComponent();
        }

        private void txtLocation_DragDrop(object sender, DragEventArgs e)
        {
            //Make sure we have a treenode being dropped
            if (!e.Data.GetDataPresent(typeof(TreeNode))) return;

            //From the node, grab the text to display and then save the note for use later
            TreeNode node = ((TreeNode)e.Data.GetData(typeof(TreeNode))).Clone() as TreeNode;
            txtLocation.Text = node.Text;
            txtLocation.Tag = node.Tag;
        }

        private void txtLocation_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.None;

            //make sure that we only allow point data from notes
            TreeNode node = ((TreeNode)e.Data.GetData(typeof(TreeNode))).Clone() as TreeNode;

            ESRI.ArcGISExplorer.Mapping.Note result = node.Tag as Note;
            if (result != null)
            {
                if (result.Graphic.Geometry.GetType() == typeof(ESRI.ArcGISExplorer.Geometry.Point))
                {
                    e.Effect = DragDropEffects.Move;
                    return;
                }
            }

            return;
        }

        private void btnExecute_Click(object sender, EventArgs e)
        {
            //Make sure we have values in the location and distance boxes.
            if (txtDistance.Text == "" || txtLocation.Text == "") return;

            //Make sure the value in the distance box is numeric
            if (!IsNumeric(txtDistance.Text))
            {
                MessageBox.Show("Distance value must be numeric!!", "Bad Value");
                return;
            }

            //Make sure distance value is less than 10 miles
            if (System.Convert.ToDouble(txtDistance.Text) > 10)
            {
                MessageBox.Show("Distance value must be less than 10 miles!!", "Bad Value");
                return;
            }

            //Get the point geometry out of the tag on the Location textbox
            ESRI.ArcGISExplorer.Geometry.Point queryPt = null;
            if (txtLocation.Tag.GetType() == typeof(Note))
            {
                ESRI.ArcGISExplorer.Mapping.Note result = txtLocation.Tag as Note;
                queryPt = result.Graphic.Geometry as ESRI.ArcGISExplorer.Geometry.Point;
            }
            else
            {
                queryPt = txtLocation.Tag as ESRI.ArcGISExplorer.Geometry.Point;
            }
            if (queryPt == null) return;

            //Execute the query against the Demographics data
            QueryDemographics(queryPt, System.Convert.ToDouble(txtDistance.Text));
        }

        private void QueryDemographics(ESRI.ArcGISExplorer.Geometry.Point queryPt, double dist)
        {
            //Open up ProgressHelper to show are progress.
            ProgressHelper help = new ProgressHelper("Query Progress", "Calculating Demographics in the area", "Forming query polygon ...");
            help.Show();

            List<ESRI.ArcGISExplorer.Geometry.Point> pts = new List<ESRI.ArcGISExplorer.Geometry.Point>();
            pts.Add(GeometryOperations.Move(queryPt, dist, dist, Unit.Linear.MilesStatute) as ESRI.ArcGISExplorer.Geometry.Point);
            pts.Add(GeometryOperations.Move(queryPt, dist, (dist * -1), Unit.Linear.MilesStatute) as ESRI.ArcGISExplorer.Geometry.Point);
            pts.Add(GeometryOperations.Move(queryPt, (dist * -1), (dist * -1), Unit.Linear.MilesStatute) as ESRI.ArcGISExplorer.Geometry.Point);
            pts.Add(GeometryOperations.Move(queryPt, (dist * -1), dist, Unit.Linear.MilesStatute) as ESRI.ArcGISExplorer.Geometry.Point);
            Polygon searchPoly = new Polygon(pts, queryPt.CoordinateSystem);
            searchPoly.Close();
            searchPoly = (Polygon)GeometryOperations.Project(searchPoly, CoordinateSystem.GeographicCoordinateSystems.NorthAmerica.NAD1983);

                   help.UpdateMessage("Executing SOAP call to ArcGIS Online ...");

            ////Make the connection to the server we will use for retrieving demographic information
            censusServer.ESRI_Census_USA_MapServer mapServer = new censusServer.ESRI_Census_USA_MapServer();
            mapServer.Url = "http://sampleserver1.arcgisonline.com/ArcGIS/services/Demographics/ESRI_Census_USA/MapServer";

            //Call the Census service to calculate the demographic information for the polygon.
            censusServer.SpatialFilter spf = new censusServer.SpatialFilter();
            spf.FilterGeometry = SoapConverter.GeometryToSoap<Polygon,censusServer.PolygonN>(searchPoly);

            spf.SpatialRel = censusServer.esriSpatialRelEnum.esriSpatialRelIntersects;
            spf.SubFields = "AGE_UNDER5, AGE_5_17, AGE_18_21, AGE_22_29, AGE_30_39, AGE_40_49, AGE_50_64, AGE_65_UP, MALES, FEMALES";
            string temp = mapServer.GetDefaultMapName();
            censusServer.RecordSet recs = mapServer.QueryFeatureData("Layers", 1, spf);

            help.UpdateMessage("Summing results of query ...");

            //Loop through the records that were returned and tally up the demographic data
            int males = 0;
            int females = 0;
            int age_u5 = 0;
            int age_5_17 = 0;
            int age_18_21 = 0;
            int age_22_29 = 0;
            int age_30_39 = 0;
            int age_40_49 = 0;
            int age_50_64 = 0;
            int age_65_up = 0;
            for (int j = 0; j < recs.Records.Length; j++)
            {
                censusServer.Record censusRec = recs.Records[j];
                males += System.Convert.ToInt16(censusRec.Values[9]);
                females += System.Convert.ToInt16(censusRec.Values[10]);
                age_u5 += System.Convert.ToInt16(censusRec.Values[1]);
                age_5_17 += System.Convert.ToInt16(censusRec.Values[2]);
                age_18_21 += System.Convert.ToInt16(censusRec.Values[3]);
                age_22_29 += System.Convert.ToInt16(censusRec.Values[4]);
                age_30_39 += System.Convert.ToInt16(censusRec.Values[5]);
                age_40_49 += System.Convert.ToInt16(censusRec.Values[6]);
                age_50_64 += System.Convert.ToInt16(censusRec.Values[7]);
                age_65_up += System.Convert.ToInt16(censusRec.Values[8]);
            }
            int genderTotal = males + females;
            int ageTotal = age_u5 + age_5_17 + age_18_21 + age_22_29 + age_30_39 + age_40_49 + age_50_64 + age_65_up;
            double dMales = (System.Convert.ToDouble(males) / System.Convert.ToDouble(genderTotal)) * 100;
            double dFemales = (System.Convert.ToDouble(females) / System.Convert.ToDouble(genderTotal)) * 100;
            double dage_u5 = (System.Convert.ToDouble(age_u5) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_5_17 = (System.Convert.ToDouble(age_5_17) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_18_21 = (System.Convert.ToDouble(age_18_21) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_22_29 = (System.Convert.ToDouble(age_22_29) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_30_39 = (System.Convert.ToDouble(age_30_39) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_40_49 = (System.Convert.ToDouble(age_40_49) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_50_64 = (System.Convert.ToDouble(age_50_64) / System.Convert.ToDouble(ageTotal)) * 100;
            double dage_65_up = (System.Convert.ToDouble(age_65_up) / System.Convert.ToDouble(ageTotal)) * 100;

            help.UpdateMessage("Creating popup message ...");

            //Figure out the percentage information for males and females, then generate the popup using
            //the Google chart api.
            string perc = "Males(" + dMales.ToString("###.#") + "%)|Females(" + dFemales.ToString("###.#") + "%)";
            string popup = "<b><font size='4'>" + "Gender</font></b>" + "<br><img src=\"" + "http://chart.apis.google.com/chart?cht=p3&chd=t:" + dMales.ToString() + "," + dFemales.ToString() + "&chs=350x100&chl=" + perc + "\" />";
            popup += "<br><br><b><font size='4'>" + "Age</font></b>" + "<br><img src=\"" + "http://chart.apis.google.com/chart?cht=p3&chd=t:" + dage_u5.ToString() + "," + dage_5_17.ToString();
            popup += "," + dage_18_21.ToString() + "," + dage_22_29.ToString() + "," + dage_30_39.ToString() + "," + dage_40_49.ToString() + "," + dage_50_64 + "," + dage_65_up + "&chs=350x100&chl=Under 5|5 to 17|18 to 21|22 to 29|30 to 39|40 to 49|50 to 64|over 64" + "\" />";

            //Add the polygon to the map as a note or graphic depending on the checkbox
            Graphic searchGraphic = new Graphic(searchPoly, Symbol.CreateFill(Color.Orange, Color.Black));
            searchGraphic.Tag = "demographic query polygon";
            if (chkResult.Checked)
            {
                Note newNote = new Note(txtDistance.Text + " mile demographic query", searchGraphic);
                newNote.Popup.Content  = popup;
                _mapDisp.Map.ChildItems.Add(newNote);
            }
            else
            {
                RemovePreviousGraphic();
                _mapDisp.Graphics.Add(searchGraphic);            
                btnRemove.Enabled = true;
            }

            //Check extent and make sure we aren't zoomed in too far to show all of search shape
            Envelope env = GeometryOperations.Scale(searchPoly.GetEnvelope(),1.25) as Envelope;
            if (_mapDisp.Extent.Height < env.Height || _mapDisp.Extent.Width < env.Width) _mapDisp.ZoomTo(env);

            //Whether we are adding a graphic or a note, we can still show the popup information (it
            //just won't be stored for the graphic).
            ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay.HidePopups(true);
            Popup tempPopup = new Popup(ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay, popup, "Demographic information");
            tempPopup.Activate();
          
            help.Close();

            return;
        }

        private void RemovePreviousGraphic()
        {
            if (_mapDisp.Graphics.Count == 0) return;

            //Loop through the graphics in the map and remove the first one found with the demographic query tag
            GraphicCollection graphs = _mapDisp.Graphics;
            foreach (Graphic graph in graphs)
            {
                if ((string)graph.Tag == "demographic query polygon")
                {
                    _mapDisp.Graphics.Remove(graph);
                    return;
                }
            }
        }

        // IsNumeric Function
        static bool IsNumeric(object Expression)
        {
            // Variable to collect the Return value of the TryParse method.
            bool isNum;

            // Define variable to collect out parameter of the TryParse method. If the conversion fails, the out parameter is zero.
            double retNum;

            // The TryParse method converts a string in a specified style and culture-specific format to its double-precision floating point number equivalent.
            // The TryParse method does not generate an exception if the conversion fails. If the conversion passes, True is returned. If it does not, False is returned.
            isNum = Double.TryParse(Convert.ToString(Expression), System.Globalization.NumberStyles.Any, System.Globalization.NumberFormatInfo.InvariantInfo, out retNum);
            return isNum;
        }

        private void btnRemove_Click(object sender, EventArgs e)
        {
            //Call the routine to search for a demographic query graphic
            RemovePreviousGraphic();
            btnRemove.Enabled = false;
        }

        private void btnLocate_Click(object sender, EventArgs e)
        {
            //Capture a point from the map
            ESRI.ArcGISExplorer.Geometry.Point loc = _mapDisp.TrackPoint();
            if (loc == null) return;

            //If we got a point, then add some text to the location box and the point as the tag
            txtLocation.Text = loc.Y.ToString("###.##") + " " + loc.X.ToString("###.##");
            txtLocation.Tag = loc as object;
        }

    }
}