If you've visited us at the Microsoft booth at the Shop.org conference, you may have seen the Microsoft Virtual Earth demonstration application illustrating Microsoft Commerce Server 2007 and Virtual Earth working together. The concept for the application is based on inventory data in Commerce Server being used to help end users find the nearest location that carries the item you're searching for - an advanced store locator and inventory search scenario.

image

The demonstration application is posted online branded as "Soundshift Multi-Channel Retailing." To use the application, you'll want to go to the "stores" tab and enter '90210' in the address box. You'll be presented with the results around the Los Angeles area inclusive of custom pushpins, custom EROs and how 'bout that custom navigation bar? Nice. In the "Directions" tab you can get directions, find gas stations along the route, find coffee shops along the route, show traffic, print the directions, send to email, and send to mobile. A pretty robust application, eh?

So, to most of you this is just a plain old store location - whoopie! The important piece is what happens behind the scenes. The folks at Cactus Commerce built the implementation and wrote a white paper describing how to leverage both Windows Live ID and Virtual Earth within Commerce Server. The document provides architecture and code details of how this can be done using the Commerce Server's extensibility model. From the document, you'll find the architecture and code level instructions for integrating the following features into Commerce Server - geocoding a user's address, finding the nearest stores, retrieving items in the basket, getting inventory status, and adding store and inventory information to the Virtual Earth map.

image

Geocode User’s Address

The first step in locating nearby stores is to geocode the user’s address to latitude and longitude coordinates. You can use the Find method from the Virtual Earth’s Map Control to resolve the address. For simplicity, we will assume only one result is returned:

function findAddress(address, map)

{

// Call VEMap.Find() to geocode address

map.Find(null, address, null, null, null, null, false, false, true, true,

findAddressCallBack);

}

// Call back function for VEMap.Find()

function findAddressCallBack(thelayer, resultsArray, places, hasMore, veErrorMessage)

{

if(places != null && places.length >0)

{

var latitude = places[0].LatLong.Latitude;

var longitude = places[0].LatLong.Longitude;

}

}

Retrieve Nearby Stores

Once we have the user’s geocode, we can implement the logic to find nearby stores within a given radius:

private void GetNearbyStores(ProfileManagementContext profileCtx, double latitude,

double longitude, double radius, List<Store> stores)

{

// Retrieve store list

DataSet dsEntities = profileCtx.GetSearchableEntities();

SearchClauseFactory scf = profileCtx.GetSearchClauseFactory(dsEntities, "Store");

SearchClause searchClause = scf.CreateClause();

Microsoft.CommerceServer.SearchOptions searchOptions =

new Microsoft.CommerceServer.SearchOptions();

searchOptions.PropertiesToReturn =

"u_store_id,u_name,u_address,u_virtual_catalog, latitud,longitude";

DataSet ds = profileCtx.ExecuteSearch("StoreObject", searchClause, searchOptions);

// Find nearby stores

if (ds != null && ds.Tables.Count > 0)

{

foreach (DataRow r in ds.Tables[0].Rows)

{

double storeLat = double.Parse(r["latitud"].ToString());

double storeLon = double.Parse(r["longitude"].ToString());

// CalculateDistance() implements the Mercator's Projection

// to determine the distance between 2 points

if (CalculateDistance(lat, lon, storeLat, storeLon) < radius)

{

// Store is within the specified radius

Store store = new Store();

store.ID = r["u_store_id"].ToString();

store.Name = r["u_name"].ToString();

store.Address = r["u_address"].ToString();

store.VirtualCatalog = r["u_virtual_catalog"].ToString();

store.Latitude = storeLat;

store.Longitude = storeLon;

stores.Add(store);

}

}

}

}

Retrieve Basket Items

In order to build up the store stock status return data set, we need to retrieve the list of products from the user’s basket:

// Get list of product and quantity from user's basket

private void GetBasketItems(GUID userID, string basketName, List<BasketItem> basketItems)

{

// Get the user's shopping cart

Basket basket = CommerceContext.Current.OrderSystem.GetBasket(userID, basketName);

foreach (OrderForm orderForm in basket.OrderForms)

{

foreach (LineItem lineItem in orderForm.LineItems)

{

// Store basket item

BasketItem basketItem = new BasketItem();

basketItem.ProductID = lineItem.ProductId.ToString();

basketItem.ProductName = lineItem.DisplayName.ToString();

basketItem.Quantity = lineItem.Quantity;

basketItems.Add(basketItem);

}

}

}

Retrieve Store Inventory Status

We are now ready to retrieve on hand quantity for each store items:

private void GetStoreInventoryStatus(InventoryContext inventoryCtx, List<Store> stores,

List<BasketItem> basketItems)

{

foreach (Store store in stores)

{

// Get the inventory catalog for the current store

InventoryCatalog inventoryCatalog =

inventoryCtx.GetAssociatedInventoryCatalog(store.VirtualCatalog);

foreach (BasketItem basketItem in basketItems)

{

// Get Inventory Sku

InventorySku inventorySku =

inventoryCatalog.GetSku(store.VirtualCatalog,

basketItem.ProductID);

// Add inventory status to store

ItemStatus itemStatus = new ItemStatus();

itemStatus.ProductID = basketItem.ProductID;

itemStatus.StockStatus = (inventorySku.Quantity - basketItem.Quantity) >= 0 ?

"In Stock" : "Not Available";

store.ItemStatusList.Add(itemStatus);

}

}

}

Add Stores and Inventory Status to Virtual Earth Map

Now that we have all the Store inventory data, we will be adding pushpins and popup descriptions to the Virtual Earth Map via client side scripting:

· Create a new instance of the map control and add a shape layer:

map = new VEMap('myMap');

map.LoadMap();

layer = new VEShapeLayer();

map.AddShapeLayer(layer);

layer.Hide();

· Create pushpin shape and add the shape layer

// jsStoreStockData contains store inventory status retrived from

// previous steps

for (var i=0; i < jsStoreStockData.stores.length; i++)

{

// Create shape (pushpin)

var latLong = new VELatLong(jsStoreStockData.stores[i].Latitude,

jsStoreStockData.stores[i].Longitude) ;

var shape = new VEShape(VEShapeType.Pushpin, latLong);

shape.SetTitle('<H3>'+jsStoreStockData.stores[i].Name+'</H3>');

shape.SetCustomIcon("<img width='20' height='20' alt='store'

src='images/logo_vista.png'/>");

// Generate basket item stock status as shape description

var desc = '<div>'+jsStoreStockData.stores[i].Address+'<table>' ;

desc += '<tr><td><b>Product</b></td><td><b>Stock Status<td></td></tr>'

for (var j = 0; k < jsStoreStockData.stores[i].ItemStatusList.length; j++)

{

desc += '<tr><td>'+jsStoreStockData.stores[i].ItemStatusList[j].ProductName

+ '</td><td>';

desc += jsStoreStockData.stores[i].ItemStatusList[j].StockStatus

+ '</td></tr>';

}

desc += '</table></div>';

shape.SetDescription(desc);

// Add pushpin to ShapeLayer

layer.AddShape(shape);

// Center map and set zoom level

SetCenterAndZoom();

// Show the ShapeLayer

layer.Show();

}

If you have Microsoft Commerce Server 2007, your integration with Microsoft Virtual Earth just got a heck of a lot easier. The flexibility of the Virtual Earth platform allows for data visualization from almost any data repository whether it be Microsoft products like Commerce Server, CRM or SQL Server or non-Microsoft data sources such as Siebel, Oracle or MySQL. The proliferation of data visualization using mapping across the web and software products just keeps getting better, doesn't it?

CP