# 添加应用数据至 Windows Phone 8.1 地图控件
**[Keith Pijanowski](https://msdn.microsoft.com/zh-cn/magazine/ee532098.aspx?sdmr=KeithPijanowski&sdmi=authors)**
**[下载代码示例](https://msdn.microsoft.com/zh-cn/magazine/msdnmag0115)**
在我一 11 月关于映射功能的 Windows Phone 8.1 的文章,我介绍了 Windows Phone 8.1 地图控件和映射服务 API。我展示了如何使用地图控件添加到电话应用程序,例如,映射功能添加图像来标记一个指定的位置,计算一个指定的地址 (编码) 的地理坐标,确定指定的地理坐标 (反向地理编码)、 地址和提供开车和走路的方向。
这篇文章将演示如何通过将控件添加到地图控件使用 XAML 和应用程序数据绑定到这些控件进入地图控件的应用程序数据。如果您的应用程序将使用脱机,以及,亦可供脱机使用由地图控件使用的基础数据。这使得地图控件以适应您的应用程序的脱机功能。我还会展示如何脱机地图控件并使脱机数据保持最新。
## 将控件添加到使用 XAML 的地图控件
在我上一篇文章中,我用代码来将椭圆控件添加到一张地图,以绕圆圈在地图上的特定位置。这是一种好方法,如果您需要完全控制。然而,如果你想要得到创造性和批注图做了很多不同类型的控件,使用代码可以笨拙。将控件添加到地图使用 XAML 是效率更高。此外,XAML 方法是容易得多时您需要映射集合内使用数据绑定的一张地图的位置。
首先,我将展示如何将控件使用 XAML 添加以及如何将应用程序数据绑定到这些控件。然后我将深入进一步成数据绑定和显示如何数据绑定到该地图控件位置的集合。
**添加基本的 XAML 控件**有两种方式将控件添加到地图控件,以将应用程序数据添加到映射:您可以将控件添加为地图控件的子控件或您可以使用 MapItemsControl 来包含的控件。
**图 1** 演示如何将控件添加为地图控件的子级。因为儿童是地图控件的默认内容属性,它不需要显式指定在 XAML 标记中。**图 2** 显示将控件添加到地图的控制,这使得使用的 MapItemControl 包含已添加的控件的另一种方法。
**图 1 为地图控件的子控件中添加控件**
~~~
<Maps:MapControl
x:Name="myMapControl" Grid.Row="1"
MapServiceToken="{StaticResource MapServiceTokenString}" >
<!-- Progress bar which is used while the page is loading. -->
<ProgressBar Name="pbProgressBar" IsIndeterminate="True" Height="560"
Width="350" />
<TextBlock Name="tbMessage" Text="{Binding Message}"
Maps:MapControl.Location="{Binding Location}"
Maps:MapControl.NormalizedAnchorPoint="1.0,1.0"
FontSize="15" Foreground="Black" FontWeight="SemiBold"
Padding="4,4,4,4"
Visibility="Collapsed"
/>
<Image Name="imgMyLocation" Source="Assets/PinkPushPin.png"
Maps:MapControl.Location="{Binding Location}"
Maps:MapControl.NormalizedAnchorPoint="0.25, 0.9"
Height="30" Width="30"
Visibility="Collapsed" />
</Maps:MapControl>
~~~
**图 2 包含控件内 MapItemControl**
~~~
<Maps:MapControl
x:Name="myMapControl"
MapServiceToken="{StaticResource MapServiceTokenString}">
<Maps:MapItemsControl>
<TextBlock Name="tbAddress" Text="{Binding Message}"
Maps:MapControl.Location="{Binding Location}"
Maps:MapControl.NormalizedAnchorPoint="1.0,1.0"
Visibility="Collapsed" />
<Image Name="imgMyLocation" Source="Assets/PinkPushPin.png"
Maps:MapControl.Location="{Binding Location}"
Maps:MapControl.NormalizedAnchorPoint="0.25, 0.9"
Height="30" Width="30"
Visibility="Collapsed"/>
</Maps:MapItemsControl>
</Maps:MapControl>
~~~
如果您使用中所示的 MapItemControl 技术**图 2**,会意识到你将无法访问您的代码隐藏文件中的控件。智能感知将确认您的控件名称作为有效的变量,但在运行时这些变量将始终为 null,如果你引用它们你会举出。MapItemControl 通常用于包含用于将对象的集合绑定到地图控件的数据模板。(这会将讨论在本文稍后部分。因此,如果你不绑定到的对象的集合,它是更好地将您的控件添加为地图控件的子级中所示**图 1**。
**数据绑定** 中的代码 **图 1** 使用数据绑定来设置图像控件和文本块的位置属性。Text 属性的 TextBlock 还利用数据绑定。要快速查看,位置属性是类型的附加的属性 Geopoint。它用来指定在地图上将放置该控件的位置。NormalizedAnchorPoint 附加属性允许将微调控件的位置。例如,您可以使用 NormalizedAnchorPoint 属性来中心位置的控制权或放置控件左上角的位置上。在我第一篇文章中详细讨论了这两个属性 ([msdn.microsoft.com/magazine/dn818495](https://msdn.microsoft.com/magazine/dn818495))。
数据绑定允许 XAML 控件从底层对象中获取其值。从下面的类创建的对象将用于保存中所示的 XAML 控件所需的值**图 1**:
~~~
public class LocationEntity
{
public Geopoint Location { get; set; }
public string Message { get; set; }
}
~~~
**图 3** 显示完整的 OnNavigatedTo 事件,对于包含在控件的网页**图 1**。(如果某个页面或应用程序中的视图需要大量的安装程序,考虑将此代码放置在创作中一个视图模型,并以异步方式运行)。此代码将设置设备的当前的位置成以及短消息 LocationEntity 类的一个实例。LocationEntity 对象被设置到 DataContext 属性中地图控件的通知。
**图 3 OnNavigatedTo 事件创建对象所需的数据绑定**
~~~
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
// Call the navigation helper base function.
this.navigationHelper.OnNavigatedTo(e);
// Get the user's current location so that it can be
// used as the center point of the map control.
Geolocator geolocator = new Geolocator();
// Set the desired accuracy.
geolocator.DesiredAccuracyInMeters = 200;
// The maximum acceptable age of cached location data.
TimeSpan maxAge = new TimeSpan(0, 2, 0);
// Timeout used for the call to GetGeopositionAsync.
TimeSpan timeout = new TimeSpan(0, 0, 5);
Geoposition geoposition = null;
try
{
geoposition = await geolocator.GetGeopositionAsync(maxAge, timeout);
}
catch (Exception)
{
// Add exception logic.
}
// Set up the LocationEntity object.
LocationEntity locationEntity = new LocationEntity();
locationEntity.Location = geoposition.Coordinate.Point;
locationEntity.Message = "You are here";
// Specify the location that will be at the center of the map.
myMapControl.Center = locationEntity.Location;
// Set the map controls data context so data binding will work.
myMapControl.DataContext = locationEntity;
// Zoom level.
myMapControl.ZoomLevel = 15;
// The map control has done most of its work. Make the controls visible.
imgMyLocation.Visibility = Windows.UI.Xaml.Visibility.Visible;
tbMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
// Collapse the progress bar.
pbProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
~~~
## 几个小贴士
在所示的代码**图 3**,OnNavigatedTo 事件执行耗时的工作。具体而言,这一事件唤起定位器对象的 GetGeopositionAsync 功能。这个函数获取该设备当前的位置,可以在地图上正确放置图像控件和 TextBlock 控件之前,需要。在这种情况下,作为儿童通过 XAML 添加到地图的任何控件最初会显示在地图的左上角,直到他们可以在地图上定位。这将创建差 ux 选项。若要更正此问题,只是设置任何已添加的控件作为在 XAML 中的折叠。可以使添加的控件可见,一旦控制位置的计算和设置,这些控件将绑定的对象。(指**图 1** 和代码中的最后几行**图 3**.)
请考虑使用定位器对象的 DesiredAccuracyInMeters 属性。将此属性设置为 100 米或更少,会得到最准确的资料。如果设备有 GPS 功能,然后将使用 GPS 来确定设备的当前的位置。如果此值大于 100 米,定位器的对象将优化电源,并使用非 GPS 数据,如 Wi-Fi 信号。100 米阈值可以改变随着设备的发展,所以将此属性设置基于需求的应用程序和不底层的阈值,触发不同的行为。最后,DesiredAccuracyInMeters 属性将重写任何定位器的 DesiredAccuracy 属性中设置。
此外,请考虑使用 GetGeopositionAsync,接受一个 maximumAge 参数和一个超时参数的重载。最大年龄参数是指定缓存的位置数据的最大可接受的年龄的时间跨度。超时参数也是时间跨度,并将导致 GetGeopositionAsync 函数抛出异常,如果确定当前所在的位置比指定的时间。
前面的两个技巧将显著地提高性能。然而,在地区与贫穷的互联网连接和低的内存和 Cpu 较慢的设备上,地图控件和映射服务 Api 可能仍然需要时间,导致用户遇到延迟。在这种情况下,地图控件直到进入地图控件中心点属性设置的位置在当前缩放级别会显示全球地图。这也是差的用户体验,因为用户应给予工作正在发生一些可视化的指示。在 XAML 中**图 1**,一个进度栏作为地图控件的子级添加和可见。进度栏是设置为不确定因为精确量所需的时间尚不清楚。一旦地图的中心属性是计算和设置地图控件中,可以折叠进度栏。不确定的进度栏使用的最佳做法表明他们应始终放在页面的顶端,对于大多数情况,我同意。然而,当使用地图控件,我更愿意看到对面地图的中心显示进度栏。地图控件倾向抓住用户的焦点,为此进度栏的页面顶部很可能被忽视。同时,整个地图控件显示一个进度栏告诉用户它是地图控件做的工作。
## 数据绑定集合
将集合绑定到地图控件是类似于将一个集合绑定到一个列表框、 列表视图、 GridView 或显示集合的任何其他控件。为了说明这一点与地图控件,请考虑下面的类:
~~~
public class Team
{
public string TeamName { get; set; }
public Uri ImageSource { get; set; }
public string Division { get; set; }
public string StadiumName { get; set; }
public Geopoint StadiumLocation { get; set; }
public Point NAP { get; set; }
}
~~~
此类的实例可用于保存有关专业运动团队的基本信息。请注意属性之一是一个 Geopoint 属性包含作为球队的主场体育馆的位置。
在 XAML **图 4** 设置地图控件,这样团队对象的集合,可以绑定到它,并且每个球队的主场体育馆的位置都将标有一个小图标。
**图 4 为集合绑定设置地图控件的**
~~~
<Maps:MapControl
x:Name="myMapControl" Grid.Row="1"
MapServiceToken="{StaticResource MapServiceTokenString}" >
<Maps:MapItemsControl x:Name="MapItems" >
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"
Maps:MapControl.Location="{Binding StadiumLocation}"
Maps:MapControl.NormalizedAnchorPoint="{Binding NAP}"
Height="15" Width="15"
/>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
</Maps:MapControl>
~~~
如果你熟悉其他集合绑定的控件,没有在**图 4** 应该是不熟悉。图像控件包含在 DataTemplate 中。图像控件的附加属性绑定到团队对象的 StadiumLocation 属性的位置。图像控件也具有一个附加的 NormalizedAnchorPoint 属性,将绑定到在体育场位置会居中对齐图像的属性。
请注意,在地图控件绑定到一个集合时,NormalizedAnchorPoint 属性不能是硬编码。必须将它绑定到基础对象中的值。如果您尝试设置此属性,像这样:
~~~
Maps:MapControl.NormalizedAnchorPoint="0.5,0.5"
~~~
XAML 编辑器不喜欢这种语法和值将不会设置。微软是意识到了这个问题。
**图 5** 显示地图控件显示 NFL 球场位置。(此屏幕抓图是从本文附带的代码示例。该代码示例包含此处未显示为简洁起见,如实例化集合和正确格式要合身网页上的控件的所有辅助代码)。
![](https://box.kancloud.cn/2016-01-07_568e4d6723107.png)
**图 5 全国足球联赛球场**
## 缩放控件
有可能调整控件大小,所以他们代表指定的距离。例如,你可能想要在你的地图上显示的规模。规模为用户提供了一种可视化的线索,让他们去猜在地图上的对象之间的距离。此外,如果你使用的地图控件来描绘一个地点坐标,好的办法做到这一点是圆的将一个椭圆添加到地图控件,塑造成一个圆圈,这样圈子包括地点坐标警戒距离大小半径。
Windows Phone 8.1 中的控件大小由指定宽度和高度,以像素为单位。因此,缩放控件来表示某一距离涉及到确定多少距离由地图控件上的单个像素。这里是为确定由上地图控件的单个像素的距离方程:
~~~
const double BING_MAP_CONSTANT = 156543.04;
double MetersPerPixel =
BING_MAP_CONSTANT * Math.Cos(latitude) / Math.Pow(2, zoomLevel);
~~~
使用的纬度应该来自地图控制中心属性。它必须以弧度为单位,不度。因为纬度通常以度为单位表示的你得将其转换为弧度为单位) 乘以 Pi/180 (Math.PI/180) 的程度值。缩放级别来自地图控件的缩放级别,并且是一个从 1 到 20 的值。 在 Windows Phone 8.1,它表示为一张双人床。
完整的数学推导,此处所示的方程是超出了这篇文章 ; 然而,几个评论的顺序。
第一眼看方程看起来可能不正确。纬度为什么很重要?地图的比例只是应该在缩放级别的函数吗?事实是纬度不要紧,缩放级别仅是不够的当确定地图的比例。Bing Maps,是为地图控件的后端,利用墨卡托投影。墨卡托投影是表面的球体的一种技术为 (在本例中地球) 细节投射到一个方形的表面。此投影介绍了尺度不一致之处,因为得越远,你去从赤道,那里维持一个正方形适合更多伸展运动。一个简单的实验可以帮助您更好地理解墨卡托投影。剥一个橘子并保存所有的碎片。当完全剥橘子时,把这些碎片重新在一起在一个平面上,努力去完全融入一座呈方形。它是不可能的。这些碎片进入一座呈方形的必由之路是桔子的伸展你"赤道"不是桔子的件。你越接近,"两极",越伸展你需要做好一个方形的适合。这个拉伸创建规模较接近赤道的地区差异。拉伸所需的金额是距离的从赤道的函数。因此,对纬度的依赖。作为这一伸展在墨卡托投影的地球行动的例子,考虑这两个城市的加拿大魁北克省和佛罗里达州基韦斯特市 完全放大的视图 (缩放级别 20) 的魁北克使用地图控件将导致代表 33.5 脚的 100 个像素。基韦斯特的相同的缩放级别会导致代表 44.5 公尺的 100 个像素。魁北克居民享受是 11 英尺比居民的基韦斯特的规模 — — 小小的安慰,为寒冷的气候。墨卡托投影的更多信息,查阅 MSDN 库文章,"理解规模和决议,"在 [bit.ly/1xIqEwC](http://bit.ly/1xIqEwC)。
要注意对方程的最后事实是 Bing 地图恒定的。Bing 地图不断基于地球的半径和 Microsoft 可以使用来确定指定的缩放级别的地图视图的方程。它的单位是米每个像素。
**图 6** 显示用于作为距离标尺的 textblock) 和添加到地图控件的矩形。**图 7** 显示 ZoomLevelChanged 事件并创建距离规模所需的逻辑。(代码示例检查 RegionInfo.CurrentRegion.IsMetric 属性和显示用户都更熟悉公制系统度量值)。最后, **图 8** 显示的代码示例中的截图 — — 添加到地图控件的距离尺度。
**图 6 Xaml TextBlock 和使用作为距离标尺的矩形**
~~~
<Maps:MapControl
x:Name="myMapControl" Grid.Row="1"
MapServiceToken="{StaticResource MapServiceTokenString}"
ZoomLevelChanged="myMapControl_ZoomLevelChanged">
<!-- Distance scale, which is located at the lower left of the Map control. -->
<TextBlock Name="tbScale" Text="Scale Text" FontSize="15" Foreground="Black"
Margin="24,530,24,6" Opacity="0.6" />
<Rectangle Name="recScale" Fill="Purple" Width="100" Height="6"
Margin="24,548,24,24" Opacity="0.6" />
</Maps:MapControl>
~~~
**图 7 ZoomLevelChanged 事件**
~~~
private void myMapControl_ZoomLevelChanged(MapControl sender, object args)
{
// Get the Map control's current zoom level.
double zoomLevel = sender.ZoomLevel;
// Use the latitude from the center point of the Map control.
double latitude = sender.Center.Position.Latitude;
// The following formula for map resolution needs latitude in radians.
latitude = latitude * (Math.PI / 180);
// This constant is based on the diameter of the Earth and the
// equations Microsoft uses to determine the map shown for the
// Map control's zoom level.
const double BING_MAP_CONSTANT = 156543.04;
// Calculate the number of meters represented by a single pixel.
double MetersPerPixel =
BING_MAP_CONSTANT * Math.Cos(latitude) / Math.Pow(2, zoomLevel);
// Aditional units.
double KilometersPerPixel = MetersPerPixel / 1000;
double FeetPerPixel = MetersPerPixel * 3.28;
double MilesPerPixel = FeetPerPixel / 5280;
// Determine the distance represented by the rectangle control.
double scaleDistance = recScale.Width * MilesPerPixel;
tbScale.Text = scaleDistance.ToString() + " miles";
}
~~~
![](https://box.kancloud.cn/2016-01-07_568e4d67538b6.png)
**图 8 距离尺度添加到地图控件显示日落码头在佛罗里达州基韦斯特市**
## 下载地图供脱机使用
地图可以下载供脱机使用。这是非常方便的应用程序需要提供映射功能,没有任何形式的互联网连接设备时,如当用户正行驶在一个长的公路之间市区或深藏在树林里徒步旅行。
因为它们占用大量的存储,不能未经用户同意以编程方式下载的地图。用户必须选择在每次下载到通过使用一个系统 — —提供了允许的 UX 需选择并下载的地图。这被通过使用静态类 MapManager,属于 Windows.Services.Maps 命名空间。此类提供对下载的地图和更新映射函数。这里所示的 ShowDownloadedMapsUI 函数将启动内置的地图应用程序并显示中显示的网页**图 9**:
~~~
MapManager.ShowDownloadedMapsUI();
~~~
![](https://box.kancloud.cn/2016-01-07_568e4d67732f6.png)
**图 9 用于下载地图的 UI**
内置的地图应用程序名地图中显示的网页**图 9** 也可以通过点击下载映射按钮从设置菜单选项访问。因为此函数把用户带到另一个应用程序 (内置的地图应用程序),他将不得不手动返回到原始的应用程序。
中显示的网页**图 9** 显示先前已下载的所有映射。它还包含一个菜单选项,允许您删除任何先前已下载的映像存储在设备上需要被释放或不再需要一张地图的情况下。
点击添加 (+) 按钮启动地图选择向导,显示一个页面,允许您指定包含您想要下载的地图的大陆。一旦选定了一个大洲,将显示页,其中显示在选定的非洲大陆的所有国家。您可以选择在大陆的所有国家和下载地图为整个非洲大陆,但这将需要大量的存储空间。例如,对于美国的所有映射都需要 3.4 GB 的存储空间。整个大陆的北美洲和中美洲地区将需要更多。
如果您选择包含多个地区或国家的一个大的国家,你会看到一个页面,允许您选择特定区域或国家。您可以通过使用选择按钮来选择多个区域。当指定一个区域时,你会被带到示的下载页面**图 10**。这个页面还将是否您选择一个小的国家,没有任何地区或国家所示。
![](https://box.kancloud.cn/2016-01-07_568e4d678576b.png)
**图 10 下载一张地图**
在下载之前的任何映射,它是一个好主意,以鼓励使用者通过去设置应用程序并选择存储感检查设备上的可用空间。这将显示可用空间在设备上,如空间需要被释放的情况下使用每个应用程序在设备上的存储空间。在写这篇文章的时候,没有 WinRT Api,这使您可以以编程方式确定使用设备上的存储量或可用存储量。
它也是一个好的主意来下载地图虽然连接到不受限制的网络,无线上网等。这篇文章的代码示例演示如何检查当前的网络连接,并警告用户,如果他们是在计量的连接上。
## 更新已下载的映像
地图是偶尔更改或修改。新的道路可能会添加到一个城市,街道名称可能会改变,有时改变了区域的边界。因此,应定期更新已下载的映像。MapManager 静态类包含一个功能叫做 ShowMapsUpdateUI 的更新已下载的映像:
~~~
MapManager.ShowMapsUpdateUI();
~~~
ShowMapsUpdateUI 函数是类似于 ShowDownloadedMapsUI 的功能,因为它具有内置的地图应用程序用户。当调用此函数时,所有已下载的映像检查,看看是否他们需要更新。如果任何先前下载的地图需要更新,用户会告诉更新的大小,并给出了选择取消或继续执行下载。这相同的功能也是从设置菜单选项的地图应用程序可用。
如果您的应用程序鼓励下载地图,利用 ShowDownloadedMapsUI,它是最佳的做法,也使用 ShowMapsUpdateUI,这样下载的地图可以保持当前。
## 总结
在这篇文章,我将向您展示如何将应用程序数据添加到地图控制 Windows Phone 8.1。这包括向地图添加 XAML 控件、 数据绑定和绘图控件到规模。此外演示了如何将地图控件基础数据脱机,所以可以设置地图控件内设计供脱机使用的应用程序无缝地工作。