作者: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完成,咱们最终实现的效果:
相应的代码比较容易看懂,结合上面实现的UI效果图和代码注释就能明白每部分代码所完成的功能,实现代码:
protected
override
void
CreateChildControls()
{
Controls.Clear();
base.CreateChildControls();
Create top level table#region Create top level table
System.Web.UI.WebControls.Table table = new System.Web.UI.WebControls.Table();
table.Width = System.Web.UI.WebControls.Unit.Pixel(240);
Controls.Add(table);
TableRow tr;
TableCell td;
#endregion
Orders Label#region Orders Label
tr = new TableRow();
td = new TableCell();
td.Text = "Select orders to service";
tr.Cells.Add(td);
table.Rows.Add(tr);
#endregion
Create and populate orders Listbox#region Create and populate orders Listbox
_oids = new List<int>();
_ordersCheckBoxList = new CheckBoxList();
_ordersCheckBoxList.ID = "OrdersCheckBoxList";
_ordersCheckBoxList.Width = System.Web.UI.WebControls.Unit.Point(200);
IServerContext serverContext = MapResourceLocal.ServerContextInfo.ServerContext;
IMap vrpMap = Utility.GetCartoIMap(MapInstance, "NA_MapResourceItem");
IFeatureLayer ordersInputFLayer = Utility.GetFeatureLayer("Stores", vrpMap);
IFeatureClass ordersInputFClass = ordersInputFLayer.FeatureClass;
int nameIndex = ordersInputFClass.FindField("Name");
IFeatureCursor ordersInputFCursor = ordersInputFClass.Search(null, false);
IFeature orderFeature = ordersInputFCursor.NextFeature();
while (orderFeature != null)
{
ListItem li = new ListItem(orderFeature.get_Value(nameIndex).ToString());
li.Selected = true;
_ordersCheckBoxList.Items.Add(li);
_oids.Add(orderFeature.OID);
orderFeature = ordersInputFCursor.NextFeature();
}
#endregion
OrdersPanel#region OrdersPanel
tr = new TableRow();
td = new TableCell();
Panel ordersPanel = new Panel();
ordersPanel.Height = 200;
ordersPanel.Width = 240;
ordersPanel.BorderColor = System.Drawing.Color.Black;
ordersPanel.BorderStyle = BorderStyle.Inset;
ordersPanel.BorderWidth = 1;
ordersPanel.ScrollBars = ScrollBars.Vertical;
ordersPanel.Controls.Add(_ordersCheckBoxList);
td.Controls.Add(ordersPanel);
tr.Cells.Add(td);
table.Rows.Add(tr);
#endregion
Get Directions Button#region Get Directions Button
tr = new TableRow();
tr.Attributes.Add("align", "right");
td = new TableCell();
td.ColumnSpan = 2;
HtmlInputButton button = new HtmlInputButton();
button.Value = "Get Directions";
button.ID = "execute";
td.Controls.Add(button);
tr.Cells.Add(td);
table.Rows.Add(tr);
#endregion
OnClick Event for executing task#region OnClick Event for executing task
string argument = string.Format("'selectedIndexes=' + getCheckedItemIndexes('{0}', '{1}')", _ordersCheckBoxList.ClientID, _ordersCheckBoxList.Items.Count);
string onClick = string.Format("executeTask({0},\"{1}\");", argument, CallbackFunctionString);
button.Attributes.Add("onclick", onClick);
#endregion
// Access the graphics layer so it is created and shown in the TOC
ElementGraphicsLayer pointsGraphicsLayer = PointsGraphicsLayer;
}
CreateChildControls用于构建VRPTask UI,除了界面要素之外,还需要从源数据中读取商店信息,如读取商店名称显示在界面上,当VRPTask中的商店被勾选上时,车辆将为该商店送货。商店供货信息存储在数据源中单独的一个图层中stores.shp,包含商店所需的货物数量和预计提供服务的时间。
VRPTask UI完成之后,接下来要设计VRP的业务逻辑,ArcGIS 9.3 Network Extension提供了一个基本的VRP解决方案,因此我们在发布NATasks服务的时候需要勾选Network Analyst功能,通过ServerContext去远程调用AO方法。
第一步,获取VRP分析图层。
IServerContext serverContext
=
MapResourceLocal.ServerContextInfo.ServerContext;
IMap vrpMap
=
Utility.GetCartoIMap(MapInstance,
"
NA_MapResourceItem
"
);
IGPMessages gpMessages
=
serverContext.CreateObject(
"
esriGeodatabase.GPMessages
"
)
as
IGPMessages;
INALayer2 vrpNALayer
=
Utility.GetNALayer(
"
Vehicle Routing Problem
"
, vrpMap);
INAContext vrpNAContext
=
vrpNALayer.CopyContext();
INAContextEdit vrpNAContextEdit
=
vrpNAContext
as
INAContextEdit;
vrpNAContextEdit.Bind(vrpNALayer.Context.NetworkDataset, gpMessages);
第二步,获取配送中心信息、商店信息、车辆信息和司机午餐时间。
IFeatureLayer depotsInputFLayer
=
Utility.GetFeatureLayer(
"
DistributionCenters
"
, vrpMap);
IFeatureClass depotsInputFClass
=
depotsInputFLayer.FeatureClass;
IFeatureCursor depotsInputFCursor
=
depotsInputFClass.Search(
null
,
false
);
LoadAnalysisClass(serverContext, vrpNAContext,
"
Depots
"
, depotsInputFCursor
as
ICursor);
//
Load Orders
IFeatureLayer ordersInputFLayer
=
Utility.GetFeatureLayer(
"
Stores
"
, vrpMap);
IFeatureClass ordersInputFClass
=
ordersInputFLayer.FeatureClass;
IFeatureCursor ordersInputFCursor
=
ordersInputFClass.GetFeatures(oids,
true
);
LoadAnalysisClass(serverContext, vrpNAContext,
"
Orders
"
, ordersInputFCursor
as
ICursor);
//
Load the Routes
ITable routesInputTable
=
Utility.GetStandaloneTable(
"
Vehicles
"
, vrpMap).Table;
ICursor routesInputCursor
=
routesInputTable.Search(
null
,
true
);
LoadAnalysisClass(serverContext, vrpNAContext,
"
Routes
"
, routesInputCursor
as
ICursor);
//
Load the Breaks
ITable breaksInputTable
=
Utility.GetStandaloneTable(
"
LunchBreaks
"
, vrpMap).Table;
ICursor breaksInputCursor
=
breaksInputTable.Search(
null
,
true
);
LoadAnalysisClass(serverContext, vrpNAContext,
"
Breaks
"
, breaksInputCursor
as
ICursor);
//
Message all of the network analysis agents that the analysis context has changed
vrpNAContextEdit.ContextChanged();
配送中心、商店信息均存储在物理图层中,分别对应DistributionCenters.shp、Stores.shp,车辆信息和司机午餐时间存储于Table表中。车辆Table包含物流配送过程中和车辆相关的一切信息,如起止配送中心、承载量、最多订单数、发车时间、最长驾驶时间、最长行驶距离等,司机午餐Table包含允许的午餐持续时间、允许的午餐时间范围等,这些都将用于ArcGIS VRP模型的计算中。
第三步,路径计算,做过ArcEngine Network Analyst开发的工程师对INASolver、INAVRPSolver一定非常熟悉了,调用过程比较简单,路径计算的同时处理系统反馈的消息信息。
gpMessages.Clear();
INASolver naSolver
=
vrpNAContext.Solver;
INAVRPSolver vrpSolver
=
naSolver
as
INAVRPSolver;
vrpSolver.GenerateInternalRouteContext
=
true
;
//
Required for true-shape and directions
vrpSolver.DefaultDate
=
DateTime.Today;
//
Set the default date to be today
bool
partialResults
=
naSolver.Solve(vrpNAContext, gpMessages,
null
);
//
report errors
if
(partialResults
||
gpMessages.Count
>
0
)
{
StringBuilder sErrors = new StringBuilder();
for (int i = 0; i < gpMessages.Count; i++)
sErrors.AppendLine(gpMessages.GetMessage(i).Description);
Results = sErrors.ToString();
return;
}
第四步,处理结果,VRP计算后最重要的结果就是生成的车辆分配情况、配送顺序和车辆配送路径,将车辆行驶的详细信息以图文并茂的方式展示出来。
//
Get Map's Spatial Reference (to project output geometries
ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality mf
=
(ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality)MapInstance.GetFunctionality(
"
NA_MapResourceItem
"
);
SpatialReference mapSpatialReference
=
mf.MapDescription.SpatialReference;
//
Output result Routes and Stops
Utility.OutputRoutesAsGraphics(serverContext, vrpNAContext, RoutesGraphicsLayer, mapSpatialReference);
Utility.OutputOrdersAsGraphics(serverContext, vrpNAContext, PointsGraphicsLayer, mapSpatialReference);
//
Create results node
TaskResultNode parentTaskResultNode
=
Utility.CreateTaskResultNode(
"
VRP Results
"
);
parentTaskResultNode.Expanded
=
true
;
//
Get the Route Context from the results to use for directions
INAVRPResult vrpResult
=
vrpNAContext.Result
as
INAVRPResult;
INAContext routeNAContext
=
vrpResult.InternalRouteContext;
//
Loop through the resulting routes and add items for each route (vehicle)
ISet routeSet
=
serverContext.CreateObject(
"
esriSystem.Set
"
)
as
ISet;
IFeatureClass routeRoutesFClass
=
routeNAContext.NAClasses.get_ItemByName(
"
Routes
"
)
as
IFeatureClass;
int
routeNameIndex
=
routeRoutesFClass.FindField(
"
Name
"
);
IFeatureCursor routesRouteFCursor
=
routeRoutesFClass.Search(
null
,
false
);
int
routeNumber
=
0
;
IFeature routeFeature
=
routesRouteFCursor.NextFeature();
while
(routeFeature
!=
null
)
{
string routeName = routeFeature.get_Value(routeNameIndex).ToString();
Choose color for each route#region Choose color for each route
TaskResultNode routeTaskResultNode = Utility.CreateTaskResultNode(routeName);
routeTaskResultNode.TextCellStyleAttributes.Add("font-weight", "bold");
routeTaskResultNode.TextCellStyleAttributes.Add("font-size", "12");
if (routeNumber == 0)
routeTaskResultNode.TextCellStyleAttributes.Add("color", System.Drawing.ColorTranslator.ToHtml(System.Drawing.Color.Blue));
else if (routeNumber == 1)
routeTaskResultNode.TextCellStyleAttributes.Add("color", System.Drawing.ColorTranslator.ToHtml(System.Drawing.Color.Purple));
else if (routeNumber == 2)
routeTaskResultNode.TextCellStyleAttributes.Add("color", System.Drawing.ColorTranslator.ToHtml(System.Drawing.Color.Green));
else if (routeNumber == 3)
routeTaskResultNode.TextCellStyleAttributes.Add("color", System.Drawing.ColorTranslator.ToHtml(System.Drawing.Color.Brown));
#endregion
routeTaskResultNode.Expanded = true;
parentTaskResultNode.Nodes.Add(routeTaskResultNode);
// Add Statistics
TaskResultNode vrpRouteStatisticsNode = Utility.GetVRPRouteStatisticsNode(serverContext, vrpNAContext, routeName);
vrpRouteStatisticsNode.TextCellStyleAttributes.Add("font-weight", "bold");
routeTaskResultNode.Nodes.Add(vrpRouteStatisticsNode);
// Add Directions
// Get the directions for the specified route
routeSet.RemoveAll();
routeSet.Add(routeFeature);// Get Directions
// Generate the directions
TaskResultNode directionsTaskResultNode = Utility.GetDirectionsNode(false, routeNAContext, routeSet);
directionsTaskResultNode.TextCellStyleAttributes.Add("font-weight", "bold");
// Add the directions to the results node
routeTaskResultNode.Nodes.Add(directionsTaskResultNode);
routeNumber++;
routeFeature = routesRouteFCursor.NextFeature();
}
通过上述过程,完成了VRPTask的UI设计和业务逻辑程序,之后需要将应用重新生成为dll,添加到ASP.Net工具箱中,方便WebGIS应用调用该Task控件,我们在Web Mapping Application模板应用程序基础上添加VRPTask,运行后效果:
数据源vehicles table中包含三辆汽车的记录,在应用中勾选需要进行配送的商店,
例如选择15家商店,点击"Get Directions"执行VRP计算,生成结果如下所示:
我们可以发现,很多路径配送需要考虑的问题ArcGIS VRP模型都提供了一套非常简便的解决方案,能够解决一般情况下的VRP问题,但是就如文章前面所说,VRP没有统一的解决方法,但是至少我们可以选择基于ArcGIS Server进行扩展,请思考:
1.地图数据结构。
2.配送分区怎么考虑。
3.配送效率测试。
4.结对订单。