Estrutura de código aberto GIS: análise e armazenamento do banco de dados geográfico (GDB) de arquivos ArcGIS

        Para os alunos que se formaram em GIS, eles não devem estar muito familiarizados com o software ArcGIS e também têm um certo entendimento e experiência no uso de bancos de dados geográficos. No entanto, independentemente do nível de operação do software, como desenvolvedor WebGIS/GIS, como podemos concluir as operações automáticas de análise e armazenamento de bancos de dados geográficos por meio da estrutura de código aberto GIS ? Esta é a questão central que discutiremos em profundidade a seguir.

Índice

geodatabase

O que é um geodatabase?

O papel dos geodatabases

Sistema básico de banco de dados geográfico ArcGIS

Esquema do banco de dados geográfico

Conceitos relacionados a geodatabases

 tipo de geodatabase

Estrutura GIS de código aberto back-end Java

GeoTools

recursos principais

Formatos de fonte de dados compatíveis

GeoTools Architecture (Arquitetura)

GeoTools Plugins (Plugins)

Extensões GeoTools

Projeto derivado do GeoTools

GDAL (Biblioteca de Abstração de Dados Geoespaciais)

GDAL: Construindo o ambiente de desenvolvimento do Windows

GDAL: analisa o GDB com base no FileGDB

GDAL analisa geodatabases de arquivo GDB

GeoTools: processamento automático de armazenamento da fonte de dados GDB


geodatabase

O que é um geodatabase?

        Primeiro introduza o que é um banco de dados geográfico.

        Para amigos que têm lutado no campo de desenvolvimento de back-end o ano todo, eles devem ter um conhecimento profundo de " banco de dados relacional ". Em termos leigos, é: um sistema de gerenciamento de arquivos baseado em computador que abstrai entidades no mundo objetivo e os expressa de maneira relacional.Ele é armazenado no computador na forma de dados para que os desenvolvedores de software acessem, atualizem e até mesmo analisem . Por exemplo, ao abstrair os grupos de funcionários da empresa, podemos obter uma tabela de informações de funcionários bem conhecida (tb_employee) , que armazena várias informações, como número, nome, horário de entrada e departamento de cada funcionário. Esses também são os dados de atributos sobre os quais a profissão GIS tem falado durante todo o ano .

Definimos " base de dados geográfica "         por analogia. Em termos leigos , é: um sistema de gerenciamento de arquivos baseado em computador que abstrai entidades geográficas no mundo objetivo e as armazena no computador na forma de uma tabela relacional, que é especialmente usada para GIS Os desenvolvedores executam operações como acesso, atualização e análise . Nota: Usamos o termo entidade geográfica aqui. Comumente, pontos POI, uma série de áreas de parcela, áreas de divisão administrativa, faixas de sistemas de água, etc., qualquer entidade objetiva que possa ser expressa pelos elementos geométricos básicos de pontos, linhas e superfícies , podemos chamá-la de entidade geográfica e armazená-la em um geodatabase.

O papel dos geodatabases

        Portanto, a questão é: qual é a importância dos bancos de dados geográficos no campo do desenvolvimento de GIS? Como entender o valor da aplicação do banco de dados geográfico na prática?

        Usamos a documentação oficial dos produtos ArcGIS como referência para explicar o significado multinível de bancos de dados geográficos em diferentes aspectos.

        ① Estrutura de dados : Como a estrutura de dados original do ArcGIS, o banco de dados geográfico é o principal formato de dados usado para edição e gerenciamento de dados e assume a função de armazenamento de dados geográficos;

        ②Armazenamento de dados : Como o suporte de armazenamento de dados geográficos, os bancos de dados geográficos ainda são implementados com base em sistemas de gerenciamento de banco de dados ou sistemas de arquivos. Portanto, no desenvolvimento real, veremos que vários bancos de dados como MySQL, Oracle, PostGreSQL etc., na forma de autocontidos ou na forma de plug-ins, forneceram sucessivamente armazenamento e consulta de dados geométricos, como como pontos, linhas e superfícies. , atualização e outros suportes de operações.

        ③Modelo de informação : O modelo de informação do banco de dados geográfico é muito abrangente, não se limitando a elementos geométricos como pontos, linhas e planos—comumente conhecidos como dados vetoriais; também inclui naturalmente dados de imagem—comumente conhecidos como dados raster (por exemplo: imagem de sensoriamento remoto, modelo de elevação digital DEM, etc.), para realizar a representação e gerenciamento de informações geográficas. Ao mesmo tempo, os bancos de dados geográficos também suportam a definição de relacionamentos espaciais e regras de integridade espacial (por exemplo: integridade estrutural topológica de figuras geométricas) para dados geográficos.

        ④ Versatilidade . Estritamente falando, o software geodatabase suporta dados geográficos em uma variedade de formatos, bem como operações de troca de dados entre diferentes formatos, incluindo: shapefiles, arquivos de desenho assistido por computador (CAD), redes irregulares trianguladas (TINs), grades, dados CAD, imagens , arquivos Geography Markup Language (GML) e uma série de outras fontes de dados GIS .

        ⑤ Aspecto do fluxo de trabalho (Workflow) . O Geodatabase não é apenas uma ferramenta para armazenamento, troca e integração de dados, mas também fornece um modelo de transação de fluxo de trabalho baseado em dados GIS, que pode realizar naturalmente a abstração, descrição geral e integração de regras de negócios entre alguns fluxos de trabalho e suas várias etapas de operação .processamento automatizado.

Sistema básico de banco de dados geográfico ArcGIS

Esquema do banco de dados geográfico

        Com relação aos bancos de dados relacionais, não há nada de especial na implementação de geodatabases, porque os geodatabases ainda são implementados com base na mesma arquitetura de aplicativo multicamada de outros aplicativos avançados de DBMS. Como mencionamos anteriormente, MySQL, Oracle, etc. fornecem funções de expansão espacial. Portanto, a partir deste nível, o MySQL também pode ser usado como um banco de dados geográfico - porque não apenas suporta o armazenamento de dados GIS, mas também fornece um módulo de função para cálculo e saída de resultados.

        Vejamos um exemplo de uso do banco de dados PostGreSQL como banco de dados geográfico. A seguir está a forma de armazenamento dos dados GIS na tabela de dados. A tabela a seguir armazena os dados da divisão administrativa de cada província da China (o valor da Geometria campo são os dados geométricos correspondentes),

Captura de tela parcial do PostGreSQL da tabela de dados

        Para ver mais claramente o efeito de exibição dos dados que armazenamos no nível geográfico, o seguinte usa o QGIS (um software de desktop GIS de código aberto) para se conectar ao banco de dados PostGreSQL e, em seguida, visualizar os dados na tabela de dados.

Visualização de dados geográficos em tabelas de dados

         A figura acima é um exemplo de armazenamento de dados geográficos baseado no banco de dados PostGreSQL.

        Quanto à forma como este mecanismo é implementado? Podemos transferir a implementação para o tipo de dados do PostGreSQL, consultando os documentos oficiais do site , veremos que o PostGreSQL suporta o campo de tipo geométrico Geometry, que é usado para armazenar dados GIS. Em outras palavras, desde que um campo seja definido como o tipo Geometry, o armazenamento de dados GIS pode ser realizado e um banco de dados PostGreSQL pode ser usado como um banco de dados geográfico . As tabelas usadas para armazenar dados geográficos são armazenadas como arquivos em disco ou em um banco de dados em um DBMS como Oracle, IBM DB2, PostgreSQL, IBM Informix ou Microsoft SQL Server . - É por isso que dissemos " não há nada de especial na implementação do geodatabase " na seção acima.

Captura de tela do documento do site oficial do PostGreSQL

Conceitos relacionados a geodatabases

        (1) Conjunto de dados : Geodatabase contém 3 tipos de conjuntos de dados: ① classe de recurso; ② conjunto de dados raster; ③ tabela. Normalmente, costumamos armazená-lo na forma de uma tabela com base nas três formas acima para realizar a construção do banco de dados geográfico.

        (2) Estratégia de geodatabase : Uma importante estratégia de geodatabase é fazer pleno uso do sistema de gerenciamento de banco de dados (DBMS) para expandir o conjunto de dados GIS e o número de usuários para uma escala muito grande (por exemplo, de um banco de dados simples e pequeno que pode suportam apenas um ou alguns usuários) Os bancos de dados são dimensionados para grandes bancos de dados que podem suportar milhões de recursos e milhares de usuários simultâneos). As tabelas são a principal forma de armazenamento para conjuntos de dados geográficos. O SQL é adequado para consultar e definir linhas em tabelas, portanto, uma estratégia de geodatabase é aquela que aproveita ao máximo esses recursos. Geodatabases suportam acesso SQL para feições geométricas nos seguintes DBMSs:

        (3) Elementos do banco de dados geográficos : não importa qual sistema GIS os usuários usem, eles usarão os três tipos básicos de conjuntos de dados a seguir.

superfície
Classe de recurso (dados geométricos são armazenados por meio do campo shpe)

Dados raster e referência espacial

 tipo de geodatabase

        Um geodatabase é um "contêiner" que contém uma coleção de conjuntos de dados. O ArcGIS o divide em três tipos: banco de dados geográfico ArcSDE, banco de dados geográfico de arquivo e banco de dados geográfico pessoal.

        em:

                ① Banco de dados geográfico de arquivos (gdb) : armazenado na forma de pastas no sistema de arquivos. Cada conjunto de dados é salvo na forma de um arquivo e um único arquivo pode ser expandido até 1 TB.

                ②Banco de dados geográfico pessoal (mdb) : Todos os conjuntos de dados são armazenados em arquivos de dados do Microsoft Access, cujo tamanho máximo é 2G.

                ③ Banco de dados geográfico ArcSDE : também conhecido como banco de dados geográfico multiusuário, esse tipo de banco de dados é armazenado em um banco de dados relacional usando Oracle, Microsoft SQL Server, IBM DB2, IBM Informix ou PostgreSQL. Esses dados geográficos requerem o ArcSDE e são ilimitados em tamanho e número de usuários.

        A seguir está o resultado da comparação fornecido pelo documento oficial do ArcGIS,

Comparação de três tipos de geodatabases

        PS: Com base nos resultados da comparação acima, em aplicações práticas, é recomendável usar geodatabases de arquivo em vez de geodatabases pessoais.

Arquivo Geodatabase - geralmente com o sufixo GDB

Estrutura GIS de código aberto back-end Java

        Com base no conteúdo acima, discutimos ainda como analisar o geodatabase do arquivo GDB com base na estrutura GIS de código aberto.

        A seguir apresentamos as estruturas GIS de software livre com as quais a maioria das pessoas está familiarizada: geotools, gdal e ambas fornecem suporte para a linguagem de desenvolvimento Java.

GeoTools

Captura de tela da página inicial do site oficial do geotools

         A página do site oficial do geotools pode ser visualizada aqui .

         Geotools é uma biblioteca de desenvolvimento Java compatível com OGC de software livre para processamento de dados espaciais (dados geoespaciais) . Sua estrutura básica é a seguinte,

         Com base na estrutura Geotools, pode ser realizado o desenvolvimento de serviços de mapas da web (serviços da web), ferramentas de linha de comando (ferramentas de linha de comando) e aplicativos de desktop (aplicativos de desktop).

recursos principais

        (1) Definir o conceito espacial e a interface da estrutura de dados : principalmente com base no Java Topology Suite (JTS), a definição da classe de entidade de dados geométricos Goemetry é realizada e o suporte de filtragem para dados de atributos e dados espaciais é realizado.

        (2) Fornece uma interface de acesso a dados : suporta aquisição de recursos, suporte a transações e operações de bloqueio de thread.

                Os dados podem ser obtidos a partir de uma variedade de arquivos e bancos de dados espaciais; suporte ao sistema de coordenadas geográficas; operações de projeção de mapas; operações de filtragem e análise com base em dados de atributos e dados espaciais.

        (3) O suporte do renderizador é fornecido : um renderizador sem estado e com baixo consumo de memória adequado para ambientes do lado do servidor, que pode realizar exibição de mapa de estilo complexo, rótulo de texto e controle de renderização de cores.

        (4) plug-in GeoTools

        (5) Extensão GeoTools : fornece gráficos e suporte de rede (para encontrar o caminho mais curto), verificação de topologia, cliente de servidor de mapa da web, análise xml e ligação de codificação e definição/renderização de cores.

Formatos de fonte de dados compatíveis

(1) Formato raster

 (2) Suporte a banco de dados jdbc

 (3) Formato vetorial

 (4) arquivo XML

GeoTools Architecture (Arquitetura)

        É necessário entender algumas estruturas básicas da GeoTools Library, que nos ajudarão a encontrar rapidamente pacotes jar adequados para cenários de negócios durante o processo de desenvolvimento.

Diagrama de infraestrutura do GeoTools

         PS: No desenvolvimento real, a ferramenta Maven pode nos ajudar a analisar os pacotes jar que precisam ser importados.

        Com base no diagrama de arquitetura acima, o GeoTools pode ser dividido nos seguintes módulos principais, e as funções de cada módulo são as seguintes,

Análise do módulo GeoTools

GeoTools Plugins (Plugins)

        Com base nos módulos principais acima, a GeoTools lançou vários plug-ins para atender aos requisitos reais da aplicação.

plug-ins geoferramentas

Extensões GeoTools

        Com base na arquitetura de nível superior, o GeoTools também fornece um conjunto de módulos de extensão, que podem realizar funções como desenho, verificação de topologia de dados espaciais e serviços de mapas.

         Obviamente, o GeoTools também fornece suporte para documentos XML, os detalhes podem ser vistos aqui .

Projeto derivado do GeoTools

        A seguir está uma lista de projetos derivados do GeoTools, ou alguns produtos desenvolvidos com base no GeoTools.

         Para outros conteúdos, consulte a documentação do site oficial do GeoTools . Ou alguns artigos serão publicados no futuro para discussões detalhadas.

GDAL (Biblioteca de Abstração de Dados Geoespaciais)

GDAL é uma biblioteca de transformação de dados espaciais raster de código aberto         sob o contrato de licença X/MIT . Ele utiliza um modelo de dados abstrato para expressar os vários formatos de arquivo suportados. Ele também possui uma coleção de ferramentas de linha de comando para transformação e manipulação de dados . GDAL também é uma estrutura GIS de software livre bastante complexa que fornece suporte para a linguagem de desenvolvimento Java.

Suporte API GDAL

        A razão pela qual o GDAL é introduzido é porque na introdução anterior ao framework GeoTools, não o vimos e, de fato, ele não suporta a operação de análise para o banco de dados geográfico de arquivo ArcGIS (GDB). E o GDAL é suportado.Através de seu módulo de driver FileGDB, operações como análise de fonte de dados e aquisição de informações de tabela/elemento do geodatabase de arquivo GDB podem ser realizadas.

GDAL-Arquivo GDB

         Como o GDAL é um projeto de código aberto muito grande, a seguir apresentamos apenas a parte interessante do driver FileGDB e a construção do ambiente do GDAL.

GDAL: Construindo o ambiente de desenvolvimento do Windows

(1) Baixe o pacote de instalação.

         O pacote de instalação pode ser baixado do site oficial do GDAL , e a versão atual é release-1900-x64-2.4.4-mapserver-7.4.3 . Você também pode baixar outros pacotes de instalação que correspondam ao ambiente JDK local (o pacote zip é suficiente).

(2) Descompacte o pacote zip

        Após a conclusão do download, descompacte o pacote zip localmente (o diretório de descompactação é mostrado na figura abaixo),

Entre eles: gdal.jar         no diretório bin/gdal/java é um pacote de dependência para ser usado no desenvolvimento.

 (3) Configurar variáveis ​​de ambiente

           Em seguida, configure as variáveis ​​de ambiente. Configure o caminho da subpasta no diretório bin para a variável de ambiente do sistema,

 (4) Teste o ambiente GDAL

        Use o seguinte comando para visualizar as informações de versão do GDAL e testar se o GDAL foi instalado com êxito.

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

        Se forem exibidas informações semelhantes às seguintes, a configuração foi bem-sucedida. 

GDAL: analisa o GDB com base no FileGDB

        Para usar a versão Java do GDAL para analisar o GDB, você precisa mesclar a biblioteca de vínculo dinâmico (arquivo dll) no diretório de instalação do GDAL no diretório bin no diretório de instalação do jdk e, em seguida, importar o arquivo gdal.jar para o projeto normalmente .

Configuração da biblioteca de vínculo dinâmico

        Copie todos os arquivos dll no caminho bin do diretório de instalação gdal para o diretório bin do jdk para configurar a biblioteca de vínculo dinâmico.

 O projeto de back-end Java apresenta o gdal.jar

        No projeto back-end construído com base no Maven, para usar o GDAL, basta importar o arquivo gdal.jar . Existem duas maneiras de importar,

(1) Apresentado como um arquivo ,

        Crie uma pasta lib no diretório raiz do projeto e coloque o arquivo gdal.jar no diretório de instalação do gdal,

        Por exemplo: Um projeto chamado gdalApps é criado aqui, ou seja, copie o arquivo gdal.jar para o caminho gdalApps/lib ,

        Então você precisa configurar o nó de dependências do arquivo pom.xml, o conteúdo da configuração é o seguinte,

<!--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) Introduzido na forma de configuração do arquivo pom.xml ,

        Você também pode importar a dependência de desenvolvimento gdal.jar configurando diretamente o arquivo pom.xml, mas observe: é altamente recomendável que a versão do arquivo gdal.jar introduzida dessa maneira seja consistente com a versão do GDAL configurado localmente ambiente.

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

        Quando gdal.jar é exibido nas dependências do Maven, a importação é bem-sucedida.

GDAL analisa geodatabases de arquivo GDB

        GDAL analisa o geodatabase do arquivo GDB e depende do driver FileGDB/OpenFileGDB. Portanto, a ideia básica de usar o GDAL para analisar o GDB geralmente é a seguinte,

        ①Chame o método ogr.GetDriverByName ( GDBDriver ) para obter o driver de instância do driver FileGDB/OpenFileGDB e use o método driver.Open(filePath) para obter uma fonte de dados GDB para obter uma instância DataSource ;

②Use a instância DataSource         obtida em ① , chame seu método getLayer(layerIndex) para obter cada objeto Layer de camada contido na fonte de dados GDB e, em seguida, use o método GetLayerDefn() do objeto para obter as metainformações da camada, como : Referência espacial/informações do sistema de coordenadas, informações do campo da tabela de atributos, objeto do cursor para obter a instância da classe de feição dentro da camada, etc. (para obter detalhes, consulte o código de amostra).

③Chame o método GetNextFeature()         através do objeto de camada Layer para analisar as informações do elemento Feature na camada, uma a uma .

        PS: Quando a versão Java do GDAL analisa o geodatabase do arquivo GDB, as classes e suas estruturas hierárquicas usadas são as seguintes,

        O código de exemplo é o seguinte,

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: processamento automático de armazenamento da fonte de dados GDB

        Para carregar automaticamente a fonte de dados GDB na biblioteca, você precisa do suporte do plug-in GeoTools-PostGIS e pode introduzir as dependências correspondentes no pom. Para informações de referência espacial, informações de codificação EPSG são necessárias; para filtragem de conjunto de resultados através do plug-in PostGIS, o módulo CQL é necessário.

        <!--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>

        O código de amostra completo do processamento de armazenamento automático da fonte de dados GDB é o seguinte,

        PS: O seguinte é serializar a fonte de dados GDB em um único arquivo pequeno e armazená-lo localmente, desserializá-lo ao armazená-lo e, em seguida, executar a operação de armazenamento.

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 + "]表中");
            }
        }
    }


}

Acho que você gosta

Origin blog.csdn.net/weixin_43524214/article/details/129000479
Recomendado
Clasificación