21、构建交互式谷歌地图实时行程应用

构建交互式谷歌地图实时行程应用

在本文中,我们将详细介绍如何构建一个交互式谷歌地图应用,该应用会根据朋友在南美洲的旅行信息进行动画展示。以下是具体的操作步骤和相关代码实现。

准备工作

此应用的许多元素基于之前的工作,但我们将从头开始构建,不会过多关注已学内容。当用户在世界地图上“旅行”,且数据源中有消息时,地图会淡出并显示消息,之后用户可继续旅行。

操作步骤

我们需要创建两个文件:一个 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;
  1. 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);
}
  1. 文档加载时,触发 onTripDataReady 监听器,创建 GoogleMapTraveler 对象:
function onTripDataReady(response){
  var gmt = new GoogleMapTraveler(response.g.D,map);    
}
  1. 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);
}
  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;
}
  1. 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);
}
  1. 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
}
  1. renderLine 方法更新线路信息:
GoogleMapTraveler.prototype.renderLine = function(){
  this.lines.setPath(this.pathPoints);
  if(this.traveler.isReady)this.traveler.refreshPosition();
}
  1. bind 方法:
GoogleMapTraveler.prototype.bind = function(scope, fun){
   return function () {
        fun.apply(scope, arguments);
    };
}
  1. 创建 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;
  }
  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. 优化动画逻辑 :添加更智能、详细的逻辑,使动画能够处理正负区域的经纬度值。
操作步骤总结
步骤 操作
1 创建 HTML 文件,设置地图容器和样式,引入必要的脚本库
2 创建 JavaScript 文件,初始化可视化库和地图,加载电子表格数据
3 创建 GoogleMapTraveler 对象,管理地图、标记和线路
4 实现 nextPathPoint 方法,控制地图动画和旅行点切换
5 创建 Traveler 类,实现标记的动画和位置更新
6 创建 Animator 类,处理动画逻辑和回调

通过以上步骤,你可以构建一个完整的交互式谷歌地图实时行程应用。希望这篇文章对你有所帮助,如果你有任何问题或建议,欢迎随时交流。你可以通过 http://02geek.com 联系我,邮箱是 ben@02geek.com。

构建交互式谷歌地图实时行程应用

GoogleMapTraveler.prototype.nextPathPoint 方法逻辑深入剖析

GoogleMapTraveler.prototype.nextPathPoint 方法在整个应用逻辑中处于核心地位,下面我们详细分析其内部逻辑。

  1. 设置地图视图
this.setPosition(index - 1, 2);

setPosition 方法会根据传入的当前索引数据重新定位地图和调整缩放级别。它接收第二个参数,可用于对两个点进行平均处理。当我们在两点之间移动时,将该参数设为 2 能使地图位于两点中心。其内部逻辑是循环处理所需数量的项目,以计算出正确的平均位置。

  1. 添加新点到路径数组
this.pathPoints.push(this.getPosition(index - 1, 1)); //add last point again

我们将已在数组中的相同点复制一份添加到 this.pathPoints 数组中,这样新的第二个点就从起始点开始。后续我们可以不断更新数组中的最后一个值,直到其达到真正的下一个目标点。

  1. 创建辅助变量
var currentPoint = this.pathPoints[this.pathPoints.length - 1];
var point = this.getPosition(index, 1);

currentPoint 是对 pathPoints 数组中最后一个点的引用,而 point 是我们动画结束时要到达的新目标点。

  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);

我们使用 Animator 对象对 currentPoint traveler.ll 进行动画处理,使其从当前值过渡到目标值。给 traveler.ll 的动画添加了 0.75 秒的延迟,让动画效果更丰富。

  1. 设置回调函数
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。让我们一起探索更多地图应用的可能性!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值