Click or drag to resize

Autocomplete Geocoder Tutorial

Verizon Connect Logo
Print this page
Learn more about Verizon Connect GeoBase.
Get information about the latest release
Overview

The AutocompleteGeocoder class takes partial address strings, and generates a list of suggestions of full addresses. This can be used to create an interactive address search, where suggested addresses are displayed in near real-time as users type, potentially allowing the correct address to be selected before the user has keyed the full address string.

In this tutorial, we will create a Windows Forms application with an address search box, and a list of address suggestions that updates as the search string changes. These suggestions can be selected with the mouse, and the corresponding location will then be displayed on a map.

Note Note

The AutocompleteGeocoder requires specific GBFS files:

  • If you are using a file from before Q3 2014, ensure there is an _acgc suffix immediately prior to the .gbfs extension, for example HERE_USAWestUSA_14Q1_acgc.gbfs.
  • If you are using a file from Q3 2014 to Q2 2017, ensure that there is an uppercase A among the letters immediately prior to the .gbfs extension, for example HERE_USAWestUSA_15Q4_30x151208-1141_CaIAZ.gbfs. Most GBFS files that are from Q3 2014 or later include Autocomplete Geocoder data by default.
  • If you are using a file from Q3 2017 onward, ensure that there is an uppercase L among the letters immediately prior to the .gbfs extension, for example HERE_USAWestUSA_17Q3_30x151208-1141_ICLP.gbfs. If you are using these files, you must also use GeoBase version 4.31 or later.
  • If you would like to use data from 17Q3 onward with a GeoBase version prior to 4.31, contact GeoBase support via gbsupport@verizonconnect.com to request a custom GBFS file.
  • If you do not have the appropriate file, contact GeoBase support via gbsupport@verizonconnect.com to request a trial.
Prior Knowledge

This tutorial assumes you are familiar with setting up a simple GeoBase application. If you have never created a GeoBase application before, you may want to read through the following topics first:

Efficient use of the AutocompleteGeocoder requires your application to make use of an asynchronous, thread-based structure. This tutorial assumes basic familiarity with threading in .NET, and the approach has been structured to be as simple as possible, obviating the need for locks or cross-thread invocation. If you are unfamiliar with using threading in .NET, we would recommend reading a primer such as Threading in C# by Joseph Albahari, which the author has made freely available online.

Creating the Application & Laying Out the UI

Create a new Visual Studio Project containing a Windows Forms Application, targeting .NET Framework 4.0 or higher. Name your project AutocompleteGeocoderExample. Add a reference to geobase.net.dll, then add the following controls to your form:

  • A MapCtrl named mapMain - this will display the location of suggested addresses.
  • A TextBox named textBoxSearch - this is where we will enter the addresses we want to search for.
  • A ListBox named listBoxResults - this will display the current address suggestions.
  • A Label named labelStatus - this will display the SearchResult value indicating the success of the most recent search.

A suggested layout is in the screenshot below. Note that to achieve this layout you will need to set the AutoSize property of labelStatus to false, then set the control's Size, TextAlign and Border properties appropriately.

acgc example layout

Using the events view on the Properties panel, double click to create handler stubs for the following events:

  • The TextChanged event of textBoxSearch
  • The SelectedIndexChanged event of listBoxResults
Implementing the Code

Switch to the code view for your form, and add the following using directives to the top of the project form:

C#
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using Telogis.GeoBase;
using Telogis.GeoBase.Geocoding;
using Telogis.GeoBase.Repositories;
using Telogis.GeoBase.Addresses;

First, we will set up a BackgroundWorker, which lets us send items of work to be processed on a background thread. Add the following properties to the Form1 class:

C#
private BackgroundWorker geocodeWorker = new BackgroundWorker();
private AutocompleteGeocoderArgs nextSearch = null;
private const int SEARCH_TIMEOUT_SECS = 1;

This constructs our BackgroundWorker, geocodeWorker, and sets up a property and a constant that will be required shortly.

Next, we need to create a repository that points to the appropriate map data file containing Autocomplete Geocoding data.

Additionally, we need to set up the DoWork and RunWorkerCompleted handlers for geocodeWorker. DoWork is the engine of our application, receiving items of work to process asynchronously on a background thread. When this work is completed, the RunWorkerCompleted handler is triggered on the UI thread, allowing UI components to be updated with the new information.

In the Form1 constructor, after the call to InitializeComponent(), add the following lines:

C#
// Set the map data to point at data containing ACGC data.
SimpleRepository repository = new SimpleRepository(@"[Path to the GBFS data file]");
Repository.Default = repository;

// Set up the BackgroundWorker handlers.
geocodeWorker.DoWork += new DoWorkEventHandler(geocodeWorker_DoWork);
geocodeWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(geocodeWorker_RunWorkerCompleted);
// Ensure the UI has the correct focus on startup.
textBoxSearch.Focus();

Next, we will define the DoWork handler - a stub for this may have been created automatically in the previous step; if not, then create it using the code sample below. This handler receives an AutocompleteGeocoderArgs object passed in as an argument, runs this through the AutocompleteGeocoder to generate suggestions, and provides an AutocompleteGeocoderResult to be passed to the RunWorkerCompleted handler.

C#
void geocodeWorker_DoWork(object sender, DoWorkEventArgs e) {
    // Set the Autocomplete Geocoder arguments and get the results of the Autocomplete Geocoding operation.
    AutocompleteGeocoderArgs args = (AutocompleteGeocoderArgs)e.Argument;
    AutocompleteGeocoderResult result = AutocompleteGeocoder.Geocode(args);
    e.Result = result;
}

Now we need a way of feeding searches into this background engine. We will do this by detecting changes to the text in textBoxSearch, and constructing AutocompleteGeocoderArgs objects to pass to geocodeWorker for processing. Update your textBoxSearch_TextChanged handler to contain the following:

C#
private void textBoxSearch_TextChanged(object sender, EventArgs e) {
    // Get the text from the search box.
    string searchTerm = (sender as TextBox).Text;

    // Build an AutocompleteGeocoder argument object to pass to the background thread.
    AutocompleteGeocoderArgs args = new AutocompleteGeocoderArgs {
        Query = searchTerm,
        Countries = new Country[] { Country.USA },
        LocationHint = this.mapMain.Center,
        Timeout = TimeSpan.FromSeconds(SEARCH_TIMEOUT_SECS)
    };

    if (!geocodeWorker.IsBusy && nextSearch == null) {
        // If background worker isn't running at present, and there is nothing
        // scheduled to run next, run straight away.
        geocodeWorker.RunWorkerAsync(args);
    } else {
        // Otherwise, set to run next. This will replace
        // anything previously scheduled to run next.
        nextSearch = args;
    }
}

Our geocodeWorker can only handle one request at a time, so we are checking if it is busy before telling it to action our AutocompleteGeocoderArgs object. If so, we will instead store this object in the nextSearch property, to be picked up when the current search has completed.

So, we can now feed requests into geocodeWorker, and we have defined the engine that will process them. Next, we need to pick up the results produced by the this engine, and display them in our list box. To do this, we need to define the geocodeWorker_RunWorkerCompleted handler - a stub for this may have been created automatically in an earlier step; if not, then create it using the code sample below.

C#
void geocodeWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    // Clear the ListBox of any old suggestions
    listBoxResults.Items.Clear();

    // Get the result of the Autocomplete Geocoding operation.
    AutocompleteGeocoderResult result = (AutocompleteGeocoderResult)e.Result;

    // Update the status label text, and color - green for Complete, red for anything else
    labelStatus.Text = result.Status.ToString();
    labelStatus.BackColor = (result.Status == SearchResult.SearchCompleted) ? Color.LightGreen : Color.Tomato;

    // Add suggestions to ListBox
    listBoxResults.Items.AddRange(result.Suggestions);
    listBoxResults.SelectedIndex = -1;

    // If there's more work waiting, kick it off on the background thread
    if (!geocodeWorker.IsBusy && nextSearch != null) {
        geocodeWorker.RunWorkerAsync(nextSearch);
        nextSearch = null;
    }
}

The Status property indicates how the search concluded, and indicates whether the output provided should be considered comprehensive. Values are taken from the SearchResult enumeration. Those which are relevant for the AutocompleteGeocoder are:

  • SearchCompleted - the search completed successfully. The results returned should be considered comprehensive.
  • TooManyResults - indicates that the search detected a large number of potential results, and the number of suggestions was limited by the results limit. Results returned should not be considered comprehensive, and further qualification of the search string might reveal results missing from this suggestion set.
  • TooGeneric - indicates that the search query was too generic, e.g. the query was too short. Searching was not performed or was only partially performed. If there were any results returned, they should not be considered comprehensive, and further qualification of the search string might reveal results missing from this suggestion set.
  • Timeout - the search timed out. Results returned should not be considered comprehensive. If you are seeing this status frequently, you may need to increase the timeout value passed as an argument to the AutocompleteGeocoderArgs constructor; in this example we are using 1 second, as defined in the SEARCH_TIMEOUT_SECS constant.
  • SearchCancelled - indicates that the search was cancelled using the Cancel method. Results should not be considered comprehensive.

So, we can now enter search strings in our TextBox, and see suggestions appear in our ListBox. The final step is to let us select suggestions from the ListBox and see them displayed on the map. We will use BalloonPushPin objects for this, and adjust the map zoom and center to appropriately display each type of suggestion. Populate your listBoxResults_SelectedIndexChanged handler with the following code:

C#
private void listBoxResults_SelectedIndexChanged(object sender, EventArgs e) {
    // Get the selected search result.
    AutocompleteGeocoderSuggestion suggest = (AutocompleteGeocoderSuggestion)listBoxResults.SelectedItem;

    // Results will usually have a location, although some region results only have a BoundingBox. We will
    // place our pin at Location if available, BoundingBox centroid if not.
    LatLon loc = suggest.Location != LatLon.Empty ? suggest.Location : suggest.BoundingBox.Center;

    // Check what type of result this is.
    string resultType;
    if (suggest.ResultType == AutocompleteGeocoder.ResultType.Region) {
        resultType = "Region Result";
    } else if (suggest.ResultType == AutocompleteGeocoder.ResultType.PostCode) {
        resultType = "Postcode Result";
    } else if (suggest.ResultType == AutocompleteGeocoder.ResultType.Street) {
        resultType = "Street Result";
    } else {
        resultType = "Unknown Result";
    }

    // Place a marker on the map containing the location's details.
    BalloonPushPin bpp = new BalloonPushPin(resultType, loc);
    bpp.Information = string.Join("\n", AddressFormatter.Default.GetLines(suggest.Address, string.Empty));
    mapMain.Renderer = bpp;

    // Zoom to the BoundingBox provided by the region or to the street level of the location.
    if (suggest.BoundingBox != null) {
        mapMain.ZoomToBoundingBox(suggest.BoundingBox, 20);
    } else {
        mapMain.Center = suggest.Location;
        mapMain.Zoom = ZoomLevel.StreetLevel;
    }

    mapMain.Invalidate();
}
Testing

Run your application. Start typing an address from the regions available in your USA data files. You should see the ListBox populate with suggestions, and the Label text/color change to indicate search status. Click on a suggestion to see it displayed on the map.

acgc example testing
Full Example Code
C#
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using Telogis.GeoBase;
using Telogis.GeoBase.Geocoding;
using Telogis.GeoBase.Repositories;
using Telogis.GeoBase.Addresses;

namespace AutocompleteGeocoderExample {

    public partial class Form1 : Form {

        private BackgroundWorker geocodeWorker = new BackgroundWorker();
        private AutocompleteGeocoderArgs nextSearch = null;
        private const int SEARCH_TIMEOUT_SECS = 2;

        public Form1() {
            InitializeComponent();

            // Set the map data to point at data containing ACGC data.
            SimpleRepository repository = new SimpleRepository(@"C:\Program Files (x86)\Telogis\GeoBase\bin\HERE_USAWestUSA_15Q4_30x151208-1141_CaIAZ.gbfs");
            Repository.Default = repository;

            // Set up the BackgroundWorker handlers.
            geocodeWorker.DoWork += new DoWorkEventHandler(geocodeWorker_DoWork);
            geocodeWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(geocodeWorker_RunWorkerCompleted);
            // Ensure the UI has the correct focus on startup.
            textBoxSearch.Focus();

        }

        void geocodeWorker_DoWork(object sender, DoWorkEventArgs e) {
            // Set the Autocomplete Geocoder arguments and get the results of the Autocomplete Geocoding operation.
            AutocompleteGeocoderArgs args = (AutocompleteGeocoderArgs)e.Argument;
            AutocompleteGeocoderResult result = AutocompleteGeocoder.Geocode(args);
            e.Result = result;
        }

        void geocodeWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            // Clear the ListBox of any old suggestions
            listBoxResults.Items.Clear();

            // Get the result of the Autocomplete Geocoding operation.
            AutocompleteGeocoderResult result = (AutocompleteGeocoderResult)e.Result;

            // Update the status label text, and color - green for Complete, red for anything else
            labelStatus.Text = result.Status.ToString();
            labelStatus.BackColor = (result.Status == SearchResult.SearchCompleted) ? Color.LightGreen : Color.Tomato;

            // Add suggestions to ListBox
            listBoxResults.Items.AddRange(result.Suggestions);
            listBoxResults.SelectedIndex = -1;

            // If there's more work waiting, kick it off on the background thread
            if (!geocodeWorker.IsBusy && nextSearch != null) {
                geocodeWorker.RunWorkerAsync(nextSearch);
                nextSearch = null;
            }
        }

        private void listBoxResults_SelectedIndexChanged(object sender, EventArgs e) {
            // Get the selected search result.
            AutocompleteGeocoderSuggestion suggest = (AutocompleteGeocoderSuggestion)listBoxResults.SelectedItem;

            // Results will usually have a location, although some region results only have a BoundingBox. We will
            // place our pin at Location if available, BoundingBox centroid if not.
            LatLon loc = suggest.Location != LatLon.Empty ? suggest.Location : suggest.BoundingBox.Center;

            // Check what type of result this is.
            string resultType;
            if (suggest.ResultType == AutocompleteGeocoder.ResultType.Region) {
                resultType = "Region Result";
            } else if (suggest.ResultType == AutocompleteGeocoder.ResultType.PostCode) {
                resultType = "Postcode Result";
            } else if (suggest.ResultType == AutocompleteGeocoder.ResultType.Street) {
                resultType = "Street Result";
            } else {
                resultType = "Unknown Result";
            }

            // Place a marker on the map containing the location's details.
            BalloonPushPin bpp = new BalloonPushPin(resultType, loc);
            bpp.Information = string.Join("\n", AddressFormatter.Default.GetLines(suggest.Address, string.Empty));
            mapMain.Renderer = bpp;

            // Zoom to the BoundingBox provided by the region or to the street level of the location.
            if (suggest.BoundingBox != null) {
                mapMain.ZoomToBoundingBox(suggest.BoundingBox, 20);
            } else {
                mapMain.Center = suggest.Location;
                mapMain.Zoom = ZoomLevel.StreetLevel;
            }

            mapMain.Invalidate();
        }

        private void textBoxSearch_TextChanged(object sender, EventArgs e) {

            // Get the text from the search box.
            string searchTerm = (sender as TextBox).Text;

            // Build an AutocompleteGeocoder argument object to pass to the background thread.
            AutocompleteGeocoderArgs args = new AutocompleteGeocoderArgs {
                Query = searchTerm,
                Countries = new Country[] { Country.USA },
                LocationHint = this.mapMain.Center,
                Timeout = TimeSpan.FromSeconds(SEARCH_TIMEOUT_SECS)
            };

            if (!geocodeWorker.IsBusy && nextSearch == null) {
                // If background worker isn't running at present, and there is nothing
                // scheduled to run next, run straight away.
                geocodeWorker.RunWorkerAsync(args);
            } else {
                // Otherwise, set to run next. This will replace
                // anything previously scheduled to run next.
                nextSearch = args;
            }
        }
    }
}