# -*- coding: utf-8 -*- """ *************************************************************************** Grid.py --------------------- Date : May 2010 Copyright : (C) 2010 by Michael Minn Email : pyqgis at michaelminn dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** """ __author__ = 'Michael Minn' __date__ = 'May 2010' __copyright__ = '(C) 2010, Michael Minn' # This will get replaced with a git SHA1 when you do a git archive __revision__ = '$Format:%H$' import math from PyQt4.QtCore import * from qgis.core import * from processing.core.GeoAlgorithm import GeoAlgorithm from processing.core.GeoAlgorithmExecutionException import \ GeoAlgorithmExecutionException from processing.core.parameters import ParameterNumber from processing.core.parameters import ParameterCrs from processing.core.parameters import ParameterSelection from processing.core.outputs import OutputVector class Grid(GeoAlgorithm): TYPE = 'TYPE' WIDTH = 'WIDTH' HEIGHT = 'HEIGHT' HSPACING = 'HSPACING' VSPACING = 'VSPACING' CENTERX = 'CENTERX' CENTERY = 'CENTERY' CRS = 'CRS' OUTPUT = 'OUTPUT' TYPES = ['Rectangle (line)', 'Rectangle (polygon)', 'Diamond (polygon)', 'Hexagon (polygon)' ] def defineCharacteristics(self): self.name = 'Create grid' self.group = 'Vector creation tools' self.addParameter(ParameterSelection( self.TYPE, 'Grid type', self.TYPES)) self.addParameter(ParameterNumber( self.WIDTH, 'Width', default=360.0)) self.addParameter(ParameterNumber( self.HEIGHT, 'Height', default=180.0)) self.addParameter(ParameterNumber( self.HSPACING, 'Horizontal spacing', default=10.0)) self.addParameter(ParameterNumber( self.VSPACING, 'Vertical spacing', default=10.0)) self.addParameter(ParameterNumber( self.CENTERX, 'Center X', default=0.0)) self.addParameter(ParameterNumber( self.CENTERY, 'Center Y', default=0.0)) self.addParameter(ParameterCrs(self.CRS, 'Output CRS')) self.addOutput(OutputVector(self.OUTPUT, 'Output')) def processAlgorithm(self, progress): idx = self.getParameterValue(self.TYPE) width = self.getParameterValue(self.WIDTH) height = self.getParameterValue(self.HEIGHT) hSpacing = self.getParameterValue(self.HSPACING) vSpacing = self.getParameterValue(self.VSPACING) centerX = self.getParameterValue(self.CENTERX) centerY = self.getParameterValue(self.CENTERY) originX = centerX - width / 2.0 originY = centerY - height / 2.0 crs = QgsCoordinateReferenceSystem(self.getParameterValue(self.CRS)) if hSpacing <= 0 or vSpacing <= 0: raise GeoAlgorithmExecutionException( 'Invalid grid spacing: %s/%s' % (hSpacing, vSpacing)) if width < hSpacing: raise GeoAlgorithmExecutionException( 'Horizontal spacing is too small for the covered area') if height < vSpacing: raise GeoAlgorithmExecutionException( 'Vertical spacing is too small for the covered area') if self.TYPES[idx].find('polygon') >= 0: geometryType = QGis.WKBPolygon else: geometryType = QGis.WKBLineString fields = [QgsField('left', QVariant.Double, '', 24, 16), QgsField('top', QVariant.Double, '', 24, 16), QgsField('right', QVariant.Double, '', 24, 16), QgsField('bottom', QVariant.Double, '', 24, 16) ] writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, geometryType, crs) if idx == 0: self._rectangleGridLine( writer, width, height, originX, originY, hSpacing, vSpacing) elif idx == 1: self._rectangleGridPoly( writer, width, height, originX, originY, hSpacing, vSpacing) elif idx == 2: self._diamondGrid( writer, width, height, originX, originY, hSpacing, vSpacing) elif idx == 3: self._hexagonGrid( writer, width, height, originX, originY, hSpacing, vSpacing) del writer def _rectangleGridLine(self, writer, width, height, originX, originY, hSpacing, vSpacing): ft = QgsFeature() columns = int(math.floor(float(width) / hSpacing)) rows = int(math.floor(float(height) / vSpacing)) # Longitude lines for col in xrange(0, columns + 1): polyline = [] x = originX + (col * hSpacing) for row in xrange(0, rows + 1): y = originY + (row * vSpacing) polyline.append(QgsPoint(x, y)) ft.setGeometry(QgsGeometry.fromPolyline(polyline)) ft.setAttributes([x, originY, x, originY + (rows * vSpacing)]) writer.addFeature(ft) # Latitude lines for row in xrange(0, rows + 1): polyline = [] y = originY + (row * vSpacing) for col in xrange(0, columns + 1): x = originX + (col * hSpacing) polyline.append(QgsPoint(x, y)) ft.setGeometry(QgsGeometry.fromPolyline(polyline)) ft.setAttributes([originX, y, originX + (col * hSpacing), y]) writer.addFeature(ft) def _rectangleGridPoly(self, writer, width, height, originX, originY, hSpacing, vSpacing): ft = QgsFeature() columns = int(math.floor(float(width) / hSpacing)) rows = int(math.floor(float(height) / vSpacing)) for col in xrange(0, columns): # (column + 1) and (row + 1) calculation is used to maintain # topology between adjacent shapes and avoid overlaps/holes # due to rounding errors x1 = originX + (col * hSpacing) x2 = originX + ((col + 1) * hSpacing) for row in xrange(0, rows): y1 = originY + (row * vSpacing) y2 = originY + ((row + 1) * vSpacing) polyline = [] polyline.append(QgsPoint(x1, y1)) polyline.append(QgsPoint(x2, y1)) polyline.append(QgsPoint(x2, y2)) polyline.append(QgsPoint(x1, y2)) polyline.append(QgsPoint(x1, y1)) ft.setGeometry(QgsGeometry.fromPolygon([polyline])) ft.setAttributes([x1, y1, x2, y2]) writer.addFeature(ft) def _diamondGrid(self, writer, width, height, originX, originY, hSpacing, vSpacing): ft = QgsFeature() halfHSpacing = hSpacing / 2 halfVSpacing = vSpacing / 2 columns = int(math.floor(float(width) / halfHSpacing)) rows = int(math.floor(float(height) / vSpacing)) for col in xrange(0, columns): x1 = originX + ((col + 0) * halfHSpacing) x2 = originX + ((col + 1) * halfHSpacing) x3 = originX + ((col + 2) * halfHSpacing) for row in xrange(0, rows): if (col % 2) == 0: y1 = originY + (((row * 2) + 0) * halfVSpacing) y2 = originY + (((row * 2) + 1) * halfVSpacing) y3 = originY + (((row * 2) + 2) * halfVSpacing) else: y1 = originY + (((row * 2) + 1) * halfVSpacing) y2 = originY + (((row * 2) + 2) * halfVSpacing) y3 = originY + (((row * 2) + 3) * halfVSpacing) polyline = [] polyline.append(QgsPoint(x1, y2)) polyline.append(QgsPoint(x2, y1)) polyline.append(QgsPoint(x3, y2)) polyline.append(QgsPoint(x2, y3)) polyline.append(QgsPoint(x1, y2)) ft.setGeometry(QgsGeometry.fromPolygon([polyline])) ft.setAttributes([x1, y1, x3, y3]) writer.addFeature(ft) def _hexagonGrid(self, writer, width, height, originX, originY, hSpacing, vSpacing): ft = QgsFeature() # To preserve symmetry, hspacing is fixed relative to vspacing xVertexLo = 0.288675134594813 * vSpacing; xVertexHi = 0.577350269189626 * vSpacing; hSpacing = xVertexLo + xVertexHi halfVSpacing = vSpacing / 2 columns = int(math.floor(float(width) / hSpacing)) rows = int(math.floor(float(height) / vSpacing)) for col in xrange(0, columns): # (column + 1) and (row + 1) calculation is used to maintain # topology between adjacent shapes and avoid overlaps/holes # due to rounding errors x1 = originX + (col * hSpacing) # far left x2 = x1 + (xVertexHi - xVertexLo) # left x3 = originX + ((col + 1) * hSpacing) # right x4 = x3 + (xVertexHi - xVertexLo) # far right for row in xrange(0, rows): if (col % 2) == 0: y1 = originY + (((row * 2) + 0) * halfVSpacing) # hi y2 = originY + (((row * 2) + 1) * halfVSpacing) # mid y3 = originY + (((row * 2) + 2) * halfVSpacing) # lo else: y1 = originY + (((row * 2) + 1) * halfVSpacing) # hi y2 = originY + (((row * 2) + 2) * halfVSpacing) # mid y3 = originY + (((row * 2) + 3) * halfVSpacing) # lo polyline = [] polyline.append(QgsPoint(x1, y2)) polyline.append(QgsPoint(x2, y1)) polyline.append(QgsPoint(x3, y1)) polyline.append(QgsPoint(x4, y2)) polyline.append(QgsPoint(x3, y3)) polyline.append(QgsPoint(x2, y3)) polyline.append(QgsPoint(x1, y2)) ft.setGeometry(QgsGeometry.fromPolygon([polyline])) ft.setAttributes([x1, y1, x4, y3]) writer.addFeature(ft)