65.9K
CodeProject 正在变化。 阅读更多。
Home

探索云计算的机会:第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (7投票s)

2011年1月20日

CPOL

18分钟阅读

viewsIcon

23822

downloadIcon

225

一系列文章,探讨利用Bing地图和手机进行Azure开发。

引言

这是本系列文章中的第二篇,我将在此描述我探索云开发新领域的经历。据预测,几年内,三分之一以上的开发都将基于云。所以,如果我们不想落后,熟悉云环境就显得很有意义。我学习新技术的方法是设想它们的应用。具体来说,我试图着眼于它们如何应用于解决业务问题。我认为利用云技术可以带来巨大的商业机会。为了提供一些具体的例子并聚焦讨论,我决定将Bing地图服务作为这些文章中讨论的业务场景的中心主题。为了让事情更有趣,我特别关注利用Bing地图服务手机作为解决方案一部分的业务场景。原因是,在我看来,手机将在为云开发的业务解决方案中扮演重要角色。

上一篇文章提供了关于入门Bing地图服务的信息,以及我们想要遵循的应用程序结构的设置。在这篇文章中,我们将深入探讨,演示如何为基本的地图控件添加功能。同时,我们将讨论一些受益于所呈现功能的典型业务场景。

走向云端

让我们首先将当前解决方案更改为托管在Azure上。如果还没有安装,您需要下载并安装Windows Azure SDK。您可以在此处找到Azure所需的一切。安装SDK后,您将在Visual Studio中访问新的“云”项目模板。在解决方案资源管理器选项卡上,右键单击以添加新项目。在新建项目对话框中,选择“云”,并将其命名为“SLAzureMapTest”。然后,在后续的角色选择对话框中,只需单击“确定”而不选择任何角色。这将创建一个不带任何角色的项目,我们可以使用现有的Web项目。

您将在解决方案资源管理器中看到新创建的项目,但其中有一个空的角色文件夹。如下所示,右键单击该文件夹以添加新角色

AzureWebRoleAssignment.png

在随后的对话框中,您应该会看到现有的SLMapTest.Web项目,所以请选择它以建立关联。将现有的Web项目修改为托管在Azure上,仅此而已。

如果您重新编译并尝试运行应用程序,您将收到一条消息,提示您需要以管理员身份启动Visual Studio才能运行应用程序。所以从现在开始,请始终以管理员身份为该项目启动Visual Studio。

我对我们在上一篇文章中开始的项目进行了一些小的结构性更改。没什么大事,只是将主页上的地图视图分解为单独的控件。这意味着View:ViewModel关联也有了新的条目。但这就是我所做的全部更改。在下载文件中,我包含了一个修订版本,如果您想跟进代码或查看修订后的代码,可以使用它作为起点。

第一步

好的,让我们开始添加一些用户与地图的交互。我们首先要允许用户点击地图,并提供一些反馈,即与点击位置相关的数据。地图服务只能处理地理坐标(纬度/经度),但我们的视图鼠标点击是屏幕坐标,所以我们必须在两者之间进行转换。幸运的是,地图控件公开了一个方法,可以提供这种转换!

首先,在代码隐藏中创建一个鼠标单击事件处理程序。由于我们需要访问地图控件进行转换并且我们将进行一些UI渲染,所以将其放在这里是合适的。

因此,为了能够在地图上绘图,我们使用MapLayer。这些是地图控件暴露的类型,本质上是地图上的叠加层。我们将从一个叠加层开始,以识别用户单击的位置,稍后我们将添加额外的层来分隔显示的不同类型的信息。以下是显示SelectionLayer的XAML文件更改。

... 
<m:Map x:Name="bingMap" CredentialsProvider="YourKey">
<m:MapLayer x:Name="SelectionLayer"/>
...

以下是单击事件处理程序的代码隐藏文件更改

public SLMapView()
{
    InitializeComponent();
    //An event handler for the mouse clicks...
    bingMap.MouseClick += 
      new EventHandler<MapMouseEventArgs>(bingMap_MouseClick);
    //Subscribe to message, we know what to do with user input
    Messenger.Default.Register<SetMapViewMsg>(this, SetViewHandler);
}

void bingMap_MouseClick(object sender, MapMouseEventArgs e)
{
    Location pointLocation = 
      bingMap.ViewportPointToLocation(e.ViewportPoint);
    Rectangle r = new Rectangle();
    r.Fill = new SolidColorBrush(Colors.Red);
    r.Stroke = new SolidColorBrush(Colors.Yellow);
    r.StrokeThickness = 1;
    r.Width = 8;
    r.Height = 8;

    SelectionLayer.Children.Clear();
    SelectionLayer.AddChild(r, pointLocation);
    //Pass the translated point to request reverse geocoding
    Messenger.Default.Send<MapPointClickMsg>(
       new MapPointClickMsg() { ClickLocation = pointLocation });
}

您可以像对待其他渲染表面一样,将UI元素添加到MapLayer。如果编译并运行,您将看到下面的结果。您会注意到,用户可以滚动视图并更改缩放级别,选定的位置仍会正确显示。当然,您可以将渲染更改为您认为合适的内容。例如,一个图钉?当然,这不必是点击的结果。您可能有一系列项目要显示在已知位置。关键在于,您可以在地图上绘制任何内容并将其与物理位置关联起来。

ClickPointRendering.png

上面的代码为我们接下来要做的事情提供了一个提示。在事件处理程序中,我们发送一条消息(给所有订阅者,目前还没有人订阅),并传递被点击位置的地理坐标。接下来,我们要做的是显示与用户单击的位置对应的街道地址,如果存在的话。当然,我们必须确定这个地址是什么。但在我们着手之前,让我们向用户界面添加一个重要的反馈功能。

忍耐吧,年轻人

在处理网络通信时,有一件事是可以保证的,那就是响应时间是不可预测的。在我们的应用程序中,我们正在异步调用服务,这些服务无疑会因各种网络问题而具有不同的响应时间。为了控制应用程序的状态并向用户提供反馈,我们需要向用户表明正在进行一项耗时活动。在搜索可用的内容时,我偶然发现了David Poll开发并与社区分享的这个控件。该控件充当应用程序的活动指示器。它非常灵活,而且使用起来相当直观。您可以在此处下载源代码和所有相关信息。要使用它,只需用该控件包装所需的UI区域,然后切换一个标志来显示/隐藏忙碌指示器并启用/禁用用户交互。在下面的代码中,整个MainPage都包装在控件中。

...
  xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"

  xmlns:ctl="clr-namespace:SLMapTest.View"
  mc:Ignorable="d"
  MinHeight="300" MinWidth="300"
  DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelMain}">
  <activity:Activity IsActive="{Binding IsBusy}">
    <Grid>
      <ctl:SLMapView></ctl:SLMapView>
    </Grid>
  </activity:Activity>
</UserControl>

现在,每当我们调用服务时,我们都会设置一个标志来显示我们正在忙碌,并在收到服务响应时清除该标志。

你在哪里?

好的,现在让我们向用户显示与点击位置相关的一些信息。这些信息将是离点击位置最近的地址。为此,我们需要在视图中添加一些字段来显示地址。下面显示了显示最终功能的修订版MainPage

ClickResponseData.png

所以,在我们之前显示的点击消息处理程序中,我们发送一条包含点击位置坐标的消息。但没有人收听,所以让我们添加一个监听器。SLMapViewModel知道如何调用Bing服务,所以我们将在此添加对消息的订阅。当我们收到结果时,我们将发布一条包含结果的相应消息。由于我们将MainPage上的控件绑定到MainViewModel,所以我们将在此添加对响应消息的订阅。所以,让我们从调用服务以响应MapPointClickMsg开始。首先,我们注册消息并提供处理程序

public SLMapViewModel()
{
   ...
   //Register to receive message when user clicks on the map
   Messenger.Default.Register<MapPointClickMsg>(this, MapClickHandler);
}

void MapClickHandler(MapPointClickMsg msgData)
{
    ReverseGeocode(msgData.ClickLocation);
}

我们上次使用的Bing地图服务公开了一个额外的方法,该方法提供了地理编码方法的反向功能。它被称为ReverseGeocodeAsync,它返回给定位置坐标的地址(如果存在)。所以它做的与我们之前做的正好相反。同样,像以前一样,由于这是一个异步调用,我们需要提供一个回调处理程序,我们可以在其中处理调用结果。以下是剩余的代码以及用于发布调用结果的消息的定义

private void ReverseGeocode(Location pointLocation)
{
    //Pack up a request
    BingGeocodeService.ReverseGeocodeRequest request = 
                 new BingGeocodeService.ReverseGeocodeRequest();
    // Don't raise exceptions.
    request.ExecutionOptions = new BingGeocodeService.ExecutionOptions();
    request.ExecutionOptions.SuppressFaults = true;

    request.Location = pointLocation;

    request.Credentials = new Credentials();
    request.Credentials.ApplicationId = 
       (string)App.Current.Resources["BingCredentialsKey"];

    // Make asynchronous call to fetch the data
    GeocodeClient.ReverseGeocodeAsync(request);
}
void geocodeClient_ReverseGeocodeCompleted(object sender, 
     BingGeocodeService.ReverseGeocodeCompletedEventArgs e)
{
    string callResult = "";
    try
    {
        if (e.Result.ResponseSummary.StatusCode != 
                     BingGeocodeService.ResponseStatusCode.Success ||
            e.Result.Results.Count == 0)
        {
            callResult = "Can't find it!";
        }
        else
        {
            //Publish results, somebody will know what to do with it
            ReverseGeocodeResultMsg resultMsg = new ReverseGeocodeResultMsg()
            {
                StreetAddress = e.Result.Results[0].Address.AddressLine,
                City = e.Result.Results[0].Address.Locality,
                State = e.Result.Results[0].Address.AdminDistrict
            };
            Messenger.Default.Send<ReverseGeocodeResultMsg>(resultMsg);
        }
    }
    catch
    {
        callResult = "Error processing request.";
    }

    resultString = callResult;
    this.RaisePropertyChanged("ResultString");
}
public struct ReverseGeocodeResultMsg
{
    public string StreetAddress;
    public string City;
    public string State;
}

现在,在MainViewModel中,我们订阅ReverseGeocodeResultMsg消息,以便我们可以用从服务调用返回的数据更新显示。

public MainViewModel()
{
    //Subscribe to the message, we know what to do
    Messenger.Default.Register<ReverseGeocodeResultMsg>(
                      this, ReverseGeocodeMessageHandler);
}
#region messenger message handler
void ReverseGeocodeMessageHandler(ReverseGeocodeResultMsg msgData)
{
    currentSelection = msgData;
    this.RaisePropertyChanged("Address");
    this.RaisePropertyChanged("City");
    this.RaisePropertyChanged("State");
}
#endregion

就这样。如果您编译并运行应用程序,您将看到上面显示的结果。我在下载中包含了一个包含此版本所有源代码的第二个项目。尽管如此,迄今为止并没有什么令人印象深刻的东西。但是我们已经知道如何处理用户与地图的交互,我们可以将用户鼠标单击位置转换为纬度/经度坐标,我们可以在特定纬度/经度坐标的地图上渲染图形,现在我们可以检索与这些坐标关联的地址信息。所以这已经很多功能了。然而,真正有趣的部分(至少对我来说)在于,当您为常见业务问题创建独特的解决方案时。让我们继续学习核心功能,并添加一些可以应用于真实世界业务场景的附加功能。

路线配送业务场景

我认为基于云的解决方案有很多机会可以受益于我们一直在描述的功能。其中一类业务围绕产品分销。送到您家门口的杂货店或便利店的牛奶就是由该类公司配送的。收银台通道的杂志和糖果通常由外部公司管理,这些公司负责货架维护。您在校园、休息区和其他许多地方看到的自动售货机由该类公司提供服务。您经常会看到百事可乐或百威啤酒的卡车停在商店里,它们正在进行送货。我可以继续说下去,但您可以看到这些类型的企业有很多例子。所有这些公司都有类似的流程。首先,它们都有一个或多个要运送到各个地点以供配送的产品。配送由一名司机完成,该司机通常按照固定的时间表遵循特定的路线。路线上的站点(地点)的安排是为了最大限度地减少完成配送所需的时间。

现在,不言而喻,仅仅提供行车路线并不能构成一个商业解决方案。任何解决方案都需要涵盖更多的业务流程,才能为企业带来利益。而这种利益通常是以成本降低的形式出现的,一切都关乎利润。如果一个解决方案能够通过优化运营和降低成本(减少错误的配送,减少产品损坏等)来提高企业的利润,那么您就有了销量。而且我还要补充一点,将手机作为解决方案的一部分可以带来全新的降低成本的机会。

所以,针对这些企业实体的任何解决方案的一个组成部分都必须包括一个路线编辑器。一个允许企业定义其服务区域的实用工具。一条路线是特定司机将要走的、用于配送产品的路径。一条路线又是由一个或多个路线段组成的集合。而路线段是同一条道路或街道的连续一段。当您从Bing获取行车路线时,它是以一系列路线段的形式提供的。然而,这些线段的粒度更细,因为它们被用作人类的行车指令。对我们来说,我们不需要那么详细。

正如我们上面所说的,所有这些应用程序的一个共同要求是允许企业定义一套路线,构成服务区域。这就是我们现在想要完成的。首先,我们将创建一个路线段的实体定义。然后,我们将提供用户界面,允许用户定义构成路线的新线段。之后我们会添加路线管理。

X标记地点

以下屏幕显示了为我们的演示项目修改后的用户界面。您可以在下载中包含的项目的最终版本中找到完整的项目源代码。

XMarksTheSpot1.png

因此,我们希望能够为用户提供定义路线的能力,而路线是由路线段组成的。路线段只是街道或道路的连续一部分。我们将路线段定义为具有以下属性

public class RouteSegment
{
    public int ID { get { return id; } }
    public int RouteID { get; set; }
    public string StreetName { get; set; }
    public int StartNumber { get; set; }
    public int EndNumber { get; set; }
    public Location StartXStreet { get; set; }
    public Location EndXStreet { get; set; }
    public List<Location> PlotPoints { get { return plotPoints; } }
    List<Location> plotPoints;
    int id;
    public RouteSegment(int id)
    {
        this.id = id;
        plotPoints = new List<Location>();
    }
    public override string ToString()
    {
        return StreetName + "(" + StartNumber + "-" + EndNumber + ")";
    }
}

在我们的演示项目中(如上所示),用户可以通过在地图上选择街道来定义线段(名称)。然后,通过选择开始和结束交叉街道来指定线段的“边界”。我们已经知道如何使用地图服务的GeocodeAsync方法获取地址的坐标。要获取交叉口的坐标,只需调整我们呈现给地图服务的查询格式即可。在街道地址的位置,我们将输入交叉街道的名称。例如,“38th St & Hudson Ave”将返回38街和哈德逊大道交叉口的坐标。以下是我们处理用户按下StartX按钮将当前选择设置为起始交叉口的方式。

void SetStartXStreetClick()
{
    //Cross street name can't be same as segment street
    string streetName = GetCurrentSelectionStreet();
    if (streetName != currentSegment.StreetName)
    {
        //Get intersection, if exists
        Messenger.Default.Send<GetStreetIntersectionMsg>(
                  new GetStreetIntersectionMsg()
        {
            SegmentStreet = currentSegment.StreetName,
            XStreetName = streetName,
            XCity = currentSelection.City,
            XState = currentSelection.State
        });
    }
    else
    {
        Messenger.Default.Send<UserActionAlertMsg>(
              new UserActionAlertMsg() { AlertText="Streets are same!"});
    }
}

因为我们只需要街道名称,所以我们使用了一个辅助方法GetCurrentSelectionStreet,该方法可以去除地址编号。然后我们构造一个消息,因为服务是由SLMapViewModel托管的。由于我们从两个不同的源发出请求,并且服务调用是异步的,所以我们在调用时传递一个令牌,以便在收到响应时能够确定如何操作。这就是下面和回调中所示的RequestType的目的。

void StreetIntersectionHandler(GetStreetIntersectionMsg msgData)
{
    GeocodeAddress(msgData.XStreetName + " & " + msgData.SegmentStreet + 
       "," + msgData.XCity + "," + msgData.XState, RequestType.XRequest);
}
...
private void GeocodeCompleted(object sender, 
             BingGeocodeService.GeocodeCompletedEventArgs e)
{
    //Clear busy flag...
    showBusy = false;
    this.RaisePropertyChanged("IsBusy");

    string callResult = "";
    Location xLocation = new Location();
    string xStreets = "";
    bool foundIntersection = false;

    try
    {
        //Was the service able to parse it? And did it return anything?
        if (e.Result.ResponseSummary.StatusCode != 
                      BingGeocodeService.ResponseStatusCode.Success ||
            e.Result.Results.Count == 0)
        {
            if ((RequestType)e.UserState == RequestType.FindRequest)
                callResult = "Can't find it!";
            else
                Messenger.Default.Send<StreetIntersectionResultMsg>(
                                     new StreetIntersectionResultMsg());
        }
        else
        {
            //What are we getting back?
            if ((RequestType)e.UserState == RequestType.FindRequest)
            {
                Location geoLocation = new Location(
                  e.Result.Results[0].Locations[0].Latitude, 
                  e.Result.Results[0].Locations[0].Longitude);
                // Zoom the map to the desired location
                Messenger.Default.Send<SetMapViewMsg>(
                  new SetMapViewMsg() { CenterLocation = geoLocation });
            }
            else
            {
                xLocation = e.Result.Results[0].Locations[0];
                xStreets = e.Result.Results[0].Address.AddressLine;
                foundIntersection = true;

                StreetIntersectionResultMsg result = 
                          new StreetIntersectionResultMsg();
                result.IntersectionLocation = xLocation;
                result.Intersection = xStreets;
                result.StreetsIntersect = foundIntersection;
                Messenger.Default.Send<StreetIntersectionResultMsg>(result);
            }
        }
    }
    catch
    {
        callResult = "Error processing request.";
    }

    resultString = callResult;
    this.RaisePropertyChanged("ResultString");
}

一旦我们有了交叉口的坐标,我们就保存信息并在交叉口处画一个“X”作为给用户的反馈。而且我们已经知道如何在地图上绘制东西。

void GetIntersectionMessageHandler(StreetIntersectionResultMsg msgData)
{
    //Start or end intersection?
    if (definitionState == SegmentDefinitionStates.StartXStreet)
    {
        if (msgData.StreetsIntersect)
        {
            currentSegment.StartXStreet = msgData.IntersectionLocation;
            //Plot an 'X' at the intersection
            Messenger.Default.Send<PlotIntersectionMsg>(
              new PlotIntersectionMsg(){ IntersectionLocation = 
              msgData.IntersectionLocation});
            definitionState = SegmentDefinitionStates.EndXStreet;
        }
        else
        {
            //Tell user that the selected street does not intersect segment street
            Messenger.Default.Send<UserActionAlertMsg>(
              new UserActionAlertMsg() { AlertText = "Streets do not intersect!" });
        }
    }
    else
    ...
}

路线

还有最后一件事情需要弄清楚,我们就有了路线编辑器的基本功能。一旦用户选择了开始和结束交叉口,我们就需要在地图上绘制路线。是的,我们知道如何在地图上绘制任何东西,这意味着我们可以轻松地绘制一条线。但是我们需要显示“可导航”的路径。换句话说,我们不能显示一条穿过建筑物或横跨没有道路的河流的路径。所以我们需要沿着一条可以步行或驾驶的路线绘制一条路径。一种可能的方法是强制用户指定路径上的所有交叉口。然后我们可以简单地连接这些点。这行不通,因为街道通常不是直的。幸运的是,解决方案来自于使用另一个Bing地图服务:路线服务。一旦我们知道了开始和结束的坐标,我们就可以简单地将它们传递给路线服务,我们将获得一个坐标集合,对应于要遵循的路径。这些坐标对应于街道中心坐标,并可用于绘制路径。将返回尽可能多的点,以确保我们可以绘制位于正在遵循的街道中心的线。

继续我们的演示项目,右键单击“服务引用”以添加一个服务代理,就像我们为地理编码服务所做的那样。对于WSDL地址,输入:http://staging.dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc?wsdl。对于命名空间,将默认值更改为BingRouteService,并且不要忘记“高级”按钮以消除名称冲突。由于我们将在MainViewModel中使用此服务,所以我们将在此添加一个成员属性。

private BingRouteService.RouteServiceClient RouteClient
{
    get
    {
        if (null == routeClient)
        {
            BasicHttpBinding binding = 
              new BasicHttpBinding(BasicHttpSecurityMode.None);
            UriBuilder serviceUri = new UriBuilder("http://dev.virtualearth.net/" + 
                       "webservices/v1/routeservice/routeservice.svc");

            //Create the Service Client
            routeClient = new BingRouteService.RouteServiceClient(binding, 
                              new EndpointAddress(serviceUri.Uri));
            routeClient.CalculateRouteCompleted += 
              new EventHandler<BingRouteService.CalculateRouteCompletedEventArgs>(
              routeClient_CalculateRouteCompleted);
        }
        return routeClient;
    }
}

正如您所看到的,我们还定义了异步调用的回调方法。以下是打包服务调用请求的辅助方法的代码

private void GetRouteSegmentPlot(Location startLocation, 
             Location endLocation, int segmentID)
{
    BingRouteService.RouteRequest request = 
              new BingRouteService.RouteRequest();

    request.Waypoints = new ObservableCollection<BingRouteService.Waypoint>();
    BingRouteService.Waypoint start = new BingRouteService.Waypoint();
    start.Location = new Location();
    start.Location.Latitude = startLocation.Latitude;
    start.Location.Longitude = startLocation.Longitude;
    start.Location.Altitude = startLocation.Altitude;
    request.Waypoints.Add(start);
    BingRouteService.Waypoint end = new BingRouteService.Waypoint();
    end.Location = new Location();
    end.Location.Altitude = endLocation.Altitude;
    end.Location.Latitude = endLocation.Latitude;
    end.Location.Longitude = endLocation.Longitude;
    request.Waypoints.Add(end);

    request.Options = new BingRouteService.RouteOptions();
    request.Options.RoutePathType = BingRouteService.RoutePathType.Points;
    request.Options.TrafficUsage = BingRouteService.TrafficUsage.None;
    request.Options.Optimization = 
             BingRouteService.RouteOptimization.MinimizeDistance;
    request.Options.Mode = BingRouteService.TravelMode.Walking;

    // Don't raise exceptions.
    request.ExecutionOptions = new BingRouteService.ExecutionOptions();
    request.ExecutionOptions.SuppressFaults = true;

    request.Credentials = new Credentials();
    request.Credentials.ApplicationId = 
      (string)App.Current.Resources["BingCredentialsKey"];

    // Make asynchronous call to fetch the data. Set busy indicator...
    RouteClient.CalculateRouteAsync(request, segmentID);
}

从上面的代码可以看出,该服务提供了比我们在应用程序中使用的更多的选项。它主要用于旅行方向,通常涉及优化距离或时间、交通影响等。所以我们并没有真正将其用于预期目的,但它满足了我们的需求。与地理编码服务一样,我们可以传递一个令牌来识别结果。在这种情况下,我们传递了segmentID来跟踪我们所请求的内容。目前我们还没有使用它,但如果我们想持久化数据,它将是必要的。回调处理如下所示。

void routeClient_CalculateRouteCompleted(object sender, 
       BingRouteService.CalculateRouteCompletedEventArgs e)
{
    try
    {
        if (e.Result.ResponseSummary.StatusCode != 
                BingRouteService.ResponseStatusCode.Success)
        {
            Messenger.Default.Send<UserActionAlertMsg>(
              new UserActionAlertMsg() { 
                  AlertText = "Could not determine route segment." });
        }
        else
        {
            BingRouteService.RoutePath routePath = e.Result.Result.RoutePath;
            currentSegment.PlotPoints.AddRange(routePath.Points);

            //Plot the segment
            PlotRouteSegmentMsg msg = new PlotRouteSegmentMsg();
            msg.segmentPoints = new List<Location>(routePath.Points);

            Messenger.Default.Send<PlotRouteSegmentMsg>(msg);
        }
    }
    catch
    {
        Messenger.Default.Send<UserActionAlertMsg>(
          new UserActionAlertMsg(){ AlertText="Got an exception calling route service."});
    }
}

现在我们有了一组点,我们所要做的就是将它们绘制在地图上的一个图层上。地图控件包含许多促进渲染操作的类型。我们已经使用了MapLayer,还有PushPinMapPolygon,以及我们将要使用的MapPolyline。这正是我们渲染路线段所需要的。以下是我们处理PlotRouteSegmentMsg消息的方式

void PlotSegmentHandler(PlotRouteSegmentMsg msgData)
{
    Color routeColor = Colors.Blue;
    SolidColorBrush routeBrush = new SolidColorBrush(routeColor);

    MapPolyline polyLine = new MapPolyline();
    polyLine.Locations = new LocationCollection();
    polyLine.Stroke = routeBrush;
    polyLine.Opacity = 0.65;
    polyLine.StrokeThickness = 5.0;

    foreach (Location pt in msgData.segmentPoints)
    {
        polyLine.Locations.Add(pt);
    }
    SegmentLayer.Children.Clear();
    SegmentLayer.Children.Add(polyLine);
}

RouteEditor.png

上面显示了编辑器的实际操作。开始和结束地址编号实际上并非定义线段所必需,但包含它们是为了展示解决方案实体(商店地址、自动售货机位置等)如何与路线相关联。

披萨配送

假设一个企业不像上面描述的产品分销场景那样遵循特定的路线。相反,这种类型的企业有一个服务区域。上面的编辑器可以轻松修改,允许用户选择任意数量的交叉路口作为其配送区域的边界。定义的区域可以在单独的图层上绘制以供识别,并且可以支持任意数量的区域。定义的区域中的所有地址都将构成他们的客户。为了确定哪个区域(如果有的话)包含特定地址,我们可以使用坐标并执行简单的包含检查。类似于检查一个点是否在矩形或图形内。

例如,考虑一个全国性或区域性的披萨连锁店,它可能有几个分支机构。每个分支机构又服务于一个预定义的区域,以便他们可以在允许的时间内配送产品。对于这种类型的企业,需要一条路线,但不是预先定义的。这种类型的企业将受益于一个解决方案,该解决方案能够为给定的一组位置(配送)动态确定最高效的路线。给定一组配送坐标(地址)和餐馆作为起点,算法可以轻松确定最佳配送路线。该解决方案甚至可以足够智能,以便在可用驱动程序之间平均分配工作负载。当然,解决方案的手机应用程序将指导配送,无需纸质说明,无需司机知道该区域,并且允许经理跟踪每次配送的进行情况(系统知道手机在哪里)。

调度服务

在大多数大城市,都有提供当日送达服务的公司。这些公司拥有一支信使队伍(通常是骑自行车的人),负责在一个地点取货,然后送到城市内或附近地区的另一个地点。利用我们一直在描述的设施,可以为这样的公司提供一个服务,使业务流程完全自动化。换句话说,无需办公室人员。客户可以通过网页输入取件/送件信息。然后系统可以自动确定完成配送的最有效方式。这将涉及一个算法,该算法将确定哪个信使最接近取件地点,哪些订单仍在队列中,目的地相对于其他取件订单的位置等等。然后,订单将通过手机应用程序以及所有其他相关的业务逻辑信息下发给每个信使。一些“炫酷”的功能可能是一个页面,客户可以在其中看到他们的包裹在移动,以及动态更新的预计送达时间。头脑风暴可以很有趣!

下一部分

我们一直在描述的目标应用程序的核心组件之一是手机。所以接下来,我计划看看将手机纳入解决方案需要做些什么。同时,我们将头脑风暴更多可以从我们所描述的功能中受益的业务解决方案。

© . All rights reserved.