Vehicle Tracker Extension
Extension.vb
' Copyright 2011 ESRI
' 
' All rights reserved under the copyright laws of the United States
' and applicable international laws, treaties, and conventions.
' 
' You may freely redistribute and use this sample code, with or
' without modification, provided you include the original copyright
' notice and use restrictions.
' 
' See the use restrictions.
' 

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Linq
Imports System.Text
Imports System.Drawing

Imports ESRI.ArcGISExplorer
Imports ESRI.ArcGISExplorer.Application
Imports ESRI.ArcGISExplorer.Geometry
Imports ESRI.ArcGISExplorer.Mapping
Imports ESRI.ArcGISExplorer.Data
Imports ESRI.ArcGISExplorer.Threading


Public Class Extension
  Inherits ESRI.ArcGISExplorer.Application.Extension

  Private _bgWorker As ESRI.ArcGISExplorer.Threading.BackgroundWorker
  Private _timer As System.Windows.Forms.Timer

  Private Shared _vehicles As List(Of Vehicle)
  Private _ambulanceSymbol As Symbol
  Private _fileCount As Integer

  Private _dataPath As String = String.Empty
  Private _dataOk As Boolean = 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 Overrides Sub OnStartup()

    Dim sdkPath As String = Environment.GetEnvironmentVariable("ArcGIS_E3SDK")
    _dataPath = sdkPath + "\Samples\VehicleTrackingExt\Data\"

    If (System.IO.Directory.Exists(_dataPath)) Then
      _dataOk = True
    End If

    If (_dataOk) Then
      AddHandler Application.DocumentOpened, AddressOf Application_DocumentOpened
      AddHandler Application.DocumentClosed, AddressOf Application_DocumentClosed
    End If

    'Setup the background worker
    InitializeBackgroundWorker()
  End Sub

  ''' <summary>
  ''' Called by Explorer when the application shuts down. Stop the Timer, unhook any event handlers.
  ''' </summary>
  Public Overrides Sub OnShutdown()
    If Not _timer Is Nothing Then
      _timer.Stop()
      _timer = Nothing
    End If

    RemoveHandler Application.DocumentOpened, AddressOf Application_DocumentOpened
    RemoveHandler Application.DocumentClosed, AddressOf Application_DocumentClosed

  End Sub

  ''' <summary>
  ''' Initializes the vehicle list and the background worker, listen for graphic clicked events
  ''' </summary>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub Application_DocumentOpened(ByVal sender As Object, ByVal e As EventArgs)
    InitializeBackgroundWorker()
  End Sub

  Private Sub InitializeBackgroundWorker()
    AddHandler Application.ActiveMapDisplay.GraphicClicked, AddressOf ActiveMapDisplay_GraphicClicked

    _vehicles = New List(Of Vehicle)()

    _bgWorker = New ESRI.ArcGISExplorer.Threading.BackgroundWorker()
    AddHandler _bgWorker.DoWork, AddressOf DoWork
    AddHandler _bgWorker.RunWorkerCompleted, AddressOf RunWorkerCompleted
    If (Not _bgWorker.IsBusy) Then
      ' 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)
    End If

  End Sub

  ''' <summary>
  ''' Stop the timer and remove the graphics from the map
  ''' </summary>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub Application_DocumentClosed(ByVal sender As Object, ByVal e As EventArgs)
    If Not _timer Is Nothing Then
      _timer.Stop()
    End If
    _timer = Nothing

    ' Unhook event handlers while there is no document.
    RemoveHandler Application.ActiveMapDisplay.GraphicClicked, AddressOf ActiveMapDisplay_GraphicClicked
    If (Not _bgWorker Is Nothing) Then
      RemoveHandler _bgWorker.DoWork, AddressOf DoWork
      RemoveHandler _bgWorker.RunWorkerCompleted, AddressOf RunWorkerCompleted
      _bgWorker = Nothing
    End If

  End Sub

  ''' <summary>
  ''' Re-execute the background worker to poll the GeoRSS feed.
  ''' </summary>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub _timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
    If ((Not _bgWorker Is Nothing) AndAlso (Not _bgWorker.IsBusy)) Then
      _bgWorker.RunWorkerAsync(_dataPath)
    End If
  End Sub

  ''' <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>
  Private Sub DoWork(ByVal sender As Object, ByVal e As ESRI.ArcGISExplorer.Threading.DoWorkEventArgs)
    ' Pull vehicle records from the GeoRSS feed / files.
    Dim ds As DataSet = New DataSet()
    Dim path As String = CType(e.Argument, String)

    _fileCount += 1
    If _fileCount > 20 Then
      _fileCount = 1
    End If

    Dim filePath As String = path + "GetAmbulances" + _fileCount.ToString() + ".xml"
    _dataOk = System.IO.File.Exists(filePath)
    If _dataOk Then
      ds.ReadXml(filePath, XmlReadMode.Auto)
      Dim table As DataTable = ds.Tables(0)
      ProcessVehicles(table)
    End If
  End Sub

  ''' <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>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub RunWorkerCompleted(ByVal sender As Object, ByVal e As ESRI.ArcGISExplorer.Threading.RunWorkerCompletedEventArgs)
    'Don't update the graphic display if the user is in the process of navigating
    If ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay.IsNavigating Then Return

    If (Not e.Error Is Nothing) Then
      ' There was an exception thrown within the DoWork event.
      ' May wish to investigate here.
      Debug.WriteLine(e.Error.ToString())
    End If


    If (Not _vehicles Is Nothing) Then
      'Put the updating of vehicles inside a SuspendDrawing block for better performance
      Dim mapDisp As MapDisplay = ESRI.ArcGISExplorer.Application.Application.ActiveMapDisplay
      Using mapDisp.SuspendDisplay
        Dim graphics As GraphicCollection = Application.ActiveMapDisplay.Graphics
        For Each veh As Vehicle In _vehicles
          If veh.GraphicUpdateStatus = UpdateStatus.Delete Then
            graphics.Remove(veh.MapGraphic)
          End If
        Next veh

        GetVehicleGraphics()

        For Each veh As Vehicle In _vehicles
          If (Not graphics.Contains(veh.MapGraphic)) Then
            graphics.Add(veh.MapGraphic)
          End If
        Next veh
      End Using

    End If

    ' If this is the first time the worker has completed, then start the timer to start 
    ' generating more updates.
    If _timer Is Nothing Then
      _timer = New System.Windows.Forms.Timer()
      _timer.Interval = (5000)
      AddHandler _timer.Tick, AddressOf _timer_Tick
      _timer.Start()
    End If
  End Sub

  ''' <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>
  Private Sub ProcessVehicles(ByVal table As DataTable)
    For Each row As DataRow In table.Rows
      Dim newVehicle As Vehicle = New Vehicle()
      newVehicle.X = Convert.ToDouble(row.ItemArray.GetValue(1).ToString())
      newVehicle.Y = Convert.ToDouble(row.ItemArray.GetValue(2).ToString())
      newVehicle.Status = row.ItemArray.GetValue(5).ToString()
      newVehicle.ID = row.ItemArray.GetValue(0).ToString()
      newVehicle.Speed() = row.ItemArray.GetValue(3).ToString()
      newVehicle.Bearing() = row.ItemArray.GetValue(4).ToString()
      newVehicle.Name() = row.ItemArray.GetValue(6).ToString()
      newVehicle.GraphicUpdateStatus() = UpdateStatus.None

      ' verify the vehicle exists 
      Dim existing As Vehicle = FindVehicle(row.ItemArray.GetValue(0).ToString())
      If existing Is Nothing Then
        _vehicles.Add(newVehicle)
      ElseIf existing.X <> newVehicle.X OrElse existing.Y <> newVehicle.Y Then
        existing.X = newVehicle.X
        existing.Y = newVehicle.Y
        existing.GraphicUpdateStatus = UpdateStatus.Update
      Else
        existing.GraphicUpdateStatus = UpdateStatus.None
      End If
    Next row
  End Sub

  ''' <summary>
  ''' Creates graphics for the new vehicles from the feed and updates those that
  ''' have been updated since the last poll
  ''' </summary>
  Private Sub GetVehicleGraphics()
    If _ambulanceSymbol Is Nothing Then
      _ambulanceSymbol = Symbol.Marker.Health.Ambulance
    End If

    If (Not _vehicles Is Nothing) Then

      For Each vehicle As Vehicle In _vehicles
        Dim xoPoint As ESRI.ArcGISExplorer.Geometry.Point = New ESRI.ArcGISExplorer.Geometry.Point(vehicle.X, vehicle.Y)
        If vehicle.MapGraphic Is Nothing Then
          Dim vehicleGraphic As Graphic = New Graphic(xoPoint, _ambulanceSymbol, vehicle.ID)
          vehicleGraphic.Placement3D = Placement3D.AttachToSurface
          vehicle.MapGraphic = vehicleGraphic
        ElseIf vehicle.GraphicUpdateStatus = UpdateStatus.Update Then
          vehicle.MapGraphic.MoveTo(xoPoint.X, xoPoint.Y)
          vehicle.GraphicUpdateStatus = UpdateStatus.None
        End If
      Next vehicle
    End If

  End Sub

  ''' <summary>
  ''' Finds a vehicle in the collection based on its ID or graphic
  ''' </summary>
  ''' <param name="ID"></param>
  ''' <returns></returns>
  Private Shared Function FindVehicle(ByVal ID As String) As Vehicle
    If (Not _vehicles Is Nothing) Then

      For Each vehicle As Vehicle In _vehicles
        If vehicle.ID = ID Then
          Return vehicle
        End If
      Next vehicle
    End If

    Return Nothing
  End Function

  ''' <summary>
    ''' Finds a vehicle with the specified graphic
  ''' </summary>
  ''' <param name="g"></param>
  ''' <returns></returns>
  Private Shared Function FindVehicle(ByVal g As Graphic) As Vehicle
    If (Not _vehicles Is Nothing) Then

      For Each veh As Vehicle In _vehicles
        If veh.MapGraphic Is g Then
          Return veh
        End If
      Next veh
    End If

    Return Nothing
  End Function

  ''' <summary>
  ''' Event handler when a graphic is clicked on the map: open its infopopup window
  ''' </summary>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub ActiveMapDisplay_GraphicClicked(ByVal sender As Object, ByVal e As GraphicMouseEventArgs)
    Dim vehicle As Vehicle = FindVehicle(e.Graphics(0)) ' Use first clicked.
    If Not vehicle Is Nothing Then
      ShowPopup(vehicle)
    End If
  End Sub

  ''' <summary>
  ''' Display the popup for the specified vehicle
  ''' </summary>
  ''' <param name="vehicle"></param>
  Public Shared Sub ShowPopup(ByVal vehicle As Vehicle)
    Dim g As Graphic = vehicle.MapGraphic
    Dim content As String = "<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)
    Dim myPopup As Popup = New Popup(Application.ActiveMapDisplay, content, "Vehicle Information", DirectCast(g.Geometry, ESRI.ArcGISExplorer.Geometry.Point))
    myPopup.Activate()



  End Sub

End Class