Introduction

Telogis GeoBase's 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.

Prerequisites

This tutorial assumes familiarity with Microsoft Visual Studio. You will need a copy of Visual Studio 2005 or later and a moderately specified desktop computer. You will also need a licensed copy of the Telogis GeoBase SDK. A 30-day free-of-charge trial may be downloaded from the GeoBase developer portal: http://dev.telogis.com/. Two versions of the trial SDK are available, one loaded with US (West Coast) map data, the other version loaded with map data for the UK. The locations used in this tutorial assume US data.

This tutorial also assumes that you are already familiar with the basics of creating a GeoBase-specific Visual Studio Project and adding a map control to a GeoBase project.

The Application

Create a new Visual Studio Project (a Windows Forms Application) called 'Logging'.

Add a reference to geobase.net.dll then, to simplify our code, add the following usings 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:

  • • A GeoBase map control named mapMain
  • • A button named buttonGo

When run, your new application should appear similar to the screenshot below:

Application Example Framework

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:

  • • check if the Navigator is on course for the set Destination and log whenever it is found to be traveling off course
  • • occasionally place a PushPin at the Navigator's location
  • • center and rotate the map to match the Navigator's location and heading
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();
}

Testing

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 Format

<LOG DIRECTION> <TIME STAMP> <THREAD ID> <COMPONENT> - <MESSAGE>

  1. 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
  2. TIME STAMP. The date and time of the log entry
  3. THREAD ID. The thread process ID number
  4. COMPONENT. The type or name of the log entry
  5. 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:

  1. Log Direction: ----
  2. Time Stamp: 2012-07-31T23:07:53.
  3. Thread ID: 9.
  4. Component: Click Button Log.
  5. Message: The buttonGo button has been clicked.

Complete Code

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");
        }
    }
}

Published, Jul 7th 2016, 22:13

Tagged under: dotnet geobase routing map data navigation maps