Monday, June 29, 2015

ASP.Net C# Displaying Farmers Markets Data from MySafeInfo in Google Maps

This example uses a combination of technologies such ASP.Net, C#, JavaScript, jQuery, JSON, XML, and XSL to retrieve State Farmers Markets data from MySafeInfo and display the results in a Bootstrap table and on a Google Map.

Here's the ASPX page:

 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.farmers_markets._default" %>  
   
 <!DOCTYPE html>  
   
 <html xmlns="http://www.w3.org/1999/xhtml">  
 <head runat="server">  
   <!-- title -->  
   <title>State Farmers Markets</title>  
   
   <!-- jquery -->  
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>  
   
   <!-- bootstrap -->  
   <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />  
   <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>  
   
   <!-- google maps -->  
   <script language="javascript" type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={key}&sensor=false&language=en"></script>  
   
   <!-- scripts -->  
   <script language="javascript" type="text/javascript" src="../js/functions.js?cache=2015062801"></script>  
   <script language="javascript" type="text/javascript" src="../js/spin.min.js?cache=2015062801"></script>  
   
   <!-- custom style -->  
   <style>  
     body {  
       margin: 25px !important;  
     }  
   
     .form-group {  
       width: 100%;  
     }  
   
     #map-canvas {  
       width: 100%;  
       height: 500px;  
     }  
   </style>  
   
   <script language="javascript" type="text/javascript">  
     // initialize  
     $(document).ready(function () {  
       InitializeMap();  
     });  
   
     function InitializeMap() {  
       // variables  
       var options = { center: new google.maps.LatLng(0, 0), mapTypeId: google.maps.MapTypeId.ROADMAP, scrollwheel: false, draggable: true };  
       var map = new google.maps.Map(document.getElementById("map-canvas"), options);  
       var bounds = new google.maps.LatLngBounds();  
       var locations = jQuery.parseJSON($("#lblLocations").val());  
   
       // add locations to map  
       $.each(locations, function () {  
         // variables  
         var latLong = new google.maps.LatLng(this.latitude, this.longitude);  
         var Url = this.url.replace(/&amp;/g, "&");  
   
         // marker  
         var marker = new google.maps.Marker({  
           map: map,  
           title: "{0} ({1})".format(this.market, this.city),  
           position: latLong,  
           draggable: false,  
           icon: "images/marker.png"  
         });  
   
         // bounds  
         bounds.extend(latLong);  
   
         // click event  
         if (Url.HasValue()) {  
           google.maps.event.addListener(marker, 'click', function () { window.open(Url) });  
         }  
       });  
         
       // fit bounds  
       map.fitBounds(bounds);  
     }  
   
     function btnSearch_Click() {  
       // controls  
       var txtZipCode = $("#txtZipCode");  
   
       // variables  
       var ZipCode = txtZipCode.val().trim();  
   
       // validation  
       if (!IsValidZip(ZipCode)) {  
         ShowMessage('Please enter a valid zip code.', 'txtZipCode', 'txtZipCode');  
         return false;  
       }  
   
       // show progress  
       ShowProgress();  
   
       // return value  
       return true;  
     }  
   </script>  
 </head>  
   
 <body>  
   <form id="form1" runat="server">  
     <div class="form-group">  
       <h2>  
         <asp:Literal ID="lblTitle" runat="server" />  
       </h2>  
     </div>  
   
     <!-- map -->  
     <div class="form-group">  
        <div id="map-canvas"></div>  
     </div>  
   
     <!-- filter -->  
     <div class="form-group" style="padding-top: 15px">  
       <asp:Panel ID="pnlSearch" DefaultButton="btnSearch" runat="server">  
         <asp:DropDownList ID="ddlList" CssClass="form-control" Width="250px" Style="display: inline" ClientIDMode="Static" runat="server" />  
         &nbsp;within&nbsp;  
         <asp:DropDownList ID="ddlRadius" CssClass="form-control" Width="100px" Style="display: inline" ClientIDMode="Static" runat="server">  
           <asp:ListItem>25</asp:ListItem>  
           <asp:ListItem>50</asp:ListItem>  
           <asp:ListItem>75</asp:ListItem>  
           <asp:ListItem>100</asp:ListItem>  
         </asp:DropDownList>  
         &nbsp;miles of:&nbsp;  
         <asp:TextBox ID="txtZipCode" Text="46237" MaxLength="5" CssClass="form-control" Width="100px" Style="display: inline; margin-right: 10px" ClientIDMode="Static" runat="server" />  
         <asp:Button ID="btnSearch" Text="Search" CssClass="btn btn-primary" OnClientClick="javascript: return btnSearch_Click();" OnClick="btnSearch_Click" ClientIDMode="Static" runat="server" />  
       </asp:Panel>  
     </div>  
   
     <!-- source -->  
     <div class="form-group">  
       <em>This demo is powered by data from mysafeinfo.com</em><br />  
       <asp:HyperLink ID="lnkList" Target="_blank" ClientIDMode="Static" runat="server" />  
     </div>  
   
     <!-- grid -->  
     <div class="form-group">  
       <asp:Literal ID="lblResults" runat="server" />  
     </div>  
       
     <!-- hidden fields -->  
     <asp:HiddenField ID="lblLocations" ClientIDMode="Static" runat="server" />  
   
     <!-- progess -->  
     <div id="loading">  
       <div id="loadingcontent">  
         <p id="loadingspinner">  
         </p>  
       </div>  
     </div>  
   
     <!-- message modal -->  
     <div class="modal fade" id="modal-message" tabindex="-1" role="dialog" aria-labelledby="lblModalMessage" aria-hidden="true">  
       <div class="modal-dialog">  
         <div class="modal-content">  
           <div class="modal-header">  
             <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>  
             <h4 class="modal-title" id="lblModalMessage">Message</h4>  
           </div>  
           <div class="modal-body">  
             <div class="message"></div>  
           </div>  
           <div class="modal-footer">  
             <div class="pull-left">  
               <button id="btnMessageClose" type="button" class="btn btn-primary" data-dismiss="modal">Close</button>  
             </div>  
           </div>  
         </div>  
       </div>  
     </div>  
   
     <!-- hidden fields -->  
     <input type="hidden" name="lblFocus" id="lblFocus" />  
     <input type="hidden" name="lblSelect" id="lblSelect" />  
   </form>  
 </body>  
 </html>  

The only references not available on a content delivery network (CDN) are the following:

 <!-- scripts -->  
 <script language="javascript" type="text/javascript" src="../js/functions.js?cache=2015062801"></script>  
 <script language="javascript" type="text/javascript" src="../js/spin.min.js?cache=2015062801"></script>

These files can be downloaded directly using the following:

http://pavey.azurewebsites.net/js/functions.js
http://pavey.azurewebsites.net/js/spin.min.js

Next we'll look at the code-behind file:
 using System;  
 using System.Collections.Generic;  
 using System.IO;  
 using System.Linq;  
 using System.Net;  
 using System.Web;  
 using System.Web.UI;  
 using System.Web.UI.WebControls;  
 using System.Xml;  
 using System.Xml.Linq;  
 using Newtonsoft.Json;  
 using Utilities;  
   
 namespace Web.farmers_markets  
 {  
   public partial class _default : System.Web.UI.Page  
   {  
     protected void Page_Load(object sender, EventArgs e)  
     {  
       // bind lists and data  
       if (!Page.IsPostBack)  
       {  
         BindLists();  
         BindData();  
       }  
     }  
   
     private void BindData()  
     {  
       // variables  
       string List = ddlList.SelectedValue;  
       string Title = ddlList.SelectedItem.Text;  
       string Url = string.Format("https://mysafeinfo.com/api/data?list={0}&format=xml&select=cd,ct,nm,add,lat,lng,url&rootname=data&elementname=r&alias=cd=state,ct=city,nm=market,add=address,lat=latitude,lng=longitude,url=url", List);  
       XmlDocument XmlDoc = new XmlDocument();  
       string XmlData = string.Empty;  
       string XslData = string.Empty;  
       string XslFile = Path.Combine(Request.PhysicalApplicationPath, "farmers-markets", "xsl", "results.xsl");  
       string ZipCode = txtZipCode.Text.Trim();  
       int Radius = Convert.ToInt32(ddlRadius.SelectedValue);  
       List<Market> Markets = new List<Market>();  
   
       // labels  
       lblTitle.Text = Title;  
       lnkList.NavigateUrl = Url;  
       lnkList.Text = Url;  
   
       // geocode zip code  
       Coordinate Origin = GetLatLong(Address: ZipCode);  
   
       // request  
       using (WebClient client = new WebClient())  
       {  
         XmlData = client.DownloadString(Url);  
       }  
   
       // load xml into xml document  
       XmlDoc.LoadXml(XmlData);  
   
       // iterate through data first so we can do distance calculation  
       foreach(XmlElement x in XmlDoc.SelectNodes("//r"))  
       {  
         // variables  
         string Latitude = x.GetAttribute("latitude");  
         string Longitude = x.GetAttribute("longitude");  
         double OriginLatitude = Origin.Latitude;  
         double OriginLongitude = Origin.Longitude;  
         double DestinationLatitude = 0;  
         double DestinationLongitude = 0;  
   
         // parse  
         double.TryParse(Latitude, out DestinationLatitude);  
         double.TryParse(Longitude, out DestinationLongitude);  
   
         // calculate distance from origin  
         double Distance = GeoCodeCalc.CalculateDistance(OriginLatitude: OriginLatitude, OriginLongitude: OriginLongitude, DestinationLatitude: DestinationLatitude, DestinationLongitude: DestinationLongitude);  
         bool InRange = Distance <= Radius;  
   
         // add attribute indicating distance to origin  
         x.SetAttribute("distance", Distance.ToString());  
   
         // add attribute indicating if location is in range  
         x.SetAttribute("inrange", InRange.ToString().ToLower());  
   
         // add to markets list for easy conversion to json later  
         if (InRange)  
         {  
           Markets.Add(new Market() { state = x.GetAttribute("state"), city = x.GetAttribute("city"), market = x.GetAttribute("market"), address = x.GetAttribute("address"), latitude = x.GetAttribute("latitude"), longitude = x.GetAttribute("longitude"), url = x.GetAttribute("url") });  
         }  
       }  
   
       // xsl  
       using (Utilities.Xsl Xsl = new Utilities.Xsl())  
       {  
         using (Utilities.Xsl.XslConfiguration XslConfiguration = new Utilities.Xsl.XslConfiguration(XslFile))  
         {  
           // parameters  
           XslConfiguration.AddXslParameter("radius", Radius.ToString());  
           XslConfiguration.AddXslParameter("zipcode", ZipCode);  
           XslConfiguration.AddXslParameter("state", Title);  
   
           // transform  
           XslData = Xsl.TransformXml(XmlType: Utilities.Xsl.XmlTypes.XmlData, XmlSource: XmlDoc.OuterXml, XslConfig: XslConfiguration);  
   
           // check for errors  
           if (Xsl.ErrorDescription.HasValue())  
           {  
             XmlData = Xsl.ErrorDescription;  
           }  
         }  
       }  
   
       // bind grid  
       lblResults.Text = XslData;  
   
       // use Json.NET (Newtonsoft.Json) to serialize locations for the map
       // https://www.nuget.org/packages/Newtonsoft.Json
       lblLocations.Value = JsonConvert.SerializeObject(Markets);  
     }  
   
     private void BindLists()  
     {  
       // variables  
       string Result = string.Empty;  
       string Url = "https://mysafeinfo.com/api/data?list=all&format=json&sort=nm&select=lst,nm&lst=farmermarkets,startswith&alias=lst=list,nm=name";  
       List<MarketList> Lists = new List<MarketList>();  
   
       // request  
       using (WebClient client = new WebClient())  
       {  
         Result = client.DownloadString(Url);  
       }  
   
       // use Json.NET (Newtonsoft.Json) to deserialize json string into a strongly typed list
       // https://www.nuget.org/packages/Newtonsoft.Json
       Lists = JsonConvert.DeserializeObject<List<MarketList>>(Result);  
   
       // bind  
       ddlList.DataValueField = "List";  
       ddlList.DataTextField = "Name";  
       ddlList.DataSource = Lists;  
       ddlList.DataBind();  
   
       // default  
       ddlList.SetValue("farmermarketsin");  
     }  
   
     protected void btnSearch_Click(object sender, EventArgs e)  
     {  
       BindData();  
     }  
   
     public static Coordinate GetLatLong(string Address, string Country = "")  
     {  
       // variables  
       string Url = string.Empty;  
   
       // determine url  
       if (Address.HasValue() && Country.HasValue())  
       {  
         Url = string.Format("https://maps.googleapis.com/maps/api/geocode/xml?address={0}&components=country:{1}&sensor=false", Address.Trim(), Country.Trim());  
       }  
       else  
       {  
         Url = string.Format("https://maps.googleapis.com/maps/api/geocode/xml?address={0}&sensor=false", Address.Trim());  
       }  
   
       // return  
       return GetLatLongByUrl(Url);  
     }  
   
     public static Coordinate GetLatLongByUrl(string Url)  
     {  
          // variables  
          Coordinate Coordinate = new Coordinate();  
          XElement XmlElement = XElement.Load(Url);  
          XElement Status = (from x in XmlElement.Descendants() where x.Name.ToString().IsEqual("status") select x).FirstOrDefault();  
   
          // check status  
          if (Status != null && Status.Value.IsEqual("ok"))  
          {  
            // variables  
            string strLatitude = XmlElement.Element("result").Element("geometry").Element("location").Element("lat").Value;  
            string strLongitude = XmlElement.Element("result").Element("geometry").Element("location").Element("lng").Value;  
            double Latitude = 0;  
            double Longitude = 0;  
   
            // parse  
            double.TryParse(strLatitude, out Latitude);  
            double.TryParse(strLongitude, out Longitude);  
   
            // coordinates  
            Coordinate.Latitude = Latitude;  
            Coordinate.Longitude = Longitude;  
          }  
   
          //return value  
          return Coordinate;  
     }  
   
     private class MarketList  
     {  
       public string List { get; set; }  
       public string Name { get; set; }  
     }  
   
     private class Market  
     {  
       public string state { get; set; }  
       public string city { get; set; }  
       public string market { get; set; }  
       public string address { get; set; }  
       public string latitude { get; set; }  
       public string longitude { get; set; }  
       public string url { get; set; }  
     }  
   
     public class Coordinate  
     {  
       public double Latitude { get; set; }  
       public double Longitude { get; set; }  
     }  
   
     // Calculate Distance Between Geocodes in C# and JavaScript  
     // http://pietschsoft.com/post/2008/02/Calculate-Distance-Between-Geocodes-in-C-and-JavaScript  
     public static class GeoCodeCalc  
     {  
       public const double EarthRadiusInMiles = 3956.0;  
       public const double EarthRadiusInKilometers = 6367.0;  
   
       public static double ToRadian(double val) { return val * (Math.PI / 180); }  
       public static double DiffRadian(double val1, double val2) { return ToRadian(val2) - ToRadian(val1); }  
   
       public static double CalculateDistance(double OriginLatitude, double OriginLongitude, double DestinationLatitude, double DestinationLongitude)  
       {  
         return CalculateDistance(OriginLatitude, OriginLongitude, DestinationLatitude, DestinationLongitude, GeoCodeCalcMeasurement.Miles);  
       }  
   
       public static double CalculateDistance(double OriginLatitude, double OriginLongitude, double DestinationLatitude, double DestinationLongitude, GeoCodeCalcMeasurement Measurement)  
       {  
         // variables  
         double Radius = GeoCodeCalc.EarthRadiusInMiles;  
         if (Measurement == GeoCodeCalcMeasurement.Kilometers) { Radius = GeoCodeCalc.EarthRadiusInKilometers; }  
   
         // return  
         return Radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt((Math.Pow(Math.Sin((DiffRadian(OriginLatitude, DestinationLatitude)) / 2.0), 2.0) + Math.Cos(ToRadian(OriginLatitude)) * Math.Cos(ToRadian(DestinationLatitude)) * Math.Pow(Math.Sin((DiffRadian(OriginLongitude, DestinationLongitude)) / 2.0), 2.0)))));  
       }  
     }  
   
     public enum GeoCodeCalcMeasurement : int  
     {  
       Miles = 0,  
       Kilometers = 1  
     }  
   }  
 }  

This example also relies on the following:

- Extensions.cs
- Xsl.cs
- results.xsl
- marker.png

This might look like a lot of code at first glance. But pulling all of this together in an ASP.Net Web Forms Application is very simple, and will help you understand the following key concepts:

- Using WebClient to retrieve XML data from a 3rd party RESTful web service
- Working with XML data using an XmlDocument and XmlElement
- Using Google Maps API to geocode a zip code
- Calculating the distance between two points
- Using an XSL transformation to show the results in a Bootstrap table
- Using Json.NET (Newtonsoft.Json) to serialize locations for the Google Map
- Using Json.NET (Newtonsoft.Json) to deserialize JSON data into a strongly typed list
- Using JavaScript and jQuery to iterate a JSON array
- Using JavaScript and the Google Maps API to add locations to the Google Map
- Using JavaScript and jQuery to validate a valid zip code is entered
- Using JavaScript, jQuery, and Bootstrap to show validation errors in modal
- Using JavaScript and jQuery to show custom progress spinner

Click here to see a full working example of this demo.

Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter @matthewpavey

0 comments: