本控件使用dotnet编写,基于WPF的数据绑定自动生成,可以用于展示瓦片地图。为了提高地图加载速度,我们使用了内存缓存和本地文件缓存技术,并采用从中心扩散异步等加载方式。这些技术的结合,使得地图的加载更加流畅。
你可以在Nuget上搜索xyxandwxx.MapControl
直接引用该控件
瓦片的数据结构,其中X、Y、Level是瓦片本身的属性,用于获取到瓦片图的路径Url,后面的高度Height、宽度Width就是显示为图像控件的大小,偏移量LayerOffsetPixelX与LayerOffsetPixelY就是基于地图左上角的瓦片,在右边第几个LayerOffsetPixelX就是几倍的
Width,在下面第几个LayerOffsetPixelY就是几倍的Height。
public class TitleBlock : INotifyPropertyChanged
{
///
/// X
///
public int TitleX { get; set; }
///
/// Y
///
public int TitleY { get; set; }
///
/// 所在的地图等级
///
public int Level { get; set; }
///
/// 地址
///
public Uri Url { get; set; }
///
/// 宽度
///
public double Width { get; set; } = 256;
///
/// 高度
///
public double Height { get; set; } = 256;
///
/// 绘制的时候的偏移量
///
public double LayerOffsetPixelX { get; set; }
///
/// 绘制的时候的偏移量
///
public double LayerOffsetPixelY { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
}
///
/// 基础的地图布局类
///
public abstract class BaseMapLayout : INotifyPropertyChanged
{
///
/// 当前地图的显示层级
///
public virtual int Level { get; set; }
///
/// 瓦片地图宽度
///
public virtual double MapTitleWidth { get; set; } = 256;
///
/// 最大和最小的显示等级
///
public abstract int MinLevel { get; set; }
public abstract int MaxLevel { get; set; }
///
/// 当前的行数
///
public int Rows { get; protected set; }
///
/// 当前的列数
///
public int Cols { get; protected set; }
///
/// 总的像素宽度
///
public double PixelWidth { get; set; }
///
/// 总的像素高度
///
public double PixelHeight { get; set; }
///
/// 修改的次数
///
public int ModifyCount { get; set; } = 0;
///
/// 当前的偏移box
///
public TitleBlock OffsetTitleBox { get; set; }
///
/// 缓存管理
///
internal CacheManager CacheManager { get; set; }
///
/// 当前的block集合
///
public virtual IList TitleBlocks { get; protected set; }
///
/// 当前层一共有多少个区块
///
public virtual int TotalBlock
{
get
{
var t = (int)Math.Pow(Math.Pow(2, Level), 2);
if (t == 0) return 1;
return t;
}
}
}
1.其中Level、MinLevel、MaxLevel是表示地图等级,一般是前端通过鼠标滚轮事件MouseWheel
、触摸屏双指操作更改ManipulationDelta
。
2.其中行数、列数,一般是前端通过按住鼠标移动、触摸屏滑动事件更改,其中触摸事件可以参考创建你的第一个触控应用程序。
这里主要是一些依赖属性
public partial class GisLayout : UserControl
{
#region 扩展属性
public static readonly DependencyProperty BaseMapProperty = DependencyProperty.Register("BaseMap", typeof(BaseMapLayout), typeof(GisLayout));
public static DependencyProperty AreasProperty = DependencyProperty.Register("Areas", typeof(IEnumerable), typeof(GisLayout));
public static DependencyProperty OffsetXProperty = DependencyProperty.Register("OffsetX", typeof(double), typeof(GisLayout));
public static DependencyProperty OffsetYProperty = DependencyProperty.Register("OffsetY", typeof(double), typeof(GisLayout));
public static DependencyProperty FillProperty = DependencyProperty.Register("Fill", typeof(Brush), typeof(GisLayout));
public static DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(GisLayout));
public static DependencyProperty CenterPointProperty = DependencyProperty.Register("CenterPoint", typeof(Point), typeof(GisLayout), new PropertyMetadata(CenterPointCallback));
public static DependencyProperty IsZoomAutoCenterProperty = DependencyProperty.Register("IsZoomAutoCenter", typeof(bool), typeof(GisLayout), new PropertyMetadata(false));
public static DependencyProperty TextFontSizeProperty = DependencyProperty.Register("TextFontSize", typeof(double), typeof(GisLayout), new PropertyMetadata(12.0));
public static DependencyProperty LevelProperty = DependencyProperty.Register("Level", typeof(int), typeof(GisLayout), new PropertyMetadata(new PropertyChangedCallback(OnLevelChanged)));
public static DependencyProperty MaxLevelProperty = DependencyProperty.Register("MaxLevel", typeof(int), typeof(GisLayout));
public static readonly DependencyPropertyKey LevelMinusPropertyKey = DependencyProperty.RegisterReadOnly("LevelMinus", typeof(int), typeof(GisLayout), null);
public static readonly DependencyProperty LevelMinusProperty = LevelMinusPropertyKey.DependencyProperty;
public static DependencyProperty ItemVisibilityProperty = DependencyProperty.Register("ItemVisibility", typeof(Visibility), typeof(GisLayout));
private static void CenterPointCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GisLayout layout = d as GisLayout;
Point p = (Point)e.NewValue;
layout.SetCenter(p.X, p.Y);
}
public static DependencyProperty DragMouseButtonProperty = DependencyProperty.Register("DragMouseButton", typeof(MouseButton), typeof(GisLayout), new PropertyMetadata(MouseButton.Right));
#endregion
///
/// Item的Visibility状态
///
public Visibility ItemVisibility { get => (Visibility)GetValue(ItemVisibilityProperty); set => SetValue(ItemVisibilityProperty, value); }
///
/// 地图最大Level与当前Level的差值
///
public int LevelMinus { get => (int)GetValue(LevelMinusProperty); private set => SetValue(LevelMinusPropertyKey, value); }
///
/// 地图当前Level
///
public int Level { get => (int)GetValue(LevelProperty); set => SetValue(LevelProperty, value); }
///
/// 地图最大Level
///
public int MaxLevel { get => (int)GetValue(MaxLevelProperty); set => SetValue(MaxLevelProperty, value); }
///
/// 文本字体大小
///
public double TextFontSize { get => (double)GetValue(TextFontSizeProperty); set => SetValue(TextFontSizeProperty, value); }
///
/// 设置缩放自动更新中心点
///
public bool IsZoomAutoCenter { get => (bool)GetValue(IsZoomAutoCenterProperty); set => SetValue(IsZoomAutoCenterProperty, value); }
///
/// 地图操作类
///
public BaseMapLayout BaseMap { get => (BaseMapLayout)GetValue(BaseMapProperty); set => SetValue(BaseMapProperty, value); }
///
/// 经纬度区域数据集
///
public IEnumerable Areas { get => (IEnumerable)GetValue(AreasProperty); set => SetValue(AreasProperty, value); }
///
/// 控件偏移坐标
///
public double OffsetX { get => (double)GetValue(OffsetXProperty); set => SetValue(OffsetXProperty, value); }
///
/// 控件拖拽偏移坐标
///
public double OffsetY { get => (double)GetValue(OffsetYProperty); set => SetValue(OffsetYProperty, value); }
///
/// 经纬度遮罩填充颜色
///
public Brush Fill { get => (Brush)GetValue(FillProperty); set => SetValue(FillProperty, value); }
///
/// 中心经纬度
///
public Point CenterPoint { get => (Point)GetValue(CenterPointProperty); set => SetValue(CenterPointProperty, value); }
///
/// 拖拽鼠标的按键
///
public MouseButton DragMouseButton { get => (MouseButton)GetValue(DragMouseButtonProperty); set => SetValue(DragMouseButtonProperty, value); }
///
/// 子项模板
///
public DataTemplate ItemTemplate { get => (DataTemplate)GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); }
///
/// 构造函数
///
public GisLayout()
{
InitializeComponent();
this.Loaded += GisLayout_Loaded;
}
private void GisLayout_Loaded(object sender, RoutedEventArgs e)
{
SetCenter(CenterPoint.X, CenterPoint.Y);
}
public event PropertyChangedEventHandler PropertyChanged;
private static void OnLevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int maxLevel = (int)d.GetValue(MaxLevelProperty);
int level = (int)d.GetValue(LevelProperty);
d.SetValue(LevelMinusPropertyKey, maxLevel - level);
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
SetCenter(CenterPoint.X, CenterPoint.Y);
}
///
/// 经纬度坐标转当前地图的像素坐标
///
/// 经纬度坐标
/// 像素坐标
private Point ConvertToPixelPoint(Point mapPoint)
{
if (BaseMap is null) return new Point();
PixelTitleBlock block = BaseMap.LongitudeAndAtitudeConvertToPixel(mapPoint.X, mapPoint.Y);
var centerX = (block.TitleX - BaseMap.OffsetTitleBox.TitleX) * block.Width + block.OffsetX;
var centerY = (block.TitleY - BaseMap.OffsetTitleBox.TitleY) * block.Height + block.OffsetY;
return new Point(centerX, centerY);
}
///
/// 设置居中 在设置的经纬度
///
/// 维度
/// 经度
public void SetCenter(double lng, double lat)
{
if (BaseMap is null) return;
var center = ConvertToPixelPoint(new Point(lng, lat));
var offsetx = RenderSize.Width / 2 - center.X;
var offsety = RenderSize.Height / 2 - center.Y;
var clip = OffsetClip(new Point(offsetx, offsety));
OffsetX = clip.X;
OffsetY = clip.Y;
}
#region 鼠标控制
///
/// 前一个坐标
///
private Point ForntPoint { get; set; }
///
/// 是否移动
///
private bool IsMove { get; set; }
///
/// 是否是多指操作
///
private bool IsManipulationOption = false;
#region 移动控制
///
/// 偏移裁剪过滤 防止超出边界
///
/// 目标偏移
/// 实际偏移
private Point OffsetClip(Point target)
{
var tempX = target.X;
var tempY = target.Y;
if (tempX > 0)
{
tempX = 0;
}
else if (tempX <= -BaseMap.PixelWidth + this.RenderSize.Width)
{
tempX = -BaseMap.PixelWidth + this.RenderSize.Width;
}
if (tempY > 0)
{
tempY = 0;
}
else if (tempY <= -BaseMap.PixelHeight + this.RenderSize.Height)
{
tempY = -BaseMap.PixelHeight + this.RenderSize.Height;
}
return new Point(tempX, tempY);
}
///
/// 计算2个点的距离
///
///
///
///
private double GetDistance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
}
///
/// 开始移动
///
///
private void StartMove(Point point)
{
IsMove = true;
ForntPoint = point;
}
///
/// 停止移动
///
private void StopMove()
{
IsMove = false;
}
///
/// 使用新的点更新移动
///
///
private void UpdateMove(Func getCurrentPoint)
{
if (!IsMove || IsManipulationOption) return;
Point now = getCurrentPoint();
var x = now.X - ForntPoint.X;
var y = now.Y - ForntPoint.Y;
var dis = Math.Sqrt(x * x + y * y);
if (dis < 3)
{
return;
}
if (dis > 200)
{
return;
}
var tempX = OffsetX + x;
var tempY = OffsetY + y;
var clip = OffsetClip(new Point(tempX, tempY));
OffsetX = clip.X;
OffsetY = clip.Y;
ForntPoint = now;
}
///
/// 使用新的点更新移动
///
///
private void UpdateMove(Vector offset)
{
var x = offset.X;
var y = offset.Y;
var dis = Math.Sqrt(x * x + y * y);
if(dis < 2)
{
return;
}
if (dis > 200)
{
return;
}
var tempX = OffsetX + x;
var tempY = OffsetY + y;
var clip = OffsetClip(new Point(tempX, tempY));
OffsetX = clip.X;
OffsetY = clip.Y;
}
private bool IsZooming = false; // 是否正在缩放
private DateTime ForntTime = DateTime.Now; // 上次操作的事件
private long SpanMiliseconds = 1000; // 间隔的毫秒
///
/// 更新缩放
///
/// 是变大还是缩小
/// 缩放中心点
private async Task UpdateZoom(bool isBigger, Point zoomCenterPoint)
{
if (IsZooming) return;
if ((DateTime.Now - ForntTime).TotalMilliseconds < SpanMiliseconds) return;
IsZooming = true;
var point = zoomCenterPoint;
var lngLat = BaseMap.PixelConvertToLongitudeAndAtitude(point.X, point.Y);
if (!isBigger)
{
await BaseMap.ResetLayout(lngLat.X, lngLat.Y, BaseMap.Level - 1);
}
else
{
await BaseMap.ResetLayout(lngLat.X, lngLat.Y, BaseMap.Level + 1);
}
SetCenter(lngLat.X, lngLat.Y);
IsZooming = false;
ForntTime = DateTime.Now;
}
#endregion
#region 鼠标控制的移动
private void MMove(object sender, MouseEventArgs e)
{
UpdateMove(() => e.GetPosition(MapControlGrid));
}
private void MDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == DragMouseButton)
{
StartMove(e.GetPosition(MapControlGrid));
}
}
private void MUp(object sender, MouseButtonEventArgs e)
{
StopMove();
}
private void MLeave(object sender, MouseEventArgs e)
{
StopMove();
}
#endregion
#region 鼠标控制的缩放
///
/// 注册了滚轮放大事件
///
///
///
private async void MWheel(object sender, MouseWheelEventArgs e)
{
var res = e.Delta;
var point = e.GetPosition(sender as FrameworkElement);
await UpdateZoom(res > 0, IsZoomAutoCenter ? point : ConvertToPixelPoint(CenterPoint));
}
#endregion
///
/// 双指操作
///
///
///
private async void Grid_ManipulationDeltaAsync(object sender, ManipulationDeltaEventArgs e)
{
if (e.Manipulators.Count() < 2)
{
IsManipulationOption = false;
UpdateMove(e.DeltaManipulation.Translation);
return;
}
var point1 = e.Manipulators.First().GetPosition(MapLayout);
var point2 = e.Manipulators.Last().GetPosition(MapLayout);
var point = new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
IsManipulationOption = true;
var scale = e.DeltaManipulation.Scale;
if (scale.X < 1 && scale.Y < 1)
{
await UpdateZoom(false, IsZoomAutoCenter ? point : ConvertToPixelPoint(CenterPoint));
}
else if (scale.X > 1 && scale.Y > 1)
{
await UpdateZoom(true, IsZoomAutoCenter ? point : ConvertToPixelPoint(CenterPoint));
}
IsManipulationOption = false;
}
///
/// 多指操作开始
///
///
///
private void Grid_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = sender as FrameworkElement;
e.Mode = ManipulationModes.All;
}
///
/// 多指操作结束
///
///
///
private void Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
IsManipulationOption = false;
}
#endregion
}
高德地图的瓦片获取方式,前者lang可以通过zh_cn设置中文,en设置英文,size基本无作用,scl设置标注还是底图,scl=1代表注记,scl=2代表底图(矢量或者影像),style设置影像和路网,style=6为影像图,style=7为矢量路网,style=8为影像路网
public override string GetUri(int row, int column, int level)
{
if (MapImage == MapImageType.RoadNetwork)
return "http://webrd0" + (column % 4 + 1) + ".is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x=" + column + "&y=" + row + "&z=" + level;
else
return "http://webst0" + (column % 4 + 1) + ".is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x=" + column + "&y=" + row + "&z=" + level;
}