Pular para conteúdo

PGST-LIB

Introdução

A biblioteca PGST-LIB contém uma série de funcionalidades implementadas em Python que são comuns aos módulos PGST-PORTAL, PGST-GATEWAY e PGST-CONSUMER. A ideia dessa biblioteca é permitir a reutilização de código nos diversos módulos do GeoProcess.

A seguir será descrita uma visão geral da biblioteca PGST-LIB.

Visão Geral

A estrutura com as pastas e arquivos principais do projeto PGST-LIB está destacada a seguir.

Estrutura de Pastas PGST Lib

Na raiz da biblioteca PGST-LIB, há um módulo Python chamado main.py, utilizado para realizar testes de integração e testes de carga.

Na raiz do projeto tem também uma pasta chamada src em que ficam localizados os módulos do Python. Dentro desta pasta é importante destacar as subpastas baseconsumer e util.

Na pasta baseconsumer existem dois importantes módulos Python que são: worker.py e controller.py.

Na pasta util, encontram-se importantes módulos Python, como answer.py e contract.py.

Na pasta util/postgis existem alguns importantes módulos Python que são: models.py e postgis.py.

Por fim, há outras pastas, como integration, load e testserver, que contêm módulos Python de menor importância.

A seguir, detalharemos alguns dos principais módulos desta biblioteca.

Módulo baseconsumer/worker.py

A classe Worker definida em baseconsumer/worker.py é uma classe abstrata que será herdada nos consumidores. Essa classe contém os elementos comuns a todos os consumidores. Analise o diagrama de classes a seguir.

classDiagram
  ABC <|-- Worker : Herança
  class Worker {
    +Logger : logger
    +boolean : testMode
    +PostGis : postGis
    +dict : questionsInfo
    +Answer : answer
    +str : dbName
    +str : dbUser
    +str : dbPassword
    +str : dbHost
    +str : dbPort
    +str : gsUser
    +str : gsPassword
    +str : gsHost
    +Worker()
    +getVersion()*
    +getName()*
    +startDB()
    +confGeoServer()
    +getPostGis()
    +createAnswer()
    +setQuestions(data)
    +parametersToView(questionId, data)
    +process(contract)
    +test()
  }

A classe Worker é responsável por iniciar a conexão com o banco de dados PostGis e com o Geoserver, caso o consumidor utilize esses recursos. Essa classe pode criar objetos do tipo Answer, pode definir Questões e realizar o processamento do consumidor. Essa classe possui dois métodos abstratos (getVersion e getName) que devem ser implementados no consumidor que a herdar. Essa classe possui um construtor sem parâmetros.

Módulo baseconsumer/controller.py

A classe Controller definida em baseconsumer/controller.py é uma classe concreta responsável pela inicialização e execução de um consumidor. O diagrama de classes da Controller é destacado a seguir com os principais atributos e métodos. Analise o diagrama de classes a seguir.

classDiagram
  class Controller {
    +Logger : logger
    +Worker : worker
    +BlockingConnection : connection
    +BlockingConnection : channel
    +str : consumerCode 
    +str : mainQueue
    +str: queueOut
    +str: queueIn
    +str: questionsData
    +dict: questionsInfo
    +str : mode
    +str: rabbit_host
    +int: rabbit_port
    +str: rabbit_user
    +str: rabbit_password
    +bool : docker
    +bool : ready
    +Controller(worker)
    +startConsuming()
    +sendQuestions(data)
    +sendMessage(message)
    +sendAnswer(data, channel, method)
    +answerRequest(data, channel, method)
    +verifyQuestionVersion(questionId, version)
    +verifyVersion(data, channel, method)
    +callback(channel, method, properties, body)
    +start()
  }

A classe Controller é responsável por fazer a comunicação entre o Consumidor e o Gateway. Essa comunicação se dá através da troca de mensagens, perguntas, respostas. Essa classe possui um construtor que recebe um objeto do tipo Worker como parâmetros.

Módulo util/answer.py

O módulo util/answer.py contém os as seguintes classes definidas internamente: Color, ChartLayout, ChartType, ChartLib, FileType, MessageType, Message e Answer. A seguir é apresentado um breve diagrama de classe de cada uma dessas classes.

classDiagram
  class Color {
    +str : RED
    +str : GREEN
    +str : BLUE
    +str : ORANGE
    +str : PURPLE
    +str : YELLOW
    +str : BROWN
  }
  class ChartLayout {
    +str : FULL
    +str : DEFAULT
  }
  class ChartType {
    +str : BAR
    +str : LINE
    +str : PIE
    +str : POLAR_AREA
  }
  class ChartLib {
    +str : CHART_JS
    +str : PLOTLY
  }
classDiagram
  class FileType{
    +str : PDF
    +str : KML
    +str : EXCEEDED_SIZE
  }
  class MessageType {
    +str : SUCCESS
    +str : INFO
    +str : WARNING
    +str : DANGER
  }
  class Message {
    +str : code
    +str : title
    +str : text
    +str : type
    +Message(code, title, text, type)
  }
classDiagram
  class Answer {
    +Logger : logger
    +dict : data
    +str : gsHost
    +str : gsUser
    +str : gsPassword

    +Answer()
    +addMapLayer(title, layerLabel, geoJson, geoWkb, group, color, colorPin, tip, pinType)
    +addMapLines(title, layerLabel, lines, color, arrow_color, group)
    +addMapCircles(title, layerLabel, circles, color, group)
    +addTable(title, description, head, data, identifier)
    +addChart(title, label, labels, data, type, layout, color)
    +addChartPlotly(title, data, identifier)
    +addChartLineDataSet(title, titles, labels, values, colors, layout)
    +addWMSLayer(worker, workspace, id, title, file, style)
    +setGeoServerData(gsUser, gsPassword, gsHost)
    +isError()
    +getUserInfo()
    +addMessage(message)
    +addSysMessage(code, title, text, type)
    +addTemplate(data)
    +addFile(description, file, type)
    +setManualFit(area)
    +setAlert(message, triggered, date, level)
    +tipToText(tip)
  }

As classes Color, ChartLayout, ChartType, ChartLib, FileType e MessageType contém apenas constantes definidas para simplificar a utilização no código. A classe Message contém basicamente uma estrutura de dados dos elementos constituintes de uma mensagem. A classe Message contém um construtor com quatro parâmetros para definir o mensagem. Por fim, temos a classe Answer que é a mais importante desse módulo e contém os principais métodos que podem ser chamados para formar uma resposta para uma pergunta. Os consumidores implementados devem criar objetos dessa classe para modelar a resposta a uma determinada pergunta.

A classe Answer herda a classe Contract. O diagrama de classes a seguir destaca a relação de herança e os métodos das classes Contract, Answer e AnswerRequest.

classDiagram
  Contract <|-- Answer : Herança
  Contract <|-- AnswerRequest : Herança
  class Contract {
    +dict : data
    +str : consumerCode
    +Contract()
    +loadJson(data)
    +getType()
    +setType(type)
    +loadContent(data)
    +hasPropertie(propertie)
    +getPropertie(propertie)
    +setPropertie(propertie, value)
    +getConfPropertie(propertie)
    +getCurrentChainConf()
    +addNext(consumerCode, conf)
    +getNextConsumer()
    +cleanNext()
    +setConf(conf)
    +toJson()
    +contentToJson()
    +setCurrentDate()
  }
  class Answer {
    +Answer()
  }
  class AnswerRequest {
    +AnswerRequest()
  }

A classe Answer será melhor detalhada dos módulos consumidores.

Módulo util/postgis/models.py

O diagrama de classe a seguir contém a estrutura de duas classes que foram modeladas no módulo models.py definido na subpasta util/postgis.

classDiagram
  class Property {
    +int : id
    +str : geometry
    +dict : geojson
    +Property(id, geometry, geojson)
  }
  class City {
    +int : id
    +str : geometry
    +dict : geojson
    +City(id, geometry, geojson)
  }

Repare que no módulo models.py existem duas classes. Uma chamada Property (Propriedade) e a outra City (Cidade), ambas com atributos de mesmo nome e mesmo tipo. A classe City será utilizada para definirmos a entidade de uma cidade. A classe Property será utilizada para definirmos a entidade de uma propriedade. Cada um dessas classe possui um construtor com os parâmetros id, geometry e geojson para organizar a estrutura de dados.

O atributo geometry é do tipo string, mas os dados representam de fato um código em hexadecimal com as coordenadas da geometria da propriedade ou da cidade. Veja um exemplo de como é a informação do atributo geometry:

0106000020421201010300100505C789C3080484AC0BE07339FBF323BC04F2C22 ... FBF323BC0

O atributo geojson é do tipo dicionário e possui o seguinte formato:

{
  'type': 'MultiPolygon', 
  'crs': {
    'type': 'name', 
    'properties': {
      'name': 'EPSG:4674'
    }
  }, 
  'coordinates': 
    [[[[-52.566412045, -27.198236418], 
       [-52.565110699, -27.199583449], 
       [-52.574959585, -27.207013026], 
       [-52.576173296, -27.205790115], 
       [-52.566412045, -27.198236418]]]]
}

No dicionário exemplo acima, temos as seguintes chaves: type, crs e coordinates. Em que, type indica o tipo do geojson que nesse caso é MultiPolygon. Em que, CRS indica o sistema de referência de coordenadas (coordinate reference system) note que nesse exemplo estamos utilizando EPSG 4674 (SIRGAS 2000). Por fim, coordinates destaca as coordenadas geográficas da propriedade ou da cidade.

Módulo util/postgis/postgis.py

O diagrama de classe a seguir contém a classe PostGis que foi modelada no módulo postgis.py definido na subpasta util/postgis.

classDiagram
  class PostGis {
    +Logger : logger
    +str : dbName
    +str : dbUser
    +str : dbPassword
    +str : dbHost
    +str : dbPort
    +PostGis(dbName, dbUser, dbPassword, dbHost, dbPort)
    +connected()
    +connect()
    +close()
    +createMultiPolygon(region)
    +crossLayerGeom(geom, layer_table, proportion, columns)
    +crossLayerMultipolygon(multipolygon, layer_table, proportion, columns, conditions)
    +getPropertyFromCode(code)
    +getCityFromCode(code)
    +executeSQL(sql, params, fechall)
    +verifyMaxArea(polygon, max_area_search)
  }

A ideia dessa classe é fornecer um conjunto de métodos comuns sobre dados geoespaciais. Essa classe pode abrir ou fechar a comunicação com os dados do Banco de Dados PostGis. Assim, o construtor dessa classe contém todas as informações para o acesso a esse banco de dados.

O método createMultiPolygon recebe como parâmetro uma região ou área selecionada em um mapa. A região selecionada é do tipo dicionário e possui a seguinte estrutura geral:

{
  'type': 'FeatureCollection', 
  'features': [
    {
      'type': 'Feature', 
      'properties': {}, 
      'geometry': {
        'coordinates': [
          [[-52.79853429987142, -26.668723440791226], 
           [-52.79853429987142, -27.722008785135223], 
           [-50.72010819942622, -27.722008785135223], 
           [-50.72010819942622, -26.668723440791226], 
           [-52.79853429987142, -26.668723440791226]]
        ], 
        'type': 'Polygon'
      }
    }
  ]
}

A seguir temos um exemplo de utilização do método createMultiPolygon.

def createMultiPolygon(self, region):
region = answer_request.getPropertie("map")
multipolygon = self.getPostGis().createMultiPolygon(region)

A variável answer_request é um parâmetro do método processN do consumidor implementado e esse trecho de código foi suprimido para melhor entendimento. O método getPropertie("map") retorna uma região baseado na propriedade mapa que está definido em algum arquivo json, oculto para melhor entendimento. O método getPostGis está definido na classe Worker e retorna uma instância para a classe PostGis. A variável de saída multipolygon é um dicionário que possui a seguinte estrutura geral.

{
  'type': 'MultiPolygon', 
  'coordinates': [
    [[[-52.79853429987142, -26.668723440791226], 
     [-52.79853429987142, -27.722008785135223], 
     [-50.72010819942622, -27.722008785135223], 
     [-50.72010819942622, -26.668723440791226], 
     [-52.79853429987142, -26.668723440791226]]]
  ], 
  'crs': {
    'type': 'name', 
    'properties': {
      'name': 'EPSG:4674'
    }
  }
}

O objetivo do método createMultiPolygon é definir uma estrutura do tipo MultiPolygon que possa ser utilizada como entrada para outros métodos.

O método crossLayerMultipolygon recebe como parâmetro de entrada uma região do tipo multipolygon, o nome de uma tabela que deve estar armazenada no BD Postgres e dois parâmetros opcionais que são: tem proporção do tipo booleano por padrão seu valor é False e colunas selecionadas do tipo lista por padrão seu valor é None.

def crossLayerMultipolygon(self, multipolygon, layer_table, proportion=False, columns=None, conditions=None):
area = answer_request.getPropertie("map")
multipolygon = self.getPostGis().createMultiPolygon(area)
layer = self.getPostGis().crossLayerMultipolygon(multipolygon, "area_imovel")
area = answer_request.getPropertie("map")
multipolygon = self.getPostGis().createMultiPolygon(area)
layer, columns_data = self.getPostGis().crossLayerMultipolygon(multipolygon, 
              "area_imovel", proportion=True, columns=["gid", "cod_imovel"])

O objetivo do método crossLayerMultipolygon é retornar todas as geometrias da tabela (camada) passada como argumento que intersectam (cruzam) com o multipolygon passado como argumento. No exemplo acima, entitulado "Exemplo: sem proporção e sem colunas", é retornado todas as geometrias da tabela áreas de imóvel (area_imovel) que intersectam com a área especificada (definida na variável multipolygon). No exemplo acima, entitulado "Exemplo: com proporção e com colunas", é retornado todas as geometrias e também as colunas gid, cod_imovel e proportion da tabela áreas de imóvel (area_imovel) que intersectam com a área especificada (definida na variável multipolygon). As colunas gid e cod_imovel são selecionadas, pois foi solicitado via parâmetro columns. A coluna proportion é retornada, pois foi solicitada via parâmetro proportion igual a True.

O método getPropertyFromCode recebe como parâmetro um código de uma propriedade e retorna um objeto do tipo Property correspondente aquele código. Caso não encontre a propriedade no banco de dados o valor None é retornado.

Atenção

Esta propriedade é buscada na Tabela area_imovel. Dessa maneira, a Tabela area_imovel deve estar disponível no BD Postgres.

Exemplo de utilização:

def getPropertyFromCode(self, code):
property = self.getPostGis().getPropertyFromCode("SC-4204202-F13E8669...D7")

O método getCityFromCode recebe como parâmetro um código de uma cidade e retorna um objeto do tipo City correspondente aquele código. Caso não encontre a cidade no banco de dados o valor None é retornado.

Atenção

Esta propriedade é buscada na Tabela br_municipios_2022. Dessa maneira, a Tabela br_municipios_2022 deve estar disponível no BD Postgres.

Exemplo de utilização:

def getCityFromCode(self, code):
city = self.getPostGis().getCityFromCode("3138203")

O método crossLayerGeom recebe como parâmetro de entrada uma geometria, o nome de uma tabela que deve estar armazenada no BD Postgres e dois parâmetros opcionais que são: tem proporção do tipo booleano por padrão seu valor é False e colunas selecionadas do tipo lista por padrão seu valor é None.

def crossLayerGeom(self, geom, layer_table, proportion=False, columns=None):
city = self.getPostGis().getCityFromCode("3138203")
geom = city.geometry
layer_table = "area_imovel"
layer = self.getPostGis().crossLayerGeom(geom, layer_table)
cod_movel = answer_request.getPropertie("cod_imovel")
property = self.getPostGis().getPropertyFromCode(cod_movel)
geom = property.geometry
layer_table = "terras_indigenas"
col = ["uf_sigla", "municipio_", "etnia_nome"]
layer, col_data = self.getPostGis().crossLayerGeom(geom, layer_table, proportion=True, columns=col)

O objetivo do método crossLayerGeom é retornar todas as geometrias das camadas (layer) da tabela (layer_table) passada como argumento que intersectam (cruzam) com a geometria (geom) passada como argumento. No exemplo acima, entitulado "Exemplo: sem proporção e sem colunas", é retornado todas as geometrias da tabela "area_imovel" que intersectam com a geometria da cidade passada (código 3138203 que representa a cidade de Lavras-MG). No exemplo acima, entitulado "Exemplo: com proporção e com colunas", é retornado todas as geometrias e também as colunas uf_sigla, municipio_ e etnia_nome da tabela "terras_indigenas" que intersectam com a propriedade recebida como parâmetro (definida na property). As colunas uf_sigla, municipio_ e etnia_nome são selecionadas, pois foi solicitado via parâmetro columns. A coluna proportion é retornada, pois foi solicitada via parâmetro proportion igual a True.

O método executeSQL é um método mais genérico e recebe como parâmetros uma linha de código SQL, os parâmetros dessa SQL e um parâmetro opcional indicando se irá retornar todos os resultado da busca ou apenas um. Exemplo de utilização:

def executeSQL(self, sql, params, fechall=True):
codImovel = "SC-4204202-F13E8669...D7"
sql = "SELECT gid, ST_AsGeoJSON(geom) FROM area_imovel WHERE cod_imovel = %s"
params = (codImovel,)
data = self.getPostGis().executeSQL(sql, params, fechall=True)
codImovel = "SC-4204202-F13E8669...D7"
sql = "SELECT gid, ST_AsGeoJSON(geom) FROM area_imovel WHERE cod_imovel = %s"
params = (codImovel,)
data = self.getPostGis().executeSQL(sql, params, fechall=False)

A função ST_AsGeoJSON das querys acima retornam uma geometria GeoJSON. A query SQL acima retorna o código gid e a geometria da tabela area_imovel onde o código do imóvel é igual ao código passado. A primeira query retorna todos os resultados e a segunda query retorna apenas o primeiro resultado.

Para mais informações sobre a classe PostGis veja sua utilização nos módulos consumidores.

Módulo util/tests.py

O módulo util/tests.py possui duas funções úteis que são getConfPropertie(propertie) e loadDataForm(). A função getConfPropertie obtém o valor da propriedade do arquivo env.conf passada como argumento. Analise o exemplo mostrado a seguir em que o valor do nome do BD é armazenado na variável db_name.

def getConfPropertie(propertie)
db_name = getConfPropertie("DB_NAME")

Instalação

Pré-Requisitos

Instalações: São necessárias as seguintes instalações.

  • Python
  • Pip
  • Git

Clonando a Aplicação

Primeiramente, clone o projeto pgst-lib utilizando o comando:

git clone https://github.com/ZETTA-ORG/pgst-lib.git

Observação

Esse projeto deve ser clonado dentro de uma pasta geoprocess, conforme a organização do projeto.

Configurando a Aplicação

Em seguida, copie o arquivo de configuração do ambiente utilizando o comando:

cp env_sample.conf env.conf
copy env_sample.conf env.conf

Em seguida, abra o arquivo env.conf e faça as atualizações necessárias conforme a configuração do seu ambiente de trabalho.

Instale o ambiente virtual venv para isolar as dependências do Python. Utilize o comando abaixo:

python3 -m venv venv
py -m venv venv

Ative o ambiente virtual no seu computador utilizando o comando abaixo:

source venv/bin/activate
venv\Scripts\activate

Instale as dependências do projeto utilizando o comando abaixo:

pip3 install -r requirements.txt

Github do Projeto

A seguir temos o link para o github do projeto.

PGST-LIB