Hello everyone back again,
It was some time ago I didn’t write this blog but I have been really busy working on new gvSIG developments. Anyway the other day, somebody said me they needed a new tool for check geometry problems in a table before load it into a posgreSQL database. They told me….
“It would be nice if we could have a tool that processes geometries in a layer, list all the problems and it allows clicking the geometry conflict and, better yet, locating the geometry conflict”
Indeed, gvSIG does not have anything near it but straight away I thought that, with a bit of luck, I could get a similar tool which they need.
But the script isn’t as simple. It cuts across a whole range of variables although it is not too big and it can help to design more complex tools.
The principle is simple: we analyze the features of the layer, select geometry and we invoke the method: “getValidationStatus”. This method is designed to validate geometry and returns information about validation process: identify problems and if it is possible where they are. Thus, we are building up a little HTML validation report.
An entry is generated for each incident with the following information:
- Line number where the incident is located
- Dump data with feature for line
- Description of the problem
- Link to the incident is located. Instead of add an URL, we will add the WKT point for the incident layer.
wktgeom = vs.getProblemLocation().convertToWKT() output.write('<a href="%s">%5.5d</a> %s<br>%s' % (wktgeom,i, feature.toString(),vs.getMessage()))
Once we have been analysed all the features we will have to generate the report. To do so, we will display a window with HTML text using “showTexDialog” function from “ApplicationManager”.
application = ApplicationLocator.getManager() application.showTextDialog( WindowManager.MODE.TOOL, "Problems in layer "+layer.getName(), informe.getvalue() )
It would be enough if we simply want to generate the report but it would be very nice if the incident area is displayed on the screen by clicking the incident in the report. To do this, we will have a bit more work to do. “showTextDialog” function allow us to pass a “listener” as optional parameter to receive events coming from HTML report. To do so, we will have to create a class to inherit from “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)
Our class will have in the constructor, a view to work with and we add the code to execute in “hyperlinkUpdate” method. The code will be executed every time while interact with link of our report. Either because we mouse over the link or because of we click on the link. We will insert the code in this method, verifying that click the link is filtered. Then, we will receive “ACTIVATED” event type. We will use the associated point for the hyperlink and rebuild our point according we had added to WKT point.
manager = GeometryLocator.getGeometryManager() point = manager.createFrom(wktgeom)
And then you pan the view to the point
There are a few more things to comment…
Sometimes, it will be run on the big layers and it would be nice if it included progress reporting. To do so, we will use “taskStatus” variable. It is associated with each script to report progress of the process. First of all, we associate it to the application status bar to see the progress of the process. That’s as simple as invoking the “add” method of the variable.
Then we will use the “setRangeOfValues” method to indicate the iterations necessary for our process. With this method, we will indicate that iterations go from 0 to number of records included in the layer to process.For each iteration, we will invoke “setCurValue” method to keep the process located. After our features have been reviewed we will invoke “terminate” method to get rid of “taskStatus” variable and indicate that the process is completed. Finally, we will use “remove” method to delete the variable from gvSIG status bar.
try: # Adding a progress bar to the gvSIG status bar taskStatus.add() # Indicate the number of iterations taskStatus.setRangeOfValues(0,layer.features().getCount()) for feature in layer.features(): # For each iteration there is a report process in the progress bar indicating where is located taskStatus.setCurValue(i) ... i+=1 finally: # Finally # For indicate to the status bar the process is completed taskStatus.terminate() # To delete from gvSIG status bar taskStatus.remove()
Well, it still lacks many details like catching errors, StringIO use to create reports or generate HTML code, but I believe that people who know something about gvSIG scripting and python programming could follow it. At the end of this blog you will find the full code.
I also have a problem with “certerViewinPoint” method. There is some calculation error and we can’t correctly pan the view. That is a minor detail that you can improve if you work with the script.
I hope it will serve you as gvSIG tool and example of new types of scriptings.
To test the script you need a layer with damage or non valid geometries. I have any missed layer, if I located it, I upload the layer and I give you the link to test.
The script just run with gvSIG desktop 2.1.0 build 2231 or higher ;)
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": # Just process “ACTIVATED” events which are the clicks on the links # Collect the geometry we have used in WKT link. wktgeom = h.getDescription() print wktgeom # Collect Rebuild the point from WKT manager = GeometryLocator.getGeometryManager() point = manager.createFrom(wktgeom) # Once we have that, we pan the view to the point centerViewinPoint(self.vista,point) def main(*args): layer = currentLayer() if layer == None: msgbox("Select layer to validate") return informe = StringIO.StringIO() informe.write("<ul>\n") i=0 hay_problemas = False try: # Add progress bar to gvSIG status bar. taskStatus.add() # Indicate number of iterations that are going to be carried out. for feature in layer.features(): # For each iteration, we will report to the progress bar to keep the process located taskStatus.setCurValue(i) vs = feature.geometry().getValidationStatus(); if not vs.isValid() : hay_problemas = True if vs.getProblemLocation()!=None: wktgeom = vs.getProblemLocation().convertToWKT() else: try: # Catch the errors to avoid failure in calculating centroid # because of damage geometry. 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: # Finally # Indicate the process is finished to the progress bar taskStatus.terminate() taskStatus.terminate() # And eliminate it from the gvSIG status bar. taskStatus.remove() informe.write("</ul>\n") if hay_problemas : application = ApplicationLocator.getManager() application.showTextDialog( WindowManager.MODE.TOOL, # TOOL or WINDOW depending on whether we are interested. "Problems in layer "+layer.getName(), informe.getvalue(), MyHyperlinkListener(currentView()) ) else: msgbox("Layer "+layer.getName()+" is valid.")