基于瓦片地图的控件
本控件使用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);
界面效果如下图所示