RSS Weather 3D Layer
RSSWeatherLayer3DClass.cs
// Copyright 2006 ESRI
//
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
//
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
//
// See the use restrictions.
//
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Timers;
using System.Threading;
using System.Xml;
using System.Text.RegularExpressions;
using System.IO;
using Microsoft.Win32;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.GlobeCore;
using ESRI.ArcGIS.Analyst3D;
using ESRI.ArcGIS.DataSourcesFile;
using OpenGL;
namespace RSSWeatherLayer3D
{
/// <summary>
/// RSSWeatherLayer3D is a custom layer for GlobeControl/ArcGlobe. It inherits GlobeCustomLayerBase
/// which implements the relevant interfaces required by globe.
/// 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 3D environment using direct OpenGL plug-in.
/// In this sample you can find implementation of simple editing capabilities, selection by
/// attribute and by location, persistence and identify.
/// </summary>
[Guid("C3E81E34-7421-4bce-86D6-4DB58813A1B4")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[ProgId("RSSWeatherLayer3D.RSSWeatherLayer3DClass")]
public sealed class RSSWeatherLayer3DClass : GlobeCustomLayerBase, IIdentify
{
#region class members
private IVector3D m_vector3D = null;
private IGlobeDisplay m_globeDisplay = null;
private IGlobeViewUtil m_globeViewUtil = null;
private bool m_bDisplayListCreated = false;
private long m_lItemToFlash = -1L;
private DataTable m_TextureMap = null;
private DataTable m_locations = null;
private System.Timers.Timer m_timer = null;
private System.Timers.Timer m_redrawTimer = null;
private Thread m_updateThread = null;
private uint m_billboardRectList = 0;
private uint m_selectionDisplayList = 0;
private double[] m_modelViewMatrix = null;
private double[] m_billboardMatrix = null;
private double[] m_projMatrix = null;
private int[] m_viewport = null;
private static int m_flashCount = 0;
private static bool m_flashDraw = true;
private string m_iconFolder = string.Empty;
private string m_weatherXmlFile = string.Empty;
private bool m_bTimerIsRunning = false;
//redraw delegate, invokes the timer's event handler on the main thread
private delegate void RedrawEventHandler();
#endregion
#region Ctor
/// <summary>
/// The class has only default CTor.
/// </summary>
public RSSWeatherLayer3DClass() : base()
{
m_sName = "RSSWeatherLayer3D";
m_vector3D = new Vector3DClass();
//set the bitmaps for the LayerInfo
base.m_smallBitmap = new System.Drawing.Bitmap(GetType().Assembly.GetManifestResourceStream(GetType(), "Bitmaps.JaneSmall.bmp"));
if (base.m_smallBitmap != null)
{
base.m_smallBitmap.MakeTransparent(base.m_smallBitmap.GetPixel(1, 1));
base.m_hSmallBitmap = m_smallBitmap.GetHbitmap();
}
base.m_largeBitmap = new System.Drawing.Bitmap(GetType().Assembly.GetManifestResourceStream(GetType(), "Bitmaps.JaneLarge.bmp"));
if (base.m_largeBitmap != null)
{
base.m_largeBitmap.MakeTransparent(base.m_largeBitmap.GetPixel(1, 1));
base.m_hLargeBitmap = m_largeBitmap.GetHbitmap();
}
//get the directory for the layer's cache. If it does not exist, create it.
string dir = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "WeatherIcons");
if (!System.IO.Directory.Exists(dir))
{
System.IO.Directory.CreateDirectory(dir);
}
m_iconFolder = dir;
//The cached data of the layer gets stored as XML
m_weatherXmlFile = System.IO.Path.Combine(m_iconFolder, "Weather.xml");
//create the spatial reference for the layer (in this case WGS1984)
m_spRef = CreateGeographicSpatialReference();
//initialize the layer's tables (main table as well as the textures table)
InitializeTables();
//get the location list from a featureclass (US major cities) and synchronize it with the
//cached information should it exists.
if (null == m_locations)
InitializeLocations();
//instantiate the timer for the weather update (to periodically update the
//information against the RSS weather service)
m_timer = new System.Timers.Timer(1000);
m_timer.Enabled = false;
m_timer.Elapsed += new ElapsedEventHandler(OnUpdateTimer);
//initialize the redraw timer
m_redrawTimer = new System.Timers.Timer(200);
m_redrawTimer.Enabled = false;
m_redrawTimer.Elapsed += new ElapsedEventHandler(OnRedrawUpdateTimer);
//initialize the OpenGL matrices
m_modelViewMatrix = new double[16];
m_billboardMatrix = new double[16];
m_projMatrix = new double[16];
m_viewport = new int[4];
}
#endregion
#region overriden methods
/// <summary>
/// This is where the actuall drawing takes place.
/// </summary>
/// <param name="pGlobeViewer"></param>
public override void DrawImmediate(IGlobeViewer pGlobeViewer)
{
//make sure that the layer is valid, visible and that the main table exists
if (!m_bVisible || !m_bValid | null == m_table)
return;
//get the OpenGL rendering mode
uint mode;
unsafe
{
int m;
GL.glGetIntegerv(GL.GL_RENDER_MODE, &m);
mode = (uint)m;
//GL.glGetIntegerv(GL.GL_RENDER_MODE, out mode);
}
//get the OpenGL matrices (required for the viewport filtering and the billboard orientation)
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX, m_modelViewMatrix);
GL.glGetIntegerv(GL.GL_VIEWPORT, m_viewport);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, m_projMatrix);
//populate the billboard matrix
m_billboardMatrix[0] = m_modelViewMatrix[0];
m_billboardMatrix[1] = m_modelViewMatrix[4];
m_billboardMatrix[2] = m_modelViewMatrix[8];
m_billboardMatrix[3] = 0;
m_billboardMatrix[4] = m_modelViewMatrix[1];
m_billboardMatrix[5] = m_modelViewMatrix[5];
m_billboardMatrix[6] = m_modelViewMatrix[9];
m_billboardMatrix[7] = 0;
m_billboardMatrix[8] = m_modelViewMatrix[2];
m_billboardMatrix[9] = m_modelViewMatrix[6];
m_billboardMatrix[10] = m_modelViewMatrix[10];
m_billboardMatrix[11] = 0;
m_billboardMatrix[12] = 0;
m_billboardMatrix[13] = 0;
m_billboardMatrix[14] = 0;
m_billboardMatrix[15] = 1;
ISceneViewer sceneViewer = pGlobeViewer.GlobeDisplay.ActiveViewer;
//only once, create display lists and do initializations
if (!m_bDisplayListCreated)
{
CreateDisplayLists();
m_globeDisplay = pGlobeViewer.GlobeDisplay;
}
//get the globeViewUtil which allow to convert between the different globe coordinate systems
m_globeViewUtil = sceneViewer.Camera as IGlobeViewUtil;
IGlobeViewUtil globeViewUtil = sceneViewer.Camera as IGlobeViewUtil;
IGlobeAdvancedOptions advOpt = m_globeDisplay.AdvancedOptions;
//the ClipNear value is required for the viewport filtering (since we dont
//want to draw an item which is beyound the clipping planes).
double clipNear = advOpt.ClipNear;
double dblObsX, dblObsY, dblObsZ, dMagnitude;
ICamera camera = pGlobeViewer.GlobeDisplay.ActiveViewer.Camera;
//query the camera locationin geocentric coordinate (OpenGL coordsystem)
camera.Observer.QueryCoords(out dblObsX, out dblObsY);
dblObsZ = camera.Observer.Z;
double lat, lon, X = 0.0, Y = 0.0, Z = 0.0;
//iterate through all the records of the layer, test whether the item is within the
//viewport are and draw it onto the globe.
foreach (DataRow rec in m_table.Rows)
{
lat = Convert.ToDouble(rec[3]);
lon = Convert.ToDouble(rec[4]);
X = Convert.ToDouble(rec[5]);
Y = Convert.ToDouble(rec[6]);
Z = Convert.ToDouble(rec[7]);
#region get the OGL geocentric coordinates X,Y,Z
if (X == 0.0 && Y == 0.0 && Z == 0.0)
{
//calculate the geocentric coordinates
globeViewUtil.GeographicToGeocentric(lon, lat, 1000.0, out X, out Y, out Z);
//write the calculated geocentric coordinate to the table
lock (m_table)
{
rec[5] = X;
rec[6] = Y;
rec[7] = Z;
rec.AcceptChanges();
}
}
#endregion
//make sure that the item is inside the viewport, otherwise no need to draw it
if (!InsideViewport(X, Y, Z, clipNear, mode))
continue;
//get the distance from the camera to the drawn item.
//This distance will determine whteher to draw the item as a dot or as
//a full icon.
m_vector3D.SetComponents(dblObsX - X, dblObsY - Y, dblObsZ - Z);
dMagnitude = m_vector3D.Magnitude;
//call the drawing method
DrawItem(rec, dMagnitude);
}
}
//since this is Globe where the default coord-sys is WGS1984, there is no need to
//reproject the underlying data (which is already WGS1984)
public override ISpatialReference SpatialReference
{
get
{
if (null == m_spRef )
{
m_spRef = CreateGeographicSpatialReference();
}
return m_spRef ;
}
}
public override IEnvelope AreaOfInterest
{
get
{
return this.Extent;
}
}
public override IEnvelope Extent
{
get
{
//iterate throu all the items in the layer and get the overall extent.
m_extent = GetLayerExtent();
if (null == m_extent )
return null;
//return a copy of the extent (in case that the caller is on another thread)
IEnvelope env = ((IClone)m_extent ).Clone() as IEnvelope;
return env;
}
}
//the ProgID of the layer
public override UID ID
{
get
{
ESRI.ArcGIS.esriSystem.UID uid = new ESRI.ArcGIS.esriSystem.UIDClass();
uid.Value = "RSSWeatherLayer3D.RSSWeatherLayer3DClass";
return uid;
}
}
//the guid of the layer
public override void GetClassID(out Guid pClassID)
{
ESRI.ArcGIS.esriSystem.UID uid = new ESRI.ArcGIS.esriSystem.UIDClass();
uid.Value = "RSSWeatherLayer3D.RSSWeatherLayer3DClass";
pClassID = new Guid(Convert.ToString(uid.Value));
}
public override void Load(IStream pstm)
{
//allocate a new vector 3D, no need to mess around with writing and reading...
m_vector3D = new Vector3DClass();
base.Load(pstm);
}
/// <summary>
/// Hit method is used to get items by interacting with the display (such as select by area).
/// The mechanizm used in here is OpenGL selection buffer.
/// Each object is assigned with a unique id (zipCode) and is loaded to the selection buffer
/// 'glLoadName'. The globe framework will return this ID and we will use it in order to
/// select the items from the table.
/// </summary>
/// <param name="hitObjectID"></param>
/// <param name="pHit3D"></param>
public override void Hit(int hitObjectID, ESRI.ArcGIS.Analyst3D.IHit3D pHit3D)
{
try
{
object owner = pHit3D.Owner;
//make sure that the owner is a weather layer
if (!(owner is RSSWeatherLayer3DClass))
return;
//get the record by the zip code received from the selection buffer
DataRow[] rows = m_table.Select("ZIPCODE = " + hitObjectID.ToString());
if (rows.Length == 0)
return;
//serialize the information into a propertySet.
DataRow r = rows[0];
IPropertySet propSet = new PropertySetClass();
propSet.SetProperty("ZIPCODE", r[1]);
propSet.SetProperty("CITYNAME", r[2]);
propSet.SetProperty("LATITUDE", r[3]);
propSet.SetProperty("LONGITUDE", r[4]);
propSet.SetProperty("TEMPERATURE", r[8]);
propSet.SetProperty("DESCRIPTION", r[9]);
propSet.SetProperty("DAY", r[11]);
propSet.SetProperty("DATE", r[12]);
propSet.SetProperty("LOW", r[13]);
propSet.SetProperty("HIGH", r[14]);
propSet.SetProperty("UPDATED", r[16]);
IPoint hitPoint = pHit3D.Point;
if (null == hitPoint)
{
double lat, lon, alt;
lat = Convert.ToDouble(r[3]);
lon = Convert.ToDouble(r[4]);
alt = 1000.0;
IPoint point = new PointClass();
((IZAware)point).ZAware = true;
point.PutCoords(lon, lat);
point.Z = alt;
pHit3D.Point = point;
}
string shortIconName = Convert.ToString(r[10]);
shortIconName = shortIconName.Substring(shortIconName.LastIndexOf("/") + 1);
propSet.SetProperty("ICON", shortIconName);
//pass the propertySet to the caller.
pHit3D.Object = propSet;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("Hit: " + ex.Message);
}
}
//override the OnHandleDestroyed in order to prevent from calling Invoke after the handle
//has been destroyed
protected override void OnHandleDestroyed(EventArgs e)
{
m_bTimerIsRunning = false;
base.OnHandleDestroyed(e);
}
#endregion
#region private utility methods
/// <summary>
/// create a WGS1984 geographic coordinate system.
/// In this case, the underlying data is in WGS1984 as well as the input required by the
/// globe. for that reason, there is no need to reproject the data.
/// </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()
{
if (null == base.m_spRef)
{
base.m_spRef = CreateGeographicSpatialReference();
}
IEnvelope env = new EnvelopeClass();
env.SpatialReference = base.m_spRef;
IPoint point = new PointClass();
point.SpatialReference = m_spRef;
foreach (DataRow r in m_table.Rows)
{
point.Y = Convert.ToDouble(r[3]);
point.X = Convert.ToDouble(r[4]);
env.Union(point.Envelope);
}
return env;
}
/// <summary>
/// initialize the main table used by the layer as well as the texture table.
/// The base class calles new on the table and adds a default ID field.
/// </summary>
private void InitializeTables()
{
//In case that there is no existing cache on the local machine, create the table.
if (!System.IO.File.Exists(m_weatherXmlFile))
{
//add fields to the table
m_table.Columns.Add("ZIPCODE", typeof(long));
m_table.Columns.Add("CITYNAME", typeof(string));
m_table.Columns.Add("LAT", typeof(double)); //3
m_table.Columns.Add("LON", typeof(double));
m_table.Columns.Add("X", typeof(double));
m_table.Columns.Add("Y", typeof(double));
m_table.Columns.Add("Z", typeof(double)); //7
m_table.Columns.Add("TEMP", typeof(int));
m_table.Columns.Add("CONDITION", typeof(string));
m_table.Columns.Add("ICONNAME", typeof(string)); //10
m_table.Columns.Add("DAY", typeof(string));
m_table.Columns.Add("DATE", typeof(string));
m_table.Columns.Add("LOW", typeof(string));
m_table.Columns.Add("HIGH", typeof(string));
m_table.Columns.Add("SELECTED", typeof(bool)); //15
m_table.Columns.Add("UPDATEDATE", typeof(DateTime)); //16
m_table.Columns.Add("ICONID", typeof(int)); //17
//make sure to autoincrement the ID column
m_table.Columns[0].AutoIncrement = true;
m_table.Columns[0].ReadOnly = true;
//ZipCode columns must not be null
m_table.Columns[1].AllowDBNull = false;
// set the ZIPCODE primary key for the table
m_table.PrimaryKey = new DataColumn[] { m_table.Columns["ZIPCODE"] };
//initialize the texture map table
m_TextureMap = new DataTable("TextureMap");
//add fields to the table
m_TextureMap.Columns.Add("ID", typeof(int));
m_TextureMap.Columns.Add("ICONNAME", typeof(string));
m_TextureMap.Columns.Add("TEXTUREID", typeof(int));
m_TextureMap.Columns.Add("ICONID", typeof(int));
//make sure to autoincrement the ID column
m_TextureMap.Columns[0].AutoIncrement = true;
m_TextureMap.Columns[0].ReadOnly = true;
//TextureID columns must not be null
m_TextureMap.Columns[1].AllowDBNull = false;
//set ICONID as the primary key for the table.
//searching for a texture by its ID is most efficiant.
m_TextureMap.PrimaryKey = new DataColumn[] { m_TextureMap.Columns["ICONID"] };
}
else //in case that the local cache exists, simply load the tables from the cache.
{
DataSet ds = new DataSet();
ds.ReadXml(m_weatherXmlFile);
m_table = ds.Tables["RECORDS"];
m_TextureMap = ds.Tables["TextureMap"];
// set the ZIPCODE primary key for the table
m_table.PrimaryKey = new DataColumn[] { m_table.Columns["ZIPCODE"] };
//set ICONID as the primary key for the table
m_TextureMap.PrimaryKey = new DataColumn[] { m_TextureMap.Columns["ICONID"] };
//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) //in case that the record does not exists
{
DataRow rec = m_locations.NewRow();
rec[1] = Convert.ToString(r[1]);
rec[2] = Convert.ToInt64(r[2]);
//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.Tables.Remove(m_TextureMap);
ds.Dispose();
GC.Collect();
}
}
/// <summary>
/// Initialize the location table. Gets the location from a featureclass
/// </summary>
private void InitializeLocations()
{
//ceate 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 background thread to populate the locations table
Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc));
t.Start();
System.Threading.Thread.Sleep(200);
}
/// <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\ArcGIS");
string path = Convert.ToString(key.GetValue("InstallDir"));
//set the path to the featureclass used by the GPS simulator
path = System.IO.Path.Combine(path, @"DeveloperKit\SamplesNET\data\USZipCodeData");
//open the featureclass
IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass() as IWorkspaceFactory;
IWorkspace ws = wf.OpenFromFile(path, 0);
IFeatureWorkspace fw = ws as IFeatureWorkspace;
IFeatureClass featureClass = fw.OpenFeatureClass("ZipCode_Boundaries_US_Major_Cities");
//map the name and zip fields
int zipIndex = featureClass.FindField("ZIP");
int nameIndex = featureClass.FindField("NAME");
string cityName;
long zip;
try
{
//iterate through the features and add the information to the table
IFeatureCursor fCursor = null;
fCursor = featureClass.Search(null, true);
IFeature feature = fCursor.NextFeature();
int index = 0;
while (null != feature)
{
object obj = feature.get_Value(nameIndex);
if (obj == null)
continue;
cityName = Convert.ToString(obj);
obj = feature.get_Value(zipIndex);
if (obj == null)
continue;
zip = long.Parse(Convert.ToString(obj));
if (zip <= 0)
continue;
//add the current location to the location table
DataRow r = m_locations.Rows.Find(zip);
if (null == r)
{
r = m_locations.NewRow();
r[1] = zip;
r[2] = cityName;
lock (m_locations)
{
m_locations.Rows.Add(r);
}
}
feature = fCursor.NextFeature();
index++;
}
//release the feature cursor
Marshal.ReleaseComObject(fCursor);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// the main update thread for the layer.
/// </summary>
/// <remarks>Sine 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 hand whie waiting
/// for respond. Therefor, 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 on the website.
foreach (DataRow r in m_locations.Rows)
{
System.Threading.Thread.Sleep(300);
lZipCode = Convert.ToInt32(r[1]);
AddWeatherItem(lZipCode, 0.0, 0.0);
}
//save the DS
//clone the texture map table
DataTable clonedTextureMap = m_TextureMap.Copy();
//set all the texture IDs to -1 in order to prevent problems at loadtime
//next time that the application loads the layer from the cache.
foreach (DataRow r in clonedTextureMap.Rows)
{
r[2] = -1;
r.AcceptChanges();
}
//serialize the tables onto the local machine
DataSet ds = new DataSet();
ds.Tables.Add(m_table);
ds.Tables.Add(clonedTextureMap);
ds.WriteXml(m_weatherXmlFile);
ds.Tables.Remove(m_table);
ds.Tables.Remove(clonedTextureMap);
clonedTextureMap.Dispose();
ds.Dispose();
GC.Collect();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <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>
/// Globe redraw timer event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnRedrawUpdateTimer(object sender, ElapsedEventArgs e)
{
//since this is the timer event handler, it gets executed on a different thread than
//the main one. Therefore the Invoke call is required in order to force the call
//on the main thread and thus prevent cross apartment calls
if(m_bTimerIsRunning)
base.Invoke(new RedrawEventHandler(OnRedrawEventHandler));
}
/// <summary>
/// Globe redraw event handler.
/// </summary>
/// <remarks>since this method is the delegate method of an 'Invoke' call it is
/// guaranteed to run on the main thread and therefore does not end up in
/// making cross apartment COM calls</remarks>
void OnRedrawEventHandler()
{
m_globeDisplay.RefreshViewers();
}
/// <summary>
/// Create the display lists used by the layer
/// </summary>
/// <remarks>the cal to this method must be made from BeforeDraw, AfterDraw or DrawImmidiate.
/// calling this method from anywhere else might end up in unexpected results since OpenGL state is
/// not guaranteed</remarks>
private void CreateDisplayLists()
{
//create the display list for the weather icons
//the quad size is set to 1 unit. Therefor you will have to scale it
//each time before drawing.
m_billboardRectList = GL.glGenLists(1);
GL.glNewList(m_billboardRectList, GL.GL_COMPILE);
GL.glPushMatrix();
//shift the item 1/2 unit to the left so that it'll get drawn around the
//middle of its base
GL.glTranslatef(-0.5f, 0.0f, 0.0f);
GL.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
GL.glDisable(GL.GL_LIGHTING);
//enable texture in order to allow for texture binding
GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_MODULATE);
//set blending to allow for transparency
GL.glEnable(GL.GL_BLEND);
GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
GL.glDepthFunc(GL.GL_LEQUAL);
//create the geometry (quad) and specify the texture coordinates
GL.glBegin(GL.GL_QUADS);
GL.glTexCoord2f(0.0f, 0.0f); GL.glVertex3f(0.0f, 0.0f, 0.0f);
GL.glTexCoord2f(0.0f, 1.0f); GL.glVertex3f(0.0f, 1.0f, 0.0f);
GL.glTexCoord2f(1.0f, 1.0f); GL.glVertex3f(1.0f, 1.0f, 0.0f);
GL.glTexCoord2f(1.0f, 0.0f); GL.glVertex3f(1.0f, 0.0f, 0.0f);
GL.glEnd();
GL.glPopMatrix();
GL.glEndList();
//create the list for the selection symbol (used for selection and to flash items)
m_selectionDisplayList = GL.glGenLists(1);
GL.glNewList(m_selectionDisplayList, GL.GL_COMPILE);
GL.glPushMatrix();
GL.glLineWidth(2.0f);
GL.glTranslatef(-0.5f, 0.0f, 0.0f);
GL.glBegin(GL.GL_LINE_STRIP);
GL.glVertex3f(0.0f, 0.0f, 0.0f);
GL.glVertex3f(1.0f, 0.0f, 0.0f);
GL.glVertex3f(1.0f, 1.0f, 0.0f);
GL.glVertex3f(0.0f, 1.0f, 0.0f);
GL.glVertex3f(0.0f, 0.0f, 0.0f);
GL.glEnd();
GL.glPopMatrix();
GL.glEndList();
m_bDisplayListCreated = true;
}
/// <summary>
/// Given a bitmap (GDI+), create for it an OpenGL texture and return its ID
/// </summary>
/// <param name="bitmap"></param>
/// <returns>the OGL texture id</returns>
/// <remarks>in order to allow hardware acceleration, texture size must be power of two.</remarks>
private uint CreateTexture(Bitmap bitmap)
{
try
{
//get the bitmap's dimensions
int h = bitmap.Height;
int w = bitmap.Width;
int s = Math.Max(h, w);
//calculate the closest power of two to match the bitmap's size
//(thank god for high-school math...)
double x = Math.Log(Convert.ToDouble(s)) / Math.Log(2.0);
s = Convert.ToInt32(Math.Pow(2.0, Convert.ToDouble(Math.Ceiling(x))));
int bufferSizeInPixels = s * s;
//get the bitmap's raw data
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
System.Drawing.Imaging.BitmapData bitmapData;
bitmapData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
byte[] bgrBuffer = new byte[bufferSizeInPixels * 3];
//scale the bitmap to be a power of two
unsafe
{
fixed (byte* pBgrBuffer = bgrBuffer)
{
GLU.gluScaleImage(GL.GL_BGR_EXT, bitmap.Size.Width, bitmap.Size.Height, GL.GL_UNSIGNED_BYTE, bitmapData.Scan0.ToPointer(), s, s, GL.GL_UNSIGNED_BYTE, pBgrBuffer);
}
}
//create a new buffer to store the raw data and set the transparency color (alpha = 0)
byte[] bgraBuffer = new byte[bufferSizeInPixels * 4];
int posBgr = 0;
int posBgra = 0;
for (int i = 0; i < bufferSizeInPixels; i++)
{
bgraBuffer[posBgra] = bgrBuffer[posBgr]; //B
bgraBuffer[posBgra + 1] = bgrBuffer[posBgr + 1]; //G
bgraBuffer[posBgra + 2] = bgrBuffer[posBgr + 2]; //R
//take care of the alpha
if (255 == bgrBuffer[posBgr] && 255 == bgrBuffer[posBgr + 1] && 255 == bgrBuffer[posBgr + 2])
{
bgraBuffer[posBgra + 3] = 0;
}
else
{
bgraBuffer[posBgra + 3] = 255;
}
posBgr += 3;
posBgra += 4;
}
//ceate the texture
uint[] texture = new uint[1];
GL.glEnable(GL.GL_TEXTURE_2D);
GL.glGenTextures(1, texture);
GL.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);
//set the texture parameters
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR);
GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_MODULATE);
unsafe
{
fixed (byte* pBgraBuffer = bgraBuffer)
{
GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, (int)GL.GL_RGBA, s, s, 0, GL.GL_BGRA_EXT, GL.GL_UNSIGNED_BYTE, pBgraBuffer);
}
}
//unlock the bitmap from memory
bitmap.UnlockBits(bitmapData);
//return the newly created texture id
return texture[0];
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
return (uint)0;
}
/// <summary>
/// orient the weather icons so that it'll face the camera
/// </summary>
private void OrientBillboard()
{
GL.glMultMatrixd(m_billboardMatrix);
}
/// <summary>
/// Test whether an item is inside the current viewport
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <param name="clipNear"></param>
/// <param name="mode"></param>
/// <returns></returns>
/// <remarks>given geocentric coordinate, convert it into window coordinate and then
/// test whether it is within the current viewport</remarks>
private bool InsideViewport(double x, double y, double z, double clipNear, uint mode)
{
bool inside = true;
//In selection mode the projection matrix is changed.
//Therefor use the GlobeViewUtil because calling gluProject would give unexpected results.
if (GL.GL_SELECT == mode)
{
int winX, winY;
m_globeViewUtil.GeocentricToWindow(x, y, z, out winX, out winY);
inside = (winX >= m_viewport[0] && winX <= m_viewport[2]) && (winY >= m_viewport[1] && winY <= m_viewport[3]);
}
else
{
//use gluProject in order to convert into windows coordinate
unsafe
{
double winx, winy, winz;
GLU.gluProject(x, y, z, m_modelViewMatrix, m_projMatrix, m_viewport, &winx, &winy, &winz);
inside = (winx >= m_viewport[0] && winx <= m_viewport[2]) && (winy >= m_viewport[1] && winy <= m_viewport[3] && (winz >= clipNear && winz <= 1.0));
}
}
return inside;
}
/// <summary>
/// Makes a request against Yahoo! 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;
//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 cae 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. The description tag is an HTML tag.
Match m = Regex.Match(desc, regxQry);
if (m.Success)
{
iconPath = m.Value;
//add the iconPath to the textureMap table
DataRow tr = m_TextureMap.Rows.Find(iconCode);
//test whether the texture table does not already include an entry for that icon
if (null == tr)
{
//ceate a new record
tr = m_TextureMap.NewRow();
tr[1] = iconPath;
tr[2] = -1;
tr[3] = iconCode;
lock (m_TextureMap)
{
m_TextureMap.Rows.Add(tr);
}
}
}
else
{
iconPath = "";
}
//test whether the record already exists in the layer's table.
DataRow dbr = m_table.Rows.Find(zipCode);
if (null == dbr)
{
//create a new record
dbr = m_table.NewRow();
dbr[1] = zipCode;
dbr[2] = cityName;
dbr[3] = lat;
dbr[4] = lon;
dbr[5] = 0.0;
dbr[6] = 0.0;
dbr[7] = 0.0;
dbr[8] = temp;
dbr[9] = condition;
dbr[10] = iconPath;
dbr[11] = day;
dbr[12] = date;
dbr[13] = low;
dbr[14] = high;
dbr[15] = false;
dbr[16] = DateTime.Now;
dbr[17] = iconCode;
//add the new record to the table
lock (m_table)
{
m_table.Rows.Add(dbr);
}
}
else //update the existing record
{
dbr[8] = temp;
dbr[9] = condition;
dbr[10] = iconPath;
dbr[11] = day;
dbr[12] = date;
dbr[13] = low;
dbr[14] = high;
dbr[16] = DateTime.Now;
dbr[17] = iconCode;
lock (m_table)
{
dbr.AcceptChanges();
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
/// <summary>
/// draw an item on the globe's surface
/// </summary>
/// <param name="r"></param>
/// <param name="dMagnitude"></param>
/// <remarks>the magnitude determine whether to draw the item as a dot or with the icon symbol.
/// The determination of the magnitude threshold is empirically.</remarks>
private void DrawItem(DataRow r, double dMagnitude)
{
double lat, lon, X = 0.0, Y = 0.0, Z = 0.0;
long lZipCode;
string cityName;
string condition;
string iconPath;
int iconId;
int temp;
bool bIsSelected;
Bitmap b = null;
if (null != r)
{
//get the information from the record
lZipCode = Convert.ToInt64(r[1]);
lat = Convert.ToDouble(r[3]);
lon = Convert.ToDouble(r[4]);
X = Convert.ToDouble(r[5]);
Y = Convert.ToDouble(r[6]);
Z = Convert.ToDouble(r[7]);
cityName = Convert.ToString(r[2]);
condition = Convert.ToString(r[9]);
iconPath = Convert.ToString(r[10]);
temp = Convert.ToInt32(r[8]);
bIsSelected = Convert.ToBoolean(r[15]);
iconId = Convert.ToInt32(r[17]);
#region search for the icon in the texture map table
DataRow tr = m_TextureMap.Rows.Find(iconId);
int bitmapTextureId = -1;
if (null != tr)
{
bitmapTextureId = Convert.ToInt32(tr[2]);
//in case that the texture id is not valid, ceate the texture
if (-1 == bitmapTextureId || (byte)0 == GL.glIsTexture((uint)bitmapTextureId))
{
try
{
//search for the icon on the local hard drive first
string iconFileName = System.IO.Path.Combine(m_iconFolder, iconPath.Substring(iconPath.LastIndexOf("/") + 1));
if (File.Exists(iconFileName))
{
b = new Bitmap(iconFileName, true);
}
else
{
//get the bitmap from the web
using (System.Net.WebClient webClient = new System.Net.WebClient())
{
using (System.IO.Stream stream = webClient.OpenRead(iconPath))
{
b = new Bitmap(stream, true);
//save the bitmap to the icons folder
b.Save(iconFileName);
}
}
}
//create the texture and store it in the textures table
if (null != b)
{
bitmapTextureId = (int)CreateTexture(b);
tr[2] = bitmapTextureId;
//add the record to the table
lock (m_TextureMap)
{
tr.AcceptChanges();
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
}
#endregion
#region determine weather the item need to be flashed
//in case that the user has identified the item or explicitly asked required to flash
//the item. The layer is using timer to redraw the display in a constant pace, we will
//take advantage of that fact in order to alternate the selection list around the flashed
//item and therefor give the notion of blinking.
if (lZipCode == m_lItemToFlash)
{
if (m_flashCount > 10)
{
m_lItemToFlash = -1;
m_flashDraw = true;
m_flashCount = 0;
}
else
{
m_flashCount++;
m_flashDraw = !m_flashDraw;
}
}
#endregion
if (dMagnitude > 1.75)
{
#region draw the point location
//in case that the item is far from the camera, it can be drawn as a simple dot.
GL.glEnable(GL.GL_POINT_SMOOTH);
if (bIsSelected)
{
//set selction size and color
GL.glPointSize(7.0f);
GL.glColor3ub(255, 255, 128);
}
else
{
//set size and color
GL.glPointSize(5.0f);
GL.glColor3ub(255, 0, 0);
}
//flash the shape
if (lZipCode == m_lItemToFlash && m_flashDraw == true)
{
GL.glPointSize(5.0f);
GL.glColor3ub(0, 255, 0);
}
//draw the dot
GL.glLoadName((uint)lZipCode);
GL.glBegin(GL.GL_POINTS);
GL.glVertex3f(Convert.ToSingle(X), Convert.ToSingle(Y), Convert.ToSingle(Z));
GL.glEnd();
GL.glDisable(GL.GL_POINT_SMOOTH);
#endregion
}
else
{
#region draw the forecast icon
//enable 2D texture and bind the icon's texture
GL.glEnable(GL.GL_TEXTURE_2D);
if (-1 != bitmapTextureId)
{
GL.glBindTexture(GL.GL_TEXTURE_2D, (uint)bitmapTextureId);
}
GL.glPushMatrix();
//translate to the items location
GL.glTranslatef(Convert.ToSingle(X), Convert.ToSingle(Y), Convert.ToSingle(Z));
//orient the icon so that it'll face the camera
OrientBillboard();
//scale the item (original size is 1 ubit)
double useScale = 0.04 * dMagnitude;
GL.glScaled(useScale, useScale, 1.0);
GL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
//loads the zipcode onto the name stack. The name stack is used during selection mode (please refer to method 'Hit').
GL.glLoadName((uint)lZipCode);
//draw the item
GL.glCallList(m_billboardRectList);
//switch back the OGL different modes
GL.glDisable(GL.GL_TEXTURE_2D);
GL.glDisable(GL.GL_BLEND);
GL.glDisable(GL.GL_DEPTH_TEST);
GL.glDisable(GL.GL_ALPHA_TEST);
//in case that the item is selected, draw the selection list.
if (bIsSelected)
{
GL.glColor4ub(255, 255, 128, 255);
GL.glCallList(m_selectionDisplayList);
}
//flash the shape
if (lZipCode == m_lItemToFlash && m_flashDraw == true)
{
GL.glColor4ub(0, 255, 0, 255);
GL.glCallList(m_selectionDisplayList);
}
GL.glPopMatrix();
#endregion
}
}
}
#endregion
#region public methods and props
/// <summary>
/// connects to the RSS weather service
/// </summary>
public void Connect()
{
//make sure that the control's handle got created since it is necessary
//for the 'Invoke' calls
if (IntPtr.Zero == this.Handle)
throw new Exception("RSSWeatherLayer3D.Connect: Error creating handle for the layer");
//enable the update and redraw timers
m_timer.Enabled = true;
m_redrawTimer.Enabled = true;
m_bTimerIsRunning = true;
}
/// <summary>
/// disconnects from the RSS weather service
/// </summary>
public void Disconnect()
{
//disable the update timer
m_bTimerIsRunning = false;
m_timer.Enabled = false;
m_redrawTimer.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("RSSWeatherLayer3D update thread has been terminated");
}
}
/// <summary>
/// specify the item to flash
/// </summary>
/// <param name="zipCode"></param>
public void Flash(long zipCode)
{
m_lItemToFlash = zipCode;
}
/// <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_globeDisplay)
return;
DataRow[] rows = m_table.Select("ZIPCODE = " + zipCode.ToString());
if (rows.Length == 0)
return;
DataRow r = rows[0];
//get the lat/lon from the table
double lat = Convert.ToDouble(r[3]);
double lon = Convert.ToDouble(r[4]);
//set an envelope around the point
IEnvelope env = new EnvelopeClass();
((IZAware)env).ZAware = true;
env.PutCoords(lon - 0.1, lat - 0.1, lon + 0.1, lat + 0.1);
env.ZMax = 1500.0;
env.ZMin = 500.0;
m_globeDisplay.IsNavigating = true;
//zoom to the item's location
((IGlobeCamera)m_globeDisplay.ActiveViewer.Camera).SetToZoomToExtents(env, m_globeDisplay.Globe, m_globeDisplay.ActiveViewer);
m_globeDisplay.IsNavigating = false;
}
/// <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 it is a new selection, unselect any selected items
if (newSelection)
{
//unselect all the currently selected items
lock (m_table)
{
foreach (DataRow r in m_table.Rows)
{
r[15] = false;
}
lock (m_table)
m_table.AcceptChanges();
}
}
//get the record from the table
DataRow[] rows = m_table.Select("ZIPCODE = " + zipCode.ToString());
//make sure that the record exists
if (rows.Length == 0)
return;
DataRow rec = rows[0];
//set the selection flag to true
lock (m_table)
{
rec[15] = true;
rec.AcceptChanges();
}
}
/// <summary>
/// Run the update thread
/// </summary>
/// <remarks>calling this method too 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. </remarks>
public void RefreshDB()
{
try
{
m_updateThread = new Thread(new ThreadStart(ThreadProc));
//run the update thread
m_updateThread.Start();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
//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
public bool AddItem(long zipCode)
{
return AddItem(zipCode, 0.0, 0.0);
}
//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.
public bool AddItem(long zipCode, double lat, double lon)
{
try
{
if (null == m_table)
return false;
double X = 0.0, Y = 0.0, Z = 0.0;
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
{
//calculate the geocentric coordinates
if (null != m_globeDisplay)
{
IGlobeViewUtil globeViewUtil = (IGlobeViewUtil)m_globeDisplay.ActiveViewer.Camera;
globeViewUtil.GeographicToGeocentric(lon, lat, 1000.0, out X, out Y, out Z);
}
//update the record
r[3] = lat;
r[4] = lon;
r[5] = X;
r[6] = Y;
r[7] = Z;
lock (m_table)
{
r.AcceptChanges();
}
}
}
else
{
//add new zip code to the locations list
DataRow rec = m_locations.NewRow();
rec[1] = zipCode;
lock (m_locations)
{
m_locations.Rows.Add(rec);
}
//need to connect to the service and get the info
AddWeatherItem(zipCode, lat, lon);
}
return true;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
return false;
}
}
/// <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
{
//delete the record
lock (m_table)
{
r.Delete();
m_table.AcceptChanges();
}
return true;
}
return false;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
return false;
}
}
/// <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>
/// 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();
}
}
/// <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;
}
#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)
{
try
{
if (!m_bValid || null == m_globeDisplay)
return null;
IEnvelope intersectEnv = new EnvelopeClass();
IEnvelope inEnv;
IArray array = new ArrayClass();
//get the envelope from the ggeometry
if (pGeom.GeometryType == esriGeometryType.esriGeometryEnvelope)
inEnv = pGeom.Envelope;
else
inEnv = pGeom as IEnvelope;
if (inEnv.IsEmpty)
return array;
double xMin, xMax, yMin, yMax, zMin, zMax;
inEnv.QueryCoords(out xMin, out yMin, out xMax, out yMax);
zMin = inEnv.ZMin;
zMax = inEnv.ZMax;
//get the middle coordinate of the envelope
double xC, yC, zC;
xC = (xMin + xMax) * 0.5;
yC = (yMin + yMax) * 0.5;
zC = (zMin + zMax) * 0.5;
ISceneViewer sceneViewer = m_globeDisplay.ActiveViewer;
IGlobeViewUtil globeViewUtil = (IGlobeViewUtil)sceneViewer.Camera;
int winX, winY;
globeViewUtil.GeographicToWindow(xC, yC, zC * 1000.0, out winX, out winY);
IHit3DSet hits;
//locate all the items that falls within the given location
m_globeDisplay.LocateMultiple(sceneViewer, winX, winY, true, false, false, false, out hits);
if (null == hits)
return array;
IHit3D hit3D = null;
IPropertySet propSet = null;
IIdentifyObj idObj = null;
IIdentifyObject idObject = null;
IArray objArray = hits.Hits;
int nCount = objArray.Count;
bool bIdentify = false;
//iterate through the hit items and create identify objects
for (int i = 0; i < nCount; i++)
{
hit3D = objArray.get_Element(i) as IHit3D;
//make sure that the hit object is a propertyset
propSet = hit3D.Object as IPropertySet;
if (null != propSet)
{
//instantiate the identify object and add it to the array
idObj = new GlobeWeatherIdentifyObject();
//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;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("IIdentify.Identify: " + ex.Message);
return null;
}
}
#endregion
}
}