Tag Archives: ArcGIS Server

ArcGIS中的线性参考/动态分段技术(三)几个Linear Referencing应用场景在AGS中的实现

        现在来实现上面提到的3个功能。
1、对于某条公路上的一点进行Identify操作,要求返回该点在公路上的桩号值:
        以Silverlight API为例。为了在服务器端使用ArcObjects,在Asp.net工程中添加一个名为LinearRef的Silverlight-Enabled WCF Service,在LinearRef.svc.cs文件中添加以下代码:

namespace RoadDycSeg.Web.WCF
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class LinearRef
{
public IRouteLocator2 pRtLocator = null;
ESRI.ArcGIS.Server.IServerObjectManager pSOM = null;
public ESRI.ArcGIS.Server.IServerContext pServerContext = null;

public LinearRef()
{
ESRI.ArcGIS.ADF.Identity identity = new ESRI.ArcGIS.ADF.Identity("ArcGISWebServices", "yourpassword", "");
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsconn = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection("localhost", identity);
agsconn.Connect();
pSOM = agsconn.ServerObjectManager;
pServerContext = pSOM.CreateServerContext("shaanxi", "MapServer");
ESRI.ArcGIS.Carto.IMapServer2 pMapServer = pServerContext.ServerObject as ESRI.ArcGIS.Carto.IMapServer2;
ESRI.ArcGIS.Carto.IMapServerObjects2 pMapServerObjects = pMapServer as ESRI.ArcGIS.Carto.IMapServerObjects2;
ESRI.ArcGIS.Carto.IMap pMap = pMapServerObjects.get_Map(pMapServer.DefaultMapName);
IFeatureClass pFC = (pMap.get_Layer(2) as ESRI.ArcGIS.Carto.IFeatureLayer).FeatureClass;

//create the RouteLocator
IDataset dS = (IDataset)pFC; // A polylineM feature class.
IName name = dS.FullName;
IRouteLocatorName rtLocatorName = pServerContext.CreateObject("esriLocation.RouteMeasureLocatorName") as IRouteLocatorName;
rtLocatorName.RouteFeatureClassName = name;
rtLocatorName.RouteIDFieldName = "道路编码";
rtLocatorName.RouteMeasureUnit = esriUnits.esriUnknownUnits;
name = (IName)rtLocatorName;
pRtLocator = (IRouteLocator2)name.Open();

pServerContext.ReleaseContext();
}

// Add more operations here and mark them with [OperationContract]

[OperationContract]
///

/// used by the client Identify operation and return the result
///

/// the x coords of the mouseclick in map units /// the y coords of the mouseclick in map units /// the map distance of 1 pixel /// a identifyresult class defined in Classes.cs. If no result, mvalue=x=y=-1
public IdentifyResult Identify(double mapX, double mapY, double resolution)
{
pServerContext = pSOM.CreateServerContext("shaanxi", "MapServer");

IdentifyResult ir = new IdentifyResult("", -1, -1, -1);

IPoint pPoint = pServerContext.CreateObject("esriGeometry.Point") as IPoint;
pPoint.PutCoords(mapX, mapY);
IEnvelope pEnvelope = pPoint.Envelope;
pEnvelope.Expand(10 * resolution, 10 * resolution, false);

IEnumRouteIdentifyResult pEnumResult = pRtLocator.Identify(pEnvelope, "");
pEnumResult.Reset();
//only get the first result
if (pEnumResult.Count > 0)
{
IRouteLocation pRL = null;
IFeature pF = null;
pEnumResult.Next(out pRL, out pF);
IRouteMeasurePointLocation pRMPL = pRL as IRouteMeasurePointLocation;

//retrieve the location geometry by calling LOCATE method
IRouteLocation routeLocation = pServerContext.CreateObject("esriLocation.RouteMeasurePointLocation") as IRouteLocation;
routeLocation.MeasureUnit = esriUnits.esriUnknownUnits;
routeLocation.RouteID = pRL.RouteID;
routeLocation.LateralOffset = 0;
IRouteMeasurePointLocation rMPointLoc = (IRouteMeasurePointLocation)routeLocation;
rMPointLoc.Measure = pRMPL.Measure;

IGeometry geom;
esriLocatingError locError;
pRtLocator.Locate((IRouteLocation)rMPointLoc, out geom, out locError);

ir.RouteID = pRL.RouteID.ToString();
ir.MValue = pRMPL.Measure;
ir.X = (geom as IPointCollection5).get_Point(0).X;
ir.Y = (geom as IPointCollection5).get_Point(0).Y;
}

pServerContext.ReleaseContext();

return ir;
}

        其中,IdentifyResult是自定义的一个类,用来存储查询的结果:
namespace RoadDycSeg.Web.WCF
{
///

/// the result of Identify operation
///

[DataContract]
public class IdentifyResult
{
[DataMember]
public string RouteID {get;set;}
[DataMember]
public double MValue {get;set;}
[DataMember]
public double X {get;set;}
[DataMember]
public double Y {get;set;}

public IdentifyResult(string routeid, double mvalue, double x, double y)
{
RouteID = routeid;
MValue = mvalue;
X = x;
Y = y;
}
}
}

        Identify方法需要传入3个参数,一个点的地理坐标以及当前地图的Resolution,后者用来生成一个10像素的缓冲区,方便用户的点击操作;其中利用IRouteLocator2.Identify方法获得M值,据此利用 IRouteLocator2.Locate方法获得精确落在公路上的点的Geometry,用以显示在客户端;如果用户点击的点距离公路较远,则IRouteLocator2.Identify结果为空,返回的IdentifyResult中M值为-1。
        在Silverlight工程中,Add Service Reference,找到刚才的LinearRef服务,取名为LRService。
        客户端中,新建一个COperation类,实现业务操作。
public class COperation
{
private LRService.LinearRefClient LR = null;
private GraphicsLayer glayer = null;
private bool isBusy = false;//indicate whether there is an operation is performing
private Map Map1 = null;
public COperation(Map map)
{
Map1=map;
glayer = Map1.Layers["glayer"] as GraphicsLayer;
//initialize the LR object
LR = new RoadDycSeg.LRService.LinearRefClient();
LR.IdentifyCompleted += new EventHandler(LR_IdentifyCompleted);
}
private void LR_IdentifyCompleted(object sender, LRService.IdentifyCompletedEventArgs e)
{
LRService.IdentifyResult ir = e.Result;
//add a point graphic to the map with attributes displaying by the maptip, maptip defined in mainpage.xaml
if (ir.X > 0)//the result exists
{
Graphic g = new Graphic()
{
Geometry = new MapPoint(ir.X, ir.Y),
Symbol = Application.Current.Resources["strobeSymbol"] as Symbol,
};
g.Attributes.Add("RouteID", ir.RouteID);
g.Attributes.Add("桩号", ir.MValue);
g.Attributes.Add("X坐标", ir.X);
g.Attributes.Add("Y坐标", ir.Y);
glayer.Graphics.Clear();
glayer.Graphics.Add(g);
}
isBusy = false;
}

private void Map1_Identify(object sender, ESRI.ArcGIS.Client.Map.MouseEventArgs e)
{
if (!isBusy)
{
LR.IdentifyAsync(e.MapPoint.X, e.MapPoint.Y, Map1.Resolution);
isBusy = true;
}
}

        在需要使用Identify功能时,将Map控件的Click事件绑定到Map1_Identify即可。结果如图:

2、输入起始和终止桩号,要求将其间的路段显示在地图上:
        要解决这个问题,关键是根据两个M值,求的其间一段公路的Geometry。利用的仍然是IRouteLocator2.Locate方法,其中的第一个参数routeLocation,传入的是用IRouteMeasureLineLocation接口定义的“线性位置”,具体可参考帮助文档。
        仍然是在LinearRef.svc.cs文件的LinearRef类中,添加以下方法:
[OperationContract(Name = "RetrieveRoutePortionGeometry")]
///

/// used by the client,such as QueryPortion operation, to retrieve the geometry of the route portion
///

/// the route id which the portion is part of /// fromMvalue /// toMvalue /// a json string with specific format,such as [{"x":1111,"y":1111},{"x":2222,"y":2222},{"x":3333,"y":3333}.....]
public string RetrieveRoutePortionGeometry(string routeid, double fromMValue, double toMValue)
{
pServerContext = pSOM.CreateServerContext("shaanxi", "MapServer");
IRouteLocation routeLoc = pServerContext.CreateObject("esriLocation.RouteMeasureLineLocation") as IRouteLocation;
routeLoc.MeasureUnit = esriUnits.esriUnknownUnits;
routeLoc.RouteID = routeid;
routeLoc.LateralOffset = 0;
IRouteMeasureLineLocation rMLineLoc = (IRouteMeasureLineLocation)routeLoc;
rMLineLoc.FromMeasure = fromMValue;
rMLineLoc.ToMeasure = toMValue;

IGeometry geom;
esriLocatingError locError;
pRtLocator.Locate((IRouteLocation)rMLineLoc, out geom, out locError);

pServerContext.ReleaseContext();

//return the routeportion's vertices
StringBuilder sb = new StringBuilder("[");
IPolyline pLine = geom as IPolyline;
IPointCollection pPC = pLine as IPointCollection;
//json format such as:
//[{"x":1111,"y":1111},{"x":2222,"y":2222},{"x":3333,"y":3333}.....]
for (int i = 0; i < pPC.PointCount; i++)
{
sb.Append("{"x":");
sb.Append(pPC.get_Point(i).X.ToString());
sb.Append(","y":");
sb.Append(pPC.get_Point(i).Y.ToString());
sb.Append("},");
}
//remove the last ","
sb.Remove(sb.Length - 1, 1);
sb.Append("]");
return sb.ToString();
}


        由于在服务器端的AO操作,产生的结果是ESRI.ArcGIS.Geometry.IGeometry类型,而客户端需要的则是ESRI.ArcGIS.Client.Geometry.Geometry类型,所以需要对结果序列化/反序列化,这里采用JSON字符串处理。客户端处理结果的函数仍然放在COperation.cs文件中,如下:
private void LR_RetrieveRoutePortionGeometryCompleted(object sender,LRService.RetrieveRoutePortionGeometryCompletedEventArgs e)
{
//create a esri.arcgis.client.geometry.polyline from the coords of points contained in the json string
string jsonpoints = e.Result;
JsonArray jsonarrays = (JsonArray)JsonArray.Load(new System.IO.StringReader(jsonpoints));
ESRI.ArcGIS.Client.Geometry.Polyline line = new ESRI.ArcGIS.Client.Geometry.Polyline()
{
SpatialReference = Map1.SpatialReference,
};
ESRI.ArcGIS.Client.Geometry.PointCollection pc = new ESRI.ArcGIS.Client.Geometry.PointCollection();
foreach (JsonObject jo in jsonarrays)
{
pc.Add(new ESRI.ArcGIS.Client.Geometry.MapPoint(jo["x"], jo["y"]));
}
line.Paths.Add(pc);

//add a graphic
Graphic g = new Graphic()
{
Geometry = line,
Symbol = Application.Current.Resources["LineSymbol"] as SimpleLineSymbol,
};
glayer.Graphics.Add(g);
}

        Silverlight中提供了System.Json库,可以非常方便的对JSON字符串进行解析。结果如图:

3、类似GoogleMap的交通流量地图:
        首先看一下Google的交通流量地图:

        可以看出,之所以能产生不同颜色段,需要的数据有3个:该段的起始和终止M值,该段的车流量。假设我们获取的业务数据表(EventTable)如下:

        联想到上面第二个应用场景,我们便想可以获取所有交通数据后,用循环的办法展示出交通流量地图。但这里有两个问题需要思考:1、即使使用IRouteLocator2.LocateRow方法,服务器端根据起止M值解析出所有的分段图形需要一次循环,客户端将这些图形显示出来还需要一次循环,这样是否合理?2、由于结果显示在客户端,不同用户发出请求时,都将重复上面两个循环,如何改进?
        解决问题一:在第二节的Linear Referencing实现原理中,提到了RouteEventSource类,可以将它看作是把Route FeatureClass和EventTable组合在一起的结果,而该类继承自FeatureClass,可当作一个FeatureClass来用。在ArcMap中观察生成后(利用Make Route Event Layer工具)的RouteEventSource层:

        不仅包含了EventTable中的事件,还有了我们想要的Shape字段(可取出Geometry)。其实Shape字段中的内容,是利用Dynamic Segmentation技术动态计算出来的。看其图层属性便知,它是在内存中动态生成的:

        有了FeatureClass,我们便可以对整个图层进行一次渲染,避免服务器端和客户端的两次循环了。这里要注意,FeatureClass是动态生成的,图层便是动态添加,在地图服务的REST接口中无法暴露出来,也就不能利用客户端API的渲染技术了。
        解决问题二:利用SOA的思想,将交通流量图做成一个Traffic服务,不同用户请求时动态添加该服务即可。想想这个服务应该具有的特性:不同时段的交通图,需要动态生成;如果交通图已经生成,则无需重新生成,显示即可。所以我们采取池化方式服务(pooled-service)的特性,刚好解决这一问题。
        这个场景我们用Web ADF来实现(客户端API与前两个场景相同)。
        首先准备两个Map Service:底图服务:shaanxi,交通流量图服务:ShaanxiTraffic。
        后者在ArcMap中打开是这样的:

        ShaanxiTraffic服务除了动态生成的交通流量图外,不需要在显示任何信息,所以发布时是空白的;因为动态生成的RouteEventSource将作为图层插入到这个地图服务中,所以Route FeatureClass必须也在这个mxd中才行。
        Default.aspx页面中放置一个MapResourceManager控件、一个Map控件、一个CallbackButton按钮和一个GridView控件。先看一下结果:

        点击按钮后:

        看一下Default.aspx.cs中按钮的点击事件:
protected void CallbackButton1_Clicked(object sender, EventArgs args)
{
//先判断Traffic服务中是否已经产生结果
if (hasTrafficResult())//Traffic服务中已经有结果
{
//将结果插入地图
InsertTrafficService();
}
else//服务中还没有结果
{
//先生成交通流量结果
MakeResult();
//将结果插入地图
InsertTrafficService();
}
//显示RouteEventSource的属性表
DisplayAttributeTable();
}

        思路都在代码中。依次来看几个函数:
///

/// 判断池化的Traffic服务中,获取的ServerContext是否已经产生结果
///

/// true or false
private bool hasTrafficResult()
{
ESRI.ArcGIS.ADF.Identity identity = new ESRI.ArcGIS.ADF.Identity("ArcGISWebServices", "yourpassword", "");
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsconn = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection("machinename", identity);
agsconn.Connect();
pSOM = agsconn.ServerObjectManager;
ESRI.ArcGIS.Server.IServerContext pServerContext = pSOM.CreateServerContext("ShaanxiTraffic", "MapServer");
ESRI.ArcGIS.Carto.IMapServer2 pMapServer = pServerContext.ServerObject as ESRI.ArcGIS.Carto.IMapServer2;
ESRI.ArcGIS.Carto.IMapServerObjects2 pMapServerObjects = pMapServer as ESRI.ArcGIS.Carto.IMapServerObjects2;
ESRI.ArcGIS.Carto.IMap pMap = pMapServerObjects.get_Map(pMapServer.DefaultMapName);
pServerContext.ReleaseContext();
return pMap.LayerCount == 1 ? false : true;
}

        对于ShaanxiTraffic服务,在获得的Server Object中,查看当前Map的图层数(默认有一个隐藏的Road图层),若为1则还未产生结果。
        下面是动态添加服务的函数,注意要设置背景透明,以便能够看到底图服务:
///

/// 动态添加服务
///
private void InsertTrafficService()
{
if (MapResourceManager1.ResourceItems.Count==1)//若已插入交通流量服务,则不做动作
{
MapResourceItem mapResourceItem = new MapResourceItem();
GISResourceItemDefinition definition = new GISResourceItemDefinition();
mapResourceItem.Name = "Traffic";
definition.ResourceDefinition = "Layers@ShaanxiTraffic";
definition.DataSourceDefinition = "machinename";
definition.DataSourceType = "ArcGIS Server Local";
mapResourceItem.Parent = MapResourceManager1;
mapResourceItem.Definition = definition;
ESRI.ArcGIS.ADF.Web.DisplaySettings displaysettings = new ESRI.ArcGIS.ADF.Web.DisplaySettings();
displaysettings.Transparency = 0.0F;
displaysettings.Visible = true;
ESRI.ArcGIS.ADF.Web.ImageDescriptor imagedescriptor = new ESRI.ArcGIS.ADF.Web.ImageDescriptor();
imagedescriptor.TransparentBackground = true;
imagedescriptor.TransparentColor = System.Drawing.Color.White;
displaysettings.ImageDescriptor = imagedescriptor;
mapResourceItem.DisplaySettings = displaysettings;

MapResourceManager1.ResourceItems.Insert(0, mapResourceItem);
MapResourceManager1.CreateResource(mapResourceItem);

if (Map1.ImageBlendingMode == ImageBlendingMode.WebTier)
{
Map1.Refresh();
}
else
{
Map1.RefreshResource("Traffic");
}

CallbackButton1.CallbackResults.CopyFrom(Map1.CallbackResults);
}
}

        MakeResult函数利用ESRI.ArcGIS.Location库来生成交通流量图。由于Traffic服务是池化服务,当第一个用户请求,生成交通流量图后,该实例(servercontext)会返回服务器上而不被销毁,所以下一个用户再次请求时可以直接显示。如果要实现实时的交通流量图,定时在服务器端重生servercontext,并读取当前时段的EventTable即可。
///

/// 生成交通流量的结果
///

private void MakeResult()
{
ESRI.ArcGIS.Server.IServerContext pServerContextTraffic = pSOM.CreateServerContext("ShaanxiTraffic", "MapServer");
ESRI.ArcGIS.Carto.IMapServer2 pMapServer = pServerContextTraffic.ServerObject as ESRI.ArcGIS.Carto.IMapServer2;
ESRI.ArcGIS.Carto.IMapServerObjects2 pMapServerObjects = pMapServer as ESRI.ArcGIS.Carto.IMapServerObjects2;
ESRI.ArcGIS.Carto.IMap pMap = pMapServerObjects.get_Map(pMapServer.DefaultMapName);
//创建RouteLocator
IFeatureClass pFC = (GetLayerByName("Road",pMap) as IFeatureLayer).FeatureClass;
IDataset dS = (IDataset)pFC; // A polylineM feature class.
IName name = dS.FullName;
IRouteLocatorName rtLocatorName = pServerContextTraffic.CreateObject("esriLocation.RouteMeasureLocatorName") as IRouteLocatorName;
rtLocatorName.RouteFeatureClassName = name;
rtLocatorName.RouteIDFieldName = "道路编码";
rtLocatorName.RouteMeasureUnit = esriUnits.esriUnknownUnits;
name = (IName)rtLocatorName;
IRouteLocator2 pRtLocator = (IRouteLocator2)name.Open();

//创建RouteEventProperties,为RouteEventSource做准备
IRouteEventProperties2 rtProp = pServerContextTraffic.CreateObject("esriLocation.RouteMeasureLineProperties") as IRouteEventProperties2;
rtProp.AddErrorField = true;
rtProp.ErrorFieldName = "LOC_ERROR";
rtProp.EventMeasureUnit = esriUnits.esriUnknownUnits;
rtProp.EventRouteIDFieldName = "RouteID";
IRouteMeasureLineProperties rMLineProp = (IRouteMeasureLineProperties)rtProp;
rMLineProp.FromMeasureFieldName = "FromM";
rMLineProp.ToMeasureFieldName = "ToM";

//创建RouteEventSource
ITable eventTable = (pFC.FeatureDataset.Workspace as IFeatureWorkspace).OpenTable("lineEventsTable");
IDataset ds = (IDataset)eventTable;
IName name2 = ds.FullName;
IRouteEventSourceName rtEvtSrcName = pServerContextTraffic.CreateObject("esriLocation.RouteEventSourceName") as IRouteEventSourceName;
rtEvtSrcName.EventTableName = name2;
rtEvtSrcName.EventProperties = (IRouteEventProperties)rMLineProp;
rtEvtSrcName.RouteLocatorName = rtLocatorName;
name2 = (IName)rtEvtSrcName;
IRouteEventSource rtEvtSrc = (IRouteEventSource)name2.Open();
//转换到FeatureClass
pFC = rtEvtSrc as IFeatureClass;

//将RouteEventSource添加到地图中,并进行唯一值渲染
IUniqueValueRenderer pUVR = pServerContextTraffic.CreateObject("esriCarto.UniqueValueRenderer") as IUniqueValueRenderer;
pUVR.FieldCount = 1;
pUVR.set_Field(0, "Vehicles");
pUVR.UseDefaultSymbol = false;
IFeatureCursor pFeatureCursor = pFC.Search(null, false);
IFeature pFeature = pFeatureCursor.NextFeature();
int ifiledindex = pFC.Fields.FindField("Vehicles");//EventTable中的车流量字段
while (pFeature != null)
{
ISimpleLineSymbol pSymbol = pServerContextTraffic.CreateObject("esriDisplay.SimpleLineSymbol") as ISimpleLineSymbol;
double value = double.Parse(pFeature.get_Value(ifiledindex).ToString());
if (value <= 20)
pSymbol.Color = ESRI.ArcGIS.ADF.Converter.ToRGBColor(pServerContextTraffic, System.Drawing.Color.FromArgb(71, 196, 69)) as IColor;
else if (value > 20 && value <= 50)
pSymbol.Color = ESRI.ArcGIS.ADF.Converter.ToRGBColor(pServerContextTraffic, System.Drawing.Color.FromArgb(255, 255, 0)) as IColor;
else if (value > 50 && value <= 100)
pSymbol.Color = ESRI.ArcGIS.ADF.Converter.ToRGBColor(pServerContextTraffic, System.Drawing.Color.FromArgb(201, 59, 46)) as IColor;
else
pSymbol.Color = ESRI.ArcGIS.ADF.Converter.ToRGBColor(pServerContextTraffic, System.Drawing.Color.FromArgb(250, 52, 17)) as IColor;
pSymbol.Width = 5;
pUVR.AddValue(pFeature.get_Value(ifiledindex).ToString(), "Vehicles", pSymbol as ISymbol);
pFeature = pFeatureCursor.NextFeature();
}
IFeatureLayer pFL = pServerContextTraffic.CreateObject("esriCarto.FeatureLayer") as IFeatureLayer;
pFL.Name = "RouteEventSource";
pFL.FeatureClass = pFC;
(pFL as IGeoFeatureLayer).Renderer = pUVR as IFeatureRenderer;

pMap.AddLayer(pFL as ILayer);//to the 0 index
pMapServerObjects.RefreshServerObjects();
pServerContextTraffic.ReleaseContext();
}

        一点注意,在使用IServerContext接口时要及时进行释放。
        思考一下:如何通过Geoprocessing Service来实现“交通流量图”?

利用免费的地图来解决实际问题

        Google Map的API虽然免费,好用,但终究需要那么一些Javascript基础;ESRI最近推出的Mapping for Everyone使用起来就更简便,内容也更丰富,能够让大家轻松地在自己网页里嵌入一张可互动的地图,比如这样就可以查看美国09年的失业率统计:

        它能够帮助我们做什么呢?这就有个例子,这位老兄正利用一张地图,劝说他的好友卖掉房子,准备搬家呢:)

ArcGIS中的线性参考/动态分段技术(二):Linear Referencing在ArcGIS Server中的实现原理

        首先来思考几个Linear Referencing的应用场景(后面会对这些场景一一实现):
        1、对于某条公路上的一点进行Identify操作,要求返回该点处在公路上的里程值;
        2、输入起始和终止桩号,要求将其间的路段显示在地图上;
        3、类似GoogleMap的交通流量地图。
        要将这些功能在ArcGIS Server中“落地”,首先决定采用哪种开发方式:客户端API还是Web ADF?两者皆可。但是稍加思考,两种方式的API中都没有提供有关Linear Referencing的解决办法。所以,Linear Referencing在ArcGIS Server中的实现原理还是得依靠ArcObjects了。
        ArcObjects中,Linear Referencing的功能在ESRI.ArcGIS.Location库中,请见:http://resources.esri.com/help/9.3/ArcGISEngine/dotnet/10745641-b5e3-4600-979f-cdf9d2bbc7ce.htm#About%20linear%20referencing
        为了实现上述功能,其中比较关键的有两个类:RouteLocator和RouteEventSource。RouteLocator的IRouteLocator2接口有Identify和Locate两个方法,前者可根据地理坐标识别出Route上的位置(M值);后者可根据M值来定位出Route上相应的Geometry。如果已有EventTable,需要对上面的每个Event进行定位,除了重复使用IRouteLocator2.Locate方法(或者LocateRow方法)外,也可通过构建RouteEventSource来实现。它是一种特殊的FeatureClass(继承自FeatureClass),其中的每个Feature代表了EventTable中的一个Event,而这个Feature的Shape字段内容则是根据EventTable和Route FeatureClass,利用Dynamic Segmentation技术实时计算出来的。
        关于如何获得可用的IRouteLocator2接口和RouteEventSource,文档中已经讲的很清楚;另外ESRI.ArcGIS.Location中还提供了实现ArcMap中Linear Referencing里所有功能的接口,有兴趣的朋友可以仔细阅读。

ArcGIS中的线性参考/动态分段技术(一):Linear Referencing背景

Linear Referencing背景
        定位:介绍Linear Referencing背景;在ArcMap中关于Linear Referencing的结构和功能;以及ArcGIS Server中几个应用场景的实现。
        注:有关更详细的操作可查看在线帮助,或搜索论坛。
什么是Linear Referencing
        Linear Referencing(下文引用为LR)翻译过来是线性参考,在公路,管网等行业的GIS应用中时常提到。LR是一种利用沿着可测量的线要素的相对方位来存储地理位置的方法。比如下图中:

        下面线的长度一次标为0,10,20,30,40……,而沿着这条线,我们看到上面有:

  • 一个位于坐标12处的点;
  • 一个位于坐标10东侧4个坐标的点;
  • 一段起始坐标分别为18和26的线段;
  • 一段起始坐标为28,长度为12的线段。

        为什么要用到LR技术?主要有两个原因:1、很多事件,像上边的例子一样,是通过沿着(曲)线的相对位置来记录的;2、要显示一条线上的多个属性集合时,由于各个属性在(曲)线上所对应的位置不同,同一数据源如果不做处理,很难达到要求。使用LR技术可解决此问题。
        以公路方面的一个应用场景为例说明。我们要显示一条公路的4种不同属性:道路管辖情况、路面材料、路段限速情况和路况,假设该公路长100公里:

1、前40公里为交警2大队管辖,后60公里为交警4大队管辖;
2、30至70公里为水泥路面,其余为沥青路面;
3、0-20公里的路段限速45km/h,20-40公里的路段限速35km/h,40-70公里路段限速45km/h,70-100公里路段限速55km/h;
4、0-20公里路况一般,20-40公里路况很好,40-60公里路况很差,60-100公里路况很好。
        属于同一数据源对应多个属性(且属沿线分布)的情况。如果不使用LR技术,那么需要4个公路图层,每个图层的公路根据属性分成长短不同的段落(Feature),才能够将这些属性展示出来;而是用了LR技术后,只需要一个公路数据(Feature数量不限),和四个事件表即可在不改变实际公路数据的情况下,按要求显示上述四种属性。
        Dynamic Segmentation:动态分段,属于LR采用的一种技术(一般应称之为线性参考问题中的动态分段技术)。是根据属性表中存储的相对位置信息,以及相应的线性数据,动态计算出线性数据上相对位置所对应的实际地理坐标的过程。动态分段正是因为表达不同属性时,不用去分割实际的地理数据,而是动态计算出该属性对应的地理位置而得名。
ArcGIS中Linear Referencing的实现原理
        ArcGIS中实现Linear Referencing主要通过以下两种数据结构:
1、Route FeatureClass
2、Event Table
        通过Dynamic Segmentation技术,Event Table中不同位置的Event就定位到了Route FeatureClass下对应的Line Feature上。
Route FeatureClass
        实际上是拥有两个特殊字段的Polyline FeatureClass:
1、必须包含有M(Measure)值的Shape字段。拥有M值的Shape字段,不但能存储x,y(,z)坐标,还能够多存储一个M数值;
2、必须包含有一个标识线段ID的字段,可以是Number或者Text类型。
        除了上述两个字段要求,还可以有其他字段。

        Route FeatureClass不同于普通的Polyline FeatureClass,是因为它具有一个测量系统,而这个测量系统的原理就是,通过存储在Feature的Vertice中的M值以及该Feature实际的ShapeLength,来动态插值出线上每一点的相对位置。比如一条公路数字化时,有两个节点(Vertice),分别表示该公路的起始桩号0公里和结束桩号100公里,该Feature存储在WGS1984坐标系中,ShapeLength为2.00,那么该公路上40公里处的位置,就应该位于图上该Feature的40%的位置,也就是ShapeLength为0.80的位置。
        而ArcGIS中,不要求每个节点都必须有M值。对于没有M值的节点其M值会标记为NaN(not a number):

        此外在不违反逻辑错误的情况下,M值可以随意设置。比如上图左边的图形,0-10段的实际ShapeLength看起来比10-20段的ShapeLength明显要长,也可以将上述四个节点的M值随意地设置为0,3,7,30。但是如果设置为0,10,5,30的话就产生了逻辑错误(route measure anomalies),ArcMap中会将其突出显示,提醒用户使用工具修改。
Event Table
        实际上是拥有2-3个特殊字段的表(ArcGIS支持的表均可,甚至可以是有特定格式的text文件):
1、类似Route FeatureClass中RouteID的一个字段。用来存储与Route FeatureClass中哪个线Feature对应。可以是Number或Text类型;
2、根据Event类型不同而必须的1-2个字段:

  • Point Event:比如108国道1900公里处发生了交通事故这个Event,需要一个Number类型的字段来记录1900这个相对位置;
  • Line Event:比如108国道1850到1950公里段的路况很差这个
    Event,需要两个Number类型的字段来分别记录1850、1950这两个起止位置。

        除了上述两个字段要求,还可以有其他字段。
        Event Table中一条记录就对应了需要在线上定位的一个点或一段距离,可以用来标识事件或属性等。下图描述了Dynamic Segmentation技术利用Route FeatureClass和Event Table产生的结果:

ArcGIS桌面中对Linear Referencing的实现
        ArcGIS桌面中不仅提供了专门的Linear Referencing Toolbox,对于产生的结果图层,也能像普通图层一样进行各种操作,而针对Linear Referencing的方方面面也提供了非常细粒度的操作供用户调整。

  • 对Route FeatureClass的创建与修改。包括:通过Catalog或Tool或向导创建RouteFeatureClass;利用Tool从已有的Polyline FeatureClass创建Route FeatureClass;通过Tool或向导利用已有的Point FeatureClass来校准Route FeatureClass的M值(注:通过对RouteID字段建立索引,可提高Dynamic Segmentation的效率); 利用Route Editing Toolbar对其进行修改;
  • 将Route FeatureClass作为图层显示,并对其进行查询。包括:对Route进行Identify。查询出线上任意点的M值(Identify Route Locations工具需从Customize中调出);查找某条线上指定M值的位置(包括Point和Line两种),通过Find工具实现;自定义符号显示M值的异常。包括逻辑错误等,可通过Route图层的属性对话框实现;在地图上像刻度尺一样任意显示Route的M值(Hatch)。通过将定义Hatch Definition(可多个),将其组织在Hatch Class(可多个)下,并附加SQL Query和Range Scale操作,基本实现任意复杂的“道路标尺”,而“标尺”的Label也可进行Script的高级控制,最终还能将“标尺”转换为Graphics,或将“标尺”风格另存为Style。在Route图层的属性对话框中实现;
  • 通过Tool和Attribute Table,对Event Table进行创建和修改;
  • 通过Tool,利用Dynamic Segmentation技术将Event Table中的Event显示在Route上(可显示点、线、面),其结果作为一个图层添加到TOC中,可进行显示、查询和分析等操作;
  • 通过Tool或向导,对Route Event进行Overlay,Aggregate,Transform等操作。

        以上所有具体操作可参考桌面帮助中的内容,如果安装了ArcTutor,还会有Linear Referencing Tutorial的PDF可供学习。

Local方式连接成功,Internet方式连接失败的原因之一

        以前也看到论坛中有朋友说这个问题,前两天刚好碰到,记录一下,方便其他朋友查阅。
        症状:
1、在ArcCatalog中,通过Local方式连接gis server没有问题,通过Internet方式连接(首先保证输入的字符串格式正确)提示:the request method (get.post,ect) was not allowed。。。。
2、ADF开发时(比如新建一个web mappling application模板),在MapResourceManager中,通过Local方式连接数据源没有问题,通过Internet连接时出错如下图:

        解决办法:很有可能是安装时未进行Web Application Post Install引起的,(重)做一遍Web Application Post Install即可。

ArcGIS API for Silverlight 1.0兼容Silverlight 3

        silverlight api 1.0正式版(build 269)可以使用silverlight 3,在silverlight 2的开发环境下直接下载silverlight 3 tools安装即可。
        silverlight 3 RTW版是7月10号发布的,关于新特性,网上有很多文章,相比以前的版本,怀疑的声音已经听不到了,只有褒奖和期待,而其中更有被誉为神作的.NET RIA Service,使得在.NET框架下的前后台通信变得更加方便。拭目以待吧:)
        截个图,体验一下sl3中非真正3d引擎的3d效果:

ArcGIS API for Silverlight/WPF RC版本发布

        ArcGIS API for Silverlight/WPF RC(release candidate)版本发布,正式版也倒计时了。
        bug:修正的一个最大bug就是在vs2008中使用design mode时十开九死的状况。
        几个比较大的变化:
1、所有的assembly和namespace都添加“Client”后缀,比如ESRI.ArcGIS.*变为ESRI.ArcGIS.Client.*,就是为了区别Silverlight API和其他ESRI API,比如ArcObjects。由此可以看出,在SL程序中调用AO也是名正言顺的事情;
2、ESRI.ArcGIS.Widgets.*变成了ESRI.ArcGIS.Client.Toolkit.*;
3、ESRI.ArcGIS.VirtualEarth.*变成了ESRI.ArcGIS.Client.Bing.*;
4、Clustering属性从FeatureLayer挪到了GraphicsLayer,允许对其进行更大程度的自定义;
        另外,对于209版本中,用QueryTask无法完成带中文字符的属性查询问题,也得到了修正。
        具体参见:What’s New

ArcGIS API for Silverlight开发入门外传:在程序中调用AO

        Silverlight API的开发入门系列的文章上回已经结束,但其实能讲的东西还有很多,实在不能面面俱到。这次再补充一个比较重要的功能,就是在Silverlight API程序中调用ArcObjects,来完成一些复杂的geoprocess功能。
        ADF之所以强大,除了其本身的类库外,它还能在服务器端调用AO,来完成比如在线编辑或者Geoemtry的处理工作;而ESRI官方的说法,建立在REST服务基础上的程序目前都不具备在线编辑的功能(因为要与服务器端的数据库打交道),但他们把这项功能作为日程,提到了研发计划内。目前而言,要在Silverlight API中完成数据编辑工作,他们的劝告是,“如果你不怕把你的双手弄脏,那就去做吧”。
        言归。AO本身是COM组件,.NET是利用COM Interop技术与其打交道的,这显然是Silverlight的客户端环境办不到的。这就需要与服务器端进行通信。Silverlight与服务器端通信,最常用的是WebClient类,稍复杂的还有HttpWebRequest类,而传统的ASP.NET Web Service与更强大的WCF则为远程调用AO提供了可能。下面还是来看一个实例,功能是在程序中向数据库中添加一个点,以Web Service为例(关于如何使用Web Service可以查资料,这里不做讲解)。
        在Silverlight所在的ASP.NET工程中新建一个ASP.NET Web Service,添加一个WebMethod,如下:
public class WS_AOAddPoint : System.Web.Services.WebService
{

[WebMethod]
public string HelloWorld()
{
return "Hello World";
}

[WebMethod]
public bool AddPointToFileGDB(double x, double y)
{
IAoInitialize aoInit = new AoInitializeClass();
aoInit.Initialize(esriLicenseProductCode.esriLicenseProductCodeEngine);

IWorkspaceFactory pWSF = new ESRI.ArcGIS.DataSourcesGDB.FileGDBWorkspaceFactoryClass() as IWorkspaceFactory;
ESRI.ArcGIS.esriSystem.IPropertySet pPropertySet = new ESRI.ArcGIS.esriSystem.PropertySetClass();
pPropertySet.SetProperty("DATABASE", @"E:arcgisserveragsedit_simpleaddpointchina_airlines.gdb");
IFeatureWorkspace pFW = pWSF.Open(pPropertySet, 0) as IFeatureWorkspace;

IPoint pPoint = new PointClass();
pPoint.X = x;
pPoint.Y = y;

IFeatureClass pFC = pFW.OpenFeatureClass("省会城市");
IFeature pF = pFC.CreateFeature();
pF.Shape = pPoint;
pF.Store();

return true;
}
}

        该方法传入两个double参数,作为新建点的坐标,这两个参数可在客户端的Draw_Complete事件中获取。为该工程设置一个固定的端口,然后在Silverlight程序中添加对该Web Service的引用。之后在Page.xaml.cs中,添加如下代码:
private void Draw1_DrawComplete(object sender,DrawEventArgs args)
{
GraphicsLayer glayer = Map1.Layers["glayer"] as GraphicsLayer;
Graphic g = new Graphic()
{
Symbol = markerSymbol,
Geometry = args.Geometry
};
glayer.Graphics.Add(g);

WS.WS_AOAddPointSoapClient client = new WS.WS_AOAddPointSoapClient();
client.AddPointToFileGDBCompleted+=new EventHandler(client_AddPointToFileGDBCompleted);
client.AddPointToFileGDBAsync((args.Geometry as ESRI.ArcGIS.Geometry.MapPoint).X, (args.Geometry as ESRI.ArcGIS.Geometry.MapPoint).Y);
}

private void client_AddPointToFileGDBCompleted(object sender, WS.AddPointToFileGDBCompletedEventArgs args)
{
MessageBox.Show(args.Result.ToString());
}

        最后看看效果:

        需要补充一下,在Web Service的方法中,如果不添加AO初始化的语句,会报The remote server returned an error: NotFound的错误;而调试该Web Service的方法,返回http 500 错误;在ie,将高级里面的“显示友好错误提示”勾掉,返回真正的错误是System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80040228,由此断定需要对AO license进行初始化工作。
        可以看出,这里对AO的调用甚至比ADF里都要简单,创建对象直接用new关键字即可。此例中仅仅添加了一个点,但线和面的编辑工作也可以通过点来完成;一些简单的Geoprocessing工作也可以通过代码而不是GP Service来完成了。
        svc与asmx的调用大同小异。至此,Silverlight API的程序又多了一个强大的靠山。

ArcGIS API for Silverlight开发入门(8):在程序中使用Virtual Earth的服务

        Silverlight API中还包括了一个ESRI.ArcGIS.VirtualEarth.dll类库,让我们可以方便的访问到老东家的VirtualEarth服务。目前Silverlight API中提供的VirtualEarth服务有三种:Map,Geocode和Routing,不过一看就知道后两种服务对于国内的数据来说又无缘了。
        直接看如何使用它的Map服务获取地图数据吧。同前,新建一个Silverlight工程,添加ESRI.ArcGIS.dll和ESRI.ArcGIS.VirtualEarth.dll的引用,引入xml命名空间,在xaml里面这样写:





        可以看出,和添加其他图层基本是一样的。SIlverlight API中针对VE地图的图层类型是TileLayer,LayerStyle有三种:Road,Aerial和AerialWithLabels,分别对应矢量图,影像图和带街道标注的影像图。ServerType就比较特殊了,有两种:Staging和Production,分别对应访问VE服务的账户类别,前者是免费的,后者是收费的。如果你此时运行程序的话,那是看不到地图的,因为TileLayer还有个关键的token属性没有设置。
        VE的服务那是相当安全,每次访问VE的服务,都要提供一个token(一个加密字符串)来进行身份验证,而这个token又是根据TokenService自动生成的,要通过TokenService生成一个token,又需要一个合法的Microsoft Virtual Earth Platform developer account……明白了这个过程,就来做我们的工作吧。
        首先,去申请一个Microsoft Virtual Earth Platform developer account,当然之前你还得有一个Windows Live账号。申请的这个账号是Evaluation版的,所以决定了以后我们只能使用Staging的服务,如果要把它变成Production版本,可以通过邮件联系微软,然后缴费;
        之后到注册时所填的邮箱去激活申请的Microsoft Virtual Earth Platform developer account账号,然后为其设置密码(必须是8-14为之间,包括大、小写字母,数字,且还要有非字母数字的字符,和windows server 2008是一样的),我们平常肯定不会这样设置密码,为了以防万一,建议赶紧把设置好的密码记录下来,
没准哪天就忘了。现在就可以用这个账户和密码来访问TokenService,通过它生成token,交给TileLayer的token属性。
        为了安全目的考虑,token是不建议也不能直接在Silverlight程序中进行设置的。那么怎么办呢?这样办:1、通过装载Silverlight的aspx页面的Page_Load方法,来申请我们的token,并把它添加到Silverlight的初始参数中,2、然后当Silverlight插件载入的时候,把token读出来,3、在Map_Loaded事件中,赋给TileLayer。
1、通过TokenService申请token:
在web app中add web reference,url用https://staging.common.virtualearth.net/find-30/common.asmx?wsdl,起个名字叫VirtualEarthService.TokenService。

其中Xaml1是Silverlight插件的ID:
2、Silverlight插件载入时读出这个token。在App.xaml.cs中:
private void Application_Startup(object sender, StartupEventArgs e)
{
VEtoken = e.InitParams["token"];
this.RootVisual = new Page();
}

3、最后在加载地图控件后,交付token:
private void Map1_Loaded(object sender, RoutedEventArgs e)
{
foreach (Layer layer in Map1.Layers)
if (layer is TileLayer)
(layer as TileLayer).Token = (Application.Current as App).VEtoken;
}

        终于能看见VE的图了。当然,我们的开发账户是免费的,所以地图上有很多“Staging”麻点(每个tile一个):

        至此,ArcGIS API for Silverlight的开发入门已经讲完了,我和大家一样也是边学边写的,刚好这两天SIlverlight API又升级了第二个Beta版。其实Silverlight和Flex一样,能使传统的WebGIS散发出全新的魅力,从而使我们的程序在RIA的道路上大踏步前进,能够做出什么样的效果也基本只受想象力的制约了。随着Silverlight 3的推出,我们也有理由相信Silverlight的明天会更好。