def add_geom_columns(context, layer: QgsVectorLayer) -> None: """ Add latitude and longitude columns in the layer. """ fields = [ QgsField('longitude', type=QVariant.Double), QgsField('latitude', type=QVariant.Double), ] transform = QgsCoordinateTransform( layer.crs(), QgsCoordinateReferenceSystem('EPSG:4326'), context.project()) with edit(layer): for field in fields: layer.addAttribute(field) request = QgsFeatureRequest() request.setSubsetOfAttributes(['latitude', 'longitude'], layer.fields()) for feature in layer.getFeatures(request): geom = QgsGeometry(feature.geometry()) if not geom: continue geom.transform(transform) geom = geom.centroid().asPoint() feature.setAttribute('longitude', geom.x()) feature.setAttribute('latitude', geom.y()) layer.updateFeature(feature)
def translateCenterGeom(self, g: QgsGeometry, target: QgsGeometry): """Translate a geometry to the center another geometry :param g QgsGeometry: Geometry that be translated :param target QgsGeometry: The target geometry """ # duplicating g_new = QgsGeometry(g) # print("g duplicated") target_new = QgsGeometry(target) # print("target duplicated") c = target_new.centroid().asQPointF() # print("centroid created") c2 = g_new.centroid().asQPointF() # print("centroid created") transX = c.x() - c2.x() # print("translate x calculated") transY = c.y() - c2.y() # print("translate y calculated") g.translate(transX, transY) # print("g translated") return g
def processAlgorithm( self, # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), QgsWkbTypes.LineString, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) roundabout_expression_string = self.parameterAsExpression( parameters, self.EXPRESSION, context) # step 1 - find all roundabouts exp = QgsExpression(roundabout_expression_string) expression_context = self.createExpressionContext( parameters, context, source) exp.prepare(expression_context) roundabouts = [] not_roundabouts = {} not_roundabout_index = QgsSpatialIndex() total = 10.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() _id = 1 for current, feature in enumerate(features): if feedback.isCanceled(): break def add_feature(f, _id, geom, is_roundabout): output_feature = QgsFeature(f) output_feature.setGeometry(geom) output_feature.setId(_id) if is_roundabout: roundabouts.append(output_feature) else: not_roundabouts[output_feature.id()] = output_feature not_roundabout_index.addFeature(output_feature) expression_context.setFeature(feature) is_roundabout = exp.evaluate(expression_context) if not feature.geometry().wkbType() == QgsWkbTypes.LineString: geom = feature.geometry() for p in geom.parts(): add_feature(feature, _id, QgsGeometry(p.clone()), is_roundabout) _id += 1 else: add_feature(feature, _id, feature.geometry(), is_roundabout) _id += 1 # Update the progress bar feedback.setProgress(int(current * total)) feedback.pushInfo( self.tr('Found {} roundabout parts'.format(len(roundabouts)))) feedback.pushInfo( self.tr('Found {} not roundabouts'.format(len(not_roundabouts)))) if feedback.isCanceled(): return {self.OUTPUT: dest_id} all_roundabouts = QgsGeometry.unaryUnion( [r.geometry() for r in roundabouts]) feedback.setProgress(20) all_roundabouts = all_roundabouts.mergeLines() feedback.setProgress(25) total = 70.0 / all_roundabouts.constGet().numGeometries( ) if all_roundabouts.isMultipart() else 1 for current, roundabout in enumerate(all_roundabouts.parts()): touching = not_roundabout_index.intersects( roundabout.boundingBox()) if not touching: continue if feedback.isCanceled(): break roundabout_engine = QgsGeometry.createGeometryEngine(roundabout) roundabout_engine.prepareGeometry() roundabout_geom = QgsGeometry(roundabout.clone()) roundabout_centroid = roundabout_geom.centroid() other_points = [] # find all touching roads, and move the touching part to the centroid for t in touching: touching_geom = not_roundabouts[t].geometry() touching_road = touching_geom.constGet().clone() if not roundabout_engine.touches(touching_road): # print('not touching!!') continue # work out if start or end of line touched the roundabout nearest = roundabout_geom.nearestPoint(touching_geom) _, v = touching_geom.closestVertexWithContext( nearest.asPoint()) if v == 0: # started at roundabout other_points.append((touching_road.endPoint(), True, t)) else: # ended at roundabout other_points.append((touching_road.startPoint(), False, t)) if not other_points: continue # see if any incoming segments originate at the same place ("V" patterns) averaged = set() for point1, started_at_roundabout1, id1 in other_points: if id1 in averaged: continue if feedback.isCanceled(): break parts_to_average = [id1] for point2, _, id2 in other_points: if id2 == id1: continue if point2 != point1: # todo tolerance? continue parts_to_average.append(id2) if len(parts_to_average) == 1: # not a <O pattern, just a round coming straight to the roundabout line = not_roundabouts[id1].geometry().constGet().clone() if started_at_roundabout1: # extend start of line to roundabout centroid line.moveVertex(QgsVertexId(0, 0, 0), roundabout_centroid.constGet()) else: # extend end of line to roundabout centroid line.moveVertex( QgsVertexId(0, 0, line.numPoints() - 1), roundabout_centroid.constGet()) not_roundabout_index.deleteFeature( not_roundabouts[parts_to_average[0]]) not_roundabouts[parts_to_average[0]].setGeometry( QgsGeometry(line)) not_roundabout_index.addFeature( not_roundabouts[parts_to_average[0]]) elif len(parts_to_average) == 2: # <O pattern src_part, other_part = parts_to_average # pylint: disable=unbalanced-tuple-unpacking averaged.add(src_part) averaged.add(other_part) averaged_line = GeometryUtils.average_linestrings( not_roundabouts[src_part].geometry().constGet(), not_roundabouts[other_part].geometry().constGet()) if started_at_roundabout1: # extend start of line to roundabout centroid averaged_line.moveVertex( QgsVertexId(0, 0, 0), roundabout_centroid.constGet()) else: # extend end of line to roundabout centroid averaged_line.moveVertex( QgsVertexId(0, 0, averaged_line.numPoints() - 1), roundabout_centroid.constGet()) not_roundabout_index.deleteFeature( not_roundabouts[src_part]) not_roundabouts[src_part].setGeometry( QgsGeometry(averaged_line)) not_roundabout_index.addFeature(not_roundabouts[src_part]) not_roundabout_index.deleteFeature( not_roundabouts[other_part]) del not_roundabouts[other_part] feedback.setProgress(25 + int(current * total)) total = 5.0 / len(not_roundabouts) current = 0 for _, f in not_roundabouts.items(): if feedback.isCanceled(): break sink.addFeature(f, QgsFeatureSink.FastInsert) current += 1 feedback.setProgress(95 + int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): if DEBUG_MODE: logMessage( "processAlgorithm(): {}".format(self.__class__.__name__), False) clayer = self.parameterAsLayer(parameters, self.INPUT, context) title_field = self.parameterAsString(parameters, self.TITLE_FIELD, context) cf_filter = self.parameterAsBool(parameters, self.CF_FILTER, context) fixed_scale = self.parameterAsEnum(parameters, self.SCALE, context) # == 1 buf = self.parameterAsDouble(parameters, self.BUFFER, context) tex_width = self.parameterAsInt(parameters, self.TEX_WIDTH, context) orig_tex_height = self.parameterAsInt(parameters, self.TEX_HEIGHT, context) header_exp = QgsExpression( self.parameterAsExpression(parameters, self.HEADER, context)) footer_exp = QgsExpression( self.parameterAsExpression(parameters, self.FOOTER, context)) exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.layerScope(clayer)) out_dir = self.parameterAsString(parameters, self.OUTPUT, context) if not QDir(out_dir).exists(): QDir().mkpath(out_dir) if DEBUG_MODE: openDirectory(out_dir) mapSettings = self.controller.settings.mapSettings baseExtent = self.controller.settings.baseExtent rotation = mapSettings.rotation() orig_size = mapSettings.outputSize() if cf_filter: cf_layer = QgsMemoryProviderUtils.createMemoryLayer( "current feature", clayer.fields(), clayer.wkbType(), clayer.crs()) layers = [ cf_layer if lyr == clayer else lyr for lyr in mapSettings.layers() ] mapSettings.setLayers(layers) doc = QDomDocument("qgis") clayer.exportNamedStyle(doc) cf_layer.importNamedStyle(doc) total = clayer.featureCount() for current, feature in enumerate(clayer.getFeatures()): if feedback.isCanceled(): break if cf_filter: cf_layer.startEditing() cf_layer.deleteFeatures( [f.id() for f in cf_layer.getFeatures()]) cf_layer.addFeature(feature) cf_layer.commitChanges() title = feature.attribute(title_field) feedback.setProgressText("({}/{}) Exporting {}...".format( current + 1, total, title)) logMessage("Exporting {}...".format(title), False) # extent geometry = QgsGeometry(feature.geometry()) geometry.transform(self.transform) center = geometry.centroid().asPoint() if fixed_scale or geometry.type() == QgsWkbTypes.PointGeometry: tex_height = orig_tex_height or int( tex_width * orig_size.height() / orig_size.width()) rect = RotatedRect(center, baseExtent.width(), baseExtent.width() * tex_height / tex_width, rotation).scale(1 + buf / 100) else: geometry.rotate(rotation, center) rect = geometry.boundingBox().scaled(1 + buf / 100) center = RotatedRect.rotatePoint(rect.center(), rotation, center) if orig_tex_height: tex_height = orig_tex_height tex_ratio = tex_width / tex_height rect_ratio = rect.width() / rect.height() if tex_ratio > rect_ratio: rect = RotatedRect(center, rect.height() * tex_ratio, rect.height(), rotation) else: rect = RotatedRect(center, rect.width(), rect.width() / tex_ratio, rotation) else: # fit to buffered geometry bounding box rect = RotatedRect(center, rect.width(), rect.height(), rotation) tex_height = tex_width * rect.height() / rect.width() rect.toMapSettings(mapSettings) mapSettings.setOutputSize(QSize(tex_width, tex_height)) self.controller.settings.setMapSettings(mapSettings) # labels exp_context.setFeature(feature) self.controller.settings.setHeaderLabel( header_exp.evaluate(exp_context)) self.controller.settings.setFooterLabel( footer_exp.evaluate(exp_context)) self.export(title, out_dir, feedback) feedback.setProgress(int(current / total * 100)) if P_OPEN_DIRECTORY and not DEBUG_MODE: openDirectory(out_dir) return {}
def get_reduction_factor(self, layer, field): """Calculate the reduction factor.""" data_provider = layer.dataProvider() meta_features = [] total_area = 0.0 total_value = 0.0 if self.min_value is None: self.min_value = self.get_min_value(data_provider, field) for feature in data_provider.getFeatures(): meta_feature = CartogramFeature() geometry = QgsGeometry(feature.geometry()) area = QgsDistanceArea().measureArea(geometry) total_area += area feature_value = feature.attribute(field) if type(feature_value) is None or feature_value == 0: feature_value = self.min_value / 100 total_value += feature_value meta_feature.area = area meta_feature.value = feature_value centroid = geometry.centroid() (cx, cy) = centroid.asPoint().x(), centroid.asPoint().y() meta_feature.center_x = cx meta_feature.center_y = cy meta_features.append(meta_feature) fraction = total_area / total_value total_size_error = 0 for meta_feature in meta_features: polygon_value = meta_feature.value polygon_area = meta_feature.area if polygon_area < 0: polygon_area = 0 # this is our 'desired' area... desired_area = polygon_value * fraction # calculate radius, a zero area is zero radius radius = math.sqrt(polygon_area / math.pi) meta_feature.radius = radius if desired_area / math.pi > 0: mass = math.sqrt(desired_area / math.pi) - radius meta_feature.mass = mass else: meta_feature.mass = 0 size_error = max(polygon_area, desired_area) / \ min(polygon_area, desired_area) total_size_error += size_error average_error = total_size_error / len(meta_features) force_reduction_factor = 1 / (average_error + 1) return (meta_features, force_reduction_factor)
def get_reduction_factor(self, layer, field): """Calculate the reduction factor.""" data_provider = layer.dataProvider() meta_features = [] total_area = 0.0 total_value = 0.0 for feature in data_provider.getFeatures(): meta_feature = CartogramFeature() geometry = QgsGeometry(feature.geometry()) area = QgsDistanceArea().measure(geometry) total_area += area feature_value = feature.attribute(field) total_value += feature_value meta_feature.area = area meta_feature.value = feature_value centroid = geometry.centroid() (cx, cy) = centroid.asPoint().x(), centroid.asPoint().y() meta_feature.center_x = cx meta_feature.center_y = cy meta_features.append(meta_feature) fraction = total_area / total_value total_size_error = 0 for meta_feature in meta_features: polygon_value = meta_feature.value polygon_area = meta_feature.area if polygon_area < 0: polygon_area = 0 # this is our 'desired' area... desired_area = polygon_value * fraction # calculate radius, a zero area is zero radius radius = math.sqrt(polygon_area / math.pi) meta_feature.radius = radius if desired_area / math.pi > 0: mass = math.sqrt(desired_area / math.pi) - radius meta_feature.mass = mass else: meta_feature.mass = 0 size_error = max(polygon_area, desired_area) / \ min(polygon_area, desired_area) total_size_error += size_error average_error = total_size_error / len(meta_features) force_reduction_factor = 1 / (average_error + 1) return (meta_features, force_reduction_factor)
def in_mask(self, feature, srid=None): if feature is None: # expression overview return False if self.layer is None: return False try: # layer is not None but destroyed ? self.layer.id() except: self.reset_mask_layer() return False # mask layer empty due to unloaded memlayersaver plugin > no filtering if self.layer.featureCount() == 0: return True mask_geom, bbox = self.mask_geometry() geom = QgsGeometry(feature.geometry()) if not geom.isGeosValid(): geom = geom.buffer(0.0, 1) if geom is None: return False if srid is not None and self.layer.crs().postgisSrid() != srid: src_crs = QgsCoordinateReferenceSystem(srid) dest_crs = self.layer.crs() xform = QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance()) try: geom.transform(xform) except: # transformation error. Check layer projection. pass if geom.type() == QgsWkbTypes.PolygonGeometry: if self.parameters.polygon_mask_method == 2 and not self.has_point_on_surface: self.parameters.polygon_mask_method = 1 if self.parameters.polygon_mask_method == 0: # this method can only work when no geometry simplification is involved return (mask_geom.overlaps(geom) or mask_geom.contains(geom)) elif self.parameters.polygon_mask_method == 1: # the fastest method, but with possible inaccuracies pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains(geom.centroid()) elif self.parameters.polygon_mask_method == 2: # will always work pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains(geom.pointOnSurface()) else: return False elif geom.type() == QgsWkbTypes.LineGeometry: if self.parameters.line_mask_method == 0: return mask_geom.intersects(geom) elif self.parameters.line_mask_method == 1: return mask_geom.contains(geom) else: return False elif geom.type() == QgsWkbTypes.PointGeometry: return mask_geom.intersects(geom) else: return False
def processAlgorithm(self, parameters, context, feedback): if DEBUG_MODE: logMessage("processAlgorithm(): {}".format( self.__class__.__name__)) source = self.parameterAsSource(parameters, self.INPUT, context) source_layer = self.parameterAsLayer(parameters, self.INPUT, context) title_field = self.parameterAsString(parameters, self.TITLE_FIELD, context) cf_filter = self.parameterAsBool(parameters, self.CF_FILTER, context) fixed_scale = self.parameterAsEnum(parameters, self.SCALE, context) # == 1 buf = self.parameterAsDouble(parameters, self.BUFFER, context) tex_width = self.parameterAsInt(parameters, self.TEX_WIDTH, context) orig_tex_height = self.parameterAsInt(parameters, self.TEX_HEIGHT, context) out_dir = self.parameterAsString(parameters, self.OUTPUT, context) mapSettings = self.controller.settings.mapSettings baseExtent = self.controller.settings.baseExtent rotation = mapSettings.rotation() orig_size = mapSettings.outputSize() if cf_filter: #TODO: FIX ME #cf_layer = QgsMemoryProviderUtils.createMemoryLayer("current feature", # source_layer.fields(), # source_layer.wkbType(), # source_layer.crs()) doc = QDomDocument("qgis") source_layer.exportNamedStyle(doc) orig_layers = mapSettings.layers() total = source.featureCount() for current, feature in enumerate(source.getFeatures()): if feedback.isCanceled(): break if cf_filter: cf_layer = QgsMemoryProviderUtils.createMemoryLayer( "current feature", source_layer.fields(), source_layer.wkbType(), source_layer.crs()) cf_layer.startEditing() cf_layer.addFeature(feature) cf_layer.commitChanges() cf_layer.importNamedStyle(doc) layers = [ cf_layer if lyr == source_layer else lyr for lyr in orig_layers ] mapSettings.setLayers(layers) title = feature.attribute(title_field) feedback.setProgressText("({}/{}) Exporting {}...".format( current + 1, total, title)) # extent geometry = QgsGeometry(feature.geometry()) geometry.transform(self.transform) center = geometry.centroid().asPoint() if fixed_scale or geometry.type() == QgsWkbTypes.PointGeometry: tex_height = orig_tex_height or int( tex_width * orig_size.height() / orig_size.width()) rect = RotatedRect(center, baseExtent.width(), baseExtent.width() * tex_height / tex_width, rotation).scale(1 + buf / 100) else: geometry.rotate(rotation, center) rect = geometry.boundingBox().scaled(1 + buf / 100) center = RotatedRect.rotatePoint(rect.center(), rotation, center) if orig_tex_height: tex_height = orig_tex_height tex_ratio = tex_width / tex_height rect_ratio = rect.width() / rect.height() if tex_ratio > rect_ratio: rect = RotatedRect(center, rect.height() * tex_ratio, rect.height(), rotation) else: rect = RotatedRect(center, rect.width(), rect.width() / tex_ratio, rotation) else: # fit to buffered geometry bounding box rect = RotatedRect(center, rect.width(), rect.height(), rotation) tex_height = tex_width * rect.height() / rect.width() rect.toMapSettings(mapSettings) mapSettings.setOutputSize(QSize(tex_width, tex_height)) self.controller.settings.setMapSettings(mapSettings) self.export(title, out_dir, feedback) feedback.setProgress(int(current / total * 100)) return {}
def in_mask(self, feature, srid=None): if feature is None: # expression overview return False if self.layer is None: return False try: # layer is not None but destroyed ? self.layer.id() except: self.reset_mask_layer() return False # mask layer empty due to unloaded memlayersaver plugin > no filtering if self.layer.featureCount() == 0: return True mask_geom, bbox = self.mask_geometry() geom = QgsGeometry(feature.geometry()) if not geom.isGeosValid(): geom = geom.buffer(0.0, 1) if geom is None: return False if srid is not None and self.layer.crs().postgisSrid() != srid: src_crs = QgsCoordinateReferenceSystem(srid) dest_crs = self.layer.crs() xform = QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance()) try: geom.transform(xform) except Exception as e: for m in e.args: QgsMessageLog.logMessage("in_mask - {}".format(m), "Extensions") # transformation error. Check layer projection. pass if geom.type() == QgsWkbTypes.PolygonGeometry: if (self.parameters.polygon_mask_method == 2 and not self.has_point_on_surface): self.parameters.polygon_mask_method = 1 if self.parameters.polygon_mask_method == 0: # this method can only work when no geometry simplification is involved return mask_geom.overlaps(geom) or mask_geom.contains(geom) elif self.parameters.polygon_mask_method == 1: # the fastest method, but with possible inaccuracies pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains( geom.centroid()) elif self.parameters.polygon_mask_method == 2: # will always work pt = geom.vertexAt(0) return bbox.contains(QgsPointXY(pt)) and mask_geom.contains( geom.pointOnSurface()) else: return False elif geom.type() == QgsWkbTypes.LineGeometry: if self.parameters.line_mask_method == 0: return mask_geom.intersects(geom) elif self.parameters.line_mask_method == 1: return mask_geom.contains(geom) else: return False elif geom.type() == QgsWkbTypes.PointGeometry: return mask_geom.intersects(geom) else: return False