Extension.cs
// Copyright 2010 ESRI // // All rights reserved under the copyright laws of the United States // and applicable international laws, treaties, and conventions. // // You may freely redistribute and use this sample code, with or // without modification, provided you include the original copyright // notice and use restrictions. // // See the use restrictions. // using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Linq; using System.ComponentModel; using ESRI.ArcGISExplorer; using ESRI.ArcGISExplorer.Application; using ESRI.ArcGISExplorer.Mapping; using ESRI.ArcGISExplorer.Geometry; using ESRI.ArcGISExplorer.Data; using ESRI.ArcGISExplorer.Threading; namespace VehicleTrackingExtCS { public class Extension : ESRI.ArcGISExplorer.Application.Extension { ESRI.ArcGISExplorer.Threading.BackgroundWorker _bgWorker; System.Windows.Forms.Timer _timer; static List<Vehicle> _vehicles; Symbol _ambulanceSymbol; int _fileCount = 0; string _dataPath = String.Empty; bool _dataOk = false; /// <summary> /// Called by Explorer when the application starts up. Work out the path to the Data, /// and hook up the document open event. /// </summary> public override void OnStartup() { string sdkPath = Environment.GetEnvironmentVariable("ArcGIS_E3SDK"); _dataPath = sdkPath + @"\Samples\VehicleTrackingExt\Data"; if (System.IO.Directory.Exists(_dataPath)) { _dataOk = true; } if (_dataOk) { Application.DocumentOpened += new EventHandler(Application_DocumentOpened); Application.DocumentClosed += new EventHandler(Application_DocumentClosed); } //Setup the background worker InitializeBackgroundWorker(); } /// <summary> /// Called by Explorer when the application shuts down. Stop the Timer, unhook any event handlers. /// </summary> public override void OnShutdown() { if (_timer != null) { _timer.Stop(); _timer = null; } Application.DocumentOpened -= new EventHandler(Application_DocumentOpened); Application.DocumentClosed -= new EventHandler(Application_DocumentClosed); } /// <summary> /// Initializes the vehicle list and the background worker, listen for graphic clicked events /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void Application_DocumentOpened(object sender, EventArgs e) { InitializeBackgroundWorker(); } void InitializeBackgroundWorker() { Application.ActiveMapDisplay.GraphicClicked += new EventHandler<GraphicMouseEventArgs>(ActiveMapDisplay_GraphicClicked); _vehicles = new List<Vehicle>(); _bgWorker = new ESRI.ArcGISExplorer.Threading.BackgroundWorker(); _bgWorker.DoWork += new ESRI.ArcGISExplorer.Threading.DoWorkEventHandler(DoWork); _bgWorker.RunWorkerCompleted += new ESRI.ArcGISExplorer.Threading.RunWorkerCompletedEventHandler(RunWorkerCompleted); if (!_bgWorker.IsBusy) { // Best practice to always check IsBusy before calling RunWorkerAsync; although in this specific // case as the object has just been created its unlikely to be busy. _bgWorker.RunWorkerAsync(_dataPath); } } /// <summary> /// Stop the timer and remove the graphics from the map /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void Application_DocumentClosed(object sender, EventArgs e) { // Stop the timer (stop any more background work being started). if (_timer != null) { _timer.Stop(); } _timer = null; // Unhook event handlers while there is no document. Application.ActiveMapDisplay.GraphicClicked -= new EventHandler<GraphicMouseEventArgs>(ActiveMapDisplay_GraphicClicked); if (_bgWorker != null) { _bgWorker.DoWork -= new ESRI.ArcGISExplorer.Threading.DoWorkEventHandler(DoWork); _bgWorker.RunWorkerCompleted -= new ESRI.ArcGISExplorer.Threading.RunWorkerCompletedEventHandler(RunWorkerCompleted); _bgWorker = null; } } /// <summary> /// Re-execute the background worker to poll the GeoRSS feed. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void _timer_Tick(object sender, EventArgs e) { if ((_bgWorker != null) && (!_bgWorker.IsBusy)) { _bgWorker.RunWorkerAsync(_dataPath); } } /// <summary> /// Called on the worker thread. Queries a GeoRSS feed to get a list of emergency vehicles /// and their locations and updates a list of known vehicles from previous queries /// on the feed. /// NOTE: In order to provide reliable sample code, this reads files saved from a GeoRSS feed, /// not a live GeoRSS feed. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void DoWork(object sender, ESRI.ArcGISExplorer.Threading.DoWorkEventArgs e) { // Pull vehicle records from the GeoRSS feed / files. DataSet ds = new DataSet(); string path = (string) e.Argument; _fileCount += 1; if (_fileCount > 20) { _fileCount = 1; } string filePath = path + "\\GetAmbulances" + _fileCount.ToString() + @".xml"; _dataOk = System.IO.File.Exists(filePath); if (_dataOk) { ds.ReadXml(filePath, XmlReadMode.Auto); DataTable table = ds.Tables[0]; ProcessVehicles(table); } } /// <summary> /// Called on the UI thread. Update the vehicle locations on the map, add and delete /// any new or removed vehicles. /// The first time this method is called for the document, initializes a timer to update /// the vehicles at a regular interval. /// </summary> void RunWorkerCompleted(object sender, ESRI.ArcGISExplorer.Threading.RunWorkerCompletedEventArgs e) { //Don't update the graphic display if the user is in the process of navigating if (ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay.IsNavigating) return; if (e.Error != null) { // There was an exception thrown within the DoWork event. // May wish to investigate here. System.Diagnostics.Debug.WriteLine(e.Error.ToString()); } if (_vehicles != null) { //Put the updating of vehicles inside a SuspendDrawing block for better performance MapDisplay mapDisp = ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay; using (mapDisp.SuspendDisplay()) { GraphicCollection graphics = Application.ActiveMapDisplay.Graphics; foreach (Vehicle vehicle in _vehicles) { if (vehicle.GraphicUpdateStatus == UpdateStatus.Delete) { graphics.Remove(vehicle.MapGraphic); } } GetVehicleGraphics(); foreach (Vehicle vehicle in _vehicles) { if (!graphics.Contains(vehicle.MapGraphic)) { graphics.Add(vehicle.MapGraphic); } } } } // If this is the first time the worker has completed, then start the timer to start // generating more updates. if (_timer == null) { _timer = new System.Windows.Forms.Timer(); _timer.Interval = (5000); _timer.Tick += new EventHandler(_timer_Tick); _timer.Start(); } } /// <summary> /// Creates Vehicle objects from the GeoRSS feed records, adds them if they are new and /// marks them as updated if they have changed /// </summary> /// <param name="table"></param> /// <param name="vehicleType"></param> private void ProcessVehicles(DataTable table) { foreach (DataRow row in table.Rows) { Vehicle newVehicle = new Vehicle() { X = Convert.ToDouble(row.ItemArray.GetValue(1).ToString()), Y = Convert.ToDouble(row.ItemArray.GetValue(2).ToString()), Status = row.ItemArray.GetValue(5).ToString(), ID = row.ItemArray.GetValue(0).ToString(), Speed = row.ItemArray.GetValue(3).ToString(), Bearing = row.ItemArray.GetValue(4).ToString(), Name = row.ItemArray.GetValue(6).ToString(), GraphicUpdateStatus = UpdateStatus.None }; // verify the vehicle exists Vehicle existing = FindVehicle(row.ItemArray.GetValue(0).ToString()); if (existing == null) { _vehicles.Add(newVehicle); } else if (existing.X != newVehicle.X || existing.Y != newVehicle.Y) { existing.X = newVehicle.X; existing.Y = newVehicle.Y; existing.GraphicUpdateStatus = UpdateStatus.Update; } else { existing.GraphicUpdateStatus = UpdateStatus.None; } } } /// <summary> /// Creates graphics for the new vehicles from the feed and updates those that /// have been updated since the last poll /// </summary> private void GetVehicleGraphics() { if (_ambulanceSymbol == null) { _ambulanceSymbol = Symbol.Marker.Health.Ambulance; } foreach (Vehicle vehicle in _vehicles) { //Create a point for the new location of the vehicle ESRI.ArcGISExplorer.Geometry.Point xoPoint = new ESRI.ArcGISExplorer.Geometry.Point(vehicle.X, vehicle.Y); if (vehicle.MapGraphic == null) { Graphic vehicleGraphic = new Graphic(xoPoint, _ambulanceSymbol, vehicle.ID); vehicleGraphic.Placement3D = Placement3D.AttachToSurface; vehicle.MapGraphic = vehicleGraphic; } else if (vehicle.GraphicUpdateStatus == UpdateStatus.Update) { vehicle.MapGraphic.MoveTo(xoPoint.X, xoPoint.Y); vehicle.GraphicUpdateStatus = UpdateStatus.None; } } } /// <summary> /// Finds a vehicle in the collection based on its ID or graphic /// </summary> /// <param name="ID"></param> /// <returns></returns> private static Vehicle FindVehicle(string ID) { foreach (Vehicle vehicle in _vehicles) { if (vehicle.ID == ID) { return vehicle; } } return null; } /// <summary> /// Finds a vehicle with the specified graphic /// </summary> /// <param name="g"></param> /// <returns></returns> private static Vehicle FindVehicle(Graphic g) { foreach (Vehicle vehicle in _vehicles) { if (vehicle.MapGraphic == g) { return vehicle; } } return null; } /// <summary> /// Event handler when a graphic is clicked on the map: open its infopopup window /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void ActiveMapDisplay_GraphicClicked(object sender, GraphicMouseEventArgs e) { Vehicle vehicle = FindVehicle(e.Graphics[0]); // Use first graphic clicked. if (vehicle != null) { ShowPopup(vehicle); } } /// <summary> /// Display the popup for the specified vehicle /// </summary> /// <param name="vehicle"></param> public static void ShowPopup(Vehicle vehicle) { Graphic g = vehicle.MapGraphic; string content = "<b>" + vehicle.ID + "</b>" + "<br><br>Driver: " + vehicle.Name + "<br>" + "Speed: " + vehicle.Speed + "<br>" + "Bearing: " + vehicle.Bearing + "<br>" + "Status: " + vehicle.Status; Application.ActiveMapDisplay.HidePopups(true); Popup popUp = new Popup(Application.ActiveMapDisplay, null, content, "Vehicle Information", (ESRI.ArcGISExplorer.Geometry.Point)g.Geometry); popUp.Activate(); } } }