(English version here)
Hola a todos.
Voy a comentar sobre dos o tres cosas que han cambiado en el acceso a datos en la versión 2.4 (alguna ya apareció en la 2.3, pero se consolidan en la versión 2.4).
En principio son cambios que no deberían afectar a los desarrollos ya existentes ya que en general se tratan de mejoras o adiciones sobre lo que ya había en versiones anteriores.
Voy a contar sobre:
- Liberación de recursos automaticamente al llegar al final de una iteración sobre un FeatureSet.
- Obtener un “List” con las features de un “store”, filtrando o sin filtrar.
- Crear una capa con los datos de una tabla filtrados.
- Crear un filtro para BBDD que sea independiente de esta.
Vamos a ir viendo estas cosas.
Liberación de recursos de un FeatureSet.
Esta funcionalidad ya apareció en los últimos builds de gvSIG 2.3. En varias clases de la librería de DAL se dio soporte al interface de java “Iterable”. Esto permite realizar construcciones de código mas cómodas. En lugar de tener que hacer:
try { FeatureSet set = store.getFeatureSet(); Iterator it = set.iterator(); while( it.hasNext() ) { Feature feature = (Feature) it.next(); ... } } finally { DisposeUtils.disposeQuietly(it); DisposeUtils.disposeQuietly(set); }
Ahora podríamos hacer algo como:
for(Feature feature : store.getFeatureSet() ) { ... }
De forma que los recursos asociados al iterador y al set se liberarán de forma automatica al llegar al final de la iteración. Para hacer esto lo que se ha hecho es que cuando se ejecuta el método hasNext del iterador, y se ha alcanzado el final de la iteración, dentro de este método se liberan los recursos automaticamente.
Tendremos que tener cuidado con estas construcciones cuando vayamos a salir del bucle antes de llegar al final de la iteración, con un break o un return, siendo recomendable seguir utilizando para esos casos la forma de acceso anterior.
Obtener un “List” de features
Esta funcionalidad también apareció en los últimos builds de gvSIG 2.3. Se han añadido los siguientes métodos al FeatureStore:
public List<Feature> getFeatures(FeatureQuery query, int pageSize); public List<Feature> getFeatures();
Hasta ahora la única forma de acceder a las features de un store era a través del método getFeatureSet, y luego iterando sobre ellas. Con la introducción de estos dos métodos podemos obtener un “List” de features directamente permitiéndonos un acceso aleatorio sobre las features de un store. Hay que tener cuidado al usar este método. Para obtener la lista de features se utiliza un FeaturePagingHelper, de forma que no se cargan en memoria todas las features del store. Se utiliza un “paginado” (por defecto de 500 elementos) y se van cargando y descargando paginas automaticamente según se van solicitando estas a través del interface de “List”. Un acceso aleatorio real sobre la lista puede llevar a una sobrecarga en el acceso a datos y una perdida de rendimiento considerable. Este método esta pensado para dar un soporte a mostrar el resultado de una búsqueda sobre datos al usuario, por ejemplo en un JList o un JTable, ya que es relativamente simple crear un TableModel o un ListModel basado en un “List<Feature>”. Normalmente los rendimientos del JTable y JList serán aceptables aun con unos cientos de miles de registros.
Una consideración importante. Si una vez obtenido el “List” intentamos entrar en edición o terminar edición en el store a partir del que lo hemos obtenido se pueden producir errores. En la versión 2.4 no debe cambiarse el modo de edición del store una vez se ha obtenido el “List”. Intentaremos subsanar este problema en próximas revisiones.
Crear una capa con los datos de una tabla filtrados.
Esto no es una mejora propiamente dicha de las librería de acceso a datos, pero como de alguna manera esta relacionado con el acceso a datos y es una mejora muy interesante lo voy a comentar aquí.
Se ha añadido al interface VectorLayer los métodos:
public FeatureQuery getBaseQuery(); public void addBaseFilter(Evaluator filter); public void addBaseFilter(String filter);
Los métodos addBaseFilter nos permiten añadir un filtro que sera aplicado cada vez que se precise obtener un FeatureSet para pintar los elementos de la capa.
Esto nos permite de forma cómoda añadir por código capas a una vista filtrando la información que se va a visualizar y a partir de una misma tabla tener varias capas con “vistas” distintas de los datos.
Crear un filtro para BBDD que sea independiente de esta.
Me voy a entretener un poco mas en la descripción de esto ya que mi experiencia es que suele costar entenderlo.
La forma de filtrar datos en gvSIG 2 es a través del método “addFilter” o “setFilter” de un “FeatureQuery”. Un filtro no es mas que una clase que implementa el interface “Evaluator”. Vemos lo con un ejemplo:
Supongamos que tenemos una tabla con un campo geometría y quisiemos filtrar por las lineas de la tabla cuyo campo geometría intersecta con una determinada área.
Implementaríamos algo como:
public class IntersectsGeometryEvaluator extends AbstractEvaluator { private String geometryColumnName; private Geometry areaOfInterest; IntersectsGeometryEvaluator(Geometry areaOfInterest, String geometryColumnName) { this.areaOfInterest = areaOfInterest; this.geometryColumnName = geometryColumnName; } @Override public Object evaluate(EvaluatorData data) throws EvaluatorException { try { Geometry geometryColumn = (Geometry) data.getDataValue(geometryColumnName); if (geometryColumn == null) { return false; } return geometryColumn.intersects(this.areaOfInterest); } catch (Exception e) { return false; } } @Override public String getName() { return "intersects with geometry"; } } ... Geometry areaOfInterest = ... FeatureQuery query = store.createFeatureQuery(); query.setFilter(new IntersectsGeometryEvaluator(areaOfInterest,"the_geom")); FeatureSet set = store.getFeatureSet(query); ...
De esta forma obtendríamos un conjunto de features filtrado por la condición que nos
interesa.
Ahora bien, aunque esto funcionaria con todas las fuentes de datos, shapes, dxf, o tablas en una BBDD, no seria óptimo cuando estemos atacando a una BBDD. Si lo dejamos tal cual, se traería a local todos los registros de la tabla asociada al store y los filtraría en local. Si la tabla es grande y la BBDD tiene soporta para índices espaciales, esto seria muy ineficiente.
¿ Como habría que hacerlo ?
El interface “Evaluator” tiene un método “getSQL”. En el ejemplo, no hemos implementado este método. Este método debería devolver la parte “where” de una sentencia SQL que realice una función “similar” a la de nuestro código java. No es preciso que sean idénticas, pero si que devuelva al menos los mismos o mas registros que los que filtraría nuestro código java (nunca menos). Cuando apliquemos ese filtro contra una fuente de datos que soporte condiciones tipo “where” de SQL, la librería de acceso a datos se encargara de hacérselas llegar. Si el filtro se aplica a una fuente de datos que no lo soporta, por ejemplo un shape, simplemente se ignorara el valor devuelto por “getSQL”. Si soporta el filtrado estilo SQL, la librería de acceso a datos montara la consulta a la BBDD con esa condición y luego filtrara los registros obtenidos con nuestro evaluador. De esta forma no sera necesario traerse todos los registros de la tabla y seguiremos disponiendo de toda la potencia de filtro de nuestro código java.
Podríamos tener un método “getSQL” algo como:
@Override public String getSQL() { return MessageFormat.format( "ST_intersects( ST_GeomFromText(''{0}'',{1}), {2} )", this.areaOfInterest.convertToWKT(), this.srsid, this.geometryColumnName ); }
De forma que este método nos devolvería la parte de condición que pondríamos en el “where” que nos permitiría filtrar por la condición que nos interesa.
Si nos fijamos un poco podemos observar un par de problemas…
- Precisamos un “srsid” para reconstruir la geometría en el servidor de BBDD a partir del WKT. Esto no debería tener grandes problemas, simplemente tendríamos que pasarle al evaluador en que proyección están nuestras geometrías… pero lo normal es que le pasemos un IProjection, que es lo que nos devuelve cualquier artefacto de gvSIG cuando le preguntamos por su proyección. UUhmmm… Aquí requeriríamos el identificador de SRS de la BBDD, no un objeto proyección.
- La sintaxis que he utilizado para realizar la consulta es muy OGC…
¿ Y si la BBDD a la que estamos accediendo no acepta esa sintaxis ?
Para solucionar estos problemas en gvSIG 2.4 se ha introducido el concepto de “ExpressionBuilder” ligado a una fuente de datos, un FeatureStore.
Podemos pedirle al store contra el que estamos trabajando un “ExpressionBuilder” adecuado para él, y utilizarlo para componer la condición que nos interesa. Veamos como seria en nuestro ejemplo:
public class IntersectsGeometryEvaluator extends AbstractEvaluator { private String geometryColumnName; private Geometry areaOfInterest; private IProjection projection; private ExpressionBuilder builder; IntersectsGeometryEvaluator( Geometry areaOfInterest, String geometryColumnName, IProjection projection, ExpressionBuilder builder ) { this.areaOfInterest = areaOfInterest; this.geometryColumnName = geometryColumnName; this.proyeccion = projection; this.builder = builder; } @Override public Object evaluate(EvaluatorData data) throws EvaluatorException { try { Geometry geometryColumn = (Geometry) data.getDataValue(geometryColumnName); if (geometryColumn == null) { return false; } return geometryColumn.intersects(this.areaOfInterest); } catch (Exception e) { return false; } } @Override public String getName() { return "intersects with geometry"; } @Override public String getSQL() { String condition = this.builder.set( this.builder.ST_Intersects( this.builder.geometry(this.areaOfInterest, this.projection), this.builder.column(this.geometryColumnName) ) ).toString(); return condition; } } ... Geometry areaOfInterest = ... FeatureQuery query = store.createFeatureQuery(); query.setFilter(new IntersectsGeometryEvaluator( areaOfInterest, "the_geom", projection, store.createExpressionBuilder() ) ); FeatureSet set = store.getFeatureSet(query); ...
Voy comentando los cambios…
Lo primero, nuestro evaluador, además de recibir la geometría con el área sobre la que queremos filtrar, y el nombre de la columna por la que queremos filtrar, recibirá el objeto “IProjection” con la proyección en la que están nuestras geometrías, y un “ExpressionBuilder” que habremos obtenido a partir del “store” sobre el que estamos trabajando. Estos dos nuevos parámetros nos los guardaremos en propiedades de la clase igual que hicimos con los dos anteriores. El resto de cambios están ya en el método “getSQL”. Ahora ya no hay código SQL en nuestro método. En su lugar se llama al builder para construir el código SQL. Este se encarga en:
this.builder.geometry(this.areaOfInterest, this.projection)
De construir el código SQL necesario para que nuestra geometría con su proyección en
nuestro formato sea trasladada correctamente al formato de la BBDD asociada al store con el que estamos trabajando. No tendremos que preocuparnos de si hemos de convertir a WKT o a algún otro formato o si el IProjection tiene este o aquel código en esa BBDD. Eso ya lo hace el builder por nosotros. Igual que se encarga de trasladar:
this.builder.ST_Intersects( this.builder.column("a") this.builder.column("b") )
A la sintaxis adecuada para nuestra BBDD. Por ejemplo, si estamos trabajando sobre una
tabla de PostgreSQL+PostGIS, generaría algo como:
ST_intersects("a","b")
Pero si nuestro “store” esta almacenado en una BBDD de SQLServer generaría algo como:
[a].STIntersects([b])
Encargándose él de hacer ese trabajo, y dejando nuestro código libre de tener que encargarse de gestionar los distintos dialectos de SQL.
Pingback: Some improvements in access data in gvSIG 2.4 | gvSIG blog
Reblogged this on másquesig.