Visualizing data with the XtraMap control

One important tool in my company’s flagship software offers users the ability to search for trucks and visualize that information on map to see what is available and where it is. It’s easy to display results to a grid control, but looking at tabular data over and over can be a little boring. Mixing in another control, such as the XtraMap control provides us with the ability to visualize these results in an easy-to-understand & interactive manner.

Here’s a basic overview of how it might look in my software:

XtraMap example
XtraMap example

There’s a basic set of editors in which a user can enter some search criteria and then search results are displayed with a GridControl to the left, and an XtraMap to the right. Clicking on a row show zoom us to the selected result marker on the map and vice versa. We should also make use of the tooltip ability for map markers to display some additional information when a user hovers over a marker.

To simplify things, I’ve provided a basic Xml data source with the project file that will have our “search” results. To get started, we’ll first drop an XtraLayoutControl onto the form to help us with arranging our controls.

I’ve created a LayoutGroup at the top of the form to arrange where our search criteria controls would (we’ll just execute the search with a button since our results are hardcoded), and the bottom portion of the form will have another LayoutGroup containing the GridControl and XtraMap control. Drop both of these onto your form via the VS toolbox–both are contained within the Data & Analytics tab. At this point, we have the skeleton of our interface:

form1Out of the box, the XtraMap control offers the ability to connect to 2 different map providers: Bing Maps and OpenStreetMap. I’d advise you to consult both of these providers to learn the terms and conditions of using their services. Bing Maps will require a developer account; OpenStreetMap will work immediately without the need for an account. For this reason, will choose OpenStreetMap as our provider for this example.

One common topic that pops up on the DevExpress Support Center concerns the ability to connect the XtraMap control to Google Maps. While it’s technically possible (after all, the XtraMap control simply issues an HttpWebRequest to retrieve map tiles and then displays them), it does violate Google’s terms of service for their mapping service and is beyond the scope of this example.

To connect to OpenStreetMap, click on the MapControl’s Smart Tag and click on the “Connect to OpenStreetMap Server” link:

connecttoopenmapAt this point, your XtraMap control is fully operational. Start your project and you will see that map tiles are loaded and you have the ability to pan and zoom your map. When you connected to OpenStreetMap, the XtraMap control automatically generated an Image Tiles Layer for your control. This single layer is responsible for arranging the images returned from OpenStreetMap and draws our map.

From here, we’ll handle the SimpleButton’s Click event, load the Xml into a DataSet and assign it to the GridControl’s DataSource property. We now have a grid with search results, but that’s only half of what we want. We’d like to display a little icon in the map that corresponds to each of the results, so how is that accomplished?

First, we need add another layer to our MapControl. For our purposes, we’ll use the VectorItemsLayer type. I’d recommend reviewing the Layers documentation to see a quick overview of the various layer types available and when & how you should use them.

This can be done via the designer or at runtime; we’ll choose the latter just so we can see what is happening:

/// <summary>
/// Creates a Vector Items Layer on which to display equipment icons
/// </summary>
private void CreateVectorLayer()
{
    VectorItemsLayer itemsLayer = new VectorItemsLayer();
    ListSourceDataAdapter dataAdapter = new ListSourceDataAdapter();

    dataAdapter.Mappings.Latitude = "OriginLatitude";
    dataAdapter.Mappings.Longitude = "OriginLongitude";
    itemsLayer.ItemImageIndex = 0;

    //Map the attributes used for the tooltip
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "CompanyName", Name = "CompanyName" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "MCNumber", Name = "MCNumber" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "MatchID", Name = "MatchID" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "CompanyContact.ContactMethodValue", Name = "ContactMethod" });

    //Set up the tooltip pattern for the truck icons
    itemsLayer.ToolTipPattern = "<b>{CompanyName}</b>\r\nMC/DOT #{MCNumber}\r\n{ContactMethod}";
    itemsLayer.Data = dataAdapter;
    mapControl1.Layers.Add(itemsLayer);

}   //End the CreateVectorLayer() method

Let’s break it down:

As expected, we create an instance of the VectorItemsLayer class and additionally an instance of the ListSourceDataAdapter class. The ListSourceDataAdapter is what is responsible for providing data to the XtraMap and shares the GridControl’s data source. Before we provide all that, we’ll see at basic properties to tell it what to do with that data that it will be receiving:

  • Mappings.Latitude – We set this the property in our data source (remember that Xml file?) which contains the latitude co-ordinates.
  • Mappings.Longitude – Similar to above, we set this to the property in the data source containing the longitude co-ordinates.
  • ItemImageIndex – I’ve created an ImageCollection component containing any images that I’d like to draw onto the map. Right now this is just a single png image and I’ve set the XtraMap control’s ImageList property to this ImageCollection. I then tell the VectorItemsLayer which image within the collection is going to correspond to our data source items.
  • I create a collection of MapItemAttributeMapping objects which maps items in the data source to attributes of a MapItem. It’s a little confusingly named since we’re dealing with Maps and mappings. We can create as many attributes as we’d like and connect them to a property in the data source. The Member property needs to match a property/field in the data source, and the Name property is what the VectorItemsLayer will use to identify it.
  • We create a tooltip pattern to be used when our users hover over one of these map items. Some limited HTML is supported (you’ll need to use a ToolTipController component for this) and we place our MapItemAttribute names within { } brackets as placeholders to be populated at runtime when the tooltip is created.
  • Finally, we set the data source for the VectorItemsLayer to this ListSourceDataAdapter and add it all to the MapControl.

We’ll invoke this method from the SimpleButton’s click handler after we set the GridControl’s data source. If we run the application now we’d expect both the GridControl and the XtraMap control to show the search results. This doesn’t happen though, because we never actually set the data source for the ListSourceDataAdapter! We told it WHAT to display and we told the VectorImageLayer HOW to display it, but we’re missing that one last link:

(itemsLayer.Data as ListSourceDataAdapter).DataSource = (grdResults.DataSource);

The GridView’s DataSourceChanged event is a good place to use that code to provide data for the layer’s data adapter.

Now when we run the application, our truck image appears for each of the results and the tooltips are nicely formatted to match the ToolTipPattern we created.

Let’s add in that synchronization between the map and grid that we talked about in the beginning. The first thing we want to handle is when a user clicks on an item in the MapControl. The MapControl exposes a MapItemClick event that will be fired when a user clicks on the truck icons. I’ll handle this event, find the item in the GridControl that matches what the user clicks on and focus that.

/// <summary>
/// Map Item click event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void mapControl1_MapItemClick(object sender, MapItemClickEventArgs e)
{
    if (IsItemSelecting == true)
        return;

    string MatchID = e.Item.Attributes["MatchID"].Value.ToString();
    int RowHandle = gvResults.LocateByDisplayText(0, gvResults.Columns["MatchID"], MatchID);

    if (gvResults.IsValidRowHandle(RowHandle))
    {
        IsItemSelecting = true;
        gvResults.ClearSelection();
        gvResults.FocusedRowHandle = RowHandle;
        gvResults.SelectRow(RowHandle);
        gvResults.MakeRowVisible(RowHandle);
        gvResults.Focus();
        IsItemSelecting = false;
    }

}   //End the mapControl1_MapItemClick() method

The IsItemSelecting property is a simple Boolean flag that I created and its use will be apparent in just a bit. The important take-away from the above snippet is that we want to have some sort of unique ID for each search result/map item so that we can find it within the GridControl and focus it. The flip-side to this is focusing a map item when a user clicks on a grid row. First, wire up a FocusedRowChanged event handler for the results GridView:

/// <summary>
/// Focused Row Changed event handler for the results GridView
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void gvResults_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e)
{
    if (IsItemSelecting == true)
        return;

    DataRow selectedResult = gvResults.GetFocusedDataRow();

    IsItemSelecting = true;

    SelectEquipmentIcon(selectedResult);

    IsItemSelecting = false;

}   //End the gvResults_FocusedRowChanged() method

In here we first check that IsItemSelecting flag. If a user clicks on an item from the map control, this event would be fired and vice versa resulting in an infinite loop. We want to make sure that don’t keep going back and forth like this, thus the need for that Boolean flag.

We’ll retrieve the DataRow object that corresponds to the selected row and pass this to the SelectEquipmentIcon method to zoom in on the truck that goes with the selected row:

/// <summary>
/// Selects the provided truck on the map
/// </summary>
/// <param name="selectedResult">A DataRow corresponding to the selected Grid row</param>
public void SelectEquipmentIcon(DataRow selectedResult)
{
    double SelectedLatitude = 0.0d;
    double SelectedLongitude = 0.0d;

    SelectedLatitude = Convert.ToDouble(selectedResult["OriginLatitude"]);
    SelectedLongitude = Convert.ToDouble(selectedResult["OriginLongitude"]);

    mapControl1.Zoom(1, true);
    mapControl1.CenterPoint = new GeoPoint(SelectedLatitude, SelectedLongitude);
    mapControl1.Zoom(10, true);

    VectorItemsLayer itemsLayer = mapControl1.Layers[1] as VectorItemsLayer;

    itemsLayer.SelectedItem = itemsLayer.GetMapItemBySourceObject(selectedResult);

}   //End the SelectEquipmentIcon class

And with that, we have a finished product! As always, the source code is provided with this post: XtraMapSearchResults

Visualizing data with the XtraMap control

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s