ArcObjects e Class Extensions #2
Boa tarde pessoal,
Lembram do artigo #1 sobre as class extensions? Falei um pouco sobre como as CEs funcionam? Dê lida aqui antes de ler este aqui.
Bem, as class extensions são um pouco difíceis de se manter, pois elas geram muito código, são controladas de forma esotérica (através de IPropertySet’s) e podem controlar alguns eventos importantes, como OnChange, OnCreate e OnDelete bem como as validações de uma feição.
Portanto, qual é uma maneira um pouco mais sã de construí-las? Uma primeira tentativa que fiz, foi utilizar herança para construí-las. Uma classe base com algumas funcionalidades era herdada por classes mais “abaixo” na cadeia e ia herdando funcionalidade. Mas como dizem por aí, composição é melhor que herança para extensão de função.
Na figura acima, percebemos que toda a funcionalidade é herdada através de herança. Todas as operações em OldBaseClassExtension (a mãe de todas) são marcadas como virtual, dando a possibilidade de sobreescrevemos o método pela classe filha.
Em código temos algo assim:
public interface IOldClassExtension : IClassExtension, IObjectClassExtension, IObjectClassValidation, IObjectClassEvents
{
// aqui dizemos que todas as classes derivadas de IOldClassExtension
// devem implementar os métodos das interfaces correspondentes.
}
public class OldBaseClassExtension:IOldClassExtension
{
public virtual void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
throw new NotImplementedException();
}
public virtual void Shutdown()
{
throw new NotImplementedException();
}
public virtual string ValidateField(IRow Row, string FieldName)
{
throw new NotImplementedException();
}
public virtual string ValidateRow(IRow Row)
{
throw new NotImplementedException();
}
public virtual void OnChange(IObject obj)
{
throw new NotImplementedException();
}
public virtual void OnCreate(IObject obj)
{
throw new NotImplementedException();
}
public virtual void OnDelete(IObject obj)
{
throw new NotImplementedException();
}
}
public class OldConcreteClassExtensionA : OldBaseClassExtension
{
public override void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
base.Init(ClassHelper, ExtensionProperties);
// nova funcionalidade customizada
}
public override void Shutdown()
{
base.Shutdown();
// nova funcionalidade customizada
}
public override string ValidateField(IRow Row, string FieldName)
{
base.ValidateField(Row, FieldName);
// nova funcionalidade customizada
return "resultado de nova validação de campo";
}
public override string ValidateRow(IRow Row)
{
base.ValidateRow(Row);
// nova funcionalidade customizada
return "resultado de nova validação de linha";
}
public override void OnChange(IObject obj)
{
base.OnChange(obj);
// nova funcionalidade customizada
}
public override void OnCreate(IObject obj)
{
base.OnCreate(obj);
// nova funcionalidade customizada
}
public override void OnDelete(IObject obj)
{
base.OnDelete(obj);
// nova funcionalidade customizada
}
}
public class OldConcreteClassExtensionB : OldBaseClassExtension
{
public override void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
base.Init(ClassHelper, ExtensionProperties);
// nova funcionalidade customizada
}
public override void Shutdown()
{
base.Shutdown();
// nova funcionalidade customizada
}
public override string ValidateField(IRow Row, string FieldName)
{
base.ValidateField(Row, FieldName);
// nova funcionalidade customizada
return "resultado de nova validação de campo";
}
public override string ValidateRow(IRow Row)
{
base.ValidateRow(Row);
// nova funcionalidade customizada
return "resultado de nova validação de linha";
}
public override void OnChange(IObject obj)
{
base.OnChange(obj);
// nova funcionalidade customizada
}
public override void OnCreate(IObject obj)
{
base.OnCreate(obj);
// nova funcionalidade customizada
}
public override void OnDelete(IObject obj)
{
base.OnDelete(obj);
// nova funcionalidade customizada
}
}
public class OldConcreteClassExtensionC : OldConcreteClassExtensionA
{
public override void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
base.Init(ClassHelper, ExtensionProperties);
// nova funcionalidade customizada
}
public override void Shutdown()
{
base.Shutdown();
// nova funcionalidade customizada
}
public override string ValidateField(IRow Row, string FieldName)
{
base.ValidateField(Row, FieldName);
// nova funcionalidade customizada
return "resultado de nova validação de campo";
}
public override string ValidateRow(IRow Row)
{
base.ValidateRow(Row);
// nova funcionalidade customizada
return "resultado de nova validação de linha";
}
public override void OnChange(IObject obj)
{
base.OnChange(obj);
// nova funcionalidade customizada
}
public override void OnCreate(IObject obj)
{
base.OnCreate(obj);
// nova funcionalidade customizada
}
public override void OnDelete(IObject obj)
{
base.OnDelete(obj);
// nova funcionalidade customizada
}
}
Perceba como determinamos a funcionalidade nas classes filhas: além de herdarmos e sobreescrevermos o método, chamamos também o método base, através de base.OnChange(obj), por exemplo.
Ok, funciona, mas quando temos algumas Class Extensions e uma hierarquia com dois ou três níveis já se torna impossível de manter e debugar o código. Demora-se muito para atingir um resultado razoável.
Como faríamos se usássemos composição? Bem, vamos imaginar o seguinte: temos algumas tabelas que descrevem as class extensions de acordo com sua funcionalidade básica (nem todas as CEs precisam implementar IObjectClassValidation, caso não às usem) e uma forma de ler e instanciar estas CEs de acordo com esta tabela. O diagrama de classes seria basicamente este aqui:
Desta maneira temos um código muito mais simples e nenhuma dependência entre cada ClassExtension. Todas as CEs implementariam ICustomClassExtension, pois esta dá visibilidade para todos os eventos / validações (poderíiamos até ter implementado IObjectInspector para dar formas diferentes de edição à cada CE) que precisamos e o sistema faria o restante, consultado o banco de dados (poderia esta dentro de um próprio FileGeodatabase/ArcSDE) e descobrindo à ordem e quais CEs devem ser instanciadas.
O esquema em SQL ficaria parecido com:
CREATE TABLE ENTIDADES ( -- conterá uma lista de feature classes ou de tabelas do ArcGIS nome_entidade varchar(128) NOT NULL, -- Lote nome_feature_dataset varchar(128) NOT NULL DEFAULT '', -- nome do feature dataset, caso exista nome_feature_class varchar(128) NOT NULL, -- nome da feature class, ex: FLTE_LOTES CONSTRAINT entidades_pk PRIMARY KEY (nome_entidade) ) CREATE TABLE CLASS_EXTENSIONS ( -- conterá uma lista das class extensions disponíveis em determinada assembly. nome_class_extension varchar(128) NOT NULL, -- Validar Geometria de Lote descricao_namespace varchar(128) NOT NULL, -- ValidateParcelGeometryExtensions CONSTRAINT class_extensions_pk PRIMARY KEY (nome_class_extension), CONSTRAINT descricao_namespace_un UNIQUE (descricao_namespace) ) -- tabela de relacionamento entre entidades e class extensions -- é um relacionamento M:N CREATE TABLE ENTIDADE_POSSUI_CLASS_EXTENSION ( nome_entidade varchar(128) NOT NULL REFERENCES ENTIDADES on (nome_entidade), nome_class_extension varchar(128) NOT NULL REFERENCES CLASS_EXTENSIONS on (nome_class_extension), ordem_criacao integer NOT NULL DEFAULT 0, CONSTRAINT epc_pk PRIMARY KEY (nome_entidade,nome_class_extension,ordem_criacao) )
Através de um pouco de código C#, podemos descobrir se a classe implementada está disponível, instanciá-la dinamicamente e adicioná-la à uma lista de ClassExtensions (propriedade extensions em ICustomClassExtension). Aqui segue um pouco de código para vocês verem a diferença. Dessa forma cada classe cuida só do que ela precisa e podemos compor dinamicamente class extensions poderosas, apenas alterando os registros presentes no banco de dados e contando que os campos manipulados por cada uma exista na feature class.
public interface ICustomClassExtension:IClassExtension,IObjectClassExtension,IObjectClassEvents,IObjectClassValidation
{
List extensions { get; set; }
void CreateExtensions();
}
// esta é a classe mais importante. ela provê toda a funcionalidade
// básica de buscar o banco de dados e encontrar quem são as extensões para
// cada feature class e como usá-las
public class CustomClassExtension:ICustomClassExtension
{
public List extensions
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void CreateExtensions()
{
// vá ao banco de dados e descubra quem são as class extensions aplicáveis à esta classe
/*
* foreach (Record r in Query)
* {
* Type t = Type.GetType(r.ClassExtensionAssemblyName) // ValidateParcelGeometry
* extensions.Add(Activator.CreateInstance(t));
* }
*/
}
public void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
foreach (IClassExtension classEx in this.extensions)
{
// todas as classes implementam init
classEx.Init(ClassHelper,ExtensionProperties);
}
}
public void Shutdown()
{
foreach (IClassExtension classEx in this.extensions)
{
classEx.Shutdown();
}
}
public void OnChange(IObject obj)
{
// so vou conseguir iterar sobre as classes que implementam IObjectClassEvents
foreach (IObjectClassEvents classEvents in this.extensions)
{
classEvents.OnChange(obj);
}
}
public void OnCreate(IObject obj)
{
// so vou conseguir iterar sobre as classes que implementam IObjectClassEvents
foreach (IObjectClassEvents classEvents in this.extensions)
{
classEvents.OnCreate(obj);
}
}
public void OnDelete(IObject obj)
{
// so vou conseguir iterar sobre as classes que implementam IObjectClassEvents
foreach (IObjectClassEvents classEvents in this.extensions)
{
classEvents.OnDelete(obj);
}
}
public string ValidateField(IRow Row, string FieldName)
{
string result = String.Empty;
// so vou conseguir iterar sobre as classes que implementam IObjectClassValidation
foreach (IObjectClassValidation validation in this.extensions)
{
result += validation.ValidateField(Row, FieldName);
}
return result;
}
public string ValidateRow(IRow Row)
{
string result = String.Empty;
// so vou conseguir iterar sobre as classes que implementam IObjectClassValidation
foreach (IObjectClassValidation validation in this.extensions)
{
result += validation.ValidateRow(Row);
}
return result;
}
}
public class ValidateParcelGeometryExtensions : IClassExtension, IObjectClassExtension, IObjectClassValidation
{
public void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
throw new NotImplementedException();
}
public void Shutdown()
{
throw new NotImplementedException();
}
public string ValidateField(IRow Row, string FieldName)
{
throw new NotImplementedException();
}
public string ValidateRow(IRow Row)
{
throw new NotImplementedException();
}
}
public class OnParcelEvents : IClassExtension, IObjectClassExtension, IObjectClassEvents
{
public void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
throw new NotImplementedException();
}
public void Shutdown()
{
throw new NotImplementedException();
}
public void OnChange(IObject obj)
{
throw new NotImplementedException();
}
public void OnCreate(IObject obj)
{
throw new NotImplementedException();
}
public void OnDelete(IObject obj)
{
throw new NotImplementedException();
}
}
public class ValidateDwgFile : IClassExtension, IObjectClassExtension, IObjectClassValidation
{
public void Init(IClassHelper ClassHelper, ESRI.ArcGIS.esriSystem.IPropertySet ExtensionProperties)
{
throw new NotImplementedException();
}
public void Shutdown()
{
throw new NotImplementedException();
}
public string ValidateField(IRow Row, string FieldName)
{
throw new NotImplementedException();
}
public string ValidateRow(IRow Row)
{
throw new NotImplementedException();
}
}
As vantagens de algo assim são enormes do ponto de vista de manutenção. São três simples tabelas e uma mudança simples de estrutura de código que vai dar muito mais liberdade para os desenvolvedores e usuários. No momento em que construirmos uma nova extensão, se todos os usuários tiverem à mesma instalada no PC, é só associarmos a mesma à uma entidade que imediatamente deveria funcionar perfeitamente. Poderíamos até deixar à cargo do usuário ligar ou desligar as class extensions de seu interesse.
Entendo que este post é um ArcObjects um pouco mais avançado, mas é muito divertido ver como simples mudanças de arquitetura facilitam a vida dos desenvolvedores e transformar a forma como o usuário se relaciona com o software.
O que acharam?
Um abraço pessoal,
George R. C. Silva
Configure o TerraLib no Visual Studio 2003
Configure o TerraLib no Visual Studio 2003
Por: Joao Tacio
Este é o primeiro post de uma série sobre como utilizar a biblioteca TerraLib para desenvolver suas próprias aplicações. Espero que gostem!
O tutorial apresentado é um complemento da documentação disponibilizada pelo INPE , que ensina como configurar a biblioteca TerraLib 3.3.1 no Visual Studio 2003.
1 - Requisitos para executar os exemplos
- Visual Studio C++ 2003;
- MySQL Administrator 5.o;
- Driver MySQL para Windows;
- Banco de Dados MySQL 5.1;
- Biblioteca TerraLib para Windows, 3.3 ou superior;
2 - Instruções gerais de instalação
O primeiro software a instalar é o Visual Studio 2003. Utilize a instalação personalizada e desmarque o suporte a todas as outras linguagens diferentes do C++, por exemplo: C# e VB. A seguir, instale o MySQL, Driver do MySQL e o MySQL Administrator.
Após instalar com sucesso os softwares descritos acima, crie uma pasta no disco C, por exemplo C:/TerraDir e descompacte todo o conteúdo do arquivo TerraLib_win_v_3_3_1.zip. Após descompactar, a árvore de pastas deve seguir o formato da figura 1.
3 - Compilando a biblioteca TerraLib
O primeiro passo é compilar toda biblioteca TerrLib. Para isso, abra o Visual Studio, clique em File/Open Project / e escolha a solution, referente à biblioteca que será executada. No exemplo abaixo será compilado a solution createDatabase.sln.
1) File/Open project/ ou Ctrl+Shift+O
C:\TerraDir\examples\createDatabase\createDatabase.sln. Certifique-se que o arquivo escolhido possui a extensão.sln, porque na pasta createDatabase, também possui um arquivo chamado createDatabase.dsw.
2) Build/Build Solution ou Ctrl+Shitf+B
Build Sucessfull -> Essa mensagem é indício que está indo tudo muito bem, parabéns!
Faça este mesmo procedimento para todas as bibliotecas abaixo na ordem que estão listadas.
- createLayer;
- createTable;
- importMIDMIF;
- importShape;
- importDBF;
- importJPEG;
- copyLayer;
- importGeoTab;
- convertCoordinates;
- databaseQuery;
- databaseSQLQuery;
- spatialQuery;
- addGeomRepresentation;
- createTheme;
- themeGrouping;
- createSTElementSet;
- mosaicTIFFImages;
- importCSV;
- importGridData;
- rasterSlicing;
- querierFromTheme;
- proxMatrixAndSpatialStatistics;
- spatialQueryAndBuffer;
- querierFromLayer;
- createSTElementSetFromLayer;
- image_processing.
Após compilar todas as bibliotecas acima sem erros, pode fechar o Visual Studio. A próxima etapa consiste em codificar alguns trechos de códigos para iniciar o aprendizado na biblioteca TerraLib. No tutorial do INPE existem diversas páginas com trechos de código.
4 - Configurar o projeto
Este tutorial ficará limitado em explicar detalhadamente apenas os passos necessários para criar um projeto.
O primeiro passo é criar um novo projeto, para isso abra o Visual Studio. Escolha File/New/Project, conforme a figura 2 e coloque o nome no projeto de Primeiro Exemplo.
Escolha o template Console Application (.NET), conforme imagem 3.
Após criar o projeto PrimeiroExemplo, é necessário várias configurações. A primeira é adicionar os projetos existentes do TerraLib. Para isso clique na solution e escolha add e selecione Existing Project. Veja nas figuras 4, 5 e 6 como adicionamos o projeto terralib.
Além do projeto terralib, é necessário adicionar os projetos libjpeg e tiff, que estão respectivamente nos caminhos C:\TerraDir\terralibw\libjpeg e C:\TerraDir\terralibw\tiff. Após adicionar os três projetos, faça um Build na solution.
Além desses três projetos que foram incorporados ao projeto PrimeiroExemplo, outros projetos do TerraLib poderão ser agregados conforme a necessidade.
O procedimento a seguir objetiva configurar o projeto PrimeiroExemplo. Clique nas propriedades do projeto, conforme na figura 08.
Insira o caminho para os diversos diretórios adicionais do projeto. Para realizar essa operação, copie e cole toda a linha a seguir no campo Additional Include Directories. A figura 09 detalha o que precisa ser feito.
.;..\..\src\terralib\kernel;..\..\src\terralib\drivers\MySQL;..\..\src\terralib\drivers\MySQL\include
Desabilite o Precompiled headers. Não feche a janela de propriedades. Configure o Linker do projeto PrimeiroExemplo. Esse procedimento é necessário para que o projeto criado consiga referenciar as classes do TerraLib. Inclua as dependências adicionais conforme ilustra a figura 10. Para isso copie as linhas abaixo e cole em Additional Dependencies.
../../Debug/terralib/terralib.lib ../../Debug/libjpeg/libjpeg.lib ../../Debug/tiff/tiff.lib
../../terralibw/zlib/zlibdll.lib ../../terralibw/MySQL/libMySQL.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib imm32.lib winmm.lib wsock32.lib winspool.lib delayimp.lib
Após realizar todos os procedimentos acima, faça um build do projeto. Caso ocorrer algum erro, tente remover e copiar novamente as linhas que foram inseridas nas propriedades do projeto. Atenção ao copiar essas linhas, espaços não podem ser retirados ou inseridos.
Os procedimentos abaixo consistem em adicionar itens existentes ao projeto. Para o projeto PrimeiroProjeto, será adicionado as bibliotecas TeMySQL.h e TeMySQL.cpp. As figuras de 12 14 demonstram como adicionar o item TeMySQL.cpp ao Source File do Projeto.
A figura 13 apresenta os dois arquivos da biblioteca TerraLib que deverão ser inseridos ao projeto. Inicialmente escolha apenas TeMySQL.cpp a selecione Open.
Para adicionar o arquivo TeMySQL.h ao projeto, click sobre Header Files do projeto PrimeiroProjeto. A figura 14 demonstra como fazer isso. Os próximos passos para adicionar TeMySQL.h são os mesmos das figuras 12 e 13, porém escolha TeMySQL.h e pressione open.
Ao final de todos esses procedimentos, realize um build de todo o projeto PrimeiroProjeto para certificarmos que todos as configurações estão corretas.
5 – Codificar o primeiro exemplo
Para iniciar a codificação do primeiro exemplo, click duas vezes sobre a classe PrimeiroExemplo.cpp, conforme a imagem 15 e adicione as bibliotecas e os métodos criarBancoDeDados e conectar. Após a figura 15 tem o código completo da classe PrimeiroExemplo.cpp. Após codificar a classe, realize um build no projeto e execute-o.
#include "stdafx.h"
#include "conio.h"
#include "TeMySQL.h"
#include "TeDriverMIDMIF.h"
#using
using namespace System;
//Declaração dos métodos
int conectar();
int criarBancoDeDados();
int _tmain()
{
// TODO: Please replace the sample code below with your own.
Console::WriteLine(S"Hello World");
conectar();
criarBancoDeDados();
getch();
return 0;
}
int criarBancoDeDados ()
{
// Parâmetros do servidor de bancos de dados
string host = "localhost";
string dbname = "TerraTeste";
string user = "root";
string password = "";
// Cria um novo banco
TeDatabase* db = new TeMySQL();
if (!db->newDatabase(dbname,user, password, host))
{
cout << "Erro: " << db->errorMessage() << endl;
return 1;
}
cout << "O banco de dados \"" << dbname;
cout <<"\" foi criado com sucesso no servidor MySQL localizado em\""
<< host;
cout << "\" para o usuario \"" << user << "\"!" << endl; // Fecha o banco de dados db->close();
delete db;
return 0;
}
int conectar()
{
// Cria uma conexão a um servidor de banco de dados
TeDatabase* db = new TeMySQL();
// Parametros do servidor de bancos de dados
string host ="localhost";
string dbname = "TerraTeste";
string user = "root";
string password = "";
if (!db->connect(host,user, password,dbname,0))
{
cout<< "Erro: " << db->errorMessage() << endl;
return 0;
}
cout << "Aberta conexão ao banco: " << dbname;
db->close();
delete db;
}
Se esse tutorial foi útil, deixe um comentário por favor. Obrigado
Joao Tacio/TerraLab/UFOP-MG














