def processAlgorithm(self, parameters, context, feedback): segments_layer = self.parameterAsLayer(parameters, self.IN_SEGMENTS, context) t = self.parameterAsDouble(parameters, self.THRESHOLD, context) index = QgsSpatialIndex() # Spatial index index.addFeatures(segments_layer.getFeatures()) todel = [] for ln in segments_layer.getFeatures(): index.deleteFeature(ln) cands = index.intersects(ln.geometry().boundingBox()) ln1 = ln.geometry() for ca in cands: totest = segments_layer.getFeature(ca) ln2 = totest.geometry() are_equal = self.equal_segments(ln1, ln2, t) if are_equal: index.deleteFeature(totest) todel.append(ca) (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT, context, segments_layer.fields( ), # QgsFields() for an empty fields list or source_lines.fields() QgsWkbTypes.MultiLineString, segments_layer.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) for feature in segments_layer.getFeatures(): if feature.id() not in todel: sink.addFeature(feature, QgsFeatureSink.FastInsert) return {"OUTPUT": dest_id}
class GridGeometry: """ Triangular grid geometry """ def __init__(self, extent, x_segments, y_segments, values=None): self.extent = extent self.x_segments = x_segments self.y_segments = y_segments self.values = values center = extent.center() self.width, self.height = (extent.width(), extent.height()) self.xmin, self.ymin = (center.x() - self.width / 2, center.y() - self.height / 2) self.xmax, self.ymax = (center.x() + self.width / 2, center.y() + self.height / 2) self.xres = self.width / x_segments self.yres = self.height / y_segments self.vbands = self.hbands = None def setupBands(self): xmin, ymin, xmax, ymax = (self.xmin, self.ymin, self.xmax, self.ymax) xres, yres = (self.xres, self.yres) vrects = [] hrects = [] vbands = [] hbands = [] for x in range(self.x_segments): f = QgsFeature(x) r = QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres, ymax) f.setGeometry(QgsGeometry.fromRect(r)) vrects.append(r) vbands.append(f) for y in range(self.y_segments): f = QgsFeature(y) r = QgsRectangle(xmin, ymax - (y + 1) * yres, xmax, ymax - y * yres) f.setGeometry(QgsGeometry.fromRect(r)) hrects.append(r) hbands.append(f) self.vrects = vrects self.hrects = hrects self.vbands = vbands self.hbands = hbands self.vidx = QgsSpatialIndex() self.vidx.addFeatures(vbands) self.hidx = QgsSpatialIndex() self.hidx.addFeatures(hbands) def vSplit(self, geom): """split polygon vertically""" for idx in self.vidx.intersects(geom.boundingBox()): geometry = geom.clipped(self.vrects[idx]) if geometry: yield idx, geometry def hSplit(self, geom): """split polygon horizontally""" for idx in self.hidx.intersects(geom.boundingBox()): geometry = geom.clipped(self.hrects[idx]) if geometry: yield idx, geometry # OBSOLETE def hIntersects(self, geom): """indices of horizontal bands that intersect with geom""" for idx in self.hidx.intersects(geom.boundingBox()): if geom.intersects(self.hrects[idx]): yield idx def splitPolygonXY(self, geom): return QgsGeometry.fromMultiPolygonXY(list(self._splitPolygon(geom))) def splitPolygon(self, geom): z_func = lambda x, y: self.valueOnSurface(x, y) or 0 cache = FunctionCacheXY(z_func) z_func = cache.func polygons = QgsMultiPolygon() for poly in self._splitPolygon(geom): p = QgsPolygon() ring = QgsLineString() for pt in poly[0]: ring.addVertex(QgsPoint(pt.x(), pt.y(), z_func(pt.x(), pt.y()))) p.setExteriorRing(ring) for bnd in poly[1:]: ring = QgsLineString() for pt in bnd: ring.addVertex(QgsPoint(pt.x(), pt.y(), z_func(pt.x(), pt.y()))) p.addInteriorRing(ring) polygons.addGeometry(p) return QgsGeometry(polygons) def _splitPolygon(self, geom): if self.vbands is None: self.setupBands() for x, vc in self.vSplit(geom): for y, c in self.hSplit(vc): if c.isEmpty(): continue for poly in PolygonGeometry.nestedPointXYList(c): bnds = [[[pt.x(), pt.y()] for pt in bnd] for bnd in poly] data = earcut.flatten(bnds) v = data["vertices"] triangles = earcut.earcut(v, data["holes"], data["dimensions"]) vertices = [QgsPointXY(v[2 * i], v[2 * i + 1]) for i in triangles] for i in range(0, len(vertices), 3): yield [vertices[i:i + 3]] def segmentizeBoundaries(self, geom): """geom: QgsGeometry (polygon or multi-polygon)""" xmin, ymax = (self.xmin, self.ymax) xres, yres = (self.xres, self.yres) z_func = self.valueOnSurface polys = [] for polygon in PolygonGeometry.nestedPointXYList(geom): rings = QgsMultiLineString() for i, bnd in enumerate(polygon): if GeometryUtils.isClockwise(bnd) ^ (i > 0): # xor bnd.reverse() # outer boundary should be ccw. inner boundaries should be cw. ring = QgsLineString() v = bnd[0] # QgsPointXY x0, y0 = (v.x(), v.y()) nx0 = (x0 - xmin) / xres ny0 = (ymax - y0) / yres ns0 = abs(ny0 + nx0) for v in bnd[1:]: x1, y1 = (v.x(), v.y()) nx1 = (x1 - xmin) / xres ny1 = (ymax - y1) / yres ns1 = abs(ny1 + nx1) p = set([0]) for v0, v1 in [[nx0, nx1], [ny0, ny1], [ns0, ns1]]: k = ceil(min(v0, v1)) n = floor(max(v0, v1)) for j in range(k, n + 1): p.add((j - v0) / (v1 - v0)) if 1 in p: p.remove(1) for m in sorted(p): x = x0 + (x1 - x0) * m y = y0 + (y1 - y0) * m ring.addVertex(QgsPoint(x, y, z_func(x, y))) x0, y0 = (x1, y1) nx0, ny0, ns0 = (nx1, ny1, ns1) ring.addVertex(QgsPoint(x0, y0, z_func(x0, y0))) # last vertex rings.addGeometry(ring) polys.append(QgsGeometry(rings)) return polys def value(self, x, y): return self.values[x + y * (self.x_segments + 1)] def valueOnSurface(self, x, y): x = (x - self.xmin) / self.width y = (y - self.ymin) / self.height if x < 0 or 1 < x or y < 0 or 1 < y: return None mx = x * self.x_segments my = (1 - y) * self.y_segments # inverted. top is 0. mx0 = floor(mx) my0 = floor(my) sdx = mx - mx0 sdy = my - my0 if mx0 == self.x_segments: # on right edge mx0 -= 1 sdx = 1 if my0 == self.y_segments: # on bottom edge my0 -= 1 sdy = 1 z0, z1 = (self.value(mx0, my0), self.value(mx0 + 1, my0)) z2, z3 = (self.value(mx0, my0 + 1), self.value(mx0 + 1, my0 + 1)) if sdx <= sdy: return z0 + (z1 - z0) * sdx + (z2 - z0) * sdy return z3 + (z2 - z3) * (1 - sdx) + (z1 - z3) * (1 - sdy)
class PointTool(QgsMapToolEdit): ''' Implementation of interactions of the user with the main map. Will called every time the user clicks on the map or hovers the mouse over the map. ''' def deactivate(self): QgsMapTool.deactivate(self) self.deactivated.emit() def __init__(self, canvas, iface, turn_off_snap, smooth=False): ''' canvas - link to the QgsCanvas of the application iface - link to the Qgis Interface turn_off_snap - flag sets snapping to the nearest color smooth - flag sets smoothing of the traced path ''' self.iface = iface # list of Anchors for current line self.anchors = [] # for keeping track of mouse event for rubber band updating self.last_mouse_event_pos = None self.tracing_mode = TracingModes.PATH self.turn_off_snap = turn_off_snap self.smooth_line = smooth # possible variants: gray_diff, as_is, color_diff (using v from hsv) self.grid_conversion = "gray_diff" # QApplication.restoreOverrideCursor() # QApplication.setOverrideCursor(Qt.CrossCursor) QgsMapToolEmitPoint.__init__(self, canvas) self.rlayer = None self.grid_changed = None self.snap_tolerance = None # snap to color self.snap2_tolerance = None # snap to itself self.vlayer = None self.grid = None self.sample = None self.tracking_is_active = False # False = not a polygon self.rubber_band = QgsRubberBand(self.canvas(), False) self.markers = [] self.marker_snap = QgsVertexMarker(self.canvas()) self.marker_snap.setColor(QColor(255, 0, 255)) self.find_path_task = None self.change_state(WaitingFirstPointState) self.last_vlayer = None def display_message( self, title, message, level='Info', duration=2, ): ''' Shows message bar to the user. `level` receives one of four possible string values: Info, Warning, Critical, Success ''' LEVELS = { 'Info': Qgis.Info, 'Warning': Qgis.Warning, 'Critical': Qgis.Critical, 'Success': Qgis.Success, } self.iface.messageBar().pushMessage(title, message, LEVELS[level], duration) def change_state(self, state): self.state = state(self) def snap_tolerance_changed(self, snap_tolerance): self.snap_tolerance = snap_tolerance if snap_tolerance is None: self.marker_snap.hide() else: self.marker_snap.show() def snap2_tolerance_changed(self, snap_tolerance): self.snap2_tolerance = snap_tolerance**2 # if snap_tolerance is None: # self.marker_snap.hide() # else: # self.marker_snap.show() def trace_color_changed(self, color): r, g, b = self.sample if color is False: self.grid_changed = None else: r0, g0, b0, t = color.getRgb() self.grid_changed = np.abs((r0 - r)**2 + (g0 - g)**2 + (b0 - b)**2) def get_current_vector_layer(self): try: vlayer = self.iface.layerTreeView().selectedLayers()[0] if isinstance(vlayer, QgsVectorLayer): if vlayer.wkbType() == QgsWkbTypes.MultiLineString: # if self.last_vlayer: # if vlayer != self.last_vlayer: # self.create_spatial_index_for_vlayer(vlayer) # else: # self.create_spatial_index_for_vlayer(vlayer) # self.last_vlayer = vlayer return vlayer else: self.display_message( " ", "The active layer must be" + " a MultiLineString vector layer", level='Warning', duration=2, ) return None else: self.display_message( "Missing Layer", "Please select vector layer to draw", level='Warning', duration=2, ) return None except IndexError: self.display_message( "Missing Layer", "Please select vector layer to draw", level='Warning', duration=2, ) return None def raster_layer_has_changed(self, raster_layer): self.rlayer = raster_layer if self.rlayer is None: self.display_message( "Missing Layer", "Please select raster layer to trace", level='Warning', duration=2, ) return try: sample, to_indexes, to_coords, to_coords_provider, \ to_coords_provider2 = \ get_whole_raster(self.rlayer, QgsProject.instance(), ) except PossiblyIndexedImageError: self.display_message( "Missing Layer", "Can't trace indexed or gray image", level='Critical', duration=2, ) return r = sample[0].astype(float) g = sample[1].astype(float) b = sample[2].astype(float) where_are_NaNs = np.isnan(r) r[where_are_NaNs] = 0 where_are_NaNs = np.isnan(g) g[where_are_NaNs] = 0 where_are_NaNs = np.isnan(b) b[where_are_NaNs] = 0 self.sample = (r, g, b) self.grid = r + g + b self.to_indexes = to_indexes self.to_coords = to_coords self.to_coords_provider = to_coords_provider self.to_coords_provider2 = to_coords_provider2 def remove_last_anchor_point(self, undo_edit=True, redraw=True): ''' Removes last anchor point and last marker point ''' # check if we have at least one feature to delete vlayer = self.get_current_vector_layer() if vlayer is None: return if vlayer.featureCount() < 1: return # remove last marker if self.markers: last_marker = self.markers.pop() self.canvas().scene().removeItem(last_marker) # remove last anchor if self.anchors: self.anchors.pop() if undo_edit: # it's a very ugly way of triggering single undo event self.iface.editMenu().actions()[0].trigger() if redraw: self.update_rubber_band() self.redraw() def keyPressEvent(self, e): if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_B: # delete last segment if backspace is pressed self.remove_last_anchor_point() elif e.key() == Qt.Key_A: # change tracing mode self.tracing_mode = self.tracing_mode.next() self.update_rubber_band() elif e.key() == Qt.Key_S: # toggle snap mode self.turn_off_snap() elif e.key() == Qt.Key_Escape: # Abort tracing process self.abort_tracing_process() def add_anchor_points(self, x1, y1, i1, j1): ''' Adds anchor points and markers to self. ''' anchor = Anchor(x1, y1, i1, j1) self.anchors.append(anchor) marker = QgsVertexMarker(self.canvas()) marker.setCenter(QgsPointXY(x1, y1)) self.markers.append(marker) def trace_over_image(self, start, goal, do_it_as_task=False, vlayer=None): ''' performs tracing ''' i0, j0 = start i1, j1 = goal r, g, b, = self.sample try: r0 = r[i1, j1] g0 = g[i1, j1] b0 = b[i1, j1] except IndexError: raise OutsideMapError if self.grid_changed is None: grid = np.abs((r0 - r)**2 + (g0 - g)**2 + (b0 - b)**2) else: grid = self.grid_changed if do_it_as_task: # dirty hack to avoid QGIS crashing self.find_path_task = FindPathTask( grid.astype(np.dtype('l')), start, goal, self.draw_path, vlayer, ) QgsApplication.taskManager().addTask(self.find_path_task, ) self.tracking_is_active = True else: path, cost = FindPathFunction( grid.astype(np.dtype('l')), (i0, j0), (i1, j1), ) return path, cost def trace(self, x1, y1, i1, j1, vlayer): ''' Traces path from last point to given point. In case tracing is inactive just creates straight line. ''' if self.tracing_mode.is_tracing(): if self.snap_tolerance is not None: try: i1, j1 = self.snap(i1, j1) except OutsideMapError: return _, _, i0, j0 = self.anchors[-2] start_point = i0, j0 end_point = i1, j1 try: self.trace_over_image(start_point, end_point, do_it_as_task=True, vlayer=vlayer) except OutsideMapError: pass else: self.draw_path( None, vlayer, was_tracing=False, x1=x1, y1=y1, ) def snap_to_itself(self, x, y, sq_tolerance=1): ''' finds a nearest segment line to the current vlayer ''' pt = QgsPointXY(x, y) # nearest_feature_id = self.spIndex.nearestNeighbor(pt, 1, tolerance)[0] vlayer = self.get_current_vector_layer() # feature = vlayer.getFeature(nearest_feature_id) for feature in vlayer.getFeatures(): closest_point, _, _, _, sq_distance = feature.geometry( ).closestVertex(pt) if sq_distance < sq_tolerance: return closest_point.x(), closest_point.y() return x, y def snap(self, i, j): if self.snap_tolerance is None: return i, j if not self.tracing_mode.is_tracing(): return i, j if self.grid_changed is None: return i, j size_i, size_j = self.grid.shape size = self.snap_tolerance if i < size or j < size or i + size > size_i or j + size > size_j: raise OutsideMapError grid_small = self.grid_changed grid_small = grid_small[i - size:i + size, j - size:j + size] smallest_cells = np.where(grid_small == np.amin(grid_small)) coordinates = list(zip(smallest_cells[0], smallest_cells[1])) if len(coordinates) == 1: delta_i, delta_j = coordinates[0] delta_i -= size delta_j -= size else: # find the closest to the center deltas = [(i - size, j - size) for i, j in coordinates] lengths = [(i**2 + j**2) for i, j in deltas] i = lengths.index(min(lengths)) delta_i, delta_j = deltas[i] return i + delta_i, j + delta_j def canvasReleaseEvent(self, mouseEvent): ''' Method where the actual tracing is performed after the user clicked on the map ''' vlayer = self.get_current_vector_layer() if vlayer is None: return if not vlayer.isEditable(): self.display_message( "Edit mode", "Please begin editing vector layer to trace", level='Warning', duration=2, ) return if self.rlayer is None: self.display_message( "Missing Layer", "Please select raster layer to trace", level='Warning', duration=2, ) return if mouseEvent.button() == Qt.RightButton: self.state.click_rmb(mouseEvent, vlayer) elif mouseEvent.button() == Qt.LeftButton: self.state.click_lmb(mouseEvent, vlayer) return def draw_path(self, path, vlayer, was_tracing=True,\ x1=None, y1=None): ''' Draws a path after tracer found it. ''' transform = QgsCoordinateTransform(QgsProject.instance().crs(), vlayer.crs(), QgsProject.instance()) if was_tracing: if self.smooth_line: path = smooth(path, size=5) path = simplify(path) vlayer = self.get_current_vector_layer() current_last_point = self.to_coords(*path[-1]) path_ref = [ transform.transform(*self.to_coords_provider(i, j)) for i, j in path ] x0, y0, _, _ = self.anchors[-2] last_point = transform.transform(*self.to_coords_provider2(x0, y0)) path_ref = [last_point] + path_ref[1:] else: x0, y0, _i, _j = self.anchors[-2] current_last_point = (x1, y1) path_ref = [ transform.transform(*self.to_coords_provider2(x0, y0)), transform.transform(*self.to_coords_provider2(x1, y1)) ] self.ready = False if len(self.anchors) == 2: vlayer.beginEditCommand("Adding new line") add_feature_to_vlayer(vlayer, path_ref) vlayer.endEditCommand() else: vlayer.beginEditCommand("Adding new segment to the line") add_to_last_feature(vlayer, path_ref) vlayer.endEditCommand() _, _, current_last_point_i, current_last_point_j = self.anchors[-1] self.anchors[-1] = current_last_point[0], current_last_point[ 1], current_last_point_i, current_last_point_j self.redraw() self.tracking_is_active = False def update_rubber_band(self): # this is very ugly but I can't make another way if self.last_mouse_event_pos is None: return if not self.anchors: return x0, y0, _, _ = self.anchors[-1] qgsPoint = self.toMapCoordinates(self.last_mouse_event_pos) x1, y1 = qgsPoint.x(), qgsPoint.y() points = [QgsPoint(x0, y0), QgsPoint(x1, y1)] self.rubber_band.setColor(QColor(255, 0, 0)) self.rubber_band.setWidth(3) self.rubber_band.setLineStyle( RUBBERBAND_LINE_STYLES[self.tracing_mode], ) vlayer = self.get_current_vector_layer() if vlayer is None: return self.rubber_band.setToGeometry( QgsGeometry.fromPolyline(points), self.vlayer, ) def canvasMoveEvent(self, mouseEvent): ''' Store the mouse position for the correct updating of the rubber band ''' # we need at least one point to draw if not self.anchors: return if self.snap_tolerance is not None and self.tracing_mode.is_tracing(): qgsPoint = self.toMapCoordinates(mouseEvent.pos()) x1, y1 = qgsPoint.x(), qgsPoint.y() # i, j = get_indxs_from_raster_coords(self.geo_ref, x1, y1) i, j = self.to_indexes(x1, y1) try: i1, j1 = self.snap(i, j) except OutsideMapError: return # x1, y1 = get_coords_from_raster_indxs(self.geo_ref, i1, j1) x1, y1 = self.to_coords(i1, j1) self.marker_snap.setCenter(QgsPointXY(x1, y1)) self.last_mouse_event_pos = mouseEvent.pos() self.update_rubber_band() self.redraw() def abort_tracing_process(self): ''' Terminate background process of tracing raster after the user hits Esc. ''' # check if we have any tasks if self.find_path_task is None: return self.tracking_is_active = False try: # send terminate signal to the task self.find_path_task.cancel() self.find_path_task = None except RuntimeError: return else: self.remove_last_anchor_point(undo_edit=False, ) def redraw(self): # If caching is enabled, a simple canvas refresh might not be # sufficient to trigger a redraw and you must clear the cached image # for the layer if self.iface.mapCanvas().isCachingEnabled(): vlayer = self.get_current_vector_layer() if vlayer is None: return vlayer.triggerRepaint() self.iface.mapCanvas().refresh() QgsApplication.processEvents() def pan(self, x, y): ''' Move the canvas to the x, y position ''' currExt = self.iface.mapCanvas().extent() canvasCenter = currExt.center() dx = x - canvasCenter.x() dy = y - canvasCenter.y() xMin = currExt.xMinimum() + dx xMax = currExt.xMaximum() + dx yMin = currExt.yMinimum() + dy yMax = currExt.yMaximum() + dy newRect = QgsRectangle(xMin, yMin, xMax, yMax) self.iface.mapCanvas().setExtent(newRect) def add_last_feature_to_spindex(self, vlayer): ''' Adds last feature to spatial index ''' features = list(vlayer.getFeatures()) last_feature = features[-1] self.spIndex.insertFeature(last_feature) def create_spatial_index_for_vlayer(self, vlayer): ''' Creates spatial index for the vlayer ''' self.spIndex = QgsSpatialIndex() # features = [f for f in vlayer] self.spIndex.addFeatures(vlayer.getFeatures())
class Generate: # for test purporses SHOW_WARNINGS = True def __init__(self, iface): self.iface = iface self.lyr = False # reference layer to get extent self.dir_path = '' # path to catalog with ref layer self.atlas = False # QgsVectorLayer with atlas self.atlasPr = False # dataProvider self.line = False # QgsVectorLayer with lilne to enumerate panes self.linePr = False # dataProvider self.gsize = [0, 0] # size of pane on the ground withou scale self.inter_only = False # generate only panes that intersect with lyr self.si = QgsSpatialIndex() # spatial index from self.lyr def generate(self): """Perform all steps and generate layers with atlas""" if not self.choose_reference_layer(): return if not self.check_crs_in_meters(): return self.get_data_from_user() self.create_atlas_layer() self.generate_panes() self.save_layers() def check_crs_in_meters(self): """check if layer source crs is in meters, other way it atlas will be generated incorectly""" if self.lyr.sourceCrs().mapUnits() != 0: self.show_warning( 'Layer crs map unit is diffret from meters, Aborting') return False return True def get_data_from_user(self): """Show dialog to user what size of atlas should be generated""" self.dlg = Dialog() self.dlg.exec_() # get calculated size on ground from dialog self.gsize = self.dlg.ground_size self.inter_only = self.dlg.ui.checkBox_inter.isChecked() def choose_reference_layer(self, lyr=False): """ Point to reference layer to get it boundingBox, it should be currently selected layer there is an option to point to layer - testing """ if lyr is False: self.lyr = self.iface.activeLayer() else: self.lyr = lyr if self.lyr in [False, None]: self.show_warning('No suitable layer!') self.lyr = False return False # check if layer is valid if not self.lyr.isValid(): self.show_warning('Not valid layer, choose another!') self.lyr = False return False try: self.dir_path = os.path.dirname( self.lyr.dataProvider().dataSourceUri().split("|")[0]) except Exception: return False return True def create_atlas_layer(self): """Creates atlas layer and save it do disk in catalog with referenced layer """ if self.lyr is False: return False # check what kind of crs is defined in layer crs = self.lyr.sourceCrs().authid() if 'EPSG:' not in crs: crs_txt = '' else: crs_txt = 'crs=' + crs + '&' self.atlas = QgsVectorLayer("Polygon?" + crs_txt + "index=yes", "ATLAS_AFT", "memory") self.atlasPr = self.atlas.dataProvider() self.atlas.startEditing() self.atlasPr.addAttributes([ QgsField("SITE", QVariant.Int), QgsField("Left", QVariant.Int), QgsField("Up", QVariant.Int), QgsField("Right", QVariant.Int), QgsField("Down", QVariant.Int), QgsField("ISSUES", QVariant.String, len=50), ]) self.atlas.updateFields() self.atlas.commitChanges() # Generate line vector layer to easy show order and calculate order # from vertex of Line self.line = QgsVectorLayer("LineString?" + crs_txt + "&index=yes", "LineAtlas", "memory") self.linePr = self.line.dataProvider() def generate_si(self): """Generate Spatial index from original layer""" if self.lyr.geometryType() in [ QgsWkbTypes.Polygon, QgsWkbTypes.PolygonGeometry, QgsWkbTypes.MultiPolygon ]: self.si.addFeatures(self.lyr.getFeatures()) else: # if geometry type other than poly, omit spatial index self.inter_only = False def generate_panes(self): """Generate panes of atlas to cover all extent of reference layer if layer is Polygon or Multipolygon type it is option to generate panes only there panes overlap with references """ # pobierz rozmiary warstwy którą będziemy atlasować xmin = self.lyr.extent().xMinimum() xmax = self.lyr.extent().xMaximum() ymin = self.lyr.extent().yMinimum() ymax = self.lyr.extent().yMaximum() # generate SpatialIndex only if checkbox in checked and reference layer # is at certain type (poly, multipoly) if self.inter_only: self.generate_si() x = xmin y = ymin panes_poly = [] ita = 0 # no more than 999 tile in any direction!!! while x < xmax and ita < 999: while y < ymax and ita < 999: f = self.generate_pane(x, y) if f is not False: panes_poly.append(f) y += self.gsize[1] ita += 1 x += self.gsize[0] ita += 1 y = ymin self.atlas.startEditing() self.atlas.addFeatures(panes_poly) self.atlas.commitChanges() def generate_pane(self, x, y): """generate one pane of atlas in given x,y position if spatialindex is added there will be returning False if pane not intersect with SI """ poly = [ QgsPointXY(x, y), QgsPointXY(x, y + self.gsize[1]), QgsPointXY(x + self.gsize[0], y + self.gsize[1]), QgsPointXY(x + self.gsize[0], y), QgsPointXY(x, y), ] g = QgsGeometry().fromPolygonXY([poly]) # if SpatialIndex not intersects with generated geometry, return False if self.inter_only: if len(self.si.intersects(g.boundingBox())) == 0: return False f = QgsFeature() f.setFields(self.atlas.fields()) f.setGeometry(g) return f def save_layers(self): """Save atlas layer to disk, and add atlas and line vector layer to TOC """ crs = self.lyr.sourceCrs() QgsVectorFileWriter.writeAsVectorFormat( self.atlas, os.path.join(os.path.join(self.dir_path, "ATLAS_AFT.shp")), "UTF-8", crs, "ESRI Shapefile") self.atlasF = QgsVectorLayer( os.path.join(self.dir_path, "ATLAS_AFT.shp"), "ATLAS_AFT", "ogr") QgsProject.instance().addMapLayer(self.atlasF) QgsProject.instance().addMapLayer(self.line) plugin_dir = os.path.dirname(__file__) self.atlasF.loadNamedStyle( os.path.join(plugin_dir, '..', 'qml', 'ATLAS_AFT_check.qml')) self.line.loadNamedStyle( os.path.join(plugin_dir, '..', 'qml', 'ATLAS_LINE.qml')) self.show_success() def show_success(self): # show confirmation to user on message bar if not self.SHOW_WARNINGS: return self.iface.messageBar().pushMessage("OK", 'Atlas Panes generated correctly', Qgis.Success, 5) def show_warning(self, text): """Show warning to user if something is wrong""" if not self.SHOW_WARNINGS: return message = QMessageBox() message.setIcon(QMessageBox.Information) message.setWindowTitle('Błąd') message.setText(text) message.addButton("Close", QMessageBox.ActionRole) message.exec_()