使用 Oracle Spatial 创建地图混搭

http://www.oracle.com/technology/global/cn/pub/articles/rubio-mashup.html

 

开发人员:Ajax
  下载
 Oracle 数据库(企业版安装选项)
 示例代码
 
  关键词
spatialajaxwebdev全部
 

 

使用 Oracle Spatial 创建地图混搭


作者:Daniel Rubio

 

了解如何混搭 Oracle Spatial 管理的数据,以及为您的最终用户提供与 Yahoo 和 Google 相同的增强地图绘制 GUI。

2007 年 3 月发布

随着领先的互联网门户提供非常详细的地图绘制服务,最终用户在面对数字地图时的要求也更多,因此,开发这些类型的应用程序的难度也显著增加。

幸运的是,其中许多地图绘制提供商现在都允许开发人员通过 Web 服务重用相同的可视呈现功能,该过程导致了术语“混搭”的出现 — 由可通过 Web 服务利用的应用程序数据组成的界面。正如您将看到的,一系列 Oracle 产品 — 即 Oracle Spatial(Oracle 数据库企业版的一个功能)和Oracle 应用服务器 10g — 为创建复杂的支持 Web 的地图 GUI 或地图混搭提供了理想的构建块。

Oracle Spatial 基础

空间数据的本性产生了以该信息的原始形式进行存储和操作的独特需要。就像 Oracle 数据库能够通过 BLOB 存储二进制文件一样,Oracle Spatial 允许组织在本地维护地理数据的内在价值。

由于大多数地理应用程序中的数据需要满足特定的业务逻辑要求(如定位到特定目的地的最短路线,或者计算某些坐标之间的界限区域),因此,以最高精度维护执行这些计算或查询所需的属性很关键。为此,Oracle Spatial 提供了一系列方法、操作符、函数和过程。

表 1 列出了与 Oracle Spatial 有关的一系列元素。

元素用 于
SDO_GEOMETRY

 

- 空间对象的地理描述
- 包括一个 2D/3D 坐标、直线、曲线或多边形
SDO_FILTER

 

选择 在给定的关注区域内交叉的空间对象

 

SDO_NN

 

计算 两个几何对象之间的距离

 

SDO_GEOM.SDO_UNION

 

返回 作为两个几何对象的 拓扑并(OR 操作)的几何对象

 

SDO_UTIL.GET_VERTICES

 

返回 一个包含给定输入几何顶点的坐标 的表

 

该表仅包含 Oracle Spatial 提供的一部分功能,但这也足够用来阐述在本地存储空间数据的目的,并引导您创建使用其中某些功能的自己的混搭应用程序。

了解 Oracle Spatial 为了获得地理数据而必须提供的功能之后,我们转换一下话题,了解围绕互联网的地图绘制服务可以进行哪些操作来利用这些信息。

基于 Web 的地图绘制服务:Google、Yahoo 和 MSN

到目前为止,大多数地图应用程序都要求您以按需方式存储数百或数千个要聚集的像素化图像以供最终用户查看,也就是说,地图应用程序中的每次移动都需要一个完整的客户端-服务器往返过程以生成一个新创建的图像,该过程不但会导致非常差的用户体验,而且还需要大量的服务器端资源以将用户请求的相应地图像素组合起来。

作为一项增值服务,搜索门户(如 Google、Yahoo 和 MSN)已经在开发新一代地图应用程序上进行了大量的投资。在这些努力的背后,有两种针对开发周期的相对较新的方法:Ajax 和 Web 服务。

Ajax 带给这些地图绘制应用程序的是一个反应非常迅速的浏览体验。虽然底层需要进行客户端-服务器往返以更新地图图像的方法仍然适用,但进行这些更新的方式非常有效。Ajax 允许被称为“即需即用”的调用,这是一个有趣的名称,该调用将使用新获得的服务器端数据更新 Web 页面,而不是让用户体验一个全新的屏幕刷新。在一个地图示例中,实际效果是根据用户的请求逐渐加载像素。

另一方面,如果该功能专门绑定到某个门户站点,则对我们没有太大用处,但这就是 Web 服务引人注意的地方。通过 Web 服务,可以利用这些支持 Ajax 的地图绘制服务,并将它们与您自己的应用程序进行混搭。

现在,让我们来了解混搭背后的技术体系结构。

混搭体系结构:一层、两层和三层

一个混搭应用程序可以有多个层,但所有变形的最终结果肯定会包含与您自己的数据交织在一起的 Web 服务 API 调用。在最简单的形式中,一层混搭是一个静态页面和一系列稍后在用户屏幕上呈现的 Web 服务 API 调用。

在两层的设计中,尽管应用程序的显示仍然由 Web 服务 API 调用所指定,但这些调用的组合关系和顺序将根据用户输入动态决定 — 通过标准的服务器端应用程序(如 JSP 或 servlet)。同样,三层混搭包括使用这个相同的服务器端方法,但通过从数据库提取的信息丰富编程序列。(参见图 1。)

 

图 1
图 1 三层混搭体系结构

 

如您所见,创建有效地图混搭的一个基本步骤是,了解最终将呈现在用户屏幕上的 Web 服务 API 的细节。不要低估要在混搭应用程序中绘制的地理数据的提取和准备过程,但最终结果将反映您对每个服务调用行为的结合和理解程度。

现在,我们已经介绍了基础知识,下面将从实际内容入手。

第一个混搭:使用 Oracle 管理的数据且支持 Ajax 的地图

我们的地图混搭将设计成显示我们的虚构组织 ACME Corp 的美国办事处。该应用程序将允许用户进行初始选择,然后再观察距离第一个位置最近的办事处。地图上的所有点都将存储在一个 Oracle 数据库中,通过 Oracle Spatial 函数计算距离,并通过 Java 应用程序进行提取。将允许后者使用 Ajax 类型的请求(如映射 Web 服务时所使用的请求)。

与您的 Java 应用程序一起的还有一系列 JavaScript 函数,每个函数都具有使用 Google Maps 或 Yahoo Maps 显示地图混搭所必需的 Web 服务 API 调用。图 2 和 3 分别演示了使用 Google 和 Yahoo 映射 Web 服务的地图混搭快照。

 

图 2
图 2
 使用 Google Maps 的地图混搭

 

 

图 3
图 3 使用 Yahoo! 的地图混搭地图

 

大致了解了最终结果之后,我们来感觉一下用于混搭的实际数据库模式和用于访问此类信息的应用程序代码。

准备混搭数据。您的主数据表将包含三个域:市、州以及用于归档每个办公室的经度/维度坐标的第三个域。使用这些选择是出于简单目的,假设经度/维度坐标是地理系统(包括您要使用的地图绘制 Web 服务)的标准,市/州组合充当简单的人标识符。就准备该表的数据而言,由于这将伴随一个相当复杂的过程,因此我们将特别详细地讨论这些步骤。

 

最初的地图绘制数据将从一个纯文本文件导入,首先放到一个临时表中,转换成 Oracle Spatial 格式,最后使用 INSERT INTO 语句放入主数据表中。(请参见示例代码 zip。)清单 1 包含最相关的部分。

清单 1 混搭的数据库模式结构

-- Flat file format
Chicago|IL|41.86|-87.71
Houston|TX|29.84|-95.36
Dallas|TX|32.73|-96.855
Miami|FL|25.72|-80.24

-- Main data table 
CREATE TABLE offices (
  city      VARCHAR2 (30),
  state     VARCHAR2 (30),
  location  SDO_GEOMETRY
);

-- Insert statement from office_load table
INSERT INTO offices
  SELECT city,
         state,
         sdo_geometry(2001,8307,SDO_POINT_TYPE(longitude,latitude,null),
                      null,null)
  FROM offices_load;

注意,纯文本文件位于表单 CITY|STATE|LONGITUDE|LATITUDE 中,您的主表使用 SDO_GEOMETRY 数据格式。在将数据从纯文本文件状态传递到主表的过程中,我们需要构建一个 SDO_GEOMETRY 结构,这就是最后的 INSERT 语句的目的。

这个最后的代码片断从加载了纯文本文件的临时表(即 offices_load)中选择地图点数据,并将其内容插入主 offices 表中。这个 INSERT 语句中唯一看起来可能奇怪的参数是 2001 和 8307 代码,以及 SDO_GEOMETRY 旁边的各种 null。前两个数字是 Oracle Spatial 用来指定插入的数据类型的代码。在这种情况下,2001 表示两个维度的坐标,8307 对应于 WGS 84(世界大地坐标系)格式,数值自然对应于经度/维度数据,而各种 null 语句只是我们的应用程序数据未使用的选项。

准备混搭数据的其他步骤还包括将默认的 USER_SDO_GEOM_METADATA 表更新为包括我们新添加的 offices 表以及为该表创建一个空间索引。如前面所提到的,相应的代码可以在 Resources 部分中获得。

数据模型建好后,您就可以开始通过 Java 应用程序构建空间索引了。清单 2 显示了类似于嵌入 Java 应用程序的空间索引:

清单 2

# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
SELECT  city,
        state,
        v.Y latitude,
        v.X longitude,
        SDO_NN_DISTANCE (1) distance
        FROM
        OFFICES O,
        TABLE(SDO_UTIL.GETVERTICES(O.location)) v
 WHERE
  SDO_NN(
                        O.location,
                        (SELECT location FROM offices WHERE city = 'Houston'),
                        'sdo_num_res=6 unit=mile',1 ) = 'TRUE'
  ORDER by DISTANCE;

-- Results 
CITY            STATE         X           Y          DISTANCE
-------------- ------------   ---------   ---------- ----------
Houston          TX           29.84       -95.36              0
Dallas           TX           32.73       -96.855    217.859943
Topeka           KS           39.02       -95.71     633.08558
Atlanta          GA           33.76       -84.375    700.036941
Albuquerque      NM           35.1        -106.16    726.916231
Omaha            NE           41.27       -95.97     788.754057

清单 2 中的查询获得距离休斯顿市最近的五个办事处,包括市/州对、经度/纬度坐标以及到托管办事处的距离(以英里为单位)。就嵌入到 Java 应用程序的查询而言,要接收要绘制的托管城市和办事处的数量需要对在呈现给最终用户的 HTML 列表中选择的值进行编码。

这些选择列表在写入纯 HTML 标记时将被连接以执行对 Java 应用程序的 Ajax 类型请求。清单 3 包含前述的 HTML 列表和 JavaScript 函数之一,用于执行此类调用。

清单 3

<script type="text/javascript" src="http://www.oracle.com/technology/pub/articles/prototype.js"></script>
<script type="text/javascript">
// Method invoked when user changes letter range
function updateOffices()        {
  // Declare variables for HTML select lists
  var city;
  var nearestNumber;
  // Browser detection IE
  var ie = (document.all) ? true : false;

  // Validate current selection list and assign based on browser
  if(ie) {
    // Assign values
    city = this.city.options[this.city.selectedIndex].text;
    nearestNumber = this.nearestNumber.options[this.nearestNumber.selectedIndex].text;

   } else {
    // Firefox-Mozilla
    // Assign values
    city = $F('city');
    nearestNumber = $F('nearestNumber');
  } // end assignment

  // Define URL for obtaining results
  var url = 'oracle_mashup.jsp';
  // Prepare parameters to send
  var pars = 'city=' + city + '&nearestNumber=' + nearestNumber;
  
  // Make web service Ajax request via prototype helper,
  // upon response, call displayOffices method
  new Ajax.Request( url, { method: 'get', parameters: pars, onComplete: displayOffices});
}
</script> 
<select id="city" οnchange="updateOffices()">
   <option>San Diego  </option>
   <option>Los Angeles </option>
   <option>San Francisco </option>
   <option>Seattle </option>
   <option>Chicago </option>
   <option>Houston </option>
   <option>Dallas </option>
   <option>Miami </option>
   <option>New York </option>
</select>

让我们从清单 3 的底部开始,这里有 HTML 列表。该列表中的每个选项都对应于数据库中归档的城市。但是,更重要的是,请注意与该列表相关的 JavaScript 事件 onchange。这表明任何选择更改都应触发 JavaScript 函数 updateOffices()。

如果移至上部,您将看到 updateOffices() 方法的开始部分。在该特殊的方法中,第一步是提取在 HTML 列表中选择的变量,将每个值分别赋予变量 city 和 nearestNumber。此外,您可以使用 oracle_mashup.jsp 值定义 URL 变量,它对应于 Java 应用程序的实际部署地址。

配置了这些输入变量之后,您就可以执行带外调用来从 Java 应用程序获得城市列表,最后一行说的就是该过程:

new 
Ajax.Request( url, { method: 'get', parameters: pars, onComplete: displayOffices}); 

Ajax.Request 语句是 Prototype(用于 Ajax 设计的框架)中包含的一个 helper 方法,后者作为页面顶部的 JavaScript 库导入。

Ajax.Request 的输入包括一个 URL(用于指明请求地址)、pars(包括所需的托管城市以及最近的办事处的数量)以及 displayOffices(用于定义带外调用完成后要执行的 JavaScript 方法)。关于该最后的过程要注意的最重要的事是完成时调用的方法 displayOffices 在每次用户修改 HTML 列表时会收到一组刷新的地图点,而这也使得它成为所有地图绘制 Web 服务调用的首选。

既然您已经了解了每个地图点的生成方式和位置,那么让我们来进行最后一步——将每个数据集与来自 Google Maps 和 Yahoo Maps 的 Web 服务 API 调用相集成。

与 Google Maps集成。使用 Google Maps API 需要的第一个工具是访问密钥,您可以从以下地址免费获得:www.google.com/apis/maps/signup.html。访问密钥是一个需要放入应用程序的 JavaScript 语句中的字母数字字符串。如果您使用的还是示例代码,请相应地替换该键,因为每个键的创建方式都是在一个唯一的 Web 站点上操作。

与所有其他编程 API类似,Google Maps 有很多类和函数需要大量的时间才能掌握,而我们在这里甚至不会尝试进行解释,因此请记住,我们将使用最少的类和函数来绘制地图点。了解了这些之后,我们来看清单 4 和 5,其中分别显示了生成 Google Maps 界面的 displayOffices 函数版本及其输入的一个示例。注意,该最后的输入对应于从前面提到的带外调用接收的有效载荷。

清单 4 使用 Google Maps API 呈现 GUI 的函数

function displayOffices(request)      {
   // Get office values
   officesToDisplay = request.responseText;
   // Split offices to display and place in array
   var officesSplit = officesToDisplay.split("[");

   if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        // Place center of the map near middle latitude/longitude for U.S
        // Zoom level 4
        map.setCenter(new GLatLng(37.50,-96.94), 4);

       // Loop through office array
        for (i=1; i < officesSplit.length; ++i) {
           // Split once more to get city, state, latitude/longitude, distance
           var officeData = officesSplit[i].split(",");
           var cityName =  officeData[0];
           var stateName =  officeData[1];
           var latitude =  officeData[2];
           var longitude =  officeData[3];           
           var distance =  officeData[4].replace("]","");

           var point = new GLatLng(latitude,longitude);
           map.addOverlay(createMarker(point,cityName,stateName,distance));

       } // end for office array


      } // end if GBrowserIsCompatible

  } // end displayOffices

 // Creates a marker at the given point with the given number label
   function createMarker(point, cityName,stateName,distance) {
    // Check for city origin if distance 0
    if(distance == 0){
      var baseIcon = new GIcon();
      baseIcon.shadow = "http://maps.google.com/mapfiles/kml/pal2/icon10s.png";
      baseIcon.iconSize=new GSize(32,32);
      baseIcon.shadowSize=new GSize(56,32);
      baseIcon.iconAnchor=new GPoint(16,32);
      baseIcon.infoWindowAnchor=new GPoint(16,0);
      var icon = new GIcon(baseIcon);
      icon.image = "http://maps.google.com/mapfiles/kml/pal2/icon10.png";
      var marker = new GMarker(point,icon);
      GEvent.addListener(marker, "click", function() {
      marker.openInfoWindowHtml("" + cityName + "," + stateName + "
"); }); return marker; } else { var marker = new GMarker(point); GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml("" + cityName + "," + stateName + "
Distance to selected office: " + distance + " miles."); }); return marker; } }

清单 5 从 Java 应用程序收到的即需即用的有效载荷

[Dallas,TX,32.73,-96.855,0][Houston,TX,29.84,-95.36,217][Topeka,KS,39.02,95.71,438]
[Albuquerque,NM,35.1,-106.16,558][Omaha,NE,41.27,-95.97,590]
[Denver,CO,39.75,104.98,662] 

displayOffices 首先将其输入拆分开,并将这些值放入一个单独的数组中。我们这样做是由传入的有效载荷格式决定的,这在清单 5 中已经进行了说明。这里,尽管您可以完全控制对 Ajax/Java 应用程序相应中任何可能的有效载荷结构的使用,但是为了简便起见,还是选择该格式。您可能已经非常熟悉 XML 或 JSON(JavaScript 对象表示法)等标准化格式的使用,但是这样将需要处理额外的代码,因此选择简单的括号和逗号。

拆分每个地图点并将其放入 JavaScript 数组后,您将开始使用 Google Maps 函数。首先,检查浏览器环境是否能够显示 Google Maps 的 GBrowserCompatible 对象。对此进行确认后,创建一个要以 HTML <div> id 格式通过名称地图显示的 GMap 对象。如果您密切关注 HTML 应用程序页,您将注意到声明 <div id='map'></div>,这是由这些指令而呈现 GUI 的位置。

然后,我们指定该地图对象的中心和缩放级别,指定大概为美国中心的经度/维度坐标,缩放级别为 4,为我们提供美国的完整的网状地图,所有这些形成我们地图点阵的环状。

在每次循环前面提到的环时,我们再次将每个值拆分为用于市、州、经度、维度和距离的单独的变量,然后通过这些值调用辅助函数 createMarker。该最后一个函数还包含一系列 Google Maps API 对象,它完成以下任务:

  • 生成一个文本窗口,其中包含为每个地图点选定的办事处的市、州和距离
  • 创建一个在托管城市显示的自定义图标
  • 允许单击地图点重新打开其文本窗口

就使用 Google Maps API 而言,这就足够了。回忆一下,图 2 显示了如何呈现该特定的代码,您只需拖放鼠标指针就可以轻松移动生成的地图,每次移动都通过来自 Google 服务器的 Ajax 调用逐渐加载所需的像素。

与 Yahoo Maps 集成

使用 Yahoo Maps API 与使用 Google Maps API 惊人地类似。首先,您需要从这里获得一个免费分发的访问密钥,您需要用一条 JavaScript 语句将其插入到自己的混搭中。

给出我们的设计之后,我们要做的唯一事情就是修改 JavaScript displayOffices 函数以使用 Yahoo Maps API。清单 6 说明了 displayOffices 的 Yahoo Maps 版本。

清单 6 使用 Yahoo Maps API 呈现 GUI 的函数

function displayOffices(request)      {
   // Get office values
   officesToDisplay = request.responseText;
   // Split offices to display and place in array
   var officesSplit = officesToDisplay.split("[");

   // Place center of the map near middle latitude/longitude for U.S
   var usCenter = new YGeoPoint(37.50,-96.94);
   // Create a map object
   var map = new YMap(document.getElementById('map'));
   // Display the map centered to Zoom 14
   map.drawZoomAndCenter(usCenter, 14);

   // Loop through office array
    for (i=1; i < officesSplit.length; ++i) {
        // Split once more to get city, state, latitude/longitude, distance
        var officeData = officesSplit[i].split(",");
        var cityName =  officeData[0];
        var stateName =  officeData[1];
        var latitude =  officeData[2];
        var longitude =  officeData[3];
        var distance =  officeData[4].replace("]","");

        var point = new YGeoPoint(latitude,longitude);
        map.addOverlay(createMarker(point,cityName,stateName,distance));

      } // end for office array

  } // end displayOffices

由于我们使用的是相同的版本而且它收到清单 5 中说明的相同类型的有效载荷,因此逻辑顺序还要与在 Google Maps 版本中使用的相一致。您将发现的唯一差别是关于 API 语法方面的。例如,Google 使用 GMap2 对象来定义顶级地图,而我们的 Yahoo 版本使用的是 YMap。同样,Yahoo 使用 YGeoPoint 对象来定义地图点,而 Google 选择的是 GMarker。

由于这些相似性,因此我们不必再次检查逻辑顺序,只需回忆一下图 3 显示了该代码的 GUI。以类似 Google Maps 的方式,您还可以使用鼠标指针在生成的地图中导航,每次移动通过来自 Yahoo 服务器的 Ajax 调用逐渐生成所需的像素。

结论

尽管地理信息系统仍然围绕相同的原始数据构建作为去年的补充,但是最新的一代已经在图形界面方面取得了相当大的进步,而且其中很多正朝着通过 Web 免费向大众提供的方向发展。

但结果是,要跟上这些更加复杂的实现(企业软件通常是这种情况),同类型的企业开发不必从头开始。正如您所看到的,通过 Web 服务可以使用 Oracle 应用服务器混搭所有 Oracle Spatial 管理的数据,并向您的用户提供大型 Internet 门户提供的同样的增强型地图绘制 GUI。


Daniel Rubio [http://www.webforefront.com/] 是一名在企业软件开发方面具有 10 年以上经验的软件顾问,他最近建立了 Mashup Soft,开始专门研究如何使用 Web 服务进行混搭。他对与软件平台有关的各个领域的技术都非常感兴趣。

将您的意见发送给我们

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值