MapQuest Developer Blog

Archives for Mark Blomsma

  • Creating a custom ASP.NET control for MapQuest

    In my last post we saw that using the MapQuest NET API version 5.3 in an ASP.NET web application was not all that much harder than using it in a winforms application. Now in order to easily reuse some of the code it would be nice to create an ASP.NET custom control which displays a map just based on an origin and a destination.

    So what to do?

    1. We want to put the control in a class library, so we add an item of type 'ASP.NET Server control named 'RouteMapControl'. This will create a class which inherits from WebControl.
    2. Next we add an Origin and Destination propert of type 'GeoAddress' (from the MQClientInterface namespace).
    3. Lastly we override the 'protected override void RenderContents( HtmlTextWriter output )' method and have it use the Origin and Address to create a route and use the route to create a map.

    This is what the code for the class looks like:

    using System;
    using System.ComponentModel;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using MQClientInterface;
    using DevelopOne.MapQuest.Proxies;
    using DevelopOne.MapQuest.Extensions;
    
    namespace DevelopOne.MapQuest.Web.Controls
    {
        [DefaultProperty( "Text" )]
        [ToolboxData( "{0}:RouteMapControl>" )]
        public class RouteMapControl : WebControl
        {
            [Bindable( true )]
            [Category( "MapQuest" )]
            [DefaultValue( "" )]
            [Localizable( false )]
            public GeoAddress Origin
            {
                get
                {
                    GeoAddress o = new GeoAddress();
                    o.Init();
                    o.LatLng.Latitude = (double) ViewState["Origin_Latitude"];
                    o.LatLng.Longitude = (double) ViewState["Origin_Longitude"];
                    return o;
                }
    
                set
                {
                    ViewState["Origin_Longitude"] = value.LatLng.Longitude;
                    ViewState["Origin_Latitude"] = value.LatLng.Latitude;
                }
            }
    
            [Bindable( true )]
            [Category( "MapQuest" )]
            [DefaultValue( "" )]
            [Localizable( false )]
            public GeoAddress Destination
            {
                get
                {
                    GeoAddress o = new GeoAddress();
                    o.Init();
                    o.LatLng.Latitude = (double) ViewState["Destination_Latitude"];
                    o.LatLng.Longitude = (double) ViewState["Destination_Longitude"];
                    return o;
                }
    
                set
                {
                    ViewState["Destination_Longitude"] = value.LatLng.Longitude;
                    ViewState["Destination_Latitude"] = value.LatLng.Latitude;
                }
            }
    
            protected override void RenderContents( HtmlTextWriter output )
            {
                if ( this.Origin == null || this.Destination == null )
                {
                    return;
                }
                else
                {
                    if ( this.Width.IsEmpty )
                        this.Width = new Unit( 800 );
    
                    if ( this.Height.IsEmpty )
                        this.Height = new Unit( 600 );
    
                    RouteResults route;
                    using ( RouteServerProxy proxy = new RouteServerProxy() )
                    {
                        route = proxy.CreateRoute( this.Origin, this.Destination );
                    }
                    using ( MapServerProxy proxy = new MapServerProxy() )
                    {
                        string url = proxy.GetMapUrl( route.ShapePoints.ToLine(), (int)this.Width.Value, (int)this.Height.Value, true).ToString();
                        url = HttpUtility.HtmlEncode( url );
                        string html = "<img src=\"" + url + "\"/><br/>Distance: " + Math.Round(route.Distance, 2).ToString() + " miles.";
                        output.Write( html );
    
                    }
                }
            }
        }
    }
    

    Notice in the RenderContents method that:

    1. We're creating an HTML image tag, but also some text to display the distance of the route.
    2. The proxy.GetMapUrl has a boolean parameter as the fourth parameter. I discovered that for longer routes the amount of information in the Url becomes too much, making the Url too long and it won't load. Using the true parameter makes the proxy use a serverside session. Now the Url just contains a reference to the session and stays much shorter. The code needed to create a session is straightforward, use the Exec object in the MapQuest library.:
      string sessionId = Exec.CreateSessionEx( session );
      url = Exec.GetMapFromSessionURL( sessionId, new MQClientInterface.DisplayState() );

    By adding a reference to the class library Visual Studio 2008 will detect the control and make it available in the toolbar when you're editing an ASP.NET page. Just drag and drop the control on you form and your ready to go.

    I like my controls to have meaning full tags so I add a control reference in the web.config:

    
    <pages>
       <controls>
            <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            <add tagPrefix="mq" namespace="DevelopOne.MapQuest.Web.Controls" assembly="DevelopOne.MapQuest"/>
      </controls>
    </pages>
    
    
    

    The markup in the page now looks like this:

    <mq:RouteMapControl ID="RouteMapControl1" runat="server" />
    

    If you choose to skip the web.config step you'll see the tag as 'cc1:RouteMapControl'.

    Happy coding!

    - Mark Blomsma

    Download the code here.

  • The AIM Map Phone

    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!

  • Using MapQuest 5.3 in an ASP.NET application

    Earlier this week I was thinking, wouldn't it be cool if the MapQuest .NET API would allow me to program all my logic in C# (or VB.NET) and then instead of having it generate a bitmap, have it generate just the URL of a map? The URL can be used in web applications or I can embed the url in an image tag which I send as an email. A whole new world of possibilities opens up. Guess what? Generating an URL for a MapQuest map is part of the API today!

    In a previous blog post I described how to use the Exec object to create a bitmap of a map:

    sbyte[] imageBytes = Exec.GetMapImageDirect( session );
    byte[] bytes = (byte[]) (Array) imageBytes;
    
    MemoryStream stream = new MemoryStream( bytes );
    Bitmap bitmap = new Bitmap( stream );
    

    Using the same Exec object, just use the GetMapDirectURLEx method to build an URL to the same image.

    string url = Exec.GetMapDirectURLEx( session, new MQClientInterface.DisplayState() );
    
    Just assign the URL to an the ImageURL property of an ASP.NET Image control and the map will be displayed.
    imgMap.ImageUrl = url;   // imgMap is an Image control
    

    I ran my code on the development environment and as such I get back a reference to the MapQuest dev box:

    http://map.dev.mapquest.com//mqserver.dll?e=0&GetMapDirect.1=Session:5,MapState:,,314159.265358,314159.265358,11.111111,8.333333,0,
    CoverageStyle:2,,DTStyle:3072,0,2147483647,MQ09191,0,0,1,-1,,-1,-1,-1,-1,-1,-1,
    DTStyle:3073,0,2147483647,MQ09192,0,0,1,-1,,-1,-1,-1,-1,-1,-1,
    FeatureCollection:2,PointFeature:,3072,0,,,0,45.36333,-68.504058,32767,32767,
    PointFeature:,3073,0,,,0,45.315357,-68.474258,32767,32767,
    PrimitiveCollection:1,LinePrimitive.2:3617,RouteShape,1,255,65280,150,0,
    LatLngCollection.1:7,45363330,-68504058,-6889,-3235,-7343,-9316,-6359,6417,-11261,9460,-14687,23003,-1434,3471,
    PointCollection:0,BestFit.2:1.2,1,DTCollection.1:0,0,0,DisplayState.1:0,72,1,
    Authentication.3:b70fHwouU7>yb446,73655,,
    NET_5.3.0_RC1,2141274732,
    

    As you can see my route from work to home is not very long :-)

  • Building a reusable Windows Control with MapQuest API 5.3 and C#

    Time to take the MapQuest API 5.3 beta a step further and see how hard it is to embed MapQuest mapping a windows application. As a developer I&#39;m constantly thinking "is this a one time effort? or do I want to reuse this code?". Well for embedding MapQuest into my .NET application I would like to create a reusable component, in fact, I&#39;d like to create a WinForm usercontrol which can be reused in any application simply by dragging and dropping the control onto a form.

    So what are the steps to build such a control?

    1. Create a new library project in C#

    2. Add a new item of type UserControl

      add user control
    3. Is intended to display a MapQuest route, for this we need a PictureBox, so add a PictureBox control to the design surface and set the Dock property to Fill. This will make the picture resize automatically to the size of the UserControl.

    4. Since we&#39;ll be accessing the Internet and there is no telling how fast our connection is, we&#39;ll want all the interaction with the MapQuest server to be done on a background thread, so add a BackgroundWorker control to the usercontrol as well.

    5. We want our control to show a &#39;loading&#39; image while it it busy dowloading the map, and we need an error image in case an error occurs and we&#39;re unable to show a map. Create a resource file and add two images for this purpose:
      image resources

    6. Time to write some code. We add a method to the control for showing a route. The method takes three parameters: origin, destination and errorhandler:
      public void ShowRoute( GeoAddress origin, GeoAddress destination, HandleException handler )
      {
      _handler = handler;
      picMap.Image = null;
      try
      {
      Validations.Parameters.NotNull( origin, "origin",
      "Origin cannot be null when showing a route." );
      Validations.Parameters.NotNull( destination, "destination",
      "Destination cannot be null when showing a route." );
      }
      catch ( Exception exception )
      {
      picMap.Image = Images.Error;
      if ( _handler == null )
      {
      throw;
      }
      
      _handler( exception );
      }
      
      RouteData data = new RouteData
      {
      Origin = origin,
      Destination = destination,
      Width = picMap.Width,
      Height = picMap.Height
      };
      BuildMapWorker.RunWorkerAsync( data );
      picMap.Image = Images.Loading;
      }
      

      Notice in the code above we perform some basic parameter checking and then pass the data to the BuildMapWorker thread. We&#39;re using the C# 3.0 object initializer for initializing the values of the struct.
      Also in the code, if the handler parameter is null, then any exception thrown during the MapQuest server interaction will be thrown without handling. HandleException is a delegate allowing the user of the control to decide how to handle any exceptions.
      Right after the work is handed off to the BuildMapWorker thread the image is set to &#39;Loading&#39;.

    7. The RunWorkAsync method on the BackgroundWorker will trigger the code in the DoWork eventhandler. Here we use the helper proxies that we created in our previous post to first crearte a route and the map the route to an actual map.
      private void BuildMapWorker_DoWork( object sender, DoWorkEventArgs e )
      {
      RouteData data = (RouteData) e.Argument;
      
      RouteResults route;
      using ( RouteServerProxy proxy = new RouteServerProxy() )
      {
      route = proxy.CreateRoute( data.Origin, data.Destination );
      }
      using ( MapServerProxy proxy = new MapServerProxy() )
      {
      // e.Result will be a bitmap
      e.Result = proxy.GetMap( route.ShapePoints.ToLine(), data.Width, data.Height );
      }
      }
      

      Note that RouteData is a custom struct for passing information from the UI thread to the background thread. The background thread is not allowed to interact with the UI controls, so all information needs to be passed as a parameter.
      Similarly the result of GetMap is a bitmap, we don&#39;t put the bitmap directly back onto the control, instead we pass it back to the UI thread via the DoWorkEventArgs parameter.

    8. Let&#39;s take a look at the proxy code, there are a couple of overloads but the following method gets called to create the route:
      public RouteResults CreateRoute( LocationCollection addresses )
      {
      // This is the collection that will hold the geocoded locations to be utilized in the call to DoRoute.
      LocationCollection routeLocations = addresses;
      
      // The RouteOptions object contains information pertaining to the Route to be performed.
      RouteOptions routeOptions = new RouteOptions();
      routeOptions.MaxShapePointsPerManeuver = 100;
      
      // The RouteResults object will contain the results of the DoRoute call.  The
      // results contains information such as the narrative, drive time and distance.
      RouteResults routeResults = new RouteResults();
      
      // This call to the server actually generates the route.
      Exec.DoRoute( routeLocations, routeOptions, routeResults, String.Empty );
      
      // Throw an exception if an error occurred.
      ThrowExceptionOnError( routeResults );
      
      return routeResults;
      }
      

    9. Then the code in the MapServerProxy to load the map looks like this:
      public Bitmap GetMap( LinePrimitive route, int width, int height )
      {
      // The MapState object contains the information necessary to display the map,
      // such as size, scale, and latitude/longitude coordinates for centering the map.
      MapState map = new MapState();
      
      // Define the width and height of the map in pixels.
      map.WidthPixels = width;
      map.HeightPixels = height;
      
      // The MapQuest Session object is composed of multiple objects,
      // such as the MapState and CoverageStyle.
      Session session = new Session();
      
      // Add user defined styles in order to create custom points of interest.
      CoverageStyle styles = new CoverageStyle();
      DTStyle start = styles.AddIcon( 3072, Miscellaneous.Start.Value() );
      DTStyle end = styles.AddIcon( 3073, "MQ09192" );
      
      // A point of interest is considered a feature on the map.
      // A map can have multiple features.
      FeatureCollection features = new FeatureCollection();
      features.AddPointFeature( route.LatLngs.First(), start );
      features.AddPointFeature( route.LatLngs.Last(), end );
      
      // Add the line primitive to a primitiveCollection
      PrimitiveCollection primitives = new PrimitiveCollection();
      primitives.Add( route );
      
      // The best fit object is used to draw a map at an appropriate scale determined
      // by the features you have added to the session, along with the optional primitives.
      // In this case we want the map to be displayed at a scale that includes the origin
      // and destination locations (pointFeatures) as well as the routeHighlight(linePrimitive).
      // The scaleAdjustmentFactor is then used to increase the scale by this factor based
      // upon the best fit that was performed.  This results in a border around the edge of
      // the map that does not include any features so the map appears clearer.
      BestFit scaling = new BestFit();
      scaling.ScaleAdjustmentFactor = 1.2;
      scaling.IncludePrimitives = true;
      
      // Add objects to the session.
      session.AddOne( map );
      session.AddOne( styles );
      session.AddOne( features );
      session.AddOne( primitives );
      session.AddOne( scaling );
      
      sbyte[] imageBytes = Exec.GetMapImageDirect( session );
      byte[] bytes = (byte[]) (Array) imageBytes;
      
      MemoryStream stream = new MemoryStream( bytes );
      Bitmap bitmap = new Bitmap( stream );
      
      return bitmap;
      }
      

    10. Lastly, the RunWorkerCompleted event is fired by the BackgroundWorker and here the event arguments now contain the bitmap we want to put on the control:
      private void BuildMapWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
      {
      try
      {
      if ( e.Error != null )
      {
      // throw the error on the UI thread.
      throw e.Error;
      }
      else
      {
      // update the image back on the UI thread
      this.picMap.Image = (Bitmap) e.Result;
      }
      }
      catch ( Exception exception )
      {
      // handle the exception on the UI thread (in case the handler implement UI interaction)
      picMap.Image = Images.Error;
      if ( _handler == null )
      {
      throw;
      }
      _handler( exception );
      }
      }
      

    I&#39;ve created a demo application, which actually has a couple more controls, which demos the use of this control. You can download it here, all the sources are included. To make the application run you will need to supply your own ClientID and Password in the app.config file.

    The demo works like this, enter the point of origin on the first tab:

    screenshot 1

    Then enter the destination on the second tab:

    screenshot 2

    After which you can go to the third tab and see the route:

    screenshot 3

    Happy coding!

    - Mark Blomsma

    Download sources here.

  • Getting started with the MapQuest API 5.3 and C#

    If you're a C# .NET developer and want to get started using the new MapQuest 5.3 API then you can get started today with the beta/release candidate version available on http://developer.mapquest.com/Beta.
    Download the .NET library and start exploring.

    From a .NET point of view not much has changed (yet) in the 5.3 API:
    - The library has been renamed to mapquest20.dll to reflect that this is for .Net 2.0 and above only
    - It has been tested with the 2.0 framework AND the 3.5 framework
    - It has been tested with both 32-bit and 64-bit environments
    - No interface changes have been made

    Getting a free developer account. Right now there are two types of developer account available:
    - MapQuest Platform: Free Edition
    - Free Developer License for MapQuest Platform: Enterprise Edition [1]

    Just apply to the first one since that will give you access to both. After signing up and confirming your account, you may have to wait a couple of hours for your account to become active. Mine took about 6 hours to become active.

    In the meantime you can download the .NET library. The whole library is contained in one DLL named mapquest20.dll, start a new project, add an assembly reference and you're ready to go.

    Check your account confirmation email for a list of the server names that you can use to access the MapQuest Platform, note that the information in the email will be more accurate than what you may find on the beta website.

    There are four MapQuest servers, each offering a unique set of services to make the MapQuest offering whole. These servers are:

    • Map Server - used for creating basic maps
    • Geocode Server - used for finding latitude/longitude for addresses
    • Route Server - used for created routes
    • Spatial Server - used for searching in a specific area

    The C# API offers one central class which is responsible for all server calls and it is called the Exec class. Depending on what server you point the Exec class to the appropriate methods become available.

    In order to produce a basic map you'll need at minimum the Map Server and probably the Geocode Server for locating an address.

    One of the things you'll notice when using the mapquest20.dll is that it uses very few C#2.0 features (like generics). In order to make the provided API easier to use from my own code I've created some wrapper functions.
    The first method can be used to geocode an address:

    
    /// <summary>
    /// Find a list of geo encoded addresses based on a regular adress.
    /// </summary>
    /// <param name="address">Address</param>
    /// <returns>List of geocoded address, or null if none are available.</returns>
    public List<GeoAddress> Geocode( Address address )
    {
        Disposable.ClassVariableCannotBeNull( this, Exec, "Exec" );
        Parameters.NotNullOrEmpty( this, address.Country, "address.Country", "Geocode" );
    
        LocationCollection locations = new LocationCollection();
        try
        {
            Exec.Geocode( address, locations );
        }
        catch ( Exception exception )
        {
            // TODO: Log exception
            return null;
        }
        List<GeoAddress> results = new List<GeoAddress>( locations.Size );
        for ( int i = 0, size = locations.Size; i < size; i++ )
        {
            results.Add( (GeoAddress) locations.GetAt( i ) );
        }
        return results;
    }
    
    /// <summary>
    /// Find a list of geo encoded addresses based on individual parts of an address.
    /// </summary>
    /// <param name="street"></param>
    /// <param name="city"></param>
    /// <param name="county"></param>
    /// <param name="state"></param>
    /// <param name="postalCode"></param>
    /// <param name="country"></param>
    /// <returns>List of geocoded address, or null if none are available.</returns>
    public List<GeoAddress> Geocode( string street, string city, string county, string state, string postalCode, string country )
    {
        Address address = new Address();
    
        address.Init();
        address.Street = street;
        address.City = city;
        address.County = county;
        address.State = state;
        address.PostalCode = postalCode;
        address.Country = country;
    
        return Geocode( address );
    }
    
    

    The second method will create an image based on the latitude/longitude of the point of interest. It will also show a basic point of interest at the specified location.

    /// <summary>
    /// Return a (bit)map centered on the provided address.
    ///
    /// Address needs to have LatLng property set.
    /// </summary>
    /// <param name="address">The address to center on.</param>
    /// <param name="width">Width of the map in pixels (DPI = 72)</param>
    /// <param name="height">Height of the map in pixels (DPI = 72)</param>
    /// <param name="scale">Scale of the map. Level of detail displayed varies depending on the scale of the map.</param>
    /// <returns>Return a bitmap of the map centered on provided address.</returns>
    public Bitmap GetMap( GeoAddress address, int width, int height, int scale )
    {
        return GetMap( address.LatLng, width, height, scale );
    }
    
    /// <summary>
    /// Return a (bit)map centered on the provided latitude/longitude.
    /// </summary>
    /// <param name="center">Coordinates for the center of the map.</param>
    /// <param name="width">Width of the map in pixels (DPI = 72)</param>
    /// <param name="height">Height of the map in pixels (DPI = 72)</param>
    /// <param name="scale">Scale of the map. Level of detail displayed varies depending on the scale of the map.</param>
    /// <returns>Return a bitmap of the map centered on provided latitude/longitude.</returns>
    public Bitmap GetMap( LatLng center, int width, int height, int scale )
    {
        // The MapState object contains the information necessary to display the map,
        // such as size, scale, and latitude/longitude coordinates for centering the map.
        MapState map = new MapState();
    
        // Define the width and height of the map in pixels.
        map.WidthPixels = width;
        map.HeightPixels = height;
    
        // The MapScale property tells the server the scale at which to display the map.
        // Level of detail displayed varies depending on the scale of the map.
        map.MapScale = scale;
    
        // Specify the latitude/longitude coordinate to center the map.
        map.Center = center;
    
        // The MapQuest Session object is composed of multiple objects,
        // such as the MapState and CoverageStyle.
        Session session = new Session();
    
        // Add objects to the session.
        session.AddOne( map );
        session.AddOne( Features.CreateBasicPOI(center) );
    
        sbyte[] imageBytes = Exec.GetMapImageDirect( session );
        byte[] bytes = (byte[]) (Array) imageBytes;
    
        MemoryStream stream = new MemoryStream( bytes );
        Bitmap bitmap = new Bitmap( stream );
    
        return bitmap;
    }
    

    The MapQuest API returns an image as an array of sbytes. Since .NET has a basic type for images, named Bitmap, it is much more useful to have the method return a bitmap which can then be used anyway you please. To do this the array is converted from sbytes to bytes since that is all the MemoryStream can handle.

    The samples posted on the MapQuest site are a great help in figuring out how to use the C# MapQuest API and is actually more detailed than the provided documentation.

    Happy coding!

    - Mark Blomsma

    [1] MapQuest Platform Services offers a free Developer License for Enterprise Edition, providing you access to the features and functionality of the Enterprise Edition while you are developing and prototyping your map application. Register for the free Developer License at MapQuest Developer Network and get started today.