c++ builder和mapobjects实现

387
地理信息系统二次开发实例教程 ——C++ Builder MapObjects 实现 刘小东 编著 北京科海电子出版社

Upload: others

Post on 17-Oct-2021

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程

——C++ Builder和MapObjects实现

刘 光 刘小东 编著

北京科海电子出版社

Page 2: C++ Builder和MapObjects实现

内 容 提 要

本书以“北京市地理信息公众查询系统”为例,按照软件工程的思想与要求,介绍如何运

用 C++ Builder 语言及地理信息系统二次开发组件 MapObjects,进行地理信息系统(GIS)的

二次开发。

全书共分 8 章,首先概述了地理信息系统的软件工程设计方法,从第 2 章开始,以“北京

市地理信息公众查询系统”为例,详细阐述了 GIS 系统的需求分析、总体设计、详细设计、

主界面实现与主要功能的编码实现。

本书适用于政府、企业相关部门的 GIS 研究开发人员,也适合作为高等院校地理学、地理

信息系统、房地产、环境科学、资源与城乡规划管理、区域经济学等专业学生的 GIS 实习教材

和参考书,以及各种 GIS 培训学员的教材用书。

图书在版编目(CIP)数据

地理信息系统二次开发实例教程. C++ Builder 和 MapObjects

实现/刘光,刘小东编著.—北京:科学出版社,2004.9

ISBN 7-03-014422-8

I. 地… II. ①刘… ②刘… III. 地理信息系统—程序设计—教材

IV. P208

中国版本图书馆 CIP 数据核字(2004)第 099261 号

责任编辑:何武 / 责任校对:科海

责任印刷:科海 / 封面设计:林陶

      

出版 北京东黄城根北街 16 号

邮政编码:100717

http://www.sciencep.com

北京科普瑞印刷有限责任公司印刷

科学出版社发行 各地新华书店经销

*

2004 年 10 月第一版 开本:16 开

2004 年 10 月第一次印刷 印张:24.25

印数:1-4 000 字数:590 千字

定价:36.00 元(1CD)

(如有印装质量问题,我社负责调换)

Page 3: C++ Builder和MapObjects实现

前 言

以计算机为核心的信息处理系统技术是二次世界大战后科技革命的主要标志之一。在

信息的诸多类型中,与空间相关的信息是十分重要的,人类赖以生存的地球是个三维空间,

其中的万物无不与空间位置相关,如何利用计算机处理空间相关信息是地理信息系统

(Geographic Information System,GIS)产生和发展的原动力。GIS起源于20世纪60年代,

它作为有关空间数据管理、空间信息分析及其传播的计算机系统,在其40多年的发展历程

中已经取得了很大成就,并广泛地应用于土地利用、资源管理、环境监测、交通运输、城

市规划、经济建设以及政府各职能部门。并且随着计算机技术的不断发展,计算速度越来

越快,使得地理信息系统技术应用领域越来越广泛。最近几年来,地理信息系统无论是在

理论上还是应用上都处在一个飞速发展的阶段,并呈现出广阔的应用前景。“数字地球”

概念的提出,更进一步推动了作为其技术支撑的GIS的发展。不管人们将21世纪称为什么世

纪,GIS的广泛应用、普及必将是其一个重要的特征。 今天,GIS 已是一个全球拥有数十万开发人员和数十亿美元的产业。世界各国已设计

出大量实用化的地理信息系统,常用的GIS软件已达400多种,比较著名的有美国环境系统

研究所(ESRI)的ARC/INFO和ArcView,澳大利亚GENASYS公司开发的GENAMAP,美

国Clark大学George Perkins Marsh研究所的IDRISI,中国地质大学开发的MapGIS,原武汉测

绘科技大学开发的GeoStar,北京大学遥感与地理信息系统研究所开发的CityStar等等。另外,

随着Internet/Intranet的迅猛发展,万维网地理信息系统(WebGIS)软件也开始走向市场,

国内的产品主要有成都华好网景科技有限公司的OK Map、武汉测绘科技大学开发的 Internet GeoStar(GeoSurf)、国家遥感应用工程技术研究中心网络与运行工程部开发的地

网GeoBeans。 虽然GIS软件产品繁多,但是由于GIS软件具有专业性强的特点,它们不可能解决所有

的问题,因此,针对某些具体问题,还必须由用户进行二次开发才能解决。为方便用户进

行二次开发,各大GIS厂商在推出基础地理信息系统平台的同时,一般都提供专门的语言与

二次开发组件,例如MapInfo公司的MapBasic、MapX,ESRI公司的AVENUE、MapObjects,以及RSI公司的IDL、IDLDrawWidget等。我国在这方面主要有北京超图地理信息技术有限

公司的SuperMap。虽然目前介绍这方面知识的书籍逐渐开始多起来,但都只是零散地介绍

各种组件的功能,至今国内市场上还没有介绍如何利用某一地理信息系统二次开发组件来

实现一个功能相对齐全并且实用的系统。这正是本书编写的目的。 本书按照软件工程技术的要求,以“北京市地理信息公众查询系统”为例,介绍了如

何利用C++ Builder语言以及地理信息系统二次开发组件MapObjects,进行地理信息系统的

二次开发,包括系统的需求分析、总体设计、详细设计、主界面实现与主要功能的编码实

现等几部分内容。 第1章“地理信息系统软件工程”讲述了软件工程技术在GIS系统开发中的几个应用方

Page 4: C++ Builder和MapObjects实现

面,包括需求分析、数据管理设计、用户界面设计、设计模式在GIS软件开发中的应用。在

GIS开发过程中应用软件工程技术,可以提高软件开发效率和质量。第2章“需求分析”介

绍了“北京市地理信息公众查询系统”的需求分析,包括需求概述、功能需求以及功能需

求详细描述。第3章“系统总体设计”介绍了系统平台选择、系统总体框架、系统数据组织

及系统开发进度安排等。第4章“系统详细设计”根据系统的总体设计结构分别从北京市地

理信息公众查询系统的数据库设计和一些相关类的设计两方面来详细阐述GIS系统的设计。

第5章“系统主界面的实现”首先简单介绍MapObjects的功能、特点、结构及其数据源,然

后介绍如何设计系统的主界面及主要实现代码。第6章“选择与查询功能的实现”主要介绍

如何通过查询与数据集有关的表从数据中获取信息,以及如何通过空间和逻辑的查询方法

从数据中获取信息。第7章“系统其他辅助功能”介绍了系统中其他一些辅助功能的实现,

例如当鼠标移动到某地物上并稍做停留后,出现一个小标签,显示该地物的名称,以及距

离量算、面积量算的实现,等等。为了让读者更加全面地掌握MapObjects的开发,第8章介

绍了“北京市地理信息公众查询系统”开发过程中没有涉及到的一些对象,包括投影对象、

地址匹配对象、动态跟踪层(Tracking Layer)对象与地理事件(GeoEvent)对象。 本书配套光盘提供了书中涉及的所有源代码,以及实现书中内容所需的部分资料。 由于时间仓促,书中难免有一些错误、遗漏,恳请读者谅解,并提出批评和指正。

编 者

2004年6月

Page 5: C++ Builder和MapObjects实现

目 录

第1章 地理信息系统软件工程 ........................................................................................ 1 1.1 软件工程简介 ....................................................................................................................................... 1

1.1.1 基本概念....................................................................................................................................... 1 1.1.2 软件系统开发过程 ....................................................................................................................... 2 1.1.3 开发过程模型............................................................................................................................... 5

1.2 GIS需求分析 ......................................................................................................................................... 7 1.2.1 需求获取....................................................................................................................................... 7 1.2.2 需求规约....................................................................................................................................... 8

1.3 GIS数据管理设计 ................................................................................................................................. 9 1.3.1 全部采用文件管理 ....................................................................................................................... 9 1.3.2 文件结合关系数据库管理 ........................................................................................................... 9 1.3.3 全部采用关系数据库管理 ......................................................................................................... 10 1.3.4 采用面向对象数据库管理 ......................................................................................................... 11

1.4 GIS用户界面设计 ............................................................................................................................... 12 1.4.1 界面设计原则............................................................................................................................. 12 1.4.2 GIS界面设计中的要素............................................................................................................... 14 1.4.3 GIS界面样式 .............................................................................................................................. 16

1.5 GIS应用模式与开发方式 ................................................................................................................... 18 1.5.1 GIS应用模式 .............................................................................................................................. 18 1.5.2 GIS开发方式 .............................................................................................................................. 20

1.6 “北京市地理信息公众查询系统”介绍 .......................................................................................... 22

第2章 需求分析............................................................................................................ 24 2.1 需求概述 ............................................................................................................................................. 24 2.2 功能性需求 ......................................................................................................................................... 25

2.2.1 系统体系结构............................................................................................................................. 26 2.2.2 用户描述..................................................................................................................................... 26 2.2.3 具体功能需求............................................................................................................................. 27

2.3 非功能性需求 ..................................................................................................................................... 30 2.3.1 性能需求..................................................................................................................................... 30 2.3.2 安全性需求................................................................................................................................. 30

2.4 功能需求详细描述 ............................................................................................................................. 30

Page 6: C++ Builder和MapObjects实现

2 目 录

第3章 系统总体设计..................................................................................................... 41 3.1 系统平台选择 ..................................................................................................................................... 41

3.1.1 硬件平台..................................................................................................................................... 41 3.1.2 系统操作平台............................................................................................................................. 41 3.1.3 数据库平台................................................................................................................................. 41 3.1.4 系统开发模式与GIS组件选择................................................................................................... 41 3.1.5 开发工具..................................................................................................................................... 44

3.2 系统总体框架 ..................................................................................................................................... 45 3.2.1 系统功能框架............................................................................................................................. 45 3.2.2 系统数据库................................................................................................................................. 46 3.2.3 系统的开发结构 ......................................................................................................................... 46 3.2.4 系统界面组织............................................................................................................................. 46

3.3 系统数据组织 ..................................................................................................................................... 48 3.3.1 系统数据的逻辑组织 ................................................................................................................. 48 3.3.2 系统的主要数据类型 ................................................................................................................. 49

3.4 进度规划 ............................................................................................................................................. 49

第4章 系统详细设计..................................................................................................... 50 4.1 数据库详细设计 ................................................................................................................................. 50

4.1.1 地名分类编码............................................................................................................................. 50 4.1.2 元数据表结构............................................................................................................................. 67 4.1.3 电子地图数据............................................................................................................................. 71

4.2 系统相关类的详细设计...................................................................................................................... 76 4.2.1 辅助类的详细设计 ..................................................................................................................... 76 4.2.2 TEnvironment类的详细设计...................................................................................................... 80 4.2.3 TPath类的详细设计 ................................................................................................................... 83 4.2.4 NetLayer类的详细设计 .............................................................................................................. 84 4.2.5 MapTip类的详细设计 ................................................................................................................ 92

第5章 系统主界面的实现 ............................................................................................. 93 5.1 MapObjects简介 .................................................................................................................................. 93

5.1.1 MapObjects的功能 ..................................................................................................................... 93 5.1.2 MapObjects的特点 ..................................................................................................................... 94 5.1.3 MapObjects的结构 ..................................................................................................................... 94

5.2 导入MapObjects组件 .......................................................................................................................... 97 5.3 系统主界面设计 ................................................................................................................................. 98

5.3.1 创建资源..................................................................................................................................... 98 5.3.2 设计地图控制工具栏 ................................................................................................................. 99 5.3.3 设计地物类型工具栏 ............................................................................................................... 102 5.3.4 设计状态栏............................................................................................................................... 104

Page 7: C++ Builder和MapObjects实现

目 录 3

5.3.5 设计“地图”页面 ................................................................................................................... 104 5.3.6 设计“查询”页面 ................................................................................................................... 105 5.3.7 其他辅助控件........................................................................................................................... 111

5.4 TEnvironment类的初步实现 ............................................................................................................ 111 5.4.1 辅助类的实现........................................................................................................................... 112 5.4.2 TEnvironment类的成员变量.................................................................................................... 114

5.5 读取元数据 ....................................................................................................................................... 116 5.6 “地图”页面的实现........................................................................................................................ 123 5.7 图层的加入与控制 ........................................................................................................................... 131

5.7.1 在地图中加入图层 ................................................................................................................... 131 5.7.2 依据比例尺控制图层显示 ....................................................................................................... 148

5.8 通过“地图”页面控制地图显示.................................................................................................... 154 5.8.1 控制显示的地物类型 ............................................................................................................... 154 5.8.2 控制地图显示区域 ................................................................................................................... 165

5.9 地图的放大、缩小、全图显示和漫游 ............................................................................................ 171 5.10 其他辅助功能的实现...................................................................................................................... 175

5.10.1 鹰眼功能的实现 ..................................................................................................................... 175 5.10.2 显示经纬度与比例尺 ............................................................................................................. 176 5.10.3 资源释放................................................................................................................................. 177

第6章 选择与查询功能的实现 .................................................................................... 178 6.1 选择地物 ........................................................................................................................................... 178 6.2 查询地物信息 ................................................................................................................................... 188 6.3 地名查询 ........................................................................................................................................... 199 6.4 查找最近地物 ................................................................................................................................... 206 6.5 公交查询 ........................................................................................................................................... 217

6.5.1 公交站点与线路查询 ............................................................................................................... 217 6.5.2 乘车路线查询........................................................................................................................... 246

6.6 地名索引 ........................................................................................................................................... 273 6.7 查询结果的定位与更详细信息........................................................................................................ 285 6.8 最短路径查询 ................................................................................................................................... 292

第7章 系统其他辅助功能 ........................................................................................... 332 7.1 地名的快速显示 ............................................................................................................................... 332 7.2 距离与面积量算 ............................................................................................................................... 337 7.3 地图输出子系统的实现.................................................................................................................... 342 7.4 在线帮助子系统的实现.................................................................................................................... 343 7.5 其他快捷按钮功能的实现................................................................................................................ 344 7.6 程序封面的实现 ............................................................................................................................... 346

Page 8: C++ Builder和MapObjects实现

4 目 录

第8章 MapObjects的其他对象 ................................................................................... 352 8.1 动态跟踪层对象与GeoEvent对象.................................................................................................... 352

8.1.1 TrackingLayer对象的属性 ....................................................................................................... 352 8.1.2 TrackingLayer对象的方法 ....................................................................................................... 353 8.1.3 GeoEvent对象的属性 ............................................................................................................... 353 8.1.4 GeoEvent对象的方法 ............................................................................................................... 353 8.1.5 实例应用................................................................................................................................... 354

8.2 投影对象 ........................................................................................................................................... 356 8.2.1 坐标系....................................................................................................................................... 356 8.2.2 地图投影................................................................................................................................... 358 8.2.3 投影转换................................................................................................................................... 363

8.3 地理编码 ........................................................................................................................................... 368 8.3.1 用于地址匹配的专用文件 ....................................................................................................... 368 8.3.2 绘制街道文件........................................................................................................................... 369 8.3.3 地理编码对象........................................................................................................................... 370 8.3.4 地址定位对象........................................................................................................................... 371 8.3.5 地址标准化对象 ....................................................................................................................... 371 8.3.6 交互式地址匹配 ....................................................................................................................... 373 8.3.7 批地址匹配............................................................................................................................... 378

Page 9: C++ Builder和MapObjects实现

第 1 章 地理信息系统软件工程

在地理信息系统(Geographic Information System,GIS)开发过程中应用软件工程技术,

可以提高软件开发效率和质量。本章首先介绍了软件工程的基本概念、软件系统开发过程

和开发过程模型。然后讲述了软件工程技术在GIS系统开发中的应用:需求分析、数据管理

设计、用户界面设计、设计模式在GIS软件开发中的应用。这些方面涉及了GIS开发过程中

的不同阶段及不同层次,有些方法之间是互斥的,如UML和Code方法,但是软件工程技术

重要的特点是实用,开发者可以根据具体情况选用不同的技术。

1.1 软件工程简介

1.1.1 基本概念

计算机软件工程是一类求解工程。它应用计算机科学、数学、工程科学及管理科学等

原理,借鉴传统工程的原则、方法创建软件,以达到提高软件质量、降低开发成本的目的。

其中,计算机科学、数学用于构造模型与算法,工程科学用于制定规范、设计范型、评估

成本及确定权衡,管理科学用于管理计划、资源、质量、成本等。从学科角度来看,软件

工程是一门指导计算机软件开发和维护的工程学科。 软件工程的提出是为了解决20世纪60年代出现的软件危机,当时在大型软件开发项目

中存在着成本高、开发进程不易控制、开发工作量难于估算、软件质量低、项目失败率高

等诸多问题,给软件行业带来了巨大的冲击。软件工程提出了一系列理论、原则、方法及

工具,试图解决这种软件危机。 和其他工程一样,软件工程有自己的目标、活动和原则,其框架可以概括为图1.1所示

的内容。

开发范型

设计方法

支持过程

管理过程

可用性

正确性

合算性

图 1.1 软件工程框架

Page 10: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

2

软件工程的目标可以概括为“生产具有正确性、可用性及开销合宜的产品”,其活动

包括需求、设计、实现、确认及支持等。围绕工程设计、支持及管理,软件工程有以下4条基本原则:

(1)选取适宜的开发模型。选取适宜的开发模型可以利于认识需求易变性并加以控制,

以保证软件产品满足用户的需求。 (2)采用合适的设计方法。通常要考虑实现软件的模块化、抽象与信息隐藏、本地化、

一致性及适应性等特征。 (3)提供高质量的工程支持。在软件工程中,软件工具与环境对软件过程的支持颇为

重要。 (4)重视开发过程的管理。开发过程的管理直接影响可用资源的有效利用、 终的软

件产品的满意度,软件组织的生产能力等问题。只有对开发过程实施有效管理,才能实现

有效的软件工程。

GIS软件工程把软件工程的思想和方法应用于GIS软件的开发过程。如前所述,GIS软件工程活动包括需求、设计、实现、确认及支持等,它们对应于软件开发过程的不同阶段。

一般来说,软件开发都要经历从分析设计到实现确认的过程。每个阶段按照相应的规范进

行工作,并得到该阶段的成果,是保证整个开发过程成功的关键。

1.1.2 软件系统开发过程

前面讲过,软件工程活动包括需求、设计、实现、确认及支持,它们对应于整个软件

开发过程的不同阶段。

1.1.2.1 需求分析

需求分析阶段处于软件开发过程的前期,其基本活动是准确定义未来系统的目标,确

定为满足用户的需求必须做什么。需求分析又划分为两个阶段,即需求获取和需求规约,

前者用自然语言清楚地描述用户的需求,而后者的目的是消除获取需求的二义性和不一致

性。 在软件项目的生命周期中,一个错误发现得越晚,修复错误的代价也越高,所以,高

质量的需求工程是软件项目得以正确、高效完成的前提。对于系统分析人员,建立需求面

临着以下3个方面的困难:

· 问题空间的理解 系统开发人员通常是计算机专业人员,难以深入理解各种业务系

统所要解决的问题空间。 · 人与人之间的通信 对于系统分析人员而言,通信主要包括同用户的通信以及同事

之间的通信,由于自然语言的二义性,会给准确刻画需求造成障碍。 · 需求的不断变化 造成需求变化的原因很多,包括技术、用户方、市场等等。作为

分析人员,必须采用一些策略以适应变化。

面向对象的分析方法被认为是解决上述困难的较好技术,但是完整、准确地刻划问题

空间始终是分析人员所面临的挑战。

Page 11: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

3

1.1.2.2 系统设计

一般来说,需求分析阶段的主要任务是确定系统“做什么”,而系统设计阶段则要解

决“怎么做”的问题。系统设计的任务是将系统分析阶段提出的逻辑模型转化为相应的物

理模型,其设计的内容随系统的目标、数据的性质和系统的不同而有很大的差异。一般而

言,首先应根据系统的目标,确定系统必须具备的空间操作功能,称为功能设计;其次是

系统的建模和产品的输出,称为应用设计。系统设计是系统整个开发工作的核心,不但要

完成逻辑模型所规定的任务,而且要使所设计的系统达到优化。所谓优化,就是选择 优

方案,使系统具有运行效率高、控制性能好和可变性强等特点。要提高系统的运行效率,

一般要尽量避免中间文件的建立,减少文件扫描的遍数,并尽量采用优化的数据处理算法。

为增强系统的控制能力,要拟定对数字和字符出错时的校验方法;在使用数据文件时,要

设置口令,防止数据泄密和被非法修改,保证只能通过特定的通道存取数据。为了提高系

统的可变性, 有效的方法是采用模块化的方法,即先将整个系统看成一个模块,然后按

功能分解为若干个子模块。一个模块只执行一种功能,一种功能只用一个模块实现,这样

设计出来的系统才能做到可变性好并具有生命力。 功能设计又称为系统的总体设计,它的主要任务是根据系统的目标来规划系统的规模,

确定系统的各个组成部分,并说明它们在整个系统中的作用与相互关系,以及确定系统的

硬件配置,规定系统采用的合适技术规范,以保证系统总体目标的实现。图1.2给出了通用

GIS的总体设计结构图。 因此,系统的总体设计大致包括:

· 数据库设计 · 硬件配置与选购 · 软件设计等

应用设计又称详细设计。详细设计包括详细的算法、数据表示和数据结构、实施的功

能和使用数据之间的关系。详细设计过程中采用了一些工具,以便对数据、算法等进行描

述,包括流程图、PAD(Problem Analysis Diagram,问题分析图)、盒图(N-S图)、伪码

等。

1.1.2.3 实现阶段

软件实现阶段将设计的结果变换成程序设计语言编写的程序。一般情况下在实现阶段,

首先要确定程序设计语言,其影响因素包括:开发人员对语言的熟悉程度、语言的可移植

性、编译程序的效率、编译工具的支持等等。目前,C++语言是被普遍采用的构造系统软

件的编程语言,而Java则更多地应用于编写网络应用程序。 无论采用哪一种编程语言,都要求编写高质量的源程序代码,程序质量通常包含正确

性、可读性、可移植性、程序效率等指标。考虑到系统的维护和演化,提高源程序的可读

性是实现阶段的一个重要目标,其途径包括添加注释、规范书写格式、确定标识符命名原

则、采用结构化的程序设计方法(不用或减少使用goto语句)等。

Page 12: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

4

空间

数据库

属性

数据库

DBMS

完整性

保护

数据字典

初始化

插入

更新

删除

检索

编 辑 子 系 统 综合分类

专家判读

图像处理系统 编图 报表生成

输出子系统

手扶跟踪 数字化

扫描数字化

属性输入

网络数据输入

更新子系统

输入子系统

命令程序

命令处理

叠加

缓冲区

统计分析

DTM

格式变换

投影变换

网络分析

网络操作

管理

分析功能 用

图 1.2 通用 GIS 的总体设计结构图

1.1.2.4 确认活动

确认活动贯穿于软件开发活动的始终,系统完成后的软件测试是主要的确认活动。软

件测试是指按照特定规程发现软件错误的过程。软件测试技术大体上可以分为两类,即白

盒测试技术和黑盒测试技术,前者依据的是程序逻辑结构,后者依据的是软件行为描述。

根据测试的步骤,测试活动又可以分为单元测试、集成测试、确认测试和系统测试,其中

确认测试是为了检验软件的功能和性能是否与用户需求一致,而系统测试主要是测试软件

同硬件、其他支持软件、数据等结合在一起,在实际运行情况下同用户需求的匹配程度。

1.1.2.5 软件维护

当软件开发完成并交付用户使用后,就进入软件的运行/维护阶段。在此阶段对软件进

行的修改称为软件维护。软件维护活动可以分为以下几类:

· 改正性维护 其目的是为了纠正运行阶段发现的软件错误、性能上的缺陷以及排除

实施中的误用。 · 适应性维护 随着计算机的发展,软件的外部环境或者数据环境发生变化,为了适

Page 13: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

5

应这种变化而对软件作出的修改称为适应性维护。 · 完善性维护 在使用过程中,用户往往会对软件提出新的功能和性能需求,为了满

足这些需求,需要修改或再开发软件,这称为完善性维护。 · 预防性维护 预防性维护的目的是为了提高软件的可维护性、可靠性等,为进一步

的软件维护打下良好的基础。预防性维护一般由开发单位主动进行。

1.1.3 开发过程模型

软件开发过程模型是软件开发的全部过程、活动和任务的结构框架。软件开发过程模

型能够清晰、直观地表达软件开发过程,明确规定要完成的主要活动和任务,可以作为软

件项目工作的基础。 随着软件工程的不断实践,人们相继提出了如下一系列开发模型。

1.1.3.1 瀑布模型

在瀑布模型中,各项活动在依照固定顺序连接的若干阶段上工作,形如瀑布流水(见

图1.3)。瀑布模型的特征是:每一阶段接受上一阶段的工作结果作为输入,其工作输出传

入下一阶段;每一阶段工作都要进行评审,得到确认后,才能进入下一阶段的工作。瀑布

模型较好地支持结构化软件开发,但是缺乏灵活性,无法通过软件开发活动澄清本来不够

确切的需求。

1.1.3.2 演化模型

演化模型主要针对事先不能完整定义需求的软件开发项目。在这种模型中,用户可以

先给出核心需求,当开发人员将核心需求实现后,用户提出反馈意见,以支持系统的 终

设计和实现。

1.1.3.3 螺旋模型

螺旋模型是在瀑布模型以及演化模型的基础上,加入风险分析所建立的模型。在螺旋

模型每一次演化过程中,都经历以下4个方面的活动:

(1)制定计划——确定软件目标,选定实施方案,弄清项目开发的限制条件。 (2)风险分析——分析所选方案,考虑如何识别和消除风险。 (3)实施工程——实施软件开发。 (4)客户评估——评价开发工作,提出修正建议。

每一次演化都开发出更为完善的一个新的软件版本,形成了螺旋模型的一圈。螺旋模

型借助于原型获取用户需求,进行软件开发的风险分析。对于大型软件的开发,这是颇为

实际的方法。

Page 14: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

6

系统需求

软件需求

需求分析

设计

编码

测试

运行

图 1.3 瀑布模型

1.1.3.4 喷泉模型

喷泉模型体现了软件开发过程中所固有的迭代和无间隙的特征(见图1.4)。喷泉模型

表明了软件开发活动需要多次重复。例如,在编码之前,再次进行分析和设计,并添加有

关功能,使系统得以演化。同时,该模型还表明各活动之间没有明显的间隙,例如在分析

和设计之间没有明确的界限。

演化

维护

确认

实现

设计

分析

图 1.4 喷泉模型

Page 15: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

7

在面向对象技术中,由于对象概念的引入,使分析、设计、实现之间的表达连贯而一

致,所以,喷泉模型主要用于支持面向对象的软件开发过程。 目前,随着面向对象技术的发展和UML建模语言的成熟,人们总结并提出了统一软件

开发过程(USDP,Unified Software Development Process)以指导软件开发,它是一个用例

(use case)驱动的、体系结构为中心的增量迭代式开发过程模型,适合利用面向对象技术

进行软件开发。

1.2 GIS需求分析

GIS需求分析是GIS产品在其生存期中重要的具有决定性的一步。只有通过GIS需求分

析,才能把GIS的功能和性能的总体概念描述为具体的GIS产品需求规格说明,从而奠定GIS开发的基础。同时GIS需求分析是一个不断认识和逐步细化的过程。

GIS需求分析所要做的工作是深入描述GIS的功能和性能,确定GIS系统设计的限制和

GIS同其他系统元素的接口细节,定义GIS的其他有效性需求。

1.2.1 需求获取

需求获取是软件开发活动的第一步,获得正确的需求描述是成功软件的前提。一般而

言,用户需求分为两类:功能性需求(Functional Requirements)和非功能性需求(Nofunctional Requirements),前者定义系统做什么,包括输入、输出及其间的转换;后者定义系统工作

时的特性,如效率、可靠性、安全性、可维护性、可移植性等要求。具体的需求获取内容

包括:

· 物理环境 物理设备的位置及其分布的集中程度。 · 接口 与其他软件系统的接口以及对数据格式的要求。 · 用户或人的因素 包括系统用户熟练程度,使用系统需要接受的培训。 · 功能 系统要完成什么,性能如何。 · 文档 需要哪些文档及其针对的读者。 · 数据 数据格式、数据精度、数据量、接收和发送数据的频率。 · 资源 使用系统需要的设备,开发需要的人力资源、计算机资源、时间表。 · 安全性 对访问信息的控制程度,数据的备份等。 · 质量保证 对系统的可靠性要求、平均系统出错时间、可移植性、可维护性等。

地理信息系统的需求获取内容和上述内容基本一致,只是在数据、人员和组织等方面

要进行额外的考虑。

(1)数据 在一个GIS应用系统中,数据占有举足轻重的位置。进行需求获取时,与数据有关的因

素包括:

Page 16: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

8

· 数据的输出样式 包括屏幕显示、Web发布、出版、工程图等; · 输出数据的内容和要求 输出数据要包括哪些内容,数据的精度、比例尺等; · 数据的分布性 数据是集中管理还是分布管理; · 现有的纸质地图 现有的纸质地图的内容,其比例尺、时效性、机密性; · 现有的电子数据 数据形式(栅格/矢量/属性数据库)、数据格式、完整性、精度、

投影方式、比例尺等因素。 · 数据录入 数据量大小、输入设备(包括数字化仪、扫描仪)、软件的支持程度、

进行数据录入的人员数目、能否在预定时间内完成数据录入; · 数据购买 数据量及价格。

(2)人员 因为地理信息系统一般是针对专业领域,在进行需求获取时不仅要考虑一般用户,而

且要听取领域专家的意见,将他们的理论、经验模型化,并应用于系统中。 (3)组织 要考虑现有的组织机构、有关部门的职责、业务流程、GIS如何在其业务流程中体现,

以及应用GIS可能引起的组织机构变化。

进行需求获取的方式是多种多样的,包括面谈、电话访谈、参观、问卷调查、获取领

域相关资料等。在地理信息系统开发中,由于GIS的概念、功能等还没有被用户深入理解并

接受,采用GIS专题报告可以很好地激励用户提出需求。如果时间和资金允许,开发原型系

统也可以更好地挖掘用户需求。

1.2.2 需求规约

需求获取阶段得到了用自然语言描述的用户需求,但是其中存在着不一致性和二义性,

这些问题要通过需求规约解决。目前有许多方法用于支持需求规约,如功能分解方法、数

据流方法、信息模型方法(实体关系模型)、面向对象方法。每种方法都有相应的概念体

系、符号表现和工具支持。 需求规约的结果是形式化或半形式化的,系统的需求报告必须完整地刻画问题域,能

够适应需求变化。此外,它必须满足用户、分析人员、设计开发人员进行交流的需要,换

言之,需求报告中的符号、描述,对各类相关人员的意义应是一致的。 正如一再强调的,面向对象的分析方法很好地解决了问题空间理解、需求易变性、交

流等方面的问题。将通用的面向对象分析方法应用于GIS,固然可以描述系统需求,但是由

于GIS更加关注空间对象和空间模型,在GIS中要经常处理一些特定的空间对象类以及特定

的空间关系(拓扑关系、方位关系、度量关系等),所以对基本的面向对象方法进行特化

扩展,采用特定的符号表示这些类及其关系,可以使需求报告的表述更加简捷,便于信息

交流。

Page 17: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

9

1.3 GIS数据管理设计

数据管理设计的目的是确定在数据管理系统中存储和检索数据的基本结构,其原则是

要隔离数据管理方法的影响,不管该方法是普通文件、关系数据库、面向对象数据库还是

其他方法。 目前,有3种主要的数据管理方法:

· 普通文件管理 普通文件管理提供基本的文件处理和分类能力。 · 关系型数据库管理系统(RDBMS) 关系型数据库管理系统建立在关系理论的基

础上,采用多个表管理数据,每个表的结构遵循一系列“范式”进行规范化,以减

少数据冗余。 · 面向对象的数据库管理系统 面向对象的数据库是一种正在走向成熟的技术,它通

过增加抽象数据类型和继承特性以及一些用来创建和操作类和对象的服务,实现对

象的持续存储。

不论在需求分析阶段采用何种方法,实现阶段是采用OOP还是非OOP,都可以选择上

述任意的一种数据管理方法实现数据的管理。 在地理信息系统中,需要管理的数据主要包括:空间几何体数据、时间数据、结构化

的非空间属性数据以及非结构化的描述数据。对于地籍管理系统中的地块,它们的内容描

述如下:

· 空间几何体数据 地块界点的坐标; · 时间数据 地块存在的时段; · 非空间属性数据 地块的权属,地价等等; · 非结构化的描述数据 描述地块所需要的图像、声音数据等等。

通常的数据管理方案包括以下4种。

1.3.1 全部采用文件管理

将所有的数据都存放于一个或多个文件中,包括结构化的属性数据。采用文件管理方

案的优点是灵活,即每个软件厂商可以任意定义自己的文件格式,管理各种数据,这一点

在存储加密数据及非结构化的、不定长几何体坐标记录时是有帮助的。文件管理的缺点也

是显而易见的,就是需要由开发者实现属性数据的更新、查询、检索等操作,增加了属性

数据管理的开发量,并且也不利于数据共享。目前,许多GIS软件采用文本文件进行数据存

储,其目的是实现数据的转入和转出,与其他应用系统交换数据。

1.3.2 文件结合关系数据库管理

这是目前大多数GIS软件采用的数据管理方案。考虑到空间数据是非结构化的、不定长

的,而且施加于空间数据的操作需要GIS软件实现,这就可以利用文件存储空间数据,而借

Page 18: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

10

助于已有的关系数据库管理系统(RDBMS)管理属性数据。采用这种数据管理方案:

· 空间数据 通过文件进行管理; · 时间数据 是结构化的,可以利用数据库进行管理; · 非空间属性数据 利用数据库进行管理; · 非结构化的描述数据 描述数据(不论是文本、图像还是声音、录像)一般都对应

于一个文件,这样可以简单地在关系数据库中记录其文件路径,优点是关系数据库

数据量小,缺点是文件路径常常会因为文件的删除、移动操作而变得不可靠。如果

关系数据库支持二进制数据块字段,也可以利用它来管理文本、图像甚至声音、录

像文件。

由于空间几何体坐标数据和属性数据是分开存储管理的,所以需要定义它们之间的对

应关系。通常的解决方案是为文件中的每个地物都分配一个惟一标识码(地物ID),而在

关系数据表结构中也对应有一个标识码(地物ID)这样数据表中的每条记录可以通过该标

识码确定与文件中对应地物的连接关系(见图1.5)。

地物 ID 坐标

X1,Y1,X2,Y2,…

X1,Y1,X2,Y2,…

X1,Y1,X2,Y2,…

ID1

ID2

ID3

地物 ID 属性 1

属性值

属性值

属性值

ID1

ID2

ID3

属性 2

属性值

属性值

属性值

(a)通过文件管理空间数据 (b) 通过关系数据库管理属性数据

图 1.5 同时使用文件和关系数据库管理 GIS 数据

采用该管理方案的缺点在于需要经常进行基于地物ID的查找(既包括从给定地物查找

其对应记录,又包括根据给定记录检索相应的地物),使查询、模型运算等操作的速度变

慢。

1.3.3 全部采用关系数据库管理

在这种管理方案中,不定长的空间几何体坐标数据以二进制数据块的形式被关系数据

库管理,换言之,坐标数据被集成到RDBMS中,形成空间数据库,其结构如图1.6所示。

Page 19: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

11

空间

数据库

数据库访问接口

空间

模型服务

GIS 应用 GIS 应用 GIS 应用

RD

BM

S G

IS

空间数据访问接口

图 1.6 集成化的 GIS 空间数据管理

可以认为一个地物对应于数据表中的一条记录,这样带来的 直接好处是避免了对“连

接关系”的查找。目前,关系数据库不论是其理论还是工具都已经成熟,它们提供了一致

的访问接口(SQL)以操作分布的海量数据,并且支持多用户并发访问、安全性控制和一

致性检查,这些正是构造企业级的地理信息系统所需要的。此外,通用的访问接口也便于

实现数据共享。 对GIS采用全关系数据库管理,由于几何体坐标数据不定长,会造成存储效率低下。此

外,现有的SQL并不支持空间数据检索,因此需要软件厂商自行开发空间数据访问接口,

如果要支持空间数据共享,则要对SQL进行扩展。

1.3.4 采用面向对象数据库管理

如果应用面向对象数据库管理GIS数据,则需要扩充面向对象数据库中的数据类型以支

持空间数据,包括点、线、多边形等几何体,并且允许定义对于这些几何体的基本操作,

包括计算距离、检测空间关系,甚至稍微复杂的运算,如缓冲区计算、叠加复合模型等,

也可以由面向对象数据库管理系统“无缝”地支持。 这样,面向对象数据库管理系统提供了对于各种数据的一致访问接口以及部分空间模

型服务,不仅实现了数据共享,而且空间模型服务也可以共享,使GIS软件可以将重点放在

数据表现以及开发复杂的专业模型上(见图1.7)。

Page 20: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

12

空间

数据库

数据库访问接口

空间模型服务

GIS 应用 GIS 应用 GIS 应用

OO

-DB

MS

GIS

空间模型服务

图 1.7 采用面向对象数据库管理系统管理 GIS 数据

不过,目前面向对象数据库管理系统远未成熟,许多技术问题仍需要进一步研究解决。

例如,由于支持用户自定义功能,可能会引发对系统的恶意入侵。查询优化也是面向对象

数据库所面临的一个难题。例如,要得到所有有铁路通过并且人口大于10万的县,很明显,

得到人口大于10万的县所需要的计算时间要远远小于得到有铁路通过的县,系统必须了解

这一点,在执行查询时,先得到人口大于10万的县,然后再在该集合中依据空间关系进行

进一步的查找——而不考虑用户的实际输入次序。对于这种简单的情况,即结构化查询和

空间运算的消耗时间,系统容易比较判断,但是如果查询语句中包括多个空间运算函数,

那么其优化将变得十分困难。

1.4 GIS用户界面设计

同其他软件一样,GIS用户界面设计作为人机接口起着越来越重要的作用,它的好坏直

接影响到GIS产品的寿命。在功能、性能等方面类似时,用户会毫不犹豫地选择具有友好界

面的GIS产品。要开发一个更具竞争力的GIS产品,好的界面非常重要。

1.4.1 界面设计原则

在软件系统设计阶段除了设计算法、数据结构等内容外,一个很重要的部分就是系统

界面的设计。系统界面是人机交互的接口,包括用户如何命令系统以及系统如何向用户提

交信息。一个设计良好的用户界面使得用户更容易掌握系统,从而增加对系统的接受程度。

此外,系统用户界面直接影响到用户在使用系统时的情绪。下面的一些情形无疑会使用户

感到厌倦和茫然:

Page 21: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

13

(1)过于花哨的界面使用户难以理解其具体含义,不知从何入手; (2)模棱两可的提示; (3)较长(超过10秒)的响应时间; (4)额外的操作(用户本意是只做这件事情,但是系统除了完成这件事之外,还做了

另外的事情)。

与之相反,一个成功的用户界面必然是以用户为中心的、集成的和互动的。 目前图形用户界面(GUI,Graphical User Interface)已经被广泛采用,并且得到了很

多界面设计工具的支持,由于上述原因,在系统开发过程中我们应该将用户界面设计放在

相当重要的位置上。 设计用户界面的策略由以下几点构成:

(1)对人分类 通过仔细研究使用系统的人,对其进行分类。分类的原则包括按照技能层次(初学者、

高级人员……),按照组织层次(管理人员、一般员工……),按照身份(职员、顾客……)。

通过分类,弄清每类人员使用系统的目的,进而可以确定其相应的人机交互操作。 (2)描述人和他们的使用环境 对人员分类之后,确定每一类人员的特征,包括使用系统的目的、特征(年龄、教育

水平、限制等)、对系统的期望(必须/想要,喜欢/不喜欢/有偏见)、熟练程度、使用系

统的环境。依据这些特征,可以指导系统的人机交互设计。 (3)设计命令层 命令层的设计包括3个方面的工作,即研究现有用户交互活动的寓意和准则;建立一个

初始命令层;细化命令层。 在图形用户界面的设计过程中,已经形成了一些形式或非形式的准则和寓意,如菜单

排列(例如,在几乎所有的MS-Windows应用系统中,前3个一级菜单项总是“文件”、“编

辑”、“视图”,而 后的两个则是“窗口”,“帮助”),一些操作(例如,打开文件、

保存文件、打印)的图形隐喻等等。遵循这些准则,便于用户更快地熟悉系统。 在细化命令层时,需要考虑排列、整体/部分组合、宽度与深度的对比、 小操作步骤

等问题。一个层次太“深”的命令项会让用户难以发现,而太多命令项则使用户难以掌握。 (4)设计详细的交互 人机交互的设计有若干准则,包括:

· 一致性 采用一致的术语、一致的步骤和一致的活动; · 操作步骤少 使敲击键盘和点按鼠标的次数减到 少; · 不要“哑播放” 长时间的操作需要告诉用户进展的状况; · 闭包 用一些小步骤引出定义良好的活动,用户应该感觉到他们的活动中闭包的意

义; · 恢复 人难免做错事,通常在这种情况下系统应该支持恢复原状,或者至少部分支

持; · 减少人脑的记忆负担 不应该要求用户从一个窗口记忆或者写下一些信息然后在

Page 22: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

14

另一个窗口中使用; · 学习的时间和效果 为更多的高级特性提供联机参考信息; · 趣味和吸引力 人们通常喜欢使用那些有趣的软件。

(5)继续做原型 通过做原型系统,可以直接了解用户对界面设计的反应,然后进行改善,使之臻于完

美。 (6)设计用户界面类 在完成上面的工作后,就可以着手设计用户界面类。在开发GUI程序时,通常已经提

供了一系列通用界面类,如窗口、按钮、菜单等,只要从这些类派生特定的子类即可。 (7)根据现有的图形用户界面进行设计 目前主要的GUI包括Windows,Macintosh,X-Windows,Motif等,基于它们开发应用

软件可以使界面的设计简单化,但是事先要清楚其特性,如事件处理方式等等。

1.4.2 GIS界面设计中的要素

要创建成功的GIS软件,在设计时就要遵循前述的原则和步骤,要求其界面允许用户选

择并检索相应的空间数据,操作这些数据,并且表现分析结果。对于基本的数据检索、操

作和表现,与普通的软件是一致的。在GIS中要考虑以下几个要素。

1.4.2.1 数据选择

选择数据所采用的过滤器可能包括空间属性和非空间属性,或者是两者的结合。例如

(针对一个县级行政区划分数据):

· 检索所有人口大于10万的县。 · 检索所有有铁路通过的县。 · 检索所有有铁路通过并且人口大于10万的县。

用户可以通过多种方式来选择数据,例如,输入一个命令语句,通过菜单选择,填充

表单(Form),或者直接使用鼠标。利用命令语句需要了解数据表的结构,并且需要对传

统的SQL进行扩展以支持空间过滤。直接操作选择需要将数据显示在屏幕上,它和SQL查询组合通常间接完成。当一些地物显示在相近的位置时,直接操作选择会有二义性。为了

方便用户操作,要提供一些额外的选项,如漫游、放大显示、缩小显示等。

1.4.2.2 数据表现

好的数据表现形式有利于用户直接操作以进行进一步的分析。在地理信息系统中,需

要更多地考虑图形显示。图形显示的相似性或者差异可以表达给用户,这些地物对象具有

某种程度的相似性。一些图形显示变量有:

· 多边形轮廓 颜色、灰度、黑白、线型; · 多边形填充 颜色、灰度、黑白、填充模式; · 线 颜色、灰度、黑白、线型;

Page 23: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

15

· 符号 颜色、灰度、黑白、形状、大小等等。

对图形显示需要进行仔细设计,以正确地表达地物对象的含义,便于用户理解。

1.4.2.3 数据处理

数据处理由一系列空间和非空间操作组成,一个设计良好的界面能够使这些操作更加

容易。与标准的关系数据库相比,GIS所管理的数据更具有面向对象的特征,所以一个面向

对象界面有利于用户与GIS系统的交互操作,完成数据处理。在GIS软件中,面向对象的界

面设计包括将地理实体(如点、线、多边形)以及一些操作以象形符号表现出来,而用户

只需通过简单的点击、拖放等操作实现相应的数据处理。 下面列出了一些具有图形寓意的操作,很容易将其图形显示与对应的操作联系起来。

· 创建:根据给定的图形实体以及相应的属性,创建一个空间对象。 · 删除:删除一个选定的空间对象。 · 集合:根据选择的对象形成一个集合。 · 更新:只显示 后一次操作的结果。 · 叠合:相当于集合操作中的并运算。 · 求交:相当于集合操作中的交运算。 · 求差:相当于集合操作中的差运算。 · 转换:对选择的地物进行缩放、移动、镜像、坐标变换等操作。 · 检查点:设置检查点,当对后面工作不满意时,可以回退到该点。 · 回送:返回上一个检查点的状态。 · 提交:将所有的处理结果传给数据库,并更新之。

1.4.2.4 SQL

传统的SQL并不能处理空间查询,这是由于关系数据库技术的弱点造成的,对于GIS而言,需要对SQL进行扩展。目前正在制定的SQL/MM主要应用于多媒体数据,其中包含

了全面的GIS操作集合。 新的SQL标准带来了概念上的改变。传统的SQL要实现空间操作,需要将SQL命令嵌

入一种编程语言中,如C语言;而新的SQL允许用户定义自己的操作,并将其嵌入到SQL命令中。

这种扩展的SQL实际上是增加了面向对象的支持,它在地理信息系统中称为GeoSQL。GeoSQL可以写出形如下列代码的查询命令。

SELECT Soils.Map From Soils,Parcels Where Parcels.Value>6000 and Overlay(Soils,Parcels);

由于完全实现GeoSQL仍然有相当的难度,所以目前大多数GIS软件的实现思路是分别

输入标准SQL查询语句以及空间查询(见图1.8),然后分开处理, 后将结果合并。其缺

点是不够灵活。

Page 24: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

16

图 1.8 一个实现空间查询的界面,将空间关系和标准 SQL 分开输入

1.4.2.5 可视化

地理信息系统是基于图形的,其分析和解释结果也通常以可视化形式表现出来。可视

化是指为了识别、沟通和解释模式或结构,概括性地表现信息的过程。空间分析需要考虑

信息模式以及空间特征的感受,对于GIS,可视化可以描述为从信息到知识的转化过程。地

理信息系统除了以可视化形式表现各种信息,实现表达的所见即所得(WYSIWYG,What You See Is What You Get)亦是界面设计的重要原则。

1.4.3 GIS界面样式

在GIS用户界面设计中,有3种基本的用户界面样式,即基于命令行的界面、菜单驱动

的界面以及基于数据流图的GUI界面,这3种界面在实现和使用时各有其长处和短处。在具

体实现时,可以同时支持一种或几种样式。

1.4.3.1 基于命令行的界面

命令行是 简单的界面样式,并且很早就已经在各种操作系统软件中采用,如图1.9所示。它只使用文本语言,要求用户了解可以使用的选项,这需要记忆各种命令或者不断查

找帮助文档。基于命令行的界面不提供任何提示信息和建议,用户要依赖于印刷文档来学

习系统。采用命令行界面需要开发一个命令行解释器。在命令行界面软件中,功能模块之

间关系较为简单,常常是一个模块的输出作为另一个模块的输入,便于开发和实现。利用

批命令文件或者脚本文件,可以依次完成多步操作,这是命令行界面的长处。 GIS软件因为包含大量的图形操作,所以采用命令行界面时需要有一个图形窗口以显示

操作结果,这样命令行界面起到控制台的作用。由于支持批命令和脚本文件,GIS软件可以

使用命令行界面实现批量的、流程化的数据处理,但比较耗时。

Page 25: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

17

图 1.9 基于命令行的 GIS 界面

1.4.3.2 菜单驱动的界面

在MS-Windows成为PC上的主流操作系统之后,菜单驱动的用户界面几乎在所有的应

用软件中被采用。它按照层次列出了系统提供的所有操作,用户可以通过键盘或者指点设

备(通常是鼠标)来选择并执行一个操作。每个菜单项都有相应的帮助信息,便于用户随

时参看,如图1.10所示。

图 1.10 Windows 环境下菜单驱动的 GIS 界面

菜单驱动界面 大的长处在于界面友好,便于用户掌握系统。但是对于高级用户而言,

与命令行界面相比,它往往显得不够灵活而且效率低下。在GIS系统中,往往需要连续地对

批量数据进行处理,并且需要较长的计算时间,在这种情况下采用菜单驱动界面就变得不

可忍受。

1.4.3.3 基于数据流图的 GUI 界面

在基于数据流图的界面中,用户通过一种“可视化语言”,而不是严格的文本来控制

Page 26: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

18

系统。系统用图形符号表现其提供的功能,这些符号称为“图标”。图标不仅可以表示操

作,也可以表示数据或者硬件设备。图1.11给出了一个基于数据流图的GUI界面示例。

图 1.11 基于数据流图的 GUI 界面

在基于数据流图的用户界面中,用户可以通过“拖放(Drag and Drop)”操作实现相

应的操作(例如,可以将表示插值计算的图标拖放到表示等值线数据的图标上,以进行等

值线插值运算),设计和组织数据处理流程。 数据流图界面适用于数据流清晰、相对比较简单的系统,如数字图像处理软件。其缺

点是实现较为困难。

1.5 GIS应用模式与开发方式

地理信息系统开发方式应该随着地理信息系统应用模式的不同而不同。

1.5.1 GIS应用模式

地理信息系统可以分为通用地理信息系统平台和面向特定专题或领域的地理信息系统

平台,前者提供了基本的空间信息处理方案,可以应用于各个领域,一般由专门的软件开

发商完成,其开发过程类同于普通软件系统(如数据库管理系统、桌面出版系统等等);

后者往往针对某个用户单位提供特定的技术手段。具体而言,GIS按照其应用模式又可以分

为两类,即科学研究工具和办公服务系统。

1.5.1.1 科学研究工具

这种应用模式把地理信息系统作为科学研究工具,强调对于科学计算结果的获取和分

析。作为科学研究的辅助手段,它主要应用于有关地学研究领域的科研项目中。比较来说,

科研项目的规模有大小和难易之分,对于规模相对较小、内容相对简单的研究项目,如公

Page 27: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

19

共服务设施的选址等,通用地理信息系统具有的一般空间分析功能就能够满足研究的需求;

而对于那些规模相对较大、内容相对复杂的项目,如全球变化研究或大型项目环境评估等

项目,不仅需要用到地理信息系统通用软件所具有的一般功能,还要应用专业分析模型或

专家系统等。

1.5.1.2 办公服务系统

办公服务系统应用于涉及空间数据的政府部门及企业,以提高管理效率、制定正确的

决策和实现组织目标。 办公服务系统按照其应用层次的高低,又可以分为空间事务处理系统(STPS,Spatial

Transaction Process System)、空间管理信息系统(SMIS,Spatial Management Information System)、空间决策支持系统(SDSS,Spatial Decision Support System)和专家系统(ES,Expert System)。

空间事务处理系统的目标是迅速、及时、准确地处理大量空间信息,有效地进行日常

事务的自动化处理。它注重空间数据的收集、处理和存储,以供将来使用,在各种大型应

用地理信息系统的数据采集部门和具体事务部门都有着广泛的应用,包括测绘、资源调查、

地籍管理、地图出版等领域。 空间管理信息系统是基于空间事务处理系统发展起来的,除了提供高效率的信息处理

以外,还对决策者提供辅助决策信息,包括数据的查询和统计以及专业模型的分析功能。

SMIS运用专业模型来处理和分析数据,以实现对业务工作中确定性问题的处理和管理,提

供决策服务。 空间决策支持系统为决策者提供了一个模拟决策过程,并提供了选择方案的决策支持

环境,强调系统推理的有效性,更多地应用于宏观决策过程。 专家系统是能够模仿人工决策处理过程的基于计算机的信息系统,它由知识库、推理

机、解释系统、用户接口和知识获取系统组成。它扩大了计算机的应用范围,使其从传统

的资料处理领域扩展到智能推理上来。SMIS能够提供信息帮助制定决策,SDSS能够帮助

改善决策质量,只有专家系统能够应用智能推理制定决策并解释决策理由。 上述两种应用模式之间的界限并不是绝对的。一个决策支持系统可以使用与科学研究

工具中一致的分析模型,并且广义上讲,所有的地理信息系统应用的 终目标都是为了进

行空间决策。 除了可以从应用模式上划分GIS应用,还可以从规模上将其划分为小型、中型和大型应

用。小型GIS应用数据量小,使用系统的用户少,主要针对一个部门或特定领域,注重于专

业模型的开发和应用。中型GIS应用适于多个部门,数据量大,运行于局域网或城域网环境,

侧重于决策支持。大型的GIS应用则拥有非常多的用户和海量的数据,注重数据的管理,并

通过网络实现分布式计算和数据管理,通过Internet发布空间信息。 考虑到GIS具体的应用领域,我们对GIS应用进行三元划分,结果如图1.12所示。表1.1

给出了一个GIS应用的三元划分示例,其中每个具体的GIS应用系统的分类标准都对应于图

1.12中三维空间的特定坐标。

Page 28: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

20

图 1.12 地理信息系统应用的划分

表1.1 GIS应用的三元划分示例

名称 规模 应用模式与层次 领域

XX小流域水土流失系统 小型 科学研究工具 环境

XX城区土地划拨系统 中型 空间事务处理系统(STPS) 地籍

XX林场管理系统 中型 空间管理信息系统(SMIS) 林业

XX大城市市政管理系统 大型 空间管理信息系统(SMIS) 城市管理

XX省可持续发展决策支持系统 大型 空间决策支持系统(SDSS) 资源与环境

1.5.2 GIS开发方式

对从事地理信息系统研究和应用的工程技术人员而言,开发和设计地理信息系统具有

两个方面的含义:一是从底层开发一个通用的地理信息系统,即通用平台的开发;二是在

商业化地理信息系统(主要是通用的地理信息系统开发平台)的基础上进行二次开发,完

成专用地理信息系统的开发任务。二次开发又可分单纯二次开发与集成二次开发。

1.5.2.1 独立开发

独立开发是指不依赖于任何GIS工具软件,从空间数据的采集、编辑到数据的处理分析

及结果输出,所有的算法都由开发者独立设计,然后选用某种程序设计语言(如Visual C++、Delphi等),在一定的操作系统平台上编程实现。这种方式的好处在于无须依赖任何商业

GIS工具软件,减少了开发成本,但对于大多数开发者来说,能力、时间、财力方面的限制

使其开发出来的产品很难在功能上与商业化GIS工具软件相比,而且在购买GIS工具软件上

省下的钱可能还抵不上开发者在开发过程中所耗的时间与精力。

具体的GIS应用系统

应用模式与层次 科学研究工具 STPS SMIS SDSS ES

领域

规模

资源与环境农业

气候气象

地质城市规划与管理

军事

……

大型

中型

小型

Page 29: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

21

1.5.2.2 单纯二次开发

单纯二次开发指完全借助于GIS工具软件提供的开发语言进行应用系统开发。GIS工具

软件大多提供了可供用户进行二次开发的宏语言,如ESRI的ArcView提供了Avenue语言,

MapInfo公司开发的MapInfo Professional提供了MapBasic语言等等。用户可以利用这些宏语

言,以原GIS工具软件为开发平台,开发出自己的针对不同应用对象的应用程序。这种方式

省时省心,但进行二次开发的宏语言作为编程语言只能算是二流,功能极弱,用它们来开

发应用程序仍然不尽人意。

1.5.2.3 集成二次开发

集成二次开发是指利用专业的GIS工具软件(如ArcView、MapInfo等)实现GIS的基本

功能,以通用软件开发工具尤其是可视化开发工具,如Delphi、Visual C++、C#、Visual Basic、PowerBuilder等为开发平台,进行二者的集成开发。

集成二次开发目前主要有两种方式:

(1)OLE/DDE 采用OLE Automation技术或DDE(Dynamic Data Exchange,动态数据交换)技术,用

软件开发工具开发前台可执行应用程序,以OLE自动化方式或DDE方式启动GIS工具软件在

后台执行,利用回调技术动态获取其返回信息,实现应用程序中的地理信息处理功能。 (2)GIS组件 利用GIS工具软件生产厂家提供的建立在OCX(OLE Custom Controls,OLE自定义控

件)技术基础上的GIS功能组件,如ESRI的MapObjects、MapInfo公司的MapX等,在C#、Visual C++、C++ Builder、Delphi、Visual Basic等编程语言编写的应用程序中,直接将GIS功能嵌入其中,实现地理信息系统的各种功能。

上述的GIS开发方式各有利弊,如表1.2所示,各个组织可以根据具体情况确定采用何

种方案。

表1.2 建立GIS应用方案的比较

实施方案 用户独立 开发

购买通用

平台 购买完 整软件

购买完 整系统

购买 服务

承包开发 合作开发

对提供者依赖性 低 低 高 很高 很高 很高 中

系统运行时间 长 长/中长 短 很短 很短 长/中长 长/中长

初始费用 低 中等 中等 高 高 高 中等

人力费用 高 中等 低 低 很低 低 中等

风险和不确定性 高 较低 低 低 中等 高 中等

灵活性 很高 很高 中等 中等 不定 高 很高

对用户技术要求 很高 高 中等 中等 很低 中等 高

现有资源的利用 高 高 中等 低 很低 低 中等/高

Page 30: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

22

1.6 “北京市地理信息公众查询系统”介绍

在后续章节中,本书将以“北京市地理信息公众查询系统”为例来具体介绍如何进行

GIS的二次开发。因此,本节简单介绍该系统的功能及主窗口的布局,以便读者在开发系统

时有一个全局概念。 “北京市地理信息公众查询系统”主要包含如下一些功能:

(1)显示各类北京市地理信息,包括行政区划、交通运输、组织机构、娱乐休闲、地

名等。 (2)在地图中可以控制地理信息的显示类别。例如可只显示交通运输信息。 (3)可放大、缩小、漫游或打印地图。 (4)可根据用户在地图中选择的图形查询地物信息。 (5)可根据地名信息查找其对应地物在地图上的位置。 (6)公交路线查询。 (7)公交换乘方案查询。 (8) 短路径查询。

“北京市地理信息公众查询系统”的主界面如图1.13所示。

图 1.13 系统主界面的组成

地物类型工具栏 地图控制工具栏

状态栏

地图显示窗口

鹰眼窗口

工作区

Page 31: C++ Builder和MapObjects实现

第 1章 地理信息系统软件工程

23

地物类型工具栏中的按钮用于控制地图中显示的地物类型,例如可以只显示医院的分

布。也可以同时显示所有类型的地物。 地图控制工具栏中的按钮用于控制用户在地图上的操作,例如通过其中的按钮可放大、

缩小、漫游、打印地图,也可测量两点间的距离,测量多边形的面积,查询地物的信息。 工作区包括2个选项卡及鹰眼窗口。2个选项卡分别用于控制地图的显示及地名与公交

查询。鹰眼窗口按全图的显示比例显示地图,鹰眼窗口中有一个矩形框,代表地图显示窗

口中当前的显示区域。 地图显示窗口用于显示各种地理信息,还可以与用户进行交互,放大、缩小、漫游地

图,以及查询地物信息等。 状态栏中主要显示了当前鼠标所指地图位置的经纬度及当前地图显示比例等信息。

Page 32: C++ Builder和MapObjects实现

第 2 章 需 求 分 析

需求分析阶段位于软件开发的前期,它的基本任务是准确地定义未来系统的目标,确

定为了满足用户的需求系统必须做什么。 需求获取的目的是清楚地了解所要解决的问题,完整地获取用户需求。这项工作主要

包括以下几个方面:通过学习、请教领域专家、向用户提问等手段,了解所要解决的问题,

获取用户需求,确认谁是真正的用户,以及系统实现所受到的各种限制。 本章主要以我们开发的“北京市地理信息公众查询系统”为例介绍需求分析。

2.1 需 求 概 述

地理信息是指与空间和地理分布有关的信息。权威的统计资料和研究报告都表明,在

人类活动所接触到的信息中有80%与地理位置和空间分布有关。地理信息用于地球研究即

为地理信息系统。 随着信息产业的迅猛发展,作为信息产业重要领域的地理信息技术的发展也迅猛异常。

作为这一产业的核心,地理信息软件是我国软件重点发展对象之一。 人们的日常生活与地理信息密切相关,包括衣、食、住、行、学习、工作、娱乐、消

费等各个方面。城市是人们现实生活中一个重要的活动空间,随着现代城市的飞速发展,

人们对城市的了解不再停留在原有的数字图或平面图上,而是要求有一个直观的、现实的

感受和了解。城市信息是指为满足城市居民日常生活、工作需要的时间和空间信息,如城

市道路、交通、旅游、电信、服务机构 (包括医疗、商业、政府机关)等。城市信息服务

是为城市居民提供各种信息、日常业务等以信息数据处理为主要内容的各类服务项目,如

提供城市交通路况、旅游景点分布及其详情、商业网点的布局及各自特色、城市道路与建

筑物的空间分布等。在数字城市中,人们只需在计算机前告诉系统自己感兴趣的城市或想

了解的信息,即可以对该空间信息进行定位、浏览,甚至可以通过数字地球开展业务运作、

购物、旅游、休闲、娱乐、与朋友聚会聊天等。 你肯定有过这样的尴尬,一个重要的约会,时间快到了,可对地形不熟的你却怎么也

找不到约会的地点。如果地理信息系统(GIS)能在公众中普及就不会再有类似的麻烦。国

家基础地理信息中心主任陈军教授这样形容数字化地理的 高境界:“把复杂的地理信息

变成全社会都能够充分利用和享受的信息数据。” 在武汉市的一些酒店,人们可以使用触摸屏式电脑查询当地的地理信息。存在电脑里

的数字化地理信息包括旅游景区、商业街区,甚至还可以找到 地道的名小吃。过去专业

味特别浓的地理信息技术正在走出象牙塔,其服务对象将不再只是政府部门、专业机构,

而会扩大到所有有需求的公众。无线通信技术日新月异的发展更为地理信息个人化提供了

Page 33: C++ Builder和MapObjects实现

25

第 2章 需求分析

坚实的基础。 这里 值得注意的是地理信息个人化。早在2001年10月,日本一家通信公司就推出了

新一代移动电话服务——位置服务,用户可以通过手机从地图上搜索地址、邮编、车站、

饭店、宾馆等,大大方便了人们的出行。该公司规划,不久将提供日本733个城市的地理信

息。 由此可见,公众现在越来越需要地理信息的服务。特别是对于一些大城市(如北京),

由于流动人口、旅游人数众多,公众对地理信息的需求越显突出。

2.2 功能性需求

直接面向公众,为公众提供信息服务和辅助公众进行行为决策的GIS称为公众GIS。公

众GIS与专业或行业GIS相比较,存在以下几个方面的差异:

(1)产业特点。与专业和行业GIS相比,公众GIS由于面向社会和公众,因此具有信

息服务业的特点。 (2)用户对象。专业和行业GIS面向的是GIS专业人员和行业管理人员,他们均具有

较强的GIS基础,对GIS的概念、原理和技术内核有很深或一定程度的理解和掌握。而公众

GIS面向的是社会公众,他们具有不同的知识水平和职业背景,其中大多数对GIS并不十分

了解,甚至完全陌生。 (3)系统目标。专业和行业GIS往往是为某一个行业、某一个部门或者某一具体工程

项目提供相关地理信息的管理与决策工具,而公众GIS的目标是为公众提供地理信息服务和

辅助公众进行行为决策。 (4)信息内容。专业和行业GIS提供的信息内容大多带有明显的专业和行业特点,而

公众GIS提供的是公众所关心和需要(通俗地说与衣食住行相关)的信息,而公众关心和需

要的信息往往是跨行业的,具有综合性。以上差异决定了公众GIS与专业和行业GIS的实用

模式、功能需求和实现方法上的差别。

与传统GIS相比较,公众GIS的特点如下:

(1)信息的全面性、现势性和准确性。全面性是指系统提供的信息应该涉及面广,尽

量包括城市社会的各个方面,从而做到为公众提供全方位的地理信息服务;现势性是指系

统提供的公众信息必须是 新的,因为公众信息经常变动,而系统提供的信息必须反映社

会的发展变化。 (2)信息的共享特性和易维护性。公众信息具有数据量大、更新频率高的特点,因此,

系统建设时应充分考虑如何使数据更新和维护变得容易,并尽量减少数据更新和维护的工

作量,这样既可以缩短信息的更新周期,又可以减少数据维护所需的人力和物力。

当然,“北京市地理信息公众查询系统”属于公众GIS的范畴。针对该特点,系统应该

满足如下总体功能需求(具体功能需求见2.2.3节):

Page 34: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

26

(1)界面设计简单、美观; (2)用户不需要接受任何培训,便能直接使用系统进行查询; (3)使用的空间数据必须是 新的; (4)属性数据的更改不应该影响系统运行的稳定性; (5)系统运行结果应该反映属性数据的更改。

2.2.1 系统体系结构

“北京市地理信息公众查询系统”根据其功能需求分为6个子系统,如图2.1所示。

(1)电子地图控制子系统 (2)电子地图显示子系统 (3)电子地图输出子系统 (4)地名查询子系统 (5)在线帮助子系统 (6)其他

图 2.1 北京市地理信息公众查询系统的体系结构

2.2.2 用户描述

北京市地理信息公众查询系统的用户是广大的公众,因此其中可能有对地理信息系统

比较熟悉的用户,但是绝大部分用户对地理信息系统没有多少概念,因此更谈不上熟练操

作专业的地理信息系统。面对这样的用户群体,对系统的易操作性要求非常高。

北京市地理信息公众查询系统

电子地图控制

电子地图显示

电子地图输出

地名查询

在线帮助

其他

Page 35: C++ Builder和MapObjects实现

27

第 2章 需求分析

2.2.3 具体功能需求

2.2.3.1 电子地图控制子系统

(1)地图索引 通过树状列表向用户显示“北京市地理信息公众查询系统”中所有地图的信息,同时

允许用户通过鼠标在地图集中选择目标地图作为当前地图。 (2)地物控制 通过树状列表向用户显示组成当前地图的所有图层信息,包括图层名称、可见性。用

户通过鼠标能够设置地图各个图层的可见性。

2.2.3.2 电子地图显示子系统

(1)地图显示功能 当用户在地图索引树状列表窗口中双击某地图名称时,地图显示窗口将显示该地图。 (2)地图放大功能 点击“放大”按钮,光标呈放大镜状。此后,当鼠标在地图上某一点单击时,地图将

以该地点为中心放大一倍比例尺显示;当鼠标在地图上拉一矩形框放大时(按下鼠标左键

并移动光标到适当位置),屏幕将以无级缩放的形式显示矩形框指定范围的地图。拉出的

框长宽比可能与显示屏长宽比不一致,但显示时会自动调整到 佳状态。随着地图不断放

大,可显示的图层数将逐渐增多,电子地图内容越来越丰富。这样,通过调整放大级别,

达到在计算机屏幕上对大范围地图的纵观全局及细查局部的效果;为实现 佳显示效果,

地图放大若干倍后不再放大。

键盘操作:按键盘上“+”键,效果同点击“放大”按钮相同。 (3)地图缩小功能 点击“缩小”按钮,光标呈缩小镜状。在地图上任一位置单击鼠标左键,地图将以该

点为中心缩小一倍比例尺显示。随着地图的不断缩小,可显示的图层数和地物内容也相应

减少,当地图缩小到比全图显示还要小时不再缩小。若地图偏离窗口中央,系统自动将地

图拉回到窗口中央显示。

键盘操作:按键盘上“-” 键,效果同点击“缩小”按钮相同。

(4)地图漫游功能 点击“漫游”按钮,光标呈手状,将光标移至某一位置按下鼠标左键在屏幕上拖动,

地图将向拖动方向连续漫游,此时地图比例尺和图层数保持不变。

键盘操作:按键盘上“↑”、“↓”、“←”、“→” 键,地图向上、下、左、右移

动四分之一屏。

当光标在地图上移动到显示窗口的边界或四角时,光标变成方向箭头状,此时点击光

标地图向相反方向移动。

(5)地图刷新功能 用户可以随时刷新显示电子地图。 (6)地图鹰眼功能 鹰眼(缩略图)窗口按全图显示比例显示电子地图的缩略图,缩略图上有一个矩形框,

Page 36: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

28

代表地图显示窗口中当前显示的内容。用鼠标拖动矩形框,当矩形框移动到用户需要的区

域上时,地图显示窗口里的电子地图也快速显示相应位置的内容。

2.2.3.3 电子地图输出子系统

(1)输出全图到BMP 用户可以将当前电子地图保存为BMP(位图)形式。 (2)输出全图到打印机 用户可以打印当前电子地图。 (3)输出屏幕到BMP 用户可以将当前电子地图在屏幕上显示的部分保存为BMP(位图)。 (4)输出屏幕到打印机 用户可以打印当前电子地图在屏幕上显示的部分。

2.2.3.4 地名查询子系统

(1)Info工具 用户利用该工具便可通过鼠标查询地物的属性信息。当用户点击工具栏上的“信息”

按钮后,该功能启动。鼠标移动到电子地图上时,鼠标样式即变为查询状;而鼠标移出电

子地图时,样式恢复原状。当鼠标在电子地图上的某一点单击时,即查询该点所在位置上

的所有地物。查询结果显示在查询结果视图中。 (2)点选择工具 用户可通过点选择工具选择目标地物。当用户点击工具栏上的“点选择”按钮后,该

功能激活。当鼠标移动到电子地图上时,鼠标样式即变为点选择状;而鼠标移出电子地图

时,样式恢复原状。当鼠标在电子地图的某一点上单击时,选择该点所在位置上的所有地

物,选择结果显示在查询结果视图上。 (3)矩形选择工具 用户可通过矩形选择工具选择目标地物。当用户点击工具栏上的“矩形选择”按钮后,

该功能激活。当鼠标移动到电子地图上时,鼠标样式即变为矩形选择状;而鼠标移出电子

地图时,样式恢复原状。用鼠村在电子地图上拉出一个矩形区域,系统将选择包含在这个

矩形内的所有地物,并将选择结果显示在查询结果视图上。 (4)多边形选择工具 用户可通过多边形选择工具选择目标地物。当用户点击工具栏上的“多边形选择”按

钮后,该功能激活。当鼠标移动到电子地图上时,鼠标样式即变为多边形选择状;而鼠标

移出电子地图时,样式恢复原状。用鼠标在电子地图上拉出一个多边形区域,系统将选择

包含在这个多边形内的所有地物,并将选择结果显示在查询结果视图上。 (5)查询 近目标 该功能允许用户输入地名查询距离该地名 近的单位(地物),并将查询结果显示在

查询结果视图上。当用户选择“查询”页面的“查找 近”选项卡后,该功能激活。用户

通过文本框输入地名,通过组合框选择要查询的距离,例如 近50m、100m、200m等等,

通过组合框选择要查询目标的类型,例如所有单位、政府机关、学校等等。 后单击“搜

Page 37: C++ Builder和MapObjects实现

29

第 2章 需求分析

索”按钮,查询结果显示在查询结果视图上。 (6)距离量算 该功能用于计算用户在电子地图上输入一条折线的长度。当用户点击工具栏上的“距

离量算”按钮后,该功能激活。当用户将鼠标移动到电子地图上时,鼠标样式即变为十字

状;而鼠标移出电子地图时,鼠标样式恢复原状。单击鼠标左键,即输入折线的结点,单

击鼠标右键,折线结点输入结束,这时弹出的对话框显示折线的长度(单位为m)。 (7)面积量算 该功能用于计算用户在电子地图上输入一个多边形的面积和周长。当用户点击工具栏

上的“面积量算”按钮后,该功能激活。当鼠标移动到电子地图上时,鼠标样式即变为十

字状;而鼠标移出电子地图时,鼠标样式恢复原状。单击鼠标左键,即输入多边形的结点,

单击鼠标右键,多边形结点输入结束,这时弹出的对话框显示多边形的周长(单位为m)

和面积(单位为m2)。 (8)地名索引 通过选择地名类型,显示所有该类型的地名,再选择某一具体地名就可在地图上定位

到该地名。当用户选择“查询”页面的“地名索引”选项卡时,该功能激活。默认状态下,

地名类型为全部类型,地名列表框中显示所有的地名。用户通过组合框选择某一地名类型

后,地名列表框中显示该类型所有的地名。从地名列表框中选择所需的地名后双击鼠标左

键,则在地图上定位此地名。 (9)地名精确查询 用户通过输入地名或电话号码精确查询地名,查询结果显示在查询结果视图上。当用

户选择“查询”页面的“地名查询”选项卡时,该功能激活。默认状态为精确查询,用户

也可通过单选按钮设置模糊查询为假从而实现精确查询。通过文本框输入地名或电话(或

两者都输入,组合关系为“与”),单击“搜索”按钮,查询结果显示在查询结果视图上。 (10)地名的定位 根据地名的名称,用户将地名定位在电子地图上,即从属性查图形。在“地名查询”

选项卡中,查询结果出现在查询结果窗口上,该功能激活。用户通过鼠标在查询结果窗口

上选择一个地名,双击鼠标左键。如果当前电子地图中没有这个地名,则提示用户,用例

结束;如果当前电子地图中有这个地名,并且地名正好落在地图窗口的显示区域,则高亮

显示地名对应的地物;如果地名在地图窗口的显示区域外,则以这个地名对应的地物为显

示中心移动电子地图,并高亮显示这个地名对应的地物。 (11)地名信息的浏览 用户通过信息窗口(一个独立的窗口)浏览地名的详细信息。地名的查询结果出现在

查询结果窗口上,该功能激活。用户通过鼠标在查询结果窗口中选择一个地名。如果信息

窗口已经打开,则在信息窗口中显示这个地名的详细信息。如果信息窗口没有打开,则单

击“浏览”按钮,显示信息窗口。 (12)公交路线查询功能 此功能的目的是在地图上查询并显示从当前位置到目的地所需的乘车路线、沿途车站、

换乘车次。

Page 38: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

30

(13)目标分布图功能 此功能可以方便快捷地查询出你想要的数据,只需选择查询区域和类别,查询结果窗

口就显示出相关信息。例如,选择某一个区域,其类别为“小学”,查询结果窗口则显示

出该区域内的全部小学。

2.2.3.5 在线帮助子系统

用户存在疑问时,可通过在线帮助寻找答案,从而更方便地使用本产品。用户点击地

图控制工具栏的“帮助”按钮后,该功能激活。通过鼠标在帮助列表框中选择所需条目,

在帮助条目上双击,相应的帮助内容显示在显示区的帮助窗口内。

2.2.3.6 其他

其他一些功能需求包括系统启动界面、安装程序制作等。

2.3 非功能性需求

2.3.1 性能需求

由于北京市地理信息公众查询系统数据基本固定,所需处理的数据量不大,并且面对

的用户是广大公众,因此基本上没有对性能有严格的要求。

2.3.2 安全性需求

需要对空间数据以及属性数据加密。

2.4 功能需求详细描述

前面对系统功能需求的描述很简单,我们需要对其核心功能定义更详细的功能需求。

(1)地图的放大

用例编号: UC-2-2 用例名称: 地图的放大

作者: 张云星 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了放大电子地图的过程。

前置条件: 显示区中的活动窗口是地图显示窗口。

Page 39: C++ Builder和MapObjects实现

31

第 2章 需求分析

正常路径: 1. 当用户点击工具栏上的“放大”按钮后,用例开始。 2. 鼠标移动到电子地图上,鼠标的样式即变为放大镜状;而鼠标移出

电子地图,鼠标的样式又变为原状。 3. 当鼠标在电子地图上的某一点单击时:

a)如果允许继续放大,则地图将以该点为中心放大显示,比例为原

来的一倍,用例结束。 b)如果不允许继续放大,则提示“地图不能再进行放大操作”,用

例结束。 可选路径: 4. 在第3步,用鼠标在地图上拉出一个矩形框(按下左键保持并移动光

标到适当位置释放左键): a) 如果允许继续放大,则地图显示窗口满屏显示矩形框指定的范

围,达到地图无级缩放的目的,用例结束。 b)如果不允许继续放大,则提示“地图不能够再进行放大操作”,

用例结束。

异常处理: 后置条件: 电子地图按照用户的要求实现了放大显示。此时,鼠标的样式保持为

放大镜状,即又可执行第3步。

(2)地图的缩小

用例编号: UC-2-3 用例名称: 地图的缩小

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了缩小电子地图的过程。

前置条件: 1. 显示区中的活动窗口是地图显示窗口。 2. 电子地图的显示比例大于全图显示比例。

正常路径: 1. 当用户点击工具栏上的“缩小”按钮后,用例开始。 2. 鼠标移动到电子地图上,鼠标的样式即变为缩小镜状;而鼠标移出

电子地图,鼠标的样式又变为原状。 3. 当鼠标在电子地图上的任意位置单击时:

a)如果此时显示比例大于全图显示比例,则地图将以该点为中心缩

小显示,比例为原来的二分之一,用例结束。 b)如果此时显示比例小于或等于全图显示比例,则不允许继续缩小,

提示“地图不能够再进行缩小操作”,用例结束。 可选路径: 异常处理:

Page 40: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

32

后置条件: 电子地图按照用户的要求实现了缩小显示。此时,鼠标的样式保持为缩

小镜状,即又可执行第3步。

(3)地图的全图显示

用例编号: UC-2-4 用例名称: 地图的全图显示

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了电子地图全图显示的过程。

前置条件: 显示区中的活动窗口是地图显示窗口。 正常路径: 1. 当用户点击工具栏上的“全图显示”按钮后,用例开始。

2. 电子地图的显示比例设为全图显示比例,鼠标的样式变为正常状,

用例结束。 可选路径: 异常处理: 后置条件: 电子地图按照用户的要求实现了全图显示。

(4)地图的漫游

用例编号: UC-2-5 用例名称: 地图的漫游

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了移动电子地图的过程。

前置条件: 1. 显示区中的活动窗口是地图显示窗口。 2. 电子地图的显示比例大于全图显示比例。

正常路径: 1. 当用户点击工具栏上的“漫游”按钮后,用例开始。 2. 鼠标移动到电子地图上,鼠标的样式即变为手状;而鼠标移出电子

地图,鼠标的样式又变为原状。 3. 将鼠标移至地图某一位置,按下鼠标左键在屏幕上拖动鼠标,地图

向拖动方向连续移动。 4. 松开鼠标左键,地图停止移动,用例结束。

可选路径: 异常处理:

Page 41: C++ Builder和MapObjects实现

33

第 2章 需求分析

后置条件: 电子地图按照用户的要求实现了移动。

(5)地图的刷新

用例编号: UC-2-10 用例名称: 地图的刷新

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了刷新地图的过程。

前置条件: 显示区中的活动窗口是地图显示窗口。 正常路径: 1. 当用户点击工具栏上的“刷新”按钮后,用例开始。

2. 地图显示窗口被重新绘制。 可选路径: 异常处理: 后置条件: 电子地图按照用户的要求实现了刷新。

(6)地图鹰眼

用例编号: UC-2-13 用例名称: 地图鹰眼

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了通过鹰眼快速移动电子地图的过程。

前置条件: 显示区中的活动窗口是地图显示窗口。 正常路径: 1. 打开鹰眼窗口后,用例开始。

2. 鹰眼窗口按全图显示比例显示电子地图的缩略图,缩略图上有一个

矩形,代表地图显示窗口中的当前显示区域。 3. 将鼠标移动到矩形上,按下鼠标左键,拖动矩形。 4. 当矩形移动到用户需要的区域,释放鼠标左键,地图显示窗口里的

电子地图也快速移动到了相应的位置。 可选路径: 异常处理: 后置条件: 电子地图按照用户的要求实现了快速移动。

Page 42: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

34

用例编号: UC-4-1 用例名称: Info工具

作者: 雷睿勇 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了用户通过鼠标查询地物的属性信息。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“信息”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为查询状;而鼠标移出电

子地图,鼠标的样式又变为原状。 3. 当鼠标在电子地图上的某一点单击时,查询该点所在位置的所有地

物。 4. 查询结果显示在查询结果视图上,用例结束。

可选路径: 异常处理: 后置条件: 通过Info工具,将查询结果显示在查询结果视图上。

(8)点选择工具

用例编号: UC-4-2 用例名称: 点选择工具

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了用户通过点选择工具选择目标地物。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“点选择”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为点选择状;而鼠标移出

电子地图,鼠标的样式又变为原状。 3. 当鼠标在电子地图上的某一点单击时,选择该点所在位置的所有地

物。 4. 选择结果显示在查询结果视图上,用例结束。

可选路径: 异常处理: 后置条件: 通过点选择工具选择目标地物,选择结果显示在查询结果视图上。

(7)Info工具

Page 43: C++ Builder和MapObjects实现

35

第 2章 需求分析

用例编号: UC-4-4 用例名称: 矩形选择工具

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了用户通过矩形选择工具选择目标地物。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“矩形选择”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为矩形选择状;而鼠标移

出电子地图,鼠标的样式又变为原状。 3. 用鼠标在电子地图上拉出一个矩形区域,选择包含在这个矩形内的

所有地物。 4. 选择结果显示在查询结果视图上,用例结束。

可选路径: 异常处理: 后置条件: 通过矩形选择工具选择目标地物,选择结果显示在查询结果视图上。

(10)多边形选择工具

用例编号: UC-4-5 用例名称: 多边形选择工具

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 描述了用户通过多边形选择工具选择目标地物。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“多边形选择”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为多边形选择状;而鼠标

移出电子地图,鼠标的样式又变为原状。 3. 用鼠标在电子地图上拉出一个多边形区域,选择包含在这个多边形

内的所有地物。 4. 选择结果显示在查询结果视图上,用例结束。

可选路径: 异常处理: 后置条件: 通过多边形选择工具选择目标地物,选择结果显示在查询结果视图上。

(9)矩形选择工具

Page 44: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

36

用例编号: UC-4-7 用例名称: 查找 近目标

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户输入地名,查询距离它 近的单位(地物),并将查询结果显示

在查询结果视图上。 前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户选择“查询”页面的“查找 近”选项卡,用例开始。

2. 通过文本框输入地名。 3. 通过组合框选择要查询的距离,例如50m、100m、200m等等。 4. 通过组合框选择要查询的单位类型,例如所有单位、政府机关、学

校等等。 5. 点击搜索按钮,查询结果显示在查询结果视图上,用例结束。

可选路径: 6. 第5步,如果用户输入的地名不存在,则输出错误信息“数据库中没

有用户输入的地名”,用例结束。 异常处理: 后置条件: 查询 近单位,查询结果显示在查询结果视图上。

(12)距离量算

用例编号: UC-4-11 用例名称: 距离量算

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户在电子地图上输入一条折线,量算出折线的距离长度。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“距离量算”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为十字状;而鼠标移出电

子地图,鼠标的样式又变为原状。 3. 单击鼠标左键,即输入折线的结点。 4. 单击鼠标右键,折线结点输入结束。 5. 弹出一个对话框显示折线的长度(单位为m),用例结束。

可选路径: 异常处理: 鼠标移到电子地图上,单击鼠标左键仅一次就单击鼠标右键结束结点

输入,则没有生成折线,用例结束。 后置条件:

(11)查询最近目标

Page 45: C++ Builder和MapObjects实现

37

第 2章 需求分析

(13)面积量算

用例编号: UC-4-12 用例名称: 面积量算

作者: 陈文明 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户在电子地图上输入一个多边形,量算出多边形的面积和周长。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户点击工具栏上的“面积量算”按钮后,用例开始。

2. 鼠标移动到电子地图上,鼠标的样式即变为十字状;而鼠标移出电

子地图,鼠标的样式又变为原状。 3. 单击鼠标左键,即输入多边形的结点。 4. 单击鼠标右键,多边形结点输入结束。 5. 弹出一个对话框显示多边形的周长(单位为m)和面积(单位为m2),

用例结束。 可选路径: 异常处理: 鼠标移到电子地图上,单击鼠标左键少于3次后就单击鼠标右键结束结

点输入,则没有生成多边形,用例结束。 后置条件:

(14)地名索引

用例编号: UC-4-13 用例名称: 地名索引

作者: 陈文明 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 通过选择地名类型,显示所有该类型的地名,再定位某一具体地名。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户选择“查询”页面的“地名索引”选项卡时,用例开始。

2. 默认状态下,地名类型为全部类型,地名列表框中显示所有的地名。

3. 用户通过组合框选择地名的类型后,地名列表框中显示所有这一类

型的地名。 4. 从地名列表框中选择所需的地名双击鼠标左键,则在地图上定位此

地名,用例结束。 可选路径:

Page 46: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

38

异常处理: 定位的地名如果在空间数据库中不存在,则向用户显示提示。 后置条件:

(15)地名精确查询

用例编号: UC-4-14 用例名称: 地名的精确查询

作者: 陈文明 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户通过输入地名或电话号码精确查询地名,查询结果显示在查询结

果视图上。 前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户选择“查询”页面的“地名查询”选项卡时,用例开始。

2. 默认状态为精确查询,或用户设置模糊查询为假。 3. 用户通过文本框输入地名或电话号码(或两者都输入,组合关系为

“与”)。 4. 单击搜索按钮,查询结果显示在查询结果视图上,用例结束。

可选路径: 异常处理: 后置条件:

(16)地名模糊查询

用例编号: UC-4-15 用例名称: 地名的模糊查询

作者: 陈文明 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户通过输入地名或电话号码模糊查询地名,查询结果显示在查询结

果视图上。 前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 当用户选择“查询”页面的“地名查询”选项卡时,用例开始。

2. 默认状态为精确查询,此时用户设置模糊查询为真。 3. 用户通过文本框输入地名或电话(或两者都输入,组合关系为“与”)。

4. 单击搜索按钮,查询结果显示在查询结果视图上,用例结束。 可选路径:

Page 47: C++ Builder和MapObjects实现

39

第 2章 需求分析

异常处理: 后置条件:

(17)地名的定位

用例编号: UC-4-16 用例名称: 地名的定位

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 根据地名的名称,系统将地名定位在电子地图上,即从属性查图形。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 在地名查询用例中,查询的结果出现在查询结果窗口上,本用例开

始。 2. 用户通过鼠标在查询结果窗口上选择一个地名,双击鼠标左键。

a)如果当前电子地图中没有这个地名,则提示用户,用例结束。

b)如果当前电子地图中有这个地名: 1)地名正好落在地图窗口的显示区域,则高亮显示地名对应的

地物,用例结束。 2)地名在地图窗口的显示区域外,则

2.1)以这个地名对应的地物为显示中心,移动电子地图。

2.2)高亮显示这个地名对应的地物,用例结束。 可选路径: 3. 第1步,在地名索引用例中,地名出现在地名列表框中,本用例开

始。用户通过鼠标在地名列表框中选择一个地名,双击鼠标左键。

异常处理: 后置条件:

(18)地名信息的浏览

用例编号: UC-4-17 用例名称: 地名信息的浏览

作者: 陈文明 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户通过信息窗口浏览地名的详细信息。

前置条件: “北京市地理信息公众查询系统”正在显示电子地图。 正常路径: 1. 地名查询结果出现在查询结果窗口上,本用例开始。

Page 48: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

40

2. 用户通过鼠标在查询结果窗口中选择一个地名。 a)如果信息窗口已经打开,则在信息窗口中显示这个地名的详细信

息,用例结束。 b)如果信息窗口没有打开,则单击“浏览”按钮,显示信息窗口,

用例结束。 可选路径: 第1步,在地名索引用例中,地名出现在地名列表框中,本用例开始。

用户通过鼠标在地名列表框中选择一个地名,余下步骤同2。 异常处理: 后置条件:

(19)在线帮助

用例编号: UC-5-1 用例名称: 在线帮助

作者: 张行 后修改的人: 杜海江 创建时间: 后修改的时间:

用户: “北京市地理信息公众查询系统”用户 概述: 用户通过在线帮助,可以更方便地使用本产品。

前置条件: 正常路径: 1. 用户点击工具栏上的“帮助”按钮,用例开始。

2. 通过鼠标在帮助列表框中选择所需条目。 3. 在帮助条目上双击鼠标左键,帮助内容显示在显示区的帮助窗口内,

用例结束。 可选路径: 异常处理: 后置条件: 显示区的活动窗口为帮助窗口。

Page 49: C++ Builder和MapObjects实现

第 3 章 系统总体设计

前面讲过,“北京市地理信息公众查询系统”属于公众GIS系统。与专业和行业GIS相比,公众GIS系统的用户友好性是十分关键的。公众GIS的系统设计必须考虑到公众心理,

如:(1)界面简洁明了,并且具有一定的趣味性,使用户对该系统有信心和兴趣;(2)操作简单,无须花太多时间就可以掌握系统的使用方法;(3)在GIS原理和功能表达上,

某些计算机术语应该通俗化,易于公众接受;(4)系统应该实时对用户的操作做出响应,

尽量缩短等待时间等等。因此,系统必须从界面设计、辅助帮助、屏幕动画、信息的动感

表现、操作风格等方面满足公众的要求。

3.1 系统平台选择

3.1.1 硬件平台

由于本系统属于公众GIS,面对的用户是广大民众,因此系统对硬件平台的要求不应该

太高,越低越好。

3.1.2 系统操作平台

考虑到国内个人计算机的操作系统一般都是Microsoft的Windows系列,因此本系统操

作平台选择Windows系列。

3.1.3 数据库平台

目前的数据库系统ORACLE,SYBASE,INFORMIX,DB2等,各有千秋,根据“北

京市地理信息公众查询系统”对数据量的要求,无需采用大型的数据库管理系统,因此本

系统使用Microsoft的Access 2000。

3.1.4 系统开发模式与GIS组件选择

组件式软件开发技术已经成为当今软件技术的潮流之一,为了适应这种技术潮流,GIS软件像其他软件一样,已经或正在发生着革命性的变化,即由过去厂家提供全部系统或具

有二次开发功能的软件,过渡到厂家提供组件由用户自己再开发的方向上来。无疑,组件

式GIS技术将给整个GIS技术体系和应用模式带来巨大影响。 GIS技术的发展,在软件开发模式上经历了功能模块、包式软件、核心式软件,到组件

Page 50: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

42

式GIS和Web GIS的过程。传统GIS虽然在功能上已经比较成熟,但是这些系统多是基于十

多年前的软件技术,属于独立封闭的系统。同时,GIS软件变得日益庞大,用户难以掌握,

费用昂贵,阻碍了GIS的普及和应用。组件式GIS的出现为传统GIS面临的多种问题提供了

全新的解决思路。 组件式GIS的基本思想是把GIS的各大功能模块划分为几个组件,每个组件完成不同的

功能。各个GIS组件之间及GIS组件与其他非GIS组件之间,可以方便地通过可视化的软件

开发工具集成起来,形成 终的GIS应用。形象地说,组件如同一堆各式各样的积木,它们

分别实现不同的功能(包括GIS和非GIS功能),人们根据需要把实现各种功能的“积木”

搭建起来,就构成了GIS应用系统。 把GIS的功能适当抽象,以组件形式供开发者使用,将会带来许多传统GIS工具无法比

拟的优点。

(1)小巧灵活、价格便宜 由于传统GIS结构的封闭性,往往使得软件本身变得越来越庞大,不同系统的交互性差,

系统的开发难度大。在组件模型下,各组件都集中地实现与自己 紧密相关的系统功能,

用户可以根据实际需要选择所需组件,这样 大限度地降低了用户的经济负担。组件化的

GIS平台集中提供空间数据管理能力,并且能以灵活的方式与数据库系统连接。在保证功能

的前提下,系统表现得小巧灵活,而其价格仅是传统GIS开发工具的十分之一,甚至更少。

这样,用户便能以较好的性价比获得或开发GIS应用系统。 (2)无须专门GIS开发语言,直接嵌入MIS(Manage Information System,管理信息系

统)开发工具 传统GIS往往需要独立的二次开发语言,对用户和应用开发者而言存在学习上的负担。

而且使用系统所提供的二次开发语言,开发往往受到限制,难以处理复杂问题。而组件式

GIS建立在严格的标准之上,不需要额外的GIS二次开发语言,只需实现GIS的基本功能函

数,按照Microsoft的ActiveX控件标准开发接口。这有利于减轻GIS软件开发者的负担,而

且增强了GIS软件的可扩展性。GIS应用开发者不必掌握额外的GIS开发语言,只需熟悉基

于Windows平台的通用集成开发环境,以及GIS各个组件的属性、方法和事件,就可以完成

应用系统的开发和集成。目前,可供选择的集成开发环境很多,如Visual C++、Visual Basic、C#、Visual FoxPro、Borland C++、Delphi、C++ Builder以及Power Builder等都可直接成为

GIS或GMIS(Geographic Manage Information System,地理管理信息系统)的优秀开发工具,

它们各自的优点都能够得到充分发挥。这与传统GIS专门性的开发环境相比,是一种质的飞

跃。 (3)强大的GIS功能 新的GIS组件都是基于32位系统平台的,采用InProc直接调用形式,所以无论是管理大

容量数据的能力还是处理速度方面均不比传统GIS软件逊色。小小的GIS组件完全能提供拼

接、裁剪、叠合、缓冲区等空间处理能力和丰富的空间查询与分析能力。 (4)开发简捷 由于GIS组件可以直接嵌入MIS开发工具中,这样,广大开发人员就可以自由选用他们

熟悉的开发工具。而且,GIS组件提供的API形式非常接近MIS工具的模式,开发人员可以

Page 51: C++ Builder和MapObjects实现

43

第 3章 系统总体设计

像管理数据库表一样熟练地管理地图等空间数据,无须进行特殊的培训。在GIS或GMIS的开发过程中,开发人员的素质与熟练程度是十分重要的因素。这将使大量的MIS开发人员

能够较快地过渡到GIS或GMIS的开发工作中,从而大大加速GIS的发展。 (5)更加大众化 组件式技术已经成为业界标准,用户可以像使用其他ActiveX控件一样使用GIS组件,

这样,非专业的普通用户也能够开发和集成GIS应用系统,推动了GIS大众化进程。组件式

GIS 的出现使GIS不仅是专家们的专业分析工具,同时也成为普通用户对地理相关数据进

行管理的可视化工具。

综上所述,本系统将采用GIS组件式开发模式。GIS组件的代表作应首推MapObjects及MapX,其中MapObjects由全球 大的GIS厂商ESRI(美国环境系统研究所)推出;MapX由著名的桌面GIS厂商美国MapInfo公司推出。另外还有加拿大阿波罗科技集团的TITAN等。

表3.1给出了MapObjects和MapX的主要功能对比。

表3.1 MapObjects和MapX的主要功能对比

功能 MapObjects MapX

显示的地图数据格式 Arcview 的 SHP 、 ARC/INFO 的

coverage、SDE图层

MapInfo的数据格式

叠加栅格图像 有 有

对地图的常用操作 放大、缩小、漫游等 放大、缩小、漫游等

图层控制 增加、移走、设置当前层 增加、移走、设置当前层

属性数据绑定 有 有

地图信息查询方式 1. 通过鼠标选取特征

2. 通过SQL查找特征

3. 通过空间操作选取特征

1. 通过鼠标选取特征

2. 通过SQL查找特征

3. 通过空间操作选取特征

专题地图 较弱 有

GPS集成 有 有

用户绘图图层 无 有

生成/编辑地图对象 较弱 较弱

地图标注 有 有

地图符号化 较弱 较强

分析功能 无 无

地理编码 有 有

可使用的开发语言 VC、VB、PowerBuilder、C++ Builder、

Delphi、Access等

VC 、 VB 、 PowerBuilder 、 C++

Builder、Delphi、Access等

MapObjects组件是由美国环境系统研究所推出,而美国环境系统研究所是GIS软件技术

的拓荒者,同时也是当今GIS技术的领跑者,并且MapObjects是美国环境系统研究所产品系

列的有机组成部分,与其他产品(例如ArcSDE)能很好地衔接。在比较了MapObjects与MapX的主要功能之后,我们决定选择MapObjects来开发本系统。

Page 52: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

44

3.1.5 开发工具

可视化的开发工具也有许多,主要有Visual C++、Visual Basic、C#、C++ Builder与Delphi,各个开发工具各有千秋。

Borland公司的拳头产品Delphi自问世以来,就一直以其强大的数据库开发功能扮演着

“VB杀手”的角色,在Microsoft产品一统天下的开发工具领域内独树一帜,吸引了大批从

事数据库开发工作的程序员。而Borland公司推出的全新Borland C++ Builder不仅秉承了

Delphi数据库开发的强大功能,而且对目前流行的面向对象程序设计方法和C++语言进行了

集成,从而结束了长期以来广大C++程序员没有快速应用开发(RAD)产品的尴尬局面,

成为当今 热门的开发工具之一。 C++ Builder具有强大的数据库处理能力。C++ Builder的数据感知控件多达20多个。在

许多情况下,甚至不需要编写任何程序代码,便可以开发一个复杂的应用程序。C++ Builder提供的数据感知控件具有很高的实用性,但是如果需要更高级的灵活性,也可以直接调用

Borland数据库引擎BDE/IDAPI以满足更高的要求。 C++ Builder提供了强大的Borland数据库引擎,这是一种非常成熟的数据库连接技术。

它提供了3种数据库访问方式:一是可以直接存取dBase、FoxBase、Foxpro、Paradox等文件

型数据库生成的DB、DBF文件;二是提供了标准的ODBC接口,通过这个接口可以存取任

何一种ODBC数据库;三是提供了一个高效的SQL Links数据库驱动程序,允许直接存取

Oracle、Informix、SyBase、MSSQLServer、DB2和Borland InterBase。SQL Links是一种快

速数据库驱动程序,使用它可以获得非常高效的数据库存取能力。此外,C++ Builder还提

供了一组ADO组件。使用ADO和ADO组件允许C++ Builder编程人员不用依靠BDE创建数据

库应用程序,而使用ADO存取数据。 C++ Builder在传统的单层数据库应用和双层数据库应用体系的基础上,率先引入了多

层数据库应用模型,极大地扩展了C++ Builder的数据库应用空间。通过C++ Builder提供的

多层分布式应用服务(MIDS),程序员可以轻松开发出高可靠性、高效率、高负载的分布

式数据处理系统。此外,C++ Builder还可以通过ActiveForm或InterBaseExpress为多层数据

库应用程序创建基于Web的客户端,这样,用户只要通过普通的浏览器就可以与远程数据

库系统进行交互。在C++ Builder 6中,MIDAS更名为DataSnap。DataSnap不但强化了MIDAS原有的功能,更加入了许多新的组件,使程序员可以开发出更为强壮的应用系统,此外

DataSnap也改善了MIDAS的执行效率,让应用程序能够执行得更快。DataSnap还提供了一

些以前MIDAS没有的功能,总之,新的DataSnap将会让程序员更满意。 Borland 软件公司持续不断地在Web服务领域开拓创新,C++ Builder 6中加入了一组

WebServices控件。由C++ Builder 6开发的符合SOAP的应用可以和COM+、EJB或Microsoft的.NET沟通,它们将通过一整套完全集成且支持Web服务的可视化工具、高效编译器和可

重用软件组件,帮助用户构建支持Web服务规范的服务器端和客户端应用程序。C++ Builder 6是一个完全支持新兴Web服务技术的Windows快速应用开发环境,提供了SOAP和Web服务,使程序员可以快速开发基于SOAP的应用系统,也可以融入WSDL自动产生程序框架代

码。由于SOAP是未来Web和分布式应用系统的主流技术,而Web服务更是未来Web应用系

统的主要软件架构,对程序员而言,驾驭它至关重要。程序员如果能够结合WebServices和

Page 53: C++ Builder和MapObjects实现

45

第 3章 系统总体设计

C++ Builder的数据库能力及COM+功能,将能够开发出功能强大的Web数据库应用系统。 地理信息系统的大部分功能都要借助数据库,C++ Builder的强大数据库开发功能使其

非常适合开发地理信息系统应用,所以我们选择C++ Builder作为“北京市地理信息公众查

询系统”的开发工具。

3.2 系统总体框架

我们将从系统功能框架、系统数据库、开发结构以及系统界面组织等方面设计北京市

地理信息公众查询系统的总体框架。

3.2.1 系统功能框架

系统功能框架如图3.1所示。

图 3.1 系统功能框架

地图控制

北京市地理

信息公众查

询系统

属性数据

库管理

其他

地图显示

地图输出

空间查询

在线帮助

北京市郊区

北京市市区

查询 近目标

距离量算

面积量算

地名查询

公交线路查询

地图放大

地图缩小

地图漫游

全图输出为位图

显示部分输出为位图

打印全图

打印显示部分

Page 54: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

46

3.2.2 系统数据库

考虑到空间数据是非结构化的、不定长的,而且施加于空间数据的操作需要GIS软件实

现,因此我们利用文件存储空间数据,而借助于已有的关系数据库管理系统(RDBMS)管

理属性数据。由于采用MapObjects进行二次开发,因此北京市地理信息公众查询系统的空

间数据管理采用的是Shape文件。

3.2.3 系统的开发结构

本系统的开发结构如图3.2所示。系统开发按照数据流向主要分两大块,一是利用

MapObjects组件显示电子地图数据,并对地图数据进行查询;二是利用ADO组件访问电子

地图数据的元数据,这些元数据详细描述了地图数据的分类信息,通过对元数据的查询可

以更进一步细分查询类型。

电子地图

属性数据库

GIS组件

ADO组件

北京市地理信息公众查询系统

空间查询

SQL 查询

查询

结果

图 3.2 系统的开发结构

3.2.4 系统界面组织

界面是系统与用户实现交互的部分,它表现了系统的整体感觉。是否拥有友好的界面

是用户能否接受系统的前提。 系统界面设计原则:

(1)以用户为中心。一方面注意不要使屏幕显得拥挤,另一方面,应考虑运用恰当的

交互方式,如直接交互。为了实现有效的人机交互,必须使用用户熟悉的和易理解的术语

和概念。例如:用公用的数据层名代替文件名;用英尺或米作为距离量算单位,而不用象

元或码。有效的指导信息还包括“哪些可以做”和“哪些不可以做”等等。当系统执行较

长时间的任务时,界面上应立即显示表示进度执行情况的指示器。系统界面必须友好,满

足用户的视觉感受。 (2)界面整洁。 (3)菜单与工具栏能够根据需要切换,使用方便。

Page 55: C++ Builder和MapObjects实现

47

第 3章 系统总体设计

(4)整体风格一致,尤其是各对话框的字体大小(建议用5号字)、按钮摆放位置等。

界面设计重点:

(1)界面设计重点是确定一种规范,保持各窗口风格一致性。尤其是有些操作的分步

骤提示窗口一定要完全一致。这里的一致性主要是指对话框大小、字体(建议都用5号字)、

按钮排列顺序(例如“确定”在“取消”的左边)的一致性。 (2)界面上工具栏与菜单在不同使用状态下的切换。常用工具如放大、缩小、漫游、

保存、打印、打印预览等,一直出现在工具栏中。一些拼版、地图等工具只在使用时才会

出现。

根据界面设计原则和重点要求,我们实现的北京市地理信息公众查询系统界面总体布

局如图3.3所示,它包括地图控制工具栏、地物类型工具栏、地图显示窗口、输入输出控制

窗口、缩略图窗口、状态栏等6部分。

图 3.3 北京市地理信息公众查询系统界面总体布局

(1)地图控制工具栏 该工具栏中的按钮主要用于控制地图操作,尤其方便了使用频率高的操作。地图控制

工具栏的默认位置是在主窗口的正上方,但也可按用户要求随意摆放。 (2)地物类型工具栏 通过地物类型工具栏中的快捷按钮可以控制在地图中显示哪些类型的地物。该工具栏

Page 56: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

48

默认位置在主窗口的左方。 (3)地图显示窗口 用于显示地图,也可以响应用户通过工具栏按钮执行的一些请求。地图显示窗口位于

主窗口的正中央。 (4)输入输出控制窗口 主要用于让用户输入适当的参数,系统将依据这些参数进行空间查询,例如地名信息

查询、 近目标查询、公交线路查询等。该控制窗口默认情况下位于主窗口的右上方位置。 (5)缩略图(鹰眼)窗口 按全图显示比例显示电子地图。缩略图上有一个矩形,代表地图显示窗口的当前显示

内容相对于整个地图的位置与大小。该窗口的默认位置位于主窗口的右下角。 (6)状态栏 状态栏主要显示工具栏、菜单功能及部分操作的提示信息。状态栏所显示信息要求简

单明了、语言通俗易懂。状态栏也是独立的,其显示状态也可由用户控制。默认情况下,

状态栏位于主窗口的 底端。

3.3 系统数据组织

3.3.1 系统数据的逻辑组织

系统数据的逻辑组织也就是用户面对的数据组织视图,其特点就是分类组织、简单明

了。系统数据的逻辑组织与物理组织之间的差别在于,数据的物理组织更多地是从实现方

面考虑数据视图。 在本系统中,空间数据按照地图来组织,多个地图组成地图集,每个地图又包含多个

图层。因此,系统数据的逻辑组织如下:

地图集(Maps)

地图(Map)1

图层(Layer)1 图层(Layer)2

……

图层(Layer)n 地图(Map)2

图层(Layer)1

图层(Layer)2 ……

图层(Layer)n

…… 地图(Map)n

图层(Layer)1

图层(Layer)2 ……

Page 57: C++ Builder和MapObjects实现

49

第 3章 系统总体设计

图层(Layer)n

3.3.2 系统的主要数据类型

本系统的数据分为电子地图数据和元数据数据,它们分别以Arc/Info文件格式和关系型

数据的形式存储,因此系统的主要数据类型有两类,一是Access数据库数据,用于存放关

系型数据,二是Shape文件数据,用于存放空间位置数据。

3.4 进 度 规 划

本系统的进度规划安排如下:

· 进行需求分析和系统设计,形成设计文档,时间约3周。 · 确定系统的属性数据存储模式,时间2周。 · 在完成设计和建库后,进行信息系统的开发,时间4周。 · 信息系统开发完毕后,进行调试并制作安装程序和多媒体界面,时间3周。 · 机动时间1周。

由此算来,总计开发时间为13周(3个月)。

Page 58: C++ Builder和MapObjects实现

第 4 章 系统详细设计

本章根据第3章给出的系统总体设计结构,从北京市地理信息公众查询系统的数据库设

计和一些相关类的设计两方面来分析GIS系统的详细设计。

4.1 数据库详细设计

本系统的数据分为电子地图数据和元数据数据。它们分别以Arc/Info文件格式和关系型

数据的形式存储,所有的操作都是在这些数据上完成的,因此数据建模和数据库模式的设

计至关重要。因为“北京市地理信息公众查询系统”需要区分各类不同的信息,电子地图

数据本身也需要区分不同类型的信息,并且电子地图数据需要根据一定方式进行组织,这

些都需要用元数据来说明。

4.1.1 地名分类编码

为了方便检索以及归类,北京市地理信息公众查询系统根据需要将地名分成5类,分别

为大类、中类、小类、次小类与第三小类,并分别对它们进行编码,编码方式如表4.1~表4.83所示。

表4.1 大类编码方式

大类名称 编码

北京纵览 1

党政机关与民间组织 2

交通运输 3

文教科技 4

体育卫生 5

邮政电信 6

商业、服务业 7

旅游、住宿、娱乐 8

其他企事业单位 9

Page 59: C++ Builder和MapObjects实现

51

第 4章 系统详细设计

表4.2 北京纵览大类的中类编码方式

中类名称 编码

北京市 11

各区县 12

乡镇、街道 13

村委会、居委会 14

居民点 15

基础地理要素 16

非行政区域 17

表4.3 基础地理要素中类的小类编码方式

小类名称 编码

河流 161

湖泊 162

岛屿 163

平原 164

丘陵山地 165

其他陆地景观 166

标志性建筑物 167

其他 168

表4.4 非行政区域中类的小类编码方式

小类名称 编码

工业区、开发区 171

地片 172

区片 173

住宅区 174

其他 175

表4.5 党政机关与民间组织大类的中类编码方式

中类名称 编码

中央 21

市级 22

区县级 23

民间组织 24

其他 25

Page 60: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

52

表4.6 中央中类的小类编码方式

小类名称 编码

中国共产党 211

人大 212

政府 213

政协 214

民主党派 215

军队、武警 216

其他 217

表4.7 市级中类的小类编码方式

小类名称 编码

中国共产党 221

人大 222

政府 223

政协 224

民主党派 225

军队、武警 226

其他 227

表4.8 区县级中类的小类编码方式

小类名称 编码

中国共产党 231

人大 232

政府 233

政协 234

民主党派 235

下属机构 236

其他 237

表4.9 下属机构小类的次小类编码方式

次小类名称 编码 公安 2361 税务 2362 工商 2363 房管 2364 城管 2365 交通 2366 其他 2367

Page 61: C++ Builder和MapObjects实现

53

第 4章 系统详细设计

表4.10 民间组织中类的小类编码方式

小类名称 编码

全国性社团 241

全国性民办非企业组织 242

市域社团 243

市域民办非企业组织 244

区县社团 245

区县民办非企业组织 246

其他 247

表4.11 党政机关与民间组织大类其他中类的小类编码方式

小类名称 编码

各地政府驻京办 251

国际组织和各国政府驻京办事机构 252

表4.12 各地政府驻京办小类的次小类编码方式

次小类名称 编码

各省驻京办 2511

其他各级政府驻京办 2512

其他 2513

表4.13 国际组织和各国政府驻京办事机构小类的次小类编码方式

次小类名称 编码

联合国驻京办事机构 2521

各国政府驻京办事机构 2522

其他国际组织驻京办事机构 2523

表4.14 交通运输大类的中类编码方式

中类名称 编码

无轨交通 31

轨道交通 32

铁路 33

航空 34

道路 35

水运 36

交通服务机构 37

交通附属设施 38

其他 39

Page 62: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

54

表4.15 无轨交通中类的小类编码方式

小类名称 编码

公共汽车 311

无轨电车 312

表4.16 公共汽车小类的次小类编码方式

次小类名称 编码

路线 3111

站点 3112

表4.17 无轨电车小类的次小类编码方式

次小类名称 编码

路线 3121

站点 3122

表4.18 轨道交通中类的小类编码方式

小类名称 编码

地铁 321

城铁 322

轻轨 323

表4.19 地铁小类的次小类编码方式

次小类名称 编码

路线 3211

站点 3212

表4.20 城铁小类的次小类编码方式

次小类名称 编码

路线 3221

站点 3222

表4.21 轻轨小类的次小类编码方式

次小类名称 编码

路线 3231

站点 3232

Page 63: C++ Builder和MapObjects实现

55

第 4章 系统详细设计

表4.22 铁路中类的小类编码方式

小类名称 编码

火车站 331

售票处 332

铁路托运 333

其他 339

表4.23 火车站小类的次小类编码方式

次小类名称 编码

北京站 3311

北京西站 3312

北京南站 3313

北京北站 3314

表4.24 航空中类的小类编码方式

小类名称 编码

航空公司 341

售票处 342

民航班机 343

表4.25 民航班机小类的次小类编码方式

次小类名称 编码

线路 3431

站点 3432

表4.26 道路中类的小类编码方式

小类名称 编码

高速公路 351

国道 352

其他公路 353

市内道路 354

表4.27 市内道路小类的次小类编码方式

次小类名称 编码

主干道 3541

次干道 3542

街巷胡同 3543

Page 64: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

56

表4.28 水运中类的小类编码方式

小类名称 编码

水运线路 361

码头 362

表4.29 交通服务机构中类的小类编码方式

小类名称 编码

交通队、收费站 371

车检所 372

驾校 373

运输企业 374

交通工具维修、养护、救援企业 375

交通工具及配件销售企业 376

表4.30 交通工具维修、养护、救援企业小类的次小类编码方式

次小类名称 编码

汽车修理厂 3751

汽车俱乐部 3752

其他 3753

表4.31 交通附属设施中类的小类编码方式

小类名称 编码

桥梁 381

加油站 382

停车场 383

表4.32 文教科技大类的中类编码方式

中类名称 编码

教育 41

科技 42

大众传媒 43

文化艺术 44

其他 45

表4.33 教育中类的小类编码方式

小类名称 编码

高等教育 411

中等教育 412

Page 65: C++ Builder和MapObjects实现

57

第 4章 系统详细设计

(续表)

小类名称 编码

初等教育 413

学前教育 414

不能归入上述4类的民办教育机构 415

特殊教育 416

教育服务机构 417

表4.34 高等教育小类的次小类编码方式

次小类名称 编码

普通高校 4111

成人高校 4112

其他 4119

表4.35 中等教育小类的次小类编码方式

次小类名称 编码

普通中学 4121

中等专业学校 4122

职业学校 4123

技工学校 4124

表4.36 民办教育机构小类的次小类编码方式

次小类名称 编码

中外合作举办的教育机构 4151

民办高等院校 4152

其他 4159

表4.37 特殊教育小类的次小类编码方式

次小类名称 编码

盲人学校、聋哑学校 4161

其他 4162

表4.38 教育服务机构小类的次小类编码方式

次小类名称 编码

各级招生办、自考办 4171

出国留学中介机构 4172

其他 4173

Page 66: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

58

表4.39 科技中类的小类编码方式

小类名称 编码

科研机构 421

技术服务 422

表4.40 技术服务小类的次小类编码方式

次小类名称 编码

气象 4221

地震 4222

测绘 4223

专利 4224

技术监督 4225

工程设计 4226

翻译 4227

其他 4228

表4.41 大众传媒中类的小类编码方式

小类名称 编码

新闻 431

出版 432

广播电影电视 433

表4.42 新闻小类的次小类编码方式

次小类名称 小类

国内新闻机构 4311

国外驻京新闻机构 4312

其他 4313

表4.43 出版小类的次小类编码方式

次小类名称 编码

报社 4321

出版社 4322

杂志社 4323

其他 4324

Page 67: C++ Builder和MapObjects实现

59

第 4章 系统详细设计

表4.44 广播电影电视小类的次小类编码方式

次小类名称 编码

广播电台 4331

电影制片厂 4332

电视台 4333

其他 4334

表4.45 文化艺术中类的小类编码方式

小类名称 编码

文化 441

艺术 442

表4.46 文化小类的次小类编码方式

次小类名称 编码

图书馆 4411

档案馆 4412

博物馆 4413

纪念馆、故居、遗址 4414

其他 4419

表4.47 艺术小类的次小类编码方式

次小类名称 编码

文艺演出团体 4421

影剧院、音乐厅 4422

美术馆 4423

其他 4424

表4.48 体育卫生大类的中类编码方式

中类名称 编码

体育 51

医院 52

特殊医疗 53

疗养院 54

药店 55

其他 56

Page 68: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

60

表4.49 医院中类的小类编码方式

小类名称 编码

综合医院 521

专科医院(专科防治所) 522

中医医院 523

门诊部、医务室 524

其他医院 525

表4.50 其他医院小类的次小类编码方式

次小类名称 编码

流动医院 5251

临终关怀医院 5252

试管婴儿医院 5253

动物医院 5254

其他 5255

表4.51 特殊医疗中类的小类编码方式

小类名称 编码

戒毒所 531

心理咨询与音乐治疗中心 532

亲子鉴定机构 533

献血处 534

遗体接受站 535

盲人保健按摩机构 536

其他 537

表4.52 邮政电信大类的中类编码方式

中类名称 编码

邮政 61

电信 62

其他 63

表4.53 邮政中类的小类编码方式

小类名称 编码

邮电局、所 611

快递公司 612

其他 613

Page 69: C++ Builder和MapObjects实现

61

第 4章 系统详细设计

表4.54 电信中类的小类编码方式

小类名称 编码

电信局 621

其他 622

表4.55 商业、服务业大类的中类编码方式

中类名称 编码

金融与保险 71

批发 72

零售 73

信托与拍卖 74

餐饮 75

居民服务 76

咨询与中介 77

其他 78

表4.56 金融与保险中类的小类编码方式

小类名称 编码

金融 711

保险 712

表4.57 金融小类的次小类编码方式

次小类名称 编码

银行 7111

证券、期货与投资 7112

表4.58 银行次小类的第三小类编码方式

第三小类名称 编码

中国工商银行 71111

中国建设银行 71112

中国农业银行 71113

中国交通银行 71114

中国银行 71115

其他银行 71116

信用社 71117

Page 70: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

62

表4.59 批发中类的小类编码方式

小类名称 编码

粮油、农副产品市场(含农贸市场) 721

服装批发市场 722

小商品批发市场 723

电子市场 724

五金、交电、化工批发市场 725

其他类市场 726

表4.60 零售中类的小类编码方式

小类名称 编码

大中型商场 731

超市和粮油、食品、茶叶、烟酒店 732

日用品(含百货、首饰、日杂) 733

纺织品和鞋帽 734

五金家电和办公设备 735

图书报刊 736

家具、家装和建材 737

鲜花、礼品店 738

其他零售 739

表4.61 餐饮中类的小类编码方式

小类名称 编码

正餐 751

快餐 752

其他餐饮业 753

表4.62 正餐小类的次小类编码方式

次小类名称 编码

中餐馆 7511

西餐厅 7512

其他正餐(料理等) 7513

表4.63 快餐小类的次小类编码方式

次小类名称 编码

快餐盒饭送餐公司 7521

快餐连锁店(麦当劳店等) 7522

Page 71: C++ Builder和MapObjects实现

63

第 4章 系统详细设计

表4.64 其他餐饮业小类的次小类编码方式

次小类名称 编码

北京名小吃 7531

冰点连锁店 7532

茶馆、酒吧、咖啡馆 7533

其他类 7539

表4.65 居民服务中类的小类编码方式

小类名称 编码

美容院、理发馆 761

沐浴业 762

洗染业 763

摄影及扩印 764

日用品修理 765

家务服务 766

殡葬服务 767

市政服务 768

其他居民服务 769

表4.66 摄影及扩印小类的次小类编码方式

次小类名称 编码

照相馆和扩印部 7641

婚纱摄影店 7642

其他 7643

表4.67 日用品修理小类的次小类编码方式

次小类名称 编码

开锁、修锁服务单位 7651

家电维修单位 7652

疏通管道服务公司 7653

其他 7654

表4.68 家务服务小类的次小类编码方式

次小类名称 编码

搬家公司 7661

社区服务、家政服务 7662

托儿所 7663

托老所 7664

Page 72: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

64

(续表)

次小类名称 编码

福利院和养老院 7665

保洁公司 7666

杀虫公司 7667

防水公司 7668

其他 7669

表4.69 市政服务小类的次小类编码方式

次小类名称 编码

水、电、煤气、暖气服务 7681

绿化与清洁 7682

市政工程养护 7683

消防 7684

其他 7689

表4.70 其他居民服务小类的次小类编码方式

次小类名称 编码

制卡公司 7691

打字、复印、名片制作、出片公司 7692

婚庆、庆典、礼仪公司 7693

其他 7694

表4.71 咨询与中介中类的小类编码方式

小类名称 编码

电话查询(无空间位置) 771

广告公司 772

公证处 773

律师事务所 774

会计、审计、统计事务所 775

中介机构 776

其他 777

表4.72 电话查询(无空间位置)小类的次小类编码方式

次小类名称 编码

便民热线网 7711

国际长途电话区号及时差查询表 7712

举报电话 7713

Page 73: C++ Builder和MapObjects实现

65

第 4章 系统详细设计

(续表)

次小类名称 编码

投诉电话 7714 中国长途区号和邮政编码查询表 7715 违例停车被拖的电话查询 7716 常用的咨询和求助电话 7717 其他 7718

表4.73 中介机构小类的次小类编码方式

次小类名称 编码

企业登记代理、商标代理 7761 职介 7762 婚介 7763 其他 7764

表4.74 旅游、住宿、娱乐大类的中类编码方式

中类名称 编码

旅游 81 住宿 82 娱乐 83 其他 84

表4.75 旅游中类的小类编码方式

小类名称 编码

旅游景点 811 旅游路线 812 旅行社 813

表4.76 住宿中类的小类编码方式

小类名称 编码

星级宾馆 821 普通旅馆 822 其他 823

表4.77 娱乐中类的小类编码方式

小类名称 编码

健身、健美场所 831 娱乐城、舞厅、夜总会 832 高尔夫、保龄球馆 833 其他 839

Page 74: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

66

表4.78 其他企事业单位大类的中类编码方式

中类名称 编码

农、林、牧、渔业 91

采掘、制造业 92

水、电、煤气生产、供应业 93

建筑业 94

地质勘查和水利管理业 95

仓储 96

信息 97

商业贸易 98

其他 99

表4.79 采掘、制造业中类的小类编码方式

小类名称 编码

采掘 921

生活和办公用品制造业(含食品、饮料、服装鞋帽、烟草、

家具、文体办公用品、家用电器制造企业)

922

其他制造 923

表4.80 生活和办公用品制造业小类的次小类编码方式

次小类名称 编码

印刷及文体用品(包括相机、眼镜生产) 9221

厨卫用品(包括酒店设备) 9222

服装公司 9223

家具厂(包括厨柜生产) 9224

家用电器 9225

其他 9229

表4.81 建筑业中类的小类编码方式

小类名称 编码

建筑公司 941

写字楼、办公楼 942

物业管理 943

房地产公司 944

装修装饰 945

其他 946

Page 75: C++ Builder和MapObjects实现

67

第 4章 系统详细设计

表4.82 信息中类的小类编码方式

小类名称 编码

软件公司 971

信息中心 972

其他信息产业单位 979

表4.83 商业贸易中类的小类编码方式

小类名称 编码

进出口企业 981

外国或外地企业驻北京办事处、分公司 982

一般商业贸易单位 983

其他 984

4.1.2 元数据表结构

元数据主要包括如下一些表:

(1)地图集信息表 该表用于存贮“北京市地理信息公众查询系统”可用的地图信息,包括地图编号、名

称、地图信息对应的表名、地图的描述信息等。该表的结构如表4.84。

表4.84 地图集信息表的结构

字段名称 数据类型 大小 描述

id 自动编号 长整型 地图编号

名称 文本 100 地图名称

表名 文本 50 地图信息对应的表名

描述 文本 255 地图的描述信息

在本系统中只包含如图4.1所示的两个地图。

图 4.1 地图集信息表的内容

(2)北京市城区图层信息表与北京市郊区图层信息表 北京市城区图层信息表描述了“北京市市区”地图所包括的图层的信息,北京市郊区

图层信息表描述的是“北京市郊区”地图所包含的图层的信息。这两个表的结构完全一样,

Page 76: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

68

如表4.85所示。

表4.85 北京市城区图层信息表与北京市郊区图层信息表的结构

字段名称 数据类型 大小 描述

id 自动编号 长整型 编号

大类 文本 255 类型

大类代码 数字 双精度型 类型编号

中类 文本 255 类型

中类代码 数字 双精度型 类型编号

小类 文本 255 类型

小类代码 数字 双精度型 类型编号

次小类 文本 255 类型

次小类代码 数字 双精度型 类型编号

第三小类 文本 255 类型

第三小类代码 数字 双精度型 类型编号

显示次序1 数字 整型 1级显示次序

显示次序2 数字 整型 2级显示次序

图层名 文本 255 描述图层信息的元数据表名称

图层类型 文本 50 图层的类型

属性表名 文本 50 包含图层属性信息的文件名称

图形文件 文本 50 该图层的空间数据对应的文件

名称 文本 50 图层显示的名称

控制 是/否 是否可控制

存在 是/否 现阶段该图层是否存在

显示 是/否 是否显示该图层

选择 是/否 该图层是否可选择

注记 是/否 该图层中是否存在注记信息

地物 是/否 该图层中是否存在地物信息

字段名 文本 50 注记对应的字段名

显示比例尺 数字 双精度型 图层显示的比例尺

注记比例尺 数字 双精度型 注记显示的比例尺

注记大小 数字 长整型 注记的大小

字体名称 文本 50 注记显示的字体

符号索引 数字 长整型 地物显示的符号的索引

符号大小 数字 长整型 地物显示的符号的大小

符号颜色 数字 长整型 地物显示的符号的颜色

(3)图层元数据表 地图中的每个图层都有一个对应的元数据表,该表包含该图层中地物的类别信息。图

Page 77: C++ Builder和MapObjects实现

69

第 4章 系统详细设计

层元数据表的结构如表4.86所示。

表4.86 图层元数据表的结构

字段名称 数据类型 大小 描述

名称 文本 80 地物名称

类型 文本 10 大类类型名称

中类型 文本 50 中类类型名称

属性表名 文本 50 属性表的表名

图层名 文本 50 图层的名称

(4)公交车站表与公交线路表 为了能在电子地图中查询公交信息,需要公交车站表、公交线路表与公交车站线路表,

这3个表的结构分别如表4.87、表4.88与表4.89所示。

表4.87 公交车站表的结构

字段名称 数据类型 大小 描述

站名 文本 40 站点名称

类型 文本 10 大类类型名称

中类型 文本 50 中类类型名称

属性表名 文本 50 属性表的表名

图层名 文本 50 图层的名称

表4.88 公交线路表的结构

字段名称 数据类型 大小 描述

路线名 文本 40 公交路线名称

类型 文本 10 大类类型名称

中类型 文本 50 中类类型名称

属性表名 文本 50 属性表的表名

图层名 文本 50 图层的名称

表4.89 公交车站线路表的结构

字段名称 数据类型 大小 描述

路线名 文本 50 公交路线名称

类型 文本 50 公交路线类型

站名 文本 50 某公交路线中某一站的站名

顺序 数字 长整型 该站在该公交路线中所处的顺序

(5)北京市城区索引表与北京市郊区索引表 这两个表分别提供北京市区、县多边形对应的外包矩形范围以及中心点范围,目的是

实现较快的查询速度。这两个表的结构完全一样,如表4.90所示。

Page 78: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

70

表4.90 北京市城区索引表与北京市郊区索引表的结构

字段名称 数据类型 大小 描述

id 数字 长整型 区(县)编码

名称 文本 50 区(县)名称

X坐标 数字 双精度型 中心点的X坐标

Y坐标 数字 双精度型 中心点的Y坐标

X1 数字 双精度型 外包矩形左上角点的X坐标

Y1 数字 双精度型 外包矩形左上角点的Y坐标

X2 数字 双精度型 外包矩形右下角点的X坐标

Y2 数字 双精度型 外包矩形右下角点的Y坐标

(6)弧段表(AAT)与结点属性表(NAT) 弧段表用于记录边的属性,在建立网络拓扑时生成。网络图层必须有弧段表。弧段表

需要指定正向阻力和逆向阻力。弧段表的结构如表4.91所示。

表4.91 弧段表的结构

字段名称 数据类型 大小 描述

ARCID 数字 长整型 弧段号 GEOID 数字 长整型 内部标识号 FNODE 数字 长整型 起始结点号 TNODE 数字 长整型 终止结点号 LENGTH 数字 双精度型 长度 FIMP 数字 双精度型 正向阻力 TIMP 数字 双精度型 逆向阻力 RESOURCE 数字 双精度型 资源需求。可以在计算时临时从数据表中获得

结点属性表用于记录结点的属性,在建立网络拓扑时生成。每个结点有一个惟一标识

号。在资源分配中,一个结点只能分配给一个中心。结点属性表的结构如表4.92所示。

表4.92 结点属性表的结构

字段名称 数据类型 大小 描述

NODEID 数字 长整型 结点标识号

LABEL 文本 255 注记文字

X 数字 双精度型 注记位置的X坐标

Y 数字 双精度型 注记位置的Y坐标

ARCID 文本 255 连接弧段的标识号

ANGLE 文本 255 弧段角度

LEFTTURN 数字 双精度型 向左偏转角度

RIGHTTURN 数字 双精度型 向右偏转角度

RESOURCE 数字 双精度型 资源需求

Page 79: C++ Builder和MapObjects实现

71

第 4章 系统详细设计

4.1.3 电子地图数据

由于“北京市地理信息公众查询系统”使用MapObjects组件进行二次开发,因此由

MapObjects本身来管理电子地图数据。 MapObjects可以使用Shape文件、图像文件、属性表或通过ESRI的专用数据库引擎连接

的专用数据库。Shape文件是地图数据的矢量形式,图像文件是栅格图像或尤指航空或卫量

的畸变图像的纠正照片,属性表是可用ODBC装入的任意格式,专用数据库是网络上通过

ESRI专用数据库引擎连接的UNIX服务器。Shape文件适用于中小型地图数据,而大型数据

(如省、国家道路网)就需要使用专用数据库。用MapObjects编写的软件是可伸缩的。

初我们可用Shape文件,当用户需要与大型数据库连接时,几乎所有代码都可转移应用于与

专用数据库连接后的工作中,要做的仅仅是修改用于打开数据源的几行代码。

4.1.3.1 Shape 文件

Shape文件是ESRI提供的用于存储地理矢量数据的文件格式,这就意味着地图特征可以

以X、Y矢量坐标形式表现,其坐标系是笛卡尔坐标。注意,笛卡尔坐标与屏幕坐标有所不

同。 每一特征的几何形状以包括一组矢量坐标的形的形式存储,其属性存放在Shape文件的

dBase表的记录中。 一个Shape文件由一个主文件、一个索引文件和一个dBase表3个文件组成。主文件

(*.shp)包含几何形状,是一个直接存取、变长记录的文件。索引文件(*.shx)包含数据

的索引值,文件中每个记录包含对应主文件记录距离主文件头的偏移。dBase表(*.dbf)包

含几何图形的属性,可以修改属性的定义。 每一个Shape文件包含一种图形类型(点、弧、多边形等)。

· 点包含(X,Y)坐标和一个属性。 · 弧段包含一条或一组(可连,可不连)多义线。一条多义线是一组有序结点。每一

弧段有一个属性记录。 · 多边形包括一个或多个边界,一个边界是一个无交叉点的闭合环,一个边界可嵌于

一个多边形中而形成环形。边界的方向决定它是否代表区域内的面积。每一多边形

有一个属性记录。

Shape文件通过ODBC读入,ODBC在安装MapObjects的同时被安装并注册。 ARC/INFO用户应注意,Shape文件中弧、多边形的定义不同于ARC/INFO coverage中的

定义。Shape文件无拓扑,因此,Shape文件允许特征的合成,如把几条polyline合成arc。通

过Shape文件,可快速显示图形并形成简单数据模型,以简单数据模型换取快速显示,这使

得对Shape文件进行拓扑编辑或高级分析变得十分困难。

4.1.3.2 图像(Image)文件

可通过MapObjects编写应用程序来显示多种图像文件。在地图中,图像多来自航空照

片和卫星图像。

Page 80: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

72

图像文件依靠带有灰度值或色标的一组像元来表示图片,这些像元无属性连接,其坐

标系统与Shape文件不同。 可把图像文件精确投影于Shape文件的地理坐标。MapObjects(或其他 ESRI 软件)用

world文件来匹配图像像元在地理坐标中的位置。 world文件是一个简单的文本文件,它包括一些数学参数用来定义转换关系。其公式为:

x' = Ax + By + C

y' = Dx + Ey + F

其中:

x'表示像元在地理坐标系统中经过换算后得到的坐标值X。 y'表示像元在地理坐标系统中经过换算后得到的坐标值Y。 x表示像元列数。 y表示像元行数。 A表示X轴上像元的尺寸。 B、D是旋转关系项。 E代表负的Y轴上像元的尺寸。 C、F代表左上角像元中心的X、Y地图坐标值。

注意:E为“负”值,因为Shape文件坐标与图像文件坐标的Y方向正相反。

world文件是包含A、B、C、D、E、F值的文本文件。

注意:MapObjects不支持图像旋转。这样B、D的值在world文件中是被忽略的。如

果需要旋转,可用 ESRI的ARC GRID。

表4.93是MapObjects支持的图像文件格式。

表4.93 MapObjects支持的图像文件格式

名称 描述 扩展名 World file扩展名

BMP Windows bitmap *.bmp *.bpw

TIFF Tag image file *.tif *.tfw

SUN Sun raster file *.sun *.snw

ERDAS ERDAS GIS或LAN *.gis *.gsw

IMPELL IMPELL bitmap *.rls *.rlw

BIL Band interleaved by line *.bil *.blw

BIP Band interleaved by pixel *.bip *.bpw

BSQ Band sequential *.bsq *.bqw

4.1.3.3 属性表

用MapObjects编写的应用程序可通过一种关系与外部属性表相连。关系是连接特征表

Page 81: C++ Builder和MapObjects实现

73

第 4章 系统详细设计

(特征表可以是Shape文件的dBase表,也可以是从SDE层中得到的表)与属性表的表。要

得到这种连接,可安装 ODBC。这种关系留存于应用程序运行期间,它不会被写入文件中。 要建立这种关系,需要确认一个特征表的某一字段,一个要与特征表建立关系的属性

表和该属性表的一个字段。属性表的相关字段必须是主键(primary key)或允许在其上建

立一个独一无二的索引的字段。也有一个例外,在少于100条记录的小型特征表上可建立无

特殊字段的关系。 一旦建立了关系,MapObjects就在特征表上建立了一种纽带,用户可通过属性表的主

键字段查询属性,但不能在MapObjects中通过SQL表达式向属性表里增加数据。

4.1.3.4 空间数据引擎连接的专用数据库

如果MapObjects需要采用大规模地图数据组,应该考虑使用空间数据引擎(SDE)连

接的专用数据库。SDE是一种高性能的制图数据服务器。通过SDE,空间数据可存放于UNIX服务器上。用户的SDE应用程序可基于UNIX或WIN环境编写。SDE提供下列软件开发和数

据管理能力:

· 管理大规模地理数据,提供地图无缝显示。 · 通过某种商业关系数据库存储数据。 · 通过一组高效的尖端空间数据操作来查询空间数据。

SDE包括一个C语言应用程序接口(API),它提供尽可能高的执行效率和极大的灵活

性。 “北京市地理信息公众查询系统”中只包含了Shape文件,这些图形文件的清单如表

4.94所示。

表4.94 “北京市地理信息公众查询系统”包含的Shape文件

文件名称 图层类型

区县境界 多边形

乡镇 多边形

村庄 多边形

水系 线

山峰 点

突出建筑物 点

京域长城 线

(中央)人大 点

(中央)政府 点

(中央)政协 点

(中央)民主党派 点

(市)中国共产党 点

(市级)人大 点

(市级)政府 点

Page 82: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

74

(续表)

文件名称 图层类型

(市级)政协 点

(市级)民主党派 点

(区县)人大 点

(区县)政协 点

各省驻京办 点

各地、县驻京办 点

公交车站 点

公交路线 线

地铁线 线

地铁站 点

轻轨铁路 线

轻轨车站 点

火车站 点

铁路 线

市区道路 线

道路 线

市区胡同 线

驾校 点

立交桥 点

加油站 点

普通高等教育 点

成人高校 点

大学附属类 点

普通中学 点

中等专业学校 点

职业技工学校 点

初等教育 点

学前教育 点

民办高等院校 点

其他学校 点

盲人学校 点

各级自考办 点

出国留学中介机构 点

报社 点

出版社 点

杂志社 点

Page 83: C++ Builder和MapObjects实现

75

第 4章 系统详细设计

(续表)

文件名称 图层类型

广播电台 点

电影制片厂 点

电视台 点

图书馆 点

档案馆 点

纪念馆 点

文艺演出团体 点

电影,音乐厅 点

美术馆 点

体育场跑道 线

综合医院 点

专科医院 点

门诊部、医务室 点

流动医院 点

动物医院 点

献血处 点

盲人保健按摩 点

疗养院 点

药店 点

邮电局、所 点

快递公司 点

电信局 点

中国工商银行 点

中国建设银行 点

中国农业银行 点

中国银行 点

中国交通银行 点

保险 点

大中型商场 点

超市(粮、食、茶、烟) 点

旅游景点 点

星级宾馆 点

市区公厕 点

绿地 多边形

天桥 点

地下通道 点

Page 84: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

76

(续表)

文件名称 图层类型

立交桥路面 多边形

长途汽车站 点

4.2 系统相关类的详细设计

在实现了数据库数据的设计与组织之后,若要进行主要功能与界面类的实现,还需要

定义一些辅助结构与类。

4.2.1 辅助类的详细设计

下面介绍本系统将要用到的一些辅助类和结构。

(1)LayerInfo 该结构用于包含某一图层的主要信息,例如图层名称、图层对应的文件名、图层是否

可见、图层中的地物是否可选择、图层比例尺以及所用符号等信息。该结构包含如表4.95所示的属性。

表4.95 LayerInfo结构的属性

属性名称 数据类型 说明

szName string 图层显示的名称

szLayerName string 图层名

szFileName string 图层对应的Shape文件名

szTableName string 图层对应的表名

szFieldName string 图层中的字段名

szType string 图层地物所属的大类

szSubType string 图层地物所属的中类

szSubType2 string 图层地物所属的小类

szSubType3 string 图层地物所属的次小类

bCanControl bool 图层是否可控制

bVisible bool 图层是否可见

bSelected bool 图层中的地物是否被选择

bCanSelected bool 图层中的地物是否可选择

bBackground bool 图层是否只作为背景

bLabel bool 图层是否注记

dScale double 图层的比例尺

dShowScale double 图层的显示比例尺

Page 85: C++ Builder和MapObjects实现

77

第 4章 系统详细设计

(续表)

属性名称 数据类型 说明

nCharacterIndex int 符号字体的索引

szFontName string 符号字体名称

nFontSize int 符号字体大小

nSymSize int 符号大小

nSymColor uint 符号颜色

Layer MapObjects2.MapLayer 图层对应的MapLayer对象

rsSel MapObjects2.Recordset 图层中被选择的记录集合

(2)MPoint MPoint类用于表示地图上的一个点。由于C++ Builder中提供的点类CPoint的坐标只能

是整数,而地图上点的坐标可能是小数,因此本系统利用MPoint类来扩展CPoint。该类很

简单,只包括两个double型属性x、y,分别表示点的X与Y坐标。 (3)MLine MLine类用于表示地图上的一条线。同样,C++ Builder中提供的线类CLine的坐标只能

是整数,因此本系统用MLine对其进行扩展。该类包括两个属性,一个是 int类型的

nPointNumber,用于表示线上点的个数,另一个pPoint用于表示线上的点集,类型是

MPoint[]。 (4)CloestPath CloestPath类表示某一 近的路径。该类包含两个MPoint类型的属性pt1与pt2。 (5)Buses Buses类定义一条公交线路。该类包括两个属性,一个是int类型的nNumber,用于表示

公交线路上点的个数,另一个pts用于表示该线路上的点集,类型是MPoint[]。 (6)MapInfo MapInfo类用于定义某一地图的信息。该类的属性如表4.96所示。

表4.96 MapInfo类的属性

属性名称 数据类型 说明

szName string 地图名称

szMetaTable string 说明地图信息的元数据表表名

szIndexTable string 索引数据表表名

szType string 类型

rect MapObjects2.Rectangle 地图的外包矩形

(7)IndexInfo IndexInfo类用于定义索引。该类的属性如表4.97所示。

Page 86: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

78

表4.97 IndexInfo类的属性

属性名称 数据类型 说明

szName string 索引名称

dX double X坐标

dY double Y坐标

m_extent MapObjects2.Rectangle 外包矩形

(8)MapOpr MapOpr类用于定义一枚举类型,枚举当前用户在地图上所做的操作类型。MapOpr包

括如表4.98所示的成员。

表4.98 MapOpr枚举类型的成员

成员名称 说明

MO_NULL 无操作

MO_ZOOMIN 放大

MO_ZOOMOUT 缩小

MO_ZOOMFULL 全图显示

MO_PAN 漫游

MO_POINTSEL 点选择

MO_RECTSEL 矩形选择

MO_CIRCLESEL 圆选择

MO_POLYGONSEL 多边形选择

MO_INFO 信息查询

MO_LINEMEAS 距离量算

MO_POLYMEAS 面积量算

MO_SEARCHBYDIST 查询某距离范围内的目标

MO_CLOSEST 查询 近目标

(9)MapDisp MapDisp也是一枚举类型,枚举当前地图显示地物的类型。MapDisp包括如表4.99所示

的成员。

表4.99 MapDisp枚举类型包括的成员

成员名称 说明

MO_ALL 显示所有类型地物

MO_SCHOOL 教育

MO_TOUR 旅游

MO_HOSPITAL 医院

MO_SHOP 零售

Page 87: C++ Builder和MapObjects实现

79

第 4章 系统详细设计

(续表)

成员名称 说明

MO_HOTEL 酒店

MO_GAS 加油站

MO_BANK 银行

MO_MOVIE 电影院

MO_RESTAURANT 餐馆

MO_WC 公厕

MO_POST 邮局

MO_LIBRARY 图书馆

MO_STATION 站点

(10)CGisSegLine CGisSegLine类用于定义只包含两个点的线段。CGisSegLine类只包含两个MPoint类型

的属性m_pStartPoint与m_pEndPoint,分别代表线段的两个端点。CGisSegLine类的方法如

表4.100所示。

表4.100 CGisSegLine类的方法

方法名称 参数 返回值 说明

CGisSegLine 构造函数

GetDistance MPoint, MPoint, ref double int 得到某点到该线段的距离

(11)Routine Routine结构定义了一条公交线路。该结构包含如表4.101所示的成员。

表4.101 Routine结构的成员

成员名称 数据类型 说明

nFlag short 0表示双向,1表示上行,2表示下行

nStationNumber short 该公交线路的站点数目

szRoutineName string 公交线路名称

szStationName string [] 公交线路上的站点名称

(12)Node Node结构定义了一个结点集合。该结构包含如表4.102所示的成员。

表4.102 Node结构的成员

成员名称 数据类型 说明

nNodeNumber short 结点数目

nRoutineOrder short [] 线路索引

nStationOrder short [] 站点索引

Page 88: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

80

(13)Station Station结构定义了一个站点。该结构包括如表4.103所示的成员。

表4.103 Station结构的成员

成员名称 数据类型 说明

szStationName string 站点名称

nRoutineNumber short 经过该站点的公交线路的数目

pnRoutineID short [] 经过该站点的公交线路的标识

pnOrder short [] 该站点在公交线路上的索引集

(14)PathNode PathNode结构分段定义了一条公交线路。该结构包括如表4.104所示的成员。

表4.104 PathNode结构的成员

名称 数据类型 说明

nSegNumber short 公交线路分段数目

szRoutineName string 公交线路名称

szFromStationName string [] 起始站名集合

szToStationName string [] 终点站名集合

4.2.2 TEnvironment类的详细设计

TEnvironment类是“北京市地理信息公众查询系统”中主要功能的实现类,用于控制

图层的显示、地物距离量算、地物面积量算、查询 近目标、公交线路查询功能等。 TEnvironment类的成员属性如表4.105所示。

表4.105 TEnvironment类的属性

名称 数据类型 说明

m_szDBName string 元数据数据库名称

m_szSDBPath string 元数据库所在路径

m_AppPath string 应用程序的路径

m_szHelpPath string 帮助文件的路径

m_db MapObjects2.DataConnection 与空间数据的属性数据连接的对象

m_nCurrMapIndex int 当前地图在地图集中的索引

m_nMapNum int 当前地图集中的地图数目

m_mapInfos MapInfo[] 当前地图的信息

m_nLayerNum int 当前地图包含的图层数目

m_layerInfos LayerInfo[] 当前地图中所有图层的信息集合

m_nIndexNum int 索引数目

m_indexInfos IndexInfo[] 索引信息集合

Page 89: C++ Builder和MapObjects实现

81

第 4章 系统详细设计

(续表)

名称 数据类型 说明

m_selSymbol MapObjects2.Symbol 选择地物显示的符号

m_szfntStation string 注记字体的名称

m_chStation char 注记字体的索引

m_nfntStation int 注记字体的大小

m_dataSet System.Data.DataSet 表示元数据集合的对象

m_x long 鼠标位置的X坐标

m_y long 鼠标位置的Y坐标

m_MapOpr int 用户当前在地图上的操作类型

m_cloestPath CloestPath 近路径

m_dDistance double 距离阈值

m_selectedFeature object 被选择的地物对象

m_selectedSymbol MapObjects2.Symbol 被选择地物显示的符号

m_selectedSymbolSize short 被选择地物显示的符号的大小

m_selectedScale double 被选择地物的比例尺

m_drawLine MLine[] 线对象数组

m_buses Buses 查询得到的公交路线对象

m_szBusFilter string 用于过滤公交路线类型的字符串

m_bPathInit bool 是否进行了 短路径查询初始化

m_path TPath 短路径对象

m_szPlaceName string 用于地名查询时用户输入的地名

m_shapeRect MapObjects2.Rectangle 当前图层的外包矩形

m_layerRoad MapObjects2.MapLayer 包含公交线路的图层

TEnvironment类的成员方法如表4.106所示。

表4.106 TEnvironment类的方法

方法名称 参数 返回值 说明

TEnvironment 构造函数,初始化变量

GetMapIndex string int 根据地图名称得到该地图在地

图集中的索引位置

GetLayerIndexByName string int 根据图层名称得到该图层在地

图中的索引位置

GetLayerName string string 根据地名得到地名所在的图层

GetFieldName string string 依据地名得到包含该地名的数

据表,然后在该数据表中查询

地名所在字段

Page 90: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

82

(续表)

方法名称 参数 返回值 说明

GetTableName string, string string 根据地名从指定的表中查询该

地名所在的属性表的表名

ExecuteSpatial AxMapObjects2.AxMap,

object, MapObjects2.

SearchMethodConstants,

bool

void 执行空间查询操作

ClearSelRsts void 清空选择记录集

DrawRecordset AxMapObjects2.AxMap void 绘制记录集

DrawSelectedShape AxMapObjects2.AxMap,

frmMain

void 绘制选择的地物

GetLayerVisible int bool 判断图层是否可见

SetLayerVisible int, bool, double void 设置图层是否可见

SubGuassFs ref double, ref double,

double, double, int

void 根据高斯投影分带计算给定

经、纬度的点的X、Y坐标值

SubGuassFs double, double, double,

ref double, ref double

void 根据某点的局地坐标值计算

经、纬度

CalGuassToLB double, double, ref

double, ref double

void 根据某点的局地坐标值计算

经、纬度

CalGuassFromLB ref double, ref double,

double, double

void 根据高斯投影分带计算给定

经、纬度的点的X、Y坐标值

CalcLength MPoint [], int double 计算点集合的长度

CalcArea MPoint [], int double 计算点集合的面积

GetLineLength MapObjects2.Line double 计算一条线的长度

GetPolygonLength MapObjects2.Polygon double 计算某一多边形的周长

GetPolygonArea MapObjects2.Polygon double 计算某一多边形的面积

CreatePolygon double, double, double,

ref MPoint [], ref int

void 创建多边形

CreatePolygon MapObjects2.Polygon,

double, double, double

void 创建多边形

SearchByDistance double, double, double,

Windows.Forms.ListBox

long 查询某点给定距离内的所有地

IsImage string bool 判断某图层是否是图像图层

CreateLine MapObjects2.Line MLine 创建线

GetPoint string MapObject2.Point 得到某地名的中心点

GetLine string MapObjects2.Line 得到某地名代表的线

Page 91: C++ Builder和MapObjects实现

83

第 4章 系统详细设计

(续表)

方法名称 参数 返回值 说明

FromMapPoint AxMapObjects2.AxMap,

double, double

MPoint 将地图中的点的坐标换算为屏

幕坐标

DrawLine AxMapObjects2.AxMap void 绘制一条线

CalcScale AxMapObjects2.AxMap double 计算地图比例尺

CalcScale AxMapObjects2.AxMap,

MapObjects2.Rectangle

double 计算地图比例尺

GetStation object, int, Buses, ref int bool 得到公交线路的车站

GetStation string, Buses, ref int bool 得到公交线路的车站

GetLayerByName string MapObjects2.

MapLayer

根据名称得到图层对象

IsStation AxMapObjects2.AxMap,

string

bool 判断某地名是否是一公交线路

上的站点

IsBusLine AxMapObjects2.AxMap,

string

bool 判断地图中是否存在指定的公

交线路

GetStationPt string, MPoint bool 得到给定站点的地理坐标

GetStationOrder string, string int 得到给定站点在给定的公交线

路上的车站顺序(如第2站)

4.2.3 TPath类的详细设计

TPath类用于实现公交线路查询。TPath类的成员属性如表4.107所示。

表4.107 TPath类的属性

属性名称 数据类型 说明

TIMELIMIT int 换乘次数限制阈值

_pRoutine Routine [] 公交线路集合

_pStations Station [] 站点集合

_nRCount short 公交线路数目

_nSCount short 站点数目

TPath类的成员方法如表4.108所示。

表4.108 TPath类的方法

方法名称 参数 返回值 说明

TPath 构造函数,初始化变量

HasStation Routine, string, short, short short 根据站名得到它是给定公交线路上

第几站

Page 92: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

84

(续表)

方法名称 参数 返回值 说明

BuildStationIndex Routine [], short, Station [] short 创建站点索引

FreeStationIndex Station[], short void 清除站点索引信息

FreeRoutineInfo Routine[], short void 清除公交线路信息

SearchStation Station[], short, string short 根据站名得到该站点在公交线路上

的索引

Search Routine[], short, Station[], short,

string, string, ArrayList

short 查询两个指定地名之间的公交线路

BuildRoutine Routine[], TEnvironment short 创建公交线路信息

Search string, string, ArrayList ArrayList 查询两个指定地名之间的公交线路

4.2.4 NetLayer类的详细设计

NetLayer类用于定义网络图层。在设计NetLayer类之前,需要理解几个重要的概念。

4.2.4.1 几个重要的概念

(1)网络图层(Network Layer) 由结点和连接结点的边构成的网络图。 (2)路径图层(Route Layer) 网络分析的结果图层,同样是一个由结点和连接结点的边构成的图。路径图层由若干

个路径系统构成。 (3)边/链(Link) 直接将两个结点连接的线,反映的是两个结点的关系,其本身不具有位置意义。在静

态状态(长度不发生变化)下,长度和形状也都没有实际意义。其状态属性包括阻力和需

求。 (4)结点(Node) 边的端点。结点本身只是标示边的起始和终止,位置不再有实际意义。其状态属性包

括阻力和需求。 (5)障碍(Barrier) 结点的一种。禁止网络中边上流动的点。可以认为是阻力无穷大的点。 (6)转弯(Turn) 出现在网络边中的分割结点位置,该点邻接边数量大于1。它表示的仅仅是相互连接的

边的关系,而不代表具体的地物。其状态属性主要是转弯阻力。在每个结点上有n*n个可能

的转弯,其中n为该点邻接的边数。 (7)中心(Center) 接受和分配资源的结点,其状态属性包括资源供给(Supply)、阻力限额。 (8)站点(Stop) 在路径选择中资源增减的结点,其状态属性包括阻力、资源需求。

Page 93: C++ Builder和MapObjects实现

85

第 4章 系统详细设计

(9)阻力(Impedance) 通过边或结点时的消耗,可以是事件、费用等等。该因素通常构成边的权重。边和结

点的阻力可以是有方向的。阻力无穷大时,表示不能通过。 (10)资源供给和需求(Supply & Demand) 结点或边上资源的供给量和需求(消耗)量。一般只用于资源分配,不用于路径分析。 (11)路径系统(Route System) 网络分析生成的路径集合,其内部具有一定的规则用于约定路径的属性和相互关系。 (12)动态分段(Dynamic Segmentation) 一组线性要素的若干特征的集合,可以在不影响原线性要素坐标位置的情况下实现存

储、显示、查询、分析等操作。可以将其认为是网络中若干边(包括某些边的一部分)的

镜像。 (13)路径(Route) 网络分析生成的由一组边或段及其上的结点构成的线性要素。路径由若干段组成。 (14)段(Section) 段是路径系统的 基本结构。段构成路径的一个部分,可能是一个边或者边的一部分。

边依靠起始和终止位置来确定,起始和终止位置用弧段长度的百分比表示。 (15)事件(Event) 用于动态分段或地址匹配规则,通常描述的是一条路径的某一段或者某一个位置。包

括线性事件、连续事件、点事件。

4.2.4.2 NetLayer 类的辅助类的详细设计

在设计NetLayer类之前,还需要设计几个辅助的类。

(1)NetPoint NetPoint类定义了网络图层中的某个结点。该类包含两个double类型的成员属性x、y,

分别表示结点的X、Y坐标。 (2)NetLine NetLine类用于定义网络图层中的线对象,主要记录线起始结点、终止结点、阻力等。

在生成弧段表时,需要计算弧段的长度,并通过用户指定的阻力系数生成阻力。该类包含

两个成员属性,一个是ArrayList类型的m_pCoords,用于保存线对象上的点坐标,另一个是

MapObjects2.MapLayer类型的m_layer。NetLine类的方法如表4.109所示。

表4.109 NetLine类的方法

方法名称 参数 返回值 说明

NetLine MapObjects2.MapLayer 类的构造函数

CalcLength double 计算线的几何长度

GetLineData int bool 从Shape文件中得到指定

ID的线对象数据

Page 94: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

86

(续表)

方法名称 参数 返回值 说明

GetNearestLineData double, double int 得到距离某点 近的线对

象的标识

AddCoord NetPoint void 在网络线上加入一个结点

IsPtCoincide NetPoint, NetPoint bool 判断两点是否重合

GetNearestPoint NetPoint, NetPoint, NetPoint,

NetPoint, double

void 得到距离 近的点对象

GetSplitRatioByNearestPoint NetPoint, out NetPoint,

out double

bool 获得根据给定点分裂线得

到的两个部分的比例,但

并不真正分裂线

(3)NetEdge NetEdge类用于定义一条弧段。该类包括两个成员属性,一个是int类型的nLink,用于

表示连接的弧段索引,即数组下标索引;另一个是float类型的fAngle,表示该弧段的水平夹

角。 (4)NetNode NetNode类从NetPoint派生而来。除了继承NetPoint类的所有成员属性之外,该类新加

入了一个ArrayList类型的m_arrLinks属性,表示连接的弧段。该类的成员方法如表4.110所示。

表4.110 NetNode类的方法

方法名称 参数 返回值 说明

NetNode 类的构造函数

NetNode double, double 类的构造函数

Add int, double bool 加入一个连接的弧段

Remove int bool 删除一个已连接的弧段

GetLinkAngle int double 得到一个连接弧段的水平夹角

(5)NetLink NetLink类用于定义网络弧段(链)。NetLink类包括如表4.111所示的属性。

表4.111 NetLink类的属性

属性名称 数据类型 说明

m_GeoID int 弧段的标识,即弧段的ID

m_nFNode int 起始结点(数组下标索引)

m_nTNode int 终止结点(数组下标索引)

m_fLength double 弧段长度

m_fFromImp double 正向阻力

m_fToImp double 逆向阻力

Page 95: C++ Builder和MapObjects实现

87

第 4章 系统详细设计

NetLink类包括如表4.112所示的方法。

表4.112 NetLink类的方法

方法名称 参数 返回值 说明

NetLink 类的构造函数

Copy NetLink void 复制弧段

IsEqual NetLink bool 判断两弧段是否相等

(6)NetLinkSeg NetLinkSeg类用于弧段分裂操作。该类包括两个属性,一个是int类型的nSegID,表示

分裂点后面部分的弧段索引,另一个是double类型的dRatio,表示分裂点到起始结点部分的

比例。 (7)NetLinkBackup NetLinkBackup类是用于备份弧段的类。在调入外部站点时,需要更新弧段表和结点表,

该类用于备份那些修改过的弧段,在分析结束后恢复弧段数据。NetLinkBackup类包括3个属性,第一个是int类型的m_nIndex,表示弧段的索引;第二个是NetLink类型的m_Link,表

示备份的弧段对象;第三个是ArrayList类型的m_arrSegs,表示该弧段被多次分割的比例列

表。NetLinkBackup类除了构造函数外,只有一个Add方法,用于添加一个弧段备份。 (8)NetPath NetPath类用于定义路径。图层中记录了该类的一个临时数组,用于存储某个结点到所

有结点的 短路径。路径的记录方法是每个点记录其在该路径中的前趋结点。NetPath类包

括两个属性,一个是double类型的m_dLength,表示该点到给定点的 短路径长度;另一个

是int类型的m_nPreNode,表示该点在该路径上的前趋结点。

4.2.4.3 NetLayer 类

具备了上述辅助类,便可设计NetLayer类了。NetLayer类包括如表4.113所示的属性。

表4.113 NetLayer类的属性

属性名称 数据类型 说明

m_arrLinks ArrayList 弧段表

m_arrNodes ArrayList 结点表

m_arrStops ArrayList 站点表

m_arrLinkBackups ArrayList 弧段备份表。私有成员

m_nLinkNum int 原始弧段数目(网络拓扑建成后)

注意:和m_arrLinks的大小可能是不相等的

m_nNodeNum int 原始结点数目(网络拓扑建成后)

注意:和m_arrNodes的大小可能是不相等的

m_pPath NetPath[] 某一个点到所有点的 短路径。每个点只记录它在路

径上的前趋结点

Page 96: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

88

(续表)

属性名称 数据类型 说明

m_layer MapObjects2.MapLayer 连接的图层对象

m_dataSet System.Data.DataSet 连接的数据集对象

NetLayer类包括的方法如表4.114所示。

表4.114 NetLayer类的方法

方法名称 参数 返回值 说明

NetLayer MapObjects2.MapLayer,

System.Data.DataSet

类的构造函数

ReadNetTable bool 将弧段表和结点属性表从数据

库读入到内存

LoadStops ArrayList, out ArrayList bool 加入点图层中的点作为站点或

者中心点,用于分析

UnloadStop bool 去除加入的站点或中心点

UpdateLinkNodeTable int, NetPoint, double, out int bool 利用外部结点更新弧段表和结

点表

IsConnectedDirectly int, int, out int, out double,

bool

int 判断两个结点是否直接相连,即

两个结点在同一条弧段上

GetConnectedDistance int, int, bool double 得到两个结点直接相连的距离。

不直接连接则返回-1

CalcPath int, int, bool bool 计算一个点到所有点的 短路

Path int, int, out ArrayList, bool double 计算两点间的 短路径

CreateResultPath ArrayList, out NetLine, bool bool 由结点生成路径的结果图层

PathAnalysis double, double, double,

double, out ArrayList

bool 两点路径分析, 并生成线图层

4.2.4.4 核心算法

要实现网络图层不是一件容易的事情。在进行详细设计时应该同时设计其中的核心算

法。 为了设计方便,我们做出如下约定:在所有的变量中,小于0的值表示无效;对于阻力

值,小于0表示阻力无穷大,即不连通;对于弧段号和结点号,小于0表示无相关弧段或结

点。

1. 一点到所有点的最短路径(CalcPath)

这里采用Dijkstra算法,只是每一段路径的长度都用正向阻力值和逆向阻力值计算。

短路径和 佳路径在算法上区分不大,只在于阻力值确定的方式。但是需要特别注意的是,

Page 97: C++ Builder和MapObjects实现

89

第 4章 系统详细设计

正向阻力值和逆向阻力值需要分别考虑。 Dijkstra算法的基本思想如下:

问题描述:设图G=(V, E),v0∈V,求从点v0出发到其他点的 短路径。 算法描述:设图G中有n个点,设置一个集合U,存放已经求出 短路径的点。V-U是

尚未确定 短路径的点集合,每个点对应一个距离值。集合U中点的距离值是从点v0到该点

的 短路径长度,集合V-U中点的距离值是从点v0到该点的只包括以集合U中点为中间点的

短路径长度。初始时,集合U中只有点v0,点v0对应的距离值为0,集合V-U中点vi的距离

值为边(v0,vi)的权值(i=1,2,…,n-1),如果v0和vi间无直接相连的边,则vi的距离值为∞。

在集合V-U中选择距离值 小的点vmin加入集合U,然后对集合V-U中各点的距离值进行修

正。如果加入点vmin为中间点后,使v0到vi的距离值比原来的距离值更小,则修改vi的距离

值。如此反复操作,直到从v0出发可以到达的所有点都在集合U中为止。 算法实现:设置一个CGisNetPath类的数组P[n],存放点v0到其他各个点的 短路径及

其 短路径长度。设D(i,j)为点vi到点vj的距离。

(1)初始时,集合U中只有点v0,从点v0到其他点vi(i=1,2,…,n-1)的 短路径长度

为边(v0,vi)的长度。如果点v0和vi不是直接相连,则假设存在一条从v0到vi长度为无穷(小

于0)的边。 (2)在集合V-U中找出距离值 小的点vmin,将其加入到集合U,从点v0到点vmin的

短路径长度就是vmin的距离值。 (3)调整集合V-U中点的距离值。如果将新加入的点vmin作为中间点后,v0到vi(vi∈

V-U)的距离值更小,则应修改vi的距离值。即:如果P[i].dLength > P[min].dLength + D(min, i),则将点vi的距离值改为P[min].dLength + D(min,i),并将路径上vi的前趋点改为vmin,即:

P[i].nPreNode = min。 (4)重复(2)、(3)操作,直到集合V-U中的点都加入到集合U中为止。

2. 计算资源分配结果的范围多边形算法

问题描述:将平面空间中分布的非空点集连接为一个凸多边形。 算法描述:这里采用格雷厄姆算法。算法依据:凸多边形的各顶点必在该多边形的任

意一条边的同一侧。 算法实现:

(1)设点集中y坐标 小的点为p0,把p0同点集中其他各点用线段连接,并计算这些

线段与水平线的夹角。然后按夹角大小及到p0的距离进行词典式分类(先按角度从小到大

排序,角度相等的按距离从小到大排序,角度和距离都相等的属于同一点,只需要记录其

中一个即可),得到一序列p0,p1,…,pn-1,依次连接这些点,便得到一个多边形。p0是凸壳边

界的起点,p1与pn-1也必是凸壳的顶点。pn= p0。 (2)删去p2,p3,…,pn-2中不是凸壳顶点的点,方法如下:

i)令k=3; ii)令j=1;

Page 98: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

90

iii)如果p0和pk分别在直线pk-j pk-j-1两侧,或者pk在直线pk-j pk-j-1上 那么删去pk-1,后继顶点编号减1:k=k-1,j=j-1; 否则pk-1暂时为凸壳顶点,并记录下来;

iv)令j=j+1,转iii),直到j=k+1; v)令k=k+1,转ii),直到k=n;

(3)顺序输出凸壳顶点。

在判定点p0和点p1是否在线段S(端点分别为s.p1和s.p0)的同侧时使用same函数。

same = ( dx * dy0 – dy * dx0 ) * ( dx * dy1 – dy * dx1 ) > 0

其中:dx = s.p1.x – s.p0.x dy = s.p1.y – s.p0.y dx0 = p0.x – s.p0.x dy0 = p0.y – s.p0.y dx1 = p1.x – s.p1.x dy1 = p1.y – s.p1.y

另外,当( dx * dy1 – dy * dx1 ) = 0时,pk在直线pk-j pk-j-1上。 如果是只计算范围多边形,不要求是凸多边形,那么只执行步骤(1)就可以。

3. 最短路径搜索问题

问题描述:给定图G,在只考虑距离因素的前提下,寻找从点A到B的 短路径。 算法描述:弧段的正向阻力和逆向阻力都使用弧段的长度。利用CalcPath方法计算从点

A到其他所有点的 短路径,然后从中找出到点B的路径。

4. 最佳路径搜索问题

问题描述:在考虑边的方向权重和点的连通性以及必须经过的中间点的前提下,选择

从点A到B的 佳路径。 算法描述:

(1)以起点为起始点,中间点集的第一个点为终止点,执行CalcPath; (2)以中间点集的第i点为起始点,第i+1点为终止点,执行CalcPath; (3)以中间结点集的 后一点为起始点,终点为终止点,执行CalcPath; (4)结束。

5. 可达性分析(一)

问题描述:在考虑边的方向权重和点的连通性的前提下,判断两个点是否连通。 算法描述:可达性分析不关心具体距离是多少,所以可以利用 佳路径算法,只要起

始点和终止点之间存在路径,则认为这两个点是连通的。可以省略算法中计算距离的步骤。

但是实际上利用 短路径算法计算连通性开销过大,可以采用递归算法逐个寻找每个相连

通的点。在实际实现中采用堆栈记录每个需要处理的点,依次弹栈,处理与其相连通的点。

Page 99: C++ Builder和MapObjects实现

91

第 4章 系统详细设计

6. 可达性分析(二)

问题描述:在考虑边的方向权重和点的连通性的前提下,确定从已知M个点中的每个

点出发可以到达的点。 算法描述:对于每个点,计算其连通的所有点,然后对其结果取并集。实际上,如果

点A和点B连通,那么所有与点A连通的点必然同时和点B连通,因此,如果已经处理过点A,

则不必再处理点B,但是还需要在结果集中去除B。

7. 追溯问题

问题描述:对于有向网络,在考虑边的方向权重和点的连通性的前提下,确定与已知

点连通的边,包括上游方向、下游方向和同时考虑上下游方向。下游方向就是弧段的方向,

上游方向就是弧段的逆方向。 算法描述:这里以下游方向为例,上游方向与此相同。

(1)将给定点压栈; (2)从栈顶取一个点; (3)对于与该点相连的一条弧段,如果该点是该弧段的起始点,并且该弧段正向连通,

则标记该点并记录该弧段,同时如果该弧段的终止点未标记,则将该终止点压栈; (4)对于与该点相连的每条弧段,重复执行(3); (5)标记该点已经处理; (6)重复执行(2)、(3)、(4)、(5),直到栈空为止。

如果同时考虑上下游,算法与确定与给定点连通的所有点相似,只是返回的是弧段。

8. 易达性问题(腹地问题)

在考虑边的方向权重和点的连通性的前提下,按 佳原则,将已知M个点无重复地分

配到N个目标点上。从M到N是多对一的关系。

9. 吸引区/服务区问题

问题描述:在考虑边的方向权重和点的连通性的前提下,按 佳原则,将网络内符合

条件的部分点和边分配到N个目标点(中心点)上,部分边需要动态分段。该分配属于多

对多的关系,但有两个限制条件:(1)每个中心点和服务区之间的运输消耗有可容许的上

限,(2)要区分网络流的方向:从中心点出发或流向中心点。 算法描述:这里描述一个中心点的服务区算法,多个中心点是一个中心点的简单重复。

(1)计算中心点到其他所有点的 短路径。将中心点压栈; (2)从栈顶取一个点; (3)对于与该给定点的一个直接相临的点,在规定网络流方向的条件下,如果连接两

点的弧段是连通的,那么:(a)如果给定点的阻力加上弧段的阻力小于既定的消耗上限,

则记录该连接弧段,并且记录弧段有效比率为1,同时,如果该弧段另一端点还未处理,则

将其压栈;(b)如果给定点的阻力加上弧段的阻力大于既定的消耗上限,则计算该弧段应

该分配的比率,记录该连接弧段以及分配比率;

Page 100: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

92

(4)对于与该给定点的每个直接相临的点,重复执行(3); (5)标记该点为已经处理; (6)重复执行(2)、(3)、(4)、(5),直到栈空为止。

在考虑网络流方向时,如果是从中心点出发则正常处理,如果是流向中心点,则互换

弧段的正向阻力和逆向阻力,然后再按照从中心点出发的步骤计算。

4.2.5 MapTip类的详细设计

MapTip类用于实现当用户鼠标在地图中某地物上停留时,动态显示该地物的名称。

MapTip类包括的属性如表4.115所示。

表4.115 MapTip类的属性

属性名称 数据类型 说明

m_x double 当前鼠标位置的X坐标

m_y double 当前鼠标位置的Y坐标

m_lastX double 当计时器启动时鼠标位置的X坐标

m_lastY double 当计时器启动时鼠标位置的Y坐标

m_map AxMapObjects2.AxMap 当前的地图对象

m_timer System.Windows.Forms.Timer 计时器对象

m_picture System.Windows.Forms.PictureBox 放置在标签控件后的图形对象,用作

背景

m_label System.Windows.Forms.Label 显示地物名称的标签控件对象

m_env TEnvironment 包含其他信息的类

m_szField string 显示地物属性数据的字段

MapTip类包括的方法如表4.116所示。

表4.116 MapTip类的方法

方法名称 参数 返回值 说明

MapTip frmMain 类的构造函数

MouseMove double, double void 移动鼠标

ShowTipText string void 显示地物的信息

InMap double, double bool 判断当前鼠标是否在地图内

Timer void 计时器触发时间响应函数

DoSearch MapObjects2.Recordset 查询地物对应的记录

Page 101: C++ Builder和MapObjects实现

第 5 章 系统主界面的实现

为使读者能够比较容易地理解程序代码,并从整体上把握MapObjects组件,本章将首

先简单介绍MapObjects的功能、特点和结构,然后介绍如何设计北京市地理信息公众查询

系统的主界面以及主要实现代码。

5.1 MapObjects简介

MapObjects包括一个OLE控件(OCX,地图控件)和一组(40多个)OLE对象(Object),它适用于工业标准程序设计环境,开发人员可在自己熟悉的开发环境中,如Visual Basic、Delphi、C#、C++ Builder、PowerBuilder与MS Access等,利用MapObjects开发系统开销小

的GIS应用,或在现有的应用中增加GIS功能。 MapObjects不适用于 终用户,它是为程序开发者设计的。程序开发者可利用

MapObjects开发应用程序并把这些程序提供给下一级用户使用。 MapObjects运行于Windows 95或Windows NT 3.51及更高版本。

5.1.1 MapObjects的功能

通过MapObjects可完成以下甚至更多功能:

(1)显示一张多图层地图,包括道路、河流与边界等。 (2)放大、缩小、漫游整个地图。 (3)生成图形特征(Feature),如点、线、圆、多边形等。 (4)显示说明注记。 (5)识别地图上被选中的特征。 (6)通过线、方框、区域、多边形、圆来选择特征。 (7)选择距某参照物特定范围内的特征。 (8)通过SQL描述来选择特征。 (9)对选取特征进行基本统计。 (10)对所选特征的属性进行更新和查询。 (11)绘制专题图。 (12)标注地图特征。 (13)从航空或卫星图片上截取图像。 (14)动态显示实时或系列时间组数据。 (15)在图上标注地址或定位。

Page 102: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

94

MapObjects可执行许多基础制图功能,但不能执行某些高级功能,如高质量地图输出、

地图坐标系投影、表面模型或网络分析等高级空间分析功能,以及拓扑编辑。可利用ESRI的其他产品(如Arc/Info、ArcView)来实现这些高级功能。

5.1.2 MapObjects的特点

MapObjects允许定制利用制图和GIS组件的应用程序,它主要包括如下方面的特点:

(1)支持Arc/Info的层(Coverage)。 (2)支持ESRI的Shape文件格式、SDE(空间数据库引擎)图层(Layer)以及大量栅

格图像格式,如BMP、JPEG、TIF、GIF等。 (3)支持通过Microsoft的ODBC规范访问外部数据库。 (4)把数据作为多个图层在一张地图中进行显示,并可以进行图幅变化。 (5)强大的专题图绘制功能。 (6)自动文字注记。 (7)用一个动态跟踪层动态显示实时数据。 (8)用标准SQL表达式进行特征选择和查询。 (9)通过大量搜索和框架操作符进行空间选择。 (10)地址匹配(地理编码)。 (11)强大而出色的对象模型。 (12)支持数据库版本管理。

5.1.3 MapObjects的结构

MapObjects是建立在Microsoft公司的对象和嵌入式技术(ActiveX)之上的。ActiveX是当今得到广泛支持的面向对象的软件集成技术,用户可以利用ActiveX组件开发和集成

Windows应用程序。 MapObjects包括一个地图控件和40多个具有属性、事件和方法的OLE对象。初次使用

MapObjects时,应了解这些对象及其属性和方法,这对于MapObjects的全部组织是非常有

用的。这些OLE对象是MapObjects区别于同类产品的制图软件控件,它们提供了灵活性和

多种功能。 MapObjects的对象分为6组——数据访问对象组、地图显示对象组、几何图形对象组、

地址匹配对象组、实用对象组与投影对象组。

5.1.3.1 数据访问对象组(Data Access Objects)

通过数据访问对象组,MapObjects便能建立与地图数据的联系,增加属性值,从地图

特征上反馈属性信息。数据访问对象组由以下对象组成:

(1)数据连接(DataConnection)对象:该对象是MapObjects通向地图数据的通道,

它通过自己的属性和方法来建立与地理数据集合(GeoDataset)的联系。 (2)地理数据集合(GeoDataset)对象:该对象代表制图数据并可引用图层。它可引

Page 103: C++ Builder和MapObjects实现

95

第 5章 系统主界面的实现

用Shape文件或SDE图层的数据。 (3)地理数据集集合(GeoDatasets)对象:该集合是针对一个数据连接的所有地理数

据集合对象的总合。它包括特定文件夹中的所有Shape文件或SDE数据库中所有的SDE图层。

(4)记录集合(Recordset)对象:该对象代表一个图层的所有记录。如果用户选择了

某特征,它就代表与所选特征相关的所有记录。它类似于数据库指针。 (5)TableDesc对象:该对象用于描述与记录集合相连的表的字段信息。 (6)表(Table)对象:它是一个只读数据通道对象,代表来自ODBC数据源的一个数

据表。你可增加一个表对象作为与图层对象的关联或用于批地址匹配。 (7)字段集合对象:它包括记录集合对象中的字段对象。 (8)统计对象:该对象代表关于一个记录集合的简单统计信息。它首先应用一种方法

计算关于记录集合的统计值,然后在统计对象中检查结果。

5.1.3.2 地图显示对象组(Map Display Objects)

通过地图显示对象组,你能用符号或专题描述绘制一张地图,也可加入图像作为背景,

在地图上显示动态数据。地图显示对象组由以下对象组成:

(1)地图控件:该控件用于显示图层、图像层和动态跟踪层对象,可以编写代码来控

制鼠标驱动的绘图事件,设置显示参数,通过方法绘制地理特征,闪烁显示选择的特征,

计算点与特征的距离、输入线、圆等。 (2)层(Layer)集合:是服务于地图控件的图层对象和图像层对象的集合。 (3)图层对象:它代表带有一些显示属性的地理数据集合对象。利用图层对象可以处

理专题地图,此对象有几个方法用来查找和选择地理特征。 (4)图像层对象:该对象代表一个作为地图控件背景的影像文件。 (5)动态跟踪层(TrackingLayer)对象:该对象能动态拖拽地理特征而无需重显。这

对实时数据的获取是十分理想的(如GPS)。它也可用于显示基本几何形状(如三角形、

圆形)和描述性文本,它们都不是地图数据的一部分。 (6)GeoEvent对象:代表可加到TrackingLayer对象上的点特征。 (7)符号(Symbol)对象:该对象使用非常广泛,它影响地图显示特征的许多方面,

其属性包括颜色、字形、大小、形状。 (8)文本(TextSymbol)对象:代表文本的某些属性(如准线、字型)。 (9)ClassBreaksRenderer对象:使用该对象能在图层对象中通过分类的办法依据数值

字段显示地理特征。 (10)ValueMapRender对象:通过该对象可以在图层对象中通过特殊字段中单独的值,

用符号来显示地理特征。 (11)LableRenderer对象:通过该对象可以在图层对象中依据地理特征的某一字段的

属性标注文本。

5.1.3.3 几何图形对象组(Geometric Objects)

几何图形对象组提供几种功能,包括:依据从图层中选择的特征反馈几何图形信息;

Page 104: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

96

向图层添加几何对象;向地图中绘制几何对象而不更新图层。几何图形对象组由以下对象

组成:

(1)矩形(Rectangle)对象:该对象经常用来设置和反馈地图范围,也用来绘制矩形。 (2)点集合:用于存储线和多边形对象的坐标。 (3)点对象:代表具有X、Y坐标的点。 (4)线对象:代表地图上的一条线。 (5)多边形对象:代表多边形,它的第一个点和 后一个点在它的点集合上是相同的。 (6)椭圆对象:该对象代表椭圆和圆。

5.1.3.4 地址匹配对象组(Address Matching Objects)

通过地址匹配对象组,可以访问一个图层上的某个地址,该地址具有街道和地址范围

并返回一个位置,并可标出十字路口的位置和地名。地址匹配对象组由以下对象组成:

(1)地址位置(AddressLocation)对象:是地址匹配结果。 (2)地理编码(GeoCoder)对象:在街道网络中,根据地址、路口或地址列表等信息

定位。 (3)地点定位(PlaceLocator)对象:通过该对象,可以列出带有地名的地理数据集,

并通过一个方法找出地名的位置。 (4)地址标准化(Standardizer)对象:该对象用于标准化地址、路口信息。

5.1.3.5 实用对象组(Utility Objects)

该对象组仅包含String集合,它是一个字符串集合,用于管理字符串。

5.1.3.6 投影对象组(Projection Objects)

通过投影对象组中的对象,可以定义坐标系和在不同坐标系之间进行坐标转换。投影

对象组由以下对象组成:

(1)基准面(Datum)对象:该对象确定了投影的基准面。 (2)地理坐标系统(GeoCoordSys)对象:该对象使用经纬度坐标系统描述地球上点

的位置。地理坐标系统依赖于基准面,该基准面由Datum属性确定。0度经线称为本初子午

线,该子午线由PrimeMeridian属性确定。而Unit属性确定该坐标系统的单位。 (3)地理坐标转换(GeoTransformation)对象:该对象用于将矢量数据从一个坐标系

转换到另一个坐标系。 (4)本初子午线(PrimeMeridian)对象:该对象定义了地理坐标系统对象的本初子午

线。 (5)投影坐标系统(ProjCoordSys)对象:经过投影的坐标系统不再使用经纬度来描

述某点的位置,而是使用X和Y坐标值。投影坐标系统是基于地理坐标系统,然后依据某一

方法将地球椭球体投影到平面上。该对象的GeoCoordSys属性定义了本投影坐标系统是从哪

个地理坐标系统投影而来,而Projection属性确定了投影方法。该坐标系统的单位由Unit属性确定。

Page 105: C++ Builder和MapObjects实现

97

第 5章 系统主界面的实现

(6)投影(Projection)对象:该对象表示将地理坐标系统转变成投影坐标系统所使用

的数学转换方法,例如高斯—克吕格投影、彭纳投影等。 (7)地球椭球体(Spheroid)对象:为了从数学上定义地球,必须建立一个地球表面

的几何模型。这个模型是由地球的形状决定的,是一个较为接近地球形状的几何模型,即

椭球体,包括一个椭圆及其所绕短轴。关于地球椭球体的大小,由于采用不同的测量推算

方法,椭球体的元素值是不同的。椭球体模型种类主要有白塞尔(Bessel)、克拉克(Clarke)等。MapObjects提供了40多个预定义的椭球体模型。

(8)单位(Unit)对象:该对象定义了地理坐标系统或投影坐标系统的单位。

5.2 导入MapObjects组件

启动C++ Builder 5,选择File菜单的Close All命令,将默认为空的项目关闭。然后按照

下面的步骤在C++ Builder集成开发环境中加入MapObjects组件。

(1)选择菜单Component中的Import ActiveX Control命令。 (2)如果原来没有注册过MapObjects,必须单击Add命令按钮,打开Register OLE

Control对话框来注册该组件,找到mo20.ocx并将它加入。 (3)拖动滚动条从库列表中选择ESRI MapObjects 2.0,如图5.1所示。

图 5.1 导入 MapObjects 对话框

(4)单击Install命令按钮,将MapObjects安装到控件选项板上。单击Install命令按钮将

产生MapObjects2_TLB与 MapObjects2_OCX两个单元文件,然后弹出如图5.2所示的安装对

话框。

Page 106: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

98

图 5.2 安装 MapObjects 组件对话框

(5)接受默认设置,单击OK命令按钮。C++ Builder就会重新编译dclusr50.bpk包文件。

编译完毕,MapObjects就被嵌入到用户组件包中,并且C++ Builder会弹出一消息框显示

TMap组件已被注册。单击OK命令按钮完成安装,然后选择File菜单中的Save All命令保存

所做的改变。这时切换到控件选项板的ActiveX页,就会看到新增加的MapObjects地图控件,

如图5.3所示。

图 5.3 安装 MapObjects 组件后的控件选项板

5.3 系统主界面设计

启动C++ Builder 5,选择File菜单中的New Application命令,新建一项目。将Name属性

默认为Form1的主窗体命名为MainForm,并将其文件名更改为Main.cpp。 在窗体 MainForm 中加入两个 ToolBar 控件,分别命名为 MapControlToolBar 与

FeatureToolBar,将其Align属性分别设置为alTop与alLeft。然后在窗体MainForm中加入一个

StatusBar控件,命名为StatusBar1,加入一个MapObjects地图控件,命名为Map, 后加入

一个Panel控件,命名为Panel1。将Map与Panel1控件的Align属性分别设置为alClient与alRight。

在Panel1控件中首先加入一个PageControl控件,命名为PageControl1,然后加入一个

Splitter控件,命名为Splitter1, 后加入一个MapObjects地图控件,命名为EyeMap。将它

们的Align属性分别命名为alTop、alTop与alClient。

5.3.1 创建资源

工具栏中需要显示小位图,此外,地图索引与地物控制树状列表框中也需要图标,因

此准备工作还需创建这些位图。

MapObjects地图控件

Page 107: C++ Builder和MapObjects实现

99

第 5章 系统主界面的实现

选择Tools菜单的Image Editor命令,打开图像编辑器应用程序。利用图像编辑器工具创

建如图5.4、5.5、5.6与5.7所示的位图文件。

图 5.4 MapControlToolBar.bmp 位图文件显示效果

图 5.5 FeatureControlToolBar.bmp 位图文件显示效果

图 5.6 Maps.bmp 位图文件显示效果

图 5.7 VisibleStatus.bmp 位图文件显示效果

5.3.2 设计地图控制工具栏

在窗体MainForm的MapControlToolBar控件上单击鼠标右键,选择New Button命令,便

可在工具栏中加入快捷按钮控件。按照该方法在MapControlToolBar控件中加入20个ToolButton控件,这些控件的名称及其属性如表5.1所示。

表5.1 MapControlToolBar控件中ToolButton控件的属性

控件 属性 属性值

Hint 放大

Group true

ShowHint true

ZoomIn

Style tbsCheck

Hint 缩小

Group true

ShowHint true

ZoomOut

Style tbsCheck

Hint 全图显示

Group true

ShowHint true

FullExtent

Style tbsCheck

Hint 漫游

Group true

ShowHint true

Pan

Style tbsCheck

Page 108: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

100

(续表)

控件 属性 属性值

Hint 点选择

Group true

ShowHint true

PointSelect

Style tbsCheck

Hint 矩形选择

Group true

ShowHint true

RectSelect

Style tbsCheck

Hint 多边形选择

Group true

ShowHint true

PolylineSelect

Style tbsCheck

Hint 信息

Group true

ShowHint true

InfoShow

Style tbsCheck

Hint 地图索引

Group true

ShowHint true

MapIndex

Style tbsCheck

Hint 地物控制

Group true

ShowHint true

FeatureControl

Style tbsCheck

Hint 地名索引

Group true

ShowHint true

IndexToolButton

Style tbsCheck

Hint 地名查询

Group true

ShowHint true

QueryToolButton

Style tbsCheck

Page 109: C++ Builder和MapObjects实现

101

第 5章 系统主界面的实现

(续表)

控件 属性 属性值

Hint 查找 近地名

Group true

ShowHint true

DistToolButton

Style tbsCheck

Hint 公交查询

Group true

ShowHint true

BusToolButton

Style tbsCheck

Hint 距离量算

Group true

ShowHint true

LineMeasure

Style tbsCheck

Hint 面积量算

Group true

ShowHint true

PolyMeasure

Style tbsCheck

Hint 短路径查询

Group true

ShowHint true

MinimizeDist

Style tbsCheck

Hint 打印地图

Group true

ShowHint true

PrintToolButton

Style tbsCheck

Hint 帮助

Group true

ShowHint true

ShowHelp

Style tbsCheck

Hint 退出系统

Group true

ShowHint true

ExitToolBar

Style tbsCheck

在窗体MainForm中加入一个ImageList控件,将其命名为MapImageList。双击该控件,

Page 110: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

102

或选择该控件右键菜单的ImageList Editor命令,打开如图5.8所示的图像列表编辑器对话框。

在其中单击Add按钮,并在其后弹出的Add Images对话框中选择我们先前创建的位图文件

MapControlToolBar.bmp。系统将会弹出一确定对话框,问是否分解为20个位图,在其中单

击Yes to All按钮即可。

图 5.8 图像列表编辑器对话框

将MapControlToolBar控件的ImageList属性设置为MapImageList,系统将按照顺序为其

中的每个ToolButton控件分配位图,并设置它们的ImageIndex属性。

5.3.3 设计地物类型工具栏

按照上述方法,在FeatureToolBar控件中加入14个ToolButton控件,这些控件的属性如

表5.2所示。

表5.2 FeatureToolBar控件中ToolButton控件的属性

控件 属性 属性值

Hint 全部地物

ShowHint true

AllFeature

Style tbsCheck

Hint 购物分布

ShowHint true

ShopFeature

Style tbsCheck

Hint 旅游地分布

ShowHint true

TourFeatrue

Style tbsCheck

Hint 学校分布

ShowHint true

SchoolFeatrue

Style tbsCheck

Page 111: C++ Builder和MapObjects实现

103

第 5章 系统主界面的实现

(续表)

控件 属性 属性值

Hint 医院分布

ShowHint true

HospitalFeature

Style tbsCheck

Hint 宾馆分布

ShowHint true

HotelFeature

Style tbsCheck

Hint 银行分布

ShowHint true

BankFeature

Style tbsCheck

Hint 加油站分布

ShowHint true

GasStationFeature

Style tbsCheck

Hint 电影音乐厅分布

ShowHint true

FilmFeature

Style tbsCheck

Hint 餐馆分布

ShowHint true

RestaurantFeature

Style tbsCheck

Hint WC分布

ShowHint true

WCFeature

Style tbsCheck

Hint 邮局分布

ShowHint true

PostalFeature

Style tbsCheck

Hint 图书馆分布

ShowHint true

LibraryFeature

Style tbsCheck

Hint 公交分布

ShowHint true

BusFeature

Style tbsCheck

在窗体MainForm中加入一个ImageList控件,将其命名为FeatureImageList。通过图像列

表编辑器,将FeatureControlToolBar.bmp加入到FeatureImageList控件中,并将FeatureToolBar控件的ImageList属性设置为FeatureImageList。

Page 112: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

104

5.3.4 设计状态栏

双击StatusBar1控件,开发环境将弹出如图5.9所示的面板编辑器。利用其中的Add New按钮加入3个面板。

图 5.9 状态栏面板编辑器

3个状态栏面板的属性如表5.3所示。

表5.3 3个状态栏面板的属性

控件 属性 属性值

Text 北京市地理信息公众查询系统 StatusBar1.Panels[0]

Width 200

Text 经度:0.0000,纬度:0.0000 StatusBar1.Panels[1]

Width 300

Text 比例尺: StatusBar1.Panels[2]

Width 200

5.3.5 设计“地图”页面

在PageControl1控件上单击鼠标右键,选择New Page命令,加入一个TabSheet控件,将

其命名为MapTabSheet。然后按照同样的方法在PageControl1中加入一个名为QueryTabSheet的TabSheet控件。将PageControl1控件的TabPosition属性设置为tpBottom。

在MapTabSheet中加入一个PageControl控件,命名为PageControl2。在PageControl2中加

入2个TabSheet控件,分别命名为MapIndexTabSheet与FeatureTabSheet。 在MapIndexTabSheet控件中加入一个TreeView控件,命名为MapIndexTreeView,将其

Align属性设置为alClient。 在FeatureTabSheet控件中加入一个TreeView控件,命名为FeatureControlTreeView,将

其Align属性设置为alClient。 “地图”页面MapTabSheet在设计期间的界面如图5.10所示。

Page 113: C++ Builder和MapObjects实现

105

第 5章 系统主界面的实现

图 5.10 MapTabSheet 在设计期间的界面

MapTabSheet及其控件的属性如表5.4所示。

表5.4 MapTabSheet及其控件的属性

控件 属性 属性值

MapTabSheet Caption 地图

PageControl2 Align alClient

MapIndexTabSheet Caption 地图索引

FeatureTabSheet Caption 地物控制

MapIndexTreeView Align alClient

FeatureControlTreeView Align alClient

5.3.6 设计“查询”页面

在QueryTabSheet控件中加入一个PageControl命令,命名为PageControl3,并将其Align属性设置为alClient。在PageControl3中利用右键菜单的New Page命令,在其中加入5个TabSheet控件,分别命名为IndexTabSheet、NameQueryTabSheet、DistTabSheet、BusTabSheet与ResultTabSheet。

在 IndexTabSheet 控 件 中 加 入 2 个 Label 控 件 , 分 别 命 名 为 IndexNameLabel 与IndexClassLabel;然后加入 3个ComboBox控件,分别命名为 IndexNameComboBox、IndexFirstClass与IndexSecondClass; 后加入一个ListBox控件,命名为IndexNameList。

IndexTabSheet控件在设计期间的界面如图5.11所示。

Page 114: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

106

图 5.11 IndexTabSheet 控件在设计期间的界面

IndexTabSheet及其控件的属性如表5.5所示。

表5.5 IndexTabSheet及其控件的属性

控件 属性 属性值

IndexTabSheet Caption 地名索引

IndexNameLabel Caption 地名名称:

IndexClassLabel Caption 地名分类:

IndexNameComboBox ImeMode imChinese

IndexFirstClass Style csDropDown

IndexSecondClass Style csDropDown

IndexNameList Sorted false

在NameQueryTabSheet控件中加入3个Label控件,分别命名为QueryNameLabel、QueryPhoneLabel与QueryFilterLabel;然后加入2个Edit控件,分别命名为QueryNameEdit与QueryPhoneEdit ; 再 加 入 2 个 ComboBox 控 件 , 分 别 命 名 为 QueryFirstClass 与

QuerySecondClass;接着加入一个Button按钮,命名为QuerySearch, 后加入3个CheckBox控件,分别命名为QueryBothCond、QueryNameCond与QueryPhoneCond。

NameQueryTabSheet控件在设计期间的界面如图5.12所示。

Page 115: C++ Builder和MapObjects实现

107

第 5章 系统主界面的实现

图 5.12 NameQueryTabSheet 控件在设计期间的界面

NameQueryTabSheet及其控件的属性如表5.6所示。

表5.6 NameQueryTabSheet及其控件的属性

控件 属性 属性值

NameQueryTabSheet Caption 地名查询

QueryNameLabel Caption 地名:

QueryPhoneLabel Caption 电话:

QueryFilterLabel Caption 被过滤:

QueryNameEdit Text

QueryPhoneEdit Text

QueryFirstClass Style csDropDown

QuerySecondClass Style csDropDown

QuerySearch Caption 搜索

QueryBothCond Caption 同时满足地名与电话条件

QueryNameCond Caption 使用地名模糊查询

QueryPhoneCond Caption 使用电话模糊查询

在DistTabSheet控件中首先加入2个RadioButton控件,分别命名为DistPositionCond与DistNameCond;再加入一个Edit控件,命名为DistNameEdit;然后加入2个Label控件,分别

命名为DistLabel与DistNote;接着加入3个ComboBox控件,分别命名为DistDistCond、DistFirstClass与DistSecondClass; 后加入一个Button控件,命名为DistSearch。在对象监视

器中双击DistDistCond控件的Item属性,在随后弹出的字符串列表编辑器中输入如图5.13所示的字符串。

Page 116: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

108

图 5.13 字符串列表编辑器

DistTabSheet控件在设计期间的界面如图5.14所示。

图 5.14 DistTabSheet 控件在设计期间的界面

DistTabSheet及其控件的属性如表5.7所示。

表5.7 DistTabSheet及其控件的属性

控件 属性 属性值

DistTabSheet Caption 查找 近

Enabled false DistNameEdit

Text

Caption 按位置查找 DistPositionCond

Checked true

Page 117: C++ Builder和MapObjects实现

109

第 5章 系统主界面的实现

(续表)

控件 属性 属性值

DistNameCond Caption 按地名查找

DistLabel Caption 距离:

DistNote Caption 按位置查找时,选择了距离和过滤类型后,先按"搜索"按钮,然

后将鼠标移动到地图上选择你所要查询的位置。

DistDistCond Text 近的

DistFirstClass Style csDropDown

DistSecondClass Style csDropDown

DistSearch Caption 搜索

在BusTabSheet控件中首先加入一个Edit控件,命名为BusName;然后加入2个RadioButton控件,分别命名为BusStation与BusLine;再加入一个ListBox控件,命名为

BusListBox;接着加入7个CheckBox控件,分别命名为BusUrban、BusNeighbor、BusYuntong、BusTaxi、BusLong、BusNight与BusSub; 后加入一个Button控件,命名为BusSearch。

BusTabSheet控件在设计期间的界面如图5.15所示。

图 5.15 BusTabSheet 控件在设计期间的界面

BusTabSheet及其控件的属性如表5.8所示。

表5.8 BusTabSheet及其控件的属性

控件 属性 属性值

BusTabSheet Caption 公交查询

BusName Text

Caption 公交车站 BusStation

Checked true

Page 118: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

110

(续表)

控件 属性 属性值

BusLine Caption 公交路线

BusListBox Sorted false

Caption 市区车 BusUrban

Checked true

Caption 郊区车 BusNeighbor

Checked true

Caption 运通专线 BusYuntong

Checked true

Caption 巴士专线 BusTaxi

Checked true

Caption 长途车 BusLong

Checked true

Caption 夜班车 BusNight

Checked true

Caption 地铁 BusSub

Checked true

BusSearch Caption 选择乘车路线

在ResultTabSheet控件中首先加入一个ListBox控件,命名为ResultList;然后加入3个Button控件,分别命名为ResultContent、ResultMedia与ResultPosition。

ResultTabSheet控件在设计期间的界面如图5.16所示。

图 5.16 ResultTabSheet 控件在设计期间的界面

Page 119: C++ Builder和MapObjects实现

111

第 5章 系统主界面的实现

ResultTabSheet及其控件的属性如表5.9所示。

表5.9 ResultTabSheet及其控件的属性

控件 属性 属性值

ResultTabSheet Caption 查询结果

ResultList Sorted true

Caption 内容 ResultContent

Enabled false

Caption 多媒体 ResultMedia

Enabled false

Caption 定位 ResultPosition

Enabled false

5.3.7 其他辅助控件

在窗体 MainForm中加入 2 个 ImageList 控件,分别命名为 MapIndexImageList 与

VisibleImageList。通过图像列表编辑器将前面创建的位图文件Maps.bmp与VisibleStatus.bmp分别加入到这两个ImageList控件中。将MapIndexTreeView控件的ImageList属性设置为

MapIndexImageList , 将 FeatureControlTreeView 控 件 的 ImageList 属 性 设 置 为

VisibleImageList。 经过上述步骤,主窗体MainForm在设计期间的界面如图5.17所示。

图 5.17 主窗体 MainForm 在设计期间的界面

5.4 TEnvironment类的初步实现

选择File菜单的New命令,打开New Item对话框,在该对话框的New选项卡中选择Unit

Page 120: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

112

图标,单击OK按钮后,将在当前项目中加入一个空单元文件,将其保存为Environment.cpp。

5.4.1 辅助类的实现

要实现TEnvironment类,还需要使用其他一些辅助类。根据系统的详细设计,切换到

Environment.h文件中,加入如下代码:

//---------------------------------------------------------------------

#include <system.hpp>

#include <ADODB.hpp> #include <DB.hpp>

#include "MapObjects2_OCX.h"

//--------------------------------------------------------------------- struct LayerInfo

{

String szName; String szLayerName;

String szFileName;

String szTableName; String szFieldName;

String szType;

String szSubType; //中类 String szSubType2; //小类

String szSubType3;

bool bCanControl; bool bVisible; //可显示状态,只能通过“地物控制”选项卡来改变

bool bSelected;

bool bCanSelected; //可选择 bool bBackground;

bool bLable;

double dScale; double dShowScale;

int nCharacterIndex;

String szFontName; int nFontSize;

int nSymSize;

int nSymColor;

IMoMapLayerPtr layer;

IMoRecordset* rsSel; };

//---------------------------------------------------------------------

class MPoint {

public:

double x;

Page 121: C++ Builder和MapObjects实现

113

第 5章 系统主界面的实现

double y;

MPoint() {

x = 0.0;

y = 0.0; }

};

//--------------------------------------------------------------------- class MLine

{

public: int nPointNumber;

MPoint* pPoint;

MLine()

{

} };

//---------------------------------------------------------------------

class CloestPath {

public:

MPoint pt1; MPoint pt2;

CloestPath() {

}

}; //---------------------------------------------------------------------

class Buses

{ public:

int nNum;

MPoint* pts;

Buses()

{ pts = new MPoint[200];

}

}; //---------------------------------------------------------------------

enum MapOpr { MO_NULL=0, MO_ZOOMIN, MO_ZOOMOUT, MO_ZOOMFULL, MO_PAN,

MO_POINTSEL, MO_RECTSEL, MO_CIRCLESEL, MO_POLYGONSEL, MO_INFO, MO_LINEMEAS, MO_POLYMEAS, MO_SEACHBYDIST, MO_CLOSEST };

Page 122: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

114

enum MapDisp { MO_ALL = 0, MO_SCHOOL = 1, MO_TOUR, MO_HOSPITAL, MO_SHOP,

MO_HOTEL, MO_GAS, MO_BANK, MO_MOVIE, MO_RESTAURANT, MO_WS, MO_POST,

MO_LIBRAY, MO_STATION }; //---------------------------------------------------------------------

struct MapInfo

{ String szName;

String szMetaTable;

String szIndexTable; String szType;

IMoRectanglePtr rect;

}; //---------------------------------------------------------------------

struct IndexInfo

{ String szName;

double dX;

double dY; IMoRectanglePtr m_extent;

};

//---------------------------------------------------------------------

5.4.2 TEnvironment类的成员变量

在TEnvironment类中加入如下一些公有成员变量:

//---------------------------------------------------------------------

String BUSLINE_LAYERNAME;

String BUSSTATION_LAYERNAME; int SYMBOL_COLOR_NONE;

String m_szDBName ; String m_szSDBPath;

String m_AppPath;

String m_szHelpPath;

IMoDataConnectionPtr m_db;

int m_nCurrMapIndex; int m_nMapNum;

MapInfo* m_mapInfos;

int m_nLayerNum;

LayerInfo* m_layerInfos ;

int m_nIndexNum; IndexInfo* m_indexInfos;

IMoSymbolPtr m_selSymbol;

Page 123: C++ Builder和MapObjects实现

115

第 5章 系统主界面的实现

String m_szfntStation;

char m_chStation ;

int m_nfntStation ;

TADODataSet* m_dataSet;

long m_x,m_y;

int m_MapOpr;

CloestPath m_cloestPath;

double m_dDistance; // 短距离查询

IMoSymbolPtr m_selectedSymbol; short m_selectedSymbolSize;

double m_selectedScale;

MLine* m_drawLine; String m_szBusFilter;

bool m_bPathInit; String m_szPlaceName;

IMoDataConnectionPtr m_shapeRect; IMoMapLayerPtr m_layerRoad;

TADOConnection* m_DataConnection;

//---------------------------------------------------------------------

在构造函数中初始化这些成员变量,代码如下:

//---------------------------------------------------------------------

TEnvironment::TEnvironment()

{ BUSLINE_LAYERNAME = "公交线路";

BUSSTATION_LAYERNAME = "公交车站";

SYMBOL_COLOR_NONE = 9999; m_szDBName = "";

m_szSDBPath = "";

m_AppPath = ""; m_szHelpPath = "";

m_nCurrMapIndex = -1;

m_nMapNum = -1; m_mapInfos = NULL;

m_nLayerNum = -1;

m_layerInfos = NULL; m_nIndexNum = -1;

m_indexInfos = NULL;

m_szfntStation = ""; m_chStation = 'a';

m_nfntStation = 10;

Page 124: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

116

m_drawLine = NULL;

m_szBusFilter = "";

m_bPathInit = false; m_szPlaceName = "";

m_DataConnection = NULL;

m_MapOpr = MO_NULL;

m_selSymbol = (IDispatch*)CreateOleObject("MapObjects2.Symbol");

m_selSymbol->SymbolType = moPointSymbol; m_selSymbol->Color = 0xff;

}

//---------------------------------------------------------------------

5.5 读取元数据

北京市地理信息公众查询系统将该系统中包含的地图及其图层信息放置在Access数据

库“地名数据库.mdb”中,因此我们首先需要从该数据库中获取元数据信息。 通常,C++ Builder数据库应用程序并不直接与这些数据库文件打交道,而是通过一个

名为BDE(Borland Database Engine,Borland数据库引擎)的数据库接口来处理各数据库文

件。BDE定义了各类数据库文件的接口,如dBase、Paradox、Oracle、Sybase等数据库。C++ Builder系统文件中有一个专门管理接口的程序bdeadmin.exe(系统安装时会自动将其挂到

Windows的开始菜单中),启动该程序,显示如图5.18所示的界面。通过这个界面,可以配

置和修改其底层接口。

图 5.18 BDE 接口管理程序

只有通过BDE,C++ Builder才能处理各种数据库文件,其处理数据库文件的机制如图

5.19所示。由此图可见,使用C++ Builder数据库应用程序必须先安装BDE接口。

Page 125: C++ Builder和MapObjects实现

117

第 5章 系统主界面的实现

图 5.19 C++ Builder 处理数据库文件的机制

C++ Builder数据库应用程序基本由两部分组成,即DataAccess(数据访问)控件和

DataControl(数据控制)控件。DataAccess控件负责和BDE进行数据交换,并为DataControl控件提供数据,将在DataControl控件中修改的数据经BDE传送到数据库文件中。在和BDE打交道时要用到TDataSet控件,TDataSet控件包括TTable、TQuery、TStoreProc等控件,每

个TDataSet控件都与一个数据库文件连接,并且为TDataSource控件提供数据,而每个

TDataSource控件也应该指定一个TDataSet控件。这些控件的关系如图5.20所示。

图 5.20 数据库应用程序结构

TDataSource控件负责从TDataSet控件中接收数据并把在TDataControl控件中修改的数

据经TDataSource控件传给BDE。所有的DataAccess控件均为运行时不可见控件,它们隐藏

在用户界面后面,而由DataControl控件显示其数据,并提供浏览、修改数据的工具。 除了基于BDE的连接和数据集组件,C++ Builder还提供了基于ADO的连接和数据集组

件。这些组件允许编程者连接一个ADO数据库,然后执行命令并从数据库表中读取数据。 这些基于ADO的数据存取组件连接到ADO数据仓库,只使用ADO框架对数据进行操

作,BDE在这种处理中根本没有参与。因此,程序员可以在BDE不可用而ADO可用或不希

数据库应用程序

BDE别名

BDE驱动

本地DBMS驱动 ODBC驱动

TDatabase

TTable TQuery TStoreProc

TDataSource

TTable TQuery TStoreProc

Page 126: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

118

望使用BDE时使用ADO组件。ADO 2 .1(或更高版本)必须在主机上安装。此外,目标数

据库系统(如Microsoft SQL Server)客户端软件也必须安装。 多数ADO连接组件或数据集组件与基于BDE的连接组件或数据集组件具有一一对应

的关系。例如,TADOConnection组件与基于BDE应用程序的TDatabase组件功能相似,

TADOTable对应于TTable,TADOQuery对应于TQuery,TADOStoredProc对应于TStoredProc。使用这些ADO组件与使用BDE组件的习惯和内容都相似。TADODataSet没有直接对应的

BDE组件,但提供了许多与TTable和TQuery相同的功能。同样,也没有与TADOCommand对应的BDE组件,在C++ Builder & ADO环境中,TADOCommand具有特殊的用途。

ADO组件由表5.10中的控件组成。

表5.10 ADO组件所包含的控件

控件 用途

TADOConnection 用于与ADO数据仓库建立连接;多个ADO数据集和命令组件能够共享这个连接来

执行命令、获取数据以及对元数据进行操作

TADODataset 是用于获取和操作数据的主要组件,可以从单个或多个数据表中获取数据,可以直

接或通过TADOConnection控件连接到数据仓库

TADOTable 用于获取和操作由单个数据表产生的数据集,可以直接或通过TADOConnection控

件连接到数据仓库

TADOQuery 用于获取和操作由一条有效的SQL语句产生的数据集,也能执行数据定义语言

(DDL)SQL语句,如CREATE TABLE;可以直接或通过TADOConnection控件连

接到数据仓库

TADOStoredProc 用于执行存储过程,可以执行获取数据或执行DDL SQL语句的存储过程,可以直

接或通过TADOConnection控件连接到数据仓库

TADOCommand 主要用于执行命令(不返回结果集的SQL语句);与数据集组件结合使用,也能够

从数据表获取数据集;可以直接或通过TADOConnection控件连接到数据仓库

在MainForm类的公有段加入如下成员变量:

//---------------------------------------------------------------------

public: TEnvironment* _environment;

bool _bTVChecked;

double MAX_SCALE; double MIN_SCALE;

//---------------------------------------------------------------------

在MainForm类的构造函数中初始化这些成员变量,代码如下:

//--------------------------------------------------------------------- __fastcall TMainForm::TMainForm(TComponent* Owner)

: TForm(Owner)

{ _environment = NULL;

Page 127: C++ Builder和MapObjects实现

119

第 5章 系统主界面的实现

MAX_SCALE = 4000;

MIN_SCALE = 20000;

} //---------------------------------------------------------------------

连接数据库并从中读取元数据的主要函数是CreateDataSet,该函数的代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::CreateDataSet() {

String strConnectionString;

String strDatabasePath;

// 创建连接字符串

strDatabasePath = _environment->m_AppPath; strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" +

_environment->m_szDBName + "; Persist Security Info=False";

_environment->m_DataConnection = new TADOConnection(this); _environment->m_DataConnection->ConnectionString =

strConnectionString;

_environment->m_DataConnection->LoginPrompt = false;

try

{ _environment->m_DataConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

//创建数据库

//创建数据字典 _environment->m_dataSet = new TADODataSet(this);

_environment->m_dataSet->Connection = _environment->

m_DataConnection; _environment->m_dataSet->CommandText = "select * from 地图集信息表";

_environment->m_dataSet->Open();

//地图数目等于地图集信息表中的记录数

_environment->m_nMapNum = _environment->m_dataSet->RecordCount;

_environment->m_mapInfos = new MapInfo[_environment->m_nMapNum];

IMoRectanglePtr rect =

Page 128: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

120

(IDispatch*)CreateOleObject("MapObjects2.Rectangle");

_environment->m_dataSet->First();

for(int i=0; i< _environment->m_nMapNum; i++) {

AnsiString szTableName = _environment->m_dataSet->Fields->

FieldByName("表名")->AsString; if (szTableName != "")

{

_environment->m_mapInfos[i].szName = _environment-> m_dataSet->Fields->FieldByName("名称")->AsString;

_environment->m_mapInfos[i].szMetaTable =

szTableName; _environment->m_mapInfos[i].szIndexTable =

_environment->

m_dataSet->Fields->FieldByName( "索引表名")->AsString;

_environment->m_mapInfos[i].rect = (IDispatch*) CreateOleObject("MapObjects2.Rectangle");

_environment->m_mapInfos[i].rect->Left =

_environment-> m_dataSet->Fields->FieldByName("X1")->AsFloat;

_environment->m_mapInfos[i].rect->Top =

_environment-> m_dataSet->Fields->FieldByName("Y1")->AsFloat;

_environment->m_mapInfos[i].rect->Right =

_environment-> m_dataSet->Fields->FieldByName("X2")->AsFloat;

_environment->m_mapInfos[i].rect->Bottom =

_environment->m_dataSet->Fields->FieldByName("Y2")->AsFloat; }

_environment->m_dataSet->Next();

}

_environment->m_nCurrMapIndex = 0;

_environment->m_DataConnection->Close(); }

//---------------------------------------------------------------------

在上述代码中,我们利用TADOConnection控件来连接数据库。其实,应用程序中的每

一个ADO数据集组件和命令组件可以直接连接到数据仓库。然而,当使用许多命令和数据

集组件时,使用一个TADOConnection建立该连接,然后在命令组件和数据集组件之间共享

这个连接,这种方法更容易维护连接。与分别连接每一个命令组件或数据集组件相比,使

用TADOConnection控件建立连接的方法提供了对连接的更多控制。这些控制由

TADOConnection 控件的属性、方法和事件提供,而这些功能在其他情况下是不可用的。 要使用TADOConnection控件为ADO数据集和命令组件提供一个共享连接,首先要建立

Page 129: C++ Builder和MapObjects实现

121

第 5章 系统主界面的实现

连接。可以通过指定连接控件的ConnectionString属性中的连接信息来建立连接。在设计时,

在对象监视器中单击ConnectionString属性的带省略号的按钮,打开连接字符串编辑器对话

框。这个对话框(由ADO系统本身提供)允许通过从列表中选择连接元素(如提供者和服

务器)来建立连接字符串。在运行时,给ConnectionString指定一个AnsiString值作为连接信

息,设置连接组件的Connected属性为true,将激活连接。然而,这不是必要的,尽管在设

计时这是一个很好的测试连接的方法。 ConnectionString属性包含了多个连接参数,参数之间由分号隔开。这些参数包括提供

者名字、用户名和口令(用于登录)以及远程服务器的参考标识。ConnectionString属性也

可以包括包含了连接参数的文件名,该文件与ConnectionString属性有相同的内容:一个或

多个参数,每个参数带有给定值,参数之间用分号隔开。要连接Access数据库,使用

Microsoft.Jet.OLEDB.4.0,其连接字符串的格式如下:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\CsExample\MainSystem\地名

数据库.mdb

要激活一个ADO 连接控件,设置TADOConnection控件的Connected 属性为true,或者

调用控件的Open方法。可以使用TADOConnection的属性和事件处理程序来控制连接的条件

和属性。 可以使用TADOConnection的ConnectionOptions属性来强制连接为异步方式。默认时,

ConnectionOptions被设置为coConnectUnspecified,表示允许服务器决定 好的连接方式。

要明确地指定连接为异步方式,设置ConnectionOptions为coAsyncConnect。要设置连接为异

步或者由服务器决定,可以为连接组件的ConnectionOptions属性指定TConnectOption常数之

一,然后通过调用它的Open方法,设置Connected属性为true,或者通过激活一个关联的命

令组件或数据集组件来激活该连接控件。 使用TADOConnection控件的Attributes属性可以控制连接控件保持提交或保持取消的

状态。Attributes可以同时包含xaCommitRetaining和xaAbortRetaining,也可以只包含两者中

的一个,这就使得程序员可以使用相同的属性来互斥控制保持提交与保持取消。 可以使用TADOConnection控件的ConnectionTimeout属性和CommandTimeout属性来控

制在试图执行命令和连接时是否超时。ConnectionTimeout属性可建立到数据仓库的超时值。

如果调用Open方法初始化连接在ConnectionTimeout属性指定的时间到达后仍未成功,则该

连接尝试被取消。设置ConnectionTimeout属性值单位为s。 使用连接控件尝试连接到数据仓库会触发一个安全登录事件OnLogin。这个事件表现为

打开一个登录对话框,提示用户输入用户名和口令。如果需要,可以禁用该对话框,通过

编程提供用户名和口令。要禁用默认的登录对话框,首先设置连接组件的LoginPrompt属性

为false,然后,在激活连接组件之前,通过设置类似ConnectionString属性的方法提供所有

需要的登录信息。 TADODataSet控件为C++ Builder应用程序提供从通过ADO关联的数据库的一个或多

个数据表存取数据的能力。使用ADO数据集组件的CommandText属性,通过名字或者使用

一条SQL语句来指定要存取的数据表。可以使用数据库连接访问数据库,该连接是通过ADO数 据 集 控 件 的 ConnectionString 属 性 或 在 Connection 属 性 中 指 定 的 一 个 独 立 的

Page 130: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

122

TADOConnection控件建立的。在CommandText属性中指定一个数据表的名字或一条SQL语句,并激活该控件。在设计期间,可以使用命令行文本编辑器建立命令。要打开这个编辑

器,可在对象监视器的CommandText属性中单击带省略号的按钮。在运行时,以AnsiString形式指定一条命令到CommandText属性。使用CommandType属性指定执行的命令的类型:

如果命令是一个数据表名,则CommandType为cmdTable(或cmdTableDirect);如果命令

是一条SQL语句,则CommandType为cmdText。如果不知道命令的类型,或希望ADO基于

CommandText的内容来决定命令的类型,也可以为该属性指定cmdUnknown值。在设计时,

从对象监视器的下拉列表框中选择想要的CommandType值。在运行时,给TCommandType赋一个类型值。 后调用TADODataSet的Open方法或将其Active属性值赋为true,激活

TADODataSet控件。激活TADODataSet控件之后,便可利用该控件的属性来访问数据表内

容。 TADODataSet控件的RecordCount属性表示对应数据集中的记录条数。在ADO数据集中

浏览数据记录所需操作与在基本的数据集控件中一样。使用First、Next、Last和Prior方法在

数据集组件中将记录指针从一个记录移动到另一个记录。循环浏览可以基于Eof和Bof属性,

这样就可以对数据集中所有的记录进行操作。可以使用数据集组件的Fields属性和

FieldByname属性提供动态的TField引用。可以使用TField类和派生类的属性和方法来设置

或得到字段值、验证数据以及决定字段数据的类型。 调用CreateDataSet函数的是LoadData函数,该函数的代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::LoadData()

{ CreateDataSet();

_environment->m_db = (IDispatch*)CreateOleObject("MapObjects2.DataConnection");

_environment->m_db->Database = WideString

(_environment->m_szSDBPath).Detach() ; }

//---------------------------------------------------------------------

LoadData函数除了调用CreateDataSet函数外,还调用了CreateOleObject ( "MapObjects2. DataConnection")创建DataConnection对象,并赋予_environment变量的m_db成员变量。

DataConnection对象用来连接装有Shape文件的文件夹或SDE数据库。 北京市地理信息公众查询系统在主窗体MainForm的OnCreate事件响应函数中调用

LoadData函数,实现读取元数据信息的功能。不过在调用LoadData函数之前,需要先初始

化_environment成员变量,所用函数为Initialize,其代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm::Initialize()

{

AnsiString applicationPath = ExtractFilePath(Application->ExeName); _environment = new TEnvironment();

Page 131: C++ Builder和MapObjects实现

123

第 5章 系统主界面的实现

_environment->m_szSDBPath = applicationPath + "电子地图\\";

_environment->m_szDBName = applicationPath + "地名数据库.mdb";

_environment->m_AppPath = applicationPath; _environment->m_szHelpPath = applicationPath + "帮助\\start.htm";

}

//---------------------------------------------------------------------

创建主窗体MainForm的OnCreate事件响应函数,在其中加入如下代码,用于初始化

_environment变量,以及从数据库中读入元数据信息:

//---------------------------------------------------------------------

void __fastcall TMainForm::FormCreate(TObject *Sender)

{ Initialize();

LoadData();

} //---------------------------------------------------------------------

5.6 “地图”页面的实现

由系统的详细设计可知,系统主窗口中的右边工作区用于控制地图的显示及查询,包

括“地图”与“查询”两个页面,以及一个地图的缩略图。这里先实现“地图”页面。 “地图”页面又包含两个选项卡,分别是“地图索引”与“地物控制”。实现“地图

索引”选项卡的函数包括LoadIndexInfos与LoadMapIndexTreeView,前者用于装载当前地图

的索引信息,后者用于在MapIndexTreeView控件中显示地图索引信息。 LoadIndexInfos函数的代码如下:

//---------------------------------------------------------------------

// 装载索引信息 void __fastcall TMainForm:: LoadIndexInfos(int nIndex)

{

AnsiString szIndexTable = _environment->m_mapInfos[nIndex].szIndexTable;

// 索引信息表 if(_environment->m_DataConnection->Connected == false)

_environment->m_DataConnection->Connected = true;

TADODataSet* IndexDataSet = new TADODataSet(this);

IndexDataSet->Connection = _environment->m_DataConnection;

IndexDataSet->CommandText = "Select * from " + szIndexTable + " Order By id";

IndexDataSet->Open() ;

Page 132: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

124

_environment->m_nIndexNum = IndexDataSet->RecordCount;

_environment->m_indexInfos = new IndexInfo[_environment->m_nIndexNum];

IndexDataSet->First() ;

for(int i=0; i< _environment->m_nIndexNum; i++)

{ _environment->m_indexInfos[i].szName = IndexDataSet->FieldByName("

名称") ->AsString;

_environment->m_indexInfos[i].m_extent = (IDispatch*)

CreateOleObject("MapObjects2.Rectangle");

_environment->m_indexInfos[i].m_extent->Top = IndexDataSet-> FieldByName("Y1")->AsFloat;

_environment->m_indexInfos[i].m_extent->Bottom = IndexDataSet->

FieldByName("Y2")->AsFloat; _environment->m_indexInfos[i].m_extent->Right = IndexDataSet->

FieldByName("X2")->AsFloat;

_environment->m_indexInfos[i].m_extent->Left = IndexDataSet-> FieldByName("X1")->AsFloat;

IndexDataSet->Next() ; }

IndexDataSet->First (); IndexDataSet->Close ();

}

//---------------------------------------------------------------------

上述代码利用TADODataSet控件从数据表中获取当前地图所包含的索引信息,并将其

保存在_environment变量的m_ indexInfos数组中。 LoadMapIndexTreeView函数代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm ::LoadMapIndexTreeView()

{ //处理地图索引树状列表

MapIndexTreeView->Items->Clear();

for (int i = 0; i < _environment->m_nMapNum; i ++ )

{

TTreeNode* mapNode = MapIndexTreeView->Items->Add ( NULL, _environment->m_mapInfos[i].szName);

mapNode->ImageIndex = 0;

//装载地图索引信息

LoadIndexInfos(i);

Page 133: C++ Builder和MapObjects实现

125

第 5章 系统主界面的实现

//加入地图索引子结点

for (int j = 0; j < _environment->m_nIndexNum; j ++)

{ TTreeNode* subNode = MapIndexTreeView->Items->AddChild

( mapNode,_environment->m_indexInfos[j].szName);

subNode->ImageIndex = 0; }

//展开树状列表 mapNode->Expand(true);

}

} //---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数的尾部加入如下代码,调用上述两个函数:

//---------------------------------------------------------------------

// 装载地图索引信息 LoadIndexInfos(_environment->m_nCurrMapIndex);

// 以树状列表形式显示地图索引信息

LoadMapIndexTreeView(); //---------------------------------------------------------------------

编译并运行程序,系统运行界面如图5.21所示。“地图”页面中的“地图索引”选项

卡以树状列表的形式列出了地图索引信息。

图 5.21 实现“地图索引”树状列表后系统的运行界面

实现“地物控制”选项卡的是LoadLayerInfos函数和LoadFeatureControlTreeView函数。

前者用于装载当前地图的图层信息,后者用于在FeatureControlTreeView控件中显示地物控

制信息。 LoadLayerInfos函数的代码如下:

Page 134: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

126

//---------------------------------------------------------------------

//装载图层信息

void __fastcall TMainForm:: LoadLayerInfos(int nIndex) {

String szMetaTable = _environment->m_mapInfos[nIndex].szMetaTable;

TADODataSet* InfoDataSet = new TADODataSet(this);

InfoDataSet->Connection = _environment->m_DataConnection;

InfoDataSet->CommandText = "Select * From " + szMetaTable + " Where 存在 = True Order By 显示次序1,显示次序2,id";

InfoDataSet->Open() ;

_environment->m_nLayerNum = InfoDataSet->RecordCount;

_environment->m_layerInfos = NULL;

_environment->m_layerInfos = new LayerInfo[_environment->m_nLayerNum];

InfoDataSet->First() ;

for(int i=0; i< _environment->m_nLayerNum; i++) {

_environment->m_layerInfos[i].szName = InfoDataSet->FieldByName("

名称") ->AsString;

_environment->m_layerInfos[i].szType =

InfoDataSet->FieldByName("大类") ->AsString;

_environment->m_layerInfos[i].szFileName =

InfoDataSet->FieldByName ("图形文件")->AsString;

_environment->m_layerInfos[i].szSubType =

InfoDataSet->FieldByName ("中类") ->AsString;

_environment->m_layerInfos[i].szTableName =

InfoDataSet->FieldByName ("属性表名")->AsString;

_environment->m_layerInfos[i].szLayerName =

InfoDataSet->FieldByName ("图层名")->AsString;

_environment->m_layerInfos[i].bVisible =

InfoDataSet->FieldByName("显示") ->AsBoolean;

_environment->m_layerInfos[i].bLable =

InfoDataSet->FieldByName("注记") ->AsBoolean;

_environment->m_layerInfos[i].bCanSelected =

InfoDataSet->FieldByName ("选择")->AsBoolean;

Page 135: C++ Builder和MapObjects实现

127

第 5章 系统主界面的实现

_environment->m_layerInfos[i].bCanControl =

InfoDataSet->FieldByName

("控制")->AsBoolean; _environment->m_layerInfos[i].bBackground =

InfoDataSet->FieldByName

("地物")->AsBoolean; _environment->m_layerInfos[i].szSubType2 =

InfoDataSet->FieldByName

("小类")->AsString; _environment->m_layerInfos[i].szSubType3 =

InfoDataSet->FieldByName

("次小类")->AsString; _environment->m_layerInfos[i].dScale = InfoDataSet->FieldByName

("注记比例尺")->AsFloat;

if(_environment->m_layerInfos[i].dScale == 0) _environment->m_layerInfos[i].dScale = 999999999;

_environment->m_layerInfos[i].dShowScale =

InfoDataSet->FieldByName ("显示比例尺")->AsFloat;

if(_environment->m_layerInfos[i].dShowScale == 0)

_environment->m_layerInfos[i].dShowScale = 999999999; _environment->m_layerInfos[i].nCharacterIndex =

InfoDataSet->FieldByName

("符号索引")->AsInteger; if(_environment->m_layerInfos[i].nCharacterIndex == 0)

_environment->m_layerInfos[i].nCharacterIndex = -1;

_environment->m_layerInfos[i].nFontSize = InfoDataSet->FieldByName

("注记大小")->AsInteger;

if(_environment->m_layerInfos[i].nFontSize == 0) _environment->m_layerInfos[i].nFontSize = 10;

_environment->m_layerInfos[i].szFontName

=InfoDataSet->FieldByName ("字体名称")->AsString;

_environment->m_layerInfos[i].nSymSize = InfoDataSet->FieldByName

("符号大小")->AsInteger; if(_environment->m_layerInfos[i].nSymSize == 0)

_environment->m_layerInfos[i].nSymSize = 4;

_environment->m_layerInfos[i].nSymColor = InfoDataSet->FieldByName ("符号颜色")->AsInteger;

if(_environment->m_layerInfos[i].nSymColor == 0)

_environment->m_layerInfos[i].nSymColor = 9999; _environment->m_layerInfos[i].szFieldName =

InfoDataSet->FieldByName

("字段名")->AsString; if(_environment->m_layerInfos[i].szFieldName == "")

Page 136: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

128

_environment->m_layerInfos[i].szFieldName = "单位名称";

if (_environment->m_layerInfos[i].szLayerName =="公交车站") {

_environment->m_szfntStation = _environment->

m_layerInfos[i].szFontName; _environment->m_chStation = (char)_environment->

m_layerInfos[i].nCharacterIndex;

_environment->m_nfntStation = _environment->m_layerInfos[i].nFontSize;

}

InfoDataSet->Next() ;

}

InfoDataSet->First ();

// 关闭数据表

InfoDataSet->Close (); }

//---------------------------------------------------------------------

上述代码依然是利用TADODataSet控件从数据表中获取当前地图所包含的图层信息,

并将其保存在_environment变量的m_layerInfos数组中。 LoadFeatureControlTreeView函数的代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm ::LoadFeatureControlTreeView()

{

//处理地物控制树状列表 FeatureControlTreeView->Items->Clear();

TTreeNode* topNode = FeatureControlTreeView->Items->Add ( NULL, "北京市图层集");

topNode->ImageIndex = 0;

// 地名类型数据表

TADODataSet* ClassDataSet = new TADODataSet(this);

ClassDataSet->Connection = _environment->m_DataConnection; ClassDataSet->CommandText = "Select 大类 From 地名类型";

ClassDataSet->Open() ;

ClassDataSet->First() ;

//分4层

for(int i=0; i< ClassDataSet->RecordCount; i++) {

//加第1层

AnsiString Type = ClassDataSet->FieldByName("大类")->AsString;

Page 137: C++ Builder和MapObjects实现

129

第 5章 系统主界面的实现

TTreeNode* node = FeatureControlTreeView->Items->AddChild(topNode,

Type);

node->ImageIndex = 0;

//加第2层

TADODataSet* subTypeTb1 = new TADODataSet(this); subTypeTb1->Connection = _environment->m_DataConnection;

subTypeTb1->CommandText = "Select * From 地名中类型 where 大类='" +

ClassDataSet->FieldByName("大类")->AsString + "'"; subTypeTb1->Open() ;

int* aIndex = new int[_environment->m_nLayerNum];

subTypeTb1->First() ;

for(int j=0; j<subTypeTb1->RecordCount; j++) {

String szSubType = subTypeTb1->FieldByName("中类")->AsString;

TTreeNode* node2 = FeatureControlTreeView->Items->AddChild (

node, szSubType);

node2->ImageIndex = 0;

//加第3层

int nCount = GetNodeArray2(aIndex,node->Text,szSubType); if (nCount > 0)

{

for(int k=0; k<nCount; k++) {

if (_environment->m_layerInfos[aIndex[k]].bCanControl)

{ TTreeNode* node3 = FeatureControlTreeView->

Items->AddChild(node2,

_environment->m_layerInfos[aIndex[k]].szName);

if (_environment->m_layerInfos[aIndex[k]].bVisible)

node3->ImageIndex = 0; else

node3->ImageIndex = 1;

} }

}

subTypeTb1->Next() ;

}

subTypeTb1->First ();

Page 138: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

130

//关闭子类数据表

subTypeTb1->Close ();

delete subTypeTb1; subTypeTb1 = NULL;

ClassDataSet->Next() ; }

ClassDataSet->First (); ClassDataSet->Close();

//展开所有的结点 topNode->Expand(true);

}

//---------------------------------------------------------------------

上述LoadFeatureControlTreeView函数又调用了GetNodeArray2函数,该函数的实现代码

如下:

//---------------------------------------------------------------------

int __fastcall TMainForm::GetNodeArray2(int* aIndex, String szType, String

szSubType) {

int nCount = 0;

for (int i = 0; i < _environment->m_nLayerNum; i ++)

{

if (_environment->m_layerInfos[i].szType == szType && _environment->m_layerInfos[i].szSubType == szSubType)

{

aIndex[nCount] = i; nCount ++;

}

}

return nCount;

} //---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数的尾部加入如下代码,调用上述两个函数:

//---------------------------------------------------------------------

// 装载图层集信息 LoadLayerInfos(_environment->m_nCurrMapIndex);

// 以树状列表形式显示地物显示控制信息

LoadFeatureControlTreeView(); //---------------------------------------------------------------------

Page 139: C++ Builder和MapObjects实现

131

第 5章 系统主界面的实现

编译并运行程序,系统运行界面如图5.22所示。“地图”页面中的“地物控制”选项

卡按照地物类型列出了所有地物分类信息。

图 5.22 实现“地物控制”树状列表后的系统运行界面

至此,“地图”页面的基本功能实现完毕。本章的5.10节讨论了缩略图(鹰眼)窗口

的具体实现,关于“查询”页面的具体实现细节,本书放在第6章专门讲述。 实现输入输出工作区功能后,下一步要实现的当然就是在地图显示窗口中显示地图。

5.7 图层的加入与控制

5.7.1 在地图中加入图层

使用MapObjects向地图中添加数据有3种方式:

(1)通过建立DataConnection、GeoDataset和图层对象及向地图对象的层集中添加数

据来加入矢量地图数据。 (2)通过建立影像层对象及向地图对象的层集中映射层来显示影像地图数据以作为背

景。 (3)通过使用动态跟踪层对象和添加GeoEvent对象实现动态跟踪。

地图包含许多层,也就是层集(Layers)。顾名思义,层集是图层的集合。在MapObjects中,图层分矢量图层(MapLayer)和影像层(ImageLayer)。通过地图控件的层集属性和

方法,便可以加入、删除图层,访问地图中的各个图层。 地图的 上方是动态跟踪层, 下方为地图控件,中间为层集。层集中矢量图层对象

和影像层对象可以按任意顺序排放,但通常影像层显示在 底层以作为背景。 下面的图

层 先绘制, 上面的图层 后绘制。在应用程序中,合理地安排好每个图层在层集中的

Page 140: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

132

顺序是至关重要的。比如说,有两个图层,一层为点,一层为区域(多边形图层),应该

将点层放在区域层的上方,否则区域会将点覆盖。矢量图层有3种类型,分别是点图层、线

图层和多边形图层。 矢量图层对象的shapeType属性表明该图层属于哪种图层。如果某图层的shapeType属

性为moShapeTypePolygon,那么表示该图层是一个多边形图层,如果为moShapeTypeLine,表示为线图层,如果为moShapeTypePoint,表示为点图层。

地图控件是显示图层的平台,它有两个重要的属性——层集和动态跟踪层对象。层集

包含矢量图层对象和影像层对象,它们的顺序决定了在地图控件中的相互覆盖关系。矢量

图层对象代表矢量数据,影像层对象代表栅格数据,动态跟踪层对象显示实时数据。 地图控件有一个名为Layers的属性,它代表当前地图图层集合,它本身也是一个对象,

也包含一系列的属性和方法。通过图层对象的Add方法,便可以向地图中加入一个新图层。 每一个矢量图层对应一个DataSet和Recordset,即一个数据库和一个记录集对象。不同

的图层可以来自不同的数据类型。MapObjects的矢量图层主要来自3种数据类型:Shape文件、Arc/Info的Coverage和SDE的Coverage,也可以来自CAD格式文件。

本系统使用的是Shape文件。添加Shape文件的步骤如下:

(1)创建一个新的DataConnection对象。 (2)设置数据库属性为包含Shape文件的文件夹。 (3)调出一个新的矢量图层对象。 (4)在DataConnection上使用FileGeoDataset方法,用Shape文件名设置矢量图层对象

的GeoDataset属性。 (5)向层集里加入图层。

在地图显示窗口中加入与显示图层的函数是LoadLayers,在鹰眼窗口中加入图层的函

数是LoadLayersForEagleEye。 LoadLayers函数的实现代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::LoadLayers()

{ Map->Layers->Clear() ;

Map->ScrollBars = false;

for (int m = 0; m < 3; m ++)

{

for (int i = 0; i < _environment->m_nLayerNum; i ++) {

IMoMapLayerPtr layer = (IDispatch*)

CreateOleObject("MapObjects2.MapLayer");

wchar_t* dest = WideString

(_environment->m_layerInfos[i].szFileName).Detach() ; layer->GeoDataset = _environment->m_db->FindGeoDataset(dest);

Page 141: C++ Builder和MapObjects实现

133

第 5章 系统主界面的实现

if (layer->Valid)

{

switch (m) {

case 0:

if (layer->shapeType != moShapeTypePolygon) continue;

break;

case 1: if (layer->shapeType != moShapeTypeLine)

continue;

break; case 2:

if (layer->shapeType != moShapeTypePoint)

continue; break;

default:

continue; }

_environment->m_layerInfos[i].layer = layer; Map->Layers->Add(layer);

//设置图层是否显示 if (!_environment->m_layerInfos[i].bVisible)

_environment->m_layerInfos[i].layer->Visible = false;

//设置注记

if (_environment->m_layerInfos[i].bLable &&

(_environment->CalcScale (Map) < _environment->m_layerInfos[i].dScale))

{

IMoLabelPlacerPtr myRD = (IDispatch*) CreateOleObject("MapObjects2.LabelPlacer");

myRD->Field = WideString("名称").Detach();

myRD->DrawBackground = _environment-> m_layerInfos[i].bBackground;

myRD->AllowDuplicates = false;

myRD->MaskLabels = false;

if (_environment->m_layerInfos[i].layer->shapeType ==

moShapeTypeLine) myRD->PlaceAbove = true;

else

myRD->PlaceAbove = false; myRD->PlaceOn = true;

Page 142: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

134

TFont* fnt = new TFont();

fnt->Name = "楷体";

fnt->Size = _environment->m_layerInfos[i].nFontSize; myRD->DefaultSymbol->Font = (IFontDisp *)

((IDispatch *)FontToOleFont(fnt));;

_environment->m_layerInfos[i].layer->Renderer = myRD;

}

//设置符号

switch (_environment->m_layerInfos[i].layer->shapeType)

{ case moShapeTypePoint:

//点符号

if (_environment->m_layerInfos[i].nCharacterIndex >= 0 ) {

TFont* font = new TFont();

font->Name = _environment-> m_layerInfos[i].szFontName;

_environment->m_layerInfos[i].layer->Symbol->Font =

(IFontDisp *) ((IDispatch *)FontToOleFont(font));

_environment->m_layerInfos[i].layer->Symbol

->CharacterIndex = (short)_environment->m_layerInfos[i].nCharacterIndex;

_environment->m_layerInfos[i].layer->Symbol->Style = 4;

_environment->m_layerInfos[i].layer->Symbol ->SymbolType = moPointSymbol;

_environment->m_layerInfos[i].layer->Symbol->Size =

(short)_environment->m_layerInfos[i].nSymSize; }

else

{ _environment->m_layerInfos[i].layer->Symbol->Size =

(short)_environment->m_layerInfos[i].nSymSize;

} break;

case moShapeTypeLine:

//线符号 if (_environment->m_layerInfos[i].nCharacterIndex >= 0 &&

_environment->m_layerInfos[i].nCharacterIndex < 5)

{ _environment->m_layerInfos[i].layer->Symbol->Style =

(short)_environment->m_layerInfos[i].nCharacterIndex;

}

Page 143: C++ Builder和MapObjects实现

135

第 5章 系统主界面的实现

_environment->m_layerInfos[i].layer->Symbol->Size =

(short)_environment->m_layerInfos[i].nSymSize;

break; case moShapeTypePolygon:

_environment->m_layerInfos[i].layer->Symbol->Outline = false;

if (_environment->m_layerInfos[i].nCharacterIndex >= 0 &&

_environment->m_layerInfos[i].nCharacterIndex <= 10)

{ _environment->m_layerInfos[i].layer->Symbol->Style =

(short)_environment->m_layerInfos[i].nCharacterIndex;

} else if (100 ==

_environment->m_layerInfos[i].nCharacterIndex)

{ SetPolygonLayerColor(

_environment->m_layerInfos[i].layer, "名称","颜色",false);

} break;

}

//设置颜色

if (_environment->m_layerInfos[i].nSymColor != 9999)

{ _environment->m_layerInfos[i].layer->Symbol->Color

=_environment->m_layerInfos[i].nSymColor;

} }

}

} }

//---------------------------------------------------------------------

LoadLayersForEagleEye函数的代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm::LoadLayersForEagleEye()

{

EyeMap->Layers->Clear();

for (int i = 0; i < _environment->m_nLayerNum; i ++)

{ IMoMapLayerPtr layer = (IDispatch*)

CreateOleObject("MapObjects2.MapLayer");

if (_environment->m_layerInfos[i].szType == "北京纵览")

{

Page 144: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

136

wchar_t* dest = WideString

(_environment->m_layerInfos[i].szFileName).Detach();

layer->GeoDataset = _environment->m_db->FindGeoDataset(dest);

if (layer->Valid )

{ if (layer->shapeType == moShapeTypePoint)

continue;

if (layer->shapeType == moShapeTypeLine)

continue;

EyeMap->Layers->Add(layer);

switch (_environment->m_layerInfos[i].layer->shapeType) {

case moShapeTypePoint:

//点符号 if (_environment->m_layerInfos[i].nCharacterIndex >= 0 )

{

TFont* font = new TFont(); font->Name = _environment->

m_layerInfos[i].szFontName;

layer->Symbol->Font = (IFontDisp *)

((IDispatch *)FontToOleFont(font));

layer->Symbol->CharacterIndex = (short)

_environment->m_layerInfos[i].nCharacterIndex;

layer->Symbol->Style = 4; layer->Symbol->SymbolType = moPointSymbol;

layer->Symbol->Size = (short)

_environment->m_layerInfos[i].nSymSize; }

else

{ layer->Symbol->Size = (short)

_environment->m_layerInfos[i].nSymSize;

} break;

case moShapeTypeLine:

//线符号 if (_environment->m_layerInfos[i].nCharacterIndex >= 0 &&

_environment->m_layerInfos[i].nCharacterIndex < 5)

{ layer->Symbol->Style = (short)

Page 145: C++ Builder和MapObjects实现

137

第 5章 系统主界面的实现

_environment->m_layerInfos[i].nCharacterIndex;

}

layer->Symbol->Size = (short)

_environment->m_layerInfos[i].nSymSize;

break; case moShapeTypePolygon:

layer->Symbol->Outline = false;

if (_environment->m_layerInfos[i].nCharacterIndex >= 0 &&

_environment->m_layerInfos[i].nCharacterIndex <= 10)

{ layer->Symbol->Style = (short)

_environment->m_layerInfos[i].nCharacterIndex;

} else if (100 ==

_environment->m_layerInfos[i].nCharacterIndex)

{ SetPolygonLayerColor(layer, "名称","颜色",false);

}

break; }

//设置颜色 if (_environment->m_layerInfos[i].nSymColor != 9999)

{

layer->Symbol->Color = _environment-> m_layerInfos[i].nSymColor;

}

} }

}

EyeMap->Extent = EyeMap->FullExtent; }

//---------------------------------------------------------------------

在LoadLayers与LoadLayersForEagleEye函数中都调用了一个SetPolygonLayerColor函数,该函数用于设置多边形图层的颜色,其实现代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm::SetPolygonLayerColor(IMoMapLayerPtr ly, String

szField1, String szField2, bool bBorder)

{ IMoStringsPtr strings =

(IDispatch*)CreateOleObject("MapObjects2.Strings");

IMoStringsPtr strColors = (IDispatch*)CreateOleObject("MapObjects2.Strings");

Page 146: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

138

IMoRecordsetPtr rst = ly->Records;

rst->MoveFirst(); while (! bool(rst->EOF ))

{

String strValue = rst->Fields->Item(szField1)->ValueAsString; strings->Add(WideString(strValue).Detach());

strValue = rst->Fields->Item(szField2)->ValueAsString;

strColors->Add(WideString(strValue).Detach() ); rst->MoveNext();

}

// 利用ValueMapRenderer对象符号化多边形图层中的每个多边形

IMoValueMapRendererPtr rd = (IDispatch*)

CreateOleObject("MapObjects2.ValueMapRenderer"); ly->Renderer = rd;

rd->Field = WideString(szField1).Detach() ;

rd->ValueCount = strings->Count;

for (int i = 0; i < strings->Count ; i ++)

{ rd->set_Value((short)i, strings->Item(i));

IMoSymbolPtr sym = rd->get_Symbol((short)i);

int nColor = StrToInt(strColors->Item(i)); sym->Color = nColor;

sym->Outline = bBorder;

} }

//---------------------------------------------------------------------

LoadLayers函数还调用了TEnvironment类的CalcScale函数计算地图的比例尺,只有当地

图的比例尺小于某个图层的比例尺时才对该图层进行注记。 在TEnvironment类中加入CalcScale函数,代码如下:

//---------------------------------------------------------------------

double TEnvironment::CalcScale(TMap* map)

{ HANDLE hWnd =(void *)map->Handle;

HDC hDC = GetDC(hWnd);

double dpix = GetDeviceCaps(hDC, LOGPIXELSX);

MPoint* pts = new MPoint[2];

pts[0].x = map->Extent->Left; pts[0].y = map->Extent->Top;

pts[1].x = map->Extent->Right;

pts[1].y = map->Extent->Top;

Page 147: C++ Builder和MapObjects实现

139

第 5章 系统主界面的实现

double dLen1 = CalcLenght(pts,2);

double dLen2 = map->Width / dpix * 2.54 /100;

delete pts;

pts = NULL;

return dLen1 / dLen2;

} //---------------------------------------------------------------------

double TEnvironment::CalcScale(TMap* map, IMoRectanglePtr extent)

{ HANDLE hWnd =(void *)map->Handle;

HDC hDC = GetDC(hWnd);

double dpix = GetDeviceCaps(hDC, LOGPIXELSX);

MPoint* pts = new MPoint[2];

pts[0].x = extent->Left; pts[0].y = extent->Top;

pts[1].x = extent->Right;

pts[1].y = extent->Top;

double dLen1 = CalcLength(pts,2);

double dLen2 = map->Width / dpix * 2.54 /100;

return dLen1 / dLen2;

} //---------------------------------------------------------------------

CalcScale函数调用了TEnvironment类的CalcLength函数,该函数的代码如下:

//---------------------------------------------------------------------

double TEnvironment::CalcLenght(MPoint* pt,int nSize) {

double dLength = 0;

double x1=0,x2=0,y1=0,y2=0; int nCenterL = ((int)(pt[0].x)/6+1)*6-3;

for(int i=0;i<nSize-1;i++) {

CalGuassFromLB(pt[i].x, pt[i].y, &x1, &y1, nCenterL);

CalGuassFromLB(pt[i+1].x, pt[i+1].y, &x2, &y2, nCenterL); dLength += sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

}

return dLength;

}

Page 148: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

140

//---------------------------------------------------------------------

CalcLenght函数又调用了TEnvironment类的CalGuassFromLB函数,该函数的代码如下:

//---------------------------------------------------------------------

void TEnvironment::CalGuassToLB(double dX, double dY, double* dLongitude, double* dLatitude)

{

double L0; int nZoonNum;

nZoonNum = (int)(dY/(1.0E+6)); L0 = nZoonNum * 6-3;

dY = dY - nZoonNum*1.0E+6; SubGussFs(dX, dY-500000,L0, dLatitude, dLongitude);

*dLongitude = *dLongitude + nZoonNum * 6 - 3 ;

} //---------------------------------------------------------------------

void TEnvironment::CalGuassFromLB(double dLongitude, double dLatitude,

double* dX, double* dY, long nCenterL) {

int CenterL = (int)nCenterL;

SubGussFs(dX, dY, dLatitude, dLongitude, CenterL);

nCenterL = (long)CenterL;

} //---------------------------------------------------------------------

CalGuassFromLB函数又调用了TEnvironment类的SubGussFs函数,该函数的代码如下:

//---------------------------------------------------------------------

void TEnvironment::SubGussFs(double* X, double* Y,double B,double L,int nCenterLongi)

{

//高斯投影分带 int nzonenum;

if(nCenterLongi==0)

{ nzonenum = (int)L/6+1;

nCenterLongi = nzonenum*6-3;

} else

nzonenum = (int)nCenterLongi/6+1;

//以弧度为单位的经纬度数值

double rB = B/180*3.1415926;

double rL = (L-nCenterLongi)/180*3.1415926; //同时计算了中央经线

Page 149: C++ Builder和MapObjects实现

141

第 5章 系统主界面的实现

//1980坐标系参数

const double a = 6378245.00; //长轴

const double b = 6356863.50; //短轴 double sqre1 = (a*a-b*b)/(a*a); //第一偏心率平方

//B:纬度

//L:经度 //子午圈曲率半径

double sinb = sin(rB);

double cosb = cos(rB); double M = a*(1-sqre1)/(1-sqre1*sinb*sinb)/sqrt(1-sqre1*sinb*sinb);

//卯酉圈曲率半径

double N = a/sqrt(1-sqre1*sinb*sinb); double sqrita = N/M-1;

//该纬度点到赤道的子午线弧长 double s = a * ( 1 - sqre1 ) * ( 1.00505117739 * rB - 0.00506237764/

2 * sin(2*rB) + 0.0000106245 / 4 * sin(4*rB) - 0.00000002081/6 *

sin(6*rB));

double tanb = tan(rB);

*X = s + rL*rL*N/2*sinb*cosb + rL*rL*rL*rL*N/24*sinb*cosb*cosb*cosb * (5-tanb*tanb + 9*sqrita*sqrita +4*sqrita);

*Y = rL*N*cosb + rL*rL*rL*N/6*cosb*cosb*cosb*(1-tanb*tanb+

sqrita)+rL*rL*rL * rL*rL*N/120*cosb*cosb*cosb*cosb*cosb* (5-18*tanb*tanb+tanb*tanb*tanb*tanb);

*Y = *Y + 500000 + nzonenum * 1.0e+6;

} //---------------------------------------------------------------------

void TEnvironment::SubGussFs(double X,double Y,double L0, double* B, double*

L) {

double p=57.29577951472;

const double a=6.378245000e+06; const double e2=0.00669342162297;

const double e12=0.00673852541468;

const double c0=0.157046064172e-06; const double c1=0.005051773759;

const double c2=0.000029837302;

const double c3=0.000000238189;

double bf0 = c0*X;

double bf0c = cos(bf0); double bf0s = sin(bf0);

double bf = bf0 + bf0c*(c1*bf0s - c2*pow(bf0s,3) + c3* pow(bf0s,5));

double bt = tan(bf); double bc = cos(bf);

Page 150: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

142

double bs = sin(bf);

double bi = e12 * pow(bc,2);

double v2 = 1.0e+0 +bi; double bn = a/sqrt(1.0-e2* pow(bs,2));

double yn=Y/bn;

// 计算纬度

double b1 = -v2*bt*pow(yn,2)/2.0;

double b2 = -(5.0+3.0*pow(bt,2) + bi-9.0*bi* pow(bt,2)) *b1* pow(yn,2) /12.0;

double b3 = (61.0+90.0* pow(bt,2) + 45.0* pow(bt,4))*b1* pow(yn,4)/360.0;

*B = bf + b1 + b2 + b3; *B = *B * p;

// 计算经度 double l1=yn/bc;

double l2=-(1.0+2.0* pow(bt,2)+bi)*l1*pow(yn,2)/6.0;

double l3 = ( 5.0 + 28.0 * pow(bt,2) + 24.0 * pow(bt,4) + 6.0 * bi + 8.0 * bi * pow(bt,2)) * l1 * pow(yn,4)/120.0;

*L = l1 + l2 + l3;

*L = *L * p;

*L += L0;

if(*L > 360.0) *L -= 360.0;

}

//---------------------------------------------------------------------

在LoadLayers函数中,我们首先调用CreateOleObject("MapObjects2.MapLayer")创建矢

量图层对象MapLayer,然后调用_environment变量的m_db(DataConnection类型)成员变量

的FindGeoDataset方法。 DataConnection对象用来连接装有Shape文件的文件夹或SDE数据库。要连接Shape文件

文件夹,需设置Dataset属性为具有该文件夹名称的字符串,并应用Connect方法进行连接。

连接SDE数据库,需设置Database(数据库),Password(密码),Server(服务器)和User(用户)属性,并应用Connect方法检查连接属性。如果连接错误,检查ConnectError属性,

对比ConnectionErrorCode,寻找错误原因。该属性的取值及其说明如表5.11所示。

表5.11 ConnectError属性的取值及其说明

常量 值 说明

moNoError 0 没有出错

moUnknownError 1 不知错误原因

moAccessDenied 2 不能存取

moInvalidUser 3 非法用户

moNetworkTimeout 4 网络超时

Page 151: C++ Builder和MapObjects实现

143

第 5章 系统主界面的实现

(续表)

常量 值 说明

moInvalidDatabase 5 非法数据库

moTasksExceeded 6 超越任务

moFileNotFound 7 没有发现文件

moInvalidDirectory 8 非法目录

moHostUnknown 9 主机未知

当进行连接时,就会形成一个GeoDatasets集合对象,该对象包含DataConnection对象中

的所有GeoDataset对象。需要使用DataConnection对象的FindGeoDataset方法,设置MapLayer对象的GeoDataset属性。

可使用AddGeoDataset方法生成一个新的Shape文件。当用这种方法生成一个Shape文件

时,也就是生成了一个TableDesc来设置新的Shape文件的生成。 GeoDataset对象表示从Shape文件或SDE层中得到的地图数据的一层。GeoDatasets集合

表示DataConnection里所有的GeoDataset,即文件夹里所有的Shape文件或SDE数据库里所有

的SDE层。每一个图层对应一个GeoDataset对象。 注意,MapObjects中GeoDataset属性是只写的。一旦将GeoDataset放置到MapLayer或其

他对象上作为一种属性,它便不能恢复或改写。 当通过DataConnection对象的FindGeoDataset方法把图层加到GeoDataset对象中时,以

下操作将自动执行:

· Extent属性被更新为地图的 大范围。 · Recordset属性被分配了一个Recordset对象。 · ShapeType属性将依据Shape文件类型被设成moPoint、moLine或moPolygon。 · Symbol设为默认,并随即赋予颜色。 · 如果FindGeoDataset方法成功,Valid属性将被设成True。 · Visible属性设成True。 · Renderer属性将设为Nothing,等待设定一个Renderer对象——ClassBreaksRenderer、

ValueMapRenderer、DotDensityRenderer或LableRenderer。 · MapLayer提供了一些强有力的方法以改变MapLayer的属性数据。

LoadLayers函数调用DataConnection对象的FindGeoDataset方法连接Shape文件后,调用

地图对象的Layers(图层集对象)成员变量的Add方法,将图层加入到地图中。 LoadLayers函数在地图中加入图层之后,利用LabelPlacer对象设置图层的注记。

LabelPlacer对象是表示一种地理特征的方法,它在地理特征上显示字符。LabelPlacer对象不

同于LabelRenderer对象,后者存在注记和定位的冲突,而LabelPlacer对象可以提供更多的

美术效果。Field属性用来标注字符所在的字段。Symbol属性指向显示字符的TextSymbol对象。属性SymbolCount表示LabelPlacer对象中TextSymbol对象的数目。PlaceAbove属性、

PlaceOn属性和PlaceBelow属性决定相关的地理特征和注记之间的相对位置。SymbolHeight和SymbolWidth规定点符号所占的空间,以便注记与字符不发生冲突。

Page 152: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

144

可以通过LabelPlacer对象的几个属性控制字符的显示形状。用DrawBackground属性控

制是否显示地理特征;利用BackgroundRenderer属性设置如何显示地理特征;用

AllowDuplicates属性控制是否重复注记;利用MaskLabels属性控制是否掩蔽注记。另外,

可以用MaskColor属性设置掩蔽的颜色。 ValueCount属性是决定在ValueField字段中有多少值显示的符号特性。属性Value对特

定的值产生特定的字符特性,与ValueMapRenderer中相似。另外,属性UseDefault表示

LabelPlacer对象对Value中未列出的值是否利用DefaultSymbol属性中规定的符号显示。 LoadLayers函数在设置了图层的注记之后,根据图层类型设置图层的符号。 矢量图层(MapLayer)对象的Symbol(符号)属性决定一个图层或几何对象以何种样

式画出。可以读写符号对象的所有属性。然而有一些属性是否可用取决于符号的Type(类

型)属性是否被设置为moPiontSymbol、moLineSymbol、moFillSymbol中的一个常量。 当创建一个新的矢量图层对象时,MapObjects会自动先给该对象的Symbol属性的

SymbolType 属性设置 一默认值 ,对于 GeoDatasets 中的点状地 物,默认 设置为

moPiontSymbol,对于GeoDatasets中的线状地物默认设置为moLineSymbol,而对于

GeoDatasets中的多边形地物默认设置为moFillSymbol。 可直接设置符号对象的SymbolType属性而不必考虑符号的实现。例如在图层中,不必

将SymbolType设置为 moPiontSymbol,而直接符号化多边形中心。 以下5个对象均与符号对象有关:

(1)层对象有Symbol属性,用统一的符号来画一个图层上的所有属性,这是符号对象

的 一般用法。 (2)地图控件的DrawShape方法使用符号对象来画几何对象。 (3)ClassBreaksRenderer对象用一组符号来画各类地物。 (4)ValueMapRenderer对象是把一组符号对象赋给几个具有特定属性值的地物。 (5)TrackingLayer对象是用一组符号对象来画GeoEvent对象。

符号对象由属性组成,它表示如何显示地理特征或形状。按照表示的地理特征或形状

的类型,用符号对象的SymbolType和Style来区分。例如,如果地理特征是线,我们可以设

置线的特征为实线、虚线或点线。对于点,可以设置符号的大小。类似地,我们可以利用

Color属性,设置符号的不同颜色。如果一个符号指向一个Font对象,可以利用Font对象的

CharacterIndex属性使用特殊的符号。当使用点符号时,可以利用Rotation属性设置符号的角

度。使用多边形符号时,可以利用OutLineColor属性设置多边形边界的颜色。 符号对象的CharacterIndex属性表示符号在字符集中的字符码。利用符号对象的Font属

性可以设定字符集。 符号对象的Color属性表示符号对象的颜色。MapObjects 为方便使用而提供了23个颜

色常量,便于设置Visual Basic或C++ Builder等应用程序中没有定义的颜色。这些颜色常量

如表5.12所示。

Page 153: C++ Builder和MapObjects实现

145

第 5章 系统主界面的实现

表5.12 颜色常量

常量 说明 常量 说明

moBlack 黑 moYellow 黄

moRed 红 moLimeGreen 灰绿

moGreen 绿 moTeal 浅绿

moBlue 蓝 moDarkGreen 墨绿 moMagenta 紫红 moMaroon 紫酱 moCyan 青蓝 moPurPle 紫 moWhite 白 moOrange 橙 moLightGray 浅灰 moKhaki 土黄 moDarkGray 深灰 moOlive 橄榄绿 moGray 灰 moBroun 褐 moPaleYellow 暗黄 moNavy 天蓝

moLightYellow 浅黄

符号对象的OutLine属性只对区域(多边形或矩形)的符号显示有效,它表示是否显示

对象的边界。 符号对象的Style属性表示符号形状,其值对于不同的符号类型(由SymbolType属性决

定)具有不同的含义。 符号对象的SymbolType属性表示使用符号对象的类型,它可以取moPointSymbol(点

符号)、moLineSymbol(线符号)和moFillSymbol(区域符号)3个值之一。 当符号类型为点符号时,符号对象的Style属性可取如表5.13所示的值。

表5.13 当符号类型为点符号时Style属性可取值及其说明

常量 值 说明 形状 moCircleMarker 0 圆形点 moSquareMarker 1 正方形点 moTriangleMarker 2 三角形点 moCrossMarker 3 十字形点 moTrueTypeMarker 4 True Type字体

当符号类型为线符号时,Style属性可取如表5.14所示的值。

表5.14 当符号类型为线符号时Style属性可取值及其说明

常量 值 说明 形状

moSolidLine 0 实线 moDashLine 1 虚线 moDotLine 2 点线 moDashDotLine 3 点划线 moDashDotDotLine 4 双点划线

当符号类型为区域时,Style属性可取如表5.15所示的值。

Page 154: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

146

表5.15 当符号类型为区域时Style属性可取值及其说明

常量 值 说明 形状

moSolidFill 0 实填充 moTransparentFill 1 空填充 moHorizontalFill 2 水平线填充 moVerticalFill 3 垂直线填充 moUpwardDiagonalFill 4 向上对角线填充 moDownwardDiagonalFill 5 向下对角线填充 moCrossFill 6 十字线填充 moDiagonalCrossFill 7 对角十字线填充 moLightGrayFill 8 浅灰色填充 moGrayFill 9 灰色填充 moDarkGrayFill 10 深灰色填充

尽管控制图层显示属性的 简单方法就是给一个图层设置符号属性,但是其局限之处

就是在一个图层上所有属性都用同一符号描绘。在这一点上,Renderer对象提供了更大的

灵活性,Renderer对象可用带参数的函数描绘属性。 MapObjects提供了如下4个Renderer对象:

(1)ValueMapRenderer对象,用于把一组符号赋给几个惟一值来绘制图层特征。 (2)ClassBreaksRenderer对象,把一组符号赋给一定值域的几个值来绘制图层特征。 (3)DotDensityRenderer对象,以不同密度的点状图块对应不同值的方式绘制多边形。 (4)LabelRenderer对象,从属性值的下一个元素中提取文本。

SetPolygonLayerColor函数利用了ValueMapRenderer对象,人们经常会用通过该对象惟

一属性值符号化地图。假设当前有一个土地使用图层,它由不同使用方式的土地组成:一

些是住宅区,一些是公园,一些是工业区,还有一些是耕地,等等。现在要做的工作是,

从记录字段中提取属性按土地使用类别给土地使用图层着色。ValueMapRenderer对象就是

用来生成这类图的。这种方法用属性字段的每一个值显示一种符号,以Symbol(I)设置具体

的符号特性,以SymbolType设置图形特征的类型(点、线和多边形),ValueCount设置提

供的符号个数,其他的值由UseDefault属性设置是否利用默认符号。默认符号由

DefaultSymbol属性设置。如果符号类型为点,可以利用RatationField和ScalingField属性设置

符号的角度和比例。在ValueMapRenderer对象的应用中,若事先不知道惟一的属性值是什

么,那么通常在MapObjects中收集惟一属性值的方法是:迭代设置记录或使用字符串数组。

如果一开始便设置字符串的Unique属性为真,那么仅仅当一个字符串原先不在字符串数组

中时,这个字符串才能用Add方法加入字符串数组。Count属性表示字符串集中惟一属性值

(即不重复属性值)的个数。值数组中没必要包括区域中每个惟一的属性值。例如,共有

20种土地使用类型,为了只给其中5种主要的土地使用类型着色,而只用一种符号描述剩下

15种土地使用类型,要做的工作是给值数组和符号数组设置5组值,设置UseDefault 属性为

真,则设置该图层的符号属性是剩下15 种土地使用类型的描述符号。再例如,在我国的县

Page 155: C++ Builder和MapObjects实现

147

第 5章 系统主界面的实现

级地图中,我们可以根据各县所在的省进行着色。 ValueMapRenderer对象的属性有:DefaultSymbol、Field、RotationField、Symbol、

ScalingField、SymbolType、Tag、UseDefault、Value和ValueCount等。DefaultSymbol属性

指向一个符号对象作为默认设置。如果UseDefault属性值为True,则对Value组中没有明确

设置符号特性的值,用该默认符号对象的特性显示地理特征。Field属性是一个字段名,该

字段的值用来确定其在符号组中的成员。语法为:

value = ValueMapRenderer.Field

或 ValueMapRenderer.Field = value

其中value是一个表示字段名的字符串。 RotationField属性是一个字段名,该字段的值用来确定地理特征显示时的旋转角度。这

个属性只对点特征有意义。语法为:

value = ValueMapRenderer.RotationField

ValueMapRenderer.RotationField = value

其中,value是一个表示字段名的字符串。 ScalingField属性是一个字段名,该字段的值用来确定地理特征显示时的放大系数。例

如字段值为2,则符号大小放大一倍。这个属性也只对点特征有意义。 Symbol属性包含一个符号对象组,它与Value值相对应,表示各个值对应的符号特性。 Value属性是一个值数组,它的每一个成员是指定字段中可能出现的字段值,这些值是

可以读写的。使用语法为:

value = ValueMapRenderer.Value(index)

ValueMapRenderer.Value(index) = value

其中,参数index是数组成员的索引号,value是数值表达式或字符串。 SetPolygonLayerColor函数还利用了记录集(Recordset)对象,该对象描述地理数据集

对象的记录集,或查询后产生的选择集的记录集。当生成一个记录集对象时,当前记录指

针定位在第一条记录上。可以通过记录集的成员方法MoveNext将当前记录指针移动到下一

条记录,用MovePrevious方法将当前记录指针移动到前一条记录,用MoveFirst方法将记录

指针定位到第一条记录。Count属性显示了当前记录集中包含多少条记录。EOF属性显示当

前记录指针是否在 后一条记录。该对象的Fields属性描述组成记录集的各个字段。 在窗体MainForm的OnCreate事件响应函数的尾部加入如下代码,用于调用LoadLayers

函数与LoadLayersForEagleEye函数,在地图显示窗口与鹰眼窗口中加入图层:

//---------------------------------------------------------------------

if (_environment->m_nCurrMapIndex >= 0 )

{

Page 156: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

148

// 在地图显示窗口中加入图层

LoadLayers();

// 在鹰眼窗口中加入图层 LoadLayersForEagleEye();

}

//---------------------------------------------------------------------

编译并运行系统后,立即显示北京市地图,如图5.23所示。从图中可以看到,由于地

图窗口中显示了太多的图层,以至什么都看不清楚。为了防止该情况的发生,可以利用图

层的显示比例尺来控制地图图层的显示,使得在一定比例尺内显示一定的图层。例如,对

于一些描述地物详细信息的图层,在小比例尺地图上显示该图层会妨碍其他图层的显示,

因此没有必要显示该图层。

图 5.23 显示所有图层后的地图

5.7.2 依据比例尺控制图层显示

要控制是否显示某一图层,只需要调用图层对象的set_Visible方法或设置图层对象的

Visible属性即可。 要实现依据比例尺来控制图层是否显示,首先要计算地图的比例尺,系统中用于计算

地图比例尺的函数是TEnvironment类的CalcScale函数。实现依据地图比例尺及每个图层的

显示比例,控制图层是否显示的是MainForm窗体的ReShowLayers成员函数,该函数的代码

如下:

//---------------------------------------------------------------------

//功能:根据每个图层的显示比例设置图层是否显示

void __fastcall TMainForm::ReShowLayers()

Page 157: C++ Builder和MapObjects实现

149

第 5章 系统主界面的实现

{

double dScale = _environment->CalcScale(Map); //计算地图显示比例尺

for (int i = 0; i < _environment->m_nLayerNum; i ++)

{

if (!_environment->m_layerInfos[i].bVisible) continue;

if (dScale < _environment->m_layerInfos[i].dShowScale) {

_environment->m_layerInfos[i].layer->Visible = true;

} else

{

_environment->m_layerInfos[i].layer->Visible = false; }

}

} //---------------------------------------------------------------------

各个图层的Visible属性取决于当前地图的显示范围。如果地图的显示比例小于图层的

显示比例,那么该图层显示,否则不显示。 此外,还需要重新调整地物类型工具栏按钮的状态,实现函数是ReloadToolbar,其代

码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::ReloadToolbar() {

if( _environment->GetLayerVisible(MO_HOSPITAL ) &&

_environment->GetLayerVisible(MO_HOTEL ) && _environment->GetLayerVisible(MO_POST ) &&

_environment->GetLayerVisible(MO_SCHOOL ) &&

_environment->GetLayerVisible(MO_STATION ) && _environment->GetLayerVisible(MO_SHOP ) &&

_environment->GetLayerVisible(MO_TOUR ))

{ FeatureToolBar->Buttons[0]->Down = true;

}

else {

FeatureToolBar->Buttons[0]->Down = false;

}

//购物分布

if (_environment->GetLayerVisible(MO_SHOP)) FeatureToolBar->Buttons[1]->Down = true;

else

Page 158: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

150

FeatureToolBar->Buttons[1]->Down = false;

//旅游地分布 if (_environment->GetLayerVisible(MO_TOUR))

FeatureToolBar->Buttons[2]->Down = true;

else FeatureToolBar->Buttons[2]->Down = false;

//学校分布 if (_environment->GetLayerVisible(MO_SCHOOL))

FeatureToolBar->Buttons[3]->Down = true;

else FeatureToolBar->Buttons[3]->Down = false;

//医院分布 if (_environment->GetLayerVisible(MO_HOSPITAL))

FeatureToolBar->Buttons[4]->Down = true;

else FeatureToolBar->Buttons[4]->Down = false;

//宾馆分布 if (_environment->GetLayerVisible(MO_HOTEL))

FeatureToolBar->Buttons[5]->Down = true;

else FeatureToolBar->Buttons[5]->Down = false;

//银行分布 if (_environment->GetLayerVisible(MO_BANK ))

FeatureToolBar->Buttons[6]->Down = true;

else FeatureToolBar->Buttons[6]->Down = false;

//加油站分布 if (_environment->GetLayerVisible(MO_GAS ))

FeatureToolBar->Buttons[7]->Down = true;

else FeatureToolBar->Buttons[7]->Down = false;

//电影音乐厅分布 if (_environment->GetLayerVisible(MO_MOVIE ))

FeatureToolBar->Buttons[8]->Down = true;

else FeatureToolBar->Buttons[8]->Down = false;

//餐馆分布 if (_environment->GetLayerVisible(MO_RESTAURANT ))

Page 159: C++ Builder和MapObjects实现

151

第 5章 系统主界面的实现

FeatureToolBar->Buttons[9]->Down = true;

else

FeatureToolBar->Buttons[9]->Down = false;

//WC分布

if (_environment->GetLayerVisible(MO_WS )) FeatureToolBar->Buttons[10]->Down = true;

else

FeatureToolBar->Buttons[10]->Down = false;

//邮局分布

if (_environment->GetLayerVisible(MO_POST )) FeatureToolBar->Buttons[11]->Down = true;

else

FeatureToolBar->Buttons[11]->Down = false;

//图书馆分布

if (_environment->GetLayerVisible(MO_LIBRAY )) FeatureToolBar->Buttons[12]->Down = true;

else

FeatureToolBar->Buttons[12]->Down = false;

//车站分布

if (_environment->GetLayerVisible(MO_STATION)) FeatureToolBar->Buttons[13]->Down = true;

else

FeatureToolBar->Buttons[13]->Down = false; }

//---------------------------------------------------------------------

该函数调用了TEnvironment类的GetLayerVisible函数,该函数的代码如下:

//--------------------------------------------------------------------- // 功能:判断图层是否可见 bool TEnvironment::GetLayerVisible(int disp) { bool bVisible = true; int nType = 2; String szSubType = ""; switch ((MapDisp)disp) { case MO_ALL: nType = 1; break; case MO_HOSPITAL: szSubType = "医院"; break; case MO_SCHOOL:

Page 160: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

152

szSubType = "教育"; break; case MO_SHOP: szSubType = "零售"; break; case MO_TOUR: szSubType = "旅游"; break; case MO_GAS: break; case MO_HOTEL: szSubType = "住宿"; break; case MO_LIBRAY: szSubType = "图书馆"; nType = 3; break; case MO_MOVIE: szSubType = "影剧院、音乐厅"; nType = 3; break; case MO_POST: szSubType = "邮政"; break; case MO_RESTAURANT: szSubType = "餐饮"; break; case MO_WS: szSubType = "dddd"; break; case MO_STATION: szSubType = "站点"; nType = 4; break; } int nCount = 0; for (int i = 0; i < m_nLayerNum; i ++) { if (m_layerInfos[i].bCanControl) { switch (nType) { case 1: nCount ++; if (!m_layerInfos[i].bVisible) { return false; }

Page 161: C++ Builder和MapObjects实现

153

第 5章 系统主界面的实现

break; case 2: if (szSubType == m_layerInfos[i].szSubType) { nCount ++; if (!m_layerInfos[i].bVisible) { return false; } } break; case 3: if (szSubType == m_layerInfos[i].szSubType2) { nCount ++; if (!m_layerInfos[i].bVisible) { return false; } } break; case 4: if (szSubType == m_layerInfos[i].szSubType3) { nCount ++; if (!m_layerInfos[i].bVisible) { return false; } } break; } } } if (0 == nCount) bVisible = false; return bVisible; } //---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数的尾部加入上述两个函数的调用,实现依据

地图比例尺控制图层的显示,代码如下:

//---------------------------------------------------------------------

ReShowLayers();

ReloadToolbar(); Map->Extent = _environment->m_mapInfos[0].rect;

Map->MousePointer = moArrow;

Page 162: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

154

//---------------------------------------------------------------------

由于Map控件的Align属性设置为alClient,因此当窗体大小改变时,地图显示窗口的大

小也同时被改变,这时地图的显示比例尺也随之改变,同时需要处理的还有图层中注记、

符号的大小。创建窗体MainForm的OnResize事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::FormResize(TObject *Sender) {

ReShowLayers();

ReLabelLayers(); }

//---------------------------------------------------------------------

ReLabelLayers函数的相关实现参见5.8.2节。 编译并运行程序,便可以显示如图5.24所示的系统运行界面,此图中有许多图层没有

显示。

图 5.24 根据比例尺控制是否显示图层

5.8 通过“地图”页面控制地图显示

5.8.1 控制显示的地物类型

系统要求通过人机交互来分类控制地物的显示。系统把地物分为北京纵览、党政机关

与民间组织、交通运输、旅游住宿娱乐、商业服务业、体育卫生、文教科技、邮政电信8大类,下面又分中类和小类。

Page 163: C++ Builder和MapObjects实现

155

第 5章 系统主界面的实现

创建FeatureControlTreeView控件的OnClick事件响应函数,在其中加入如下代码,用于

实现当用户单击某结点时,切换该类地物的显示状态:

//---------------------------------------------------------------------

void __fastcall TMainForm::FeatureControlTreeViewClick(TObject *Sender)

{ int Level = GetNodeLevel(FeatureControlTreeView->Selected);

if (FeatureControlTreeView->Selected->ImageIndex == 1) {

// 如果原来不可显示,则设置为可显示

FeatureControlTreeView->Selected->ImageIndex = 0; FeatureControlTreeView->Selected->SelectedIndex = 0;

switch (Level)

{ case 0:

for(int i=0; i<FeatureControlTreeView->Selected->Count; i++)

{ TTreeNode* childNode = FeatureControlTreeView

->Selected->Item[i];

childNode->ImageIndex = 0; //如果子结点又有子结点,则循环设置

for(int j=0; j<childNode->Count; j++)

{ TTreeNode* childsChild = childNode->Item[j];

childsChild->ImageIndex = 0;

for(int k=0; k<childsChild->Count; k++) {

childsChild->Item[k]->ImageIndex = 0;

int nIndex = _environment-> GetLayerIndexByName(childsChild->Item[k]->Text);

if (nIndex >=0)

{ _environment->m_layerInfos[nIndex].bVisible

= true;

//判断是否在显示比例之内

double dScale = _environment->CalcScale(Map);

if ( dScale < _environment-> m_layerInfos[nIndex].dShowScale)

_environment->m_layerInfos[nIndex].layer

->Visible = true; }

}

} }

break;

Page 164: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

156

case 1:

for(int i=0; i<FeatureControlTreeView->Selected->Count; i++)

{ TTreeNode* childNode = FeatureControlTreeView->

Selected->Item[i];

childNode->ImageIndex = 0; //如果子结点又有子结点,则循环设置

for(int j=0; j<childNode->Count; j++)

{ childNode->Item[j]->ImageIndex = 0;

int nIndex = _environment->

GetLayerIndexByName(childNode->Item[j]->Text); if (nIndex >=0)

{

_environment->m_layerInfos[nIndex].bVisible =true;

//判断是否在显示比例之内 double dScale = _environment->CalcScale(Map);

if ( dScale < _environment->

m_layerInfos[nIndex].dShowScale) environment->m_layerInfos[nIndex].layer->Visible

= true;

} }

}

break; case 2:

//将所有子结点的显示图标设置为可显示状态

for(int i=0; i<FeatureControlTreeView->Selected->Count; i++) {

FeatureControlTreeView->Selected->Item[i]->ImageIndex = 0;

int nIndex = _environment->GetLayerIndexByName ( FeatureControlTreeView->Selected->Item[i]->Text);

if (nIndex >=0)

{ _environment->m_layerInfos[nIndex].bVisible = true;

//判断是否在显示比例之内 double dScale = _environment->CalcScale(Map);

if ( dScale < _environment->

m_layerInfos[nIndex].dShowScale) _environment->m_layerInfos[nIndex].layer->Visible =

true;

} }

Page 165: C++ Builder和MapObjects实现

157

第 5章 系统主界面的实现

break;

case 3:

{ int nIndex = _environment->GetLayerIndexByName

(FeatureControlTreeView->Selected->Text );

if (nIndex >=0) {

_environment->m_layerInfos[nIndex].bVisible = true;

//判断是否在显示比例之内

double dScale = _environment->CalcScale(Map);

if ( dScale < _environment-> m_layerInfos[nIndex].dShowScale)

_environment->m_layerInfos[nIndex].layer->Visible = true;

} }

break;

} }

else

{ //设置不可显示

FeatureControlTreeView->Selected->ImageIndex = 1;

FeatureControlTreeView->Selected->SelectedIndex = 1; switch (Level)

{

case 0: for(int i=0; i<FeatureControlTreeView->Selected->Count; i++)

{

TTreeNode* childNode = FeatureControlTreeView-> Selected->Item[i];

childNode->ImageIndex = 1;

//如果子结点又有子结点,则循环设置 for(int j=0; j<childNode->Count; j++)

{

TTreeNode* childsChild = childNode->Item[j]; childsChild->ImageIndex = 1;

for(int k=0; k<childsChild->Count; k++)

{ childsChild->Item[k]->ImageIndex = 1;

int nIndex = _environment->GetLayerIndexByName

(childsChild->Item[k]->Text); if (nIndex >=0)

{

_environment->m_layerInfos[nIndex].layer-> Visible = false;

Page 166: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

158

_environment->m_layerInfos[nIndex].bVisible =

false;

} }

}

} case 1:

for(int i=0; i<FeatureControlTreeView->Selected->Count; i++)

{ TTreeNode* childNode = FeatureControlTreeView->

Selected->Item[i];

childNode->ImageIndex = 1; //如果子结点又有子结点,则循环设置

for(int j=0; j<childNode->Count; j++)

{ childNode->Item[j]->ImageIndex = 1;

int nIndex = _environment->GetLayerIndexByName

(childNode->Item[j]->Text); if (nIndex >=0)

{

_environment->m_layerInfos[nIndex].layer->Visible = false;

_environment->m_layerInfos[nIndex].bVisible = false;

} }

}

case 2: //将所有子结点的显示图标设置为不可显示状态

for(int i=0; i<FeatureControlTreeView->Selected->Count; i++)

{ FeatureControlTreeView->Selected->Item[i]->ImageIndex = 1;

int nIndex = _environment->GetLayerIndexByName

(FeatureControlTreeView->Selected->Item[i]->Text); if (nIndex >=0)

{

_environment->m_layerInfos[nIndex].layer->Visible = false; _environment->m_layerInfos[nIndex].bVisible = false;

}

} break;

case 3:

{ int nIndex = _environment->GetLayerIndexByName

(FeatureControlTreeView->Selected->Text);

if (nIndex >=0) {

Page 167: C++ Builder和MapObjects实现

159

第 5章 系统主界面的实现

_environment->m_layerInfos[nIndex].layer->Visible = false;

_environment->m_layerInfos[nIndex].bVisible = false;

} }

break;

} }

_environment->m_drawLine = NULL;

ReloadToolbar();

Map->Extent = Map->Extent; }

//---------------------------------------------------------------------

上述函数中调用了GetNodeLevel函数,该函数用于判断某结点在树状列表中所处的级

别,实现代码如下:

//--------------------------------------------------------------------- int __fastcall TMainForm::GetNodeLevel(TTreeNode* e)

{

int nLevel = 0;

while (e->Parent != NULL)

{ e = e->Parent;

nLevel ++;

}

return nLevel;

} //---------------------------------------------------------------------

FeatureControlTreeViewClick函数中还调用了TEnvironment类的GetLayerIndexByName函数,该函数依据图层名称得到该图层在地图中对应的索引位置,其代码如下:

//---------------------------------------------------------------------

int TEnvironment::GetLayerIndexByName(String szName) {

int nIndex = -1;

for (int i = 0; i < m_nLayerNum; i ++)

{

if (szName == m_layerInfos[i].szName) {

nIndex = i;

break; }

Page 168: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

160

}

return nIndex; }

//---------------------------------------------------------------------

另外一个分类控制地物显示的入口是地物类型工具栏按钮。 创建AllFeature控件的OnClick事件响应函数,在其中加入如下代码,用于控制是否显

示所有地物:

//--------------------------------------------------------------------- void __fastcall TMainForm::AllFeatureClick(TObject *Sender)

{

double dScale = _environment->CalcScale(Map); _environment->SetLayerVisible(MO_BANK, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_GAS, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_HOSPITAL, AllFeature->Down, dScale); _environment->SetLayerVisible(MO_HOTEL, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_LIBRAY, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_MOVIE, AllFeature->Down, dScale); _environment->SetLayerVisible(MO_POST, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_RESTAURANT,AllFeature->

Down,dScale); _environment->SetLayerVisible(MO_SCHOOL, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_SHOP, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_STATION, AllFeature->Down, dScale); _environment->SetLayerVisible(MO_TOUR, AllFeature->Down, dScale);

_environment->SetLayerVisible(MO_WS, AllFeature->Down, dScale);

Map->Extent = Map->Extent; ReloadToolbar();

ReloadFeatureControlTreeView();

} //---------------------------------------------------------------------

上述事件响应函数中调用了TEnvironment类的SetLayerVisible函数,该函数用于设置图

层是否可见,其代码如下:

//--------------------------------------------------------------------- void TEnvironment::SetLayerVisible(int disp, bool bVisible, double dScale) { int nType = 2; String szSubType = ""; switch (disp) { case MO_ALL: nType = 1; break;

Page 169: C++ Builder和MapObjects实现

161

第 5章 系统主界面的实现

case MO_HOSPITAL: szSubType = "医院"; break; case MO_SCHOOL: szSubType = "教育"; break; case MO_SHOP: szSubType = "零售"; break; case MO_TOUR: szSubType = "旅游"; break; case MO_GAS: break; case MO_HOTEL: szSubType = "住宿"; break; case MO_LIBRAY: szSubType = "图书馆"; nType = 3; break; case MO_MOVIE: szSubType = "影剧院、音乐厅"; nType = 3; break; case MO_POST: szSubType = "邮政"; break; case MO_RESTAURANT: szSubType = "餐饮"; break; case MO_WS: szSubType = "厕所"; break; case MO_STATION: szSubType = "站点"; nType = 4; break; } for (int i = 0; i < m_nLayerNum; i ++) { if (m_layerInfos[i].bCanControl) { switch (nType) { case 1: if (!bVisible) { m_layerInfos[i].bVisible = bVisible;

Page 170: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

162

m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } else { m_layerInfos[i].bVisible = bVisible; if (m_layerInfos[i].dScale > dScale) m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } break; case 2: if (szSubType == m_layerInfos[i].szSubType) { if (!bVisible) { m_layerInfos[i].bVisible = bVisible; m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } else { m_layerInfos[i].bVisible = bVisible; if (m_layerInfos[i].dScale > dScale) m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } } break; case 3: if (szSubType == m_layerInfos[i].szSubType2) { if (!bVisible) { m_layerInfos[i].bVisible = bVisible; m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } else { m_layerInfos[i].bVisible = bVisible; if (m_layerInfos[i].dScale > dScale) m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; }

Page 171: C++ Builder和MapObjects实现

163

第 5章 系统主界面的实现

} break; case 4: if (szSubType == m_layerInfos[i].szSubType3) { if (!bVisible) { m_layerInfos[i].bVisible = bVisible; m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } else { m_layerInfos[i].bVisible = bVisible; if (m_layerInfos[i].dScale > dScale) m_layerInfos[i].layer->Visible = m_layerInfos[i].bVisible; } } break; } } } } //---------------------------------------------------------------------

事件响应函数AllFeatureClick中还调用了ReloadFeatureControlTreeView函数,该函数在

改变地物显示类型后用于更新“地物控制”树状列表,使其与对应工具栏按钮保持同步。

其代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm::ReloadFeatureControlTreeView() { _bTVChecked = false; for (int i = 0; i < FeatureControlTreeView->Items->Count; i ++) { TTreeNode* firstLayerNode =

FeatureControlTreeView->Items->Item[i]; int index =

_environment->GetLayerIndexByName(firstLayerNode->Text); if (index >= 0) { if(_environment->m_layerInfos[index].bVisible) { firstLayerNode->ImageIndex = 0; } else

Page 172: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

164

{ firstLayerNode->ImageIndex = 1; } } } // 再循环处理倒数第二层结点的显示状态, // 如果某结点中所有子结点不可显示,那么则应设置该结点也为不可显示 for (int i = 0; i < FeatureControlTreeView->Items->Count; i ++) { TTreeNode* node = FeatureControlTreeView->Items->Item[i]; if(node->HasChildren) { bool visible = false; for(int j=0; j<node->Count; j++) { if(node->Item[j]->ImageIndex == 0) visible = true; } if(visible == false) node->ImageIndex = 1; else node->ImageIndex = 0; } } _bTVChecked = true; } //---------------------------------------------------------------------

创建ShopFeature控件的OnClick事件响应函数,该函数用于控制是否显示购物场所地

物,代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::ShopFeatureClick(TObject *Sender)

{ double dScale = _environment->CalcScale(Map);

_environment->SetLayerVisible(MO_SHOP, ShopFeature->Down, dScale);

Map->Extent = Map->Extent; ReloadFeatureControlTreeView();

}

//---------------------------------------------------------------------

创建TourFeature控件的OnClick事件响应函数,该函数用于在显示与不显示旅游地之间

切换,代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::TourFeatureClick(TObject *Sender)

{

Page 173: C++ Builder和MapObjects实现

165

第 5章 系统主界面的实现

double dScale = _environment->CalcScale(Map);

_environment->SetLayerVisible(MO_TOUR, TourFeature->Down, dScale);

Map->Extent = Map->Extent; ReloadFeatureControlTreeView();

}

//---------------------------------------------------------------------

以同样的方法控制其他11种地物类型的显示。由于代码相似,这里不再罗列。 编译并运行程序,便可通过“地图”页面的“地物控制”选项卡,或者单击地物类型

工具栏按钮来控制地图的显示,如图5.25所示。

图 5.25 通过地物类型控制地图显示

“地物控制”选项卡中的内容是只读的,用户不能修改。但是在C++ Builder中,如果

不对TreeView控件加入处理,用户可以修改结点的显示内容。为了防止用户修改结点内容,

需要创建FeatureControlTreeView控件的OnEditing事件响应函数,在该函数中将参数

AllowEdit设置为false,函数的代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::FeatureControlTreeViewEditing(TObject *Sender,

TTreeNode *Node, bool &AllowEdit) {

AllowEdit = false;

} //---------------------------------------------------------------------

5.8.2 控制地图显示区域

通过“地图”页面的“地图索引”树状列表可以控制当前地图的显示区域。

Page 174: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

166

创建MapIndexTreeView控件的OnDblClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

// 双击地图索引列表中区(县)名称的事件响应函数

void __fastcall TMainForm::MapIndexTreeViewDblClick(TObject *Sender) {

TTreeNode* node;

bool bIndex = false; String szIndex = "";

if (MapIndexTreeView->Selected->Parent == NULL) {

node = MapIndexTreeView->Selected;

} else

{

//将被选择结点的名称保存在szIndex变量中 szIndex = MapIndexTreeView->Selected->Text ;

node = MapIndexTreeView->Selected->Parent;

bIndex = true; }

String szMapName = node->Text; int nIndex = _environment->GetMapIndex(szMapName);

if ((nIndex != _environment->m_nCurrMapIndex) && (nIndex >= 0)) {

_environment->m_nCurrMapIndex = nIndex;

if (nIndex == 0)

_environment->m_szSDBPath = _environment->m_AppPath +

"\\电子地图\\"; else

_environment->m_szSDBPath = _environment->m_AppPath +

"\\电子地图\\";

LoadLayerInfos(_environment->m_nCurrMapIndex);

LoadFeatureControlTreeView();

LoadLayers();

LoadLayersForEagleEye(); }

if (bIndex) {

for (int i = 0; i < _environment->m_nIndexNum; i ++)

{

Page 175: C++ Builder和MapObjects实现

167

第 5章 系统主界面的实现

// 循环判断用户当前选择的是哪个区域

if (szIndex == _environment->m_indexInfos[i].szName)

{ Map->Extent = _environment->m_indexInfos[i].m_extent;

ReLabelLayers();

ReShowLayers(); }

}

} }

//---------------------------------------------------------------------

上述事件响应函数中调用了TEnvironment类的GetMapIndex函数,该函数用于根据地图

名称获取地图索引,实现代码如下:

//--------------------------------------------------------------------- int TEnvironment::GetMapIndex(String szMapName)

{

int nIndex = -1;

for (int i = 0; i < m_nMapNum; i ++)

{ if (szMapName == m_mapInfos[i].szName)

{

nIndex = i; break;

}

}

return nIndex;

} //---------------------------------------------------------------------

在事件响应函数MapIndexTreeViewDblClick中还调用了ReLabelLayers函数,用于重新

对地图中的图层设置注记、符号的大小,其实现代码如下:

//---------------------------------------------------------------------

//功能:缩放或切换显示区域时,重新设置注记、符号的大小 void __fastcall TMainForm::ReLabelLayers()

{

double dScale = _environment->CalcScale(Map); //地图比例尺

if (_environment->m_selectedSymbol) {

_environment->m_selectedSymbol->Size = ReCalcFontSize ( _environment->

m_selectedSymbol->Size, dScale); }

Page 176: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

168

//放大缩小地图时,循环设置图层的符号和注记

for (int i = 0; i < _environment->m_nLayerNum; i ++) {

//图层不显示,下一个循环

if (!_environment->m_layerInfos[i].bVisible) continue;

//重新设置注记 if (_environment->m_layerInfos[i].bLable &&

(_environment->CalcScale(Map) <

_environment->m_layerInfos[i].dScale)) {

IMoLabelPlacerPtr myRD =

_environment->m_layerInfos[i].layer->Renderer;

if(!myRD)

{ //设置注记

myRD =

(IDispatch*)CreateOleObject("MapObjects2.LabelPlacer"); myRD->Field = WideString("名称").Detach();

myRD->DrawBackground = _environment->

m_layerInfos[i].bBackground ; myRD->AllowDuplicates = true;

myRD->MaskLabels = false;

if (_environment->m_layerInfos[i].layer->shapeType ==

moShapeTypeLine)

myRD->PlaceAbove = true; else

myRD->PlaceAbove = false;

myRD->PlaceOn = true;

TFont* font = new TFont();

font->Name = "楷体"; font->Size = ReCalcFontSize ((short) _environment->

m_layerInfos[i].nFontSize,dScale);

myRD->DefaultSymbol->Font = (IFontDisp *)((IDispatch *) FontToOleFont(font));

_environment->m_layerInfos[i].layer->Renderer = myRD; }

else

{ WideString tempStr = WideString(myRD->Field);

Page 177: C++ Builder和MapObjects实现

169

第 5章 系统主界面的实现

if(tempStr != WideString(""))

{

TFont* font = new TFont(); font->Name = "楷体";

font->Size = ReCalcFontSize ((short) _environment->

m_layerInfos[i].nFontSize, dScale); myRD->DefaultSymbol->Font = (IFontDisp *)((IDispatch *)

FontToOleFont(font));

} }

}

else {

//显示比例尺小于注记显示比例尺,则不显示注记

if (_environment->m_layerInfos[i].bLable) {

IMoLabelPlacerPtr myRD = (IDispatch*)

CreateOleObject("MapObjects2.LabelPlacer");

if (myRD)

myRD->Field = WideString("").Detach(); }

}

//重新设置图层的符号

if (_environment->m_layerInfos[i].layer->Symbol)

{ if(_environment->m_layerInfos[i].layer->shapeType ==

moShapeTypePoint)

{ _environment->m_layerInfos[i].layer->Symbol->Size =

ReCalcFontSize

((short)_environment->m_layerInfos[i].nSymSize, dScale); }

}

}

//街道注记

if (_environment->m_layerRoad) {

IMoLabelPlacerPtr roadRD = (IDispatch*)

CreateOleObject("MapObjects2.LabelPlacer");

if (dScale < 100000)

{ roadRD->Field = WideString("名称").Detach();

Page 178: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

170

TFont* font = new TFont();

font->Name = "楷体";

font->Size = ReCalcFontSize(10,dScale); roadRD->DefaultSymbol->Font = (IFontDisp *)((IDispatch *)

FontToOleFont(font));

} else

{

roadRD->Field = WideString("").Detach(); }

}

} //---------------------------------------------------------------------

ReLabelLayers函数中还调用了ReCalcFontSize函数,该函数根据显示比例返回符号和注

记的大小,实现代码如下:

//---------------------------------------------------------------------

// 功能:根据显示比例返回符号和注记的大小。 // <param name="nSize">short,原始大小</param>

// <param name="dScale">double,当前显示比例</param>

// <returns>short,返回计算后的符号和注记的大小</returns> short __fastcall TMainForm::ReCalcFontSize(short nSize,double dScale)

{

if (dScale > 15000) return nSize;

if (dScale > 10000) return (short)(nSize * 1.0);

return (short)(nSize * 1.0); }

//---------------------------------------------------------------------

同样,需要防止用户修改MapIndexTreeView控件中结点的显示内容。创建该控件的

OnEditing事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::MapIndexTreeViewEditing(TObject *Sender,

TTreeNode *Node, bool &AllowEdit)

{ AllowEdit = false;

}

//---------------------------------------------------------------------

Page 179: C++ Builder和MapObjects实现

171

第 5章 系统主界面的实现

5.9 地图的放大、缩小、全图显示和漫游

利用Map控件的Extent属性便可以控制地图的放大、缩小和漫游。Extent表示当前地图

的显示范围,它是一个矩形(Rectangle)类型的属性。 利用Map控件的FullExtent和Extent两个属性便可以显示整个地图。FullExtent属性表示

所有图层的总的坐标值范围。 下面是一些实现上述功能需要使用的方法。

(1)CenterAt方法可将图幅移至以(X,Y)为中心的位置;Pan方法通过拖拉操作,将

图幅移动位置;TrackRectangle方法可用来从图上拖出一个矩形框来放大地图。 (2)Intersect方法可找到两矩形框的交叉位置;Union方法可找到两矩形框的接合处;

ScaleRectangle方法可增大或减小矩形的尺寸。

现在创建ZoomIn控件的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::ZoomInClick(TObject *Sender)

{ if(ZoomIn->Down == true)

{

//放大操作 _environment->m_MapOpr = MO_ZOOMIN;

Map->MousePointer = moZoomIn;

} else

{

//取消继续放大操作 Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

} }

//---------------------------------------------------------------------

上述代码首先判断ZoomIn控件是否处于按下状态,如果是,则设置_environment变量

的m_MapOpr属性为MO_ZOOMIN,并将地图显示窗口中的鼠标状态设置为放大镜状;如

果不是,则将地图显示窗口中的鼠标状态设置为正常状态,并将_environment变量的

m_MapOpr属性设置为MO_NULL。 创建ZoomOut控件的OnClick事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::ZoomOutClick(TObject *Sender)

{

if(ZoomOut->Down == true) {

Page 180: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

172

//缩小操作

_environment->m_MapOpr = MO_ZOOMOUT;

Map->MousePointer = moZoomOut; }

else

{ //取消继续缩小操作

Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL; }

}

//---------------------------------------------------------------------

创建Pan控件的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::PanClick(TObject *Sender)

{ if(Pan->Down == true)

{

//漫游 _environment->m_MapOpr = MO_PAN;

Map->MousePointer = moPan;

} else

{

Map->MousePointer = moArrow; _environment->m_MapOpr = MO_NULL;

}

} //---------------------------------------------------------------------

上述代码只是设置了将要操作的类型,而真正实现放大、缩小与漫游操作的是Map控件的OnMouseDown事件响应函数。在该函数中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::MapMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { switch (_environment->m_MapOpr) { case MO_ZOOMIN : //放大 { if (_environment->CalcScale(Map) < MAX_SCALE) return; IMoRectanglePtr rect; rect = Map->TrackRectangle();

Page 181: C++ Builder和MapObjects实现

173

第 5章 系统主界面的实现

double dScale = _environment->CalcScale(Map, rect); double dWidth = rect->Width; double dHeight = rect->Height; if(rect) { Map->Extent = rect; } else { if((dWidth < 0.00005) || ( dHeight < 0.00005) || (dScale < MAX_SCALE)) { IMoPointPtr pt = Map->ToMapPoint(X, Y ); IMoRectanglePtr r = Map->Extent; r->ScaleRectangle(0.6667); r->Offset(-(r->Center->X-pt->X),-(r->Center->Y-pt->Y )); Map->Extent = r; } } ReLabelLayers(); ReShowLayers(); EyeMap->Extent = EyeMap->Extent; break; } case MO_ZOOMOUT : //缩小 { IMoRectanglePtr rect; rect = Map->TrackRectangle(); if(rect) { if((rect->Width < 0.00005) || (rect->Height < 0.00005)) { rect = Map->Extent; rect->ScaleRectangle(1.5); } else { double dRate = Map->Extent->Width / rect->Width * 10; rect->ScaleRectangle(dRate); } } else { rect = Map->Extent; rect->ScaleRectangle(1.5);

Page 182: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

174

} Map->Extent = rect; EyeMap->Extent = EyeMap->Extent; ReLabelLayers(); ReShowLayers(); break; } case MO_ZOOMFULL: //全图显示 break; case MO_PAN : //漫游 Map->Pan(); EyeMap->Extent = EyeMap->Extent; break; } } //---------------------------------------------------------------------

在地图放大操作中,上面的代码利用了地图控件的TrackRectangle方法,该方法返回用

户在地图上画出的矩形对象,将该矩形对象赋给地图控件的Extent属性,从而实现放大地图。

Pan方法用于漫游地图。 实现全图显示功能很简单,只要将地图的Extent属性设置为FullExtent属性即可。创建

FullExtent控件的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::FullExtentClick(TObject *Sender)

{ //全图显示

Map->Extent = Map->FullExtent;

EyeMap->Extent = EyeMap->Extent; Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

FullExtent->Down = false;

ReLabelLayers();

ReShowLayers(); }

//---------------------------------------------------------------------

编译并运行程序,现在便可利用地图控制工具栏中的前4个按钮实现地图的放大、缩小、

全图显示与漫游操作了,如图5.26所示。

Page 183: C++ Builder和MapObjects实现

175

第 5章 系统主界面的实现

图 5.26 实现地图的放大、缩小、全图显示与漫游操作

5.10 其他辅助功能的实现

5.10.1 鹰眼功能的实现

鹰眼窗口按全图显示比例显示电子地图,即缩略图。缩略图上具有一个矩形框,代表

地图显示窗口的当前显示区域。当矩形框移动到用户指定的区域上,释放鼠标左键,地图

显示窗口里的电子地图也快速移动到相应的位置。 创建EyeMap控件的OnAfterLayerDraw事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::EyeMapAfterLayerDraw(TObject *Sender, short index, TOLEBOOL canceled, OLE_HANDLE hDC) { if(index!=0) return; IMoSymbolPtr sym = (IDispatch*)CreateOleObject("MapObjects2.Symbol"); sym->OutlineColor = moRed ; sym->Outline = true; sym->SymbolType = moFillSymbol; sym->Style = moTransparentFill; EyeMap->DrawShape(Map->Extent, sym); } //---------------------------------------------------------------------

在上述事件响应函数中,首先调用CreateOleObject("MapObjects2.Symbol")创建一个符

Page 184: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

176

号对象,然后将符号对象的边线颜色设置为红色,接着设置符号对象的SymbolType与Style属性, 后调用地图对象的DrawShape方法,在鹰眼窗口中绘制当前地图显示窗口内容的

矩形。 创建EyeMap控件的OnMouseUp事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::EyeMapMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { IMoSymbolPtr sym = (IDispatch*)CreateOleObject("MapObjects2.Symbol"); sym->OutlineColor = moRed ; sym->Outline = true; sym->SymbolType = moFillSymbol; sym->Style = moTransparentFill; IMoPointPtr point; point = EyeMap->ToMapPoint(X, Y); Map->CenterAt(point->X, point->Y); EyeMap->Extent = EyeMap->Extent; } //---------------------------------------------------------------------

上述函数利用地图控件的CenterAt方法将地图显示窗口的中心位置设置为用户在鹰眼

窗口中单击的位置。

5.10.2 显示经纬度与比例尺

为了显示当前鼠标所在点的经纬度位置,创建Map控件的OnMouseMove事件响应函数,

在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::MapMouseMove(TObject *Sender, TShiftState Shift,

int X, int Y) {

IMoPointPtr point;

point = Map->ToMapPoint(X, Y); StatusBar1->Panels->Items[1]->Text = "东经=" + FloatToStr(point->X) +

"; 北纬=" + FloatToStr(point->Y);

} //---------------------------------------------------------------------

ToMapPoint方法将点的位置从屏幕坐标表示转换成地图坐标表示。语法为:

ToMapPoint(xControl, yControl)

其中,参数xControl是屏幕坐标的x值,yControl是屏幕坐标的y值。该方法的返回值是以地

图坐标系表示的点位置。

Page 185: C++ Builder和MapObjects实现

177

第 5章 系统主界面的实现

要得到某一点的地图坐标,可通过响应鼠标事件(Mouse Down、Mouse Move、Mouse Up),从屏幕上选定一点,返回的 X、Y值就是控制坐标,然后使用ToMapPoint得到地图

坐标。 Mouse Down、Mouse Move、Mouse Up这些鼠标操作将返回地图中以窗体单位表示的

位置,可用这些操作来得到屏幕坐标,然后通过坐标转换方式把它们转换成为地图坐标。 创建Map控件的OnAfterLayerDraw事件响应函数,在其中加入如下代码,用于在状态

栏中显示当前地图的显示比例尺:

//--------------------------------------------------------------------- void __fastcall TMainForm::MapAfterLayerDraw(TObject *Sender, short index, TOLEBOOL canceled, OLE_HANDLE hDC) { // 计算地图比例尺 long dScale = (long)_environment->CalcScale(Map); StatusBar1->Panels->Items[2]->Text = "比例尺:1 : " + IntToStr(dScale); } //---------------------------------------------------------------------

5.10.3 资源释放

在系统关闭时,应该释放系统所占有的内存资源。 创建MainForm窗体的OnClose事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::FormClose(TObject *Sender, TCloseAction &Action) { if (MessageDlg("退出系统 ?", mtConfirmation, TMsgDlgButtons() << mbYes << mbNo, 0) == mrYes) Action = caFree; else { Action = caNone; return; } if(_environment != NULL) { delete _environment; _environment = NULL; } } //---------------------------------------------------------------------

上述代码先调用MessageDlg函数弹出一对话框,询问用户是否确定要退出系统。如果

用户单击了Yes按钮,则将Action设置为caFree,表示窗体MainForm将关闭,并释放窗体所

占用的内存,然后删除变量_environment所占有的内存。如果用户单击了No按钮,则将Action设置为caNone,表示窗体不允许被关闭,一切照常。

Page 186: C++ Builder和MapObjects实现

第 6 章 选择与查询功能的实现

地图是关于地点地理信息的存储库,这些信息包括地点的地理位置、地点之间的空间

关系及其属性值。 本章将主要介绍如何通过查询与数据集有关的表从数据中获取信息,以及如何通过空

间和逻辑的查询方法从数据中获取信息。管理有关MapObjects信息的关键是记录集

(Recordset),它表示了地图层中的属性信息,并提供了使用表记录的方法。

6.1 选 择 地 物

北京市地理信息公众查询系统提供3种选择地物的方式,分别是点选、矩形选与多边形

选。这3种选择方式是通过“地图控制工具栏”中第5、6、7个快捷按钮提供的。 创建PointSelect控件的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::PointSelectClick(TObject *Sender)

{ if(PointSelect->Down == true)

{

// 点选择 _environment->m_MapOpr = MO_POINTSEL;

Map->MousePointer = moArrowQuestion;

} else

{

// 取消继续点选择操作 Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

} }

//---------------------------------------------------------------------

创建RectSelect控件的OnClick事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::RectSelectClick(TObject *Sender)

{

if(RectSelect->Down == true) {

Page 187: C++ Builder和MapObjects实现

179

第 6章 选择与查询功能的实现

// 矩形选择

_environment->m_MapOpr = MO_RECTSEL;

Map->MousePointer = moArrowQuestion; }

else

{ // 取消继续矩形选择操作

Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL; }

}

//---------------------------------------------------------------------

创建PolylineSelect控件的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::PolylineSelectClick(TObject *Sender)

{ if(RectSelect->Down == true)

{

// 多边形选择 _environment->m_MapOpr = MO_RECTSEL;

Map->MousePointer = moArrowQuestion;

} else

{

// 取消继续多边形选择操作 Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

} }

//---------------------------------------------------------------------

上面的代码只是把用户即将进行的操作保存在_environment->m_MapOpr属性中,具体

实现选择操作的是Map控件的OnMouseDown事件响应函数。在MapMouseDown函数中加入

如下代码:

//--------------------------------------------------------------------- case MO_POINTSEL: //点选择

{

_environment->m_selectedFeature = NULL; _environment->m_drawLine = NULL;

_environment->m_buses = NULL;

IMoPointPtr pt;

pt = Map->ToMapPoint(X, Y);

_environment->ExecuteSpatialByPoint(Map, pt); Map->Extent= Map->Extent ;

Page 188: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

180

output_LoadData();

PageControl1->ActivePage = QueryTabSheet;

PageControl3->ActivePage = ResultTabSheet; break;

}

case MO_RECTSEL: //矩形选择 {

_environment->m_selectedFeature = NULL;

_environment->m_drawLine = NULL; _environment->m_buses = NULL;

IMoRectanglePtr rect; rect = Map->TrackRectangle();

_environment->ExecuteSpatialByRect(Map, rect,

(SearchMethodConstants )5); Map->Extent = Map->Extent ;

output_LoadData();

PageControl1->ActivePage = QueryTabSheet; PageControl3->ActivePage = ResultTabSheet;

break;

} case MO_POLYGONSEL: //多边形选择

{

_environment->m_selectedFeature = NULL; _environment->m_drawLine = NULL;

_environment->m_buses = NULL;

IMoPolygonPtr poly;

poly = Map->TrackPolygon();

_environment->ExecuteSpatialByPolygon(Map, poly,

(SearchMethodConstants )5);

Map->Extent = Map->Extent ; output_LoadData();

PageControl1->ActivePage = QueryTabSheet;

PageControl3->ActivePage = ResultTabSheet; break;

}

//---------------------------------------------------------------------

对于点选,获取用户当前鼠标单击的位置后,我们调用了TEnvironment类的

ExecuteSpatialByPoint函数来执行查询;在矩形选择中,我们调用地图控件的TrackRectangle方法,让用户在地图中拖出一个矩形框,接着调用TEnvironment类的ExecuteSpatialByRect函数来执行查询;在多边形选择中,调用地图控件的TrackPolygon方法,让用户在地图中

绘制一个多边形,接着调用TEnvironment类的ExecuteSpatialByPolygon函数来执行查询。然

后调用output_LoadData函数,将选择结果显示在“查询”页面的“查询结果”选项卡中。

Page 189: C++ Builder和MapObjects实现

181

第 6章 选择与查询功能的实现

后将“查询结果”选项卡设置为当前活动页面,即将该页面调到前台显示。 TEnvironment类的ExecuteSpatialByPoint函数的代码如下:

//---------------------------------------------------------------------

void TEnvironment::ExecuteSpatialByPoint(TMap* map, IMoPointPtr shape)

{ for (int i = 0; i < m_nLayerNum; i ++)

{

// 先计算地图显示比例尺 double dScale = CalcScale(map);

if (dScale > 8000)

{ dScale = dScale/10000;

dScale = dScale / 5000;

} else

{

dScale = dScale/10000; dScale = dScale / 2500;

}

if (bool(m_layerInfos[i].layer->Visible) &&

m_layerInfos[i].bCanSelected)

// 距离查询 m_layerInfos[i].rsSel = m_layerInfos[i].layer->SearchByDistance

( shape,dScale, WideString(""));

} }

//---------------------------------------------------------------------

TEnvironment类的ExecuteSpatialByRect函数的代码如下:

//--------------------------------------------------------------------- void TEnvironment::ExecuteSpatialByRect(TMap* map, IMoRectanglePtr shape,

SearchMethodConstants sMode)

{ for (int i = 0; i < m_nLayerNum; i ++)

{

if ( (bool)m_layerInfos[i].layer->Visible && m_layerInfos[i].bCanSelected)

m_layerInfos[i].rsSel = m_layerInfos[i].layer->SearchShape

( shape, sMode, WideString("")); }

}

//---------------------------------------------------------------------

TEnvironment类的ExecuteSpatialByPolygon函数的代码如下:

Page 190: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

182

//---------------------------------------------------------------------

void TEnvironment::ExecuteSpatialByPolygon(TMap* map, IMoPolygonPtr shape,

SearchMethodConstants sMode) {

for (int i = 0; i < m_nLayerNum; i ++)

{ if ( (bool)m_layerInfos[i].layer->Visible &&

m_layerInfos[i].bCanSelected)

m_layerInfos[i].rsSel = m_layerInfos[i].layer->SearchShape ( shape, sMode, WideString(""));

}

} //---------------------------------------------------------------------

在 MapObjects中,可以通过图层对象的3种方法来查询地理特征,结果将以一个记录

集的形式返回。这3种方法分别是:

(1)SearchByDistance:按相对位置的范围搜寻特征。 (2)SearchExpression:按逻辑查询,即按照SQL语句的条件从句查询。 (3)SearchShape:根据某个或某些图形特征查询其他相关联的特征。

对于点选,我们调用图层对象的SearchByDistance方法来执行查询,而对于矩形选与多

边形选,调用的是SearchShape方法。 通过距离来搜索地理特征需要使用SearchByDistance方法。该方法返回一个图层的子

集,此子集中的所有特征满足两个条件,一是与已知特征的距离小于规定的距离,二是某

些字段满足规定的条件。方法原型为:

SearchByDistance( shape, tolerance, expression)

其中,参数shape是点、线、多边形或矩形的图形特征。参数tolerance是搜索距离值。

参数expression是条件表达式,句法需要满足ANSI SQL语言的语法,该参数为可选参数,

如果不设置该参数,那么所有符合空间搜索条件的特征都将在记录集中返回。 SearchByDistance和SearchExpression方法相对比较简单。SearchShape方法提供了一套

丰富的搜索方式,该方法原型如下所示:

SearchShape ( shape, searchMethod, expression )

其中,参数shape是一个几何对象,它可以是一个点、一条线、一个矩形或多边形对象,

或是这些对象的集合。这个对象可以从像MouseDown、TrackLine、TrackPolygon或TrackRectangle等地图控制事件和方法中定义,或通过勾画创建,或通过遍历一个记录集并

从“形状”信息组返回的一个或多个形状中获得。 参数searchMethod是一个常量,为了方便,可以直接输入一个搜索类型常量,如果愿

意也可以输入对应于搜索类型常量的整数值。后面马上就要详细解释搜索类型常量。 参数expression是一个字符串,它是ANSI SQL 语句的“Where”从句。此表达式是可

选的。但当调用SearchShape方法时,如果不需要该表达式,必须放置两个引号来表示一个

Page 191: C++ Builder和MapObjects实现

183

第 6章 选择与查询功能的实现

空表达式。 MapObjects为SearchShape方法提供了15种搜索类型常量。每一种常量对应一个整数值,

分别为0~14。下面详细介绍这些常量及其含义。

(1)moExtentOverlap:形状和特征边界重叠。如果特征的矩形范围与搜索形状的矩形

范围重叠,则选择该特征。这种方法主要用在已知搜索形状为矩形或多边形子集的情况下,

此搜索方法的一个实际用法是在当前地图范围内选定所有特征。该方法能够用三维矩形搜

索三维特征。 (2)moCommonPoint:形状与特征有公共点。选择与搜索形状至少具有一个公共点的

地理特征。此种方法不能与MouseDown事件共同使用来选择地理特征。然而,在从一个矢

量图层中得到点,然后利用这些点给所有将该点的精确位置共享为一个多边形或线的顶点

的其他特征定位时,此搜索方法将是十分有效的。 (3)moLineCross:形状和特征共享交叉边界。选择与搜索形状相交的地理特征。通

过地图控件上某一鼠标事件构造的几何对象或通过一个矢量图层中得到的形状,使用此方

法构造搜索特征将是非常有用的。 (4)moCommonLine:形状和特征共享一个公共线。选择与搜索形状至少具有一个公

共线的地理特征。此搜索类型对从一个矢量图层中获得的搜索形状是十分适用的,但对由

地图控件上的鼠标事件构造的搜索形状并不适用。 (5)moCommonPointOrLineCross:形状和特征共享公共点或交叉边界。选择与搜索

形状具有一个公共点或交叉边界的地理特征。 (6)moEdgeTouchOrAreaIntersect:形状和特征相交叉。选择与已知搜索形状接触,

或者全部或部分形状包含在搜索形状中的地理特征。这种方法主要用在已知搜索形状为矩

形或多边形子集的情况下。 (7)moAreaIntersect:形状和特征在内部交叉。如果搜索形状是一个多边形(或矩形),

此方法返回全部或部分包含在搜索形状中的特征,但不是与之接近的。被选择的特征必须

是多边形特征,并且部分或全部包含在搜索形状中。 (8)moAreaIntersectNoEdgeTouch:形状和特征相交叉但边界没有相切边界。此搜索

方法与moAreaIntersect相同,但搜索形状和特征的边界不可以相切或接触。 (9)moContainedBy:特征包含形状。此搜索方法返回完全包含搜索形状的特征。如

果此特征是一个多边形特征,那么搜索形状必须全部包含在该特征中,包括此形状的边界。

如果此特征为一线特征,搜索形状必须沿着此特征的路线。如果此特征为特征,搜索形状

必须在其顶点上。 (10)moContaining:形状包含特征。选择包含在搜索形状内的地理特征。 (11)moContainedByNoEdgeTouch:特征完全包含形状。搜索形状完全包含在地理特

征中,而且没有边界接触。地理特征必须是多边形特征,搜索形状必须整个位于地理特征

内部,它们的边界没有相交或接触。 (12)moContainingNoEdgeTouch:形状完全包含特征。搜索形状必须是多边形特征,

地理特征必须整个位于搜索形状内,它们的边界没有相交或接触。 (13)moPointInPolygon:特征包含搜索形状的第一个点。此搜索类型返回包含搜索形

Page 192: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

184

状第一个点的坐标位置的多个多边形特征。如果搜索形状是一条线,那么线的起点即是被

使用的位置。如果搜索形状是一个多边形,那么起点与终点相同,都被使用。 (14)moCentroidInPolygon:形状包含特征的重心。此搜索类型返回其重心包含在搜

索形状中的多边形特征。重心是一个多边形受重力的中心位置。 (15)moIdentical:特征与形状完全相同。搜索形状和地理特征完全相同。这种方法

一般用于验证地理特征。

MainForm窗体的output_LoadData函数的实现代码如下:

//---------------------------------------------------------------------

// 功能:将选中的地名名称显示在查询结果选项卡中

void __fastcall TMainForm::output_LoadData() {

ResultList->Items->Clear();

int nCount = 0;

for (int i = 0; i < _environment->m_nLayerNum; i ++)

{ if (!_environment->m_layerInfos[i].bVisible)

continue;

IMoRecordsetPtr rs;

rs = _environment->m_layerInfos[i].rsSel;

String szFieldName = "名称";

if (rs)

{ int nCount = rs->Count ;

rs->MoveFirst();

while (!bool(rs->EOF))

{

String szName = rs->Fields->Item(szFieldName)-> ValueAsString;

if (szName != "") {

ResultList->Items->Add(szName);

nCount ++; }

rs->MoveNext(); }

}

}

Page 193: C++ Builder和MapObjects实现

185

第 6章 选择与查询功能的实现

if ( 0 == nCount )

ResultLabel->Caption = "您这次的查询结果:0";

else ResultLabel->Caption ="您这次的查询结果:" + IntToStr(nCount);

}

//---------------------------------------------------------------------

通过TEnvironment类的ExecuteSpatialByPoint、ExecuteSpatialByRect与ExecuteSpatialBy- Polygon函数执行查询操作后,被选择的地物保存在_environment.m_layerInfos[i].rsSel变量

中。output_LoadData函数的作用就是将这些地物的名称显示在“查询结果”选项卡的列表

框中。 上述代码利用了字段集合(Fields)对象与字段(Field)对象。字段集合对象包含记录

集中所有的字段。字段集合对象包含一个Count属性和一个Item函数,前者表示字段数目,

后者用于读取指定名字的字段对象。字段(Field)对象表示数据库中的一列数据,包括一

般的数据类型和一系列属性值。每一个字段对象有名字、数据类型等属性。字段对象的数

据类型有字符串、整数、浮点数、布尔值、日期值、空数据、点、线和多边形等。对特殊

记录,可以把字段对象读作Variant类型或读取其数据值。也可忽略字段对象的数据类型,

而用ValueAsString方法将值转换成字符串进行显示。字段对象的Name属性表示字段的名

称,Type属性表示字段的数据类型,它的取值及其说明如表6.1所示。Value属性记录了字

段当前记录的数据值,返回值的数据类型根据字段对象的数据类型而定。如果字段对象来

自基于Shape文件的数据库,则其数据类型是可修改的,否则是只读的。

表6.1 Type属性的取值及其说明

常量 值 说明

moNone 0 空值

moLong 3 整型

moDouble 5 浮点型

moDate 7 日期型

moString 8 字符串

moBoolean 22 布尔型

moPoint 21 点

moLine 22 线

moPolygon 23 多边形

moPoints 24 点集

为了高亮显示用户选择的结果,将Map控件的OnAfterLayerDraw事件响应函数修改为

如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::MapAfterLayerDraw(TObject *Sender, short index, TOLEBOOL canceled, OLE_HANDLE hDC)

{

Page 194: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

186

// 计算地图比例尺

long dScale = (long)_environment->CalcScale(Map);

//绘画选中的地物

if (dScale < 100000)

{ _environment->DrawRecordset(Map);

}

StatusBar1->Panels->Items[2]->Text = "比例尺:1 :" + IntToStr(dScale);

}

//---------------------------------------------------------------------

上述事件响应函数中调用了类的DrawRecordset函数在地图上高亮显示用户选择的地

物。该函数的实现代码如下:

//---------------------------------------------------------------------

void TEnvironment::DrawRecordset(TMap* map)

{ for (int i = 0; i < m_nLayerNum; i ++)

{

// 当然只需要绘制那些可见图层中被选择的地物 if (m_layerInfos[i].layer->Visible)

{

if (m_layerInfos[i].rsSel) {

// 如果有被选择的地物,则先创建一个符号对象

IMoSymbolPtr sym = (IDispatch*) CreateOleObject("MapObjects2.Symbol");

sym->SymbolType =

m_layerInfos[i].layer->Symbol->SymbolType; sym->Style = m_layerInfos[i].layer->Symbol->Style;

sym->Size = m_layerInfos[i].layer->Symbol->Size;

sym->Color = 0xff;

if (m_layerInfos[i].nCharacterIndex >= 0 &&

m_layerInfos[i].layer->shapeType == moShapeTypePoint ) {

TFont* font = new TFont();

font->Name = m_layerInfos[i].szFontName; sym->SymbolType = moPointSymbol;

sym->Font = (IFontDisp *)((IDispatch *)

FontToOleFont(font)); sym->Style = 4;

sym->Size = m_layerInfos[i].layer->Symbol->Size;

sym->CharacterIndex = (short)m_layerInfos[i].nCharacterIndex;

Page 195: C++ Builder和MapObjects实现

187

第 6章 选择与查询功能的实现

}

// 移动到第一条记录 m_layerInfos[i].rsSel->MoveFirst();

while (!(bool)m_layerInfos[i].rsSel->EOF) {

map->DrawShape ( m_layerInfos[i].rsSel->

Fields->Item("Shape")->Value, sym); m_layerInfos[i].rsSel->MoveNext();

}

} }

}

} //---------------------------------------------------------------------

编译并运行程序,通过点选择、矩形选择与多边形选择快捷按钮便可以从地图中选择

地物,系统会将被选择地物的名称显示在“查询”页面的“查询结果”选项卡中。图6.1显示当前选择了“北海”、“五四大街”与“景山后街”等地物。

图 6.1 从地图中选择地物

虽然通过上述代码,我们能得到正确的查询结果,但程序并不完善。当选择了某一道

路后,地图需要重新绘制,这时地图出现令人难以忍受的闪烁。这主要是由于需要重新绘

制所有图层,但是对于本功能来说,并不需要重新绘制所有图层,因为底下的图层根本没

有变化。改善方法有很多,例如可以使用地图控件的RefreshRect方法,将需要重新绘制的

Page 196: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

188

地区作为参数传递该矩形,然后地图重新绘制该局部范围,从而避免闪烁。另外,可以在

AfterTrackingLayerDraw事件句柄中绘制查询结果。该事件句柄使用位图备份存储,如果图

层没有改变则用该备份位图对地图控件进行屏后刷新,然后一次性刷新整个地图。由于我

们并没有真正改变图层,因此该事件句柄是高亮显示记录的理想地方。 创建Map控件的OnAfterTrackingLayerDraw事件响应函数,在其中加入如下代码,并将

新加入在OnAfterLayerDraw事件响应函数中的代码删除:

//---------------------------------------------------------------------

void __fastcall TMainForm::MapAfterTrackingLayerDraw(TObject *Sender,

OLE_HANDLE hDC) {

// 计算地图比例尺

long dScale = (long)_environment->CalcScale(Map);

//绘画选中的地物

if (dScale < 100000) {

_environment->DrawRecordset(Map);

} }

//---------------------------------------------------------------------

然后,分别在Map控件的OnMouseDown事件响应函数的case MO_POINTSEL、case MO_RECTSEL与case MO_POLYGONSEL段的break之前加入如下几行代码:

//--------------------------------------------------------------------- TVariant va;

VariantInit(&va);

va.vt = VT_NULL; Map->TrackingLayer->Refresh(TRUE, va);

//---------------------------------------------------------------------

编译并运行程序,就会看到这时在进行点选择、矩形选择与多边形选择时没有闪烁现

象了。

6.2 查询地物信息

系统通过“地图控制工具栏”的第8个快捷按钮,即“信息”按钮,提供了详细查询所

选地物信息的功能。该功能的操作顺序是用户先选择“信息”按钮,然后在地图中选择希

望查询其信息的地物,系统将弹出一对话框,显示该地物的详细信息。因此我们需要在当

前项目中加入一个窗体。 选择File菜单的New Form命令,在当前项目中加入一个名为Form1的窗体。将该窗体命

名为InfoForm,并将其单元文件保存为Info.cpp。在该窗体中加入2个Label控件,分别命名

Page 197: C++ Builder和MapObjects实现

189

第 6章 选择与查询功能的实现

为InfoLabel与LayerLabel,然后加入一个ComboBox控件,命名为InfoComboBox, 后加入

一个ListBox控件,命名为InfoListBox。InfoForm窗体在设计期间的界面如图6.2所示。

图 6.2 InfoForm 窗体在设计期间的界面

InfoForm窗体及其控件的属性如表6.2所示。

表6.2 InfoForm窗体及其控件的属性

控件 属性 属性值

Caption 地名信息

BorderIcons.biMinimize false

BorderIcons.biMaximize false

BorderStyle bsSizeToolWin

InfoForm

FormStyle fsStayOnTop

InfoLabel AutoSize false

AutoSize false LayerLabel

Caption 图层名:

InfoComboBox Text

InfoListBox Sorted false

切换到Info.h文件中,在InfoForm类定义的上面加入如下代码,用于声明一个结构:

//---------------------------------------------------------------------

struct ItemInfo {

String szName;

String szType; String szSubType;

String szTable;

String szFieldName; IMoRecordsetPtr rst;

IMoMapLayerPtr layer;

Page 198: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

190

};

//---------------------------------------------------------------------

在InfoForm类中加入如下几个成员变量:

//--------------------------------------------------------------------- private:

ItemInfo* m_ItmInfos;

int m_nIdx; //---------------------------------------------------------------------

在InfoForm类的构造函数中,加入如下代码,初始化成员变量:

//---------------------------------------------------------------------

__fastcall TInfoForm::TInfoForm(TComponent* Owner) : TForm(Owner)

{

m_ItmInfos = NULL; m_nIdx = -1;

}

//---------------------------------------------------------------------

为了访问主窗体,首先加入对Main.h的包含,然后在“TInfoForm *InfoForm;”代码行

的下面加入如下代码,用于声明一个外部变量:

extern TMainForm *MainForm;

确定指定位置处地物的函数为Identify,该函数的代码如下:

//--------------------------------------------------------------------- //功能:确定指定位置处的地物

//参数:[in]long x 鼠标位置的X值(像素坐标)

// [in]long y 鼠标位置的Y值(像素坐标) //返回值:void

void __fastcall TInfoForm::Identify(long x , long y)

{ TEnvironment* env = MainForm->_environment;

env->ClearSelRsts();

int nFeatCount = 0; //选中地物的数目

IMoPointPtr pt; //鼠标的位置(地图坐标)

pt = MainForm->Map->ToMapPoint(x,y);

m_nIdx = -1;

//初始化控件

InfoComboBox->Items->Clear();

InfoListBox->Items->Clear(); m_ItmInfos = new ItemInfo[env->m_nLayerNum];

Page 199: C++ Builder和MapObjects实现

191

第 6章 选择与查询功能的实现

//动态计算查询距离

double dScale = env->CalcScale(MainForm->Map);

if (dScale > 8000) {

dScale = dScale/1000;

dScale = dScale / 5000; }

else

{ dScale = dScale/10000;

dScale = dScale / 2500;

}

//首先,查询点地物,其次,查询线地物, 后查询面状地物

//查询的图层只要是可见的 ShapeTypeConstants aShapeType[3];

aShapeType[0] = moShapeTypePoint;

aShapeType[1] = moShapeTypeLine; aShapeType[2] = moShapeTypePolygon;

for (int j = 0; j < 3; j ++) {

for (int i = 0; i < env->m_nLayerNum; i ++)

{ if (env->m_layerInfos[i].layer->shapeType != aShapeType[j])

continue;

m_ItmInfos[i].szName = "";

//图层可见并且可选择,才能够identify if (bool(env->m_layerInfos[i].layer->Visible) &&

env->m_layerInfos[i].bCanSelected)

{ m_ItmInfos[i].rst = env->m_layerInfos[i].layer->

SearchByDistance ( pt, dScale, WideString(""));

} else

{

//图层不可显示,则跳到下一个 continue;

}

m_ItmInfos[i].szTable = env->m_layerInfos[i].szTableName;

m_ItmInfos[i].layer = env->m_layerInfos[i].layer ;

m_ItmInfos[i].szType = env->m_layerInfos[i].szType; m_ItmInfos[i].szSubType = env->m_layerInfos[i].szSubType;

Page 200: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

192

m_ItmInfos[i].szFieldName = env->m_layerInfos[i].szFieldName;

if (m_ItmInfos[i].rst) {

if (!(bool)m_ItmInfos[i].rst->EOF)

{ if (m_ItmInfos[i].rst->Fields->Item("名称")->Value)

{

m_ItmInfos[i].szName = m_ItmInfos[i].rst->Fields-> Item("名称")->ValueAsString;

}

else {

continue;

}

if (m_ItmInfos[i].szName != "")

{ InfoComboBox->Items->Add(m_ItmInfos[i].szName);

nFeatCount ++;

} }

}

} }

if (nFeatCount > 0) {

InfoLabel->Caption = "总共找到" + IntToStr(nFeatCount)+"个地名";

InfoComboBox->ItemIndex = 0; m_nIdx = 0;

LoadListBox(GetIndex(InfoComboBox->Items->Strings[0]));

} else

{

InfoLabel->Caption = "没有找到任何地名"; LayerLabel->Caption = "类型:没有";

}

} //---------------------------------------------------------------------

上述函数首先调用TEnvironment类的ClearSelRsts函数,清除选择记录;然后调用地图

控件的ToMapPoint函数转换点坐标;然后调用TEnvironment类的CalcScale函数计算地图显

示比例尺,并根据该比例尺动态计算查询距离;然后分3次对各个图层调用函数

SearchByDistance实现距离查询,并将查询结果保存在ItemInfo结构的rst(Recordset对象)

成员变量中;将查找到的地物记录的名称保存在ItemInfo结构的szName成员变量中,并加

Page 201: C++ Builder和MapObjects实现

193

第 6章 选择与查询功能的实现

入到InfoForm窗体的组合框中; 后调用GetIndex 函数与LoadListBox函数在InfoForm窗体

的列表框中显示第一个地物的详细信息。 TEnvironment类的ClearSelRsts函数的实现代码如下:

//---------------------------------------------------------------------

void TEnvironment::ClearSelRsts()

{ for (int i = 0; i < m_nLayerNum; i ++)

{

delete m_layerInfos[i].rsSel; }

}

//---------------------------------------------------------------------

InfoForm类的GetIndex函数的实现代码如下:

//---------------------------------------------------------------------

int __fastcall TInfoForm::GetIndex(String szName)

{ int nIndex = -1;

for (int i = 0; i < MainForm->_environment->m_nLayerNum; i ++) {

if (m_ItmInfos[i].szName == szName)

{ nIndex = i;

break;

} }

return nIndex; }

//---------------------------------------------------------------------

InfoForm类的LoadListBox函数的代码如下:

//--------------------------------------------------------------------- // 功能:显示地名的详细信息并几次闪烁对应的地物

void __fastcall TInfoForm::LoadListBox(int nIndex)

{ if (m_ItmInfos[nIndex].szName == "")

return;

InfoListBox->Items->Clear();

LayerLabel->Caption = "类型:" + m_ItmInfos[nIndex].szSubType;

if (m_ItmInfos[nIndex].szTable == "")

{

Page 202: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

194

InfoListBox->Items->Add("没有详细信息");

goto FLASH;

} else

{

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + MainForm->_environment->m_szDBName + ";

Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(this); myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

myConnection->Open();

TADODataSet* dataSet = new TADODataSet(this);

dataSet->Connection = myConnection; String szSQL;

szSQL = "Select * From [" + m_ItmInfos[nIndex].szTable+"]Where"+

m_ItmInfos[nIndex].szFieldName +" ='" + m_ItmInfos[nIndex]. szName+"'";

dataSet->CommandText = szSQL;

try

{

dataSet->Open (); }

catch(...)

{ goto FLASH;

}

if (0 == dataSet->RecordCount )

{

InfoListBox->Items->Add("没有详细信息"); goto FLASH;

}

else {

for (int i = 0; i < dataSet->Fields->Count; i ++)

{ AnsiString fieldName = dataSet->Fields->Fields[i]->FullName;

String szValue = fieldName + ":" +

dataSet->Fields->Fields[i]->AsString; InfoListBox->Items->Add(szValue);

}

} }

Page 203: C++ Builder和MapObjects实现

195

第 6章 选择与查询功能的实现

FLASH:

MainForm->Map->FlashShape (

m_ItmInfos[nIndex].rst->Fields->Item("Shape")->Value, 4);

// 设置选中地物的符号

if (MainForm->_environment->m_layerInfos[nIndex].nCharacterIndex >= 0 && MainForm->_environment->m_layerInfos[nIndex].layer->shapeType

== moShapeTypePoint

) {

MainForm->_environment->m_selectedSymbol = (IDispatch*)

CreateOleObject("MapObjects2.Symbol"); MainForm->_environment->m_selectedSymbol->SymbolType =

moPointSymbol;

TFont* font = new TFont(); font->Name =

MainForm->_environment->m_layerInfos[nIndex].szFontName;

MainForm->_environment->m_selectedSymbol->Font = (IFontDisp *) ((IDispatch *)FontToOleFont(font));

MainForm->_environment->m_selectedSymbol->Style = 4;

MainForm->_environment->m_selectedSymbol->Size = (short)

MainForm->_environment->m_layerInfos[nIndex].layer->Symbol->Size;

MainForm->_environment->m_selectedSymbol->CharacterIndex = (short)

MainForm->_environment->m_layerInfos[nIndex].nCharacterIndex;

MainForm->_environment->m_selectedSymbol->Color = 0xff;

MainForm->_environment->m_selectedSymbolSize = (short)

MainForm->_environment->m_layerInfos[nIndex].nSymSize; }

else

{ if (MainForm->_environment->m_layerInfos[nIndex].layer->shapeType

== moShapeTypePoint )

{ MainForm->_environment->m_selectedSymbol = (IDispatch*)

CreateOleObject("MapObjects2.Symbol");

MainForm->_environment->m_selectedSymbol->SymbolType = MainForm->

_environment->m_layerInfos[nIndex].layer->Symbol-> SymbolType;

MainForm->_environment->m_selectedSymbol->Style = MainForm->

Page 204: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

196

_environment->m_layerInfos[nIndex].layer->Symbol->Style;

MainForm->_environment->m_selectedSymbol->Size = MainForm->

_environment->m_layerInfos[nIndex].layer->Symbol->Size; MainForm->_environment->m_selectedSymbol->Color = 0xff;

MainForm->_environment->m_selectedSymbolSize = (short) MainForm->_environment->m_layerInfos[nIndex].nSymSize;

}

}

MainForm->_environment->m_selectedFeature = m_ItmInfos[nIndex].rst->

Fields->Item ("Shape")->Value; MainForm->Map->Extent = MainForm->Map->Extent;

}

//---------------------------------------------------------------------

上述代码通过ADO控件查找地物的详细信息,并利用地图控件的FlashShape方法,将

地物高亮闪烁4次。 通过上述方法可在地图中查找到一个或多个地物,系统将这些地物的名称放置在组合

框中,列表框中显示的是第一个地物的详细信息,对于其他所选地物,可通过组合框来切

换。 创建InfoComboBox控件的OnChange事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TInfoForm::InfoComboBoxChange(TObject *Sender)

{ if (m_nIdx != InfoComboBox->ItemIndex )

m_nIdx = InfoComboBox->ItemIndex;

else {

MainForm->Map->FlashShape(m_ItmInfos[GetIndex(InfoComboBox->Items-> Strings[m_nIdx])].rst->Fields->Item("Shape")->Value, 4);

return;

}

LoadListBox(GetIndex(InfoComboBox->Items->Strings[m_nIdx]));

} //---------------------------------------------------------------------

下面要完成的工作就是调用InfoForm窗体的Identify函数,并显示InfoForm窗体。 切换到MainForm窗体中,双击InfoShow控件,创建该控件的OnClick事件响应函数,在

其中加入如下代码:

Page 205: C++ Builder和MapObjects实现

197

第 6章 选择与查询功能的实现

//---------------------------------------------------------------------

void __fastcall TMainForm::InfoShowClick(TObject *Sender)

{

if(InfoShow->Down == true)

{

// 查询地名信息

_environment->m_MapOpr = MO_INFO;

Map->MousePointer = moIdentify;

}

else

{

// 取消继续查询地名信息

Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

}

}

//---------------------------------------------------------------------

在Map控件的OnMouseDown事件响应函数中加入如下代码,实现查询地名信息:

//---------------------------------------------------------------------

case MO_INFO:

{

// 地名信息

_environment->m_x = X;

_environment->m_y = Y;

_environment->m_drawLine = NULL;

_environment->m_buses = NULL;

if (NULL == m_InfoForm)

{

m_InfoForm = new TInfoForm(this);

}

m_InfoForm->Identify(_environment->m_x, _environment->m_y);

m_InfoForm->Show();

break;

}

//---------------------------------------------------------------------

在C++ Builder中,默认情况下,当我们新增一个窗体时,应用程序将此窗体作为“自

动创建窗体(Auto Create Form)”,表示程序启动时自动创建此窗体。在多数情况下,我

们并不需要应用程序在启动时自动创建某一窗体,而是在需要的情况下由我们自己创建此

Page 206: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

198

窗体。此时应该将此窗体设置为“可获取窗体(Available form)”。这当然也应该由入口

函数来管理,C++ Builder不需要程序员在此函数中编写代码,而是提供了一个可视化界面

来实现。选择Project菜单中的Options命令,打开一个如图6.3所示的Project Options对话框。

从图中可以看出,默认情况下,所有的窗体都放在Auto Create forms列表框中。由于我们不

需要应用程序自动创建窗体InfoForm,因此可将InfoForm由Auto-create forms列表框移至

Available forms列表框中。

图 6.3 Project Options 对话框

改为自创窗体的另一种方法是直接修改应用程序的入口函数WinMain。选择Project菜单的View Source命令,打开AddrManager.cpp文件。如果没有通过Project Options对话框将

InfoForm由Auto-create forms列表框移至Available forms列表框中,那么此时WinMain函数如

下所示:

//---------------------------------------------------------------------

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{ try

{

Application->Initialize(); Application->CreateForm(__classid(TMainForm), &MainForm);

Application->CreateForm(__classid(TInfoForm), &InfoForm);

Application->Run(); }

catch (Exception &exception)

Page 207: C++ Builder和MapObjects实现

199

第 6章 选择与查询功能的实现

{

Application->ShowException(&exception);

} return 0;

}

//---------------------------------------------------------------------

要实现将InfoForm由自动创建窗体变为可获取窗体,只需要删除如下语句即可:

Application->CreateForm(__classid(TInfoForm), &InfoForm);

编译并运行程序,便可运用“信息”按钮查看地物的详细信息,如图6.4所示。

图 6.4 查询地物详细信息

6.3 地 名 查 询

根据地名进行模糊与精确查询是通过“查询”页面的“地名查询”选项卡提供的,如

图6.5所示。在“地名”文本框中输入需要查询的地物名称,然后选择查询方式, 后单击

“搜索”按钮执行查询,并将结果显示在“查询结果”选项卡中。

Page 208: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

200

图 6.5 地名查询功能入口

首先要实现的是在“地名查询”选项卡的QueryFirstClass控件中加入所有大类的名称,

例如“北京纵览”、“交通运输”等。实现该功能的函数是LoadFilter,其代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::LoadFilter(TComboBox* comboBox)

{ comboBox->Items->Clear();

comboBox->Items->Add(NOFILTER);

TADODataSet* typeDataset = new TADODataSet(this);

typeDataset->Connection = _environment->m_DataConnection;

typeDataset->CommandText = "Select 大类 From 地名类型";

try

{ typeDataset->Open();

typeDataset->First ();

for(int i=0; i< typeDataset->RecordCount; i++) {

AnsiString className = typeDataset->Fields->

Fields[0]->AsString; comboBox->Items->Add(className);

typeDataset->Next();

}

typeDataset->First ();

Page 209: C++ Builder和MapObjects实现

201

第 6章 选择与查询功能的实现

typeDataset->Close ();

delete typeDataset;

typeDataset = NULL; }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

} //---------------------------------------------------------------------

上述函数利用了一个字符串常量NOFILTER,因此需要在Main.cpp文件的前部对它进

行定义。在“TMainForm *MainForm;”代码行的下面加入如下代码:

const AnsiString NOFILTER = "(全部类型)";

然后在“地名查询”选项卡的QuerySecondClass控件中加入所有中类的名称,例如“基

础地理要素”、“居民点”等。实现该功能的函数是LoadFilter2,其代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::LoadFilter2 ( TComboBox* comboBox, String

szFilter, bool bLayerType) {

comboBox->Items->Clear();

comboBox->Items->Add(NOSUBFILTER);

String szFilter2 = "";

if (szFilter != NOFILTER) {

szFilter2 = " where 大类 = '"+szFilter+"'";

}

TADODataSet* typeDataset = new TADODataSet(this);

typeDataset->Connection = _environment->m_DataConnection; typeDataset->CommandText = "Select 中类 From 地名中类型" + szFilter2;

try {

typeDataset->Open();

typeDataset->First(); for(int i=0; i< typeDataset->RecordCount; i++)

{

AnsiString className = typeDataset->Fields-> Fields[0]->AsString;

if(bLayerType == true && className == "乡镇、街道")

continue; comboBox->Items->Add(className);

typeDataset->Next();

Page 210: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

202

}

typeDataset->First (); typeDataset->Close ();

delete typeDataset;

typeDataset = NULL; }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

} //---------------------------------------------------------------------

上述函数利用了另一个字符串常量NOSUBFILTER,因此需要在Main.cpp文件的前部对

它进行定义。在“const AnsiString NOFILTER = "(全部类型)";”代码行的下面加入如下代码:

const AnsiString NOSUBFILTER = "(全部子类型)";

同时调用上述两个函数的是InitQueryPanel函数,其代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::InitQueryPanel()

{ //初始化大类过滤器

LoadFilter(QueryFirstClass);

QueryFirstClass->ItemIndex = 0;

//初始化中类过滤器

LoadFilter2(QuerySecondClass, NOFILTER, false); QuerySecondClass->ItemIndex = 0;

}

//---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数FormCreate的 后加入对上述函数的调用,

这样当系统运行后,便在“地名查询”选项卡的两个组合框中分别显示所有的大类与中类

名称,代码如下:

InitQueryPanel();

创建QueryFirstClass的OnChange事件响应函数,在其中加入如下代码,用于在中类组

合框中显示大类中包含的所有中类名称:

//--------------------------------------------------------------------- void __fastcall TMainForm::QueryFirstClassChange(TObject *Sender)

{

String szType = QueryFirstClass->Text; LoadFilter2(QuerySecondClass, szType, false);

QuerySecondClass->ItemIndex = 0;

Page 211: C++ Builder和MapObjects实现

203

第 6章 选择与查询功能的实现

}

//---------------------------------------------------------------------

下面要实现的就是执行查询,并将查询结果显示在“查询结果”选项卡的列表框中。

切换到MainForm窗体,双击QuerySearch控件,创建该控件的OnClick事件响应函数,在其

中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::QuerySearchClick(TObject *Sender)

{

String szSQL = ""; String szSQL2 = "";

String szFilter = "";

String szType = QueryFirstClass->Text;

String szSubType = QuerySecondClass->Text;

if ((szSubType == NOSUBFILTER) || (szSubType == ""))

{

if((szType != "") && (szType != NOFILTER)) {

szFilter = "类型='" + szType + "' ";

} }

else

{ szFilter = "中类型='" + szSubType + "' ";

}

if (QueryNameEdit->Text != "")

{

if (QueryNameCond->Checked) szSQL = "(名称 like '" + QueryNameEdit->Text + "%')";

else

szSQL = "(名称 like '" + QueryNameEdit->Text + "')"; }

if (QueryPhoneEdit->Text != "" ) {

if (QueryPhoneCond->Checked)

szSQL2 = "(电话 like '" + QueryPhoneEdit->Text + "%')"; else

szSQL2 = "(电话 like '" + QueryPhoneEdit->Text + "')";

}

if (szSQL == "" )

{

Page 212: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

204

szSQL = szSQL2;

}

else {

if (szSQL2 != "")

{ if (QueryBothCond->Checked)

szSQL = szSQL + " And " + szSQL2;

else szSQL = szSQL + " Or " + szSQL2;

}

}

if (szFilter != "")

{ if (szSQL == "")

szSQL = szFilter;

else szSQL = szSQL + " AND (" + szFilter + ")";

}

Query(szSQL);

}

//---------------------------------------------------------------------

上述代码首先根据用户在“地名查询”选项卡中输入的条件构造查询字符串,然后调

用Query函数执行查询,并将查询结果显示在“查询结果”选项卡的列表框中。Query函数

的实现代码如下:

//---------------------------------------------------------------------

//功能:执行查询,并将结果显示在“查询结果”选项卡中

void __fastcall TMainForm::Query(AnsiString szFilter) {

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + _environment->m_szDBName + "; Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(this);

myConnection->ConnectionString = strConnectionString; myConnection->LoginPrompt = false;

try {

myConnection->Open();

String szSQL;

if (szFilter == "")

szSQL = "Select * From 地名索引 Order By 名称";

Page 213: C++ Builder和MapObjects实现

205

第 6章 选择与查询功能的实现

else

szSQL="Select*From 地名索引Where"+szFilter+"Order By名称";

TADODataSet* dataSet = new TADODataSet(this);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{ dataSet->Open();

long nCount = dataSet->RecordCount;

dataSet->First();

for(int i=0; i<nCount; i++)

{ AnsiString result = dataSet->Fields->FieldByName("

名称")->AsString;

ResultList->Items->Add(result); dataSet->Next();

}

dataSet->First();

dataSet->Close();

delete dataSet; dataSet = NULL;

if ( 0 == nCount ) ResultLabel->Caption = "您这次的查询结果:0";

else

ResultLabel->Caption = "您这次的查询结果:" + IntToStr(nCount);

// 断开与数据库的连接

myConnection->Close(); delete myConnection;

myConnection = NULL;

} catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); }

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

Page 214: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

206

//将“查询结果”选项卡调到 前台显示

PageControl1->ActivePage = QueryTabSheet;

PageControl3->ActivePage = ResultTabSheet; }

//---------------------------------------------------------------------

编译并运行程序,切换到“查询”页面的“地名查询”选项卡,便可进行地名的精确

与模糊查询。图6.6显示了对“北师大”进行模糊查询的结果。

图 6.6 对“北师大”进行模糊查询的结果

6.4 查找 近地物

查找 近地物功能是通过“查询”页面的“查找 近”选项卡提供的,如图6.7所示。

查找 近地物又可细分为两类查询,一类是按照位置查找,另一类是按照地名查找。对于

前一类查找,选择了距离和过滤类型后,先按“搜索”按钮,然后将鼠标移动到地图上选

择所要查询的位置即可。对于后一类查找,输入需要查找的地名,然后选择距离和过滤类

型, 后单击“搜索”按钮执行查找。查找结果将显示在“查询结果”选项卡中。

Page 215: C++ Builder和MapObjects实现

207

第 6章 选择与查询功能的实现

图 6.7 查找 近地物功能入口

初始化“查找 近”选项卡中控件属性的函数是InitDistPanel,该函数的实现代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::InitDistPanel()

{ DistDistCond->ItemIndex = 0;

//初始化大类过滤器 LoadFilter(DistFirstClass);

DistFirstClass->ItemIndex = 0;

//初始化中类过滤器

LoadFilter2(DistSecondClass, NOFILTER, false);

DistSecondClass->ItemIndex = 0; }

//---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数FormCreate的 后加入对上述函数的调用,

这样当系统运行后,便在“查找 近”选项卡的两个组合框中分别显示所有的大类与中类

名称。 创建DistFirstClass的OnChange事件响应函数,在其中加入如下代码,用于在中类组合

框中显示大类中包含的所有中类名称:

//--------------------------------------------------------------------- void __fastcall TMainForm::DistFirstClassChange(TObject *Sender)

Page 216: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

208

{

String szType = DistFirstClass->Text;

LoadFilter2(DistSecondClass, szType, false); DistSecondClass->ItemIndex = 0;

}

//---------------------------------------------------------------------

创建DistPositionCond与DistNameCond的OnClick事件响应函数,用于控制其他控件的

显示,代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::DistPositionCondClick(TObject *Sender)

{ if (DistPositionCond->Checked)

{

DistNameEdit->Enabled = true; if (DistNameEdit->Text == "")

DistSearch->Enabled = false;

else DistSearch->Enabled = true;

DistNote->Visible = false; }

else

{ DistNameEdit->Enabled = false;

DistNote->Visible = true;

} }

//---------------------------------------------------------------------

void __fastcall TMainForm::DistNameCondClick(TObject *Sender) {

if (DistNameCond->Checked)

{ DistSearch->Enabled =true;

DistNote->Visible = true;

} else

{

DistNote->Visible = false; Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL;

} }

//---------------------------------------------------------------------

上述代码实现对界面的控制,而真正实现查询操作的是“搜索”按钮。创建该按钮的

Page 217: C++ Builder和MapObjects实现

209

第 6章 选择与查询功能的实现

OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::DistSearchClick(TObject *Sender)

{ TEnvironment* env = _environment;

switch (DistDistCond->ItemIndex) {

case 0:

env->m_dDistance = 0.0; break;

case 1:

env->m_dDistance = 10.0; break;

case 2:

env->m_dDistance = 20.0; break;

case 3:

env->m_dDistance = 50.0; break;

case 4:

env->m_dDistance = 100.0; break;

case 5:

env->m_dDistance = 200.0; break;

case 6:

env->m_dDistance = 500.0; break;

case 7:

env->m_dDistance = 1000.0; break;

default:

env->m_dDistance = 0.0; break;

}

if (DistSecondClass->Text == NOSUBFILTER)

{

if (DistFirstClass->Text == NOFILTER) {

for (int i = 0; i < env->m_nLayerNum; i ++)

{ if (env->m_layerInfos[i].bCanSelected)

env->m_layerInfos[i].bSelected = true;

else

Page 218: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

210

env->m_layerInfos[i].bSelected = true;

}

} else

{

for (int i = 0; i < env->m_nLayerNum; i ++) {

if ( env->m_layerInfos[i].szType == DistFirstClass->Text

&& env->m_layerInfos[i].bCanSelected) env->m_layerInfos[i].bSelected = true;

else

env->m_layerInfos[i].bSelected = false; }

}

} else

{

for (int i = 0; i < env->m_nLayerNum; i ++) {

if (env->m_layerInfos[i].szSubType == DistSecondClass->Text

&& env->m_layerInfos[i].bCanSelected) env->m_layerInfos[i].bSelected = true;

else

env->m_layerInfos[i].bSelected = false; }

}

if (DistPositionCond->Checked ) // 通过位置查找

{

env->m_MapOpr = MO_SEACHBYDIST; Map->MousePointer = moCross;

}

else // 通过地名查找 {

String szTable = _environment->GetTableName(DistNameEdit->Text,

"地名索引"); if (szTable == "")

{

MessageBox(NULL, "属性库中无此地名", "北京市地理信息 公众查询系统", MB_OK);

return ;

}

_environment->m_szPlaceName = DistNameEdit->Text;

String szLayer = _environment->GetLayerName(DistNameEdit->Text, "地名索引");

Page 219: C++ Builder和MapObjects实现

211

第 6章 选择与查询功能的实现

int nIndex = _environment->GetLayerIndexByName(szTable);

if (nIndex < 0)

return;

if (!env->m_layerInfos[nIndex].bCanSelected)

return;

IMoPointPtr pt;

IMoRecordsetPtr rs = env->m_layerInfos[nIndex].layer->SearchExpression (

WideString("名称='" + DistNameEdit->Text+"'"));

if (!rs)

{

MessageBox(NULL, "电子地图上没有这个地名", "北京市地理信息 公众查询系统", MB_OK);

return;

}

rs->MoveFirst();

if (rs->EOF) {

MessageBox(NULL, "电子地图上没有这个地名", "北京市地理信息

公众查询系统", MB_OK); return;

}

pt = rs->Fields->Item("shape")->Value;

long nCount = env->SearchByDistance(pt->X, pt->Y, env->m_dDistance, ResultList);

if ( 0 == nCount ) ResultLabel->Caption ="您这次的查询结果:0";

else

ResultLabel->Caption ="您这次的查询结果:" + IntToStr(nCount);

// 将“查询结果”选项卡调到 前台显示

PageControl1->ActivePage = QueryTabSheet; PageControl3->ActivePage = ResultTabSheet;

env->m_MapOpr = MO_NULL; Map->MousePointer = moArrow ;

}

} //---------------------------------------------------------------------

Page 220: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

212

从上述代码可以看到,DistSearchClick事件响应函数又调用了TEnvironment类的

GetTableName、GetLayerName与SearchByDistance函数。这3个函数的实现代码如下:

//---------------------------------------------------------------------

String TEnvironment::GetTableName(String szName, String szTbleName)

{

String szTableName = "";

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + m_szDBName + ";Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{

myConnection->Open();

}

catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return szTableName;

}

String szSQL;

szSQL = "Select * From " + szTbleName + " Where 名称 ='"+szName+"'";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{

dataSet->Open();

if(dataSet->RecordCount == 0)

return szTableName;

szTableName = dataSet->Fields->FieldByName("属性表名")->AsString;

dataSet->First();

dataSet->Close();

delete dataSet;

dataSet = NULL;

myConnection->Close();

delete myConnection;

Page 221: C++ Builder和MapObjects实现

213

第 6章 选择与查询功能的实现

myConnection = NULL;

}

catch(EOleException& e)

{

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK);

}

return szTableName;

}

//---------------------------------------------------------------------

// 根据地名得到地名所在的图层名

// <param name="szName">String 地名名称</param>

// <param name="szTblName">String 索引表名称</param>

// <returns>图层名称</returns>

String TEnvironment::GetLayerName(String szName, String szTblName)

{

String szTableName = "";

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + m_szDBName + ";Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{

myConnection->Open();

}

catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return szTableName;

}

String szSQL;

szSQL = "Select * From " + szTblName + " Where 名称 ='"+szName+"'";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{

dataSet->Open();

if(dataSet->RecordCount == 0)

return "";

Page 222: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

214

szTableName = dataSet->Fields->FieldByName("图层名")->AsString;

dataSet->First();

dataSet->Close();

delete dataSet;

dataSet = NULL;

myConnection->Close();

delete myConnection;

myConnection = NULL;

}

catch(EOleException& e)

{

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK);

}

return szTableName;

}

//---------------------------------------------------------------------

long TEnvironment::SearchByDistance ( double dX, double dY,double dDistance,

TListBox* listBox)

{

bool bClosest = false;

if (dDistance <= 0.0)

{

bClosest = true;

dDistance = 1000000000;

}

listBox->Items->Clear();

for (int i = 0; i < m_nLayerNum; i ++)

{

if (m_layerInfos[i].bSelected

&& m_layerInfos[i].layer->shapeType == moShapeTypePoint)

{

String szPlaceName = "";

double dMinDist = -1.0;

double dDist = 0.0;

IMoRecordsetPtr rs;

rs = m_layerInfos[i].layer->Records;

Page 223: C++ Builder和MapObjects实现

215

第 6章 选择与查询功能的实现

if (rs)

{

rs->MoveFirst();

while (!(bool)rs->EOF)

{

MPoint pts[2];

IMoPointPtr pt;

pt = rs->Fields->Item("shape")->Value;

pts[0].x = dX;

pts[0].y = dY;

pts[1].x = pt->X ;

pts[1].y = pt->Y;

dDist = CalcLenght(pts, 2);

if (dDistance >= CalcLenght(pts, 2))

{

if (bClosest)

{

//查找 近

String szTemp = String(rs->Fields->Item("名称")

->Value);

if (szTemp == "")

continue;

if (((dMinDist < 0) || (dMinDist > dDist))

&& (m_szPlaceName != szTemp))

{

dMinDist = dDist;

szPlaceName = szTemp;

}

}

else

{

String szTemp = String(rs->Fields->Item("名称")

->Value);

listBox->Items->Add(szTemp);

}

}

rs->MoveNext();

}

Page 224: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

216

if (bClosest && (szPlaceName != "")

&& (m_szPlaceName != szPlaceName) )

listBox->Items->Add(szPlaceName);

}

}

}

return listBox->Items->Count;

}

//---------------------------------------------------------------------

通过地名查找的实现是通过图层对象的SearchExpression方法实现的。这种查询方式是

按属性数据库中字段的值查询地理特征,而查询表达式的语法等于ANSI SQL语句的

“Where”从句,返回值为图层的选择集。调用方法如下:

variable = object.SearchExpression (expression)

variable指向地理特征的选择集。object是图层变量。参数expression是查询条件表达式。 真正实现按位置查询的是地图控件的OnMouseDown事件响应函数,在该函数中加入如

下代码:

//---------------------------------------------------------------------

case MO_SEACHBYDIST: {

//查找 近地名

IMoPointPtr pt; pt = Map->ToMapPoint(X, Y);

Map->MousePointer = moHourglass;

ResultList->Items->Clear();

long nCount = _environment->SearchByDistance(pt->X, pt->Y,

_environment->m_dDistance, ResultList); if (nCount <= 0)

{

ResultLabel->Caption = "您这次的查询结果:0"; MessageBox(NULL, "您的这一次查询没有找到任何符合条件的地名",

"北京市地理信息公众查询系统", MB_OK);

} else

{

ResultLabel->Caption ="您这次的查询结果:" + IntToStr(nCount); PageControl1->ActivePage = QueryTabSheet;

PageControl3->ActivePage = ResultTabSheet;

}

_environment->m_MapOpr = MO_NULL;

Map->MousePointer = moArrow ;

Page 225: C++ Builder和MapObjects实现

217

第 6章 选择与查询功能的实现

break;

}

//---------------------------------------------------------------------

编译并运行程序,切换到“查询”页面的“查找 近”选项卡,便可通过位置或地名

两种查询方法来查找特定距离内特定类型的地物。图6.8显示的是查找北京大学附近1000米内所有文教科技单位的结果。

图 6.8 查找北京大学附近 1000 米内所有文教科技单位的结果

6.5 公 交 查 询

6.5.1 公交站点与线路查询

公交查询功能是通过“查询”页面的“公交查询”选项卡提供的,如图6.9所示。用户

可以查询某个公交站点,也可查询某条公交线路。当在“公交查询”选项卡中单击“公交

车站”单选按钮时,下方列表框中显示的是所有公交站点,在列表框中双击某站点名称,

将在地图中高亮闪烁该站点,然后弹出一个独立窗体,显示所有经过该站点的公交路线名

称。当在“公交查询”选项卡中单击“公交路线”单选按钮时,列表框中显示的是所有的

公交路线,当双击某公交路线名称时,将在地图中高亮闪烁行车路线,然后弹出一个独立

窗体,显示该公交路线中的所有站名,当在该窗体中选择某站点时,又可显示所有经过该

站点的公交路线,如图6.9所示。

Page 226: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

218

图 6.9 公交线路查询

在MainForm窗体中首先加入如下函数,初始化“公交查询”选项卡中控件的属性:

//---------------------------------------------------------------------

void __fastcall TMainForm::InitBusPanel()

{ String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + _environment->m_szDBName +";

Persist Security Info=False"; TADOConnection* myConnection = new TADOConnection(this);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{ myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return; }

AnsiString strSQL="Select distinct 站名 From 公交车站路线 Order By 站名"; TADODataSet* dataSet = new TADODataSet(this);

Page 227: C++ Builder和MapObjects实现

219

第 6章 选择与查询功能的实现

dataSet->Connection = myConnection;

dataSet->CommandText = strSQL;

try

{

dataSet->Open();

int nCount = dataSet->RecordCount;

dataSet->First(); for(int i=0; i<nCount; i++)

{

AnsiString strValue = dataSet->Fields->Fields[0]->AsString; BusListBox->Items->Add(strValue);

dataSet->Next();

}

dataSet->First();

dataSet->Close(); delete dataSet;

dataSet = NULL;

} catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); }

myConnection->Close(); delete myConnection;

myConnection = NULL;

} //---------------------------------------------------------------------

在MainForm窗体的OnCreate事件响应函数FormCreate的 后加入对上述函数的调用,

这样当系统运行后,便在“公交查询”选项卡的列表框中显示所有公交站点名称,代码如

下:

InitBusPanel ();

创建BusLine控件的OnClick事件响应函数,用于实现当用户单击“公交线路”单选按

钮时,在列表框中显示所有公交线路名称。为了共享程序代码,切换到对象监视器中,将

BusStation控件的OnClick事件响应函数指向为BusLineClick,这样当用户单击“公交站点”

单选按钮时,也调用BusLineClick函数。BusLineClick函数的代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusLineClick(TObject *Sender)

{ LoadBusData();

Page 228: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

220

}

//---------------------------------------------------------------------

void __fastcall TMainForm::LoadBusData() {

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source =" +_environment->m_szDBName + "; Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(this);

myConnection->ConnectionString = strConnectionString; myConnection->LoginPrompt = false;

try {

myConnection->Open();

} catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); return;

}

AnsiString strSQL;

if(BusStation->Checked)

{ if (_environment->m_szBusFilter == "")

strSQL = "Select distinct 站名 From 公交车站路线 Order By 站名";

else strSQL = "Select distinct 站名 From 公交车站路线 Where " +

_environment->m_szBusFilter +" Order By 站名";

} else

{

if (_environment->m_szBusFilter == "") strSQL = "Select distinct 线路名 From 公交车站路线 Order By 线路名";

else

strSQL = "Select distinct 线路名 From 公交车站路线 Where " + _environment->m_szBusFilter + " Order By 线路名";

}

TADODataSet* dataSet = new TADODataSet(this);

dataSet->Connection = myConnection;

dataSet->CommandText = strSQL;

try

{ dataSet->Open();

Page 229: C++ Builder和MapObjects实现

221

第 6章 选择与查询功能的实现

int nCount = dataSet->RecordCount;

BusListBox->Items->Clear(); dataSet->First();

for(int i=0; i<nCount; i++)

{ AnsiString strValue = dataSet->Fields->Fields[0]->AsString;

BusListBox->Items->Add(strValue);

dataSet->Next(); }

dataSet->First(); dataSet->Close();

delete dataSet;

dataSet = NULL; }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

myConnection->Close();

delete myConnection;

myConnection = NULL;

BusName->Text = "";

} //---------------------------------------------------------------------

上述程序代码根据用户选择的不同单选按钮,构造不同的SQL语句进行查询,并将查

询结果显示在列表框中。 创建BusListBox控件的OnClick事件响应函数,在其中加入如下代码,用于将用户选择

的站名名称或公交线路名称显示在文本框中:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusListBoxClick(TObject *Sender) {

BusName->Text = BusListBox->Items->Strings[BusListBox->ItemIndex];

} //---------------------------------------------------------------------

创建BusName控件的OnChange事件响应函数,在其中加入如下代码,用于实现当用户

在文本框中输入字符串时,迅速在列表框中定位该字符串:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusNameChange(TObject *Sender) {

Page 230: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

222

for(int i=0; i<BusListBox->Items->Count; i++)

{

if(BusListBox->Items->Strings[i].AnsiCompare(BusName->Text) >= 0) {

BusListBox->ItemIndex = i;

return; }

}

} //---------------------------------------------------------------------

真正实现查询功能的是BusListBox控件的OnDblClick事件响应函数,其代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusListBoxDblClick(TObject *Sender) {

if (BusListBox->Items->Count <= 0)

return;

String szName = BusListBox->Items->Strings[BusListBox->ItemIndex];

String szLayer = _environment->GetLayerName(szName,"地名索引"); String szTable = _environment->GetTableName(szName,"地名索引");

int nIndex = _environment->GetLayerIndexByName(szTable);

if (nIndex < 0)

{

//属性库中没有这个车站或线路 _environment->m_selectedFeature = NULL;

Map->Extent = Map->Extent;

return; }

IMoRecordsetPtr rs; rs = _environment->

m_layerInfos[nIndex].layer->SearchExpression(WideString

("名称 like '" + szName + "'"));

if (rs)

{ rs->MoveFirst();

if (! (bool)rs->EOF)

{ IMoPointPtr pt;

IMoLinePtr line;

if (BusStation->Checked)

{

Page 231: C++ Builder和MapObjects实现

223

第 6章 选择与查询功能的实现

pt = rs->Fields->Item("shape")->Value;

}

else {

line = rs->Fields->Item("shape")->Value;

pt = line->Extent->Center; }

if (!IsWithin(Map->Extent, pt)) {

Map->CenterAt(pt->X, pt->Y );

}

if (_environment->m_layerInfos[nIndex].nCharacterIndex >= 0

&& _environment->m_layerInfos[nIndex].layer->shapeType == moShapeTypePoint )

{

_environment->m_selectedSymbol = (IDispatch*) CreateOleObject("MapObjects2.Symbol");

_environment->m_selectedSymbol->SymbolType = moPointSymbol;

TFont* font = new TFont(); font->Name = _environment->m_layerInfos[nIndex].szFontName;

_environment->m_selectedSymbol->Font = (IFontDisp *)

((IDispatch *)FontToOleFont(font));; _environment->m_selectedSymbol->Style = 4;

_environment->m_selectedSymbol->Size = (short)

_environment->m_layerInfos[nIndex].layer->Symbol->Size; _environment->m_selectedSymbol->CharacterIndex = (short)

_environment->m_layerInfos[nIndex].nCharacterIndex;

_environment->m_selectedSymbol->Color = 0xff; _environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize;

_environment->m_selectedScale = _environment->m_layerInfos[nIndex].dScale;

}

else {

if (_environment->m_layerInfos[nIndex].layer->shapeType ==

moShapeTypePoint ) {

_environment->m_selectedSymbol = (IDispatch*)

CreateOleObject("MapObjects2.Symbol"); _environment->m_selectedSymbol->SymbolType =

_environment->m_layerInfos[nIndex].layer->

Symbol->SymbolType; _environment->m_selectedSymbol->Style = _environment->

Page 232: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

224

m_layerInfos[nIndex].layer->Symbol->Style;

_environment->m_selectedSymbol->Size = _environment->

m_layerInfos[nIndex].layer->Symbol->Size; _environment->m_selectedSymbol->Color = 0xff;

_environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize; _environment->m_selectedScale =

_environment->m_layerInfos[nIndex].dScale;

} }

if (BusStation->Checked) {

//车站

_environment->m_selectedFeature = rs->Fields->Item("shape")->Value;

}

else {

//公交路线

//设置_environment.m_drawLine和_environment.m_buses IMoPointsPtr pts;

pts = line->Parts->Item(0);

_environment->m_nSelectedLineNum = 1;

if(_environment->m_drawLine)

{ delete _environment->m_drawLine;

_environment->m_drawLine = NULL;

} _environment->m_drawLine = new

MLine[_environment->m_nSelectedLineNum];

_environment->m_drawLine->nPointNumber = pts->Count; _environment->m_drawLine->pPoint = new MPoint[pts->Count];

_environment->m_drawLine->pPoint = new MPoint[pts->Count]; for (int i = 0; i < pts->Count; i ++)

{

pt = pts->Item(i); _environment->m_drawLine->pPoint[i].x = pt->X;

_environment->m_drawLine->pPoint[i].y = pt->Y;

}

_environment->m_buses = new Buses();

int nCount = 0; if(!_environment->GetStation(szName, _environment->m_buses,

Page 233: C++ Builder和MapObjects实现

225

第 6章 选择与查询功能的实现

&nCount))

_environment->m_buses = NULL;

else _environment->m_buses->nNum = nCount;

}

Map->FlashShape(rs->Fields->Item("shape")->Value, 4);

Map->Extent = Map->Extent;

EyeMap->Extent = EyeMap->Extent;

if (BusStation->Checked )

{ TStationForm* frm = new TStationForm(this);

frm->m_strStationName = szName;

frm->ShowModal(); }

else

{ TLineForm* frm = new TLineForm(this);

frm->m_strLineName = szName;

frm->ShowModal(); }

}

else {

MessageBox(NULL, "电子地图上没有这个地物",

"北京市地理信息公众查询系统",MB_OK); }

}

} //---------------------------------------------------------------------

上面的代码将查询到的公交站点保存在_environment->m_selectedFeature中,而将查询

到的公交路线保存在_environment->m_buses中,公交线路由于在地图的图层中本身并不存

在对应的线对象,因此需要根据站点名称构造该线对象。 上面的事件响应函数用到了其他许多函数与窗体。首先是IsWithin函数,该函数的代码

如下,用于判断某点是否在某个矩形中:

//---------------------------------------------------------------------

bool __fastcall TMainForm::IsWithin(IMoRectanglePtr rect, IMoPointPtr pt) {

bool bWithin = false;

bWithin = rect->IsPointIn(pt); return bWithin;

}

//---------------------------------------------------------------------

Page 234: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

226

此外还调用了TEnvironment类的GetStation函数。切换到Environment.cpp文件,在其中

加入如下代码:

//---------------------------------------------------------------------

//功能:得到公交线路上的车站

//参数:[in]object node 乘车路线结构 // [in]int nIndex 第几次换乘

// [out]Buses buses 车站数组

// [out]int nCount 车站数目 //返回值:true (成功)

bool TEnvironment::GetStation(PathNode* node, int nIndex, Buses* buses, int*

nCount) {

PathNode* line = node;

int nOrder1,nOrder2;

nOrder1 = GetStationOrder(

line->szRoutineName[nIndex],line->szFromStationName[nIndex]);

nOrder2 = GetStationOrder(

line->szRoutineName[nIndex],line->szToStationName[nIndex]);

if ((nOrder1 < 0) || (nOrder2 < 0))

return false;

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + m_szDBName + ";Persist Security Info=False"; TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{ myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return false; }

String strSQL; strSQL = "Select * From 公交车站路线 Where 线路名 ='" +

line->szRoutineName[nIndex]+ "' And 顺序 Between " + IntToStr(nOrder1)

+ " And " + IntToStr(nOrder2) ;

TADODataSet* dataSet = new TADODataSet(MainForm);

Page 235: C++ Builder和MapObjects实现

227

第 6章 选择与查询功能的实现

dataSet->Connection = myConnection;

dataSet->CommandText = strSQL;

try

{

try {

dataSet->Open();

int nCountTemp = dataSet->RecordCount;

if(nCountTemp == 0)

return false;

buses->pts = new MPoint[nCountTemp];

dataSet->First(); for(int i=0; i<nCountTemp; i++)

{

String szStation; szStation = dataSet->Fields->FieldByName("站名")->AsString;

if(!GetStationPt(szStation, &(buses->pts[*nCount]))) {

buses->pts[*nCount].x = -1;

buses->pts[*nCount].y = -1; }

*nCount ++;

}

dataSet->First();

dataSet->Close(); delete dataSet;

dataSet = NULL;

} catch(EOleException& e)

{

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK); myConnection->Close();

delete myConnection;

myConnection = NULL; return false;

}

} __finally

{

myConnection->Close(); delete myConnection;

Page 236: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

228

myConnection = NULL;

}

return true;

}

//--------------------------------------------------------------------- //功能:得到公交线路上的车站

//参数:[in]string szLineName 公交线路名

// [out]Buses buses 车站数组 // [out]int nCount 车站数目

//返回值:true(成功)

bool TEnvironment::GetStation(String szLineName, Buses* buses, int* nCount) {

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + m_szDBName + ";Persist Security Info=False"; TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{ myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return false; }

String strSQL; strSQL = "Select * From 公交车站路线 Where 线路名 ='"+szLineName +"'" ;

TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = myConnection;

dataSet->CommandText = strSQL;

try

{

dataSet->Open();

int nCountTemp = dataSet->RecordCount;

if(nCountTemp == 0) return false;

buses->pts = new MPoint[nCountTemp]; dataSet->First();

Page 237: C++ Builder和MapObjects实现

229

第 6章 选择与查询功能的实现

for(int i=0; i<nCountTemp; i++)

{

String szStation; szStation = dataSet->Fields->FieldByName("站名")->AsString;

if(!GetStationPt(szStation, &(buses->pts[*nCount]))) {

buses->pts[*nCount].x = -1;

buses->pts[*nCount].y = -1; }

(*nCount) ++;

dataSet->Next();

}

dataSet->First();

dataSet->Close();

delete dataSet; dataSet = NULL;

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return false; }

myConnection->Close(); delete myConnection;

myConnection = NULL;

return true; }

//---------------------------------------------------------------------

// 功能:得到给定车站名在给定公交线路上的车站顺序(如第2站) /// 参数:[in]string szLineName 公交线路名

/// [in]string szStationName 公交车站名

/// 返回值:> 0 车站顺序,否则失败 int TEnvironment::GetStationOrder(String szLineName, String szStationName)

{

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + m_szDBName + ";Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString; myConnection->LoginPrompt = false;

try {

Page 238: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

230

myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return -1; }

String strSQL; strSQL = "Select * From 公交车站路线 Where 线路名 ='" +szLineName + "'

And 站名 ='" + szStationName + " '";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = myConnection;

dataSet->CommandText = strSQL;

int nResult = -1;

try {

dataSet->Open();

if(dataSet->RecordCount == 0)

nResult = -1;

else nResult = dataSet->Fields->FieldByName("顺序")->AsInteger;

dataSet->First(); dataSet->Close();

delete dataSet;

dataSet = NULL; }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

myConnection->Close();

delete myConnection;

myConnection = NULL;

return nResult;

} //---------------------------------------------------------------------

// 功能:得到给定车站名的地理坐标

/// 参数:[in]string szLineName 公交线路名 /// [out]MPoint pt 公交车的地理坐标

Page 239: C++ Builder和MapObjects实现

231

第 6章 选择与查询功能的实现

/// 返回值:false 失败

bool TEnvironment::GetStationPt(String szStationName, MPoint* pt)

{ String szName = szStationName;

String szLayer = GetLayerName(szName, "地名索引");

String szTable = GetTableName(szName, "地名索引");

int nIndex = GetLayerIndexByName(szTable);

if (nIndex < 0) return false;

IMoRecordsetPtr rs; rs = m_layerInfos[nIndex].layer->SearchExpression( WideString("名称

like '" + szName + "'"));

if (rs) {

rs->MoveFirst();

if (!(bool)rs->EOF)

{

IMoPointPtr pt1; pt1 = rs->Fields->Item("shape")->Value;

pt->x = pt1->X;

pt->y = pt1->Y;

return true;

} }

return false; }

//---------------------------------------------------------------------

上述函数调用了一个新的结构PathNode。选择File菜单的New命令,在随后弹出的New Item对话框中选择New选项卡中的Unit图标,创建一个新的单元文件。将该单元文件保存为

Path.cpp。切换到该单元文件的头文件Path.h中,加入如下代码,用于声明PathNode结构:

//--------------------------------------------------------------------- struct PathNode

{

short nSegNumber; String* szRoutineName;

String* szFromStationName;

String* szToStationName; };

//---------------------------------------------------------------------

Page 240: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

232

此外,在BusListBox控件的OnDblClick事件响应函数中,利用了两个窗体来分别显示

公交站点与公交线路查询的结果。 选择File菜单的New Form命令,在当前项目中加入一个新窗体,将该窗体命名为

StationForm,将单元文件保存为Station.cpp。在StationForm窗体中首先加入两个Label控件,

分别命名为StationName与LineName;然后加入一个Button控件,命名为PositionBtn; 后

加入一个ListBox控件,命名为LineListBox。StationForm窗体在设计期间的界面如图6.10所示。

图 6.10 StationForm 窗体在设计期间的界面

StationForm窗体及其控件的属性如表6.3所示。

表6.3 StationForm窗体及其控件的属性

控件 属性 属性值

BorderIcons.biMinimize false

BorderIcons.biMaximize false

BorderStyle bsSizeToolWin

StationForm

Caption 车站信息

StationName Caption 车站名称:

LineName Caption 经过该站的公交线路

PositionBtn Caption 定位

LineListBox MultiSelect false

切换到StationForm窗体的头文件Station.h中,在其公有段加入如下成员变量,表示查询

的公交站点:

AnsiString m_strStationName;

然后在StationForm窗体的构造函数中加入如下代码,初始化成员变量:

Page 241: C++ Builder和MapObjects实现

233

第 6章 选择与查询功能的实现

//---------------------------------------------------------------------

__fastcall TStationForm::TStationForm(TComponent* Owner)

: TForm(Owner) {

m_strStationName = "";

} //---------------------------------------------------------------------

在Station.cpp文件中加入如下代码,将主窗体声明为外部变量:

#include "main.h"

extern TMainForm *MainForm;

然后创建窗体StationForm的OnShow事件响应函数,在其中加入如下代码,用于实现当

显示窗体时,在窗体的列表框中显示经过该站点的所有公交线路的名称:

//--------------------------------------------------------------------- void __fastcall TStationForm::FormShow(TObject *Sender) { StationName->Caption = " 车站名称:" + m_strStationName; LineName->Caption = "经过" + m_strStationName + "的公交线路:"; LineListBox->Items->Clear(); if (m_strStationName == "") return ; String szFilter = "Select * From 公交车站路线 where 站名='" + m_strStationName + "'"; if (MainForm->_environment->m_szBusFilter != "") { szFilter += " And "; szFilter += MainForm->_environment->m_szBusFilter; } szFilter += "Order By 线路名"; TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = MainForm->_environment->m_DataConnection; dataSet->CommandText = szFilter; try { // 执行查询 dataSet->Open(); // 将查询结果显示在列表框中 dataSet->First(); for(int i=0; i<dataSet->RecordCount; i++) { AnsiString str = dataSet->Fields->FieldByName("

线路名")->AsString;

Page 242: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

234

LineListBox->Items->Add(str); dataSet->Next(); } dataSet->First(); dataSet->Close(); delete dataSet; dataSet = NULL; MainForm->_environment->m_DataConnection->Close(); } catch(EOleException& e) { MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); } } //---------------------------------------------------------------------

“定位”按钮用于闪烁并高亮显示用户在列表框中选择的公交路线。创建该按钮的

OnClick事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TStationForm::PositionBtnClick(TObject *Sender) { //公交路线名称 String szName = LineListBox->Items->Strings[LineListBox->ItemIndex]; //公交路线所在图层名称 String szLayer = MainForm->_environment->BUSLINE_LAYERNAME; int nIndex = MainForm->_environment->GetLayerIndexByName(szLayer); IMoMapLayerPtr ly = MainForm->Map->Layers->Item(nIndex); if (!ly) return; IMoRecordsetPtr rs = ly->SearchExpression(WideString("名称

like '"+szName +"'")); if (!rs) { MessageBox(NULL, "地图上没有这条公交线路",

"北京市地理信息公众查询系统", MB_OK); return; } rs->MoveFirst(); if ((bool)rs->EOF) { MessageBox(NULL, "地图上没有这条公交线路", "北京市地理信息公众查询系统",MB_OK); return; }

Page 243: C++ Builder和MapObjects实现

235

第 6章 选择与查询功能的实现

//在地图中找到这条线路,画出来 //首先定位 IMoPointPtr pt; IMoLinePtr line = rs->Fields->Item("shape")->Value; pt = line->Extent->Center; if (!MainForm->IsWithin(MainForm->Map->Extent, pt)) { MainForm->Map->CenterAt(pt->X, pt->Y ); } //其次设置_environment.m_drawLine和_environment.m_buses IMoPointsPtr pts; pts = line->Parts->Item(0); if(MainForm->_environment->m_drawLine) { delete MainForm->_environment->m_drawLine; MainForm->_environment->m_drawLine = NULL; } MainForm->_environment->m_drawLine = NULL; MainForm->_environment->m_nSelectedLineNum = 1; MainForm->_environment->m_drawLine = new MLine[1]; MainForm->_environment->m_drawLine[0].nPointNumber = pts->Count; MainForm->_environment->m_drawLine[0].pPoint = new MPoint[pts->Count]; for (int i = 0; i < pts->Count; i ++) { pt = pts->Item(i); MainForm->_environment->m_drawLine[0].pPoint[i].x = pt->X; MainForm->_environment->m_drawLine[0].pPoint[i].y = pt->Y; } MainForm->_environment->m_buses = new Buses(); int nCount = 0; if(!MainForm->_environment->GetStation ( szName, MainForm->

_environment->m_buses, &nCount)) MainForm->_environment->m_buses = NULL; else MainForm->_environment->m_buses->nNum = nCount; //闪烁,重画 MainForm->Map->FlashShape(rs->Fields->Item("shape")->Value, 4); MainForm->Map->Extent = MainForm->Map->Extent; } //---------------------------------------------------------------------

Page 244: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

236

上述代码首先调用图层对象的SearchExpression方法查询选择的公交路线,然后利用地

图控件的FlashShape方法闪烁显示该公交路线。 当用户在列表框中双击某公交路线的名称时,将弹出一个独立窗体显示该公交路线经

过的所有站点。实现代码如下:

//--------------------------------------------------------------------- void __fastcall TStationForm::LineListBoxDblClick(TObject *Sender)

{

if (LineListBox->ItemIndex < 0) return;

TLineForm* frm = new TLineForm(MainForm); frm->m_strLineName =

LineListBox->Items->Strings[LineListBox->ItemIndex];

Visible = false; frm->ShowModal();

Close();

} //---------------------------------------------------------------------

上 述 代 码 与 MainForm 主 窗 体 BusListBox 控 件 的 OnDblClick 事 件 响 应 函 数

BusListBoxDblClick一样,用到了LineForm窗体。 选择File菜单的New Form命令,在当前项目中加入一个新窗体,将该窗体命名为

LineForm,将单元文件保存为Line.cpp。在LineForm窗体中首先加入3个Label控件,分别命

名为LineNameLabel、StationLabel与TransLineLabel;然后加入2个ListBox控件,分别命名

为StationListBox与TransLineListBox。LineForm窗体在设计期间的界面如图6.11所示。

图 6.11 LineForm 窗体在设计期间的界面

LineForm窗体及其控件的属性如表6.4所示。

Page 245: C++ Builder和MapObjects实现

237

第 6章 选择与查询功能的实现

表6.4 LineForm窗体及其控件的属性

控件 属性 属性值

BorderIcons.biMinimize false

BorderIcons.biMaximize false

BorderStyle bsSizeToolWin

LineForm

Caption 公交路线信息

LineNameLabel Caption 公交路线:

StationLabel Caption 经过的公交车站:

TransLineLabel Caption 在此站可转的车:

StationListBox MultiSelect false

TransLineListBox MultiSelect false

切换到LineForm窗体的头文件Line.h中,在其公有段加入如下成员变量,表示查询的公

交路线:

AnsiString m_strLineName;

然后在LineForm窗体的构造函数中加入如下代码,初始化成员变量:

//--------------------------------------------------------------------- __fastcall TLineForm::TLineForm(TComponent* Owner)

: TForm(Owner)

{ m_strLineName = "";

}

//---------------------------------------------------------------------

在Line.cpp文件中加入如下代码,将主窗体声明为外部变量:

#include "main.h"

extern TMainForm *MainForm;

然后创建窗体LineForm的OnShow事件响应函数,在其中加入如下代码,用于实现当显

示窗体时,在窗体的StationListBox列表框中显示该公交线路经过的所有公交站点的名称:

//--------------------------------------------------------------------- void __fastcall TLineForm::FormShow(TObject *Sender)

{

LineNameLabel->Caption = "线路名称:"+ m_strLineName; StationListBox->Items->Clear();

if (m_strLineName == "") return ;

String szFilter = "Select * From 公交车站路线 where 线路名='" + m_strLineName + "'";

Page 246: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

238

szFilter += " Order By 顺序";

TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = MainForm->_environment->m_DataConnection;

dataSet->CommandText = szFilter;

try

{

// 执行查询 dataSet->Open();

// 将查询结果显示在列表框中 dataSet->First();

for(int i=0; i<dataSet->RecordCount; i++)

{ AnsiString str = dataSet->Fields->FieldByName("站名")->AsString;

StationListBox->Items->Add(str);

dataSet->Next();

}

dataSet->First();

dataSet->Close();

delete dataSet; dataSet = NULL;

MainForm->_environment->m_DataConnection->Close(); }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

} //---------------------------------------------------------------------

当用户在站点列表框中选择某站点时,需要在公交路线列表框中显示所有经过该站点

的公交路线。要实现该功能,创建StationListBox控件的OnClick事件响应函数,在其中加入

如下代码:

//---------------------------------------------------------------------

void __fastcall TLineForm::StationListBoxClick(TObject *Sender) {

AnsiString strStationName = StationListBox->Items->

Strings[StationListBox->ItemIndex]; if(strStationName == "")

return;

Page 247: C++ Builder和MapObjects实现

239

第 6章 选择与查询功能的实现

TransLineListBox->Items->Clear();

String szFilter = "Select * From 公交车站路线 where 站名='" + strStationName + "'";

if (MainForm->_environment->m_szBusFilter != "")

{ szFilter += " And ";

szFilter += MainForm->_environment->m_szBusFilter;

} szFilter += "Order By 线路名";

TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = MainForm->_environment->m_DataConnection;

dataSet->CommandText = szFilter;

try

{

// 执行查询 dataSet->Open();

// 将查询结果显示在列表框中 dataSet->First();

for(int i=0; i<dataSet->RecordCount; i++)

{ AnsiString str = dataSet->Fields->FieldByName("

线路名")->AsString;

TransLineListBox->Items->Add(str);

dataSet->Next();

}

dataSet->First();

dataSet->Close(); delete dataSet;

dataSet = NULL;

MainForm->_environment->m_DataConnection->Close();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

} }

//---------------------------------------------------------------------

系统希望在用户双击站点列表框中某站点名称时,在地图中闪烁并高亮显示该站点。

要完成该功能,创建StationListBox控件的OnDblClick事件响应函数,在其中加入如下代码:

Page 248: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

240

//---------------------------------------------------------------------

void __fastcall TLineForm::StationListBoxDblClick(TObject *Sender)

{ String szName = StationListBox->

Items->Strings[StationListBox->ItemIndex];

String szLayer = MainForm->_environment->GetLayerName(szName, "地名索引");

String szTable = MainForm->_environment->GetTableName(szName,

"地名索引");

int nIndex = MainForm->_environment->GetLayerIndexByName(szTable);

if (nIndex < 0) return;

IMoRecordsetPtr rs; rs = MainForm->_environment->

m_layerInfos[nIndex].layer->SearchExpression

(WideString("名称 like '" + szName + "'")); if (rs)

{

rs->MoveFirst(); if (! (bool)rs->EOF)

{

IMoPointPtr pt = rs->Fields->Item("shape")->Value; if (!MainForm->IsWithin(MainForm->Map->Extent, pt))

{

MainForm->Map->CenterAt(pt->X, pt->Y ); }

if (MainForm->_environment-> m_layerInfos[nIndex].nCharacterIndex >= 0

&& MainForm->_environment->

m_layerInfos[nIndex].layer->shapeType == moShapeTypePoint )

{

MainForm->_environment->m_selectedSymbol = (IDispatch*) CreateOleObject("MapObjects2.Symbol");

MainForm->_environment->m_selectedSymbol->SymbolType =

moPointSymbol; TFont* font = new TFont();

font->Name = MainForm->_environment->

m_layerInfos[nIndex].szFontName; MainForm->_environment->m_selectedSymbol->Font=

(IFontDisp*)

((IDispatch *)FontToOleFont(font));; MainForm->_environment->m_selectedSymbol->Style = 4;

Page 249: C++ Builder和MapObjects实现

241

第 6章 选择与查询功能的实现

MainForm->_environment->m_selectedSymbol->Size = (short)

MainForm->_environment->m_layerInfos[nIndex].nSymSize;

MainForm->_environment->m_selectedSymbol->CharacterIndex = (short) ainForm->_environment->m_layerInfos[nIndex].

nCharacterIndex;

MainForm->_environment->m_selectedSymbol->Color = 0xff; }

MainForm->_environment->m_selectedFeature = rs->Fields->Item("shape")->Value;

MainForm->Map->FlashShape

(MainForm->_environment->m_selectedFeature, 4); MainForm->Map->Extent = MainForm->Map->Extent;

}

} }

//---------------------------------------------------------------------

上述函数与MainForm窗体BusListBox控件的OnDblClick事件响应函数的代码一样,将

公交站点查询结果保存在MainForm->_environment->m_selectedFeature变量中。但是上面的

代码除了几次闪烁站点之外,并没有高亮显示该站点。同样,在BusListBox控件的

OnDblClick事件响应函数中,将公交线路查询结果保存在_environment->m_buses变量中,

除了闪烁显示该线路之外,也没有在地图中高亮绘制该线路。实现这项功能的是地图控件

Map的OnAfterTrackingLayerDraw事件响应函数。切换到Main.cpp中,将该事件响应函数修

改为如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::MapAfterTrackingLayerDraw(TObject *Sender,

OLE_HANDLE hDC)

{ // 计算地图比例尺

long dScale = (long)_environment->CalcScale(Map);

//绘画选中的地物

if (dScale < 100000)

{ _environment->DrawRecordset(Map);

if (_environment->m_selectedFeature) {

DrawSelectedShape();

} }

// 绘画公交线路,或 短路经 if (_environment->m_drawLine != NULL && dScale < 200000)

Page 250: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

242

{

HDC hDC ;

HANDLE hWnd ; // 初始化hWnd和hDC变量

hWnd =(void *)Map->Handle;

hDC = GetDC(hWnd); // 创建红色宽度为7像素的画笔对象

HPEN tempPen = CreatePen(PS_SOLID, 7, RGB(255, 0, 0));

// 应用画笔对象 SelectObject(hDC, tempPen);

for (int i = 0; i < _environment->m_nSelectedLineNum; i ++) {

POINT* pts = new

POINT[_environment->m_drawLine[i].nPointNumber]; for(int j = 0; j < _environment->m_drawLine[i].nPointNumber; j ++)

{

MPoint pt =_environment->FromMapPoint(Map, _environment-> m_drawLine[i].pPoint[j].x, _environment->

m_drawLine[i].pPoint[j].y);

pts[j].x = pt.x; pts[j].y = pt.y;

}

::Polyline(hDC, pts, _environment->m_drawLine[i].nPointNumber);

// 释放资源

::ReleaseDC(hWnd, hDC); }

}

} //---------------------------------------------------------------------

在上面的代码中,我们调用了两个新函数,一个是DrawSelectedShape函数,另一个是

TEnvironment类的FromMapPoint函数。 DrawSelectedShape函数的代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::DrawSelectedShape() {

IMoSymbolPtr sym = (IDispatch*)CreateOleObject("MapObjects2.Symbol");

sym->Color = 0xff;

if (_environment->m_selectedSymbol)

{ double dScale = _environment->CalcScale(Map);

_environment->m_selectedSymbol->Size = ReCalcFontSize (

Page 251: C++ Builder和MapObjects实现

243

第 6章 选择与查询功能的实现

_environment->m_selectedSymbolSize,dScale);

Map->DrawShape ( _environment->m_selectedFeature,

_environment->m_selectedSymbol ); }

else

Map->DrawShape(_environment->m_selectedFeature,sym ); }

//---------------------------------------------------------------------

TEnvironment类的FromMapPoint函数的代码如下:

//--------------------------------------------------------------------- MPoint TEnvironment::FromMapPoint(TMap* map, double x,double y)

{

MPoint pt; double dW = fabs( map->Extent->Right - map->Extent->Left);

double dH = fabs(map->Extent->Top - map->Extent->Bottom);

double dRatio = 1.0;

double dOrgX=0;

double dOrgY=0;

if(map->Width / dW > map->Height / dH) //横向居中

{ dRatio = map->Height / dH;

dOrgX = (map->Width - dW * dRatio) / 2;

} else if(map->Width / dW < map->Height / dH) //纵向居中

{

dRatio = map->Width / dW; dOrgY = (map->Height - dH * dRatio) / 2;

}

pt.x = (x - map->Extent->Left) * dRatio + dOrgX;

pt.y = map->Height - (y - map->Extent->Bottom) * dRatio + dOrgY;

return pt;

}

//---------------------------------------------------------------------

在MapAfterTrackingLayerDraw函数的绘制公交路线的代码中,调用了一些Windows API函数,例如CreatePen、SelectObject与Polyline函数,原因是,虽然MapObjects提供了许

多对象及其方法,但是仅仅使用这些对象和方法,有时很难实现需要的功能。MapObjects为方便利用API来扩展应用,特提供一般Windows API函数都需要的变量——Handle。

Handle是真正的句柄。尽管它设计成为地图的属性,但实际上Handle是Windows变量,

不能够改变。很多API函数都需要提供该句柄。

Page 252: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

244

通过Handle使用GetDC函数,便可获取设备描述表(Device Context, DC)。设备描

述表是一个句柄,一个Windows分配给表面设备的惟一ID号,如屏幕或打印机。设备描述

表是Windows中支持与设备无关的图形操作的关键。应用程序必须通知Windows装入一定

的设备驱动程序,一旦装入,即准备输出操作,诸如线的颜色与边宽、画刷的模式与颜色、

字体、裁剪区域等。这些工作都是通过创建并维护一个设备描述表来完成的。大多数用于

绘图的API的第一个参数都是DC提供的一个句柄。一旦使用完设备描述表,必须调用

ReleaseDC函数来释放该资源。 此外,系统还提供依据公交类型来划分公交线路的功能。创建_bus_checkBox1控件的

CheckedChanged事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusUrbanClick(TObject *Sender)

{ SetBusFilter();

LoadBusData();

} //---------------------------------------------------------------------

其中SetBusFilter函数是新创建的函数,其代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::SetBusFilter() {

_environment->m_bPathInit = false;

String szFilter = "";

if (BusUrban->Checked)

szFilter = "'市区路线'";

if (BusNeighbor->Checked)

{ if (szFilter != "")

szFilter += ",'郊区路线'";

else szFilter = "'郊区路线'";

}

if (BusYuntong->Checked)

{

if (szFilter != "") szFilter += ",'运通专线'";

else

szFilter = "'运通专线'"; }

if (BusTaxi->Checked)

Page 253: C++ Builder和MapObjects实现

245

第 6章 选择与查询功能的实现

{

if (szFilter != "")

szFilter += ",'巴士专线'"; else

szFilter = "'巴士专线'";

}

if (BusLong->Checked)

{ if (szFilter != "")

szFilter += ",'专线长途公共汽车'";

else szFilter = "'专线长途公共汽车'";

}

if (BusNight->Checked)

{

if (szFilter != "") szFilter += ",'夜班车路线'";

else

szFilter = "'夜班车路线'"; }

if (BusSub->Checked) {

if (szFilter != "")

szFilter += ",'地铁车'"; else

szFilter = "'地铁车'";

}

if (szFilter != "")

_environment->m_szBusFilter = "类型 In (" + szFilter + ")"; else

_environment->m_szBusFilter = "";

} //---------------------------------------------------------------------

通过对象监视器,将其他复选框控件的OnClick事件响应函数指向BusUrbanClick函数,

这样,这些控件便可以共享上述程序代码,无需为每个控件创建相同的事件响应函数。 选择Project菜单的Options命令,打开Project Options对话框。在该对话框的Form选项卡

中,将StationForm与LineForm由Auto-create forms列表框移至Available forms列表框。 编译并运行程序,切换到“查询”页面的“公交查询”选项卡,便可以查询某站点或

某公交线路。图6.12显示的是所有经过中关园站点的公交线路,地图中红色正方形表示的

是该站点所在位置。

Page 254: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

246

图 6.12 公交查询

6.5.2 乘车路线查询

此外,系统还可以通过“公交查询”选项卡的“选择乘车路线”按钮来提供查询两地

之间乘车方案的功能。 创建“选择乘车路线”按钮的OnClick事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::BusSearchClick(TObject *Sender)

{ if (!_environment->m_bPathInit)

{

Cursor = crSQLWait; _environment->m_pPath->Build();

_environment->m_bPathInit = true;

Cursor = crDefault; }

TBusForm* frm = new TBusForm(this); frm->ShowModal();

}

//---------------------------------------------------------------------

上述代码利用了TEnvironment类的一个TPath类型的成员变量。TPath类的主要功能就是

短路径查询。

Page 255: C++ Builder和MapObjects实现

247

第 6章 选择与查询功能的实现

切换到Environment.cpp文件,在TEnvironment类中加入如下成员变量:

public TPath m_path;

在TEnvironment类的构造函数中加入如下代码,初始化m_path变量:

m_path = new TPath();

下面实现TPath类。切换到Path.cpp文件,首先加入如下结构的定义:

//---------------------------------------------------------------------

struct Routine //一条公交线路 {

short nFlag; //0:双向;1:上行;2:下行

short nStationNumber; String szRoutineName;

String* szStaionName;

}; //---------------------------------------------------------------------

struct Node

{ short nNodeNumber;

short* nRoutineOrder;

short* nStationOrder; };

//---------------------------------------------------------------------

struct Station {

String szStationName;

short nRoutineNumber; short* pnRoutineID;

short* pnOrder;

}; //---------------------------------------------------------------------

在TPath类中加入如下成员变量:

//---------------------------------------------------------------------

public: int TIMELIMIT;

Routine* m_pRoutine;

Station* m_pStations; short m_nRCount;

short m_nSCount;

//---------------------------------------------------------------------

在TPath类的构造函数中加入如下代码,用于初始化成员变量:

//---------------------------------------------------------------------

TPath::TPath()

Page 256: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

248

{

TIMELIMIT = 3;

m_pRoutine = new Routine[1000]; m_pStations = new Station[5000];

m_nRCount = 0;

m_nSCount = 0; }

//---------------------------------------------------------------------

BusSearchClick函数调用了TPath类的Build函数,该函数的代码如下:

//--------------------------------------------------------------------- void TPath::Build()

{

m_nRCount = BuildRoutine(m_pRoutine); m_nSCount = BuildStationIndex(m_pRoutine, m_nRCount, m_pStations);

}

//---------------------------------------------------------------------

上述函数又调用了BuildRoutine与BuildStationIndex两个函数,这两个函数的代码如下:

//---------------------------------------------------------------------

short TPath::BuildRoutine(Routine* pRoutine)

{ String szFilter;

szFilter = "Select * From 公交车站路线 ";

if (MainForm->_environment->m_szBusFilter != "") szFilter += MainForm->_environment->m_szBusFilter;

szFilter += " Order By 线路名";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = MainForm->_environment->m_DataConnection;

dataSet->CommandText = szFilter;

short nCount = 0;

short nSCount = 0;

try

{ String rName;

String sName;

String sTemp1; String sTemp2;

String* sNames = new String[350];

// 执行查询

dataSet->Open();

dataSet->First();

Page 257: C++ Builder和MapObjects实现

249

第 6章 选择与查询功能的实现

for(int i=0; i<dataSet->RecordCount; i++)

{

rName = dataSet->Fields->Fields[0]->AsString; sTemp1 = dataSet->Fields->Fields[1]->AsString;

sName = dataSet->Fields->Fields[2]->AsString;

sTemp2 = dataSet->Fields->Fields[0]->AsString;

if(nCount == 0)

{ pRoutine[nCount].szRoutineName = rName;

if(rName.Length() > 4 && rName.SubString(rName.Length() - 4,4)

== "上行") pRoutine[nCount].nFlag = 1;

else if(rName.Length() > 4 && rName.SubString(rName.Length()

- 4,4) == "下行") pRoutine[nCount].nFlag = 2;

else

pRoutine[nCount].nFlag = 0;

nCount++;

nSCount = 0; sNames[nSCount] = sName;

nSCount = 1;

} else

{

if(pRoutine[nCount-1].szRoutineName == rName) {

sNames[nSCount] = sName;

nSCount++; }

else

{ //结束上一站

pRoutine[nCount-1].nStationNumber = nSCount;

pRoutine[nCount-1].szStaionName = new String [nSCount]; for(short i=0; i<nSCount; i++)

pRoutine[nCount-1].szStaionName[i] = sNames[i];

if(rName.Length() > 4 && rName.SubString(rName.

Length() - 4,4) == "上行")

pRoutine[nCount].nFlag = 1; else if(rName.Length() > 4 && rName.SubString(

rName.Length() – 4,4) == "下行")

pRoutine[nCount].nFlag = 2; else

Page 258: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

250

pRoutine[nCount].nFlag = 0;

pRoutine[nCount].szRoutineName = rName; nCount++;

nSCount = 0;

sNames[nSCount] = sName; nSCount = 1;

}

}

dataSet->Next();

}

pRoutine[nCount-1].nStationNumber = nSCount;

pRoutine[nCount-1].szStaionName = new String [nSCount]; for(short i=0;i<nSCount;i++)

pRoutine[nCount-1].szStaionName[i]= sNames[i];

dataSet->First();

dataSet->Close();

delete dataSet; dataSet = NULL;

MainForm->_environment->m_DataConnection->Close(); }

catch(EOleException& e)

{ MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

return nCount;

}

//--------------------------------------------------------------------- short TPath::BuildStationIndex(Routine* pAllRoutines, short nSize, Station*

pStations)

{ short nCount = 0;

for(short i=0;i<nSize;i++)

{ for(short j=0;j<pAllRoutines[i].nStationNumber;j++)

{

short k; for( k=0;k<nCount;k++)

{

if(pStations[k].szStationName == pAllRoutines[i].szStaionName[j])break;

Page 259: C++ Builder和MapObjects实现

251

第 6章 选择与查询功能的实现

}

if(k==nCount) {

//添加一个新站

pStations[k].szStationName = pAllRoutines[i].szStaionName[j];

pStations[k].nRoutineNumber = 1;

nCount++; }

else

pStations[k].nRoutineNumber++; }

}

//为各个站的信息分配内存

for(int i=0;i<nCount;i++)

{ pStations[i].pnRoutineID = new short[pStations[i].nRoutineNumber];

pStations[i].pnOrder = new short[pStations[i].nRoutineNumber];

pStations[i].nRoutineNumber = 0; //下面重新数 }

//重新设置信息 for(int i=0;i<nSize;i++)

{

for(short j=0;j<pAllRoutines[i].nStationNumber;j++) {

short k;

for(k=0;k<nCount;k++) {

if(pStations[k].szStationName ==

pAllRoutines[i].szStaionName[j]) break;

}

pStations[k].pnRoutineID[pStations[k].nRoutineNumber] =

(short)i;

pStations[k].pnOrder[pStations[k].nRoutineNumber] = j; pStations[k].nRoutineNumber++;

}

}

return nCount;

} //---------------------------------------------------------------------

Page 260: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

252

BusSearchClick函数中利用了BusForm窗体,我们需要创建该窗体。选择File菜单的New Form命令,在当前项目中加入一个窗体,将该窗体命名为BusForm,将其单元文件保存为

Bus.cpp。在窗体BusForm中加入2个Label控件,分别命名为StartLabel与EndLabel,然后加

入2个Edit控件,分别命名为StartEdit与EndEdit,再加入2个ListBox控件,分别命名为

StartListBox与EndListBox, 后加入一个Button控件,命名为QueryBtn。窗体BusForm在设

计期间的界面如图6.13所示。

图 6.13 窗体 BusForm 在设计期间的界面

按表6.5所示设置窗体BusForm及其控件的属性。

表6.5 窗体BusForm及其控件的属性

控件 属性 属性值

BorderIcons.biMinimize false

BorderIcons.biMaximize false

BorderStyle bsSizeToolWin

BusForm

Caption 选择乘车路线

StartLabel Caption 起点站

EndLabel Caption 终点站

StartEdit Text

EndEdit Text

StartListBox MultiSelect false

EndListBox MultiSelect false

QueryBtn Caption 查询

在Bus.cpp文件的头部加入如下两行代码,用于引用MainForm外部变量:

//---------------------------------------------------------------------

#include "main.h"

extern TMainForm *MainForm; //---------------------------------------------------------------------

Page 261: C++ Builder和MapObjects实现

253

第 6章 选择与查询功能的实现

创建窗体BusForm的OnShow事件响应函数,在其中加入如下代码,用于在显示该窗体

时,在两个列表框中显示所有站点的名称:

//---------------------------------------------------------------------

void __fastcall TBusForm::FormShow(TObject *Sender)

{ AnsiString strSQL = "Select distinct 站名 From 公交车站路线 Order By 站名

";

TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = MainForm->_environment->m_DataConnection;

dataSet->CommandText = strSQL;

try

{

// 执行查询 dataSet->Open();

// 将查询结果显示在列表框中

dataSet->First(); for(int i=0; i<dataSet->RecordCount; i++)

{

AnsiString str = dataSet->Fields->FieldByName("站名")->AsString; StartListBox->Items->Add(str);

dataSet->Next(); }

dataSet->First(); dataSet->Close();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

strSQL = "Select * From 公交车站";

dataSet->CommandText = strSQL;

try

{ // 执行查询

dataSet->Open();

// 将查询结果显示在列表框中 dataSet->First();

for(int i=0; i<dataSet->RecordCount; i++)

{ AnsiString str = dataSet->Fields->FieldByName("站名")->AsString;

EndListBox->Items->Add(str);

Page 262: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

254

dataSet->Next();

}

dataSet->First();

dataSet->Close(); delete dataSet;

dataSet = NULL;

MainForm->_environment->m_DataConnection->Close();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

} }

//---------------------------------------------------------------------

创建StartEdit与EndEdit控件的OnChanged事件响应函数,在其中加入如下代码,用于实

现在列表框中查找用户在文本框中输入的站点名称:

//--------------------------------------------------------------------- void __fastcall TBusForm::StartEditChange(TObject *Sender)

{

for(int i=0; i<StartListBox->Items->Count; i++) {

if(StartListBox->Items->Strings[i].AnsiCompare(StartEdit->Text) >=

0) {

StartListBox->ItemIndex = i;

return; }

}

} //---------------------------------------------------------------------

void __fastcall TBusForm::EndEditChange(TObject *Sender)

{ for(int i=0; i<EndListBox->Items->Count; i++)

{

if(EndListBox->Items->Strings[i].AnsiCompare(EndEdit->Text) >= 0) {

EndListBox->ItemIndex = i;

return; }

}

} //---------------------------------------------------------------------

Page 263: C++ Builder和MapObjects实现

255

第 6章 选择与查询功能的实现

创建StartListBox与EndListBox控件的OnClick事件响应函数,在其中加入如下代码,用

于实现当用户在列表框中选择站点名称时,将选择的站点名称显示到文本框中:

//---------------------------------------------------------------------

void __fastcall TBusForm::StartListBoxClick(TObject *Sender)

{ StartEdit->Text = StartListBox->Items->

Strings[StartListBox->ItemIndex];

} //---------------------------------------------------------------------

void __fastcall TBusForm::EndListBoxClick(TObject *Sender)

{ EndEdit->Text = EndListBox->Items->Strings[EndListBox->ItemIndex];

}

//---------------------------------------------------------------------

创建“查询”按钮的Click事件响应函数,在该函数中实现公交乘车路线查询工作。实

现代码如下:

//---------------------------------------------------------------------

void __fastcall TBusForm::QueryBtnClick(TObject *Sender)

{ if(EndEdit->Text == "" || StartEdit->Text == "")

return;

//判断站名的正确性

if (!IsValidStation(StartEdit->Text))

{ MessageBox(NULL, "错误的起始站名","北京市地理信息公众查询系统",

MB_OK);

return; }

if (!IsValidStation(EndEdit->Text))

{ MessageBox(NULL, "错误的起始站名","北京市地理信息公众查询系统",

MB_OK);

return; }

Cursor = crSQLWait; TList* array = new TList();

MainForm->_environment->m_pPath->Search(StartEdit->Text,

EndEdit->Text, array); Cursor = crDefault;

if (array->Count ==0) {

Page 264: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

256

MessageBox(NULL, "没有合适的乘车路线。",

"北京市地理信息公众查询系统", MB_OK);

return; }

else

{ TBusResultForm* frm = new TBusResultForm(MainForm);

frm->m_arrayResult = array;

Visible = false; frm->ShowModal();

Close();

} }

//---------------------------------------------------------------------

上述代码首先调用IsValidStation函数判断用户在两个文本框中输入的字符串是否是有

效的站点名称,然后调用TPath类的Search函数执行查询,并将查询结果保存在TList类型的

array变量中, 后利用TBusResultForm类型的窗体显示该查询结果。 IsValidStation函数的实现代码如下:

//--------------------------------------------------------------------- bool __fastcall TBusForm::IsValidStation(String szStation)

{

AnsiString strSQL = "Select distinct 站名 From 公交车站路线 where 站名='"; strSQL += szStation + "' ";

strSQL += "Order By 站名";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = MainForm->_environment->m_DataConnection;

dataSet->CommandText = strSQL;

bool result = false;

try {

// 执行查询

dataSet->Open(); if(dataSet->RecordCount > 0)

result = true;

dataSet->First();

dataSet->Close();

delete dataSet; dataSet = NULL;

MainForm->_environment->m_DataConnection->Close(); }

catch(EOleException& e)

Page 265: C++ Builder和MapObjects实现

257

第 6章 选择与查询功能的实现

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

}

return result;

} //---------------------------------------------------------------------

关于TPath类的Search方法的实现原理与技术在第4章有详细的描述。Search方法及其调

用的其他方法的代码如下:

//---------------------------------------------------------------------

TList* TPath::Search(String sz1, String sz2,TList* array) {

Search(m_pRoutine, m_nRCount, m_pStations, m_nSCount, sz1, sz2, array);

return array; }

//---------------------------------------------------------------------

short TPath::Search(Routine* pAllRoutines, short nSize, Station* pStations, short nSCount, String szFrom, String szTo, TList* pResPath)

{

short nCount = 0; short nStationOrder;

Node* nodeCurrent;

TQueue* queueNodes = new TQueue(); //存储需要访问的结点 short nRoutineCurrent;

short nStationCurrent;

short i; short nMinChangeTimes=1000; //第一次找到的换乘次数

//先得到起点站的线路 for(i=0; i<nSize; i++)

{

nStationOrder = HasStation( pAllRoutines[i],szFrom,0,0); if(nStationOrder >= 0)

{

nodeCurrent = new Node(); nodeCurrent->nNodeNumber = 1;

nodeCurrent->nRoutineOrder = new short [10];

nodeCurrent->nStationOrder = new short [10];

nodeCurrent->nRoutineOrder[0] = i;

nodeCurrent->nStationOrder[0] = nStationOrder;

//入队列操作

queueNodes->Push(nodeCurrent); }

Page 266: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

258

}

while( !(0 == queueNodes->Count()) ) {

//得到队列头

nodeCurrent = (Node*)queueNodes->Pop();

if((nodeCurrent->nNodeNumber>=TIMELIMIT+1)

|| (nodeCurrent->nNodeNumber >nMinChangeTimes-1) || (queueNodes->Count() >1500000)) //换乘次数限制

break;

//从队头站点序列的 后一个站开始检索是否能够找到到站

nRoutineCurrent = nodeCurrent->

nRoutineOrder[nodeCurrent->nNodeNumber-1]; nStationCurrent = nodeCurrent->nStationOrder[nodeCurrent->

nNodeNumber-1];

nStationOrder = HasStation( pAllRoutines[nRoutineCurrent], szTo,

nStationCurrent, pAllRoutines[nRoutineCurrent].nFlag);

if(nStationOrder >=0 ) //表示找到 {

nodeCurrent->nRoutineOrder[nodeCurrent->nNodeNumber] =

nRoutineCurrent; nodeCurrent->nStationOrder[nodeCurrent->nNodeNumber] =

nStationOrder;

nodeCurrent->nNodeNumber++;

if(nodeCurrent->nNodeNumber > nMinChangeTimes)

break;

PathNode* pPathTempNode = new PathNode();

pPathTempNode->nSegNumber = (short)(nodeCurrent->nNodeNumber-1);

pPathTempNode->szRoutineName = new

String[pPathTempNode->nSegNumber]; pPathTempNode->szFromStationName = new

String[pPathTempNode->nSegNumber];

pPathTempNode->szToStationName = new String[pPathTempNode->nSegNumber];

for(i=0;i<pPathTempNode->nSegNumber;i++)

{ pPathTempNode->szRoutineName[i] =

pAllRoutines[nodeCurrent->nRoutineOrder[i]].szRoutineName;

pPathTempNode->szFromStationName[i] = pAllRoutines[nodeCurrent

Page 267: C++ Builder和MapObjects实现

259

第 6章 选择与查询功能的实现

>nRoutineOrder[i]].szStaionName[nodeCurrent->

nStationOrder[i]];

pPathTempNode->szToStationName[i] = pAllRoutines[nodeCurrent->

nRoutineOrder[i+1]].szStaionName[nodeCurrent->

nStationOrder[i+1]]; }

pResPath->Add(pPathTempNode);

if(nCount == 0)

nMinChangeTimes = nodeCurrent->nNodeNumber;

nCount++; if(nCount > 20)

break;

} else //要将所有可能的路径加入队列

{

for(i=(short)(nStationCurrent+1); i < pAllRoutines[nRoutineCurrent].nStationNumber; i++)

{

String szTheStation; szTheStation =

pAllRoutines[nRoutineCurrent].szStaionName[i];

short j = SearchStation(pStations,nSCount,szTheStation);

for(short k=0;k<pStations[j].nRoutineNumber;k++) {

short l;

for(l=0;l<nodeCurrent->nNodeNumber;l++) if(pStations[j].pnRoutineID[k] ==

nodeCurrent->nRoutineOrder[l])

break; if(l<nodeCurrent->nNodeNumber) //说明已经处理过该线路

continue;

Node* nodeTemp = new Node();

nodeTemp->nNodeNumber = (short)

(nodeCurrent->nNodeNumber+1); nodeTemp->nRoutineOrder = new short [10];

nodeTemp->nStationOrder = new short [10];

for(l=0;l<nodeCurrent->nNodeNumber;l++) {

nodeTemp->nRoutineOrder[l] =

nodeCurrent->nRoutineOrder[l]; nodeTemp->nStationOrder[l] =

Page 268: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

260

nodeCurrent->nStationOrder[l];

}

nodeTemp->nRoutineOrder[nodeCurrent->nNodeNumber] = pStations[j].pnRoutineID[k];

nodeTemp->nStationOrder[nodeCurrent->nNodeNumber]

= pStations[j].pnOrder[k]; queueNodes->Push(nodeTemp);

}

}

// 双向线路,需要两个方向查询

if( pAllRoutines[nRoutineCurrent].nFlag==0 ) {

for(i=(short)(nStationCurrent-1);i>=0;i--)

{ String szTheStation;

szTheStation =

pAllRoutines[nRoutineCurrent].szStaionName[i];

short j = SearchStation(pStations,nSCount,szTheStation);

for(short k=0; k<pStations[j].nRoutineNumber; k++) {

short l;

for(l=0;l<nodeCurrent->nNodeNumber;l++) if(pStations[j].pnRoutineID[k] ==

nodeCurrent->nRoutineOrder[l])

break; if(l<nodeCurrent->nNodeNumber)

//说明已经处理过该线路

continue; Node* nodeTemp = new Node();

nodeTemp->nNodeNumber = (short)

(nodeCurrent->nNodeNumber + 1); nodeTemp->nRoutineOrder = new short [10];

nodeTemp->nStationOrder = new short [10];

for(l=0;l<nodeCurrent->nNodeNumber;l++) {

nodeTemp->nRoutineOrder[l] =

nodeCurrent->nRoutineOrder[l]; nodeTemp->nStationOrder[l] =

nodeCurrent->nStationOrder[l];

} nodeTemp->nRoutineOrder[nodeCurrent->nNodeNumber] =

pStations[j].pnRoutineID[k];

nodeTemp->nStationOrder[nodeCurrent->nNodeNumber] = pStations[j].pnOrder[k];

Page 269: C++ Builder和MapObjects实现

261

第 6章 选择与查询功能的实现

queueNodes->Push(nodeTemp);

}

} }

}

}

return nCount;

} //---------------------------------------------------------------------

//根据站名得到它是一条公交线路上的第几站,基数为0,-1表示没有

//nSearchFrom为开始搜寻的站序号 //nFlag标记向那个方向搜索,0表示双方向,1表示单向

short TPath::HasStation(Routine theRoutine, String szStation,

short nSearchFrom, short nFlag) {

if( nFlag != 0 )

{ for(short i=nSearchFrom; i<theRoutine.nStationNumber; i++)

if(theRoutine.szStaionName[i] == szStation)

return i; }

else if( nFlag == 0 )

{ for(short i=nSearchFrom; i<theRoutine.nStationNumber; i++)

//正向搜索

if(theRoutine.szStaionName[i] == szStation) return i;

for(short i=nSearchFrom; i>=0; i--) //反向搜索

if(theRoutine.szStaionName[i] == szStation) return i;

}

return -1;

}

//--------------------------------------------------------------------- short TPath::SearchStation(Station* pStations, short nSCount, String

theName)

{ short j;

//用于统计时间

for(j=0; j<nSCount; j++) {

if(pStations[j].szStationName == theName)

{ return j;

Page 270: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

262

}

}

return j; }

//---------------------------------------------------------------------

在QueryBtnClick函数中还调用了BusResultForm窗体,该窗体用于显示查询结果。选择

File菜单的New Form命令,在当前项目中加入一个新窗体。将该窗体命名为BusResultForm,

将其单元文件保存为BusResult.cpp文件。在窗体BusResultForm中加入一个Label控件与一个

ListBox控件,分别命名为NumLabel与ResultListBox。 窗体BusResultForm在设计期间的界面如图6.14所示。

图 6.14 窗体 BusResultForm 在设计期间的界面

按照表6.6所示设置窗体BusResultForm及其控件的属性。

表6.6 窗体BusResultForm及其控件的属性

控件 属性 属性值

BorderIcons.biMinimize false

BorderIcons.biMaximize false

BorderStyle bsSizeToolWin

BusResultForm

Caption 乘车路线查询结果

Align alTop

Alignment taCenter

NumLabel

Caption 方案数目:

ResultListBox Align alClient

在窗体BusResultForm中加入如下成员变量:

public:

TList* m_arrayResult;

Page 271: C++ Builder和MapObjects实现

263

第 6章 选择与查询功能的实现

在BusResultForm窗体的构造函数中初始化该成员变量,代码如下:

//---------------------------------------------------------------------

__fastcall TBusResultForm::TBusResultForm(TComponent* Owner)

: TForm(Owner) {

m_arrayResult = NULL;

} //---------------------------------------------------------------------

在BusResult.cpp文件的头部加入如下两行代码,用于引用MainForm外部变量:

//---------------------------------------------------------------------

#include "main.h" extern TMainForm *MainForm;

//---------------------------------------------------------------------

创建窗体BusResultForm的OnShow事件响应函数,在其中加入如下代码,用于在显示

该窗体时,在列表框中显示所有乘车方案:

//--------------------------------------------------------------------- void __fastcall TBusResultForm::FormShow(TObject *Sender)

{

NumLabel->Caption="总共查询到"+IntToStr(m_arrayResult->Count)+"种方案";

ResultListBox->Items->Clear();

int index = 1; for (int i = 0; i < m_arrayResult->Count; i ++)

{

PathNode node = *(PathNode*)m_arrayResult->Items[i]; String str;

str = "第" + IntToStr(index) + "方案:";

for (int j = 0; j < node.nSegNumber; j ++)

{

if (j > 0) str += ",转车,坐";

else

str +="坐";

str += node.szRoutineName[j];

str += "车,从"; str += node.szFromStationName[j];

str += "到";

str += node.szToStationName[j]; }

ResultListBox->Items->Add(str);

Page 272: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

264

index ++;

}

} //---------------------------------------------------------------------

创建ResultListBox控件的OnDblClick事件响应函数,在其中加入如下代码,用于实现

当用户双击某路线名称时,在地图中闪烁显示该公交路线:

//---------------------------------------------------------------------

void __fastcall TBusResultForm::ResultListBoxDblClick(TObject *Sender) {

if (ResultListBox->ItemIndex < 0 )

return;

PathNode* node =

(PathNode*)m_arrayResult->Items[ResultListBox->ItemIndex]; MainForm->_environment->m_drawLine = new MLine[node->nSegNumber];

MainForm->_environment->m_buses = new Buses(); int nCount = 0;

bool bBus = true;

for (int j = 0; j < node->nSegNumber; j ++)

{

IMoLinePtr moline = MainForm->_environment-> GetLine( node->szRoutineName[j]);

if (!moline)

{ MainForm->_environment->m_drawLine = NULL;

MainForm->_environment->m_buses = NULL;

MainForm->Map->Extent = MainForm->Map->Extent;

return;

}

IMoPointPtr pt;

MPoint pt1, pt2;

pt = MainForm->_environment->GetPoint(node->szFromStationName[j]);

if (!pt) {

MainForm->_environment->m_drawLine = NULL;

MainForm->_environment->m_buses = NULL; MainForm->Map->Extent = MainForm->Map->Extent;

return;

} pt1.x = pt->X ;

Page 273: C++ Builder和MapObjects实现

265

第 6章 选择与查询功能的实现

pt1.y = pt->Y ;

pt = MainForm->_environment->GetPoint(node->szToStationName[j]); if (!pt)

{

MainForm->_environment->m_drawLine = NULL; MainForm->_environment->m_buses = NULL;

MainForm->Map->Extent = MainForm->Map->Extent;

return; }

pt2.x = pt->X ; pt2.y = pt->Y ;

MLine* line = MainForm->_environment->CreateLine(moline); TBusLine* busLine = new TBusLine();

//MainForm->_environment->m_drawLine[j] = new MLine();

busLine->CutLine(line, pt1, pt2, &MainForm->_environment->m_drawLine[j]);

if (bBus) bBus = MainForm->_environment->GetStation (node, j,

MainForm->_environment->m_buses, &nCount);

}

if (!bBus)

MainForm->_environment->m_buses = NULL; else

MainForm->_environment->m_buses->nNum = nCount;

MainForm->Map->CenterAt(MainForm->_environment->

m_drawLine[0].pPoint[0].x,

MainForm->_environment->m_drawLine[0].pPoint[0].y); MainForm->Map->RefreshLayer(0);

MainForm->EyeMap->Extent = MainForm->EyeMap->Extent ;

} //---------------------------------------------------------------------

上述函数调用了TEnvironment类的GetLine、GetPoint与CreateLine方法。这些方法的代

码如下:

//---------------------------------------------------------------------

IMoLinePtr TEnvironment::GetLine(String szName) {

IMoLinePtr line;

String szLayer = GetLayerName(szName, "地名索引");

Page 274: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

266

String szTable = GetTableName(szName,"地名索引");

if (szTable == "") {

return line;

}

int nIndex = GetLayerIndexByName(szTable);

if (nIndex < 0) return line;

IMoRecordsetPtr rs; rs = m_layerInfos[nIndex].layer->SearchExpression(WideString("

名称 like '" + szName + "'"));

if (rs)

{

rs->MoveFirst(); if (! (bool)rs->EOF)

{

switch (m_layerInfos[nIndex].layer->shapeType) {

case moShapeTypePoint:

return line; case moShapeTypeLine:

line = rs->Fields->Item("shape")->Value;

break; case moShapeTypePolygon:

return line;

default: break;

}

} }

return line; }

//---------------------------------------------------------------------

IMoPointPtr TEnvironment::GetPoint(String szName) {

IMoPointPtr pt;

String szLayer = GetLayerName(szName,"地名索引");

String szTable = GetTableName(szName,"地名索引");

if (szTable == "")

Page 275: C++ Builder和MapObjects实现

267

第 6章 选择与查询功能的实现

{

return pt;

}

int nIndex = GetLayerIndexByName(szTable);

if (nIndex < 0) return pt;

IMoRecordsetPtr rs; rs = m_layerInfos[nIndex].layer->SearchExpression(WideString(" 名 称

like '" + szName + "'"));

if (rs)

{

rs->MoveFirst(); if (! (bool)rs->EOF)

{

switch (m_layerInfos[nIndex].layer->shapeType) {

case moShapeTypePoint:

{ pt = rs->Fields->Item("shape")->Value;

break;

} case moShapeTypeLine:

{

IMoLinePtr line; line = rs->Fields->Item("shape")->Value;

pt = line->Extent->Center;

break; }

case moShapeTypePolygon:

{ IMoPolygonPtr poly;

poly = rs->Fields->Item("shape")->Value;

pt = poly->Extent->Center; break;

}

default: break;

}

} }

return pt; }

Page 276: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

268

//---------------------------------------------------------------------

MLine* TEnvironment::CreateLine(IMoLinePtr moline)

{ MLine* line = new MLine();

IMoPointsPtr pts = moline->Parts->Item(0);

line->nPointNumber = pts->Count;

line->pPoint = new MPoint[line->nPointNumber];

for (int i = 0; i < line->nPointNumber; i++)

{

IMoPointPtr pt = pts->Item(i); line->pPoint[i].x = pt->X;

line->pPoint[i].y = pt->Y;

}

return line;

} //---------------------------------------------------------------------

ResultListBoxDblClick函数中还应用了TBusLine类。TBusLine类用于将某线段截断,该

类又利用了另一个名为TGisSegLine的类。 选择File菜单的New命令,打开New Items对话框,在该对话框的New选项卡中选择Unit

图标,在当前项目中加入一个单元文件,将该单元文件保存为BusLine.cpp。 在BusLine.h文件中首先加入如下代码,用于声明TGisSegLine类:

//---------------------------------------------------------------------

class TGisSegLine {

public:

MPoint* m_ptStartPoint ; MPoint* m_ptEndPoint ;

public:

TGisSegLine(); ~TGisSegLine();

int GetDistance(MPoint* point, MPoint* ptHFoot, double* distance );

}; //---------------------------------------------------------------------

TGisSegLine类的实现代码如下:

//---------------------------------------------------------------------

TGisSegLine::TGisSegLine() {

m_ptStartPoint = new MPoint() ;

m_ptEndPoint = new MPoint(); }

Page 277: C++ Builder和MapObjects实现

269

第 6章 选择与查询功能的实现

//---------------------------------------------------------------------

TGisSegLine::~TGisSegLine()

{ delete m_ptStartPoint;

m_ptStartPoint = NULL;

delete m_ptEndPoint; m_ptEndPoint = NULL;

}

//--------------------------------------------------------------------- int TGisSegLine::GetDistance(MPoint* point, MPoint* ptHFoot,

double* distance )

{ double Px,Py,Ax,Ay,Bx,By;

const double ZERODIST=0.00000001;

double AB2,PA2,PB2,AB,PA,PB,S,AREA; double med,k1,k2,b1,b2;

Px = point->x;

Py = point->y; Ax = m_ptStartPoint->x;

Ay = m_ptStartPoint->y;

Bx = m_ptEndPoint->x; By = m_ptEndPoint->y;

AB2 = (Ax-Bx)*(Ax-Bx)+(Ay-By)*(Ay-By);

PB2 = (Px-Bx)*(Px-Bx)+(Py-By)*(Py-By); PA2 = (Ax-Px)*(Ax-Px)+(Ay-Py)*(Ay-Py);

if(AB2 < ZERODIST)

{ med = sqrt(PA2);

*distance = med;

ptHFoot->x = Ax; ptHFoot->y = Ay;

return -1 ;

}

if(PA2 < ZERODIST)

{ med = sqrt(PA2);

*distance = med;

ptHFoot->x = Ax; ptHFoot->y = Ay;

return -2 ;

}

if(PB2 < ZERODIST)

{ med = sqrt(PB2);

Page 278: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

270

*distance = med;

ptHFoot->x = Bx;

ptHFoot->y = By; return -2 ;

}

if(PA2 + AB2 < PB2 || AB2 + PB2 < PA2)

{

if(PA2 > PB2) {

med = PB2;

ptHFoot->x = Bx; ptHFoot->y = By;

}

else {

med = PA2;

ptHFoot->x = Ax; ptHFoot->y = Ay;

}

med = sqrt(med);

*distance = med;

return -3 ; }

else

{ AB = sqrt(AB2);

PA = sqrt(PA2);

PB = sqrt(PB2); S = (AB+PA+PB)/2.0;

AREA = S;

AREA *= (S-PA); AREA *= (S-PB);

AREA *= (S-AB);

AREA = sqrt(AREA); med = (2.0*AREA)/AB;

*distance = med;

med = Ay-By;

if(med == 0.0)

{ ptHFoot->x = Px;

ptHFoot->y = Ay;

return -4 ; }

Page 279: C++ Builder和MapObjects实现

271

第 6章 选择与查询功能的实现

med = Ax-Bx;

if(med == 0.0) {

ptHFoot->y = Py;

ptHFoot->x = Ax; return -4 ;

}

k1 = (Ay-By)/(Ax-Bx);

k2 = -1.0/k1;

b1 = Ay-k1*Ax; b2 = Py-k2*Px;

S = (b2-b1)/(k1-k2);

ptHFoot->x = S; S = k1*S+b1;

ptHFoot->y = S;

return 0; }

}

//---------------------------------------------------------------------

TBusLine类的声明如下:

//---------------------------------------------------------------------

class TBusLine

{ public:

TBusLine();

void CutLine(MLine* LineSrc, MPoint pt1,MPoint pt2, MLine* LineRes); };

//---------------------------------------------------------------------

TBusLine类的实现代码如下:

//--------------------------------------------------------------------- TBusLine::TBusLine()

{

} //---------------------------------------------------------------------

void TBusLine::CutLine(MLine* LineSrc, MPoint pt1,MPoint pt2, MLine*

LineRes) {

//计算到线段的距离

double dMinDistance1 = 10000.0; double dMinDistance2 = 10000.0;

double dTheDis = 0.0;

MPoint ptRealFrom,ptRealTo;

Page 280: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

272

MPoint ptTemp;

int nPointOrderInMLine1=0,nPointOrderInMLine2=0;

TGisSegLine* SegMLine = new TGisSegLine();

for(int i=0; i<LineSrc->nPointNumber-1; i++)

{ SegMLine->m_ptStartPoint->x = LineSrc->pPoint[i].x;

SegMLine->m_ptStartPoint->y = LineSrc->pPoint[i].y;

SegMLine->m_ptStartPoint->x = LineSrc->pPoint[i+1].x; SegMLine->m_ptStartPoint->y = LineSrc->pPoint[i+1].y;

SegMLine->GetDistance(&pt1, &ptTemp, &dTheDis); if(dTheDis < dMinDistance1)

{

dMinDistance1 = dTheDis; nPointOrderInMLine1 = i;

ptRealFrom = ptTemp;

}

SegMLine->GetDistance(&pt2, &ptTemp, &dTheDis);

if(dTheDis < dMinDistance2) {

dMinDistance2 = dTheDis;

nPointOrderInMLine2 = i; ptRealTo = ptTemp;

}

}

if(nPointOrderInMLine2 - nPointOrderInMLine1 > 0 )

{ LineRes->nPointNumber=nPointOrderInMLine2 - nPointOrderInMLine1 +2;

LineRes->pPoint = new MPoint[LineRes->nPointNumber];

int i; LineRes->pPoint[0] = pt1;

for(i=1; i<LineRes->nPointNumber-1; i++)

{ LineRes->pPoint[i] = LineSrc->pPoint[nPointOrderInMLine1+i];

}

LineRes->pPoint[i] = pt2; }

else

{ LineRes->nPointNumber = nPointOrderInMLine1-nPointOrderInMLine2+2;

LineRes->pPoint = new MPoint[LineRes->nPointNumber];

int i; LineRes->pPoint[0] = pt2;

Page 281: C++ Builder和MapObjects实现

273

第 6章 选择与查询功能的实现

for(i=1; i<LineRes->nPointNumber-1; i++)

{

LineRes->pPoint[i] = LineSrc->pPoint[nPointOrderInMLine2+i]; }

LineRes->pPoint[i] = pt1;

} }

//---------------------------------------------------------------------

编译并运行程序,切换到“查询”页面的“公交查询”选项卡,单击“选择乘车路线”

按钮,将显示“选择乘车路线”对话框,输入两个地名,便可查询两者之间的换乘方案。

图6.15显示的是从“中关园”到“北京西站”的乘车方案。

图 6.15 从“中关园”到“北京西站”的乘车方案

6.6 地 名 索 引

地名索引功能主要实现查找指定地名的地物,在本系统中,是通过“查询”页面的“地

名索引”选项卡来提供该功能。 首先切换到main.h文件,在MainForm类中加入如下所示的两个成员变量:

String m_strIndexType; String m_strIndexSubType;

在MainForm类中首先加入如下函数,初始化“地名索引”选项卡中控件的属性:

Page 282: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

274

//---------------------------------------------------------------------

void __fastcall TMainForm::InitIndexPanel()

{ //初始化大类过滤器

LoadFilter(IndexFirstClass);

IndexFirstClass->ItemIndex = 0; m_strIndexType = NOFILTER;

//初始化中类过滤器 LoadFilter2(IndexSecondClass, NOFILTER, false);

IndexSecondClass->ItemIndex = 0;

m_strIndexSubType = NOSUBFILTER;

Index_LoadData(NOFILTER, NOSUBFILTER);

} //---------------------------------------------------------------------

InitIndexPanel函数调用了LoadData函数,该函数用于在列表框中显示所有的地名,代

码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::Index_LoadData(String szType, String szSubType) {

IndexNameList->Items->Clear();

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + _environment->m_szDBName + ";

Persist Security Info=False"; TADOConnection* myConnection = new TADOConnection(this);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{ myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return; }

String szFilter = ""; if (szSubType == NOSUBFILTER)

{

if(szType != NOFILTER) {

Page 283: C++ Builder和MapObjects实现

275

第 6章 选择与查询功能的实现

szFilter = "类型='" + szType + "' ";

}

} else

{

szFilter = "中类型='" + szSubType + "' "; }

String szSQL; if (szFilter == "")

szSQL = "Select * From 地名索引 Where 名称 is not null Order By 名称";

else szSQL = "Select * From 地名索引 Where " + szFilter +

" and 名称 is not null Order By 名称";

TADODataSet* dataSet = new TADODataSet(this);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{ dataSet->Open();

dataSet->First();

for(int i=0; i<dataSet->RecordCount; i++)

{

AnsiString strValue = dataSet->Fields-> FieldByName("名称")->AsString;

IndexNameList->Items->Add(strValue);

dataSet->Next(); }

dataSet->First(); dataSet->Close();

delete dataSet;

dataSet = NULL;

myConnection->Close();

delete myConnection; myConnection = NULL;

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return; }

Page 284: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

276

}

//---------------------------------------------------------------------

然后在MainForm的OnCreate事件响应函数的尾部加入对InitIndexPanel函数的调用。 创建IndexFirstClass控件及IndexSecondClass控件的OnChange事件响应函数,在其中加

入如下代码,用于过滤在列表框中显示的地物名称:

//--------------------------------------------------------------------- void __fastcall TMainForm::IndexFirstClassChange(TObject *Sender)

{

String szType = IndexFirstClass-> Items->Strings[IndexFirstClass->ItemIndex];

if (szType == m_strIndexType)

return;

m_strIndexType = szType;

m_strIndexSubType = ""; LoadFilter2(IndexSecondClass, szType, false);

IndexSecondClass->ItemIndex = 0;

Index_LoadData(m_strIndexType, NOSUBFILTER);

}

//--------------------------------------------------------------------- void __fastcall TMainForm::IndexSecondClassChange(TObject *Sender)

{

String szSubType = IndexSecondClass->Items-> Strings[IndexSecondClass->ItemIndex];

if (m_strIndexSubType == szSubType)

return;

m_strIndexSubType = szSubType;

Index_LoadData(m_strIndexType, szSubType); }

//---------------------------------------------------------------------

上述代码在列表框中显示过滤后的地物名称,下面要实现的是根据用户在文本框中输

入的地名迅速定位对应的地物。创建IndexNameEdit控件的OnChange事件响应函数,在其中

加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::IndexNameEditChange(TObject *Sender)

{

for(int i=0; i<IndexNameList->Items->Count; i++) {

if(IndexNameList->Items->Strings[i].AnsiCompare(

IndexNameEdit->Text) >= 0) {

Page 285: C++ Builder和MapObjects实现

277

第 6章 选择与查询功能的实现

IndexNameList->ItemIndex = i;

return;

} }

}

//---------------------------------------------------------------------

上述代码只是在列表框中高亮显示用户在文本框中输入的地名,并没有在地图上高亮

显示地名对应的地物。这需要用户在列表框中双击该地名,然后,系统就会在地图上闪烁

显示对应的地物,并高亮显示之。实现这项功能的函数是_index_listBox控件的DoubleClick事件响应函数,代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::IndexNameListDblClick(TObject *Sender)

{ if(IndexNameList->ItemIndex < 0)

return;

AnsiString str =

IndexNameList->Items->Strings[IndexNameList->ItemIndex];

Position(str); }

//---------------------------------------------------------------------

上述代码调用了Position函数来实现定位功能。Position函数的代码如下:

//--------------------------------------------------------------------- // 功能:定位指定地名的位置

// 参数:[in]string szName 地名名称

void __fastcall TMainForm::Position(String szName) {

_environment->m_drawLine = NULL;

_environment->m_buses = NULL; _environment->m_selectedFeature = NULL;

IMoRecordsetPtr rs;

//公交路线

if (_environment->IsBusLine(Map, szName)) {

IMoMapLayerPtr ly = _environment->

GetLayerByName(_environment->BUSLINE_LAYERNAME); rs = ly->SearchExpression(WideString("名称 like '"+szName +"'"));

if (rs) {

rs->MoveFirst();

Page 286: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

278

if (!(bool)rs->EOF)

{

IMoPointPtr pt; IMoLinePtr line;

line = rs->Fields->Item("shape")->Value;

pt = line->Extent->Center;

if (!IsWithin(Map->Extent, pt))

{ Map->CenterAt(pt->X, pt->Y );

}

//公交路线

//设置_environment.m_drawLine和_environment.m_buses

IMoPointsPtr pts; pts = line->Parts->Item(0);

_environment->m_drawLine = NULL; _environment->m_nSelectedLineNum = 1;

_environment->m_drawLine = new MLine[1];

_environment->m_drawLine[0].nPointNumber = pts->Count; _environment->m_drawLine[0].pPoint = new MPoint[pts->Count];

for (int i = 0; i < pts->Count; i ++) {

pt = pts->Item(i);

_environment->m_drawLine[0].pPoint[i].x = pt->X; _environment->m_drawLine[0].pPoint[i].y = pt->Y;

}

_environment->m_buses = new Buses();

int nCount = 0;

if(!_environment->GetStation(szName, _environment->m_buses, &nCount))

_environment->m_buses = NULL;

else _environment->m_buses->nNum = nCount;

Map->FlashShape(rs->Fields->Item("shape")->Value, 4); Map->Extent = Map->Extent;

EyeMap->Extent = EyeMap->Extent;

} }

return;

}

Page 287: C++ Builder和MapObjects实现

279

第 6章 选择与查询功能的实现

String szLayer = _environment->GetLayerName(szName,"地名索引");

String szTable = _environment->GetTableName(szName,"地名索引");

if (szTable == "")

{

MessageBox(NULL, "属性库中无此地名","北京市地理信息公众查询系统", MB_OK);

return;

}

int nIndex = _environment->GetLayerIndexByName(szTable);

if (nIndex < 0) return;

//车站的定位 if (_environment->IsStation(Map, szName))

{

IMoMapLayerPtr ly = _environment->m_layerInfos[nIndex].layer; rs = ly->SearchExpression(WideString("名称 like '"+szName +"'"));

if (rs) {

rs->MoveFirst();

if (!(bool)rs->EOF) {

IMoPointPtr pt;

pt = rs->Fields->Item("shape")->Value;

if (!IsWithin(Map->Extent, pt))

{ Map->CenterAt(pt->X, pt->Y );

}

if (_environment->m_layerInfos[nIndex].nCharacterIndex >= 0

&& _environment->m_layerInfos[nIndex].layer->shapeType

== moShapeTypePoint ) {

TFont* font = new TFont();

font->Name = _environment-> m_layerInfos[nIndex].szFontName;

_environment->m_selectedSymbol = (IDispatch*)

CreateOleObject("MapObjects2.Symbol"); _environment->m_selectedSymbol->SymbolType =

moPointSymbol;

_environment->m_selectedSymbol->Font = (IFontDisp *) ((IDispatch *)FontToOleFont(font));

Page 288: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

280

_environment->m_selectedSymbol->Style = 4;

_environment->m_selectedSymbol->Size = (short)

_environment->m_layerInfos[nIndex].layer->Symbol->Size; _environment->m_selectedSymbol->CharacterIndex = (short)

_environment->m_layerInfos[nIndex].nCharacterIndex;

_environment->m_selectedSymbol->Color = 0xff;

_environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize; _environment->m_selectedScale = _environment->

m_layerInfos[nIndex].dScale;

} else

{

if(_environment->m_layerInfos[nIndex].layer-> shapeType == moShapeTypePoint )

{

_environment->m_selectedSymbol = (IDispatch*) CreateOleObject("MapObjects2.Symbol");

_environment->m_selectedSymbol->SymbolType =

_environment->m_layerInfos[nIndex].layer->Symbol-> SymbolType;

_environment->m_selectedSymbol->Style =

_environment->m_layerInfos[nIndex].layer-> Symbol->Style;

_environment->m_selectedSymbol->Size = (short)

_environment->m_layerInfos[nIndex].layer-> Symbol->Size;

_environment->m_selectedSymbol->Color = 0xff;

_environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize;

_environment->m_selectedScale = _environment->m_layerInfos[nIndex].dScale;

}

}

_environment->m_selectedFeature = rs->Fields->

Item("shape")->Value; Map->FlashShape(rs->Fields->Item("shape")->Value, 4);

Map->Extent = Map->Extent;

EyeMap->Extent = EyeMap->Extent; }

}

return; }

Page 289: C++ Builder和MapObjects实现

281

第 6章 选择与查询功能的实现

if (!_environment->m_layerInfos[nIndex].bVisible)

{ AnsiString str = _environment->m_layerInfos[nIndex].szName +

"所在的图层不可见";

MessageBox(NULL, str.c_str(), "北京市地理信息公众查询系统", MB_OK);

return;

}

String szFieldName = "名称";

rs = _environment->m_layerInfos[nIndex].layer->SearchExpression (WideString(szFieldName + " like '" + szName + "'"));

if (rs) {

rs->MoveFirst();

if (!(bool)rs->EOF) {

IMoPointPtr pt;

switch (_environment->m_layerInfos[nIndex].layer->shapeType) {

case moShapeTypePoint:

{ pt = rs->Fields->Item("shape")->Value;

break;

}

case moShapeTypeLine:

{ IMoLinePtr line;

line = rs->Fields->Item("shape")->Value;

pt = line->Extent->Center; break;

}

case moShapeTypePolygon:

{

IMoPolygonPtr poly; poly = rs->Fields->Item("shape")->Value;

pt = poly->Extent->Center;

break; }

}

Map->CenterAt(pt->X, pt->Y );

Page 290: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

282

EyeMap->Extent = EyeMap->Extent;

Map->FlashShape(rs->Fields->Item("shape")->Value, 4); ShapeTypeConstants shapeType = _environment->

m_layerInfos[nIndex].layer->shapeType;

if (shapeType == moShapeTypePoint ) {

if(_environment->m_layerInfos[nIndex].nCharacterIndex >= 0)

{ TFont* font = new TFont();

font->Name =

_environment->m_layerInfos[nIndex].szFontName; _environment->m_selectedSymbol = (IDispatch*)

CreateOleObject("MapObjects2.Symbol");

_environment->m_selectedSymbol->SymbolType = moPointSymbol;

_environment->m_selectedSymbol->Font = (IFontDisp *)

((IDispatch *)FontToOleFont(font)); _environment->m_selectedSymbol->Style = 4;

_environment->m_selectedSymbol->Size = (short)

_environment->m_layerInfos[nIndex].nSymSize; _environment->m_selectedSymbol->CharacterIndex = (short)

_environment->m_layerInfos[nIndex].nCharacterIndex;

_environment->m_selectedSymbol->Color = 0xff;

_environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize; _environment->m_selectedScale = _environment->

m_layerInfos[nIndex].dScale;

} else

{

_environment->m_selectedSymbol = (IDispatch*) CreateOleObject("MapObjects2.Symbol");

_environment->m_selectedSymbol->SymbolType =

_environment->m_layerInfos[nIndex].layer-> Symbol->SymbolType;

_environment->m_selectedSymbol->Style = _environment->

m_layerInfos[nIndex].layer->Symbol->Style; _environment->m_selectedSymbol->Size = (short)

_environment->m_layerInfos[nIndex].nSymSize;

_environment->m_selectedSymbol->Color = 0xff;

_environment->m_selectedSymbolSize = (short)

_environment->m_layerInfos[nIndex].nSymSize; _environment->m_selectedScale = _environment->

Page 291: C++ Builder和MapObjects实现

283

第 6章 选择与查询功能的实现

m_layerInfos[nIndex].dScale;

}

}

_environment->m_selectedFeature =

rs->Fields->Item("shape")->Value; Map->Extent = Map->Extent;

}

else {

MessageBox(NULL, "电子地图上没有这个单位",

"北京市地理信息公众查询系统", MB_OK); }

}

} //---------------------------------------------------------------------

上面的 Position 函数又调用了 TEnvironment 类的 3 个函数,分别是 IsBusLine 、GetLayerByName与IsStation。这3个函数的代码如下:

//---------------------------------------------------------------------

bool TEnvironment::IsBusLine(TMap* map, String szName) {

int i;

for (i = 0; i < m_nLayerNum; i ++) {

if (m_layerInfos[i].szName == BUSLINE_LAYERNAME)

break; }

if (i == m_nLayerNum) return false;

IMoMapLayerPtr ly = m_layerInfos[i].layer; IMoRecordsetPtr rs = ly->SearchExpression(WideString("

名称 like '"+szName +"'"));

if (!rs)

return false;

rs->MoveFirst();

if ((bool)rs->EOF)

return false;

return true;

} //---------------------------------------------------------------------

Page 292: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

284

IMoMapLayerPtr TEnvironment::GetLayerByName(String szName)

{

int i; for (i = 0; i < m_nLayerNum - 1; i ++)

{

if (m_layerInfos[i].szName == szName) break;

}

if (i == m_nLayerNum)

{

IMoMapLayerPtr nullLayer; return nullLayer;

}

return m_layerInfos[i].layer;

}

//--------------------------------------------------------------------- bool TEnvironment::IsStation(TMap* map, String szName)

{

int i; for (i = 0; i < m_nLayerNum ; i ++)

{

if (m_layerInfos[i].szName == BUSSTATION_LAYERNAME) break;

}

if (i == m_nLayerNum)

return false;

IMoMapLayerPtr ly = m_layerInfos[i].layer;

IMoRecordsetPtr rs = ly->SearchExpression(WideString(" 名 称 like

'"+szName +"'"));

if (!rs)

return false;

rs->MoveFirst();

if ((bool)rs->EOF) return false;

return true; }

//---------------------------------------------------------------------

编译并运行程序,切换到“查询”页面的“地名索引”选项卡,根据地名分类限制查

询范围,然后在列表框中双击某地名或公交路线名称,系统就会在地图中闪烁显示对应的

Page 293: C++ Builder和MapObjects实现

285

第 6章 选择与查询功能的实现

地物并高亮显示之。图6.16显示的是11路公交线路的行车路线。

图 6.16 11 路公交线路的行车路线

6.7 查询结果的定位与更详细信息

当用户通过“查询”页面的“地名查询”选项卡进行地名精确与模糊查询,或通过“查

找 近”选项卡进行范围查询时,系统将查询结果显示在“查询结果”选项卡中。通过“查

询结果”选项卡中的3个按钮及列表框,可以获取查询结果的更进一步信息,并对它们进行

定位。 创建“查询结果”选项卡中“内容”按钮的Click事件响应函数,用于显示用户在列表

框中所选地物的更进一步信息。代码如下:

//---------------------------------------------------------------------

void __fastcall TMainForm::ResultContentClick(TObject *Sender)

{ if (ResultList->Items->Count <= 0)

return;

if (ResultList->ItemIndex < 0)

Page 294: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

286

return;

AnsiString strSelected = ResultList-> Items->Strings[ResultList->ItemIndex];

String szTable = _environment->GetTableName(strSelected, "地名索引");

if (szTable == "") {

MessageBox(NULL, "数据库中无此地名", "北京市地理信息公众查询系统",

MB_OK); return;

}

TContentForm* frm = new TContentForm(this);

frm->SetName(strSelected);

frm->ShowModal(); }

//---------------------------------------------------------------------

上述代码打开一个ContentForm窗口用于显示地物的详细信息,因此我们需要创建

ContentForm窗体。选择File菜单的Add Form命令,在当前项目中加入一个新窗体,将该窗

体命名为ContentForm,并将其单元文件保存为Content.cpp。在窗体ContentForm中加入一个

ListBox控件,命名为ContentListBox。 按照表6.7所示设置窗体ContentForm及其控件的属性。

表6.7 窗体ContentForm及其控件的属性

控件 属性 属性值

ContentForm BorderIcons.biMinimize

BorderIcons.biMaximize

BorderStyle

Caption

false

false

bsSizeToolWin

详细信息

ContentListBox Align alClient

在ContentForm窗体中加入如下成员变量:

private:

String m_strName;

然后在ContentForm窗体的构造函数中初始化成员变量,代码如下:

//---------------------------------------------------------------------

__fastcall TContentForm::TContentForm(TComponent* Owner)

: TForm(Owner) {

m_strName = "";

} //---------------------------------------------------------------------

Page 295: C++ Builder和MapObjects实现

287

第 6章 选择与查询功能的实现

在Content.cpp文件的头部加入如下两行代码,用于引用MainForm外部变量:

//---------------------------------------------------------------------

#include "main.h"

extern TMainForm *MainForm; //---------------------------------------------------------------------

在ContentForm类中加入SetName函数,代码如下:

//---------------------------------------------------------------------

void __fastcall TContentForm::SetName(String szName) {

m_strName = szName;

} //---------------------------------------------------------------------

创建ContentForm窗体的OnShow事件响应函数,在其中加入如下代码,用于显示窗体

时,在列表框中显示地名的详细信息:

//---------------------------------------------------------------------

void __fastcall TContentForm::FormShow(TObject *Sender) {

if (m_strName == "")

return;

String szTableName = MainForm->_environment->GetTableName(m_strName,

"地名索引"); if (szTableName == "")

{

MessageBox(NULL, "属性库中无此地名", "北京市地理信息公众查询系统", MB_OK);

return;

} ContentListBox->Items->Clear();

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + MainForm->_environment->m_szDBName + ";

Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(MainForm); myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{

myConnection->Open(); }

catch(EOleException& e)

{

Page 296: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

288

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return;

}

String szSQL;

String szFieldName; szFieldName = MainForm->_environment->GetFieldName(m_strName);

if (szFieldName == "") szFieldName = "单位名称";

szSQL = "Select * From [" + szTableName + "] Where "+ szFieldName+" ='" + m_strName + "'";

TADODataSet* dataSet = new TADODataSet(MainForm); dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{

dataSet->Open();

if(dataSet->RecordCount == 0)

return;

for (int i = 0; i < dataSet->Fields->Count; i ++)

{ String szValue = dataSet->Fields->Fields[i]->FullName + ":" +

dataSet->Fields->Fields[i]->AsString;

ContentListBox->Items->Add(szValue); }

}

catch(EOleException& e) {

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK);

return; }

}

//---------------------------------------------------------------------

上述代码调用了TEnvironment类的GetFieldName函数,该函数用于依据地名名称得到

字段名称,实现代码如下:

//---------------------------------------------------------------------

String TEnvironment::GetFieldName(String szName)

{ String szTable = GetTableName(szName,"地名索引");

Page 297: C++ Builder和MapObjects实现

289

第 6章 选择与查询功能的实现

String szFieldName = "单位名称";

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;

Data Source=" + m_szDBName + ";Persist Security Info=False"; TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString;

myConnection->LoginPrompt = false;

try

{ myConnection->Open();

}

catch(EOleException& e) {

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK);

return szFieldName; }

String szSQL; szSQL = "Select * From "+ m_mapInfos[m_nCurrMapIndex].szMetaTable +

" Where 属性表名 ='"+szTable+"'";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

try

{ dataSet->Open();

if(dataSet->RecordCount == 0) return szFieldName;

szFieldName = dataSet->Fields->FieldByName("字段名")->AsString;

dataSet->First();

dataSet->Close(); delete dataSet;

dataSet = NULL;

myConnection->Close();

delete myConnection;

myConnection = NULL;

return szFieldName;

} catch(EOleException& e)

Page 298: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

290

{

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK);

return szFieldName; }

}

//---------------------------------------------------------------------

创建“定位”按钮的OnClick事件响应函数,在其中加入如下代码,用于在地图中定位

用户在列表框中选择的地物:

//---------------------------------------------------------------------

void __fastcall TMainForm::ResultPositionClick(TObject *Sender)

{ if (ResultList->Items->Count <= 0)

return;

AnsiString strSelected = ResultList->

Items->Strings[ResultList->ItemIndex];

Position(strSelected); }

//---------------------------------------------------------------------

上述代码调用Position函数用来定位指定的地物。 创建ResultList控件的OnClick事件响应函数,用于控制3个按钮是否可选择,代码如下:

//--------------------------------------------------------------------- void __fastcall TMainForm::ResultListClick(TObject *Sender)

{

if(ResultList->Items->Count <= 0) {

ResultContent->Enabled = false;

ResultMedia->Enabled = false; ResultPosition->Enabled = false;

return ;

}

if (ResultList->ItemIndex < 0)

{ ResultContent->Enabled = false;

ResultMedia->Enabled = false;

ResultPosition->Enabled = false; return ;

}

else {

ResultContent->Enabled = true;

ResultPosition->Enabled = true; }

Page 299: C++ Builder和MapObjects实现

291

第 6章 选择与查询功能的实现

if (_environment->IsImage(ResultList->Items->

Strings[ResultList->ItemIndex])) ResultMedia->Enabled = true;

else

ResultMedia->Enabled = false; }

//---------------------------------------------------------------------

上述代码调用了TEnvironment类的IsImage函数,该函数的代码如下:

//--------------------------------------------------------------------- bool TEnvironment::IsImage(String szName)

{

String strConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + m_szDBName + ";Persist Security Info=False";

TADOConnection* myConnection = new TADOConnection(MainForm);

myConnection->ConnectionString = strConnectionString; myConnection->LoginPrompt = false;

try {

myConnection->Open();

} catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); return false;

}

String szSQL;

szSQL = "Select * From 多媒体索引 Where 名称 ='"+szName+"'";

TADODataSet* dataSet = new TADODataSet(MainForm);

dataSet->Connection = myConnection;

dataSet->CommandText = szSQL;

bool Result = false;

try {

dataSet->Open();

if(dataSet->RecordCount == 0)

Result = false;

else Result = true;

}

Page 300: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

292

catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); return false;

}

return Result;

}

//---------------------------------------------------------------------

编译并运行程序,便可通过“查询”页面的“查询结果”选项卡中进一步显示查询结

果的详细信息,如图6.17所示。

图 6.17 通过“查询结果”选项卡获取查询结果的更进一步信息

6.8 短路径查询

短路径查询的实现非常复杂,需要利用许多复杂的算法。本系统通过一个名为

TnetLayer的网络图层类来实现 短路径查询功能,在该类中还需要许多其他的辅助类。 选择File菜单的New命令,打开New Items对话框。在该对话框的New选项卡中选择Unit

图标,然后单击OK,将在当前项目中加入一个单元文件,将该单元文件保存为NetLayer.cpp。 切换到NetLayer.h中,首先加入如下几个头文件的包含代码:

#include "MapObjects2_OCX.h"

#include <ADODB.hpp>

Page 301: C++ Builder和MapObjects实现

293

第 6章 选择与查询功能的实现

然后在其中加入NetPoint辅助类的声明,该类用于定义网络上的一点:

//---------------------------------------------------------------------

class NetPoint

{ public:

double x;

double y; public:

// 构造函数

NetPoint(); NetPoint( double x, double y );

};

//---------------------------------------------------------------------

切换到NetLayer.cpp文件,加入如下NetPoint类构造函数的代码:

//---------------------------------------------------------------------

NetPoint::NetPoint()

{ x = 0;

y = 0;

} //---------------------------------------------------------------------

NetPoint::NetPoint( double x, double y )

{ this->x = x;

this->y = y;

} //---------------------------------------------------------------------

再切换到NetLayer.h中,加入NetLine类的声明,该类用来表示网络上的线对象:

//---------------------------------------------------------------------

class NetLine {

public:

TList* m_pCoords; private:

IMoMapLayerPtr m_layer;

public: //构造函数

NetLine(IMoMapLayerPtr layer);

// 计算线的几何长度 double CalcLength();

// 通过线对象的标识得到线对象的数据

bool GetLineData(int id); // 得到距离某点 近的线的标识

Page 302: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

294

int GetNearestLineData( double x, double y);

// 在线对象中加入一个点对象

void AddCoord( NetPoint* pt ); // 判断两点是否重合

bool IsPtCoincide( NetPoint ptFirst, NetPoint ptSecond );

// 得到某点 邻近的点 void GetNearestPoint( NetPoint* ptP, NetPoint* ptA, NetPoint* ptB,

NetPoint* ptNearest, double* dDistance );

void GetNearestPoint( NetPoint* point, NetPoint* ptNearestPoint, int* nSegmentIndex, double* dLeastDistance);

bool GetSplitRatioByNearestPoint( NetPoint* point,

NetPoint* ptNearest, double* dRatio ); };

//---------------------------------------------------------------------

切换到NetLayer.cpp文件,加入如下实现NetLine类的代码:

//--------------------------------------------------------------------- NetLine::NetLine(IMoMapLayerPtr layer)

{

m_pCoords = new TList(); m_layer = layer;

}

//--------------------------------------------------------------------- // 计算线的几何长度

double NetLine::CalcLength()

{ double dLength = 0.0 ; // 保存计算出的线的几何长度

int loop ; // 保存循环计数

// 检查线的有效性

if ( m_pCoords->Count < 2 )

return 0.0 ;

// 计算线的几何长度

double dist = 0.0 ; for ( loop = 1 ; loop < m_pCoords->Count; loop ++ )

{

dist = sqrt ( ( ((NetPoint*)m_pCoords->Items[loop -1])->x – ((NetPoint*)m_pCoords->Items[loop])->x ) *

( ((NetPoint*)m_pCoords->Items[loop -1])->x –

((NetPoint*)m_pCoords->Items[loop])->x ) + ( ((NetPoint*)m_pCoords->Items[loop -1])->y –

((NetPoint*)m_pCoords->Items[loop])->y ) *

( ((NetPoint*)m_pCoords->Items[loop -1])->y – ((NetPoint*)m_pCoords->Items[loop])->y ) ) ;

dLength += dist ;

Page 303: C++ Builder和MapObjects实现

295

第 6章 选择与查询功能的实现

}

return dLength ; }

//---------------------------------------------------------------------

// 通过线对象的标识得到线对象的数据 bool NetLine::GetLineData(int id)

{

IMoRecordsetPtr rs; rs = m_layer->SearchExpression(WideString("GeoID = " + IntToStr(id)));

if (!rs) return false;

rs->MoveFirst(); if ((bool)rs->EOF)

return false;

IMoLinePtr line = rs->Fields->Item("shape")->Value;

IMoPointsPtr pts;

pts = line->Parts->Item(0);

m_pCoords->Clear();

for (int i = 0; i < pts->Count; i ++)

{

NetPoint* pt = new NetPoint(pts->Item(i)->X, pts->Item(i)->Y); m_pCoords->Add(pt);

}

return true;

}

//--------------------------------------------------------------------- // 得到距离某点 近的线的标识

int NetLine::GetNearestLineData( double x, double y)

{ IMoRecordsetPtr rs = m_layer->Records;

IMoPointPtr pt = (IDispatch*)CreateOleObject("MapObjects2.Point");

pt->X = x; pt->Y = y;

double dDist = 9999999; int id = -1;

rs->MoveFirst(); while(!(bool)rs->EOF)

Page 304: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

296

{

IMoLinePtr line= rs->Fields->Item("shape")->Value;

double d = line->DistanceTo(pt);

if (dDist > d)

{ dDist = d;

String szValue = rs->Fields->Item("Geoid")->ValueAsString;

id = StrToInt(szValue); }

rs->MoveNext(); }

if (id != -1) {

if (!GetLineData(id))

return -1; }

return id; }

//---------------------------------------------------------------------

void NetLine::AddCoord( NetPoint* pt ) {

m_pCoords->Add( pt );

} //---------------------------------------------------------------------

bool NetLine::IsPtCoincide( NetPoint ptFirst, NetPoint ptSecond )

{ if ( fabs(ptFirst.x - ptSecond.x ) <= 0.00000001

&& fabs(ptFirst.y - ptSecond.y ) <= 0.00000001 )

return true; return false;

}

//--------------------------------------------------------------------- void NetLine::GetNearestPoint( NetPoint* ptP, NetPoint* ptA, NetPoint* ptB,

NetPoint* ptNearest, double* dDistance )

{ double Px,Py,Ax,Ay,Bx,By ;

double AB2,PA2,PB2,AB,PA,PB,S,AREA ;

double med,med1,k1,k2,b1,b2 ;

if ( IsPtCoincide(*ptA, *ptB) )

{ ptNearest->x = ptA->x;

Page 305: C++ Builder和MapObjects实现

297

第 6章 选择与查询功能的实现

ptNearest->y = ptA->y;

return ;

}

Px = ptP->x ;

Py = ptP->y ; Ax = ptA->x ;

Ay = ptA->y ;

Bx = ptB->x ; By = ptB->y ;

AB2 = (Ax - Bx) * (Ax - Bx) + (Ay - By) * (Ay - By) ;

PB2 = (Px - Bx) * (Px - Bx) + (Py - By) * (Py - By) ; PA2 = (Ax - Px) * (Ax - Px) + (Ay - Py) * (Ay - Py) ;

if(PA2 + AB2 < PB2 || AB2 + PB2 < PA2) {

if(PA2 > PB2)

{ med = PB2 ;

ptNearest->x = Bx ;

ptNearest->y = By ; }

else

{ med = PA2 ;

ptNearest->x = Ax ;

ptNearest->y = Ay ; }

med = sqrt(med) ;

*dDistance = med ; return ;

}

if (PA2 < 0.00000001 || PB2 < 0.00000001)

{

if(PA2 < 0.00000001) {

med = sqrt(PA2) ;

*dDistance = med ; ptNearest->x = Ax ;

ptNearest->y = Ay ;

return ; }

else

{ med = sqrt(PB2) ;

Page 306: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

298

*dDistance = med ;

ptNearest->x = Bx ;

ptNearest->y = By ; return ;

}

}

AB = sqrt(AB2) ;

PA = sqrt(PA2) ; PB = sqrt(PB2) ;

S = (AB + PA + PB) / 2.0 ;

AREA = S ; AREA *= (S - PA) ;

AREA *= (S - PB) ;

AREA *= (S - AB) ; AREA = sqrt(AREA) ;

med = (2.0 * AREA) / AB ;

*dDistance = med ;

med = Ay - By ;

med1 = Ax - Bx ; if(fabs(med) < 0.00000001 || fabs(med1) < 0.00000001)

{

if(fabs(med) < 0.00000001) {

ptNearest->x = Px ;

ptNearest->y = Ay ; }

else

{ ptNearest->y = Py ;

ptNearest->x = Ax ;

} }

else

{ k1 = (Ay - By) / ( Ax - Bx) ;

k2 = -1.0 / k1 ;

b1 = Ay - k1 * Ax ; b2 = Py - k2 * Px ;

S = (b2 - b1) / (k1 - k2) ;

ptNearest->x = S ; S = k1 * S + b1 ;

ptNearest->y = S ;

}

Page 307: C++ Builder和MapObjects实现

299

第 6章 选择与查询功能的实现

return;

}

//--------------------------------------------------------------------- void NetLine::GetNearestPoint( NetPoint* point, NetPoint* ptNearestPoint,

int* nSegmentIndex, double* dLeastDistance)

{ int nPointNum = m_pCoords->Count;

double dDistance;

GetNearestPoint( point, (NetPoint*)m_pCoords->Items[0],

(NetPoint*)m_pCoords->Items[1],

ptNearestPoint, dLeastDistance); *nSegmentIndex = 0 ;

//遍历每一条弧段来搜索 近的点 int nIndex ;

for( nIndex = 1 ; nIndex < nPointNum-1 ; nIndex ++ )

{ NetPoint* ptTemp;

ptTemp = new NetPoint();

//得到 近的点 GetNearestPoint( point, (NetPoint*)m_pCoords->Items[0],

(NetPoint*)m_pCoords->Items[1], ptTemp, &dDistance ) ;

//比较 小的距离

if( dDistance < *dLeastDistance )

{ *dLeastDistance = dDistance ;

ptNearestPoint = ptTemp ;

*nSegmentIndex = nIndex ; }

}

return ; }

//---------------------------------------------------------------------

// 获得根据给定点分裂线得到的两个部分的比例, 但并不真正分裂线 // point: 给定点

// ptNearestPoint: 分裂线时的分裂点 (返回)

// dRatio: 起始结点部分的比例 (返回) bool NetLine::GetSplitRatioByNearestPoint( NetPoint* point,

NetPoint* ptNearest, double* dRatio )

{ int nIndex;

double dDistance;

int nPointNum = m_pCoords->Count;

Page 308: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

300

//首先得到 近的点和线段索引

GetNearestPoint( point, ptNearest, &nIndex, &dDistance );

//检查线上 近的点是否与首尾点重合

if( nIndex == 0 )

{ NetPoint* pTemp = (NetPoint*)m_pCoords->Items[0];

if( IsPtCoincide( *ptNearest, *pTemp))

{ dRatio = 0;

return true;

} }

if( nIndex == nPointNum-2 ) {

NetPoint* pTemp = (NetPoint*)m_pCoords->Items[nPointNum-1];

if( IsPtCoincide( *ptNearest, *pTemp)) {

*dRatio = 1.0;

return true; }

}

//计算分裂出来的第二条线的长度

int nLoop;

double dLength = 0; //如果 近点与本线上的下一点不重合,则需将 近点计算在内

NetPoint* pTempPoint = (NetPoint*)m_pCoords->Items[nIndex+1];

if ( !IsPtCoincide(*ptNearest, *pTempPoint) ) {

double temp1 = ((NetPoint*)m_pCoords->Items[nIndex+1])->x;

temp1 -= ptNearest->x; temp1 *= temp1;

double temp2 = ((NetPoint*)m_pCoords->Items[nIndex+1])->y;

temp2 -= - ptNearest->y; temp2 *= temp2;

dLength += sqrt(temp1 + temp2);

}

for( nLoop = nIndex+2 ; nLoop < nPointNum ; nLoop ++ )

{ dLength += sqrt( ( ((NetPoint*)m_pCoords->Items[nLoop])->x –

((NetPoint*)m_pCoords->Items[nLoop-1])->x ) *

( ((NetPoint*)m_pCoords->Items[nLoop])->x – ((NetPoint*)m_pCoords->Items[nLoop-1])->x ) +

Page 309: C++ Builder和MapObjects实现

301

第 6章 选择与查询功能的实现

( ((NetPoint*)m_pCoords->Items[nLoop])->y –

((NetPoint*)m_pCoords->Items[nLoop-1])->y ) *

( ((NetPoint*)m_pCoords->Items[nLoop])->y – ((NetPoint*)m_pCoords->Items[nLoop-1])->y )

);

}

*dRatio = 1 - dLength / CalcLength();

return true; }

//---------------------------------------------------------------------

NetEdge类很简单,其声明与实现代码如下:

//--------------------------------------------------------------------- class NetEdge

{

public: int nLink; // 连接的弧段索引(数组下标索引)

float fAngle; // 该弧段的水平夹角

public: NetEdge()

{

nLink = -1; fAngle = 0;

}

}; //---------------------------------------------------------------------

结点类NetNode从NetPoint类继承而来,其声明代码如下:

//---------------------------------------------------------------------

class NetNode : public NetPoint {

public:

TList* m_arrLinks; // 与该点连接的弧段数组, 弧段按角度排序 public:

NetNode();

NetNode( double x, double y ); bool Add( int nLink, double dAngle );

bool Remove( int nLink );

double GetLinkAngle( int nLink ); };

//---------------------------------------------------------------------

NetNode类的实现代码如下:

//--------------------------------------------------------------------- NetNode::NetNode()

Page 310: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

302

{

m_arrLinks = new TList();

} //---------------------------------------------------------------------

NetNode::NetNode( double x, double y )

{ this->x = x;

this->y = y;

m_arrLinks = new TList(); }

//---------------------------------------------------------------------

// 加入一个连接的弧段(调用前需确定弧段是连接在该点上的) bool NetNode::Add( int nLink, double dAngle )

{

// 结点连接的弧段按角度排序 int i = 0;

for ( i = 0; i < m_arrLinks->Count; i++ )

{ if ( dAngle < ((NetEdge*)m_arrLinks->Items[i])->fAngle )

break;

}

NetEdge* pEdge = new NetEdge();

pEdge->nLink = nLink; pEdge->fAngle = (float)dAngle;

m_arrLinks->Insert(i, pEdge );

return true;

}

//--------------------------------------------------------------------- // 删除一个已连接的弧段

bool NetNode::Remove( int nLink )

{ for ( int i = 0; i < m_arrLinks->Count ; i++ )

{

if ( nLink == ((NetEdge*)m_arrLinks->Items[i])->nLink ) {

m_arrLinks->Delete( i );

break; }

}

return true;

}

//--------------------------------------------------------------------- // 得到一个连接弧段的角度

Page 311: C++ Builder和MapObjects实现

303

第 6章 选择与查询功能的实现

double NetNode::GetLinkAngle( int nLink )

{

for ( int i = 0; i < m_arrLinks->Count ; i++ ) {

if ( nLink == ((NetEdge*)m_arrLinks->Items[i])->nLink )

{ return ((NetEdge*)m_arrLinks->Items[i])->fAngle;

}

}

return -1;

} //---------------------------------------------------------------------

网络弧段(链)类NetLink的声明代码如下:

//---------------------------------------------------------------------

class NetLink {

public:

int m_GeoID; // 弧段ID(GeoID) int m_nFNode; // 起始结点(数组下标索引)

int m_nTNode; // 终止结点(数组下标索引)

double m_fLength; // 长度 double m_fFromImp; // 正向阻力(阻力系数*长度 或 (1+阻力系数)*长度)

double m_fToImp; // 逆向阻力

public: NetLink();

void Copy( NetLink* link );

bool IsEqual( NetLink link ); };

//---------------------------------------------------------------------

NetLink类的实现代码如下:

//--------------------------------------------------------------------- NetLink::NetLink()

{

m_GeoID = -1; m_nFNode = -1;

m_nTNode = -1;

m_fLength = 0; m_fFromImp = 0;

m_fToImp = 0;

} //---------------------------------------------------------------------

void NetLink::Copy( NetLink* link )

{

Page 312: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

304

m_GeoID = link->m_GeoID;

m_nFNode = link->m_nFNode;

m_nTNode = link->m_nTNode; m_fLength = link->m_fLength;

m_fFromImp = link->m_fFromImp;

m_fToImp = link->m_fToImp; }

//---------------------------------------------------------------------

bool NetLink::IsEqual( NetLink link ) {

return m_GeoID == link.m_GeoID;

} //---------------------------------------------------------------------

结构NetLinkSeg的声明代码如下:

//---------------------------------------------------------------------

struct NetLinkSeg {

int nSegID; // 分裂点后面部分的弧段索引(数组下标索引)

double dRatio; // 分裂点前面到起始结点部分的比例 };

//---------------------------------------------------------------------

备份弧段的类NetLinkBackup的声明代码如下:

//--------------------------------------------------------------------- class NetLinkBackup

{

public: int m_nIndex; // 弧段的索引

NetLink* m_Link; // 备份的弧段对象

TList* m_arrSegs; // 该弧段被多次分割的比例列表 public:

NetLinkBackup();

bool Add( int nSeg, double dRatio ); };

//---------------------------------------------------------------------

NetLinkBackup类的实现代码如下:

//--------------------------------------------------------------------- NetLinkBackup::NetLinkBackup()

{

m_nIndex = -1; m_Link = new NetLink();

m_arrSegs = new TList();

} //---------------------------------------------------------------------

Page 313: C++ Builder和MapObjects实现

305

第 6章 选择与查询功能的实现

bool NetLinkBackup::Add( int nSeg, double dRatio )

{

int i = 0; for ( i = 0; i < m_arrSegs->Count; i++ )

{

if ( dRatio < ((NetLinkSeg*)m_arrSegs->Items[i])->dRatio ) break;

}

NetLinkSeg* pSeg = new NetLinkSeg();

pSeg->nSegID = nSeg;

pSeg->dRatio = dRatio; m_arrSegs->Insert(i, pSeg );

return true; }

//---------------------------------------------------------------------

网络路径类NetPath的声明与实现代码如下:

//--------------------------------------------------------------------- class NetPath

{

public: double m_dLength; // 该点到给定点的 短路径长度, -1表示不连通

int m_nPreNode; // 该点在该路径上的前趋结点

public: NetPath()

{

m_dLength = -1; m_nPreNode = -1;

}

}; //---------------------------------------------------------------------

上面是实现TNetLayer类所需要的辅助类与结构。TNetLayer包括如下成员变量:

//---------------------------------------------------------------------

private: TList* m_arrLinks; // 弧段表

TList* m_arrNodes; // 结点表

TList* m_arrStops; // 站点表 TList* m_arrLinkBackups; // 弧段备份表

int m_nLinkNum; // 原始弧段数目(网络拓扑建成后) int m_nNodeNum; // 原始结点数目(网络拓扑建成后)

NetPath* m_pPath; // 某一个点到所有点的 短路径

Page 314: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

306

IMoMapLayerPtr m_layer;

TADODataSet* m_dataSet;

//---------------------------------------------------------------------

TNetLayer类的构造函数如下,用于初始化NetLayer类的成员变量:

//---------------------------------------------------------------------

TNetLayer::TNetLayer(IMoMapLayerPtr layer, TADODataSet* dataSet)

{ m_nLinkNum = 0;

m_nNodeNum = 0;

m_pPath = NULL; m_dataSet = dataSet;

m_layer = layer;

} //---------------------------------------------------------------------

网络图层类首先需要从弧段表中读入数据。弧段表用于记录边的属性,在建立网络拓

扑时生成。网络图层类必须有AAT,然后需要从结点属性表中读入结点的属性。实现函数

如下:

//---------------------------------------------------------------------

bool TNetLayer::ReadNetTable() {

m_arrLinks = new TList();

m_arrNodes = new TList(); m_arrStops = new TList();

m_arrLinkBackups = new TList();

AnsiString strSQL = "Select * From AAT Order By ARCID";

if(m_dataSet->Active)

m_dataSet->Close(); m_dataSet->CommandText = strSQL;

try {

m_dataSet->Open();

} catch(EOleException& e)

{

MessageBox(NULL,e.Message.c_str(), "错误", MB_OK); return false;

}

m_dataSet->First();

for(int i=0; i<m_dataSet->RecordCount; i++)

{ NetLink* lk = new NetLink();

Page 315: C++ Builder和MapObjects实现

307

第 6章 选择与查询功能的实现

lk->m_GeoID = m_dataSet->Fields->FieldByName("GeoID")->AsInteger;

lk->m_nFNode = m_dataSet->Fields->FieldByName("FNODE")->AsInteger;

lk->m_nTNode = m_dataSet->Fields->FieldByName("TNODE")->AsInteger; lk->m_fLength = m_dataSet->Fields->FieldByName("LENGTH")->AsFloat;

lk->m_fFromImp = m_dataSet->Fields->FieldByName("FIMP")->AsFloat;

lk->m_fToImp = m_dataSet->Fields->FieldByName("TIMP")->AsFloat;

m_arrLinks->Add( lk );

m_dataSet->Next(); }

strSQL = "Select * From NAT Order By NODEID"; if(m_dataSet->Active)

{

m_dataSet->First(); m_dataSet->Close();

}

m_dataSet->CommandText = strSQL;

try

{ m_dataSet->Open();

}

catch(EOleException& e) {

MessageBox(NULL, e.Message.c_str(), "错误", MB_OK);

return false; }

m_dataSet->First(); for(int i=0; i<m_dataSet->RecordCount; i++)

{

int nNode, nLink; double x, y, dAngle;

AnsiString szArcID, szAngle;

nNode = m_dataSet->Fields->FieldByName("NODEID")->AsInteger;

x = m_dataSet->Fields->FieldByName("X")->AsFloat;

y = m_dataSet->Fields->FieldByName("Y")->AsFloat; szArcID = m_dataSet->Fields->FieldByName("ARCID")->AsString;

szAngle = m_dataSet->Fields->FieldByName("ANGLE")->AsString;

NetNode* node = new NetNode(x,y);

m_arrNodes->Add( node );

int nPos;

Page 316: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

308

AnsiString szTemp;

while ( (nPos = szArcID.Pos(";")) != 0 )

{ szTemp = szArcID.SubString(1, nPos - 1);

nLink = StrToInt(szTemp );

szArcID = szArcID.SubString(nPos+1, szArcID.Length()- nPos);

nPos = szAngle.Pos(";");

if ( nPos == 0 ) continue;

szTemp = szAngle.SubString(1, nPos - 1);

dAngle = StrToFloat(szTemp); szAngle = szAngle.SubString(nPos + 1, szAngle.Length() - nPos);

node->Add( nLink, dAngle ); }

m_dataSet->Next(); }

return true; }

//---------------------------------------------------------------------

读入弧段表与结点属性表之后,便可对两点之间的 短路径进行分析了。用于 短路

径分析的函数PathAnalysis如下:

//--------------------------------------------------------------------- bool TNetLayer::PathAnalysis( double x1, double y1, double x2, double y2,

TList* path )

{ NetPoint* pt1 = new NetPoint( x1, y1 );

NetPoint* pt2 = new NetPoint( x2, y2 );

TList* points = new TList();

points->Add( pt1 );

points->Add( pt2 );

TList* stops = NULL;

stops = new TList(); if ( !LoadStops( points, stops ) )

return false;

if ( stops->Count != 2 ) return false;

TList* nodes = NULL; nodes = new TList();

Page 317: C++ Builder和MapObjects实现

309

第 6章 选择与查询功能的实现

double dDistance;

dDistance = Path( *((int*)stops->Items[0]), *((int*)stops->Items[1]), nodes, false );

if ( dDistance < 0 )

return false;

NetLine* line = NULL;

line = new NetLine(m_layer); if ( !CreateResultPath( nodes, line, false ) )

return false;

for ( int i = 0; i < line->m_pCoords->Count; i++ )

{

NetPoint* tempPoint = (NetPoint*)(line->m_pCoords->Items[i]); double* tempX = new double;

*tempX = tempPoint->x;

double* tempY = new double; *tempY = tempPoint->y;

path->Add(tempX);

path->Add(tempY); }

UnloadStops();

return true;

}

//---------------------------------------------------------------------

PathAnalysis函数中又调用了其他4个函数。首先是LoadStops函数,该函数加入一组坐

标作为站点或者中心点用于分析,代码如下:

//---------------------------------------------------------------------

// 加入一组坐标作为站点或者中心点用于分析

// LoadStops与UnloadStops必须一一对应 bool TNetLayer::LoadStops( TList* pPoints, TList* pNodes )

{

int nLineID; int i;

NetPoint* ptNearest = NULL;

ptNearest = new NetPoint(); double dRatio;

// 先清空站点表 m_arrStops->Clear();

int nNum = pPoints->Count; for ( i = 0; i < nNum; i++ )

Page 318: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

310

{

// 计算距离该点 近的线

NetLine* line = new NetLine(m_layer); nLineID =

line->GetNearestLineData( ((NetPoint*)pPoints->Items[i])->x,

((NetPoint*)pPoints->Items[i])->y); if ( nLineID == -1 )

{

line = NULL; return false;

}

// 计算该点分裂该线的位置,并不实际分裂该线,

// 只是计算分裂的比例,用于更改弧段表和结点表

dRatio = 0; line->GetSplitRatioByNearestPoint((NetPoint*)pPoints->Items[i],

ptNearest, &dRatio );

// 更新弧段表和结点表

int* nNewNode = new int;

UpdateLinkNodeTable( nLineID, ptNearest, dRatio, nNewNode );

line = NULL;

if ( pNodes->Count > 0 )

{

int* temp; temp = (int*)pNodes->Items[pNodes->Count-1];

if ( *temp == *nNewNode )

continue; }

pNodes->Add( nNewNode );

}

// 填充需要返回的结点数组

if ( pNodes->Count == 0 ) {

pNodes = NULL;

return false; }

return true; }

//---------------------------------------------------------------------

上述代码中又调用UpdateLinkNodeTable函数来更新弧段表和结点表,代码如下,其中

nNewNode表示加入某点后返回的新结点的标识:

Page 319: C++ Builder和MapObjects实现

311

第 6章 选择与查询功能的实现

//---------------------------------------------------------------------

// 外部结点更新弧段表和结点表

// nNewNode: 加入该点后返回的新结点的标识 bool TNetLayer::UpdateLinkNodeTable( int nLineID, NetPoint* ptNearest,

double dRatio, int* nNewNode )

{ int i, j;

bool bFound;

double dRatio2; int nCurLink, nNewLink;

int nCurFNode, nCurTNode;

*nNewNode = -1;

// 与某条弧段的首点或者尾点重合, 不需要更改弧段表和结点表

if ( fabs(dRatio) < 0.00000001 ) {

// 首点

nCurLink = GetNode( nLineID, &nCurFNode, &nCurTNode ); *nNewNode = nCurFNode;

return true;

} else if ( fabs(1-dRatio) < 0.00000001 )

{

// 尾点 nCurLink = GetNode( nLineID, &nCurFNode, &nCurTNode );

*nNewNode = nCurTNode;

return true; }

bFound = false; for ( i = 0; i < m_arrLinkBackups->Count; i++ )

{

if ( ((NetLinkBackup*)m_arrLinkBackups->Items[i])->m_Link-> m_GeoID == nLineID )

{

bFound = true; break;

}

}

if ( bFound )

{ for ( j = 0; j < ((NetLinkBackup*) m_arrLinkBackups->

Items[i]) -> m_arrSegs-> Count; j++ )

{ // 如果新点与原有的点重合, 则直接返回原来的点

Page 320: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

312

double r = ((NetLinkSeg*)((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j])->dRatio;

if ( fabs( r - dRatio) < 0.00000001 ) {

if ( j == 0 )

{ nCurLink = GetNode( nLineID, &nCurFNode, &nCurTNode );

*nNewNode = nCurTNode;

return true; }

else

{ nCurLink =

((NetLinkSeg*)((NetLinkBackup*)m_arrLinkBackups

->Items[i])->m_arrSegs->Items[j-1])->nSegID; *nNewNode = ((NetLink*)m_arrLinks->Items[nCurLink])

->m_nTNode;

return true; }

}

// 没有重合的点

r = ((NetLinkSeg*)((NetLinkBackup*)m_arrLinkBackups

->Items[i])->m_arrSegs->Items[j])->dRatio; if ( dRatio < r )

break;

}

if ( j == 0 )

{ // 第一段

nCurLink = GetNode( nLineID, &nCurFNode, &nCurTNode );

if ( nCurLink == -1 ) return false;

// 更新结点表 nNewLink = m_arrLinks->Count;

NetNode* pNode = new NetNode( ptNearest->x, ptNearest->y );

pNode->Add( nNewLink, -1 ); pNode->Add( nCurLink, -1 );

m_arrNodes->Add( pNode );

((NetNode*)m_arrNodes->Items[nCurTNode])->Remove( nCurLink );

((NetNode*)m_arrNodes->Items[nCurTNode])->Add( nNewLink, -1 );

// 更新弧段表

Page 321: C++ Builder和MapObjects实现

313

第 6章 选择与查询功能的实现

*nNewNode = m_arrNodes->Count - 1;

dRatio2 = ((NetLinkSeg*)((NetLinkBackup*)m_arrLinkBackups

->Items[i])->m_arrSegs->Items[0])->dRatio; NetLink* pLink = new NetLink();

pLink->m_GeoID = nLineID;

pLink->m_nFNode = *nNewNode; pLink->m_nTNode = ((NetLink*)m_arrLinks-> Items [((NetLinkSeg*)

((NetLinkBackup*) m_arrLinkBackups->Items[i])->

m_arrSegs->Items[0])->nSegID]) ->m_nFNode; pLink->m_fLength =

(float)(((NetLink*)m_arrLinks->Items[nCurLink])

->m_fLength * ( dRatio2 - dRatio )); pLink->m_fFromImp = (float)(((NetLink*)m_arrLinks->

Items[nCurLink]) ->m_fFromImp * ( dRatio2 - dRatio ));

pLink->m_fToImp = (float)(((NetLink*)m_arrLinks ->Items[nCurLink]) ->m_fToImp * ( dRatio2 - dRatio ));

m_arrLinks->Add( pLink );

((NetLink*)m_arrLinks->Items[nCurLink])->m_nTNode = *nNewNode;

((NetLink*)m_arrLinks->Items[nCurLink])->m_fLength = (float) (((NetLink*)m_arrLinks->Items[nCurLink])->m_fLength * dRatio);

((NetLink*)m_arrLinks->Items[nCurLink])->m_fFromImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink])->m_fFromImp * dRatio); ((NetLink*)m_arrLinks->Items[nCurLink])->m_fToImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink])->m_fToImp* dRatio);

((NetLinkBackup*)m_arrLinkBackups->Items[i])->Add

( nNewLink, dRatio );

} else if ( j == ((NetLinkBackup*) m_arrLinkBackups->Items[i])

->m_arrSegs->Count )

{ // 后一段

nCurLink = ((NetLinkSeg*)((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j-1])->nSegID;

nCurFNode = ((NetLink*)m_arrLinks->

Items[nCurLink])->m_nFNode; nCurTNode = ((NetLink*)m_arrLinks->

Items[nCurLink])->m_nTNode;

// 更新结点表

nNewLink = m_arrLinks->Count;

NetNode* pNode = new NetNode( ptNearest->x, ptNearest->y ); pNode->Add( nNewLink, -1 );

Page 322: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

314

pNode->Add( nCurLink, -1 );

m_arrNodes->Add( pNode );

double dAngle = ((NetNode*)m_arrNodes->Items[nCurTNode])

->GetLinkAngle( nCurLink ); // 后一段保留原角度

((NetNode*)m_arrNodes->Items[nCurTNode])->Remove( nCurLink ); ((NetNode*)m_arrNodes->Items[nCurTNode])->Add( nNewLink,

dAngle );

// 更新弧段表

*nNewNode = m_arrNodes->Count - 1;

NetLink* pLink = new NetLink(); pLink->m_GeoID = nLineID;

pLink->m_nFNode = *nNewNode;

pLink->m_nTNode = nCurTNode; pLink->m_fLength =

(float)(((NetLink*)m_arrLinks->Items[i])->m_fLength

* ( 1 - dRatio )); pLink->m_fFromImp = (float)(((NetLink*)m_arrLinks->Items[i])

->m_fFromImp * ( 1 - dRatio ));

pLink->m_fToImp = (float)(((NetLink*) m_arrLinks->Items[i])->m_fToImp * ( 1 - dRatio ));

m_arrLinks->Add( pLink );

dRatio2 = ((NetLinkSeg*)((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j-1])->dRatio;

((NetLink*)m_arrLinks->Items[nCurLink])->m_nTNode = *nNewNode; ((NetLink*)m_arrLinks->Items[nCurLink])->m_fLength = (float)

(((NetLink*) m_arrLinks->Items[nCurLink])->m_fLength *

( dRatio - dRatio2) ); ((NetLink*)m_arrLinks->Items[nCurLink])->m_fFromImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink])->m_fFromImp * (

dRatio - dRatio2) ); ((NetLink*)m_arrLinks->Items[nCurLink])->m_fToImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink])->m_fToImp* ( dRatio

- dRatio2) );

((NetLinkBackup*)m_arrLinkBackups->Items[i])->Add( nNewLink,

dRatio ); }

else

{ // 中间某一段

nCurLink = ((NetLinkSeg*)

((NetLinkBackup*)m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j-1])->nSegID;

Page 323: C++ Builder和MapObjects实现

315

第 6章 选择与查询功能的实现

nCurFNode = ((NetLink*)m_arrLinks->

Items[nCurLink])->m_nFNode;

nCurTNode = ((NetLink*)m_arrLinks-> Items[nCurLink])->m_nTNode;

// 更新结点表 nNewLink = m_arrLinks->Count;

NetNode* pNode = new NetNode( ptNearest->x, ptNearest->y );

pNode->Add( nNewLink, -1 ); pNode->Add( nCurLink, -1 );

m_arrNodes->Add( pNode );

((NetNode*)m_arrNodes->Items[nCurTNode])

->Remove( nCurLink );

((NetNode*)m_arrNodes->Items[nCurTNode]) ->Add( nNewLink, -1 );

// 更新弧段表 *nNewNode = m_arrNodes->Count - 1;

dRatio2 = ((NetLinkSeg*)((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j])->dRatio; NetLink* pLink = new NetLink();

pLink->m_GeoID = nLineID;

pLink->m_nFNode = *nNewNode; pLink->m_nTNode = nCurTNode;

pLink->m_fLength = (float)(((NetLink*)

m_arrLinks->Items[i])->m_fLength* ( dRatio2 - dRatio )); pLink->m_fFromImp = (float)(((NetLink*)m_arrLinks->Items[i])

->m_fFromImp * ( dRatio2 - dRatio ));

pLink->m_fToImp = (float)(((NetLink*) m_arrLinks->Items[i])->m_fToImp

* ( dRatio2 - dRatio ));

m_arrLinks->Add( pLink );

dRatio2 = ((NetLinkSeg*)((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_arrSegs->Items[j-1])->dRatio;

((NetLink*)m_arrLinks->Items[nCurLink])

->m_nTNode = *nNewNode; ((NetLink*)m_arrLinks->Items[nCurLink])->m_fLength = (float)

(((NetLink*)m_arrLinks->Items[nCurLink])

->m_fLength * ( dRatio - dRatio2) ); ((NetLink*)m_arrLinks->Items[nCurLink])->

m_fFromImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink]) ->m_fFromImp * ( dRatio - dRatio2) );

Page 324: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

316

((NetLink*)m_arrLinks->Items[nCurLink])

->m_fToImp = (float)

(((NetLink*)m_arrLinks->Items[nCurLink]) ->m_fToImp* ( dRatio - dRatio2) );

((NetLinkBackup*)m_arrLinkBackups->Items[i])->Add ( nNewLink, dRatio );

}

} else

{

nCurLink = GetNode( nLineID, &nCurFNode, &nCurTNode ); if ( nCurLink == -1 )

return false;

nNewLink = m_arrLinks->Count;

// 备份

NetLinkBackup* pBackup = new NetLinkBackup(); pBackup->m_nIndex = nCurLink;

pBackup->m_Link->Copy((NetLink*)m_arrLinks->Items[nCurLink]);

pBackup->Add( nNewLink, dRatio ); m_arrLinkBackups->Add( pBackup );

// 更新结点表 NetNode* pNode = new NetNode( ptNearest->x, ptNearest->y );

pNode->Add( nNewLink, -1 );

pNode->Add( nCurLink, -1 ); m_arrNodes->Add( pNode );

double dAngle = ((NetNode*)m_arrNodes->Items[nCurTNode]) ->GetLinkAngle( nCurLink ); // 后一段保留原角度

((NetNode*)m_arrNodes->Items[((NetLink*)m_arrLinks->

Items[nCurLink]) ->m_nTNode])->Remove( nCurLink ); ((NetNode*)m_arrNodes->Items[((NetLink*)m_arrLinks->

Items[nCurLink]) ->m_nTNode])->Add( nNewLink, dAngle );

// 更新弧段表

*nNewNode = m_arrNodes->Count - 1;

NetLink* pLink = new NetLink(); pLink->m_GeoID = nLineID;

pLink->m_nFNode = *nNewNode;

pLink->m_nTNode = ((NetLink*)m_arrLinks-> Items[nCurLink])->m_nTNode;

pLink->m_fLength = (float) (((NetLink*)m_arrLinks->Items[nCurLink])

->m_fLength * ( 1 - dRatio )); pLink->m_fFromImp = (float)(((NetLink*)

Page 325: C++ Builder和MapObjects实现

317

第 6章 选择与查询功能的实现

m_arrLinks->Items[nCurLink]) ->m_fFromImp * ( 1 - dRatio ));

pLink->m_fToImp = (float)(((NetLink*)m_arrLinks->Items[nCurLink])

->m_fToImp * ( 1 - dRatio )); m_arrLinks->Add( pLink );

((NetLink*)m_arrLinks->Items[nCurLink])->m_nTNode = *nNewNode; ((NetLink*)m_arrLinks->Items[nCurLink])->m_fLength =

(float)(((NetLink*) m_arrLinks->Items[nCurLink])->

m_fLength * dRatio); ((NetLink*)m_arrLinks->Items[nCurLink])->m_fFromImp =

(float)(((NetLink*) m_arrLinks->Items[nCurLink])->

m_fFromImp * dRatio); ((NetLink*)m_arrLinks->Items[nCurLink])->m_fToImp =

(float)(((NetLink*) m_arrLinks->Items[nCurLink])->

m_fToImp* dRatio); }

return true; }

//---------------------------------------------------------------------

在更新弧段表和结点表的UpdateLinkNodeTable函数中又调用了GetNode函数,该函数

用于得到结点号,返回弧段索引号(即数组下标索引),-1表示函数调用失败。该函数的

实现代码如下:

//--------------------------------------------------------------------- // 得到结点号,返回弧段索引号(数组下标索引),-1表示失败

int TNetLayer::GetNode( int nLineID, int* nFNode, int* nTNode )

{ *nFNode = -1;

*nTNode = -1;

for ( int i = 0; i < m_arrLinks->Count; i++ ) {

if ( nLineID == ((NetLink*)m_arrLinks->Items[i])->m_GeoID )

{ *nFNode = ((NetLink*)m_arrLinks->Items[i])->m_nFNode;

*nTNode = ((NetLink*)m_arrLinks->Items[i])->m_nTNode;

return i; }

}

return -1; }

//---------------------------------------------------------------------

在PathAnalysis函数中调用的第二个函数是Path,该函数用于计算两点间的 短路径。

该函数的实现代码如下,其中参数nBeginNode表示起始结点,nEndNode表示终止结点,返

回参数pNodes表示路径顺序经过的结点数组,bWeight为TRUE表示计算 短路径时考虑方

Page 326: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

318

向权重,为FALSE表示不考虑。该函数返回路径总长度,小于0表示没有连通的路径。

//---------------------------------------------------------------------

// 计算两点间的 短路径

// 参数含义: nBeginNode,起始结点 // nEndNode,终止结点

// pNodes,路径顺序经过的结点数组, 返回参数

// bWeight,TRUE为计算 短路径时考虑方向权重, FALSE为不考虑 // 注意: pNode在函数内部开辟内存, 函数调用者负责在外部删除该内存

// 返回路径总长度,<0 表示没有连通的路径

double TNetLayer::Path( int nBeginNode, int nEndNode, TList* pNodes, bool bWeight )

{

if ( nBeginNode < 0 || nBeginNode >= m_arrNodes->Count ) return -1;

if ( nEndNode < 0 || nEndNode >= m_arrNodes->Count )

return -1;

// 如果两个结点相同

if ( nBeginNode == nEndNode ) {

pNodes->Add( &nBeginNode );

return 0; }

// 计算nBeginNode到其他所有结点的 短路径 if ( !CalcPath( nBeginNode, nEndNode, bWeight ) )

return -1;

// 提取从nBeginNode到nEndNode的路径

// 从nEndNode向前搜索前趋结点

int* nNode = new int; *nNode = nEndNode;

while ( true )

{ // 如果没有前趋结点, 不连通

if ( m_pPath[*nNode].m_nPreNode == -1 )

return -1;

// 如果前趋结点为起始结点, 路径结束

if ( m_pPath[*nNode].m_nPreNode == nBeginNode ) {

pNodes->Add( nNode );

break; }

// 加入中间结点

Page 327: C++ Builder和MapObjects实现

319

第 6章 选择与查询功能的实现

pNodes->Add( nNode );

int temp = *nNode;

nNode = new int; *nNode = m_pPath[temp].m_nPreNode;

}

// 加入起点

int *nBeginNodeTemp = new int;

*nBeginNodeTemp = nBeginNode; pNodes->Add(nBeginNodeTemp);

// 因为是从nEndNode到nBeginNode加入的, 所以要作一次倒序 for(int i=0; i<pNodes->Count/2; i++)

pNodes->Exchange (i, pNodes->Count-1-i);

return m_pPath[nEndNode].m_dLength;

}

//---------------------------------------------------------------------

Path函数中又调用了CalcPath函数。CalcPath函数采用Dijkstra算法,计算一个点到所有

点的 短路径,实现代码如下。参数bWeight表示是否考虑方向权重,如果nEndNode不等

于-1,则只计算nNode到nEndNode的 短路径:

//---------------------------------------------------------------------

// 计算一个点到所有点的 短路径. 采用Dijkstra算法

// 参数bWeight表示是否考虑方向权重 // 如果nEndNode不等于-1, 则只计算nNode到nEndNode的 短路径,

bool TNetLayer::CalcPath( int nNode, int nEndNode , bool bWeight )

{ if ( nNode < 0 || nNode >= m_arrNodes->Count )

return false;

int i, j, nNodeNum;

int nLink;

byte* pMark = NULL; // 处理标志数组

nNodeNum = m_arrNodes->Count;

if ( nNodeNum == 0 ) return true;

pMark = new byte[nNodeNum]; for ( i = 0; i < nNodeNum; i++ )

pMark[i] = 0;

// (1) 初始化

// 初始化路径数组, 类的构造函数会将长度置为-1, 前趋结点为-1

m_pPath = NULL;

Page 328: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

320

m_pPath = new NetPath[nNodeNum];

// 自身 m_pPath[nNode].m_dLength = 0;

m_pPath[nNode].m_nPreNode = nNode;

// 对于与该结点直接相连的点, 初始化路径长度为连接弧度的阻力

for ( i = 0; i < ((NetNode*)m_arrNodes->Items[nNode])->

m_arrLinks->Count; i++ ) {

nLink = ((NetEdge*)((NetNode*)m_arrNodes

->Items[nNode])->m_arrLinks->Items[i])->nLink; if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nFNode == nNode )

{

// 路径长度, 正向 m_pPath[((NetLink*)m_arrLinks->Items[nLink])

->m_nTNode].m_dLength = bWeight ? ((NetLink*)m_arrLinks

->Items[nLink])->m_fFromImp : ((NetLink*)m_arrLinks ->Items[nLink])->m_fLength;

// 前趋结点, 如果长度<0, 无前趋结点 if ( m_pPath[((NetLink*)m_arrLinks

->Items[nLink])->m_nTNode].m_dLength < 0 )

m_pPath[((NetLink*)m_arrLinks ->Items[nLink])->m_nTNode].m_nPreNode = -1;

else

m_pPath[((NetLink*)m_arrLinks ->Items[nLink])->m_nTNode].m_nPreNode = nNode;

}

else if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nTNode == nNode ) {

// 路径长度, 逆向

m_pPath[((NetLink*)m_arrLinks ->Items[nLink])->m_nFNode].m_dLength = bWeight ?

((NetLink*)m_arrLinks->Items[nLink])->m_fToImp :

((NetLink*)m_arrLinks->Items[nLink])->m_fLength;

// 前趋结点, 如果长度<0, 无前趋结点

if ( m_pPath[((NetLink*)m_arrLinks ->Items[nLink])->m_nFNode].m_dLength < 0 )

m_pPath[((NetLink*)m_arrLinks

->Items[nLink])->m_nFNode].m_nPreNode = -1; else

m_pPath[((NetLink*)m_arrLinks

->Items[nLink])->m_nFNode].m_nPreNode = nNode; }

Page 329: C++ Builder和MapObjects实现

321

第 6章 选择与查询功能的实现

}

// 开始处理 int nMinNode;

double dDist, dMinDist;

for ( i = 0; i < nNodeNum; i++ ) {

// (2) 在未处理结点中找出距离值 小的结点

nMinNode = -1; dMinDist = 1.7e+308;

for ( j = 0; j < nNodeNum; j++ )

{ // 跳过自身

if ( j == nNode )

continue;

// 跳过不连通结点

if ( m_pPath[j].m_dLength < 0 ) // <0 表示无穷大 continue;

// 跳过处理过的结点 if ( pMark[j] == 1 )

continue;

// 在未处理过的结点中找出距离 小的结点

if ( m_pPath[j].m_dLength < dMinDist )

{ dMinDist = m_pPath[j].m_dLength;

nMinNode = j;

} }

// 如果没找到, 则表示与其他点不连通 if ( nMinNode == -1 )

{

pMark = NULL; return true;

}

// 处理该距离 小的点

pMark[nMinNode] = 1;

// (3) 调整余下的结点的 短路径

for ( j = 0; j < nNodeNum; j++ )

{ // 跳过自身

Page 330: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

322

if ( j == nNode )

continue;

// 跳过处理过的结点

if ( pMark[j] == 1 )

continue;

// 调整未处理过的结点的 短路径

// 计算直接相连的结点间的距离 dDist = GetConnectedDistance( nMinNode, j, bWeight );

if ( dDist < 0 ) // 不连通

continue;

// 更新未处理过的结点的 短路径

if ( m_pPath[j].m_dLength < 0 || m_pPath[j].m_dLength > m_pPath[nMinNode].m_dLength + dDist )

{

m_pPath[j].m_dLength = m_pPath[nMinNode].m_dLength + dDist ; m_pPath[j].m_nPreNode = nMinNode;

}

} }

pMark = NULL; return true;

}

//---------------------------------------------------------------------

CalcPath函数又调用了GetConnectedDistance函数。GetConnectedDistance函数用于得到

两个结点直接连接的距离,对于不直接连接的两点返回-1。返回的结果考虑了方向权重,

因此返回结果与参数nNode1、nNode2的参数顺序有关。该函数的实现代码如下:

//---------------------------------------------------------------------

// 得到两个结点直接连接的距离, 不直接连接则返回-1

// 返回的结果考虑了方向权重, 因此与nNode1,nNode2的参数顺序有关 double TNetLayer::GetConnectedDistance( int nNode1, int nNode2,

bool bWeight )

{ if ( nNode1 < 0 || nNode1 >= m_arrNodes->Count )

return -1;

if ( nNode2 < 0 || nNode2 >= m_arrNodes->Count ) return -1;

if ( nNode1 == nNode2 ) return 0;

int nLink;

Page 331: C++ Builder和MapObjects实现

323

第 6章 选择与查询功能的实现

double dDistance;

double dMinDist = 1.7e+308;

int nRes = 0; int i = 0;

//遍历与结点1相连的弧段,判断弧段的另一端的结点是否为结点2,是则返回距离

for ( i = 0; i < ((NetNode*)m_arrNodes ->Items[nNode1])->m_arrLinks->Count; i++ )

{

nLink = ((NetEdge*)((NetNode*)m_arrNodes ->Items[nNode1])->m_arrLinks

->Items[i])->nLink;

if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nFNode == nNode1 ) {

if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nTNode == nNode2 )

{ dDistance = bWeight ?

((NetLink*)m_arrLinks->Items[nLink])->m_fFromImp :

((NetLink*)m_arrLinks->Items[nLink])->m_fLength; if ( dDistance < dMinDist )

{

dMinDist = dDistance; nRes = 1;

}

} }

else if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nTNode == nNode1 )

{ if ( ((NetLink*)m_arrLinks->Items[nLink])->m_nFNode == nNode2 )

{

dDistance = bWeight ? ((NetLink*)m_arrLinks->Items[nLink])->m_fToImp :

((NetLink*)m_arrLinks->Items[nLink])->m_fLength;

if ( dDistance < dMinDist ) {

dMinDist = dDistance;

nRes = -1; }

}

} }

if ( nRes == 0 ) return -1;

return dMinDist; }

Page 332: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

324

//---------------------------------------------------------------------

PathAnalysis函数调用的第三个函数是CreateResultPath,该函数用于由结点生成路径的

结果图层,实现代码如下。参数pNodes表示网络分析结果结点集,nNum表示结点数目,

szLayerName表示结果图层名称,当bWeight为TRUE时,表示计算 短路径时考虑方向权重,

为FALSE则表示不考虑:

//---------------------------------------------------------------------

// 由结点生成路径的结果图层 // 参数含义:

// pNodes:网络分析结果结点集

// nNum:结点数目 // szLayerName:结果图层名称

//bWeight:TRUE为计算 短路径时考虑方向权重,FALSE为不考虑

bool TNetLayer::CreateResultPath( TList* pNodes, NetLine* line, bool bWeight )

{

int i, j;

// 将分析结果结点集转换为线加入到结果图层中

int nNum; int nRes;

int nNode1, nNode2, nLink;

double dDistance, dRatio; double dTotalImp;

nNum = pNodes->Count;

int idLine;

dTotalImp = 0;

for ( i = 0; i < nNum-1; i++ ) {

// 得到连接两个结点的 短弧段

nNode1 = *((int*)pNodes->Items[i]); nNode2 = *((int*)pNodes->Items[i+1]);

nRes = IsConnectedDirectly( nNode1, nNode2, &nLink, &dDistance,

bWeight );

// 不连通则返回false. 这种情况理论上是不可能出现的

if ( nRes == 0 ) {

return false;

} if ( nLink == -1 )

continue;

// 将该弧段的结点按路径顺序加入到结果图层的线中

idLine = ((NetLink*)m_arrLinks->Items[nLink])->m_GeoID;

Page 333: C++ Builder和MapObjects实现

325

第 6章 选择与查询功能的实现

NetLine* tmpLine = new NetLine(m_layer);

tmpLine->GetLineData( idLine );

// 只加入起始结点和终止结点之间的点

double dDist;

int nSegIndex1, nSegIndex2; NetPoint *ptNearst1, *ptNearst2, *ptTemp;

ptTemp = new NetPoint(); ptTemp->x = ((NetNode*)m_arrNodes->Items[nNode1])->x;

ptTemp->y = ((NetNode*)m_arrNodes->Items[nNode1])->y;

ptNearst1 = new NetPoint(); tmpLine->GetNearestPoint( ptTemp, ptNearst1, &nSegIndex1, &dDist );

ptTemp->x= ((NetNode*)m_arrNodes->Items[nNode2])->x;

ptTemp->y= ((NetNode*)m_arrNodes->Items[nNode2])->y;

ptNearst2 = new NetPoint(); tmpLine->GetNearestPoint( ptTemp, ptNearst2, &nSegIndex2, &dDist );

dRatio = ((NetLink*)m_arrLinks->Items[nLink])->m_fLength / tmpLine->CalcLength();

if ( nRes == 1 )

{ // 正向

line->AddCoord( ptNearst1 );

for ( j = nSegIndex1; j < nSegIndex2; j++ ) line->AddCoord( (NetPoint*)tmpLine->m_pCoords->

Items[j+1] );

line->AddCoord( ptNearst2 );

dTotalImp += ((NetLink*)m_arrLinks->Items[nLink])->m_fFromImp;

} else if ( nRes == -1 )

{

// 逆向 line->AddCoord( ptNearst1 );

for ( j = nSegIndex1; j > nSegIndex2; j-- )

line->AddCoord( (NetPoint*)tmpLine->m_pCoords->Items[j] ); line->AddCoord( ptNearst2 );

dTotalImp += ((NetLink*)m_arrLinks->Items[nLink])->m_fToImp; }

tmpLine = NULL; if ( line->m_pCoords->Count < 2 )

Page 334: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

326

{

line->m_pCoords->Clear();

continue; }

}

return true;

}

//---------------------------------------------------------------------

CreateResultPath函数又调用了IsConnectedDirectly函数,该函数用于判断两个结点是否

直接相连,即两个结点在同一条弧段上。函数实现代码如下,返回0表示不直接连通;返回

1表示直接连通,且从nNode1到nNode2为正向连接;返回-1表示直接连通,且从nNode1到nNode2为逆向连接。如果两个结点直接连通,则该函数同时返回连接两个结点的弧段nLink,以及直接连通的 小阻力加权距离 dDistance。如果不直接连通,则 *nLink=-1,*dDistance=-1:

//---------------------------------------------------------------------

// 判断两个结点是否直接相连. 即两个结点在同一条弧段上

// 返回值: 0 不直接连通 // 1 直接连通, 且从nNode1到nNode2为正向连接

// -1 直接连通, 且从nNode1到nNode2为逆向连接

/* 如果两个结点直接连通, 则同时返回连接两个结点的弧段*nLink,以及直接连通的 小阻力加

权距离*dDistance */

// 如果不直接连通, 则*nLink=-1, *dDistance=-1

int TNetLayer::IsConnectedDirectly( int nNode1, int nNode2, int* nLink, double* dDistance,

bool bWeight )

{ *nLink = -1;

if ( nNode1 < 0 || nNode1 >= m_arrNodes->Count ) return 0;

if ( nNode2 < 0 || nNode2 >= m_arrNodes->Count )

return 0;

if ( nNode1 == nNode2 )

return 1;

int i = 0;

int nRes = 0; int nMinLink = -1;

double dMinDist = 1.7e+308;

// 遍历与结点1相连的弧段, 判断弧段的另一端的结点是否为结点2 for ( i = 0; i < ((NetNode*)m_arrNodes->

Items[nNode1])->m_arrLinks->Count; i++ )

Page 335: C++ Builder和MapObjects实现

327

第 6章 选择与查询功能的实现

{

*nLink = ((NetEdge*)((NetNode*)m_arrNodes

->Items[nNode1])->m_arrLinks ->Items[i])->nLink;

if ( ((NetLink*)m_arrLinks->Items[*nLink])->m_nFNode == nNode1 )

{ if ( ((NetLink*)m_arrLinks->Items[*nLink])->m_nTNode == nNode2 )

{

double temp1 = ((NetLink*)m_arrLinks->Items[*nLink]) ->m_fFromImp;

double temp2 =

((NetLink*)m_arrLinks->Items[*nLink])->m_fLength; if(bWeight)

*dDistance = temp1;

else *dDistance = temp2;

if ( *dDistance < dMinDist )

{ dMinDist = *dDistance;

nMinLink = *nLink;

nRes = 1; }

}

} else if ( ((NetLink*)m_arrLinks->Items[*nLink])->m_nTNode ==

nNode1 )

{ if ( ((NetLink*)m_arrLinks->Items[*nLink])->m_nFNode == nNode2 )

{

double temp1 = ((NetLink*)m_arrLinks->Items[*nLink])->m_fToImp;

double temp2 =

((NetLink*)m_arrLinks->Items[*nLink])->m_fLength; *dDistance = bWeight ? temp1: temp2;

if ( *dDistance < dMinDist )

{ dMinDist = *dDistance;

nMinLink = *nLink;

nRes = -1; }

}

} }

if ( nRes == 0 ) {

Page 336: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

328

*nLink = -1;

*dDistance = -1;

return 0; }

*nLink = nMinLink; *dDistance = dMinDist;

return nRes;

} //---------------------------------------------------------------------

PathAnalysis函数中调用的 后一个函数是UnloadStops,该函数用于去除加入的站点或

中心点。UnloadStops与LoadStops必须一一对应。UnloadStops函数的代码如下:

//---------------------------------------------------------------------

// 去除加入的站点或中心点,UnloadStops与LoadStops必须一一对应 bool TNetLayer::UnloadStops()

{

int i, j; int nLink, nNode;

double dAngle;

// 清空站点表

m_arrStops->Clear();

// 利用备份数据恢复弧段表和结点表, 并清除备份数据

for ( i = 0; i < m_arrLinkBackups->Count; i++ )

{ nLink = ((NetLinkBackup*)m_arrLinkBackups->Items[i])->m_nIndex;

// 恢复弧段表 ((NetLink*)m_arrLinks->Items[nLink])->Copy( ((NetLinkBackup*)

m_arrLinkBackups->Items[i]) ->m_Link );

// 恢复结点表

nNode = ((NetLink*)m_arrLinks->Items[nLink])->m_nTNode;

dAngle = ((NetNode*)m_arrNodes ->Items[nNode])->GetLinkAngle( nLink );

for ( j = ((NetNode*)m_arrNodes->Items[nNode])->m_arrLinks

->Count-1; j >=0 ; j-- ) {

if ( ((NetEdge*)((NetNode*)m_arrNodes

->Items[nNode])->m_arrLinks ->Items[j])->nLink >= m_nLinkNum )

{

((NetNode*)m_arrNodes->Items[nNode]) ->m_arrLinks->Delete( j );

Page 337: C++ Builder和MapObjects实现

329

第 6章 选择与查询功能的实现

}

}

((NetNode*)m_arrNodes->Items[nNode])->Add( nLink, dAngle );

}

m_arrLinkBackups->Clear();

m_pPath = NULL;

return true; }

//---------------------------------------------------------------------

至此,用于实现 短路径查询功能的网络图层类已完成,下面要完成的就是在主界面

中调用该图层的方法实现 短路径查询。切换到主窗体MainForm,双击MinimizeDist控件,

创建该控件的OnClick事件响应函数,在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::MinimizeDistClick(TObject *Sender)

{

if (MinimizeDist->Down == true) {

// 短距离查询操作

Map->MousePointer = moCross; _environment->m_MapOpr = MO_CLOSEST;

}

else {

// 取消继续 短距离查询操作

_environment->m_MapOpr = MO_NULL; Map->MousePointer = moArrow;

}

} //---------------------------------------------------------------------

然后在Map控件的OnMouseDown事件响应函数的switch部分加入如下代码:

//--------------------------------------------------------------------- case MO_CLOSEST: // 短路经 { if (_environment->m_cloestPath.pt1 == NULL) { IMoPointPtr pt; pt = Map->ToMapPoint(X, Y); _environment->m_cloestPath.pt1 = new MPoint(); _environment->m_cloestPath.pt1->x = pt->X; _environment->m_cloestPath.pt1->y = pt->Y; _environment->m_cloestPath.pt2 = NULL; }

Page 338: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

330

else { if (m_netLayer == NULL ) { m_netLayer = new TNetLayer(m_netMapLayer,

_environment->m_dataSet); m_path = NULL; m_netLayer->ReadNetTable(); } IMoPointPtr pt; pt = Map->ToMapPoint(X, Y); _environment->m_cloestPath.pt2 = new MPoint(); _environment->m_cloestPath.pt2->x = pt->X; _environment->m_cloestPath.pt2->y = pt->Y; double x1,y1,x2,y2; x1 =_environment->m_cloestPath.pt1->x; y1 =_environment->m_cloestPath.pt1->y; x2 =_environment->m_cloestPath.pt2->x; y2 =_environment->m_cloestPath.pt2->y; _environment->m_cloestPath.pt1 = NULL; _environment->m_cloestPath.pt2 = NULL; m_path = new TList(); bool bOK = m_netLayer->PathAnalysis(x1,y1,x2,y2, m_path); if (!bOK) return; _environment->m_drawLine = NULL; _environment->m_nSelectedLineNum = 1; _environment->m_drawLine = new MLine[1]; _environment->m_drawLine[0].nPointNumber = m_path->Count/2; _environment->m_drawLine[0].pPoint = new MPoint[_environment-> m_drawLine[0].nPointNumber]; int j = 0; for (int i = 0; i < m_path->Count; i +=2) { _environment->m_drawLine[0].pPoint[j].x =

*((double*)m_path->Items[i]); _environment->m_drawLine[0].pPoint[j].y = *((double*) m_path->Items[i+1]); j ++; } Map->Extent = Map->Extent; }

Page 339: C++ Builder和MapObjects实现

331

第 6章 选择与查询功能的实现

break; } //---------------------------------------------------------------------

上述代码中利用了几个新的成员变量。切换到Main.h,在MainForm类中加入如下几个

成员变量:

//---------------------------------------------------------------------

TNetLayer* m_netLayer;

IMoMapLayerPtr m_netMapLayer; TList* m_path;

//---------------------------------------------------------------------

在窗体构造函数的其他代码的尾部,将m_netLayer与m_path成员变量初始化为NULL。此外还需要在LoadLayers函数的末尾加入如下代码,用于初始化网络图层:

//--------------------------------------------------------------------- m_netMapLayer = (IDispatch*)CreateOleObject("MapObjects2.MapLayer");

m_netMapLayer->GeoDataset = _environment->m_db->FindGeoDataset

(WideString("street")); m_netMapLayer->Visible = false;

Map->Layers->Add(m_netMapLayer);

//---------------------------------------------------------------------

编译并运行程序。先在地图控制工具栏中单击“ 短路径”按钮,然后在地图窗口中

分别单击需要查询的两点,系统将高亮显示这两点之间的 短路径,如图6.18所示。

图 6.18 短路径查询

Page 340: C++ Builder和MapObjects实现

第 7 章 系统其他辅助功能

本章将实现系统的其他一些辅助功能,例如当鼠标移动到某地物上并停留片刻后,出

现一个小标签显示该地物的名称,以及距离、面积量算等。

7.1 地名的快速显示

为了方便用户,北京市地理信息公众查询系统提供了直接从地图上查询某地物名称的

功能。当用户将鼠标移动到某地物上并停留片刻后,出现一个小标签,显示该地物的名称。

实现该功能的是TMapTip类。 选择File菜单的New命令,打开New Items对话框。在该对话框的New选项卡中选择Unit

图标,然后单击OK,将在当前项目中加入一个单元文件,将该单元文件保存为MapTip.cpp。 首先在TMapTip类中加入如下成员变量:

//---------------------------------------------------------------------

private:

double m_x; // 当前位置的x坐标 double m_y; // 当前位置的y坐标

double m_lastX; // 当计时开始时鼠标位置的x坐标

double m_lastY; // 当计时开始时鼠标位置的y坐标

TMap* m_map;

TTimer* m_timer; TEdit* m_MapTipEdit;

TEnvironment* m_env;

String m_szField; //---------------------------------------------------------------------

MapTip类的构造函数的实现代码如下,用于初始化类的成员变量:

//---------------------------------------------------------------------

TMapTip::TMapTip() {

m_x = 0;

m_y = 0; m_lastX = 0;

m_lastY;

} //---------------------------------------------------------------------

Page 341: C++ Builder和MapObjects实现

333

第 7章 系统其他辅助功能

为了引用主窗体,需要在MapTip.cpp的头部加入如下代码:

#include "Main.h"

extern TMainForm *MainForm;

依据主窗体MainForm中的成员变量设置TMapTip类成员变量的函数如下:

//--------------------------------------------------------------------- void TMapTip::Initialize()

{

m_map = MainForm->Map; m_timer = MainForm->Timer1;

m_szField = "名称";

m_env = MainForm->_environment; m_MapTipEdit->Visible = false;

m_MapTipEdit = MainForm->MapTipEdit;

} //---------------------------------------------------------------------

TMapTip类主要有MouseMove、ShowTipText、InMap、Timer与DoSearch等函数。这些

函数的代码如下:

//---------------------------------------------------------------------

// 响应地图控件的OnMouseMove事件 void TMapTip::MouseMove(double X, double Y)

{

m_x = X; m_y = Y;

if (m_timer->Interval == 10) {

m_lastX = X;

m_lastY = Y; m_timer->Interval = 100;

}

else {

m_MapTipEdit->Visible = false;

} }

//---------------------------------------------------------------------

// 显示地物名称 void TMapTip::ShowTipText(String text)

{

// 设置文本框控件的显示内容与位置 m_MapTipEdit->Visible = true;

m_MapTipEdit->Text = text;

m_MapTipEdit->Left = m_map->Left + (int)m_x + 10;

Page 342: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

334

m_MapTipEdit->Top = m_map->Top + (int)m_y + 10;

}

//--------------------------------------------------------------------- // 判断当前点的位置是否在地图控件中

bool TMapTip::InMap(double x, double y)

{ if (x > m_map->Left + m_map->Width || x < m_map->Left)

return false;

if (y < m_map->Top || y > m_map->Top + m_map->Height)

return false;

return true;

}

//--------------------------------------------------------------------- // 计时器OnTimer事件响应函数

void TMapTip::Timer()

{ if (m_x == m_lastX && m_y == m_lastY)

{

if(!InMap(m_x, m_y)) {

m_MapTipEdit->Visible = false;

return; }

m_timer->Interval = 10;

IMoRecordsetPtr recs;

recs = DoSearch();

if(recs)

{ if(!(bool)recs->EOF)

{

String szText = String(recs->Fields->Item (m_szField)->Value);

if (szText != "")

ShowTipText(szText); }

else

{ m_MapTipEdit->Visible = false;

}

} else

Page 343: C++ Builder和MapObjects实现

335

第 7章 系统其他辅助功能

{

m_MapTipEdit->Visible = false;

} }

else

{ m_lastX = m_x;

m_lastY = m_y;

} }

//---------------------------------------------------------------------

// 查询当前点对应的地物记录 IMoRecordsetPtr TMapTip::DoSearch()

{

IMoPointPtr pt; IMoRecordsetPtr rst;

pt = m_map->ToMapPoint((float)m_x,(float)m_y); double dScale = m_env->CalcScale(m_map);

dScale = dScale/10000;

dScale = dScale / 5000;

for (int i = m_env->m_nLayerNum - 1; i >= 0 ; i --)

{ if (m_env->m_layerInfos[i].layer->Visible == true

&& m_env->m_layerInfos[i].bCanSelected

&& m_env->m_layerInfos[i].layer->shapeType == moShapeTypePoint) rst = m_env->m_layerInfos[i].layer->SearchByDistance(pt,dScale,

WideString(""));

if (rst)

{

rst->MoveFirst(); if (!(bool)rst->EOF)

return rst;

} }

for (int i = 0; i < m_env->m_nLayerNum; i ++) {

if (m_env->m_layerInfos[i].bVisible &&

m_env->m_layerInfos[i].bCanSelected && m_env->m_layerInfos[i].layer->shapeType == moShapeTypeLine)

rst = m_env->m_layerInfos[i].layer->SearchByDistance(pt,dScale,

WideString(""));

Page 344: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

336

if (rst)

{

rst->MoveFirst(); if (!(bool)rst->EOF)

return rst;

} }

for (int i = 0; i < m_env->m_nLayerNum; i ++) {

if (m_env->m_layerInfos[i].bVisible &&

m_env->m_layerInfos[i].bCanSelected && m_env->m_layerInfos[i].layer->shapeType ==

moShapeTypePolygon )

rst = m_env->m_layerInfos[i].layer->SearchByDistance(pt,dScale, WideString(""));

if (rst) {

rst->MoveFirst();

if (!(bool)rst->EOF) return rst;

}

}

return rst;

} //---------------------------------------------------------------------

至此,TMapTip类的实现代码已完成。切换到MainForm窗体中,在其中加入一个Timer控件,命名为Timer1,将其Enabled属性设置为True,Interval属性设置为100。创建Timer1控件的OnTimer事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::Timer1Timer(TObject *Sender) {

if (Map->MousePointer == moArrow)

m_mapTip->Timer(); }

//---------------------------------------------------------------------

在MainForm窗体中再加入一个Edit控件,命名为MapTipEdit,将其AutoSelect属性设置

为false,BorderStyle属性设置为bsNone,Color属性设置为clYellow,Enabled属性设置为false,Visible属性设置为false。

然后在MainForm类中加入m_mapTip成员变量,代码如下:

TMapTip m_mapTip;

Page 345: C++ Builder和MapObjects实现

337

第 7章 系统其他辅助功能

在MainForm窗体的构造函数中加入如下代码,初始化m_mapTip:

m_mapTip = new TMapTip();

然后在MainForm窗体的OnCreate事件响应函数的尾部加入如下代码,设置m_mapTip对象中的成员变量:

m_mapTip->Initialize();

在地图控件的OnMouseMove事件响应函数的末尾加入如下代码:

//---------------------------------------------------------------------

if (Map->MousePointer == moArrow) {

m_mapTip->MouseMove(X, Y);

} //---------------------------------------------------------------------

编译并运行程序。当鼠标移动到某地物上并停留片刻后,会出现一个小标签,显示该

地物的名称,如图7.1所示。

图 7.1 快速显示地物名称

7.2 距离与面积量算

距离量算即线量算,计算的是两点之间的距离,而面积量算计算的是某一多边形的面

积。实现量算的具体函数是 GetLineLength 、 GetPolygonLength 与 GetPolygonArea 。GetLineLength函数用于计算用户在地图上绘制的一条线段的长度,GetPolygonLength函数

Page 346: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

338

用于计算用户在地图上绘制的多边形的周长,而GetPolygonArea函数用于计算多边形的面

积。 首先切换到Environment.h文件,在TEnvironment类中加入如下代码,用于声明几个新

增加的成员函数:

//---------------------------------------------------------------------

double GetLineLength(IMoLinePtr line);

double GetPolygonLength(IMoPolygonPtr poly); double GetPolygonArea(IMoPolygonPtr poly);

double CalcArea(MPoint* pt, int nSize);

//---------------------------------------------------------------------

这些成员函数的实现代码如下:

//---------------------------------------------------------------------

double TEnvironment::GetLineLength(IMoLinePtr line)

{ MPoint* pt;

int nPtCount;

IMoPointsPtr pts;

pts = line->Parts->Item(0);

nPtCount = pts->Count;

if (nPtCount < 2)

return 0.0;

pt = new MPoint[nPtCount];

for (int i = 0; i < nPtCount; i ++) {

IMoPointPtr point = pts->Item(i);

pt[i].x = point->X; pt[i].y = point->Y;

}

return CalcLenght(pt, nPtCount);

}

//--------------------------------------------------------------------- double TEnvironment::GetPolygonLength(IMoPolygonPtr poly)

{

MPoint* pt; int nPtCount;

IMoPointsPtr pts;

pts = poly->Parts->Item(0);

nPtCount = pts->Count;

Page 347: C++ Builder和MapObjects实现

339

第 7章 系统其他辅助功能

if (nPtCount < 2)

return 0.0;

pt = new MPoint[nPtCount+1];

for (int i = 0; i < nPtCount; i ++)

{ IMoPointPtr point = pts->Item(i);

pt[i].x = point->X;

pt[i].y = point->Y; }

pt[nPtCount].x = pt[0].x; pt[nPtCount].y = pt[0].y;

return CalcLenght(pt, nPtCount+1); }

//---------------------------------------------------------------------

double TEnvironment::GetPolygonArea(IMoPolygonPtr poly) {

MPoint* pt;

int nPtCount; IMoPointsPtr pts;

pts = poly->Parts->Item(0); nPtCount = pts->Count;

if (nPtCount < 3) return 0.0;

pt = new MPoint[nPtCount+1]; for (int i = 0; i < nPtCount; i ++)

{

IMoPointPtr point = pts->Item(i); pt[i].x = point->X;

pt[i].y = point->Y;

}

pt[nPtCount].x = pt[0].x;

pt[nPtCount].y = pt[0].y;

return CalcArea(pt, nPtCount+1);

} //---------------------------------------------------------------------

double TEnvironment::CalcArea(MPoint* pt, int nSize)

{ double dArea = 0;

Page 348: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

340

double x1=0,x2=0,y1=0,y2=0;

int nCenterL = ((int)(pt[0].x)/6+1)*6-3;

for(int i=0;i<nSize-1;i++)

{

CalGuassFromLB(pt[i].x, pt[i].y, &x1, &y1, nCenterL); CalGuassFromLB(pt[i+1].x, pt[i+1].y, &x2, &y2, nCenterL);

dArea += (x2-x1)*(y1+y2)/2;

}

if (dArea < 0)

dArea = 0 - dArea;

return dArea;

} //---------------------------------------------------------------------

下面要做的只是调用这些函数,实现距离与面积量算。 首先实现距离量算。在主窗体MainForm中双击LineMeasure控件,创建该控件的OnClick

事件响应函数,在其中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::LineMeasureClick(TObject *Sender) {

if(LineMeasure->Down == true)

{ // 距离量算操作

_environment->m_MapOpr = MO_LINEMEAS;

Map->MousePointer = moCross; }

else

{ // 取消继续距离量算操作

Map->MousePointer = moArrow;

_environment->m_MapOpr = MO_NULL; }

}

//---------------------------------------------------------------------

然后在Map控件的OnMouseDown事件响应函数的switch部分加入如下代码:

//---------------------------------------------------------------------

case MO_LINEMEAS: // 距离量算

{ IMoLinePtr line;

line = Map->TrackLine();

double dLength = _environment->GetLineLength(line); AnsiString strResult = "您选择的线路长度:" + FloatToStr(dLength) + "米。

Page 349: C++ Builder和MapObjects实现

341

第 7章 系统其他辅助功能

";

MessageBox(NULL, strResult.c_str(), "北京市地理信息公众查询系统", MB_OK);

break; }

//---------------------------------------------------------------------

上述代码利用地图控件的TrackLine方法跟踪用户在地图中绘制的一条线段,然后调用

TEnvironment类的GetLineLength方法得到该线段的长度, 后调用MessageBox函数显示线

段的长度。 在主窗体MainForm中双击PolyMeasure控件,创建该控件的OnClick事件响应函数,在

其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::PolyMeasureClick(TObject *Sender)

{

if(PolyMeasure->Down == true) {

// 距离量算操作

_environment->m_MapOpr = MO_POLYMEAS; Map->MousePointer = moCross;

}

else {

// 取消继续距离量算操作

Map->MousePointer = moArrow; _environment->m_MapOpr = MO_NULL;

}

} //---------------------------------------------------------------------

然后在Map控件的OnMouseDown事件响应函数的switch部分加入如下代码:

//---------------------------------------------------------------------

case MO_POLYMEAS: //面积量算 {

IMoPolygonPtr poly;

poly = Map->TrackPolygon(); double dLength,dArea;

dLength = _environment->GetPolygonLength(poly);

dArea = _environment->GetPolygonArea(poly); AnsiString strResult = "您选择的区域周长:"

+ FloatToStr(dLength) + "米,面积:"

+ FloatToStr(dArea)+"平方米。" ; MessageBox(NULL, strResult.c_str(), "北京市地理信息公众查询系统", MB_OK);

break;

} //---------------------------------------------------------------------

Page 350: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

342

编译并运行程序,便可通过地图控制工具栏中的“距离量算”与“面积量算”两个按

钮来实现长度、周长、面积的量算。

7.3 地图输出子系统的实现

输出在MapObjects中的地图有3种方法。一是将地图复制到剪贴板上,然后粘贴到其他

应用程序中,例如Microsoft Word、画笔等。二是将地图保存为栅格文件,例如位图文件、

图元文件等等。三是将地图提供给另一个设备,比如一台打印机或另一个窗口。“北京市

地理信息公众查询系统”的输出子系统只需要实现打印地图。 Map控件的PrintMap方法用于打印显示部分的地图。该方法的语法如下:

PrintMap ( docName, outputFile, landscape)

其中,参数docName表示打印队列中的作业名,outputFile是说明文件名规范的字符串,也

可以为空,landscape是一个布尔变量,说明是否按横向打印地图。True为横向打印,False为纵向打印。

MapObjects主要用于在计算机屏幕上显示地图,虽然可以将地图输出到任一个已在 Windows 中安装了驱动程序的打印机或绘图仪上,但是MapObjects 并不擅长于高质量的

制图。它缺少用来定义用户需求的线和填充符号的软设备,也没有支持地图布局的内在功

能。因此如果想制作发行级别的地图,请使用其他的GIS软件,如 ARC/INFO 或 Arc View。 在主窗体MainForm中双击PrintToolButton控件,创建该控件的OnClick事件响应函数,

在其中加入如下代码:

//--------------------------------------------------------------------- void __fastcall TMainForm::PrintToolButtonClick(TObject *Sender)

{

Map->PrintMap(WideString(_environment->m_mapInfos[_environment-> m_nCurrMapIndex].szName), WideString(""),true);

}

//---------------------------------------------------------------------

MapObjects的地图控件提供了两个方法用于将地图输出到剪贴板和文件中,它们分别

是ExportMap和ExportMap2。 ExportMap方法将当前图形显示部分以Windows位图或增强型的图元文件格式输出。语

法为:

ExportMap(format, formatData, scaleFactor)

其中,参数format是输出格式常量,用于说明输出文件的格式,可取的常量值及其说明如

表7.1所示。如果是输出到文件中,参数formatData则表示输出文件在硬盘中的路径和文件

名。如果是输出到剪贴板上,表示是否清理剪贴板中的内容。True表示清理剪贴板,False表示不清理。参数scaleFactor表示输出分辨率,如果值为1,表示以屏幕上显示图形的点阵

Page 351: C++ Builder和MapObjects实现

343

第 7章 系统其他辅助功能

数输出;如果值为10,则以原来x方向和y方向上点阵的10倍输出。

表7.1 输出格式常量及其说明

常量 值 说明

moExportEMF 0 输出一个Windows增强的图元格式文件(EMF)

moExportBMP 1 输出一个位图文件(BMP)

moExportClipboardEMF 2 在剪贴板上复制一个图元文件

moExportClipboardBMP 3 在剪贴板上复制一个位图文件

ExportMap2方法同样将图形显示部分以Windows位图或增强型的图元文件格式输出。

语法为:

ExportMap2(format, formatData, scaleFactor, useSourceDept)

该方法的前3个参数与ExportMap中的参数意义完全一样, 后一个参数是一个布尔表

达式,表明是否利用控件中栅格文件层的颜色模板。 此外,MapObjects的地图控件也提供了两个方法用于将显示部分的地图输出到指定的

设备上,这些设备可以是显示器、绘图仪、打印机等,这两个方法分别是OutputMap和OutputMap2。

OutputMap方法的语法如下:

OutputMap ( hDC)

其中,参数hDC表示设备描述表。 OutputMap2方法能设置输出范围。如果不想绘制地图的背景范围,可以将参数drawFlag

设置为moNoBackground。给定任意的高度和宽度,该方法将按比例把地图放到设备中设定

的这个范围内。该方法的语法如下:

OutputMap ( hDC, x, y, width, height, drawFlag )

参数hDC表示设备描述表,参数x表示设备坐标系目标范围的左上角的x坐标值,参数y表示设备坐标系目标范围的左上角的y坐标值,width表示设备坐标系目标范围的宽度,

height表示设备坐标系目标范围的高度,drawFlag决定如何在设备上输出地图:如果将该参

数设置为moNoBackgound或1,表示不画背景矩形,如果设置为moClipToExtent或2,表示

裁剪输出的地图,以保证输出的结果地图不大于真实的地图范围。

7.4 在线帮助子系统的实现

在主窗体MainForm中双击ShowHelp控件,创建该控件的OnClick事件响应函数,在其

中加入如下代码:

//---------------------------------------------------------------------

void __fastcall TMainForm::ShowHelpClick(TObject *Sender)

Page 352: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

344

{

AnsiString strFileName = "start.htm";

ShellExecute(Handle, "open", strFileName.c_str(), 0, 0, SW_SHOW); }

//---------------------------------------------------------------------

上述函数调用ShellExecute函数在应用程序中运行另一个程序,该程序是操作系统默认

打开文件的应用程序。ShellExecute函数可用于打开或打印文件,这里的文件可以是可执行

文件或其他文档文件。如果在函数中指定的文件是一个文档文件,就启动默认打开此文件

的可执行文件,并打开此文件。 函数ShellExecute()的原型如下所示:

HINSTANCE ShellExecute( HWND hwnd, // 父窗口句柄

LPCTSTR lpOperation, // 要执行操作的字符串指针

LPCTSTR lpFile, // 文件名或目录字符串指针 LPCTSTR lpParameters, // 可执行文件参数的字符串指针

LPCTSTR lpDirectory, // 默认路径字符串指针

INT nShowCmd // 指定在打开文件时是否显示 );

参数lpOperation可以是Open、Print或Explore三者之一。这里使用的都是Open,指的是打开

由参数lpFile指定的文件。 另外两个在应用程序中启动另一个程序的函数是ShellExecuteEx与CreateProcess函数。

ShellExecute函数的调用相对比较简单,CreateProcess函数可以更多地控制低级属性,例如

安全性以及创建后台进程,而ShellExecuteEx函数更适合用于前台的程序,可以通过使用

WaitForSingleObject函数来等待。

7.5 其他快捷按钮功能的实现

至此,系统的大部分功能都已实现。下面的任务是完成地图控制工具栏中快捷按钮的

功能。 创建MapIndex控件的OnClick事件响应函数,在其中加入如下代码,用于将“地图”页

面的“地图索引”选项卡显示在 前端:

//---------------------------------------------------------------------

void __fastcall TMainForm::MapIndexClick(TObject *Sender)

{ PageControl1->ActivePage = MapTabSheet;

PageControl2->ActivePage = MapIndexTabSheet;

} //---------------------------------------------------------------------

创建FeatureControl控件的OnClick事件响应函数,在其中加入如下代码,用于将“地图”

页面的“地物控制”选项卡显示在 前端:

Page 353: C++ Builder和MapObjects实现

345

第 7章 系统其他辅助功能

//---------------------------------------------------------------------

void __fastcall TMainForm::FeatureControlClick(TObject *Sender)

{ PageControl1->ActivePage = MapTabSheet;

PageControl2->ActivePage = FeatureTabSheet;

} //---------------------------------------------------------------------

创建IndexToolButton控件的OnClick事件响应函数,在其中加入如下代码,用于将“查

询”页面的“地名索引”选项卡显示在 前端:

//---------------------------------------------------------------------

void __fastcall TMainForm::IndexToolButtonClick(TObject *Sender) {

PageControl1->ActivePage = QueryTabSheet;

PageControl3->ActivePage = IndexTabSheet; }

//---------------------------------------------------------------------

创建QueryToolButton控件的OnClick事件响应函数,在其中加入如下代码,用于将“查

询”页面的“地名查询”选项卡显示在 前端:

//--------------------------------------------------------------------- void __fastcall TMainForm::QueryToolButtonClick(TObject *Sender) { PageControl1->ActivePage = QueryTabSheet; PageControl3->ActivePage = NameQueryTabSheet; } //---------------------------------------------------------------------

创建DistToolButton控件的OnClick事件响应函数,在其中加入如下代码,用于将“查

询”页面的“查找 近”选项卡显示在 前端:

//--------------------------------------------------------------------- void __fastcall TMainForm::DistToolButtonClick(TObject *Sender) { PageControl1->ActivePage = QueryTabSheet; PageControl3->ActivePage = DistTabSheet; } //---------------------------------------------------------------------

创建BusToolButton控件的OnClick事件响应函数,在其中加入如下代码,用于将“查询”

页面的“公交查询”选项卡显示在 前端:

//--------------------------------------------------------------------- void __fastcall TMainForm::BusToolButtonClick(TObject *Sender) { PageControl1->ActivePage = QueryTabSheet; PageControl3->ActivePage = BusTabSheet; } //---------------------------------------------------------------------

Page 354: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

346

创建PrintToolButton控件的OnClick事件响应函数,在其中加入如下代码,用于实现打

印当前地图:

//--------------------------------------------------------------------- void __fastcall TMainForm::PrintToolButtonClick(TObject *Sender) { Map->PrintMap(WideString(_environment->m_mapInfos[_environment-> m_nCurrMapIndex].szName), WideString(""),true); } //---------------------------------------------------------------------

创建ExitToolButton控件的OnClick事件响应函数,在其中加入如下代码,退出系统:

//--------------------------------------------------------------------- void __fastcall TMainForm::ExitToolButtonClick(TObject *Sender) { Close(); } //---------------------------------------------------------------------

7.6 程序封面的实现

有些读者可能很羡慕一些程序在启动的时候,先出现一个非常漂亮的窗口,过一会儿

才进入应用程序的主体。这一节就来介绍这种技术的实现,以及如何实现图像的淡入淡出。 选择File菜单的New Form命令,在当前项目中加入一个新窗体,命名为StartForm,将

其单元文件命名为StartPage.cpp。在StartForm窗体中加入一个Image控件,命名为Image1,然后加入2个Label控件,分别命名为Label1与Label2, 后加入一个TTimer控件,命名为

Timer1。StartForm窗体在设计期间的界面如图7.2所示。

图 7.2 StartForm 窗体在设计期间的界面

Page 355: C++ Builder和MapObjects实现

347

第 7章 系统其他辅助功能

按照表7.2设置StartForm窗体及其控件的属性。

表7.2 StartForm窗体及其控件的属性

控件 属性 属性值

AutoScroll false

AutoSize false

BorderStyle bsNone

StartForm

Position poDesktopCenter

Image1 Align alClient

Font.Color clBlue

Font.Name 华文行楷

Label1

Font.Size 26

Font.Color clRed

Font.Name 华文行楷

Label2

Font.Size 26

Timer1 Interval 1000

切换到StartPage.h文件,首先加入如下代码,定义一个全局变量:

#define BRUSHCOUNT 128

然后在TStartForm类的私有段加入如下成员变量与成员方法:

//---------------------------------------------------------------------

private:

TRect BitmapRect; TBrush *PositiveMasks[BRUSHCOUNT]; //正画刷

TBrush *NegativeMasks[BRUSHCOUNT]; //负画刷

TBrush *SolidBlackBrush; Graphics::TBitmap *FadeBitmap;

Graphics::TBitmap *WorkSpaceBitmap;

int Progress; void ProcessFadeFromBlackStep(void); //淡出

//---------------------------------------------------------------------

创建StartForm窗体的OnCreate事件响应函数,在其中加入如下代码,用于初始化成员

变量、创建画刷、装载图像等:

//--------------------------------------------------------------------- void __fastcall TStartForm::FormCreate(TObject *Sender)

{

RECT BrushRect; BrushRect.left = BrushRect.top = 0;

BrushRect.right= BrushRect.bottom=8;

Page 356: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

348

for(int j=0; j<BRUSHCOUNT;j++)

{

PositiveMasks[j] = new TBrush; NegativeMasks[j] = new TBrush;

PositiveMasks[j]->Bitmap = new Graphics::TBitmap;

NegativeMasks[j]->Bitmap = new Graphics::TBitmap; PositiveMasks[j]->Bitmap->Width=8;

PositiveMasks[j]->Bitmap->Height=8;

Randomize(); for(int i=0;i<8*j;i++)

PositiveMasks[j]->Bitmap->Canvas->Pixels[random(8)]

[random(8)]=clBlack; NegativeMasks[j]->Bitmap->Assign(PositiveMasks[j]->Bitmap);

InvertRect(NegativeMasks[j]->Bitmap->Canvas->Handle, &BrushRect);

} SolidBlackBrush = new TBrush;

SolidBlackBrush->Style = bsSolid;

SolidBlackBrush->Color = clBlack; FadeBitmap = new Graphics::TBitmap;

WorkSpaceBitmap = new Graphics::TBitmap;

Progress = 0;

FadeBitmap->LoadFromFile("start.bmp");

WorkSpaceBitmap->Width = FadeBitmap->Width; WorkSpaceBitmap->Height = FadeBitmap->Height;

WorkSpaceBitmap->Canvas->Draw(0,0,FadeBitmap);

BitmapRect = Rect(0,0,FadeBitmap->Width,FadeBitmap->Height);

}

//---------------------------------------------------------------------

创建StartForm窗体的OnDestroy事件响应函数,在其中加入如下代码,用于释放资源:

//---------------------------------------------------------------------

void __fastcall TStartForm::FormDestroy(TObject *Sender)

{ for (int j=0;j<BRUSHCOUNT;j++)

{

delete PositiveMasks[j]->Bitmap; delete NegativeMasks[j]->Bitmap;

delete PositiveMasks[j];

delete NegativeMasks[j]; }

delete SolidBlackBrush; delete FadeBitmap;

delete WorkSpaceBitmap;

Page 357: C++ Builder和MapObjects实现

349

第 7章 系统其他辅助功能

}

//---------------------------------------------------------------------

创建Timer1的OnTimer事件句柄,并在其中加入如下代码,用于调用自定义函数

ProcessFadeFromBlackStep,实现图像的淡出。

//--------------------------------------------------------------------- void __fastcall TStartForm::Timer1Timer(TObject *Sender)

{

ProcessFadeFromBlackStep(); }

//---------------------------------------------------------------------

自定义函数ProcessFadeFromBlackStep的实现代码如下所示:

//--------------------------------------------------------------------- void TStartForm::ProcessFadeFromBlackStep(void)

{

if(Progress == BRUSHCOUNT) {

Progress = 0;

Timer1->Enabled = false; WorkSpaceBitmap->Canvas->CopyMode=cmSrcCopy;

WorkSpaceBitmap->Canvas->Draw(0,0,FadeBitmap);

} else

{

WorkSpaceBitmap->Canvas->Brush = NegativeMasks[Progress]; WorkSpaceBitmap->Canvas->CopyMode=cmMergeCopy;

WorkSpaceBitmap->Canvas->Draw(0,0,FadeBitmap);

Progress++; }

Image1->Canvas->Draw(0,0,WorkSpaceBitmap);

} //---------------------------------------------------------------------

后创建Image1的OnClick事件响应函数,在其中加入如下代码,用于实现当用户单击

窗体时,关闭系统启动窗体,进入系统的主界面:

//---------------------------------------------------------------------

void __fastcall TStartForm::Image1Click(TObject *Sender) {

Close();

} //---------------------------------------------------------------------

在上面的程序中,我们利用了图像的淡入淡出技术。图像淡入、淡出在Windows的多

媒体程序设计中是一种常用技术,主要用来制作程序片头或者多幅图像之间的切换等。淡

入淡出图像实现的方法很多,例如利用调色板以及利用光栅映射模式等。

Page 358: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

350

利用调色板,这种方法只能在基于调色板的显示模式下工作。在Windows的显示模式

中,只有256色模式是基于调色板的,这种模式多用于多媒体程序。在基于调色板的256色模式下,当显示一幅图像时,Windows把图像的逻辑调色板载入,并通过调色板映射把逻

辑调色板映射到系统调色板,图像的每一像素的显示颜色都映射自系统调色板的一个颜色

索引值,每个索引对应一个24位的RGB全彩色值。当系统调色板改变时,当前引用系统调

色板的窗口的像素颜色也随之改变,而这种改变是系统在硬件刷屏中自动完成的,速度极

快,这样就为利用调色板实现图像的淡入淡出提供了引擎。实际上,利用这种技术可以实

现多种动画效果,图像淡入淡出只是其中的一种。 利用调色板实现图像的淡入淡出有一些缺陷,实现过程中的显示效果可能不尽人意。

因此,还可以通过位图画刷和光栅操作来克服其中的不足。 一幅图像可以在其上点 黑色而使其变黑,淡出为黑色意味着起先加入少量黑点然后

依次增多直到图像消失为止。从黑色淡入则反之,起先加入大量黑点,然后依次减少直到

图像清晰显示为止。 可以建立一系列8×8黑白相间的位图画刷,位图画刷提供了使图像变黑的泼溅属性,

浅色的画刷(意思是比黑色画刷白一点),可以使图像稍微变黑一些。而一幅图像可以用

比白色黑得多的画刷使其迅速变黑。为了使图像淡入为黑色,可以利用一串逐渐加深的画

刷向图像上泼溅黑点。当图像从黑色淡出时,可以在开始时用黑一些的画刷,然后逐渐改

用浅色的画刷。图7.3是11个颜色渐变的位图画刷,颜色由浅变深,我们称它为正画刷,相

反,颜色由深变浅的位图画刷称作负画刷。

图 7.3 位图画刷

如果这时编译应用程序,没有任何错误,但是一运行,程序还是直接进入系统的主界

面,并没有出现StartForm。那么StartForm哪里去了呢?我们如何实现程序的封面呢? 要实现程序的封面,就要让窗体StartForm出现在系统主界面窗体之前。那么如何控制

它们的出现顺序呢?显然,它们的出现顺序不能由它们自身来控制,而是由应用程序的入

口来控制。Windows应用程序都有一个WinMain函数作为应用程序的入口,就像DOS应用

程序中的main函数一样。那么在C++ Builder中,这个入口在哪里呢? 一般情况下,程序员并不需要直接编写这个入口函数,因此C++ Builder将包含此函数

的文件隐藏了起来。不过我们可以通过选择Project菜单的View Source命令打开此文件。将

该文件修改成如下代码:

#include "StartPage.h"

//---------------------------------------------------------------------

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {

Page 359: C++ Builder和MapObjects实现

351

第 7章 系统其他辅助功能

try

{

Application->Initialize(); TStartForm *StartForm = new TStartForm(NULL);

StartForm->ShowModal();

delete StartForm; StartForm = NULL;

Application->CreateForm(__classid(TMainForm), &MainForm);

Application->Run(); }

catch (Exception &exception)

{ Application->ShowException(&exception);

}

return 0; }

//---------------------------------------------------------------------

在上面的程序中,在调用Application->CreateForm方法之前,我们动态地创建了一个

StartForm的实例,然后用模式窗口显示。从而达到在窗体MainForm显示之前显示窗口

StartForm,实现程序封面的效果,如图7.4所示。

图 7.4 程序封面效果

Page 360: C++ Builder和MapObjects实现

第 8 章 MapObjects 的其他对象

“北京市地理信息公众查询系统”涉及到了MapObjects组件中的大部分对象,但是由

于其功能需求,并没有囊括MapObjects组件的所有对象,这其中包括投影对象、地址匹配

对象、动态跟踪层(TrackingLayer)对象与GeoEvent对象。本章将补充介绍这些对象的使

用方法。

8.1 动态跟踪层对象与GeoEvent对象

动态跟踪层(TrackingLayer)对象是地图控件中的一个特殊图层,它描绘位置可以动

态改变的地理对象,如全球定位系统(Global Position System,GPS)中的目标位置。这些

目标称为事件(Event),用GeoEvent对象来表示。使用TrackingLayer对象中的AddEvent方法,便可以增加一个GeoEvent对象到地图控件的TrackingLayer图层中;使用Clear方法可

以清除TrackingLayer图层中所有的GeoEvent对象;TrackingLayer对象的EventCount属性包

含了当前TrackingLayer图层中GeoEvent对象的个数。每一个GeoEvent对象有一个对应的

Symbol对象,描述该对象显示时的形状、颜色等。TrackingLayer对象的Visible属性控制是

否显示TrackingLayer图层。 TrackingLayer对象代表地图控件中的一个图层,它显示在层集之后,并可相对层集独

立重显。GeoEvent对象代表一些TrackingLayer图层中的离散目标,这些目标可以用编程的

方法移动。所有GeoEvent对象均显示在地图控件的TrackingLayer对象上。一个GeoEvent对象用一个Symbol对象来描述,其X、Y属性可以读取GeoEvent对象的地理位置。GeoEvent对象使用Move和MoveTo方法进行移动。

8.1.1 TrackingLayer对象的属性

TrackingLayer对象包含了Event、EventCount、Symbol、SymbolCount和Visible等5个属

性。

(1)Event属性:该属性指向TrackingLayer对象的GeoEvent组。它通过GeoEvent组中

成员的序列号,读取该位置的GeoEvent对象。 (2)EventCount属性:表示TrackingLayer对象包含GeoEvent对象的个数。该属性是只

读的。 (3)Symbol属性:该属性可根据TrackingLayer中Symbol组中各对象的相对位置,读

取Symbol对象的地址指针。 (4)SymbolCount属性:该属性值表示在TrackingLayer对象中包含的Symbol对象的数

Page 361: C++ Builder和MapObjects实现

353

第 8章 MapObjects的其他对象

目,该属性可读可写。 (5)Visible属性:该属性控制是否显示TrackingLayer图层。当该属性值为true时,

TrackingLayer中的GeoEvent对象是可见的,而当值为false时,则所有在TrackingLayer中的

GeoEvent对象都将隐藏。

8.1.2 TrackingLayer对象的方法

TrackingLayer对象包含AddEvent、ClearEvent、FindEvent、RemoveEvent和Refresh方法。

(1)AddEvent方法:该方法在指定的位置上增加一个新的GeoEvent对象,其显示的形

状将根据Symbol组中指定位置的Symbol属性决定。 (2)ClearEvent方法:该方法用于清除TrackingLayer中所有的GeoEvent对象和相应的

Symbol对象。删除单个GeoEvent对象可使用RemoveEvent方法,而要删除所有的Symbol对象,只要将SymbolCount设置为0即可。

(3)FindEvent方法:该方法用于寻找满足指定条件的GeoEvent对象。满足指定条件

的GeoEvent对象可能有多个,但是该方法只返回第一个满足条件的GeoEvent对象。 (4)RemoveEvent方法:该方法用于删除指定的GeoEvent对象和相应的TrackingLayer

中的Symbol对象。 (5)Refresh方法:该方法强制重画TrackingLayer对象。改变GeoEvent将导致系统刷新

TrackingLayer。只是在进行耗时的大量计算之前需要显示TrackingLayer的变化时,才需要

执行该方法。该方法的惟一参数是一个布尔变量,表示是否需要刷新整个TrackingLayer对象,如果该变量为true,MapObjects将删除和重画该TrackingLayer对象,如果该变量为false,则MapObjects将只刷新受影响的区域。

8.1.3 GeoEvent对象的属性

GeoEvent对象包含Index、Shape、SymbolIndex、Tag、X和Y等6个属性。

(1)Index属性:该属性表示当前GeoEvent对象在TrackingLayer图层中的索引位置。 (2)Shape属性:该属性用于表示与当前GeoEvent对象连接的Shape对象。 (3)SymbolIndex属性:该属性用于表示与当前GeoEvent对象连接的Symbol对象在

TrackingLayer中的索引值。 (4)Tag属性:该属性用于表示GeoEvent对象的tag字符串。 (5)X、Y属性:它们表示GeoEvent对象中心位置的坐标值。

8.1.4 GeoEvent对象的方法

GeoEvent对象包含Move和MoveTo两个方法。

(1)Move方法:该方法将GeoEvent对象移动指定的距离。执行该方法后,TrackingLayer将自动刷新。

(2)MoveTo方法:该方法将GeoEvent移动到指定的新位置上。

Page 362: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

354

8.1.5 实例应用

本实例将演示如何使用TrackingLayer实现一般GIS软件提供的“鹰眼”功能。 新建一个应用程序。在窗体Form1中加入一个TControlBar控件、一个TPanel控件,将

ControlBar1和Panel1的Align属性分别设置为alTop和alLeft。在ControlBar1中加入4个TSpeedButton控件,分别命名为ZoomInBtn、ZoomOutBtn、PanBtn和FullExtentBtn,将前3个控件的GroupIndex属性都设置为1,使它们成为一组。在Panel1中加入一个TListBox和TMap控件,将它们的Align属性分别设置为alClient和alBottom。 后,在窗体Form1上再加

入一个TMap控件,将它的Align属性设置为alClient。 通过地图控件的Properties属性,在Map1中加入STATES图层,在Map2中加入STATES、

USHIGH和USLAKES图层。 在Unit1.cpp中加入如下所示的代码。

//---------------------------------------------------------------------

void __fastcall TForm1::Map2MouseDown(TObject *Sender,

TMouseButton Button, TShiftState Shift, int X, int Y) {

if (ZoomInBtn->Down)

{ IMoRectanglePtr trackRect = Map2->TrackRectangle();

Map2->Extent = trackRect;

} if (ZoomOutBtn->Down)

{

IMoRectanglePtr extRect = Map2->Extent; extRect->ScaleRectangle(1.5);

Map2->Extent = extRect;

} if (PanBtn->Down)

Map2->Pan();

} //---------------------------------------------------------------------

void __fastcall TForm1::Map2MouseMove(TObject *Sender,

TShiftState Shift, int X, int Y) {

if (PanBtn->Down)

Map2->MousePointer = moPan; if (ZoomOutBtn->Down)

Map2->MousePointer = moZoomOut;

if (ZoomInBtn->Down) Map2->MousePointer = moZoomIn;

}

//--------------------------------------------------------------------- void __fastcall TForm1::FullExtentBtnClick(TObject *Sender)

{

Page 363: C++ Builder和MapObjects实现

355

第 8章 MapObjects的其他对象

IMoRectanglePtr fullExt = Map1->FullExtent;

Map2->Extent = fullExt;

} //---------------------------------------------------------------------

void __fastcall TForm1::Map2AfterLayerDraw(TObject *Sender, short index,

TOLEBOOL canceled, OLE_HANDLE hDC) {

if(index == 0)

//在Map2 绘制第一个图层以后,强制Map1刷新TrackingLayer Map1->TrackingLayer->Refresh(true);

}

//--------------------------------------------------------------------- void __fastcall TForm1::Map1AfterTrackingLayerDraw(TObject *Sender,

OLE_HANDLE hDC)

{ //首先设置一个符号

IMoSymbolPtr sym ;

sym = CreateOleObject("MapObjects2.Symbol"); sym->OutlineColor = moRed;

sym->Style = moTransparentFill;

//然后用新建的符号绘制一个大小为Map2显示范围的矩形 Map1->DrawShape ( Map2->Extent, sym);

}

//--------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender)

{

int nLayerNum = Map2->Layers->Count; IMoMapLayerPtr layer;

//在ListBox1中列出地图中所有的图层名称

for(int i=0; i<nLayerNum; i++) {

layer = (IMoMapLayerPtr)Map2->Layers->Item(i);

ListBox1->Items->Add(AnsiString(layer->Name)); }

}

//---------------------------------------------------------------------

选择Project菜单的Add To Project命令,将文件Borland\CBuilder5\Imports\ MapObjects2 _TLB.cpp加入到当前项目中。

编译并运行程序,放大Map2控件中的地图,就会看到Map1中的矩形逐渐变小,如图

8.1所示。

Page 364: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

356

图 8.1 实现“鹰眼”功能

8.2 投 影 对 象

通过投影对象组中的对象,我们可以定义坐标系以及在不同坐标系之间进行坐标转换。

8.2.1 坐标系

当使用地图控件显示一个地理区域时,必须了解MapObjects的地图坐标和窗体坐标之

间的关系。我们在这里首先解释一下容易发生混淆的几个概念。控制坐标指以控件的左上

点为原点的坐标系统,屏幕坐标就是以屏幕的左上点为原点的坐标系统,而窗体坐标是一

类特殊的控制坐标,是以应用程序主窗体的左上点为原点的坐标系统。下文中会涉及到这3个概念,请注意区分。

8.2.1.1 控制坐标

在C++ Builder中,窗体左上角为窗体坐标的原点,水平方向为X轴,竖直方向为Y轴。 C++ Builder以twips作为默认的距离测量单位。可以把窗体坐标的测量单位改成点、

pixels、字符长、英寸、毫米或厘米,或通过设置 ScaleMode属性来实现自定义单位设置。

但在本书中我们应用twips,因为它是系统默认的单位。窗体内的地图控件也有它自己的坐

标系,其坐标单位与窗体坐标相同。我们把地图控件中的坐标称为“控制坐标”(control coordinate)。如前所述,窗体坐标也属于控制坐标的一种。

8.2.1.2 地图坐标

地图控件也可工作于地图坐标中。地图坐标遵循笛卡尔坐标系原则。不同于窗体坐标

系(左上角永远是[0,0]),地图控件内显示的地图坐标范围时常在应用期间改变。每次移

Page 365: C++ Builder和MapObjects实现

357

第 8章 MapObjects的其他对象

动某一地图区时,地图控件内地图坐标范围就会发生变化。 窗体坐标与地图坐标间有一些关键的区别:

· 地图控件左上角位置是控制坐标的原点,为(0,0)。而地图坐标通常都有一个在地图

控件显示区域很远以外的原点( origin )。应当记住,你的地图控件显示区域只是地

图表面的一个小窗口。 · 控制坐标的Y轴延轴向下递增,而地图坐标Y轴延轴向上递增。 · 控制坐标以twips为测量单位,并且与计算机屏幕显示的实际尺寸有关。地图坐标

则用米、英尺等单位表示,并且与地表特征的测量有关。

可以把地图控件当作一个放大镜,在一块地域内可随意移动这个“放大镜”,并可以

增大或减小其放大倍数。 地图数据中所用的坐标值通常很大,有的甚至是成百上千或数以百万计的。这些坐标

值通常是以英尺或米为单位。地图资料的坐标可以以不同的坐标系为基础,如平面坐标系

(SPCS:State Plane Coordinate System)或麦卡脱坐标系(UTM:Universal Transverse Mercator)以及其他坐标系。如果是从别人那里得到的资料,那么其提供者应已设置了适

当的坐标系。 关于坐标系更细致的讨论超出了本书的范围。但是值得注意的一点是,应用MapObjects

时,要确保图层上的资料使用统一坐标系。如果不是,那些用了不同坐标系的图层是无法

连接的。 如果地图资料应用了不同坐标系,并且想同时在MapObjects中使用它们,那么必须用

一些其他软件,如ArcView或Arc/Info编写程序,把地图资料转换成普通坐标系。该过程称

为地图投影(map projection)。

8.2.1.3 地图坐标与控制坐标之间的相互转换

地图控件有4种方法将位置和线性尺寸在地图坐标与控制坐标之间进行转换。

(1)用FromMapDistance方法,将以地图坐标单位计算的距离长度转换成窗体坐标下

的距离长度。语法为:

FromMapDistance(float Distance)

其中,Distance为地图坐标的距离,而其返回值为屏幕坐标的距离。 在实际应用中,可用地图控件的TrackLine方法来画出一个轮廓,并得到其长度。 (2)用FromMapPoint 方法将地图上点的坐标转换成地图控件下标准单位制的X、Y 坐

标。语法为:

FromMapPoint(MapPoint, xControl, yControl)

其中,MapPoint为点对象变量,xControl为点在屏幕坐标中的x坐标值,yControl为点在屏幕

坐标中的y坐标值。 要把地图坐标转换成控制坐标,用FromMapPoint方法输入一个点对象并得到其控制坐

标的X、Y值。可以通过其他方法应用这些X、Y值进行操作,例如,定义位置或选择地物。

Page 366: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

358

(3)ToMapDistance方法将以窗体坐标单位计算的距离长度转换成地图坐标下的距离

长度。语法为:

ToMapDistance(Distance)

其中,参数Distance为屏幕坐标系的距离值。返回值为地图坐标系的距离值。 (4)ToMapPoint方法将点的位置从屏幕坐标表示转换成以地图坐标表示。语法为:

ToMapPoint(xControl, yControl)

其中,参数xControl是屏幕坐标的x值,yControl是屏幕坐标的y值。该方法的返回值是以地

图坐标表示的点位置。

要得到某一点的地图坐标,可通过响应鼠标事件(Mouse Down、Mouse Move、Mouse Up)从屏幕上选定一点,返回的X、Y值就是控制坐标,然后使用ToMapPoint得到地图坐

标。 Mouse Down、Mouse Move、Mouse Up这些鼠标操作将返回地图上以窗体坐标单位表

示的位置。可使用这些操作来得到屏幕坐标位置,然后通过坐标转换方式把它们转换成地

图坐标。

8.2.2 地图投影

在数学中,投影(Project)的含义是指建立两个点集之间一一对应的映射关系。同样,

在地图学中,地图投影就是指建立地球表面上的点与投影平面上的点之间的一一对应关系。

地图投影的基本问题就是利用一定的数学法则把地球表面上的经纬线网表示到平面上。凡

是地理信息系统,就必然要考虑到地图投影,地图投影的使用保证了控件信息在地域上的

联系和完整性。在各类地理信息系统的建立过程中,选择适当的地图投影系统是首先要考

虑的问题。由于地球椭球体表面是曲面,而地图通常是绘制在平面图纸上,因此制图时首

先要把曲面展为平面。然而球面是不可展的曲面,即把它直接展为平面时,不可能不发生

破裂或褶皱。若用这种具有破裂或褶皱的平面绘制地图,显然是不实际的,所以必须采用

特殊的方法将曲面展平,使其成为没有破裂或褶皱的平面。 MapObjects利用ESRI为SDE开发的投影引擎功能来支持地图投影。投影用来定义不同

坐标系统之间的坐标转换。MapObjects包含预定义的坐标系统和坐标转换,也允许用户创

建自己的坐标系统并完成用户定义的坐标转换功能。 由前面的介绍可知,坐标系统有地理坐标系统和经过投影后的投影坐标系统。地理坐

标系统使用经纬度来刻画点的位置,而投影坐标系统使用X、Y值来描述点的位置。 MapObjects为我们提供了一组用于描述和转换坐标系统的对象——投影对象组。投影

对象组包括基准面(Datum)对象、地理坐标系统(GeoCoordSys)对象、地理坐标转换

(GeoTransformation)对象、本初子午线(PrimeMeridian)对象、投影坐标系统(ProjCoordSys)对象、投影(Projection)对象、地球椭球体(Spheroid)对象和单位(Unit)对象。MapObjects还为开发人员提供了地球椭球体模型、地理坐标系统、投影坐标系统等常量。

下面这个实例演示了如何在C++ Builder中读入和显示MapObjects预定义的地理坐标系

Page 367: C++ Builder和MapObjects实现

359

第 8章 MapObjects的其他对象

统和投影坐标系统。 新建一个项目。在窗体Form1中加入两个TLabel控件和两个TListBox控件。切换到

Unit1.h文件,加入如下所示的头文件。

#include "MapObjects2_OCX.h"

#include <OleCtrls.hpp>

创建窗体Form1的OnCreate事件响应函数,在其中加入如下代码。

//--------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender)

{

IMoStringsPtr strGCS; strGCS = CreateOleObject("MapObjects2.Strings");

//用地理坐标系统填充Strings对象

strGCS->PopulateWithGeographicCoordSys (); //将strGCS中各个字符串显示在ListBox1中

for(int i=0; i<strGCS->Count; i++)

ListBox1->Items->Add(AnsiString(strGCS->Item(i)));

IMoStringsPtr strPCS;

strPCS = CreateOleObject("MapObjects2.Strings"); //用投影坐标系统填充Strings对象

strPCS->PopulateWithProjectedCoordSys();

//将strPCS中各个字符串显示在ListBox2中 for(int i=0; i<strPCS->Count; i++)

ListBox2->Items->Add(AnsiString(strPCS->Item(i)));

} //---------------------------------------------------------------------

编译并运行程序,运行界面如图8.2所示。

图 8.2 MapObjects 中预定义的地理坐标系统与投影坐标系统

Page 368: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

360

在上述代码中,我们应用了Strings对象。Strings对象是一个包含一系列不重复字符串

数据的集合对象,该对象中包含了几个非常有用的方法——PopulateWithDatums、Populate WithGeographicCoordSys、PopulateWithGeoTransformations、PopulateWithMeridians、Populate WithParameters、PopulateWithProjectedCoordSys、PopulateWithProjections、PopulateWith Spheroids、PopulateWithUnits,这些方法分别用于将基准面常量、地理坐标系统常量、地

理转换常量、本初子午线常量、投影坐标系统参数常量、投影坐标系统常量、投影方法常

量、地球椭球体常量、单位常量加入到Strings对象中。而该对象的Count属性表示对象中字

符串的个数。通过使用该对象的Item方法,便可以访问各个字符串。 当一个Shape文件、ArcInfo coverage或SDE图层作为一个矢量图层(MapLayer)增加到

MapObjects中时,一些有关它们如何被投影的详细信息也会被保存下来,保存在图层对象

的CoordinateSystem属性中。对于Shape文件和ArcInfo coverage来说,这个信息被保存在磁

盘上的投影文件(.prj)中。对于SDE图层来说,这个信息被保存在图层定义表中。Shape文件使用的投影文件仅仅是可被MapObjects进行写操作的投影元数据格式,而coverage所使

用的投影文件必须由ArcInfo产生。 下面这个实例演示了如何在C++ Builder中读取图层的坐标系统信息。新建一个C++

Builder应用程序。在窗体Form1中加入一个TPanel控件Panel1、一个TSplitter控件Splitter1与一个Map控件Map1,并将它们的Align属性分别设置为alRight、alRight与alClient。在Panel1中再加入一个TButton控件Button1与一个TLabel控件Label1, 后加入一个TOpenFileDialog控件OpenFileDialog1,并将其Filter属性设置为ESRI Shapefiles (*.shp)|*.shp。

在代码编辑窗口中首先加入如下全局函数:

//---------------------------------------------------------------------

//提取图层名称

AnsiString ExtractFileNameNoExt(AnsiString fileName) {

int pos = fileName.Pos(".");

AnsiString strResult = fileName.SubString(1, pos -1); return strResult;

}

//---------------------------------------------------------------------

然后创建Button1的OnClick事件响应函数,在其中加入如下代码,用于加入一个图层,

并显示该图层的坐标信息:

//---------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)

{ if(OpenDialog1->Execute ())

{

IMoMapLayerPtr layer = (IDispatch*)CreateOleObject ("MapObjects2.MapLayer");

IMoDataConnectionPtr dc;

dc = (IDispatch*)CreateOleObject("MapObjects2.DataConnection"); //得到用户选择文件所在路径

Page 369: C++ Builder和MapObjects实现

361

第 8章 MapObjects的其他对象

AnsiString name = ExtractFileDir(OpenDialog1->FileName);

//将文件所在路径赋予数据连接对象的Database属性

dc->Database = WideString(name).Detach(); //然后调用Connect方法进行连接

if(!bool(dc->Connect()))

{ MessageBox(NULL, "错误", "连接错误!", MB_OK);

return;

} //得到不带路径的文件名

name = ExtractFileName(OpenDialog1->FileName);

//从文件名中得到图层名称 name = ExtractFileNameNoExt(name);

//将数据连接对象的地理数据集合对象赋予新建图层

layer->GeoDataset = dc->FindGeoDataset(WideString(name).Detach()); //设置符号

IMoSymbolPtr sym = layer->Symbol;

//将特征的符号颜色设置为红色 sym->Color = moRed;

//调用Add方法加入新图层

Map1->Layers->Add(layer);

// 显示该图层的坐标系统信息

if(layer->CoordinateSystem) ReportLyrCS(layer);

}

} //---------------------------------------------------------------------

上述函数中用到了ReportLyrCS函数,而该函数又调用了stripProj函数。这两个函数的

实现代码如下:

//---------------------------------------------------------------------

int __fastcall TForm1::stripProj(String theProjection) {

// 得到地理坐标系统或投影坐标系统描述字符串中的整数值(即方括号中的值)

int openA, openB; // 得到[和]在字符串中的位置

openA = theProjection.Pos("[");

openB = theProjection.Pos("]"); // 得到子字符串

String str = theProjection.SubString(openA + 1, openB - openA - 1);

return StrToInt(str); }

//---------------------------------------------------------------------

void __fastcall TForm1::ReportLyrCS(IMoMapLayerPtr lyr) {

Page 370: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

362

String strCSType;

try

{ IMoProjCoordSysPtr sy = lyr->CoordinateSystem;

if(sy->IsProjected)

{ strCSType = "投影坐标系统\n";

strCSType += sy->Projection->Name;

IMoStringsPtr ParamStr = (IDispatch*) CreateOleObject("MapObjects2.Strings");

ParamStr->PopulateWithParameters (sy->Projection->Type);

Label1->Caption = "图层坐标系统:\n" + strCSType +

"\n" + "名称: " + sy->Name + "\n" +

"单位: " + sy->Unit->Name +"\n" + "基准面: " + sy->GeoCoordSys->Datum->Name +"\n" +

"椭球体模型名称: " +

sy->GeoCoordSys->Datum->Spheroid->Name + "\n"; for(int i = 0; i< ParamStr->Count; i++)

{

AnsiString parm = AnsiString(ParamStr->Item(i)); Label1->Caption = Label1->Caption + parm;

Label1->Caption = Label1->Caption + ": ";

Label1->Caption = Label1->Caption + sy->GetParameter(stripProj(parm));

Label1->Caption = Label1->Caption + "\n";

} }

}

catch(...) {

IMoGeoCoordSysPtr gsy = lyr->CoordinateSystem;

strCSType = "地理坐标系统"; Label1->Caption = "图层坐标系统:\n" + strCSType +

"名称: " + gsy->Name + "\n" +

"类型: " + gsy->Type; }

}

//---------------------------------------------------------------------

编译并运行程序,单击“增加图层”命令按钮,打开一个图层,便可以看到该图层的

坐标系统信息,如图8.3所示。

Page 371: C++ Builder和MapObjects实现

363

第 8章 MapObjects的其他对象

图 8.3 显示图层坐标信息

8.2.3 投影转换

图层对象的CoordinateSystem属性不仅保存了当前图层的坐标系统信息,而且通过该属

性还可以改变图层的坐标系统以及进行投影转换等。 下面这个实例演示了如何在C++ Builder中改变图层的坐标系统。 新建一个C++ Builder应用程序。在窗体Form1中加入一个TPanel控件Panel1、一个

TSplitter控件Splitter1与一个TMap控件Map1,并将它们的Align属性分别设置为alRight、alRight与alClient。在Panel1中按顺序加入如下一些控件:一个TPanel控件Panel2,一个TPanel控件Panel3、一个TSplitter控件Splitter2与一个TPanel控件Panel4,将它们的Align属性分别设

置为alTop、alTop、alTop与alClient。在Panel2中加入2个TButton控件Button1与Button2,再

加入2个TRadioButton控件RadioButton1与RadioButton2,然后加入一个TComboBox控件

ComboBox1。在Panel3中加入一个TLabel控件Label1,将其Align属性设置为alClient,WordWrap属性设置为true。在Panel4中加入一个TLabel控件Label4,将其Align属性设置为

alClient,WordWrap属性设置为true。 后加入一个TOpenFileDialog控件OpenFileDialog1,并将其Filter属性设置为ESRI Shapefiles (*.shp)|*.shp。

切换到代码编辑窗口,首先加入如下一个全局函数:

//---------------------------------------------------------------------

//提取图层名称

AnsiString ExtractFileNameNoExt(AnsiString fileName) {

int pos = fileName.Pos(".");

AnsiString strResult = fileName.SubString(1, pos -1); return strResult;

}

Page 372: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

364

//---------------------------------------------------------------------

然后创建Form1的OnCreate事件响应函数,在其中加入如下代码,用于在ComboBox1控件中加入预定义的地理坐标系统:

//---------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender) {

IMoStringsPtr strGCS;

strGCS = CreateOleObject("MapObjects2.Strings"); //用地理坐标系统填充Strings对象

strGCS->PopulateWithGeographicCoordSys ();

//将strGCS中各个字符串显示在ComboBox1中 for(int i=0; i<strGCS->Count; i++)

ComboBox1->Items->Add(AnsiString(strGCS->Item(i)));

ComboBox1->ItemIndex = 0; }

//---------------------------------------------------------------------

创建RadioButton1与RadioButton2控件的OnClick事件响应函数,在其中加入如下函数,

用于根据用户的选择分别在ComboBox1控件中显示预定义的地理坐标系统或投影坐标系统

名称:

//--------------------------------------------------------------------- void __fastcall TForm1::RadioButton1Click(TObject *Sender)

{

ComboBox1->Clear(); IMoStringsPtr strGCS;

strGCS = CreateOleObject("MapObjects2.Strings");

//用地理坐标系统填充Strings对象 strGCS->PopulateWithGeographicCoordSys ();

//将strGCS中各个字符串显示在ComboBox1中

for(int i=0; i<strGCS->Count; i++) ComboBox1->Items->Add(AnsiString(strGCS->Item(i)));

ComboBox1->ItemIndex = 0;

} //---------------------------------------------------------------------

void __fastcall TForm1::RadioButton2Click(TObject *Sender)

{ ComboBox1->Clear();

IMoStringsPtr strPCS;

strPCS = CreateOleObject("MapObjects2.Strings"); //用投影坐标系统填充Strings对象

strPCS->PopulateWithProjectedCoordSys();

//将strPCS中各个字符串显示在ComboBox1中 for(int i=0; i<strPCS->Count; i++)

ComboBox1->Items->Add(AnsiString(strPCS->Item(i)));

ComboBox1->ItemIndex = 0;

Page 373: C++ Builder和MapObjects实现

365

第 8章 MapObjects的其他对象

}

//---------------------------------------------------------------------

然后创建Button1控件的OnClick事件响应函数,用于在地图中加入一个图层:

//--------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender)

{

if(OpenDialog1->Execute ()) {

IMoMapLayerPtr layer = (IDispatch*)

CreateOleObject("MapObjects2.MapLayer"); IMoDataConnectionPtr dc;

dc = (IDispatch*)CreateOleObject("MapObjects2.DataConnection");

//得到用户选择文件所在路径 AnsiString name = ExtractFileDir(OpenDialog1->FileName);

//将文件所在路径赋予数据连接对象的Database属性

dc->Database = WideString(name).Detach(); //然后调用Connect方法进行连接

if(!bool(dc->Connect()))

{ MessageBox(NULL, "错误", "连接错误!", MB_OK);

return;

} //得到不带路径的文件名

name = ExtractFileName(OpenDialog1->FileName);

//从文件名中得到图层名称 name = ExtractFileNameNoExt(name);

//将数据连接对象的地理数据集合对象赋予新建图层

layer->GeoDataset = dc->FindGeoDataset(WideString(name).Detach()); //设置符号

IMoSymbolPtr sym = layer->Symbol;

//将特征的符号颜色设置为红色 sym->Color = moRed;

// 后调用Add方法加入新图层

Map1->Layers->Add(layer);

// 显示该图层的坐标系统信息

if(layer->CoordinateSystem) ReportLyrCS(layer);

}

} //---------------------------------------------------------------------

上述函数中用到的ReportLyrCS函数与上一个实例中的相应函数一样,因此这里不再给

出该函数代码。 然后创建Button2控件的OnClick事件响应函数,在其中加入如下代码,用于根据用户的

Page 374: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

366

选择进行坐标系统转换,然后在Label2中显示转换后的坐标系统信息:

//---------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)

{ // 判断用户选择的是地理坐标系统还是投影坐标系统

if(RadioButton1->Checked)

{ IMoGeoCoordSysPtr gs = (IDispatch*)

CreateOleObject("MapObjects2.GeoCoordSys");

// 设置坐标系统的类型 gs->Type = stripProj(ComboBox1->Text);

// 设置地图的坐标系统(这会引起地图刷新)

Map1->CoordinateSystem = OleVariant((IDispatch*)gs); // 显示地图坐标系统信息

ReportMapCS(gs);

} else

{

IMoProjCoordSysPtr ps = (IDispatch*)CreateOleObject ("MapObjects2.ProjCoordSys");

// 设置坐标系统的类型

ps->Type = stripProj(ComboBox1->Text); // 设置地图的坐标系统(这会引起地图刷新)

Map1->CoordinateSystem = OleVariant((IDispatch*)ps);

// 显示地图坐标系统信息 ReportMapCS(ps);

}

} //---------------------------------------------------------------------

上述函数调用了ReportMapCS这个重载函数用于显示坐标系统信息,该函数的实现代

码如下:

//---------------------------------------------------------------------

void __fastcall TForm1::ReportMapCS(IMoGeoCoordSysPtr gs) {

// 显示地图的坐标系统

AnsiString strCSType; strCSType = "地理坐标系统";

Label2->Caption = "转换后的坐标系统:\n";

Label2->Caption = Label2->Caption + strCSType + "名称: " + gs->Name + "\n" +

"类型: " + gs->Type;

} //---------------------------------------------------------------------

void __fastcall TForm1::ReportMapCS(IMoProjCoordSysPtr ps)

Page 375: C++ Builder和MapObjects实现

367

第 8章 MapObjects的其他对象

{

AnsiString strCSType;

strCSType = "投影坐标系统\n"; strCSType += ps->Projection->Name;

IMoStringsPtr ParamStr = (IDispatch*)

CreateOleObject("MapObjects2.Strings"); ParamStr->PopulateWithParameters (ps->Projection->Type);

Label2->Caption = "转换后的坐标系统:\n"; Label2->Caption = Label2->Caption + strCSType +

"\n" + "名称: " + ps->Name + "\n" +

"单位: " + ps->Unit->Name +"\n" + "基准面: " + ps->GeoCoordSys->Datum->Name +"\n" +

"椭球体模型名称: " +

ps->GeoCoordSys->Datum->Spheroid->Name + "\n"; for(int i = 0; i< ParamStr->Count; i++)

{

AnsiString parm = AnsiString(ParamStr->Item(i)); Label2->Caption = Label2->Caption + parm;

Label2->Caption = Label2->Caption + ": ";

Label2->Caption = Label2->Caption + ps->GetParameter(stripProj(parm)); Label2->Caption = Label2->Caption + "\n";

}

} //---------------------------------------------------------------------

编译并运行程序,打开一个图层,然后进行坐标转换。图8.4显示了坐标系统转换前后

图层的对比。

(a)坐标系统转换前的图层

Page 376: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

368

(b)坐标系统转换后的图层

图 8.4 坐标系统转换前后的图层对比

8.3 地 理 编 码

地理信息系统的数据库中存在大量的地址及地点名称等地理信息。数据库中可以根本

没有诸如纬度值、经度值或图像坐标之类的空间信息,但却存有大量有关地理邮政方面的

数据。计算机可根据地址确定的一些位置自动地建立一个绘图层,并能自动访问到大量地

图中的属性信息。MapObjects中包括一系列OLEAutomation对象,这些对象是专为地址匹配

及根据地点名称查找定位地点而设计的。地址匹配是指在地图上找到并标明每条地址所对

应的位置,它也称为地理编码。 地理编码(GeoCoder)对象要求指定一个地理数据集(GeoDataset)对象,其中包括

街道中心线、地址变动范围以及适用于单个或成批地址匹配的方法的设定。地址定位

(AddressLocation)对象中的代码具有表明一条地址是否被解析以及如何被解析的作用,

如果地址已被解析,它还能显示匹配地址的地理位置。 GeoCoder、AddressLocation和Standardizer对象用于交互式批地址匹配。在了解这些对

象之前,首先必须了解街道文件成立的具体要求,然后讨论如何使用这些对象进行地址匹

配。

8.3.1 用于地址匹配的专用文件

要进行地址匹配操作,就必须有一个街道文件。街道文件中包括具有地址信息的街道

中心路段。街道文件可以是一个图形文件,也可以是一个SDE(存储分配部件)层。一个

适用于地址匹配的街道文件必须满足以下3个基本条件:

Page 377: C++ Builder和MapObjects实现

369

第 8章 MapObjects的其他对象

(1)街道文件中必须有街道中心线段,并在街道交叉处形成清晰的结点。除了街道中

心线段外,街道文件中不能再有其他任何特征。 (2)这些中心线段中的每一段都必须至少具备以下5个要素(字段):街道名称、街

道左边的起始地址、街道右边的起始地址、街道左边的结束地址与街道右边的结束地址。 (3)右边的一对地址和左边的一对地址必须分别编上双号和单号。也就是说,任何一

边的起点和终点地址必须同时为双号或单号,而每一对左右两边的地址编号则形成互补,

即一单一双。显然,一条道路的左和右取决于前进的方向,街道文件则遵循以下的协定:

街道的左右视起点到终点的方向而定。

MapObjects中的地址匹配目标是为美国的使用而设计的,但只要能够使用符合上述要

求的街道文件,世界其他地区也能够使用它们进行地址匹配。不过街道两边的地址编号必

须是一边为双、一边为单。 另外,地址匹配对象也适用于中等长度的街道文件,如一个跨市中县的街道文件,但

它们并不能设计成为大范围内的国家街道文件,比如一个囊括美国所有街道的文件。如果

街道文件确实跨越了一个很大的地理区域,那么可以自由使用一些地址中的城市名、邮政

编码及乡村名以帮助解决地址匹配问题。 下面是街道文件的图解一览表:

(1)街道文件是由具有规定的左、右两边及起点、终点特征的中心线路组成。其中左、

右边之分取决于由起点到终点的方向。 (2)地址数据是按各个路段储存的。储存的4个数值有:Left-From(左起点)、Right-From

(右起点)、Left-To(左终点)和Right-To(右终点)。在美国,街道一边的地址号为双

数,而另一边则为单数。 (3)地址匹配或地理编码就是一个通过地址中某路段的起始、终点位置,并同时考虑

单双号因素以确定地理位置的过程。 (4)如果指定了一个分支,那么估算出的地址位置将落在该路段垂直线上的左边或右

边。 (5)除了返回一个位置,地址匹配还返回标准化地址数据值,包括房号、街道名、首

字符串、尾字符串和邮政编码。

在美国,可以使用由TIGER(地理地形综合密码参考系统)文件衍变而来的图形文件,

这种文件由人口普查署以低廉的价格提供,或者也可以使用由销售商提供的其他一些文件,

这些文件有的是TIGER文件增强功能后的新版本,有的则是完全由销售商自己新开发出的

版本。通常使用由商业渠道提供的街道文件要比自己建立一个要快捷有效得多。 在应用过程中,也许不会有其他包括附加通路信息的图层,如街道镶边石线这类信息,

这些必须与用于地址匹配的街道文件分离开来,成为一个独立的图层。实际上,会有一个

可见的绘有镶边石线的图层以及一个不可见的绘有街道中心线的地址匹配图层。

8.3.2 绘制街道文件

当利用街道文件和地址匹配时,如果使描绘道路镶边石的详细的设计图层可见,而使

Page 378: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

370

用于地址匹配的街道中心线图层不可见,那么就能绘制出一个精彩的界面。然而,那些绘

有详细的街道边饰的设计图层通常并不很实用。这里有一个帮助在实际运用过程中使绘制

出的街道文件实现的技巧。 表格左边的街道文件是用一根简单的1个单位宽度的细实线绘制的,而表格右边同样的

街道文件被绘制了两次,首先用5个单位宽的粗黑实线绘制一次,然后再用3个单位宽的白

实线绘制一次,将两个图层重叠起来就形成了双线道路的效果。 只要用几分钟的时间,便可以在MapControlProperties(图像控制功能)窗口自己绘制

出这张图。绘制两张同一街道文件的图层,如上所述分别使用不同颜色及粗细的线条,然

后叠加起来,将黑线的图层放在下面即可。 如果用这种方法绘图,建议 好仅在某一区域内使用双线,以免交叉引起混乱。在更

小的范围内,用单线绘制即可。通过在图像控制器的BeforeLayerDraw操作中编写代码来估

算当前图形范围、定义符号对象(这些符号对象与街道文件的矢量图层对象相关联)的可

见性及大小尺寸,就可以有条理地绘制出一个街道文件。 如果使用ValueMapRenderer对象将道路按分类绘制,例如公路用较粗的线绘制等等,

那么就能更有效地利用这些代码。这样绘制街道文件时,一定要注意规定好不同线条各自

的比例尺寸。

8.3.3 地理编码对象

地理编码(GeoCoder)对象用在街道网络中,根据地址、路口或地址列表等信息进行

定位。 StreetTable(街道表)属性设置街道网络的地理数据集GeoDataset。如果StreetTable是

第一次使用,可以使用AddIndex和BuildIndices方法产生地理数据集的地理索引,用

IndexStatus属性值可以验证是否建立了索引。SearchQueries属性规定了搜索索引的方法。

Valid属性表示StreetTable中的字段是否有效。如果无效,可以根据LastError属性检查错误

原因。 在进行地址匹配之前,需要将地址进行标准化。在对一个地址进行编码之前,必须创

建一个地址标准化(Standardizer)对象,然后将该对象赋予地理编码对象的Standardizer属性。

MapObjects支持匹配不同类型的地址。通过设置MatchRules和IntersectionMatchRules属性为某个文件,可以选择合适的匹配规则。在MapObjects安装路径中的Georules目录中有一

些匹配规则文件。 给定一街道或路口地址(两个街道名之间用“&”符号隔开)后,可以调用

GenerateCandidates方法得到可能的匹配结果,接着调用LocateCandidate 方法将返回的匹配

结果与 佳候选地址进行匹配,该方法返回一个地址定位对象。 如果在一个表中有许多地址需要匹配,则可以调用BatchMatch方法对每一条记录进行

匹配,并创建一个包含匹配结果的Shape文件。 在进行批地址匹配中,通过调整MatchWhenAmbiguous和MinimumMatchScore两个属性

的值,可以控制匹配精度。还可以设置其他属性,例如SpellingSensitivity、Offset和

Page 379: C++ Builder和MapObjects实现

371

第 8章 MapObjects的其他对象

SqueezeFactor属性,用于在交互式地址匹配和批地址匹配中控制地理编码方式。

8.3.4 地址定位对象

地址定位(AddressLocation)对象描述地址匹配的结果。MatchCode属性表示匹配的状

态。如果匹配成功,Location属性指向一个Point对象。 Location属性表示匹配位置的点对象,它是一个只读对象。当成功调用地理编码对象的

LocateCandidate方法后,返回一个地址定位对象,该地址定位对象通过Location属性显示在

地图中。如果设置了地理编码对象的SqueezeFactor和Offset 属性,那么编码后的Location会受上述两个属性的影响。

MatchScore属性表示地址匹配程度的好坏。如果MatchScore属性值为100,表示完全匹

配,如果为0,表示没有任何匹配结果。通常情况下,MatchScore属性值在30~70之间。 StreetSide属性表示匹配的地址是在街道的哪一边,它可以是moLeftSide(街道的左边)

或moRightSide(街道的右边)。

8.3.5 地址标准化对象

地址标准化(Standardizer)对象用于将单个地址字符串或街道路口进行标准化。 在进行地址匹配之前,地址字符串必须经历两个标准化程序。首先是将地址字符串分

割成不同的字段,然后这些不同字段需要转化成适当的标准值,例如“N”、“North”或

“Nrth”等。标准化以后的地址的各个字段才能与街道表中的字段进行比较,以便找到相

应的地理位置。 在创建地址标准化对象时,通过该对象的StandardizingRules属性可以指定合适的标准

化规则。MapObjects提供了一系列标准化规则,这些规则保存在.stn文件中。为该属性指定

一合适的.stn文件便可以进行标准化。 .stn文件需要依据文件名寻找其他文件。一些文件甚至指向其他的表,例如prefix.tbl。

因此,我们必须清楚地记住进行标准化时使用的一系列规则文件,并把这一系列文件放在

StandardizingRules属性指定的.stn文件所在路径中。通常,标准化地址时也需要扩展名

为.dct、.mat、.pat和.cls的文件。 地址标准化对象通常使用一个标准规则来标准化地址,而标准规则文件可以通过

StandardizingRules属性指定。但是,如果需要对街道交叉口进行编码,还必须设置

IntersectionStandardizingRules属性。如果不需要考虑街道交叉口,该属性可以为空。 下面是创建和设置地址标准化对象的C++ Builder代码:

IMoStandardizerPtr stan;

stan = (IDispatch*)CreateOleObject("MapObjects2.Standardizer");

stan->StandardizingRules = WideString("E:\\ProgramFiles\\ ESRI\\ MapObjects2\\GeoRules\\us_addr.stn");

通过Valid属性可以检查地址标准化对象设置是否正确。如果设置不正确,可以根据

LastError属性检查错误原因。

Page 380: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

372

地址标准化对象的惟一方法是StandardizeAddress,该方法根据StandardizingRules属性

或IntersectionStandardizingRules属性指定的规则,将地址进行标准化。通过FieldValue属性

可以得到标准化的结果。 下面这个实例演示了如何在C++ Builder中使用地址标准化对象和查看标准化地址的结

果。 新建一个项目。在窗体Form1中加入一个TButton控件、一个TEdit控件和一个TListBox

控件。双击Button1,创建该控件的OnClick事件响应函数,在其中加入如下代码。

//---------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)

{ IMoStandardizerPtr stan;

stan = (IDispatch*)CreateOleObject("MapObjects2.Standardizer");

stan->StandardizingRules = WideString("E:\\Program Files\\ESRI\\ MapObjects2\\ GeoRules\\us_addr.stn");

stan->IntersectionStandardizingRules = WideString("E:\\Program Files\\

ESRI\\MapObjects2\\GeoRules\\us_intsc.stn"); if(!bool(stan->Valid))

{

if(stan->LastError == mgErrorNone) MessageBox(NULL,"The standardizer is not valid. Error ",

"Error", MB_OK);

return; }

//可以将地址设置为270North Main Avenue、North Main Street & First Ave SW等 AnsiString address1 = Edit1->Text;//""

if(stan->StandardizeAddress(WideString(address1)))

{ stan->set_FieldValue(WideString("ZN"), WideString("53702"));

int f = stan->FieldCount;

for(int i=0; i<f; i++) {

WideString name = stan->get_FieldName(i);

ListBox1->Items->Add(stan->get_FieldValue(name)); }

}

} //---------------------------------------------------------------------

void __fastcall TForm1::Edit1KeyDown(TObject *Sender, WORD &Key,

TShiftState Shift) {

if(Key == VK_RETURN)

Button1Click(Sender); }

//---------------------------------------------------------------------

Page 381: C++ Builder和MapObjects实现

373

第 8章 MapObjects的其他对象

编译并运行程序。在文本框中输入一个地址,然后键入回车键或单击“解析地址”命

令按钮,便可在列表框中得到标准化后的地址,如图8.5所示。

图 8.5 标准化地址

8.3.6 交互式地址匹配

下面是在MapObjects中进行交互式地址匹配的步骤:

(1)创建一个地址标准化对象,指定该对象的标准规则,即指定StandardizingRules和IntersectionStandardizingRules为某个标准规则文件名,并将该地址标准化对象赋予地理编

码对象GeoCoder的Standardizer属性。 (2)为地理编码对象指定一个街道网络作为StreetTable的属性,它是一个GeoDataset

对象,包括街道中心线。这些街道都有一组标准化字段用于提供街道及地址信息。 (3)检查IndexStatus属性以确认StreetTable的地理编码索引是否已建立。如果没有,

则利用AddIndex方法或BuildIndices方法建立索引。 (4)检查Valid属性以确认StreetTable和指定地址字段是否符合地址匹配的要求。 (5)利用地理编码对象的SearchQueries属性设置查询条件。 (6)调用地理编码对象的GenerateCandidates方法进行地址匹配。该方法将搜索到的地

址按照它们的得分从高到低排列。可以通过LocateCandidate方法得到指定位置处的匹配结

果,该结果是一个地址定位(AddressLocation)对象,该对象的MatchScore属性反映了地

址匹配精度。

下面这个实例演示了如何在C++ Builder中进行交互地址匹配。 新建一个C++ Builder应用程序。在窗体Form1中加入一个TPanel控件Panel1和一个

TMap控件Map1,在Panel1中加入一个TButton控件Button1与两个TEidt控件Edit1和Edit2。 在Form1类中加入如下成员变量:

//---------------------------------------------------------------------

private: // User declarations

// 设置StreetTable表中字段变量 WideString m_FromLeft;

Page 382: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

374

WideString m_FromRight;

WideString m_ToLeft;

WideString m_ToRight; WideString m_PreDir;

WideString m_PreType; WideString m_StreetName;

WideString m_StreetType;

WideString m_SufDir; WideString m_LeftZone;

WideString m_RightZone;

IMoGeocoderPtr geo;

IMoStandardizerPtr stan;

IMoDataConnectionPtr dcx; IMoRectanglePtr extRect;

//---------------------------------------------------------------------

在Form1类的构造函数中加入如下代码,用于初始化成员变量:

//--------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{ m_FromLeft = "L_f_add";

m_FromRight = "R_f_add";

m_ToLeft = "L_t_add"; m_ToRight = "R_t_add";

m_PreDir = "Prefix";

m_PreType = "Pre_type";

m_StreetName = "Name";

m_StreetType = "Type"; m_SufDir = "Suffix";

m_LeftZone = "Zipl";

m_RightZone = "ZipR";

geo = (IDispatch*)CreateOleObject("MapObjects2.Geocoder");

stan = (IDispatch*)CreateOleObject("MapObjects2.Standardizer"); dcx = (IDispatch*)CreateOleObject("MapObjects2.DataConnection");

}

//---------------------------------------------------------------------

然后再加入如下控件的事件响应函数:

//---------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)

{

Page 383: C++ Builder和MapObjects实现

375

第 8章 MapObjects的其他对象

IMoDataConnectionPtr dc =

(IDispatch*)CreateOleObject("MapObjects2.DataConnection");

IMoGeoDatasetPtr gd; IMoMapLayerPtr lyr =

(IDispatch*)CreateOleObject("MapObjects2.MapLayer");

// 设置Standardizer对象的属性

stan->StandardizingRules = WideString("D:\\Program

Files\\ESRI\\MapObjects2\\GeoRules\\us_addr.stn"); stan->IntersectionStandardizingRules = WideString("D:\\Program

Files\\ESRI\\MapObjects2\\GeoRules\\us_intsc.stn");

// 指定GeoCoder对象的Standardizer属性

geo->Standardizer = stan;

dc->Database = WideString("D:\\Program

Files\\ESRI\\MapObjects2\\Samples\\Data\\Redlands");

dc->Connect(); if(! (bool)dc->Connected)

{

MessageBox(NULL, "连接错误", "错误", MB_OK ); return;

}

// 设置StreetTable属性

gd = dc->FindGeoDataset(WideString("redlands"));

lyr->GeoDataset = gd; lyr->Symbol->Color = moBlue;

Map1->Layers->Add(lyr);

geo->StreetTable = gd;

// 设置匹配规则和变量

geo->MatchRules = WideString("D:\\Program Files\\ESRI\\MapObjects2\\GeoRules\\us_addr1.mat");

geo->IntersectionMatchRules = WideString("D:\\Program

Files\\ESRI\\MapObjects2\\GeoRules\\us_intsc1.mat");

// 将匹配变量与StreetTable表中的字段相连

geo->set_MatchVariableField(WideString("FromLeft"), m_FromLeft); geo->set_MatchVariableField(WideString("FromRight"), m_FromRight);

geo->set_MatchVariableField(WideString("ToLeft"), m_ToLeft);

geo->set_MatchVariableField(WideString("ToRight"), m_ToRight);

geo->set_MatchVariableField(WideString("PreDir"), m_PreDir);

geo->set_MatchVariableField(WideString("PreType"), m_PreType); geo->set_MatchVariableField(WideString("StreetName"), m_StreetName);

Page 384: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

376

geo->set_MatchVariableField(WideString("StreetType"), m_StreetType);

geo->set_MatchVariableField(WideString("SufDir"), m_SufDir);

geo->set_MatchVariableField(WideString("LeftZone"), m_LeftZone); geo->set_MatchVariableField(WideString("RightZone"), m_RightZone);

geo->set_MatchVariableIntersectionLink(WideString("PreDir"), mgLinkPrimary, WideString("PreDir1"));

geo->set_MatchVariableIntersectionLink(WideString("PreType"),

mgLinkPrimary, WideString("PreType1")); geo->set_MatchVariableIntersectionLink(WideString("StreetName"),

mgLinkPrimary, WideString("StreetName1"));

geo->set_MatchVariableIntersectionLink(WideString("StreetType"), mgLinkPrimary, WideString("StreetType1"));

geo->set_MatchVariableIntersectionLink(WideString("SufDir"),

mgLinkPrimary, WideString("SufDir1")); geo->set_MatchVariableIntersectionLink(WideString("LeftZone"),

mgLinkPrimary, WideString("LeftZone1"));

geo->set_MatchVariableIntersectionLink(WideString("RightZone"), mgLinkPrimary, WideString("RightZone1"));

geo->set_MatchVariableIntersectionLink(WideString("PreDir"),

mgLinkSecondary, WideString("PreDir2")); geo->set_MatchVariableIntersectionLink(WideString("PreType"),

mgLinkSecondary, WideString("PreType2"));

geo->set_MatchVariableIntersectionLink(WideString("StreetName"), mgLinkSecondary, WideString("StreetName2"));

geo->set_MatchVariableIntersectionLink(WideString("StreetType"),

mgLinkSecondary, WideString("StreetType2")); geo->set_MatchVariableIntersectionLink(WideString("SufDir"),

mgLinkSecondary, WideString("SufDir2"));

geo->set_MatchVariableIntersectionLink(WideString("LeftZone"), mgLinkSecondary, WideString("LeftZone2"));

geo->set_MatchVariableIntersectionLink(WideString("RightZone"),

mgLinkSecondary, WideString("RightZone2"));

// 判断是否建立了索引,如果没有则创建索引

if(geo->IndexStatus() != mgIndexExists) if(! (bool)geo->AddIndex(m_StreetName, WideString(""),

mgIndexTypeSoundex))

{ MessageBox(NULL, "不能创建索引." , "错误", MB_OK);

return;

}

if(! (bool)geo->AddIndex(m_LeftZone, m_RightZone, mgIndexTypeNormal))

{ MessageBox(NULL, "不能创建索引.", "错误", MB_OK);

Page 385: C++ Builder和MapObjects实现

377

第 8章 MapObjects的其他对象

return;

}

if(! (bool)geo->BuildIndices(true))

{

MessageBox(NULL, "不能创建索引.", "错误", MB_OK); return;

}

// 设置搜索查询

IMoStringsPtr queries =

(IDispatch*)CreateOleObject("MapObjects2.Strings"); queries->Add(WideString("SN? & ZN"));

queries->Add(WideString("SN?"));

geo->SearchQueries = queries;

Button1->Caption = "寻找地址";

Edit1->Text = "260 Cajon St"; Edit2->Text = "92373";

}

//--------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender)

{

// 寻找 佳匹配地址 IMoAddressLocationPtr foundLoc;

if( stan->StandardizeAddress(WideString(Edit1->Text)))

{ stan->set_FieldValue(WideString("ZN"), WideString(Edit2->Text));

int result = (int)geo->GenerateCandidates();

if( geo->CandidateCount > 0 ) {

foundLoc = geo->LocateCandidate(0);

int score = foundLoc->MatchScore; Map1->FlashShape(foundLoc->location, 3); //闪烁三次

}

} }

//---------------------------------------------------------------------

void __fastcall TForm1::Map1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)

{

if (Button == mbLeft) {

if (Shift.Contains(ssShift))

{ IMoRectanglePtr extRect = Map1->Extent;

Page 386: C++ Builder和MapObjects实现

地理信息系统二次开发实例教程——C++ Builder和MapObjects实现

378

extRect->ScaleRectangle(1.5);

Map1->Extent = extRect;

} else

{

IMoRectanglePtr trackRect = Map1->TrackRectangle(); Map1->Extent = trackRect;

}

} else

Map1->Pan();

} //---------------------------------------------------------------------

编译并运行程序。应用程序执行界面如图8.6所示。

图 8.6 交互式地址匹配

8.3.7 批地址匹配

GenerateCandidates方法适用于单个地址匹配,而对于一次进行大量地址匹配就不那么

得心应手了。对于批地址匹配,一般使用地理编码对象的BatchMatch方法。步骤如下:

(1)建立一个新的DataConnection对象,将其Database属性设置为包含图形文件或

SDEDatabase的文件夹。 (2)建立一个地理编码对象GeoCoder,将其StreetTable属性设置为带有地址信息的

GeoDataset对象。 (3)检查IndexStatus属性以确认StreetTable的地理编码索引是否已建立。如果没有,

则利用AddIndex方法或BuildIndices方法建立索引。 (4)建立一个Table对象,并将Database和Name属性设置为带有需要匹配地址的一个

Page 387: C++ Builder和MapObjects实现

379

第 8章 MapObjects的其他对象

ODBC数据源。 (5)利用BatchMatch方法创建一个同名的表,该表包括地址以及一个包含需要匹配地

址的字段。这种方式将利用所指定的DataConnection对象创建一张新表,它的名字由程序指

定。该方法原型如下:

BatchMatch (addressTable, addressField, dataConnection, outputTable,

streetFields)

其中,参数streetFields(街道字段)的字符串集所指定的字段是需要从addressTable(地址

表)复制到outputTable(输出表)中的字段。 当用BatchMatch方法进行地址匹配时,是否需要一个有矢量图层的地图控件可以根据

自己的意愿而定。可以只用OLEAutomation对象进行地址匹配操作,而无需什么可见的图

层。