作者:Flyingis

    本文严禁用于商业目的,如需转载请注明作者及原文链接,其他疑问请联系:dev.vip#gmail.com

    ArcGIS Server开发系列的文章至今已经一年多了,虽然文章只有短短六篇,也比较基础,但值得高兴的是帮助了不少第一次接触ArcGIS Server的开发者,现在不少都已经完成一两个项目了,相信收获不小,有时间可以和大家一起分享经验。今天开始,我们将继续这个系列教程,争取覆盖ADF开发常用功能,以帮助更多的人轻松入门ADF开发。

    目标:
    实现简易的物流配送(VRP)

    准备工作:
    1.重新复习《ArcGIS Server 开发系列(六)--自定义 Tasks》
    2.准备数据"%ArcGISInstallDir%\DeveloperKit\SamplesNET\Server\data\SanFranciscoNetwork"
    3.发布NATasks.mxd地图服务,添加Network Analyst功能服务
    4.MapResourceManager中添加一个ArcGIS Server Local类型服务

    在这个应用中,多车配送的功能封装为一个自定义的Task,然后生成一个dll添加到ASP.Net工具箱中,由Web Mapping Application的Task Manager调用,更改自定义Task的Task Results Container为模板应用中的TaskResults1控件。

    Web Mapping Application大家已经非常熟悉,现在的重点就在如何利用ArcGIS Server实现VRP功能。VRP全称vehicle routing problem,属于NP难问题,基本没有统一的方法来解决所有的VRP问题,只能根据具体的情况采用最合适的算法,咱们下面就利用ArcGIS Server模拟一个简单的应用场景,实现多车物流的配送计算。

    自定义Task,需要构建Task的UI和业务逻辑,UI构建通过重写方法CreateChildControls完成,咱们最终实现的效果:

ArcGIS Server 开发系列(七)--物流配送_silverlight

    相应的代码比较容易看懂,结合上面实现的UI效果图和代码注释就能明白每部分代码所完成的功能,实现代码:

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02protected override void CreateChildControls()
ArcGIS Server 开发系列(七)--物流配送_silverlight_03
{
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 Controls.Clear();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
base.CreateChildControls();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_silverlight_10 
Create top level table
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_20 
Orders Label
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flex_29 
Create and populate orders Listbox
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flex_56 
OrdersPanel
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_73 
Get Directions Button
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flex_89 
OnClick Event for executing task
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Access the graphics layer so it is created and shown in the TOC
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
 ElementGraphicsLayer pointsGraphicsLayer = PointsGraphicsLayer;
ArcGIS Server 开发系列(七)--物流配送_silverlight_100}


    CreateChildControls用于构建VRPTask UI,除了界面要素之外,还需要从源数据中读取商店信息,如读取商店名称显示在界面上,当VRPTask中的商店被勾选上时,车辆将为该商店送货。商店供货信息存储在数据源中单独的一个图层中stores.shp,包含商店所需的货物数量和预计提供服务的时间。 
ArcGIS Server 开发系列(七)--物流配送_休闲_101

    VRPTask UI完成之后,接下来要设计VRP的业务逻辑,ArcGIS 9.3 Network Extension提供了一个基本的VRP解决方案,因此我们在发布NATasks服务的时候需要勾选Network Analyst功能,通过ServerContext去远程调用AO方法。

    第一步,获取VRP分析图层。

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IServerContext serverContext = MapResourceLocal.ServerContextInfo.ServerContext;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IMap vrpMap 
= Utility.GetCartoIMap(MapInstance, "NA_MapResourceItem");
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IGPMessages gpMessages 
= serverContext.CreateObject("esriGeodatabase.GPMessages"as IGPMessages;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INALayer2 vrpNALayer 
= Utility.GetNALayer("Vehicle Routing Problem", vrpMap);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INAContext vrpNAContext 
= vrpNALayer.CopyContext();
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INAContextEdit vrpNAContextEdit 
= vrpNAContext as INAContextEdit;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02vrpNAContextEdit.Bind(vrpNALayer.Context.NetworkDataset, gpMessages);


    第二步,获取配送中心信息、商店信息、车辆信息和司机午餐时间。

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureLayer depotsInputFLayer = Utility.GetFeatureLayer("DistributionCenters", vrpMap);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureClass depotsInputFClass 
= depotsInputFLayer.FeatureClass;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureCursor depotsInputFCursor 
= depotsInputFClass.Search(nullfalse);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02LoadAnalysisClass(serverContext, vrpNAContext, 
"Depots", depotsInputFCursor as ICursor);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Load Orders
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
IFeatureLayer ordersInputFLayer = Utility.GetFeatureLayer("Stores", vrpMap);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureClass ordersInputFClass 
= ordersInputFLayer.FeatureClass;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureCursor ordersInputFCursor 
= ordersInputFClass.GetFeatures(oids, true);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02LoadAnalysisClass(serverContext, vrpNAContext, 
"Orders", ordersInputFCursor as ICursor);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Load the Routes
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ITable routesInputTable = Utility.GetStandaloneTable("Vehicles", vrpMap).Table;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02ICursor routesInputCursor 
= routesInputTable.Search(nulltrue);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02LoadAnalysisClass(serverContext, vrpNAContext, 
"Routes", routesInputCursor as ICursor);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Load the Breaks
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ITable breaksInputTable = Utility.GetStandaloneTable("LunchBreaks", vrpMap).Table;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02ICursor breaksInputCursor 
= breaksInputTable.Search(nulltrue);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02LoadAnalysisClass(serverContext, vrpNAContext, 
"Breaks", breaksInputCursor as ICursor);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Message all of the network analysis agents that the analysis context has changed
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
vrpNAContextEdit.ContextChanged();


    配送中心、商店信息均存储在物理图层中,分别对应DistributionCenters.shp、Stores.shp,车辆信息和司机午餐时间存储于Table表中。车辆Table包含物流配送过程中和车辆相关的一切信息,如起止配送中心、承载量、最多订单数、发车时间、最长驾驶时间、最长行驶距离等,司机午餐Table包含允许的午餐持续时间、允许的午餐时间范围等,这些都将用于ArcGIS VRP模型的计算中。

    第三步,路径计算,做过ArcEngine Network Analyst开发的工程师对INASolver、INAVRPSolver一定非常熟悉了,调用过程比较简单,路径计算的同时处理系统反馈的消息信息。

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02gpMessages.Clear();
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INASolver naSolver 
= vrpNAContext.Solver;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INAVRPSolver vrpSolver 
= naSolver as INAVRPSolver;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02vrpSolver.GenerateInternalRouteContext 
= true// Required for true-shape and directions
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
vrpSolver.DefaultDate = DateTime.Today;        // Set the default date to be today
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
bool partialResults = naSolver.Solve(vrpNAContext, gpMessages, null);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// report errors
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
if (partialResults || gpMessages.Count > 0)
ArcGIS Server 开发系列(七)--物流配送_Arcgis_143
{
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 StringBuilder sErrors 
= new StringBuilder();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
for (int i = 0; i < gpMessages.Count; i++)
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 sErrors.AppendLine(gpMessages.GetMessage(i).Description);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 Results 
= sErrors.ToString();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
return;
ArcGIS Server 开发系列(七)--物流配送_silverlight_100}


    第四步,处理结果,VRP计算后最重要的结果就是生成的车辆分配情况、配送顺序和车辆配送路径,将车辆行驶的详细信息以图文并茂的方式展示出来。

ArcGIS Server 开发系列(七)--物流配送_Arcgis_02// Get Map's Spatial Reference (to project output geometries
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality mf = (ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality)MapInstance.GetFunctionality("NA_MapResourceItem");
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02SpatialReference mapSpatialReference 
= mf.MapDescription.SpatialReference;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Output result Routes and Stops
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
Utility.OutputRoutesAsGraphics(serverContext, vrpNAContext, RoutesGraphicsLayer, mapSpatialReference);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02Utility.OutputOrdersAsGraphics(serverContext, vrpNAContext, PointsGraphicsLayer, mapSpatialReference);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Create results node
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
TaskResultNode parentTaskResultNode = Utility.CreateTaskResultNode("VRP Results");
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02parentTaskResultNode.Expanded 
= true;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Get the Route Context from the results to use for directions
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
INAVRPResult vrpResult = vrpNAContext.Result as INAVRPResult;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02INAContext routeNAContext 
= vrpResult.InternalRouteContext;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
// Loop through the resulting routes and add items for each route (vehicle)
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
ISet routeSet = serverContext.CreateObject("esriSystem.Set"as ISet;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureClass routeRoutesFClass 
= routeNAContext.NAClasses.get_ItemByName("Routes"as IFeatureClass;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
int routeNameIndex = routeRoutesFClass.FindField("Name");
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeatureCursor routesRouteFCursor 
= routeRoutesFClass.Search(nullfalse);
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
int routeNumber = 0;
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02IFeature routeFeature 
= routesRouteFCursor.NextFeature();
ArcGIS Server 开发系列(七)--物流配送_Arcgis_02
while (routeFeature != null)
ArcGIS Server 开发系列(七)--物流配送_silverlight_177
{
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
string routeName = routeFeature.get_Value(routeNameIndex).ToString();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_silverlight_182 
Choose color for each route
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 routeTaskResultNode.Expanded 
= true;
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 parentTaskResultNode.Nodes.Add(routeTaskResultNode);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Add Statistics
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
 TaskResultNode vrpRouteStatisticsNode = Utility.GetVRPRouteStatisticsNode(serverContext, vrpNAContext, routeName);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 vrpRouteStatisticsNode.TextCellStyleAttributes.Add(
"font-weight""bold");
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 routeTaskResultNode.Nodes.Add(vrpRouteStatisticsNode);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Add Directions
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Get the directions for the specified route
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
 routeSet.RemoveAll();
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 routeSet.Add(routeFeature);
// Get Directions
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Generate the directions
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
 TaskResultNode directionsTaskResultNode = Utility.GetDirectionsNode(false, routeNAContext, routeSet);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 directionsTaskResultNode.TextCellStyleAttributes.Add(
"font-weight""bold");
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 
// Add the directions to the results node
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
 routeTaskResultNode.Nodes.Add(directionsTaskResultNode);
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 routeNumber
++;
ArcGIS Server 开发系列(七)--物流配送_flashbuilder_06 routeFeature 
= routesRouteFCursor.NextFeature();
ArcGIS Server 开发系列(七)--物流配送_silverlight_100}


    通过上述过程,完成了VRPTask的UI设计和业务逻辑程序,之后需要将应用重新生成为dll,添加到ASP.Net工具箱中,方便WebGIS应用调用该Task控件,我们在Web Mapping Application模板应用程序基础上添加VRPTask,运行后效果:
ArcGIS Server 开发系列(七)--物流配送_flex_222    数据源vehicles table中包含三辆汽车的记录,在应用中勾选需要进行配送的商店,

    例如选择15家商店,点击"Get Directions"执行VRP计算,生成结果如下所示:
ArcGIS Server 开发系列(七)--物流配送_Arcgis_223   

    我们可以发现,很多路径配送需要考虑的问题ArcGIS VRP模型都提供了一套非常简便的解决方案,能够解决一般情况下的VRP问题,但是就如文章前面所说,VRP没有统一的解决方法,但是至少我们可以选择基于ArcGIS Server进行扩展,请思考:
    1.地图数据结构。
    2.配送分区怎么考虑。
    3.配送效率测试。
    4.结对订单。