实验目的
使用C#和arcengine,结合直接聚类方法实现地图上点的聚类,并可以让使用者自定义聚类的级别
实验数据
全国省会shapefile数据
主要界面
实现逻辑
利用ArcEngine接口,计算点之间的距离,生成距离矩阵,并利用Clustering类进行距离聚类,生成聚类图,并在PictureBox的canvas中绘制聚类图,然后根据用户自定义的距离,对点进行距离分类。
实现代码
二叉树类 BinaryTree
public class BinaryTree //二叉树结构
{
public int key;
public double value;
public BinaryTree right;
public BinaryTree left;
public int getDepth()
{ //获得二叉树实例的深度
return getDepth(this);
}
private int getDepth(BinaryTree br)
{
if (br != null)
{
int rightdepth = getDepth(br.right);
int leftdepth = getDepth(br.left);
return (rightdepth > leftdepth ? rightdepth : leftdepth) + 1;
}
else
{
return 0;
}
}
public void getCutBNList(BinaryTree bt, double cutValue, List<BinaryTree> BNList)
{ //获取节点值小于规定值的树节点列表
if (bt.value <= cutValue)
{
BNList.Add(bt);
}
else
{
getCutBNList(bt.left, cutValue, BNList);
getCutBNList(bt.right, cutValue, BNList);
}
}
public void getLeafList(BinaryTree bt, List<int> leafList)
{ //获取树节点对应的叶子节点
if (bt.left == null && bt.right == null)
{
leafList.Add(bt.key);
}
else
{
if (bt.left != null) getLeafList(bt.left, leafList);
if (bt.right != null) getLeafList(bt.right, leafList);
}
}
public List<List<int>> getCutLists(double cutValue)
{ //获取分割后,每一个树节点对应的叶子节点 {树1:{叶子1, 叶子2, 叶子3}, 树2:{叶子1}}
List<BinaryTree> BNList = new List<BinaryTree>();
getCutBNList(this, cutValue, BNList);
List<List<int>> cutLists = new List<List<int>>();
foreach (BinaryTree bt in BNList)
{
List<int> cutList = new List<int>();
getLeafList(bt, cutList);
cutLists.Add(cutList);
}
return cutLists;
}
public string btToJson() //将二叉树转化为json格式
{
return btToJson(this);
}
public string btToJson(BinaryTree bt)
{
string value = "value:" + bt.key;
string right = "right:";
string left = "left:";
if (bt.right != null) { right += btToJson(bt.right); }
if (bt.left != null) { left += btToJson(bt.left); }
return "{" + value + "," + right + "," + left + "}";
}
}
聚类类 Clustering
public class Clustering //使用最短距离聚类法进行聚类
{
public int n { get; set; } //聚类的点的个数
public BinaryTree br { get; set; } //聚类使用的二叉树结构
public int root { get;set; } //当前聚类的根节点
private double[,] matrix { get; set; } //距离矩阵
public int[] dirt { get; set; } //保存需要聚类的节点的字典
public Clustering(int n, double[,] matrix)
{
this.n = n;
this.root=n-1;
if (matrix.GetLength(1) != n || matrix.GetLength(0) != n)
{
throw new MatrixExpection("输入的距离矩阵与元素个数不匹配哦!");
}
else
{
dirt=new int[n];
for (int i = 0; i < n; i++) {
dirt[i] = i;
}
this.matrix = new double[2 * n - 1, 2 * n - 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
this.matrix[i, j] = matrix[i, j];
}
}
}
}
public void run() //运行聚类方法,生成聚类二叉树
{
BinaryTree br=new BinaryTree();
List<BinaryTree> brs = new List<BinaryTree>();
for (int point = n; point > 1; point--) {
int minX = 0;
int minY = 0;
double value=getMin(matrix, point, ref minX, ref minY); //获取距离矩阵中的最小值
resetdirt(minX, minY, point); //重新设置需要聚类的节点的字典
setNewRoot(minX, minY, point); //在距离矩阵中加入新的节点的距离
br = new BinaryTree() { key = root, value = value };
br.right = minX >= n ? brs.Find(i => i.key == minX) : new BinaryTree() { key = minX }; //判断当前节点的右节点是树节点还是叶子节点
br.left = minY >= n ? brs.Find(i => i.key == minY) : new BinaryTree() { key = minY };
brs.Add(br);
}
this.br = br;
}
public double getMin(double[,] matrix,int length, ref int x, ref int y) {
double min = double.MaxValue;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (matrix[dirt[i], dirt[j]] < min && matrix[dirt[i], dirt[j]]!=0)
{
x = dirt[i]; y = dirt[j];
min = matrix[dirt[i], dirt[j]];
}
}
}
return min;
}
public void resetdirt(int x,int y,int length){
for (int i = 0; i < length-2; i++) {
if(this.dirt[i]==x||this.dirt[i]==y){
for(int j=i;j<length-1;j++){
this.dirt[j]=this.dirt[j+1];
}
if(i<length-1)i--;
}
}
this.dirt[length-2]=++this.root;
}
public void setNewRoot(int x, int y,int length) {
for (int i = 0; i < length - 2; i++) {
double ix = this.matrix[this.dirt[i], x];
double iy = this.matrix[this.dirt[i], y];
this.matrix[dirt[i], root] = ix < iy ? ix : iy;
this.matrix[root, dirt[i]] = ix < iy ? ix : iy;
}
}
}
使用递归方法绘制聚类图
private int draw(Graphics gp,Pen pen, int height, int width, double spacingX, double treeheight, int padding, BinaryTree br) //通过递归方法绘制聚类图
{
if (br.left == null && br.right == null) {
int fontsize = spacingX / 3 < 20 ? (int)(spacingX / 3) : 20;
Font font = new Font("宋体", fontsize, FontStyle.Regular);
Brush bush = new SolidBrush(Color.Black);
gp.DrawString("p" + (br.key+1), font, bush, (int)(padding + n * spacingX-fontsize/2), (int)(height - padding )); //绘制底部点序号
n++;
return (int)((n-1)*spacingX);
}else{
int fontsize = spacingX / 3 < 15 ? (int)(spacingX / 3) : 15;
int left = draw(gp,pen, height, width, spacingX, treeheight, padding, br.left); //递归左起点,返回左子树的横向位置
int right = draw(gp,pen, height, width, spacingX, treeheight, padding, br.right); //同上
gp.DrawLine(pen,padding+left,(int)(height-padding-treeheight*br.left.value),padding+left,(int)(height-padding-treeheight*br.value)); //绘制当前节点到左节点的线
gp.DrawLine(pen,padding+right,(int)(height-padding-treeheight*br.right.value),padding+right,(int)(height-padding-treeheight*br.value)); //同上
gp.DrawLine(pen,padding+left,(int)(height-padding-treeheight*br.value),padding+right,(int)(height-padding-treeheight*br.value)); //绘制横线
Font font = new Font("宋体", fontsize, FontStyle.Regular);
Brush bush = new SolidBrush(Color.Blue);
gp.DrawString(string.Format("{0:###.##}", br.value), font, bush, padding + (left + right) / 2 - fontsize * string.Format("{0:###.##}", br.value).Length/2,
(int)(height - padding - treeheight * br.value + 3)); //绘制当前节点的聚类距离
return (left+right)/2;
}
}
pictureBox的绘制方法
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int height = this.ClientRectangle.Height;
int width = this.ClientRectangle.Width;
int padding = 30;
double treeHeight = (height - padding * 2) / clu.br.value;
if (isFirst) //第一次绘制是使用draw()方法,之后直接绘制第一次保存的bitmap
{
Console.WriteLine("map:" + height + "," + width);
double spacingX = (width - padding * 2) / (clu.n - 1);
this.bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics gp = Graphics.FromImage(bitmap);
gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Pen redPen = new Pen(Color.Red, 3);
draw(gp, redPen, height, width, spacingX, treeHeight, padding, clu.br);
isFirst=false;
}
Graphics egp = e.Graphics;
Pen greenPen=new Pen(Color.Green,1);
greenPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
greenPen.DashPattern = new float[] { 5, 5 };
egp.DrawImage(bitmap, 0, 0);
int y = mouseLocation.Y > padding ? mouseLocation.Y : padding;
y = y < height - padding ? y : height - padding;
Font font = new Font("宋体", 10, FontStyle.Regular);
Brush bush = new SolidBrush(Color.Green);
egp.DrawLine(greenPen, 0, y, width, y); //绘制鼠标所在高度的距离值
egp.DrawString(string.Format("{0:###.##}", clu.br.value * (height - y-padding) / (height - padding * 2)), font, bush, 5, y - 13);
/*
**相应点击事件,生成聚类图层
*/
if (isClick) {
cutHeight = y;
List<List<int>> cutLists = clu.br.getCutLists(clu.br.value * (height - y - padding) / (height - padding * 2));
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
IFeatureLayer pCluLayer = parentForm.createClusteringLayer(pFeatureLayer, cutLists); //生成聚类方法
stopwatch.Stop();
Console.WriteLine("运行时长" + stopwatch.Elapsed.TotalMilliseconds);
for (int i = 0; i < parentForm.axMapControl1.LayerCount; i++) {
if (parentForm.axMapControl1.get_Layer(i).Name == pCluLayer.Name) {
parentForm.axMapControl1.DeleteLayer(i);
}
}
parentForm.axMapControl1.AddLayer(pCluLayer);
ProportionalRenderer(pCluLayer, "cluNum", ColorToIColor(Color.Blue), 3); //按照点个数分级显示
isClick = false;
}
if (cutHeight >= padding) {
egp.DrawLine(new Pen(Color.Yellow, 2), 0, cutHeight, width, cutHeight);
}
egp.DrawString(string.Format("{0:###.##}", clu.br.value * (height - y - padding) / (height - padding * 2)), font, bush, 5, y - 13);
}
创建聚类图层
public IFeatureLayer createClusteringLayer(IFeatureLayer pFeatureLayer, List<List<int>> lists)
{
IFeatureClass pfeatureClass = pFeatureLayer.FeatureClass;
ISpatialReference pSpatialReference = this.axMapControl1.ActiveView.FocusMap.SpatialReference;
IWorkspaceFactory pWF = new ShapefileWorkspaceFactory();
IFeatureWorkspace pFeatureWorkspace = (IFeatureWorkspace)pWF.OpenFromFile("D:/",0);
IFeatureClass clusteringClass = null;
if (File.Exists("D:/cluster" + pFeatureLayer.Name + ".shp")) //判断是否有该图层对应的距离聚类图层
{
clusteringClass = pFeatureWorkspace.OpenFeatureClass("cluster" + pFeatureLayer.Name + ".shp");
IFeatureCursor clusteringCursor = clusteringClass.Update(null, true);
IFeature clusteringFeature = clusteringCursor.NextFeature();
while (clusteringFeature != null) //删除上一次聚类的点
{
clusteringCursor.DeleteFeature();
clusteringFeature = clusteringCursor.NextFeature();
}
}
else
{
//创建聚类点图层
IFields pFields = new FieldsClass();
IFieldsEdit pFieldsEdit = (IFieldsEdit)pFields;
IField pField = new FieldClass();
IFieldEdit pFieldEdit = (IFieldEdit)pField;
pFieldEdit.Name_2 = "SHAPE";
pFieldEdit.Type_2 = esriFieldType.esriFieldTypeGeometry;
IGeometryDefEdit pGeoDef = new GeometryDefClass();
IGeometryDefEdit pGeoDefEdit = (IGeometryDefEdit)pGeoDef;
pGeoDefEdit.GeometryType_2 = esriGeometryType.esriGeometryPoint;
pGeoDefEdit.SpatialReference_2 = pSpatialReference;
pFieldEdit.GeometryDef_2 = pGeoDef;
pFieldsEdit.AddField(pField);
pField = new FieldClass();
pFieldEdit = (IFieldEdit)pField;
pFieldEdit.Name_2 = "cluNum";
pFieldEdit.Type_2 = esriFieldType.esriFieldTypeInteger;
pFieldsEdit.AddField(pField);
clusteringClass = pFeatureWorkspace.CreateFeatureClass("cluster" + pFeatureLayer.Name + ".shp", pFields, null, null, esriFeatureType.esriFTSimple, "SHAPE", "");
}
IFeatureCursor cursor = pfeatureClass.Search(null, true);
IFeature pFeature = cursor.NextFeature();
List<IPoint> pointsColl = new List<IPoint>();
while (pFeature != null)
{
//Console.WriteLine((pFeature.Shape as IPoint).X);
IPoint pt = new PointClass();
pt.X = (pFeature.Shape as IPoint).X;
pt.Y = (pFeature.Shape as IPoint).Y;
pointsColl.Add(pt);
pFeature = cursor.NextFeature();
}
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
IFeatureCursor insertCursor = clusteringClass.Insert(true);
foreach(var list in lists){
double aveX = list.Average(i => pointsColl[i].X);
double aveY = list.Average(i => pointsColl[i].Y);
IPoint pt = new PointClass();
pt.X = aveX;
pt.Y = aveY;
IFeatureBuffer pAddFeature = clusteringClass.CreateFeatureBuffer();
pAddFeature.Shape = pt;
pAddFeature.set_Value(pAddFeature.Fields.FindField("cluNum"), list.Count);
insertCursor.InsertFeature(pAddFeature);
};
insertCursor.Flush();
IFeatureLayer pCluLayer = new FeatureLayerClass();
pCluLayer.Name = "cluster" + pFeatureLayer.Name;
pCluLayer.FeatureClass = clusteringClass;
stopwatch.Stop();
Console.WriteLine("加入:" + stopwatch.Elapsed.TotalMilliseconds);
return pCluLayer;
}
聚类图层分级显示
public void ProportionalRenderer(IFeatureLayer featLayer, string fieldName, IColor pColor, double count)
{
IProportionalSymbolRenderer psrender = new ProportionalSymbolRendererClass();
psrender.Field = fieldName;
psrender.ValueUnit = esriUnits.esriUnknownUnits;
psrender.ValueRepresentation = esriValueRepresentations.esriValueRepUnknown;
//选择渲染的样式,与颜色 minsymbol为比填内容,否则没有效果
ISimpleMarkerSymbol markersym = new SimpleMarkerSymbol();
markersym.Size = count;
markersym.Style = esriSimpleMarkerStyle.esriSMSCircle;
markersym.Color = pColor;
psrender.MinSymbol = markersym as ISymbol;
//IFeatureLayer featLayer = featLayer;
IGeoFeatureLayer geofeat = featLayer as IGeoFeatureLayer;
ICursor cursor = ((ITable)featLayer).Search(null, true);
IDataStatistics datastat = new DataStatisticsClass();
datastat.Cursor = cursor;
datastat.Field = fieldName;//千万不能忽视
IStatisticsResults statisticsResult;
try
{
statisticsResult = datastat.Statistics;
psrender.MinDataValue = statisticsResult.Minimum + 0.1;
psrender.MaxDataValue = statisticsResult.Maximum;
设置background的样式
IFillSymbol fillsym = new SimpleFillSymbolClass();
fillsym.Color = getcolor(201, 201, 251);
ILineSymbol linesym = new SimpleLineSymbolClass();
linesym.Width = 2;
fillsym.Outline = linesym;
psrender.BackgroundSymbol = fillsym;
psrender.LegendSymbolCount = 2;//legend的数量
psrender.CreateLegendSymbols();//创建TOC的legend
geofeat.Renderer = (IFeatureRenderer)psrender;
}
catch
{
MessageBox.Show("错误,选择的属性不是数值型!");
}
}
功能实现效果图
资源下载