第08章-标注与标签图层
第08章:标注与标签图层
8.1 LabelLayer 基础
8.1.1 创建标注图层
using SharpMap.Layers;
using SharpMap.Data.Providers;
using SharpMap.Styles;
// 创建标注图层
var labelLayer = new LabelLayer("城市标注");
// 设置数据源(通常与矢量图层共享)
labelLayer.DataSource = cityLayer.DataSource;
// 设置标注字段
labelLayer.LabelColumn = "CITY_NAME";
// 设置样式
labelLayer.Style = new LabelStyle
{
Font = new Font("Microsoft YaHei", 10, FontStyle.Bold),
ForeColor = Color.Black,
CollisionDetection = true
};
// 启用
labelLayer.Enabled = true;
// 添加到地图
map.Layers.Add(labelLayer);
8.1.2 标注字段配置
// 使用单个字段
labelLayer.LabelColumn = "NAME";
// 使用委托组合多个字段
labelLayer.LabelStringDelegate = (row) =>
{
string name = row["NAME"]?.ToString() ?? "";
string code = row["CODE"]?.ToString() ?? "";
return $"{name}\n({code})";
};
// 根据条件显示不同内容
labelLayer.LabelStringDelegate = (row) =>
{
double population = Convert.ToDouble(row["POPULATION"] ?? 0);
string name = row["NAME"]?.ToString() ?? "";
if (population > 1000000)
{
return $"{name}\n人口: {population / 10000:N0}万";
}
return name;
};
// 格式化数值
labelLayer.LabelStringDelegate = (row) =>
{
string name = row["NAME"]?.ToString() ?? "";
double area = Convert.ToDouble(row["AREA"] ?? 0);
return $"{name}\n{area:N2} km²";
};
8.2 标注样式详解
8.2.1 字体设置
// 基本字体
labelLayer.Style.Font = new Font("Arial", 12);
// 粗体
labelLayer.Style.Font = new Font("Arial", 12, FontStyle.Bold);
// 斜体
labelLayer.Style.Font = new Font("Arial", 12, FontStyle.Italic);
// 组合样式
labelLayer.Style.Font = new Font("Arial", 12, FontStyle.Bold | FontStyle.Italic);
// 中文字体
labelLayer.Style.Font = new Font("Microsoft YaHei", 11, FontStyle.Regular);
labelLayer.Style.Font = new Font("SimSun", 10, FontStyle.Regular);
labelLayer.Style.Font = new Font("KaiTi", 12, FontStyle.Regular);
8.2.2 颜色和背景
// 前景色
labelLayer.Style.ForeColor = Color.Black;
labelLayer.Style.ForeColor = Color.FromArgb(50, 50, 50);
// 背景色(标注框)
labelLayer.Style.BackColor = new SolidBrush(Color.White);
labelLayer.Style.BackColor = new SolidBrush(Color.FromArgb(200, 255, 255, 200));
// 透明背景
labelLayer.Style.BackColor = null;
// 光晕效果(描边)
labelLayer.Style.Halo = new Pen(Color.White, 2);
labelLayer.Style.Halo = new Pen(Color.FromArgb(200, 255, 255, 255), 3);
8.2.3 对齐和偏移
// 水平对齐
labelLayer.Style.HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Left;
labelLayer.Style.HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Center;
labelLayer.Style.HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Right;
// 垂直对齐
labelLayer.Style.VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Top;
labelLayer.Style.VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Middle;
labelLayer.Style.VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Bottom;
// 偏移(像素)
labelLayer.Style.Offset = new PointF(0, -15); // 向上偏移
labelLayer.Style.Offset = new PointF(10, 0); // 向右偏移
labelLayer.Style.Offset = new PointF(5, -10); // 右上偏移
// 常用组合:点符号上方显示标注
labelLayer.Style.HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Center;
labelLayer.Style.VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Bottom;
labelLayer.Style.Offset = new PointF(0, -12);
8.3 碰撞检测
8.3.1 基本碰撞检测
// 启用碰撞检测
labelLayer.Style.CollisionDetection = true;
// 设置碰撞缓冲区
labelLayer.Style.CollisionBuffer = new SizeF(5, 5); // 5 像素的缓冲
labelLayer.Style.CollisionBuffer = new SizeF(10, 3); // 水平 10,垂直 3
8.3.2 优先级控制
// 使用属性字段作为优先级
labelLayer.PriorityColumn = "PRIORITY";
// 使用人口作为优先级(人口多的优先显示)
labelLayer.PriorityColumn = "POPULATION";
// 使用委托计算优先级
labelLayer.GetPriorityDelegate = (row) =>
{
// 首都最高优先级
if (row["IS_CAPITAL"]?.ToString() == "Y")
return 100;
// 省会次之
if (row["IS_PROVINCIAL"]?.ToString() == "Y")
return 80;
// 根据人口计算优先级
double population = Convert.ToDouble(row["POPULATION"] ?? 0);
return (int)(population / 100000);
};
8.3.3 多点位置尝试
// 自定义标注位置选择器
public class MultiPositionLabelLayer : LabelLayer
{
private readonly PointF[] _positionOffsets = new[]
{
new PointF(0, -15), // 上方
new PointF(15, 0), // 右侧
new PointF(0, 15), // 下方
new PointF(-15, 0), // 左侧
new PointF(10, -10), // 右上
new PointF(10, 10), // 右下
new PointF(-10, -10), // 左上
new PointF(-10, 10) // 左下
};
// 实现多位置尝试的渲染逻辑
// ...
}
8.4 标注旋转
8.4.1 使用属性字段旋转
// 使用角度字段
labelLayer.RotationColumn = "ANGLE";
// 使用方位角字段
labelLayer.RotationColumn = "BEARING";
8.4.2 动态计算旋转角度
// 根据线的方向旋转
labelLayer.GetRotationDelegate = (row) =>
{
if (row.Geometry is LineString line && line.NumPoints >= 2)
{
// 获取线的起点和终点
var start = line.GetPointN(0).Coordinate;
var end = line.GetPointN(line.NumPoints - 1).Coordinate;
// 计算角度
double angle = Math.Atan2(end.Y - start.Y, end.X - start.X);
double degrees = angle * 180 / Math.PI;
// 调整角度确保文字不会上下颠倒
if (degrees > 90 || degrees < -90)
{
degrees += 180;
}
return (float)degrees;
}
return 0;
};
8.4.3 沿线标注
// 道路标注配置
var roadLabelLayer = new LabelLayer("Road Labels");
roadLabelLayer.DataSource = roadLayer.DataSource;
roadLabelLayer.LabelColumn = "ROAD_NAME";
roadLabelLayer.Style = new LabelStyle
{
Font = new Font("Arial", 9),
ForeColor = Color.FromArgb(100, 100, 100),
Halo = new Pen(Color.FromArgb(230, 255, 255, 255), 2),
HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Center,
VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Middle,
CollisionDetection = true,
CollisionBuffer = new SizeF(20, 5)
};
// 沿线显示
roadLabelLayer.GetRotationDelegate = (row) =>
{
if (row.Geometry is LineString line && line.Length > 0)
{
// 计算线中点处的方向
var midPoint = line.GetPointN(line.NumPoints / 2);
var nextPoint = line.GetPointN(Math.Min(line.NumPoints / 2 + 1, line.NumPoints - 1));
double dx = nextPoint.X - midPoint.X;
double dy = nextPoint.Y - midPoint.Y;
double angle = Math.Atan2(dy, dx) * 180 / Math.PI;
// 确保文字不会上下颠倒
if (angle > 90) angle -= 180;
if (angle < -90) angle += 180;
return (float)angle;
}
return 0;
};
8.5 多部分几何的标注
8.5.1 标注行为设置
// 标注所有部分
labelLayer.MultipartGeometryBehaviour =
LabelLayer.MultipartGeometryBehaviourEnum.All;
// 只标注第一个部分
labelLayer.MultipartGeometryBehaviour =
LabelLayer.MultipartGeometryBehaviourEnum.First;
// 标注最大的部分
labelLayer.MultipartGeometryBehaviour =
LabelLayer.MultipartGeometryBehaviourEnum.Largest;
// 标注公共中心
labelLayer.MultipartGeometryBehaviour =
LabelLayer.MultipartGeometryBehaviourEnum.CommonCenter;
8.5.2 多边形标注位置
// 使用质心
// 默认行为:使用几何的质心作为标注位置
// 自定义标注位置
labelLayer.GetLabelPointDelegate = (row) =>
{
var geometry = row.Geometry;
if (geometry is Polygon polygon)
{
// 使用内点(确保点在多边形内部)
return polygon.InteriorPoint;
}
// 默认使用质心
return geometry.Centroid;
};
8.6 标注过滤
8.6.1 比例尺过滤
// 设置可见比例尺范围
labelLayer.MinVisible = 1000; // 最小可见比例尺
labelLayer.MaxVisible = 500000; // 最大可见比例尺
// 大比例尺显示详细标注
var detailLabelLayer = new LabelLayer("Detail Labels");
detailLabelLayer.MinVisible = 0;
detailLabelLayer.MaxVisible = 10000;
// 小比例尺显示概要标注
var summaryLabelLayer = new LabelLayer("Summary Labels");
summaryLabelLayer.MinVisible = 10000;
summaryLabelLayer.MaxVisible = 1000000;
8.6.2 属性过滤
// 使用定义查询过滤
if (labelLayer.DataSource is ShapeFile shp)
{
// ShapeFile 不直接支持定义查询
// 需要使用其他方式
}
// 使用标注委托过滤
labelLayer.LabelStringDelegate = (row) =>
{
// 只标注人口超过 10 万的城市
double population = Convert.ToDouble(row["POPULATION"] ?? 0);
if (population < 100000)
return null; // 返回 null 不显示标注
return row["NAME"]?.ToString();
};
8.6.3 长度过滤
// 设置线的最小长度(用于道路标注)
labelLayer.Style.IgnoreLength = false; // 启用长度检查
// 自定义长度检查
labelLayer.LabelStringDelegate = (row) =>
{
if (row.Geometry is LineString line)
{
// 线太短时不显示标注
if (line.Length < 0.01) // 根据坐标系调整
return null;
}
return row["NAME"]?.ToString();
};
8.7 标注主题
8.7.1 基于属性的标注样式
public class LabelTheme : ITheme
{
private readonly string _typeColumn;
private readonly Dictionary<string, LabelStyle> _styles;
public LabelTheme(string typeColumn)
{
_typeColumn = typeColumn;
_styles = new Dictionary<string, LabelStyle>();
}
public void AddStyle(string type, LabelStyle style)
{
_styles[type] = style;
}
public IStyle GetStyle(FeatureDataRow row)
{
string type = row[_typeColumn]?.ToString();
if (!string.IsNullOrEmpty(type) && _styles.ContainsKey(type))
{
return _styles[type];
}
return new LabelStyle
{
Font = new Font("Arial", 10),
ForeColor = Color.Black
};
}
}
// 使用
var labelTheme = new LabelTheme("CITY_TYPE");
labelTheme.AddStyle("Capital", new LabelStyle
{
Font = new Font("Arial", 14, FontStyle.Bold),
ForeColor = Color.Red,
Halo = new Pen(Color.White, 3)
});
labelTheme.AddStyle("Provincial", new LabelStyle
{
Font = new Font("Arial", 12, FontStyle.Bold),
ForeColor = Color.DarkBlue,
Halo = new Pen(Color.White, 2)
});
labelTheme.AddStyle("City", new LabelStyle
{
Font = new Font("Arial", 10),
ForeColor = Color.Black,
Halo = new Pen(Color.White, 1)
});
labelLayer.Theme = labelTheme;
8.7.2 基于数值的动态字号
public class DynamicSizeLabelTheme : ITheme
{
private readonly string _sizeColumn;
private readonly double _minValue;
private readonly double _maxValue;
private readonly float _minSize;
private readonly float _maxSize;
public DynamicSizeLabelTheme(string sizeColumn, double minValue, double maxValue,
float minSize, float maxSize)
{
_sizeColumn = sizeColumn;
_minValue = minValue;
_maxValue = maxValue;
_minSize = minSize;
_maxSize = maxSize;
}
public IStyle GetStyle(FeatureDataRow row)
{
double value = Convert.ToDouble(row[_sizeColumn] ?? _minValue);
// 计算字号
double ratio = (value - _minValue) / (_maxValue - _minValue);
ratio = Math.Max(0, Math.Min(1, ratio));
float fontSize = _minSize + (float)((_maxSize - _minSize) * ratio);
return new LabelStyle
{
Font = new Font("Arial", fontSize, FontStyle.Bold),
ForeColor = Color.Black,
Halo = new Pen(Color.White, Math.Max(1, fontSize / 6)),
HorizontalAlignment = LabelStyle.HorizontalAlignmentEnum.Center,
VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Middle,
CollisionDetection = true
};
}
}
// 使用:根据人口大小调整字号
labelLayer.Theme = new DynamicSizeLabelTheme("POPULATION",
100000, // 最小人口
10000000, // 最大人口
8, // 最小字号
18); // 最大字号
8.8 性能优化
8.8.1 减少标注数量
// 1. 使用比例尺过滤
labelLayer.MinVisible = 5000;
labelLayer.MaxVisible = 100000;
// 2. 使用优先级减少标注
labelLayer.PriorityColumn = "IMPORTANCE";
labelLayer.Style.CollisionDetection = true;
// 3. 使用属性过滤
labelLayer.LabelStringDelegate = (row) =>
{
int importance = Convert.ToInt32(row["IMPORTANCE"] ?? 0);
if (importance < 5)
return null;
return row["NAME"]?.ToString();
};
8.8.2 简化碰撞检测
// 使用较小的碰撞缓冲区
labelLayer.Style.CollisionBuffer = new SizeF(2, 2);
// 禁用碰撞检测(谨慎使用)
labelLayer.Style.CollisionDetection = false;
8.8.3 缓存标注结果
public class CachedLabelLayer : LabelLayer
{
private Image _cachedLabels;
private Envelope _cachedEnvelope;
private double _cachedZoom;
public override void Render(Graphics g, Map map)
{
// 检查缓存是否有效
if (_cachedLabels != null &&
_cachedEnvelope.Equals(map.Envelope) &&
Math.Abs(_cachedZoom - map.Zoom) < 0.001)
{
// 使用缓存
g.DrawImage(_cachedLabels, 0, 0);
return;
}
// 创建新的缓存
_cachedLabels = new Bitmap(map.Size.Width, map.Size.Height);
using (var tempG = Graphics.FromImage(_cachedLabels))
{
tempG.Clear(Color.Transparent);
base.Render(tempG, map);
}
_cachedEnvelope = map.Envelope;
_cachedZoom = map.Zoom;
g.DrawImage(_cachedLabels, 0, 0);
}
public void InvalidateCache()
{
_cachedLabels?.Dispose();
_cachedLabels = null;
}
}
8.9 本章小结
本章详细介绍了 SharpMap 的标注图层系统:
- LabelLayer 基础:了解了标注图层的创建和配置
- 标注样式:掌握了字体、颜色、对齐等样式设置
- 碰撞检测:学习了避免标注重叠的配置方法
- 标注旋转:了解了沿线标注和动态旋转
- 多部分几何:掌握了复杂几何的标注处理
- 标注过滤:学习了基于比例尺和属性的过滤
- 标注主题:了解了动态标注样式的实现
- 性能优化:掌握了标注性能优化技巧
8.10 参考资源
下一章预告:第09章将详细介绍瓦片图层与在线地图的集成,包括 BruTile、WMS 等服务的使用。

浙公网安备 33010602011771号