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

28 篇文章 3 订阅
4 篇文章 0 订阅

基于瓦片地图的控件

本控件使用dotnet编写,基于WPF的数据绑定自动生成,可以用于展示瓦片地图。为了提高地图加载速度,我们使用了内存缓存和本地文件缓存技术,并采用从中心扩散异步等加载方式。这些技术的结合,使得地图的加载更加流畅。注:通读本文章需要有WPF的基本操作能力。如果不想看具体实现,想直接引用该控件,请直接看本文最下方外部调用地图控件。本控件代码部分参考了GMap.Net,这个更加成熟,网上也有很多教程与示例。

多个瓦片组成地图控件

<UserControl x:Class="MapControl.Gis.GisLayout"
	         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
	         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	         xmlns:local="clr-namespace:MapControl.Gis"
	         mc:Ignorable="d" 
	         d:DesignHeight="450" d:DesignWidth="800" Name="MainControl" Background="#00000000">
	<UserControl.Resources>
	    <ResourceDictionary>
	        <ResourceDictionary.MergedDictionaries>
	            <ResourceDictionary Source="Styles/GisMapGridStyle.xaml"></ResourceDictionary>
	        </ResourceDictionary.MergedDictionaries>
	    </ResourceDictionary>
	</UserControl.Resources>
	<Canvas Background="#00000000" Name="MapControlGrid" 
	        IsManipulationEnabled="True" ManipulationStarting="Grid_ManipulationStarting" 
	        ManipulationDelta="Grid_ManipulationDeltaAsync" 
	        ManipulationCompleted="Grid_ManipulationCompleted"
	        >
	    <Grid Canvas.Left="{Binding OffsetX,ElementName=MainControl}" Canvas.Top="{Binding OffsetY,ElementName=MainControl}"
	                        MouseMove="MMove" MouseDown="MDown" MouseUp="MUp" MouseLeave="MLeave" MouseWheel="MWheel" 
	          >
	        <ItemsControl ItemsSource="{Binding BaseMap.TitleBlocks,ElementName=MainControl}"
	                      ScrollViewer.CanContentScroll="False" Name="MapLayout"
	             Style="{StaticResource MapCanvasImageList}">
	        </ItemsControl>
	        <ItemsControl ItemsSource="{Binding Areas,ElementName=MainControl}"
	                      ScrollViewer.CanContentScroll="False"
	                      Style="{StaticResource MaskListStyle}"
	                      ></ItemsControl>
	    </Grid>
	</Canvas>
	   
</UserControl>

单个瓦片数据结构

瓦片的数据结构,其中X、Y、Level是瓦片本身的属性,用于获取到瓦片图的路径Url,后面的高度Height、宽度Width就是显示为图像控件的大小,偏移量LayerOffsetPixelX与LayerOffsetPixelY就是基于地图左上角的瓦片,在右边第几个LayerOffsetPixelX就是几倍的
Width,在下面第几个LayerOffsetPixelY就是几倍的Height。

public class TitleBlock : INotifyPropertyChanged
{
    /// <summary>
    /// X
    /// </summary>
    public int TitleX { get; set; }
    /// <summary>
    /// Y
    /// </summary>
    public int TitleY { get; set; }
    /// <summary>
    /// 所在的地图等级
    /// </summary>
    public int Level { get; set; }
    /// <summary>
    /// 地址
    /// </summary>
    public Uri Url { get; set; }
    /// <summary>
    /// 宽度
    /// </summary>
    public double Width { get; set; } = 256;
    /// <summary>
    /// 高度
    /// </summary>
    public double Height { get; set; } = 256;
    /// <summary>
    /// 绘制的时候的偏移量
    /// </summary>
    public double LayerOffsetPixelX { get; set; }
    /// <summary>
    /// 绘制的时候的偏移量
    /// </summary>
    public double LayerOffsetPixelY { get; set; }
    public event PropertyChangedEventHandler? PropertyChanged;
}

瓦片的样式

<Style TargetType="ItemsControl" x:Key="MapCanvasImageList">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True" 
                                 Width="{Binding BaseMap.PixelWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}"
                                 Height="{Binding BaseMap.PixelHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}"
                            ></Canvas>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding LayerOffsetPixelX}"></Setter>
                <Setter Property="Canvas.Top" Value="{Binding LayerOffsetPixelY}"></Setter>
            </Style>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <local:LoadingImage Width="{Binding Width}" Height="{Binding Height}" Source="{Binding Url,IsAsync=True}"></local:LoadingImage>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

地图布局的数据结构

/// <summary>
/// 基础的地图布局类
/// </summary>
public abstract class BaseMapLayout : INotifyPropertyChanged
{
    /// <summary>
    /// 当前地图的显示层级
    /// </summary>
    public virtual int Level { get; set; }
    /// <summary>
    /// 瓦片地图宽度
    /// </summary>
    public virtual double MapTitleWidth { get; set; } = 256;
    /// <summary>
    /// 最大和最小的显示等级
    /// </summary>
    public abstract int MinLevel { get; set; }
    public abstract int MaxLevel { get; set; }
    /// <summary>
    /// 当前的行数
    /// </summary>
    public int Rows { get; protected set; }
    /// <summary>
    /// 当前的列数
    /// </summary>
    public int Cols { get; protected set; }
    /// <summary>
    /// 总的像素宽度
    /// </summary>
    public double PixelWidth { get; set; }
    /// <summary>
    /// 总的像素高度
    /// </summary>
    public double PixelHeight { get; set; }
    /// <summary>
    /// 修改的次数
    /// </summary>
    public int ModifyCount { get; set; } = 0;
    /// <summary>
    /// 当前的偏移box
    /// </summary>
    public TitleBlock OffsetTitleBox { get; set; }
    /// <summary>
    /// 缓存管理
    /// </summary>
    internal CacheManager CacheManager { get; set; }
    /// <summary>
    /// 当前的block集合
    /// </summary>
    public virtual IList<TitleBlock> TitleBlocks { get; protected set; }
    /// <summary>
    /// 当前层一共有多少个区块
    /// </summary>
    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
    /// <summary>
    /// Item的Visibility状态
    /// </summary>
    public Visibility ItemVisibility { get => (Visibility)GetValue(ItemVisibilityProperty); set => SetValue(ItemVisibilityProperty, value); }
    /// <summary>
    /// 地图最大Level与当前Level的差值
    /// </summary>
    public int LevelMinus { get => (int)GetValue(LevelMinusProperty); private set => SetValue(LevelMinusPropertyKey, value); }
    /// <summary>
    /// 地图当前Level
    /// </summary>
    public int Level { get => (int)GetValue(LevelProperty); set => SetValue(LevelProperty, value); }
    /// <summary>
    /// 地图最大Level
    /// </summary>
    public int MaxLevel { get => (int)GetValue(MaxLevelProperty); set => SetValue(MaxLevelProperty, value); }
    /// <summary>
    /// 文本字体大小
    /// </summary>
    public double TextFontSize { get => (double)GetValue(TextFontSizeProperty); set => SetValue(TextFontSizeProperty, value); }
    /// <summary>
    /// 设置缩放自动更新中心点
    /// </summary>
    public bool IsZoomAutoCenter { get => (bool)GetValue(IsZoomAutoCenterProperty); set => SetValue(IsZoomAutoCenterProperty, value); }
    /// <summary>
    /// 地图操作类
    /// </summary>
    public BaseMapLayout BaseMap { get => (BaseMapLayout)GetValue(BaseMapProperty); set => SetValue(BaseMapProperty, value); }
    /// <summary>
    /// 经纬度区域数据集
    /// </summary>
    public IEnumerable Areas { get => (IEnumerable)GetValue(AreasProperty); set => SetValue(AreasProperty, value); }
    /// <summary>
    /// 控件偏移坐标
    /// </summary>
    public double OffsetX { get => (double)GetValue(OffsetXProperty); set => SetValue(OffsetXProperty, value); }
    /// <summary>
    /// 控件拖拽偏移坐标
    /// </summary>
    public double OffsetY { get => (double)GetValue(OffsetYProperty); set => SetValue(OffsetYProperty, value); }
    /// <summary>
    /// 经纬度遮罩填充颜色
    /// </summary>
    public Brush Fill { get => (Brush)GetValue(FillProperty); set => SetValue(FillProperty, value); }
    /// <summary>
    /// 中心经纬度
    /// </summary>
    public Point CenterPoint { get => (Point)GetValue(CenterPointProperty); set => SetValue(CenterPointProperty, value); }
    /// <summary>
    /// 拖拽鼠标的按键
    /// </summary>
    public MouseButton DragMouseButton { get => (MouseButton)GetValue(DragMouseButtonProperty); set => SetValue(DragMouseButtonProperty, value); }
    /// <summary>
    /// 子项模板
    /// </summary>
    public DataTemplate ItemTemplate { get => (DataTemplate)GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); }
    /// <summary>
    /// 构造函数
    /// </summary>
    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);
    }
    /// <summary>
    /// 经纬度坐标转当前地图的像素坐标
    /// </summary>
    /// <param name="mapPoint">经纬度坐标</param>
    /// <returns>像素坐标</returns>
    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);
    }
    /// <summary>
    /// 设置居中 在设置的经纬度
    /// </summary>
    /// <param name="lat">维度</param>
    /// <param name="lng">经度</param>
    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 鼠标控制
    /// <summary>
    /// 前一个坐标
    /// </summary>
    private Point ForntPoint { get; set; }
    /// <summary>
    /// 是否移动
    /// </summary>
    private bool IsMove { get; set; }
    /// <summary>
    /// 是否是多指操作
    /// </summary>
    private bool IsManipulationOption = false;
    #region 移动控制
    /// <summary>
    /// 偏移裁剪过滤 防止超出边界
    /// </summary>
    /// <param name="target">目标偏移</param>
    /// <returns>实际偏移</returns>
    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);
    }
    /// <summary>
    /// 计算2个点的距离
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    private double GetDistance(Point p1, Point p2)
    {
        return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
    }
    /// <summary>
    /// 开始移动
    /// </summary>
    /// <param name="point"></param>
    private void StartMove(Point point)
    {
        IsMove = true;
        ForntPoint = point;
    }
    /// <summary>
    /// 停止移动
    /// </summary>
    private void StopMove()
    {
        IsMove = false;
    }
    /// <summary>
    /// 使用新的点更新移动
    /// </summary>
    /// <param name="point"></param>
    private void UpdateMove(Func<Point> 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;
    }

    /// <summary>
    /// 使用新的点更新移动
    /// </summary>
    /// <param name="point"></param>
    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;  // 间隔的毫秒
    /// <summary>
    /// 更新缩放
    /// </summary>
    /// <param name="isBigger">是变大还是缩小</param>
    /// <param name="zoomCenterPoint">缩放中心点</param>
    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 鼠标控制的缩放
    /// <summary>
    /// 注册了滚轮放大事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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
    /// <summary>
    /// 双指操作
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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;
    }

    /// <summary>
    /// 多指操作开始
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Grid_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
    {
        e.ManipulationContainer = sender as FrameworkElement;
        e.Mode = ManipulationModes.All;
    }
    /// <summary>
    /// 多指操作结束
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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;
}

外部调用地图控件

本系列其他地方都是写具体如何实现MapControl的,如果不需要知道怎么实现,可以直接看这里。具体如何引用分两步:

1.调用地图控件

首先在Nuget上搜索xyxandwxx.MapControl直接引用该控件。
最上方xmlns:map="clr-namespace:MapControl.Gis;assembly=MapControl"
这个控件的BaseMap是必须有值的。Areas只是地图上的区域,为空的话,就是纯地图。GisLayout.ItemTemplate这个只是为了给Areas服务的,如果不需要可以不写。

<map:GisLayout CenterPoint="{Binding CenterPoint,ElementName=main}" BaseMap="{Binding MapLayout}" 
			   Areas="{Binding ElementName=main,Path=Areas}" Fill="#40FF0000" 
			   Level="{Binding MapLayout.Level,Mode=OneWay}" MaxLevel="{Binding MapLayout.Level,Mode=OneWay}">
    <map:GisLayout.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" Foreground="Black"  HorizontalAlignment="Center" VerticalAlignment="Center" IsHitTestVisible="False"/>
        </DataTemplate>
    </map:GisLayout.ItemTemplate>
</map:GisLayout>

2.后台调用

属性

/// <summary>
/// 地图布局
/// </summary>
public GAODEMapLayout MapLayout { get; set; } = new GAODEMapLayout(MapImageType.SatelliteMap, 3) { MaxVisibleRow = 8, MaxVisibleCol = 8};
/// <summary>
/// 区域
/// </summary>
public ObservableCollection<MyArea> Areas { get; set; } = new ObservableCollection<MyArea>();
/// <summary>
/// 中心点
/// </summary>
public Point CenterPoint { get; set; }

初始化

await MapLayout.ResetLayout(centerx, centery, 16).ConfigureAwait(false);

上面这段就够用了。
如果再需要Area区域,必须要继承Area类。

public class MyArea : Area
{
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
}

测试数据

var json = "[[\"119.687271502018,30.62019139731845\",\"119.68858042001727,30.620136000293304\",\"119.68757190942767,30.61841867678973\",\"119.68630590677265,30.619101916671443\"],[\"119.6858918273449,30.617915707592687\",\"119.68621369242669,30.616900066553676\",\"119.68559141993524,30.615755149334817\",\"119.68479748606677,30.61717706062098\",\"119.68520518183709,30.61795263979333\"],[\"119.68575797706842,30.621210679611796\",\"119.68561850219965,30.620305867091762\",\"119.68682549625635,30.620550536617653\"],[\"119.68393407493829,30.62037049683795\",\"119.68405745655298,30.61941489547886\",\"119.68484066158533,30.61953953966933\",\"119.68482993274927,30.620282785029087\",\"119.68443296581506,30.619798060441703\",\"119.68439541488885,30.61986269052691\"],[\"119.68398507982494,30.621021126705532\",\"119.68598064333202,30.620688746928053\",\"119.68470927894117,30.62033790036922\",\"119.68459126174452,30.620360982418738\"],[\"119.68458053424956,30.62145506294481\",\"119.68469318702819,30.622165979339748\",\"119.68638297870757,30.621893615883355\",\"119.68625423267486,30.621298106673486\",\"119.68615230873229,30.621293490308712\"]]";
var datas = Newtonsoft.Json.JsonConvert.DeserializeObject<List<List<Point>>>(json);

var allPs = datas.SelectMany(p => p);
var centerx = allPs.Average(p => p.X);
var centery = allPs.Average(p => p.Y);

foreach (var item in datas)
{
    MyArea points = new MyArea();
    var ps = new List<Point>();
    points.Name = "测试区域";
    foreach (var point in item)
    {
        ps.Add(point);
    }
    points.ClickCommand = new RelayCommand(o =>
    {
        MessageBox.Show("点击...");
    });
    points.Points = ps;
    Areas.Add(points);
}

await MapLayout.ResetLayout(centerx, centery, 16).ConfigureAwait(false);

CenterPoint = new(centerx, centery);

界面效果如下图所示
地图控件示例

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值