Hola a todos de nuevo,
Hacia ya un tiempo que no escribía por aquí…cosas de ir bastante liado con nuevos desarrollos que estamos abordando en gvSIG…pero el otro día me comentaron que necesitarían una herramienta en gvSIG que les permitiera localizar problemas en las geometrías de una tabla antes de cargar dicha tabla en una BBDD de postgreSQL.
Me dijeron…
“Estaría bien disponer de una herramienta que procese las geometrías de una capa y me saque un listado con los problemas y me permita pinchar e ir a la geometría conflictiva y mejor aun a donde este el conflicto”
Efectivamente, gvSIG no tiene nada que se le parezca, pero enseguida pensé que igual con un poco de scripting podía sacar algo parecido a lo que me pedían.
El script no es simple, para que vamos a engañarnos, toca muchos palos, pero no es muy grande y puede servir de inicio para abordar cosas mas complejas.
El principio es simple… nos recorremos las features de la capa, cogemos la geometría, e invocamos al método “getValidationStatus” de esta. Este método realiza una serie de validaciones sobre la geometría y nos devuelve que ha pasado con esas validaciones… si todo fue bien o mal, y en caso de ir mal nos da información sobre que problema se encontró y si puede donde lo encontró. Con esta información, vamos a ir construyendo un pequeño informe en HTML.
Por cada incidencia que encontremos generaremos una entrada con:
- El número de línea en el que se produjo la incidencia
- Un volcado de los datos de la feature asociada a esa línea
- Una descripción textual del problema
- Un enlace al punto en donde se produjo la incidencia. En el hiperenlace, en lugar de poner una URL meteremos el punto en WKT asociado a la incidencia.
wktgeom = vs.getProblemLocation().convertToWKT()
output.write('<a href="%s">%5.5d</a> %s<br>%s' % (wktgeom,i, feature.toString(),vs.getMessage()))
Cuando ya nos hemos recorrido todas las features de la capa, tendremos que presentar el informe. Para ello utilizaremos la función “showTextDialog” del“ApplicationManager”, que nos permite mostrar una venta con un texto HTML.
application = ApplicationLocator.getManager() application.showTextDialog( WindowManager.MODE.TOOL, "Problems in layer "+layer.getName(), informe.getvalue() )
Si solo quisiésemos mostrar el informe con esto nos bastaría. Pero estaría muy bien que al pinchar sobre la incidencia en el informe nos centrase la vista en el área donde se ha producido la incidencia. Para esto tendremos que hacer un poco mas de faena. La función “showTextDialog” permite que le pasemos un ultimo parámetro opcional que sea un “listener” que recibirá los eventos relacionados con los hiperenlaces de nuestro informe en HTML. Para eso tendremos que crear una clase que herede de “HyperlinkListener”.
class MyHyperlinkListener(HyperlinkListener): def __init__(self,vista): self.vista = vista def hyperlinkUpdate(self, h): if str(h.getEventType()) == "ACTIVATED": wktgeom = h.getDescription() print wktgeom manager = GeometryLocator.getGeometryManager() point = manager.createFrom(wktgeom) centerViewinPoint(self.vista,point)
Nuestra clase recibirá en el constructor la vista sobre la que ha de trabajar, y en el método “hyperlinkUpdate” pondremos el código que se ejecutara cada vez que se interactúe con un enlace de nuestro informe, bien por que se pasa el ratón sobre el enlace, o por que se hace click sobre el. Así que nuestro código lo meteremos en este método cercionándonos que filtramos las acción de hacer click en el enlace que sera cuando recibamos un evento de tipo “ACTIVATED”. Lo que haremos será recoger el punto asociado del hiperenlace. Este lo habíamos metido en WKT, así que lo primero será reconstruir nuestro punto a partir de él.
manager = GeometryLocator.getGeometryManager() point = manager.createFrom(wktgeom)
Y luego ya centrar la vista en ese punto.
centerViewinPoint(self.vista,point)
Mas cositas que comentar…
A veces lo ejecutaremos sobre capas grandes y estaría bien que fuese informando del progreso. Para ello utilizaremos la variable “taskStatus”. Todos los scripts tienen una asociada y sirve precisamente para eso, para ir informado del progreso de nuestro proceso. Lo primero que tendremos que hacer será asociarla a la barra de estado de la aplicación para que se vaya mostrando ahí el proceso. Tan simple como invocar al método “add” de ella.
Luego le indicaremos las iteraciones que va a tener nuestro proceso, para lo que utilizaremos el método “setRangeOfValues” indicando que las iteraciones irán desde 0 al número de registros que tiene la capa que vamos a procesar. En cada iteración invocaremos al método “setCurValue” para informar de por donde vamos, y al terminar de recorrer nuestras features nos desharemos de nuestro taskStatus invocando al método “terminate” para indicarle que ya termino nuestro proceso y al método“remove” para eliminarla de la barra de estado de gvSIG.
try: # Añadimos la barra de progreso a la barra de estado de gvSIG taskStatus.add() # Le indicamos cuantas iteraciones vamos a realizar taskStatus.setRangeOfValues(0,layer.features().getCount()) for feature in layer.features(): # En cada iteración informamos a la barra de progreso de por donde vamos taskStatus.setCurValue(i) ... i+=1 finally: # Al terminar # Le indicamos a la barra de progreso que ya hemos terminado taskStatus.terminate() # Y la eliminamos de la barra de estado de gvSIG. taskStatus.remove()
Bueno, hay un montón de detallitos, como atrapar errores aquí y allí, el manejo del StringIO para generar nuestro informe o la generación del código HTML, pero creo que los que han estado ya viendo algunas cosillas del scripting en gvSIG y tienen algo de conocimiento de programación en python pueden seguirlo. Al final os dejo el código completo.
También tengo un problema en el metodo “centerViewinPoint”, algun cálculo no es correcto y no se centra bien la vista, pero creo que es un detalle menor que ya podréis pulir si os es de utilidad el script.
Espero que os sirva… como herramienta para gvSIG y como ejemplo de como hacer cosas nuevas en scripting.
Eso si, para probarlo precisareis de una capa con geometrías dañadas o no validas. Alguna tengo por ahi… a ver si puedo colgarla en algun sitio y la enzalo para que podais probar.
Para ejecutar el script precisareis un gvSIG desktop 2.1.0 build 2231 o superior 😉
Un saludo a todos
Joaquin
from gvsig import * from org.gvsig.tools.swing.api.windowmanager import WindowManager import StringIO from commonsdialog import * from javax.swing.event import HyperlinkListener from org.gvsig.fmap.geom import GeometryLocator from org.gvsig.fmap.geom import Geometry def centerViewinPoint(view,center): env = view.getMap().getViewPort().getEnvelope(); movX = center.getX()-env.getCenter(0); movY = center.getY()-env.getCenter(1); minx = env.getMinimum(0) + movX; miny = env.getMinimum(1) + movY; maxX = env.getMaximum(0) + movX; maxY = env.getMaximum(1) + movY; env = GeometryLocator.getGeometryManager().createEnvelope( minx, miny, maxX, maxY, Geometry.SUBTYPES.GEOM2D); view.getMap().getViewPort().setEnvelope(env); view.getMap().invalidate() class MyHyperlinkListener(HyperlinkListener): def __init__(self,vista): self.vista = vista def hyperlinkUpdate(self, h): if str(h.getEventType()) == "ACTIVATED": # Sopo procesamos los eventos "ACTIVATED" que son los clicks sobre los enlaces # Recogemos la geometría que metimos en el enlace en WKT wktgeom = h.getDescription() print wktgeom # Y reconstruimos el punto a partir de ese WKT manager = GeometryLocator.getGeometryManager() point = manager.createFrom(wktgeom) # Y una vez lo tenemos centramos la vista en ese punto. centerViewinPoint(self.vista,point) def main(*args): layer = currentLayer() if layer == None: msgbox("Debera seleccionar la capa que desea validar") return informe = StringIO.StringIO() informe.write("<ul>\n") i=0 hay_problemas = False try: # Añadimos la barra de progreso a la barra de estado de gvSIG taskStatus.add() # Le indicamos cuantas iteraciones vamos a realizar for feature in layer.features(): # En cada iteración informamos a la barra de progreso de por donde vamos taskStatus.setCurValue(i) vs = feature.geometry().getValidationStatus(); if not vs.isValid() : hay_problemas = True if vs.getProblemLocation()!=None: wktgeom = vs.getProblemLocation().convertToWKT() else: try: # Atrapamos los errores no vaya a ser que la geometría este tan dañada que # nos falle el calculo de su centroide. wktgeom = feature.geometry().centroid().convertToWKT() except: wktgeom = None informe.write("<li>") if wktgeom == None: informe.write('%5.5d%s<br>%s' % (i, feature.toString(),vs.getMessage())) else: informe.write('<a href="%s">%5.5d</a> %s<br>%s' % (wktgeom,i, feature.toString(),vs.getMessage())) informe.write("</li>") i+=1 finally: # Al terminar # Le indicamos a la barra de progreso que ya hemos terminado taskStatus.terminate() # Y la eliminamos de la barra de estado de gvSIG. taskStatus.remove() informe.write("</ul>\n") if hay_problemas : application = ApplicationLocator.getManager() application.showTextDialog( WindowManager.MODE.TOOL, # TOOL o WINDOW según nos interese. "Problems in layer "+layer.getName(), informe.getvalue(), MyHyperlinkListener(currentView()) ) else: msgbox("Layer "+layer.getName()+" is valid.")
Pingback: Scripting en gvSIG: Barra de estado | másquesig
Pingback: Scripting en gvSIG: Validar geometrías simplificado | másquesig
Perfecta explicación. ¿que ocurre cuando en una misma feature tenemos varios problemas? entiendo que getProblemLocation() devuelve un punto, el primer error que se encuentra? Y si hay más errores?
Gracias!