上一篇到现在过去好几天了,本来打算用ESRI官方自带的例子呢,后来觉得还是应该实践一下。这几天都在忙着写这个例子,其中也出现了好多问题,从一开始思考解决问题的方法,到找代码,再调试成功,费了不少事,好在问题都已经解决了。不喜欢说废话,还是忍不住说了这么多,下面正式进入主题。
首先说说我们要解决的问题。gp服务广泛使用的一个原因是他可以做栅格数据的分析,那好我们就用SOE来解决一个插值问题。在gp服务中,我们可以通过设置输入输出类型来保客户端成功加载分析结果,大部分分析结果是以图片形式传到客户端。在SOE中理论上是可以设置输出图片形式,但帮助中只是简单地说了几句,很不详细,所以我用的方法是将栅格分类后转换成矢量数据,再将矢量数据序列化成json格式,传给客户端。流图如下:
获取要素类---》插值---》重分类---》栅格转面---》序列化成json---》客户端
1.编写代码
新建一个工程,命名为Saturation
首先加入以下引用:
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Server;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SOESupport;
using ESRI.ArcGIS.GeoAnalyst;
using ESRI.ArcGIS.DataSourcesRaster;
//TODO: sign the project (project properties > signing tab > sign the assembly)
// this is strongly suggested if the dll will be registered using regasm.exe <your>.dll /codebase
namespace Saturation
{
[ComVisible(true)]
[Guid("f8cb9f88-1dc9-4f34-a0a9-fa931afb8730")]
[ClassInterface(ClassInterfaceType.None)]
public class Saturation : ServicedComponent, IServerObjectExtension, IObjectConstruct, IRESTRequestHandler
{
private string soe_name;
private IPropertySet configProps;
private IServerObjectHelper serverObjectHelper;
private ServerLogger logger;
private IRESTRequestHandler reqHandler;
//Member variables
private string m_layerNameToAnalyst = null;
private IFeatureClass m_fcToAnalyst = null;//进行插值的要素类
public Saturation()
{
soe_name = this.GetType().Name;
logger = new ServerLogger();
reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler;
}
#region IServerObjectExtension Members
public void Init(IServerObjectHelper pSOH)
{
serverObjectHelper = pSOH;
}
public void Shutdown()
{
logger.LogMessage(ServerLogger.msgType.infoStandard, "Shutdown", 8000, "Custom error message: Shutting down the SOE");
soe_name = null;
m_fcToAnalyst = null;
serverObjectHelper = null;
m_layerNameToAnalyst = null;
logger = null;
}
#endregion
#region IObjectConstruct Members
public void Construct(IPropertySet props)
{
logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE Construct:start");
configProps = props;
m_layerNameToAnalyst = "testPoint";
//获得所要分析的FeatureClass
try
{
IMapServer3 mapServer = serverObjectHelper.ServerObject as IMapServer3;
string mapName = mapServer.DefaultMapName;
IMapLayerInfos layerInfos = mapServer.GetServerInfo(mapName).MapLayerInfos;
IMapLayerInfo layerInfo = null;
//获得所要分析图层的index
int c = layerInfos.Count;
int layerIndex = 0;
for (int i = 0; i < c; i++)
{
layerInfo = layerInfos.get_Element(i);
if (layerInfo.Name == m_layerNameToAnalyst)
{
layerIndex = i;
break;
}
}
//使用IMapServerDataAccess获取数据
IMapServerDataAccess dataAccess = mapServer as IMapServerDataAccess;
m_fcToAnalyst = dataAccess.GetDataSource(mapName, layerIndex) as IFeatureClass;
if (m_fcToAnalyst == null)
{
logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Layer name not found.");
return;
}
}
catch (Exception es)
{
logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Could not get the feature layer.");
}
}
#endregion
#region IRESTRequestHandler Members
public string GetSchema()
{
return reqHandler.GetSchema();
}
public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties)
{
return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties);
}
#endregion
private RestResource CreateRestSchema()
{
RestResource rootRes = new RestResource(soe_name, false, RootResHandler);
RestOperation sampleOper = new RestOperation("sampleOperation",
new string[] { "field" },
new string[] { "json" },
SaturationAnalystOpeartionHandler);
rootRes.operations.Add(sampleOper);
return rootRes;
}
private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
{
responseProperties = null;
JsonObject result = new JsonObject();
//result.AddString("hello", "world");
return Encoding.UTF8.GetBytes(result.ToJson());
}
private byte[] SaturationAnalystOpeartionHandler(NameValueCollection boundVariables,
JsonObject operationInput,
string outputFormat,
string requestProperties,
out string responseProperties)
{
responseProperties = null;
//反序列化参数
string strField;
bool found = operationInput.TryGetString("field", out strField);
if (!found || string.IsNullOrEmpty(strField))
throw new ArgumentNullException("field");
byte[] result = SaturationAnalyst(strField);
return result;
}
private byte[] SaturationAnalyst(string field)
{
if (field.Length <= 0)
{
throw new ArgumentOutOfRangeException("field");
}
//插值
IRaster krigeRaster = BuildKrigeRaster(m_fcToAnalyst, field);
//重分类
IRaster reclassRastr = BuildReclassRaster(krigeRaster, 4);
//栅格转面,字段名作为生成的要素名称
IFeatureClass ConPolygon = RasterToPolygon(reclassRastr, field);
//将要素类序列化成json格式
JsonObject resultJsonObject = FclassToJsonObj(ConPolygon);
byte[] result = Encoding.UTF8.GetBytes(resultJsonObject.ToJson());
return result;
}
#region Helper 函数
/// <summary>
/// 克里金插值
/// </summary>
/// <param name="inputFclass">插值数据</param>
/// <param name="inputField">字段</param>
/// <returns></returns>
private IRaster BuildKrigeRaster(IFeatureClass inputFclass, string inputField)
{
if (inputFclass == null)
{
logger.LogMessage(ServerLogger.msgType.error, "BuildKrigeRaster", 8000, "SOE custom error: inputFclass is null.");
return null;
}
if (inputField.Length == 0)
{
logger.LogMessage(ServerLogger.msgType.error, "BuildKrigeRaster", 8000, "SOE custom error: inputField is null.");
return null;
}
//生成IFeatureClassDescriptor
IFeatureClassDescriptor pFcDescriptor = new FeatureClassDescriptorClass();
pFcDescriptor.Create(inputFclass, null, inputField);
//设置分析环境
IInterpolationOp pInterpolationOp = new RasterInterpolationOpClass();
IRasterAnalysisEnvironment pEnv = pInterpolationOp as IRasterAnalysisEnvironment;
object cellSize = 5.80481468000016E-04;
pEnv.Reset();//栅格单元大小
pEnv.SetCellSize(esriRasterEnvSettingEnum.esriRasterEnvValue, ref cellSize);
IRasterRadius pRadius = new RasterRadius();
object obj = Type.Missing;//可变搜索半径
pRadius.SetVariable(12, ref obj);
IGeoDataset pGeodataset = inputFclass as IGeoDataset;
IEnvelope pRasterExt = new EnvelopeClass();
pRasterExt.XMin = 117.178625;
pRasterExt.XMax = 117.376715;
pRasterExt.YMax = 31.922649;
pRasterExt.YMin = 31.777529;
object extentPro = pRasterExt;//分析范围
pEnv.SetExtent(esriRasterEnvSettingEnum.esriRasterEnvValue, ref extentPro, ref obj);
pEnv.OutSpatialReference = pGeodataset.SpatialReference;
//执行差值
IGeoDataset pGeoDataset = pInterpolationOp.Krige((IGeoDataset)pFcDescriptor, esriGeoAnalysisSemiVariogramEnum.esriGeoAnalysisLinearSemiVariogram,
pRadius, false, ref obj);
return pGeoDataset as IRaster;
}
/// <summary>
/// 对栅格进行等间距重分类
/// </summary>
/// <param name="inputRaster">待分类栅格</param>
/// <param name="pClassNo">分类数</param>
/// <returns></returns>
private IRaster BuildReclassRaster(IRaster inputRaster, int pClassNo)
{
if (inputRaster == null)
{
logger.LogMessage(ServerLogger.msgType.error, "BuildReclassRaster", 8000, "SOE custom error: inputRaster is null.");
return null;
}
if (pClassNo <= 0)
{
logger.LogMessage(ServerLogger.msgType.error, "BuildReclassRaster", 8000, "SOE custom error: pClassNo is null.");
return null;
}
//获取栅格分类数组和频度数组
object dataValues = null, dataCounts = null;
GetRasterClass(inputRaster, out dataValues, out dataCounts);
//获取栅格分类间隔数组
IClassifyGEN pEqualIntervalClass = new EqualIntervalClass();
pEqualIntervalClass.Classify(dataValues, dataCounts, ref pClassNo);
double[] breaks = pEqualIntervalClass.ClassBreaks as double[];
//设置新分类值
INumberRemap pNemRemap = new NumberRemapClass();
for (int i = 0; i < breaks.Length - 1; i++)
{
pNemRemap.MapRange(breaks[i], breaks[i + 1], i+1);
}
IRemap pRemap = pNemRemap as IRemap;
//设置环境
IReclassOp pReclassOp = new RasterReclassOpClass();
IGeoDataset pGeodataset = inputRaster as IGeoDataset;
IRasterAnalysisEnvironment pEnv = pReclassOp as IRasterAnalysisEnvironment;
object obj = Type.Missing;
IEnvelope pRasterExt = new EnvelopeClass();
pRasterExt.XMin = 117.178625;
pRasterExt.XMax = 117.376715;
pRasterExt.YMax = 31.922649;
pRasterExt.YMin = 31.777529;
object extentPro = pRasterExt;//分析范围
pEnv.SetExtent(esriRasterEnvSettingEnum.esriRasterEnvValue, ref extentPro, ref obj);
pEnv.OutSpatialReference = pGeodataset.SpatialReference;
//重分类
IRaster pRaster = pReclassOp.ReclassByRemap(pGeodataset, pRemap, false) as IRaster;
return pRaster;
}
/// <summary>
/// 获取栅格分类数组和频度数组
/// </summary>
/// <param name="inputRaster">输入栅格</param>
/// <param name="dataValues"></param>
/// <param name="dataCounts"></param>
private void GetRasterClass(IRaster inputRaster, out object dataValues, out object dataCounts)
{
IRasterBandCollection pRasBandCol = inputRaster as IRasterBandCollection;
IRasterBand pRsBand = pRasBandCol.Item(0);
pRsBand.ComputeStatsAndHist();//IRasterBand中本无统计直方图,必须先进行ComputeStatsAndHist()
IRasterStatistics pRasterStatistic = pRsBand.Statistics;
double mMean = pRasterStatistic.Mean;
double mStandsrdDeviation = pRasterStatistic.StandardDeviation;
IRasterHistogram pRasterHistogram = pRsBand.Histogram;
double[] dblValues;
dblValues = pRasterHistogram.Counts as double[];
int intValueCount = dblValues.GetUpperBound(0) + 1;
double[] vValues = new double[intValueCount];
double dMaxValue = pRasterStatistic.Maximum;
double dMinValue = pRasterStatistic.Minimum;
double BinInterval = Convert.ToDouble((dMaxValue - dMinValue) / intValueCount);
for (int i = 0; i < intValueCount; i++)
{
vValues[i] = i * BinInterval + pRasterStatistic.Minimum;
}
dataValues = vValues as object;
dataCounts = dblValues as object;
}
/// <summary>
/// 栅格转面
/// </summary>
/// <param name="inputRaster">待转换栅格</param>
/// <param name="fcName">生成要素类名称</param>
/// <returns></returns>
private IFeatureClass RasterToPolygon(IRaster inputRaster, string fcName)
{
//获得testPoint所在工作空间
IWorkspace pWorkspace = (m_fcToAnalyst as IDataset).Workspace;
IRasterBandCollection pRasBandCol = inputRaster as IRasterBandCollection;
IRasterBand pRsBand = pRasBandCol.Item(0);
IRasterDataset pRasterDataset = pRsBand as IRasterDataset;
IGeoDataset pRasterGeoDataset = pRasterDataset as IGeoDataset;
//栅格转面
IConversionOp pConversionOp = new RasterConversionOpClass();
ISpatialReference pSpatialReference = pRasterGeoDataset.SpatialReference;
IGeoDataset pGeoDataset = pConversionOp.RasterDataToPolygonFeatureData(pRasterGeoDataset, pWorkspace, fcName, true);
return pGeoDataset as IFeatureClass;
}
/// <summary>
/// 将要素类序列化成json格式对象
/// </summary>
/// <param name="inputFeaClass">输入要素类</param>
/// <returns></returns>
private JsonObject FclassToJsonObj(IFeatureClass inputFeaClass)
{
//获取要素数目
IQueryFilter pQueryFilter = new QueryFilterClass();
pQueryFilter.WhereClause = null;
int count = inputFeaClass.FeatureCount(pQueryFilter);
//将每一个要素序列化成json数据
IFeature pFeature = null;
List<JsonObject> jsonGeometries = new List<JsonObject>();
for (int i = 1; i < count; i++)//OBJECTID从1开始
{
pFeature = inputFeaClass.GetFeature(i);
IGeometry pGeometry = pFeature.Shape;
JsonObject featureJson = new JsonObject();
JsonObject feaGeoJson = null;//几何对象
if (pGeometry != null)
{
feaGeoJson = Conversion.ToJsonObject(pGeometry);
featureJson.AddJsonObject("geometry", feaGeoJson);//加入几何对象
}
double grid_Code = (double)pFeature.get_Value(pFeature.Fields.FindField("GRIDCODE"));
featureJson.AddLong("id", i);//id
featureJson.AddDouble("gridCode", grid_Code);//等级
jsonGeometries.Add(featureJson);
}
JsonObject resultJson = new JsonObject();
resultJson.AddArray("geometries", jsonGeometries.ToArray());
return resultJson;
}
#endregion
}
}
增加一个控制台应用程序,命名为RegisterSaturation
加入以下引用
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Server;
using ESRI.ArcGIS;
using ESRI.ArcGIS.ADF.Connection.AGS;
namespace RegisterSaturation
{
class Program
{
static void Main(string[] args)
{
// Must run as an user in the agsadmin group on the SOM
ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsServerConnection =
new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection();
agsServerConnection.Host = "localhost";
agsServerConnection.Connect();
ESRI.ArcGIS.Server.IServerObjectAdmin2 serverObjectAdmin =
agsServerConnection.ServerObjectAdmin as ESRI.ArcGIS.Server.IServerObjectAdmin2;
// This name must match those defined for property pages
string extensionName = "Saturation";
// Check command line arguments to see if SOE is to be unregistered
if (args.Length == 1 && args[0] == "/unregister")
{
// Check whether the SOE is registered
if (ExtensionRegistered(serverObjectAdmin, extensionName))
{
// Delete the SOE
serverObjectAdmin.DeleteExtensionType("MapServer", extensionName);
Console.WriteLine(extensionName + " successfully unregistered");
}
else
Console.WriteLine(extensionName + " is not registered with ArcGIS Server");
}
else
{
// Check whether the SOE is registered
if (!ExtensionRegistered(serverObjectAdmin, extensionName))
{
// Use IServerObjectExtensionType3 to get access to info properties
ESRI.ArcGIS.Server.IServerObjectExtensionType3 serverObjectExtensionType =
serverObjectAdmin.CreateExtensionType() as ESRI.ArcGIS.Server.IServerObjectExtensionType3;
// Must match the namespace and class name of the class implementing IServerObjectExtension
serverObjectExtensionType.CLSID = "Saturation.Saturation";
//serverObjectExtensionType.CLSID = "{C41E8674-F186-4a0c-8FC9-AAB7885EFD00}";
serverObjectExtensionType.Description = "the shops saturation";
serverObjectExtensionType.Name = extensionName;
// Name that will be shown in the capabilities list on property pages
serverObjectExtensionType.DisplayName = "Saturation REST";
// Use info properties to define capabilities and msd support
serverObjectExtensionType.Info.SetProperty("DefaultWebCapabilities", "GetInfo");
serverObjectExtensionType.Info.SetProperty("AllWebCapabilities", "GetInfo,Saturation");
serverObjectExtensionType.Info.SetProperty("SupportsMSD", "true");
// Required to enable exposure of SOE with ArcGIS Server REST endpoint
serverObjectExtensionType.Info.SetProperty("SupportsREST", "true");
// Register the SOE with the server
serverObjectAdmin.AddExtensionType("MapServer", serverObjectExtensionType);
Console.WriteLine(extensionName + " successfully registered with ArcGIS Server");
}
else
Console.WriteLine(extensionName + " is already registered with ArcGIS Server");
}
Console.ReadLine();
}
// Checks whether an extension with the passed-in name is already registered with the passed-in server
static private bool ExtensionRegistered(ESRI.ArcGIS.Server.IServerObjectAdmin2 serverObjectAdmin, string extensionName)
{
// Get the extensions that extend MapServer server objects
ESRI.ArcGIS.Server.IEnumServerObjectExtensionType extensionTypes = serverObjectAdmin.GetExtensionTypes("MapServer");
extensionTypes.Reset();
// If an extension with a name matching that passed-in is found, return true
ESRI.ArcGIS.Server.IServerObjectExtensionType extensionType = extensionTypes.Next();
while (extensionType != null)
{
if (extensionType.Name == extensionName)
{
//serverObjectAdmin.DeleteExtensionType("MapServer", extensionName);
return true;
}
extensionType = extensionTypes.Next();
}
// No matching extension was found, so return false
return false;
}
}
}
在代码中有个地方需要注意:
serverObjectExtensionType.CLSID = "Saturation.Saturation";
CLSID的值必须与你的命名空间的类名相一致
2.注册
(1)注册COM组件
SOE其实是运行在服务器端的COM组件,并且你需要在每台运行SOC的机器上都为其注册COM组件
* 右击Saturation工程--》属性--》生成,将目标平台选为“x86”,因为ArcServer是运行在32位机器上
*在属性中选择“签名”选项卡,为程序集生成一个签名
*在开始菜单-》visual studio 20110-》visual studio tools-》命令提示,打开命令提示工具
*在命令提示工具中输入命令:regasm <path to DLL> /codebase,在本例中输入如下命令:
regasm “C:\Users\LZZ\Documents\Visual Studio 2010\Projects\Saturation\Saturation\bin\Debug\Saturation.dll” /codebase
(2)ArcServer服务器注册
现在程序集已经注册号,接下来要在ArcServer服务器上注册,这里要用到RegisterSaturation工程,右击工程-》属性-》调试-》启动新实例,如果注册成功,你会看到一个提示成功的信息。
注意:在注册的时候一定要保证让运行在这台机器上的soc用户能够访问到你上面注册的dll,否则会提示你无法注册(给soc用户能够读取你注册的dll的权限)
3.调试
如果没有调试的话,那岂不是太痛苦了,不过还有说回来,SOE这个东西有调试也很痛苦。他需要附加到SOC进程中去,而你的机器上往往会有多个SOC进程,这时候如果你附加错误的话,你是无法进行调试的,我常用的方法是把其他的服务全部删除,是删除不是停止;这里还会碰到一个古怪的问题,就是第一次你可以调试,当你停止调后,再一次附加到进城后却无法调试,我的方法是停止服务,将该工程重新生成,然后启动服务,在附加到进程中去。这是我的这几天捉摸出来的经验。在调试之前我们首先需要使服务支持SOE,貌似在ArcGIS10中只支持MapServer的SOE。
这里我建议你用Manager来发布服务,发布服务的方法跟发布普通mapServer的方法一样,如果你的SOE在服务器上注册成功的话,你会看到以下内容:
在这里你不需要选中,我碰到过注册成功后这在里面却不显示的问题,这时候你只要重启一下计算机即可。发布服务成功后,点击编辑服务:
在这里选中Saturation REST,在下方选中两个check框,点击右边按钮保存并重启,现在我们的mapServer已经有了SOE能力。接下来我们进行调试:
在VS中点击调试-》附加到进程:
如果你看到你附加到进城后,断点依然显示的话,说明可以正常调试,否则你就需要看一下前文提到的解决方法啦。
当点击按钮时,后进入VS中的断点处:
运行成功后,你会看到返回的结果为json类型对象
现在我们已经介绍完SOE的内容了,如果你还有什么不了解的可以直接看帮助:
这里补充几句:官网中有三个例子,其中空间查询的那个我没运行成功,是跟里面设置属性也有关,因为我没用过里面的WebControl对他不太了解,而且我们的东西不需要使用属性页,所以暂时还无法解决他。希望有哪位大神看了这篇文章后能给小人指点一下,感激不尽。
这个教程到这里就暂时告一段落了,如果以后还碰到关于这种技术的问题话,我会继续发博文的。
在这里顺便感谢一下群哥多年来的帮助,这个技术也是从他那听说的。可以说没有群哥,也就没有这系列教程。呵呵,祝大家好运!!!