def delete_invalid_geometries(self, query_small_area=lambda feat: True): """ Delete invalid geometries. Test if any of it acute angle vertex could be deleted. Also removes zig-zag and spike vertex (see Point.get_spike_context). """ if log.app_level <= logging.DEBUG: debshp = DebugWriter("debug_notvalid.shp", self, QgsFields(), WKBPolygon) debshp2 = DebugWriter("debug_spikes.shp", self) to_change = {} to_clean = [] to_move = {} parts = 0 rings = 0 zz = 0 spikes = 0 geometries = {} msg = _("Delete invalid geometries") pbar = self.get_progressbar(msg, len(geometries)) for feat in self.getFeatures(): fid = feat.id() geom = feat.geometry() badgeom = False pn = 0 for polygon in Geometry.get_multipolygon(geom): f = QgsFeature(QgsFields()) g = Geometry.fromPolygonXY(polygon) if g.area() < config.min_area and query_small_area(feat): parts += 1 geom.deletePart(pn) to_change[fid] = geom f.setGeometry(QgsGeometry(g)) if log.app_level <= logging.DEBUG: debshp.addFeature(f) continue pn += 1 for i, ring in enumerate(polygon): if badgeom: break skip = False for n, v in enumerate(ring[0:-1]): ( angle_v, angle_a, ndx, ndxa, is_acute, is_zigzag, is_spike, vx, ) = Point(v).get_spike_context(geom) if skip or not is_acute: skip = False continue g = Geometry.fromPolygonXY([ring]) f.setGeometry(QgsGeometry(g)) g.deleteVertex(n) if not g.isGeosValid() or g.area() < config.min_area: if i > 0: rings += 1 geom.deleteRing(i) to_change[fid] = geom if log.app_level <= logging.DEBUG: debshp.addFeature(f) else: badgeom = True to_clean.append(fid) if log.app_level <= logging.DEBUG: debshp.addFeature(f) break if len(ring) > 4: # (can delete vertexs) va = Point(geom.vertexAt(ndxa)) if is_zigzag: g = QgsGeometry(geom) if ndxa > ndx: g.deleteVertex(ndxa) g.deleteVertex(ndx) skip = True else: g.deleteVertex(ndx) g.deleteVertex(ndxa) valid = g.isGeosValid() if valid: geom = g zz += 1 to_change[fid] = g if log.app_level <= logging.DEBUG: debshp2.add_point( va, "zza %d %d %d %f" % (fid, ndx, ndxa, angle_a), ) debshp2.add_point( v, "zz %d %d %d %s" % (fid, ndx, len(ring), valid), ) elif is_spike: g = QgsGeometry(geom) to_move[va] = vx g.moveVertex(vx.x(), vx.y(), ndxa) g.deleteVertex(ndx) valid = g.isGeosValid() if valid: spikes += 1 skip = ndxa > ndx geom = g to_change[fid] = g if log.app_level <= logging.DEBUG: debshp2.add_point(vx, "vx %d %d" % (fid, ndx)) debshp2.add_point( va, "va %d %d %d %f" % (fid, ndx, ndxa, angle_a)) debshp2.add_point( v, "v %d %d %d %s" % (fid, ndx, len(ring), valid), ) geometries[fid] = geom if geom.area() < config.min_area and query_small_area(feat): to_clean.append(fid) if fid in to_change: del to_change[fid] pbar.update() pbar.close() if to_move: for fid, geom in geometries.items(): if fid in to_clean: continue n = 0 v = Point(geom.vertexAt(n)) while v.x() != 0 or v.y() != 0: if v in to_move: g = QgsGeometry(geom) vx = to_move[v] if log.app_level <= logging.DEBUG: debshp2.add_point(v, "mv %d %d" % (fid, n)) debshp2.add_point(vx, "mvx %d %d" % (fid, n)) g.moveVertex(vx.x(), vx.y(), n) if g.isGeosValid(): geom = g to_change[fid] = g n += 1 v = Point(geom.vertexAt(n)) if to_change: self.writer.changeGeometryValues(to_change) if parts: msg = _("Deleted %d invalid part geometries in the '%s' layer") log.debug(msg, parts, self.name()) report.values["geom_parts_" + self.name()] = parts if rings: msg = _("Deleted %d invalid ring geometries in the '%s' layer") log.debug(msg, rings, self.name()) report.values["geom_rings_" + self.name()] = rings if to_clean: self.writer.deleteFeatures(to_clean) msg = _("Deleted %d invalid geometries in the '%s' layer") log.debug(msg, len(to_clean), self.name()) report.values["geom_invalid_" + self.name()] = len(to_clean) if zz: msg = _("Deleted %d zig-zag vertices in the '%s' layer") log.debug(msg, zz, self.name()) report.values["vertex_zz_" + self.name()] = zz if spikes: msg = _("Deleted %d spike vertices in the '%s' layer") log.debug(msg, spikes, self.name()) report.values["vertex_spike_" + self.name()] = spikes
def topology(self): """Add to nearest segments each vertex in a polygon layer.""" threshold = self.dist_thr # Distance threshold to create nodes dup_thr = self.dup_thr straight_thr = self.straight_thr tp = 0 td = 0 if log.app_level <= logging.DEBUG: debshp = DebugWriter("debug_topology.shp", self) geometries = { f.id(): QgsGeometry(f.geometry()) for f in self.getFeatures() } index = self.get_index() to_change = {} nodes = set() pbar = self.get_progressbar(_("Topology"), len(geometries)) for (gid, geom) in geometries.items(): if geom.area() < config.min_area: continue for point in frozenset(Geometry.get_outer_vertices(geom)): if point not in nodes: area_of_candidates = Point(point).boundingBox(threshold) fids = index.intersects(area_of_candidates) for fid in fids: g = QgsGeometry(geometries[fid]) (p, ndx, ndxa, ndxb, dist_v) = g.closestVertex(point) (dist_s, closest, vertex) = g.closestSegmentWithContext(point)[:3] va = Point(g.vertexAt(ndxa)) vb = Point(g.vertexAt(ndxb)) note = "" if dist_v == 0: dist_a = va.sqrDist(point) dist_b = vb.sqrDist(point) if dist_a < dup_thr**2: g.deleteVertex(ndxa) note = "dupe refused by isGeosValid" if Geometry.is_valid(g): note = "Merge dup. %.10f %.5f,%.5f->%.5f,%.5f" % ( dist_a, va.x(), va.y(), point.x(), point.y(), ) nodes.add(p) nodes.add(va) td += 1 if dist_b < dup_thr**2: g.deleteVertex(ndxb) note = "dupe refused by isGeosValid" if Geometry.is_valid(g): note = "Merge dup. %.10f %.5f,%.5f->%.5f,%.5f" % ( dist_b, vb.x(), vb.y(), point.x(), point.y(), ) nodes.add(p) nodes.add(vb) td += 1 elif dist_v < dup_thr**2: g.moveVertex(point.x(), point.y(), ndx) note = "dupe refused by isGeosValid" if Geometry.is_valid(g): note = "Merge dup. %.10f %.5f,%.5f->%.5f,%.5f" % ( dist_v, p.x(), p.y(), point.x(), point.y(), ) nodes.add(p) td += 1 elif (dist_s < threshold**2 and closest != va and closest != vb): va = Point(g.vertexAt(vertex)) vb = Point(g.vertexAt(vertex - 1)) angle = abs(point.azimuth(va) - point.azimuth(vb)) note = "Topo refused by angle: %.2f" % angle if abs(180 - angle) <= straight_thr: note = "Topo refused by insertVertex" if g.insertVertex(point.x(), point.y(), vertex): note = "Topo refused by isGeosValid" if Geometry.is_valid(g): note = "Add topo %.6f %.5f,%.5f" % ( dist_s, point.x(), point.y(), ) tp += 1 if note.startswith("Merge") or note.startswith("Add"): to_change[fid] = g geometries[fid] = g if note and log.app_level <= logging.DEBUG: debshp.add_point(point, note) if len(to_change) > BUFFER_SIZE: self.writer.changeGeometryValues(to_change) to_change = {} pbar.update() pbar.close() if len(to_change) > 0: self.writer.changeGeometryValues(to_change) if td: log.debug(_("Merged %d close vertices in the '%s' layer"), td, self.name()) report.values["vertex_close_" + self.name()] = td if tp: log.debug(_("Created %d topological points in the '%s' layer"), tp, self.name()) report.values["vertex_topo_" + self.name()] = tp