About the RSS weather GraphicTracker Sample
[C#]
RSSWeather.cs
using System;
using System.Data;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Xml;
using System.IO;
using System.Threading;
using System.Timers;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.EngineCore;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DataSourcesFile;
namespace RSSWeatherGraphicTracker
{
#region WeatherItemEventArgs class members
public sealed class WeatherItemEventArgs : EventArgs
{
private int m_iconId;
private long m_zipCode;
private double m_x;
private double m_y;
private int m_iconWidth;
private int m_iconHeight;
public WeatherItemEventArgs(int iconId, long zipCode, double X, double Y, int iconWidth, int iconHeight)
{
m_iconId = iconId;
m_zipCode = zipCode;
m_x = X;
m_y = Y;
m_iconWidth = iconWidth;
m_iconHeight = iconHeight;
}
public int IconID
{
get { return m_iconId; }
}
public long ZipCode
{
get { return m_zipCode; }
}
public double mapY
{
get { return m_y; }
}
public double mapX
{
get { return m_x; }
}
}
#endregion
//declare delegates for the event handling
public delegate void WeatherItemAdded(object sender, WeatherItemEventArgs args);
public delegate void WeatherItemsUpdated(object sender, EventArgs args);
class RSSWeather
{
private sealed class InvokeHelper : Control
{
//delegate used to pass the invoked method to the main thread
public delegate void RefreshWeatherItemHelper(WeatherItemEventArgs weatherItemInfo);
private RSSWeather m_weather = null;
public InvokeHelper(RSSWeather rssWeather)
{
m_weather = rssWeather;
CreateHandle();
CreateControl();
}
public void RefreshWeatherItem(WeatherItemEventArgs weatherItemInfo)
{
try
{
// Invoke the RefreshInternal through its delegate
if (!this.IsDisposed && this.IsHandleCreated)
Invoke(new RefreshWeatherItemHelper(RefreshWeatherItemInvoked), new object[] { weatherItemInfo });
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
private void RefreshWeatherItemInvoked(WeatherItemEventArgs weatherItemInfo)
{
if (m_weather != null)
m_weather.UpdateTracker(weatherItemInfo);
}
}
#region class members
private System.Timers.Timer m_timer = null;
private Thread m_updateThread = null;
private string m_iconFolder = string.Empty;
private DataTable m_weatherItemTable = null;
private DataTable m_symbolTable = null;
private DataTable m_locations = null;
private string m_dataFolder = string.Empty;
private string m_installationFolder = string.Empty;
private IPoint m_point = null;
private ISpatialReference m_SRWGS84 = null;
private IBasicMap m_mapOrGlobe = null;
private ISimpleTextSymbol m_textSymbol = null;
private IGraphicTracker m_graphicTracker = null;
private InvokeHelper m_invokeHelper = null;
//weather items events
public event WeatherItemAdded OnWeatherItemAdded;
public event WeatherItemsUpdated OnWeatherItemsUpdated;
#endregion
public RSSWeather()
{
//get the directory for the cache. If it does not exist, create it.
m_dataFolder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "RSSWeather");
if (!System.IO.Directory.Exists(m_dataFolder))
{
System.IO.Directory.CreateDirectory(m_dataFolder);
}
m_iconFolder = m_dataFolder;
m_installationFolder = ESRI.ArcGIS.RuntimeManager.ActiveRuntime.Path;
}
public void Init(IBasicMap mapOrGlobe)
{
System.Diagnostics.Trace.WriteLine("Init - Thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
if (mapOrGlobe == null)
return;
m_mapOrGlobe = mapOrGlobe;
try
{
//initialize the tables (main table as well as the symbols table)
InitializeTables();
//get the location list from a featureclass (US major cities) and synchronize it with the
//cached information in case it exists.
if (null == m_locations)
InitializeLocations();
m_point = new PointClass();
m_SRWGS84 = CreateGeographicSpatialReference();
m_point.SpatialReference = m_SRWGS84;
m_textSymbol = new TextSymbolClass() as ISimpleTextSymbol;
m_textSymbol.Font = ToFontDisp(new Font("Tahoma", 10.0f, FontStyle.Bold));
m_textSymbol.Size = 10.0;
m_textSymbol.Color = (IColor)ToRGBColor(Color.FromArgb(0, 255, 0));
m_textSymbol.XOffset = 0.0;
m_textSymbol.YOffset = 16.0;
m_graphicTracker = new GraphicTrackerClass();
m_graphicTracker.Initialize(mapOrGlobe as object);
if (m_weatherItemTable.Rows.Count > 0)
PopulateGraphicTracker();
m_invokeHelper = new InvokeHelper(this);
this.OnWeatherItemAdded += new WeatherItemAdded(OnWeatherItemAddedEvent);
//instantiate the timer for the weather update thread
m_timer = new System.Timers.Timer(1000);
m_timer.Elapsed += new ElapsedEventHandler(OnUpdateTimer);
//enable the update timer
m_timer.Enabled = true;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
public void Remove()
{
this.OnWeatherItemAdded -= new WeatherItemAdded(OnWeatherItemAddedEvent);
m_invokeHelper = null;
m_timer.Enabled = false;
// wait for the update thread to exit
m_updateThread.Join();
m_graphicTracker.RemoveAll();
m_graphicTracker = null;
}
public void UpdateTracker(WeatherItemEventArgs weatherItemInfo)
{
System.Diagnostics.Trace.WriteLine("UpdateTracker - Thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
if (m_graphicTracker == null)
throw new Exception("Graphic tracker is not initialized!");
// 1. lock the m_weatherItemTable and get the record
DataRow row;
lock (m_weatherItemTable)
{
row = m_weatherItemTable.Rows.Find(weatherItemInfo.ZipCode);
if (row == null)
return;
// 2. get the symbol for the item
IGraphicTrackerSymbol symbol = GetSymbol(weatherItemInfo.IconID, Convert.ToString(row[7]));
if (symbol == null)
return;
string label = string.Format("{0}, {1}?F", Convert.ToString(row[2]), Convert.ToString(row[5]));
// 3. check whether it has a tracker ID (not equals -1)
int trackerID = Convert.ToInt32(row[15]);
//m_graphicTracker.SuspendUpdate = true;
m_point.PutCoords(weatherItemInfo.mapX, weatherItemInfo.mapY);
m_point.SpatialReference = m_SRWGS84;
m_point.Project(m_mapOrGlobe.SpatialReference);
if (trackerID == -1) // new tracker
{
trackerID = m_graphicTracker.Add(m_point as IGeometry, symbol);
m_graphicTracker.SetTextSymbol(trackerID, m_textSymbol);
row[15] = trackerID;
}
else // existing tracker
{
m_graphicTracker.MoveTo(trackerID, m_point.X, m_point.Y, 0);
m_graphicTracker.SetSymbol(trackerID, symbol);
}
m_graphicTracker.SetLabel(trackerID, label);
row.AcceptChanges();
//m_graphicTracker.SuspendUpdate = false;
}
}
#region private utility methods
void PopulateGraphicTracker()
{
m_graphicTracker.SuspendUpdate = true;
foreach (DataRow row in m_weatherItemTable.Rows)
{
IGraphicTrackerSymbol symbol = GetSymbol(Convert.ToInt32(row[8]), Convert.ToString(row[7]));
if (symbol == null)
continue;
string label = string.Format("{0}, {1}?F", Convert.ToString(row[2]), Convert.ToString(row[5]));
m_point.PutCoords(Convert.ToDouble(row[4]), Convert.ToDouble(row[3]));
int trackerID = m_graphicTracker.Add(m_point as IGeometry, symbol);
m_graphicTracker.SetTextSymbol(trackerID, m_textSymbol);
m_graphicTracker.SetScaleMode(trackerID, esriGTScale.esriGTScaleAuto);
m_graphicTracker.SetOrientationMode(trackerID, esriGTOrientation.esriGTOrientationAutomatic);
m_graphicTracker.SetElevationMode(trackerID, esriGTElevation.esriGTElevationClampToGround);
m_graphicTracker.SetLabel(trackerID, label);
row[15] = trackerID;
row.AcceptChanges();
}
m_graphicTracker.SuspendUpdate = false;
}
/// <summary>
/// create a WGS1984 geographic coordinate system.
/// In this case, the underlying data provided by the service is in WGS1984.
/// </summary>
/// <returns></returns>
private ISpatialReference CreateGeographicSpatialReference()
{
ISpatialReferenceFactory spatialRefFatcory = new SpatialReferenceEnvironmentClass();
IGeographicCoordinateSystem geoCoordSys;
geoCoordSys = spatialRefFatcory.CreateGeographicCoordinateSystem((int)esriSRGeoCSType.esriSRGeoCS_WGS1984);
geoCoordSys.SetFalseOriginAndUnits(-180.0, -180.0, 5000000.0);
geoCoordSys.SetZFalseOriginAndUnits(0.0, 100000.0);
geoCoordSys.SetMFalseOriginAndUnits(0.0, 100000.0);
return geoCoordSys as ISpatialReference;
}
/// <summary>
/// run the thread that does the update of the weather data
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnUpdateTimer(object sender, ElapsedEventArgs e)
{
m_timer.Interval = 2700000; //(45 minutes)
m_updateThread = new Thread(new ThreadStart(ThreadProc));
//run the update thread
m_updateThread.Start();
}
/// <summary>
/// the main update thread for the data.
/// </summary>
/// <remarks>Since the information is coming from a web service which might
/// take a while to respond, it is not logical to let the application hang while waiting
/// for response. Therefore, running the request on a different thread frees the application to
/// continue working while waiting for a response.
/// Please note that in this case, synchronization of shared resources must be addressed,
/// otherwise you might end up getting unexpected results.</remarks>
private void ThreadProc()
{
try
{
long lZipCode;
//iterate through all the records in the main table and update it against
//the information from the website.
foreach (DataRow r in m_locations.Rows)
{
//put the thread to sleep in order not to overwhelm the RSS website
//System.Threading.Thread.Sleep(200);
//get the zip code of the record (column #1)
lZipCode = Convert.ToInt32(r[1]);
//make the request and update the item
AddWeatherItem(lZipCode, 0.0, 0.0);
}
//serialize the tables onto the local machine
DataSet ds = new DataSet();
ds.Tables.Add(m_weatherItemTable);
ds.WriteXml(System.IO.Path.Combine(m_dataFolder, "Weather.xml"));
ds.Tables.Remove(m_weatherItemTable);
ds.Dispose();
GC.Collect();
//fire an event to notify update of the weather items
if(OnWeatherItemsUpdated != null)
OnWeatherItemsUpdated(this, new EventArgs());
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// given a bitmap url, saves it on the local machine and returns its size
/// </summary>
/// <param name="iconPath"></param>
/// <param name="width"></param>
/// <param name="height"></param>
private Bitmap DownloadIcon(string iconPath, out int width, out int height)
{
//if the icon does not exist on the local machine, get it from RSS site
string iconFileName = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) + ".png");
width = 0;
height = 0;
Bitmap bitmap = null;
if (!File.Exists(iconFileName))
{
using (System.Net.WebClient webClient = new System.Net.WebClient())
{
//open a readable stream to download the bitmap
using (System.IO.Stream stream = webClient.OpenRead(iconPath))
{
bitmap = new Bitmap(stream, true);
//save the image as a bitmap in the icons folder
bitmap.Save(iconFileName, ImageFormat.Png);
//get the bitmap's dimensions
width = bitmap.Width;
height = bitmap.Height;
}
}
}
else
{
//get the bitmap's dimensions
{
bitmap = new Bitmap(iconFileName);
width = bitmap.Width;
height = bitmap.Height;
}
}
return bitmap;
}
/// <summary>
/// get the specified symbol from the symbols table.
/// </summary>
/// <param name="iconCode"></param>
/// <param name="dbr"></param>
/// <returns></returns>
private IGraphicTrackerSymbol GetSymbol(int iconCode, string iconPath)
{
IGraphicTrackerSymbol symbol;
int iconWidth, iconHeight;
Bitmap bitmap = null;
//search for an existing symbol in the table
DataRow r = m_symbolTable.Rows.Find(iconCode);
if (r == null) //in case that the symbol does not exist in the table, create a new entry
{
r = m_symbolTable.NewRow();
r[1] = iconCode;
//Initialize the picture marker symbol
symbol = InitializeSymbol(iconPath, out iconWidth, out iconHeight, out bitmap);
if (null == symbol)
return null;
//update the symbol table
lock (m_symbolTable)
{
r[2] = symbol;
r[3] = iconWidth;
r[4] = iconHeight;
r[5] = bitmap;
m_symbolTable.Rows.Add(r);
}
}
else
{
if (r[2] is DBNull) //in case that the record exists but the symbol hasn't been initialized
{
//Initialize the symbol
symbol = InitializeSymbol(iconPath, out iconWidth, out iconHeight, out bitmap);
if (null == symbol)
return null;
//update the symbol table
lock (m_symbolTable)
{
r[2] = symbol;
r[5] = bitmap;
r.AcceptChanges();
}
}
else //the record exists in the table and the symbol has been initialized
//get the symbol
symbol = r[2] as IGraphicTrackerSymbol;
}
//return the requested symbol
return symbol;
}
/// <summary>
/// Initialize a character marker symbol for a given bitmap path
/// </summary>
/// <param name="iconPath"></param>
/// <param name="iconWidth"></param>
/// <param name="iconHeight"></param>
/// <returns></returns>
private IGraphicTrackerSymbol InitializeSymbol(string iconPath, out int iconWidth, out int iconHeight, out Bitmap bitmap)
{
iconWidth = iconHeight = 0;
bitmap = null;
try
{
//make sure that the icon exit on dist or else download it
DownloadIcon(iconPath, out iconWidth, out iconHeight);
string iconFileName = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) + ".png");
if (!System.IO.File.Exists(iconFileName))
return null;
IGraphicTrackerSymbol symbol = m_graphicTracker.CreateSymbolFromPath(iconFileName, iconFileName);
return symbol;
}
catch
{
return null;
}
}
/// <summary>
/// Makes a request against RSS Weather service and add update the table
/// </summary>
/// <param name="zipCode"></param>
/// <param name="Lat"></param>
/// <param name="Lon"></param>
private void AddWeatherItem(long zipCode, double Lat, double Lon)
{
try
{
string cityName;
double lat, lon;
int temp;
string condition;
string desc;
string iconPath;
string day;
string date;
int low;
int high;
int iconCode;
int iconWidth = 52; //default values
int iconHeight = 52;
Bitmap bitmap = null;
DataRow dbr = m_weatherItemTable.Rows.Find(zipCode);
if (dbr != null)
{
// get the date
DateTime updateDate = Convert.ToDateTime(dbr[14]);
TimeSpan ts = DateTime.Now - updateDate;
// if the item had been updated in the past 15 minutes, simply bail out.
if (ts.TotalMinutes < 15)
return;
}
//the base URL for the service
string url = "http://xml.weather.yahoo.com/forecastrss?p=";
//the RegEx used to extract the icon path from the HTML tag
string regxQry = "(http://(\\\")?(.*?\\.gif))";
XmlTextReader reader = null;
XmlDocument doc;
XmlNode node;
try
{
//make the request and get the result back into XmlReader
reader = new XmlTextReader(url + zipCode.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
return;
}
//load the XmlReader to an xml doc
doc = new XmlDocument();
doc.Load(reader);
//set an XmlNamespaceManager since we have to make explicit namespace searches
XmlNamespaceManager xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
//Add the namespaces used in the xml doc to the XmlNamespaceManager.
xmlnsManager.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0");
xmlnsManager.AddNamespace("geo", "http://www.w3.org/2003/01/geo/wgs84_pos#");
//make sure that the node exists
node = doc.DocumentElement.SelectSingleNode("/rss/channel/yweather:location/@city", xmlnsManager);
if (null == node)
return;
//get the city name
cityName = doc.DocumentElement.SelectSingleNode("/rss/channel/yweather:location/@city", xmlnsManager).InnerXml;
if (Lat == 0.0 && Lon == 0.0)
{
//in case that the caller did not specify a coordinate, get the default coordinate from the service
lat = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:lat", xmlnsManager).InnerXml);
lon = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:long", xmlnsManager).InnerXml);
}
else
{
lat = Lat;
lon = Lon;
}
//extract the rest of the information from the RSS response
condition = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@text", xmlnsManager).InnerXml;
iconCode = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@code", xmlnsManager).InnerXml);
temp = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@temp", xmlnsManager).InnerXml);
desc = doc.DocumentElement.SelectSingleNode("/rss/channel/item/description").InnerXml;
day = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@day", xmlnsManager).InnerXml;
date = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@date", xmlnsManager).InnerXml;
low = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@low", xmlnsManager).InnerXml);
high = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@high", xmlnsManager).InnerXml);
//use regex in order to extract the icon name from the html script
Match m = Regex.Match(desc, regxQry);
if (m.Success)
{
iconPath = m.Value;
//add the icon ID to the symbology table
DataRow tr = m_symbolTable.Rows.Find(iconCode);
if (null == tr)
{
//get the icon from the website
bitmap = DownloadIcon(iconPath, out iconWidth, out iconHeight);
//create a new record
tr = m_symbolTable.NewRow();
tr[1] = iconCode;
tr[3] = iconWidth;
tr[4] = iconHeight;
tr[5] = bitmap;
//update the symbol table. The initialization of the symbol cannot take place in here, since
//this code gets executed on a background thread.
lock (m_symbolTable)
{
m_symbolTable.Rows.Add(tr);
}
}
else //get the icon's dimensions from the table
{
//get the icon's dimensions from the table
iconWidth = Convert.ToInt32(tr[3]);
iconHeight = Convert.ToInt32(tr[4]);
}
}
else
{
iconPath = "";
}
//test whether the record already exists in the table.
if (null == dbr) //in case that the record does not exist
{
//create a new record
dbr = m_weatherItemTable.NewRow();
if (!m_weatherItemTable.Columns[0].AutoIncrement)
dbr[0] = Convert.ToInt32(DateTime.Now.Millisecond);
//add the item to the table
lock (m_weatherItemTable)
{
dbr[1] = zipCode;
dbr[2] = cityName;
dbr[3] = lat;
dbr[4] = lon;
dbr[5] = temp;
dbr[6] = condition;
dbr[7] = iconPath;
dbr[8] = iconCode;
dbr[9] = day;
dbr[10] = date;
dbr[11] = low;
dbr[12] = high;
dbr[13] = false;
dbr[14] = DateTime.Now;
dbr[15] = -1;
m_weatherItemTable.Rows.Add(dbr);
}
}
else //in case that the record exists, just update it
{
//update the record
lock (m_weatherItemTable)
{
dbr[5] = temp;
dbr[6] = condition;
dbr[7] = iconPath;
dbr[8] = iconCode;
dbr[9] = day;
dbr[10] = date;
dbr[11] = low;
dbr[12] = high;
dbr[14] = DateTime.Now;
dbr.AcceptChanges();
}
}
//fire an event to notify the user that the item has been updated
if (OnWeatherItemAdded != null)
{
WeatherItemEventArgs weatherItemEventArgs = new WeatherItemEventArgs(iconCode, zipCode, lon, lat, iconWidth, iconHeight);
OnWeatherItemAdded(this, weatherItemEventArgs);
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("AddWeatherItem: " + ex.Message);
}
}
private IRgbColor ToRGBColor(System.Drawing.Color color)
{
IRgbColor rgbColor = new RgbColorClass();
rgbColor.Red = color.R;
rgbColor.Green = color.G;
rgbColor.Blue = color.B;
return rgbColor;
}
private stdole.IFontDisp ToFontDisp(System.Drawing.Font font)
{
stdole.IFont aFont;
aFont = new stdole.StdFontClass();
aFont.Name = font.Name;
aFont.Size = (decimal)font.Size;
aFont.Bold = font.Bold;
aFont.Italic = font.Italic;
aFont.Strikethrough = font.Strikeout;
aFont.Underline = font.Underline;
return aFont as stdole.IFontDisp;
}
/// <summary>
/// initialize the main table as well as the symbols table.
/// The base class calls new on the table and adds a default ID field.
/// </summary>
private void InitializeTables()
{
string path = System.IO.Path.Combine(m_dataFolder, "Weather.xml");
//In case that there is no existing cache on the local machine, create the table.
if (!System.IO.File.Exists(path))
{
//create the table the table in addition to the default 'ID' and 'Geometry'
m_weatherItemTable = new DataTable("RECORDS");
m_weatherItemTable.Columns.Add("ID", typeof(long)); //0
m_weatherItemTable.Columns.Add("ZIPCODE", typeof(long)); //1
m_weatherItemTable.Columns.Add("CITYNAME", typeof(string)); //2
m_weatherItemTable.Columns.Add("LAT", typeof(double)); //3
m_weatherItemTable.Columns.Add("LON", typeof(double)); //4
m_weatherItemTable.Columns.Add("TEMP", typeof(int)); //5
m_weatherItemTable.Columns.Add("CONDITION", typeof(string)); //6
m_weatherItemTable.Columns.Add("ICONNAME", typeof(string)); //7
m_weatherItemTable.Columns.Add("ICONID", typeof(int)); //8
m_weatherItemTable.Columns.Add("DAY", typeof(string)); //9
m_weatherItemTable.Columns.Add("DATE", typeof(string)); //10
m_weatherItemTable.Columns.Add("LOW", typeof(string)); //11
m_weatherItemTable.Columns.Add("HIGH", typeof(string)); //12
m_weatherItemTable.Columns.Add(" SELECTED", typeof(bool)); //13
m_weatherItemTable.Columns.Add("UPDATEDATE", typeof(DateTime)); //14
m_weatherItemTable.Columns.Add("TRACKERID", typeof(long)); //15
//set the ID column to be auto increment
m_weatherItemTable.Columns[0].AutoIncrement = true;
m_weatherItemTable.Columns[0].ReadOnly = true;
//the zipCode column must be the unique and nut allow null
m_weatherItemTable.Columns[1].Unique = true;
// set the ZIPCODE primary key for the table
m_weatherItemTable.PrimaryKey = new DataColumn[] { m_weatherItemTable.Columns["ZIPCODE"] };
}
else //in case that the local cache exists, simply load the tables from the cache.
{
DataSet ds = new DataSet();
ds.ReadXml(path);
m_weatherItemTable = ds.Tables["RECORDS"];
if (null == m_weatherItemTable)
throw new Exception("Cannot find 'RECORDS' table");
if (16 != m_weatherItemTable.Columns.Count)
throw new Exception("Table 'RECORDS' does not have all required columns");
m_weatherItemTable.Columns[0].ReadOnly = true;
// set the ZIPCODE primary key for the table
m_weatherItemTable.PrimaryKey = new DataColumn[] { m_weatherItemTable.Columns["ZIPCODE"] };
//synchronize the locations table
foreach (DataRow r in m_weatherItemTable.Rows)
{
try
{
//in case that the locations table does not exists, create and initialize it
if (null == m_locations)
InitializeLocations();
//get the zipcode for the record
string zip = Convert.ToString(r[1]);
//make sure that there is no existing record with that zipCode already in the
//locations table.
DataRow[] rows = m_locations.Select("ZIPCODE = " + zip);
if (0 == rows.Length)
{
DataRow rec = m_locations.NewRow();
rec[1] = Convert.ToInt64(r[1]); //zip code
rec[2] = Convert.ToString(r[2]); //city name
//add the new record to the locations table
lock (m_locations)
{
m_locations.Rows.Add(rec);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
//dispose the DS
ds.Tables.Remove(m_weatherItemTable);
ds.Dispose();
GC.Collect();
}
//initialize the symbol map table
m_symbolTable = new DataTable("Symbology");
//add the columns to the table
m_symbolTable.Columns.Add("ID", typeof(int)); //0
m_symbolTable.Columns.Add("ICONID", typeof(int)); //1
m_symbolTable.Columns.Add("SYMBOL", typeof(IGraphicTrackerSymbol)); //2
m_symbolTable.Columns.Add("SYMBOLWIDTH", typeof(int)); //3
m_symbolTable.Columns.Add("SYMBOLHEIGHT", typeof(int)); //4
m_symbolTable.Columns.Add("BITMAP", typeof(Bitmap)); //5
//set the ID column to be auto increment
m_symbolTable.Columns[0].AutoIncrement = true;
m_symbolTable.Columns[0].ReadOnly = true;
m_symbolTable.Columns[1].AllowDBNull = false;
m_symbolTable.Columns[1].Unique = true;
//set ICONID as the primary key for the table
m_symbolTable.PrimaryKey = new DataColumn[] { m_symbolTable.Columns["ICONID"] };
}
/// <summary>
/// Initialize the location table. Gets the location from a featureclass
/// </summary>
private void InitializeLocations()
{
//create a new instance of the location table
m_locations = new DataTable();
//add fields to the table
m_locations.Columns.Add("ID", typeof(int));
m_locations.Columns.Add("ZIPCODE", typeof(long));
m_locations.Columns.Add("CITYNAME", typeof(string));
m_locations.Columns[0].AutoIncrement = true;
m_locations.Columns[0].ReadOnly = true;
//set ZIPCODE as the primary key for the table
m_locations.PrimaryKey = new DataColumn[] { m_locations.Columns["ZIPCODE"] };
PopulateLocationsTable();
}
/// <summary>
/// Load the information from the MajorCities featureclass to the locations table
/// </summary>
private void PopulateLocationsTable()
{
string path = System.IO.Path.Combine(m_installationFolder + @"\..", @"DeveloperKit10.0\Samples\data\USZipCodeData\");
//open the featureclass
IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass() as IWorkspaceFactory;
IWorkspace ws = wf.OpenFromFile(path, 0);
IFeatureWorkspace fw = ws as IFeatureWorkspace;
IFeatureClass featureClass = fw.OpenFeatureClass("ZipCode_Boundaries_US_Major_Cities");
//map the name and zip fields
int zipIndex = featureClass.FindField("ZIP");
int nameIndex = featureClass.FindField("NAME");
string cityName;
long zip;
try
{
//iterate through the features and add the information to the table
IFeatureCursor fCursor = null;
fCursor = featureClass.Search(null, true);
IFeature feature = fCursor.NextFeature();
int index = 0;
while (null != feature)
{
object obj = feature.get_Value(nameIndex);
if (obj == null)
continue;
cityName = Convert.ToString(obj);
obj = feature.get_Value(zipIndex);
if (obj == null)
continue;
zip = long.Parse(Convert.ToString(obj));
if (zip <= 0)
continue;
//add the current location to the location table
DataRow r = m_locations.Rows.Find(zip);
if (null == r)
{
r = m_locations.NewRow();
r[1] = zip;
r[2] = cityName;
lock (m_locations)
{
m_locations.Rows.Add(r);
}
}
feature = fCursor.NextFeature();
index++;
}
//release the feature cursor
Marshal.ReleaseComObject(fCursor);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// weather ItemAdded event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <remarks>gets fired when an item is added to the table</remarks>
private void OnWeatherItemAddedEvent(object sender, WeatherItemEventArgs args)
{
// use the invoke helper since this event gets fired on a different thread
m_invokeHelper.RefreshWeatherItem(args);
}
#endregion
}
}
[Visual Basic .NET]
RSSWeather.vb
Imports System.Data
Imports System.Collections.Generic
Imports System.Text.RegularExpressions
Imports System.Xml
Imports System.IO
Imports System.Threading
Imports System.Timers
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports ESRI.ArcGIS.ADF.BaseClasses
Imports ESRI.ArcGIS.ADF.CATIDs
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.EngineCore
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.DataSourcesFile
#Region "WeatherItemEventArgs class members"
Public NotInheritable Class WeatherItemEventArgs
Inherits EventArgs
Private m_iconId As Integer
Private m_zipCode As Long
Private m_x As Double
Private m_y As Double
Private m_iconWidth As Integer
Private m_iconHeight As Integer
Public Sub New(iconId As Integer, zipCode As Long, X As Double, Y As Double, iconWidth As Integer, iconHeight As Integer)
m_iconId = iconId
m_zipCode = zipCode
m_x = X
m_y = Y
m_iconWidth = iconWidth
m_iconHeight = iconHeight
End Sub
Public ReadOnly Property IconID() As Integer
Get
Return m_iconId
End Get
End Property
Public ReadOnly Property ZipCode() As Long
Get
Return m_zipCode
End Get
End Property
Public ReadOnly Property mapY() As Double
Get
Return m_y
End Get
End Property
Public ReadOnly Property mapX() As Double
Get
Return m_x
End Get
End Property
End Class
#End Region
'declare delegates for the event handling
Public Delegate Sub WeatherItemAdded(sender As Object, args As WeatherItemEventArgs)
Public Delegate Sub WeatherItemsUpdated(sender As Object, args As EventArgs)
Class RSSWeather
Private NotInheritable Class InvokeHelper
Inherits Control
'delegate used to pass the invoked method to the main thread
Public Delegate Sub RefreshWeatherItemHelper(weatherItemInfo As WeatherItemEventArgs)
Private m_weather As RSSWeather = Nothing
Public Sub New(rssWeather As RSSWeather)
m_weather = rssWeather
CreateHandle()
CreateControl()
End Sub
Public Sub RefreshWeatherItem(weatherItemInfo As WeatherItemEventArgs)
Try
' Invoke the RefreshInternal through its delegate
If Not Me.IsDisposed AndAlso Me.IsHandleCreated Then
Invoke(New RefreshWeatherItemHelper(AddressOf RefreshWeatherItemInvoked), New Object() {weatherItemInfo})
End If
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
End Sub
Private Sub RefreshWeatherItemInvoked(weatherItemInfo As WeatherItemEventArgs)
If m_weather IsNot Nothing Then
m_weather.UpdateTracker(weatherItemInfo)
End If
End Sub
End Class
#Region "class members"
Private m_timer As System.Timers.Timer = Nothing
Private m_updateThread As Thread = Nothing
Private m_iconFolder As String = String.Empty
Private m_weatherItemTable As DataTable = Nothing
Private m_symbolTable As DataTable = Nothing
Private m_locations As DataTable = Nothing
Private m_dataFolder As String = String.Empty
Private m_installationFolder As String = String.Empty
Private m_point As IPoint = Nothing
Private m_SRWGS84 As ISpatialReference = Nothing
Private m_mapOrGlobe As IBasicMap = Nothing
Private m_textSymbol As ISimpleTextSymbol = Nothing
Private m_graphicTracker As IGraphicTracker = Nothing
Private m_invokeHelper As InvokeHelper = Nothing
'weather items events
Public Event OnWeatherItemAdded As WeatherItemAdded
Public Event OnWeatherItemsUpdated As WeatherItemsUpdated
#End Region
Public Sub New()
'get the directory for the cache. If it does not exist, create it.
m_dataFolder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "RSSWeather")
If Not System.IO.Directory.Exists(m_dataFolder) Then
System.IO.Directory.CreateDirectory(m_dataFolder)
End If
m_iconFolder = m_dataFolder
m_installationFolder = ESRI.ArcGIS.RuntimeManager.ActiveRuntime.Path
End Sub
Public Sub Init(mapOrGlobe As IBasicMap)
System.Diagnostics.Trace.WriteLine("Init - Thread ID: " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString())
If mapOrGlobe Is Nothing Then
Return
End If
m_mapOrGlobe = mapOrGlobe
Try
'initialize the tables (main table as well as the symbols table)
InitializeTables()
'get the location list from a featureclass (US major cities) and synchronize it with the
'cached information in case it exists.
If m_locations Is Nothing Then
InitializeLocations()
End If
m_point = New PointClass()
m_SRWGS84 = CreateGeographicSpatialReference()
m_point.SpatialReference = m_SRWGS84
m_textSymbol = TryCast(New TextSymbolClass(), ISimpleTextSymbol)
m_textSymbol.Font = ToFontDisp(New Font("Tahoma", 10F, FontStyle.Bold))
m_textSymbol.Size = 10.0
m_textSymbol.Color = DirectCast(ToRGBColor(Color.FromArgb(0, 255, 0)), IColor)
m_textSymbol.XOffset = 0.0
m_textSymbol.YOffset = 16.0
m_graphicTracker = New GraphicTrackerClass()
m_graphicTracker.Initialize(TryCast(mapOrGlobe, Object))
If m_weatherItemTable.Rows.Count > 0 Then
PopulateGraphicTracker()
End If
m_invokeHelper = New InvokeHelper(Me)
AddHandler Me.OnWeatherItemAdded, New WeatherItemAdded(AddressOf OnWeatherItemAddedEventHandler)
'instantiate the timer for the weather update thread
m_timer = New System.Timers.Timer(1000)
AddHandler m_timer.Elapsed, New ElapsedEventHandler(AddressOf OnUpdateTimer)
'enable the update timer
m_timer.Enabled = True
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
End Sub
Public Sub Remove()
RemoveHandler Me.OnWeatherItemAdded, New WeatherItemAdded(AddressOf OnWeatherItemAddedEventHandler)
m_invokeHelper = Nothing
m_timer.Enabled = False
' wait for the update thread to exit
m_updateThread.Join()
m_graphicTracker.RemoveAll()
m_graphicTracker = Nothing
End Sub
Public Sub UpdateTracker(weatherItemInfo As WeatherItemEventArgs)
System.Diagnostics.Trace.WriteLine("UpdateTracker - Thread ID: " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString())
If m_graphicTracker Is Nothing Then
Throw New Exception("Graphic tracker is not initialized!")
End If
' 1. lock the m_weatherItemTable and get the record
Dim row As DataRow
SyncLock m_weatherItemTable
row = m_weatherItemTable.Rows.Find(weatherItemInfo.ZipCode)
If row Is Nothing Then
Return
End If
' 2. get the symbol for the item
Dim symbol As IGraphicTrackerSymbol = GetSymbol(weatherItemInfo.IconID, Convert.ToString(row(7)))
If symbol Is Nothing Then
Return
End If
Dim label As String = String.Format("{0}, {1}?F", Convert.ToString(row(2)), Convert.ToString(row(5)))
' 3. check whether it has a tracker ID (not equals -1)
Dim trackerID As Integer = Convert.ToInt32(row(15))
'm_graphicTracker.SuspendUpdate = true;
m_point.PutCoords(weatherItemInfo.mapX, weatherItemInfo.mapY)
m_point.SpatialReference = m_SRWGS84
m_point.Project(m_mapOrGlobe.SpatialReference)
If trackerID = -1 Then
' new tracker
trackerID = m_graphicTracker.Add(TryCast(m_point, IGeometry), symbol)
m_graphicTracker.SetTextSymbol(trackerID, m_textSymbol)
row(15) = trackerID
Else
' existing tracker
m_graphicTracker.MoveTo(trackerID, m_point.X, m_point.Y, 0)
m_graphicTracker.SetSymbol(trackerID, symbol)
End If
m_graphicTracker.SetLabel(trackerID, label)
'm_graphicTracker.SuspendUpdate = false;
row.AcceptChanges()
End SyncLock
End Sub
#Region "private utility methods"
Private Sub PopulateGraphicTracker()
m_graphicTracker.SuspendUpdate = True
For Each row As DataRow In m_weatherItemTable.Rows
Dim symbol As IGraphicTrackerSymbol = GetSymbol(Convert.ToInt32(row(8)), Convert.ToString(row(7)))
If symbol Is Nothing Then
Continue For
End If
Dim label As String = String.Format("{0}, {1}?F", Convert.ToString(row(2)), Convert.ToString(row(5)))
m_point.PutCoords(Convert.ToDouble(row(4)), Convert.ToDouble(row(3)))
Dim trackerID As Integer = m_graphicTracker.Add(TryCast(m_point, IGeometry), symbol)
m_graphicTracker.SetTextSymbol(trackerID, m_textSymbol)
m_graphicTracker.SetScaleMode(trackerID, esriGTScale.esriGTScaleAuto)
m_graphicTracker.SetOrientationMode(trackerID, esriGTOrientation.esriGTOrientationAutomatic)
m_graphicTracker.SetElevationMode(trackerID, esriGTElevation.esriGTElevationClampToGround)
m_graphicTracker.SetLabel(trackerID, label)
row(15) = trackerID
row.AcceptChanges()
Next
m_graphicTracker.SuspendUpdate = False
End Sub
''' <summary>
''' create a WGS1984 geographic coordinate system.
''' In this case, the underlying data provided by the service is in WGS1984.
''' </summary>
''' <returns></returns>
Private Function CreateGeographicSpatialReference() As ISpatialReference
Dim spatialRefFatcory As ISpatialReferenceFactory = New SpatialReferenceEnvironmentClass()
Dim geoCoordSys As IGeographicCoordinateSystem
geoCoordSys = spatialRefFatcory.CreateGeographicCoordinateSystem(CInt(esriSRGeoCSType.esriSRGeoCS_WGS1984))
geoCoordSys.SetFalseOriginAndUnits(-180.0, -180.0, 5000000.0)
geoCoordSys.SetZFalseOriginAndUnits(0.0, 100000.0)
geoCoordSys.SetMFalseOriginAndUnits(0.0, 100000.0)
Return TryCast(geoCoordSys, ISpatialReference)
End Function
''' <summary>
''' run the thread that does the update of the weather data
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub OnUpdateTimer(sender As Object, e As ElapsedEventArgs)
m_timer.Interval = 2700000
'(45 minutes)
m_updateThread = New Thread(New ThreadStart(AddressOf ThreadProc))
'run the update thread
m_updateThread.Start()
End Sub
''' <summary>
''' the main update thread for the data.
''' </summary>
''' <remarks>Since the information is coming from a web service which might
''' take a while to respond, it is not logical to let the application hang while waiting
''' for response. Therefore, running the request on a different thread frees the application to
''' continue working while waiting for a response.
''' Please note that in this case, synchronization of shared resources must be addressed,
''' otherwise you might end up getting unexpected results.</remarks>
Private Sub ThreadProc()
Try
Dim lZipCode As Long
'iterate through all the records in the main table and update it against
'the information from the website.
For Each r As DataRow In m_locations.Rows
'put the thread to sleep in order not to overwhelm the RSS website
'System.Threading.Thread.Sleep(200);
'get the zip code of the record (column #1)
lZipCode = Convert.ToInt32(r(1))
'make the request and update the item
AddWeatherItem(lZipCode, 0.0, 0.0)
Next
'serialize the tables onto the local machine
Dim ds As New DataSet()
ds.Tables.Add(m_weatherItemTable)
ds.WriteXml(System.IO.Path.Combine(m_dataFolder, "Weather.xml"))
ds.Tables.Remove(m_weatherItemTable)
ds.Dispose()
GC.Collect()
'fire an event to notify update of the weather items
RaiseEvent OnWeatherItemsUpdated(Me, New EventArgs())
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
End Sub
''' <summary>
''' given a bitmap url, saves it on the local machine and returns its size
''' </summary>
''' <param name="iconPath"></param>
''' <param name="width"></param>
''' <param name="height"></param>
Private Function DownloadIcon(iconPath As String, width As Integer, height As Integer) As Bitmap
'if the icon does not exist on the local machine, get it from RSS site
Dim iconFileName As String = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) & ".png")
width = 0
height = 0
Dim bitmap As Bitmap = Nothing
If Not File.Exists(iconFileName) Then
Using webClient As New System.Net.WebClient()
'open a readable stream to download the bitmap
Using stream As System.IO.Stream = webClient.OpenRead(iconPath)
bitmap = New Bitmap(stream, True)
'save the image as a bitmap in the icons folder
bitmap.Save(iconFileName, ImageFormat.Png)
'get the bitmap's dimensions
width = bitmap.Width
height = bitmap.Height
End Using
End Using
Else
'get the bitmap's dimensions
If True Then
bitmap = New Bitmap(iconFileName)
width = bitmap.Width
height = bitmap.Height
End If
End If
Return bitmap
End Function
''' <summary>
''' get the specified symbol from the symbols table.
''' </summary>
''' <param name="iconCode"></param>
''' <param name="dbr"></param>
''' <returns></returns>
Private Function GetSymbol(iconCode As Integer, iconPath As String) As IGraphicTrackerSymbol
Dim symbol As IGraphicTrackerSymbol
Dim iconWidth As Integer, iconHeight As Integer
Dim bitmap As Bitmap = Nothing
'search for an existing symbol in the table
Dim r As DataRow = m_symbolTable.Rows.Find(iconCode)
If r Is Nothing Then
'in case that the symbol does not exist in the table, create a new entry
r = m_symbolTable.NewRow()
r(1) = iconCode
'Initialize the picture marker symbol
symbol = InitializeSymbol(iconPath, iconWidth, iconHeight, bitmap)
If symbol Is Nothing Then
Return Nothing
End If
'update the symbol table
SyncLock m_symbolTable
r(2) = symbol
r(3) = iconWidth
r(4) = iconHeight
r(5) = bitmap
m_symbolTable.Rows.Add(r)
End SyncLock
Else
If TypeOf r(2) Is DBNull Then
'in case that the record exists but the symbol hasn't been initialized
'Initialize the symbol
symbol = InitializeSymbol(iconPath, iconWidth, iconHeight, bitmap)
If symbol Is Nothing Then
Return Nothing
End If
'update the symbol table
SyncLock m_symbolTable
r(2) = symbol
r(5) = bitmap
r.AcceptChanges()
End SyncLock
Else
'the record exists in the table and the symbol has been initialized
'get the symbol
symbol = TryCast(r(2), IGraphicTrackerSymbol)
End If
End If
'return the requested symbol
Return symbol
End Function
''' <summary>
''' Initialize a character marker symbol for a given bitmap path
''' </summary>
''' <param name="iconPath"></param>
''' <param name="iconWidth"></param>
''' <param name="iconHeight"></param>
''' <returns></returns>
Private Function InitializeSymbol(iconPath As String, iconWidth As Integer, iconHeight As Integer, bitmap As Bitmap) As IGraphicTrackerSymbol
iconWidth = InlineAssignHelper(iconHeight, 0)
bitmap = Nothing
Try
'make sure that the icon exit on dist or else download it
DownloadIcon(iconPath, iconWidth, iconHeight)
Dim iconFileName As String = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) & ".png")
If Not System.IO.File.Exists(iconFileName) Then
Return Nothing
End If
Dim symbol As IGraphicTrackerSymbol = m_graphicTracker.CreateSymbolFromPath(iconFileName, iconFileName)
Return symbol
Catch
Return Nothing
End Try
End Function
''' <summary>
''' Makes a request against RSS Weather service and add update the table
''' </summary>
''' <param name="zipCode"></param>
''' <param name="Lat"></param>
''' <param name="Lon"></param>
Private Sub AddWeatherItem(zipCode As Long, Lat__1 As Double, Lon__2 As Double)
Try
Dim cityName As String
Dim lat__3 As Double, lon__4 As Double
Dim temp As Integer
Dim condition As String
Dim desc As String
Dim iconPath As String
Dim day As String
Dim [date] As String
Dim low As Integer
Dim high As Integer
Dim iconCode As Integer
Dim iconWidth As Integer = 52
'default values
Dim iconHeight As Integer = 52
Dim bitmap As Bitmap = Nothing
Dim dbr As DataRow = m_weatherItemTable.Rows.Find(zipCode)
If dbr IsNot Nothing Then
' get the date
Dim updateDate As DateTime = Convert.ToDateTime(dbr(14))
Dim ts As TimeSpan = DateTime.Now - updateDate
' if the item had been updated in the past 15 minutes, simply bail out.
If ts.TotalMinutes < 15 Then
Return
End If
End If
'the base URL for the service
Dim url As String = "http://xml.weather.yahoo.com/forecastrss?p="
'the RegEx used to extract the icon path from the HTML tag
Dim regxQry As String = "(http://(\"")?(.*?\.gif))"
Dim reader As XmlTextReader = Nothing
Dim doc As XmlDocument
Dim node As XmlNode
Try
'make the request and get the result back into XmlReader
reader = New XmlTextReader(url & zipCode.ToString())
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
Return
End Try
'load the XmlReader to an xml doc
doc = New XmlDocument()
doc.Load(reader)
'set an XmlNamespaceManager since we have to make explicit namespace searches
Dim xmlnsManager As XmlNamespaceManager = New System.Xml.XmlNamespaceManager(doc.NameTable)
'Add the namespaces used in the xml doc to the XmlNamespaceManager.
xmlnsManager.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0")
xmlnsManager.AddNamespace("geo", "http://www.w3.org/2003/01/geo/wgs84_pos#")
'make sure that the node exists
node = doc.DocumentElement.SelectSingleNode("/rss/channel/yweather:location/@city", xmlnsManager)
If node Is Nothing Then
Return
End If
'get the city name
cityName = doc.DocumentElement.SelectSingleNode("/rss/channel/yweather:location/@city", xmlnsManager).InnerXml
If Lat__1 = 0.0 AndAlso Lon__2 = 0.0 Then
'in case that the caller did not specify a coordinate, get the default coordinate from the service
lat__3 = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:lat", xmlnsManager).InnerXml)
lon__4 = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:long", xmlnsManager).InnerXml)
Else
lat__3 = Lat__1
lon__4 = Lon__2
End If
'extract the rest of the information from the RSS response
condition = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@text", xmlnsManager).InnerXml
iconCode = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@code", xmlnsManager).InnerXml)
temp = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:condition/@temp", xmlnsManager).InnerXml)
desc = doc.DocumentElement.SelectSingleNode("/rss/channel/item/description").InnerXml
day = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@day", xmlnsManager).InnerXml
[date] = doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@date", xmlnsManager).InnerXml
low = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@low", xmlnsManager).InnerXml)
high = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("/rss/channel/item/yweather:forecast/@high", xmlnsManager).InnerXml)
'use regex in order to extract the icon name from the html script
Dim m As Match = Regex.Match(desc, regxQry)
If m.Success Then
iconPath = m.Value
'add the icon ID to the symbology table
Dim tr As DataRow = m_symbolTable.Rows.Find(iconCode)
If tr Is Nothing Then
'get the icon from the website
bitmap = DownloadIcon(iconPath, iconWidth, iconHeight)
'create a new record
tr = m_symbolTable.NewRow()
tr(1) = iconCode
tr(3) = iconWidth
tr(4) = iconHeight
tr(5) = bitmap
'update the symbol table. The initialization of the symbol cannot take place in here, since
'this code gets executed on a background thread.
SyncLock m_symbolTable
m_symbolTable.Rows.Add(tr)
End SyncLock
Else
'get the icon's dimensions from the table
'get the icon's dimensions from the table
iconWidth = Convert.ToInt32(tr(3))
iconHeight = Convert.ToInt32(tr(4))
End If
Else
iconPath = ""
End If
'test whether the record already exists in the table.
If dbr Is Nothing Then
'in case that the record does not exist
'create a new record
dbr = m_weatherItemTable.NewRow()
If Not m_weatherItemTable.Columns(0).AutoIncrement Then
dbr(0) = Convert.ToInt32(DateTime.Now.Millisecond)
End If
'add the item to the table
SyncLock m_weatherItemTable
dbr(1) = zipCode
dbr(2) = cityName
dbr(3) = lat__3
dbr(4) = lon__4
dbr(5) = temp
dbr(6) = condition
dbr(7) = iconPath
dbr(8) = iconCode
dbr(9) = day
dbr(10) = [date]
dbr(11) = low
dbr(12) = high
dbr(13) = False
dbr(14) = DateTime.Now
dbr(15) = -1
m_weatherItemTable.Rows.Add(dbr)
End SyncLock
Else
'in case that the record exists, just update it
'update the record
SyncLock m_weatherItemTable
dbr(5) = temp
dbr(6) = condition
dbr(7) = iconPath
dbr(8) = iconCode
dbr(9) = day
dbr(10) = [date]
dbr(11) = low
dbr(12) = high
dbr(14) = DateTime.Now
dbr.AcceptChanges()
End SyncLock
End If
'fire an event to notify the user that the item has been updated
If OnWeatherItemAddedEvent IsNot Nothing Then
Dim weatherItemEventArgs As New WeatherItemEventArgs(iconCode, zipCode, lon__4, lat__3, iconWidth, iconHeight)
RaiseEvent OnWeatherItemAdded(Me, weatherItemEventArgs)
End If
Catch ex As Exception
System.Diagnostics.Trace.WriteLine("AddWeatherItem: " & ex.Message)
End Try
End Sub
Private Function ToRGBColor(color As System.Drawing.Color) As IRgbColor
Dim rgbColor As IRgbColor = New RgbColorClass()
rgbColor.Red = color.R
rgbColor.Green = color.G
rgbColor.Blue = color.B
Return rgbColor
End Function
Private Function ToFontDisp(font As System.Drawing.Font) As stdole.IFontDisp
Dim aFont As stdole.IFont
aFont = New stdole.StdFontClass()
aFont.Name = font.Name
aFont.Size = CDec(font.Size)
aFont.Bold = font.Bold
aFont.Italic = font.Italic
aFont.Strikethrough = font.Strikeout
aFont.Underline = font.Underline
Return TryCast(aFont, stdole.IFontDisp)
End Function
''' <summary>
''' initialize the main table as well as the symbols table.
''' The base class calls new on the table and adds a default ID field.
''' </summary>
Private Sub InitializeTables()
Dim path As String = System.IO.Path.Combine(m_dataFolder, "Weather.xml")
'In case that there is no existing cache on the local machine, create the table.
If Not System.IO.File.Exists(path) Then
'create the table the table in addition to the default 'ID' and 'Geometry'
m_weatherItemTable = New DataTable("RECORDS")
m_weatherItemTable.Columns.Add("ID", GetType(Long))
'0
m_weatherItemTable.Columns.Add("ZIPCODE", GetType(Long))
'1
m_weatherItemTable.Columns.Add("CITYNAME", GetType(String))
'2
m_weatherItemTable.Columns.Add("LAT", GetType(Double))
'3
m_weatherItemTable.Columns.Add("LON", GetType(Double))
'4
m_weatherItemTable.Columns.Add("TEMP", GetType(Integer))
'5
m_weatherItemTable.Columns.Add("CONDITION", GetType(String))
'6
m_weatherItemTable.Columns.Add("ICONNAME", GetType(String))
'7
m_weatherItemTable.Columns.Add("ICONID", GetType(Integer))
'8
m_weatherItemTable.Columns.Add("DAY", GetType(String))
'9
m_weatherItemTable.Columns.Add("DATE", GetType(String))
'10
m_weatherItemTable.Columns.Add("LOW", GetType(String))
'11
m_weatherItemTable.Columns.Add("HIGH", GetType(String))
'12
m_weatherItemTable.Columns.Add(" SELECTED", GetType(Boolean))
'13
m_weatherItemTable.Columns.Add("UPDATEDATE", GetType(DateTime))
'14
m_weatherItemTable.Columns.Add("TRACKERID", GetType(Long))
'15
'set the ID column to be auto increment
m_weatherItemTable.Columns(0).AutoIncrement = True
m_weatherItemTable.Columns(0).[ReadOnly] = True
'the zipCode column must be the unique and nut allow null
m_weatherItemTable.Columns(1).Unique = True
' set the ZIPCODE primary key for the table
m_weatherItemTable.PrimaryKey = New DataColumn() {m_weatherItemTable.Columns("ZIPCODE")}
Else
'in case that the local cache exists, simply load the tables from the cache.
Dim ds As New DataSet()
ds.ReadXml(path)
m_weatherItemTable = ds.Tables("RECORDS")
If m_weatherItemTable Is Nothing Then
Throw New Exception("Cannot find 'RECORDS' table")
End If
If 16 <> m_weatherItemTable.Columns.Count Then
Throw New Exception("Table 'RECORDS' does not have all required columns")
End If
m_weatherItemTable.Columns(0).[ReadOnly] = True
' set the ZIPCODE primary key for the table
m_weatherItemTable.PrimaryKey = New DataColumn() {m_weatherItemTable.Columns("ZIPCODE")}
'synchronize the locations table
For Each r As DataRow In m_weatherItemTable.Rows
Try
'in case that the locations table does not exists, create and initialize it
If m_locations Is Nothing Then
InitializeLocations()
End If
'get the zipcode for the record
Dim zip As String = Convert.ToString(r(1))
'make sure that there is no existing record with that zipCode already in the
'locations table.
Dim rows As DataRow() = m_locations.[Select]("ZIPCODE = " & zip)
If 0 = rows.Length Then
Dim rec As DataRow = m_locations.NewRow()
rec(1) = Convert.ToInt64(r(1))
'zip code
rec(2) = Convert.ToString(r(2))
'city name
'add the new record to the locations table
SyncLock m_locations
m_locations.Rows.Add(rec)
End SyncLock
End If
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
Next
'dispose the DS
ds.Tables.Remove(m_weatherItemTable)
ds.Dispose()
GC.Collect()
End If
'initialize the symbol map table
m_symbolTable = New DataTable("Symbology")
'add the columns to the table
m_symbolTable.Columns.Add("ID", GetType(Integer))
'0
m_symbolTable.Columns.Add("ICONID", GetType(Integer))
'1
m_symbolTable.Columns.Add("SYMBOL", GetType(IGraphicTrackerSymbol))
'2
m_symbolTable.Columns.Add("SYMBOLWIDTH", GetType(Integer))
'3
m_symbolTable.Columns.Add("SYMBOLHEIGHT", GetType(Integer))
'4
m_symbolTable.Columns.Add("BITMAP", GetType(Bitmap))
'5
'set the ID column to be auto increment
m_symbolTable.Columns(0).AutoIncrement = True
m_symbolTable.Columns(0).[ReadOnly] = True
m_symbolTable.Columns(1).AllowDBNull = False
m_symbolTable.Columns(1).Unique = True
'set ICONID as the primary key for the table
m_symbolTable.PrimaryKey = New DataColumn() {m_symbolTable.Columns("ICONID")}
End Sub
''' <summary>
''' Initialize the location table. Gets the location from a featureclass
''' </summary>
Private Sub InitializeLocations()
'create a new instance of the location table
m_locations = New DataTable()
'add fields to the table
m_locations.Columns.Add("ID", GetType(Integer))
m_locations.Columns.Add("ZIPCODE", GetType(Long))
m_locations.Columns.Add("CITYNAME", GetType(String))
m_locations.Columns(0).AutoIncrement = True
m_locations.Columns(0).[ReadOnly] = True
'set ZIPCODE as the primary key for the table
m_locations.PrimaryKey = New DataColumn() {m_locations.Columns("ZIPCODE")}
PopulateLocationsTable()
End Sub
''' <summary>
''' Load the information from the MajorCities featureclass to the locations table
''' </summary>
Private Sub PopulateLocationsTable()
Dim path As String = System.IO.Path.Combine(m_installationFolder & "\..", "DeveloperKit10.0\Samples\data\USZipCodeData\")
'open the featureclass
Dim wf As IWorkspaceFactory = TryCast(New ShapefileWorkspaceFactoryClass(), IWorkspaceFactory)
Dim ws As IWorkspace = wf.OpenFromFile(path, 0)
Dim fw As IFeatureWorkspace = TryCast(ws, IFeatureWorkspace)
Dim featureClass As IFeatureClass = fw.OpenFeatureClass("ZipCode_Boundaries_US_Major_Cities")
'map the name and zip fields
Dim zipIndex As Integer = featureClass.FindField("ZIP")
Dim nameIndex As Integer = featureClass.FindField("NAME")
Dim cityName As String
Dim zip As Long
Try
'iterate through the features and add the information to the table
Dim fCursor As IFeatureCursor = Nothing
fCursor = featureClass.Search(Nothing, True)
Dim feature As IFeature = fCursor.NextFeature()
Dim index As Integer = 0
While feature IsNot Nothing
Dim obj As Object = feature.get_Value(nameIndex)
If obj Is Nothing Then
Continue While
End If
cityName = Convert.ToString(obj)
obj = feature.get_Value(zipIndex)
If obj Is Nothing Then
Continue While
End If
zip = Long.Parse(Convert.ToString(obj))
If zip <= 0 Then
Continue While
End If
'add the current location to the location table
Dim r As DataRow = m_locations.Rows.Find(zip)
If r Is Nothing Then
r = m_locations.NewRow()
r(1) = zip
r(2) = cityName
SyncLock m_locations
m_locations.Rows.Add(r)
End SyncLock
End If
feature = fCursor.NextFeature()
index += 1
End While
'release the feature cursor
Marshal.ReleaseComObject(fCursor)
Catch ex As Exception
System.Diagnostics.Trace.WriteLine(ex.Message)
End Try
End Sub
''' <summary>
''' weather ItemAdded event handler
''' </summary>
''' <param name="sender"></param>
''' <param name="args"></param>
''' <remarks>gets fired when an item is added to the table</remarks>
Private Sub OnWeatherItemAddedEventHandler(ByVal sender As Object, ByVal args As WeatherItemEventArgs)
' use the invoke helper since this event gets fired on a different thread
m_invokeHelper.RefreshWeatherItem(args)
End Sub
Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
#End Region
End Class