用 OpenLayers 集合数据

IBMdW 发布于 2011/05/18 18:42
阅读 2K+
收藏 5

OpenLayers 让您可以无缝地使用来源各异的数据,比如来自 Web Map Service (WMS)、Web Feature Service (WFS) 和 Google Maps 的数据。这个库还提供了能够优化地图显示的平铺特性,也包括了对于地图的可用性意义非凡的功能,比如 pan 控件、层交换控件甚至用鼠标滚轮控制缩放的功能。在本文中,了解如何创建一个综合了 Microsoft® Virtual Earth 的卫星影像与 NOAA Weather Radar WMS 的远足跟踪网站,其中使用了 Google Gears 来存储远足数据。

不管您是向客户展示您的服务、显示水质的样例站点还是地理标记您的度假照片,地图都是一种很有趣的显示数据的方式。在网站上使用地图日益常见,并且 人们常常会使用免费的服务,比如 Google Maps、Microsoft Virtual Earth 和 Yahoo! Maps。这三个最主流的服务商提供的工具很有用并且可以让您很快就能拥有一个具有地图功能的网页。不过,除了添加标记或路点这些基本功能外,它们不是很 好定制。

假设您是一名制图师或地理信息系统(GIS)专业人士,或是一名资深的爱好者,想要将不同来源的数据集中在一起。传统的方式是使用 web 地图解决方案,比如商业的 ESRI ArcIMS 或开源的 MapServer 工具。这两个工具都很优秀,均可用来综合不同的数据源并加以显示。但是,它们本身都不允许使用像 Google Maps 和 Virtual Earth 这样的数据源。此外,要通过 MapServer 模板或自己编写的 JavaScript 来构建一个功能完善的地图网站会十分耗时。

OpenLayers 可以解决这些问题。使用 OpenLayers,您可以轻松快速地在您的网站上装配一个功能全面的地图。得到的这个地图可以使用来源各异的数据,包括公共服务器、 MapServer、ArcIMS 服务、ESRI ArcGIS 甚至 NASA World Wind。通过让地图使用 MapServer 驱动的 Web Map Service (WMS) 服务,还可以将您自己的数据集包括进来,比如 shapefile 和 raster 数据。

注意: 如果您尚不熟悉 GIS 术语,比如 projectionshapefile,请参阅 参考资料 部分,内有题为 “Using geospatial data in applications on Linux using GDAL” 的 developerWorks 文章的相关链接。

库架构概览

OpenLayers 是一个独立的 JavaScript 库,不要求服务器端组件或特定的 web 服务器,由一组组织得很好的 JavaScript 类构成。它以 OpenLayers.Map 开始,这是实际的地图对象。各个层类,比如 Layer.ArcIMSLayer.Google,实例化后被添加到地图。Control.Pan 等代表地图控件的类的作用是与地图交互。最后,是用于样式化、数据格式化、坐标存储等的各种实用工具类。完整的文档,请参见 参考资料 部分,内有到 OpenLayers API 文档的链接。

本文将通过构建如图 1 所示的这个示例网站介绍这些类,这个示例网站综合了来自 Google Earth、Virtual Earth、一个本地 shapefile 以及 National Oceanic and Atmospheric Administration North Radar Integrated Display with Geospatial Elements (NOAA RIDGE) 雷达覆盖范围 WMS 的数据。


图 1. 完成后的示例网站 hike.chrismichaelis.com
示例应用程序的屏幕快照,左边显示了具有交互选项的一个菜单,右边显示了具有彩色路径叠加的一个卫星地图

这个示例网站还允许用户在地图上绘制远足路线。用户可以使用 Google Gears 保存浏览器会话间的这些路线,也可以将它们导出到 Keyhole Markup Language(KML)。Google Gears 是一个浏览器插件,扩展了 JavaScript 以便它能代表页面存储与会话间一致的数据。它还能提供后台 JavaScript 执行和其他好处。在本例中,只将其用于存储。更多信息,请参见 参考资料 部分,内有到 Google Gears 开发人员网站的链接。

构建网站

第一步是构建一个包含地图的网站。为了便于组织,本文中的例子为 HTML、CSS 和 JavaScript 内容使用了一个单独的文件。这种结构还有利于未来的维护,也方便日后扩展这个站点。由于 OpenLayers 是纯 JavaScript,所以页面是纯 HTML 且不会使用任何的服务器端语言。这样的结果是可以使用快而瘦的 web 服务器,比如 Nginx 来托管它而不是像 Apache 这样的服务器。由于所有数据均存储于 Google Gears 或拉出自 Internet 资源,所以无需数据库服务器。

jQuery JavaScript 库也包含在这个新站点内。可以使用 jQuery 提供的工具来加速 JavaScript 开发,比如 selectors 支持快速访问文档对象 — 比如,使用 $('.btnActive').removeClass('btnActive'); 可以从所有它出现在的实例中删除 btnActive CSS 类,而无需具体地对由 getElementsByClassName 函数检索到的对象进行循环。同样地, $('#someID') 可通过其 HTML id 属性选择一个元素。

实现 OpenLayers

首先从 OpenLayers 站点(参见 参考资料)下载 OpenLayers 包。将其解压缩到您网站位置内的一个子目录,并将其包含在 HTML 文件内的如下一行代码内:

<script type="text/javascript" 
    src="OpenLayers/OpenLayers.js"></script>'


在 JavaScript 代码内,使用 jQuery "document ready" 函数来设置地图。这个函数晚于 onLoad 事件触发;并不是所有图像和 DOM 元素都可以在 onLoad 事件触发时加载和准备好,但它们可以保证会在 jQuery "ready" 函数击发时加载并准备好。创建一个地图并将其分配到一个变量,如清单 1 所示。此构造函数的第二个参数允许传递选项,比如添加到地图的控件、地图的数据投影和显示投影以及可查看的最大程度。maxExtents 参数限制了查看者所能平移的最大程度,并且必须指定它来避免异常操作,尤其是在使用多个数据层时。如果不指定,可能会出问题,比如根本不能平移或是有额外的层在基层上不能很好地对齐。


清单 1. 在 div mapContainer 中创建一个 OpenLayers 地图
				
$(document).ready(function() {
  var olMap = new OpenLayers.Map('mapContainer', {
    controls: 
      new OpenLayers.Control.MouseDefaults(),
    ],
    projection: new OpenLayers.Projection('EPSG:900913'),
    displayProjection: new OpenLayers.Projection('EPSG:4326'),
    maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34) 
  });
});


OpenLayers 内的一个地图包含了一个基础层和(可选地)几个叠加层(或非基础)层。一个基础层通常是一个像 Google Earth 或 Virtual Earth 这样的图层;一个叠加层则通常是一个用户绘制的矢量层或来自 MapServer 的图层。一次只能激活一个基础层。

从多个资源集合数据

通常,Google Earth 或 Virtual Earth 可用作基础层。这二者以及很多其他的商业的免费数据源都会使用球形的 Mercator 数据投影(由 European Petroleum Survey Group [EPSG] code 900913 标识)。这个投影基于地球的形状是球体模型的前提提供了以米测量的地球数据。在大比例尺时,它看上去还算正确,但如果在本地级别,就不正确了。 OpenLayers 本身并不具备重新投影数据的能力,所以包含在地图内的 所有数据必须在 Spherical Mercator 投影内提供。详细信息,请参见 参考资料 部分,内有到 Spherical Mercator 相关信息的链接。

现在,将基础层添加到地图。由于一次只能激活一个基础层,所以可以随意添加很多的基础层,但默认地先激活第一个基础层。稍后,我们还会创建一种方式来在这些基础层之间切换。清单 2 显示了用来添加 Virtual Earth 和 Google Earth 层的代码。


清单 2. 添加 Virtual Earth 和 Google Earth 基础层
				
// Create the Virtual Earth layer
var veSatLayer = new OpenLayers.Layer.VirtualEarth('Virt. Earth', {
  sphericalMercator: true,
  'type': VEMapStyle.Aerial,
});
olMap.addLayer(veSatLayer);

// Create a Google Earth layer (because it is not first, it is not selected by default)
var googleEarth = new OpenLayers.Layer.Google('Google', {
  sphericalMercator: true,
});
olMap.addLayer(googleEarth);


下一个要添加的层是 NOAA RIDGE WMS。这个服务提供了全美国的反射率值,通常用于天气预报和云层覆盖分析,它为您的远足计划工具提供了方便的天气指标。来自该服务的数据的格式是 NAD83 纬度/经度坐标(EPSG 代码 4269),而这与使用 Spherical Mercator 投影的要求是不兼容的。因此,不能将其直接添加到 OpenLayers。

需要使用 MapServer 执行再投影。有时,您可以请求 Spherical Mercator 投影内的数据,并且数据提供者会为您进行投影 — 但不要依赖于此。毕竟在您自己的服务器上执行再投影会更为合理些,因为那会减少对第三方服务器的负载和需求。第一步是在您的 web 服务器上安装 MapServer(大多数 Linux® 软件存储库都会提供)。接下来,将如下代码:

<900913> +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 
    +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null 
    +no_defs


添加到您 web 服务器上文件 /usr/share/proj/epsg 的底部。这行代码定义了 Spherical Mercator 投影以便 MapServer(及其投影库 PROJ4)能够知道如何将数据转变到这个投影。

接着,创建一个 MapServer 地图文件来定义想要包括的数据(如清单 3 所示)。关键的几点是:将地图文件的投影设为 EPSG:4326 以及定义想要拉入的 WMS 层及其相应的元数据(LAYER 部分)。最后,这个地图文件必须提供一个 WEB 部分,其中 wms_srs 值包括您想要请求的这个投影 — 在本例中为 900913。这个设置列出了允许用户请求的那些投影。对于其他投影的查询会导致错误消息,要求访问者 “请求一个受支持的 SRS”(即一个空间参考系统 — 本质上,就是这个投影)。


清单 3. 为再投影到 Spherical Mercator 而拉入到 NOAA RIDGE 雷达层的 MapServer 地图文件
				
MAP
  IMAGEQUALITY 95
  IMAGETYPE png

  OUTPUTFORMAT
    NAME png
    DRIVER 'GD/PNG'
    MIMETYPE 'image/png'
    IMAGEMODE RGBA
    EXTENSION 'png'
  END

  PROJECTION
    "init=epsg:4326"
  END

  WEB
    IMAGEPATH '/tmp/'
    IMAGEURL '/tmp/'
    METADATA
      "wms_srs" "EPSG:4326 EPSG:4326 EPSG:3857 EPSG:900913"
    END
  END

  LAYER
    NAME noaaradar
    TYPE RASTER
    CONNECTIONTYPE WMS
    CONNECTION "http://gis.srh.noaa.gov/arcgis/services/RIDGERadar/MapServer/WMSServer?" 

    METADATA
      "wms_srs" "EPSG:4326"
      "wms_name" "0"
      "wms_server_version" "1.1.1"
      "wms_format" "image/png"
    END

    PROJECTION
      "init=epsg:4629"
    END
  END
END


最后,可以通过引用新创建的这个地图文件向地图添加 NOAA RIDGE 雷达层,如清单 4 所示。


清单 4. 通过本地 MapServer 实例添加 NOAA RIDGE 雷达 WMS
				
// Add the NOAA RIDGE radar WMS by routing it through our local MapServer instance
// so that it can be re-projected on the fly to Spherical Mercator
var ridgeRadar = new OpenLayers.Layer.WMS('NOAA RidgeRadar',
        'http://hike.chrismichaelis.com/cgi-bin/mapserv.cgi', {
  layers: 'noaaradar',
  map: '/var/www/hike.chrismichaelis.com/mapserver/NOAARidgeRadar.map',
  transparent: true,
  singleTile: true,
  visibility: false,
  srs: 'EPSG:900913'
});
olMap.addLayer(ridgeRadar);


通常,拉入您自己的本地数据会很有用,不管是您下载的数据还是您自己创建的数据。您可以像包括 NOAA RIDGE 雷达层那样通过 MapServer 实现这个目的。结果会如 清单 3 那样创建一个 MapServer 地图文件,只不过 LAYER 部分描述的是一个 shapefile 而不是之前的 WMS,如清单 5 所示。


清单 5. MapServer 地图文件 LAYER 部分用以再投影并服务一个本地 shapefile
				
LAYER
  NAME usaroads
  TYPE LINE
  DATA '/var/www/hike.chrismichaelis.com/mapserver/usaroads/roadtrl020.shp'

  PROJECTION
    "init=epsg:4326"
  END

  CLASS
     NAME 'roadtrl020' 
     STYLE
     WIDTH 2 
     COLOR 255 0 0
  END
END


您还可以使用与 清单 4 内相同的方法向地图添加道路层,只不过需要用适当的值代替 layersmap 选项。此时,您就拥有了一个完全可以发挥作用的地图,可从多个资源拉入数据,包括默认的用鼠标平移以及用鼠标滑轮缩放的功能。不过,您还缺少控制可视层或 绘制远足路线的功能。地图已经可以发挥作用后,现在就可以将地图缩放到您感兴趣的区域,而不是让地图保持缩小于整个美国。实现的方法是缩放到一个整数的缩 放级别(可尝试一个任意的固定比例尺以便观看不同值的效果)并通过纬度和经度定义一个点。之后就可以将坐标从 WGS84 纬度和经度(EPSG:4326)投影(或转变)到地图的当前投影 (Spherical Mercator) 并缩放到它。这个过程如清单 6 所示。


清单 6. 缩放到一个任意的纬度和经度位置
				
// Move to your default location - Pocatello, ID
olMap.zoomTo(10);
var newCenter = new OpenLayers.LonLat(-112.4447222, 42.8713889);
newCenter.transform(new OpenLayers.Projection('EPSG:4326'), olMap.getProjectionObject());
olMap.setCenter(newCenter);

充实功能

没有导航控制,地图就不完美。默认地,OpenLayers 提供基于鼠标滑轮的缩放以及基于鼠标的平移功能。也可以添加地图上的控件 — 比如:

olMap.addControls(new OpenLayers.Control.NavToolbar);


而使用您自己的 HTML 布局来放置和呈现导航控件带来了更大的灵活性,也避免了诸如底层地图数据和导航控件间严重的颜色不协调问题。当比例尺条和文本信息叠加置于地图上(比如雪白的图和白色的比例尺文本)时,这个问题尤其突出。

在本例中,使用简单的 HTML <img> 标记来拉入图标图像,使用 jQuery 将一个匿名的函数追加到该图像。这些函数可恰当地处理地图,调用缩放函数或设置地图的中心,如清单 7 所示。也可以添加控件来显示鼠标的位置和地图比例尺并提供更改可见层的功能(包括选择基础层)— 也如清单 7 所示。对于这些控件,可以提供一个 <div> 参数来指导 OpenLayers 将这些控件呈现到特定的 HTML <div> 元素而非默认的(直接在地图上绘制它)。


清单 7. 添加缩放、平移和层选择控件以及鼠标定位和比例尺指示器
				
// Set up your map GUI controls (vs. using OpenLayers built-in controls)
$('#zoomIn').click(function() {
  olMap.zoomIn();
});
$('#zoomOut').click(function() {
  olMap.zoomOut();
});
$('#moveUp').click(function() {
  // toArray on bounds are in order of left, bottom, right, top
  olMap.setCenter(
    new OpenLayers.LonLat(olMap.getCenter().lon, olMap.getExtent().toArray()[3]));
});
$('#moveDown').click(function() {
  olMap.setCenter(
    new OpenLayers.LonLat(olMap.getCenter().lon, olMap.getExtent().toArray()[1]));
});
$('#moveLeft').click(function() {
  olMap.setCenter(
    new OpenLayers.LonLat(olMap.getExtent().toArray()[0], olMap.getCenter().lat));
});
$('#moveRight').click(function() {
  olMap.setCenter(
    new OpenLayers.LonLat(olMap.getExtent().toArray()[2], olMap.getCenter().lat));
});

// Add the mouse position to your Controls area
olMap.addControl(new OpenLayers.Control.MousePosition({
  'div': OpenLayers.Util.getElement('mousePosition'),
  numDigits: 4
}));

// Add a scale indicator to the Controls area
olMap.addControl(new OpenLayers.Control.ScaleLine({
  'div': OpenLayers.Util.getElement('scaleBar'),
  geoDesic: true
}));

// Add a layer switcher to your Controls area
olMap.addControl(new OpenLayers.Control.LayerSwitcher({
  'div': OpenLayers.Util.getElement('layerSwitcher'),
  roundedCorner: false
}));


高级功能

您可能还会希望您网站的访问者能够在屏幕上绘制远足路线。您可以通过向 HTML 文件添加几个具有图像标记的按钮:pan modedraw modedelete modeSave to KML,来实现这个功能。在 JavaScript 中,首先设置一个包含了远足路线的矢量层并将其添加到地图。接下来,创建两个控件: SelectFeatureDrawFeature。在 SelectFeature 控件上,向 featurehighlighted 事件添加一个函数来删除所选择的特性,它实际上就是一个“删除控件”。 将 box 设为 True,将 hover 设为 False,这会让此控件绘制一个与此线交叉的框以删除它,而不是只在其上悬浮。而绘图控件则更为直白,只需将其添加到地图即可。

接下来,使用 jQuery 向每个新图像 “按钮” 添加一个函数。这些函数的主要目的是对未使用的控件调用 deactivate(),对使用中的控件调用 activate()。如果 SelectFeatureDrawFeature 控件均被去激活,那么地图将转变为平移模式。通过添加和删除 btnActive CSS 类还突出了这些函数内的活动按钮,这可以将边界颜色更改为白色。所有这些步骤均显示在清单 8 中,这之后,地图就达到了理想的功能水平。


清单 8. 添加矢量层并设置函数来与其交互
				
// Create a vector layer that will represent your hiking routes
var hikePaths = new OpenLayers.Layer.Vector("Hike Routes", {
  strategies: [new OpenLayers.Strategy.Fixed()],
  style: {
    strokeWidth: 3,
    strokeColor: "#00eeee"
  }
});
olMap.addLayer(hikePaths);

// Set up your pan and draw mode toggling and edit controls
featureSelect = new OpenLayers.Control.SelectFeature(hikePaths, {
  box: true,
  multiple: true,
  hover: false,
  eventListeners: {
    featurehighlighted: function(feat) {
      // Remove a feature from the layer
      hikePaths.destroyFeatures(hikePaths.selectedFeatures);
    }
  }
});
featureDraw = new OpenLayers.Control.DrawFeature(hikePaths, OpenLayers.Handler.Path);
olMap.addControls([featureSelect, featureDraw]);

$('#drawMode').click(function() {
  featureSelect.deactivate();
  featureDraw.activate();
  $('.btnActive').removeClass('btnActive');
  $('#drawMode').addClass('btnActive');
});
$('#panMode').click(function() {
  featureSelect.deactivate();
  featureDraw.deactivate();
  $('.btnActive').removeClass('btnActive');
  $('#panMode').addClass('btnActive');
});
// Set up your delete button - 
// just turns on selection mode, and on select the listener will destroy the feature
$('#deleteMode').click(function() {
  featureDraw.deactivate();
  featureSelect.activate();
  $('.btnActive').removeClass('btnActive');
  $('#deleteMode').addClass('btnActive');
});


由于很多人都使用 Google Earth 和 Google Maps,Layer 对象的一个方便特性是可以导出成 KML 格式。可以通过 OpenLayers.Format.KML 类,如清单 9 实现这个功能。


清单 9. 将矢量层特性导出为 KML 格式
				
// Set up the Save to KML button
$('#saveKML').click(function() {
  var kmlFormat = new OpenLayers.Format.KML();
  var newWindow = window.open('', 
      'KML Export ' + (new Date()).getTime(), "width=300,height=300");
  newWindow.document.write('<textarea id="kml" style="width: 100%; height: 100%">' +
      kmlFormat.write(hikePaths.features) + '</textarea>');
});


使用 Google Gears 存储数据

使用 Google Gears 让网站可以保存浏览器会话间的远足数据,这就使网站更为方便和无缝地供日常使用。首先, OpenLayers.Protocol.SQL.Gears 类必须被实例化并被给定一个数据库和表名。随后,只需将此矢量层的 protocol 属性设为 Gears 实例。在初始设置阶段,可使用 read() 函数从数据库拉出已存储的特性,而这些特性可随后被传递到矢量层的 addFeatures(),如清单 10 所示。


清单 10. 用 Google Gears 添加矢量层(对比清单 8 — 没有用 Gears 的矢量层)
				
// Create a vector layer that will represent your hiking routes
var hikePaths = new OpenLayers.Layer.Vector("Hike Routes", {
  strategies: [new OpenLayers.Strategy.Fixed()],
  style: {
    strokeWidth: 3,
    strokeColor: "#00eeee"
  }
});

// Because you'll use Google Gears to store hiking routes, set up that protocol:
var gears = new OpenLayers.Protocol.SQL.Gears({
  saveFeatureState: false,
  databaseName: 'hikingPaths',
  tableName: 'hikingPaths'
});
if (!gears.supported()) {
  alert('Warning: Google Gears is not available. You will not be able to use the hike 
         path drawing tools. Visit http://gears.google.com to install Gears.');
}
else {
  hikePaths.protocol = gears;
  var existingGearsData = gears.read();
  if (existingGearsData.features) hikePaths.addFeatures(existingGearsData.features);
}
// Finally, add the layer
olMap.addLayer(hikePaths);


当特性删除后,它们必须从 Gears 数据库以及该层删除,所以将此添加到 SelectFeature 控件的 featurehighlighted 事件(参见清单 11)。同样地,当添加特性时,使用 DrawFeature 控件的 featureAdded 属性。


清单 11. 添加和删除 Gears 数据库内的特性(对比清单 8 — 在没有 Gears 时管理特性)
				
// Set up your pan and draw mode toggling and edit controls
featureSelect = new OpenLayers.Control.SelectFeature(hikePaths, {
  box: true,
  multiple: true,
  hover: false,
  eventListeners: {
    featurehighlighted: function(feat) {
      // Remove it from the Gears database
      if (gears.supported()) gears.delete(hikePaths.selectedFeatures);
      // Remove the actual feature from the layer
      hikePaths.destroyFeatures(hikePaths.selectedFeatures);
    }
  }
});
featureDraw = new OpenLayers.Control.DrawFeature(hikePaths, OpenLayers.Handler.Path, {
  featureAdded: function(feat) {
    // Send to the Gears database
    if (gears.supported()) gears.create(feat);
  }
});
olMap.addControls([featureSelect, featureDraw]);

结束语

虽然这里所构建的网站完全可以发挥作用,但还是有一些增进可以做的:用一个定位器 lap 来更好地显示位置、为 NOAA RIDGE 雷达和其他层用一个图例或键、用一个社交网络关联来共享远足的经历等等。请参见 参考资料 部分,内有到生动示例的链接,也可以参考下载部分以获得全部源代码。如您想编写自己的代码,可以参考 参考资料 部分的这些 OpenLayers 示例。一切顺利!

加载中
返回顶部
顶部