Logging Tutorial |
The Telogis.GeoBase.Diagnostics namespace includes two primary classes, Log and LogEntry, that are used to manage the creation and management of logs and log events.
In this tutorial we will create an application that demonstrates the:
creation of different log types using the Log class
use of the LogEventHandler delegate to handle log events
use of the LogEntry class to manage individual log entries
steps needed to save log entries to a local text file
The application we create will use a Navigator created using a SimulatedGps to navigate between two LatLon locations. It will be initialized by a UI button. We will log details of both the clicking of this button at the outset and the Navigator's eventual arrival at its Destination.
During navigation we will monitor the speed of the navigator and compare it to the posted speed limit, logging any speed infringements with the speed and nearest street address. We will also track the path of the navigator, triggering a log entry if it goes off course. All log events will be saved to a text file.
Create a new Visual Studio Project (a Windows Forms Application) called 'Logging'.
Add a reference to geobase.net.dll. Then, to make the code simpler and more readable, add the following using directives to the top of the project form (Form1.cs).
using Telogis.GeoBase.Routing; using Telogis.GeoBase.Navigation; using Telogis.GeoBase.Diagnostics; using Telogis.GeoBase; using System.IO;
Return to the 'Design' view (Shift+F7) and add the following controls to the form:
When run, your new application should appear similar to the screenshot below:
Next, add the following code to the top of the project such that the items are global (immediately above the Form1 constructor).
This code will:
create two render lists to display the map and PushPins
create a LabelBox
specify the path to the GeoBase \langs\ folder
create two LatLon objects that will be used as the start and destination locations for the route our Navigator will take
declare a StreamWriter for creating a text file that will contain our log entries, a Navigator and an ILogger
private Navigator nav; private RendererList renderList = new RendererList(); private RendererList pins = new RendererList(); private LabelBox lBox = new LabelBox(); private ILogger _myLog; readonly private String LangsPath = Settings.GeoBasePath("langs"); readonly private LatLon StartLocation = new LatLon(33.65856, -117.75528); readonly private LatLon DestinationLocation = new LatLon(33.65006, -117.75523); readonly private StreamWriter LogFile;
Copy the following UpdateLocation method below the Form1 constructor. This code will:
update the location of the Navigator, which will stand in for a vehicle in our example
call the UpdateUI method, which we will add later.
determine the Navigator's current speed using GPS data
determine the posted speed limit of the roadway being traversed by reverse geocoding the Navigator's current location, then using the ImpliedSpeedLimit property of the current StreetLink
If the reverse geocode fails, an Error log will be created detailing the LatLon location of the failed geocode.
If the vehicle is found to be traveling above the speed limit, an Info log entry containing an alert, the vehicle's speed, the current street address and the vehicle's LatLon location is generated. If the vehicle is traveling below the speed limit then an Info log containing the same information, but without an alert, will be created.
private void UpdateLocation(object sender, EventArgs e) { // update current location of Navigator nav.AddPoint(); // call the UpdateUI method BeginInvoke(new MethodInvoker(UpdateUI)); // we will obtain the data needed for logging below: // determine the speed of the navigator (vehicle) Double vehicleSpeed = Math.Floor(nav.Gps.Position.speed); if (vehicleSpeed > 0) { // determine the speed limit of the street we are traveling on StreetLink currentLink = GeoCoder.ReverseGeoCodeFull(nav.Gps.Position.location).StreetLink; // convert the speed to MPH and remove decimal places double speedLimit = Math.Floor(MathUtil.ConvertUnits(currentLink.Flags.ImpliedSpeedLimit, currentLink.Flags.SPEED_UNITS, SpeedUnit.MilesPerHour)); // get details of the closest street ReverseGeoCodeArgs args = new ReverseGeoCodeArgs(nav.Gps.Position.location); GeoCodeFull streetDetails = GeoCoder.ReverseGeoCodeFull(args); // if the street's details are unknown, log the geocoding error. if (streetDetails == null) { _myLog.Error("ERROR >> Reverse geocoding of GPS position {0} gave an invalid location" , nav.Gps.Position.location); } else { Address nearest = GeoCoder.ReverseGeoCode(nav.Gps.Position.location); // compare the vehicle speed with the posted speed limit // create separate logs for under/over speed limit conditions if (vehicleSpeed > speedLimit) { _myLog = Log.WithCategory("Speed And Location Log"); _myLog.Info("ALERT >> Vehicle over speed limit. Speed: " + vehicleSpeed + " MPH. Location: " + nearest + ". LatLon: " + nav.Location); } else { _myLog = Log.WithCategory("Speed And Location Log"); _myLog.Info("Vehicle speed " + vehicleSpeed + " MPH. Location: " + nearest + ". LatLon: " + nav.Location); } } } }
Copy and paste the following UpdateUI method directly below the UpdateLocation() method. This code code will:
private void UpdateUI() { // are we on course for our destination? if (nav.Destination != null) { if (nav.IsOffCourse) { lBox.MajorText = "Off Course!"; // log that we are off course _myLog = Log.WithCategory("Course Status"); _myLog.Info("ALERT >> Vehicle traveling off course"); } else { lBox.MajorText = "On Course"; } } // show no more than 20 pins if (pins.Count > 20) { pins.RemoveAt(0); } // add a pin at the navigator location pins.Add(new PushPin(nav.Gps.Position.location)); // display the current address in the labelbox lBox.MinorText = nav.Address.Address.ToString(); // update the map center and heading mapMain.Center = nav.Gps.Position.location; mapMain.Heading = nav.Gps.Position.heading; // invalidate the map, causing a redraw mapMain.Invalidate(); }
Add a click event to buttonGo. We will use the click event to create the Navigator and set its Destination, and to create a log entry that records that the buttonGo button has been clicked. It will also contain the triggers for our UpdateLocation and OnArrived methods, and add our LabelBox and PushPins to the render list.
Update the buttonGo_Click event with the following code.
private void buttonGo_Click(object sender, EventArgs e) { _myLog = Log.WithCategory("Click Button Log"); _myLog.Info("The buttonGo button has been clicked"); // render everything in the list mapMain.Renderer = renderList; // zoom into the map mapMain.Zoom = 0.5; // create the navigator nav = new Navigator(new SimulatedGps(StartLocation), LangsPath, System.Globalization.CultureInfo.CurrentCulture); // set the navigator destination */ nav.Destination = new RouteStop(DestinationLocation); // use the navigator GPS ticks, once per second, to call our eventhandler */ nav.Gps.Update += UpdateLocation; // use the Arrived event to called the OnArrived method */ nav.Arrived += OnArrived; // resize the labelbox & position it at bottom-center of the map */ lBox.Size = new System.Drawing.Size(380, 75); lBox.Top = mapMain.Bottom - lBox.Height - 20; lBox.Left = (lBox.Width + mapMain.Width) / 2 - lBox.Width; // add the navigator to the render list renderList.Add(nav); // add the labelbox and pushpins to the renderlist */ renderList.Add(lBox); renderList.Add(pins); // set the map to render everything on our list */ mapMain.Renderer = renderList; }
Next, copy and paste the following OnArrived method below the buttonGo_Click event. This will generate a log entry indicating that we have reached our destination, and update the LabelBox text to advise that the Destination has been reached. This NavigationEvent is triggered by the navigator's Arrived event.
private void OnArrived(object sender, NavigationEvent e) { // change the text to indicate arrival lBox.MajorText = "You have arrived at your destination"; _myLog = Log.WithCategory("Arrival Log"); _myLog.Info("Destination has been reached"); }
We now have log objects that will be triggered when:
The buttonGo button is clicked
The navigator is traveling above or below the posted speed limit
The navigator is traveling off course
The navigator has reached its destination
These logs are being generated, but not saved. We will do this now by writing our log entries to a text file.
Edit the Form1 constructor to match the following code. In it we add a new StreamWriter object called LogFile, which will create a text file called log.txt.
public Form1() { LogFile = new StreamWriter("log.txt"); InitializeComponent(); Log.LogEvent += OnLogEvent; }
Note |
---|
The log text file above will be created in the current directory. In our case, this is either the bin\Debug or bin\Release folders of the Visual Studio project folder. To specify a different output location, pass in a path together with the file name. For example: LogFile = new StreamWriter(@"C:\MyLog\log.txt"); |
Our updated Form1 constructor includes a new event handler that calls OnLogEvent. Copy the following OnLogEvent method directly below the Form1 constructor.
This code uses the LogEntry class to create an object representing individual log entries. Each entry is then written to the LogFile, created previously, as plain text.
void OnLogEvent(LogEntry ev)
{
LogFile.Write(ev.ToString());
LogFile.Flush();
}
Run the application. After clicking the 'Go' button your location will be periodically shown on the map as a PushPin, and the map will rotate to match your direction of travel.
Run the application until the destination is reached (~90 seconds), then close it. On your computer, navigate to your Visual Studio project directory's bin\Debug or bin\Release folder (the folder the log file was saved to will depend on your individual project configuration).
Open the log.txt file in the folder to view the log file created. It will include entries for the 'Click Button Log', 'Speed And Location Log' and 'Arrival Log' categories. As we are using a Navigator using a SimulatedGps that travels at the posted speed limit and will not travel off course, our other log entries will not appear.
Note |
---|
The log file will include additional log entries generated automatically by GeoBase, such as direction Instructions and GPS data. |
Log entries are formatted as follows:
<LOG DIRECTION> <TIME STAMP> <THREAD ID> <COMPONENT> - <MESSAGE>
LOG DIRECTION. This indicates the direction of the log. For most GeoBase applications this will be '----', indicating no direction. For some applications, such as GeoStream servers, the direction may also be '>>>>' for logs that are being sent, or '<<<<' for logs that are being received
TIME STAMP. The date and time of the log entry
THREAD ID. The thread process ID number
COMPONENT. The type or name of the log entry
MESSAGE. The content of the log message
For example:
---- 2012-07-31T23:07:53 9 Click Button Log - The buttonGo button has been clicked
In this example, the five components are:
Log Direction: ----
Time Stamp: 2012-07-31T23:07:53.
Thread ID: 9.
Component: Click Button Log.
Message: The buttonGo button has been clicked.
The complete code for this example project is included below:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Telogis.GeoBase.Routing; using Telogis.GeoBase.Navigation; using Telogis.GeoBase.Diagnostics; using Telogis.GeoBase; using System.IO; namespace Logging { public partial class Form1 : Form { private Navigator nav; private RendererList renderList = new RendererList(); private RendererList pins = new RendererList(); private LabelBox lBox = new LabelBox(); private ILogger _myLog; readonly private String LangsPath = Settings.GeoBasePath("langs"); readonly private LatLon StartLocation = new LatLon(33.65856, -117.75528); readonly private LatLon DestinationLocation = new LatLon(33.65006, -117.75523); readonly private StreamWriter LogFile; public Form1() { LogFile = new StreamWriter("log.txt"); InitializeComponent(); Log.LogEvent += OnLogEvent; } void OnLogEvent(LogEntry ev) { LogFile.Write(ev.ToString()); LogFile.Flush(); } private void UpdateLocation(object sender, EventArgs e) { // update current location of Navigator nav.AddPoint(); // determine the speed of the navigator (vehicle) Double vehicleSpeed = Math.Floor(nav.Gps.Position.speed); if (vehicleSpeed > 0) { // determine the speed limit of the street we are traveling on StreetLink currentLink = GeoCoder.ReverseGeoCodeFull(nav.Gps.Position.location).StreetLink; // convert the speed to MPH and remove decimal places double speedLimit = Math.Floor(MathUtil.ConvertUnits(currentLink.Flags.ImpliedSpeedLimit, currentLink.Flags.SPEED_UNITS, SpeedUnit.MilesPerHour)); // get details of the closest street ReverseGeoCodeArgs args = new ReverseGeoCodeArgs(nav.Gps.Position.location); GeoCodeFull streetDetails = GeoCoder.ReverseGeoCodeFull(args); // if the street's details are unknown, log the geocoding error. if (streetDetails == null) { _myLog.Error("ERROR >> Reverse geocoding of GPS position {0} gave an invalid location" , nav.Gps.Position.location); } else { Address nearest = GeoCoder.ReverseGeoCode(nav.Gps.Position.location); // compare the vehicle speed with the posted speed limit // create separate logs for under/over speed limit conditions if (vehicleSpeed > speedLimit) { _myLog = Log.WithCategory("Speed And Location Log"); _myLog.Info("ALERT >> Vehicle over speed limit. Speed: " + vehicleSpeed + " MPH. Location: " + nearest + ". LatLon: " + nav.Location); } else { _myLog = Log.WithCategory("Speed And Location Log"); _myLog.Info("Vehicle speed " + vehicleSpeed + " MPH. Location: " + nearest + ". LatLon: " + nav.Location); } } } // call the UpdateUI method BeginInvoke(new MethodInvoker(UpdateUI)); } private void UpdateUI() { // are we on course for our destination? if (nav.Destination != null) { if (nav.IsOffCourse) { lBox.MajorText = "Off Course!"; _myLog = Log.WithCategory("Course Status"); _myLog.Info("ALERT >> Vehicle traveling off course"); } else { lBox.MajorText = "On Course"; } } // show no more than 20 pins if (pins.Count > 20) { pins.RemoveAt(0); } // add a pin at the navigator location pins.Add(new PushPin(nav.Gps.Position.location)); // display the current address in the labelbox lBox.MinorText = nav.Address.Address.ToString(); // update the map center and heading mapMain.Center = nav.Gps.Position.location; mapMain.Heading = nav.Gps.Position.heading; // invalidate the map, causing a redraw mapMain.Invalidate(); } private void buttonGo_Click(object sender, EventArgs e) { _myLog = Log.WithCategory("Click Button Log"); _myLog.Info("The buttonGo button has been clicked"); // render everything in the list mapMain.Renderer = renderList; // zoom into the map mapMain.Zoom = 0.5; // create the navigator nav = new Navigator(new SimulatedGps(StartLocation), LangsPath, System.Globalization.CultureInfo.CurrentCulture); // set the navigator destination */ nav.Destination = new RouteStop(DestinationLocation); // use the navigator GPS ticks, once per second, to call our eventhandler */ nav.Gps.Update += UpdateLocation; // use the Arrived event to called the OnArrived method */ nav.Arrived += OnArrived; // resize the labelbox & position it at bottom-center of the map */ lBox.Size = new System.Drawing.Size(380, 75); lBox.Top = mapMain.Bottom - lBox.Height - 20; lBox.Left = (lBox.Width + mapMain.Width) / 2 - lBox.Width; // add the navigator to the render list renderList.Add(nav); // add the labelbox and pushpins to the renderlist */ renderList.Add(lBox); renderList.Add(pins); // set the map to render everything on our list */ mapMain.Renderer = renderList; } private void OnArrived(object sender, NavigationEvent e) { // change the text to indicate arrival lBox.MajorText = "You have arrived at your destination"; _myLog = Log.WithCategory("Arrival Log"); _myLog.Info("Destination has been reached"); } } }