基于开源GIS的电子地图开发

xhk24 贡献于2013-08-23

作者 lchang  创建于2012-11-12 08:31:00   修改者lchang  修改于2012-11-12 08:31:00字数60309

文档摘要:   顾名思义,开源GIS就是开放源代码的GIS软件。开源:软件开发者以某种协议发布某些软件的源代码,并允许他人在遵守该协议的基础之上可以自由下载、修改、使用和散布其源代码。开源协议:GNU GPL、BSD、LGPL、MIT、MS-PL;开源网站:open source GIS、sourceforge、google code、apache、codeplex。<br>    随着商业GIS的发展而发展壮大,功能性强、技术强劲,背后是来自技术狂热者、科研院所和非盈利机构的大力支持。开放、集成、标准和互操作,跨各类操作平台:linux、windows、mac、mobile;跨各类语言:C、C++、Java、C#、Python、Ruby、Perl、JavaScript、ActionScript;各种层次的产品:桌面、服务器、数据库、客户端、中间件、工具集。
关键词:

目录 1 开源GIS概述 2 1.1 什么是开源GIS 2 1.2 为什么需要开源GIS 2 1.3 开源GIS项目简介 3 1.3.1 PostGIS简介 3 1.3.2 GeoServer简介 5 1.3.3 OpenLayers简介 7 1.4 体系结构布署 7 1.4.1 基于J2EE 的WebGIS 体系结构 7 1.4.2 WMS 规范和WFS 规范 8 2 基于PostgreSQL和PostGIS的地图呈现 9 2.1 PostgreSQL和PostGIS的安装 11 2.2 PostGIS中的几何类型 12 2.3 PostGIS中空间信息处理的实现 13 2.4 PostGIS中的常用函数 15 2.5 向PostGIS导入shapefile数据 21 2.6 基于PostGIS的地图呈现实例 21 3 OpenLayers实践 23 3.1 项目介绍 24 3.2 源代码总体结构分析 25 3.3 BaseTypes :定义底层类与定制JS内置类 28 3.4 BaseTypes: OpenLayers中定制JavaScript内置类 30 3.5 空间数据的组织与实现 33 3.6 OpenLayers 数据解析—以GML为例 35 3.7 数据渲染分析 37 3.8 地图表现 38 3.9 OpenLayers中的控件 42 3.10 OpenLayers事件机制分析 45 3.11 体系结构 47 3.12 GeoServer自带OpenLayers实例 47 3.13 OpenLayers官网经典例子 57 3.13.1 图层叠加 58 3.13.2 编辑功能 60 3.13.3 书签及样式 63 3.13.4 改变显示内容 64 3.13.5 动画效果 66 3.13.6 获得属性 66 3.13.7 局部放大 67 3.13.8 编辑功能 70 3.13.9 全屏 70 数据来源于百度,网易等论坛 基于开源GIS的电子地图开发 1 开源GIS概述 1.1 什么是开源GIS 顾名思义,开源GIS就是开放源代码的GIS软件。开源:软件开发者以某种协议发布某些软件的源代码,并允许他人在遵守该协议的基础之上可以自由下载、修改、使用和散布其源代码。开源协议:GNU GPL、BSD、LGPL、MIT、MS-PL;开源网站:open source GIS、sourceforge、google code、apache、codeplex。 随着商业GIS的发展而发展壮大,功能性强、技术强劲,背后是来自技术狂热者、科研院所和非盈利机构的大力支持。开放、集成、标准和互操作,跨各类操作平台:linux、windows、mac、mobile;跨各类语言:C、C++、Java、C#、Python、Ruby、Perl、JavaScript、ActionScript;各种层次的产品:桌面、服务器、数据库、客户端、中间件、工具集。 1.2 为什么需要开源GIS 商业软件需要高昂的使用和维护费用,频繁的升级换代和兼容性。其数据格式无法完全的共享和转换,而且商业软件的跨平台支持不够完善。与之对比,开源软件是完全免费使用的,开源软件的发展足以满足常用的需求,提供做种解决方案。 开源GIS项目以及应用领域: 桌面:GRASS、QGIS、UDig 服务器:GeoServer、MapServer(free) 数据库:PostGIS、MySQL Spatial、MS-SQL Spatial 客户端:QGIS、OpenLayers、WorldKit 工具集:JTS(NTS)、GEOS、Shapely、GDAL/OGR 中间件:GeoTools、MapTools 其他:WorldWind、TileCache、Proj4 开源GIS主要应用于:资源管理 (Resource Management) ,资源配置 (Resource Configuration) ,土地信息系统和地籍管理 (Land Information System and Cadastral Applicaiton) ,生态、环境管理与模拟 (Environmental Management and Modeling) 以及分布式地理信息应用 (Distributed Geographic Information Application)等众多领域。 1.3 开源GIS项目简介 开源GIS项目主要有三个方面,即: ·数据库:PostGIS ·服务器:GeoServer ·客户端(B/S):OpenLayers 1.3.1 PostGIS简介 PostGIS是加拿大Refractions公司支持的开源项目,它为开源数据库PostgreSQL提供了空间支持。PostGIS安装后,Postgre SQL中出现一个模版数据库,新建空间数据库时只需以PostGIS为模版即可。PostGIS在SQL级别上实现了基本的空间运算功能。另外绝大多数开源GIS软件(即使是不严格遵守OpenGIS标准的)都支持PostGIS数据表的直接载入,读写等功能。毋庸置疑,PostGIS 是OpenGIS数据源最佳实现。 图 1 PostGIS数据 PostGIS安装和使用: 首先下载(http://www.postgresql.org/)并安装PostgreSQL8.x版本,目前最新的为8.4。安装完毕后设置默认的用户名和密码,添加此用户。然后打开Application Stack Builder选择需要安装的插件,里面选中PostGIS1.5 for PostgreSQL 8.x,选择合适的版本。下载后自动安装。安装后可以看到一个模板数据库和一个默认的空间数据库,之后可以根据模板数据库添加一个空间数据库来使用,里面实现的大量的空间分析和空间应用的函数。同时包含一个shapefile dbf input loader工具。再登录到PostgreSQL中可以进行空间数据的增删改查。 PostGIS 中的几何数据类型 1)OGC 的WKB 和WKT 格式 PostGIS 支持所有OGC 规范的“Simple Features”类型。OGC 定义了两种描述几何对象的格式,WKB(Well-Known Binary)和WKT(Well-Known Text)。WKT 是以文本形式描述,WKB 是以二进制形式描述。使用WKB 和WKT 能够很好的与其他系统数据交换,目前大部分支持空间数据存储的数据库构造空间数据都采用这两种方式。支持的几何对象在Simple Features Geometries 1.0 中包括:Point,Line String,Polygon,Multi-Polygon,Multi Line String,Multi Point,Geometry Collection。 2)EWKT、EWKB 和Canonical 格式 OGC 中定义的WKT 与WKB 只支持二维的几何数据类型,并且不支持空间参考。PostGIS 对OGC 的数据格式进行扩展得到EWKT 和EWKB 格式,主要扩展有3DZ、3DM、4D 坐标和内嵌空间参考支持。每个有效的WKT 和WKB 格式都是有效的EWKT 和EWKB。然而这种数据格式并不稳定,如果与OGC 推出的新数据格式冲突,那么它将来有可能会改变。而目前OGC 的Simple Features1.2 已经推出支持3D,4D 的WKT,WKB 格式,EWKT 与EWKB 的发展方向还需拭目以待。Canonical 格式是一种简单查询结果(没有任何函数调用),并且此格式支持简单的插入、更新和复制,是一种16 进制编码的几何对象。 3)SQL-MM 格式 QL 多媒体及应用包(SQL-MM)格式定义了一些插值曲线,这些插值曲线和EWKT 类似,也支持3DZ、3DM、4D 坐标,但是不支持嵌入空间参考。 PostGIS 中的地理数据类型 在 PostGIS 最近发布的版本1.5.0 中加入了地理数据类型(Geography Type)。这种数据类型直接支持大地坐标(geodetic coordinates),即经纬度。PostGIS 几何数据类型的基础是一个平面,平面上两点之间的最短距离是一条直线。因此,在计算几何图形的面积、距离、长度、交集等操作时可以使用笛卡尔数学计算公式和直线向量。PostGIS地理数据类型的基础是一个球体。球体上两点之间的最短距离是大圆圆弧(great circle arc)。如果要结果更加精确,则需要考虑真实世界的球体形状,这样将使计算变得复杂。因此,基于地理数据类型的功能函数少于基于几何数据类型的功能函数。而且这种数据类型现在还只支持WGS84(SRID (Spatial reference system):4326)的经纬度坐标。PostGIS 地理数据类型现在仅支持最简单的要素:OGC 规范的“Simple Features”类型。在数据格式方面,支持OGC 的WKB 和WKT 格式,也可以使用EWKB 或EWKT 插入数据。 PostGIS 对空间数据的读取 现存的GIS 软件产生的数据格式很多,PostGIS 提供了多种方式支持数据的读取。 1)PSQL 语言 Psql 语言是PostgreSQL 内嵌的一个命令行工具,其语法基本上跟标准的SQL 语法一致,结合标准的SQL 语法和一些PostGIS 的扩展对PostGIS 数据库进行读写操作。 2)使用转换工具 ①PostGIS 自带的转换工具:shp2pgsql、pgsql2shp 可在shapefile 数据与PostGIS 数据库之间转换; ②使用ogr 工具:这个工具PostGIS 自身并没有提供,但它同样是一个开源软件,Ogr 是GDAL的一个组成部分,GDAL 是一个各种GIS 数据格式的转换软件库,ogr 是转换矢量GIS 数据的软件库,目前ogr 所支持的数据格式有以下几种:ESRI Shapefile, MapInfo Tab file, TIGER, s57, DGN, CSV,GML, KML, Interlis, SQLite, ODBC, PostGIS/PostgreSQL, MySQL[11];③使用桌面软件QuantumGIS中的SPIT 插件来将shapefile 读到PostGIS 数据库中。 1.3.2 GeoServer简介 GeoServer 是 OpenGIS Web 服务器规范的 J2EE 实现,利用 GeoServer 可以方便的发布地图数据,允许用户对特征数据进行更新、删除、插入操作,通过 GeoServer 可以比较容易的在用户之间迅速共享空间地理信息。GeoServer 主要特性包括:兼容 WMS 和 WFS 特性;支持 PostGIS 、 Shapefile 、 ArcSDE 、 Oracle 、 VPF 、 MySQL 、 MapInfo ;支持上百种投影;能够将网络地图输出为 jpeg 、 gif 、 png 、 SVG 、 KML 等格式;能够运行在任何基于 J2EE/Servlet 容器之上;嵌入 OpenLayers 支持 AJAX 的地图客端;除此之外还包括许多其他的特性。 图 2 GeoServer功能 图 3 GeoServer界面图 客户端Open layers的使用及开发方法 GeoServer中集成了Openlayers,也可以使用单独下载的OpenLayers软件包。在默认情况下,Openlayers只有基本的缩放(Zoom)、拖动(Pan)功能。如果需要更丰富的服务,可以调用openlayers.layer 的子类完成图层的初始化; 在创建好图层后, 还可以调用Openlayers 提供的openlayers.control类为地图添加一些与用户有互动功能的工具栏或者是“按钮”。GeoServer支持多种客户端,WMS可以返回GeoRSS和KML用来和其他地图服务互通。 GeoServer 服务启动后,在浏览器中输入http://localhost:8080/geoserver/即可以看到操作界面。要完成数据的发布,需要进行四个方面的配置。 Server:设置服务器信息和联系信息,服务信息包括:最大地理要素数限制、是否显示详细异常信息、数字精度、语言编码、日志相关;联系信息包括:单位名称、地址、联系方式等。 WFS:设置WFS 相关信息,包括:是否启用WFS 服务、服务层次(基本服务、事务处理层次、完全服务)以及WFS 服务器描述信息和Test Suites 的使用。 WMS:设置内容包括,是否启用WMS 服务、描述信息、SVG 图形表现形式。 Data:数据配置,有四部分内容:名称空间、数据、要素类、样式。GeoServer 默认可读取的数据有PostGIS、shapefile 等几种格式,对ArcSDE,GML 等格式的支持,可通过插件实现。在本文中,使用PostGIS 加载数据。 1.3.3 OpenLayers简介 OpenLayers是一个用于开发WebGIS客户端的JavaScript包。OpenLayers符合行业标准,比如 OpenGIS的WMS和WFS规范。OpenLayers采用面向对象方式开发,并使用来自Prototype.js和Rico中的一些组件。OpenLayers 支持的地图来源包括Google Maps、Yahoo! Map、微软Virtual Earth 等。用户还可以用简单的图片地图作为背景图,与其他的图层在OpenLayers 中进行叠加。OpenLayers 支持Open GIS 协会制定的WMS和WFS等网络服务规范,可以通过远程服务的方式,将以OGC 服务形式发布的地图数据加载到基于浏览器的OpenLayers 客户端中进行显示。OpenLayers 可以在浏览器中实现地图浏览的基本效果,比如放大、缩小、平移等常用操作之外,还可以进行选取面、选取线、要素选择、图层叠加等不同的操作。可以对已有的OpenLayers 操作和数据支持类型进行扩充,为其赋予更多的功能。可以为OpenLayers 添加网络处理服务WPS 的操作接口,从而利用已有的空间分析处理服务来对加载的地理空间数据进行计算。 OpenLayers安装和使用: 首先下载(http://openlayers.org/)压缩包,通过Web服务器发布。Builder文件夹用来打包压缩所有的js文件,doc文件夹存放API文档,examples文件夹存放所有的例子(重要的学习途径),lib是源文件库,test文件夹是一个测试例子,tools存放用来打包的工具(python)。在浏览器输入发布地址+/examples/xx.html进入需要了解的例子查看即可。 1.4 体系结构布署 1.4.1 基于J2EE 的WebGIS 体系结构 将WebGIS与J2EE相结合,利用J2EE的平台无关性与分布式结构,以EJB(Enterprise JavaBean)封装WebGIS 的应用功能,实现WebGIS 应用层的可移植性。对应于J2EE 从业务逻辑上的划分,将WebGIS 分为3 层:客户层、中间层、数据层。 1)客户层:可以是应用程序、浏览器,本文采用浏览器与OpenLayers 相结合,为用户提供栅格或者矢量地理信息。 2)中间层:包括Web 层、Web 应用服务层。Web 层采用Tomcat 作为Web 容器,在此容器中提供了JSP(JavaServer Pages)以及Servlet 组件,负责客户端与应用服务器的通讯和客户端的请求。 Web 应用服务层是系统的核心,它运行在WebGIS 应用服务器上,由运行在EJB 容器中的实体EJB组件与会话EJB 组件组成。本系统的GIS 服务器采用GeoServer1.6.0,由它来处理各种来自于浏览器或者其他应用程序的WMS 与WFS 请求,完成WebGIS 空间数据访问和复杂的空间任务,并可以通过多种数据源接口直接访问空间数据,将处理的结果以栅格、矢量或者GML 的形式传输到客户端。 3)数据层:空间数据源可以有多种,可以是单独的文件或者是数据库。本文采用了PostGIS 空间数据库存放数据源,数据源可由应用服务器内的EJB 通过JDBC 访问。图 4是系统结构图。 图 4系统结构图 1.4.2 WMS 规范和WFS 规范 OGC 的宗旨是让用户能从任何一个网络、应用程序或计算机平台中,方便地获取地理信息和服务;通过共同的接口规范,让数据、服务提供者、应用系统开发者和信息整合者,能在短时间内花最少的费用,让使用者容易获取、使用数据及服务[5]。在OGC 完成的正式规范中,用于网络客户端与服务器端之间通信的共同接口规范Web 地图服务接口规范(WMS)和Web 要素服务实现规范(WFS)等在不同程度上解决了地理空间数据和服务的互操作问题。 WMS 规范 WMS 是OGC 提出的Open Web Services 规范之一。它利用具有空间地理位置信息的数据制作地图。在WMS 规范中,将地图定义为地理数据可视化表现,通过请求WMS 返回的是地图图像,而不是地理数据。 此规范定义了三个基础性操作协议:GetCapabilities、GetMap、GetFeatureInfo。这些协议共同构成了利用WMS 创建和叠加显示不同来源的远程异构地图服务的基础[6]。 1)GetCapabilities 用来请求获得WMS 的服务级元数据,服务器端返回包括版本信息、服务类型、请求内容等元数据使用XML 形式来表示。 2)GepMap 根据请求内容的不同,返回不同格式的数据。可以返回常用图片格式的栅格地图片段,也可以通过安装第三方插件返回矢量地图供用户浏览。地图的渲染是通过一个样式文件SLD(Styled Layer Descriptor)生成的,此文件用XML 编写。 3) GetFeatureInfo 操作是可选操作,可提供给用户地理要素信息,如用户在客户端点击地图上的某一元素,GetFeatureInfo 操作即可返回该元素的相关属性信息。 WFS 规范 WFS 也是由OGC 提出的Open Web Services 规范。WFS 更透明更开放的提供了网络地图应用。它不像WMS 一样只提供图片给用户,而是以GML 格式把源地理信息数据表现出来,GML 是一种基于XML 的数据格式,它可以完整的再现数据,使得服务器端和客户端能够在要素层面进行“通讯”。这些GML 数据可以下载并可以与其他数据结合做分析,也可以与其他的网络服务相结合,给网络提供更丰富的应用[8]。WFS 可以分为两种服务类型:Basic WFS 与Transaction WFS 服务。BasicWFS 提供了三种操作: 2 基于PostgreSQL和PostGIS的地图呈现 1986年,加州大学伯克利分校的Michael Stonebraker教授领导了Postgres的项目,它是PostgreSQL的前身。随后出现了PostGIS,PostGIS是对象-关系型数据库系统PostgreSQL的一个扩展,它的出现让人们开始重视基于数据库管理系统的空间扩展方式,而且使PostGIS有望成为今后管理空间数据的主流技术。 由于空间数据具有空间位置、非结构化、空间关系、分类编码、海量数据等特征,一般的商用数据库管理系统难以满足要求。 为了提高数据库管理系统(DBMS)对空间数据的管理能力,国内外先后出现过:文件与关系数据库混合管理系统、全关系型空间数据库管理系统、关系型数据库+空间数据引擎、扩展对象关系型数据库管理系统,以及面向对象空间数据库管理系统等多种解决方案。目前,国内外较为流行的主要集中在“关系型数据库+空间数据引擎”、“扩展对象关系型数据库”两方面。 缘起PostgrSQL 1986年,加州大学伯克利分校的Michael Stonebraker教授领导了Postgres的项目,它是PostgreSQL的前身。这个项目的成果非常显著,在现代数据库的许多方面都作出了大量的贡献,如在面向对象的数据库、部分索引技术、规则、过程和数据库扩展方面都取得了显著的成果。同时,Stonebraker将PostgreSQL纳入到BSD版权体系中,使得PostgreSQL在各种科研机构和一些公共服务组织得到了广泛的应用。 在PostgreSQL中已经定义了一些基本的集合实体类型,这些类型包括:点(POINT)、线(LINE)、线段(LSEG)、方形(BOX)、多边形(POLYGON)和圆(CIRCLE)等;另外,PostgreSQL定义了一系列的函数和操作符来实现几何类型的操作和运算;同时,PostgreSQL引入空间数据索引R-tree。 尽管在PostgreSQL提供了上述几项支持空间数据的特性,但其提供的空间特性很难达到GIS的要求,主要表现在:缺乏复杂的空间类型;没有提供空间分析;没有提供投影变换功能。为了使得PostgreSQL更好的提供空间信息服务,PostGIS应运而生。 PostGIS简介 PostGIS是对象关系型数据库系统PostgreSQL的一个扩展,PostGIS提供如下空间信息服务功能:空间对象、空间索引、空间操作函数和空间操作符。同时,PostGIS遵循OpenGIS的规范。 PostGIS的版权被纳入到GNU的GPL中,也就是说任何人可以自由得到PostGIS的源码并对其做研究和改进。正是由于这一点,PostGIS得到了迅速的发展,越来越多的爱好者和研究机构参与到PostGIS的应用开发和完善当中。 PostGIS特性 PostGIS支持所有的空间数据类型,这些类型包括:点(POINT)、线(LINESTRING)、多边形(POLYGON)、多点(MULTIPOINT)、多线(MULTILINESTRING)、多多边形(MULTIPOLYGON)和集合对象集(GEOMETRYCOLLECTION)等。PostGIS支持所有的对象表达方法,比如WKT和WKB。 PostGIS支持所有的数据存取和构造方法,如GeomFromText()、AsBinary(),以及GeometryN()等。 PostGIS提供简单的空间分析函数(如Area和Length)同时也提供其他一些具有复杂分析功能的函数,比如Distance。 PostGIS提供了对于元数据的支持,如GEOMETRY_COLUMNS和SPATIAL_REF_SYS,同时,PostGIS也提供了相应的支持函数,如AddGeometryColumn和DropGeometryColumn。 PostGIS提供了一系列的二元谓词(如Contains、Within、Overlaps和Touches)用于检测空间对象之间的空间关系,同时返回布尔值来表征对象之间符合这个关系。 PostGIS提供了空间操作符(如Union和Difference)用于空间数据操作。比如,Union操作符融合多边形之间的边界。两个交迭的多边形通过Union运算就会形成一个新的多边形,这个新的多边形的边界为两个多边形中最大边界。 PostGIS还提供以下功能: ·数据库坐标变换 数据库中的几何类型可以通过Transform函数从一种投影系变换到另一种投影系中。在OpenGIS中的几何类型都将SRID作为自身结构的一部分,但不知什么原因,在OpenGIS的SFSQL规范中,并没有引入Transform。 ·球体长度运算 存储在普通地理坐标系中的集合类型如果不进行坐标变换是无法进行程度运算的,OpenGIS所提供的坐标变换使得积累类型的程度计算变成可能。 ·三维的几何类型 SFSQL规范只是针对二维集合类型。OpenGIS提供了对三维集合类型的支持,具体是利用输入的集合类型维数来决定输出的表现方式。例如,即便所有几何对象内部都以三维形式存储,纯粹的二维交叉点通常还是以二维的形式返回。此外,还提供几何对象在不同维度间转换的功能。 ·空间聚集函数 在数据库中,聚集函数是一个执行某一属性列所有数据操作的函数。比如Sum和Average,Sum是求某一关系属性列的数据总和,Average则是求取某一关系属性列的数据平均值。与此对应,空间聚集函数也是执行相同的操作,不过操作的对象是空间数据。例如聚集函数Extent返回一系列要素中的最大的包裹矩形框,如“SELECT EXTENT(GEOM) FROM ROADS”这条SQL语句的执行结果是返回ROADS这个数据表中所有的包裹矩形框。 ·栅格数据类型 PostGIS通过一种新的数据类型片,提供对于大的栅格数据对象的存储。片由以下几个部分组成:包裹矩形框、SRID、类型和一个字节序列。通过将片的大小控制在数据库页值(32×32)以下,使得快速的随即访问变成可能。一般大的图片也是通过将其切成32×32像素的片然后再存储在数据库中的。 2.1 PostgreSQL和PostGIS的安装 PostgreSQL是基于加州大学伯克利分校计算机系写的 POSTGRES(Version 4.2 )软件包开发的对象关系型数据库管理系统(ORDBMS),是开源的,发布在 BSD许可下 。经过二十几年的发展(起始与1986年), PostgreSQL 是世界上可以获得的最先进的开放源码的数据库系统, 它提供了多版本并行控制,支持几乎所有 SQL 构件(包括子查询,事务和用户定 义类型和函数), 并且可以获得非常广阔范围的(开发)语言绑定 (包括 C,C++,Java,perl,tcl,和 python),目前最新的版本是 PostgreSQL8.3.x。 PostgreSQL 使用一种客户端/服务器的模式,即一次 PostgreSQL 会话在,需要执行数据库操作的用户的客户端(前端)应用和数据库服务器程序(postmaster)之间完成。这跟典型的客户端/服务器应用(C/S应用)一样,这些客户端和服务器可以在不同的主机上,它们通过 TCP/IP 网络联接通讯。 两个图形界面工具: pgAdmin III : 图形界面形式的管理工具 PhpPgAdmin :Web-based PostgreSQL 管理工具 PostSQL+PostGIS的安装(windows环境下) 下载 PostgreSQL安装程序,这里8.3.0版或其他。 解压postgresql-8.3.0-1.zip文件后,双击postgresql-8.3.msi安装程序即可。 安装过程大概会遇到: Secongdary Logon服务没有运行 的问题。 在系统服务里,找到 Secongdary Logon服务,启动之,即可。 这样,完成之后, pgAdmin III 同时也会被安装。 对于不使用命令进行操作的朋友来说,图形用户界面是直观方便、容易上手的,其主窗口如下图: 新版本的PostgreSQL在其安装程序中集成了PostGIS,只需要在安装过程中选中PostGIS和pgsql项就可以了。 2.2 PostGIS中的几何类型 PostGIS支持所有OGC规范的“Simple Features”类型,同时在此基础上扩展了对3DZ、3DM、4D坐标的支持。 1. OGC的WKB和WKT格式 OGC定义了两种描述几何对象的格式,分别是WKB(Well-Known Binary)和WKT(Well-Known Text)。 在SQL语句中,用以下的方式可以使用WKT格式定义几何对象: POINT(0 0) ——点 LINESTRING(0 0,1 1,1 2) ——线 POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ——面 MULTIPOINT(0 0,1 2) ——多点 MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4)) ——多线 MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1))) ——多面 GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4))) ——几何集合 以下语句可以使用WKT格式插入一个点要素到一个表中,其中用到的GeomFromText等函数在后面会有详细介绍: INSERT INTO table ( SHAPE, NAME ) VALUES ( GeomFromText('POINT(116.39 39.9)', 4326), '北京'); 2. EWKT、EWKB和Canonical格式 EWKT和EWKB相比OGC WKT和WKB格式主要的扩展有3DZ、3DM、4D坐标和内嵌空间参考支持。 以下以EWKT语句定义了一些几何对象: POINT(0 0 0) ——3D点 SRID=32632;POINT(0 0) ——内嵌空间参考的点 POINTM(0 0 0) ——带M值的点 POINT(0 0 0 0) ——带M值的3D点 SRID=4326;MULTIPOINTM(0 0 0,1 2 1) ——内嵌空间参考的带M值的多点 以下语句可以使用EWKT格式插入一个点要素到一个表中: INSERT INTO table ( SHAPE, NAME ) VALUES ( GeomFromEWKT('SRID=4326;POINTM(116.39 39.9 10)'), '北京' ) Canonical格式是16进制编码的几何对象,直接用SQL语句查询出来的就是这种格式。 3. SQL-MM格式 SQL-MM格式定义了一些插值曲线,这些插值曲线和EWKT有点类似,也支持3DZ、3DM、4D坐标,但是不支持嵌入空间参考。 以下以SQL-MM语句定义了一些插值几何对象: CIRCULARSTRING(0 0, 1 1, 1 0) ——插值圆弧 COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 1 0),(1 0, 0 1)) ——插值复合曲线 CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)) ——曲线多边形 MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4)) ——多曲线 MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11))) ——多曲面 2.3 PostGIS中空间信息处理的实现 1. spatial_ref_sys表 在基于PostGIS模板创建的数据库的public模式下,有一个spatial_ref_sys表,它存放的是OGC规范的空间参考。我们取我们最熟悉的4326参考看一下: 它的srid存放的就是空间参考的Well-Known ID,对这个空间参考的定义主要包括两个字段,srtext存放的是以字符串描述的空间参考,proj4text存放的则是以字符串描述的PROJ.4 投影定义(PostGIS使用PROJ.4实现投影)。 4326空间参考的srtext内容: GEOGCS["WGS 84",DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]], TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]], UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 4326空间参考的proj4text内容: +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs 2. geometry_columns表 geometry_columns表存放了当前数据库中所有几何字段的信息,比如我当前的库里面有两个空间表,在geometry_columns表中就可以找到这两个空间表中几何字段的定义: 其中f_table_schema字段表示的是空间表所在的模式,f_table_name字段表示的是空间表的表名,f_geometry_column字段表示的是该空间表中几何字段的名称,srid字段表示的是该空间表的空间参考。 3. 在PostGIS中创建一个空间表 在PostGIS中创建一个包含几何字段的空间表分为2步:第一步创建一个一般表,第二步给这个表添加几何字段。 以下先在test模式下创建一个名为cities的一般表: create table test.cities (id int4, name varchar(20)) 再给cities添加一个名为shape的几何字段(二维点): select AddGeometryColumn('test', 'cities', 'shape', 4326, 'POINT', 2) 4. PostGIS对几何信息的检查 PostGIS可以检查几何信息的正确性,这主要是通过IsValid函数实现的。 以下语句分辨检查了2个几何对象的正确性,显然,(0, 0)点和(1,1)点可以构成一条线,但是(0, 0)点和(0, 0)点则不能构成,这个语句执行以后的得出的结果是TRUE,FALSE。 select IsValid('LINESTRING(0 0, 1 1)'), IsValid('LINESTRING(0 0,0 0)') 默认PostGIS并不会使用IsValid函数检查用户插入的新数据,因为这会消耗较多的CPU资源(特别是复杂的几何对象)。当你需要使用这个功能的时候,你可以使用以下语句为表新建一个约束: ALTER TABLE cities ADD CONSTRAINT geometry_valid CHECK (IsValid(shape)) 这时当我们往这个表试图插入一个错误的空间对象的时候,会得到一个错误: INSERT INTO test.cities ( shape, name ) VALUES ( GeomFromText('LINESTRING(0 0,0 0)', 4326), '北京'); ERROR: new row for relation "cities" violates check constraint "geometry_valid" SQL 状态: 23514 5. PostGIS中的空间索引 数据库对多维数据的存取有两种索引方案,R-Tree和GiST(Generalized Search Tree),在PostgreSQL中的GiST比R-Tree的健壮性更好,因此PostGIS对空间数据的索引一般采用GiST实现。 以下的语句给sde模式中的cities表添加了一个空间索引shape_index_cities,在pgAdmin中也可以通过图形界面完成相同的功能。 CREATE INDEX shape_index_cities ON sde.cities USING gist (shape); 另外要注意的是,空间索引只有在进行基于边界范围的查询时才起作用,比如“&&”操作。 2.4 PostGIS中的常用函数 以下内容包括比较多的尖括号,发布到blogger的时候会显示不正常,内容太多我也无暇一个个手动改代码,因此如有问题就去参考PostGIS官方文档。 首先需要说明一下,这里许多函数是以ST_[X]yyy形式命名的,事实上很多函数也可以通过xyyy的形式访问,在PostGIS的函数库中我们可以看到这两种函数定义完全一样。 1. OGC标准函数 管理函数: 添加几何字段 AddGeometryColumn(, , , , , ) 删除几何字段 DropGeometryColumn(, , ) 检查数据库几何字段并在geometry_columns中归档 Probe_Geometry_Columns() 给几何对象设置空间参考(在通过一个范围做空间查询时常用) ST_SetSRID(geometry, integer) 几何对象关系函数: 获取两个几何对象间的距离 ST_Distance(geometry, geometry) 如果两个几何对象间距离在给定值范围内,则返回TRUE ST_DWithin(geometry, geometry, float) 判断两个几何对象是否相等 (比如LINESTRING(0 0, 2 2)和LINESTRING(0 0, 1 1, 2 2)是相同的几何对象) ST_Equals(geometry, geometry) 判断两个几何对象是否分离 ST_Disjoint(geometry, geometry) 判断两个几何对象是否相交 ST_Intersects(geometry, geometry) 判断两个几何对象的边缘是否接触 ST_Touches(geometry, geometry) 判断两个几何对象是否互相穿过 ST_Crosses(geometry, geometry) 判断A是否被B包含 ST_Within(geometry A, geometry B) 判断两个几何对象是否是重叠 ST_Overlaps(geometry, geometry) 判断A是否包含B ST_Contains(geometry A, geometry B) 判断A是否覆盖 B ST_Covers(geometry A, geometry B) 判断A是否被B所覆盖 ST_CoveredBy(geometry A, geometry B) 通过DE-9IM 矩阵判断两个几何对象的关系是否成立 ST_Relate(geometry, geometry, intersectionPatternMatrix) 获得两个几何对象的关系(DE-9IM矩阵) ST_Relate(geometry, geometry) 几何对象处理函数: 获取几何对象的中心 ST_Centroid(geometry) 面积量测 ST_Area(geometry) 长度量测 ST_Length(geometry) 返回曲面上的一个点 ST_PointOnSurface(geometry) 获取边界 ST_Boundary(geometry) 获取缓冲后的几何对象 ST_Buffer(geometry, double, [integer]) 获取多几何对象的外接对象 ST_ConvexHull(geometry) 获取两个几何对象相交的部分 ST_Intersection(geometry, geometry) 将经度小于0的值加360使所有经度值在0-360间 ST_Shift_Longitude(geometry) 获取两个几何对象不相交的部分(A、B可互换) ST_SymDifference(geometry A, geometry B) 从A去除和B相交的部分后返回 ST_Difference(geometry A, geometry B) 返回两个几何对象的合并结果 ST_Union(geometry, geometry) 返回一系列几何对象的合并结果 ST_Union(geometry set) 用较少的内存和较长的时间完成合并操作,结果和ST_Union相同 ST_MemUnion(geometry set) 几何对象存取函数: 获取几何对象的WKT描述 ST_AsText(geometry) 获取几何对象的WKB描述 ST_AsBinary(geometry) 获取几何对象的空间参考ID ST_SRID(geometry) 获取几何对象的维数 ST_Dimension(geometry) 获取几何对象的边界范围 ST_Envelope(geometry) 判断几何对象是否为空 ST_IsEmpty(geometry) 判断几何对象是否不包含特殊点(比如自相交) ST_IsSimple(geometry) 判断几何对象是否闭合 ST_IsClosed(geometry) 判断曲线是否闭合并且不包含特殊点 ST_IsRing(geometry) 获取多几何对象中的对象个数 ST_NumGeometries(geometry) 获取多几何对象中第N个对象 ST_GeometryN(geometry,int) 获取几何对象中的点个数 ST_NumPoints(geometry) 获取几何对象的第N个点 ST_PointN(geometry,integer) 获取多边形的外边缘 ST_ExteriorRing(geometry) 获取多边形内边界个数 ST_NumInteriorRings(geometry) 同上 ST_NumInteriorRing(geometry) 获取多边形的第N个内边界 ST_InteriorRingN(geometry,integer) 获取线的终点 ST_EndPoint(geometry) 获取线的起始点 ST_StartPoint(geometry) 获取几何对象的类型 GeometryType(geometry) 类似上,但是不检查M值,即POINTM对象会被判断为point ST_GeometryType(geometry) 获取点的X坐标 ST_X(geometry) 获取点的Y坐标 ST_Y(geometry) 获取点的Z坐标 ST_Z(geometry) 获取点的M值 ST_M(geometry) 几何对象构造函数: 参考语义: Text:WKT WKB:WKB Geom:Geometry M:Multi Bd:BuildArea Coll:Collection ST_GeomFromText(text,[]) ST_PointFromText(text,[]) ST_LineFromText(text,[]) ST_LinestringFromText(text,[]) ST_PolyFromText(text,[]) ST_PolygonFromText(text,[]) ST_MPointFromText(text,[]) ST_MLineFromText(text,[]) ST_MPolyFromText(text,[]) ST_GeomCollFromText(text,[]) ST_GeomFromWKB(bytea,[]) ST_GeometryFromWKB(bytea,[]) ST_PointFromWKB(bytea,[]) ST_LineFromWKB(bytea,[]) ST_LinestringFromWKB(bytea,[]) ST_PolyFromWKB(bytea,[]) ST_PolygonFromWKB(bytea,[]) ST_MPointFromWKB(bytea,[]) ST_MLineFromWKB(bytea,[]) ST_MPolyFromWKB(bytea,[]) ST_GeomCollFromWKB(bytea,[]) ST_BdPolyFromText(text WKT, integer SRID) ST_BdMPolyFromText(text WKT, integer SRID) 2. PostGIS扩展函数 管理函数: 删除一个空间表(包括geometry_columns中的记录) DropGeometryTable([], ) 更新空间表的空间参考 UpdateGeometrySRID([], , , ) 更新空间表的统计信息 update_geometry_stats([, ]) 参考语义: Geos:GEOS库 Jts:JTS库 Proj:PROJ4库 postgis_version() postgis_lib_version() postgis_lib_build_date() postgis_script_build_date() postgis_scripts_installed() postgis_scripts_released() postgis_geos_version() postgis_jts_version() postgis_proj_version() postgis_uses_stats() postgis_full_version() 几何操作符: A范围=B范围 A = B A范围覆盖B范围或A范围在B范围左侧 A &<> B A范围在B范围左侧 A <<>> B A范围覆盖B范围或A范围在B范围下方 A &<| B A范围覆盖B范围或A范围在B范围上方 A |&> B A范围在B范围下方 A <<| B A范围在B范围上方 A |>> B A=B A ~= B A范围被B范围包含 A @ B A范围包含B范围 A ~ B A范围覆盖B范围 A && B 几何量测函数: 量测面积 ST_Area(geometry) 根据经纬度点计算在地球曲面上的距离,单位米,地球半径取值6370986米 ST_distance_sphere(point, point) 类似上,使用指定的地球椭球参数 ST_distance_spheroid(point, point, spheroid) 量测2D对象长度 ST_length2d(geometry) 量测3D对象长度 ST_length3d(geometry) 根据经纬度对象计算在地球曲面上的长度 ST_length_spheroid(geometry,spheroid) ST_length3d_spheroid(geometry,spheroid) 量测两个对象间距离 ST_distance(geometry, geometry) 量测两条线之间的最大距离 ST_max_distance(linestring,linestring) 量测2D对象的周长 ST_perimeter(geometry) ST_perimeter2d(geometry) 量测3D对象的周长 ST_perimeter3d(geometry) 量测两点构成的方位角,单位弧度 ST_azimuth(geometry, geometry) 几何对象输出: 参考语义: NDR:Little Endian XDR:big-endian HEXEWKB:Canonical SVG:SVG 格式 GML:GML 格式 KML:KML 格式 GeoJson:GeoJson 格式 ST_AsBinary(geometry,{'NDR'|'XDR'}) ST_AsEWKT(geometry) ST_AsEWKB(geometry, {'NDR'|'XDR'}) ST_AsHEXEWKB(geometry, {'NDR'|'XDR'}) ST_AsSVG(geometry, [rel], [precision]) ST_AsGML([version], geometry, [precision]) ST_AsKML([version], geometry, [precision]) ST_AsGeoJson([version], geometry, [precision], [options]) 几何对象创建: 参考语义: Dump:转储 ST_GeomFromEWKT(text) ST_GeomFromEWKB(bytea) ST_MakePoint(, , [], []) ST_MakePointM(, , ) ST_MakeBox2D(, ) ST_MakeBox3D(, ) ST_MakeLine(geometry set) ST_MakeLine(geometry, geometry) ST_LineFromMultiPoint(multipoint) ST_MakePolygon(linestring, [linestring[]]) ST_BuildArea(geometry) ST_Polygonize(geometry set) ST_Collect(geometry set) ST_Collect(geometry, geometry) ST_Dump(geometry) ST_DumpRings(geometry) 几何对象编辑: 给几何对象添加一个边界,会使查询速度加快 ST_AddBBOX(geometry) 删除几何对象的边界 ST_DropBBOX(geometry) 添加、删除、设置点 ST_AddPoint(linestring, point, []) ST_RemovePoint(linestring, offset) ST_SetPoint(linestring, N, point) 几何对象类型转换 ST_Force_collection(geometry) ST_Force_2d(geometry) ST_Force_3dz(geometry), ST_Force_3d(geometry), ST_Force_3dm(geometry) ST_Force_4d(geometry) ST_Multi(geometry) 将几何对象转化到指定空间参考 ST_Transform(geometry,integer) 对3D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8) 对2D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8) 对几何对象作偏移 ST_Translate(geometry, float8, float8, float8) 对几何对象作缩放 ST_Scale(geometry, float8, float8, float8) 对3D几何对象作旋转 ST_RotateZ(geometry, float8) ST_RotateX(geometry, float8) ST_RotateY(geometry, float8) 对2D对象作偏移和缩放 ST_TransScale(geometry, float8, float8, float8, float8) 反转 ST_Reverse(geometry) 转化到右手定则 ST_ForceRHR(geometry) 参考IsSimple函数 使用Douglas-Peuker算法 ST_Simplify(geometry, tolerance) ST_SimplifyPreserveTopology(geometry, tolerance) 讲几何对象顶点捕捉到网格 ST_SnapToGrid(geometry, originX, originY, sizeX, sizeY) ST_SnapToGrid(geometry, sizeX, sizeY), ST_SnapToGrid(geometry, size) 第二个参数为点,指定原点坐标 ST_SnapToGrid(geometry, geometry, sizeX, sizeY, sizeZ, sizeM) 分段 ST_Segmentize(geometry, maxlength) 合并为线 ST_LineMerge(geometry) 线性参考: 根据location(0-1)获得该位置的点 ST_line_interpolate_point(linestring, location) 获取一段线 ST_line_substring(linestring, start, end) 根据点获取location(0-1) ST_line_locate_point(LineString, Point) 根据量测值获得几何对象 ST_locate_along_measure(geometry, float8) 根据量测值区间获得几何对象集合 ST_locate_between_measures(geometry, float8, float8) 杂项功能函数: 几何对象的摘要 ST_Summary(geometry) 几何对象的边界 ST_box2d(geometry) ST_box3d(geometry) 多个几何对象的边界 ST_extent(geometry set) 0=2d, 1=3dm, 2=3dz, 3=4d ST_zmflag(geometry) 是否包含Bounding Box ST_HasBBOX(geometry) 几何对象的维数:2、3、4 ST_ndims(geometry) 子对象的个数 ST_nrings(geometry) ST_npoints(geometry) 对象是否验证成功 ST_isvalid(geometry) 扩大几何对象 ST_expand(geometry, float) 计算一个空间表的边界范围 ST_estimated_extent([schema], table, geocolumn) 获得空间参考 ST_find_srid(, , ) 几何对象使用的内存大小,单位byte ST_mem_size(geometry) 点是否在圆上 ST_point_inside_circle(,,,) 获取边界的X、Y、Z ST_XMin(box3d) ST_YMin(box3d) ST_ZMin(box3d) ST_XMax(box3d) ST_YMax(box3d) ST_ZMax(box3d) 构造一个几何对象的数组 ST_Accum(geometry set) 长事务支持: 启用/关闭长事务支持,重复调用无副作用 EnableLongTransactions() DisableLongTransactions() 检查对行的update和delete操作是否已授权 CheckAuth([], , ) 锁定行 LockRow([], , , , []) 解锁行 UnlockRows() 在当前事务中添加授权ID AddAuth() 其它还有SQL-MM和ArcSDE样式的函数支持,可以参考 http://postgis.refractions.net/documentation/manual-1.3/ch06.html#id2750611,这里就不详细列了。 2.5 向PostGIS导入shapefile数据 (1)安装后运行pgadmin III,右击postgresql 8.3(localhost)服务器,连接,这里的密码是你安装时设置的密码,务必牢记. (2)连接后,我们发现postgis安装后自动给我们生成了一个数据库template_postgis,我们将要导入的数据就需要放到这个数据库中. (3)运行命名提示符cmd.exe,将其转向C:\Program Files\PostgreSQL\8.3\bin(或者将cmd.exe复制到该目录下)如下: C:\Program Files\PostgreSQL\8.3\bin> (4)首先将shp生成对应的sql脚本,键入以下字符 C:\Program Files\PostgreSQL\8.3\bin>shp2pgsql -W "GBK" D:\CampusGISProject\new_pku_vector\viwpt.shp viwpt > D:\CampusGISProject\new_pku_vector\viwpt.sql 这里的-W "GBK"代表字符编码的转换, D:\CampusGISProject\new_pku_vector\viwpt.shp则是要生成sql脚本的shp文件.viwpt是创建数据表的表名,>不能少, D:\CampusGISProject\new_pku_vector \viwpt.sql则是要生成SQL文件的绝对目录 生成成功后命令提示符会显示如下: Shapefile type: Point Postgis type: POINT[2] (5)然后我们执行sql语句,执行该SQL语句文件,导入数据到数据库template_postgis中 C:\Program Files\PostgreSQL\8.3\bin>psql -d template_postgis -f D:\CampusGISProject\new_pku_vector\viwpt.sql postgres 其中 template_postgis是数据库名,postgres是该数据库的用户. 执行成功后,刷新该数据库,就可以看到新生成的数据表viwpt, 这样viwpt.shp数据就成功导入到了postgis中了。 2.6 基于PostGIS的地图呈现实例 打开浏览器,通过OpenLayers用户端访问基于PostGIS的地图数据,尝试通过脚本改写PostGIS数据,可以观察到地图呈现的变化情况。 新建文档states10.sql,输入如下代码: SET CLIENT_ENCODING TO UTF8; SET STANDARD_CONFORMING_STRINGS TO ON; BEGIN; CREATE TABLE "states11_map" (gid serial PRIMARY KEY, "state_name" varchar(25), "state_fips" varchar(2), "sub_region" varchar(7), "state_abbr" varchar(2), "land_km" numeric, "water_km" numeric, "persons" numeric, "families" numeric, "houshold" numeric, "male" numeric, "female" numeric, "workers" numeric, "drvalone" numeric, "carpool" numeric, "pubtrans" numeric, "employed" numeric, "unemploy" numeric, "service" numeric, "manual" numeric, "p_male" numeric, "p_female" numeric, "samp_pop" numeric); SELECT AddGeometryColumn('','states11_map','the_geom','-1','MULTIPOLYGON',2); COMMIT; 分别新建文档states20.sql~ states25.sql,每个文档填入各自的地理数据信息。然后新建脚本文件test270.bat,输入如下代码: psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states10.sql" postgres @echo off :hello psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states20.sql" postgres ping -n 5 127.1>nul psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states21.sql" postgres ping -n 5 127.1>nul psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states22.sql" postgres ping -n 5 127.1>nul psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states23.sql" postgres ping -n 5 127.1>nul psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states24.sql" postgres ping -n 5 127.1>nul psql -d template_postgis20 -f "C:\Program Files\PostgreSQL\9.1\bin\states25.sql" postgres ping -n 5 127.1>nul goto hello rem 运行后,打开浏览器,输入http://localhost:8080/geoserver/即可以看到操作界面,可以观察到地图随PostGIS数据的改变动态更新。 网页保持周期性更新,需要加入下面这一行代码: 图 5 基于PostGIS的地图呈现 3 OpenLayers实践 OpenLayers项目分析 3.1 项目介绍 OpenLayers 是由MetaCarta公司开发的,用于WebGIS客户端的JavaScript包,目前的最高版本是2.5 V,通过BSD License 发行。它实现访问地理空间数据的方法都符合行业标准,比如OpenGIS的WMS和WFS规范, OpenLayers采用纯面向对象的JavaScript方式开发,同时借用了Prototype框架和Rico库的一些组件。 采用OpenLayers作为客户端不存在浏览器依赖性。由于OpenLayers采用JavaScript语言实现,而应用于Web浏览器中的DOM(文档对象模型)由JavaScript实现,同时,Web浏览器(比如IE,FF等)都支持DOM 。 OpenLayers APIs采用动态类型脚本语言JavaScript编写,实现了类似与Ajax功能的无刷新更新页面,能够带给用户丰富的桌面体验(它本身就有一个Ajax类,用于实现Ajax功能)。 目前,OpenLayers所能够支持的Format有:XML、GML、GeoJSON、GeoRSS、JSON、KML、WFS、WKT(Well-Known Text)。在OPenlayers.Format名称空间下的各个类里,实现了具体读/写这些Format的解析器。 OpenLayers所能够利用的地图数据资源“丰富多彩”,在这方面提供给拥护较多的选择,比如WMS、WFS、GoogleMap、KaMap、MSVirtualEarth、WorldWind等等。当然,也可以用简单的图片作为源。 第一次使用OpenLayers: 先到它的官方网站http://www.openlayers.org下载他的压缩包,解压后可以看到其中的一些目录和文件,拷贝目录下的OpenLayer.js、根目录下的lib目录、根目录下的img目录到你网站的Scripts目录下(当然,这个只是例子,您网站的目录结构您自己说得算,只要保证OpenLayers.js,/lib,/img在同一目录中即可)。 然后,创建一个index.html作为查看地图的页面,导入OpenLayers.js和你将要创建的js。 我们以加载WMS和GML文件为例。 var lon = 5;    //x-axis coodinate in map units var lat = 40;   //y-axis coordinate in map units var zoom = 5;   //number of zoom levels var map, layer; //声明变量map、layer;等同于 var map = null; var layer = null; map = new OpenLayers.Map('map'); //实例化一个地图类OpenLayers.Map layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} ); //以WMS的格式实例化图层类OpenLayers.Layer map.addLayer(layer); map.zoomToExtent(newOpenLayers.Bounds(-3.922119,44.335327, 4.866943,49.553833)); //在Map对象上加载Layer对象,并用map.zoomToExtent函数使地图合适地显示,主要就是实现图片的范围。使加载的图片可以显示全部需要显示的地图信息,不丢失 map.addLayer(new OpenLayers.Layer.GML("GML", "gml/polygon.xml")); //再在刚加载的WMS文件上,加载GML文件 剩下的工作就是,加上一些控件OpenLayers.Control之类的东西,比如LayerSwitcher等。它们会在地图浏览的“窗口”上增加一些工具栏或是“按钮”,增加互动性和功能性。 3.2 源代码总体结构分析 通过前面的项目介绍,我们大概已经知道Openlayers是什么,能够做什么,有什么意义。接下来我们分析它怎么样,以及怎样实现的等问题。 这个图是从它的文档上截取的,旨在从感官上认识一下OpenLayers的类。下面分别介绍(文档中的类是按字母顺序排列的,也按这个顺序说吧): 我们看到在类的顶层“高高在上”的是OpenLayers,它为整个项目实现提供名称空间(JavaScript语言没有名称空间一说,但是它确实有自己的机制实现类似的功能,后面会说明),它直接拥有常量 VERSION_NUMBER,以标识版本。 Ajax:顾名思义,用于实现Ajax功能,只是OpenLayers的开发者们把它单独写到一个类里了,其中用到了Prototype.js框架里的一些东西。同时,设计的时候也考虑了跨浏览器的问题。 BaseTypes:这里定制了OpenLayers中用到的string,number 和 function。比如,OpenLayers. String. startsWith,用于测试一个字符串是否一以另一个字符串开头;OpenLayers. Number. limitSigDigs,用于限制整数的有效数位;OpenLayers. Function.bind,用于把某一函数绑定于对象等等。 Console:OpenLayers.Console,此名称空间用于调试和把错误等输出到“控制台”上,需要结合使用../Firebug/firebug.js。 Control:我们通常所说的控件类,它提供各种各样的控件,比如上节中说的图层开关LayerSwitcher,编辑工具条EditingToolbar等等。加载控件的例子: class = new OpenLayers.Map('map', { controls: [] }); map.addControl(new OpenLayers.Control.PanZoomBar()); map.addControl(new OpenLayers.Control.MouseToolbar()); Events:用于实现OpenLayers的事件机制。具体来说,OpenLayers中的事件分为两种,一种是浏览器事件,例如mouseup,mousedown之类的;另外一种是自定义的,如addLayer之类的。OpenLayers中的事件机制是非常值得我们学习的,后面将具体讨论。 Feature:我们知道:Feature是geography 和attributes的集合。在OpenLayers中,特别地OpenLayers.Feature 类由一个Feature和一个lonlat组成。 OpenLayers.Feature.WFS与OpenLayers.Feature.Vector继承于它。 Format:此类用于读/写各种格式的数据,它的子类都分别创建了各个格式的解析器。这些格式有:XML、GML、GeoJSON、GeoRSS、JSON、KML、WFS、WKT(Well-Known Text)。 Geometry:怎么翻译呢,几何?是对地理对象的描述。它的子类有Collection、Curve、LinearRing、LineString、MultiLineString、MultiPoint、MultiPolygon、Point、Polygon、Rectangle、Surface,正是这些类的实例,构成了我们看到的地图。需要说明的是,Surface 类暂时还没有实现。 Handler:这个类用于处理序列事件,可被激活和取消。同时,它也有命名类似于浏览器事件的方法。当一个handler 被激活,处理事件的方法就会被注册到浏览器监听器listener ,以响应相应的事件;当一个handler被取消,这些方法在事件监听器中也会相应的被取消注册。Handler通过控件control被创建,而control通过icon表现。 Icon:在计算机屏幕上以图标的形式呈现,有url、尺寸size和位置position 3个属性。一般情况,它与 OpenLayers.Marker结合应用,表现为一个Marker。 Layer:图层。 Map:网业中动态地图。它就像容器,可向里面添加图层Layer和控件Control。实际上,单个Map是毫无意义的,正是Layer和Control成就了它。 Marker:它的实例是OpenLayers.LonLat 和OpenLayers.Icon的集合。通俗一点儿说,Icon附上一定的经纬度就是Marker。 它们的组合关系是: Popup:地图上一个小巧的层,实现地图“开关”功能。使用例子: Class = new OpenLayers.Popup("chicken", new OpenLayers.LonLat(5,40), new OpenLayers.Size(200,200),"example popup",true); map.addPopup(popup); Renderer:渲染类。在OpenLayers中,渲染功能是作为矢量图层的一个属性存在的,我们称之为渲染器,矢量图层就是通过这个渲染器提供的方法将矢量数据显示出来。以SVG和VML为例,继承关系是这样的: 至于OpenLayers. Renderer. Elements为什么要存在,以及它的渲染机制,后面会说。 Tile:设计这个类用于指明单个“瓦片”Tile,或者更小的分辨率。Tiles存储它们自身的信息,比如url和size等。它的类继承关系如下: Util:“跑龙套”的类。 写到这里,可以看到OpenLayers 的类缠绕的挺麻烦的,接下来的文章将从代码部分分析更细部的东西。 3.3 BaseTypes :定义底层类与定制JS内置类 先说基类型BaseTypes下,OpenLyers构建的“自己”的类。它们分别是:OpenLayers.LonLat、OpenLayers.Pixel、OpenLayers.Size、OpenLayers.Element、OpenLayers.Bounds和OpenLayers.Class。下面分别介绍: OpenLayers. LonLat:经纬度类,其实例为地图提供一经度、纬度对,即位置。有两个属性lon(x-axis coodinate )和lat(y-axis coordinate )。这里说明一下,怎么经纬度又与x轴坐标、y轴坐标纠缠在一起?是这样:当地图是在地理坐标投影下,它就是经纬度;不然就是地图上的x/y轴坐标。除构造函数外,实现了五个函数: toShortString:function()  把坐标转换为字符串; clone:function()   复制一个LonLat对象; Add:function(lon,lat)   改变现有地图的位置; return new OpenLayers.LonLat(this.lon + lon, this.lat + lat); equals:function(ll)   判断传入的lon,lat对是否与当前的相等; wrapDateLine:function(maxExtent) 复制下(lon,lat),指定为边界的最大范围。 OpenLayers.Pixel: 像素类,在显示器上以(x,y)坐标的的形式呈现像素位置。有两个属性x坐标、y坐标,提供四个成员函数: clone:function()  拷贝像素; equals:function(px)   判断两像素是否相等; add:function(x,y)   改变(x,y)使其成为新像素; return new OpenLayers.Pixel(this.x + x, this.y + y); offset:function(px)   调用add()使像素位置发生偏移。 newPx = this.add(px.x, px.y); OpenLayers.Size:也有两个属性,宽度width、高度height。实现了两个成员函数:clone:function()和equals:function(sz)不多说了。 OpenLayers.Element:在这个名称空间下,开发者写了好多API,有visible、toggle、hide、show、remove、getHeight、getDimensions和getStyle,以实现元素的显示、隐藏、删除、取得高度,取得范围等功能。以getHeight函数为例我们看看它的代码: /** * APIFunction: getHeight * * Parameters: * element - {DOMElement} * * Returns: * {Integer} The offset height of the element passed in */ getHeight: function(element) { element = OpenLayers.Util.getElement(element); return element.offsetHeight; } 这里涉及到文档对象模型DOM的一些东西,函数本身很简单,最后返回元素的高度。 OpenLayers.Bounds:在这个类中,数据以四个浮点型数left, bottom, right, top 的格式存储,它是一个像盒子一样的范围。它实现了三个描述一个Bound的函数:toString、toArray和toBBOX。其中,toString的代码如下: /** * APIMethod: toString * * Returns: * {String} String representation of bounds object. *          (ex."left-bottom=(5,42) right-top=(10,45)") */ toString:function() { return ( "left-bottom=(" + this.left + "," + this.bottom + ")" + " right-top=(" + this.right + "," + this.top + ")" ); } 结果类似于"left-bottom=(5,42) right-top=(10,45)" 三个Bound数据来源函数:fromString、fromArray和fromSize; 五个获取对象属性的函数:getWidth、getHeight、getSize、getCenterPixel、getCenterLonLat; 余下还有:add:function(x,y),extend:function(object),containsLonLat,containsPixel,contains,intersectsBounds,containsBounds,determineQuadrant,wrapDateLine。以函数extend为例,看看源码。 extend:function(object) { var bounds = null; if (object) { switch(object.CLASS_NAME) { case "OpenLayers.LonLat": bounds = new OpenLayers.Bounds    (object.lon, object.lat, object.lon, object.lat); break; case "OpenLayers.Geometry.Point": bounds = new OpenLayers.Bounds(object.x, object.y,object.x, object.y); break; case "OpenLayers.Bounds": bounds = object; break; } if (bounds) { if ( (this.left == null) || (bounds.left < this.left)) { this.left = bounds.left; } if ( (this.bottom == null) || (bounds.bottom  this.right) ) { this.right = bounds.right; } if ( (this.top == null) || (bounds.top > this. top) ) { this.top = bounds.top;} } } } 可以看出,对Bounds的扩展可以有三种形式:point, lonlat, 或者bounds,计算的条件是零坐标是在屏幕的左上角。 OpenLayers.Class:这个类是OpenLayers 中的“大红人”,只要创建其他类就得用它,同时也实现了多重继承。用法如下: 单继承创建:class = OpenLayers.Class(prototype); 多继承创建:class = OpenLayers.Class(Class1, Class2, prototype); 净说底层类了,对js内置类的扩展下回写。 3.4 BaseTypes: OpenLayers中定制JavaScript内置类 OpenLayers不仅“自己”写了一些底层的类,像上回说的那些都是。同时也定制了一些JS的一些内置类,即对JS内置类的扩展。这个扩展主要包含3类: String,Number,Function,存在于BaseTypes.js文件中。 String: OpenLayers对string类型定制了8个方法,分别是startsWith、contains、trim和camelize;还有另外4个方法:String. startsWith、String. contains、String.trim和String. Camelize,它们将会在3.0Version中被删除,可能是以前版本遗留下来的,这里就不说它们了。 //测试一个字符串是不是以另一个字符串开头的. startsWith: function(str, sub) { return (str.indexOf(sub) == 0); } //测试其中是否包含另一个. contains: function(str, sub) { return (str.indexOf(sub) != -1); } //Removes leading and trailing whitespace characters from a string. //在一个字符串中移除头部或者尾部的特征字符 trim: function(str) { return str.replace(/^\s*(.*?)\s*$/, "$1"); } //Camel-case a hyphenated string. //Ex."chicken-head"becomes"chickenHead", //and"-chicken-head"becomes"ChickenHead". // “骆驼”化带有连字符的字符串。 camelize: function(str) { var oStringList = str.split('-'); var camelizedString = oStringList[0]; for (var i = 1; i < oStringList.length; i++) { var s = oStringList[i]; camelizedString += s.charAt(0).toUpperCase() + s.substring(1); } return camelizedString; } Number: 项目仅对number类型扩展了一个方法OpenLayers. Number. limitSigDigs(还有一个方法Number. limitSigDigs,同样在3.0中会删除)。 //Limit the number of significant digits on an integer. limitSigDigs: function(num, sig) { var fig; if(sig > 0) { fig = parseFloat(num.toPrecision(sig)); } else { fig = 0; } return fig; } Function: 扩展了两个方法bind 和bindAsEventListener(同样存在Function.bind和Function. bindAsEventListener两个被“遗弃”的函数)。 //Bind a function to an object. //Method to easily create closures with'this' altered. bind: function(func, object) { // create a reference to all arguments past the second one var args = Array.prototype.slice.apply(arguments, [2]); return function() { // Push on any additional arguments from the actual function call. // These will come after those sent to the bind call. var newArgs = args.concat( Array.prototype.slice.apply(arguments, [0]) ); return func.apply(object, newArgs); }; } //Bind a function to an object, and configure it to receive the event //object as first parameter when called. bindAsEventListener: function(func, object) { return function(event) { return func.call(object, event || window.event); }; } 这里说说这两个方法。 首先看看bind方法,这是一个能够被Function的实例得到的方法,如下所示: Function.prototype.bind = function() { var _method = this, args = [], object = arguments[0]; for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); return function(moreargs) { for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); return  _method.apply(object, args); } }; _method 代表Function实例自身,bind可接收多个参数,不过它绑定是是第一个参数,该参数是一个function或者是调用环境,后面的都是执行函数的参数。 Function.prototype.bindAsEventListener = function(object) { var  _method = this; return function(event) { return  _method.call(object, event || window.event); } }; 这里只是将object作为_method 引用的环境,就是说现在可以在object对象中这样使用, object. _method (event||window.event)。 也许你注意到了Funtion扩展的两个方法一个用到了call而另一个用的是apply,其实这两个并没有什么太大的区别,只是参数传递的形式不同,如若没有参数要传递,那么这两个是一样的: apply(obj[,argumentsArray]),call(obj[,arg1[,arg2…]])。 3.5 空间数据的组织与实现 提到数据,先思考几个问题: GIS,核心是什么?数据?平台?服务? 空间数据的特征、表达方式? 地理数据的模型(结构)? 在OpenLayers空间数据的实现主要存在OpenLayers. Geometry类及其子类中。我们先看下面图片,表现了这些类的继承关系。从图上可以清楚的看出MultiPoint、Polygon和MultiLineString 这三个类实现了多重继承,即直接继承于Geometry类,又继承于Collection类(为什么要这样实现?)。 OpenLyers对于Geometry对象的组织是这样的,其实最基础的就是点,然后MultiPoint由点构成,继承自Openlayers.Geometry.Collection,而LinearRing,LineString均由Point构成, Polygon由OpenLayers.Geometry.LinearRing构成。OpenLyers在解析数据时候,将所有的面、线包含的点全部都对象化为Openlayers.Geometry.Point。有人测试这里面存在问题:解析矢量数巨慢,甚至在点数多的情况下,会使浏览器“崩溃”掉。想想是有道理的:OpenLyers在解析数据时候,将所有的面、线包含的点全部都对象化为点对象t,并首先将所有的对象读取到内存,得到一个Feature的集合,然后将这个集合提交给渲染器进行渲染。这样渲染起来当然慢了。至于为什么要这样,可能是OpenLayers项目本身在标准上,在框架结构上做的比较好,更细部的东西还得优化呀。可话又说回来,OpenLayers作为一个优秀的开源JS框架,学习借鉴的意义要比应用的意义大吧。 下面以Point和Collection为例来说明其内部实现过程,先看Point。 我们知道一个点就是一个坐标对(x,y)嘛,当然它得有两个属性x,y。在point类里,提供了六个成员函数,分别是clone、distanceTo、equals、move、rotate和resize。看看计算两点距离的函数是怎么写的: distanceTo: function(point) { var distance = 0.0; if ( (this.x != null) && (this.y != null) &&(point != null) && (point.x != null)  && (point.y != null) ) { var dx2 = Math.pow(this.x - point.x, 2); var dy2 = Math.pow(this.y - point.y, 2); distance = Math.sqrt( dx2 + dy2 ); } return distance; } 在collection集合对象中,可以存放同一类型的地理对象,也可以放不同的地理对象。定义了一个属性component ,以数组对象的形式存储组成collection对象的“组件”。别的不说了,看一个获取集合大小的函数getLength: getLength: function() { var length = 0.0; for (var i = 0; i < this.components.length; i++) { length += this.components[i].getLength(); } return length; } 细心的朋友也许会发现,每一个基类都有一个destroy函数。它是OpenLayers实现的垃圾回收机制,以防止内存泄露,优化性能: /* APIMethod: destroy * Destroy this geometry. */ destroy: function () { this.components.length = 0; this.components = null; }。 3.6 OpenLayers 数据解析—以GML为例 前面也提到过,OpenLayers设计是符合标准的,有良好的框架结构和实现机制,非常值得学习。OpenLayers支持的格式比较多,有XML、GML、GeoJSON、GeoRSS、JSON、KML、WFS等。这回主要以GML为例来看OpenLayers 数据的解析过程。 先来了解一下GML: GML (Geography Markup Language)即地理标识语言,它由OGC(开放式地理信息系统协会)于1999年提出,目前版本是3.0。GML是XML在地理空间信息领域的应用。利用GML可以存储和发布各种特征的地理信息,并控制地理信息在Web浏览器中的显示。地理空间互联网络作为全球信息基础架构的一部分,已成为Internet上技术追踪的热点。许多公司和相关研究机构通过Web将众多的地理信息源集成在一起,向用户提供各种层次的应用服务,同时支持本地数据的开发和管理。GML可以在地理空间Web领域完成了同样的任务。GML技术的出现是地理空间数据管理方法的一次飞跃。 我们从总体上来把握一下OpenLayers对于GML数据的解析,首先通过调用得到GML文本数据,然后通过Formate.GML类的read方法来解析这个文本,解析得到Geometry对象,然后Geometry对象用相应的渲染器画出来。其实解析得到还是那些基本的Point呀、LineString呀之类的Geometry对象,就是我们在地图上看到的那些内容。 下面看其实现过程: //read()函数读取数据,获取特征列表 read: function(data) { if(typeof data == "string") { data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); } var featureNodes = this.getElementsByTagNameNS(data.documentElement,this.gmlns,   this.featureName); var features = []; for(var i=0; i 0) { // only deal with first geometry of this type var parser = this.parseGeometry[type.toLowerCase()]; if(parser) { geometry = parser.apply(this, [nodeList[0]]); } else { OpenLayers.Console.error("Unsupported geometry type: " + type); } // stop looking for different geometry types break; } } // construct feature (optionally with attributes) var attributes; if(this.extractAttributes) { attributes = this.parseAttributes(node); } var feature = new OpenLayers.Feature.Vector(geometry, attributes); // assign fid - this can come from a "fid" or "id" attribute var childNode = node.firstChild; var fid; while(childNode) { if(childNode.nodeType == 1) { fid = childNode.getAttribute("fid") || childNode.getAttribute("id"); if(fid) { break; } } childNode = childNode.nextSibling; } feature.fid = fid; return feature; } 剩下就是由具体的函数parse and bulid基本的地理对象(还有Attribute),包括point、multipoint、linestring、multilinestring、polygon、multipolygon等,然后在write出来。 结合前面的“OpenLayers空间数据的组织”,我们可以看到OpenLayers在解析获取GML数据的时候,比如涉及到面、线的时候,总是以点为基础构建的。有的朋友做过测试,说这时候,直接用SVG画出来,性能上会好很多(具体没测试过,不想多说什么)。 3.7 数据渲染分析 实际上,OpenLayers的整个表现过程是这样的:通过调用获取数据,然后各种格式的解析器解析数据,在用所谓的渲染器渲染后加到图层上,最后再结合相应的控件表现出来,成为一幅我们看到的“动态”地图。 这里主要讨论OpenLayers. Renderer这个类及其子类。 Renderer类提供了一些虚方法,以供其子类继承,像setExtent、drawFeature、drawGeometry、eraseFeatures、eraseGeometry等。 Elements继承Renderer,具体实现渲染的类又继承Renderer类。之所以这样设计,是因为不同的矢量格式数据需要共享相应的函数,在Elements这个类中封装一下。这个类的核心是drawGeometry和drawGeometryNode两个函数。其中drawGeometry调用了drawGeometryNode,创建出基本的地理对象。 drawGeometry: function(geometry, style, featureId) { var className = geometry.CLASS_NAME; if ((className == "OpenLayers.Geometry.Collection") || (className == "OpenLayers.Geometry.MultiPoint") || (className == "OpenLayers.Geometry.MultiLineString") || (className == "OpenLayers.Geometry.MultiPolygon"))  { for (var i = 0; i < geometry.components.length; i++)  { this.drawGeometry(geometry.components[i], style, featureId); } return; }; //first we create the basic node and add it to the root var nodeType = this.getNodeType(geometry); var node = this.nodeFactory(geometry.id, nodeType, geometry); node._featureId = featureId; node._geometryClass = geometry.CLASS_NAME; node._style = style; this.root.appendChild(node); //now actually draw the node, and style it this.drawGeometryNode(node, geometry); } 渲染器的继承关系这样的: 具体实现渲染的方法在OpenLayers. Renderer.SVG和OpenLayers. Renderer.VML两个类中实现的,就是实现Elements提供的虚方法,比如drawPoint、drawCircle、drawLineString、drawLinearRing、drawLine、drawPolygon、drawSurface等。以drawCircle为例看看具体的实现过程: drawCircle: function(node, geometry, radius) { if(!isNaN(geometry.x)&& !isNaN(geometry.y)) { var resolution = this.getResolution(); node.style.left = (geometry.x /resolution).toFixed() - radius; node.style.top = (geometry.y /resolution).toFixed() - radius; var diameter = radius * 2; node.style.width = diameter; node.style.height = diameter; } } 3.8 地图表现 一开始看到OpenLayers,就有一个问题。就是它作为WebGIS的前端,通俗地说,是“显示”地图的。那么,它显示的地图是什么,是怎么显示的,又是怎么实现的?——暂且把这个问题叫做地图表现。我觉得最关键的就是Map类,把这个类分析清楚了,问题就解决了一大半了。 前面第一回里说过怎么实例化一个地图,怎么向地图里加图层加控件。其实,地图是这样的,它就像一个容器,可以盛东西。要分析它光理解这些还不够,我们要知道这个容器是怎么做出来的,及具体都有什么功能。 Map类有两个常量:Z_INDEX_BASE和EVENT_TYPES,不说了,可顾名而思其意。再看它定义的一些属性:div(The element that contains the map)、baseLayer (The currently selected base layer)、 events(An events object that handles all events on the map)。是这样,web页的div通过以id或name的形式获得map对象,然后layers和control在加载到map上,表现为地图。顺便说一句,控件control和事件event是相关联的,这以后会说。 OpenLayers.Map类提供了两种实例化方式,举例来看: // create a map with default options in an element with the id "map1" var map = new OpenLayers.Map("map1"); // create a map with non-default options in an element with id "map2" //Optional object with properties to tag onto the map. var options = { maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000), maxResolution: 156543, units: 'meters', projection: "EPSG:41001" }; var map = new OpenLayers.Map("map2", options); OpenLayers.Map类实现的函数APIMethod是分组的,比如 Layer Functions、 Control Functions、 Popup Functions、 Container Div Functions、 Zoom, Center, Pan Functions、 Layer Options、Baselayer Functions、 Zooming Functions、 Translation Functions。其中,最关键的是Layer Functions和Control Functions,因为就是Layer对象和Control对象构成了map的主体。下面从每组函数中挑选出一两个来,看看具体实现过程。 Layer Functions: 就看addLayer函数吧,下面的addLayers就是调用的它,代码如下: addLayer: function (layer) { for(var i=0; i < this.layers.length; i++) { if (this.layers[i] == layer) { var msg = "You tried to add the layer: " + layer.name + " to the map, but it has already been added"; OpenLayers.Console.warn(msg); return false; } } layer.div.style.overflow = ""; this.setLayerZIndex(layer, this.layers.length); if (layer.isFixed) { this.viewPortDiv.appendChild(layer.div); }  else { this.layerContainerDiv.appendChild(layer.div);} this.layers.push(layer); layer.setMap(this); if (layer.isBaseLayer)  { if (this.baseLayer == null) { // set the first baselaye we add as the baselayer this.setBaseLayer(layer); } else { layer.setVisibility(false); } } else { layer.redraw(); } this.events.triggerEvent("addlayer"); } 可以看到其中涉及到layer的一些方法,下一回具体介绍OpenLayers. Layer类。 Control Functions: addControl: function (control, px) { this.controls.push(control); this.addControlToMap(control, px); } 可以看出,添加控件的过程是由controls.Push()和addControlToMap()两个函数共同完成的。 addControlToMap: function (control, px) { // If a control doesn't have a div at this point, it belongs in the viewport. control.outsideViewport = (control.div != null); control.setMap(this); var div = control.draw(px); if (div) { if(!control.outsideViewport) { div.style.zIndex = this.Z_INDEX_BASE['Control'] + this.controls.length; this.viewPortDiv.appendChild( div ); } } } Popup Functions:这组函数和上两组函数相似,是在地图上添加或删除Popup 对象。 Zoom, Center, Pan Functions: //Allows user to pan by a value of screen pixels pan: function(dx, dy) { // getCenter var centerPx = this.getViewPortPxFromLonLat(this.getCenter()); // adjust var newCenterPx = centerPx.add(dx, dy); // only call setCenter if there has been a change if (!newCenterPx.equals(centerPx)) { var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx); this.setCenter(newCenterLonLat); } } Zooming Functions: 这里就看看放大缩小函数吧。 zoomIn: function() { this.zoomTo(this.getZoom() + 1); } zoomOut: function() { this.zoomTo(this.getZoom() - 1); } 显然,zoomIn和zoomOut都使用了getZoom方法,放大就是让zoom加1,缩小减1。 下面介绍组成Map的主体部分OpenLayers. Layer类,先从其实现细节上分析,看它是怎么设计出来的。关于它许许多多的子类,即各种图层,想单独写一篇。 OpenLayers. Layer提供了一个EVENT_TYPES常量,用于支持关于图层的应用事件类型,这些事件"loadstart", "loadend", "loadcancel", "Visibilitychanged"。 它“固有”的3个属性:id,name,div。其中,id和name是layer的身份,在对图层进行操作的时候,就是用它们标志的。至于div,大家都制知道,DOM元素,用于存放图层。 定义的map、event属性,是图层对象对map、event对象的引用;projection属性,设置默认情况下,地图的投影,同时也设置maxExtent, maxResolution, 和units 。 来看看构造函数: * name - {String} The layer name * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, options) { this.addOptions(options); this.name = name; if (this.id == null) { this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); this.div = OpenLayers.Util.createDiv(); this.div.style.width = "100%"; this.div.style.height = "100%"; this.div.id = this.id; this.events = new OpenLayers.Events(this, this.div,this.EVENT_TYPES); } if (this.wrapDateLine) { this.displayOutsideMaxExtent = true; } } OpenLayers中每一个类的构造函数都是以initialize命名的。 再看看其成员函数: destroy函数,相当于析构函数; onMapResize,removeMap 虚函数,提供给子类继承; //移动函数 moveTo:function(bounds, zoomChanged, dragging) { var display = this.visibility; if (!this.isBaseLayer) { display = display && this.inRange; } this.display(display); } 下面一组函数是Baselayer Functions函数,就是layer是Baselayer 的话,所用的函数。 比如,initResolutions、getResolution、getExtent等。 通过这两次的分析,可以发现,Map和Layers的关系:它们是相互引用的。实际上是这样,OpenLayers的Map类主要包含了对每个图层的引用,对每个控件的引用,对事件的引用,对装载容器的引用(其实就是那些map上层的div)以及对pop的引用,而其自身又包含有大量的方法和属性。图层主要包含了对map的引用,对自身div容器的引用以及事件的引用,而图层自身又包含了大量的属性和方法。map引用了layer,而layer又引用了map,这里就直接形成了循环引用关系。 这样的组成和关联关系,每动一下,就会涉及到大量的对象,影响了性能。 3.9 OpenLayers中的控件 OpenLayers中的控件,是通过加载到地图上而起作用的,也算地图表现的一部分。同时,控件需要对地图发生作用,所以每个控件也持有对地图(map对象)的引用。 前面说过,控件是与事件相关联的。具体的说就是控件的实现是依赖于事件绑定的,每个OpenLayers.Control及其子类的实例都会持有一个handler的引用的。 那么,怎么来创建并添加一个控件呢?用下面的语句: //实例化一个控件 var control1 = new OpenLayers.Control({div: myDiv}); //向地图中添加控件 var map = new OpenLayers.Map('map', { controls: [] }); map.addControl(control1 ); 对一些常用的OpenLayers控件,项目本身都封装好了,用下面的语句添加: map.addControl(new OpenLayers.Control.PanZoomBar()); map.addControl(new OpenLayers.Control.MouseToolbar()); map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false})); map.addControl(new OpenLayers.Control.Permalink()); map.addControl(new OpenLayers.Control.Permalink('permalink')); map.addControl(new OpenLayers.Control.MousePosition()); map.addControl(new OpenLayers.Control.OverviewMap()); map.addControl(new OpenLayers.Control.KeyboardDefaults()); 先看看OpenLayers. Control基类的实现过程,再选择几个典型的子类分析一下。 OpenLayers. Control: //设置控件的map属性,即控件所引用的地图 setMap: function(map) { this.map = map; if (this.handler) { this.handler.setMap(map); } } //drew方法,当控件准备显示在地图上是被调用。当然,这个方法只对有图标的控件起 //作用。 draw: function (px) { if (this.div == null) { this.div = OpenLayers.Util.createDiv(); this.div.id = this.id; this.div.className = this.displayClass; } if (px != null) { this.position = px.clone(); } this.moveTo(this.position); return this.div; } 前面说过,OpenLayers.Control及其子类的实例都是会持有一个handler的引用的,因为每个控件起作用时,鼠标事件都是不一样的,这需要动态的绑定和接触绑定。在OpenLayers.Control中是通过active和deactive两个方法实现,就是动态的激活和注销。 //激活方法 activate: function () { if (this.active) { return false; } if (this.handler) { this.handler.activate(); } this.active = true; return true; } //注销方法 deactivate: function () { if (this.active) { if (this.handler) { this.handler.deactivate(); } this.active = false; return true; } return false; } 再来看OpenLayers.Control的子类,即各类“特色”控件。选鹰眼控件OpenLayers. Control. OverviewMap和矢量编辑工具条控件OpenLayers. Control. EditingToolbar来说。 顺便说一句,OpenLayers中的控件有些是需要图标的,像EditingToolbar,有些是不需要的,像OpenLayers. Control. DragPan。 OpenLayers. Control. OverviewMap: “鹰眼”实际上也是地图导航的一种形式,在外部形态上跟图层开关控件有点儿像。 添加鹰眼控件的语句: map.addControl(new OpenLayers.Control.OverviewMap()); 在它实现的成员函数中,draw函数是核心,继承基类OpenLayers.Control,在地图中显示这个控件。 此控件关联了一些浏览器事件,比如 rectMouseDown: function (evt) { if(!OpenLayers.Event.isLeftClick(evt)) return; this.rectDragStart = evt.xy.clone(); this.performedRectDrag = false; OpenLayers.Event.stop(evt); }。 OpenLayers. Control. EditingToolbar: OpenLayers从2.3版就对矢量编辑进行了支持,就是图上右上角几个图标。完成点、线、面的编辑功能。 同样,它也是用drew方法激活: draw: function() { Var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments); this.activateControl(this.controls[0]); return div; } 下面的代码是使用此控件的具体过程: Var  map, layer; map = new OpenLayers.Map( 'map', { controls: [] } ); layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} ); map.addLayer(layer); vlayer = new OpenLayers.Layer.Vector( "Editable" ); map.addLayer(vlayer); map.addControl(new OpenLayers.Control.PanZoomBar()); map.addControl(new OpenLayers.Control.EditingToolbar(vlayer)); map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 3.10 OpenLayers事件机制分析 OpenLayers中的事件封装是其一大亮点,非常值得学习。说到事件机制,在宏观上不得不涉及控件OpenLayers.Control类、OpenLayers. Marker类、OpenLayers.Icon类等。是这样,在外观上控件通过Marker和Icon表现出来,而事件包含在控件之后,用他们自己的话说就是:The controls that wrap handlers define the methods that correspond to these abstract events 。顺便再说一句,控件实现的核心是handler类,每个控件中都包含对handler的引用,通过active和deactive两个方法,实现动态的激活和注销。 OpenLayers中的事件有两种:一种是浏览器事件(比如onclick,onmouseup等),另一种是自定义的事件。自定义的事件像addLayer ,addControl等,不象浏览器事件会绑定相应的dom节点,它是与layer、map等关联的。 OpenLayers中支持的浏览器事件类型有(以常量的形式提供的): BROWSER_EVENTS: [ "mouseover", "mouseout", "mousedown", "mouseup", "mousemove", "click", "dblclick", "resize", "focus", "blur" ] 看看构造函数的的实现过程: initialize: function (object, element, eventTypes, fallThrough) { this.object     = object; this.element    = element; this.eventTypes = eventTypes; this.fallThrough = fallThrough; this.listeners  = {}; // keep a bound copy of handleBrowserEvent() so that we can // pass the same function to both Event.observe() and .stopObserving() this.eventHandler = OpenLayers.Function.bindAsEventListener( this.handleBrowserEvent, this ); // if eventTypes is specified, create a listeners list for each // custom application event. if (this.eventTypes != null) { for (var i = 0; i < this.eventTypes.length; i++) { this.addEventType(this.eventTypes[i]); } } // if a dom element is specified, add a listeners list // for browser events on the element and register them if (this.element != null) { this.attachToElement(element); } } 可以这样理解: initialize(object, element, eventTypes, fallThrough)方法会将以数组eventTypes的每个元素为key建立哈希表listeners,表中每个键对应一个数组。还会给this.eventHandler赋值,它实际只是一个包装了triggerEvent事件触发函数的方法,所有的事件,包括浏览器事件和自定义事件都是通过它来中转的。然后initialize将所有的浏览器事件放入listeners中,并为其绑定相应的dom节点element和this.eventHandler事件处理函数OpenLayers.Event.observe(element, eventType, this.eventHandler),节点上事件触发的时候会把事件传给this.eventHandler,它调用triggerEvent,从而将事件传出来。 来看其他的成员函数: addEventType:Add a new event type to this events object; attachToElement:把浏览器事件关联到相应的DOM元素上; register: Register an event on the events object. register: function (type, obj, func) { if (func != null) { if (obj == null)  { obj = this.object; } var listeners = this.listeners[type]; if (listeners != null) { listeners.push( {obj: obj, func: func} ); } } } 其中,func参数是预先定义的回调函数。 unregister:注销方法; remove:Remove all listeners for a given event type. triggerEvent:Trigger a specified registered event。 3.11 体系结构 一般来说,我们了解一个事物,先是从轮廓、外观结构去认识,然后再从内部更细部的去探究。拿做软件来说吧(就比如OpenLayers),先是在文档设计它的框架体系,有个总体的结构,然后是各个模块的设计,再下来就是具体写代码等。如果要分析一个做好的项目,恰恰与此相反,从具体的代码中分析总结出系统框架(想当初,开发者在开发OpenLayers 的时候,带有项目框架的开发文档会是人手一册的吧)。总结它的框架结构,由于本人水平有限,觉得很难,能写到哪儿算哪儿吧。 这张图基本上把OpenLayers的体系结构勾勒出来了,也就是我们看到的浏览器上地图的内部抽象表示。 图上最底层的是OpenLayers的数据源Image、GML等等,实际上,它们都是OpenLayers.Layer的子孙类。这些数据经过渲染器OpenLayers.Renderer渲染,然后显示在地图的图层Layer上。我们把整个地图看作一个容器,这个地图容器中还有一些特别的层和控件等。除此之外,还有绑定在Map和Layer上的一系列的待请求的事件。 图 1 OpenLayers体系结构 3.12 GeoServer自带OpenLayers实例

WFS Transaction Example

wfs, wfst, wfs-t, advanced

Shows the use of the WFS Transactions (WFS-T).

The WFS protocol allows for creation of new features and reading, updating, or deleting of existing features.

Use the tools to create, modify, and delete (in order from left to right) features. Use the save tool (picture of a disk) to save your changes.

To deactivate "drawing" or "modifying" depress the corresponding button.

See the bak_wfs-protocol-transactions.js source to see how this is done.

location
Click on the map to get feature info
bak_wfs-protocol-transactions.js: var map, wfs, control; var untiled; var tiled; var pureCoverage = false; OpenLayers.ProxyHost = "proxy.cgi?url="; var DeleteFeature = OpenLayers.Class(OpenLayers.Control, { initialize: function(layer, options) { OpenLayers.Control.prototype.initialize.apply(this, [options]); this.layer = layer; this.handler = new OpenLayers.Handler.Feature( this, layer, {click: this.clickFeature} ); }, clickFeature: function(feature) { // if feature doesn't have a fid, destroy it if(feature.fid == undefined) { this.layer.destroyFeatures([feature]); } else { feature.state = OpenLayers.State.DELETE; this.layer.events.triggerEvent("afterfeaturemodified", {feature: feature}); feature.renderIntent = "select"; this.layer.drawFeature(feature); } }, setMap: function(map) { this.handler.setMap(map); OpenLayers.Control.prototype.setMap.apply(this, arguments); }, CLASS_NAME: "OpenLayers.Control.DeleteFeature" }); // sets the HTML provided into the nodelist element function setHTML(response){ document.getElementById('nodelist').innerHTML = response.responseText; }; function init(){ // if this is just a coverage or a group of them, disable a few items, // and default to jpeg format format = 'image/png'; if(pureCoverage) { document.getElementById('filterType').disabled = true; document.getElementById('filter').disabled = true; document.getElementById('antialiasSelector').disabled = true; document.getElementById('updateFilterButton').disabled = true; document.getElementById('resetFilterButton').disabled = true; document.getElementById('jpeg').selected = true; format = "image/jpeg"; } var extent = new OpenLayers.Bounds( 6700000, 5600000, 15600000, 110000); map = new OpenLayers.Map('map', { projection: new OpenLayers.Projection("EPSG:900913"), displayProjection: new OpenLayers.Projection("EPSG:4326"), restrictedExtent: extent, controls: [ new OpenLayers.Control.Navigation(), new OpenLayers.Control.PanZoomBar({}), new OpenLayers.Control.LayerSwitcher({}), new OpenLayers.Control.Permalink(), new OpenLayers.Control.MousePosition({}) ] }); // setup tiled layer tiled = new OpenLayers.Layer.WMS( "mytest:Continents - Tiled", "http://130.140.20.101:8080/geoserver/mytest/wms", { LAYERS: 'mytest:Continents', STYLES: '', format: format, tiled: true, tilesOrigin : map.maxExtent.left + ',' + map.maxExtent.bottom }, { buffer: 0, displayOutsideMaxExtent: true, isBaseLayer: true, yx : {'EPSG:4326' : true} } ); // setup single tiled layer untiled = new OpenLayers.Layer.WMS( "mytest:Continents - Untiled", "http://130.140.20.101:8080/geoserver/mytest/wms", { LAYERS: 'mytest:Continents', STYLES: '', format: format }, { singleTile: true, ratio: 1, isBaseLayer: true, yx : {'EPSG:4326' : true} } ); var saveStrategy = new OpenLayers.Strategy.Save(); wfs = new OpenLayers.Layer.Vector("Editable Features", { strategies: [new OpenLayers.Strategy.BBOX(), saveStrategy], projection: new OpenLayers.Projection("EPSG:4326"), protocol: new OpenLayers.Protocol.WFS({ version: "1.1.0", srsName: "EPSG:4326", url: "http://130.140.20.101:8080/geoserver/mytest/wfs", featureNS : "http://www.opengis.com/mytest", featureType: "mytest", geometryName: "the_geom", schema: "http://130.140.20.101:8080/geoserver/wfs?service=wfs&request=DescribeFeatureType&version=1.1.0&typename=mytest:Continents" }) }); map.addLayers([untiled, tiled, wfs]); map.events.register('click', map, function (e) { document.getElementById('nodelist').innerHTML = "Loading... please wait..."; var params = { REQUEST: "GetFeatureInfo", EXCEPTIONS: "application/vnd.ogc.se_xml", BBOX: map.getExtent().toBBOX(), SERVICE: "WMS", INFO_FORMAT: 'text/html', QUERY_LAYERS: map.layers[0].params.LAYERS, FEATURE_COUNT: 50, Layers: 'mytest:Continents', WIDTH: map.size.w, HEIGHT: map.size.h, format: format, styles: map.layers[0].params.STYLES, srs: map.layers[0].params.SRS}; // handle the wms 1.3 vs wms 1.1 madness if(map.layers[0].params.VERSION == "1.3.0") { params.version = "1.3.0"; params.j = parseInt(e.xy.x); params.i = parseInt(e.xy.y); } else { params.version = "1.1.1"; params.x = parseInt(e.xy.x); params.y = parseInt(e.xy.y); } // merge filters if(map.layers[0].params.CQL_FILTER != null) { params.cql_filter = map.layers[0].params.CQL_FILTER; } if(map.layers[0].params.FILTER != null) { params.filter = map.layers[0].params.FILTER; } if(map.layers[0].params.FEATUREID) { params.featureid = map.layers[0].params.FEATUREID; } OpenLayers.loadURL("http://130.140.20.101:8080/geoserver/mytest/wms", params, this, setHTML, setHTML); OpenLayers.Event.stop(e); }); var panel = new OpenLayers.Control.Panel({ displayClass: 'customEditingToolbar', allowDepress: true }); var draw = new OpenLayers.Control.DrawFeature( wfs, OpenLayers.Handler.Polygon, { title: "Draw Feature", displayClass: "olControlDrawFeaturePolygon", multi: true } ); var edit = new OpenLayers.Control.ModifyFeature(wfs, { title: "Modify Feature", displayClass: "olControlModifyFeature" }); var del = new DeleteFeature(wfs, {title: "Delete Feature"}); var save = new OpenLayers.Control.Button({ title: "Save Changes", trigger: function() { if(edit.feature) { edit.selectControl.unselectAll(); } saveStrategy.save(); }, displayClass: "olControlSaveFeatures" }); panel.addControls([save, del, edit, draw]); map.addControl(panel); map.zoomToExtent(extent, true); } 图 2 访问GeoServer的浏览器 3.13 OpenLayers官网经典例子 案例地址 http://dev.openlayers.org/releases/OpenLayers-2.10/examples/ http://openlayers.org/dev/examples/ Popup http://openlayers.org/dev/examples/sundials.html http://openlayers.org/dev/examples/sundials-spherical-mercator.html http://openlayers.org/dev/examples/select-feature-openpopup.html 3.13.1 图层叠加 http://openlayers.org/dev/examples/layerswitcher.html 这个可以区域染色 http://openlayers.org/dev/examples/wmts-getfeatureinfo.html http://openlayers.org/dev/examples/wmts-capabilities.html http://openlayers.org/dev/examples/wmst.html 3.13.2 编辑功能 http://openlayers.org/dev/examples/wfs-snap-split.html http://openlayers.org/dev/examples/snap-split.html http://openlayers.org/dev/examples/select-feature.html 鹰眼 http://openlayers.org/dev/examples/teleportation.html 3.13.3 书签及样式 http://openlayers.org/dev/examples/styles-unique.html http://openlayers.org/dev/examples/styles-context.html http://openlayers.org/dev/examples/stylemap.html 3.13.4 改变显示内容 http://openlayers.org/dev/examples/strategy-paging.html SLD http://openlayers.org/dev/examples/SLDSelect.html http://openlayers.org/dev/examples/sld.html 3.13.5 动画效果 http://openlayers.org/dev/examples/rotate-features.html 3.13.6 获得属性 http://openlayers.org/dev/examples/osm-layer.html 3.13.7 局部放大 http://openlayers.org/dev/examples/navtoolbar.html 记录上次操作历史 http://openlayers.org/dev/examples/navigation-history.html 鼠标滚轮 http://openlayers.org/dev/examples/mousewheel-interval.html 鼠标坐标 http://openlayers.org/dev/examples/mouse-position.html 3.13.8 编辑功能 http://openlayers.org/dev/examples/mm.html 3.13.9 全屏 http://openlayers.org/dev/examples/fullScreen.html 显示缩放比例 http://openlayers.org/dev/examples/fractional-zoom.html

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享文档获得金币 ] 2 人已下载

下载文档