一、简介
在本文中,我们将了解地理空间应用程序的核心架构和重要元素。我们将首先了解什么是地理空间应用程序以及构建地理空间应用程序的典型挑战。
地理空间应用程序的重要方面之一是在直观的地图上表示有用的数据。尽管如此,在本文中,我们将主要关注后端处理地理空间数据以及当今行业中可用的选项。
2. 什么是地理空间应用?
让我们首先了解地理空间应用程序的含义。这些基本上是 利用地理空间数据来提供其核心功能的应用程序 。
简单来说,地理空间数据是表示地点、位置、地图、导航等的任何数据。即使没有任何花哨的定义,我们仍然被这些应用程序所包围。例如,我们最喜欢的乘车共享应用程序、送餐应用程序和电影预订应用程序都是地理空间应用程序。
地理空间数据基本上是 描述位于地球表面或附近的物体、事件或其他特征的信息 。例如,考虑一个应用程序,它可以向我们建议最近的剧院,在今天晚上播放我们最喜欢的莎士比亚戏剧。它可以通过将剧院的位置信息与戏剧的属性信息和事件的时间信息相结合来做到这一点。
地理空间数据还有其他一些有用的应用程序可以为我们提供日常价值,例如,当我们尝试在一天中的任何时间在附近找到一辆愿意带我们去目的地的出租车时。或者当我们迫不及待地等待重要货物到达时,幸运的是,我们可以找到它在运输途中的确切位置。
事实上,这已经成为我们现在经常使用的几个应用程序的基本要求。
3. 地理空间技术
在了解构建地理空间应用程序的细微差别之前,让我们先了解一下支持此类应用程序的一些核心技术。这些是帮助我们以有用的形式生成、处理和呈现地理空间数据的基础技术。
遥感 (RS) 是通过测量远距离反射和发射的辐射来检测和监测某个区域的物理特征的过程 。通常这是使用遥感卫星来完成的。它在测量、情报和商业应用领域有着重要的用途。
全球定位系统 (GPS) 是指基于卫星的导航系统,其基础是由 24 颗在中地球轨道 (MEO) 中飞行的卫星组成的网络 。它可以向地球上任何位置与四颗或更多 GPS 卫星具有无障碍视线的合适接收器提供地理位置和时间信息。
地理信息系统 (GIS) 是一个创建、管理、分析和绘制所有类型数据的系统 。例如,它可以帮助我们将位置数据与更具描述性的信息(例如该位置中存在的内容)集成。它有助于改善多个行业的沟通和决策。
4. 构建地理空间应用程序的挑战
要了解构建地理空间应用程序时我们应该做出哪些设计选择,了解所涉及的挑战非常重要。通常, 地理空间应用程序需要对大量地理空间数据进行实时分析 。例如,找到前往最近发生自然灾害的地点的最快替代路线对于急救人员来说至关重要。
因此,基本上,地理空间应用程序的基本要求之一是 存储大量地理空间数据并以非常低的延迟促进任意查询 。现在,了解空间数据的性质以及为什么需要特殊处理也很重要。基本上,空间数据表示几何空间中定义的对象。
假设我们在城市周围有几个感兴趣的地点。位置通常由纬度、经度和(可能的)海拔来描述:
现在,我们真正感兴趣的是查找给定位置的附近位置。因此,我们需要计算从该位置到所有可能位置的距离。此类查询与我们熟悉的常规数据库查询非常不典型。这些 称为空间查询。它们涉及几何数据类型并考虑这些几何图形之间的空间关系 。
我们已经知道,如果没有有效的索引,任何生产数据库都可能无法生存。对于空间数据来说也是如此。然而,由于其性质, 常规索引对于空间数据和我们想要执行的空间查询类型不是非常有效 。因此,我们需要称为空间索引的专门索引,它可以帮助我们更有效地执行空间操作。
5. 空间数据类型和查询
现在我们了解了处理空间数据的挑战,重要的是要注意几种类型的空间数据。此外,我们可以对它们执行一些有趣的查询来满足独特的需求。我们将介绍其中一些数据类型以及我们可以对它们执行的操作。
我们通常谈论 空间参考系统的空间数据 。它由坐标系和基准面组成。有多种坐标系,如仿射坐标系、柱坐标系、笛卡尔坐标系、椭球坐标系、线性坐标系、极坐标系、球坐标系和垂直坐标系。基准是一组参数,用于定义坐标系的原点位置、比例和方向。
从广义上讲, 许多支持空间数据的数据库将其分为两类:几何和地理 :
几何在平面坐标系上存储空间数据。这有助于我们用笛卡尔空间中的坐标来表示点、线和区域等形状。地理学基于圆形地球坐标系存储空间数据。这对于用纬度和经度坐标表示地球表面上的相同形状很有用。
我们可以利用空间数据进行两种基本类型的查询。这些基本上是 为了查找最近邻居或发送不同类型的范围查询 。我们之前已经看到过查找最近邻居的查询示例。总体思路是识别最接近查询点的一定数量的项目。
另一种重要的查询类型是范围查询。在这里,我们有兴趣了解属于查询范围内的所有项目。查询范围可以是距查询点一定半径的矩形或圆形。例如,我们可以使用这种查询来识别距我们所在位置两英里半径内的所有意大利餐馆。
6. 空间数据的数据结构
现在,我们将了解一些更适合构建空间索引的数据结构。这将帮助我们了解它们与常规索引有何不同,以及为什么它们在处理空间操作方面更有效。毫无疑问,几乎所有这些数据结构都是树数据结构的变体。
6.1.常规数据库索引
数据库索引基本上是 一种提高数据检索操作速度的数据结构 。如果没有索引,我们就必须遍历所有行来搜索我们感兴趣的行。但是,对于一个很大的表,即使遍历索引也会花费大量时间。
但是,减少获取密钥的步骤并减少磁盘操作的数量非常重要。 B树或 平衡树是一种自平衡树数据结构,在每个节点中存储多个排序的键值对 。这有助于在一次从磁盘的读取操作中从处理器缓存中提取更大的键集。
虽然B树工作得很好,但通常我们使用B+树来构建数据库索引。 B+树与 B 树非常相似,除了它仅在叶节点存储值或数据:
在这里,所有叶节点也被链接,因此提供对键值对的有序访问。这里的好处是叶子节点提供第一级索引,而内部节点提供多级索引。
常规数据库索引侧重于在单个维度上对其键进行排序。例如,我们可以对数据库表中的邮政编码等属性之一创建索引。这将帮助我们查询具有特定邮政编码或邮政编码范围内的所有位置。
6.2.空间数据库索引
在地理空间应用中,我们通常对最近邻或范围查询感兴趣。例如,我们可能想要查找特定点 10 英里范围内的所有位置。事实证明,常规数据库索引在这里并不是很有用。事实上,还有其他更适合构建空间索引的数据结构。
最常用的数据结构之一是R 树。 R树最早由Antonin Guttman于1984年提出,适合存储位置等空间对象。 R 树背后的基本思想是将附近的对象分组并用树的下一个更高级别中的最小边界矩形来表示它们 :
对于大多数操作,R 树与 B 树没有太大区别。关键区别在于使用边界矩形来决定是否在子树内搜索。为了获得更好的性能,我们应该确保矩形不会覆盖太多的空白空间,并且它们不会重叠太多。最有趣的是,R 树可以扩展到覆盖三个甚至更多维度!
用于构建空间索引的另一种数据结构是Kd 树,它是 R 树的轻微变体。 Kd树将数据空间分成两部分,而不是将其划分为多个矩形 。因此,Kd 树中的树节点表示分离平面而不是边界框。虽然 Kd 树被证明更容易实现并且速度更快,但它不适合不断变化的数据。
这些数据结构背后的关键思想基本上是将数据划分为轴对齐区域并将它们存储在树节点中。事实上,我们可以使用很多其他此类数据结构,例如 BSP 树和 R* 树。
7. 具有本机支持的数据库
我们已经了解了空间数据与常规数据的不同之处以及为什么它们需要特殊处理。因此,构建地理空间应用程序需要一个能够原生支持存储空间数据类型并能够高效执行空间查询的数据库。我们将这样的数据库管理系统称为空间数据库管理系统。
几乎所有主流数据库都开始为空间数据提供一定程度的支持。这包括一些流行的数据库管理系统,如MySQL 、 Microsoft SQL Server 、 PostgreSQL 、 Redis 、 MongoDB 、 Elasticsearch和Neo4J 。但是,也有一些专用的空间数据库可用,例如GeoMesa 、 PostGIS和Oracle Spatial 。
7.1.雷迪斯
Redis是 一种内存数据结构存储,我们可以将其用作数据库、缓存或消息代理 。由于它在内存中高效执行操作,因此可以最大限度地减少网络开销和延迟。 Redis 支持各种数据结构,如哈希、集合、排序集、列表和字符串。我们特别感兴趣的是排序集,它向成员添加有序视图,并按分数排序。
地理空间索引是在 Redis 中使用排序集作为底层数据结构来实现的。 Redis实际上是利用geohash算法 将经纬度编码成Sorted Set的分数 。 Geo Set 是使用 Sorted Set 实现的关键数据结构,并在更抽象的层面上支持 Redis 中的地理空间数据。
Redis 提供了 简单的命令来处理地理空间索引并执行常见操作, 例如创建新集以及添加或更新集中的成员。例如,要创建一个新集并从命令行向其中添加成员,我们可以使用 GEOADD 命令:
GEOADD locations 20.99 65.44 Vehicle-1 23.99 55.45 Vehicle-2
在这里,我们将一些车辆的位置添加到称为“位置”的地理集中。
Redis还提供了多种读取索引的方式,例如ZRANGE、ZSCAN和GEOPOS。此外,我们可以使用命令 GEODIST 来计算集合中成员之间的距离。但最有趣的命令是那些允许我们按位置搜索索引的命令。例如,我们可以使用命令 GEORADIUSYMEMBER 来搜索特定成员半径范围内的成员:
GEORADIUSBYMEMBER locations Vehicle-3 1000 m
在这里,我们有兴趣找到第三辆车一公里半径内的所有其他车辆。
Redis 非常强大且简单,支持存储大量地理空间数据并执行低延迟地理空间查询。
7.2. MongoDB
MongoDB是 一个面向文档的数据库管理系统,它使用类似 JSON 的文档 和可选模式来存储数据。它提供了多种搜索文档的方式,例如字段查询、范围查询和正则表达式。我们甚至可以将文档索引到主要索引和辅助索引。此外,具有分片和复制功能的 MongoDB 提供了高可用性和水平可扩展性。
我们可以 将空间数据作为 GeoJSON 对象或传统坐标对存储在 MongoDB 中 。 GeoJSON 对象对于存储类似地球表面的位置数据非常有用,而传统坐标对对于存储我们可以在欧几里德平面中表示的数据非常有用。
为了指定 GeoJSON 数据,我们可以使用一个嵌入文档,其中一个名为 type 的字段来指示 GeoJSON 对象类型,另一个名为 paths 的 字段 来指示对象的坐标:
db.vehicles.insert( {
name: "Vehicle-1",
location: { type: "Point", coordinates: [ 83.97, 70.77 ] }
} )
在这里,我们在名为 cars 的集合中添加一个文档。嵌入文档是 Point 类型的 GeoJSON 对象及其经度和纬度坐标。
此外,MongoDB 提供了多种地理空间索引类型(例如 2dsphere 和 2d) 来支持地理空间查询。 2dsphere 支持计算类似地球球体上的几何形状的查询:
db.vehicles.createIndex( { location : "2dsphere" } )
在这里,我们在集合的 位置 字段上创建 2dsphere 索引。
最后,MongoDB 提供了几个地理空间查询运算符来促进地理空间数据的搜索 。一些运算符包括 geoIntersects 、 geoWithin 、 near 和 nearSphere 。这些运算符可以解释平面或球体上的几何形状。
例如,让我们看看如何使用 近 运算符:
db.places.find(
{
location:
{ $near:
{
$geometry: { type: "Point", coordinates: [ 93.96, 30.78 ] },
$minDistance: 500,
$maxDistance: 1000
}
}
}
)
在这里,我们正在搜索距离上述 GeoJSON Point 至少 500 米、最多 1,000 米的文档。
MongoDB 能够以灵活的模式、扩展效率以及对地理空间数据的固有支持来表示类似 JSON 的数据,这使得 MongoDB 非常适合地理空间应用程序。
7.3.后地理信息系统
PostgreSQL是 一个关系数据库管理系统,提供 SQL 合规性并具有 ACID 事务功能 。它在支持各种工作负载方面非常通用。 PostgreSQL 包括对同步复制的内置支持以及对常规 B 树和哈希表索引的内置支持。 PostGIS是 PostgreSQL 的空间数据库扩展程序。
基本上, PostGIS 添加了对在 PostgreSQL 中存储地理空间数据以及在 SQL 中执行位置查询的支持 。它添加了 Point 、 LineString 、 Polygon 等几何类型。此外,它使用 R-tree-over-GiST(广义搜索树)提供空间索引。最后,它还添加了用于地理空间测量和集合操作的空间运算符。
我们可以像往常一样在 PostgreSQL 中创建一个数据库,并启用 PostGIS 扩展来开始使用它。从根本上讲,数据存储在行和列中,但 PostGIS 引入了几何列,其中的数据位于由空间参考标识符 (SRID) 定义的特定坐标系中。 PostGIS 还添加了许多用于加载不同 GIS 数据格式的选项。
PostGIS 支持几何和地理数据类型 。我们可以使用常规 SQL 查询来创建表并插入地理数据类型:
CREATE TABLE vehicles (name VARCHAR, geom GEOGRAPHY(Point));
INSERT INTO vehicles VALUES ('Vehicle-1', 'POINT(44.34 82.96)');
在这里,我们创建了一个新表“车辆”,并使用 点几何 体添加了特定车辆的位置。
PostGIS 添加了相当多的空间函数来对数据执行空间操作。例如,我们可以使用空间函数 ST_AsText 将几何数据读取为文本:
SELECT name, ST_AsText(geom) FROM vehicles;
当然,对我们来说,更有用的查询是查找给定点附近的所有车辆:
SELECT geom FROM vehicles
WHERE ST_Distance( geom, 'SRID=4326;POINT(43.32 68.35)' ) < 1000
在这里,我们正在搜索所提供点一公里半径内的所有车辆。
PostGIS 为 PostgreSQL 添加了空间功能,使我们能够利用众所周知的 SQL 语义来处理空间数据。此外,我们还可以受益于使用 PostgreSQL 的所有优势。
八、行业标准和规范
虽然我们看到数据库层对空间数据的支持不断增长,但应用程序层又如何呢?为了构建地理空间应用程序,我们需要编写能够有效处理空间数据的代码。
此外,我们需要标准和规范来表示和在不同组件之间传输空间数据。此外,语言绑定可以支持我们用 Java 等语言构建地理空间应用程序。
在本节中,我们将介绍地理空间应用领域中发生的一些标准化、它们制定的标准以及可供我们使用的库。
8.1.标准化工作
该领域已经取得了很大的发展,并且通过多个组织的协作努力,已经建立了一些标准和最佳实践。让我们首先了解一些为不同行业的地理空间应用的进步和标准化做出贡献的组织。
环境系统研究所 (ESRI) 可能是历史最悠久、规模最大的地理信息系统 (GIS) 软件和地理数据库管理应用程序国际供应商之一。他们 开发了一套名为 ArcGIS 的 GIS 软件,适用于桌面、服务器和移动设备等多个平台 。它还建立并推广了矢量和栅格数据类型的数据格式,例如 Shapefile、文件地理数据库、Esri Grid 和 Mosaic。
开放地理空间联盟 (OGC) 是一个由 300 多家公司、政府机构和大学组成的国际行业联盟,参与制定公开可用的接口规范的共识过程。这些 规范使得复杂的空间信息和服务能够被各种应用程序访问和使用 。目前,OGC标准包括30多个标准,包括空间参考系统标识符(SRID)、地理标记语言(GML)和简单特征-SQL(SFS)。
开源地理空间基金会 (OSGeo) 是一个非营利性非政府组织,支持和促进开放地理空间技术和数据的协作开发。它促进了诸如平铺地图服务(TMS)之类的地理空间规范。此外,它还 有助于开发多个地理空间库,例如 GeoTools 和 PostGIS 。它还适用于QGIS等应用程序,QGIS 是一个用于数据查看、编辑和分析的桌面 GIS。这些只是 OSGeo 在其旗下推广的几个项目。
8.2.地理空间标准:OGC GeoAPI
GeoAPI实现标准通过GeoAPI库定义了 一个Java语言API,其中包括一组我们可以用来操作地理信息的类型和方法 。地理信息的底层结构应遵循国际标准化组织(ISO)技术委员会211和OGC采用的规范。
GeoAPI 提供了实现中立的 Java 接口。在我们实际使用 GeoAPI 之前,我们必须 从第三方实现列表中进行选择 。我们可以使用 GeoAPI 执行多种地理空间操作。例如,我们可以从 EPSG 代码中获取坐标参考系统 (CRS)。然后,我们可以在一对 CRS 之间执行类似地图投影的坐标操作:
CoordinateReferenceSystem sourceCRS =
crsFactory.createCoordinateReferenceSystem("EPSG:4326"); // WGS 84
CoordinateReferenceSystem targetCRS =
crsFactory.createCoordinateReferenceSystem("EPSG:3395"); // WGS 84 / World Mercator
CoordinateOperation operation = opFactory.createOperation(sourceCRS, targetCRS);
double[] sourcePt = new double[] {
27 + (59 + 17.0 / 60) / 60, // 27°59'17"N
86 + (55 + 31.0 / 60) / 60 // 86°55'31"E
};
double[] targetPt = new double[2];
operation.getMathTransform().transform(sourcePt, 0, targetPt, 0, 1);
在这里,我们使用 GeoAPI 执行地图投影来转换单个 CRS 点。
有多种 GeoAPI 第三方实现可用作现有库的包装器,例如 NetCDF Wrapper、Proj.6 Wrapper 和 GDAL Wrapper。
8.3.地理空间库:OSGeo GeoTools
GeoTools是一个 OSGeo 项目,它 提供了一个用于处理地理空间数据的开源 Java 库 。 GeoTools数据结构基本上基于OGC规范。它定义了关键空间概念和数据结构的接口。它还配备了支持功能访问的数据访问 API、无状态低内存渲染以及使用 XML 模式绑定到 GML 内容的解析技术。
要在 GeoTools 中创建地理空间数据,我们需要定义要素类型。最简单的方法是使用 SimpleFeatureType 类:
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("Location");
builder.setCRS(DefaultGeographicCRS.WGS84);
builder
.add("Location", Point.class);
.length(15)
.add("Name", String.class);
SimpleFeatureType VEHICLE = builder.buildFeatureType();
一旦我们准备好特征类型,我们就可以使用它来通过 SimpleFeatureBuilder 创建特征:
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(VEHICLE);
DefaultFeatureCollection collection = new DefaultFeatureCollection();
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
我们还实例化该集合来存储我们的要素和 GeoTools 工厂类来为该位置创建一个 点 。现在,我们可以将特定位置作为特征添加到我们的集合中:
Point point = geometryFactory.createPoint(new Coordinate(13.46, 42.97));
featureBuilder.add(point);
featureBuilder.add("Vehicle-1");
collection.add(featureBuilder.buildFeature(null))
这只是我们使用 GeoTools 库可以做的事情的冰山一角。 GeoTools 支持使用矢量和栅格数据类型 。它还允许我们使用标准格式(如 shapefile) 的数据。
9. 结论
在本教程中,我们了解了构建地理空间应用程序的基础知识。我们讨论了构建此类应用程序的性质和挑战。这帮助我们了解可以使用的不同类型的空间数据和数据结构。
此外,我们还研究了一些具有本地支持的开源数据库,用于存储空间数据、构建空间索引和执行空间操作。最后,我们还介绍了一些推动地理空间应用标准化工作的行业合作。