jjzjj

GIS开源框架:ArcGIS文件地理数据库(GDB)解析与入库

是席木木啊 2023-06-07 原文

        对于GIS专业毕业的同学,想必对于ArcGIS软件不会太陌生,对于地理数据库也有一定的了解和使用经验。但是,撇开软件操作层面不谈,作为一个WebGIS/GIS开发人员,我们如何通过GIS开源框架去完成地理数据库的自动化解析和入库操作呢?这是接下来我们要深入讨论的核心问题。

目录

地理数据库

什么是地理数据库?

地理数据库的作用

ArcGIS地理数据库的基本体系

地理数据库的架构

地理数据库的相关概念

 地理数据库类型

Java后端开源GIS框架

GeoTools

核心特性

支持的数据源格式

GeoTools架构(Architecture)

GeoTools插件(Plugins)

GeoTools拓展(Extensions)

GeoTools衍生项目

GDAL(Geospatial Data Abstraction Library)

GDAL:Windows开发环境搭建

GDAL:基于FileGDB解析GDB

GDAL解析GDB文件地理数据库

GeoTools:GDB数据源自动入库处理


地理数据库

什么是地理数据库?

        先介绍一下什么是地理数据库。

        对于常年征战于后端开发领域的小伙伴们,对于“关系型数据库”一定有深入的理解,通俗地讲,就是:基于计算机的文件管理系统,将客观世界的实体进行抽象,并以关系表的形式存储到计算机中,供软件开发人员进行存取和更新、乃至分析等操作。例如:对公司的职工群体进行抽象,我们就能得到一张大名鼎鼎的职工信息表(tb_employee),里面存储了每一位员工的职工号、姓名、入职时间、所在部门等等各种信息。这也是GIS专业常年在谈的属性数据

        我们通过类比的方式对“地理数据库”做个定义,通俗地讲,就是:基于计算机的文件管理系统,将客观世界的地理实体进行抽象,并以关系表的形式存储到计算机中,专门供GIS开发人员进行存取、更新、分析等操作。注意:我们这里用了地理实体这个词汇,常见的,POI点、一系列宗地区域、行政区划区域、水系范围等等,任何可以通过点、线、面基础几何图形元素进行表达的任何客观实体,我们都可以将其以称之为地理实体,然后将其存储到地理数据库中。

地理数据库的作用

        那么问题来了,地理数据库在GIS开发领域有什么重要意义呢?如何理解地理数据库在实际中的应用价值呢?

        我们以ArcGIS产品的官方文档为基准,来阐述地理数据库在不同方面的多层次含义。

        ①数据结构方面:作为ArcGIS的原生数据结构,地理数据库是用于编辑和数据管理的主要数据格式,承担地理数据的存储载体职能;

        ②数据存储方面:作为地理数据的存储载体,地理数据库仍然是基于数据库管理系统或者文件系统实现的。因此,在实际开发中,我们会发现,像MySQL、Oracle、PostGreSQL等多种数据库,或以自包含的形式、或以插件的形式,先后都提供了对于点线面等几何数据的存储、查询、更新等操作的支持。

        ③信息模型方面:地理数据库的信息模型十分全面,不局限于点线面等几何要素——俗称的矢量数据;也天然的将图像数据——俗称的栅格数据包含在内(例如:遥感影像、DEM数字高程模型等),实现地理信息的表示和管理。同时,地理数据库也支持对于地理数据的空间关系、空间完整性规则(例如:几何图形的拓扑结构完整性)的定义。

        ④通用性方面。严格意义上讲,地理数据库软件可支持各种格式的地理数据,以及不同格式之间的数据交换操作,包括:shapefile、计算机辅助绘图 (CAD) 文件、不规则三角网 (TIN)、格网、CAD 数据、影像、地理标记语言 (GML) 文件和大量其他 GIS 数据源

        ⑤工作流(Workflow)方面。地理数据库不仅仅是数据存储和数据交换与集成的工具,同时也可提供基于GIS数据的工作流事务模型,可天然实现一些工作流程及其各操作步骤之间业务规则的抽象、概括描述、与自动化处理。

ArcGIS地理数据库的基本体系

地理数据库的架构

        以关系型数据库为参考,地理数据库的实现并不存在任何特别之处,因为地理数据库仍是基于其它高级DBMS应用程序中的相同多层应用程序架构实现的。我们在之前有提到,MySQL、Oracle等提供有空间拓展功能,因此,从这个层面上来讲,MySQL也可以作为一个地理数据库来使用——因为它不仅支持GIS数据的存储,还提供了用于计算、输出结果的功能函数模块。

        我们来看一个将PostGreSQL数据库作为地理数据库进行使用的示例,如下为GIS数据在数据表中的存储形式,下表中存储了中国各省份的行政区划面数据(Geometry字段值就是对应的几何数据),

PostGreSQL-数据表部分截图

        为了更加明显的看到我们存储的数据在地理层面的显示效果,以下通过QGIS(一款开源GIS桌面软件)连接PostGreSQL数据库,然后将数据表中的数据进行可视化展示。

数据表中地理数据的可视化展示

         如上图,即为基于PostGreSQL数据库实现的地理数据存储示例。

        至于这种机制是如何实现的呢?我们可以将实现转移到PostGreSQL的数据类型方面,通过查阅官网文档,会发现:PostGreSQL内置的支持Geometry几何类型字段,该字段就是用于存储GIS数据的。换句话说,只要将一个字段设置为Geometry类型,那么就可以实现GIS数据的存储,将一个PostGreSQL数据库作为地理数据库使用而用于存储地理数据的这些表或者以文件的形式存储到磁盘上,或者存储到 DBMS 的数据库中,如 Oracle、IBM DB2、PostgreSQL、IBM Informix 或 Microsoft SQL Server。——这也是上面部分我们为什么说“地理数据库的实现并不存在任何特别之处”的原因所在。

PostGreSQL-官网文档截图

地理数据库的相关概念

        (1)数据集:地理数据库中包含3种数据集类型:①要素类;②栅格数据集;③表。通常我们通常是基于上述3种形式,将其以表的形式进行存储,实现地理数据库的构建的。

        (2)地理数据库策略:一个重要的地理数据库策略是充分利用数据库管理系统 (DBMS) 将 GIS 数据集以及用户数量扩展到极大的规模(例如,从仅能支持一个或几个用户的简单小型数据库扩展到可以支持上百万个要素和几千个同步用户的大型数据库)。表是地理数据集的主要存储形式。SQL 十分适用于对表中的行进行查询和集处理,因此地理数据库策略就是要充分利用这些功能。地理数据库支持使用 SQL 访问以下 DBMS 中的要素几何:

        (3)地理数据库元素:无论 GIS 用户使用什么系统,他们都将用到以下三种基本的数据集类型。

要素类(几何数据通过shpe字段存储)

栅格数据与空间参考

 地理数据库类型

        地理数据库是用于保存数据集集合的“容器”。ArcGIS将其分为3种类型:ArcSDE地理数据库、文件地理数据库、个人地理数据库。

        其中:

                ①文件地理数据库(gdb):在文件系统中以文件夹形式存储。每个数据集都以文件形式保存,单个文件最多可拓展至1TB。

                ②个人地理数据库(mdb):所有数据集都存储在Microsoft Access数据文件内,该数据文件最大为2G。

                ③ArcSDE地理数据库:也称为多用户地理数据库,这种类型的数据库使用Oracle、Microsoft SQL Server、IBM DB2、IBM Informix 或 PostgreSQL 存储于关系数据库中。这些地理数据需要使用ArcSDE,并且在大小和用户数量方面没有限制。

        以下为ArcGIS官方文档给出的对比结果,

三种类型地理数据库的比较

        PS:基于以上对比结果,在实际应用中,更建议使用文件地理数据库而不是个人地理数据库。

文件地理数据库-后缀名通常为GDB

Java后端开源GIS框架

        在以上内容的基础上,我们进一步讨论如何基于开源GIS框架来解析GDB文件地理数据库。

        以下介绍较大多数人熟知的开源GIS框架:geotools、gdal,并且这两者提供了对Java开发语言的支持。

GeoTools

geotools-官网首页截图

         geotools的官网页面可点击此处查看。

         geotools是一款用于处理空间数据(geospatial data)的、开源的、遵循OGC标准的Java开发库。其基本结构如下,

         基于Geotools框架,可以实现web地图服务(web services)、命令行工具(command line tools)以及桌面软件(desktop applications)的开发。

核心特性

        (1)定义了空间概念和数据结构接口:主要是基于Java Topology Suite (JTS)实现了对Goemetry几何数据实体类的定义,并实现了对属性数据和空间数据的的过滤支持。

        (2)提供了数据访问接口:支持feature要素获取、事务支持以及线程锁操作。

                可以从多种文件和空间数据库中获取数据;提供了地理坐标系支持;地图投影操作;基于属性数据和空间数据的过滤和分析操作。

        (3)提供了渲染器支持:一种适用于服务器端环境的、无状态的、低内存消耗的渲染器,可以实现复杂样式的地图展示、文本标签和颜色渲染控制。

        (4)GeoTools插件

        (5)GeoTools拓展:提供了对图形和网络支持(用于寻找最短路径)、拓扑验证、web地图服务器客户端、xml解析和编码的绑定以及颜色定义/渲染。

支持的数据源格式

(1)栅格格式

 (2)数据库jdbc支持

 (3)矢量格式

 (4)XML文件

GeoTools架构(Architecture)

        了解一些基本的GeoTools Library的结构是很有必要的,这将帮助我们在开发过程中快速找到适合业务场景的jar包。

GeoTools基础架构图

         PS:实际开发中,Maven工具可以帮助我们分析出需要引入的jar包。

        基于以上架构图,可以将GeoTools划分为以下几大模块,其中各模块的作用如下,

GeoTools-模块解析

GeoTools插件(Plugins)

        在以上核心模块的基础上,GeoTools推出了各种插件,以满足实际的应用需求。

geotools plugins

GeoTools拓展(Extensions)

        GeoTools在顶层架构基础上,还提供了一套拓展模块,可以实现绘、空间数据拓扑验证、地图服务等功能。

         当然,GeoTools还提供了对XML文档的支持,详细内容可点击此处查看。

GeoTools衍生项目

        如下为GeoTools的衍生项目,或者说是基于GeoTools开发出来的一些产品列表。

         其它内容可参考GeoTools官网文档。或者在之后也会再出一些文章进行详细的讨论。

GDAL(Geospatial Data Abstraction Library)

        GDAL是一个在X/MIT许可协议下的开源栅格空间数据转换库。它利用抽象数据模型来表达所支持的各种文件格式。它还有一系列命令行工具来进行数据转换和处理。GDAL也是一个相当复杂的开源GIS框架,提供了对Java开发语言的支持。

GDAL API支持情况

        之所以要介绍GDAL,是因为在之前对GeoTools框架的介绍中,我们并没有看到、实际上它也不支持对于ArcGIS文件地理数据库(GDB)的解析操作。而GDAL是支持的,通过它的FileGDB驱动模块,可以实现GDB文件地理数据库的数据源解析、表信息/要素信息获取等操作。

GDAL-FileGDB

         由于GDAL是一个相当庞大的开源项目,以下我们只针对感兴趣的FileGDB驱动部分、以及GDAL的环境搭建方面进行介绍。

GDAL:Windows开发环境搭建

(1)安装包下载。

         安装包可从GDAL官网进行下载,当前使用版本为release-1900-x64-2.4.4-mapserver-7.4.3。也可下载其它与本地JDK环境相配套的安装包(zip包即可)。

(2)zip包解压

        下载完成后,将zip包解压到本地(解压目录如下图),

        其中:bin/gdal/java目录下的gdal.jar是开发中要使用到的依赖包。

 (3)配置环境变量

           接着进行环境变量的配置操作。将bin目录下的子文件夹路径配置到系统环境变量中,

 (4)测试GDAL环境

        使用如下命令,查看GDAL的版本信息,测试GDAL是否安装成功。

# 查看GDAl版本信息
gdalinfo --version
# 获取GDAL支持的驱动列表
gdalinfo --formats

        显示类似如下的信息即为配置成功。 

GDAL:基于FileGDB解析GDB

        使用Java版本的GDAL解析GDB,需要将GDAL安装目录下的动态链接库(dll文件)合并到jdk安装目录下的bin目录,然后正常引入gdal.jar文件到项目中即可。

动态链接库配置

        将gdal安装目录的bin路径下的所有dll文件拷贝到jdk的bin目录下,进行动态链接库的配置。

 Java后端项目引入gdal.jar

        基于Maven构建的后端项目中,要使用GDAL,只需要引入gdal.jar文件即可。有两种引入方式,

(1)以文件方式引入

        在项目的根目录下创建lib文件夹,并将gdal安装目录下的gdal.jar文件放入,

        例如:此处创建了名称为gdalApps的项目,即拷贝gdal.jar文件到gdalApps/lib路径下,

        接着需要配置pom.xml文件的dependencies结点,配置内容如下,

<!--gdal-->
<dependency>
    <groupId>org.gdal</groupId>
    <artifactId>gdal</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/gdal.jar</systemPath>
</dependency>

(2)以pom.xml文件配置的方式引入

        还可以通过直接配置pom.xml文件的方式引入gdal.jar开发依赖,但是请注意:强烈建议此种方式引入的gdal.jar文件版本与本地配置好的GDAL环境版本保持一致。

<dependency>
    <groupId>org.gdal</groupId>
    <artifactId>gdal</artifactId>
    <version>xxx</version>
</dependency>

        至Maven依赖项下显示gdal.jar即为引入成功。

GDAL解析GDB文件地理数据库

        GDAL解析GDB文件地理数据库,依赖于FileGDB/OpenFileGDB驱动项。因此,使用GDAL解析GDB的基本思路一般如下,

        ①调用ogr.GetDriverByName(GDBDriver)方法获取FileGDB/OpenFileGDB驱动实例driver,并借助driver.Open(filePath)方法获取一个GDB数据源,得到一个DataSource实例;

        ②使用①中获取到的DataSource实例,调用其getLayer(layerIndex)方法获取GDB数据源中包含的每一个图层Layer对象,然后借助该对象的GetLayerDefn()方法获取到图层的元信息,如:空间参考/坐标系信息、属性表字段信息、获取图层内部Feature要素类实例的游标对象等(具体可参考示例代码)。

        ③通过Layer图层对象调用GetNextFeature()方法,逐个解析图层内的Feature要素信息即可。

        PS:Java版本的GDAL解析GDB文件地理数据库时,使用到的类及其层次结构如下,

        示例代码如下,

package com.xwd;

import org.gdal.gdal.gdal;
import org.gdal.ogr.*;
import org.gdal.osr.SpatialReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ResourceUtils;

import java.io.FileNotFoundException;
import java.util.*;

/**
 * @className ReadGDB_Test
 * @description: com.xwd
 * @auther: xiwd
 * @date: 2023-02-13 - 02 - 13 - 21:04
 * @version: 1.0
 * @jdk: 1.8
 */
@SpringBootTest(classes = GDALSpringBootApp.class)
@RunWith(value = SpringRunner.class)
public class ReadGDB_Test {
    //properties

    //static properties
    static {
        gdal.AllRegister();//设置gdal环境
        // 为了支持中文路径,请添加下面这句代码
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8","YES");
        // 为了使属性表字段支持中文,请添加下面这句
        gdal.SetConfigOption("SHAPE_ENCODING","");
        try {
            //防止报错
            gdal.SetConfigOption("GDAL_DATA", ResourceUtils.getFile("classpath:gdal-data").getPath());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    //methods
    @Test
    public void readGDB(){
        String GDBPath = "D:\\GIS_workspace\\gdb_data\\test-data\\中国行政区划数据.gdb\\test.gdb";
        String FileDriver = "OpenFileGDB";
        Driver driver = null;
        DataSource dataSource = null;
        driver = ogr.GetDriverByName(FileDriver);
        dataSource = driver.Open(GDBPath);
        if (Objects.isNull(dataSource)) return;
        int layerCount = dataSource.GetLayerCount();
        for (int i = 0; i < layerCount; i++) {
            Layer layer = dataSource.GetLayer(i);
            String layerName = layer.GetName();
            double[] extent = layer.GetExtent();
            System.out.println("正在解析"+layerName+",范围:"+ Arrays.toString(extent));
            FeatureDefn featureDefn = layer.GetLayerDefn();
            //解析Layer属性字段信息
            int fieldCount = featureDefn.GetFieldCount();
            Map<String,Object> fieldMap = new HashMap<>();
            for (int j = 0; j < fieldCount; j++) {
                FieldDefn fieldDefn = featureDefn.GetFieldDefn(j);
                String fieldName = fieldDefn.GetName();
                int fieldType = fieldDefn.GetFieldType();
                String typeName = fieldDefn.GetFieldTypeName(fieldType);
                fieldMap.put(fieldName,typeName);
            }
            System.out.println("图层"+layerName+"范围信息:"+fieldMap.toString());
            //解析Layer属性表
            long featureCount = layer.GetFeatureCount();
            for (int j = 0; j < featureCount; j++) {
                Feature feature = null;
                while ((feature = layer.GetNextFeature()) != null){
                    Set<Map.Entry<String, Object>> entries = fieldMap.entrySet();
                    for (Map.Entry<String, Object> next : entries) {
                        String key = next.getKey();
                        String value = feature.GetFieldAsString(key);
                        System.out.print(key + ":" + value);
                    }
                    System.out.println();
                }
            }
            System.out.println("\n");
        }
    }
}

GeoTools:GDB数据源自动入库处理

        将GDB数据源自动入库,则需要借助GeoTools-PostGIS插件的支持,可在pom.xml文件中引入对应的依赖项,其中:设置几何字段时,需要用到JTS模块内置的Geometry类类型;设置空间参考信息时,需要用到EPSG编码信息;通过PostGIS插件进行结果集过滤时,需要借助CQL模块实现。

        <!--gt-postgis-jdbc数据库PostGIS操作支持-->
        <dependency>
            <groupId>org.geotools.jdbc</groupId>
            <artifactId>gt-jdbc-postgis</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <!--gt-cql过滤查询支持-->
        <dependency>
            <groupId>org.geotools</groupId>
            <artifactId>gt-cql</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <!-- jts -->
        <dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>${jts.version}</version>
        </dependency>
        <dependency>
            <groupId>org.geotools</groupId>
            <artifactId>gt-epsg-hsql</artifactId>
            <version>${geotools.version}</version>
        </dependency>

        GDB数据源自动入库处理的完整的示例代码如下,

        PS:以下是先将GDB数据源序列化为单个小文件存储到本地,然后入库时将其做反序列化处理,再执行入库操作。

package com.xwd;

import org.gdal.gdal.gdal;
import org.gdal.ogr.*;
import org.geotools.data.*;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.WKBReader;
import org.geotools.referencing.CRS;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @className DataStoreDemo
 * @description: com.xwd
 * @auther: xiwd
 * @date: 2023-02-09 - 02 - 09 - 22:34
 * @version: 1.0
 * @jdk: 1.8
 */
@SpringBootTest(classes = GDALSpringBootApp.class)
@RunWith(value = SpringRunner.class)
class DataStoreToolClass {
    //static properties
    private static final Map<String, Object> dbParams = new HashMap<>();
    private static final GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
    private static final WKTReader wktReader = new WKTReader(geometryFactory);
    //properties
    private DataStore dataStore;



    static {
        /**
         * PostGIS连接参数
         * dbtype数据库类型(固定值):Must be the string postgis
         * host主机名:Machine name or IP address to connect to
         * port端口:Port number to connect to, default 5432
         * schema空间:The database schema to access
         * database数据库名: The database to connect to
         * user用户名:User name
         * passwd密码:Password
         */
        dbParams.put("dbtype", "postgis");
        dbParams.put("host", "localhost");
        dbParams.put("port", 5432);
        dbParams.put("schema", "public");
        dbParams.put("database", "gdb_db");
        dbParams.put("user", "postgres");
        dbParams.put("passwd", "postgres");
    }

    /**
     * 构造器
     */
    DataStoreToolClass() throws IOException {
        /**
         * FactoryFinder-提供多线程环境的安全、默认同步支持
         *      DataStoreFinder - 用于获取一个DataStore实例
         *  * access(访问):shapefile、database(PostGIS/web service[WFS])
         *  * create(创建):shapefile,然后借助FileDataStoreFinder对shp文件进行操作
         *  PS:DataStore数据源是不可重复的,即以单例的形式存在,但是可以复用
         *
         */
        this.dataStore = DataStoreFinder.getDataStore(dbParams);
    }

    /**
     * 动态创建表结构
     *
     * @param tableName 数据表名
     * @param fieldsMap 字段->字段类型映射表,强制使用LinkedHashMap,保证字段有序性
     * @param crs       空间参考系的EPSG编码
     * @return SimpleFeatureType表结构对象
     */
    public SimpleFeatureType createSimpleFeatureType(String tableName, LinkedHashMap<String, Class> fieldsMap, String crs) throws FactoryException {
        if (Objects.isNull(crs) || "".equals(crs)) {
            crs = "4326";
        }
        SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder();
        ftb.setName(tableName);
        ftb.setCRS(CRS.decode("EPSG:" + crs));
        Set<Map.Entry<String, Class>> entries = fieldsMap.entrySet();
        for (Map.Entry<String, Class> next : entries) {
            if ("geometry".equals(next.getKey())) {
                ftb.add(next.getKey(), next.getValue());
            } else {
                ftb.add(next.getKey(), next.getValue());
            }
        }
        return ftb.buildFeatureType();
    }

    /**
     *
     * @param schemaName 数据表名称
     * @return 布尔值,true-表示数据表已经存在,无需创建;false-表示数据表不存在,需要创建
     */
    public boolean hasSchema(String schemaName){
        SimpleFeatureType schema = null;
        try {
            schema = dataStore.getSchema(schemaName);
            return Objects.nonNull(schema);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除数据表
     * @param schemaName 数据表名
     */
    public void dropSchema(String schemaName){
        try {
            this.dataStore.removeSchema(schemaName);
            System.out.println(schemaName+"删除表成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 动态创建数据表
     *
     * @param schemaName  数据表名
     * @param featureType 数据表结构
     * @param dropExistsBefore 否先删除已有同名表-再执行添加操作-true-是;false-否
     * @return boolean, 布尔值-true:创建成功;false-创建失败
     */
    public boolean createSchema(String schemaName, SimpleFeatureType featureType,boolean dropExistsBefore) {
        // create a featureType and write it to PostGIS
        try {
            boolean hasSchema = this.hasSchema(schemaName);
            if (hasSchema) {
                if (dropExistsBefore){
                    this.dropSchema(schemaName);
                }else{
                    return true;
                }
            }
            dataStore.createSchema(featureType);
            //尝试获取数据表
            SimpleFeatureType schema = dataStore.getSchema(schemaName);
            if (Objects.nonNull(schema)) return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /***
     * 为数据表添加数据
     * @param schemaName 数据表名
     * @param dataMap 包含id值和data数据体的Map映射表
     * @return int,影响的行数/添加成功的记录条数
     */
    public int inertForSchema(String schemaName,LinkedHashMap<String,Object[]> dataMap) {
        if (Objects.isNull(schemaName) || "".equals(schemaName)) return 0;
        SimpleFeatureType schema = null;
        FeatureIterator iterators = null;
        FeatureWriter<SimpleFeatureType, SimpleFeature> featureWriter = null;
        int affectRows = 0;
        try {
            schema = this.dataStore.getSchema(schemaName);//从数据源获取数据表对象
            SimpleFeatureBuilder build = new SimpleFeatureBuilder(schema);
            List<SimpleFeature> featureList = new ArrayList<>();
            Set<Map.Entry<String, Object[]>> entries = dataMap.entrySet();
            for (Map.Entry<String, Object[]> next : entries) {
                SimpleFeature simpleFeature = build.buildFeature(next.getKey(), next.getValue());
                featureList.add(simpleFeature);
            }
            FeatureCollection collection = new ListFeatureCollection(schema, featureList);
            featureWriter = this.dataStore.getFeatureWriterAppend(schemaName, Transaction.AUTO_COMMIT);
            iterators = collection.features();
            while (iterators.hasNext()){
                Feature feature = iterators.next();
                SimpleFeature simpleFeature = featureWriter.next();
                Collection<Property> properties = feature.getProperties();//获取features属性集合
                for (Property property : properties) {
                    simpleFeature.setAttribute(property.getName(), property.getValue());
                }
                featureWriter.write();
                affectRows++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(iterators))
                iterators.close();
            if (Objects.nonNull(featureWriter)) {
                try {
                    featureWriter.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return affectRows;
    }


    /**
     * 数据表查询-[过滤查询]
     * @param schemaName 数据表名称
     * @param sqlFilter 过滤条件的SQL语句,例如:模糊查询过滤,name like '%a'
     * @return Collection<Property> 查询结果集,查询失败则返回null
     */
    public Collection<Property> queryFilterForSchema(String schemaName, String sqlFilter) {
        if (Objects.isNull(schemaName) || "".equals(schemaName)) return null;
        Filter filter = null;
        Query query = null;
        FeatureReader<SimpleFeatureType, SimpleFeature> featureReader = null;
        Collection<Property> properties = null;
        try {
            filter = CQL.toFilter("name like '%a'");
            query = new Query(schemaName,filter);
            featureReader = this.dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT);
            while (featureReader.hasNext()) {
                SimpleFeature feature = featureReader.next();
                properties = feature.getProperties();
                System.out.println(Arrays.toString(properties.toArray()));
            }
            return properties;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (Objects.nonNull(featureReader)) {
                try {
                    featureReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return properties;
    }

    /**
     * 数据表查询操作-[全量查询]
     * @param schemaName 数据表名称
     * @return Collection<Property> 查询结果集,查询失败则返回null
     */
    public Collection<Property> queryAllForSchema(String schemaName){
        if (Objects.isNull(schemaName) || "".equals(schemaName)) return null;
        SimpleFeatureSource featureSource = null;
        SimpleFeatureIterator iterator = null;
        Collection<Property> properties = null;
        try {
            featureSource = this.dataStore.getFeatureSource(schemaName);
            SimpleFeatureCollection features = featureSource.getFeatures();
            iterator = features.features();
            while (iterator.hasNext()){
                SimpleFeature simpleFeature = iterator.next();
                properties = simpleFeature.getProperties();
//                for (Property property : properties) {
//                    System.out.print(property.getName() + "(" + property.getType().getBinding().getName() + "):" + property.getValue()+"\t");
//                }
            }
            return properties;
        } catch (IOException e) {
            e.printStackTrace();
            return properties;
        } finally {
            if (Objects.nonNull(iterator))
                iterator.close();
        }
    }

}

class GDBToolClass{
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);//线程池
    private static final String GDBDriver = "OpenFileGDB";

    private String  GDBPath;
    private DataSource dataSource;

    static {
        gdal.AllRegister();//设置gdal环境
        // 为了支持中文路径,请添加下面这句代码
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
        // 为了使属性表字段支持中文,请添加下面这句GDBReadTaskExecutor
        gdal.SetConfigOption("SHAPE_ENCODING", "");
//        gdal.SetConfigOption("GDAL_DATA", "classpath:gdal-data");
        gdal.SetConfigOption("OGR_ORGANIZE_POLYGONS", "ONLY_CCW");//开启此配置-加速读取
        try {
            //防止报错
            gdal.SetConfigOption("GDAL_DATA", ResourceUtils.getFile("classpath:gdal-data").getPath());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }


    GDBToolClass(String GDBPath){
        this.GDBPath = GDBPath;
        Driver driver = ogr.GetDriverByName(GDBDriver);
        this.dataSource = driver.Open(this.GDBPath);
    }

    /**
     * 获取所有图层
     * @return
     */
    public List<Layer> getLayers(){
        int count = this.dataSource.GetLayerCount();
        List<Layer> layers = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            layers.add(this.dataSource.GetLayer(i));
        }
        return layers;
    }

    /**
     * 根据字符串类型描述获取对应的Class运行时类类型
     * @param name 类型字符串
     * @return Class运行时类类型
     */
    private Class getClassByStringName(String name){
        if ("".equals(name)||Objects.isNull(name)) return String.class;
        switch (name){
            case "String":{
                return String.class;
            }
            case "Real":{
                return Double.class;
            }
            default:
                return String.class;
        }
    }

    /**
     * 获取名称为layerName的图层属性表结构
     * @param layerName 图层名称
     * @return 以LinkedHashMap<String,Class>描述的属性表结构
     */
    public LinkedHashMap<String,Class> getLayerSchemaStructure(String layerName){
        Layer layer = this.dataSource.GetLayer(layerName);
        System.out.println(layer);
        FeatureDefn featureDefn = layer.GetLayerDefn();
        LinkedHashMap<String,Class> classMap = new LinkedHashMap<>();
        for (int i = 0; i < featureDefn.GetFieldCount(); i++) {
            FieldDefn fieldDefn = featureDefn.GetFieldDefn(i);
            int fieldType = fieldDefn.GetFieldType();
            String typeName = fieldDefn.GetFieldTypeName(fieldType);
            String name = fieldDefn.GetName();
            System.out.println(typeName+"-"+name);
            classMap.put(name,getClassByStringName(typeName));
        }
        classMap.put("geometry",Geometry.class);
        return classMap;
    }
}

class FileToolClass{
    private static final GeometryFactory factory = JTSFactoryFinder.getGeometryFactory(null);
    private static final WKBReader wkbReader = new WKBReader(factory);

    FileToolClass(){ }

    /**
     * 从文件中解析List<Map>结构的数据集
     * @param filePath 文件路径
     * @return List<Map>结构的数据集
     */
    public List<Map<String,Object>> readFile(String filePath){
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(filePath);
            ois = new ObjectInputStream(fis);
            List<Map<String,Object>> object = (List<Map<String, Object>>) ois.readObject();
            return object;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从文件中解析List<Map>结构的数据集
     * @param file 文件路径
     * @return List<Map>结构的数据集
     */
    public List<Map<String,Object>> readFileByFile(File file){
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(file);
            ois = new ObjectInputStream(fis);
            List<Map<String,Object>> object = (List<Map<String, Object>>) ois.readObject();
            return object;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     *
     * @param maps readFile读取单个文件返回的结果集
     * @param fieldClassMap 原始GDB中获取到的字段-字段类型的映射Map
     * @return 转换之后符合要求的结果集
     */
    public LinkedHashMap<String,Object[]> trans2ParamMap(List<Map<String,Object>> maps,LinkedHashMap<String, Class> fieldClassMap){
        LinkedHashMap<String,Object[]> mapResult = new LinkedHashMap<>();
        for (int i = 0; i < maps.size(); i++) {
            Map<String, Object> map = maps.get(i);
            int size = fieldClassMap.size();
            Object[] objects = new Object[size];
            int count = 0;
            for (Map.Entry<String, Class> next : fieldClassMap.entrySet()) {
                if ("geometry".equals(next.getKey())) {
                    Geometry geometry = null;
                    try {
                        geometry = wkbReader.read((byte[]) map.get(next.getKey()));
                        objects[count] = geometry;
                    } catch (ParseException e) {
                        e.printStackTrace();
                        objects[count] = null;
                    }
                } else {
                    objects[count] = map.get(next.getKey());
                }
                count++;
            }
            mapResult.put(String.valueOf(i+1),objects);
        }
        return mapResult;
    }


}

public class DataStoreDemo {
    //static properties

    //测试GDB---GDB数据入库处理关键代码
    @Test
    public void readAllFilesFromDirectory() throws Exception {
        String GDBPath = "D:\\GIS_workspace\\gdb_data\\test-data\\中国行政区划数据.gdb\\test.gdb";
        String outputDirectory = "D:\\GIS_workspace\\gdb_data\\test-data\\中国行政区划数据.gdb\\output";
        File file = new File(outputDirectory);
        if (!file.isDirectory()) throw new Exception("文件夹路径不合法");
        boolean dropExistsBefore = true;//是否先删除已有同名表-再执行添加操作-true-是;false-否
        GDBToolClass toolClass = new GDBToolClass(GDBPath);//GDB数据源解析类
        DataStoreToolClass dataStoreToolClass = new DataStoreToolClass();//数据库操作类
        FileToolClass fileToolClass = new FileToolClass();//文件解析操作类
        List<Layer> layers = toolClass.getLayers();
        for (Layer layer : layers) {
            String layerName = layer.GetName();//图层名称/数据表名
            LinkedHashMap<String, Class> classMap = toolClass.getLayerSchemaStructure(layerName);//获取图层的数据表结构
            SimpleFeatureType simpleFeatureType = dataStoreToolClass.createSimpleFeatureType(layerName, classMap, "4326");
            boolean flag = dataStoreToolClass.createSchema(layerName, simpleFeatureType,dropExistsBefore);//创建数据表
            //判断数据表是否创建成功
            if (flag) {
                System.out.println("数据表创建成功");
            } else {
                System.out.println("数据表创建失败/已存在");
            }
            File[] files = file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.getName().contains(layerName);
                }
            });
            if (Objects.isNull(files) || files.length == 0) {
                System.out.println("数据源-[" + layerName + "]解析失败!");
                continue;
            }
            //解析数据源
            for (File currentFile : files) {
                List<Map<String, Object>> maps = fileToolClass.readFileByFile(currentFile);
                LinkedHashMap<String, Object[]> hashMap = fileToolClass.trans2ParamMap(maps, classMap);
                //添加数据到数据库中
                int rows = dataStoreToolClass.inertForSchema(layerName, hashMap);
                System.out.println("成功添加" + rows + "条记录到[" + layerName + "]表中");
            }
        }
    }


}

有关GIS开源框架:ArcGIS文件地理数据库(GDB)解析与入库的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  10. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

随机推荐