【ArcEngine入门与提高】Element(元素)、Annotation(注记)旋转

时间:2022-11-01 08:21:30
因项目需要,需要做一个旋转注记的工具。因为注记这玩意用的比较少,网上资源也很少,所以做起来相当头疼。在经过一番研究之后,终于搞清楚注记的存储原理了,原来是和Element的类似,只不过注记是要把Element写入FeatureClass的。那么好,事情简单多了,能实现旋转Element,就能实现旋转注记了。于是乎又在网上找旋转Element的资源,没想到又遇上难题。ArcEngine里面实现带轨迹的元素旋转的接口是IRotateTracker,但是这玩意很不好用,不知道是不是我没研究到位,我用了之后一点反应也没有,RotateTracker的Angle值始终为0,这可愁死人了,网上搜索发现好多网友也跟我同样的遭遇,这难道是BUG??且不管这些了,项目总是要进行的,我只能换别的方法了。
说到旋转其实倒是不难,ITransform2D.Rotate()方法还是很常用的,难就难在旋转的轨迹上。所幸Elment的操作还是很轻松的,只要在OnMouseMove里面时时进行旋转就行了,轨迹也很顺畅。而Annotation的操作就不那么轻松了,由于涉及到动态存储数据到空间数据库中,旋转轨迹相当不顺畅,出现卡滞现象,没有操作感可言。于是乎又挠头了,在挠掉了数以万计的头皮屑之后终于想到方法了。既然Elment操作很流畅,那为什么不用Elment做为轨迹呢,然后在OnMouseUp里面做最终的旋转?说干就干,我想的方法果然可行。但最核心的难题出现了,怎么在OnMouseMove里面动态获取Elment的角度呢,相信这也是很多网友想知道的问题,让大家听了这么多废话,那么直接上代码吧!

/// <summary>
/// 旋转元素工具
/// </summary>
public sealed class BTGraphicsRotateElement : BaseTool
{
#region 成员变量

private IHookHelper m_hookHelper = null;
private IPoint m_point;
private IElement m_element;
private bool m_moving;
private IPoint m_oldPoint;
private IElement m_viewElement;
/// <summary>
/// 获取或设置标注图层
/// </summary>
public IFeatureLayer AnnotationLayer { get; set; }


#endregion

#region 构造函数

public BTGraphicsRotateElement()
{
base.m_category = "";
base.m_caption = "";
base.m_message = "";
base.m_toolTip = "旋转元素";
base.m_name = "";
base.m_cursor = Cursors.Default;
try
{
base.m_bitmap = Properties.Resources.bmp_EditingRotate;
}
catch
{
base.m_bitmap = null;
}
}
public BTGraphicsRotateElement(IFeatureLayer annotationLayer)
: this()
{
AnnotationLayer = annotationLayer;
}
#endregion

#region 方法覆写

public override void OnCreate(object hook)
{
try
{
m_hookHelper = new HookHelperClass();
m_hookHelper.Hook = hook;
if (m_hookHelper.ActiveView == null)
{
m_hookHelper = null;
}
}
catch
{
m_hookHelper = null;
}

if (m_hookHelper == null)
base.m_enabled = false;
else
base.m_enabled = true;

// TODO: Add other initialization code

}

public override void OnClick()
{
// TODO: Add StationTool.OnClick implementation
}

public override void OnMouseDown(int Button, int Shift, int X, int Y)
{
if (Button == 1)
{
IGraphicsContainer pGraphicsContainer = m_hookHelper.ActiveView as IGraphicsContainer;
m_point = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);
IEnumElement pEnumElement = pGraphicsContainer.LocateElements(m_point, 1);
if (pEnumElement == null) return;
m_element = pEnumElement.Next();

if (m_element is AnnotationElement)
{
if (AnnotationLayer == null)
{
ILayer pLayer = Utility.GetFeatureLayer("注记", m_hookHelper);
AnnotationLayer = pLayer as IFeatureLayer;
}
if (AnnotationLayer == null)
{
MessageBox.Show("未找到注记图层!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 根据注记的参考坐标调整字体显示大小【修正部分】
IAnnotationClassExtension pAnnotationClassExtension = AnnotationLayer.FeatureClass.Extension as IAnnotationClassExtension;
ITextSymbol pSymbol = ((ITextElement)m_element).Symbol;
pSymbol.Size = pSymbol.Size / (m_hookHelper.ActiveView.FocusMap.MapScale / pAnnotationClassExtension.ReferenceScale);
ITextElement pTextElement = new TextElementClass { Text = ((ITextElement)m_element).Text, Symbol = pSymbol };

m_viewElement = pTextElement as IElement;
m_viewElement.Geometry = m_element.Geometry;
m_hookHelper.ActiveView.GraphicsContainer.AddElement(m_viewElement, 0);
m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, m_viewElement, null);
}

// 移动状态信息
m_moving = true;



}
}

public override void OnMouseMove(int Button, int Shift, int X, int Y)
{
if (Button == 1)
{
if (!m_moving) return;

IPoint centerPoint = new PointClass
{
X = (m_element.Geometry.Envelope.LowerLeft.X + m_element.Geometry.Envelope.LowerRight.X) / 2,
Y = (m_element.Geometry.Envelope.LowerLeft.Y + m_element.Geometry.Envelope.UpperRight.Y) / 2
};
if (m_element is AnnotationElement)
{
// 如果旧点为空,则赋OnMouseDown事件所获取的点值
if (m_oldPoint == null)
m_oldPoint = m_point;
// 新点为当前的鼠标点坐标
IPoint newPoint = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);
// 获取旧点角度
IPointCollection pPointCollection = new PolylineClass();
object missing = Type.Missing;
pPointCollection.AddPoint(centerPoint, ref missing, ref missing);
pPointCollection.AddPoint(m_oldPoint, ref missing, ref missing);
double oldAngle = GetAngle(pPointCollection as IPolyline);
// 获取新点角度
IPointCollection pointCollection = new PolylineClass();
pointCollection.AddPoint(centerPoint, ref missing, ref missing);
pointCollection.AddPoint(newPoint, ref missing, ref missing);
double newAngle = GetAngle(pointCollection as IPolyline);
// 旋转Element,角度为新旧点之差
ITransform2D pTransform2D = m_viewElement as ITransform2D;
pTransform2D.Rotate(centerPoint, (newAngle - oldAngle));
m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, m_viewElement, m_hookHelper.ActiveView.Extent);

// 更新旧点变量
m_oldPoint = newPoint;

}
else
{
object missing = Type.Missing;
IPoint newPoint = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);
IPointCollection pPointCollection = new PolylineClass();
pPointCollection.AddPoint(centerPoint, ref missing, ref missing);
pPointCollection.AddPoint(m_point, ref missing, ref missing);
double oldAngle = GetAngle(pPointCollection as IPolyline);

IPointCollection pointCollection = new PolylineClass();
pointCollection.AddPoint(centerPoint, ref missing, ref missing);
pointCollection.AddPoint(newPoint, ref missing, ref missing);
double newAngle = GetAngle(pointCollection as IPolyline);

ITransform2D pTransform2D = m_element as ITransform2D;
pTransform2D.Rotate(centerPoint, (newAngle - oldAngle));
m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, m_element, m_hookHelper.ActiveView.Extent);
m_point = newPoint;
}



}
}

public override void OnMouseUp(int Button, int Shift, int X, int Y)
{
if (Button == 1)
{
if (m_element is AnnotationElement)
{
IPoint centerPoint = new PointClass
{
X = (m_element.Geometry.Envelope.LowerLeft.X + m_element.Geometry.Envelope.LowerRight.X) / 2,
Y = (m_element.Geometry.Envelope.LowerLeft.Y + m_element.Geometry.Envelope.UpperRight.Y) / 2
};
if (AnnotationLayer == null)
{
ILayer pLayer = Utility.GetFeatureLayer("注记", m_hookHelper);
AnnotationLayer = pLayer as IFeatureLayer;
}
if (AnnotationLayer == null)
{
MessageBox.Show("未找到注记图层!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
IFeatureClass pFeatureClass = AnnotationLayer.FeatureClass;
IDataset pDataset = (IDataset)pFeatureClass;
IWorkspaceEdit pWorkspaceEdit = (IWorkspaceEdit)pDataset.Workspace;
pWorkspaceEdit.StartEditing(true);
pWorkspaceEdit.StartEditOperation();
pWorkspaceEdit.EnableUndoRedo();

double angle = ((ITextElement)m_viewElement).Symbol.Angle;
double oldAngle = ((ITextElement)m_element).Symbol.Angle;
ITransform2D pTransform2D = m_element as ITransform2D;
pTransform2D.Rotate(centerPoint, (angle - oldAngle) * Math.PI / 180);

IGraphicsContainer pGraphicsContainer = m_hookHelper.ActiveView as IGraphicsContainer;
pGraphicsContainer.UpdateElement(m_element);
pWorkspaceEdit.DisableUndoRedo();
pWorkspaceEdit.StopEditOperation();
pWorkspaceEdit.StopEditing(true);
pGraphicsContainer.DeleteElement(m_viewElement);
m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, m_hookHelper.ActiveView.Extent);
m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, m_hookHelper.ActiveView.Extent);
}
m_moving = false;
m_element = null;
m_point = null;
m_viewElement = null;
m_oldPoint = null;
}
}

#endregion

#region 方法函数

/// <summary>
/// 获取线的角度(弧度)
/// </summary>
/// <param name="pPolyline">线</param>
/// <returns></returns>
private static double GetAngle(IPolyline pPolyline)
{
ILine pTangentLine = new Line();
pPolyline.QueryTangent(esriSegmentExtension.esriNoExtension, 0.5, true, pPolyline.Length, pTangentLine);
Double radian = pTangentLine.Angle;

/*
Double angle = radian * 180 / Math.PI;
// 如果要设置正角度执行以下方法
while (angle < 0)
{
angle = angle + 360;
}
// 返回角度
return angle;
*/

// 返回弧度
return radian;
}

#endregion
}


以上代码是做成BaseTool类,可以直接放到项目中使用,唯一要注意的是,里面涉及到注记的图层需要指定,如果不指定的话可能会报错,我是用的我项目里面的注记图层来做的,如果单纯操作Elment可以不考虑。后面还附了一个获取角度的方法,准确的来说是获取弧度,因为ITransform2D.Rotate()里面的Angle是弧度。
/*****分割线*****/
以上工具在项目中可以正常使用,唯一不足的是用作旋转时临时显示的元素大小在不同比例尺下跟注记本身的大小会有差异,因此第二个版本我已经做了改进,原理就是利用比例尺的差异修正元素大小,测试通过,见【修正部分】。