(Not Signed In)

Sign In

Resources

A Simple Mobile Navigation Application Using the NavigationManager Class - Part 2

Introduction

This is the second of three tutorials focusing on designing mapping applications for mobile devices; the first tutorial in this series can be found here.

In this tutorial we'll use a drill-down geocoder to search for an address. A drill-down geocoder provides an efficient and intuitive means of searching for an address, and doesn't require the use of a keyboard, which makes it particularly useful for mobile applications. After the address has been found we'll use a real GPS unit to guide the user of a mobile device to the address.

Also in this tutorial we'll combine the drill-down geocoder with a POI (point of interest) search, which will allow the user to search and display on the map POIs such as, gas stations, hospitals, restaurants and tourist sites.

Design the form

Please note: it is important that all modifications to the form layout are carried out in the mobile solution, and not the desktop solution. Use the mobile solution to alter form layout, and the desktop solution for code changes and additions.

Open the mobile application project created in the previous tutorial. Open the Form Designer and add the following components on top of the MapCtrl:

The layout of these components is shown below, in Figure 1. It can be seen, on the left-hand side, the listBox covering over the map control. Use the controls to completely cover the map control; we will add a toggle in the next section to view or hide the map.


Figure 1 - Form layout

Show and hide the map

In this section we'll add code to show and hide the map. By default, the map will be hidden, so add the next line of code to the Form1_Load method:

mapMain.Visible = false;

In the form designer, double-click the 'Toggle Map' menu item (a sub-item of the 'Map' menu) and add the following code to the click event handler:

listBoxResults.Visible =
  textBoxSearch.Visible =
  radioButtonGeocode.Visible =
  radioButtonPOI.Visible = mapVisible;

mapMain.Visible = mapVisible = !mapVisible;

The code above relies on the boolean 'mapVisible' toggling state on the menu item's click event. Add the following to your Form1:

bool mapVisible = false;

Drill-Down Geocoder

The drill-down geocoder works by allowing the user to select successively smaller regions, 'drilling-down' towards a target street. To demonstrate this procedure, consider the following drill-down:

At the conclusion of this search, Enterprise (in Aliso Viejo, Orange, California) will have been located. 'California', 'Orange' and 'Aliso Viejo' are all regions, whereas 'Enterprise' is a street. California (a top-level region) is assigned a region level of zero. Each child region has a region level one higher than its parent region. For example, Aliso Viejo has a region level of 2.

The code below will request an array of regions in a given region level (using the GetRegions() method). If the user selects a region from the list box, we'll set that region in the drill-down geocoder (using the SetRegion() method). After all regions have been set, we will perform a street search (using the GetStreets() method).

Code

Add the following member properties to your Form1 class, in 'Form1.cs'.

// for drill-down geocoder
DrillDownGeoCoder ddgc;
int regionLevel;
BalloonPushPin bpp = new BalloonPushPin(new LatLon());

Add the following code snippet to the end of the 'Form1_Load' method.

// change the country if you're not in the USA
ddgc = new DrillDownGeoCoder(Country.USA);

// this will be incremented as we drill-down towards street level
regionLevel = 0;

// load top-level regions into our listbox
filter();

// draw the balloon push pin on the map
renderList.Add(bpp);

Add a 'text changed' event to textBoxSearch. Modify the text-changed method to contain only the following code. This will update the regions, and streets, displayed in the listbox when the user enters search text.

filter();

Modify the click method of the reset menu item to contain only the following code. This code will reset any selections and input given by the user.

ddgc.Reset();
regionLevel = 0;
listBoxResults.Items.Clear();
textBoxSearch.Text = "";
filter();

The following filter() method will use the contents of the textbox to display regions at the selected region level. Regions will be displayed in the listbox. Later, we'll add a code snippet to allow the user to select a region from the listbox.

private void filter() {

string filterText = textBoxSearch.Text;

// If all regions have been set, search for a street
if (regionLevel >= ddgc.NumRegionLevels)  {
    listBoxResults.Items.Clear();
    if (filterText.Length == 0) filterText = "a";

    // Get an array of streets
    StreetData[] sd = ddgc.GetStreets(filterText).Results;

    // Add the filtered streets to the listbox
    for (int i = 0; i < sd.Length; i++)
      listBoxResults.Items.Add(sd[i].ToString());
  }

  // ...otherwise, set remaining region levels
  else if (regionLevel < ddgc.NumRegionLevels) {
    listBoxResults.Items.Clear();

    // Get an array of filtered regions
    RegionData[] rd;
    if (filterText.Length > 0)
      rd = ddgc.GetRegions(regionLevel, filterText).Results;
    else
      rd = ddgc.GetRegions(regionLevel).Results;

    // Add the filtered regions to the listbox
    for (int i = 0; i < rd.Length; i++)
      listBoxResults.Items.Add(rd[i].ToString());
  }
}

Add a 'selected index changed' event to listBoxResults. This event will allow the user to select a region, and then present a list of all child regions in the selected region. Modify the event to match the following:

private void listBoxResults_SelectedIndexChanged(object sender, EventArgs e) {

  if (listBoxResults.SelectedItem != null) {
    // Are we at region level? or street level?
    if (regionLevel < ddgc.NumRegionLevels){
      // At region level. Set the selected region
      RegionData rd = ddgc.GetRegions(
      regionLevel,
      listBoxResults.SelectedItem.ToString()).Results[0];

      ddgc.SetRegion(rd);
      regionLevel++;
      textBoxSearch.Text = "";

      // Center and zoom the map on the selected region -- as we
      // drill down the map will zoom closer and closer.
      mapMain.Center = rd.Location;
      int zoom = 4 * (ddgc.NumRegionLevels - regionLevel);
      mapMain.Zoom = 1 + zoom; // = 1 at street level
      filter(); // Filter on the next region level
    }
    else {
      // Show the location on the map
      string filterText;
      string address = listBoxResults.SelectedItem.ToString();

      if (textBoxSearch.Text.Length == 0) filterText = "a";
      else filterText = textBoxSearch.Text;

      StreetData[] sd = ddgc.GetStreets(filterText).Results;
      LatLon ll = sd[listBoxResults.SelectedIndex].GetLocation();
      mapMain.Center = ll;
      mapMain.Zoom = 0.8;

      // Put a balloon at the address
      bpp.Name = "Drill-Down Search Result";
      bpp.Information = address;
      bpp.Location = ll;
    }
  }
}

GPS

In the previous tutorial we used a StaticGps and GpsSimulator to test our application. GeoBase provides a number of classes to interface with different GPS devices. These classes include:

Code

In this tutorial, we'll conditionally use a simulated GPS device (if we're on a desktop computer) or a CEGps (if we're on a mobile device). The CEGps class interfaces with a built-in GPS unit on a Windows CE mobile device. Remove the startRoute() method, and the following line of code from the 'Form1_Load' method.

startRoute();

Create a 'Route to PushPin' click event and add the following code, which will set the GPS unit if the balloon push pin is set to a valid location.

if (bpp.Location.IsValid){
  #if DESKTOP
    nMan.SetGps(new StaticGps(33.71581, -117.77641));
    //Set destination
    nMan.SetDestination(new LatLon(bpp.Location));
    //Configure navigation manager
    nMan.SetGps(new GpsSimulator(nMan.Navigator.Directions, 2));
  #else
    //Set destination
    nMan.SetDestination(new LatLon(bpp.Location));
    nMan.SetGps(new CEGps());
  #endif
}

For more information on using GPS devices with GeoBase, read the Navigation Tutorials in the GeoBase reference documentation (GeoBase | GeoBase Tutorials | Navigation Tutorial). The reference documentation is available online at http://docs.geobase.info/.

Testing

Build and run your application. You can drill-down through successive region levels either by selecting from the listbox, or by typing in the textbox. When a single address is selected, click on 'Toggle Map' to see the location displayed on the map, and indicated by the balloon pushpin.


Figure 2 - Testing

Then, click 'Map | Route to PushPin' to be given turn-by-turn directions from your current location to the address.

You can re-open your mobile project and build and run your mobile application. You'll see that it has exactly the same functionality and behavior as your desktop application! You'll note that the desktop version of the application is significantly faster to test than the mobile version of the application.

POI Query

Points of interest (POIs) are becoming an important feature of modern mapping applications. POIs can include a wide variety of types, most commonly gas stations, fast-food franchises, tourist sites, hospitals, police stations and other significant locations. In this tutorial, we'll perform a simple 'data query' (search) to obtain a list of all POIs in a specified area. We'll use a listbox to filter the POIs by type (e.g., 'gas station').

Code

Add the following namespace directive:

using System.Reflection;

to the top of your source code file. This is necessary because the Enum.GetValues() method is not supported in the Compact Framework. Instead, we must use a workaround that is compatible for both the desktop and mobile versions of our application.

Add the following code snippet to the start of the filter() method, after the 'string filterText...' line. This code snippet will load the listBox with all the POI types and prevent the drill-down geocoder from displaying regions, if the 'POI' radio button is selected.

if (radioButtonPOI.Checked) {
    // add names of POI types to listbox
    listBoxResults.Items.Clear();
    Enum myPOIs = new PoiType();
   
    foreach (FieldInfo fieldInfo in myPOIs.GetType().GetFields(
      BindingFlags.Static | BindingFlags.Public)){
        Enum item = (Enum)fieldInfo.GetValue(myPOIs);
        listBoxResults.Items.Add(item.ToString());
    }
     
    return;//break
}

Add a 'CheckedChanged' event to 'radioButtonGeocode'. Add the following line of code to the event's method:

filter();

This will force the contents of the listbox to change, reflecting the user's choice between a drill-down geocoder and POI search.

Add the following code snippet to the start of the listBox's SelectedIndexChanged event. This code snippet prevents the drill-down geocoder from setting a region level (or street) when the 'POI' radio button is selected.

if (radioButtonPOI.Checked && listBoxResults.SelectedItem != null){
    // Get the selected POI type from the listbox
    PoiType pt = (PoiType)System.Enum.Parse(
        typeof(PoiType),
        listBoxResults.SelectedItem.ToString(),
        false);

    PoiType[] pts = { pt };

    // Get a bounding box covering the visible map area
    LatLon ll_one = new LatLon(mapMain.XYtoLatLon(320, 240));
    LatLon ll_two = new LatLon(mapMain.XYtoLatLon(0, 0));
    BoundingBox bbox = new BoundingBox(ll_one, ll_two);
    bbox.InflateBy(2); // increase the area of the box by x4

    // Search for POIs only of the given type
    Poi[] pois = DataQuery.QueryPoi(bbox, pts, "");

    // Add each POI as a balloon pushpin
    renderList.Clear();
   
    for (int i = 0; i < pois.Length; i++) {
        BalloonPushPin pp = new BalloonPushPin(pois[i].Location);
        pp.Name = pois[i].Name;
        pp.Information = pois[i].Phone;
        renderList.Add(pp);
    }

    mapMain.Renderer = renderList;
    return;
}

Testing

Build and run your application. At any time you can click the 'POI' radio button, then select a POI type to be shown all POIs of that type in the visible map area.


Figure 3 - Testing

Conclusion

This tutorial has demonstrated how to create and use a drill-down geocoder to quickly, and efficiently, search for a location. Once the location has been found, GeoBase's POI query capabilities were used to identify nearby POIs.

We trust that you enjoyed the simplicity and ease-of-use afforded by the GeoBase .NET component.

Published, Jun 20th, 20:09

Tagged under: dotnet mobile geobase routing navigation forward geocoding