WPF中手写地图控件(1)——基于瓦片地图的控件

基于瓦片地图的控件

本控件使用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;
}

外部调用地图控件


    
        
    
    
        
            
                
                    
                        
                    
                    
                        
                    
                
                
                
            
        
    

你可能感兴趣的:(C#,WPF,wpf)