作者: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.結對訂單。