构建交互式谷歌地图实时行程应用
在本文中,我们将详细介绍如何构建一个交互式谷歌地图应用,该应用会根据朋友在南美洲的旅行信息进行动画展示。以下是具体的操作步骤和相关代码实现。
准备工作
此应用的许多元素基于之前的工作,但我们将从头开始构建,不会过多关注已学内容。当用户在世界地图上“旅行”,且数据源中有消息时,地图会淡出并显示消息,之后用户可继续旅行。
操作步骤
我们需要创建两个文件:一个 HTML 文件和一个 JavaScript 文件。
1. 创建 HTML 文件
<!DOCTYPE html>
<html>
<head>
<title>Google Maps Markers and Events</title>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link href='http://fonts.googleapis.com/css?family=Yellowtail' rel='stylesheet' type='text/css'>
<style>
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map { height: 100%; width:100%; position:absolute; top:0px; left:0px }
.overlay {
background: #000000 scroll;
height: 100%;
left: 0;
opacity: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 50;
}
.overlayBox {
left: -9999em;
opacity: 0;
position: absolute;
z-index: 51;
text-align:center;
font-size:32px;
color:#ffffff;
font-family: 'Yellowtail', cursive;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://maps.googleapis.com/maps/api/js?key=AIzaSyBp8gVrtxUC2Ynjwqox7I0dxrqjtCYim-8&sensor=false"></script>
<script src="https://www.google.com/jsapi"></script>
<script src="./10.05.travel.js"></script>
</head>
<body>
<div id="map"></div>
</body>
</html>
2. 创建 JavaScript 文件(10.05.travel.js)
以下是具体的代码实现和操作步骤:
1. 初始化可视化库并存储全局地图变量:
google.load('visualization', '1.0');
google.setOnLoadCallback(init);
var map;
-
init函数触发时,加载地图并触发加载存储朋友旅行信息的 Google 电子表格:
function init() {
var BASE_CENTER = new google.maps.LatLng(48.516817734860105,13.005318750000015 );
map = new google.maps.Map(document.getElementById("map"),{
center: BASE_CENTER,
mapTypeId: google.maps.MapTypeId.SATELLITE,
disableDefaultUI: true,
});
var query = new google.visualization.Query(
'https://spreadsheets.google.com/tq?key=0Aldzs55s0XbDdERJVlYyWFJISFN3cjlqU1JnTGpOdHc');
query.send(onTripDataReady);
}
- 文档加载时,触发
onTripDataReady监听器,创建GoogleMapTraveler对象:
function onTripDataReady(response){
var gmt = new GoogleMapTraveler(response.g.D,map);
}
-
GoogleMapTraveler对象的构造函数准备变量,创建Animator对象、Traveler对象和google.maps.Polyline对象,并调用nextPathPoint方法创建第一个旅行点:
function GoogleMapTraveler(aData,map){
this.latLong; //will be used to store current location
this.zoomLevel; //to store current zoom level
this.currentIndex=0;
this.data = aData; //locations
this.map = map;
//this.setPosition(0,2);
this.animator = new Animator(30);
this.pathPoints = [this.getPosition(0,1)]; //start with two points at same place.
var lineSymbol = {
path: 'M 0,-1 0,1',
strokeOpacity: .6,
scale: 2
};
this.lines = new google.maps.Polyline({
path: this.pathPoints,
strokeOpacity: 0,
strokeColor: "#FF0000",
icons: [{
icon: lineSymbol,
offset: '0',
repeat: '20px'
}],
map: map
});
this.traveler = new Traveler(this.map,this.getPosition(0,1));
this.nextPathPoint(1);
}
-
getPosition方法创建新的google.maps.LatLng对象:
GoogleMapTraveler.prototype.getPosition = function (index,amount){
var lat=0;
var lng=0;
for(var i=0; i<amount; i++){
lat+= parseFloat(this.data[index+i].c[0].v);
lng+= parseFloat(this.data[index+i].c[1].v);
}
var ll=new google.maps.LatLng(
lat/amount,
lng/amount);
return ll;
}
-
setPosition方法设置旅行者的位置:
GoogleMapTraveler.prototype.setPosition = function(index,amount){
this.currentFocus = index;
var lat=0;
var lng=0;
for(var i=0; i<amount; i++){
lat+= parseFloat(this.data[index+i].c[0].v);
lng+= parseFloat(this.data[index+i].c[1].v);
}
var ll=new google.maps.LatLng(
lat/amount,
lng/amount);
if(this.data[index].c[2])this.map.setZoom(this.data[index].c[2].v);
this.map.setCenter(ll);
}
-
nextPathPoint方法结合Animator对象和nextPathPoint方法实现自动从一个步骤移动到下一个步骤的逻辑:
GoogleMapTraveler.prototype.nextPathPoint = function(index){
this.setPosition(index-1,2);
this.pathPoints.push(this.getPosition(index-1,1)); //add last point again
var currentPoint = this.pathPoints[this.pathPoints.length-1];
var point = this.getPosition(index,1);
this.animator.add(currentPoint,"Za",currentPoint.Za,point.Za,1);
this.animator.add(currentPoint,"Ya",currentPoint.Ya,point.Ya,1);
this.animator.add(this.traveler.ll,"Za",this.traveler.ll.Za,point.Za,2,0.75);
this.animator.add(this.traveler.ll,"Ya",this.traveler.ll.Ya,point.Ya,2,0.75);
this.animator.onUpdate = this.bind(this,this.renderLine);
this.animator.onComplete = this.bind(this,this.showOverlayCopy);//show copy after getting to destination
}
-
renderLine方法更新线路信息:
GoogleMapTraveler.prototype.renderLine = function(){
this.lines.setPath(this.pathPoints);
if(this.traveler.isReady)this.traveler.refreshPosition();
}
-
bind方法:
GoogleMapTraveler.prototype.bind = function(scope, fun){
return function () {
fun.apply(scope, arguments);
};
}
- 创建
Traveler类:
function Traveler(map,ll) {
this.ll = ll;
this.radius = 15;
this.innerRadius = 10;
this.glowDirection = -1;
this.setMap(map);
this.isReady = false;
}
Traveler.prototype = new google.maps.OverlayView();
Traveler.prototype.onAdd = function() {
this.div = document.createElement("DIV");
this.canvasBG = document.createElement("CANVAS");
this.canvasBG.width = this.radius*2;
this.canvasBG.height = this.radius*2;
this.canvasFG = document.createElement("CANVAS");
this.canvasFG.width = this.radius*2;
this.canvasFG.height = this.radius*2;
this.div.style.border = "none";
this.div.style.borderWidth = "0px";
this.div.style.position = "absolute";
this.canvasBG.style.position = "absolute";
this.canvasFG.style.position = "absolute";
this.div.appendChild(this.canvasBG);
this.div.appendChild(this.canvasFG);
this.contextBG = this.canvasBG.getContext("2d");
this.contextFG = this.canvasFG.getContext("2d");
var panes = this.getPanes();
panes.overlayLayer.appendChild(this.div);
}
Traveler.prototype.draw = function() {
var radius = this.radius;
var context = this.contextBG;
context.fillStyle = "rgba(73,154,219,.4)";
context.beginPath();
context.arc(radius,radius, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
context = this.contextFG;
context.fillStyle = "rgb(73,154,219)";
context.beginPath();
context.arc(radius,radius, this.innerRadius, 0, Math.PI*2, true);
context.closePath();
context.fill();
var projection = this.getProjection();
this.updatePosition(this.ll);
this.canvasBG.style.opacity = 1;
this.glowUpdate(this);
setInterval(this.glowUpdate,100,this);
this.isReady = true;
};
Traveler.prototype.refreshPosition=function(){
this.updatePosition(this.ll);
}
Traveler.prototype.updatePosition=function(latlng){
var radius = this.radius;
var projection = this.getProjection();
var point = projection.fromLatLngToDivPixel(latlng);
this.div.style.left = (point.x - radius) + 'px';
this.div.style.top = (point.y - radius) + 'px';
}
Traveler.prototype.glowUpdate=function(scope){ //endless loop
scope.canvasBG.style.opacity = parseFloat(scope.canvasBG.style.opacity) + scope.glowDirection*.04;
if(scope.glowDirection==1 && scope.canvasBG.style.opacity>=1) scope.glowDirection=-1;
if(scope.glowDirection==-1 && scope.canvasBG.style.opacity<=0.1) scope.glowDirection=1;
}
- 创建
Animator类:
function Animator(refreshRate){
this.onUpdate = function(){};
this.onComplete = function(){};
this.animQue = [];
this.refreshRate = refreshRate || 35; //if nothing set 35 FPS
this.interval = 0;
}
Animator.prototype.add = function(obj,property,
from,to,time,delay){
obj[property] = from;
this.animQue.push({obj:obj,
p:property,
crt:from,
to:to,
stepSize: (to-from)/(time*1000/this.refreshRate),
delay:delay*1000 || 0});
if(!this.interval){ //only start interval if not running already
this.interval = setInterval(this._animate,this.refreshRate,this);
}
}
Animator.prototype._animate = function(scope){
var obj;
var data;
for(var i=0; i<scope.animQue.length; i++){
data = scope.animQue[i];
if(data.delay>0){
data.delay-=scope.refreshRate;
}else{
obj = data.obj;
if((data.stepSize>0 && data.crt<data.to) ||
(data.stepSize<0 && data.crt>data.to)){
data.crt = data.crt + data.stepSize;
obj[data.p] = data.crt;
}else{
obj[data.p] = data.to;
scope.animQue.splice(i,1);
--i;
}
}
}
scope.onUpdate();
if( scope.animQue.length==0){
clearInterval(scope.interval);
scope.interval = 0; //reset interval variable
scope.onComplete();
}
}
工作原理
- HTML 和 CSS :从 Google 字体库选择字体并集成到文本覆盖层。
<link href='http://fonts.googleapis.com/css?family=Yellowtail' rel='stylesheet' type='text/css'>
.overlayBox {
font-family: 'Yellowtail', cursive;
}
- JavaScript :加载 Google 可视化库,触发
init函数,加载地图和电子表格。使用google.visualization.Query对象直接处理电子表格数据。
var query = new google.visualization.Query(
'https://spreadsheets.google.com/tq?key=0Aldzs55s0XbDdERJVlYyWFJISFN3cjlqU1JnTGpOdHc');
query.send(onTripDataReady);
核心逻辑
GoogleMapTraveler 对象是管理中心,负责管理 Traveler 标记和 google.maps.Polyline 对象。 nextPathPoint 方法是核心逻辑,它会递归遍历电子表格中的所有点。
流程图
graph TD;
A[初始化地图] --> B[加载电子表格数据];
B --> C[创建 GoogleMapTraveler 对象];
C --> D[设置地图视图];
D --> E[添加新点到路径数组];
E --> F[动画当前点到目标点];
F --> G[更新可视化元素];
G --> H{动画完成?};
H -- 是 --> I[显示覆盖层文本];
I --> J[触发下一个旅行点];
H -- 否 --> F;
总结
通过以上步骤,我们成功构建了一个交互式谷歌地图应用,它可以根据朋友的旅行信息进行动画展示。这个应用涉及到多个类和方法的协同工作,包括 GoogleMapTraveler 、 Traveler 和 Animator 等。同时,我们还使用了 Google 可视化库和电子表格来存储和处理数据。希望这篇文章能帮助你理解如何构建类似的应用。
其他类的说明
Traveler 标记
Traveler 类主要基于之前自定义标记外观的工作,不同之处在于添加了内部动画和 updatePosition 方法,用于移动标记位置。
Traveler.prototype.updatePosition=function(latlng){
var radius = this.radius;
var projection = this.getProjection();
var point = projection.fromLatLngToDivPixel(latlng);
this.div.style.left = (point.x - radius) + 'px';
this.div.style.top = (point.y - radius) + 'px';
}
refreshPosition 方法在动画更新时调用,确保标记位置实时更新。
Traveler.prototype.refreshPosition=function(){
this.updatePosition(this.ll);
}
Animator 对象
对 Animator 对象进行了两个主要更新:
1. 集成回调方法 :通过 onUpdate 和 onComplete 回调函数,在动画更新和完成时执行相应逻辑。
function Animator(refreshRate){
this.onUpdate = function(){};
this.onComplete = function(){};
// ...
}
- 优化动画逻辑 :添加更智能、详细的逻辑,使动画能够处理正负区域的经纬度值。
操作步骤总结
| 步骤 | 操作 |
|---|---|
| 1 | 创建 HTML 文件,设置地图容器和样式,引入必要的脚本库 |
| 2 | 创建 JavaScript 文件,初始化可视化库和地图,加载电子表格数据 |
| 3 | 创建 GoogleMapTraveler 对象,管理地图、标记和线路 |
| 4 | 实现 nextPathPoint 方法,控制地图动画和旅行点切换 |
| 5 | 创建 Traveler 类,实现标记的动画和位置更新 |
| 6 | 创建 Animator 类,处理动画逻辑和回调 |
通过以上步骤,你可以构建一个完整的交互式谷歌地图实时行程应用。希望这篇文章对你有所帮助,如果你有任何问题或建议,欢迎随时交流。你可以通过 http://02geek.com 联系我,邮箱是 ben@02geek.com。
构建交互式谷歌地图实时行程应用
GoogleMapTraveler.prototype.nextPathPoint 方法逻辑深入剖析
GoogleMapTraveler.prototype.nextPathPoint 方法在整个应用逻辑中处于核心地位,下面我们详细分析其内部逻辑。
- 设置地图视图
this.setPosition(index - 1, 2);
setPosition 方法会根据传入的当前索引数据重新定位地图和调整缩放级别。它接收第二个参数,可用于对两个点进行平均处理。当我们在两点之间移动时,将该参数设为 2 能使地图位于两点中心。其内部逻辑是循环处理所需数量的项目,以计算出正确的平均位置。
- 添加新点到路径数组
this.pathPoints.push(this.getPosition(index - 1, 1)); //add last point again
我们将已在数组中的相同点复制一份添加到 this.pathPoints 数组中,这样新的第二个点就从起始点开始。后续我们可以不断更新数组中的最后一个值,直到其达到真正的下一个目标点。
- 创建辅助变量
var currentPoint = this.pathPoints[this.pathPoints.length - 1];
var point = this.getPosition(index, 1);
currentPoint 是对 pathPoints 数组中最后一个点的引用,而 point 是我们动画结束时要到达的新目标点。
- 启动动画
this.animator.add(currentPoint, "Za", currentPoint.Za, point.Za, 1);
this.animator.add(currentPoint, "Ya", currentPoint.Ya, point.Ya, 1);
this.animator.add(this.traveler.ll, "Za", this.traveler.ll.Za, point.Za, 2, 0.75);
this.animator.add(this.traveler.ll, "Ya", this.traveler.ll.Ya, point.Ya, 2, 0.75);
我们使用 Animator 对象对 currentPoint 和 traveler.ll 进行动画处理,使其从当前值过渡到目标值。给 traveler.ll 的动画添加了 0.75 秒的延迟,让动画效果更丰富。
- 设置回调函数
this.animator.onUpdate = this.bind(this, this.renderLine);
this.animator.onComplete = this.bind(this, this.showOverlayCopy);
onUpdate 回调函数在动画更新时触发 renderLine 方法,用于更新可视化元素; onComplete 回调函数在动画完成时触发 showOverlayCopy 方法,用于显示覆盖层文本。
覆盖层逻辑
当动画完成时,会触发 showOverlayCopy 方法。如果 Google 文档的第五列有文本,我们会使屏幕变暗并逐字显示文本;若没有文本,则跳过此步骤,直接调用 hideOverlayCopy 方法触发下一个旅行点。
GoogleMapTraveler.prototype.hideOverlayCopy = function () {
//update index now that we are done with initial element
this.currentIndex++;
//as long as the slide is not over go to the next.
if (this.data.length > this.currentIndex + 1) this.nextPathPoint(this.currentIndex + 1);
}
代码优化建议
- 错误处理 :在加载电子表格数据时,可添加错误处理逻辑,避免因数据加载失败导致应用崩溃。
function onTripDataReady(response) {
if (response.isError()) {
console.error('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var gmt = new GoogleMapTraveler(response.g.D, map);
}
- 性能优化 :在
Animator对象的动画处理中,可考虑使用更高效的算法或减少不必要的计算,提高动画性能。 - 代码复用 :将一些通用的功能封装成独立的函数或模块,提高代码的复用性和可维护性。
进一步拓展
- 多用户支持 :可以修改应用,支持多个用户的旅行信息展示,每个用户的数据存储在不同的电子表格中。
- 实时更新 :结合实时数据接口,实现旅行信息的实时更新,让地图动画更具实时性。
- 交互功能 :添加更多交互功能,如用户点击地图上的点查看详细信息、分享旅行路线等。
总结
通过本文的详细介绍,我们深入了解了如何构建一个交互式谷歌地图实时行程应用。该应用涉及多个类和方法的协同工作,包括 GoogleMapTraveler 、 Traveler 和 Animator 等,同时使用了 Google 可视化库和电子表格来存储和处理数据。我们还对核心方法的逻辑进行了深入剖析,并提出了代码优化和进一步拓展的建议。希望这篇文章能帮助你构建出更强大、更实用的地图应用。
操作步骤回顾
| 步骤 | 操作 |
|---|---|
| 1 | 创建 HTML 文件,设置地图容器和样式,引入必要的脚本库 |
| 2 | 创建 JavaScript 文件,初始化可视化库和地图,加载电子表格数据 |
| 3 | 创建 GoogleMapTraveler 对象,管理地图、标记和线路 |
| 4 | 实现 nextPathPoint 方法,控制地图动画和旅行点切换 |
| 5 | 创建 Traveler 类,实现标记的动画和位置更新 |
| 6 | 创建 Animator 类,处理动画逻辑和回调 |
流程图回顾
graph TD;
A[初始化地图] --> B[加载电子表格数据];
B --> C[创建 GoogleMapTraveler 对象];
C --> D[设置地图视图];
D --> E[添加新点到路径数组];
E --> F[动画当前点到目标点];
F --> G[更新可视化元素];
G --> H{动画完成?};
H -- 是 --> I[显示覆盖层文本];
I --> J[触发下一个旅行点];
H -- 否 --> F;
如果你在构建过程中遇到任何问题或有新的想法,欢迎通过 http://02geek.com 联系我,邮箱是 ben@02geek.com。让我们一起探索更多地图应用的可能性!
超级会员免费看
357

被折叠的 条评论
为什么被折叠?



