La librería con la lógica
La parte de la lógica de nuestro desarrollo la encontraremos dividida en dos proyectos:
- org.gvsig.visor.lib.api
- org.gvsig.visor.lib.impl
En el proyecto del API encontraremos principalmente interfaces. Estos interfaces compondrán
el API de los componentes de lógica que precisemos desarrollar.
EL modelo de clases del ejemplo es el que se muestra en el siguiente diagrama:
.. figure:: ../data/org.gvsig.visor.api.png
:align: center
Modelo de clases del API
Tendremos las siguientes entidades:
- **VisorLibrary**, que representa al objeto *Library*. De esta clase señalar los métodos:
- *doRegistration*, que se ejecuta al cargar la librería e informa qué clase de librería
es, en nuestro caso la definición de un API, así como qué otras librerías requiere que estén
inicializadas antes que esta, la librería de acceso a datos, y la de geometrías. El código
que hace esto es:
.. code-block:: java
public void doRegistration() {
this.registerAs(VisorLibrary.class, Library.TYPE.API);
this.require(DALLibrary.class);
this.require(GeometryLibrary.class);
}
- *doPostInitialize*, que se ejecuta cuando se han inicializado todas las librerías.
En nuestro caso realiza comprobaciones para verificar que al menos una implementación
del API ha sido registrada. El código que hace esto es:
.. code-block:: java
protected void doPostInitialize() throws LibraryException {
// Validate if there are any implementation registered.
VisorManager manager = VisorLocator.getManager();
if (manager == null) {
throw new ReferenceNotRegisteredException(
VisorLocator.MANAGER_NAME, VisorLocator.getInstance()
);
}
}
.. note TODO: Comentar algo sobre la referencia al META-INF/services
- **VisorLocator**, es el *locator* de nuestra librería, el encargado de
suministrarnos la instancia de nuestro manager. Se trata de una clase con métodos
estáticos para registrar implementaciones de este API u obtener una implementación
de él.
- **VisorManager**. Un interface. El del manager de la librería. Define métodos para obtener las
manzanas y parcelas así como para localizar una manzana dado un punto. Es la
entrada a las distintas entidades de nuestro modelo.
- **VisorBlock**. Un interface que representa a una manzana, y aporta métodos para obtener la geometría
que define la manzana o para obtener las parcelas que la constituyen.
- **VisorProperty**. Un interface que representa una parcela catastral. Tiene métodos para obtener
la geometría que la define, su código o municipio.
Vamos a ver qué nos encontramos en la parte de implementación. Observaremos que
mientras que en el API lo normal ha sido encontrarnos interfaces, en la implementación
nos encontraremos clases. Clases que implementan los distintos interfaces que se
definieron en el API. Por convenio, a la implementación de los distintos interfaces
que aparecen en el API las llamaremos igual que en el API anteponiéndole el prefijo *Default*.
El modelo de clases de la implementación para nuestro ejemplo es:
.. figure:: ../data/org.gvsig.visor.impl.png
:align: center
Modelo de clases de la implementación
Vamos a ir viendo las partes más relevantes de la implementación.
- **VisorDefaultImplLibrary**. Al igual que en el API, esta clase se encarga de
inicializar la librería de la implementación. De esta clase señalar los métodos:
- *doRegistration*, que se encarga de registrar la librería como una implementación
del API de *VisorLibrary*. Si tuviese otras dependencias distintas de las marcadas en el
API, se añadirían aquí para asegurarnos que esas librerías se inicializan antes
que esta, pero no es el caso de nuestro ejemplo. Al registrar esta librería como
una implementación de *VisorLibrary*, se cubren dos funciones. Por un lado la librería
del API será inicializada siempre antes que esta implementación, y por otro lado
cuando alguna otra librería fije dependencias con el API, el mecanismo de inicialización
de librerías se encargará de inicializar la implementación junto con el API para
asegurarnos de que dispondremos de una implementación del API que hemos requerido.
El código que necesitaremos poner en nuestro ejemplo será:
.. code-block:: java
public void doRegistration() {
this.registerAsImplementationOf(VisorLibrary.class);
}
- *doInitialize*, que se encarga de registrar en el locator del API la implementación
del manager que tenemos en esta librería. El código que hace esto es:
.. code-block:: java
protected void doInitialize() throws LibraryException {
VisorLocator.registerManager(DefaultVisorManager.class);
}
- **DefaultVisorManager**. De esta clase conviene resaltar principalmente:
- El método *getBlock*. Podemos ver cómo realizar una busqueda de un registro
filtrando por una condición espacial:
.. code-block:: java
try {
String attrGeomName = blocks.getDefaultFeatureType().getDefaultGeometryAttributeName();
FeatureQuery query = blocks.createFeatureQuery();
query.setFilter( new IntersectsEvaluator(attrGeomName,point) );
set = blocks.getFeatureSet(query);
if( set.isEmpty() ) {
return null;
}
it = set.fastiterator();
Feature f = (Feature) it.next();
VisorBlock block = new DefaultVisorBlock(this,f.getGeometry(attrGeomName));
return block;
} catch (DataException e) {
...
} finally {
if( it != null ) {
it.dispose();
}
if( set != null ) {
set.dispose();
}
}
.. Tip::
Puede encontrar información sobre los mecanismos
disponibles para el acceso a datos consultando la
`Guía para el desarrollador de la Librería de Acceso a Datos (DAL)`_
.. _`Guía para el desarrollador de la Librería de Acceso a Datos (DAL)` : https://gvsig.org/web/reference_catalog/lookupObject?uuid=b676641c65b75fd97c3f1cedb8482073
De aquí resaltar:
- Podemos averiguar qué atributo de la *feature* es el que contiene la geometría
a través del *getDefaultGeometryAttributeName* del *feature type* de nuestro *store*.
- Para realizar una búsqueda de *features* en un *store* lo haremos invocando al método
*getFeatureSet* pasando como parámetro una instancia de *FeatureQuery* en la que se
especifiquen las condiciones de filtrado. En ella también se pueden especificar
orden o atributos que queremos recuperar en nuestra query.
- La condición de filtro se especifica suministrando un Evaluator al método *setFilter*
de nuestra query.
- Debemos encargarnos de liberar los objetos que creemos, como son *iteradores* o *feature sets*.
Hay que tener en cuenta que dependiendo del tipo de *store* con el que estemos trabajando
estos pueden tener reservados recursos como conexiones a BBDD, recordsets o conexiones a
servidores remotos.
- El método *openShape*. Aquí podemos ver cómo abrir un *store* basado en shapes
ya existentes:
.. code-block:: java
parameters = manager.createStoreParameters("Shape");
parameters.setDynValue("shpfile", shape);
parameters.setDynValue("crs", "EPSG:23030");
return (FeatureStore) manager.openStore("Shape", parameters);
Observaremos que para abrir un *store* lo realizaremos en dos fases. Por un lado crearemos
una estructura para albergar los parámetros necesarios para abrir nuestro *store*, la
inicializaremos con los valores adecuados e invocaremos al método *openStore* del manager
de acceso a datos con esos parámetros.
Cada tipo de *store* tendrá un juego de parámetros específicos de él. Para abrir un shape,
deberemos indicarle como mínimo el nombre del fichero y el sistema de referencia en el que
se encuentra.
- **IntersectsEvaluator**. Se trata de la clase que evalúa la condición usada en el filtro.
Esta clase comprueba si el campo geometría especificado de una feature dada intersecta con
una geometría concreta. En su construcción se le suministran el campo que contiene la
geometría de la feature y la geometría con la que hay que comprobar si intersecta. De esta
clase conviene resaltar:
- el método *evaluate*, encargado de realizar la comprobación:
.. code-block:: java
Geometry op1geom = (Geometry) data.getDataValue(this.op2attrname);
return new Boolean(this.op1geom.intersects(op1geom));
Sabiendo cómo se llama el atributo que contiene la geometría podemos obtener esta
a través del método *getDataValue*. Una vez tenemos las dos geometrías podemos
invocar el método *intersecs* de la geometría para comprobar si intersectan.
- El método *getCQL*. Este método devolverá una cadena siguiendo el formato de un *where* de
sql a utilizar como filtro en *stores* que ataquen a la BBDD sql. El filtro devuelto puede
no ser exactamente el mismo que el implementado por el código del método *evaluate*, actuando
a modo de filtro previo a este siempre que el store lo soporte.
- **DefaultVisorBlock**. Representa a una manzana de nuestro dominio. Almacena la
geometría que da forma a la manzana. La parte más relevante de esta clase es el
método *getProperties* que retorna todas las parcelas que se encuentran sobre
esa manzana:
.. code-block:: java
List properties = new ArrayList();
FeatureStore store = this.manager.getProperties();
String attrGeomName = store.getDefaultFeatureType().getDefaultGeometryAttributeName();
FeatureQuery query = store.createFeatureQuery();
query.setFilter( new IntersectsEvaluator(attrGeomName, this.shape) );
set = this.manager.getProperties().getFeatureSet(query);
if( set.isEmpty() ) {
return null;
}
it = set.fastiterator();
while( it.hasNext() ) {
Feature f = (Feature) it.next();
VisorProperty property = new DefaultVisorProperty(
this.manager,
f.getString(PROPERTIES_CODE),
f.getGeometry(attrGeomName),
f.getInt(PROPERTIES_CREATIONDATE),
f.getInt(PROPERTIES_MUNICODE)
);
properties.add(property);
}
return properties;
Podemos observar que utiliza el mismo mecanismo para filtrar las parcelas que
usa el manager para recuperar un manzana. En este caso una vez a conseguido el *set*
con las parcelas, lo recorre, recuperando los datos de estas y creando los
objetos parcela.
- **DefaultVisorProperty**. Se trata de la clase que representa a un parcela. En nuestro
ejemplo no tiene apenas lógica, limitándose a almacenar los datos y exponerlos mediante
*geters*.