[QGIS Commit] r14744 - in trunk/qgis/python/plugins/fTools: . icons/default icons/gis tools

svn_qgis at osgeo.org svn_qgis at osgeo.org
Mon Nov 22 17:49:40 EST 2010


Author: cfarmer
Date: 2010-11-22 14:49:39 -0800 (Mon, 22 Nov 2010)
New Revision: 14744

Added:
   trunk/qgis/python/plugins/fTools/icons/default/voronoi.png
   trunk/qgis/python/plugins/fTools/icons/gis/voronoi.png
Modified:
   trunk/qgis/python/plugins/fTools/fTools.py
   trunk/qgis/python/plugins/fTools/resources.qrc
   trunk/qgis/python/plugins/fTools/tools/doGeometry.py
   trunk/qgis/python/plugins/fTools/tools/voronoi.py
Log:
[FEATURE] Adds voronoi polygon tool to Vector menu, addresses #2825; Also (partially) addresses #2546

Modified: trunk/qgis/python/plugins/fTools/fTools.py
===================================================================
--- trunk/qgis/python/plugins/fTools/fTools.py	2010-11-22 22:32:03 UTC (rev 14743)
+++ trunk/qgis/python/plugins/fTools/fTools.py	2010-11-22 22:49:39 UTC (rev 14744)
@@ -103,7 +103,7 @@
     self.checkGeom.setIcon( QIcon( self.getThemeIcon( "check_geometry.png") ) )
     self.centroids.setIcon( QIcon( self.getThemeIcon( "centroids.png") ) )
     self.delaunay.setIcon( QIcon( self.getThemeIcon( "delaunay.png") ) )
-    self.voronoi.setIcon( QIcon( self.getThemeIcon( "delaunay.png") ) )
+    self.voronoi.setIcon( QIcon( self.getThemeIcon( "voronoi.png") ) )
     self.extNodes.setIcon( QIcon( self.getThemeIcon( "extract_nodes.png") ) )
     self.simplify.setIcon( QIcon( self.getThemeIcon( "simplify.png") ) )
     self.multiToSingle.setIcon( QIcon( self.getThemeIcon( "multi_to_single.png") ) )

Added: trunk/qgis/python/plugins/fTools/icons/default/voronoi.png
===================================================================
(Binary files differ)


Property changes on: trunk/qgis/python/plugins/fTools/icons/default/voronoi.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/qgis/python/plugins/fTools/icons/gis/voronoi.png
===================================================================
(Binary files differ)


Property changes on: trunk/qgis/python/plugins/fTools/icons/gis/voronoi.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Modified: trunk/qgis/python/plugins/fTools/resources.qrc
===================================================================
--- trunk/qgis/python/plugins/fTools/resources.qrc	2010-11-22 22:32:03 UTC (rev 14743)
+++ trunk/qgis/python/plugins/fTools/resources.qrc	2010-11-22 22:49:39 UTC (rev 14744)
@@ -1,24 +1,24 @@
 <RCC>
-    <qresource>
-		<file>icons/default/single_to_multi.png</file>
-		<file>icons/default/simplify.png</file>
-		<file>icons/default/difference.png</file>
-		<file>icons/default/geometry.png</file>
-		<file>icons/default/check_geometry.png</file>
-		<file>icons/default/to_lines.png</file>
-		<file>icons/default/multi_to_single.png</file>
-		<file>icons/default/mean.png</file>
-		<file>icons/default/select_location.png</file>
-		<file>icons/default/union.png</file>
-		<file>icons/default/dissolve.png</file>
-		<file>icons/default/clip.png</file>
-		<file>icons/default/sym_difference.png</file>
-		<file>icons/default/intersect.png</file>
-		<file>icons/default/analysis.png</file>
-		<file>icons/default/geoprocessing.png</file>
-		<file>icons/default/sampling.png</file>
-		<file>icons/default/management.png</file>
-		<file>icons/default/ftools_logo.png</file>
+    <qresource prefix="/">
+        <file>icons/default/single_to_multi.png</file>
+        <file>icons/default/simplify.png</file>
+        <file>icons/default/difference.png</file>
+        <file>icons/default/geometry.png</file>
+        <file>icons/default/check_geometry.png</file>
+        <file>icons/default/to_lines.png</file>
+        <file>icons/default/multi_to_single.png</file>
+        <file>icons/default/mean.png</file>
+        <file>icons/default/select_location.png</file>
+        <file>icons/default/union.png</file>
+        <file>icons/default/dissolve.png</file>
+        <file>icons/default/clip.png</file>
+        <file>icons/default/sym_difference.png</file>
+        <file>icons/default/intersect.png</file>
+        <file>icons/default/analysis.png</file>
+        <file>icons/default/geoprocessing.png</file>
+        <file>icons/default/sampling.png</file>
+        <file>icons/default/management.png</file>
+        <file>icons/default/ftools_logo.png</file>
         <file>icons/default/centroids.png</file>
         <file>icons/default/export_geometry.png</file>
         <file>icons/default/define_projection.png</file>
@@ -42,28 +42,28 @@
         <file>icons/default/vector_grid.png</file>
         <file>icons/default/split_layer.png</file>
         <file>icons/default/merge_shapes.png</file>
-		<file>icons/default/neighbour.png</file>
-		<file>icons/default/delaunay.png</file>
-		<file>icons/default/layer_extent.png</file>
-		<file>icons/gis/single_to_multi.png</file>
-		<file>icons/gis/simplify.png</file>
-		<file>icons/gis/difference.png</file>
-		<file>icons/gis/geometry.png</file>
-		<file>icons/gis/check_geometry.png</file>
-		<file>icons/gis/to_lines.png</file>
-		<file>icons/gis/multi_to_single.png</file>
-		<file>icons/gis/mean.png</file>
-		<file>icons/gis/select_location.png</file>
-		<file>icons/gis/union.png</file>
-		<file>icons/gis/dissolve.png</file>
-		<file>icons/gis/clip.png</file>
-		<file>icons/gis/sym_difference.png</file>
-		<file>icons/gis/intersect.png</file>
-		<file>icons/gis/analysis.png</file>
-		<file>icons/gis/geoprocessing.png</file>
-		<file>icons/gis/sampling.png</file>
-		<file>icons/gis/management.png</file>
-		<file>icons/gis/ftools_logo.png</file>
+        <file>icons/default/neighbour.png</file>
+        <file>icons/default/delaunay.png</file>
+        <file>icons/default/layer_extent.png</file>
+        <file>icons/gis/single_to_multi.png</file>
+        <file>icons/gis/simplify.png</file>
+        <file>icons/gis/difference.png</file>
+        <file>icons/gis/geometry.png</file>
+        <file>icons/gis/check_geometry.png</file>
+        <file>icons/gis/to_lines.png</file>
+        <file>icons/gis/multi_to_single.png</file>
+        <file>icons/gis/mean.png</file>
+        <file>icons/gis/select_location.png</file>
+        <file>icons/gis/union.png</file>
+        <file>icons/gis/dissolve.png</file>
+        <file>icons/gis/clip.png</file>
+        <file>icons/gis/sym_difference.png</file>
+        <file>icons/gis/intersect.png</file>
+        <file>icons/gis/analysis.png</file>
+        <file>icons/gis/geoprocessing.png</file>
+        <file>icons/gis/sampling.png</file>
+        <file>icons/gis/management.png</file>
+        <file>icons/gis/ftools_logo.png</file>
         <file>icons/gis/centroids.png</file>
         <file>icons/gis/export_geometry.png</file>
         <file>icons/gis/define_projection.png</file>
@@ -86,8 +86,10 @@
         <file>icons/gis/sum_lines.png</file>
         <file>icons/gis/vector_grid.png</file>
         <file>icons/gis/split_layer.png</file>
-		<file>icons/gis/neighbour.png</file>
-		<file>icons/gis/delaunay.png</file>
-		<file>icons/gis/layer_extent.png</file>
+        <file>icons/gis/neighbour.png</file>
+        <file>icons/gis/delaunay.png</file>
+        <file>icons/gis/layer_extent.png</file>
+        <file>icons/gis/voronoi.png</file>
+        <file>icons/default/voronoi.png</file>
     </qresource>
 </RCC>

Modified: trunk/qgis/python/plugins/fTools/tools/doGeometry.py
===================================================================
--- trunk/qgis/python/plugins/fTools/tools/doGeometry.py	2010-11-22 22:32:03 UTC (rev 14743)
+++ trunk/qgis/python/plugins/fTools/tools/doGeometry.py	2010-11-22 22:49:39 UTC (rev 14744)
@@ -6,6 +6,8 @@
 import ftools_utils
 import math
 from itertools import izip
+import voronoi
+from sets import Set
 
 class GeometryDialog(QDialog, Ui_Dialog):
 
@@ -38,7 +40,7 @@
       QMessageBox.information(self, self.tr("Geometry"), self.tr( "Please specify input vector layer" ) )
     elif self.outShape.text() == "":
       QMessageBox.information(self, self.tr("Geometry"), self.tr( "Please specify output shapefile" ) )
-    elif self.lineEdit.isVisible() and self.lineEdit.value() <= 0.00:
+    elif self.lineEdit.isVisible() and self.lineEdit.value() < 0.00:
       QMessageBox.information(self, self.tr("Geometry"), self.tr( "Please specify valid tolerance value" ) )
     elif self.cmbField.isVisible() and self.cmbField.currentText() == "":
       QMessageBox.information(self, self.tr("Geometry"), self.tr( "Please specify valid UID field" ) )
@@ -102,12 +104,22 @@
       if self.myFunction == 8: # Delaunay triangulation
         self.setWindowTitle( self.tr( "Delaunay triangulation" ) )
         self.label_3.setText( self.tr( "Input point vector layer" ) )
+        self.label.setVisible( False )
+        self.lineEdit.setVisible( False )
+      elif self.myFunction == 10: # Voronoi Polygons
+        self.setWindowTitle( self.tr( "Voronoi polygon" ) )
+        self.label_3.setText( self.tr( "Input point vector layer" ) )
+        self.label.setText( self.tr( "Buffer region" ) )
+        self.lineEdit.setSuffix(" %")
+        self.lineEdit.setRange(0, 100)
+        self.lineEdit.setSingleStep(5)
+        self.lineEdit.setValue(0)
       else: # Polygon from layer extent
         self.setWindowTitle( self.tr( "Polygon from layer extent" ) )
         self.label_3.setText( self.tr( "Input layer" ) )
+        self.label.setVisible( False )
+        self.lineEdit.setVisible( False )
       self.label_2.setText( self.tr( "Output polygon shapefile" ) )
-      self.label.setVisible( False )
-      self.lineEdit.setVisible( False )
       self.cmbField.setVisible( False )
       self.field_label.setVisible( False )
     self.resize( 381, 100 )
@@ -117,7 +129,7 @@
       myList = ftools_utils.getLayerNames( [ QGis.Polygon, QGis.Line ] )
     elif self.myFunction == 4 or self.myFunction == 7:
       myList = ftools_utils.getLayerNames( [ QGis.Polygon ] )
-    elif self.myFunction == 8:
+    elif self.myFunction == 8 or self.myFunction == 10:
       myList = ftools_utils.getLayerNames( [ QGis.Point ] )
     elif self.myFunction == 9:
       myList = ftools_utils.getLayerNames( "all" )
@@ -135,6 +147,7 @@
 #7:  Polygon centroids
 #8: Delaunay triangulation
 #9: Polygon from layer extent
+#10:Voronoi polygons
 
   def geometry( self, myLayer, myParam, myField ):
     if self.myFunction == 9:
@@ -164,6 +177,7 @@
   def runFinishedFromThread( self, success ):
     self.testThread.stop()
     self.buttonOk.setEnabled( True )
+    extra = ""
     if success == "math_error":
       QMessageBox.warning( self, self.tr("Geometry"), self.tr("Error processing specified tolerance!\nPlease choose larger tolerance...") )
       if not QgsVectorFileWriter.deleteShapeFile( self.shapefileName ):
@@ -173,10 +187,16 @@
       if not QgsVectorFileWriter.deleteShapeFile( self.shapefileName ):
         QMessageBox.warning( self, self.tr("Geometry"), self.tr( "Unable to delete incomplete shapefile." ) )
     else:
+      if success == "valid_error":
+        extra = self.tr("One or more features in the output layer may have invalid "
+                      + "geometry, please check using the check validity tool\n")
+        success = True
       self.cancel_close.setText( "Close" )
       QObject.disconnect( self.cancel_close, SIGNAL( "clicked()" ), self.cancelThread )
       if success:
-        addToTOC = QMessageBox.question( self, self.tr("Geometry"), self.tr( "Created output shapefile:\n%1\n\nWould you like to add the new layer to the TOC?" ).arg( unicode( self.shapefileName ) ), QMessageBox.Yes, QMessageBox.No, QMessageBox.NoButton )
+        addToTOC = QMessageBox.question( self, self.tr("Geometry"), 
+          self.tr( "Created output shapefile:\n%1\n%2\n\nWould you like to add the new layer to the TOC?" ).arg( unicode( self.shapefileName ) ).arg( extra ), 
+          QMessageBox.Yes, QMessageBox.No, QMessageBox.NoButton )
         if addToTOC == QMessageBox.Yes:
           if not ftools_utils.addShapeToCanvas( unicode( self.shapefileName ) ):
             QMessageBox.warning( self, self.tr("Geoprocessing"), self.tr( "Error loading output shapefile:\n%1" ).arg( unicode( self.shapefileName ) ))
@@ -220,6 +240,8 @@
       success = self.delaunay_triangulation()
     elif self.myFunction == 9: # Polygon from layer extent
       success = self.layer_extent()
+    elif self.myFunction == 10: # Voronoi Polygons
+      success = self.voronoi_polygons()
     self.emit( SIGNAL( "runFinished(PyQt_PyObject)" ), success )
     self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
 
@@ -231,8 +253,10 @@
     allAttrs = vprovider.attributeIndexes()
     vprovider.select( allAttrs )
     fields = vprovider.fields()
+    allValid = True
+    geomType = self.singleToMultiGeom(vprovider.geometryType())
     writer = QgsVectorFileWriter( self.myName, self.myEncoding,
-    fields, vprovider.geometryType(), vprovider.crs() )
+        fields, geomType, vprovider.crs() )
     inFeat = QgsFeature()
     outFeat = QgsFeature()
     inGeom = QgsGeometry()
@@ -272,13 +296,15 @@
           nElement += 1
           self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ),  nElement )
         outFeat.setAttributeMap( atts )
-        outGeom = QgsGeometry( self.convertGeometry( multi_feature, vType ) )
-        outFeat.setGeometry( outGeom )
-        writer.addFeature( outFeat )
+        outGeom = QgsGeometry( self.convertGeometry(multi_feature, vType) )
+        if not outGeom.isGeosValid():
+          allValid = "valid_error"
+        outFeat.setGeometry(outGeom)
+        writer.addFeature(outFeat)
       del writer
     else:
       return "attr_error"
-    return True
+    return allValid
 
   def multi_to_single( self ):
     vprovider = self.vlayer.dataProvider()
@@ -424,6 +450,7 @@
 
   def delaunay_triangulation( self ):
     import voronoi
+    from sets import Set
     vprovider = self.vlayer.dataProvider()
     allAttrs = vprovider.attributeIndexes()
     vprovider.select( allAttrs )
@@ -434,39 +461,184 @@
     writer = QgsVectorFileWriter( self.myName, self.myEncoding,
     fields, QGis.WKBPolygon, vprovider.crs() )
     inFeat = QgsFeature()
-    points = []
-    while vprovider.nextFeature( inFeat ):
-      inGeom = QgsGeometry( inFeat.geometry() )
-      point = inGeom.asPoint()
-      points.append( point )
-    vprovider.rewind()
-    vprovider.select( allAttrs )
-    triangles = voronoi.computeDelaunayTriangulation( points )
+    c = voronoi.Context()
+    pts = []
+    while vprovider.nextFeature(inFeat):
+      geom = QgsGeometry(inFeat.geometry())
+      point = geom.asPoint()
+      x = point.x()
+      y = point.y()
+      pts.append((x, y))
+    if len(pts) < 3:
+      return False
+    uniqueSet = Set(item for item in pts)
+    ids = [pts.index(item) for item in uniqueSet]
+    sl = voronoi.SiteList([voronoi.Site(*i) for i in uniqueSet])
+    c.triangulate = True
+    voronoi.voronoi(sl, c)
+    triangles = c.triangles
     feat = QgsFeature()
     nFeat = len( triangles )
     nElement = 0
     self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
     self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) )
     for triangle in triangles:
-      indicies = list( triangle )
-      indicies.append( indicies[ 0 ] )
+      indicies = list(triangle)
+      indicies.append(indicies[0])
       polygon = []
       step = 0
       for index in indicies:
-        vprovider.featureAtId( index, inFeat, True,  allAttrs )
-        geom = QgsGeometry( inFeat.geometry() )
-        point = QgsPoint( geom.asPoint() )
-        polygon.append( point )
-        if step <= 3: feat.addAttribute( step, QVariant( index ) )
+        vprovider.featureAtId( ids[index], inFeat, True,  allAttrs )
+        geom = QgsGeometry(inFeat.geometry())
+        point = QgsPoint(geom.asPoint())
+        polygon.append(point)
+        if step <= 3: feat.addAttribute(step, QVariant(ids[index]))
         step += 1
-      geometry = QgsGeometry().fromPolygon( [ polygon ] )
-      feat.setGeometry( geometry )
-      writer.addFeature( feat )
+      geometry = QgsGeometry().fromPolygon([polygon])
+      feat.setGeometry(geometry)
+      writer.addFeature(feat)
       nElement += 1
       self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ),  nElement )
     del writer
     return True
 
+  def voronoi_polygons( self ):
+    vprovider = self.vlayer.dataProvider()
+    allAttrs = vprovider.attributeIndexes()
+    vprovider.select( allAttrs )
+    writer = QgsVectorFileWriter( self.myName, self.myEncoding,
+    vprovider.fields(), QGis.WKBPolygon, vprovider.crs() )
+    inFeat = QgsFeature()
+    outFeat = QgsFeature()
+    extent = self.vlayer.extent()
+    extraX = extent.height()*(self.myParam/100.00)
+    extraY = extent.width()*(self.myParam/100.00)
+    height = extent.height()
+    width = extent.width()
+    c = voronoi.Context()
+    pts = []
+    while vprovider.nextFeature(inFeat):
+      geom = QgsGeometry(inFeat.geometry())
+      point = geom.asPoint()
+      x = point.x()-extent.xMinimum()
+      y = point.y()-extent.yMinimum()
+      pts.append((x, y))
+    self.vlayer = None
+    if len(pts) < 3:
+      return False
+    uniqueSet = Set(item for item in pts)
+    ids = [pts.index(item) for item in uniqueSet]
+    sl = voronoi.SiteList([voronoi.Site(*i, sitenum=j) for j, i in enumerate(uniqueSet)])
+    voronoi.voronoi(sl, c)
+    inFeat = QgsFeature()
+    nFeat = len(c.polygons)
+    nElement = 0
+    self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
+    self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) )
+    for site, edges in c.polygons.iteritems():
+      vprovider.featureAtId(ids[site], inFeat, True,  allAttrs)
+      lines = self.clip_voronoi(edges, c, width, height, extent, extraX, extraY)
+      geom = QgsGeometry.fromMultiPoint(lines)
+      geom = QgsGeometry(geom.convexHull())
+      outFeat.setGeometry(geom)
+      outFeat.setAttributeMap(inFeat.attributeMap())
+      writer.addFeature(outFeat)
+      nElement += 1
+      self.emit(SIGNAL("runStatus(PyQt_PyObject)" ), nElement)
+    del writer
+    return True
+
+    
+  def clip_voronoi(self, edges, c, width, height, extent, exX, exY):
+    def clip_line(x1, y1, x2, y2, w, h, x, y):
+      if x1 < 0-x and x2 < 0-x:
+        return [0, 0, 0, 0]
+      if x1 > w+x and x2 > w+x:
+        return [0, 0, 0, 0]
+      if x1 < 0-x:
+        y1 = (y1*x2 - y2*x1)/(x2 - x1)
+        x1 = 0-x
+      if x2 < 0-x:
+        y2 = (y1*x2 - y2*x1)/(x2 - x1)
+        x2 = 0-x
+      if x1 > w+x:
+        y1 = y1 + (w+x - x1)*(y2 - y1)/(x2 - x1)
+        x1 = w+x
+      if x2 > w+x:
+        y2 = y1 + (w+x - x1)*(y2 - y1)/(x2 - x1)
+        x2 = w+x
+      if y1 < 0-y and y2 < 0-y:
+        return [0, 0, 0, 0]
+      if y1 > h+y and y2 > h+y:
+        return [0, 0, 0, 0]
+      if x1 == x2 and y1 == y2:
+        return [0, 0, 0, 0]
+      if y1 < 0-y:
+        x1 = (x1*y2 - x2*y1)/(y2 - y1)
+        y1 = 0-y
+      if y2 < 0-y:
+        x2 = (x1*y2 - x2*y1)/(y2 - y1)
+        y2 = 0-y
+      if y1 > h+y:
+        x1 = x1 + (h+y - y1)*(x2 - x1)/(y2 - y1)
+        y1 = h+y
+      if y2 > h+y:
+        x2 = x1 + (h+y - y1)*(x2 - x1)/(y2 - y1)
+        y2 = h+y
+      return [x1, y1, x2, y2]
+    lines = []
+    hasXMin = False
+    hasYMin = False
+    hasXMax = False
+    hasYMax = False
+    for edge in edges:
+      if edge[1] >= 0 and edge[2] >= 0:       # two vertices
+          [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], c.vertices[edge[2]][0], c.vertices[edge[2]][1], width, height, exX, exY)
+      elif edge[1] >= 0:                      # only one vertex
+        if c.lines[edge[0]][1] == 0:        # vertical line
+          xtemp = c.lines[edge[0]][2]/c.lines[edge[0]][0]
+          if c.vertices[edge[1]][1] > (height+exY)/2:
+            ytemp = height+exY
+          else:
+            ytemp = 0-exX
+        else:
+          xtemp = width+exX
+          ytemp = (c.lines[edge[0]][2] - (width+exX)*c.lines[edge[0]][0])/c.lines[edge[0]][1]
+        [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], xtemp, ytemp, width, height, exX, exY)
+      elif edge[2] >= 0:                      # only one vertex
+        if c.lines[edge[0]][1] == 0:        # vertical line
+          xtemp = c.lines[edge[0]][2]/c.lines[edge[0]][0]
+          if c.vertices[edge[2]][1] > (height+exY)/2:
+            ytemp = height+exY
+          else:
+            ytemp = 0.0-exY
+        else:
+          xtemp = 0.0-exX
+          ytemp = c.lines[edge[0]][2]/c.lines[edge[0]][1]
+        [x1, y1, x2, y2] = clip_line(xtemp, ytemp, c.vertices[edge[2]][0], c.vertices[edge[2]][1], width, height, exX, exY)
+      if x1 or x2 or y1 or y2:
+        lines.append(QgsPoint(x1+extent.xMinimum(),y1+extent.yMinimum()))
+        lines.append(QgsPoint(x2+extent.xMinimum(),y2+extent.yMinimum()))
+        if 0-exX in (x1, x2):
+          hasXMin = True
+        if 0-exY in (y1, y2):
+          hasYMin = True
+        if height+exY in (y1, y2):
+          hasYMax = True
+        if width+exX in (x1, x2):
+          hasXMax = True
+    if hasXMin:
+      if hasYMax:
+        lines.append(QgsPoint(extent.xMinimum()-exX, height+extent.yMinimum()+exY))
+      if hasYMin:
+        lines.append(QgsPoint(extent.xMinimum()-exX, extent.yMinimum()-exY))
+    if hasXMax:
+      if hasYMax:
+        lines.append(QgsPoint(width+extent.xMinimum()+exX, height+extent.yMinimum()+exY))
+      if hasYMin:
+        lines.append(QgsPoint(width+extent.xMinimum()+exX, extent.yMinimum()-exY))
+    return lines
+    
   def layer_extent( self ):
     self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
     self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, 0 ) )
@@ -615,6 +787,22 @@
     else:
       return []
 
+  def singleToMultiGeom(self, wkbType):
+      try:
+          if wkbType in (QGis.WKBPoint, QGis.WKBMultiPoint, 
+                         QGis.WKBPoint25D, QGis.WKBMultiPoint25D):
+              return QGis.WKBMultiPoint
+          elif wkbType in (QGis.WKBLineString, QGis.WKBMultiLineString,
+                           QGis.WKBMultiLineString25D, QGis.WKBLineString25D):
+              return QGis.WKBMultiLineString
+          elif wkbType in (QGis.WKBPolygon, QGis.WKBMultiPolygon, 
+                           QGis.WKBMultiPolygon25D, QGis.WKBPolygon25D):
+              return QGis.WKBMultiPolygon
+          else:
+              return QGis.WKBUnknown
+      except Exception, err:
+          print str(err)
+
   def extractAsSingle( self, geom ):
     multi_geom = QgsGeometry()
     temp_geom = []

Modified: trunk/qgis/python/plugins/fTools/tools/voronoi.py
===================================================================
--- trunk/qgis/python/plugins/fTools/tools/voronoi.py	2010-11-22 22:32:03 UTC (rev 14743)
+++ trunk/qgis/python/plugins/fTools/tools/voronoi.py	2010-11-22 22:49:39 UTC (rev 14744)
@@ -100,7 +100,7 @@
 BIG_FLOAT = 1e38
 
 #------------------------------------------------------------------
-class Context( object ):
+class Context(object):
     def __init__(self):
         self.doPrint = 0
         self.debug   = 0
@@ -110,41 +110,13 @@
         self.lines     = []    # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c  
         self.edges     = []    # edge 3-tuple: (line index, vertex 1 index, vertex 2 index)   if either vertex index is -1, the edge extends to infiinity
         self.triangles = []    # 3-tuple of vertex indices
-#        self.extra_edges = []  # list of additional vertex 2-tubles (x,y) based on bounded voronoi tesselation
-#        self.set_bounds(None)
-#        self.use_bound = False
-        self.xmin = self.ymin = self.xmax = self.ymax = None
+        self.polygons  = {}    # a dict of site:[edges] pairs
 
     def circle(self,x,y,rad):
         pass
 
-    def clip_line(self,edge,lid,rid):
+    def clip_line(self,edge):
         pass
-        # here is where I will create false verticies if
-        # the voronoi line extends to infinity...
-        # the extra verticies will be added to the
-        # extra edges list as 2-tuples
-#        a,b,c = edge.a,edge.b,edge.c
-#        if lid == -1:
-#            x = self.xMin
-#            y = (c-a*x) / b
-#            if y < self.yMin or y > self.yMax:
-#                if y < self.yMin: y = self.yMin
-#                elif y > self.yMax: y = self.yMax
-#                x = (c-b*y) / a
-#            self.extra_edges.append((x,y))
-#            lid = -(len(self.extra_edges)-1)
-#        if rid == -1:
-#            x = self.xMax
-#            y = (c-a*x) / b
-#            if y < self.yMin or y > self.yMax:
-#                if y < self.yMin: y = self.yMin
-#                elif y > self.yMax: y = self.yMax
-#                x = (c-b*y) / a
-#            self.extra_edges.append((x,y))
-#            rid = -(len(self.extra_edges)-1)
-#        print lid,rid
-#        return (lid,rid)
 
     def line(self,x0,y0,x1,y1):
         pass
@@ -155,17 +127,12 @@
         elif(self.triangulate):
             pass
         elif(self.plot):
-            self.circle (s.x, s.y, 3) #cradius)
+            self.circle (s.x, s.y, cradius)
         elif(self.doPrint):
             print "s %f %f" % (s.x, s.y)
 
     def outVertex(self,s):
         self.vertices.append((s.x,s.y))
-        if s.x < self.xmin: self.xmin = s.x
-        elif s.x > self.xmax: self.xmax = s.x
-        if s.y < self.ymin: self.ymin = s.y
-        elif s.y > self.ymax: self.ymax = s.y
-        
         if(self.debug):
             print  "vertex(%d) at %f %f" % (s.sitenum, s.x, s.y)
         elif(self.triangulate):
@@ -197,10 +164,12 @@
         sitenumR = -1
         if edge.ep[Edge.RE] is not None:
             sitenumR = edge.ep[Edge.RE].sitenum
-            
-#        if sitenumL == -1 or sitenumR == -1 and self.use_bound:
-#            sitenumL,sitenumR = self.clip_line(edge,sitenumL,sitenumR)
-
+        if edge.reg[0].sitenum not in self.polygons:
+            self.polygons[edge.reg[0].sitenum] = []
+        if edge.reg[1].sitenum not in self.polygons:
+            self.polygons[edge.reg[1].sitenum] = []
+        self.polygons[edge.reg[0].sitenum].append((edge.edgenum,sitenumL,sitenumR))
+        self.polygons[edge.reg[1].sitenum].append((edge.edgenum,sitenumL,sitenumR))
         self.edges.append((edge.edgenum,sitenumL,sitenumR))
         if(not self.triangulate):
             if self.plot:
@@ -210,153 +179,148 @@
                 print " %d " % sitenumL,
                 print "%d" % sitenumR
 
-    def set_bounds(self,bounds):
-        if not bounds == None:
-            self.xmin = bounds.xmin
-            self.ymin = bounds.ymin
-            self.xmax = bounds.xmax
-            self.ymax = bounds.ymax
-        else:
-            self.xmin = self.ymin = self.xmax = self.ymax = None
-        
-
 #------------------------------------------------------------------
 def voronoi(siteList,context):
-    edgeList  = EdgeList(siteList.xmin,siteList.xmax,len(siteList))
-    priorityQ = PriorityQueue(siteList.ymin,siteList.ymax,len(siteList))
-    siteIter = siteList.iterator()
-    
-    bottomsite = siteIter.next()
-    context.outSite(bottomsite)
-    newsite = siteIter.next()
-    minpt = Site(-BIG_FLOAT,-BIG_FLOAT)
-    while True:
-        if not priorityQ.isEmpty():
-            minpt = priorityQ.getMinPt()
+    try:
+      edgeList  = EdgeList(siteList.xmin,siteList.xmax,len(siteList))
+      priorityQ = PriorityQueue(siteList.ymin,siteList.ymax,len(siteList))
+      siteIter = siteList.iterator()
+      
+      bottomsite = siteIter.next()
+      context.outSite(bottomsite)
+      newsite = siteIter.next()
+      minpt = Site(-BIG_FLOAT,-BIG_FLOAT)
+      while True:
+          if not priorityQ.isEmpty():
+              minpt = priorityQ.getMinPt()
 
-        if (newsite and (priorityQ.isEmpty() or cmp(newsite,minpt) < 0)):
-            # newsite is smallest -  this is a site event
-            context.outSite(newsite)
-            
-            # get first Halfedge to the LEFT and RIGHT of the new site 
-            lbnd = edgeList.leftbnd(newsite) 
-            rbnd = lbnd.right                    
-            
-            # if this halfedge has no edge, bot = bottom site (whatever that is)
-            # create a new edge that bisects
-            bot  = lbnd.rightreg(bottomsite)     
-            edge = Edge.bisect(bot,newsite)      
-            context.outBisector(edge)
-            
-            # create a new Halfedge, setting its pm field to 0 and insert 
-            # this new bisector edge between the left and right vectors in
-            # a linked list
-            bisector = Halfedge(edge,Edge.LE)    
-            edgeList.insert(lbnd,bisector)       
+          if (newsite and (priorityQ.isEmpty() or cmp(newsite,minpt) < 0)):
+              # newsite is smallest -  this is a site event
+              context.outSite(newsite)
+              
+              # get first Halfedge to the LEFT and RIGHT of the new site 
+              lbnd = edgeList.leftbnd(newsite) 
+              rbnd = lbnd.right                    
+              
+              # if this halfedge has no edge, bot = bottom site (whatever that is)
+              # create a new edge that bisects
+              bot  = lbnd.rightreg(bottomsite)     
+              edge = Edge.bisect(bot,newsite)      
+              context.outBisector(edge)
+              
+              # create a new Halfedge, setting its pm field to 0 and insert 
+              # this new bisector edge between the left and right vectors in
+              # a linked list
+              bisector = Halfedge(edge,Edge.LE)    
+              edgeList.insert(lbnd,bisector)       
 
-            # if the new bisector intersects with the left edge, remove 
-            # the left edge's vertex, and put in the new one
-            p = lbnd.intersect(bisector)
-            if p is not None:
-                priorityQ.delete(lbnd)
-                priorityQ.insert(lbnd,p,newsite.distance(p))
+              # if the new bisector intersects with the left edge, remove 
+              # the left edge's vertex, and put in the new one
+              p = lbnd.intersect(bisector)
+              if p is not None:
+                  priorityQ.delete(lbnd)
+                  priorityQ.insert(lbnd,p,newsite.distance(p))
 
-            # create a new Halfedge, setting its pm field to 1
-            # insert the new Halfedge to the right of the original bisector
-            lbnd = bisector
-            bisector = Halfedge(edge,Edge.RE)     
-            edgeList.insert(lbnd,bisector)        
+              # create a new Halfedge, setting its pm field to 1
+              # insert the new Halfedge to the right of the original bisector
+              lbnd = bisector
+              bisector = Halfedge(edge,Edge.RE)     
+              edgeList.insert(lbnd,bisector)        
 
-            # if this new bisector intersects with the right Halfedge
-            p = bisector.intersect(rbnd)
-            if p is not None:
-                # push the Halfedge into the ordered linked list of vertices
-                priorityQ.insert(bisector,p,newsite.distance(p))
-            
-            newsite = siteIter.next()
+              # if this new bisector intersects with the right Halfedge
+              p = bisector.intersect(rbnd)
+              if p is not None:
+                  # push the Halfedge into the ordered linked list of vertices
+                  priorityQ.insert(bisector,p,newsite.distance(p))
+              
+              newsite = siteIter.next()
 
-        elif not priorityQ.isEmpty():
-            # intersection is smallest - this is a vector (circle) event 
+          elif not priorityQ.isEmpty():
+              # intersection is smallest - this is a vector (circle) event 
 
-            # pop the Halfedge with the lowest vector off the ordered list of 
-            # vectors.  Get the Halfedge to the left and right of the above HE
-            # and also the Halfedge to the right of the right HE
-            lbnd  = priorityQ.popMinHalfedge()      
-            llbnd = lbnd.left               
-            rbnd  = lbnd.right              
-            rrbnd = rbnd.right              
-            
-            # get the Site to the left of the left HE and to the right of
-            # the right HE which it bisects
-            bot = lbnd.leftreg(bottomsite)  
-            top = rbnd.rightreg(bottomsite) 
-            
-            # output the triple of sites, stating that a circle goes through them
-            mid = lbnd.rightreg(bottomsite)
-            context.outTriple(bot,top,mid)          
+              # pop the Halfedge with the lowest vector off the ordered list of 
+              # vectors.  Get the Halfedge to the left and right of the above HE
+              # and also the Halfedge to the right of the right HE
+              lbnd  = priorityQ.popMinHalfedge()      
+              llbnd = lbnd.left               
+              rbnd  = lbnd.right              
+              rrbnd = rbnd.right              
+              
+              # get the Site to the left of the left HE and to the right of
+              # the right HE which it bisects
+              bot = lbnd.leftreg(bottomsite)  
+              top = rbnd.rightreg(bottomsite) 
+              
+              # output the triple of sites, stating that a circle goes through them
+              mid = lbnd.rightreg(bottomsite)
+              context.outTriple(bot,top,mid)          
 
-            # get the vertex that caused this event and set the vertex number
-            # couldn't do this earlier since we didn't know when it would be processed
-            v = lbnd.vertex                 
-            siteList.setSiteNumber(v)
-            context.outVertex(v)
-            
-            # set the endpoint of the left and right Halfedge to be this vector
-            if lbnd.edge.setEndpoint(lbnd.pm,v):
-                context.outEdge(lbnd.edge)
-            
-            if rbnd.edge.setEndpoint(rbnd.pm,v):
-                context.outEdge(rbnd.edge)
+              # get the vertex that caused this event and set the vertex number
+              # couldn't do this earlier since we didn't know when it would be processed
+              v = lbnd.vertex                 
+              siteList.setSiteNumber(v)
+              context.outVertex(v)
+              
+              # set the endpoint of the left and right Halfedge to be this vector
+              if lbnd.edge.setEndpoint(lbnd.pm,v):
+                  context.outEdge(lbnd.edge)
+              
+              if rbnd.edge.setEndpoint(rbnd.pm,v):
+                  context.outEdge(rbnd.edge)
 
-            
-            # delete the lowest HE, remove all vertex events to do with the 
-            # right HE and delete the right HE
-            edgeList.delete(lbnd)           
-            priorityQ.delete(rbnd)
-            edgeList.delete(rbnd)
-            
-            
-            # if the site to the left of the event is higher than the Site
-            # to the right of it, then swap them and set 'pm' to RIGHT
-            pm = Edge.LE
-            if bot.y > top.y:
-                bot,top = top,bot
-                pm = Edge.RE
+              
+              # delete the lowest HE, remove all vertex events to do with the 
+              # right HE and delete the right HE
+              edgeList.delete(lbnd)           
+              priorityQ.delete(rbnd)
+              edgeList.delete(rbnd)
+              
+              
+              # if the site to the left of the event is higher than the Site
+              # to the right of it, then swap them and set 'pm' to RIGHT
+              pm = Edge.LE
+              if bot.y > top.y:
+                  bot,top = top,bot
+                  pm = Edge.RE
 
-            # Create an Edge (or line) that is between the two Sites.  This 
-            # creates the formula of the line, and assigns a line number to it
-            edge = Edge.bisect(bot, top)     
-            context.outBisector(edge)
+              # Create an Edge (or line) that is between the two Sites.  This 
+              # creates the formula of the line, and assigns a line number to it
+              edge = Edge.bisect(bot, top)     
+              context.outBisector(edge)
 
-            # create a HE from the edge 
-            bisector = Halfedge(edge, pm)    
-            
-            # insert the new bisector to the right of the left HE
-            # set one endpoint to the new edge to be the vector point 'v'
-            # If the site to the left of this bisector is higher than the right
-            # Site, then this endpoint is put in position 0; otherwise in pos 1
-            edgeList.insert(llbnd, bisector) 
-            if edge.setEndpoint(Edge.RE - pm, v):
-                context.outEdge(edge)
-            
-            # if left HE and the new bisector don't intersect, then delete 
-            # the left HE, and reinsert it 
-            p = llbnd.intersect(bisector)
-            if p is not None:
-                priorityQ.delete(llbnd);
-                priorityQ.insert(llbnd, p, bot.distance(p))
+              # create a HE from the edge 
+              bisector = Halfedge(edge, pm)    
+              
+              # insert the new bisector to the right of the left HE
+              # set one endpoint to the new edge to be the vector point 'v'
+              # If the site to the left of this bisector is higher than the right
+              # Site, then this endpoint is put in position 0; otherwise in pos 1
+              edgeList.insert(llbnd, bisector) 
+              if edge.setEndpoint(Edge.RE - pm, v):
+                  context.outEdge(edge)
+              
+              # if left HE and the new bisector don't intersect, then delete 
+              # the left HE, and reinsert it 
+              p = llbnd.intersect(bisector)
+              if p is not None:
+                  priorityQ.delete(llbnd);
+                  priorityQ.insert(llbnd, p, bot.distance(p))
 
-            # if right HE and the new bisector don't intersect, then reinsert it 
-            p = bisector.intersect(rrbnd)
-            if p is not None:
-                priorityQ.insert(bisector, p, bot.distance(p))
-        else:
-            break
+              # if right HE and the new bisector don't intersect, then reinsert it 
+              p = bisector.intersect(rrbnd)
+              if p is not None:
+                  priorityQ.insert(bisector, p, bot.distance(p))
+          else:
+              break
 
-    he = edgeList.leftend.right
-    while he is not edgeList.rightend:
-        context.outEdge(he.edge)
-        he = he.right
+      he = edgeList.leftend.right
+      while he is not edgeList.rightend:
+          context.outEdge(he.edge)
+          he = he.right
+      Edge.EDGE_NUM = 0
+    except Exception, err:
+      print "######################################################"
+      print str(err)
 
 #------------------------------------------------------------------
 def isEqual(a,b,relativeError=TOLERANCE):
@@ -724,16 +688,16 @@
         self.__sites = []
         self.__sitenum = 0
 
-        self.__xmin = pointList[0].x()
-        self.__ymin = pointList[0].y()
-        self.__xmax = pointList[0].x()
-        self.__ymax = pointList[0].y()
+        self.__xmin = pointList[0].x
+        self.__ymin = pointList[0].y
+        self.__xmax = pointList[0].x
+        self.__ymax = pointList[0].y
         for i,pt in enumerate(pointList):
-            self.__sites.append(Site(pt.x(),pt.y(),i))
-            if pt.x() < self.__xmin: self.__xmin = pt.x()
-            if pt.y() < self.__ymin: self.__ymin = pt.y()
-            if pt.x() > self.__xmax: self.__xmax = pt.x()
-            if pt.y() > self.__ymax: self.__ymax = pt.y()
+            self.__sites.append(Site(pt.x,pt.y,i))
+            if pt.x < self.__xmin: self.__xmin = pt.x
+            if pt.y < self.__ymin: self.__ymin = pt.y
+            if pt.x > self.__xmax: self.__xmax = pt.x
+            if pt.y > self.__ymax: self.__ymax = pt.y
         self.__sites.sort()
 
     def setSiteNumber(self,site):
@@ -769,7 +733,7 @@
 
 
 #------------------------------------------------------------------
-def computeVoronoiDiagram( points ):
+def computeVoronoiDiagram(points):
     """ Takes a list of point objects (which must have x and y fields).
         Returns a 3-tuple of:
 
@@ -782,22 +746,21 @@
                the indices of the vetices at the end of the edge.  If 
                v1 or v2 is -1, the line extends to infinity.
     """
-    siteList = SiteList( points )
+    siteList = SiteList(points)
     context  = Context()
-    context.set_bounds( siteList )
-    voronoi( siteList, context )
-    return ( context.vertices, context.lines, context.edges, (context.xmin,context.ymin,context.xmax,context.ymax))
+    voronoi(siteList,context)
+    return (context.vertices,context.lines,context.edges)
 
 #------------------------------------------------------------------
-def computeDelaunayTriangulation( points ):
+def computeDelaunayTriangulation(points):
     """ Takes a list of point objects (which must have x and y fields).
         Returns a list of 3-tuples: the indices of the points that form a
         Delaunay triangle.
     """
-    siteList = SiteList( points )
+    siteList = SiteList(points)
     context  = Context()
-    context.triangulate = True
-    voronoi( siteList, context )
+    context.triangulate = true
+    voronoi(siteList,context)
     return context.triangles
 
 #-----------------------------------------------------------------------------



More information about the QGIS-commit mailing list