RSS weather GraphicTracker
RSSWeatherGraphicTracker\RSSWeather.vb
' 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.
' 

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