RSSWeatherLayerClass.vb
' Copyright 2012 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 Microsoft.VisualBasic Imports System Imports System.Collections Imports System.Data Imports System.Runtime.InteropServices Imports System.Xml Imports System.Threading Imports System.Timers Imports System.Text.RegularExpressions Imports System.IO Imports System.Drawing Imports System.Drawing.Imaging Imports System.Windows.Forms Imports System.ComponentModel Imports Microsoft.Win32 Imports ESRI.ArcGIS.ADF.BaseClasses Imports ESRI.ArcGIS.Carto Imports ESRI.ArcGIS.Geodatabase Imports ESRI.ArcGIS.esriSystem Imports ESRI.ArcGIS.Geometry Imports ESRI.ArcGIS.Display Imports ESRI.ArcGIS.DataSourcesFile #Region "WeatherItemEventArgs class members" Public NotInheritable Class WeatherItemEventArgs : Inherits EventArgs Private m_id 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(ByVal newID As Integer, ByVal newZipCode As Long, ByVal X As Double, ByVal Y As Double, ByVal newIconWidth As Integer, ByVal newIconHeight As Integer) m_id = newID m_zipCode = newZipCode m_x = X m_y = Y m_iconWidth = newIconWidth m_iconHeight = newIconHeight End Sub Public ReadOnly Property ID() As Integer Get Return m_id 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 Public ReadOnly Property IconWidth() As Integer Get Return m_iconWidth End Get End Property Public ReadOnly Property IconHeight() As Integer Get Return m_iconHeight End Get End Property End Class #End Region 'declare delegates for the event handling Public Delegate Sub WeatherItemAdded(ByVal sender As Object, ByVal args As WeatherItemEventArgs) Public Delegate Sub WeatherItemsUpdated(ByVal sender As Object, ByVal args As EventArgs) ''' <summary> ''' RSSWeatherLayerClass is a custom layer for ArcMap/MapControl. It inherits CustomLayerBase ''' which implements the relevant interfaces required by the Map. ''' This sample is a comprehensive sample of a real life scenario for creating a new layer in ''' order to consume a web service and display the information in a map. ''' In this sample you can find implementation of simple editing capabilities, selection by ''' attribute and by location, persistence and identify. ''' </summary> <Guid("3460FB55-4326-4d28-9F96-D62211B0C754"), ClassInterface(ClassInterfaceType.None), ComVisible(True), ProgId("RSSWeatherLayerClass")> _ Public NotInheritable Class RSSWeatherLayerClass : Inherits BaseDynamicLayer : Implements IIdentify #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_table As DataTable = Nothing Private m_symbolTable As DataTable = Nothing Private m_locations As DataTable = Nothing Private m_selectionSymbol As ISymbol = Nothing Private m_display As IDisplay = Nothing Private m_dataFolder As String = String.Empty Private m_layerSRFactoryCode As Integer = 0 Private m_symbolSize As Integer = 32 Private m_point As IPoint = Nothing Private m_llPnt As IPoint = Nothing Private m_urPnt As IPoint = Nothing Private m_env As IEnvelope = Nothing ' dynamic display members Private m_dynamicGlyphFactory As IDynamicGlyphFactory2 = Nothing Private m_dynamicSymbolProperties As IDynamicSymbolProperties2 = Nothing Private m_dynamicCompoundMarker As IDynamicCompoundMarker2 = Nothing Private m_textGlyph As IDynamicGlyph = Nothing Private m_selectionGlyph As IDynamicGlyph = Nothing Private m_bDDOnce As Boolean = True 'weather items events Public Event OnWeatherItemAdded As WeatherItemAdded Public Event OnWeatherItemsUpdated As WeatherItemsUpdated #End Region #Region "Constructor" ''' <summary> ''' The class has only default CTor. ''' </summary> Public Sub New() MyBase.New() Try 'set the layer's name MyBase.m_sName = "RSS Weather Layer" 'ask the Map to create a separate cache for the layer MyBase.m_IsCached = False ' the underlying data for this layer is always in WGS1984 geographical coordinate system m_spatialRef = CreateGeographicSpatialReference() m_layerSRFactoryCode = m_spatialRef.FactoryCode 'get the directory for the layer's cache. If it does not exist, create it. m_dataFolder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "RSSWeatherLayer") If (Not System.IO.Directory.Exists(m_dataFolder)) Then System.IO.Directory.CreateDirectory(m_dataFolder) End If m_iconFolder = m_dataFolder 'instantiate the timer for the weather update m_timer = New System.Timers.Timer(1000) m_timer.Enabled = False AddHandler m_timer.Elapsed, AddressOf OnUpdateTimer 'initialize the layer's 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 Nothing Is m_locations Then InitializeLocations() End If 'initialize the selection symbol used to highlight selected weather items InitializeSelectionSymbol() m_point = New PointClass() m_llPnt = New PointClass() m_urPnt = New PointClass() m_env = New EnvelopeClass() 'connect to the RSS service Connect() Catch ex As Exception System.Diagnostics.Trace.WriteLine(ex.Message) End Try End Sub Protected Overrides Sub Finalize() Disconnect() End Sub #End Region #Region "Overridden methods" ''' <summary> ''' Draws the layer to the specified display for the given draw phase. ''' </summary> ''' <param name="drawPhase"></param> ''' <param name="Display"></param> ''' <param name="trackCancel"></param> ''' <remarks>the draw method is set as an abstract method and therefore must be overridden</remarks> Public Overrides Sub Draw(ByVal drawPhase As esriDrawPhase, ByVal Display As IDisplay, ByVal trackCancel As ITrackCancel) If drawPhase <> esriDrawPhase.esriDPGeography Then Return End If If Display Is Nothing Then Return End If If m_table Is Nothing OrElse m_symbolTable Is Nothing Then Return End If m_display = Display Dim envelope As IEnvelope = TryCast(Display.DisplayTransformation.FittedBounds, IEnvelope) Dim lat, lon As Double Dim iconCode As Integer Dim selected As Boolean Dim symbol As ISymbol = Nothing 'loop through the rows. Draw each row that has a shape For Each row As DataRow In m_table.Rows 'get the Lat/Lon of the item lat = Convert.ToDouble(row(3)) lon = Convert.ToDouble(row(4)) 'get the icon ID iconCode = Convert.ToInt32(row(8)) 'get the selection state of the item selected = Convert.ToBoolean(row(13)) If lon >= envelope.XMin AndAlso lon <= envelope.XMax AndAlso lat >= envelope.YMin AndAlso lat <= envelope.YMax Then 'search for the symbol in the symbology table symbol = GetSymbol(iconCode, row) If Nothing Is symbol Then Continue For End If m_point.X = lon m_point.Y = lat m_point.SpatialReference = m_spatialRef 'reproject the point to the DataFrame's spatial reference If Not Nothing Is m_mapSpatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode Then m_point.Project(m_mapSpatialRef) End If Display.SetSymbol(symbol) Display.DrawPoint(m_point) If selected Then Display.SetSymbol(m_selectionSymbol) Display.DrawPoint(m_point) End If End If Next row End Sub ''' <summary> ''' Draw the layer while in dynamic mode ''' </summary> ''' <param name="DynamicDrawPhase"></param> ''' <param name="Display"></param> ''' <param name="DynamicDisplay"></param> Public Overrides Sub DrawDynamicLayer(ByVal DynamicDrawPhase As esriDynamicDrawPhase, ByVal Display As IDisplay, ByVal DynamicDisplay As IDynamicDisplay) If DynamicDrawPhase <> esriDynamicDrawPhase.esriDDPCompiled Then Return End If If (Not m_bValid) OrElse (Not m_visible) Then Return End If If m_bDDOnce Then m_dynamicGlyphFactory = TryCast(DynamicDisplay.DynamicGlyphFactory, IDynamicGlyphFactory2) m_dynamicSymbolProperties = TryCast(DynamicDisplay, IDynamicSymbolProperties2) m_dynamicCompoundMarker = TryCast(DynamicDisplay, IDynamicCompoundMarker2) m_textGlyph = m_dynamicGlyphFactory.DynamicGlyph(1, esriDynamicGlyphType.esriDGlyphText, 1) ' create glyph for the selection symbol If m_selectionSymbol Is Nothing Then InitializeSelectionSymbol() End If m_selectionGlyph = m_dynamicGlyphFactory.CreateDynamicGlyph(m_selectionSymbol) m_bDDOnce = False End If m_display = Display Dim lat, lon As Double Dim iconCode As Integer Dim iconWidth As Integer = 0 Dim selected As Boolean Dim dynamicGlyph As IDynamicGlyph = Nothing Dim symbolSized As Single Dim citiName As String = String.Empty Dim temperature As String = String.Empty 'loop through the rows. Draw each row that has a shape For Each row As DataRow In m_table.Rows 'get the Lat/Lon of the item lat = Convert.ToDouble(row(3)) lon = Convert.ToDouble(row(4)) 'get the icon ID iconCode = Convert.ToInt32(row(8)) ' get citiname and temperature citiName = Convert.ToString(row(2)) temperature = String.Format("{0} F", row(5)) 'get the selection state of the item selected = Convert.ToBoolean(row(13)) 'search for the symbol in the symbology table dynamicGlyph = GetDynamicGlyph(m_dynamicGlyphFactory, iconCode, row, iconWidth) If Nothing Is dynamicGlyph Then Continue For End If m_point.X = lon m_point.Y = lat m_point.SpatialReference = m_spatialRef 'reproject the point to the DataFrame's spatial reference If Not Nothing Is m_spatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode Then m_point.Project(m_mapSpatialRef) End If symbolSized = 1.35F * CSng(m_symbolSize / CDbl(iconWidth)) ' draw the weather item ' 1. set the whether symbol properties m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker) = dynamicGlyph m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker) = esriDynamicSymbolRotationAlignment.esriDSRAScreen m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolMarker) = 0.0F m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0F, 1.0F, 1.0F, 1.0F) m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, symbolSized, symbolSized) m_dynamicSymbolProperties.Smooth(esriDynamicSymbolType.esriDSymbolMarker) = False ' 2. set the text properties m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolText) = m_textGlyph m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker) = esriDynamicSymbolRotationAlignment.esriDSRAScreen m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolText) = 0.0F m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0F, 0.85F, 0.0F, 1.0F) m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolText, 1.0F, 1.0F) m_dynamicSymbolProperties.Smooth(esriDynamicSymbolType.esriDSymbolText) = False m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = False m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHACenter m_dynamicSymbolProperties.TextRightToLeft = False ' draw both the icon and the text as a compound marker m_dynamicCompoundMarker.DrawCompoundMarker2(m_point, temperature, citiName) If selected Then ' draw the selected symbol m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0F, 1.0F, 1.0F, 1.0F) m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker) = m_selectionGlyph DynamicDisplay.DrawMarker(m_point) End If Next row MyBase.m_bIsCompiledDirty = False End Sub ''' <summary> ''' The spatial reference of the underlying data. ''' </summary> Public Overrides ReadOnly Property SpatialReference() As ISpatialReference Get If Nothing Is m_spatialRef Then m_spatialRef = CreateGeographicSpatialReference() End If Return m_spatialRef End Get End Property ''' <summary> ''' The ID of the object. ''' </summary> Public Overrides ReadOnly Property ID() As ESRI.ArcGIS.esriSystem.UID Get Dim uid As UID = New UIDClass() uid.Value = "RSSWeatherLayerClass" Return uid End Get End Property ''' <summary> ''' The default area of interest for the layer. Returns the spatial-referenced extent of the layer. ''' </summary> Public Overrides ReadOnly Property AreaOfInterest() As IEnvelope Get Return Me.Extent End Get End Property ''' <summary> ''' The layer's extent which is a union of the extents of all the items of the layer ''' </summary> ''' <remarks>In case where the DataFram's spatial reference is different than the underlying ''' data's spatial reference the envelope must be projected</remarks> Public Overrides ReadOnly Property Extent() As IEnvelope Get m_extent = GetLayerExtent() If Nothing Is m_extent Then Return Nothing End If Dim env As IEnvelope = TryCast((CType(m_extent, IClone)).Clone(), IEnvelope) If Not Nothing Is m_mapSpatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode Then env.Project(m_mapSpatialRef) End If Return env End Get End Property ''' <summary> ''' Map tip text at the specified mouse location. ''' </summary> ''' <param name="X"></param> ''' <param name="Y"></param> ''' <param name="Tolerance"></param> ''' <returns></returns> Public Overrides Function get_TipText(ByVal X As Double, ByVal Y As Double, ByVal Tolerance As Double) As String Dim envelope As IEnvelope = New EnvelopeClass() envelope.PutCoords(X - Tolerance, Y - Tolerance, X + Tolerance, Y + Tolerance) 'reproject the envelope to the datasource coordinate system If Not Nothing Is m_mapSpatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode Then envelope.SpatialReference = m_spatialRef envelope.Project(m_mapSpatialRef) End If Dim xmin, ymin, xmax, ymax As Double envelope.QueryCoords(xmin, ymin, xmax, ymax) 'select all the records within the given extent Dim qry As String = "LON >= " & xmin.ToString() & " AND LON <= " & xmax.ToString() & " AND Lat >= " & ymin.ToString() & " AND LAT <= " & ymax.ToString() Dim rows As DataRow() = m_table.Select(qry) If 0 = rows.Length Then Return String.Empty End If Dim r As DataRow = rows(0) Dim zipCode As String = Convert.ToString(r(1)) Dim cityName As String = Convert.ToString(r(2)) Dim temperature As String = Convert.ToString(r(5)) Return cityName & ", " & zipCode & ", " & temperature & "F" End Function #End Region #Region "public methods" ''' <summary> ''' connects to RSS weather service ''' </summary> Public Sub Connect() 'enable the update timer m_timer.Enabled = True MyBase.m_bIsCompiledDirty = True End Sub ''' <summary> ''' disconnects from RSS weather service ''' </summary> Public Sub Disconnect() 'disable the update timer m_timer.Enabled = False Try 'abort the update thread in case that it is alive If m_updateThread.IsAlive Then m_updateThread.Abort() End If Catch System.Diagnostics.Trace.WriteLine("WeatherLayer update thread has been terminated") End Try End Sub ''' <summary> ''' select a weather item by its zipCode ''' </summary> ''' <param name="zipCode"></param> ''' <param name="newSelection"></param> Public Sub [Select](ByVal zipCode As Long, ByVal newSelection As Boolean) If Nothing Is m_table Then Return End If If newSelection Then UnselectAll() End If Dim rows As DataRow() = m_table.Select("ZIPCODE = " & zipCode.ToString()) If rows.Length = 0 Then Return End If Dim rec As DataRow = rows(0) SyncLock m_table '13 is the selection column ID rec(13) = True rec.AcceptChanges() End SyncLock MyBase.m_bIsCompiledDirty = True End Sub ''' <summary> ''' unselect all weather items ''' </summary> Public Sub UnselectAll() If Nothing Is m_table Then Return End If 'unselect all the currently selected items SyncLock m_table For Each r As DataRow In m_table.Rows '13 is the selection column ID r(13) = False Next r m_table.AcceptChanges() End SyncLock MyBase.m_bIsCompiledDirty = True End Sub ''' <summary> ''' Run the update thread ''' </summary> ''' <remarks>calling this method to frequently might end up in blockage of RSS service. ''' The service will interpret the excessive calls as an offence and thus would block the service for a while.</remarks> Public Sub Refresh() Try m_updateThread = New Thread(AddressOf ThreadProc) 'run the update thread m_updateThread.Start() Catch ex As Exception System.Diagnostics.Trace.WriteLine(ex.Message) End Try End Sub ''' <summary> ''' add a new item given only a zipcode (will use the default location given by the service) ''' should the item exists, it will get updated ''' </summary> ''' <param name="zipCode"></param> ''' <returns></returns> Public Function AddItem(ByVal zipCode As Long) As Boolean Return AddItem(zipCode, 0.0,0.0) End Function ''' <summary> ''' adds a new item given a zipcode and a coordinate. ''' Should the item already exists, it will get updated and will move to the new coordinate. ''' </summary> ''' <param name="zipCode"></param> ''' <param name="lat"></param> ''' <param name="lon"></param> ''' <returns></returns> Public Function AddItem(ByVal zipCode As Long, ByVal lat As Double, ByVal lon As Double) As Boolean If Nothing Is m_table Then Return False End If Dim r As DataRow = m_table.Rows.Find(zipCode) If Not Nothing Is r Then 'if the record with this zipCode already exists 'in case that the record exists and the input coordinates are not valid If lat = 0.0 AndAlso lon = 0.0 Then Return False Else 'update the location according to the new coordinate SyncLock m_table r(3) = lat r(4) = lon r.AcceptChanges() End SyncLock End If Else 'add new zip code to the locations list Dim rec As DataRow = m_locations.NewRow() SyncLock m_locations rec(1) = zipCode m_locations.Rows.Add(rec) End SyncLock 'need to connect to the service and get the info AddWeatherItem(zipCode, lat, lon) End If Return True End Function ''' <summary> ''' delete an item from the dataset ''' </summary> ''' <param name="zipCode"></param> ''' <returns></returns> Public Function DeleteItem(ByVal zipCode As Long) As Boolean If Nothing Is m_table Then Return False End If Try Dim r As DataRow = m_table.Rows.Find(zipCode) If Not Nothing Is r Then 'if the record with this zipCode already exists SyncLock m_table r.Delete() End SyncLock MyBase.m_bIsCompiledDirty = True Return True End If MyBase.m_bIsCompiledDirty = True Return False Catch ex As Exception System.Diagnostics.Trace.WriteLine(ex.Message) Return False End Try End Function ''' <summary> ''' get a weather item given a city name. ''' </summary> ''' <param name="cityName"></param> ''' <returns></returns> ''' <remarks>a city might have more than one zipCode and therefore this method will ''' return the first zipcOde found for the specified city name.</remarks> Public Function GetWeatherItem(ByVal cityName As String) As IPropertySet If Nothing Is m_table Then Return Nothing End If Dim rows As DataRow() = m_table.Select("CITYNAME = '" & cityName & "'") If rows.Length = 0 Then Return Nothing End If Dim zipCode As Long = Convert.ToInt64(rows(0)(1)) Return GetWeatherItem(zipCode) End Function ''' <summary> ''' This method searches for the record of the given zipcode and retunes the information as a PropertySet. ''' </summary> ''' <param name="zipCode"></param> ''' <returns>a PropertySet encapsulating the weather information for the given weather item.</returns> Public Function GetWeatherItem(ByVal zipCode As Long) As IPropertySet Dim r As DataRow = m_table.Rows.Find(zipCode) If Nothing Is r Then Return Nothing End If Dim propSet As IPropertySet = New PropertySetClass() propSet.SetProperty("ID", r(0)) propSet.SetProperty("ZIPCODE", r(1)) propSet.SetProperty("CITYNAME", r(2)) propSet.SetProperty("LAT", r(3)) propSet.SetProperty("LON", r(4)) propSet.SetProperty("TEMPERATURE", r(5)) propSet.SetProperty("CONDITION", r(6)) propSet.SetProperty("ICONNAME", r(7)) propSet.SetProperty("ICONID", r(8)) propSet.SetProperty("DAY", r(9)) propSet.SetProperty("DATE", r(10)) propSet.SetProperty("LOW", r(11)) propSet.SetProperty("HIGH", r(12)) propSet.SetProperty("UPDATEDATE", r(14)) Return propSet End Function ''' <summary> ''' get a list of all citynames currently in the dataset. ''' </summary> ''' <returns></returns> ''' <remarks>Please note that since the unique ID is zipCode, it is possible ''' to have a city name appearing more than once.</remarks> Public Function GetCityNames() As String() If Nothing Is m_table OrElse 0 = m_table.Rows.Count Then Return Nothing End If Dim cityNames As String() = New String(m_table.Rows.Count - 1){} Dim i As Integer=0 Do While i<m_table.Rows.Count 'column #2 stores the cityName cityNames(i) = Convert.ToString(m_table.Rows(i)(2)) i += 1 Loop Return cityNames End Function ''' <summary> ''' Zoom to a weather item according to its city name ''' </summary> ''' <param name="cityName"></param> Public Sub ZoomTo(ByVal cityName As String) If Nothing Is m_table Then Return End If Dim rows As DataRow() = m_table.Select("CITYNAME = '" & cityName & "'") If rows.Length = 0 Then Return End If Dim zipCode As Long = Convert.ToInt64(rows(0)(1)) ZoomTo(zipCode) End Sub ''' <summary> ''' Zoom to weather item according to its zipcode ''' </summary> ''' <param name="zipCode"></param> Public Sub ZoomTo(ByVal zipCode As Long) If Nothing Is m_table OrElse Nothing Is m_symbolTable Then Return End If If Nothing Is m_display Then Return End If 'get the record for the requested zipCode Dim r As DataRow = m_table.Rows.Find(zipCode) If Nothing Is r Then Return End If 'get the coordinate of the zipCode Dim lat As Double = Convert.ToDouble(r(3)) Dim lon As Double = Convert.ToDouble(r(4)) Dim point As IPoint = New PointClass() point.X = lon point.Y = lat point.SpatialReference = m_spatialRef If Not Nothing Is m_mapSpatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode Then point.Project(m_mapSpatialRef) End If Dim iconCode As Integer = Convert.ToInt32(r(8)) 'find the appropriate symbol record Dim rec As DataRow = m_symbolTable.Rows.Find(iconCode) If rec Is Nothing Then Return End If 'get the icon's dimensions Dim iconWidth As Integer = Convert.ToInt32(rec(3)) Dim iconHeight As Integer = Convert.ToInt32(rec(4)) Dim displayTransformation As IDisplayTransformation = (CType(m_display, IScreenDisplay)).DisplayTransformation 'Convert the icon coordinate into screen coordinate Dim windowX, windowY As Integer displayTransformation.FromMapPoint(point,windowX, windowY) 'get the upper left coord Dim ulx, uly As Integer ulx = Convert.ToInt32(windowX - iconWidth / 2) uly = Convert.ToInt32(windowY - iconHeight / 2) Dim ulPnt As IPoint = displayTransformation.ToMapPoint(ulx, uly) 'get the lower right coord Dim lrx, lry As Integer lrx = Convert.ToInt32(windowX + iconWidth / 2) lry = Convert.ToInt32(windowY + iconHeight / 2) Dim lrPnt As IPoint = displayTransformation.ToMapPoint(lrx, lry) 'construct the new extent Dim envelope As IEnvelope = New EnvelopeClass() envelope.PutCoords(ulPnt.X, lrPnt.Y, lrPnt.X, ulPnt.Y) envelope.Expand(2,2,False) 'set the new extent and refresh the display displayTransformation.VisibleBounds = envelope MyBase.m_bIsCompiledDirty = True CType(m_display, IScreenDisplay).Invalidate(Nothing, True, CShort(esriScreenCache.esriAllScreenCaches)) CType(m_display, IScreenDisplay).UpdateWindow() End Sub Private Sub SetSymbolSize(ByVal newSize As Integer) If newSize <= 0 Then MessageBox.Show("Size is not allowed.") Return End If m_symbolSize = newSize If Nothing Is m_symbolTable OrElse 0 = m_symbolTable.Rows.Count Then Return End If Dim pictureMarkerSymbol As IPictureMarkerSymbol = Nothing SyncLock m_symbolTable For Each r As DataRow In m_symbolTable.Rows pictureMarkerSymbol = TryCast(r(2), IPictureMarkerSymbol) If Nothing Is pictureMarkerSymbol Then Continue For End If pictureMarkerSymbol.Size = newSize r(2) = pictureMarkerSymbol r.AcceptChanges() Next r End SyncLock MyBase.m_bIsCompiledDirty = True CType(m_display, IScreenDisplay).Invalidate(Nothing, True, CShort(esriScreenCache.esriAllScreenCaches)) CType(m_display, IScreenDisplay).UpdateWindow() End Sub Public Property SymbolSize() As Integer Set SetSymbolSize(Value) End Set Get Return m_symbolSize End Get End Property #End Region #Region "private utility methods" ''' <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> ''' get the overall extent of the items in the layer ''' </summary> ''' <returns></returns> Private Function GetLayerExtent() As IEnvelope 'iterate through all the items in the layers DB and get the bounding envelope Dim env As IEnvelope = New EnvelopeClass() env.SpatialReference = m_spatialRef Dim point As IPoint = New PointClass() point.SpatialReference = m_spatialRef Dim symbolCode As Integer = 0 Dim newSymbolSize As Double = 0 For Each r As DataRow In m_table.Rows If TypeOf r(3) Is DBNull OrElse TypeOf r(4) Is DBNull Then Continue For End If point.Y = Convert.ToDouble(r(3)) point.X = Convert.ToDouble(r(4)) ' need to get the symbol size in meters in order to add it to the total layer extent If Not m_display Is Nothing Then symbolCode = Convert.ToInt32(r(8)) newSymbolSize = Math.Max(GetSymbolSize(m_display, symbolCode), newSymbolSize) End If env.Union(point.Envelope) Next r ' Expand the envelope in order to include the size of the symbol env.Expand(newSymbolSize, newSymbolSize, False) 'return the layer's extent in the data underlying coordinate system Return env End Function ''' <summary> ''' initialize the main table used by the layer 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_table = New DataTable("RECORDS") m_table.Columns.Add("ID", GetType(Long)) '0 m_table.Columns.Add("ZIPCODE", GetType(Long)) '1 m_table.Columns.Add("CITYNAME", GetType(String)) '2 m_table.Columns.Add("LAT", GetType(Double)) '3 m_table.Columns.Add("LON", GetType(Double)) '4 m_table.Columns.Add("TEMP", GetType(Integer)) '5 m_table.Columns.Add("CONDITION", GetType(String)) '6 m_table.Columns.Add("ICONNAME", GetType(String)) '7 m_table.Columns.Add("ICONID", GetType(Integer)) '8 m_table.Columns.Add("DAY", GetType(String)) '9 m_table.Columns.Add("DATE", GetType(String)) '10 m_table.Columns.Add("LOW", GetType(String)) '11 m_table.Columns.Add("HIGH", GetType(String)) '12 m_table.Columns.Add("SELECTED", GetType(Boolean)) '13 m_table.Columns.Add("UPDATEDATE", GetType(DateTime)) '14 'set the ID column to be auto increment m_table.Columns(0).AutoIncrement = True m_table.Columns(0).ReadOnly = True 'the zipCode column must be the unique and nut allow null m_table.Columns(1).Unique = True ' set the ZIPCODE primary key for the table m_table.PrimaryKey = New DataColumn() {m_table.Columns("ZIPCODE")} Else 'in case that the local cache exists, simply load the tables from the cache. Dim ds As DataSet = New DataSet() ds.ReadXml(path) m_table = ds.Tables("RECORDS") If Nothing Is m_table Then Throw New Exception("Cannot find 'RECORDS' table") End If If 15 <> m_table.Columns.Count Then Throw New Exception("Table 'RECORDS' does not have all required columns") End If m_table.Columns(0).ReadOnly = True ' set the ZIPCODE primary key for the table m_table.PrimaryKey = New DataColumn() {m_table.Columns("ZIPCODE")} 'synchronize the locations table For Each r As DataRow In m_table.Rows Try 'in case that the locations table does not exists, create and initialize it If Nothing Is m_locations 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 r 'dispose the DS ds.Tables.Remove(m_table) 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(ISymbol)) '2 m_symbolTable.Columns.Add("SYMBOLWIDTH", GetType(Integer)) '3 m_symbolTable.Columns.Add("SYMBOLHEIGHT", GetType(Integer)) '4 m_symbolTable.Columns.Add("DYNAMICGLYPH", GetType(IDynamicGlyph)) '5 m_symbolTable.Columns.Add("BITMAP", GetType(Bitmap)) '6 '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")} 'spawn a thread to populate the locations table Dim t As Thread = New Thread(AddressOf PopulateLocationsTableProc) t.Start() System.Threading.Thread.Sleep(1000) End Sub ''' <summary> ''' Load the information from the MajorCities featureclass to the locations table ''' </summary> Private Sub PopulateLocationsTableProc() 'get the ArcGIS path from the registry Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\ESRI\ArcObjectsSdk10.1") Dim path As String = Convert.ToString(key.GetValue("InstallDir")) If (Not System.IO.File.Exists(System.IO.Path.Combine(path, "Samples\Data\USZipCodeData\ZipCode_Boundaries_US_Major_Cities.shp"))) Then MessageBox.Show("Cannot find file ZipCode_Boundaries_US_Major_Cities.shp!") Return End If 'open the featureclass Dim wf As IWorkspaceFactory = New ShapefileWorkspaceFactoryClass() Dim ws As IWorkspace = wf.OpenFromFile(System.IO.Path.Combine(path, "Samples\Data\USZipCodeData"), 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 Do While Not Nothing Is feature Dim obj As Object = feature.Value(nameIndex) If obj Is Nothing Then Continue Do End If cityName = Convert.ToString(obj) obj = feature.Value(zipIndex) If obj Is Nothing Then Continue Do End If zip = Long.Parse(Convert.ToString(obj)) If zip <= 0 Then Continue Do End If 'add the current location to the location table Dim r As DataRow = m_locations.Rows.Find(zip) If Nothing Is r 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 Loop 'release the feature cursor Marshal.ReleaseComObject(fCursor) Catch ex As Exception System.Diagnostics.Trace.WriteLine(ex.Message) End Try End Sub ''' <summary> ''' Initialize the symbol that would use to highlight selected items ''' </summary> Private Sub InitializeSelectionSymbol() 'use a character marker symbol: Dim chMrkSym As ICharacterMarkerSymbol chMrkSym = New CharacterMarkerSymbolClass() 'Set the selection color (yellow) Dim color As IRgbColor color = New RgbColorClass() color.Red = 0 color.Green = 255 color.Blue = 255 'set the font Dim aFont As stdole.IFont aFont = New stdole.StdFontClass() aFont.Name = "ESRI Default Marker" aFont.Size = m_symbolSize aFont.Bold = True 'char #41 is just a rectangle chMrkSym.CharacterIndex = 41 chMrkSym.Color = TryCast(color, IColor) chMrkSym.Font = TryCast(aFont, stdole.IFontDisp) chMrkSym.Size = m_symbolSize m_selectionSymbol = TryCast(chMrkSym, ISymbol) End Sub ''' <summary> ''' run the thread that does the update of the weather data ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub OnUpdateTimer(ByVal sender As Object, ByVal e As ElapsedEventArgs) m_timer.Interval = 2700000 '(45 minutes) m_updateThread = New Thread(AddressOf ThreadProc) 'run the update thread m_updateThread.Start() End Sub ''' <summary> ''' the main update thread for the layer. ''' </summary> ''' <remarks>Since the layer gets the weather information 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 yahoo web site might 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 r 'serialize the tables onto the local machine Dim ds As DataSet = New DataSet() ds.Tables.Add(m_table) ds.WriteXml(System.IO.Path.Combine(m_dataFolder, "Weather.xml")) ds.Tables.Remove(m_table) ds.Dispose() GC.Collect() MyBase.m_bIsCompiledDirty = True 'fire an event to notify update of the weatheritems If Not OnWeatherItemsUpdatedEvent Is Nothing Then RaiseEvent OnWeatherItemsUpdated(Me, New EventArgs()) End If 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(ByVal iconPath As String, <System.Runtime.InteropServices.Out()> ByRef width As Integer, <System.Runtime.InteropServices.Out()> ByRef 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) & ".bmp") width = 0 height = 0 Dim bitmap As Bitmap = Nothing If (Not File.Exists(iconFileName)) Then Using webClient As System.Net.WebClient = 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.Bmp) 'get the bitmap's dimensions width = bitmap.Width height = bitmap.Height End Using End Using Else 'get the bitmap's dimensions bitmap = New Bitmap(iconFileName) width = bitmap.Width height = bitmap.Height 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(ByVal iconCode As Integer, ByVal dbr As DataRow) As ISymbol Dim symbol As ISymbol = Nothing Dim iconPath As String Dim iconWidth, 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 iconPath = Convert.ToString(dbr(7)) 'Initialize the picture marker symbol symbol = InitializeSymbol(iconPath, iconWidth, iconHeight, bitmap) If Nothing Is symbol Then Return Nothing End If 'update the symbol table SyncLock m_symbolTable r(2) = symbol r(3) = iconWidth r(4) = iconHeight r(6) = 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 iconPath = Convert.ToString(dbr(7)) 'Initialize the picture marker symbol symbol = InitializeSymbol(iconPath, iconWidth, iconHeight, bitmap) If Nothing Is symbol Then Return Nothing End If 'update the symbol table SyncLock m_symbolTable r(2) = symbol r(6) = 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), ISymbol) End If End If 'return the requested symbol Return symbol End Function Private Function GetDynamicGlyph(ByVal dynamicGlyphFactory As IDynamicGlyphFactory2, ByVal iconCode As Integer, ByVal dbr As DataRow, <System.Runtime.InteropServices.Out()> ByRef originalIconSize As Integer) As IDynamicGlyph originalIconSize = 0 If dynamicGlyphFactory Is Nothing Then Return Nothing End If Dim iconPath As String Dim iconWidth, iconHeight As Integer Dim bitmap As Bitmap = Nothing Dim dynamicGlyph As IDynamicGlyph = Nothing 'search for an existing symbol in the table Dim r As DataRow = m_symbolTable.Rows.Find(iconCode) If r Is Nothing Then iconPath = Convert.ToString(dbr(7)) bitmap = DownloadIcon(iconPath, iconWidth, iconHeight) If Not bitmap Is Nothing Then originalIconSize = iconWidth dynamicGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255)), IColor)) 'update the symbol table r = m_symbolTable.NewRow() SyncLock m_symbolTable r(1) = iconCode r(3) = iconWidth r(4) = iconHeight r(5) = dynamicGlyph r(6) = bitmap m_symbolTable.Rows.Add(r) End SyncLock End If Else If TypeOf r(5) Is DBNull Then If TypeOf r(6) Is DBNull Then iconPath = Convert.ToString(dbr(7)) bitmap = DownloadIcon(iconPath, iconWidth, iconHeight) If bitmap Is Nothing Then Return Nothing End If originalIconSize = iconWidth SyncLock m_symbolTable r(3) = iconWidth r(4) = iconHeight r(6) = bitmap End SyncLock Else originalIconSize = Convert.ToInt32(r(3)) bitmap = CType(r(6), Bitmap) End If dynamicGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255)), IColor)) SyncLock m_symbolTable r(5) = dynamicGlyph End SyncLock Else originalIconSize = Convert.ToInt32(r(3)) dynamicGlyph = CType(r(5), IDynamicGlyph) End If End If Return dynamicGlyph 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(ByVal iconPath As String, <System.Runtime.InteropServices.Out()> ByRef iconWidth As Integer, <System.Runtime.InteropServices.Out()> ByRef iconHeight As Integer, <System.Runtime.InteropServices.Out()> ByRef bitmap As Bitmap) As ISymbol iconWidth = 0 iconHeight = 0 bitmap = Nothing Try 'make sure that the icon exit on dist or else download it bitmap = DownloadIcon(iconPath, iconWidth, iconHeight) Dim iconFileName As String = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) & ".bmp") If (Not System.IO.File.Exists(iconFileName)) Then Return Nothing End If 'initialize the transparent color Dim rgbColor As IRgbColor = New RgbColorClass() rgbColor.Red = 255 rgbColor.Blue = 255 rgbColor.Green = 255 'instantiate the marker symbol and set its properties Dim pictureMarkerSymbol As IPictureMarkerSymbol = New PictureMarkerSymbolClass() pictureMarkerSymbol.CreateMarkerSymbolFromFile(ESRI.ArcGIS.Display.esriIPictureType.esriIPictureBitmap, iconFileName) pictureMarkerSymbol.Angle = 0 pictureMarkerSymbol.Size = m_symbolSize pictureMarkerSymbol.XOffset = 0 pictureMarkerSymbol.YOffset = 0 pictureMarkerSymbol.BitmapTransparencyColor = TryCast(rgbColor, IColor) 'return the symbol Return CType(pictureMarkerSymbol, ISymbol) Catch Return Nothing End Try End Function ''' <summary> ''' Makes a request against RSS Weather service and add update the layer's table ''' </summary> ''' <param name="zipCode"></param> ''' <param name="Lat"></param> ''' <param name="Lon"></param> Private Sub AddWeatherItem(ByVal zipCode As Long, ByVal Lat As Double, ByVal Lon As Double) Try Dim cityName As String Dim newLat, newLon 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_table.Rows.Find(zipCode) If Not dbr Is Nothing Then ' get the date Dim updateDate As DateTime = Convert.ToDateTime(dbr(14)) Dim ts As TimeSpan = DateTime.Now.Subtract(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 Nothing Is node Then Return End If 'get the cityname cityName = doc.DocumentElement.SelectSingleNode("/rss/channel/yweather:location/@city", xmlnsManager).InnerXml If Lat = 0.0 AndAlso Lon = 0.0 Then 'in case that the caller did not specify a coordinate, get the default coordinate from the service newLat = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:lat", xmlnsManager).InnerXml) newLon = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("/rss/channel/item/geo:long", xmlnsManager).InnerXml) Else newLat = Lat newLon = Lon 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 Nothing Is tr 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(6) = 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 layer's table. If Nothing Is dbr Then 'in case that the record does not exist 'create a new record dbr = m_table.NewRow() If (Not m_table.Columns(0).AutoIncrement) Then dbr(0) = Convert.ToInt32(DateTime.Now.Millisecond) End If 'add the item to the table SyncLock m_table dbr(1) = zipCode dbr(2) = cityName dbr(3) = newLat dbr(4) = newLon 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 m_table.Rows.Add(dbr) End SyncLock Else 'in case that the record exists, just update it 'update the record SyncLock m_table 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 MyBase.m_bIsCompiledDirty = True 'fire an event to notify the user that the item has been updated If Not OnWeatherItemAddedEvent Is Nothing Then Dim weatherItemEventArgs As WeatherItemEventArgs = New WeatherItemEventArgs(Convert.ToInt32(dbr(0)), zipCode, newLon, newLat, iconWidth, iconHeight) RaiseEvent OnWeatherItemAdded(Me, weatherItemEventArgs) End If Catch ex As Exception System.Diagnostics.Trace.WriteLine("AddWeatherItem: " & ex.Message) End Try End Sub #End Region #Region "IIdentify Members" ''' <summary> ''' Identifying all the weather items falling within the given envelope ''' </summary> ''' <param name="pGeom"></param> ''' <returns></returns> Public Function Identify(ByVal pGeom As IGeometry) As IArray Implements IIdentify.Identify Dim intersectEnv As IEnvelope = New EnvelopeClass() Dim inEnv As IEnvelope Dim array As IArray = New ArrayClass() 'get the envelope from the geometry If pGeom.GeometryType = esriGeometryType.esriGeometryEnvelope Then inEnv = pGeom.Envelope Else inEnv = TryCast(pGeom, IEnvelope) End If 'reproject the envelope to the source coordsys 'this would allow to search directly on the Lat/Lon columns If Not Nothing Is m_spatialRef AndAlso m_mapSpatialRef.FactoryCode <> m_layerSRFactoryCode AndAlso Not Nothing Is inEnv.SpatialReference Then inEnv.Project(MyBase.m_spatialRef) End If 'expand the envelope so that it'll cover the symbol inEnv.Expand(4,4,True) Dim xmin, ymin, xmax, ymax As Double inEnv.QueryCoords(xmin, ymin, xmax, ymax) 'select all the records within the given extent Dim qry As String = "LON >= " & xmin.ToString() & " AND LON <= " & xmax.ToString() & " AND Lat >= " & ymin.ToString() & " AND LAT <= " & ymax.ToString() Dim rows As DataRow() = m_table.Select(qry) If 0 = rows.Length Then Return array End If Dim zipCode As Long Dim propSet As IPropertySet = Nothing Dim idObj As IIdentifyObj = Nothing Dim idObject As IIdentifyObject = Nothing Dim bIdentify As Boolean = False For Each r As DataRow In rows 'get the zipCode zipCode = Convert.ToInt64(r("ZIPCODE")) 'get the properties of the given item in order to pass it to the identify object propSet = Me.GetWeatherItem(zipCode) If Not Nothing Is propSet Then 'instantiate the identify object and add it to the array idObj = New RSSWeatherIdentifyObject() 'test whether the layer can be identified bIdentify = idObj.CanIdentify(CType(Me, ILayer)) If bIdentify Then idObject = TryCast(idObj, IIdentifyObject) idObject.PropertySet = propSet array.Add(idObj) End If End If Next r 'return the array with the identify objects Return array End Function Private Function GetSymbolSize(ByVal display As IDisplay, ByVal symbolCode As Integer) As Double If display Is Nothing Then Return 0 End If Dim newSymbolSize As Double = 0 Dim symbolSizePixels As Double = 0 Dim r As DataRow = m_symbolTable.Rows.Find(symbolCode) If Not r Is Nothing Then symbolSizePixels = Convert.ToDouble(m_symbolSize) ' convert the symbol size from pixels to map units Dim transform As ITransformation = TryCast(display.DisplayTransformation, ITransformation) If transform Is Nothing Then Return 0 End If Dim symbolDimensions As Double() = New Double(1){} symbolDimensions(0) = CDbl(symbolSizePixels) symbolDimensions(1) = CDbl(symbolSizePixels) Dim symbolDimensionsMap As Double() = New Double(1){} transform.TransformMeasuresFF(esriTransformDirection.esriTransformReverse, 1, symbolDimensionsMap(0), symbolDimensions(0)) newSymbolSize = symbolDimensionsMap(0) End If Return newSymbolSize End Function #End Region End Class