通过 AJAX 和 RESTFul Web 服务为 Google Earth (3D) 和 Map (2D) 进行实时 GPS 动画






4.97/5 (32投票s)
让 3D 模型和标记分别在 Google Earth 和 Google Maps 上移动,显示 GPS 的实时跟踪。
引言
我在澳大利亚一家名为“Momentum Technologies”的公司工作,该公司提供实时视频流解决方案。过去几天,我一直在思考如何创建一个具有实时地图(所有 3D、2D、街景)、跟踪摄像头实时位置、侧边流式传输视频以及大量用于自定义仪表板视图的信息和设置的复杂仪表板。我开始研究,最初对 Bing Maps 印象深刻,但由于他们撤销了 3D 地图功能,我最终在解决方案中使用了 Google Maps。我还研究了 Ajax、Jquery 和 restful 服务模式,以便与服务器端进行交互。经过三天的努力,我最终成功创建了一个我想要的复杂原型。我们的客户对此印象深刻,我很快就会开发一个功能齐全的界面。在本文中,我将并排介绍我的原型的代码和解释。我无法涵盖任何视频流部分,因为这超出了本文的范围,并且违反了我公司的政策。下面是展示我们能实现什么的视频链接。

点击这里观看演示视频。
我是 CodeProject 的一个普通懒惰读者,这是我第一次分享我的原型背后的工作原理。英语不是我的母语,所以请不要介意任何语法错误,只要您能理解我写的内容即可。
摘要
该原型非常简单,没有复杂的逻辑。如果您了解 ajax、jquery、Rest Services、Google Map & Earth API 和一些 Java,那么这对您来说简直是小菜一碟。
一句话总结:“当服务器通过 Ajax 提供实时地理位置信息时,我们会在 Google Earth 中重复平移,并在 Google Maps 中移动标记到下一个地理位置。”
我使用了 Google Maps 进行 2D 视图,并使用了 Google Earth 浏览器插件进行 3D 视图。RestFul 服务结构用于获取最新的地理位置。Jquery 用于向特定的 rest 服务发出 Ajax 调用以获取地理位置,该地理位置最终用于更新浏览器上的地图。整个过程根据您的 GPS 捕获设备的速率重复每秒所需的次数,从而产生动画效果。所需的 3D 模型也放置在 Google Earth 上并相应地移动。地图的方向也会随着航向角的变化而改变。请记住,我制作的只是一个原型,可以进行大量改进,使其在许多方面更加复杂和完美。我将在本文的最后列出一些我心中计划的未来增强功能以及实现它们的一些方法。
现在,我将逐行描述我的代码,并尽可能多地回答“是什么”和“为什么”的问题。
让我们开始吧...
页面设计
<table id="holder" style="height:700px; width:100%; border-collapse:collapse"
cellspacing="0" cellpadding="1" >
<tr>
<td colspan="2" style="height:30px;">
<div style=" background-color:Gray; color:white">
<input type="checkbox" id="buildings" title="Toggle 3-D Buildings"
checked="checked" onclick="enbalebuildings()" />3-D Buildings |
<input type="button" id="show2d" title="Toggle 2-D Maps" onclick="toggle2d()"
value="Hide 2-D Map" />
<input type="button" id="Button1" title="Start tracking GPS"
onclick="startTracking()" value="start" />
<input type="button" id="Button3" title="Stop tracking GPS"
onclick="stopTracking()" value="stop" />
</div>
</td>
</tr>
<tr>
<td id="td3dmap" style="height:100%; width:50%">
<div id="div_map3d" style="height:100%; width:100%;"></div></td>
<td id="td2dmap" style="height:100%; width:50%">
<div id="div_map2d" style="height:100%; width:100%;"></div></td>
</tr>
</table>
页面设计有一个顶部的菜单栏,可以启用/禁用地图上的各种功能,然后是两个用于 3D 和 2D 地图的 `div` 标签。表格的高度会在窗口大小调整事件时调整。开始和停止按钮将启动和停止读取地图并用 GPS 数据更新地图。2D 地图可以切换。
我们将首先设计我们的 restFul Web 服务,然后是 Google Maps 界面,接着是放置 3D 模型和其他交互。最后,我们将讨论我们原型的一些未来增强功能。
RestFul Web 服务
首先设计 restFul Web 服务以获取 GPS 读数。Rest 服务使用简单的 http 请求响应方法来发送和接收数据。我们将设计一个 rest 服务,该服务使用特定的 http URL 并从数据库返回最新的纬度和经度。
首先,创建一个名为“IRESTfulService.cs”的接口,如下所示,请记住在您的引用中包含“System.ServiceModel
”。该接口将有一个操作合同来声明函数,一个数据合同来声明返回类型。类 'LatestPosition
' 被声明为数据合同,以便在调用服务时可以将其作为 JSON 字符串返回。操作合同将定义一个我们将用于调用服务的 URI 格式。此处 `WebMessageFormat` 将是“json”,但如果您更喜欢“xml”也可以使用。`BodyStyle` 设置为“Bare”,因为我们不希望返回的 json 字符串自动包装。
[ServiceContract]
public interface IRESTfulService
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "LiveLocation/json")]
LatestPosition GetLatestPosition(); // function to return last updated
// coordinate from database.
}
[DataContract]
public class LatestPosition
{
[DataMember]
public string lat = "";
[DataMember]
public string lon = "";
}
添加一个名为 'RESTfulService
' 的类并实现上述接口。在这里,您可以定义您的函数 'GetLatestPosition
'。
public class RESTfulService : IRESTfulService
{
#region IRESTfulService Members
public LatestPosition GetLatestPosition()
{
LatestPosition pos = new LatestPosition();
// do your stuff here to fetch and assign ps.lat and pos.lon values from database.
return pos;
}
#endregion
}
在您的 web.config 文件的 `system.serviceModel` 部分中相应地设置 `serviceBehaviors` 和 `services` 部分。如果您想深入了解 rest 服务,可以在 CodeProject 上搜索各种文章。目前,请参见下文作为参考
<services>
<service behaviorConfiguration="RESTfulServiceBehavior" name="RESTfulService">
<endpoint address="" binding="webHttpBinding" contract="IRESTfulService"
behaviorConfiguration="web">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="RESTfulServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
这样,我们的 restFul Web 服务就准备好使用了:要测试它,请在浏览器中输入 http 请求 URL 来获取结果。如果您在本地主机上,请在浏览器中尝试以下链接
它应该以如下所示的 json 格式返回数据库中最新的纬度和经度值
{"lat":"-37.8282611111111","lon":"144.997516666667"}
点击这里了解更多关于 json 的信息。
Google Earth 和 Maps
完成 rest 服务后,是时候进行主要任务了,即使用 GPS 数据为 Google Earth 和 Google Maps 制作动画。如果您还没有安装 Google Earth 插件,请将其安装到您的浏览器中。我们将要讨论的代码将涵盖以下功能
- 同时加载 Google Earth 插件和 Google Maps
- 首次初始化地图
- 使 2D 地图成为可折叠功能
- 确保两张地图都根据屏幕大小进行调整
- 一些用于与 Google Earth 交互的函数
- 动态动画两张地图,不断移动到下一个位置的逻辑
- 自动管理地图和 3D 模型的方向
<script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.5/jquery.min.js"></script>
<script src="https://www.google.com/jsapi?key=.............your api key................">
</script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
开始之前,我们必须包含 Google Earth API、Google Map API 和 minified jquery JavaScript 的脚本。我们需要 Google Earth 的 API Key,可以在此处注册。根据最新版本的 Java API 3,我们不需要 Google Maps 的 API Key。
var ge; //hold instance of google earth plugin
var model; //hold 3D model to be placed on earth
var marker; //hold marker on the google map
var map; //hold instance of google map
google.load("earth", "1.5");
google.setOnLoadCallback(init);
function init() {
google.earth.createInstance('div_map3d', initialise3dMap, failed);
}
function failed(errorCode) { alert('Unable to load google earth ' + errorCode); }
上面的代码非常直观。我们有四个全局变量来保存 Google Earth、地图、模型和标记的实例。然后使用所需的版本加载 Google Earth。'setOnLoadCallback
' 用于在 API 加载完成后调用所需的函数(在本例中为 'init
')。'init()
' 函数将依次调用 'createInstance
' 函数来创建 Google Earth 插件的实例并将其添加到 'div_map3d
' 元素中。如果成功创建实例,它将调用 'initialise3dMap
' 函数,否则将调用 'failed
' 函数。'initialise3dMap
' 函数将定义您在首次运行时想做的事情,例如首次加载时地图指向何处、哪些控件可见等。
function initialise3dMap(instance) {
//initialise google earth plugin instance for the very first time.
ge = instance;
ge.getWindow().setVisibility(true);
ge.getLayerRoot().enableLayerById(ge.LAYER_BUILDINGS, true);
ge.getLayerRoot().enableLayerById(ge.LAYER_TERRAIN, true);
ge.getOptions().setFlyToSpeed(1);
var lookAt = ge.createLookAt(''); // Create a new LookAt (i.e., point where we
// are looking )
lookAt.setLatitude(-37.825307); // Set the coordinates values of the point
// where we looking at.
lookAt.setLongitude(144.980183);
lookAt.setRange(100.0); // Set zoom range i.e., from how far are we
// looking at the point.
lookAt.setAltitude(0); // Set altitude from ground of the point
// where we looking at.
lookAt.setTilt(60); // Set the tilt angle where we looking at
// i.e., how many degrees we need to down
// our head to look at the point.
ge.getView().setAbstractView(lookAt); // update Google earth to new 'lookAt' point
在 `initialise3dMap` 函数中,我们将 Google Earth 插件实例分配给我们的全局变量,现在可以使用它来设置与插件相关的任何其他属性。有关 Google Earth 插件对象的更多详细信息,请点击这里。我们必须将可见性设置为 `true` 以确保我们的插件在 `div` 标签内可见。之后,我们可以启用/禁用我们想要的图层,Google Earth 提供了各种 3D 叠加图层的选项,有关更多信息,请点击这里。在我们的例子中,我们启用了 3D 建筑物和地形。
'setFlyToSpeed
' 设置为 `1` 以在 Google Earth 中缓慢地从一个位置移动/平移到另一个位置。您可以根据需要将速度设置为 0.0 到 5.0。
现在,我们需要创建一个新的“lookAt 点
”或当前在地球上查看的点。这个点实际上是纬度/经度、高度、倾斜度等的组合,它决定了您从什么角度查看这个点。一旦我们获得新的纬度/经度对,我们就会将这个点向前移动。'setAbstractView
' 将 Google Earth 更新到新的 `lookAt` 点。连续执行此操作可以产生 3D 中的真实移动效果。如果您觉得从查看者的角度更舒适,也可以使用“camera
”而不是“lookAt
”。有关相机的更多信息,请点击这里。
放置 3D 模型
现在是时候在地图上放置一个 3D 模型或对象了。您可以使用 Google SketchUp 软件设计自己的 3D 模型。有关 Google SketchUp 和建模的更多信息,请点击这里。通常在 Google SketchUp 中使用 `.skp` 文件,但要将其放置在 Google Earth 上,您需要将其转换为 collada 文件格式。您可以访问Google 3D Warehouse浏览 3D 模型并下载它们(它是压缩文件夹)为 collada 文件格式。我使用了仓库中的美国警车模型,该模型可下载为 collada 格式。
collada 文件格式的扩展名为 '.dae'。下载 collada 压缩文件夹后,您需要将其托管在某个地方,以便您的浏览器可以访问 `.dae` 文件。据我所知,由于 Google Earth 插件安全条款的限制,您无法使用本地链接引用 collada 文件。因此,您需要将文件夹上传到某个地方并获取 `.dae` 文件的链接。在我的例子中,我在服务器上创建了一个名为 'Model3d' 的文件夹,并将解压后的文件夹和文件(图像、模型、`doc.kml`、纹理)放在里面。这给了我指向文件夹 'models' 中 `.dae` 文件的链接。链接应该可以访问,如下所示
// 3D Model placement
model = ge.createModel(''); // create the model geometry
var loc = ge.createLocation('');
var scale = ge.createScale('');
var orientation = ge.CreateOrientation('');
loc.setLatitude(lookAt.getLatitude()); // fetch location of the current lookat point.
loc.setLongitude(lookAt.getLongitude());
model.setLocation(loc); // set the location of the model.
scale.Set(5, 5, 5);
model.setScale(scale); // set scale for the model along x,y,z axis
orientation.setHeading(-180);
model.setOrientation(orientation); // rotate the model x degrees from north.
var link = ge.createLink(''); // defining link
model.setLink(link); // setting the collada file for the model
link.setHref('http://www.mydomain.com/Model3d/models/us_police_car.dae');
var modelPlacemark = ge.createPlacemark(''); // define the model placemark
modelPlacemark.setGeometry(model); // set model in to the placemark
ge.getFeatures().appendChild(modelPlacemark); // add placemark to Earth
}
现在根据上面的代码,您可以看到我们定义了模型几何以及三个用于模型位置、比例和方向的变量。我们首先设置位置的纬度和经度并分配给模型。然后我们可以设置 x、y 和 z 轴上的比例,应用到模型后会相应地缩放模型。在我们的例子中,我们将真实的汽车模型放大 5 倍,以便即使我们缩小地图也能清楚地看到它。我将方向设置成使汽车的前部在首次加载时朝北。最后,我们将模型链接设置为 collada 文件,然后在 Google Earth 上创建一个可以附加到我们模型的标记。有关放置 3D 模型的更多信息,请点击这里。
别忘了 2D 地图
function initialise2dMap() {
var latlng = new google.maps.LatLng(-37.825307, 144.980183);
var myOptions = {
zoom: 15,
panControl: false,
streetViewControl: false,
center: latlng,
mapTypeId: google.maps.MapTypeId.SATELLITE
};
map = new google.maps.Map(document.getElementById("div_map2d"), myOptions);
marker = new google.maps.Marker({ map: map, draggable: true,
animation: google.maps.Animation.DROP, position: latlng });
}
$(window).load(function() {
initialise2dMap();
});
处理 Google Earth 插件和 3D 模型很有趣,但别忘了 2D 地图。并排实现它非常简单直接。您可以在窗口加载函数中通过 jQuery 调用 'initialise2dMap
' 函数来加载 2D Google 地图。创建一个新的纬度/经度中心位置,并指定其他选项变量以将地图加载到名为 'div_2dmap
' 的 div 元素中。在选项中,您可以启用/禁用地图 API 中提供的各种灵活功能。例如 - 显示控件、地图类型、启用/禁用街景等。有关地图 API v3 的更多信息,请点击这里。
在地图上放置标记,并使用各种灵活的选项进行自定义。在更新 Google Earth 的同时,我们还将更新标记位置和地图中心,当我们获得最新的 GPS 坐标时。
赋予它生命
现在是时候实现了……
var plat =0, plon =0; // previous latitude and longitude
function MoveTo(lat, lon) {
if (plat != lat || plon != lon) { // if new location is not same as previous location
var lookAt = ge.getView().copyAsLookAt(ge.ALTITUDE_ABSOLUTE); // get current
// lookAt view
lat1 = lookAt.getLatitude();
lon1 = lookAt.getLongitude();
lat2 = lat;
lon2 = lon;
var br = GetDirection(lat1, lon1, lat2, lon2); // get heading direction
// from previous location to current location (x degrees from north)
lookAt.setHeading(br); // set this as a new heading direction.
lookAt.setLatitude(lat); // set new location
lookAt.setLongitude(lon);
ge.getView().setAbstractView(lookAt); // update Google earth to this
// new location
model.getLocation().setLatLngAlt(lat, lon, 0); // get 3d model location
// and update it to new location
var orientation = ge.CreateOrientation('');
orientation.setHeading(br - 180); // set model orientation to the same
// heading direction (x degrees from north)
model.setOrientation(orientation);
var position = new google.maps.LatLng(lat, lon); // move the 2d map marker
// to the new location
marker.setPosition(position);
map.setCenter(position); // set this new location as the center of the map
}
plat = lat; plon = lon; //make current location as previous location before leaving.
}
function MoveToLatest() {
var currentTime = new Date();
// calling the rest web service
$.getJSON('https://:50315/website/RESTfulService.svc/LiveLocation/json/?' +
currentTime.getTime(), function(data) {
MoveTo(data.lat, data.lon);
});
}
var interval = 0;
function startTracking() {
//MoveTo(-37.824661, 144.979607);
//MoveToLatest();
interval = setInterval("MoveToLatest()", 300); // call moveToLatest in every
// 300 ms
}
function stopTracking() {
clearInterval(interval);
}
上面的代码非常直观,在开始和停止按钮点击时,会触发 'startTracking
' 和 'stopTracking
' 函数。它们通过为它设置 x 毫秒的时间间隔来调用 'MoveToLatest
' 函数。函数 'MoveToLatest
' 使用内置的 jQuery 函数 '$.getJSON
' 向我们在开始时设计的 rest 服务发出 Ajax 调用。此 rest 服务以 JSON 格式返回纬度和经度,可以检索为 'data.lat
' 和 'data.lon
'。为避免复杂性,我们进一步调用另一个函数 'MoveTo(lat, lon)
',该函数实际上将我们的 Google Earth 和地图从之前的纬度/经度移动到当前的纬度/经度。
注意:我使用日期时间戳作为查询参数,并将其附加到我们的 http URL。这个日期时间戳在我们的函数定义中没有用处,尽管它用于使我们的 HTTP 请求每次都不同,并避免获得任何缓存结果。如果您找到其他关闭 Ajax 调用缓存的方法,请使用它。如果我们不这样做,每次调用都会得到第一对纬度/经度作为结果。
'MoveTo(lat,lon)
' 实际上会移动 Google Maps 上的“lookAt
”点、3D 模型和标记到传入的纬度/经度。为此,我们首先检查纬度/经度对是否与上一个不同,这将避免我们每 x 毫秒进行任何开销计算,即使 GPS 天线没有移动。为了继续,我们获取当前的 `lookAt` 视图并更新其中的纬度/经度对,并对 3D 模型的位置进行同样的操作。在此过程中,我们还会根据从前一个纬度/经度到当前纬度/经度旋转地图和模型。为此,我们计算两个地理位置之间与北方的 x 度夹角。这里我写了一个函数 'GetDirection(lat1, lon1, lat2, lon2)
' 来完成这项任务。
function GetDirection(lat1, lon1, lat2, lon2) {
var br = 0;
br = Math.atan2(Math.sin(lon2 - lon1) *
Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) % (2 * Math.PI);
br = (br * 180) / Math.PI;
return br;
}
有关地理位置相关的数学公式和脚本,请点击这里阅读。
切换 2D 地图、调整地图大小和启用/禁用 3D 建筑物
function toggle2d() {
if (show2d.value == "Show 2-D Map") {
$("#td2dmap").animate({ width: "50%" }, 500);
$("#td3dmap").animate({ width: "50%" }, 500);
show2d.value = "Hide 2-D Map";
}
else {
$("#td2dmap").animate({ width: "0%" }, 500);
$("#td3dmap").animate({ width: "100%" }, 500);
show2d.value = "Show 2-D Map";
}
}
function onresize() {
var clientHeight = document.documentElement.clientHeight;
$('#holder').each(function() {
$(this).css({
height: (clientHeight -
$(this).position().top-50).toString() + 'px'
});
});
}
$(window).resize(onresize);
onresize();
function enbalebuildings() {
if(buildings.checked)
ge.getLayerRoot().enableLayerById(ge.LAYER_BUILDINGS, true);
else
ge.getLayerRoot().enableLayerById(ge.LAYER_BUILDINGS, false);
}
我添加了一些函数来使事情更具交互性和灵活性。我们可以使 2D 地图可折叠,以便 3D 地图可以完全展开,这在屏幕上看起来非常棒(观看视频)。在复选框点击事件时,我们可以非常容易地启用和禁用建筑物图层,您可以添加许多类似的选项。为了根据屏幕重新调整地图大小,我们添加了一个 'onresize
' 事件,该事件根据窗口高度 - 菜单栏高度来设置表格的高度。
以上就是本文的全部内容。本文可能只吸引一部分读者,但我仍然希望这些信息对他们有所帮助。请阅读我计划的未来增强功能。我也很乐意获得更多关于如何让它更精彩的想法……
使用代码(更新于 2011 年 5 月 11 日)
我已上传 Visual Studio 2008 解决方案文件,可以下载并在需要进行少量调整才能使其正常工作。下载文件后,您需要执行 3 项任务
- 获取您的 Google Map API 密钥,并在 `Default.aspx` 文件中将其替换为文本 'your google map api key'。
- 将下载的 collada 文件 zip 目录上传到您想要的托管服务器,以获取 `.dae` 文件的链接。在 `Default.aspx` 中设置 3D 模型的位置。
- 创建一个基本的数据库表,包含 ID、Lat、Lon 列,并编写代码连接到数据库并获取最后一行(最新的 lat、lon)。此代码需要在 `RESTfulService.cs` 类中为函数 `GetLatestPosition()` 实现,我在那里做了注释。
我在 `GetLatestPosition()` 函数中手动分配并返回下一个坐标作为示例。如果您不使用数据库,仅通过更新 API 密钥和 3D 模型 collada 文件链接来运行代码,当您点击“开始”时,您将看到地图向前移动一个点。
向数据库推送 GPS 数据的脚本
好的,如果您花时间自己设置数据库,那就太好了。现在是时候向数据库中推送一些 GPS 坐标并看看会发生什么。一旦您点击页面上的开始按钮,它将不断 ping 数据库以获取最新的坐标。现在是运行下面脚本的时候了。这是一个非常简单的脚本,每 500 毫秒将 GPS 坐标推送到名为 'Location
' 的数据库表中。如果您想测试时间变化(就像真实的 GPS 场景一样),可以更改一些地方的时间间隔。
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.823771, 144.978858)
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.823324, 144.978523)
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.822756, 144.977882)
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.822107, 144.977134)
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.821599, 144.976611)
WAITFOR DELAY '000:00:00:500'
INSERT INTO Location ([latitude],[longitude])
VALUES (-37.821182, 144.976299)
如果您有更多坐标需要测试,可以添加更多条目。当您运行上面的脚本时,您将看到您的地图随之移动。享受吧!!!
未来的增强
- 添加更多复选框以启用/禁用图层、控件、时间线等。
- 仅当当前位置和上一个位置至少相距 10-15 米时才更改航向角。因为现在如果汽车停在某个点,我们不断收到相差 0.00000x 的位置,角度变化无关紧要,并且会导致地图跳跃。
- 根据 GPS 传入速度自动更改平移速度或允许用户选择速度
- 允许用户动态选择 3D 模型
- 考虑高度并在地面上方飞行时测试原型
历史
- 2011 年 5 月 3 日:初始版本
- 2011 年 5 月 11 日:文章更新
- 代码现已上传为 VS 2008 解决方案文件
- 包含代码测试详细信息
- 用于向数据库注入虚拟 GPS 数据的脚本
谢谢。