使用 Windows 8 Metro 应用的 Bing Maps - C# / JavaScript






4.97/5 (42投票s)
使用 Bing Maps 控件构建 Windows 8 Metro 应用程序。
介绍
Bing Maps SDK 可用于 Visual Studio 2012 RC 和 Windows 8 Release Preview,Bing Maps SDK 是一款易于使用、快速且炫酷的控件,您可以在您的 Metro 应用中使用它。
适用于 Metro 风格应用的 Bing Maps SDK (Release Preview) 结合了 Windows 8 和 Bing Maps™ 的强大功能,为 Metro 风格应用提供了增强的地图体验。该 SDK 包括适用于使用 JavaScript 构建的应用以及使用 C#、C++ 和 Visual Basic 构建的应用的控件。
本文将介绍如何为 C# 和 JavaScript Metro 应用程序使用 Bing Maps SDK。
步骤 1:地图开发者账户
在使用 Windows 8 Metro 应用的 Bing SDK 之前,您需要一个地图开发者账户,请访问 http://www.bingmapsportal.com/,使用现有 Windows Live 账户创建或登录。选择“创建或查看密钥”并为您的应用程序创建新密钥。
C#
复制并保存该密钥,在 App.xaml 中创建一个名为 BingCredentials
的资源,其值为该密钥。
<x:String
x:Key="BingCredentials">AnJwk78cjoJfBMQXAxC85FwLiLPwmy6
paQ1TsTJVg_-62hNraRRUzXRz1RELKfHa</x:String>
步骤 2:创建 Bing Maps Metro 应用程序
- 下载最新版本 - 适用于 Metro 风格应用的 Bing Maps SDK
- 打开 Visual Studio 2012 RC,并创建一个空白项目。(C# 或 JavaScript)
JavaScript
“添加引用” -> 选择“Bing Maps for JavaScript (Beta)……”并单击确定。
C#
“添加引用” -> 选择“Bing Maps for C#……”并单击确定。
在将 Bing Maps 引用添加到我们的项目后,您将看到 Bing 二进制文件上出现一个“警告”图标,原因是 Bing Maps 不支持“Any CPU”配置。
您需要更改项目配置,如下文所述 - http://msdn.microsoft.com/en-us/library/hh855146.aspx
- C#、Visual Basic:ARM、x86 或 x64
- C++:ARM、Win32 或 x64
阅读参考 API 文档:Bing Maps SDK
步骤 3:添加地图控件
C#
现在打开“MainPage.xaml”,只需从工具箱中拖动“Map”控件,或添加以下 XAML 代码:
xmlns:Maps="using:Bing.Maps"
<Maps:Map Credentials="{StaticResource BingCredentials}" />
运行应用程序,使用触摸或鼠标,您可以移动地图并进行缩放。
JavaScript
在 default.html 中,我们在 body 下添加了一个 div 元素,这将是地图的容器。
<body>
<div id="mapdiv"></div>
</body>
现在我们需要加载地图到该控件中,在此之前我们需要加载地图模块,为此我们将调用 Maps.loadMoudle
并设置一个回调函数到另一个名为 initMap
的方法。
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
} else {
}
args.setPromise(WinJS.UI.processAll().then(function () {
Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: initMap });
}));
}
};
在地图模块加载后,initMap
函数将地图放置在 mapdiv
元素中,下面的函数定义了几个默认值,例如:
- Bing Map 凭证
- 中心位置
- 地图类型
- 缩放级别
最后,我们获取 mapdiv
元素并定义一个名为 map 的新对象,该对象由 new Map 定义,接收 div 和选项。
function initMap() {
try {
var mapOptions =
{
credentials: "AvOW5Fz4QTsubTTdmaVnseeZnAQ0JYwbx_6zdMdgHk6iF-pnoTE7vojUFJ1kXFTP",
center: new Microsoft.Maps.Location(50, 50),
mapTypeId: Microsoft.Maps.MapTypeId.road,
zoom: 5
};
var mapDiv = document.querySelector("#mapdiv");
map = new Microsoft.Maps.Map(mapDiv, mapOptions);
}
catch (e) {
var md = new Windows.UI.Popups.MessageDialog(e.message);
md.showAsync();
}
}
步骤 4:基础知识
C# + JavaScript
在本节中,我们将使用 Geolocator
,它需要“Location
”权限,因此请确保将“Location
”启用为应用程序功能。
我在右侧添加了几个按钮,以帮助我们控制地图视图。
C#
Zoom
– 地图控件有一个“ZoomLevel”双精度属性,缩放范围为 0-20。
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
var zoom = map.ZoomLevel - 2;
map.SetZoomLevel(zoom < minZoom ? minZoom : zoom);
}
private void btnZoomIn_Click(object sender, RoutedEventArgs e)
{
var zoom = map.ZoomLevel + 2;
map.SetZoomLevel(zoom > maxZoom ? maxZoom : zoom);
}
MapType
– 有三种地图类型:Aerial、Road 和 Birdseye。private void btnChangeMapType_Click(object sender, RoutedEventArgs e)
{
switch (map.MapType)
{
case Bing.Maps.MapType.Aerial:
map.MapType = Bing.Maps.MapType.Birdseye;
break;
case Bing.Maps.MapType.Birdseye:
map.MapType = Bing.Maps.MapType.Road;
break;
default:
map.MapType = Bing.Maps.MapType.Aerial;
break;
}
}
Geolocator
查找当前位置,然后使用地图 SetView
将地图居中于新位置。private async void btnSetLocation_Click(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
var pos = await geolocator.GetGeopositionAsync();
Location location = new Location(pos.Coordinate.Latitude, pos.Coordinate.Longitude);
//Center map view on current location.
map.SetView(location, 15.0f);
}
JavaScript
- 缩放 – JS 地图控件内置了缩放功能,但您仍然可以从代码中更改缩放级别。
var zoomValue = map.getZoom();
map.setView({ zoom: zoomValue + 2 });
function changeMapType() {
var type = map.getMapTypeId();
switch (type) {
case Microsoft.Maps.MapTypeId.aerial:
type = Microsoft.Maps.MapTypeId.road;
break;
case Microsoft.Maps.MapTypeId.road:
type = Microsoft.Maps.MapTypeId.birdseye;
break;
default:
type = Microsoft.Maps.MapTypeId.aerial;
break;
}
map.setView({ center: map.getCenter(), mapTypeId: type });
}
Geolocator
查找当前位置,然后使用地图 SetView
将地图居中于新位置(确保将“Location”启用为应用程序功能)。function centerPosition() {
var geolocator = new Windows.Devices.Geolocation.Geolocator();
geolocator.getGeopositionAsync().then(function (loc) {
var mapCenter = map.getCenter();
mapCenter.latitude = loc.coordinate.latitude;
mapCenter.longitude = loc.coordinate.longitude;
map.setView({ center: mapCenter, zoom: 18 });
});
}
步骤 5:图钉 (Push Pin)
在步骤 4 中,我们使用“Geolocator”查找了当前位置,然后将地图居中于新位置。
我们希望在我们的位置上直接添加一个 PushPin
,而不仅仅是居中地图。
C#
首先,我们将“PushPin
”控件添加为 Map 的子项,您也可以为此创建任何用户控件(如果您想要新图像等)。
<Maps:Map x:Name="map" Margin="60,0,0,0"
Credentials="{StaticResource BingCredentials}"
ShowScaleBar="True" ZoomLevel="5"
Grid.Row="1" ShowTraffic="False" >
<Maps:Map.Children>
<Maps:Pushpin x:Name="pushPin" />
</Maps:Map.Children>
</Maps:Map>
现在,为了将 PushPin
放置在所需位置,您需要使用 MapLayer.SetPosition
,传入图钉控件和位置值。
private async void btnSetLocation_Click(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
var pos = await geolocator.GetGeopositionAsync();
Location location = new Location(pos.Coordinate.Latitude, pos.Coordinate.Longitude);
//Center map view on current location.
MapLayer.SetPosition(pushPin, location);
map.SetView(location, 15.0f);
}
JavaScript
我们需要创建一个新的 PushPin
对象,并指定图钉应放置在地图上的位置。
function addPushPin(location) {
map.entities.clear();
var pushpin = new Microsoft.Maps.Pushpin(location, null);
map.entities.push(pushpin);
}
function centerPosition() {
var geolocator = new Windows.Devices.Geolocation.Geolocator();
geolocator.getGeopositionAsync().then(function (loc) {
var mapCenter = map.getCenter();
mapCenter.latitude = loc.coordinate.latitude;
mapCenter.longitude = loc.coordinate.longitude;
map.setView({ center: mapCenter, zoom: 15 });
addPushPin(mapCenter);
});
}
步骤 6:点击和图钉
我希望添加的另一项功能是“Tap”,这意味着用户点击地图(或鼠标单击),这将在用户点击的位置放置一个图钉。
C#
首先,我创建了一个名为 CustomPin
的新用户控件,其中仅包含星形 XAML。
<Canvas>
<Path x:Name="border"
Stretch="Fill"
Data="F1 M 0,217.042L 227.5,217.042L 297.875, 0L 367.542,217L
595.542,217L 410.208,353.667L 480.708, 569.667L 297.208,
436.667L 116.208,568.167L 185.708,352.667L 0,217.042 Z"
Width="25" Height="25" Margin="-12.5,-12.5,0,0"
Fill="Yellow" Stroke="Red">
</Path>
</Canvas>
在 MainPage
构造函数中,我们将新用户控件添加到 Map 的子集合中,我们也可以直接从 XAML 添加它。
private CustomPin pin;
public MainPage()
{
this.InitializeComponent();
pin = new CustomPin();
map.Children.Add(pin);
}
我们需要为 Map 控件添加一个“Tapped
”事件。
<Maps:Map x:Name="map" Margin="60,0,0,0"
Credentials="{StaticResource BingCredentials}"
ShowScaleBar="True" ZoomLevel="5"
Grid.Row="1" ShowTraffic="False"
Tapped="map_Tapped">
<Maps:Map.Children>
<Maps:Pushpin x:Name="pushPin" />
</Maps:Map.Children>
</Maps:Map>
当触发 Tapped 事件时,我们可以使用 Tapped 事件参数通过调用 GetPosition
方法来获取点击的相对位置,这将给出地图控件上的像素位置,而不是实际位置。因此,我们需要将像素位置转换为实际位置。
我们可以通过调用地图控件的 TryPixelToLocation
方法来实现这一点,然后在我们 收到
用户点击的位置后,我们可以添加一个图钉并将地图居中于新位置。
private void map_Tapped(object sender, TappedRoutedEventArgs e)
{
var pos = e.GetPosition(map);
Location location;
map.TryPixelToLocation(pos, out location);
MapLayer.SetPosition(pin, location);
map.SetView(location);
}
JavaScript
我希望添加的另一项功能是“Tap”,这意味着用户点击地图(或鼠标单击),这将在用户点击的位置放置一个图钉。
我不使用标准的图钉图像,而是想加载自己的图像作为图钉,因此我向 images 文件夹添加了一个“star.png”文件。我还为 mapDiv
元素添加了新的“click”事件侦听器,调用一个名为 tapped
的新函数。
var mapDiv = document.querySelector("#mapdiv");
mapDiv.addEventListener('click', tapped, false);
map = new Microsoft.Maps.Map(mapDiv, mapOptions);
当 tapped
事件触发时,我们可以使用 Tapped 事件参数通过调用地图控件的 tryPixelToLocation
方法来获取点击的相对位置,然后,在我们收到用户点击的位置后,我们可以添加一个图钉并将地图居中于新位置。
function tapped(location) {
var mapCenter = map.getCenter();
var pos = map.tryPixelToLocation(new Microsoft.Maps.Point(location.clientX, location.clientY),
Microsoft.Maps.PixelReference.control);
mapCenter.latitude = pos.latitude;
mapCenter.longitude = pos.longitude;
map.setView({ center: mapCenter });
var pin = new Microsoft.Maps.Pushpin({
latitude: pos.latitude, longitude: pos.longitude }, {
icon: '/images/star.png',
anchor: new Microsoft.Maps.Point(8, 8)
});
map.entities.clear();
map.entities.push(pin);
}
步骤 7 - 应用自定义图块叠加层 - Google Maps
我收到一位朋友的提问,他想用 Google Maps 图块替换 Bing Maps 图块。
您可能会问为什么?如果您想要 Google 图块,只需替换 Bing 控件并使用 Google Maps……在这种情况下,我想使用 Bing Map 控件,因为它在 Windows 8 的 Metro 应用程序中为 C#、C++、VB.NET 和 JavaScript 提供了优势。
而且我还需要 Google Maps 的语言支持。
目前 Bing Maps 不支持除英语以外的任何语言,而我想用用户母语显示地图。
在开始解决方案之前,让我们先谈谈 Bing 和 Google Maps 的共同点。
- 每个图块由 256 x 256 像素组成。
- 每个后续缩放级别将地图划分为 4 的 N 次方个图块,其中 N 指的是缩放级别。例如:
- 在缩放级别 1,每张地图将世界划分为 2x2 网格,总共 4 个图块;
- 在缩放级别 2,每张地图将世界划分为 4x4 网格,总共 16 个图块,依此类推。
- (来自“Tile Coordinates” Google API) – 缩放级别 2 (4x4)
- (来自“Tile Coordinates and Quadkeys” MSDN) – 缩放级别 3 (8x8)
为了替换图块,我们需要一个 Google 地图 URL,该 URL 将根据 X、Y 和缩放级别返回特定图块 – 我们在这里找到了它:
Google Maps – 调用“https://mts0.google.com/vt/hl=he&src=api&x=2&s=&y=1&z=3”将返回:(X = 2, Y = 1, Zoom = 3)
那么问题是什么? 问题是,为了在 Bing Maps 中替换图块,您需要传递“QuadKey
”值。 - MapTileLayer Class
从下面的代码示例可以看出,TileSource
获取自定义图块的 URL,该 URL 带有名为 {quadkey}
的参数。
C#
MapTileLayer tileLayer = new MapTileLayer();
tileLayer.TileSource =
"http://www.microsoft.com/maps/isdk/ajax/layers/lidar/{quadkey}.png";
map.TileLayers.Add(tileLayer);
JavaScript
function addTileOverlay() {
var options = { uriConstructor:
"http://www.microsoft.com/maps/isdk/ajax/layers/lidar/{quadkey}.png"};
var tileSource = new Microsoft.Maps.TileSource(options);
var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource });
map.entities.push(tilelayer);
}
什么是 QuadKey? - Quadkey 唯一标识特定详细级别下的单个图块。
为了优化图块的索引和存储,二维图块 XY 坐标被组合成一维字符串,称为四叉树键(或简称“quadkeys”)。每个 quadkey 唯一标识特定详细级别下的单个图块,并可用作常见数据库 B 树索引中的键。要将图块坐标转换为 quadkey,我们会交织 Y 和 X 坐标的位数,并将结果解释为基数为 4 的数字(保持前导零)并转换为字符串。例如,给定级别 3 的图块 XY 坐标 (3, 5),quadkey 确定如下:
- tileX = 3 = 011 2
- tileY = 5 = 101 2
- quadkey = 100111 2 = 213 4 = “213”
Quadkeys 有几个有趣的特性。首先,quadkey 的长度(数字的位数)等于相应图块的详细级别。其次,任何图块的 quadkey 都以其父图块(上一级别的包含图块)的 quadkey 开头。如以下示例所示,图块 2 是图块 20 到 23 的父级,图块 13 是图块 130 到 133 的父级:
现在我们理解了 QuadKey 是什么,我们就明白我们需要将其值转换为 Google Map 的 X、Y 和缩放级别。我们可以使用以下方法将 QuadKey 转换为 x、y 和级别值。
C#
public static void QuadKeyToTileXY(string quadKey,
out int tileX, out int tileY, out int levelOfDetail)
{
tileX = tileY = 0;
levelOfDetail = quadKey.Length;
for (int i = levelOfDetail; i > 0; i--)
{
int mask = 1 << (i - 1);
switch (quadKey[levelOfDetail - i])
{
case '0':
break;
case '1':
tileX |= mask;
break;
case '2':
tileY |= mask;
break;
case '3':
tileX |= mask;
tileY |= mask;
break;
default:
throw new ArgumentException("Invalid QuadKey digit sequence.");
}
}
}
但是您不需要这样做,因为 quadkey 会自动转换为这些值,因此,我们不是为 TileOverlay
设置静态 URL,而是设置一个名为 convert 的函数,该函数接收 quadkey 对象。
JavaScript
function convert(quadkey) {
return "https://mts0.google.com/vt/hl=he&src=api&x=" + quadkey.x +
"&s=&y=" + quadkey.y +
"&z=" + quadkey.levelOfDetail;
}
function addTileOverlay() {
var options = { uriConstructor: convert };
var tileSource = new Microsoft.Maps.TileSource(options);
var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource });
map.entities.push(tilelayer);
}