Where Open Voice meets Geocoding

Geocoding is hot. "Geocoding is the process of assigning geographic identifiers (e.g., codes or geographic coordinates expressed as latitude-longitude) to map features and other data records, such as street addresses. You can also geocode media, for example where a picture was taken, IP addresses, and anything that has a geographic component. With geographic coordinates the features can be mapped and entered into Geographic Information Systems" - wikipedia. Using the MapQuest API we can geocode any address in the world. In my recent article on Open Voice I showed how to use C# and .NET to build a Voice over IP phone. I've also been exploring the possibilities of the MapQuest .NET API on my blog. Now wouldn't it be great if those two worlds came together to build a phone that displays the location of the person you're calling? I call it the AIM Map Phone. And it looks like this:

So what are the technologies used to build this phone? Well I found a commercial offering by Conaito which combines Voice over IP with and SIP programming stack: the VoIP SIP SDK. The SDK offers excellent .NET examples and implementing a basic VoIP phone took about an hour, not bad right? Having the phone working, the next step is to geocode the phone number. This turned out to be a two step process. ServiceObjects is a company which offers an XML web service for translating a phone number into an address. There are two separate services, DOTS GeoPhone which is for landlines and DOTS GeoPhone Wireless for wireless phones. The translated address can be used to find the geographical location using the MapQuest Geocode Server. The resulting coordinates can be used by the MapQuest Static Map Server to create a map of the location.
Let's look at some of the code involved.

Making the call

In order to place a call using the AIM Call Out service we need to use the Open Voice API. The Open Voice API consists of an infrastructure which supports a number of open standards, allowing any standards compliant application or SDK to make use of the infrastructure. The Conaito VoIP SDK supports these standards and works like a charm with AIM Call Out. The client API is accessible from C# / .NET by dragging an ActiveX control onto the form. Calls are made by programming against this ActiveX control. In the AIM Map Phone the 'Dial' button works as a toggle and uses the following code:

  private bool _calling = false;
  private int _callId = -1;
  private void btnDial_Click( object sender, EventArgs e )
    {
    if ( _calling == true )
    {
    // hang up
    HangUp();
    }
    else
    {
    // make call
    DialNumber( txtNumber.Text );
        // ... update map ...
    }
    }
  private void DialNumber( string phoneNumber )
    {
    try
    {
    // check if previous call closed down correctly
    if ( _callId != -1 )
    {
    HangUp();
    }
    // First of all transport must be configured.
    this.axUserAgent.AddTransport( conaito.Transport.UDP, 5060 );
         // Find available RTP (media) port
    int port = this.axUserAgent.FindPort( 4000, 8000, 2, conaito.Transport.UDP
      );
         // Startup User Agent
    this.axUserAgent.Startup( port, 1,
    String.Empty,
    PhoneSetting.Default.STUNServer );
         // AIM account is used for SIP authentication
    string aimAccount = PhoneSetting.Default.AIMAccount;
    // name is used for registration
    string name = aimAccount.Substring( 0, aimAccount.IndexOf( "@" )
    );
        // REGISTER is not support by SIP.AOL.COM
    // but is required to initialize the axUserAgent
    this.axUserAgent.Registrator.AuthenticationId = aimAccount;
    this.axUserAgent.Registrator.Register(
    PhoneSetting.Default.SIPServer,
    name,
    PhoneSetting.Default.AIMDevicePassword,
    name );
        // Setup eventhandler for cleanup when call terminates
    this.axUserAgent.OnTerminated += new Axconaito._IUserAgentEvents_OnTerminatedEventHandler(
      axUserAgent_OnTerminated );
        // Make the call (asynchronous)
    // Number needs to be post fixed by "@sip.aol.com"
    _callId = this.axUserAgent.CallMaker.Invite(
    phoneNumber + "@" + PhoneSetting.Default.SIPServer );
       // Update UI
    _calling = true;
    this.btnDial.Text = "Hang up";
    }
    catch ( COMException exception )
    {
    ShowError( this.axUserAgent.LastError, exception.Message );
    }
    catch ( Exception exception )
    {
    ShowError( exception.Message, "General Exception" );
    }
    }
    private void HangUp()
    {
    // Hang up
    this.axUserAgent.CallMaker.Hangup( _callId );
    // Shutdown User Agent
    this.axUserAgent.Shutdown();
    _callId = -1;
    // Update UI
    _calling = false;
    this.btnDial.Text = "Dial";
    }
void axUserAgent_OnTerminated( object sender, Axconaito._IUserAgentEvents_OnTerminatedEvent
    e )
    {
    HangUp();
    }
    

The DialNumber methods follows the basic steps needed to start a call:

  1. Add a transport mechanism, in this case UDP.
  2. Prepare for RTP transport
    1. Find a port
    2. Startup the RTP host

      Note that the SDK allows for all sorts of custom tweaking of codecs, volume and input/output devices. This sample is using the default settings for all of those.
  3. Setup an eventhandler for when the call terminates.
  4. Send a SIP INVITE message to call the actual number.

    Note that the format of the phonenumber needs to be <phonenumber>@sip.aol.com.

As you can see the sample retrieves configuration settings from the app.config file. These can be set using the Phone Settings configuration form:


Geocode the number

Using the ServiceObjects XML web service the phone number can be translated into an address. There are two API's one for landlines and one for wireless phones. It must be said that the service for landlines is excellent, but the one for wireless phone does not seem to know all the numbers, especially for where I live, numbers in Maine.

  private bool _calling = false;
  private int _callId = -1;
private void btnDial_Click( object sender, EventArgs e )
    {
    if ( _calling == true )
    {
    // hang up
    HangUp();
    }
    else
    {
    // make call
    DialNumber( txtNumber.Text );
    GeoAddress address = GetAddress( txtNumber.Text );
    if ( address == null )
    {
    // unable to resolve phone number
    MessageBox.Show( this,
    "Phone number could not be translated to a geographical location.",
    "Information", MessageBoxButtons.OK, MessageBoxIcon.Information );
    }
    else
    {
    UpdateMap( address );
    }
    }
    }
private GeoAddress GetAddress( string phoneNumber )
    {
    string americanPhoneNumber = phoneNumber.Replace( "+1", "" );
    PhoneInfo info;
    ServiceObjects.DOTSGeoPhone service = new DOTSGeoPhone();
    info = service.GetPhoneInfo( americanPhoneNumber, ServiceObjectSetting.Default.DOTSGeoPhoneLicenseKey
    );
    if ( info != null &&
    info.Contacts != null &&
    info.Contacts.Length > 0 )
    {
    Address address = new Address()
    {
    City = info.Contacts[0].City,
    Country = "US",
    PostalCode = info.Contacts[0].Zip,
    State = info.Contacts[0].State,
    Street = info.Contacts[0].Address
    };
        GeocodeServerProxy proxy = new GeocodeServerProxy();
    List<GeoAddress> result = proxy.Geocode( address );
    if ( result != null && result.Count > 0 )
    {
    return result[0];
    }
    return null;
    }
    else
    {
    return GetWirelessAddress( americanPhoneNumber );
    }
    }

From a .NET perspective the service works really, really simple. Just add a web reference from your project (don't use VS 2008 Service Reference) to the DOTS GeoPhone service ( http://trial.serviceobjects.com/gp/GeoPhone.asmx?WSDL ), use the generated proxy and call the GetPhoneInfo service. Note: You will need to get a license key.

The PhoneInfo object that is returned contains zero, one or more contacts. I the sample I take the first and use that to create a MapQuest .NET API Address object. This is passed to the GeocodeServerProxy, a custom proxy class I created (read this blog post for more details). This results in the GeoAddress of the phone number. Should the phone number not lead to a contact, then I try and use a similar method using the DOTS GeoPhone Wireless service.

The GetAddress method uses the app.config to retrieve the license key for the ServiceObjects service. These can be set with the ServiceObjects Settings screen:


Display the location

The AIM Map Phone uses a custom Winforms control which will load and display a map based on the GeoAddress. The code is based on the MapQuest .NET 5.3 API, see this blog post on more information on building such a control. You will need a MapQuest account in order to use the service. The information can be set in the MapQuest Settings screen:


The code

Download the code for the sample here.
In order to compile and run the code you will need to install VoIP SIP SDK v2.6 from Conaito. You will also need a (trial) account DOTS GeoPhone * DOTS GeoPhone Wireless from ServiceObjects.

Happy coding!