Advanced Mapping Interfaces

  • Comments 2

This is Part 2 where I supplement my “Client-inception” PoC that I developed previously with geocoding functionality.
 
I’m going to create an integrated mapping interface using the WPF control suite that I’ve been using thus far and implement it within Dynamics. It’s going to look something like this:
 
 
You could very easily develop your own WPF map control (from the ground-up) using the Microsoft published API [http://msdn.microsoft.com/en-us/library/hh830431.aspx] and then incorporate the Geocoding functionality by making a call to the SOAP services. The following code snippet extracted from this Microsoft article [http://msdn.microsoft.com/en-us/library/dd221354.aspx] will give you what you need if you just want the Geocoding element.
 
C#
private String GeocodeAddress(string address)
{
    string results = "";
    string key = "insert your Bing Maps key here";
    GeocodeRequest geocodeRequest = new GeocodeRequest();
 
    // Set the credentials using a valid Bing Maps key
    geocodeRequest.Credentials = new GeocodeService.Credentials();
    geocodeRequest.Credentials.ApplicationId = key;
 
    // Set the full address query
    geocodeRequest.Query = address;
 
    // Set the options to only return high confidence results
    ConfidenceFilter[] filters = new ConfidenceFilter[1];
    filters[0] = new ConfidenceFilter();
    filters[0].MinimumConfidence = GeocodeService.Confidence.High;
 
    // Add the filters to the options
    GeocodeOptions geocodeOptions = new GeocodeOptions();
    geocodeOptions.Filters = filters;
    geocodeRequest.Options = geocodeOptions;
 
    // Make the geocode request
    GeocodeServiceClient geocodeService = new GeocodeServiceClient();
    GeocodeResponse geocodeResponse = geocodeService.Geocode(geocodeRequest);
 
    if (geocodeResponse.Results.Length > 0)
        results = String.Format("Latitude: {0}\nLongitude: {1}",
            geocodeResponse.Results[0].Locations[0].Latitude,
            geocodeResponse.Results[0].Locations[0].Longitude);
    else
        results = "No Results Found";
 
    return results;
}
 
 

·         Please refer to my previous article for more information on how to obtain your Bing maps key.

 
In this case, I’ve saved myself a whole heap of work by using Telerik components. Telerik have already done all the hard work and wrapped-up all the functionality I need within an easy-to-use control called “RadMap”.
 
IThe markup to implement a telerik map is very straightforward:
 
Xaml
<telerik:RadMap Grid.Row="0" Grid.Column="1" x:Name="RadMap1" Width="Auto" Height="Auto">
    <telerik:InformationLayer Name="informationLayer">
    </telerik:InformationLayer>
</telerik:RadMap>
 
 
I need this control to do two things:
 

1.)   Display the map-position of the address as soon as its gets entered on the form.

2.)   Geocode the centered map-position and pass the latitude and longitude co-ordinates into my X++ client-inception routine.

 
The code to initialise this functionality is as follows:
 
C#
using Telerik.Windows.Controls.Map;
 
namespace ClientInceptionUserControl
{
    /// <summary>
    /// Interaction logic for Startup.xaml
    /// </summary>
    public partial class Startup : UserControl
    {
        BingGeocodeProvider objBingGeocodeProvider;
        public string BingKey = " insert your Bing Maps key here ";
        double Latitude, Longitude;
 
        public Startup()
        {
            InitializeComponent();
            this.objBingGeocodeProvider = new BingGeocodeProvider();
            this.objBingGeocodeProvider.ApplicationId = this.BingKey;
            this.objBingGeocodeProvider.MapControl = this.RadMap1;
            this.objBingGeocodeProvider.GeocodeCompleted += this.objBingGeocodeProvider_GeocodeCompleted;
        }
 
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            BingMapProvider objBingMapProvider = new BingMapProvider(MapMode.Aerial, true, this.BingKey);
            RadMap1.Provider = objBingMapProvider;
        }
    }
}
 
 
The Geocode request occurs asynchronously so a handler is required for the “Geocode-completed-event”:
 
C#
private void objBingGeocodeProvider_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
    foreach (GeocodeResult result in e.Response.Results)
    {
        // store co-ordinates
        Latitude = result.Locations.First().Latitude;
        Longitude = result.Locations.First().Longitude;
 
        // centre map and set zoom level
        RadMap1.ZoomLevel = 15;
        RadMap1.Center = new Location(Latitude, Longitude);
 
        // add a pinpoint on the centered location
        this.informationLayer.Items.Clear();
        this.informationLayer.Items.Add(result.Locations[0]);
 
        // only deal with highest probability result for this PoC
        break;
    }
}
 
 

·         Things like “zoom-level” and “pinpoint” are cosmetic details that can be adjusted to suit requirements.

 
Some addresses can be “generic” and result in multiple geocode results. For the purposes of the PoC we will only consider the first one returned. In order to ensure a high probability of a single-search-result we should trigger the search as soon as an adequate number of address elements have been entered.
 
 
Because of the arrangement of the address elements on the form, I’m placing the trigger-event on the selection-change event of the “city-dropdown” (the postal code should have been entered by this stage).
 
C#
private void City_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    GeocodeRequest request = new GeocodeRequest();
    request.Query = Street.Text + ", " + City.SelectedValue.ToString() + ", " + Postcode.Text + ", " + Country.Text;
    this.objBingGeocodeProvider.GeocodeAsync(request);
}
 
 
The last thing to do is pass the latitude and longitude in the client create routine. The following change needs to be made to the passthru X++.
 
X++
// setup addresss
objDirParty = DirParty::constructFromCommon(objCustTable);
objDirPartyPostalAddressView.LocationName = 'PRIMARY';
objDirPartyPostalAddressView.Street = street;
objDirPartyPostalAddressView.City = city;
objDirPartyPostalAddressView.County = county;
objDirPartyPostalAddressView.State = county;
objDirPartyPostalAddressView.CountryRegionId = country;
objDirPartyPostalAddressView.ZipCode = postCode;
objDirPartyPostalAddressView.Latitude = " + this.Latitude.ToString() + @";
objDirPartyPostalAddressView.Longitude = " + this.Longitude.ToString() + @";
objDirParty.createOrUpdatePostalAddress(objDirPartyPostalAddressView);
 
 
The effort to put this advanced functionality into Dynamics is fairly minimal (and painless) I’m sure you’ll agree and provides an enhanced user-experience that will work on both the rich-client and Sharepoint.
 
The following YouTube video demonstrates the new functionality in action:  [http://www.youtube.com/watch?v=51o-8EW4jeo&t=1m40s&hd=1].
 
REGARDS
 
  • Great post

  • Great example. Now if only Microsoft would fix the development experience to add AOT-made (ie AOT VS Projects) WPF controls to a form, that would make me a lot happier :-)