前言:根据项目经验总结了一大波iObject .net 开发的坑及处理方法,和二次封装了一些好用的函数。
参考文档:SuperMap iObjects .NET 10i 联机帮助
1.工程结构(经验总结,不坑爹)
和arcgis、qgis最显著的不同,超图不能直接打开放在硬盘里面的shp文件,tif文件等,必须要将数据导入到工程里面的udb里面才可以显示出来(类似ArcGIS把数据导入到gbd),其他的大同小异都差不多。
常用的结构分支如下,其他的在二维平面开发不怎么用到:
-| 工程文件(.smwu) #类似arcgis的.mxd
--| 数据源文件(.udb || .udbx) # 类似arcgis的gdb
--| 地图文件(.xml) # 显示地图的配置,下次打开工程的时候就可以还原上次关闭时候地图的显示形态(哪个图层显示的什么颜色、显示的中心在哪儿、显示范围多大之类的)
2. 投影转换(坑爹指数⭐⭐⭐)
同一个图层数据,在SuperMap和ArcGIS打开以后,很多参数显示名称是不一致的,用ESPG:4524这个坐标系举例:
①坐标系名字:
在arcgis打开显示为:CGCS2000 / 3-degree Gauss-Kruger zone 36”
在超图打开显示为:China_2000_3_DEGREE_GK_Zone_36
②坐标系的GeoDatum(超图里面是叫GeoDatum,位于PrjCoordSys.GeoCoordSys.Datum):
在arcgis显示为:D_CGCS_2000
在超图显示为:D_China_2000
③投影方法:
在超图设置为“GaussKruger "以后,在arcgis里面打开显示为 “Transverse_Mercator”;
在ArcGIS设置为“Transverse_Mercator”以后,超图打开显示“TransverseMercator”,少了一条下划线,谜之操作。
检查两个投影坐标系是否相同的代码如下:
// prjCoordSys1标准投影坐标, prjCoordSys2用于比较的坐标(导入的坐标系)
// 因为这个方法一般用在检查导入的图层和既定图层的坐标系是否一致,所以prjCoordSys2也就被称为导入的坐标系
// 该方法的坑是:同一个坐标系,在ArcGIS与SuperMap各自的软件里,坐标系名称和一些参数的名称不一样
public static GeoEngineResult CheckPrjTheSame(PrjCoordSys prjCoordSys1, PrjCoordSys prjCoordSys2)
{
GeoEngineResult result = new GeoEngineResult() { isSuccess = true, message = "" };
// 先判断是不是都是投影坐标
if (prjCoordSys1.Projection == null && prjCoordSys2.Projection != null)
{
result.isSuccess = false;
result.message = "第一个为地理坐标";
return result;
}
if (prjCoordSys1.Projection != null && prjCoordSys2.Projection == null)
{
result.isSuccess = false;
result.message = "第二个为地理坐标";
return result;
}
if (prjCoordSys1.Projection == null && prjCoordSys2.Projection == null)
{
bool issame = prjCoordSys1.Name == prjCoordSys2.Name;
result.isSuccess = issame;
return result;
}
// 先判断是不是超图自己生成的,直接判断ESPG,不然再通过GeoCoorSys、Projection、PrjParameter进行判断。
if (prjCoordSys1.EPSGCode == prjCoordSys2.EPSGCode)
return result;
// 地理坐标系
GeoCoordSys geoCoordSys1 = prjCoordSys1.GeoCoordSys;
GeoCoordSys geoCoordSys2 = prjCoordSys2.GeoCoordSys;
string[] sysParas = new string[] { "CoordUnit", "GeoDatum", "GeoPrimeMeridian", "GeoSpatialRefType" };
foreach (string name in sysParas)
{
var value1 = geoCoordSys1.GetType().GetProperty(name).GetValue(geoCoordSys1, null);
var value2 = geoCoordSys2.GetType().GetProperty(name).GetValue(geoCoordSys2, null);
bool isSame = value1.Equals(value2);
if (!isSame)
{
var name1 = value1.GetType().GetProperty("Name").GetValue(value1, null);
var name2 = value2.GetType().GetProperty("Name").GetValue(value2, null);
if (name == "GeoDatum")
{
string _name1 = name1.ToString();
string _name2 = name2.ToString();
if (_name1.Contains("CGCS")) _name1 = _name1.Replace("CGCS", "China");
if (_name2.Contains("CGCS")) _name2.Replace("CGCS", "China");
if (!_name1.Equals(_name2))
{
result.message += $"{name}不同,导入的为{name1},应为{name2}。";
result.isSuccess = false;
}
}
else
{
if (!name1.Equals(name2))
{
result.message += $"{name}不同,导入的为{name1},应为{name2}。";
result.isSuccess = false;
}
}
}
};
// 投影方法 GaussKruger Transverse_Mercator
string prjCoordSysType1 = prjCoordSys1.Projection.Type.ToString();
string prjCoordSysType2 = prjCoordSys2.Projection.Type.ToString();
if (prjCoordSysType1.Equals("TransverseMercator"))
{
prjCoordSysType1 = "GaussKruger";
}
if (prjCoordSys2.Equals("TransverseMercator"))
{
prjCoordSysType2 = "GaussKruger";
}
bool projectionSame = prjCoordSysType1 == prjCoordSysType2;
if (!projectionSame)
{
result.isSuccess = false;
result.message += $"投影方法错误,导入的为:{prjCoordSysType1},应为{prjCoordSysType2}。";
}
// 投影参数
PrjParameter parameter1 = prjCoordSys1.PrjParameter;
PrjParameter parameter2 = prjCoordSys2.PrjParameter;
string[] propeties = new string[] { "Azimuth", "CentralMeridian", "CentralParallel", "FalseEasting", "FalseNorthing", "FirstPointLongitude", "RectifiedAngle", "ScaleFactor", "SecondPointLongitude", "StandardParallel1", "StandardParallel2" };
foreach (string propertyName in propeties)
{
var value1 = parameter1.GetType().GetProperty(propertyName).GetValue(parameter1, null);
var value2 = parameter2.GetType().GetProperty(propertyName).GetValue(parameter2, null);
bool isSame = value1.Equals(value2);
if (!isSame)
{
result.message += $"{propertyName}不同,导入的为{value1},应为{value2}。";
result.isSuccess = false;
}
}
return result;
}
3. 撤销与恢复(坑爹指数⭐⭐⭐⭐)
MapControl.Redo()、Undo()只支持:将图层调整为编辑状态后,用鼠标在界面上对Geometry进行:拖动、节点编辑、删除的撤销和恢复,其他任何用代码对Geometry进行编辑都无法通过调用MapControl.Redo()、Undo()这两个函数对刚才的操作进行撤销或者恢复,如:Recordset.setGeometry(),Recordset.Delete()和Geometries类对Geometry的求交、擦除、合并等操作。
必须要用mapControl.EditHistory.add()来保存编辑的操作,然后再调用mapControl.EditHistory.Redo()、Undo()来实现恢复和撤销。
即:用鼠标对Geometry的操作只需调用MapControl.Redo()、Undo()来实现撤销和恢复;用代码对Geometry实现的操作需要调用mapControl.EditHistory.add()、mapControl.EditHistory.Redo()、Undo()这三个来实现撤销和恢复。
具体的代码在超图官方的CSDN账号里面找到了,地址如下:SuperMap iObjects.NET编辑撤销操作详解_SuperMap技术控-CSDN博客
4.邻接图斑的搜索(坑爹指数⭐⭐⭐)
如下图所示,被选中的蓝色图斑周围,一共有三个地块与它有邻接关系,接壤的公共边长度依次为:59、0、46,这里就有问题了:为什么存在具有邻接关系但公共边长度为0的情况?这就是最大的坑!如图所示,②图斑和蓝色地块只有一个点接触到了,但这也会被算做是接壤,点是不具有长度概念的,所以邻接长度为0。ps:其实点和线是人思想中的一个理想状态,真实世界是不存在点和线的,放大以后都会是个面,改编一下马克思哲学的运动观来概括:面是绝对的,点线是相对的🤔
搜索邻接图斑的代码如下:
/// <summary>
/// 获得与中心图斑邻接的图层,暂时只支持二维面状图层搜索
/// </summary>
/// <param name="queryDataset">待搜索的面状图层</param>
/// <param name="centerGeometry">中心图斑</param>
/// <returns>与中心图斑邻接的数据集</returns>
public static Recordset GetTouchedRecordset(DatasetVector queryDataset, GeoRegion centerGeometry)
{
if (queryDataset.RecordCount == 0 || queryDataset.Type != DatasetType.Region) return null;
Recordset recordset = queryDataset.Query($"SmID = {centerGeometry.ID}", CursorType.Dynamic);
QueryParameter parameter = new QueryParameter()
{
SpatialQueryMode = SpatialQueryMode.Touch,
//下面这个SpatialQueryObject虽然是支持Object的任意类型实例(如GeoRegion、GeoLine)传入作为参数,但是有些情况会有bug,极其建议转为Recordset传进去
SpatialQueryObject = recordset
};
Recordset touchRecordset = queryDataset.Query(parameter);
return touchRecordset;
}
求公共边长度的代码如下:
/// <summary>
/// 拿到和周围图斑的邻接长度,返回值顺序和sideGeos完全相同
/// </summary>
/// <param name="centerGeo">中心图斑</param>
/// <param name="touchGeos">求公共边长度的图斑</param>
/// <param name="touchGeos">工作空间</param>
/// <returns>返回邻接长度,顺序和传入的touchGeos一致</returns>
public static List<double> GetCommonLineLength(GeoRegion centerGeo, List<GeoRegion> touchGeos, Workspace workspace)
{
List<double> resultLenghts = new List<double>();
List<GeoRegion> _sides = new List<GeoRegion>();
DatasetVector commonLine_center, commoneLine_result;
Datasource memoryDatasource;
string centerDatasetName = "commonline_center";
string resultDatasetName = "commonline_result";
string memoryDatasourceName = "memoryDatasource";
if (!workspace.Datasources.Contains(memoryDatasourceName))
{
memoryDatasource = workspace.Datasources.Create(new DatasourceConnectionInfo()
{
Server = ":memory:",
EngineType = EngineType.UDB,
Alias = memoryDatasourceName
});
}
else
{
memoryDatasource = workspace.Datasources[memoryDatasourceName];
}
Datasets memoryDataset = memoryDatasource.Datasets;
// 准备工作环境
if (memoryDataset.Contains(centerDatasetName))
{
if (commonLine_center == null)
commonLine_center = memoryDataset[centerDatasetName] as DatasetVector;
commonLine_center.Truncate();
}
else
{
var config = new DatasetVectorInfo()
{
Name = centerDatasetName,
Type = DatasetType.Line,
};
commonLine_center = memoryDataset.Create(config);
}
if (memoryDataset.Contains(resultDatasetName))
{
if (commoneLine_result == null)
commoneLine_result = memoryDataset[resultDatasetName] as DatasetVector;
commoneLine_result.Truncate();
}
else
{
var config = new DatasetVectorInfo()
{
Name = resultDatasetName,
Type = DatasetType.Line
};
commoneLine_result = memoryDataset.Create(config);
commonLine_center.PrjCoordSys = commoneLine_result.PrjCoordSys = AppCore.Common.GlobalParameters.globale_memoryDatasouce.PrjCoordSys;
}
// 添加中心圈
var recordset_center = commonLine_center.GetRecordset(false, CursorType.Dynamic);
var orginGeo_line = centerGeo.ConvertToLine();
recordset_center.AddNew(orginGeo_line);
recordset_center.Update();
// 开始分析
var paras = new OverlayAnalystParameter() { Tolerance = 0 };
foreach (GeoRegion geoRegion in touchGeos)
{
_sides.Clear();
_sides.Add(geoRegion);
commoneLine_result.Truncate();
OverlayAnalyst.Intersect(commonLine_center, _sides.ToArray(), commoneLine_result, paras);
var _recordset = commoneLine_result.GetRecordset(false, CursorType.Static);
if (_recordset.RecordCount == 0)
{
resultLenghts.Add(0); // 只相交了一个点,就会出现没有公共边的情况
continue;
}
var _line = _recordset.GetGeometry() as GeoLine;
var _length = _line.Length;
resultLenghts.Add(_length);
}
return resultLenghts;
}
5.合并图斑 (坑爹指数 ⭐⭐⭐⭐⭐)
图斑的融合也是很大的一个坑点,必须要用 :
Recordset deleteRecord = sideRecord.Dataset.Query($"SmID == {sideRecord.GetID()}", CursorType.Dynamic);
deleteRecord.delete();
// 不能用sideRecord.delete()直接删除自己,有时候会出bug,最好是像上面代码那样绕一圈在原图层通过SmID搜索到自己然后删除掉
通过在自己的所属图层里面搜索自己的SmID来删除自己,不能直接sideRecord.delete()来删,否则会出莫名其妙的bug(被坑了5天),完整封装代码如下
// 合并两个图斑。
//centerRecord中心图斑(保留属性的图斑),sideRecord边缘图斑(不被保留字段属性)
public static bool UnionGeometry(Recordset centerRecord , Recordset sideRecord)
{
try
{
var newGeometry = Geometrist.Union(centerRecord.GetGeometry(), sideRecord.GetGeometry());
centerRecord.Edit();
bool isSet = centerRecord.SetGeometry(newGeometry);
centerRecord.Update();
Recordset deleteRecord = sideRecord.Dataset.Query($"SmID == {sideRecord.GetID()}", CursorType.Dynamic);
bool isDelete = deleteRecord.Delete();
return isSet && isDelete;
}
catch {
return false;
}
}
6.叠加分析之坑(坑爹指数⭐⭐⭐⭐⭐)
叠加分析调用SuperMap.Analyst.SpatialAnalyst就可以了,超图官方文档写的很详细,主要的坑在于它里面的一个参数:OverlayAnalystParameter的tolerance,即叠加分析的容限值,决定了叠加分析的精度。但是这个参数有两个大坑,
①如果追求极致精度,这个tolerance一定不能设为0,如果设为0则api会自动改为默认值,默认值具体是多少官方文档没说,感觉大概是0.015m左右。追求极致精度就设为0.0001之类的,切记。
②如果需要进行叠加分析连续操作,这个tolerance最好要统一并且一定不能设得太低,最小就0.01,再低会有严重的精度问题,比如:两个图斑把比例尺放的很大的情况下,目视都是邻接关系,但是容限值设的过低如0.00001米,它两者就会变成相交关系。切记!!!
7.属性表增删改查的经验
7.1 新增
图层新增一条数据
详见超图官方文档的 Recordset.AddNew方法,没啥坑
7.2 删除
删除数据: Recordset.Delete();
坑详见上面第五点合并图斑,必须要在原图层找到自己然后删除,不然很可能会失败 :
var deleteRecordset = recordset.Dataset.Query($"SmID == {recordset.GetID()}", CursorType.Dynamic);
deleteRecordset.Delete();
7.3 修改
1.修改某行数据的某字段数据
public static void SetFieldValue(string fieldName, object value, Recordset recordset)
{
try
{
recordset.Edit();
bool res = recordset.SetFieldValue(fieldName, value);
if (!res)
{
if (value == null)
{
recordset.SetFieldValueNull(fieldName);
}
else
{
recordset.SetFieldValue(fieldName, value.ToString());
}
}
recordset.Update();
}
catch{}
}
2.修改所有数据的某字段数据
/// <summary>
/// 批量修改字段信息
/// </summary>
/// <param name="recordset">要修改的数据集</param>
/// <param name="keyValuePairs">字段名字和数据的字典</param>
/// <returns></returns>
public static bool BatchSetValue(Recordset recordset, Dictionary<string, object> keyValuePairs)
{
bool isSuccess = false;
var batch = recordset.Batch;
batch.MaxRecordCount = 100;
batch.Begin();
while (!recordset.IsEOF)
{
bool isset = recordset.SetValues(keyValuePairs);
isSuccess = isset ? isSuccess : isset;
recordset.MoveNext();
}
batch.Update();
return isSuccess;
}
3.根据空间关系进行数据更新: 没有坑,详见超图官方文档的Recordset.UpdateFields
7.4 查询
详见超图官方文档的DatasetVector.Query,主要的坑是:
① 如果要查询的字段是非数值型的,需要在值的两侧加小引号,如果是数值就不需要,如下所示:cityname是文本字段搜索时候'chengdu'要加引号,区号areanumber是数值型的就不需要
datasetVector.Query( "cityname == 'chengdu' ", CursorType.Dynamic);
datasetVector.Query( "areanumber == 028 " , CursorType.Dynamic);
8.字段增删改查的经验与坑(坑爹指数⭐⭐⭐)
字段操作的坑有两点:
①数据量很大的时候,删除字段会出现删除不了的情况:如下面代码中的“ ClearFieldInfos ”函数用于清空非系统字段的时候,按常理只需要把IsSystemField等于false的删掉就可以了,但是数据量大以后会删不掉,必须while循环一直检查非系统是否被删完,删完了再退出。
②字段拷贝时候,是否保留原来字段携带的DefaultValue(默认值)和IsRequired(是否必填)属性:因为我们有时候只想复制字段的名称、类型、长度等信息,不想要它的默认值和是否必填信息。例如:现有图层A(包含字段a,b,c)和图层B(包含字段a,b),我现在要把B图层相比与A缺少的字段(即字段c)给自动补充到B图层中,如果要保留c字段的默认值,则c字段被添加到B图层后,B图层所有数据的字段c都会被填充上默认值。该坑在函数“ BatchAddField(FieldInfos targetFieldInfos, FieldInfos sourceFieldInfos, bool ignoreDefaultValue = true, bool ignoreRequired = true)”中有所体现。
字段操作已经被封装为一个静态类,完整代码如下:
public enum QueryType
{
Number = 0,
Text = 1,
others = 2,
}
public static class EditFieldInfo
{
/// <summary>
/// 添加一个新字段,自动判断是否可以加入,如果可以便加入
/// </summary>
/// <param name="tartgetFieldInfos">目标字段集</param>
/// <param name="newFieldInfo">待加入字段</param>
/// <returns></returns>
public static bool AddNewFieldInfo(FieldInfos tartgetFieldInfos, FieldInfo newFieldInfo)
{
bool addSuccess = true;
try
{
bool isAdd = AvailableAdded(tartgetFieldInfos, newFieldInfo);
if (isAdd)
{
int newIndex = tartgetFieldInfos.Add(newFieldInfo);
addSuccess = newIndex > 0 ? true : false;
}
}
catch (Exception ex)
{
addSuccess = false;
Console.WriteLine(ex.Message);
}
return addSuccess;
}
/// <summary>
/// 新字段是否可以被加入。系统字段,Sm开头字段,已经存在字段,不可以被融合进入
/// </summary>
/// <param name="targetFieldInfos">目标字段集合</param>
/// <param name="newFieldInfo">新字段</param>
/// <returns></returns>
public static bool AvailableAdded(FieldInfos targetFieldInfos, FieldInfo newFieldInfo)
{
if (newFieldInfo.IsSystemField || targetFieldInfos.IndexOf(newFieldInfo.Name) > -1)
return false;
if (newFieldInfo.Name.Length > 1 && newFieldInfo.Name.Substring(0, 2) == "Sm")
return false;
return true;
}
/// <summary>
/// 传入信息来新建字段
/// </summary>
/// <param name="targetFieldInfos">目标字段集合</param>
/// <param name="Name">名称</param>
/// <param name="Caption">别名</param>
/// <param name="fieldType">字段类型</param>
/// <param name="defaultValue">默认值</param>
/// <returns></returns>
public static bool AddNewFieldInfo(FieldInfos targetFieldInfos, string Name, string Caption, FieldType fieldType, string defaultValue = "")
{
bool addSuccess = true;
FieldInfo newFieldInfo = new FieldInfo()
{
Name = Name,
Caption = Caption,
DefaultValue = defaultValue,
Type = fieldType,
};
if (AvailableAdded(targetFieldInfos,newFieldInfo))
{
try
{
int newIndex = targetFieldInfos.Add(newFieldInfo);
addSuccess = newIndex > 0 ? true : false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message, "EditFieldInfo.cs : funciton AddNewFieldInfo");
addSuccess = false;
}
};
return addSuccess;
}
/// <summary>
/// 批量添加字段,提供摒弃原来字段的defaultValue和IsRequired信息的选项
/// </summary>
/// <param name="targetFieldInfos">被添加进的字段</param>
/// <param name="sourceFieldInfos">提供信息的字段</param>
/// <param name="ignoreDefaultValue">是否忽略默认值,默认为忽略:true</param>
/// <param name="ignoreRequired">是否忽略必填,默认为忽略:true</param>
/// <returns></returns>
public static bool BatchAddField(FieldInfos targetFieldInfos, FieldInfos sourceFieldInfos, bool ignoreDefaultValue = true, bool ignoreRequired = true)
{
bool isAdd = true;
List<FieldInfo> fieldInfos = new List<FieldInfo>();
foreach (FieldInfo item in sourceFieldInfos)
{
if (AvailableAdded(targetFieldInfos,item)) //不是系统字段、不是SmUserID,不存在
{
FieldInfo fieldInfo = item.Clone();
if (ignoreDefaultValue && ignoreRequired) // 不继承默认值和必填
{
fieldInfo.DefaultValue = string.Empty;
fieldInfo.IsRequired = false;
}
else if (!ignoreDefaultValue && ignoreRequired) // 继承默认值
{
fieldInfo.IsRequired = false;
}
else if (ignoreDefaultValue && !ignoreRequired) // 继承必填
{
fieldInfo.DefaultValue = string.Empty;
}
fieldInfos.Add(fieldInfo);
}
}
targetFieldInfos.AddRange(fieldInfos.ToArray());
return isAdd;
}
/// <summary>
/// 清空所有非系统字段
/// </summary>
/// <param name="fieldInfos"></param>
/// <returns></returns>
public static bool ClearFieldInfos(FieldInfos fieldInfos)
{
bool isSuccess = true;
try
{
while (!IsFieldEmpty(fieldInfos)) // fieldInfos.Remove内部是异步实现的,必须一直循环直到删完。
{
foreach (FieldInfo item in fieldInfos)
{
if (item.IsSystemField || item.Name == "SmUserID") continue;
fieldInfos.Remove(item.Name);
}
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message, "EditFieldInfo.cs : funciton ClearFieldInfos");
isSuccess = false;
}
return isSuccess;
}
/// <summary>
/// 检查字段是否已经被完全清空,只留系统保留字段,被完全清空返回true
/// </summary>
/// <param name="fieldInfos"></param>
/// <returns></returns>
public static bool IsFieldEmpty(FieldInfos fieldInfos)
{
for (int i = 0; i < fieldInfos.Count; i++)
{
if (!fieldInfos[i].IsSystemField && fieldInfos[i].Name != "SmUserID")
{
return false;
}
}
return true;
}
/// <summary>
/// 检查字段类型:数值、文本、其他
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public static QueryType CheckFieldType(FieldInfo fieldInfo)
{
var type = fieldInfo.Type;
QueryType fieldType;
switch (type)
{
case FieldType.Byte:
case FieldType.Char:
case FieldType.Text:
case FieldType.WText:
fieldType = QueryType.Text;
break;
case FieldType.Double:
case FieldType.Int16:
case FieldType.Int32:
case FieldType.Int64:
case FieldType.LongBinary:
case FieldType.Single:
fieldType = QueryType.Number;
break;
default:
fieldType = QueryType.Number;
break;
}
return fieldType;
}
/// <summary>
/// 去除重复字段
/// </summary>
/// <param name="resultDataset">需要去除重复字段的数据集</param>
/// <returns></returns>
public static bool EraseRepeatField(DatasetVector resultDataset)
{
// 检查别名一样的字段。如果是 xxx_1就是源数据,用xxx_2覆盖掉,然后删除xxx_2,重命名xxx_1为xxx
bool isSuccess = true;
var fieldInfos = resultDataset.FieldInfos;
Dictionary<string, FieldType> fieldCaptions = new Dictionary<string, FieldType>();
Dictionary<string, FieldType> repeatFields = new Dictionary<string, FieldType>();
foreach (FieldInfo field in fieldInfos)
{
if (!fieldCaptions.ContainsKey(field.Caption))
{
fieldCaptions.Add(field.Caption, field.Type);
}
else
{
if (!repeatFields.ContainsKey(field.Caption))
repeatFields.Add(field.Caption, field.Type);
}
}
foreach (var repeatField in repeatFields)
{
if (fieldInfos.IndexOf(repeatField.Key) == -1)
{
fieldInfos.Add(new FieldInfo()
{
Caption = repeatField.Key,
Name = repeatField.Key,
Type = repeatField.Value
});
}
string sourceFieldName = $"{repeatField.Key}_1";
string operationFieldName = $"{repeatField.Key}_2";
var recordset = resultDataset.GetRecordset(false, CursorType.Dynamic);
while (!recordset.IsEOF)
{
var oldValue = recordset.GetFieldValue(sourceFieldName);
var newValue = recordset.GetFieldValue(operationFieldName);
recordset.Edit();
bool _res = false;
if (newValue == null || newValue == string.Empty || newValue.ToString().Trim() == string.Empty)
{
if (repeatField.Value == FieldType.Double && oldValue != null)
oldValue = oldValue == null ? 0 : double.Parse(oldValue.ToString());
_res = recordset.SetFieldValue(repeatField.Key, oldValue);
if (!_res) isSuccess = false;
}
else
{
if (repeatField.Value == FieldType.Double)
newValue = double.Parse(newValue.ToString());
_res = recordset.SetFieldValue(repeatField.Key, newValue);
if (!_res)
{
isSuccess = false;
}
}
recordset.Update();
recordset.MoveNext();
}
fieldInfos.Remove(sourceFieldName);
fieldInfos.Remove(operationFieldName);
recordset.MoveFirst();
}
return isSuccess;
}
}
9.数据集添加为图层的经验
①先检查是否可以添加到地图中,因为有些datasetVector是表格(Tabular)或者数据库表(LinkTable),这些数据不携带Geometry信息,不能添加到地图中。
②检查该图层是否可以被附图层风格,因为如:栅格、模型、遥感影像等是不能被设置图层颜色等的风格信息的。
③随机给图层附上一个现有地图不存在的颜色风格。
代码如下:
/// <summary>
/// 把图层添加到地图中,并随机附上一个不存在的颜色
/// </summary>
/// <param name="dataset">数据集</param>
/// <param name="addToHead">添加到地图最顶部显示</param>
/// <param name="mapControl">地图控件</param>
/// <param name="existAdd">如果数据已经添加到了图层中,是否再次添加</param>
/// <returns></returns>
public static bool AddMapInRamdomColor(Dataset dataset, bool addToHead, MapControl mapControl ,bool existAdd = true)
{
try
{
if (!existAdd)
{
Layer layer = mapControl.Map.Layers.FindLayer(dataset);
if (layer != null)
{
mapControl.Map.Refresh();
return true;
}
}
string name = $"{dataset.Name}@{dataset.Datasource.Alias}";
if (!CanAddToMap(dataset)) return false;
if (CanSetStyle(dataset))
{
LayerSettingVector layerSetting = new LayerSettingVector();
GeoStyle style = new GeoStyle();
style.FillForeColor = GenerateColorRandom();
layerSetting.Style = style;
mapControl.Map.Layers.Add(dataset, layerSetting, addToHead);
}
else
{
mapControl.Map.Layers.Add(dataset, addToHead);
}
mapControl.Map.ViewBounds = dataset.Bounds;
mapControl.Map.Refresh();
string mapXML = mapControl.Map.ToXML();
dataset.Datasource.Workspace.Maps.SetMapXML(0, mapXML);
dataset.Datasource.Workspace.Save();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 是否可以添加到地图,即:表格和数据库外联表不能添加到地图
/// </summary>
/// <param name="dataset"></param>
/// <returns></returns>
private static bool CanAddToMap(Dataset dataset)
{
DatasetType[] datasetTypes = new DatasetType[] { DatasetType.Tabular, DatasetType.LinkTable };
if (datasetTypes.Contains(dataset.Type)) return false;
else return true;
}
/// <summary>
/// 是否可以设施图层风格,有些图层无法设置风格,如栅格、模型、遥感影像等
/// </summary>
/// <param name="dataset"></param>
/// <returns></returns>
private static bool CanSetStyle(Dataset dataset)
{
DatasetType[] datasetTypes = new DatasetType[]
{
DatasetType.Grid, DatasetType.GridCollection, DatasetType.Image, DatasetType.ImageCollection, DatasetType.Model,
DatasetType.Mosaic, DatasetType.Tabular, DatasetType.Unknown , DatasetType.Volume , DatasetType.WCS, DatasetType.WMS
};
return !datasetTypes.Contains(dataset.Type);
}
10. 多部件的检查与拆解
10.1 多部件的检查
/// <summary>
/// 检查面状图斑是否为多部件。
/// </summary>
/// <param name="geoRegion"></param>
/// <returns>存在多部件问题就返回true</returns>
public static bool HavaRegionMultiPart(GeoRegion geoRegion)
{
if (geoRegion == null)
return false;
GeoRegion[] geos = geoRegion.ProtectedDecompose();
if (geos == null || geos.Length <= 1)
{
return false;
}
foreach (GeoRegion item in geos)
{
if (!Geometrist.HasHollow(item)) // 有很多个子图斑,但是没有岛洞关系
return true;
}
return false;
}
10.2 多部件的拆解
/// <summary>
/// 打散多部件
/// </summary>
/// <param name="decomposeRecordset">待打散多部件的数据集</param>
/// <param name="areaFieldNames">跟随面积一起等比变化的字段。如宗地面积,图斑面积等字段信息</param>
/// <returns></returns>
public static bool DecomposeRegion(Recordset decomposeRecordset, List<string> areaFieldNames)
{
bool isSuccess = true;
GeoRegion geoRegion = decomposeRecordset.GetGeometry() as GeoRegion;
// 保护性拆散
GeoRegion[] partRegions;
partRegions = geoRegion.ProtectedDecompose();
if (partRegions == null)
{
return true;
}
Recordset originRecordset = decomposeRecordset.Dataset.GetRecordset(false, CursorType.Dynamic);
FieldInfos fieldInfos = decomposeRecordset.GetFieldInfos();
double originTotalArea = double.Parse(decomposeRecordset.GetFieldValue("SmArea").ToString()); // 真实面积大小
foreach (GeoRegion region in partRegions)
{
originRecordset.AddNew(region);
originRecordset.Update();
foreach (FieldInfo field in fieldInfos)
{
if (field.IsSystemField || field.Name.Equals("SmUserID")) continue;
originRecordset.Edit();
object value = decomposeRecordset.GetFieldValue(field.Name);
if (areaFieldNames.Contains(field.Name)) // 处理要均分面积的字段
{
double value_number; // 未被均分前的面积
if (value == null || string.IsNullOrEmpty(value.ToString()) || string.IsNullOrWhiteSpace(value.ToString()))
{
value_number = 0;
}
else
{
value_number = double.Parse(value.ToString());
};
value_number *= (region.Area / originTotalArea);
var _isset = originRecordset.SetFieldValue(field.Name, value_number);
if (!_isset) originRecordset.SetFieldValue(field.Name, value_number.ToString());
}
else
{
var _isSet = originRecordset.SetFieldValue(field.Name, value);
if (!_isSet) originRecordset.SetFieldValue(field.Name, value.ToString());
}
originRecordset.Update();
};
}
decomposeRecordset.Delete();
return isSuccess;
}
11. 搜索距离最近的几何对象
没什么坑,就是在官方文档里面比较难找,在这里记录一下,代码如下:
/// <summary>
/// 搜索距离中心点最近的对象
/// </summary>
/// <param name="searchRecordset">搜索数据集</param>
/// <param name="centerPoints">搜索的中心点</param>
/// <param name="minDistance">搜索最近半径</param>
/// <param name="maxDistance">搜索最远半径</param>
/// <param name="isTheSameLayer">中心点和待搜索数据集是否属于同一个图层</param>
/// <returns></returns>
public static Recordset GetClosedRecordByPoint(Recordset searchRecordset, GeoPoint[] centerPoints, double minDistance, double maxDistance, bool isTheSameLayer)
{
ComputeDistanceResult[] results = ProximityAnalyst.ComputeRangeDistance(centerPoints, searchRecordset, minDistance, maxDistance);
if (results == null || results.Length == 0) return null; // 没有最近的
if (isTheSameLayer && results.Length == 1) return null; // 搜索数据集和中心点属于同一个图层,只搜到一个的话那就是他自己
var result = results[0];
int[] IDs = result.ReferenceGeometryIDs;
Recordset resultRecord = searchRecordset.Dataset.Query($"SmID = {IDs[0]}", CursorType.Dynamic);
return resultRecord;
}
12. 数据导入
tips: 超图的数据导入设计成为了工厂模式,对设计模式不熟的可以参考学习一下
12.1 导入shp文件(坑指数⭐⭐)
导入时的时候,注意要把ImportSetting的IsImport3D参数设为false ,因为:一个二维点的.shp数据(线、面数据同理),如果用ArcGIS打开后在属性里面勾选了Z坐标,再在超图iObject进行导入,会导入为一个三维点数据(但是在iDesktop里面导入又还是二维的点数据,不知道它官方是怎么进行判断的,我能做的只是把IsImport3D设为false)。设置以后,一个二维数据就算在ArcGIS里面勾选了Z坐标属性,导入进超图后也还是会是二维数据。
DataImport import = new DataImport())
ImportSetting importSetting = new ImportSettingSHP()
{
SourceFilePath = filePath,
TargetDatasource = targetDatasource,
IsImportAs3D = false, // 导入为三维图层设为false
ImportMode = ImportMode.None
};
import.ImportSettings.Add(importSetting);
ImportResult result = import.Run();
import.Dispose();
if (result.SucceedSettings.Length > 0)
{
return true;
}
return false;
12.2 导入MDB文件(坑指数⭐⭐⭐)
需要在电脑上安装一个AccessDatabaseEngine的插件才可以在超图里成功导入MDB数据文件。插件的微软官方下载地址: https://www.microsoft.com/zh-CN/download/details.aspx?id=13255 ,导入代码如下:
ImportSettingPersonalGDBVector importSettingPersonalGDBVector = new ImportSettingPersonalGDBVector()
{
SourceFilePath = filePath,
TargetDatasource = GlobalParameters.global_workspace.Datasources[datasourceKey],
ImportMode = ImportMode.Overwrite
};
DataImport import1 = new DataImport();
ImportSettings settings = import1.ImportSettings;
settings.Add(importSettingPersonalGDBVector);
ImportResult result = import1.Run();
12.3 导入TIF文件
导入TIF的坑主要是导入以后影像每个波段都会被单独导入为一张图片,但我们希望的是导入以后就是一张影像,具体的配置如下:
var importSetting = new ImportSettingTIF()
{
SourceFilePath = filePath,
TargetDatasetName = fileName,
TargetDatasource = datasource,
IsPyramidBuilt = true, // 遥感影像一般都比较大,导入的时候就建立金字塔
MultiBandImportMode = MultiBandImportMode.MultiBand // 多个单波段融合成一个多波段
};
13. 判断边界走向
没有坑,但是不好找到,记录一下,true为逆时针,false为顺时针。注:在GIS面要素图层中,顺时针为面,逆时针为洞。
GeoReigon.IsCounterClockwise(int index)
14. 数据清空之坑(坑爹指数 ⭐⭐⭐⭐)
拿得到一个矢量图层datasetVector后,要把图层数据清空的API有两个,一个是DatasetVector自带的truncated方法:
DatasetVector.Truncate() ;
一个是通过找到他的Recordset,再调用Recordset里面的deleteAll方法:
var recordset = datasetVector.getRecordset(false, Cursor.Dynamic);
recordset.deleteAll();
一定要用第二个deleteAll方法!!! 第一个直接truncated会有很严重的bug!!!!(Truncate不能把数据删除干净,出现各种bug,而且还会把datasetvector数据给破坏掉,不能再进行任何的增删改查)被坑了2天
15. 多对象打散的坑(坑爹指数⭐⭐⭐⭐⭐)
// 面对象保护性分解
// 区别于组合对象将子对象进行简单分解,保护性分解将复杂的具有多层岛洞嵌套关系的面对象分解成只有一层嵌套关系的面对象。
GeoRegion.ProtectedDecompose
多对象的含义:一条recordset数据里面,有多个geometry,如下图所示,多个分离的图斑组合为了一条数据,也可以理解为有多块飞地的数据。
函数GeoRegion.ProtectedDecompose ,就可以把这些图斑都打散,形成一个个独立的图斑,但是在精度极高的情况下(tolerance在0.001米的时候)会出现bug,如下图所示,有一个很小的图斑(0.000025平方米)和这个多子对象图斑呈邻接关系,在把这个多子对象图斑拆解后,这个小图斑就会和被拆下来的大图版呈分离关系,两个的距离大概有0.0015米。这个的解决方案:无解,是超图内部算法有误。暂时的解决方法就是自己封装一个带tolerance的邻接算法,总体思路就是:datasetvector.query方法找到touched邻接图版 + ProximityAnalyst.ComputeRangeDistance计算离自己0.0015米以内是否有图版,如果有也看做是邻接的,这样就自己封装了一个带有tolerance的邻接搜索函数。