4.4 一个完整的Google Maps应用
这里给出一个完整的应用Google Maps的例子。这个例子展示的是一辆小汽车在地图上行驶的情况。在实际应用中,经常有这样的需求:通过车载GPS来定位车辆的位置,并实时监控车辆的方位。通常的做法是通过GPS与GPRS结合,将经纬度信息传递到服务器的数据库中,再通过服务器端提取出来之后做成图标在Web地图上显示出来。
本例中没有采用真实的车辆经纬度,而是用Delphi程序模拟显示情况,将经纬度信息不间断地写入数据库中,然后通过Ajax获取数据后展现出来。
4.4.1 开发环境配置
本节将以Google Maps API开发逐步剖析一个实例。本例中,Google Maps作为展示平台,从后台数据库中获取数据,然后将数据点展现在地图上。
本例由于使用了Google Maps API,所以需要连接至Internet,否则程序运行会报错。
软件开发配置如表4-33所示。
表4-33 开发配置
数据库 |
SQL Server 2000 |
开发平台 |
.NET 2.0 |
开发语言 |
C# |
JavaScript | |
HTML | |
Web服务器 |
IIS 5.1 |
4.4.2 数据库设置
用户可以在SQL Server服务器上使用SQL语句来建立数据表,建表的SQL语句如下:
CREATE TABLE [markers] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[name] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[lat] [varchar] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[lng] [varchar] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[mark] [char] (10) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
表中的内容如图4-78所示。
图4-78 数据表内容
数据库文件“gmtest_Data.MDF”和“gmtest_Log.LDF”在本例的Data文件夹中,可通过SQL Server的企业管理器进行附加数据库的操作,数据库sa的密码为“123”。读者也可通过另一个Access文件“gmtest.mdb”来导入,同时要修改SQL Server的sa密码,或者修改本例网页端配置文件“Web.config”中的<appSettings>节,修改方法如下:
<appSettings>
<add key="connStr" value="server=127.0.0.1;database=gmtest;uid=sa;pwd=123;"/>
</appSettings>
在上面的代码中,server表示SQL Server安装的位置,通过IP地址或者名称通道来访问,具体要看SQL Server的SqlExpress设置为IP的方式还是名称通道的方式。关于SqlExpress的设置请读者自行参考相关文档,在此不再赘述。
database指连接的是SQL Server数据库服务器中的哪一个数据库,比如为gmtest。请不要随意修改名称,如果需要重命名,请连同本例.NET程序与Delphi模拟器程序中的代码一同修改。
uid与pwd分别指的是登录数据库的用户名和密码(本例数据库sa的密码为“123”)。
4.4.3 代码分析
整个程序规模不大,构成方式上由“GPS经纬度录入模拟器”和Google Maps Web程序组成,而Web端程序在逻辑上简单地分为以下两个部分。
l 数据管理:在程序中名称为“DataMgn”类库,负责与SQL Server相连,并构造各种查询方法及检索,并返回数据结果。其中db.cs文件为主要逻辑代码文件。
l 网页展现:在程序中名称为http://localhost/gmtest,负责展示网页、展示地图、构造客户端脚本、调用并接受服务器端数据。其中Markers_class.cs为自定义的地标类型文件,gm.js为JavaScript脚本文件,Map.aspx为加载Google Maps的网页。本Web程序采用了AJAX同步方式从服务器端获取数据并展现出来,读者也可采用iframe的方式进行无刷新构造。本例中讲解的AJAX版本目的是为第6章讲解Google Maps与AJAX共同开发做铺垫。
整个Web程序的结构树如图4-79所示。
本例将重点讲解js文件夹中的脚本文件“gm.js”及Map页面的服务器端页面与代码“Map.aspx”、“Map.aspx.cs”,下面依次讲解。
首先看Map.aspx页面代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Map.aspx.cs" Inherits= "Map_cs" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
<!--Google Maps API Key注册脚本-->
<script src="http://maps.google.com/maps?file=api&
v=2&sensor=true_or_ false&key= …… " type="text/javascript">
</script>
<!--脚本引用-->
<script type="text/javascript" language="javascript" src="js/gm.js"></script>
</head>
<body onload="load();">
<form id="form1" runat="server">
<!--GMap对象容器-->
<div id="map" style=" 360px; height: 280px"></div>
<!--开始运行按钮-->
<input id="btn_run" type="button" value=" 开始 " onclick="javascript:getNew PositionFromServer();" />
</form>
</body>
</html>
注意该页面的类名为“Map_cs”,这个类名将在页面服务器端中被AJAX注册,注册代码如下:
protected void Page_Load(object sender, EventArgs e)
{
Utility.RegisterTypeForAjax(typeof(Map_cs));
}
在页面的<body>节中的load函数的代码在gm.js脚本文件中:
function load()
{
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(25.020551,121.525849), 14);
map.addControl(new GLargeMapControl());
}
}
地图首先定位在台北市,缩放级别为14,并且在地图上加入了一个地图控件。对照图4-80,页面上主要有两个可见元素:作为GMap容器的div和【开始】按钮,单击按钮之后触发getNewPositionFromServer()函数,该函数位于gm.js脚本文件内:
function getNewPositionFromServer()
{
MarkerObj = new MarkerCls();
setInterval("Map_cs.getNewRecord(getNewPositionCallBack)",800);
setInterval("refreshMap()",500);
}
function getNewPositionCallBack(res)
{
MarkerObj = res.value;
}
上面的代码包含两个定时器,第一个定时器每0.8秒触发一次“Map_cs.getNewRecord()”服务器端的AJAX方法,而getNewPositionCallBack方法是回调函数,意思为执行过AJAX方法后,再执行该回调函数。回调函数的参数res为调用服务器端AJAX函数后的返回值,如果要取得返回值,则需要调用res.value来得到。如图4-81所示为res.value的返回值。
图4-80 页面地图 图4-81 同步调用的AJAX脚本端返回值
之所以res.value的返回值包括5个值,是因为在页面服务器端AJAX方法中,返回值是自定义的Markers_class类型,该类的定义代码如下:
public class Markers_Class
{
//构造函数
public Markers_Class()
{
m_id = 0;
m_lat ="";
m_lng = "";
m_name = "";
m_mark = "";
}
//私有变量
private int m_id;
private string m_lat;
private string m_lng;
private string m_name;
private string m_mark;
//公共属性
public int Id
{
get { return m_id; }
set { m_id = value; }
}
public string Lat
{
get { return m_lat; }
set { m_lat = value; }
}
public string Lng
{
get { return m_lng; }
set { m_lng = value; }
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
public string Mark
{
get { return m_mark; }
set { m_mark = value; }
}
} //结束定义类型
Markers_Class一共定义了5个属性,没有定义方法,这5个属性与数据表gmtest的字段定义相同,目的是为了在获取SQL查询结果之后,将DataSet转换成较为简单的Markers_Class再传递回客户端,在操作大量数据的时候,这样可以节省时间。下面再讲解负责SQL查询的AJAX服务器端代码:
[AjaxPro.AjaxMethod]
public Markers_Class getNewRecord()
{
DataMgn.ConnDB dbmgn = new DataMgn.ConnDB();
Markers_Class tmp = new Markers_Class();
//执行SQL语句,返回DataSet
DataSet newLatLng = dbmgn.ReturnDataSet(
"SELECT * FROM markers WHERE (id IN (SELECT MAX(id) FROM markers))");
//Markers_Class对象tmp赋值
tmp.Id = (int)newLatLng.Tables[0].Rows[0]["id"];
tmp.Lat = (string)newLatLng.Tables[0].Rows[0]["lat"];
tmp.Lng = (string)newLatLng.Tables[0].Rows[0]["lng"];
tmp.Name = (string)newLatLng.Tables[0].Rows[0]["name"];
tmp.Mark = (string)newLatLng.Tables[0].Rows[0]["mark"];
dbmgn.Close();
//最后整个方法的返回值类型为Markers_Class
return tmp;
}
当页面端的JS脚本调用被AjaxPro注册过的方法时,服务器端的AJAX方法被执行,此时系统会自行判断该调用过程是否为异步调用。如果客户端同步调用,则客户端调用程序会停止在调用点直到取得返回值为止。本例中为同步调用,前面已经提到,要实现网页无刷新显示操作的方式方法很多,AJAX精髓在于异步调用,本例中同步调用采取AJAX方式也是对第6章内容的一个补充与铺垫。
在前面的图4-80中,车上的GPS经纬度信息连续不断地被上传到服务器的数据库中,当有新的数据添加到数据表之时,可通过编写SQL Server触发器来“通知”Web程序,从而触发页面刷新,这个例子仅仅为示例所用。采取的另一种方法:定时获取数据表的最后一条记录,并将其传递到客户端。
这个获取数据的过程由页面脚本控制,在取得数据之后,脚本开始操作GMap对象,将经纬度信息构造出GIcon对象(图标为一辆黄色的小汽车),并加载到地图上,同时清空以前的GOverLay对象。如此一来终端用户看到的则是地图上的一辆小车不断地在地图上以无刷新的方式行驶。而且要不断地移动地图,以小车为地图中心,确保小车不会跑出地图当前视野范围之外。
脚本端实现以上逻辑的代码如下:
function refreshMap()
{
//创建GIcon对象
var v_icon = new GIcon();
v_icon.image = "img/car.ico";
v_icon.iconSize = new GSize(32, 32);
v_icon.iconAnchor = new GPoint(16, 16);
if (MarkerObj != null)
if ((MarkerObj.Lat!== 0) && (MarkerObj.Lng !== 0))
{
var tmpMarker = new GMarker(new GLatLng(MarkerObj.Lat,MarkerObj.Lng),
{icon:v_icon,title:MarkerObj.mark});
//清空地图上的所有GoverLay对象
map.clearOverlays();
//以小车的经纬度为中心,移动地图
map.panTo(new GLatLng(MarkerObj.Lat,MarkerObj.Lng));
map.addOverlay(tmpMarker);
}
}
这里有两个实用的技巧。对于控制小车保持在当前地图视野范围之内,一般可以采用本例中的方法,但是这种方法的缺点是地图的移动过于频繁,而且如果地图中出现多辆汽车就无法根据其中的某一辆进行定位了;还有一种方法是对于小车的经纬度与地图边界进行判断,直到小车快跑出地图边界的时候才移动并缩放地图。
第二种方法是对于绘制点而言的,一般绘制点的方式有以下两种:
l 使用VML绘制。
l 采用Google Maps API进行绘制。
使用VML绘制的优点是快捷、灵活,可以解决某些Google Maps API提供的功能无法实现的需求。但是缺点是VML的边界较难掌控在地图图框的显示范围内,而且对开发者而言也增加了学习VML的负担。当然在网页上绘制点、线、面还有很多其他的方法,本例中采用的仅仅是Google Maps API提供的方法。
本例在执行时,需要同时打开GPS数据模拟器,小车会自动往东北方向前进,如图4-82和图4-84所示。
图4-82 前一时刻车辆位置 图4-83 后一时刻车辆位置