#region Namespace using System; using System.Collections.Generic; using System.Text; using System.Reflection; using System.Web.Services.Description; using System.Data; using HydroDesktop.Interfaces.ObjectModel; using System.Diagnostics; using System.Globalization; #endregion namespace HydroDesktop.WebServices { /// <summary> /// Calls to the HIS central web services /// </summary> public class HISCentralClient : WebServiceClientBase { Stopwatch watch1 = new Stopwatch(); Stopwatch watch2 = new Stopwatch(); /// <summary> /// Creates a new instance of HIS central web service client /// </summary> /// <param name="_assemblyxURL">The url of the server with HIS Central web services</param> public HISCentralClient(string asmxURL) : base(asmxURL) { if (_webService != null) { if (_webService.GetType().GetMethod("GetSearchableConcepts") == null) { throw new System.Net.WebException("The Service doesn't have the required method GetSearchableConcepts"); } } } #region Private Variables //the list of unique site / variable combinations private List<string> _uniqueCodeList = new List<string>(); #endregion /// <summary> /// Returns a list of mapped variables /// </summary> /// <returns></returns> public DataTable GetMappedVariables() { if (_assembly == null) return null; PropertyInfo[] properties = GetPropertyInfo("MappedVariable"); DataTable table = CreateResultTable(properties, "MappedVariable"); object result = null; object[] param = new object[2]; param[0] = new string[] { "" }; param[1] = new string[] { "" }; result = CallWebMethod("GetMappedVariables", param); WriteResults(result, properties, table); return table; } /// <summary> /// Returns a string array of searchable concept names /// </summary> /// <returns></returns> public string[] GetSearchableConcepts() { if (_assembly == null) return null; object result = null; result = CallWebMethod("GetSearchableConcepts", null); Array resultArr = result as Array; if (resultArr != null) { string[] result2 = new string[resultArr.Length]; for (int i = 0; i < resultArr.Length; i++) { result2[i] = (string)resultArr.GetValue(i); } return result2; } return null; } /// <summary> /// Gets the ontology tree, in the form of a DataTable /// </summary> /// <returns>A data table with following three columns: /// ID /// ParentID /// Keyword /// </returns> public DataTable GetOntologyTreeTable() { if (_assembly == null) return null; object result = null; DataTable tbl = new DataTable(); tbl.Columns.Add(new DataColumn("ID", typeof(int))); tbl.Columns.Add(new DataColumn("ParentID", typeof(int))); tbl.Columns.Add(new DataColumn("Keyword", typeof(string))); PropertyInfo[] properties = GetPropertyInfo("OntologyNode"); //the top-level keyword is always 'hydrosphere' object[] param = new object[1] { "hydrosphere" }; result = CallWebMethod("getOntologyTree", param); ReadTree2(result, properties, 1, tbl); return tbl; } /// <summary> /// Returns all valid pairs of conceptCode, conceptKeyword /// from the ontology concept tree /// </summary> /// <returns></returns> public Dictionary<string, string> GetOntologyTree() { if (_assembly == null) return null; object result = null; PropertyInfo[] properties = GetPropertyInfo("OntologyNode"); object[] param = new object[1] { "hydrosphere" }; // Finally, Invoke the web service method result = CallWebMethod("getOntologyTree", param); Dictionary<string, string> keywordLookup = new Dictionary<string, string>(); ReadTree(result, properties, keywordLookup); return keywordLookup; } private void ReadTree(object node, PropertyInfo[] properties, Dictionary<string, string> resultList) { if (resultList == null) return; string keyword = properties[0].GetValue(node, null).ToString(); keyword = keyword.Replace("\\c", ","); string code = properties[1].GetValue(node, null).ToString(); Array childNodes = properties[2].GetValue(node, null) as Array; //if there are no child nodes, we'll add the keyword if (resultList.ContainsKey(keyword) == false && keyword.IndexOf("other") < 0) { if (childNodes == null) { resultList.Add(keyword.ToString(), code.ToString()); } else if (childNodes.Length == 0) { resultList.Add(keyword.ToString(), code.ToString()); } } if (childNodes == null) return; if (childNodes.Length == 0) return; foreach (object obj in childNodes) { ReadTree(obj, properties, resultList); } } private void ReadTree2(object node, PropertyInfo[] properties, int parentID, DataTable resultTable) { string keyword = properties[0].GetValue(node, null).ToString(); keyword = keyword.Replace("\\c", ","); int code = Convert.ToInt32(properties[1].GetValue(node, null)); //add the keyword to table DataRow newRow = resultTable.NewRow(); newRow["ID"] = code; newRow["ParentID"] = parentID; newRow["Keyword"] = keyword; resultTable.Rows.Add(newRow); Array childNodes = properties[2].GetValue(node, null) as Array; if (childNodes == null) return; if (childNodes.Length == 0) return; //exit if there are no child nodes //otherwise, add the child keywords foreach (object obj in childNodes) { ReadTree2(obj, properties, code, resultTable); } } /// <summary> /// Returns the series catalog for the specified region (latitude | longitude bounding box) /// </summary> /// <param name="latLongBox">the latitude/longitude bounding box</param> /// <param name="keywords">the array of search keywords (not included in search criteria if null)</param> /// <param name="beginDate">the begin date</param> /// <param name="endDate">the end date</param> /// <param name="networkIDs">the ServiceIDs. These are obtained by calling GetServicesInBox() function</param> /// <returns>a list o all matching data series entries</returns> public IList<SeriesMetadata> GetSeriesCatalogForBox(Box latLongBox, string[] keywords, DateTime beginDate, DateTime endDate, int[] networkIDs) { if (_assembly == null) return null; _uniqueCodeList.Clear(); //if no keywords are specified - set them to an array with one empty string //if no keywords are specified, set the keywords[] parameter to an array with one empty string if (keywords == null) { keywords = new string[]{ String.Empty }; } if (keywords.Length == 0) { keywords = new string[]{ String.Empty }; } //if no service IDs are specified - set them to all available services within region //if (networkIDs == null) //{ // IList<DataServiceInfo> networks = GetServicesInBox(latLongBox.xmin, latLongBox.ymin, latLongBox.xmax, latLongBox.ymax); // networkIDs = new int[networks.Count]; // for (int i = 0; i < networks.Count; i++) // { // networkIDs[i] = networks[i].HISCentralID; // } //} // TODO: why not use MinValue and MaxValue //if no begin date specified - set it to a very early date if (beginDate == null) beginDate = new DateTime(1900, 1, 1); //if no end date specified - set it to current date if (endDate == null) endDate = DateTime.Now.Date; PropertyInfo[] resultProperties = GetPropertyInfo("SeriesRecord"); DataTable table = CreateResultTable(resultProperties, "SeriesRecord"); IList<SeriesMetadata> lst = new List<SeriesMetadata>(); object boxObj = InitializeBox(latLongBox); object[] param = new object[5]; MethodInfo mi = _webService.GetType().GetMethod("GetSeriesCatalogForBox"); for (int i = 0; i < keywords.Length; i++) { try { param[0] = boxObj; param[1] = keywords[i]; param[2] = networkIDs; param[3] = DateToString(beginDate); param[4] = DateToString(endDate); // Invoke the web service method watch1.Start(); object result = mi.Invoke(_webService, param); watch1.Stop(); // Process the result watch2.Start(); WriteDataSeriesResults(result, resultProperties, lst); watch2.Stop(); } finally { //System.Windows.Forms.MessageBox.Show(ex.Message + " " + ex.InnerException.Message); } } return lst; } /// <summary> /// Returns the series catalog for the specified region (latitude | longitude bounding box) /// </summary> /// <param name="latLongBox">the latitude / </param> /// <param name="keyword"></param> /// <param name="beginDate"></param> /// <param name="endDate"></param> /// <returns>a list of all matching data series entries</returns> public IList<SeriesMetadata> GetSeriesCatalogForBox(Box latLongBox, string[] keywords, DateTime beginDate, DateTime endDate) { if (_assembly == null) return null; _uniqueCodeList.Clear(); PropertyInfo[] resultProperties = GetPropertyInfo("SeriesRecord"); List<SeriesMetadata> lst = new List<SeriesMetadata>(); object[] param = new object[8]; object result = null; MethodInfo mi = _webService.GetType().GetMethod("GetSeriesCatalogForBox2"); //if no keywords are specified - set them to an array with one empty string if (keywords == null) { keywords = new string[] { string.Empty }; } //if no begin date specified - set it to a very early date if (beginDate == null) beginDate = new DateTime(1900, 1, 1); //if no end date specified - set it to current date if (endDate == null) endDate = DateTime.Now.Date; //if no keywords are specified, set the keywords[] parameter to an array with one empty string if (keywords == null) { keywords = new string[]{ String.Empty }; } if (keywords.Length == 0) { keywords = new string[]{ String.Empty }; } for (int i = 0; i < keywords.Length; i++) { try { param[0] = latLongBox.XMin; param[1] = latLongBox.XMax; param[2] = latLongBox.YMin; param[3] = latLongBox.YMax; param[4] = keywords[i]; param[5] = ""; param[6] = DateToString(beginDate); param[7] = DateToString(endDate); // Invoke the web service method watch1.Start(); result = mi.Invoke(_webService, param); watch1.Stop(); // Process the result watch2.Start(); WriteDataSeriesResults(result, resultProperties, lst); watch2.Stop(); } catch (Exception ex) { throw new Exception("Error accessing the HIS Central Web service", ex); } } return lst; } /// <summary> /// Retrieves information about CUAHSI WaterOneFlow web services available in the specified region /// (latitude / longitude bounding box). /// </summary> /// <param name="latLongBox"></param> /// <returns></returns> public IList<DataServiceInfo> GetServicesInBox(double xmin, double ymin, double xmax, double ymax) { object[] param = new object[4]; param[0] = xmin; param[1] = ymin; param[2] = xmax; param[3] = ymax; object result = null; result = CallWebMethod("GetServicesInBox2", param); // Process the result PropertyInfo[] properties = GetPropertyInfo("ServiceInfo"); DataTable table = CreateResultTable(properties, "ServiceInfo"); WriteResults(result, properties, table); return TableToWebServiceInfo(table); } /// <summary> /// Retrieves all sites within a geographic region that belong to the specified network /// (web service) and measure the specified variable /// </summary> /// <param name="latLongBox"></param> /// <param name="keyword"></param> /// <param name="networks"></param> /// <returns></returns> public DataTable GetSitesInBox(Box latLongBox, string keyword, int[] networks) { PropertyInfo[] properties = GetPropertyInfo("Site"); DataTable table = CreateResultTable(properties, "Site"); object boxObj = InitializeBox(latLongBox); object[] param = new object[3]; param[0] = boxObj; param[1] = keyword; param[2] = networks; object result = null; result = CallWebMethod("GetSitesInBox", param); //MethodInfo mi = _webService.GetType().GetMethod("GetSitesInBox"); //result = mi.Invoke(_webService, param); // Process the result WriteResults(result, properties, table); return table; } /// <summary> /// Gets all sites within the specified latitude / longitude bounding box. /// </summary> /// <param name="latLongBox"></param> /// <returns></returns> public DataTable GetAllSitesInBox(Box latLongBox) { //DataTable networkTable = GetServicesInBox(latLongBox); //int[] networkIDs = new int[networkTable.Rows.Count]; PropertyInfo[] properties = GetPropertyInfo("Site"); DataTable table = CreateResultTable(properties, "Site"); object boxObj = InitializeBox(latLongBox); object[] param = new object[3]; param[0] = boxObj; param[1] = ""; param[2] = null; object result = null; result = CallWebMethod("GetSitesInBox", param); //MethodInfo mi = _webService.GetType().GetMethod("GetSitesInBox"); //result = mi.Invoke(_webService, param); // Process the result WriteResults(result, properties, table); return table; } /// <summary> /// Returns all sites within the region, regardless of the web service or variable /// </summary> /// <param name="latLongBox"></param> /// <returns></returns> public DataTable GetAllSitesInBox2(Box latLongBox) { //first we get all available concepts string[] keywords = GetSearchableConcepts(); DataTable resultTable = null; foreach (string keyword in keywords) { DataTable tmpTable = GetAllSitesInBox(latLongBox); if (resultTable == null) { resultTable = tmpTable.Clone(); } else { foreach (DataRow row in tmpTable.Rows) { DataRow newRow = resultTable.NewRow(); newRow.ItemArray = row.ItemArray; resultTable.Rows.Add(newRow); } } } return resultTable; } /// <summary> /// Retrieves information about CUAHSI WaterOneFlow web services available at the HIS Central /// </summary> /// <returns></returns> public IList<DataServiceInfo> GetWaterOneFlowServiceInfo() { // check the dynamic assembly if (_assembly == null) return null; PropertyInfo[] properties = GetPropertyInfo("ServiceInfo"); DataTable table = CreateResultTable(properties, "ServiceInfo"); object result = null; result = CallWebMethod("GetWaterOneFlowServiceInfo", null); // Process the result WriteResults(result, properties, table); return TableToWebServiceInfo(table); } #region Private Methods private string DateToString(DateTime dat) { return dat.ToString("MM/dd/yyyy"); } // writes the results to a list of data series objects (which is a theme) private void WriteDataSeriesResults(Object resultObj, PropertyInfo[] properties, IList<SeriesMetadata> lst) { if (resultObj == null) return; //we need to get the data service information as well. IList<DataServiceInfo> servicesList = GetServicesInBox(-179, -89, 179, 89); Dictionary<string, DataServiceInfo> servicesLookup = new Dictionary<string, DataServiceInfo>(); foreach (DataServiceInfo servInfo in servicesList) { string url = servInfo.EndpointURL.ToLower().Trim(); if (!servicesLookup.ContainsKey(url)) { servicesLookup.Add(url, servInfo); } } //to prevent duplicate search results - each site code / variable code combination //is added to the 'uniqueCode' list Array arr = resultObj as Array; if (arr != null) { foreach (object obj in arr) { SeriesMetadata sm = new SeriesMetadata(); sm.Site = new Site(); sm.Variable = new Variable(); sm.DataService = new DataServiceInfo(); foreach (PropertyInfo pi in properties) { object val = pi.GetValue(obj, null); if (val != null) { string propertyName = pi.Name.ToLower(); switch(propertyName) { case "location": sm.Site.Code = val.ToString(); break; case "varcode": sm.Variable.Code = val.ToString(); break; case "servcode": string servCode = val.ToString(); sm.Site.NetworkPrefix = servCode; sm.Variable.VocabularyPrefix = servCode; sm.DataService.ServiceCode = servCode; break; case "latitude": sm.Site.Latitude = Convert.ToDouble(val); break; case "longitude": sm.Site.Longitude = Convert.ToDouble(val); break; case "sitename": sm.Site.Name = Convert.ToString(val); break; case "varname": sm.Variable.Name = Convert.ToString(val); break; case "servurl": string url = Convert.ToString(val).ToLower().Trim(); if (url.EndsWith("?wsdl")) { url = url.Replace("?wsdl", ""); } sm.DataService.EndpointURL = url; break; case "valuecount": sm.ValueCount = Convert.ToInt32(val); break; case "begindate": sm.BeginDateTime = Convert.ToDateTime(val, CultureInfo.GetCultureInfo("en-US")); break; case "enddate": sm.EndDateTime = Convert.ToDateTime(val, CultureInfo.GetCultureInfo("en-US")); break; case "datatype": sm.Variable.DataType = val.ToString(); break; case "valuetype": sm.Variable.ValueType = val.ToString(); break; case "samplemedium": sm.Variable.SampleMedium = val.ToString(); break; case "timeunits": sm.Variable.TimeUnit = new Unit(val.ToString(), "time", val.ToString()); break; case "gencategory": sm.Variable.GeneralCategory = val.ToString(); break; case "timesupport": sm.Variable.TimeSupport = Convert.ToDouble(val); break; } } } if (String.IsNullOrEmpty(sm.Variable.Code)) { continue; } string compoundCode = sm.Site.Code + "|" + sm.Variable.Code; if (_uniqueCodeList.Contains(compoundCode) == false) { if (servicesLookup.ContainsKey(sm.DataService.EndpointURL)) { sm.Source.Organization = ((DataServiceInfo)servicesLookup[sm.DataService.EndpointURL]).ServiceName; sm.Source.Description = ((DataServiceInfo)servicesLookup[sm.DataService.EndpointURL]).ServiceTitle; } lst.Add(sm); _uniqueCodeList.Add(compoundCode); } } } } //initializes the 'box' object that is used by the web services private object InitializeBox(Box box) { // Create the 'Box' object using the dynamic assembly and set its properties object boxObj = _assembly.CreateInstance("Box"); Type boxType = boxObj.GetType(); PropertyInfo[] boxProperties = boxType.GetProperties(); boxProperties[0].SetValue(boxObj, box.XMin, null); boxProperties[1].SetValue(boxObj, box.XMax, null); boxProperties[2].SetValue(boxObj, box.YMin, null); boxProperties[3].SetValue(boxObj, box.YMax, null); return boxObj; } //converts a data table to a list of WaterOneFlowServiceInfo objects private IList<DataServiceInfo> TableToWebServiceInfo(DataTable table) { List<DataServiceInfo> resultList = new List<DataServiceInfo>(); foreach (DataRow row in table.Rows) { string endpointURL = row["servURL"].ToString(); endpointURL = endpointURL.ToLower(); if (endpointURL.EndsWith("?wsdl")) endpointURL = endpointURL.Replace("?wsdl", ""); string title = row["Title"].ToString(); DataServiceInfo servInfo = new DataServiceInfo(endpointURL, title); servInfo.DescriptionURL = row["ServiceDescriptionURL"].ToString(); servInfo.ContactName = row["organization"].ToString(); servInfo.ContactEmail = row["orgwebsite"].ToString(); servInfo.Citation = row["citation"].ToString(); servInfo.Abstract = (string)row["aabstract"]; servInfo.ValueCount = Convert.ToInt32(row["valuecount"]); servInfo.SiteCount = Convert.ToInt32(row["sitecount"]); servInfo.HISCentralID = Convert.ToInt32(row["ServiceID"]); servInfo.EastLongitude = Convert.ToDouble(row["minx"]); servInfo.SouthLatitude = Convert.ToDouble(row["miny"]); servInfo.WestLongitude = Convert.ToDouble(row["maxx"]); servInfo.NorthLatitude = Convert.ToDouble(row["maxy"]); servInfo.ServiceCode = Convert.ToString(row["NetworkName"]); servInfo.ServiceName = Convert.ToString(row["organization"]); resultList.Add(servInfo); } return resultList; } //creates the empty data table for retrieving a list of web services private DataTable CreateWebServiceInfoTable() { DataTable tbl = new DataTable(); tbl.Columns.Add("ServiceID", typeof(int)); tbl.Columns.Add("ServiceTitle", typeof(string)); tbl.Columns.Add("ServiceName", typeof(string)); tbl.Columns.Add("ServiceCode", typeof(string)); tbl.Columns.Add("ServiceVersion", typeof(string)); tbl.Columns.Add("ServiceProtocol", typeof(string)); tbl.Columns.Add("ServiceEndpointURL", typeof(string)); tbl.Columns.Add("ServiceDescriptionURL", typeof(string)); tbl.Columns.Add("NorthLatitude", typeof(string)); tbl.Columns.Add("SouthLatitude", typeof(string)); tbl.Columns.Add("EastLongitude", typeof(string)); tbl.Columns.Add("WestLongitude", typeof(string)); tbl.Columns.Add("Abstract", typeof(string)); tbl.Columns.Add("ContactName", typeof(string)); tbl.Columns.Add("Citation", typeof(string)); return tbl; } #endregion } }