About the RSS weather layer Sample
[C#]
RSSWeatherLayerClass.cs
using System;
using System.Collections;
using System.Data;
using System.Runtime.InteropServices;
using System.Xml;
using System.Threading;
using System.Timers;
using System.Text.RegularExpressions;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.ComponentModel;
using Microsoft.Win32;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.DataSourcesFile;
namespace RSSWeatherLayer
{
#region WeatherItemEventArgs class members
public sealed class WeatherItemEventArgs : EventArgs
{
private int m_id;
private long m_zipCode;
private double m_x;
private double m_y;
private int m_iconWidth;
private int m_iconHeight;
public WeatherItemEventArgs(int id, long zipCode, double X, double Y, int iconWidth, int iconHeight)
{
m_id = id;
m_zipCode = zipCode;
m_x = X;
m_y = Y;
m_iconWidth = iconWidth;
m_iconHeight = iconHeight;
}
public int ID
{
get { return m_id; }
}
public long ZipCode
{
get { return m_zipCode; }
}
public double mapY
{
get { return m_y; }
}
public double mapX
{
get { return m_x; }
}
public int IconWidth
{
get { return m_iconWidth; }
}
public int IconHeight
{
get { return m_iconHeight; }
}
}
#endregion
//declare delegates for the event handling
public delegate void WeatherItemAdded(object sender, WeatherItemEventArgs args);
public delegate void WeatherItemsUpdated(object sender, EventArgs args);
/// <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("RSSWeatherLayer.RSSWeatherLayerClass")]
public sealed class RSSWeatherLayerClass : BaseDynamicLayer, IIdentify
{
#region class members
private System.Timers.Timer m_timer = null;
private Thread m_updateThread = null;
private string m_iconFolder = string.Empty;
private DataTable m_table = null;
private DataTable m_symbolTable = null;
private DataTable m_locations = null;
private ISymbol m_selectionSymbol = null;
private IDisplay m_display = null;
private string m_dataFolder = string.Empty;
private int m_layerSRFactoryCode = 0;
private int m_symbolSize = 32;
private IPoint m_point = null;
private IPoint m_llPnt = null;
private IPoint m_urPnt = null;
private IEnvelope m_env = null;
// dynamic display members
private IDynamicGlyphFactory2 m_dynamicGlyphFactory = null;
private IDynamicSymbolProperties2 m_dynamicSymbolProperties = null;
private IDynamicCompoundMarker2 m_dynamicCompoundMarker = null;
private IDynamicGlyph m_textGlyph = null;
private IDynamicGlyph m_selectionGlyph = null;
private bool m_bDDOnce = true;
//weather items events
public event WeatherItemAdded OnWeatherItemAdded;
public event WeatherItemsUpdated OnWeatherItemsUpdated;
#endregion
#region Constructor
/// <summary>
/// The class has only default CTor.
/// </summary>
public RSSWeatherLayerClass() : base()
{
try
{
//set the layer's name
base.m_sName = "RSS Weather Layer";
//ask the Map to create a separate cache for the layer
base.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 (!System.IO.Directory.Exists(m_dataFolder))
{
System.IO.Directory.CreateDirectory(m_dataFolder);
}
m_iconFolder = m_dataFolder;
//instantiate the timer for the weather update
m_timer = new System.Timers.Timer(1000);
m_timer.Enabled = false;
m_timer.Elapsed += new ElapsedEventHandler(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 (null == m_locations)
InitializeLocations();
//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 (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
~RSSWeatherLayerClass()
{
Disconnect();
}
#endregion
#region Overriden 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 override void Draw(esriDrawPhase drawPhase, IDisplay Display, ITrackCancel trackCancel)
{
if(drawPhase != esriDrawPhase.esriDPGeography) return;
if(Display == null) return;
if(m_table == null || m_symbolTable == null) return;
m_display = Display;
IEnvelope envelope = Display.DisplayTransformation.FittedBounds as IEnvelope;
double lat, lon;
int iconCode;
bool selected;
ISymbol symbol = null;
//loop through the rows. Draw each row that has a shape
foreach (DataRow row 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 && lon <= envelope.XMax && lat >= envelope.YMin && lat <= envelope.YMax)
{
//search for the symbol in the symbology table
symbol = GetSymbol(iconCode, row);
if(null == symbol)
continue;
m_point.X = lon;
m_point.Y = lat;
m_point.SpatialReference = m_spatialRef;
//reproject the point to the DataFrame's spatial reference
if (null != m_mapSpatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode)
m_point.Project(m_mapSpatialRef);
Display.SetSymbol(symbol);
Display.DrawPoint(m_point);
if(selected)
{
Display.SetSymbol(m_selectionSymbol);
Display.DrawPoint(m_point);
}
}
}
}
/// <summary>
/// Draw the layer while in dynamic mode
/// </summary>
/// <param name="DynamicDrawPhase"></param>
/// <param name="Display"></param>
/// <param name="DynamicDisplay"></param>
public override void DrawDynamicLayer(esriDynamicDrawPhase DynamicDrawPhase, IDisplay Display, IDynamicDisplay DynamicDisplay)
{
if (DynamicDrawPhase != esriDynamicDrawPhase.esriDDPCompiled)
return;
if (!m_bValid || !m_visible)
return;
if (m_bDDOnce)
{
m_dynamicGlyphFactory = DynamicDisplay.DynamicGlyphFactory as IDynamicGlyphFactory2;
m_dynamicSymbolProperties = DynamicDisplay as IDynamicSymbolProperties2;
m_dynamicCompoundMarker = DynamicDisplay as IDynamicCompoundMarker2;
m_textGlyph = m_dynamicGlyphFactory.get_DynamicGlyph(1, esriDynamicGlyphType.esriDGlyphText, 1);
// create glyph for the selection symbol
if (m_selectionSymbol == null)
InitializeSelectionSymbol();
m_selectionGlyph = m_dynamicGlyphFactory.CreateDynamicGlyph(m_selectionSymbol);
m_bDDOnce = false;
}
m_display = Display;
double lat, lon;
int iconCode;
int iconWidth = 0;
bool selected;
IDynamicGlyph dynamicGlyph = null;
float symbolSized;
string citiName = string.Empty;
string temperature = string.Empty;
//loop through the rows. Draw each row that has a shape
foreach (DataRow row 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, out iconWidth);
if (null == dynamicGlyph)
continue;
m_point.X = lon;
m_point.Y = lat;
m_point.SpatialReference = m_spatialRef;
//reproject the point to the DataFrame's spatial reference
if (null != m_spatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode)
m_point.Project(m_mapSpatialRef);
symbolSized = 1.35f * (float)(m_symbolSize / (double)iconWidth);
// draw the weather item
// 1. set the whether symbol properties
m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, dynamicGlyph);
m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);
m_dynamicSymbolProperties.set_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.set_Smooth(esriDynamicSymbolType.esriDSymbolMarker, false);
// 2. set the text properties
m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);
m_dynamicSymbolProperties.set_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.set_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) // draw the selected symbol
{
m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0f, 1.0f, 1.0f, 1.0f);
m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_selectionGlyph);
DynamicDisplay.DrawMarker(m_point);
}
}
base.m_bIsCompiledDirty = false;
}
/// <summary>
/// The spatial reference of the underlying data.
/// </summary>
public override ISpatialReference SpatialReference
{
get
{
if(null == m_spatialRef)
{
m_spatialRef = CreateGeographicSpatialReference();
}
return m_spatialRef;
}
}
/// <summary>
/// The ID of the object.
/// </summary>
public override ESRI.ArcGIS.esriSystem.UID ID
{
get
{
UID uid = new UIDClass();
uid.Value = "RSSWeatherLayer.RSSWeatherLayerClass";
return uid;
}
}
/// <summary>
/// The default area of interest for the layer. Returns the spatial-referenced extent of the layer.
/// </summary>
public override IEnvelope AreaOfInterest
{
get
{
return this.Extent;
}
}
/// <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 override IEnvelope Extent
{
get
{
m_extent = GetLayerExtent();
if(null == m_extent)
return null;
IEnvelope env = ((IClone)m_extent).Clone() as IEnvelope;
if (null != m_mapSpatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode)
env.Project(m_mapSpatialRef);
return env;
}
}
/// <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 override string get_TipText(double X, double Y, double Tolerance)
{
IEnvelope envelope = new EnvelopeClass();
envelope.PutCoords(X - Tolerance, Y - Tolerance,X + Tolerance, Y + Tolerance);
//reproject the envelope to the datasource doordinate system
if (null != m_mapSpatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode)
{
envelope.SpatialReference = m_spatialRef;
envelope.Project(m_mapSpatialRef);
}
double xmin, ymin, xmax, ymax;
envelope.QueryCoords(out xmin, out ymin, out xmax, out ymax);
//select all the records within the given extent
string qry = "LON >= " + xmin.ToString() + " AND LON <= " + xmax.ToString() + " AND Lat >= " + ymin.ToString() + " AND LAT <= " + ymax.ToString();
DataRow[] rows = m_table.Select(qry);
if(0 == rows.Length)
return string.Empty;
DataRow r = rows[0];
string zipCode = Convert.ToString(r[1]);
string cityName = Convert.ToString(r[2]);
string temperature = Convert.ToString(r[5]);
return cityName + ", " + zipCode + ", " + temperature + "F";
}
#endregion
#region public methods
/// <summary>
/// connects to RSS weather service
/// </summary>
public void Connect()
{
//enable the update timer
m_timer.Enabled = true;
base.m_bIsCompiledDirty = true;
}
/// <summary>
/// disconnects from RSS weather service
/// </summary>
public void Disconnect()
{
//disable the update timer
m_timer.Enabled = false;
try
{
//abort the update thread in case that it is alive
if(m_updateThread.IsAlive)
m_updateThread.Abort();
}
catch
{
System.Diagnostics.Trace.WriteLine("WeatherLayer update thread has been terminated");
}
}
/// <summary>
/// select a weather item by its zipCode
/// </summary>
/// <param name="zipCode"></param>
/// <param name="newSelection"></param>
public void Select(long zipCode, bool newSelection)
{
if(null == m_table)
return;
if(newSelection)
{
UnselectAll();
}
DataRow[] rows = m_table.Select("ZIPCODE = " + zipCode.ToString());
if(rows.Length == 0)
return;
DataRow rec = rows[0];
lock(m_table)
{
//13 is the selection column ID
rec[13] = true;
rec.AcceptChanges();
}
base.m_bIsCompiledDirty = true;
}
/// <summary>
/// unselect all weather items
/// </summary>
public void UnselectAll()
{
if(null == m_table)
return;
//unselect all the currently selected items
lock(m_table)
{
foreach(DataRow r in m_table.Rows)
{
//13 is the selection column ID
r[13] = false;
}
m_table.AcceptChanges();
}
base.m_bIsCompiledDirty = true;
}
/// <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 void Refresh()
{
try
{
m_updateThread = new Thread(new ThreadStart(ThreadProc));
//run the update thread
m_updateThread.Start();
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <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 bool AddItem(long zipCode)
{
return AddItem(zipCode, 0.0 ,0.0);
}
/// <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 bool AddItem(long zipCode, double lat, double lon)
{
if(null == m_table)
return false;
DataRow r = m_table.Rows.Find(zipCode);
if(null != r) //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 && lon == 0.0)
return false;
else //update the location according to the new coordinate
{
lock(m_table)
{
r[3] = lat;
r[4] = lon;
r.AcceptChanges();
}
}
}
else
{
//add new zip code to the locations list
DataRow rec = m_locations.NewRow();
lock(m_locations)
{
rec[1] = zipCode;
m_locations.Rows.Add(rec);
}
//need to connect to the service and get the info
AddWeatherItem(zipCode, lat, lon);
}
return true;
}
/// <summary>
/// delete an item from the dataset
/// </summary>
/// <param name="zipCode"></param>
/// <returns></returns>
public bool DeleteItem(long zipCode)
{
if(null == m_table)
return false;
try
{
DataRow r = m_table.Rows.Find(zipCode);
if(null != r) //if the record with this zipCode already exists
{
lock(m_table)
{
r.Delete();
}
base.m_bIsCompiledDirty = true;
return true;
}
base.m_bIsCompiledDirty = true;
return false;
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
return false;
}
}
/// <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 IPropertySet GetWeatherItem(string cityName)
{
if(null == m_table)
return null;
DataRow[] rows = m_table.Select("CITYNAME = '" + cityName + "'");
if(rows.Length == 0)
return null;
long zipCode = Convert.ToInt64(rows[0][1]);
return GetWeatherItem(zipCode);
}
/// <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 IPropertySet GetWeatherItem(long zipCode)
{
DataRow r = m_table.Rows.Find(zipCode);
if(null == r)
return null;
IPropertySet propSet = 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;
}
/// <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 string[] GetCityNames()
{
if(null == m_table || 0 == m_table.Rows.Count)
return null;
string[] cityNames = new string[m_table.Rows.Count];
for(int i=0; i<m_table.Rows.Count; i++)
{
//column #2 stores the cityName
cityNames[i] = Convert.ToString(m_table.Rows[i][2]);
}
return cityNames;
}
/// <summary>
/// Zoom to a weather item according to its city name
/// </summary>
/// <param name="cityName"></param>
public void ZoomTo(string cityName)
{
if(null == m_table)
return;
DataRow[] rows = m_table.Select("CITYNAME = '" + cityName + "'");
if(rows.Length == 0)
return;
long zipCode = Convert.ToInt64(rows[0][1]);
ZoomTo(zipCode);
}
/// <summary>
/// Zoom to weather item according to its zipcode
/// </summary>
/// <param name="zipCode"></param>
public void ZoomTo(long zipCode)
{
if(null == m_table || null == m_symbolTable )
return;
if(null == m_display)
return;
//get the record for the requested zipCode
DataRow r = m_table.Rows.Find(zipCode);
if(null == r)
return;
//get the coordinate of the zipCode
double lat = Convert.ToDouble(r[3]);
double lon = Convert.ToDouble(r[4]);
IPoint point = new PointClass();
point.X = lon;
point.Y = lat;
point.SpatialReference = m_spatialRef;
if (null != m_mapSpatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode)
point.Project(m_mapSpatialRef);
int iconCode = Convert.ToInt32(r[8]);
//find the appropriate symbol record
DataRow rec = m_symbolTable.Rows.Find(iconCode);
if(rec == null)
return;
//get the icon's dimensions
int iconWidth = Convert.ToInt32(rec[3]);
int iconHeight = Convert.ToInt32(rec[4]);
IDisplayTransformation displayTransformation = ((IScreenDisplay)m_display).DisplayTransformation;
//Convert the icon coordinate into screen coordinate
int windowX, windowY;
displayTransformation.FromMapPoint(point,out windowX, out windowY);
//get the upper left coord
int ulx, uly;
ulx = windowX - iconWidth/2;
uly = windowY - iconHeight/2;
IPoint ulPnt = displayTransformation.ToMapPoint(ulx, uly);
//get the lower right coord
int lrx,lry;
lrx = windowX + iconWidth/2;
lry = windowY + iconHeight/2;
IPoint lrPnt = displayTransformation.ToMapPoint(lrx, lry);
//construct the new extent
IEnvelope envelope = 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;
base.m_bIsCompiledDirty = true;
((IScreenDisplay)m_display).Invalidate(null, true, (short)esriScreenCache.esriAllScreenCaches);
((IScreenDisplay)m_display).UpdateWindow();
}
private void SetSymbolSize(int newSize)
{
if (newSize <= 0)
{
MessageBox.Show("Size is not allowed.");
return;
}
m_symbolSize = newSize;
if (null == m_symbolTable || 0 == m_symbolTable.Rows.Count)
return;
IPictureMarkerSymbol pictureMarkerSymbol = null;
lock (m_symbolTable)
{
foreach (DataRow r in m_symbolTable.Rows)
{
pictureMarkerSymbol = r[2] as IPictureMarkerSymbol;
if (null == pictureMarkerSymbol)
continue;
pictureMarkerSymbol.Size = newSize;
r[2] = pictureMarkerSymbol;
r.AcceptChanges();
}
}
base.m_bIsCompiledDirty = true;
((IScreenDisplay)m_display).Invalidate(null, true, (short)esriScreenCache.esriAllScreenCaches);
((IScreenDisplay)m_display).UpdateWindow();
}
public int SymbolSize
{
set { SetSymbolSize(value); }
get { return m_symbolSize; }
}
#endregion
#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 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>
/// get the overall extent of the items in the layer
/// </summary>
/// <returns></returns>
private IEnvelope GetLayerExtent()
{
//iterate through all the items in the layers DB and get the bounding envelope
IEnvelope env = new EnvelopeClass();
env.SpatialReference = m_spatialRef;
IPoint point = new PointClass();
point.SpatialReference = m_spatialRef;
int symbolCode = 0;
double symbolSize = 0;
foreach(DataRow r in m_table.Rows)
{
if (r[3] is DBNull || r[4] is DBNull)
continue;
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 (m_display != null)
{
symbolCode = Convert.ToInt32(r[8]);
symbolSize = Math.Max(GetSymbolSize(m_display, symbolCode), symbolSize);
}
env.Union(point.Envelope);
}
// Expand the envelope in order to include the size of the symbol
env.Expand(symbolSize, symbolSize, false);
//return the layer's extent in the data underlying coordinate system
return env;
}
/// <summary>
/// initialize the main table used by the layer as well as the symbols table.
/// The base class calles 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_table = new DataTable("RECORDS");
m_table.Columns.Add( "ID", typeof(long)); //0
m_table.Columns.Add( "ZIPCODE", typeof(long)); //1
m_table.Columns.Add( "CITYNAME", typeof(string)); //2
m_table.Columns.Add( "LAT", typeof(double)); //3
m_table.Columns.Add( "LON", typeof(double)); //4
m_table.Columns.Add( "TEMP", typeof(int)); //5
m_table.Columns.Add( "CONDITION", typeof(string)); //6
m_table.Columns.Add( "ICONNAME", typeof(string)); //7
m_table.Columns.Add( "ICONID", typeof(int)); //8
m_table.Columns.Add( "DAY", typeof(string)); //9
m_table.Columns.Add( "DATE", typeof(string)); //10
m_table.Columns.Add( "LOW", typeof(string)); //11
m_table.Columns.Add( "HIGH", typeof(string)); //12
m_table.Columns.Add( " SELECTED", typeof(bool)); //13
m_table.Columns.Add( "UPDATEDATE", typeof(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.
{
DataSet ds = new DataSet();
ds.ReadXml(path);
m_table = ds.Tables["RECORDS"];
if (null == m_table)
throw new Exception("Cannot find 'RECORDS' table");
if (15 != m_table.Columns.Count)
throw new Exception("Table 'RECORDS' does not have all required columns");
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
foreach(DataRow r in m_table.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);
}
}
//displose the DS
ds.Tables.Remove(m_table);
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(ISymbol)); //2
m_symbolTable.Columns.Add( "SYMBOLWIDTH", typeof(int)); //3
m_symbolTable.Columns.Add( "SYMBOLHEIGHT", typeof(int)); //4
m_symbolTable.Columns.Add( "DYNAMICGLYPH", typeof(IDynamicGlyph)); //5
m_symbolTable.Columns.Add( "BITMAP", typeof(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"]};
}
/// <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"]};
//spawn a thread to populate the locations table
Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc));
t.Start();
System.Threading.Thread.Sleep(1000);
}
/// <summary>
/// Load the information from the MajorCities featureclass to the locations table
/// </summary>
private void PopulateLocationsTableProc()
{
//get the ArcGIS path from the registry
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ESRI\ArcObjectsSdk10.1");
string path = Convert.ToString(key.GetValue("InstallDir"));
if (!System.IO.File.Exists(System.IO.Path.Combine(path, @"Samples\Data\USZipCodeData\ZipCode_Boundaries_US_Major_Cities.shp")))
{
MessageBox.Show("Cannot find file ZipCode_Boundaries_US_Major_Cities.shp!");
return;
}
//open the featureclass
IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass() as IWorkspaceFactory;
IWorkspace ws = wf.OpenFromFile(System.IO.Path.Combine(path, @"Samples\Data\USZipCodeData"), 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>
/// Initialize the symbol that would use to highlight selected items
/// </summary>
private void InitializeSelectionSymbol()
{
//use a character marker symbol:
ICharacterMarkerSymbol chMrkSym;
chMrkSym = new CharacterMarkerSymbolClass();
//Set the selection color (yellow)
IRgbColor color;
color = new RgbColorClass();
color.Red = 0;
color.Green = 255;
color.Blue = 255;
//set the font
stdole.IFont aFont;
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 = color as IColor;
chMrkSym.Font = aFont as stdole.IFontDisp;
chMrkSym.Size = m_symbolSize;
m_selectionSymbol = chMrkSym as ISymbol;
}
/// <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 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 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 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);
}
//serialize the tables onto the local machine
DataSet ds = 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();
base.m_bIsCompiledDirty = true;
//fire an event to notify update of the weatheritems
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) + ".bmp");
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.Bmp);
//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 ISymbol GetSymbol(int iconCode, DataRow dbr)
{
ISymbol symbol = null;
string iconPath;
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;
iconPath = Convert.ToString(dbr[7]);
//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[6] = 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
{
iconPath = Convert.ToString(dbr[7]);
//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[6] = bitmap;
r.AcceptChanges();
}
}
else //the record exists in the table and the symbol has been initialized
//get the symbol
symbol = r[2] as ISymbol;
}
//return the requested symbol
return symbol;
}
private IDynamicGlyph GetDynamicGlyph(IDynamicGlyphFactory2 dynamicGlyphFactory, int iconCode, DataRow dbr, out int originalIconSize)
{
originalIconSize = 0;
if (dynamicGlyphFactory == null)
return null;
string iconPath;
int iconWidth, iconHeight;
Bitmap bitmap = null;
IDynamicGlyph dynamicGlyph = null;
//search for an existing symbol in the table
DataRow r = m_symbolTable.Rows.Find(iconCode);
if (r == null)
{
iconPath = Convert.ToString(dbr[7]);
bitmap = DownloadIcon(iconPath, out iconWidth, out iconHeight);
if (bitmap != null)
{
originalIconSize = iconWidth;
dynamicGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker,
bitmap.GetHbitmap().ToInt32(),
false,
(IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255))
);
//update the symbol table
r = m_symbolTable.NewRow();
lock (m_symbolTable)
{
r[1] = iconCode;
r[3] = iconWidth;
r[4] = iconHeight;
r[5] = dynamicGlyph;
r[6] = bitmap;
m_symbolTable.Rows.Add(r);
}
}
}
else
{
if (r[5] is DBNull)
{
if (r[6] is DBNull)
{
iconPath = Convert.ToString(dbr[7]);
bitmap = DownloadIcon(iconPath, out iconWidth, out iconHeight);
if (bitmap == null)
return null;
originalIconSize = iconWidth;
lock (m_symbolTable)
{
r[3] = iconWidth;
r[4] = iconHeight;
r[6] = bitmap;
}
}
else
{
originalIconSize = Convert.ToInt32(r[3]);
bitmap = (Bitmap)r[6];
}
dynamicGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker,
bitmap.GetHbitmap().ToInt32(),
false,
(IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255))
);
lock (m_symbolTable)
{
r[5] = dynamicGlyph;
}
}
else
{
originalIconSize = Convert.ToInt32(r[3]);
dynamicGlyph = (IDynamicGlyph)r[5];
}
}
return dynamicGlyph;
}
/// <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 ISymbol 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
bitmap = DownloadIcon(iconPath, out iconWidth, out iconHeight);
string iconFileName = System.IO.Path.Combine(m_iconFolder, System.IO.Path.GetFileNameWithoutExtension(iconPath) + ".bmp");
if(!System.IO.File.Exists(iconFileName))
return null;
//initialize the transparent color
IRgbColor rgbColor = new RgbColorClass();
rgbColor.Red = 255;
rgbColor.Blue = 255;
rgbColor.Green = 255;
//instantiate the marker symbol and set its properties
IPictureMarkerSymbol pictureMarkerSymbol = 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 = rgbColor as IColor;
//return the symbol
return (ISymbol)pictureMarkerSymbol;
}
catch
{
return null;
}
}
/// <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 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_table.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 cityname
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[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.
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 layer's table.
if(null == dbr) //in case that the record does not exist
{
//create a new record
dbr = m_table.NewRow();
if (!m_table.Columns[0].AutoIncrement)
dbr[0] = Convert.ToInt32(DateTime.Now.Millisecond);
//add the item to the table
lock (m_table)
{
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;
m_table.Rows.Add(dbr);
}
}
else //in case that the record exists, just update it
{
//update the record
lock (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();
}
}
base.m_bIsCompiledDirty = true;
//fire an event to notify the user that the item has been updated
if (OnWeatherItemAdded != null)
{
WeatherItemEventArgs weatherItemEventArgs = new WeatherItemEventArgs(Convert.ToInt32(dbr[0]), zipCode, lon, lat, iconWidth, iconHeight);
OnWeatherItemAdded(this, weatherItemEventArgs);
}
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine("AddWeatherItem: " + ex.Message);
}
}
#endregion
#region IIdentify Members
/// <summary>
/// Identifying all the weather items falling within the given envelope
/// </summary>
/// <param name="pGeom"></param>
/// <returns></returns>
public IArray Identify(IGeometry pGeom)
{
IEnvelope intersectEnv = new EnvelopeClass();
IEnvelope inEnv;
IArray array = new ArrayClass();
//get the envelope from the geometry
if(pGeom.GeometryType == esriGeometryType.esriGeometryEnvelope)
inEnv = pGeom.Envelope;
else
inEnv = pGeom as IEnvelope;
//reproject the envelope to the source coordsys
//this would allow to search directly on the Lat/Lon columns
if (null != m_spatialRef && m_mapSpatialRef.FactoryCode != m_layerSRFactoryCode && null != inEnv.SpatialReference)
inEnv.Project(base.m_spatialRef);
//expand the envelope so that it'll cover the symbol
inEnv.Expand(4,4,true);
double xmin, ymin, xmax, ymax;
inEnv.QueryCoords(out xmin, out ymin, out xmax, out ymax);
//select all the records within the given extent
string qry = "LON >= " + xmin.ToString() + " AND LON <= " + xmax.ToString() + " AND Lat >= " + ymin.ToString() + " AND LAT <= " + ymax.ToString();
DataRow[] rows = m_table.Select(qry);
if(0 == rows.Length)
return array;
long zipCode;
IPropertySet propSet = null;
IIdentifyObj idObj = null;
IIdentifyObject idObject = null;
bool bIdentify = false;
foreach(DataRow r 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 = this.GetWeatherItem(zipCode);
if(null != propSet)
{
//instantiate the identify object and add it to the array
idObj = new RSSWeatherIdentifyObject();
//test whether the layer can be identified
bIdentify = idObj.CanIdentify((ILayer)this);
if(bIdentify)
{
idObject = idObj as IIdentifyObject;
idObject.PropertySet = propSet;
array.Add(idObj);
}
}
}
//return the array with the identify objects
return array;
}
private double GetSymbolSize(IDisplay display, int symbolCode)
{
if (display == null)
return 0;
double symbolSize = 0;
double symbolSizePixels = 0;
DataRow r = m_symbolTable.Rows.Find(symbolCode);
if (r != null)
{
symbolSizePixels = Convert.ToDouble(m_symbolSize);
// convert the symbol size from pixels to map units
ITransformation transform = display.DisplayTransformation as ITransformation;
if (transform == null)
return 0;
double[] symbolDimensions = new double[2];
symbolDimensions[0] = (double)symbolSizePixels;
symbolDimensions[1] = (double)symbolSizePixels;
double[] symbolDimensionsMap = new double[2];
transform.TransformMeasuresFF(esriTransformDirection.esriTransformReverse, 1, ref symbolDimensionsMap[0], ref symbolDimensions[0]);
symbolSize = symbolDimensionsMap[0];
}
return symbolSize;
}
#endregion
}
}
[Visual Basic .NET]
RSSWeatherLayerClass.vb
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