def transformedCornerCoordinates(self, center, rotation, xScale, yScale): # scale topLeft = QgsPointXY(-self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale) topRight = QgsPointXY(self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale) bottomLeft = QgsPointXY(-self.image.width() / 2.0 * xScale, - self.image.height() / 2.0 * yScale) bottomRight = QgsPointXY(self.image.width() / 2.0 * xScale, - self.image.height() / 2.0 * yScale) # rotate # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) topLeft = self._rotate(topLeft, cosRot, sinRot) topRight = self._rotate(topRight, cosRot, sinRot) bottomRight = self._rotate(bottomRight, cosRot, sinRot) bottomLeft = self._rotate(bottomLeft, cosRot, sinRot) topLeft.set(topLeft.x() + center.x(), topLeft.y() + center.y()) topRight.set(topRight.x() + center.x(), topRight.y() + center.y()) bottomRight.set(bottomRight.x() + center.x(), bottomRight.y() + center.y()) bottomLeft.set(bottomLeft.x() + center.x(), bottomLeft.y() + center.y()) return (topLeft, topRight, bottomRight, bottomLeft)
class TestQgsPointXY(unittest.TestCase): def __init__(self, methodName): """Run once on class initialization.""" unittest.TestCase.__init__(self, methodName) def setUp(self): self.mPoint = QgsPointXY(10.0, 10.0) def test_Point(self): myExpectedValue = 10.0 myActualValue = self.mPoint.x() myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myActualValue) assert myExpectedValue == myActualValue, myMessage def test_pointToString(self): myExpectedValue = '10, 10' myActualValue = self.mPoint.toString() myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myActualValue) assert myExpectedValue == myActualValue, myMessage def test_hash(self): a = QgsPointXY(2.0, 1.0) b = QgsPointXY(2.0, 2.0) c = QgsPointXY(1.0, 2.0) d = QgsPointXY(1.0, 1.0) e = QgsPointXY(2.0, 1.0) assert a.__hash__() != b.__hash__() assert e.__hash__() == a.__hash__() mySet = set([a, b, c, d, e]) assert len(mySet) == 4
def transformedCornerCoordinatesFromPoint(self, startPoint, rotation, xScale, yScale): # startPoint is a fixed point for this new movement (rotation and # scale) # rotation is the global rotation of the image # xScale is the new xScale factor to be multiplied by self.xScale # idem for yScale # Calculate the coordinate of the center in a startPoint origin # coordinate system and apply scales dX = (self.center.x() - startPoint.x()) * xScale dY = (self.center.y() - startPoint.y()) * yScale # Half width and half height in the current transformation hW = (self.image.width() / 2.0) * self.xScale * xScale hH = (self.image.height() / 2.0) * self.yScale * yScale # Actual rectangle coordinates : pt1 = QgsPointXY(-hW, hH) pt2 = QgsPointXY(hW, hH) pt3 = QgsPointXY(hW, -hH) pt4 = QgsPointXY(-hW, -hH) # Actual rotation from the center # minus sign because rotation is CW in this class and Qt) rotationRad = -self.rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # Second transformation # displacement of the origin pt1 = QgsPointXY(pt1.x() + dX, pt1.y() + dY) pt2 = QgsPointXY(pt2.x() + dX, pt2.y() + dY) pt3 = QgsPointXY(pt3.x() + dX, pt3.y() + dY) pt4 = QgsPointXY(pt4.x() + dX, pt4.y() + dY) # Rotation # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # translate to startPoint pt1 = QgsPointXY(pt1.x() + startPoint.x(), pt1.y() + startPoint.y()) pt2 = QgsPointXY(pt2.x() + startPoint.x(), pt2.y() + startPoint.y()) pt3 = QgsPointXY(pt3.x() + startPoint.x(), pt3.y() + startPoint.y()) pt4 = QgsPointXY(pt4.x() + startPoint.x(), pt4.y() + startPoint.y()) return (pt1, pt2, pt3, pt4)
def testPoint(self): point = QgsReferencedPointXY(QgsPointXY(1.0, 2.0), QgsCoordinateReferenceSystem('epsg:3111')) self.assertEqual(point.x(), 1.0) self.assertEqual(point.y(), 2.0) self.assertEqual(point.crs().authid(), 'EPSG:3111') point.setCrs(QgsCoordinateReferenceSystem('epsg:28356')) self.assertEqual(point.crs().authid(), 'EPSG:28356') # in variant v = QVariant(QgsReferencedPointXY(QgsPointXY(3.0, 4.0), QgsCoordinateReferenceSystem('epsg:3111'))) self.assertEqual(v.value().x(), 3.0) self.assertEqual(v.value().y(), 4.0) self.assertEqual(v.value().crs().authid(), 'EPSG:3111') # to QgsPointXY p = QgsPointXY(point) self.assertEqual(p.x(), 1.0) self.assertEqual(p.y(), 2.0)
def checkPoint(self, point): state = 'yellow' if type(point) != QgsPointXY: try: if type(point) == list and len(point) == 2: point = QgsPointXY(point[0], point[1]) else: point = QgsPointXY(point) except TypeError: return state, None if self.dhm != {}: extent = self.dhm['extent'] [extLx, extHy, extHx, extLy] = extent if extLx <= float(point.x()) <= extHx \ and extLy <= float(point.y()) <= extHy: state = 'green' else: state = 'red' return state, point
class QadMOVECommandClass(QadCommandClass): def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadMOVECommandClass(self.plugIn) def getName(self): return QadMsg.translate("Command_list", "MOVE") def getEnglishName(self): return "MOVE" def connectQAction(self, action): action.triggered.connect(self.plugIn.runMOVECommand) def getIcon(self): return QIcon(":/plugins/qad/icons/move.png") def getNote(self): # impostare le note esplicative del comando return QadMsg.translate("Command_MOVE", "Moves the selected objects.") def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.SSGetClass = QadSSGetClass(plugIn) self.SSGetClass.onlyEditableLayers = True self.cacheEntitySet = QadCacheEntitySet() self.basePt = QgsPointXY() def __del__(self): QadCommandClass.__del__(self) del self.SSGetClass def getPointMapTool(self, drawMode=QadGetPointDrawModeEnum.NONE): if self.step == 0: # quando si é in fase di selezione entità return self.SSGetClass.getPointMapTool() else: if (self.plugIn is not None): if self.PointMapTool is None: self.PointMapTool = Qad_move_maptool(self.plugIn) return self.PointMapTool else: return None def getCurrentContextualMenu(self): if self.step == 0: # quando si é in fase di selezione entità return None # return self.SSGetClass.getCurrentContextualMenu() else: return self.contextualMenu #============================================================================ # move #============================================================================ def move(self, entity, offsetX, offsetY): # verifico se l'entità appartiene ad uno stile di quotatura if entity.whatIs() == "ENTITY": # sposto la geometria dell'entità qadGeom = entity.getQadGeom().copy() # la copio qadGeom.move(offsetX, offsetY) f = entity.getFeature() f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.crs())) # plugIn, layer, feature, refresh, check_validity if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: return False elif entity.whatIs() == "DIMENTITY": if entity.deleteToLayers(self.plugIn) == False: return False # cancello vecchia quota newDimEntity = QadDimEntity(entity) # la copio if newDimEntity.move(offsetX, offsetY) == False: return False # sposto la quota if newDimEntity.addToLayers( self.plugIn) == False: # ricreo nuva quota return False return True #============================================================================ # moveGeoms #============================================================================ def moveGeoms(self, newPt): offsetX = newPt.x() - self.basePt.x() offsetY = newPt.y() - self.basePt.y() self.plugIn.beginEditCommand("Feature moved", self.cacheEntitySet.getLayerList()) dimElaboratedList = [] # lista delle quotature già elaborate entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) for entity in entityIterator: qadGeom = entity.getQadGeom() # così inizializzo le info qad # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is not None: if appendDimEntityIfNotExisting( dimElaboratedList, dimEntity) == False: # quota già elaborata continue entity = dimEntity if self.move(entity, offsetX, offsetY) == False: self.plugIn.destroyEditCommand() return self.plugIn.endEditCommand() def run(self, msgMapTool=False, msg=None): if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): self.showMsg( QadMsg.translate( "QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n" )) return True # fine comando #========================================================================= # RICHIESTA SELEZIONE OGGETTI if self.step == 0: # inizio del comando if self.SSGetClass.run(msgMapTool, msg) == True: # selezione terminata self.step = 1 self.getPointMapTool().refreshSnapType( ) # aggiorno lo snapType che può essere variato dal maptool di selezione entità return self.run(msgMapTool, msg) #========================================================================= # SPOSTA OGGETTI elif self.step == 1: if self.SSGetClass.entitySet.count() == 0: return True # fine comando self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) # imposto il map tool self.getPointMapTool().cacheEntitySet = self.cacheEntitySet self.getPointMapTool().setMode( Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) keyWords = QadMsg.translate("Command_MOVE", "Displacement") prompt = QadMsg.translate( "Command_MOVE", "Specify base point or [{0}] <{0}>: ").format(keyWords) englishKeyWords = "Displacement" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) self.step = 2 return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse pass # opzione di default "spostamento" else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value is None or type(value) == unicode: self.basePt.set(0, 0) self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) # si appresta ad attendere un punto msg = QadMsg.translate( "Command_MOVE", "Specify the displacement fom the origin point 0,0 <{0}, {1}>: " ) # msg, inputType, default, keyWords, nessun controllo self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ QadInputTypeEnum.POINT2D, \ self.plugIn.lastOffsetPt, \ "", QadInputModeEnum.NONE) self.step = 4 elif type( value) == QgsPointXY: # se é stato inserito il punto base self.basePt.set(value.x(), value.y()) # imposto il map tool self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(QadMsg.translate("Command_MOVE", "Specify the second point or <use first point as displacement from the origin point 0,0>: "), \ QadInputTypeEnum.POINT2D, \ None, \ "", QadInputModeEnum.NONE) self.step = 3 return False #========================================================================= # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value is None: newPt = QgsPointXY(self.basePt.x() * 2, self.basePt.y() * 2) self.moveGeoms(newPt) elif type( value ) == QgsPointXY: # se é stato inserito lo spostamento con un punto self.moveGeoms(value) return True # fine comando #========================================================================= # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg self.plugIn.setLastOffsetPt(value) self.moveGeoms(value) return True
def physiocap_filtrer(self, src, csv_sans_0, csv_avec_0, csv_0_seul, nom_dir_segment, nom_session, chemin_session, diametre_filtre, nom_fichier_synthese, err, mindiam, maxdiam, max_sarments_metre, segment_mini_vitesse, segment_maxi_vitesse, segment_mini_point, segment_max_pdop, segment_max_derive, segment_pas_de_derive, details, eer, eec, d, hv, laProjectionCRS, laProjectionTXT, version_3="NO"): """Fonction de traitement. Filtre ligne brute par ligne brute les données de source (src) pour les valeurs comprises entre mindiam et maxdiam et verifie si on n'a pas atteint le max_sarments_metre. Le résultat est écrit au fur et à mesure dans les fichiers csv_sans_0, csv_avec_0 et depuis v3 dans csv_0_seul mais aussi diametre_filtre La synthese est allongé "details" pilote l'ecriture de 5 parametres ou de la totalité des 10 parametres """ leModeDeTrace = self.fieldComboModeTrace.currentText() # S'il n'existe pas de données parcellaire, le script travaille avec les données brutes titre = "" titre_partie_details = " ; NBSARMM2 ; NBSARCEP ; BIOMMM2 ; BIOMGM2 ; BIOMGCEP " if version_3 == "NO": titre_sans_detail = "X ; Y ; XL93 ; YL93 ; NBSARM ; DIAM ; BIOM ; DATE ; VITESSE" else: # Ajout en version 3 de l'altitude titre_sans_detail = "ID;X ; Y ; XL93 ; YL93 ; ALTITUDE; PDOP ; DISTANCE; DERIVE; AZIMUTH; NBSART; NBSARM ; DIAM ; BIOM ; DATE ; VITESSE" if details == "NO": titre = titre_sans_detail else: #S'il existe des données parcellaire, le script travaille avec les données brutes et les données calculées titre = titre_sans_detail + titre_partie_details # Ecriture de l'entete pour tous les cas csv_sans_0.write("{0}\n".format(titre)) csv_avec_0.write("{0}\n".format(titre)) csv_0_seul.write("{0}\n".format(titre)) # Pour progress bar entre 15 et 40 lignes_brutes = src.readlines() max_lignes = len(lignes_brutes) progress_step = int(max_lignes / 25) #physiocap_log("Bar step: " + str( progress_step), leModeDeTrace) progress_bar = 15 barre = 1 precedent = [] on_coupe = "PREMIER" segment_en_cours = [] gid_en_cours = [] gid_sans_mesure = [] manquant_en_cours = [] info_en_cours = {} derive_en_cours = [] mes_lignes_sans_coupure = [] info_lignes_sans_coupure = [] nombre_segments_sans_coupure = 0 # Récuperer le CRS choisi, les extensions et le calculateur de distance distancearea, EXT_CRS_SHP, EXT_CRS_PRJ, EXT_CRS_RASTER, \ laProjectionCRS, laProjectionTXT, EPSG_NUMBER = \ physiocap_quelle_projection_et_lib_demandee( self) for numero_point, ligne_brute in enumerate(lignes_brutes): if not ligne_brute: break # Progress BAR de 15 à 40 % if (numero_point > barre * progress_step): progress_bar = progress_bar + 1 barre = barre + 1 self.progressBar.setValue(progress_bar) comptage = ligne_brute.count(",") # compte le nombre de virgules result = ligne_brute.split(",") # split en fonction des virgules try: # Transform GPS en L93 # on extrait les Colonnnes 1 à 8 (XY, puis GPS jusqu'à vitesse) # en on les transforme en float ### On utilise XY[0 et 1] puis Altitude XY[2] Pdop XY[5] et vitesse XY[7] XY = [float(x) for x in result[1:9]] # Puis on transforme les WGS84 (du capteur) en L93 (probablement utile) # TODO: ?V3.x autres EPSG ? et eviter cet appel dans la boucle crsDest = QgsCoordinateReferenceSystem.fromEpsgId( EPSG_NUMBER_L93) # Lambert 93 crsSrc = QgsCoordinateReferenceSystem.fromEpsgId( EPSG_NUMBER_GPS) # WGS 84 transformer = QgsCoordinateTransform() transformer.setSourceCrs(crsSrc) transformer.setDestinationCrs(crsDest) if not transformer.isValid(): raise physiocap_exception_no_transform(numero_point) # On assure la tranformation par compatibilité du CVS en GPS et L93 point_L93 = transformer.transform(QgsPointXY(XY[0], XY[1])) XY_L93 = [point_L93.x(), point_L93.y()] # aMsg = "Transformation faite X {0} et Y {1}". \ # format( XY_L93[0], XY_L93[1]) # physiocap_log( aMsg , leModeDeTrace) # physiocap_log( "La projection {0}". format( laProjectionTXT), leModeDeTrace) if (laProjectionTXT == "GPS"): le_point_projete = QgsPointXY(XY[0], XY[1]) else: # Pour le moment seulement L93 le_point_projete = QgsPointXY(XY_L93[0], XY_L93[1]) XY_projete = [le_point_projete.x(), le_point_projete.y()] except: aMsg = "{0} Erreur bloquante durant tranformation SCR : pour la ligne brute numéro {1}". \ format ( PHYSIOCAP_STOP, numero_point) physiocap_error(self, aMsg) err.write(aMsg) # on écrit la ligne dans le fichier ERREUR # monter directemenr exception raise # TODO: ?V3.x marquer les points à conserver (non filtré et dans un segment) # pour creer un 4eme csv POINTS_VALIDES # ce qui reste compliqué pour les segments courts que je ne connais pas encore try: # SEGMENT si V3 # On regarde les points sans mesure avant SEGMENT diams = [float(x) for x in result[9:NB_VIRGULES + 1] ] # on extrait les diams et on les transforme en float diamsF = [ i for i in diams if i > mindiam and i < maxdiam ] # on filtre les diams avec les paramètres entrés ci-dessus derive = 0.0 ma_distance = 0.0 mon_azimuth = 0.0 # SEGMENT si V3 if version_3 == "NO": pass elif precedent == [] or on_coupe == "PREMIER": #physiocap_log( "SEGMENT ==>> point {0} PREMIER".format( numero_point), TRACE_SEGMENT + "_DEBUG") # Stocker le premier point pour comparer au prochain tour # et la Date début precedent = XY_projete # TODO: ?V3.y passage en 3D mettre en Z la dérive info_en_cours[DATE_DEBUT] = result[0] if len(diamsF) == 0: # On ne STOCKE pas les points sans MESURE gid_sans_mesure.append(numero_point) else: gid_en_cours.append(numero_point) derive_en_cours.append(0) segment_en_cours.append(QgsPointXY(le_point_projete)) on_coupe = "NON" else: # On vérifie qualité de mesure # ################################################ # Filtre des points pour découpage en SEGMENT ou # pour montrer les limites de la capture # On cherche si le point est dans la zone attendue # calcul basé sur la vitesse annoncé par GPS sur # le point en cours et PDOP # ################################################# # Quand vitesse plus de 2.5 et moins de 8 et pdop reste cohérent segment_max_pdop if XY[7] >= segment_mini_vitesse and XY[ 7] < segment_maxi_vitesse and XY[5] < segment_max_pdop: # on est en vitesse de croisière # Calcul de la distance théorique par rapport au precedent # Introduire un calcul de distance length et l'azimuth le_point_precedent = QgsPointXY(precedent[0], precedent[1]) ma_distance = distancearea.measureLine( le_point_projete, le_point_precedent) mon_azimuth = le_point_projete.azimuth(le_point_precedent) # TODO: ?V3.y Traiter l'azimuth depuis le début du segment distance_theorique = XY[ 7] * 1000 / 3600 # On suppose une seconde d'avancement derive = (ma_distance - distance_theorique) / distance_theorique * 100 # physiocap_log( "Vitesse {3} Distance théorique {1:.2f} et ma distance {0:.2f} \ # sont distantes de \n {2:.1f} soit une derive de {4:.1f}".\ # format(ma_distance, distance_theorique, \ # ( ma_distance - distance_theorique), XY[7], derive ), \ # TRACE_SEGMENT) #remplacer le precedent par l'actuel precedent = XY_projete # Vérification de dérive if abs(derive) > (segment_max_derive + (2 * segment_pas_de_derive)): physiocap_log( "{0} DECOUPAGE point {1} : l'avancée dérive GRAVE ===> {2:.1f} ! ".\ format(PHYSIOCAP_WARNING, numero_point, derive ), \ TRACE_SEGMENT_DECOUPES) on_coupe = "OUI" elif abs(derive) > (segment_max_derive + segment_pas_de_derive): physiocap_log( "{0} DECOUPAGE point {1} : l'avancée dérive de PLUS d'un PAS ==> {2:.1f} ! ".\ format(PHYSIOCAP_WARNING, numero_point, derive ), \ TRACE_SEGMENT_DECOUPES) on_coupe = "OUI" elif abs(derive) > segment_max_derive: physiocap_log("{0} DECOUPAGE point {1} : l'avancée dérive => {2:.1f} ! ".\ format(PHYSIOCAP_WARNING, numero_point, derive ), \ TRACE_SEGMENT_DECOUPES) on_coupe = "OUI" else: # La derive < segment_max_derive en % : # Stocker ligne "droite" = orientation et sens d'avancement # Créer un flux des avancement stables pour identifier l'écartement problable # Ajouter un point à la ligne segment_en_cours.append(QgsPointXY(le_point_projete)) info_en_cours[DATE_FIN] = result[0] if len(diamsF) == 0: # On ne STOCKE pas les points sans MESURE gid_sans_mesure.append(numero_point) else: gid_en_cours.append(numero_point) derive_en_cours.append(derive) on_coupe = "NON" else: # Cas d'arret (fin de rang) ou pdop on_coupe = "OUI" # Tracer cas decoupe vitessse if XY[7] < segment_mini_vitesse: if len(segment_en_cours) > 0: physiocap_log("{0} DECOUPAGE point {1} : vitesse {2:.1f} alors que min est {3:.1f}! ".\ format(PHYSIOCAP_WARNING, numero_point, XY[7], segment_mini_vitesse), \ TRACE_SEGMENT_DECOUPES) if XY[7] > segment_maxi_vitesse: if len(segment_en_cours) > 0: physiocap_log("{0} DECOUPAGE point {1} : vitesse {2:.1f} que max est {3:.1f}! ".\ format(PHYSIOCAP_WARNING, numero_point, XY[7], segment_maxi_vitesse), \ TRACE_SEGMENT_DECOUPES) # Tracer cas decoupe pdop if XY[5] >= segment_max_pdop: physiocap_log("{0} DECOUPAGE point {1} : pdop {2:.1f} max est {3:.1f}! ".\ format(PHYSIOCAP_WARNING, numero_point, XY[5], segment_max_pdop ), \ TRACE_SEGMENT_DECOUPES) if on_coupe == "OUI": # Cas de fin de ligne if len(segment_en_cours) > segment_mini_point: # Le segment est à garder manquant_en_cours.append(numero_point) # Mémoriser la ligne des points cohérents mes_lignes_sans_coupure.append(segment_en_cours) info_en_cours[NUM_SEG] = nombre_segments_sans_coupure info_en_cours[DATE_FIN] = result[0] info_en_cours[NOMBRE] = len(segment_en_cours) info_en_cours[GID_GARDE] = gid_en_cours info_en_cours[GID_SANS_MESURE] = gid_sans_mesure info_en_cours[GID_TROU] = manquant_en_cours info_en_cours[DERIVE] = np.mean(derive_en_cours) # stocker jour_heure début et fin et derive moyenne ... info_lignes_sans_coupure.append(info_en_cours) nombre_segments_sans_coupure = nombre_segments_sans_coupure + 1 manquant_en_cours = [] else: # Vérifier les gid_sans_mesure # On ne perd pas les points manquants qui seront ajouter dans GID_TROU pour le segment suivant # On aditionne des gid en cours avec les manquants... for gid_perdu in gid_en_cours: manquant_en_cours.append(gid_perdu) manquant_en_cours.append(numero_point) if len(segment_en_cours) > 0: physiocap_log("{0} SEGMENT {1} IGNORE : trop cours == {2} points, le mini est {3} ".\ format(PHYSIOCAP_WARNING, nombre_segments_sans_coupure, len(segment_en_cours), segment_mini_point ), TRACE_SEGMENT_DECOUPES) info_en_cours = {} gid_en_cours = [] gid_sans_mesure = [] precedent = [] on_coupe = "PREMIER" segment_en_cours = [] except: aMsg = "{0} Erreur bloquante durant extraction des segments : pour la ligne brute numéro {1}". \ format ( PHYSIOCAP_STOP, numero_point) physiocap_error(self, aMsg) err.write(aMsg) # on écrit la ligne dans le fichier ERREUR # monter directemenr exception raise try: # On filtre vraiement if details == "NO": if len( diamsF ) == 0: # si le nombre de diamètre après filtrage = 0 alors pas de mesures nbsarm = 0 nbsart = 0 diam = 0 biom = 0 # Ecrire les seuls_0 et aussi les points avec 0 if version_3 == "NO": csv_0_seul.write("%.7f%s%.7f%s%.7f%s%.7f%s%i%s%i%s%i%s%s%s%0.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7])) # on écrit la ligne dans le csv avec ZERO SEUL csv_avec_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%i%s%i%s%i%s%s%s%0.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7])) # on écrit la ligne dans le fcsv avec ZERO else: # V3 on ajoute altitude, pdop, distance au point precedent et la dérive # puis AZIMUTH et NBSART = 0 a_ecrire = "{0};{1:.7f};{2:.7f};{3:.7f};{4:.7f}; \ {5:.2f};{6:.2f};{7:.2f};{8:.2f};{9:.2f};0;0;0;0;{10};{11:.7f}\n" . \ format(numero_point, XY[0],XY[1],XY_L93[0],XY_L93[1], \ XY[2],XY[5],ma_distance,derive,mon_azimuth, result[0],XY[7]) csv_0_seul.write(a_ecrire) csv_avec_0.write(a_ecrire) elif comptage == NB_VIRGULES and len( diamsF ) > 0: # si le nombre de diamètre après filtrage != 0 alors mesures # Nombre sarment total nbsart = len(diamsF) if XY[7] != 0: # Si vitesse non nulle nbsarm = len(diamsF) / (XY[7] * 1000 / 3600) else: nbsarm = 0 if nbsarm > 1 and nbsarm < max_sarments_metre: diam = sum(diamsF) / len(diamsF) biom = 3.1416 * (diam / 2) * (diam / 2) * nbsarm if version_3 == "NO": csv_avec_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%0.2f%s%.2f%s%.2f%s%s%s%0.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam,";",biom,";",result[0],";",XY[7])) # on écrit la ligne dans le csv avec ZERO csv_sans_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%0.2f%s%.2f%s%.2f%s%s%s%0.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam,";",biom,";",result[0],";",XY[7])) # on écrit la ligne dans le csv sans ZERO else: # V3 on ajoute altitude, pdop,distance au point precedent et risque de dérive # puis AZIMUTH et NBSART a_ecrire = "{0};{1:.7f};{2:.7f};{3:.7f};{4:.7f}; \ {5:.2f};{6:.2f};{7:.2f};{8:.2f};{9:.2f};{10}; \ {11:.2f}; {12:.2f};{13:.2f};{14};{15:.7f}\n" . \ format( numero_point, XY[0], XY[1], XY_L93[0] ,XY_L93[1], \ XY[2],XY[5],ma_distance,derive,mon_azimuth,nbsart, \ nbsarm,diam,biom,result[0],XY[7]) csv_avec_0.write(a_ecrire) csv_sans_0.write(a_ecrire) for n in range(len(diamsF)): diametre_filtre.write("%f%s" % (diamsF[n], ";")) elif details == "YES": if len( diamsF ) == 0: # si le nombre de diamètre après filtrage = 0 alors pas de mesures nbsart = 0 nbsarm = 0 diam = 0 biom = 0 nbsarmm2 = 0 nbsarcep = 0 biommm2 = 0 biomgm2 = 0 biomgcep = 0 if version_3 == "NO": csv_0_seul.write("%.7f%s%.7f%s%.7f%s%.7f%s%i%s%i%s%i%s%s%s%0.2f%s%i%s%i%s%i%s%i%s%i\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7],";",nbsarmm2,";",nbsarcep,";",biommm2,";",biomgm2,";",biomgcep)) csv_avec_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%i%s%i%s%i%s%s%s%0.2f%s%i%s%i%s%i%s%i%s%i\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7],";",nbsarmm2,";",nbsarcep,";",biommm2,";",biomgm2,";",biomgcep)) else: # Q3 on ajoute altitude, pdop, distance au point precedent et la dérive # puis AZIMUTH et NBSART = 0 a_ecrire = "{0};{1:.7f};{2:.7f};{3:.7f};{4:.7f}; \ {5:.2f};{6:.2f};{7:.2f};{8:.2f};{9:.2f};0;0;0;0;{10};{11:.7f}" . \ format(numero_point, XY[0],XY[1],XY_L93[0],XY_L93[1], XY[2],XY[5],ma_distance,derive,mon_azimuth, result[0],XY[7]) a_ecrire_detail = ";0;0;0;0;0\n" a_ecrire_complet = a_ecrire + a_ecrire_detail csv_0_seul.write(a_ecrire_complet) csv_avec_0.write(a_ecrire_complet) elif comptage == NB_VIRGULES and len( diamsF ) > 0: # si le nombre de diamètre après filtrage != 0 alors mesures nbsart = len(diamsF) if XY[7] != 0: nbsarm = len(diamsF) / (XY[7] * 1000 / 3600) else: nbsarm = 0 if nbsarm > 1 and nbsarm < max_sarments_metre: diam = sum(diamsF) / len(diamsF) biom = 3.1416 * (diam / 2) * (diam / 2) * nbsarm nbsarmm2 = nbsarm / eer * 100 nbsarcep = nbsarm * eec / 100 biommm2 = biom / eer * 100 biomgm2 = biom * d * hv / eer biomgcep = biom * d * hv * eec / 100 / 100 if version_3 == "NO": csv_avec_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%.2f%s%.2f%s%.2f%s%s%s%.2f%s%.2f%s%.2f%s%.2f%s%.2f%s%.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7],";",nbsarmm2,";",nbsarcep,";",biommm2,";",biomgm2,";",biomgcep)) csv_sans_0.write("%.7f%s%.7f%s%.7f%s%.7f%s%.2f%s%.2f%s%.2f%s%s%s%.2f%s%.2f%s%.2f%s%.2f%s%.2f%s%.2f\n" \ %(XY[0],";",XY[1],";",XY_L93[0],";",XY_L93[1],";",nbsarm,";",diam ,";",biom,";",result[0],";",XY[7],";",nbsarmm2,";",nbsarcep,";",biommm2,";",biomgm2,";",biomgcep)) else: # Q3 on ajoute altitude, pdop,distance au point precedent et risque de dérive # puis AZIMUTH et NBSART a_ecrire = "{0};{1:.7f};{2:.7f};{3:.7f};{4:.7f}; \ {5:.2f};{6:.2f};{7:.2f};{8:.2f};{9:.2f};{10}; \ {11:.2f}; {12:.2f};{13:.2f};{14};{15:.7f}" . \ format( numero_point, XY[0], XY[1], XY_L93[0] ,XY_L93[1], XY[2],XY[5],ma_distance,derive,mon_azimuth,nbsart, nbsarm,diam,biom,result[0],XY[7]) a_ecrire_detail = ";{0:.7f};{1:.7f};{2:.7f};{3:.7f};{4:.7f}\n". \ format( nbsarmm2, nbsarcep,biommm2,biomgm2,biomgcep) a_ecrire_complet = a_ecrire + a_ecrire_detail csv_avec_0.write(a_ecrire_complet) csv_sans_0.write(a_ecrire_complet) # Memorise diametre filtré pour histo for n in range(len(diamsF)): diametre_filtre.write("%f%s" % (diamsF[n], ";")) except: aMsg = "{0} Erreur bloquante durant filtrage : pour la ligne brute numéro {1}". \ format ( PHYSIOCAP_STOP, numero_point) physiocap_error(self, aMsg) err.write(aMsg) # on écrit la ligne dans le fichier ERREUR # Pour monter directement exception raise physiocap_exception_err_csv(nom_court_csv_concat) if version_3 == "NO": vecteur_segment = None vecteur_segment_brise = None else: if len(info_lignes_sans_coupure) != nombre_segments_sans_coupure: physiocap_error( self, "{0} on a trouvé {1} segments et {2} infos". \ format( PHYSIOCAP_INFO, nombre_segments_sans_coupure, len( info_lignes_sans_coupure))) raise physiocap_exception_calcul_segment_invalid( "Segment et leurs infos sont différents") i = 0 for info_segment in info_lignes_sans_coupure: i = i + 1 try: physiocap_log( "{0} Segment {1} contient {2} points et une dérive moyenne de {3:.1f}". \ format( PHYSIOCAP_INFO, i, info_segment[NOMBRE], info_segment[DERIVE]), TRACE_SEGMENT) physiocap_log( "gid des points :{0} \net les sans mesure\n{1}". \ format(info_segment[GID_GARDE], info_segment[GID_SANS_MESURE]), TRACE_SEGMENT) except: physiocap_error( self, "Problème : manque attribut dans info segment") raise physiocap_exception_calcul_segment_invalid( "Un attribut n'est pas présent") # try: # physiocap_log( "Date début {0} et fin {1}". \ # format( info_segment[DATE_DEBUT], info_segment[DATE_FIN]), # TRACE_SEGMENT) # except: # physiocap_error( self, "Problème : pas de date dans le segment") # raise physiocap_exception_calcul_segment_invalid( "Date non présente") # Creer les lignes simplifiés ou brisés de ces segments et infos vecteur_segment = physiocap_segment_vers_vecteur( self, chemin_session, nom_dir_segment, nom_session, mes_lignes_sans_coupure, info_lignes_sans_coupure, version_3) vecteur_segment_brise = physiocap_segment_vers_vecteur( self, chemin_session, nom_dir_segment, nom_session, mes_lignes_sans_coupure, info_lignes_sans_coupure, version_3, "BRISE") physiocap_log( "{0} {1} Fin du filtrage OK des {2} lignes.". \ format( PHYSIOCAP_INFO, PHYSIOCAP_UNI, str(numero_point - 1)), leModeDeTrace) return vecteur_segment, vecteur_segment_brise
def result(self, result): # See if OK was pressed if result: project = QgsProject.instance() # First get all the values of the GUI items crs_input = self.dlg.crs_input.crs() crs_out = QgsCoordinateReferenceSystem( 'EPSG:4326') # we need this to be WGS84 for Nominatim lineedit_text = self.dlg.lineedit_xy.value() # Protect the free text field for coordinates from generic user failure try: lineedit_yx = [ float(coord.strip()) for coord in lineedit_text.split(',') ] except: QMessageBox.critical( self.iface.mainWindow(), 'QuickAPI error', "Did you really specify a coordinate in comma-separated Lat/Long?\nExiting..." ) return # Create a Point and transform if necessary point = QgsPointXY(*reversed(lineedit_yx)) if crs_input.authid() != 'EPSG:4326': xform = QgsCoordinateTransform(crs_input, crs_out, project) point_transform = xform.transform(point) point = point_transform # Set up the GET Request to Nominatim query = QUrlQuery() query.addQueryItem('lat', str(point.y())) query.addQueryItem('lon', str(point.x())) query.addQueryItem('format', 'json') url = QUrl('https://nominatim.openstreetmap.org/reverse') url.setQuery(query) request = QNetworkRequest(url) request.setHeader(QNetworkRequest.UserAgentHeader, '*****@*****.**') nam = QgsNetworkAccessManager() response: QgsNetworkReplyContent = nam.blockingGet(request) # Only process if HTTP status code is 200 status_code = response.attribute( QNetworkRequest.HttpStatusCodeAttribute) if status_code == 200: # Get the content of the response and process it response_json = json.loads(bytes(response.content())) if response_json.get('error'): QMessageBox.critical( self.iface.mainWindow(), "Quick API error", "The request was not processed succesfully!\n\n" "Message:\n" "{}".format(response_json['error'])) return x = float(response_json['lon']) y = float(response_json['lat']) address = response_json['display_name'] license = response_json['licence'] # Create the output memory layer layer_out = QgsVectorLayer( "Point?crs=EPSG:4326&field=address:string&field=license:string", "Nominatim Reverse Geocoding", "memory") # Create the output feature (only one here) point_out = QgsPointXY(x, y) feature = QgsFeature() feature.setGeometry(QgsGeometry.fromPointXY(point_out)) feature.setAttributes([address, license]) # Add feature to layer and layer to map layer_out.dataProvider().addFeature(feature) layer_out.updateExtents() project.addMapLayer(layer_out) # build bbox for auto-zoom feature bbox = [float(coord) for coord in response_json['boundingbox']] min_y, max_y, min_x, max_x = bbox bbox_geom = QgsGeometry.fromRect( QgsRectangle(min_x, min_y, max_x, max_y)) # Transform bbox if map canvas has a different CRS if project.crs().authid() != 'EPSG:4326': xform = QgsCoordinateTransform(crs_out, project.crs(), project) bbox_geom.transform(xform) self.iface.mapCanvas().zoomToFeatureExtent( QgsRectangle.fromWkt(bbox_geom.asWkt()))
class FreehandRasterGeoreferencerLayer(QgsPluginLayer): LAYER_TYPE = "FreehandRasterGeoreferencerLayer" transformParametersChanged = pyqtSignal(tuple) def __init__(self, plugin, filepath, title, screenExtent): QgsPluginLayer.__init__( self, FreehandRasterGeoreferencerLayer.LAYER_TYPE, title ) self.plugin = plugin self.iface = plugin.iface self.title = title self.filepath = filepath self.screenExtent = screenExtent self.history = [] # set custom properties self.setCustomProperty("title", title) self.setCustomProperty("filepath", self.filepath) self.setValid(True) self.setTransparency(LayerDefaultSettings.TRANSPARENCY) self.setBlendModeByName(LayerDefaultSettings.BLEND_MODE) # dummy data: real init is done in intializeLayer self.center = QgsPointXY(0, 0) self.rotation = 0.0 self.xScale = 1.0 self.yScale = 1.0 self.error = False self.initializing = False self.initialized = False self.initializeLayer(screenExtent) self._extent = None self.provider = FreehandRasterGeoreferencerLayerProvider(self) def dataProvider(self): # issue with DBManager if the dataProvider of the QgsLayerPlugin # returns None return self.provider def setScale(self, xScale, yScale): self.xScale = xScale self.yScale = yScale def setRotation(self, rotation): # 3 decimals ought to be enough for everybody rotation = round(rotation, 3) # keep in -180,180 interval if rotation < -180: rotation += 360 if rotation > 180: rotation -= 360 self.rotation = rotation def setCenter(self, center): self.center = center def commitTransformParameters(self): QgsProject.instance().setDirty(True) self._extent = None self.setCustomProperty("xScale", self.xScale) self.setCustomProperty("yScale", self.yScale) self.setCustomProperty("rotation", self.rotation) self.setCustomProperty("xCenter", self.center.x()) self.setCustomProperty("yCenter", self.center.y()) self.transformParametersChanged.emit( (self.xScale, self.yScale, self.rotation, self.center) ) def reprojectTransformParameters(self, oldCrs, newCrs): transform = QgsCoordinateTransform(oldCrs, newCrs, QgsProject.instance()) newCenter = transform.transform(self.center) newExtent = transform.transform(self.extent()) # transform the parameters except rotation # TODO rotation could be better handled (maybe check rotation between # old and new extent) # but not really worth the effort ? self.setCrs(newCrs) self.setCenter(newCenter) self.resetScale(newExtent.width(), newExtent.height()) def resetTransformParametersToNewCrs(self): """ Attempts to keep the layer on the same region of the map when the map CRS is changed """ oldCrs = self.crs() newCrs = self.iface.mapCanvas().mapSettings().destinationCrs() self.reprojectTransformParameters(oldCrs, newCrs) self.commitTransformParameters() def setupCrsEvents(self): layerId = self.id() def removeCrsChangeHandler(layerIds): if layerId in layerIds: try: self.iface.mapCanvas().destinationCrsChanged.disconnect( self.resetTransformParametersToNewCrs ) except Exception: pass try: QgsProject.instance().disconnect(removeCrsChangeHandler) except Exception: pass self.iface.mapCanvas().destinationCrsChanged.connect( self.resetTransformParametersToNewCrs ) QgsProject.instance().layersRemoved.connect(removeCrsChangeHandler) def setupCrs(self): mapCrs = self.iface.mapCanvas().mapSettings().destinationCrs() self.setCrs(mapCrs) self.setupCrsEvents() def repaint(self): self.repaintRequested.emit() def transformParameters(self): return (self.center, self.rotation, self.xScale, self.yScale) def initializeLayer(self, screenExtent=None): if self.error or self.initialized or self.initializing: return if self.filepath is not None: # not safe... self.initializing = True absPath = self.getAbsoluteFilepath() if not os.path.exists(absPath): # TODO integrate with BadLayerHandler ? loadErrorDialog = LoadErrorDialog(absPath) result = loadErrorDialog.exec_() if result == 1: # absolute absPath = loadErrorDialog.lineEditImagePath.text() # to relative if needed self.filepath = utils.toRelativeToQGS(absPath) self.setCustomProperty("filepath", self.filepath) QgsProject.instance().setDirty(True) else: self.error = True del loadErrorDialog imageFormat = utils.imageFormat(absPath) if imageFormat == "pdf": s = QSettings() oldValidation = s.value("/Projections/defaultBehavior") s.setValue( "/Projections/defaultBehavior", "useGlobal" ) # for not asking about crs layer = QgsRasterLayer(absPath, os.path.basename(absPath)) self.image = layer.previewAsImage(QSize(layer.width(), layer.height())) s.setValue("/Projections/defaultBehavior", oldValidation) else: has_corrected = False if imageFormat == "tif": # other than TIFF => assumes can be loaded by Qt has_corrected = self.preCheckImage(absPath) if has_corrected: # image already loaded by preCheckImage self.showBarMessage( "Raster changed", "Raster content has been transformed for display in the " "plugin. " "When exporting, select the 'Only export world file' checkbox.", Qgis.Warning, 10, ) else: reader = QImageReader(absPath) self.image = reader.read() self.initialized = True self.initializing = False self.setupCrs() if screenExtent: # constructor called from AddLayer action # if not, layer loaded from QGS project file # check if image already has georef info # use GDAL dataset = gdal.Open(absPath, gdal.GA_ReadOnly) georef = None if dataset: georef = dataset.GetGeoTransform() if georef and not self.is_default_geotransform(georef): self.initializeExistingGeoreferencing(dataset, georef) else: # init to default params self.setCenter(screenExtent.center()) self.setRotation(0.0) sw = screenExtent.width() sh = screenExtent.height() self.resetScale(sw, sh) self.commitTransformParameters() def preCheckImage(self, filepath): nbands, datatype, width, height = gdal_utils.format(filepath) pixels = None if nbands not in (1, 3): pixels = gdal_utils.pixels(filepath) if nbands > 3: # first 3 pixels = pixels[:3] nbands = 3 if nbands == 2: # remove band 2 pixels = pixels[0][np.newaxis, ...] nbands = 1 if datatype != "Byte": pixels = pixels if pixels is not None else gdal_utils.pixels(filepath) bands = np.empty(np.shape(pixels), dtype=np.uint8) for i in range(nbands): band_pixels = pixels[i] bands[i] = gdal_utils.to_byte(band_pixels) pixels = bands if pixels is not None: # some transformation done# band at the end pixels = np.transpose(pixels, [1, 2, 0]) pixels = pixels.ravel() if nbands == 1: # monochrome format = QImage.Format_Grayscale8 bytesPerLine = width else: format = QImage.Format_RGB888 bytesPerLine = 3 * width # Byte qImg = QImage(pixels, width, height, bytesPerLine, format) self.image = qImg return True return False def initializeExistingGeoreferencing(self, dataset, georef): # georef can have scaling, rotation or translation rotation = 180 / math.pi * -math.atan2(georef[4], georef[1]) sx = math.sqrt(georef[1] ** 2 + georef[4] ** 2) sy = math.sqrt(georef[2] ** 2 + georef[5] ** 2) i_center_x = self.image.width() / 2 i_center_y = self.image.height() / 2 center = QgsPointXY( georef[0] + georef[1] * i_center_x + georef[2] * i_center_y, georef[3] + georef[4] * i_center_x + georef[5] * i_center_y, ) qDebug(repr(rotation) + " " + repr((sx, sy)) + " " + repr(center)) self.setRotation(rotation) self.setCenter(center) # keep yScale positive self.setScale(sx, sy) self.commitTransformParameters() crs_wkt = dataset.GetProjection() message_shown = False if crs_wkt: qcrs = QgsCoordinateReferenceSystem(crs_wkt) # TODO check change if qcrs.description() != self.crs().description(): # reproject try: self.reprojectTransformParameters(qcrs, self.crs()) self.commitTransformParameters() self.showBarMessage( "Transform parameters changed: ", "Found existing georeferencing in raster but " "its CRS does not match the CRS of the map. " "Reprojected the extent.", Qgis.Warning, 25, ) message_shown = True except Exception as ex: QgsMessageLog.logMessage(repr(ex)) self.showBarMessage( "CRS does not match", "Found existing georeferencing in raster but " "its CRS does not match the CRS of the map. " "Unable to reproject.", Qgis.Warning, 5, ) message_shown = True # if no projection info, assume it is the same CRS # as the map and no warning if not message_shown: self.showBarMessage( "Georeferencing loaded", "Found existing georeferencing in raster", Qgis.Info, 3, ) # zoom (assume the user wants to work on the image) self.iface.mapCanvas().setExtent(self.extent()) def is_default_geotransform(self, georef): """ Check if there is really a transform or if it is just the default made up by GDAL """ return georef[0] == 0 and georef[3] == 0 and georef[1] == 1 and georef[5] == 1 def resetScale(self, sw, sh): iw = self.image.width() ih = self.image.height() wratio = sw / iw hratio = sh / ih if wratio > hratio: # takes all height of current extent self.setScale(hratio, hratio) else: # all width self.setScale(wratio, wratio) def replaceImage(self, filepath, title): self.title = title self.filepath = filepath # set custom properties self.setCustomProperty("title", title) self.setCustomProperty("filepath", self.filepath) self.setName(title) fileInfo = QFileInfo(filepath) ext = fileInfo.suffix() if ext == "pdf": s = QSettings() oldValidation = s.value("/Projections/defaultBehavior") s.setValue( "/Projections/defaultBehavior", "useGlobal" ) # for not asking about crs path = fileInfo.filePath() baseName = fileInfo.baseName() layer = QgsRasterLayer(path, baseName) self.image = layer.previewAsImage(QSize(layer.width(), layer.height())) s.setValue("/Projections/defaultBehavior", oldValidation) else: reader = QImageReader(filepath) self.image = reader.read() self.repaint() def clone(self): layer = FreehandRasterGeoreferencerLayer( self.plugin, self.filepath, self.title, self.screenExtent ) layer.center = self.center layer.rotation = self.rotation layer.xScale = self.xScale layer.yScale = self.yScale layer.commitTransformParameters() return layer def getAbsoluteFilepath(self): if not os.path.isabs(self.filepath): # relative to QGS file qgsPath = QgsProject.instance().fileName() qgsFolder, _ = os.path.split(qgsPath) filepath = os.path.join(qgsFolder, self.filepath) else: filepath = self.filepath return filepath def extent(self): self.initializeLayer() if not self.initialized: qDebug("Not Initialized") return QgsRectangle(0, 0, 1, 1) if self._extent: return self._extent topLeft, topRight, bottomRight, bottomLeft = self.cornerCoordinates() left = min(topLeft.x(), topRight.x(), bottomRight.x(), bottomLeft.x()) right = max(topLeft.x(), topRight.x(), bottomRight.x(), bottomLeft.x()) top = max(topLeft.y(), topRight.y(), bottomRight.y(), bottomLeft.y()) bottom = min(topLeft.y(), topRight.y(), bottomRight.y(), bottomLeft.y()) # recenter + create rectangle self._extent = QgsRectangle(left, bottom, right, top) return self._extent def cornerCoordinates(self): return self.transformedCornerCoordinates( self.center, self.rotation, self.xScale, self.yScale ) def transformedCornerCoordinates(self, center, rotation, xScale, yScale): # scale topLeft = QgsPointXY( -self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale ) topRight = QgsPointXY( self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale ) bottomLeft = QgsPointXY( -self.image.width() / 2.0 * xScale, -self.image.height() / 2.0 * yScale ) bottomRight = QgsPointXY( self.image.width() / 2.0 * xScale, -self.image.height() / 2.0 * yScale ) # rotate # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) topLeft = self._rotate(topLeft, cosRot, sinRot) topRight = self._rotate(topRight, cosRot, sinRot) bottomRight = self._rotate(bottomRight, cosRot, sinRot) bottomLeft = self._rotate(bottomLeft, cosRot, sinRot) topLeft.set(topLeft.x() + center.x(), topLeft.y() + center.y()) topRight.set(topRight.x() + center.x(), topRight.y() + center.y()) bottomRight.set(bottomRight.x() + center.x(), bottomRight.y() + center.y()) bottomLeft.set(bottomLeft.x() + center.x(), bottomLeft.y() + center.y()) return (topLeft, topRight, bottomRight, bottomLeft) def transformedCornerCoordinatesFromPoint( self, startPoint, rotation, xScale, yScale ): # startPoint is a fixed point for this new movement (rotation and # scale) # rotation is the global rotation of the image # xScale is the new xScale factor to be multiplied by self.xScale # idem for yScale # Calculate the coordinate of the center in a startPoint origin # coordinate system and apply scales dX = (self.center.x() - startPoint.x()) * xScale dY = (self.center.y() - startPoint.y()) * yScale # Half width and half height in the current transformation hW = (self.image.width() / 2.0) * self.xScale * xScale hH = (self.image.height() / 2.0) * self.yScale * yScale # Actual rectangle coordinates : pt1 = QgsPointXY(-hW, hH) pt2 = QgsPointXY(hW, hH) pt3 = QgsPointXY(hW, -hH) pt4 = QgsPointXY(-hW, -hH) # Actual rotation from the center # minus sign because rotation is CW in this class and Qt) rotationRad = -self.rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # Second transformation # displacement of the origin pt1 = QgsPointXY(pt1.x() + dX, pt1.y() + dY) pt2 = QgsPointXY(pt2.x() + dX, pt2.y() + dY) pt3 = QgsPointXY(pt3.x() + dX, pt3.y() + dY) pt4 = QgsPointXY(pt4.x() + dX, pt4.y() + dY) # Rotation # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # translate to startPoint pt1 = QgsPointXY(pt1.x() + startPoint.x(), pt1.y() + startPoint.y()) pt2 = QgsPointXY(pt2.x() + startPoint.x(), pt2.y() + startPoint.y()) pt3 = QgsPointXY(pt3.x() + startPoint.x(), pt3.y() + startPoint.y()) pt4 = QgsPointXY(pt4.x() + startPoint.x(), pt4.y() + startPoint.y()) return (pt1, pt2, pt3, pt4) def moveCenterFromPointRotate(self, startPoint, rotation, xScale, yScale): cornerPoints = self.transformedCornerCoordinatesFromPoint( startPoint, rotation, xScale, yScale ) self.center = QgsPointXY( (cornerPoints[0].x() + cornerPoints[2].x()) / 2, (cornerPoints[0].y() + cornerPoints[2].y()) / 2, ) def _rotate(self, point, cosRot, sinRot): return QgsPointXY( point.x() * cosRot - point.y() * sinRot, point.x() * sinRot + point.y() * cosRot, ) def createMapRenderer(self, rendererContext): return FreehandRasterGeoreferencerLayerRenderer(self, rendererContext) def setBlendModeByName(self, modeName): self.blendModeName = modeName blendMode = getattr(QPainter, "CompositionMode_" + modeName, 0) self.setBlendMode(blendMode) self.setCustomProperty("blendMode", modeName) def setTransparency(self, transparency): self.transparency = transparency self.setCustomProperty("transparency", transparency) def draw(self, renderContext): if renderContext.extent().isEmpty(): qDebug("Drawing is skipped because map extent is empty.") return True self.initializeLayer() if not self.initialized: qDebug("Drawing is skipped because nothing to draw.") return True painter = renderContext.painter() painter.save() self.prepareStyle(painter) self.drawRaster(renderContext) painter.restore() return True def drawRaster(self, renderContext): painter = renderContext.painter() painter.setRenderHint(QPainter.SmoothPixmapTransform, True) self.map2pixel = renderContext.mapToPixel() scaleX = self.xScale / self.map2pixel.mapUnitsPerPixel() scaleY = self.yScale / self.map2pixel.mapUnitsPerPixel() rect = QRectF( QPointF(-self.image.width() / 2.0, -self.image.height() / 2.0), QPointF(self.image.width() / 2.0, self.image.height() / 2.0), ) mapCenter = self.map2pixel.transform(self.center) # draw the image on the map canvas painter.translate(QPointF(mapCenter.x(), mapCenter.y())) painter.rotate(self.rotation) painter.scale(scaleX, scaleY) painter.drawImage(rect, self.image) painter.setOpacity(1.0) painter.setBrush(Qt.NoBrush) pen = QPen() pen.setColor(QColor(0, 0, 0)) pen.setWidth(3) pen.setCosmetic(True) painter.setPen(pen) painter.drawRect(rect) def prepareStyle(self, painter): painter.setOpacity(1.0 - self.transparency / 100.0) def readXml(self, node, context): self.readCustomProperties(node) self.title = self.customProperty("title", "") self.filepath = self.customProperty("filepath", "") self.xScale = float(self.customProperty("xScale", 1.0)) self.yScale = float(self.customProperty("yScale", 1.0)) self.rotation = float(self.customProperty("rotation", 0.0)) xCenter = float(self.customProperty("xCenter", 0.0)) yCenter = float(self.customProperty("yCenter", 0.0)) self.center = QgsPointXY(xCenter, yCenter) self.setTransparency( int(self.customProperty("transparency", LayerDefaultSettings.TRANSPARENCY)) ) self.setBlendModeByName( self.customProperty("blendMode", LayerDefaultSettings.BLEND_MODE) ) return True def writeXml(self, node, doc, context): element = node.toElement() self.writeCustomProperties(node, doc) element.setAttribute("type", "plugin") element.setAttribute("name", FreehandRasterGeoreferencerLayer.LAYER_TYPE) return True def metadata(self): lines = [] fmt = "%s:\t%s" lines.append(fmt % (self.tr("Title"), self.title)) filepath = self.getAbsoluteFilepath() filepath = os.path.normpath(filepath) lines.append(fmt % (self.tr("Path"), filepath)) lines.append(fmt % (self.tr("Image Width"), str(self.image.width()))) lines.append(fmt % (self.tr("Image Height"), str(self.image.height()))) lines.append(fmt % (self.tr("Rotation (CW)"), str(self.rotation))) lines.append(fmt % (self.tr("X center"), str(self.center.x()))) lines.append(fmt % (self.tr("Y center"), str(self.center.y()))) lines.append(fmt % (self.tr("X scale"), str(self.xScale))) lines.append(fmt % (self.tr("Y scale"), str(self.yScale))) return "\n".join(lines) def log(self, msg): qDebug(msg) def dump(self, detail=False, bbox=None): pass def showStatusMessage(self, msg, timeout): self.iface.mainWindow().statusBar().showMessage(msg, timeout) def showBarMessage(self, title, text, level, duration): self.iface.messageBar().pushMessage(title, text, level, duration) def transparencyChanged(self, val): QgsProject.instance().setDirty(True) self.setTransparency(val) self.repaintRequested.emit() def setTransformContext(self, transformContext): pass
class QadCOPYCommandClass(QadCommandClass): def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadCOPYCommandClass(self.plugIn) def getName(self): return QadMsg.translate("Command_list", "COPY") def getEnglishName(self): return "COPY" def connectQAction(self, action): action.triggered.connect(self.plugIn.runCOPYCommand) def getIcon(self): return QIcon(":/plugins/qad/icons/copyEnt.png") def getNote(self): # impostare le note esplicative del comando return QadMsg.translate("Command_COPY", "Copies selected objects a specified distance in a specified direction.") def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.SSGetClass = QadSSGetClass(plugIn) self.SSGetClass.onlyEditableLayers = True self.cacheEntitySet = QadCacheEntitySet() self.basePt = QgsPointXY() self.series = False self.seriesLen = 2 self.adjust = False self.copyMode = QadVariables.get(QadMsg.translate("Environment variables", "COPYMODE")) self.nOperationsToUndo = 0 def __del__(self): QadCommandClass.__del__(self) del self.SSGetClass def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): if self.step == 0: # quando si é in fase di selezione entità return self.SSGetClass.getPointMapTool() else: if (self.plugIn is not None): if self.PointMapTool is None: self.PointMapTool = Qad_copy_maptool(self.plugIn) return self.PointMapTool else: return None def getCurrentContextualMenu(self): if self.step == 0: # quando si é in fase di selezione entità return None # return self.SSGetClass.getCurrentContextualMenu() else: return self.contextualMenu #============================================================================ # move #============================================================================ def move(self, entity, offsetX, offsetY): # verifico se l'entità appartiene ad uno stile di quotatura if entity.whatIs() == "ENTITY": # sposto la geometria dell'entità qadGeom = entity.getQadGeom().copy() # la copio qadGeom.move(offsetX, offsetY) f = entity.getFeature() f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.crs())) # plugIn, layer, feature, coordTransform, refresh, check_validity if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: return False elif entity.whatIs() == "DIMENTITY": newDimEntity = QadDimEntity(entity) # la copio # sposto la quota newDimEntity.move(offsetX, offsetY) if newDimEntity.addToLayers(self.plugIn) == False: return False return True #============================================================================ # copyGeoms #============================================================================ def copyGeoms(self, newPt): offsetX = newPt.x() - self.basePt.x() offsetY = newPt.y() - self.basePt.y() self.plugIn.beginEditCommand("Feature copied", self.cacheEntitySet.getLayerList()) dimElaboratedList = [] # lista delle quotature già elaborate entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) for entity in entityIterator: qadGeom = entity.getQadGeom() # così inizializzo le info qad # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is not None: if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata continue entity = dimEntity if self.seriesLen > 0: # devo fare una serie if self.adjust == True: offsetX = offsetX / (self.seriesLen - 1) offsetY = offsetY / (self.seriesLen - 1) deltaX = offsetX deltaY = offsetY for i in range(1, self.seriesLen, 1): if self.move(entity, deltaX, deltaY) == False: self.plugIn.destroyEditCommand() return deltaX = deltaX + offsetX deltaY = deltaY + offsetY else: if self.move(entity, offsetX, offsetY) == False: self.plugIn.destroyEditCommand() return self.plugIn.endEditCommand() self.nOperationsToUndo = self.nOperationsToUndo + 1 #============================================================================ # waitForBasePt #============================================================================ def waitForBasePt(self): # imposto il map tool self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ QadMsg.translate("Command_COPY", "mOde") englishKeyWords = "Displacement" + "/" + "mOde" else: # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" # e "Multipla" nel caso di modalità di copia keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ QadMsg.translate("Command_COPY", "mOde") + "/" + \ QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") englishKeyWords = "Displacement" + "/" + "mOde" + "/" + "Multiple" default = QadMsg.translate("Command_COPY", "Displacement") prompt = QadMsg.translate("Command_COPY", "Specify base point or [{0}] <{1}>: ").format(keyWords, default) keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ default, \ keyWords, QadInputModeEnum.NONE) self.step = 2 #============================================================================ # waitForSeries #============================================================================ def waitForSeries(self): # si appresta ad attendere un numero intero msg = QadMsg.translate("Command_COPY", "Number of Items to Array <{0}>: ") # msg, inputType, default, keyWords, valori positivi self.waitFor(msg.format(str(self.seriesLen)), \ QadInputTypeEnum.INT, \ self.seriesLen, \ "", \ QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) self.step = 6 #============================================================================ # waitForSecondPt #============================================================================ def waitForSecondPt(self): self.series = False self.adjust = False self.getPointMapTool().seriesLen = 0 self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) if self.nOperationsToUndo > 0: keyWords = QadMsg.translate("Command_COPY", "Array") + "/" + \ QadMsg.translate("Command_COPY", "Exit") + "/" + \ QadMsg.translate("Command_COPY", "Undo") default = QadMsg.translate("Command_COPY", "Exit") prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] <{1}>: ").format(keyWords, default) englishKeyWords = "Array" + "/" + "Exit" + "/" + "Undo" + "/" + "Exit" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ default, \ keyWords, QadInputModeEnum.NONE) else: keyWords = QadMsg.translate("Command_COPY", "Array") prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] <use first point as displacement from origin point 0,0>: ").format(keyWords) englishKeyWords = "Array" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) self.step = 3 #============================================================================ # waitForSecondPtBySeries #============================================================================ def waitForSecondPtBySeries(self): if self.adjust == False: keyWords = QadMsg.translate("Command_COPY", "Fit") englishKeyWords = "Fit" else: keyWords = QadMsg.translate("Command_COPY", "Array") englishKeyWords = "Array" prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}]: ").format(keyWords) keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, valore nullo non permesso self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ "", \ keyWords, QadInputModeEnum.NOT_NULL) self.step = 7 #============================================================================ # run #============================================================================ def run(self, msgMapTool = False, msg = None): if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) return True # fine comando #========================================================================= # RICHIESTA SELEZIONE OGGETTI if self.step == 0: # inizio del comando if self.SSGetClass.run(msgMapTool, msg) == True: # selezione terminata self.step = 1 return self.run(msgMapTool, msg) #========================================================================= # COPIA OGGETTI elif self.step == 1: if self.SSGetClass.entitySet.count() == 0: return True # fine comando self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") if self.copyMode == 0: # 0 = multipla CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Multiple") else: # 1 = singola CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Single") self.showMsg(CurrSettingsMsg) self.getPointMapTool().cacheEntitySet = self.cacheEntitySet self.waitForBasePt() self.getPointMapTool().refreshSnapType() # riagggiorno lo snapType che può essere variato dal maptool di selezione entità return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse pass # opzione di default "spostamento" else: self.setMapTool(self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value is None: value = QadMsg.translate("Command_COPY", "Displacement") if type(value) == unicode: if value == QadMsg.translate("Command_COPY", "Displacement") or value == "Displacement": self.basePt.set(0, 0) self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) # si appresta ad attendere un punto msg = QadMsg.translate("Command_COPY", "Specify the displacement from the origin point 0,0 <{0}, {1}>: ") # msg, inputType, default, keyWords, nessun controllo self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ QadInputTypeEnum.POINT2D, \ self.plugIn.lastOffsetPt, \ "", QadInputModeEnum.NONE) self.step = 4 elif value == QadMsg.translate("Command_COPY", "mOde") or value == "mOde": # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" keyWords = QadMsg.translate("Command_COPY", "Single") + "/" + \ QadMsg.translate("Command_COPY", "Multiple", "waitForMode") englishKeyWords = "Single" + "/" + "Multiple" if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" default = QadMsg.translate("Command_COPY", "Multiple", "waitForMode") else: default = QadMsg.translate("Command_COPY", "Single") prompt = QadMsg.translate("Command_COPY", "Enter a copy mode option [{0}] <{1}>: ").format(keyWords, default) keyWords += "_" + englishKeyWords # si appresta ad attendere enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.KEYWORDS, \ default, \ keyWords, QadInputModeEnum.NONE) self.step = 5 # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") or value == "Multiple": self.copyMode = 0 # Imposta il comando COPIA in modo che venga ripetuto automaticamente self.waitForBasePt() elif type(value) == QgsPointXY: # se é stato inserito il punto base self.basePt.set(value.x(), value.y()) # imposto il map tool self.getPointMapTool().basePt = self.basePt self.waitForSecondPt() return False #========================================================================= # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA (da step = 2) elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse if self.nOperationsToUndo > 0: value = QadMsg.translate("Command_COPY", "Exit") else: value = None else: self.setMapTool(self.getPointMapTool()) # riattivo il maptool return False else: value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value is None: if self.nOperationsToUndo > 0: value = QadMsg.translate("Command_COPY", "Exit") else: # utilizzare il primo punto come spostamento value = QgsPointXY(self.basePt) self.basePt.set(0, 0) self.copyGeoms(value) return True # fine comando if type(value) == unicode: if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": self.waitForSeries() elif value == QadMsg.translate("Command_COPY", "Exit") or value == "Exit": return True # fine comando elif value == QadMsg.translate("Command_COPY", "Undo") or value == "Undo": if self.nOperationsToUndo > 0: self.nOperationsToUndo = self.nOperationsToUndo - 1 self.plugIn.undoEditCommand() else: self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) self.waitForSecondPt() elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto self.copyGeoms(value) if self.copyMode == 1: # "Singola" return True # fine comando self.waitForSecondPt() return False #========================================================================= # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool(self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg self.plugIn.setLastOffsetPt(value) self.copyGeoms(value) return True # fine comando #========================================================================= # RISPOSTA ALLA RICHIESTA DELLA MODALITA' (SINGOLA / MULTIPLA) (da step = 2) elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool(self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # la parola chiave arriva come parametro della funzione value = msg if type(value) == unicode: if value == QadMsg.translate("Command_COPY", "Single") or value == "Single": self.copyMode = 1 QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 1) QadVariables.save() # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForMode") or value == "Multiple": self.copyMode = 0 QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 0) QadVariables.save() self.waitForBasePt() return False #========================================================================= # RISPOSTA ALLA RICHIESTA DELLA SERIE (da step = 3) elif self.step == 6: # dopo aver atteso un numero intero si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse value = self.seriesLen else: value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value < 2: self.showMsg(QadMsg.translate("Command_COPY", "\nThe value must be between 2 and 32767.")) self.waitForSeries() else: self.series = True self.seriesLen = value self.getPointMapTool().seriesLen = self.seriesLen self.waitForSecondPtBySeries() return False #========================================================================= # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA DA SERIE (da step = 6) elif self.step == 7: # dopo aver atteso un punto o una parola chiave if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool(self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if type(value) == unicode: if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": self.adjust = False self.getPointMapTool().adjust = self.adjust self.waitForSecondPtBySeries() elif value == QadMsg.translate("Command_COPY", "Fit") or value == "Fit": self.adjust = True self.getPointMapTool().adjust = self.adjust self.waitForSecondPtBySeries() elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto self.copyGeoms(value) if self.copyMode == 1: # "Singola" return True # fine comando self.waitForSecondPt() return False
class MobileItem(QObject): ''' A Mobile Item that reveives its position from a dataprovider and is displayed on the canvas Could be everything liek vehicles or simple beacons ''' mobileItemCount = 0 newPosition = pyqtSignal(float, QgsPointXY, float, float) newAttitude = pyqtSignal(float, float, float) # heading, pitch, roll timeout = pyqtSignal() def __init__(self, iface, params={}, parent=None): ''' Constructor :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface :param params: A dictionary defining all the properties of the item :type params: dictionary :param parent: Parent object for the new item. Defaults None. :type parent: QObject ''' super(MobileItem, self).__init__(parent) self.iface = iface self.canvas = iface.mapCanvas() MobileItem.mobileItemCount += 1 self.name = params.setdefault( 'Name', 'MobileItem_' + str(MobileItem.mobileItemCount)) self.marker = PositionMarker(self.canvas, params) self.marker.setToolTip(self.name) self.dataProvider = params.get('provider', dict()) self.messageFilter = dict() self.extData = dict() self.coordinates = None self.position = None self.heading = 0.0 self.depth = 0.0 self.altitude = 0.0 self.lastFix = 0.0 self.crsXform = QgsCoordinateTransform() self.crsXform.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326')) self.onCrsChange() self.canvas.destinationCrsChanged.connect(self.onCrsChange) if hasattr(self.canvas, 'magnificationChanged'): self.canvas.magnificationChanged.connect( self.onMagnificationChanged) self.timer = QTimer(self) self.timer.timeout.connect(self.timeout) self.notifyCount = int(params.get('nofixNotify', 0)) self.fadeOut = bool(params.get('fadeOut', False)) if self.notifyCount or self.fadeOut: self.timer.timeout.connect(self.notifyTimeout) self.timeoutCount = 0 self.timeoutTime = int(params.get('timeout', 3000)) self.notifyDuration = int(params.get('NotifyDuration', 0)) self.timedOut = False self.enabled = True def removeFromCanvas(self): ''' Remove the item and its track from the canvas ''' self.marker.removeFromCanvas() def properties(self): ''' Return the items properties as dictionary :returns: Items properties :rtype: dict ''' d = { 'Name': self.name, 'timeout': self.timeoutTime, 'nofixNotify': self.notifyCount, 'fadeOut': self.fadeOut, 'enabled': self.enabled, 'provider': self.dataProvider } d.update(self.marker.properties()) return d def subscribePositionProvider(self, provider, filterId=None): ''' Subscribe the provider for this item by connecting to the providers signals :param provider: Provider to connect to :type provider: DataProvider :param filterId: Filter Id for this item :type filterId: ''' provider.newDataReceived.connect(self.processNewData) try: if filterId['id'] not in (None, 'None') or filterId['flags']: self.messageFilter[provider.name] = filterId elif provider.name in self.messageFilter: self.messageFilter.pop(provider.name, None) except (KeyError, TypeError): self.messageFilter.pop(provider.name, None) def unsubscribePositionProvider(self, provider): ''' Unsubscribe provider by disconnecting the providers signals :param provider: Provider to diconnect from :type provider: DataProvider ''' try: provider.newDataReceived.disconnect(self.processData) self.messageFilter.pop(provider.name, None) except KeyError: pass @pyqtSlot(dict) def processNewData(self, data): ''' Process incoming data from the data provider :param data: Positon or attitude data :type data: dict ''' if not self.enabled: return flags = list() try: pname = data['name'] flags = self.messageFilter[pname]['flags'] if not self.messageFilter[pname]['id'] in (None, 'None'): if not data['id'] in (self.messageFilter[pname]['id'], str(self.messageFilter[pname]['id'])): return except Exception: pass self.extData.update(data) if '-pos' not in flags: if 'lat' in data and 'lon' in data: if self.fadeOut and self.timedOut: self.marker.setVisible(True) self.timedOut = False self.position = QgsPointXY(data['lon'], data['lat']) self.heading = data.get('heading', -9999.9) self.depth = data.get('depth', -9999.9) self.altitude = data.get('altitude', -9999.9) try: self.coordinates = self.crsXform.transform(self.position) self.marker.setMapPosition(self.coordinates) if 'time' in data: self.lastFix = data['time'] self.newPosition.emit( self.lastFix, self.position, self.extData.get('depth', -9999.9), self.extData.get('altitude', -9999.9)) self.timer.start(self.timeoutTime) self.timeoutCount = 0 except QgsCsException: pass elif self.position is not None: if 'depth' in data or 'altitude' in data: self.newPosition.emit( self.lastFix, self.position, self.extData.get('depth', -9999.9), self.extData.get('altitude', -9999.9)) if 'heading' in data and '-head' not in flags: self.newAttitude.emit(data['heading'], data.get('pitch', 0.0), data.get('roll', 0.0)) self.marker.newHeading(data['heading']) self.heading = data['heading'] elif 'course' in data and '+course' in flags: self.newAttitude.emit(data['course'], data.get('pitch', 0.0), data.get('roll', 0.0)) self.marker.newHeading(data['course']) self.heading = data['course'] @pyqtSlot(float) def onScaleChange(self, ): ''' Slot called when the map is zoomed :param scale: New scale :type scale: float ''' self.marker.updatePosition() @pyqtSlot() def onCrsChange(self): ''' SLot called when the mapcanvas CRS is changed ''' crsDst = self.canvas.mapSettings().destinationCrs() self.crsXform.setDestinationCrs(crsDst) self.marker.updatePosition() @pyqtSlot(float) def onMagnificationChanged(self, ): ''' Slot called when the map magnification has changed :param scale: New scale :type scale: float ''' self.marker.updateMapMagnification() @pyqtSlot(bool) def setEnabled(self, enabled): ''' Hide or display the item and its track on the map :param enabled: what to do :type enabled: bool ''' self.enabled = enabled self.marker.setVisible(self.enabled) self.marker.resetPosition() self.extData.clear() if self.enabled: self.timer.start(self.timeoutTime) self.timeoutCount = 0 else: self.timer.stop() @pyqtSlot() def deleteTrack(self): ''' Delete the track all points ''' self.marker.deleteTrack() @pyqtSlot() def centerOnMap(self): ''' Center the item on the map ''' if self.coordinates is not None: self.canvas.setCenter(self.coordinates) self.canvas.refresh() def reportPosition(self): ''' Report the position of the item. Used for logging :returns: geographic postion, depth and altitude :rtype: float, float, float, float, float ''' if self.position is None: return -9999.9, -9999.9, -9999.9, 0.0, -9999.9 return self.position.y(), self.position.x( ), self.depth, self.heading, self.altitude @pyqtSlot() def notifyTimeout(self): if self.fadeOut and not self.timedOut: self.marker.setVisible(False) self.timedOut = True if self.notifyCount: self.timeoutCount += 1 if self.timeoutCount == self.notifyCount: msg = self.tr(u'No fix for %s since more than %d seconds!') % ( self.name, self.timeoutTime * self.timeoutCount / 1000) w = self.iface.messageBar().createMessage( self.tr(u'PosiView Attention'), msg) label = QLabel(w) m = QMovie(':/plugins/PosiView/hand.gif') m.setSpeed(75) label.setMovie(m) m.setParent(label) m.start() w.layout().addWidget(label) self.iface.messageBar().pushWidget( w, level=Qgis.Critical, duration=self.notifyDuration) def getTrack(self): tr = [e[1] for e in self.marker.track] return tr def applyTrack(self, track): self.marker.setTrack(track)
class TestQgsPointXY(unittest.TestCase): def __init__(self, methodName): """Run once on class initialization.""" unittest.TestCase.__init__(self, methodName) def setUp(self): self.mPoint = QgsPointXY(10.0, 10.0) def test_Point(self): myExpectedValue = 10.0 myActualValue = self.mPoint.x() myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myActualValue) assert myExpectedValue == myActualValue, myMessage def test_pointToString(self): myExpectedValue = '10, 10' myActualValue = self.mPoint.toString() myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myActualValue) assert myExpectedValue == myActualValue, myMessage def test_hash(self): a = QgsPointXY(2.0, 1.0) b = QgsPointXY(2.0, 2.0) c = QgsPointXY(1.0, 2.0) d = QgsPointXY(1.0, 1.0) e = QgsPointXY(2.0, 1.0) assert a.__hash__() != b.__hash__() assert e.__hash__() == a.__hash__() mySet = set([a, b, c, d, e]) assert len(mySet) == 4 def test_issue_32443(self): p = QgsPoint() assert p.wkbType() == QgsWkbTypes.Point and p.x() != p.x() and p.y() != p.y() # ctor from QgsPointXY should be available p = QgsPoint(QgsPointXY(1, 2)) assert p.wkbType() == QgsWkbTypes.Point and p.x() == 1 and p.y() == 2 # ctor from QPointF should be available p = QgsPoint(QPointF(1, 2)) assert p.wkbType() == QgsWkbTypes.Point and p.x() == 1 and p.y() == 2 p = QgsPoint(1, 2) assert p.wkbType() == QgsWkbTypes.Point and p.x() == 1 and p.y() == 2 p = QgsPoint(1, 2, 3) assert p.wkbType() == QgsWkbTypes.PointZ and p.x() == 1 and p.y() == 2 and p.z() == 3 p = QgsPoint(1, 2, z=3) assert p.wkbType() == QgsWkbTypes.PointZ and p.x() == 1 and p.y() == 2 and p.z() == 3 p = QgsPoint(1, 2, m=3) assert p.wkbType() == QgsWkbTypes.PointM and p.x() == 1 and p.y() == 2 and p.m() == 3 p = QgsPoint(1, 2, wkbType=QgsWkbTypes.PointM) assert p.wkbType() == QgsWkbTypes.PointM and p.x() == 1 and p.y() == 2 and p.m() != p.m() p = QgsPoint(1, 2, 3, 4) assert p.wkbType() == QgsWkbTypes.PointZM and p.x() == 1 and p.y() == 2 and p.z() == 3 and p.m() == 4 p = QgsPoint(1, 2, m=4, z=3) assert p.wkbType() == QgsWkbTypes.PointZM and p.x() == 1 and p.y() == 2 and p.z() == 3 and p.m() == 4
def processLine(layer, writerLines, discardVertices, isProcessing): layercrs = layer.crs() if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(epsg4326, layercrs, QgsProject.instance()) iterator = layer.getFeatures() num_features = 0 num_bad = 0 maxseglen = settings.maxSegLength*1000.0 maxSegments = settings.maxSegments for feature in iterator: num_features += 1 try: wkbtype = feature.geometry().wkbType() if wkbtype == QgsWkbTypes.LineString: seg = [feature.geometry().asPolyline()] else: seg = feature.geometry().asMultiPolyline() numseg = len(seg) if numseg < 1 or len(seg[0]) < 2: continue # Create a new Line Feature fline = QgsFeature() # If the input is not 4326 we need to convert it to that and then back to the output CRS if discardVertices: ptStart = QgsPointXY(seg[0][0][0], seg[0][0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] numpoints = len(seg[numseg-1]) ptEnd = QgsPointXY(seg[numseg-1][numpoints-1][0], seg[numseg-1][numpoints-1][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) pts.append(ptEnd) if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) else: if wkbtype == QgsWkbTypes.LineString: line = seg[0] numpoints = len(line) ptStart = QgsPointXY(line[0][0], line[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1,numpoints): ptEnd = QgsPointXY(line[x][0], line[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) n = int(math.ceil(l.s13 / maxseglen)) if l.s13 > maxseglen: if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) else: # MultiLineString outseg = [] for line in seg: numpoints = len(line) ptStart = QgsPointXY(line[0][0], line[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1,numpoints): ptEnd = QgsPointXY(line[x][0], line[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) n = int(math.ceil(l.s13 / maxseglen)) if l.s13 > maxseglen: if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) outseg.append(pts) fline.setGeometry(QgsGeometry.fromMultiPolylineXY(outseg)) fline.setAttributes(feature.attributes()) if isProcessing: writerLines.addFeature(fline) else: writerLines.addFeatures([fline]) except: num_bad += 1 #traceback.print_exc() pass return num_bad
def processPoly(layer, writerLines, isProcessing): layercrs = layer.crs() if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(epsg4326, layercrs, QgsProject.instance()) iterator = layer.getFeatures() num_features = 0 num_bad = 0 maxseglen = settings.maxSegLength*1000.0 maxSegments = settings.maxSegments for feature in iterator: num_features += 1 try: wkbtype = feature.geometry().wkbType() if wkbtype == QgsWkbTypes.Polygon: poly = feature.geometry().asPolygon() numpolygons = len(poly) if numpolygons < 1: continue ptset = [] for points in poly: numpoints = len(points) if numpoints < 2: continue # If the input is not 4326 we need to convert it to that and then back to the output CRS ptStart = QgsPointXY(points[0][0], points[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1,numpoints): ptEnd = QgsPointXY(points[x][0], points[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) n = int(math.ceil(l.s13 / maxseglen)) if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) ptset.append(pts) if len(ptset) > 0: featureout = QgsFeature() featureout.setGeometry(QgsGeometry.fromPolygonXY(ptset)) featureout.setAttributes(feature.attributes()) if isProcessing: writerLines.addFeature(featureout) else: writerLines.addFeatures([featureout]) else: multipoly = feature.geometry().asMultiPolygon() multiset = [] for poly in multipoly: ptset = [] for points in poly: numpoints = len(points) if numpoints < 2: continue # If the input is not 4326 we need to convert it to that and then back to the output CRS ptStart = QgsPointXY(points[0][0], points[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1,numpoints): ptEnd = QgsPointXY(points[x][0], points[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) n = int(math.ceil(l.s13 / maxseglen)) if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) ptset.append(pts) multiset.append(ptset) if len(multiset) > 0: featureout = QgsFeature() featureout.setGeometry(QgsGeometry.fromMultiPolygonXY(multiset)) featureout.setAttributes(feature.attributes()) if isProcessing: writerLines.addFeature(featureout) else: writerLines.addFeatures([featureout]) except: num_bad += 1 #traceback.print_exc() pass return num_bad
def result(self, result): if result: # save a project reference project = QgsProject.instance() # Save the user input lineedit_text = self.dlg.lineedit_xy.value() crs_input = self.dlg.crs_input.crs() crs_out = QgsCoordinateReferenceSystem( 4326) # we need this to be WGS84 for Nominatim # Protect the free text field for coordinates from generic user failure:) try: lineedit_yx = [ float(coord.strip()) for coord in lineedit_text.split(',') ] except: QMessageBox.critical( self.iface.mainWindow(), 'QuickAPI error', "Did you really specify a coordinate in comma-separated Lat/Long?\nExiting..." ) return # Create a Point and transform if necessary point = QgsPointXY(*reversed(lineedit_yx)) if crs_input.authid() != 'EPSG:4326': xform = QgsCoordinateTransform(crs_input, crs_out, project) point_transform = xform.transform(point) point = point_transform # Set up and fire Nominatim GET request user_agent = '*****@*****.**' base_url = 'https://nominatim.openstreetmap.org/reverse' params = {'lat': point.y(), 'lon': point.x(), 'format': 'json'} response = requests.get(url=base_url, params=params, headers={'User-Agent': user_agent}) response_json = response.json() # Only process response if HTTP 200 if response.status_code == 200: # Unfortunately Nominatim goes against protocol and responds with 200 even if an error occurred if response_json.get('error'): QMessageBox.critical( self.iface.mainWindow(), "Quick API error", "The request was not processed succesfully!\n\n" "Message:\n" "{}".format(response.json())) return # Capture relevant response fields x = float(response_json['lon']) y = float(response_json['lat']) address = response_json['display_name'] license = response_json['licence'] # Create the output memory layer layer_out = QgsVectorLayer( "Point?crs=EPSG:4326&field=address:string&field=license:string", "Nominatim Reverse Geocoding", "memory") # Create the output feature (only one here) point_out = QgsPointXY(x, y) feature = QgsFeature() feature.setGeometry(QgsGeometry.fromPointXY(point_out)) feature.setAttributes([address, license]) # Add feature to layer and layer to map layer_out.dataProvider().addFeature(feature) layer_out.updateExtents() project.addMapLayer(layer_out) # build bbox for auto-zoom feature bbox = [float(coord) for coord in response_json['boundingbox']] min_y, max_y, min_x, max_x = bbox bbox_geom = QgsGeometry.fromPolygonXY([[ QgsPointXY(min_x, min_y), QgsPointXY(min_x, max_y), QgsPointXY(max_x, max_y), QgsPointXY(max_x, min_y), ]]) # Transform bbox if map canvas has a different CRS if project.crs().authid() != 'EPSG:4326': xform = QgsCoordinateTransform(crs_out, project.crs(), project) bbox_geom.transform(xform) self.iface.mapCanvas().zoomToFeatureExtent( QgsRectangle.fromWkt(bbox_geom.asWkt()))
def processAlgorithm(self, parameters, context, feedback): try: # read out parameters input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) in_crs = input_layer.crs() attribute_name = parameters[self.ATTRIBUTE_NAME] coverage_id = [ self.coverages[i] for i in self.parameterAsEnums( parameters, self.COVERAGE_ID, context) ][0] # start processing fields = input_layer.fields() fields.append(QgsField(attribute_name, QVariant.Double)) field_names = [field.name() for field in fields] (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT, context, fields, input_layer.wkbType(), in_crs, ) if feedback.isCanceled(): return {} wcs_proj_authid = self.get_native_proj_authid(coverage_id) if in_crs.authid() != wcs_proj_authid: wcs_crs = QgsCoordinateReferenceSystem(wcs_proj_authid) transform_input = QgsCoordinateTransform( in_crs, wcs_crs, QgsProject.instance()) for feature in input_layer.getFeatures(): geom = feature.geometry() if in_crs.authid() != wcs_proj_authid: geom.transform(transform_input) point_geom = QgsGeometry.asPoint(geom) point_xy = QgsPointXY(point_geom) x = point_xy.x() y = point_xy.y() attrs = feature.attributes() new_ft = QgsFeature(fields) for i in range(len(attrs)): attr = attrs[i] field_name = field_names[i] new_ft.setAttribute(field_name, attr) ds = self.get_gdal_ds_from_wcs(x, y, coverage_id, feedback) nodata = self.get_nodata_from_gdal_ds(ds) ahn_val = self.get_val_from_gdal_ds(x, y, ds) ds = None if ahn_val == nodata: fid = feature.id() feedback.pushWarning( f"NODATA value found for feature with id: {fid}, geom: POINT({x},{y})" ) ahn_val = None new_ft.setAttribute(attribute_name, ahn_val) new_ft.setGeometry(geom) sink.addFeature(new_ft, QgsFeatureSink.FastInsert) if feedback.isCanceled(): return {} results = {} results[self.OUTPUT] = dest_id return results except Exception as e: traceback_str = traceback.format_exc() toolname = self.displayName() message = f"Unexpected error occured while running {toolname}: {e} - traceback: {traceback_str}" self.log_message(message, loglevel=2) raise QgsProcessingException(message)
def accept(self): layer = self.inputMapLayerComboBox.currentLayer() if not layer: self.iface.messageBar().pushMessage("", "No Valid Layer", level=Qgis.Warning, duration=4) pointname = self.pointsNameLineEdit.text() linename = self.lineNameLineEdit.text() startXcol = self.startXFieldComboBox.currentIndex( ) # Returns -1 if none selected startYcol = self.startYFieldComboBox.currentIndex() endXcol = self.endXFieldComboBox.currentIndex() endYcol = self.endYFieldComboBox.currentIndex() startUseGeom = self.startCheckBox.isChecked() endUseGeom = self.endCheckBox.isChecked() inCRS = self.inputQgsProjectionSelectionWidget.crs() outCRS = self.outputQgsProjectionSelectionWidget.crs() lineType = self.lineTypeComboBox.currentIndex() showStart = self.showStartCheckBox.isChecked() showEnd = self.showEndCheckBox.isChecked() if (startUseGeom == False) and (startXcol == -1 or startYcol == -1): self.iface.messageBar().pushMessage( "", "Must specify valid starting point columns", level=Qgis.Warning, duration=4) return if (endUseGeom == False) and (endXcol == -1 or endYcol == -1): self.iface.messageBar().pushMessage( "", "Must specify valid ending point columns", level=Qgis.Warning, duration=4) return # If we are using the layer geometry then we ignore the selected input and output CRS if startUseGeom: inCRS = layer.crs() if endUseGeom: outCRS = layer.crs() # Get the field names for the input layer. The will be copied to the output layers fields = layer.fields() # Create the points and line output layers lineLayer = QgsVectorLayer("LineString?crs={}".format(outCRS.authid()), linename, "memory") pline = lineLayer.dataProvider() pline.addAttributes(fields) lineLayer.updateFields() if showStart or showEnd: pointLayer = QgsVectorLayer("Point?crs={}".format(outCRS.authid()), pointname, "memory") ppoint = pointLayer.dataProvider() ppoint.addAttributes(fields) pointLayer.updateFields() transform = QgsCoordinateTransform(inCRS, outCRS, QgsProject.instance()) if inCRS != epsg4326: transto4326 = QgsCoordinateTransform(inCRS, epsg4326, QgsProject.instance()) if outCRS != epsg4326: transfrom4326 = QgsCoordinateTransform(epsg4326, outCRS, QgsProject.instance()) iter = layer.getFeatures() num_features = 0 num_bad = 0 maxseglen = settings.maxSegLength * 1000.0 maxSegments = settings.maxSegments for feature in iter: num_features += 1 try: if startUseGeom == True: ptStart = feature.geometry().asPoint() else: ptStart = QgsPointXY(float(feature[startXcol]), float(feature[startYcol])) if endUseGeom == True: ptEnd = feature.geometry().asPoint() else: ptEnd = QgsPointXY(float(feature[endXcol]), float(feature[endYcol])) # Create a new Line Feature fline = QgsFeature() if lineType == 0: # Geodesic # If the input is not 4326 we need to convert it to that and then back to the output CRS if inCRS != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) ptEnd = transto4326.transform(ptEnd) pts = [ptStart] l = self.geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY(g['lon2'], g['lat2'])) pts.append(ptEnd) if outCRS != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) ptStart = pts[0] ptEnd = pts[len(pts) - 1] elif lineType == 1: # Great Circle # If the input is not 4326 we need to convert it to that and then back to the output CRS if inCRS != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) ptEnd = transto4326.transform(ptEnd) pts = LatLon.getPointsOnLine( ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x(), settings.maxSegLength * 1000.0, # Put it in meters settings.maxSegments + 1) if outCRS != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) ptStart = pts[0] ptEnd = pts[len(pts) - 1] else: # Simple line '''Transform the starting and end points if the input CRS and the output CRS are not the same and then create a 2 point polyline''' if inCRS != outCRS: ptStart = transform.transform(ptStart) ptEnd = transform.transform(ptEnd) fline.setGeometry( QgsGeometry.fromPolylineXY([ptStart, ptEnd])) fline.setAttributes(feature.attributes()) pline.addFeatures([fline]) # Add two point features if showStart: fpoint = QgsFeature() fpoint.setGeometry(QgsGeometry.fromPointXY(ptStart)) fpoint.setAttributes(feature.attributes()) ppoint.addFeatures([fpoint]) if showEnd: fpoint = QgsFeature() fpoint.setGeometry(QgsGeometry.fromPointXY(ptEnd)) fpoint.setAttributes(feature.attributes()) ppoint.addFeatures([fpoint]) except: num_bad += 1 pass lineLayer.updateExtents() QgsProject.instance().addMapLayer(lineLayer) if showStart or showEnd: pointLayer.updateExtents() QgsProject.instance().addMapLayer(pointLayer) if num_bad != 0: self.iface.messageBar().pushMessage( "", "{} out of {} features failed".format(num_bad, num_features), level=Qgis.Warning, duration=3) self.close()
def putKPPointsAlongLine(self, source, maxseglen): "We travel along the line and test if each consecutive segment should contain KPs and how many" layercrs = source.sourceCrs() if layercrs != KPTool.epsg4326: transto4326 = QgsCoordinateTransform(layercrs, KPTool.epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(KPTool.epsg4326, layercrs, QgsProject.instance()) new_pt_layer = QgsVectorLayer( 'Point', 'KP_points_' + str(source.name()), 'memory') # define new layer we will return new_pt_layer.setCrs(source.crs()) # get crs from input layer new_pt_layer.startEditing() provider_ptLayer = new_pt_layer.dataProvider( ) # provider for new layer to add the features to provider_ptLayer.addAttributes([QgsField('KP', QVariant.Double)]) iterator = source.getFeatures() KP_label_count = 0 for cnt, feature in enumerate(iterator): if feature.geometry().isMultipart(): seg = feature.geometry().asMultiPolyline() else: seg = [feature.geometry().asPolyline()] numseg = len(seg) if numseg < 1 or len(seg[0]) < 2: self.iface.messageBar().pushMessage( 'Less than one segment in line layer', level=Qgis.Critical, duration=2) continue for line in seg: numpoints = len(line) if self.Reverse_KP_points == 2: #reverse point order for reverse KP _ = line.reverse() ptStart = QgsPointXY(line[0][0], line[0][1]) new_kp_point = self.createFeatureFromPoint( ptStart, KP_label_count) provider_ptLayer.addFeatures([new_kp_point]) remaining_dist = 0.0 #remaining distance to next point if layercrs != KPTool.epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) for x in range(1, numpoints): ptEnd = QgsPointXY(line[x][0], line[x][1]) if layercrs != KPTool.epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) gline = KPTool.geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if remaining_dist + gline.s13 > maxseglen: #we have to place at least one KP s = maxseglen - remaining_dist g = gline.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) ptKP = QgsPointXY(g['lon2'], g['lat2']) if layercrs != KPTool.epsg4326: ptKP = transfrom4326.transform(ptKP) KP_label_count = KP_label_count + maxseglen remaining_dist = remaining_dist + gline.s13 - maxseglen new_kp_point = self.createFeatureFromPoint( ptKP, KP_label_count) provider_ptLayer.addFeatures([new_kp_point]) if remaining_dist > maxseglen: #we need to place more KP pts extra_from_start = s n = int(remaining_dist / maxseglen) for i in range(0, n): s = maxseglen * (i + 1) + extra_from_start g = gline.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) ptKP = QgsPointXY(g['lon2'], g['lat2']) if layercrs != KPTool.epsg4326: # Convert each point back to the output CRS ptKP = transfrom4326.transform(ptKP) KP_label_count = KP_label_count + maxseglen remaining_dist = remaining_dist - maxseglen new_kp_point = self.createFeatureFromPoint( ptKP, KP_label_count) provider_ptLayer.addFeatures([new_kp_point]) else: #no KPs placed in this segment, keep the cumulative distance remaining_dist = remaining_dist + gline.s13 ptStart = ptEnd new_pt_layer.commitChanges() return new_pt_layer
class SelectFeaturesTool(QgsMapTool): selection_clicked = pyqtSignal(list) def __init__(self, mission_track, canvas): QgsMapTool.__init__(self, canvas) self.setCursor(Qt.ArrowCursor) self.mission_track = mission_track self.layer = self.mission_track.get_mission_layer() self.rubber_band = None self.rubber_band_points = None self.selection_polygon = [] self.indexes_within_list = [] self.band_finished = True self.mCtrl = False self.p0, self.p1, self.p2, self.p3 = None, None, None, None self.mission_track.mission_changed.connect(self.update_rubber_band) self.mission_track.step_removed.connect(self.remove_rubber_band) self.wp = self.mission_track.find_waypoints_in_mission() self.layer.startEditing() self.rubber_band_vs_track_indexes = {} self.rubber_band_points = QgsRubberBand(self.canvas(), QgsWkbTypes.PointGeometry) self.rubber_band_points.setIcon(QgsRubberBand.ICON_CIRCLE) self.rubber_band_points.setIconSize(10) self.rubber_band_points.setColor(QColor("green")) self.rubber_band_vertex_counter = 0 def keyPressEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = True def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = False if event.key() == Qt.Key_Escape: self.p0, self.p1, self.p2, self.p3 = None, None, None, None if self.rubber_band: self.rubber_band.reset(True) self.close_polygon_band() self.band_finished = True self.canvas().refresh() return def canvasPressEvent(self, event): if event.button() == Qt.LeftButton: point = self.toMapCoordinates(event.pos()) # check if we have clicked on a vertex tolerance = self.calc_tolerance() vertex = self.find_vertex_at(event.pos(), tolerance) if self.mCtrl and vertex is not None: # if we have clicked on a vertex, identify which one # check if was already in the selection list if vertex not in self.indexes_within_list: # add it self.indexes_within_list.append(vertex) self.update_rubber_band() else: # remove it self.indexes_within_list.remove(vertex) self.update_rubber_band() self.band_finished = True elif vertex is None: # if we have not clicked on a vertex and there's no polygon band, start it if not len(self.selection_polygon ) and self.band_finished and not self.mCtrl: self.selection_clicked.emit(list()) self.band_finished = False self.rubber_band = QgsRubberBand( self.canvas(), QgsWkbTypes.PolygonGeometry) self.rubber_band.setWidth(2) select_green = QColor("green") select_green.setAlpha(128) self.rubber_band.setColor(select_green) if event.button() == Qt.LeftButton: # Left click -> add vertex self.p0 = QgsPointXY(point.x(), point.y()) self.selection_polygon.append(self.p0) elif len(self.selection_polygon) == 1 and not self.mCtrl: if event.button() == Qt.LeftButton: # Left click -> add vertex self.p2 = QgsPointXY(point.x(), point.y()) self.p1 = QgsPointXY(self.p2.x(), self.p0.y()) self.p3 = QgsPointXY(self.p0.x(), self.p2.y()) self.selection_polygon.append(self.p1) self.selection_polygon.append(self.p2) self.selection_polygon.append(self.p3) self.band_finished = True self.set_selection() self.close_polygon_band() self.selection_clicked.emit(self.indexes_within_list) def find_vertex_at(self, pos, tolerance): """ get the vertex that is closer to the clicked point :param pos: The point that we've clicked :param tolerance: The tolerance of pos :return: vertex or None """ if len(self.wp) > 0: dist_to_vertex = [] for v in range(0, len(self.wp)): a1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v])) dist_to_vertex.append( math.sqrt((pos.x() - a1.x())**2 + (pos.y() - a1.y())**2)) vertex = dist_to_vertex.index(min(dist_to_vertex)) if min(dist_to_vertex) > tolerance: return None else: return vertex else: return None def calc_tolerance(self): """ Compute the tolerance on canvas :return: tolerance """ # 2% of tolerance width_tolerance = 0.02 * self.canvas().width() height_tolerance = 0.02 * self.canvas().height() if width_tolerance < height_tolerance: tolerance = width_tolerance else: tolerance = height_tolerance return tolerance def canvasMoveEvent(self, event): if not self.band_finished and not self.mCtrl: self.p2 = self.toMapCoordinates(event.pos()) self.p1 = QgsPointXY(self.p2.x(), self.p0.y()) self.p3 = QgsPointXY(self.p0.x(), self.p2.y()) self.selection_polygon.append(self.p1) self.selection_polygon.append(self.p2) self.selection_polygon.append(self.p3) self.rubber_band.setToGeometry( QgsGeometry.fromPolygonXY([self.selection_polygon]), None) self.selection_polygon.pop() self.selection_polygon.pop() self.selection_polygon.pop() def set_selection(self): """ Set vertices highlight according to polygon """ # Check which features are within the polygon mission_track = self.layer.getFeatures() # get mission track feature for f in mission_track: # loop although mission layer only has one feature vertices_it = f.geometry().vertices() polygon_geom = QgsGeometry.fromPolygonXY([self.selection_polygon]) vertices_within_list = [] # self.indexes_within_list = [] vertex_index = 0 # Highlight them using a point rubber band self.rubber_band_vertex_counter = 0 for v in vertices_it: point_geom = QgsGeometry.fromPointXY(QgsPointXY(v.x(), v.y())) if point_geom.within(polygon_geom): vertices_within_list.append(v) if not (vertex_index in self.indexes_within_list ): # only add if not already present self.indexes_within_list.append(vertex_index) self.rubber_band_points.addPoint(QgsPointXY(v.x(), v.y())) self.rubber_band_vertex_counter = self.rubber_band_vertex_counter + 1 self.rubber_band_vs_track_indexes[ vertex_index] = self.rubber_band_vertex_counter - 1 vertex_index = vertex_index + 1 def update_rubber_band(self): if self.rubber_band_points: self.rubber_band_points.reset(QgsWkbTypes.PointGeometry) self.rubber_band_vs_track_indexes = {} self.rubber_band_vertex_counter = 0 self.wp = self.mission_track.find_waypoints_in_mission() if len(self.indexes_within_list) > 0: selected_vertices = self.mission_track.find_waypoints_in_mission( self.indexes_within_list) for v in selected_vertices: vertex_index = 0 for point in self.wp: if v == point: pc = self.toLayerCoordinates(self.layer, QgsPointXY(v)) self.rubber_band_points.addPoint(pc) self.rubber_band_vertex_counter = self.rubber_band_vertex_counter + 1 self.rubber_band_vs_track_indexes[ vertex_index] = self.rubber_band_vertex_counter - 1 vertex_index = vertex_index + 1 self.set_geometry() def remove_rubber_band(self, wp): # if self.rubber_band_points: # self.rubber_band_points.reset(QgsWkbTypes.PointGeometry) self.indexes_within_list.remove(wp) self.update_rubber_band() def set_geometry(self): """ Save rubber band to geometry of the layer """ if self.layer.featureCount() == 0: # no feature yet created f = QgsFeature() if len(self.wp) == 1: f.setGeometry( QgsGeometry.fromPointXY( QgsPointXY(self.wp[0].x(), self.wp[0].y()))) else: f.setGeometry(QgsGeometry.fromPolyline(self.wp)) # self.layer.dataProvider().addFeatures([f]) self.layer.addFeatures([f]) else: # mission feature present, edit geometry feats = self.layer.getFeatures() for f in feats: if len(self.wp) == 1: self.layer.changeGeometry( f.id(), QgsGeometry.fromPointXY( QgsPointXY(self.wp[0].x(), self.wp[0].y()))) else: self.layer.changeGeometry( f.id(), QgsGeometry.fromPolyline(self.wp)) self.layer.commitChanges() self.layer.startEditing() def close_polygon_band(self): self.selection_polygon = [] if self.rubber_band is not None: self.rubber_band.reset() self.canvas().scene().removeItem(self.rubber_band) self.rubber_band = None def close_highlight_band(self): self.rubber_band_points.reset() self.canvas().scene().removeItem(self.rubber_band_points) self.rubber_band_points = None def deactivate(self): if self.rubber_band: self.close_polygon_band() if self.rubber_band_points: self.close_highlight_band() try: self.mission_track.mission_changed.disconnect( self.update_rubber_band) self.mission_track.step_removed.disconnect(self.remove_rubber_band) except: logger.info("no connected to signal") self.layer.commitChanges()
class ShowExtent(QgsMapTool): """Show an extent in the canvas""" ShowEnded = pyqtSignal() def __init__(self, canvas): """Constructor""" QgsMapTool.__init__(self, canvas) self.canvas = canvas self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) color = QColor(30, 230, 30, 65) self.rubberBand.setColor(color) self.rubberBand.setWidth(1) self.start_point = self.end_point = None def canvasPressEvent(self, event): """Change the outcome of the click event to end the ongoing process.""" _ = event self.rubberBand.hide() self.ShowEnded.emit() def show_extent(self, extent: QgsRectangle): """Display the extent on the canvas""" self.start_point = QgsPointXY(extent.xMinimum(), extent.yMinimum()) self.end_point = QgsPointXY(extent.xMaximum(), extent.yMaximum()) self.transform_coordinates() self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) point1 = QgsPointXY(self.start_point.x(), self.start_point.y()) point2 = QgsPointXY(self.start_point.x(), self.end_point.y()) point3 = QgsPointXY(self.end_point.x(), self.end_point.y()) point4 = QgsPointXY(self.end_point.x(), self.start_point.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) self.rubberBand.show() rect = QgsRectangle(self.start_point, self.end_point) self.canvas.setExtent(rect) def transform_coordinates(self): """Transform the coordinates in 4326.""" if self.start_point is None or self.end_point is None: return None if self.start_point.x() == self.end_point.x() or self.start_point.y( ) == self.end_point.y(): return None # Defining the crs from src and destiny epsg = self.canvas.mapSettings().destinationCrs().authid() crs_dest = QgsCoordinateReferenceSystem(epsg) crs_src = QgsCoordinateReferenceSystem('EPSG:4326') # Creating a transformer transformer = QgsCoordinateTransform(crs_src, crs_dest, QgsProject.instance()) # Transforming the points self.start_point = transformer.transform(self.start_point) self.end_point = transformer.transform(self.end_point)
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) measureTotal = self.parameterAsBool(parameters, self.PrmMeasureTotalLength, context) units = self.parameterAsInt(parameters, self.PrmUnitsOfMeasure, context) autoStyle = self.parameterAsBool(parameters, self.PrmAutomaticStyline, context) srcCRS = source.sourceCrs() f = QgsFields() f.append(QgsField("label", QVariant.String)) f.append(QgsField("distance", QVariant.Double)) f.append(QgsField("units", QVariant.String)) if not measureTotal: f.append(QgsField("heading_to", QVariant.Double)) f.append(QgsField("total_distance", QVariant.Double)) (sink, dest_id) = self.parameterAsSink( parameters, self.PrmOutputLayer, context, f, QgsWkbTypes.LineString, srcCRS) if srcCRS != epsg4326: geomTo4326 = QgsCoordinateTransform(srcCRS, epsg4326, QgsProject.instance()) toSinkCrs = QgsCoordinateTransform(epsg4326, srcCRS, QgsProject.instance()) wkbtype = source.wkbType() geomtype = QgsWkbTypes.geometryType(wkbtype) featureCount = source.featureCount() total = 100.0 / featureCount if featureCount else 0 iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break if geomtype == QgsWkbTypes.LineGeometry: if feature.geometry().isMultipart(): ptdata = [feature.geometry().asMultiPolyline()] else: ptdata = [[feature.geometry().asPolyline()]] else: #polygon if feature.geometry().isMultipart(): ptdata = feature.geometry().asMultiPolygon() else: ptdata = [feature.geometry().asPolygon()] if len(ptdata) < 1: continue for seg in ptdata: if len(seg) < 1: continue if measureTotal: for pts in seg: numpoints = len(pts) if numpoints < 2: continue f = QgsFeature() f.setGeometry(QgsGeometry.fromPolylineXY(pts)) ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 ptStart = geomTo4326.transform(ptStart) # Calculate the total distance of this line segment distance = 0.0 for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) if srcCRS != epsg4326: # Convert to 4326 ptEnd = geomTo4326.transform(ptEnd) l = geod.Inverse(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) distance += l['s12'] ptStart = ptEnd distance = self.unitDistance(units, distance) # Distance converted to the selected unit of measure attr = ["{:.2f} {}".format(distance, unitsAbbr[units]), distance, unitsAbbr[units] ] f.setAttributes(attr) sink.addFeature(f) else: for pts in seg: numpoints = len(pts) if numpoints < 2: continue ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 ptStart = geomTo4326.transform(ptStart) # Calculate the total distance of this line segment totalDistance = 0.0 for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) if srcCRS != epsg4326: # Convert to 4326 ptEnd = geomTo4326.transform(ptEnd) l = geod.Inverse(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) totalDistance += l['s12'] ptStart = ptEnd totalDistance = self.unitDistance(units, totalDistance) # Distance converted to the selected unit of measure ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 pt1 = geomTo4326.transform(ptStart) else: pt1 = ptStart for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) f = QgsFeature() f.setGeometry(QgsGeometry.fromPolylineXY([ptStart, ptEnd])) if srcCRS != epsg4326: # Convert to 4326 pt2 = geomTo4326.transform(ptEnd) else: pt2 = ptEnd l = geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x()) ptStart = ptEnd pt1 = pt2 distance = self.unitDistance(units, l['s12']) attr = ["{:.2f} {}".format(distance, unitsAbbr[units]), distance, unitsAbbr[units], l['azi1'],totalDistance ] f.setAttributes(attr) sink.addFeature(f) if cnt % 100 == 0: feedback.setProgress(int(cnt * total)) if autoStyle and context.willLoadLayerOnCompletion(dest_id): context.layerToLoadOnCompletionDetails(dest_id).setPostProcessor(StylePostProcessor.create()) return {self.PrmOutputLayer: dest_id}
def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsSource(parameters, self.Centerline, context) layer2 = self.parameterAsVectorLayer(parameters, self.Polygons, context) samples = parameters[self.Samples] distance = parameters[self.Distance] FC = parameters[self.FC] context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) if layer.sourceCrs() != layer2.sourceCrs(): feedback.reportError(QCoreApplication.translate('Error','WARNING: Centerline and Polygon input do not have the same projection')) Precision=5 if FC: field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR'] else: field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR','Diff'] fields = QgsFields() fields.append( QgsField('ID', QVariant.Int )) for name in field_names: fields.append( QgsField(name, QVariant.Double )) (writer, dest_id) = self.parameterAsSink(parameters, self.Output, context, fields, QgsWkbTypes.LineString, layer.sourceCrs()) fet = QgsFeature() field_check =layer.fields().indexFromName('ID') field_check2 =layer2.fields().indexFromName('ID') if field_check == -1 or field_check2 == -1: feedback.reportError(QCoreApplication.translate('Error','Centerline and Polygon input feature require a matching ID field!')) return {} total = 0 counts = {} if FC: vertices = st.run("native:extractvertices", {'INPUT':layer2,'OUTPUT':'memory:'}) index = QgsSpatialIndex(vertices['OUTPUT'].getFeatures()) data = {feature.id():feature for feature in vertices['OUTPUT'].getFeatures()} SPS = {} SPE = {} values = {} values2 = {} feats = {f["ID"]:f for f in layer2.getFeatures()} feedback.pushInfo(QCoreApplication.translate('Update','Defining Centerline Paths')) for enum,feature in enumerate(layer.getFeatures()): total += 1 try: pnt = feature.geometry() if pnt.isMultipart(): pnt = pnt.asMultiPolyline()[0] else: pnt = pnt.asPolyline() startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision) endx,endy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision) ID = feature['ID'] c = feature['Distance'] if ID in SPS: #Get start and endpoint of each centerline v = values[ID] v2 = values2[ID] v3 = counts[ID] + 1 if c > v: SPS[ID] = [(startx,starty),(endx,endy)] values[ID] = c if c < v2: SPE[ID] = [(startx,starty),(endx,endy)] values2[ID] = c counts[ID] = v3 else: SPS[ID] = [(startx,starty),(endx,endy)] values[ID] = c SPE[ID] = [(startx,starty),(endx,endy)] values2[ID] = c counts[ID] = 1 except Exception as e: feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) continue ##Possible Collapsed Polyline? del values,values2 total = 100.0/float(total) ID = None feedback.pushInfo(QCoreApplication.translate('Update','Creating Width Measurements')) report = True for enum,feature in enumerate(layer.getFeatures()): try: if total != -1: feedback.setProgress(int(enum*total)) pnt = feature.geometry() L = pnt.length() if pnt.isMultipart(): pnt = pnt.asMultiPolyline()[0] else: pnt = pnt.asPolyline() curID = feature["ID"] if ID != curID: startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision) midx,midy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision) ID = curID if samples > 0: if distance: Counter = L Limit = float(samples) else: Counter = 1 Limit = round((counts[ID]/float(samples)),0) continue endx,endy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision) if samples > 0: if distance: Counter += L else: Counter += 1 if Counter < Limit: startx,starty = midx,midy midx,midy = endx,endy continue if FC: startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision) near = index.nearestNeighbor(QgsPointXY(startx,starty), 1) SPv = 1e12 midx,midy = data[near[0]].geometry().asPoint() dx,dy = startx-midx,starty-midy shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath < SPv: SPv = shortestPath near = index.nearestNeighbor(QgsPointXY(endx,endy), 1) midx,midy = data[near[0]].geometry().asPoint() dx,dy = endx-midx,endy-midy shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath < SPv: SP = shortestPath else: m = ((starty - endy)/(startx - endx)) #Slope inter = feats[curID] Distance = inter.geometry().boundingBox().width()/2 if startx==endx: #if vertical x1,y1 = midx+Distance,midy x2,y2 = midx - Distance,midy else: m = ((starty - endy)/(startx - endx)) #Slope angle = degrees(atan(m)) + 90 m = tan(radians(angle)) #Angle to Slope c,s = (1/sqrt(1+m**2),m/sqrt(1+m**2)) #cosine and sin x1,y1 = (midx + Distance*(c),midy + Distance*(s)) x2,y2 = (midx - Distance*(c),midy - Distance*(s)) geom = QgsGeometry.fromPolylineXY([QgsPointXY(x1,y1),QgsPointXY(midx,midy),QgsPointXY(x2,y2)]) geom = geom.intersection(inter.geometry()) if geom.isMultipart(): polyline = geom.asMultiPolyline() if len(polyline) == 0: startx,starty = midx,midy midx,midy = endx,endy continue for line in polyline: if len(line)==3: t=1 start,mid,end = line geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1])]) geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])]) geom = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])]) break else: try: line = geom.asPolyline() except Exception as e: startx,starty = midx,midy midx,midy = endx,endy if report: report = False feedback.reportError(QCoreApplication.translate('Error','Width measurement along centerline does not intersect with input polygons. Check 1. ID fields corresponds between centerline and polygons 2. Geometry of centerline and polygon inputs by using the "Fix Geometries" tool')) continue geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(line[0][0],line[0][1]),QgsPointXY(line[1][0],line[1][1])]) geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(line[1][0],line[1][1]),QgsPointXY(line[2][0],line[2][1])]) Widths = [geom1.length(),geom2.length()] SP = list(SPS[curID]) SP.extend(list(SPE[curID])) D = 0 for start,end in combinations(SP,2): dx = start[0] - end[0] dy = start[1] - end[1] shortestPath = sqrt((dx**2)+(dy**2)) if shortestPath > D: D = shortestPath s = QgsPointXY(start[0],start[1]) e = QgsPointXY(end[0],end[1]) m = s.sqrDist(e) u = ((midx - s.x()) * (e.x() - s.x()) + (midy - s.y()) * (e.y() - s.y()))/(m) x = s.x() + u * (e.x() - s.x()) y = s.y() + u * (e.y() - s.y()) d = ((e.x()-s.x())*(midy-s.y()) - (e.y() - s.y())*(midx - s.x())) #Determine which side of the SP the symmetry occurs dx = s.x() - e.x() dy = s.y() - e.y() shortestPath = sqrt((dx**2)+(dy**2)) dx = s.x() - x dy = s.y() - y shortestPath1 = sqrt((dx**2)+(dy**2)) if shortestPath < shortestPath1: sym = QgsGeometry.fromPolylineXY([QgsPointXY(e.x(),e.y()),QgsPointXY(midx,midy)]) else: sym = QgsGeometry.fromPolylineXY([QgsPointXY(x,y),QgsPointXY(midx,midy)]) if d < 0: DW = -(sym.length()) else: DW = sym.length() if FC: W = SPv*2 rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW] geom = feature.geometry() else: W = geom.length() rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW,(min(Widths)/max(Widths))*100] startx,starty = midx,midy midx,midy = endx,endy fet.setGeometry(geom) fet.setAttributes(rows) writer.addFeature(fet) if distance: Counter -= samples else: Counter = 0 except Exception as e: #feedback.reportError(QCoreApplication.translate('Error','%s'%(e))) startx,starty = midx,midy midx,midy = endx,endy continue del writer if FC: del data del SPS,SPE return {self.Output:dest_id}
def dxf_ok_param(self, dic_param): self.nw_params = dic_param idx_prec = 1 # Find the creator if "createur" in self.nw_params: ge_createur = self.nw_params["createur"] else: ge_createur = self.user # Determine the precision class attribute for (idx, prec_val) in enumerate(self.precision_class): if self.precision_class[idx][1] == self.nw_params["prec_class"]: idx_prec = idx idx_prec = int(self.precision_class[idx_prec][0]) # Find delim_pub if "delim_pub" in self.nw_params: delim_pub = self.nw_params["delim_pub"] # Create the list of all blk_typo_natures blk_lst = [] if "blk_corrs" in self.nw_params: for k in list(self.nw_params["blk_corrs"].keys()): blk_lst.append(k) # Create the list of all lim_typo_natures lim_lst = [] if "lim_lyrs" in self.nw_params: for k in list(self.nw_params["lim_lyrs"].keys()): lim_lst.append(k) # Transformations to obtain the WGS84 or the CC coordinates coords_tr_wgs, coords_tr_cc = crs_trans_params(self.canvas, self.project) # Creation of the vertices self.iface.setActiveLayer(self.l_edge) self.iface.setActiveLayer(self.l_vertex) elim_pts = [] vtx_blk_ents = [entity for entity in self.dwg_ents if entity.layer == self.nw_params["vtx_lyr"] and entity.dxftype == "INSERT"] for pt_type in blk_lst: if self.nw_params["blk_corrs"][pt_type] == all_blks: vtx_curtypeblks = vtx_blk_ents else: vtx_curtypeblks = [entity for entity in vtx_blk_ents if entity.name == self.nw_params["blk_corrs"][pt_type]] for blk in vtx_curtypeblks: blk_pt = blk.insert nw_pt = QgsPointXY(float(blk_pt[0]), float(blk_pt[1])) nw_pt_wgs = coords_tr_wgs.transform(nw_pt) # Check if the new vertex is in the tolerance of an existing vertex in the RFU to_create = True id_ptintol = NULL for vtx_feat in self.original_l_vtx.getFeatures(): vtx_feat_g = vtx_feat.geometry() vtx_tol = vtx_feat['som_tolerance'] if vtx_feat_g.type() == QgsWkbTypes.PointGeometry: vtx_feat_pt = vtx_feat_g.asPoint() vtx_feat_pt_cc = coords_tr_cc.transform(vtx_feat_pt) # if find_near(vtx_feat_pt_cc, nw_pt, self.tol_spt): pt_in_tol = find_near(vtx_feat_pt_cc, nw_pt, vtx_tol) if pt_in_tol[0]: # Case of existing RFU point in the tolerance distance if vtx_feat['@id_noeud']: id_ptintol = vtx_feat['@id_noeud'] if pt_in_tol[1] > 0: m_box = mbox_w_params(tl_pt_exst_rfu, txt_pt_exst_rfu, inftxt_pt_exst_rfu.format(nw_pt.x(), nw_pt.y(), float(vtx_tol), id_ptintol)) # Case of strictly identical point else: elim_dbpt = [] elim_dbpt.append(nw_pt) elim_dbpt.append(vtx_feat_pt_cc) elim_pts.append(elim_dbpt) to_create = False m_box = mbox_w_params(tl_ptrfu_dbl, txt_ptrfu_dbl, inftxt_ptrfu_dbl.format(nw_pt.x(), nw_pt.y(), id_ptintol)) # Case of double point in the file imported else: m_box = mbox_w_params(tl_pt_dbl, txt_pt_dbl, inftxt_pt_dbl.format(nw_pt.x(), nw_pt.y())) # Add the list of new point eliminated and corresponding point # in the RFU to the list of eliminated points # (list of 2 point lists) elim_dbpt = [] elim_dbpt.append(nw_pt) elim_dbpt.append(vtx_feat_pt_cc) elim_pts.append(elim_dbpt) to_create = False m_box.exec_() # Creation of the RFU objects in the layers if to_create: create_nw_feat( self.l_vertex, QgsGeometry.fromPointXY(nw_pt_wgs), [NULL, NULL, ge_createur, delim_pub, pt_type, pt_type, idx_prec, float("{0:.02f}".format(nw_pt.x())), float("{0:.02f}".format(nw_pt.y())), self.cc, 0.0, "false", id_ptintol] ) # Creation of the limits self.iface.setActiveLayer(self.l_vertex) self.iface.setActiveLayer(self.l_edge) for lim_type in lim_lst: edge_ents = [entity for entity in self.dwg_ents if entity.layer == self.nw_params["lim_lyrs"][lim_type] and \ (entity.dxftype == "LWPOLYLINE" or entity.dxftype == "LINE")] for lwp_ent in edge_ents: lwp_ent_pts = [] if lwp_ent.dxftype == "LWPOLYLINE": lwp_ent_pts = lwp_ent.points if lwp_ent.is_closed: lwp_ent_pts.append(lwp_ent_pts[0]) if lwp_ent.dxftype == "LINE": lwp_ent_pts.append(lwp_ent.start) lwp_ent_pts.append(lwp_ent.end) for idpt, lwp_pt in enumerate(lwp_ent_pts): if idpt < (len(lwp_ent_pts) - 1): start_pt_cc = QgsPointXY(float(lwp_pt[0]), float(lwp_pt[1])) end_pt_cc = QgsPointXY(float(lwp_ent_pts[idpt + 1][0]), float(lwp_ent_pts[idpt + 1][1])) # Creation only if no double point if check_no_dblpt(start_pt_cc, end_pt_cc): # Check if the point is an eliminated point # If yes, use the corresponding RFU point instead for elim_pt in elim_pts: if start_pt_cc == elim_pt[0]: start_pt_cc = elim_pt[1] if end_pt_cc == elim_pt[0]: end_pt_cc = elim_pt[1] start_pt = coords_tr_wgs.transform(start_pt_cc) end_pt = coords_tr_wgs.transform(end_pt_cc) # Creation of the new RFU objects in the layers # Create line geometry line = QgsGeometry.fromPolylineXY([start_pt, end_pt]) # Check if the lines intersects to_create = check_limit_cross(line, self.original_l_edge, ge_createur, delim_pub, lim_type, self.canvas, True) # Creation of the RFU objects in the layer if to_create: # Create the feature create_nw_feat(self.l_edge, line, [NULL, NULL, ge_createur, delim_pub, lim_type]) # Refresh the canvas self.canvas.refresh()
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) sourceCrs = self.parameterAsCrs(parameters, self.PrmInputCRS, context) sinkCrs = self.parameterAsCrs(parameters, self.PrmOutputCRS, context) lineType = self.parameterAsInt(parameters, self.PrmLineType, context) startUseGeom = self.parameterAsBool(parameters, self.PrmStartUseLayerGeom, context) startXcol = self.parameterAsString(parameters, self.PrmStartXField, context) startYcol = self.parameterAsString(parameters, self.PrmStartYField, context) endUseGeom = self.parameterAsBool(parameters, self.PrmEndUseLayerGeom, context) endXcol = self.parameterAsString(parameters, self.PrmEndXField, context) endYcol = self.parameterAsString(parameters, self.PrmEndYField, context) showStart = self.parameterAsBool(parameters, self.PrmShowStartPoint, context) showEnd = self.parameterAsBool(parameters, self.PrmShowEndPoint, context) dateLine = self.parameterAsBool(parameters, self.PrmDateLineBreak, context) if dateLine and lineType <= 1: isMultiPart = True else: isMultiPart = False if isMultiPart: (lineSink, lineDest_id) = self.parameterAsSink(parameters, self.PrmOutputLineLayer, context, source.fields(), QgsWkbTypes.MultiLineString, sinkCrs) else: (lineSink, lineDest_id) = self.parameterAsSink(parameters, self.PrmOutputLineLayer, context, source.fields(), QgsWkbTypes.LineString, sinkCrs) (ptSink, ptDest_id) = self.parameterAsSink(parameters, self.PrmOutputPointLayer, context, source.fields(), QgsWkbTypes.Point, sinkCrs) if not ptSink: if showStart or showEnd: feedback.pushInfo(tr('Output point layer was set to [skip output]. No point layer will be generated.')) showStart = False showEnd = False if (startUseGeom or endUseGeom) and (source.wkbType() != QgsWkbTypes.Point): msg = tr('In order to use the layer geometry for the start or ending points, the input layer must be of type Point') feedback.reportError(msg) raise QgsProcessingException(msg) if (not startUseGeom and (not startXcol or not startYcol)) or (not endUseGeom and (not endXcol or not endYcol)): msg = tr('Please select valid starting and ending point columns') feedback.reportError(msg) raise QgsProcessingException(msg) if source.wkbType() != QgsWkbTypes.Point and (startUseGeom or endUseGeom): msg = tr("In order to select the input layer's geometry as a beginning or ending point it must be a Point vector layer.") feedback.reportError(msg) raise QgsProcessingException(msg) # Set up CRS transformations geomCrs = source.sourceCrs() if (startUseGeom or endUseGeom) and (geomCrs != epsg4326): geomTo4326 = QgsCoordinateTransform(geomCrs, epsg4326, QgsProject.instance()) if sourceCrs != epsg4326: sourceTo4326 = QgsCoordinateTransform(sourceCrs, epsg4326, QgsProject.instance()) if sinkCrs != epsg4326: toSinkCrs = QgsCoordinateTransform(epsg4326, sinkCrs, QgsProject.instance()) featureCount = source.featureCount() total = 100.0 / featureCount if featureCount else 0 numBad = 0 maxseglen = settings.maxSegLength*1000.0 maxSegments = settings.maxSegments iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if (cnt % 100 == 0) and feedback.isCanceled(): break try: if startUseGeom: ptStart = feature.geometry().asPoint() if geomCrs != epsg4326: ptStart = geomTo4326.transform(ptStart) else: ptStart = QgsPointXY(float(feature[startXcol]), float(feature[startYcol])) if sourceCrs != epsg4326: ptStart = sourceTo4326.transform(ptStart) if endUseGeom: ptEnd = feature.geometry().asPoint() if geomCrs != epsg4326: ptEnd = geomTo4326.transform(ptEnd) else: ptEnd = QgsPointXY(float(feature[endXcol]), float(feature[endYcol])) if sourceCrs != epsg4326: ptEnd = sourceTo4326.transform(ptEnd) pts = [ptStart] if ptStart == ptEnd: # We cannot have a line that begins and ends at the same point numBad += 1 continue if lineType == 0: # Geodesic l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) if n > maxSegments: n = maxSegments seglen = l.s13 / n for i in range(1,n+1): s = seglen * i g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE) pts.append( QgsPointXY(g['lon2'], g['lat2']) ) else: # The line segment is too short so it is from ptStart to ptEnd pts.append(ptEnd) elif lineType == 1: # Great circle pts = GCgetPointsOnLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x(), settings.maxSegLength*1000.0, # Put it in meters settings.maxSegments+1) else: # Simple line pts.append(ptEnd) f = QgsFeature() if isMultiPart: outseg = checkIdlCrossings(pts) if sinkCrs != epsg4326: # Convert each point to the output CRS for y in range(len(outseg)): for x, pt in enumerate(outseg[y]): outseg[y][x] = toSinkCrs.transform(pt) f.setGeometry(QgsGeometry.fromMultiPolylineXY(outseg)) else: if sinkCrs != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = toSinkCrs.transform(pt) f.setGeometry(QgsGeometry.fromPolylineXY(pts)) f.setAttributes(feature.attributes()) lineSink.addFeature(f) if showStart: f = QgsFeature() if sinkCrs != epsg4326: f.setGeometry(QgsGeometry.fromPointXY(toSinkCrs.transform(ptStart))) else: f.setGeometry(QgsGeometry.fromPointXY(ptStart)) f.setAttributes(feature.attributes()) ptSink.addFeature(f) if showEnd: f = QgsFeature() if sinkCrs != epsg4326: f.setGeometry(QgsGeometry.fromPointXY(toSinkCrs.transform(ptEnd))) else: f.setGeometry(QgsGeometry.fromPointXY(ptEnd)) f.setAttributes(feature.attributes()) ptSink.addFeature(f) except: numBad += 1 '''s = traceback.format_exc() feedback.pushInfo(s)''' if cnt % 100 == 0: # Set the progress after every 100 entries feedback.setProgress(int(cnt * total)) if numBad > 0: feedback.pushInfo(tr("{} out of {} features from the input layer were invalid and were ignored.".format(numBad, featureCount))) return {self.PrmOutputLineLayer: lineDest_id, self.PrmOutputPointLayer: ptDest_id}
class QadVirtualGripCommandsClass(QadCommandClass): #============================================================================ # instantiateNewCmd #============================================================================ def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadVirtualGripCommandsClass(self.plugIn) #============================================================================ # getName #============================================================================ def getName(self): return "QadVirtualGripCommandsClass" #============================================================================ # __init__ #============================================================================ def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.commandNum = QadVirtualGripCommandsEnum.NONE self.currentCommand = None self.entitySetGripPoints = None self.basePt = QgsPointXY() #============================================================================ # __del__ #============================================================================ def __del__(self): QadCommandClass.__del__(self) del self.currentCommand #============================================================================ # getPointMapTool #============================================================================ def getPointMapTool(self, drawMode=QadGetPointDrawModeEnum.NONE): if self.currentCommand is not None: return self.currentCommand.getPointMapTool(drawMode) else: return None #============================================================================ # getCurrentContextualMenu #============================================================================ def getCurrentContextualMenu(self): if self.currentCommand is not None: return self.currentCommand.getCurrentContextualMenu() else: return None #============================================================================ # getCommand #============================================================================ def getCommand(self): if self.commandNum == QadVirtualGripCommandsEnum.STRECTH: return QadGRIPSTRETCHCommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: return QadGRIPMOVECommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: return QadGRIPROTATECommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: return QadGRIPSCALECommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: return QadGRIPMIRRORCommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: return QadGRIPCHANGEARCRADIUSCommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN: return QadGRIPLENGTHENCommandClass(self.plugIn) elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX: cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) cmd.setInsertVertexAfter_Mode() return cmd elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE: cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) cmd.setInsertVertexBefore_Mode() return cmd elif self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX: cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) cmd.setRemoveVertex_mode() return cmd elif self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC: cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) cmd.setLineToArcConvert_Mode() return cmd elif self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE: cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) cmd.setArcToLineConvert_Mode() return cmd return None #============================================================================ # initStartCommand #============================================================================ def initStartCommand(self, commandNum): if self.currentCommand is not None: del self.currentCommand self.currentCommand = None self.commandNum = commandNum self.currentCommand = self.getCommand() if self.currentCommand is not None: self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) self.currentCommand.setSelectedEntityGripPoints( self.entitySetGripPoints) return True else: return False #============================================================================ # initNextCommand #============================================================================ def initNextCommand(self): if self.currentCommand is not None: del self.currentCommand self.currentCommand = None if self.commandNum == QadVirtualGripCommandsEnum.STRECTH or \ self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN or \ self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX or \ self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE or \ self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX or \ self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC or \ self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE or \ self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: self.commandNum = QadVirtualGripCommandsEnum.MOVE elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: self.commandNum = QadVirtualGripCommandsEnum.ROTATE elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: self.commandNum = QadVirtualGripCommandsEnum.SCALE elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: self.commandNum = QadVirtualGripCommandsEnum.MIRROR elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: self.commandNum = QadVirtualGripCommandsEnum.MOVE self.currentCommand = self.getCommand() if self.currentCommand is not None: self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) self.currentCommand.setSelectedEntityGripPoints( self.entitySetGripPoints) return True else: return False #============================================================================ # run #============================================================================ def run(self, msgMapTool=False, msg=None): if self.currentCommand is None: return True res = self.currentCommand.run(msgMapTool, msg) if res == True: if self.currentCommand.skipToNextGripCommand == True: if self.initNextCommand(): # attivo comando successivo return self.currentCommand.run(msgMapTool, msg) else: # ridisegno i grip point nelle nuove posizioni resettando quelli selezionati self.plugIn.tool.clearEntityGripPoints() self.plugIn.tool.refreshEntityGripPoints() return res
class PointFeatureDlg(QDialog, Ui_PointFeature): map_tool_change_signal = pyqtSignal() landmark_added = pyqtSignal(QgsMapLayer) landmark_removed = pyqtSignal(QgsMapLayer) finish_add_landmark_signal = pyqtSignal() def __init__(self, canvas, proj, parent=None): super(PointFeatureDlg, self).__init__(parent) self.setupUi(self) self.point = None self.point_layer = None self.feat = None self.num = 1 self.canvas = canvas self.proj = proj self.double_validator = get_custom_double_validator() self.int_validator = get_custom_int_validator() self.getCoordinatesButton.setIcon(QIcon(":/resources/pickPointInMap.svg")) self.getCoordinatesButton.clicked.connect(self.set_point_tool) self.getCoordinatesButton.setToolTip("Pick point in map") self.copy_to_clipboardButton.setIcon(QIcon(":/resources/mActionCopyClipboard.svg")) self.copy_to_clipboardButton.setToolTip("Copy to clipboard") self.copy_to_clipboardButton.clicked.connect(self.copy_to_clipboard) self.tool_add_landmark = addlandmarktool.AddLandmarkTool(canvas) self.tool_add_landmark.point_signal.connect(self.add_new_landmark) self.previous_coordinates_format = self.comboBox.currentText() self.comboBox.currentIndexChanged.connect(self.set_format) def get_coordinates(self): """ Return the coordinates (latitude, longitude) :return : latitude, longitude """ return self.point.y(), self.point.x() def set_point_tool(self): """Set the maptool to pointTool""" self.map_tool_change_signal.emit() #Emit signal to warn mainwindow that we are changing a maptool self.canvas.setMapTool(self.tool_add_landmark) def add_new_landmark(self, point): """ create a landmark point :param point: Point to add on the canvas """ if self.feat is not None: self.point_layer.dataProvider().deleteFeatures([self.feat.id()]) self.landmark_removed.emit(self.point_layer) self.point = point name = "LandmarkPoint_" + str(self.num) restart_search = True # This loops tries to find a free name for the LandmarkPoint_ while restart_search: restart_search = False # for every layer for layer in self.proj.mapLayers().values(): # check name if layer.name() == name: self.num += 1 name = "LandmarkPoint_" + str(self.num) # Set to True to find the next LandmarkPoint restart_search = True self.point_layer = QgsVectorLayer( "Point?crs=epsg:4326", name, "memory") self.feat = QgsFeature() self.feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(self.point.x(), self.point.y()))) self.point_layer.dataProvider().addFeatures([self.feat]) self.landmark_added.emit(self.point_layer) if self.comboBox.currentText() == "Decimal degrees": self.set_degrees_from_point() if self.comboBox.currentText() == "Degrees, minutes": # convert degree to degreeminute self.set_degrees_minutes_from_point() if self.comboBox.currentText() == "Degrees, minutes, seconds": # convert degree to degminsec self.set_degrees_minutes_seconds_from_point() def set_format(self): """ Set widgets on dialog according with combobox coordinates format""" if self.previous_coordinates_format == "Decimal degrees": if validate_custom_double(self.lon_degrees_lineedit.text()) == QValidator.Acceptable and \ validate_custom_double(self.lat_degrees_lineedit.text()) == QValidator.Acceptable: self.point = QgsPointXY(float(self.lon_degrees_lineedit.text()), float(self.lat_degrees_lineedit.text())) else: self.point = None elif self.previous_coordinates_format == "Degrees, minutes": lat_d = self.lat_degrees_lineedit.text() lat_m = self.lat_minutes_lineedit.text() lon_d = self.lon_degrees_lineedit.text() lon_m = self.lon_minutes_lineedit.text() if validate_custom_int(lat_d) == QValidator.Acceptable \ and validate_custom_double(lat_m) == QValidator.Acceptable \ and validate_custom_int(lon_d) == QValidator.Acceptable \ and validate_custom_double(lon_m) == QValidator.Acceptable: lat_dm = [lat_d, lat_m] lon_dm = [lon_d, lon_m] # convert degree_minute to degree lat_degree, lon_degree = degree_minute_to_degree(lat_dm, lon_dm) self.point = QgsPointXY(float(lon_degree), float(lat_degree)) else: self.point = None elif self.previous_coordinates_format == "Degrees, minutes, seconds": if validate_custom_int(self.lat_degrees_lineedit.text()) == QValidator.Acceptable \ and validate_custom_int(self.lat_minutes_lineedit.text()) == QValidator.Acceptable \ and validate_custom_double(self.lat_seconds_lineedit.text()) == QValidator.Acceptable \ and validate_custom_int(self.lon_degrees_lineedit.text()) == QValidator.Acceptable \ and validate_custom_int(self.lon_minutes_lineedit.text()) == QValidator.Acceptable \ and validate_custom_double(self.lon_seconds_lineedit.text()) == QValidator.Acceptable: lat = self.lat_degrees_lineedit.text() + 'º' + self.lat_minutes_lineedit.text() + '\'' + self.lat_seconds_lineedit.text() + '\'\'' lon = self.lon_degrees_lineedit.text() + 'º' + self.lon_minutes_lineedit.text() + '\'' + self.lon_seconds_lineedit.text() + '\'\'' lat_degree, lon_degree = degree_minute_second_to_degree(lat, lon) self.point = QgsPointXY(float(lon_degree), float(lat_degree)) else: self.point = None # delete previous widgets self.delete_all(self.latitude_layout) self.delete_all(self.longitude_layout) lat_label = QLabel(self) lat_label.setText("Latitude: ") lat_label.setFixedWidth(75) lon_label = QLabel(self) lon_label.setText("Longitude: ") lon_label.setFixedWidth(75) self.latitude_layout.addWidget(lat_label) self.longitude_layout.addWidget(lon_label) if self.comboBox.currentText() == "Decimal degrees" or self.previous_coordinates_format is None: self.lat_degrees_lineedit = QLineEdit(self) self.lon_degrees_lineedit = QLineEdit(self) self.lat_degrees_lineedit.textChanged.connect(self.validate_coordinates_double) self.lon_degrees_lineedit.textChanged.connect(self.validate_coordinates_double) self.latitude_layout.addWidget(self.lat_degrees_lineedit) self.longitude_layout.addWidget(self.lon_degrees_lineedit) self.set_degrees_from_point() if self.comboBox.currentText() == "Degrees, minutes": lat_deg_label = QLabel(self) lat_deg_label.setText("º") lon_deg_label = QLabel(self) lon_deg_label.setText("º") lat_min_label = QLabel(self) lat_min_label.setText("\'") lon_min_label = QLabel(self) lon_min_label.setText("\'") self.lat_degrees_lineedit = QLineEdit(self) self.lon_degrees_lineedit = QLineEdit(self) self.lat_minutes_lineedit = QLineEdit(self) self.lon_minutes_lineedit = QLineEdit(self) self.lat_degrees_lineedit.textChanged.connect(self.validate_coordinates_int) self.lon_degrees_lineedit.textChanged.connect(self.validate_coordinates_int) self.lat_minutes_lineedit.textChanged.connect(self.validate_coordinates_double) self.lon_minutes_lineedit.textChanged.connect(self.validate_coordinates_double) self.latitude_layout.addWidget(self.lat_degrees_lineedit) self.latitude_layout.addWidget(lat_deg_label) self.latitude_layout.addWidget(self.lat_minutes_lineedit) self.latitude_layout.addWidget(lat_min_label) self.longitude_layout.addWidget(self.lon_degrees_lineedit) self.longitude_layout.addWidget(lon_deg_label) self.longitude_layout.addWidget(self.lon_minutes_lineedit) self.longitude_layout.addWidget(lon_min_label) self.set_degrees_minutes_from_point() if self.comboBox.currentText() == "Degrees, minutes, seconds": lat_deg_label = QLabel(self) lat_deg_label.setText("º") lon_deg_label = QLabel(self) lon_deg_label.setText("º") lat_min_label = QLabel(self) lat_min_label.setText("\'") lon_min_label = QLabel(self) lon_min_label.setText("\'") lat_sec_label = QLabel(self) lat_sec_label.setText("\'\'") lon_sec_label = QLabel(self) lon_sec_label.setText("\'\'") self.lat_degrees_lineedit = QLineEdit(self) self.lon_degrees_lineedit = QLineEdit(self) self.lat_minutes_lineedit = QLineEdit(self) self.lon_minutes_lineedit = QLineEdit(self) self.lat_seconds_lineedit = QLineEdit(self) self.lon_seconds_lineedit = QLineEdit(self) self.lat_degrees_lineedit.textChanged.connect(self.validate_coordinates_int) self.lon_degrees_lineedit.textChanged.connect(self.validate_coordinates_int) self.lat_minutes_lineedit.textChanged.connect(self.validate_coordinates_int) self.lon_minutes_lineedit.textChanged.connect(self.validate_coordinates_int) self.lat_seconds_lineedit.textChanged.connect(self.validate_coordinates_double) self.lon_seconds_lineedit.textChanged.connect(self.validate_coordinates_double) self.latitude_layout.addWidget(self.lat_degrees_lineedit) self.latitude_layout.addWidget(lat_deg_label) self.latitude_layout.addWidget(self.lat_minutes_lineedit) self.latitude_layout.addWidget(lat_min_label) self.latitude_layout.addWidget(self.lat_seconds_lineedit) self.latitude_layout.addWidget(lat_sec_label) self.longitude_layout.addWidget(self.lon_degrees_lineedit) self.longitude_layout.addWidget(lon_deg_label) self.longitude_layout.addWidget(self.lon_minutes_lineedit) self.longitude_layout.addWidget(lon_min_label) self.longitude_layout.addWidget(self.lon_seconds_lineedit) self.longitude_layout.addWidget(lon_sec_label) self.set_degrees_minutes_seconds_from_point() self.previous_coordinates_format = self.comboBox.currentText() def set_degrees_from_point(self): """ From point in degrees, fill degrees QLineEdits""" if self.point is not None: lat_degrees_text = str(self.point.y()) lon_degrees_text = str(self.point.x()) if (validate_custom_double(lat_degrees_text) == QValidator.Acceptable and validate_custom_double(lon_degrees_text) == QValidator.Acceptable): self.lat_degrees_lineedit.setText("{:.8F}".format(float(lat_degrees_text))) self.lon_degrees_lineedit.setText("{:.8F}".format(float(lon_degrees_text))) def set_degrees_minutes_from_point(self): """ From point in degrees, fill degrees and minutesQLineEdits""" if self.point is not None: lat_degrees_text = str(self.point.y()) lon_degrees_text = str(self.point.x()) if (validate_custom_double(lat_degrees_text) == QValidator.Acceptable and validate_custom_double(lon_degrees_text) == QValidator.Acceptable): lat_dm, lon_dm = degree_to_degree_minute(float(lat_degrees_text), float(lon_degrees_text)) lat_d, lat_m = str(lat_dm).split('º') lat_m, unused = lat_m.split('\'') lon_d, lon_m = str(lon_dm).split('º') lon_m, unused = lon_m.split('\'') self.lat_degrees_lineedit.setText("{:d}".format(int(lat_d))) self.lat_minutes_lineedit.setText("{:.8F}".format(float(lat_m))) self.lon_degrees_lineedit.setText("{:d}".format(int(lon_d))) self.lon_minutes_lineedit.setText("{:.8F}".format(float(lon_m))) def set_degrees_minutes_seconds_from_point(self): """ From point in degrees, fill degrees, minutes and seconds QLineEdits""" if self.point is not None: lat_degrees_text = str(self.point.y()) lon_degrees_text = str(self.point.x()) if (validate_custom_double(lat_degrees_text) == QValidator.Acceptable and validate_custom_double(lon_degrees_text) == QValidator.Acceptable): lat_dms, lon_dms = degree_to_degree_minute_second(float(lat_degrees_text), float(lon_degrees_text)) lat_deg, lat_ms = str(lat_dms).split('º') lat_ms, unused = lat_ms.split('\'\'') lat_min, lat_sec = lat_ms.split('\'') lon_deg, lon_ms = str(lon_dms).split('º') lon_ms, unused = lon_ms.split('\'\'') lon_min, lon_sec = lon_ms.split('\'') self.lat_degrees_lineedit.setText("{:d}".format(int(lat_deg))) self.lat_minutes_lineedit.setText("{:d}".format(int(lat_min))) self.lat_seconds_lineedit.setText("{:.8F}".format(float(lat_sec))) self.lon_degrees_lineedit.setText("{:d}".format(int(lon_deg))) self.lon_minutes_lineedit.setText("{:d}".format(int(lon_min))) self.lon_seconds_lineedit.setText("{:.8F}".format(float(lon_sec))) def validate_coordinates_double(self): """validate line edit coordinates with double""" sender = self.sender() state = validate_custom_double(sender.text()) color = get_color(state) sender.setStyleSheet('QLineEdit { background-color: %s }' % color) def validate_coordinates_int(self): """validate line edit coordinates with int""" sender = self.sender() state = validate_custom_int(sender.text()) color = get_color(state) sender.setStyleSheet('QLineEdit { background-color: %s }' % color) def reset(self): """ Reset params""" self.comboBox.setCurrentIndex(0) self.point = None self.point_layer = None self.feat = None self.previous_coordinates_format = None self.set_format() def copy_to_clipboard(self): """Copy coordinates to clipboard""" cb = QApplication.clipboard() cb.clear() if self.comboBox.currentText() == "Decimal degrees" : cb.setText("{}, {}".format(self.lat_degrees_lineedit.text(),self.lon_degrees_lineedit.text())) if self.comboBox.currentText() == "Degrees, minutes": cb.setText("{}º {}', {}º {}\'".format(self.lat_degrees_lineedit.text(), self.lat_minutes_lineedit.text(), self.lon_degrees_lineedit.text(), self.lon_minutes_lineedit.text())) if self.comboBox.currentText() == "Degrees, minutes, seconds": cb.setText("{}º {}' {}\'\', {}º {}\' {}\'\'".format(self.lat_degrees_lineedit.text(), self.lat_minutes_lineedit.text(), self.lat_seconds_lineedit.text(), self.lon_degrees_lineedit.text(), self.lon_minutes_lineedit.text(), self.lon_seconds_lineedit.text())) def accept(self): """ Insert self.pont in the canvas""" self.set_format() if self.point is not None: new_point = QgsPointXY(float(self.get_coordinates()[1]), float(self.get_coordinates()[0])) if self.point_layer is None: self.add_new_landmark(self.point) self.point_layer.startEditing() self.point_layer.beginEditCommand("Move Point") # for undo self.point_layer.moveVertex(new_point.x(), new_point.y(), self.feat.id() + 1, 0) self.point_layer.endEditCommand() self.point_layer.commitChanges() self.finish_add_landmark_signal.emit() super(PointFeatureDlg, self).accept() else: QMessageBox.warning(self, "Invalid Point", "Invalid point, make sure the coordinates are correct.", QMessageBox.Close) def reject(self): """ reject point insertion""" if self.feat is not None: # if we are aborting, point is deleted self.point_layer.dataProvider().deleteFeatures([self.feat.id()]) self.landmark_removed.emit(self.point_layer) self.finish_add_landmark_signal.emit() super(PointFeatureDlg, self).reject() def delete_all(self, layout): """ delete all widget from layout :param layout: layout is a qt layout """ if layout is not None: for i in reversed(range(layout.count())): item = layout.takeAt(i) widget = item.widget() if widget is not None: widget.deleteLater() else: self.delete_all(item.layout())
class QadSTRETCHCommandClass(QadCommandClass): def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadSTRETCHCommandClass(self.plugIn) def getName(self): return QadMsg.translate("Command_list", "STRETCH") def getEnglishName(self): return "STRETCH" def connectQAction(self, action): action.triggered.connect(self.plugIn.runSTRETCHCommand) def getIcon(self): return QIcon(":/plugins/qad/icons/stretch.png") def getNote(self): # impostare le note esplicative del comando return QadMsg.translate("Command_STRETCH", "Stretches objects.") def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.AddOnSelection = True # se = False significa remove self.points = [] self.MPOLYGONCommand = None self.SSGeomList = [ ] # lista di entità da stirare con geom di selezione self.basePt = QgsPointXY() def __del__(self): QadCommandClass.__del__(self) if self.MPOLYGONCommand is not None: del self.MPOLYGONCommand for SSGeom in self.SSGeomList: SSGeom[0].deselectOnLayer() def getPointMapTool(self, drawMode=QadGetPointDrawModeEnum.NONE): if self.step == 2: # quando si é in fase di disegno linea return self.MPOLYGONCommand.getPointMapTool(drawMode) else: if (self.plugIn is not None): if self.PointMapTool is None: self.PointMapTool = Qad_stretch_maptool(self.plugIn) return self.PointMapTool else: return None def getCurrentContextualMenu(self): if self.step == 2: # quando si é in fase di disegno linea return self.MPOLYGONCommand.getCurrentContextualMenu() else: return self.contextualMenu def stretch(self, entity, containerGeom, offsetX, offsetY, tolerance2ApproxCurve): # entity = entità da stirare # ptList = lista dei punti da stirare # offsetX, offsetY = spostamento da applicare # tolerance2ApproxCurve = tolleranza per ricreare le curve if entity.whatIs() == "DIMENTITY": dimEntity = entity else: # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is None: stretchedGeom = entity.getQadGeom() # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più if stretchedGeom is None: # se non c'è lo salto senza errore return True # stiro la feature stretchedGeom = qad_stretch_fun.stretchQadGeometry(stretchedGeom, containerGeom, \ offsetX, offsetY) if stretchedGeom is not None: # trasformo la geometria nel crs del layer f = entity.getFeature() f.setGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.crs())) # plugIn, layer, feature, refresh, check_validity if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: return False else: # stiro la quota if dimEntity.deleteToLayers(self.plugIn) == False: return False newDimEntity = QadDimEntity(dimEntity) # la copio newDimEntity.stretch(containerGeom, offsetX, offsetY) if newDimEntity.addToLayers(self.plugIn) == False: return False return True #============================================================================ # stretchFeatures #============================================================================ def stretchFeatures(self, newPt): # mi ricavo un unico QadEntitySet con le entità selezionate entitySet = QadEntitySet() for SSGeom in self.SSGeomList: entitySet.unite(SSGeom[0]) self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) dimElaboratedList = [] # lista delle quotature già elaborate tolerance2ApproxCurve = QadVariables.get( QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) offsetX = newPt.x() - self.basePt.x() offsetY = newPt.y() - self.basePt.y() entity = QadEntity() for SSGeom in self.SSGeomList: # copio entitySet entitySet = QadEntitySet(SSGeom[0]) geomSel = SSGeom[1] for layerEntitySet in entitySet.layerEntitySetList: layer = layerEntitySet.layer for featureId in layerEntitySet.featureIds: entity.set(layer, featureId) # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is None: if self.stretch(entity, geomSel, offsetX, offsetY, tolerance2ApproxCurve) == False: self.plugIn.destroyEditCommand() return else: found = False for dimElaborated in dimElaboratedList: if dimElaborated == dimEntity: found = True if found == False: # quota non ancora elaborata # aggiungo i layer dei componenti della quota self.plugIn.addLayerListToLastEditCommand( "Feature stretched", [ dimEntity.getSymbolLayer(), dimEntity.getLinearLayer(), dimEntity.getTextualLayer() ]) dimElaboratedList.append(dimEntity) if self.stretch(dimEntity, geomSel, offsetX, offsetY, tolerance2ApproxCurve) == False: self.plugIn.destroyEditCommand() return self.plugIn.endEditCommand() #============================================================================ # setEntitySetGeom #============================================================================ def setEntitySetGeom(self, entitySet, selGeom): for SSGeom in self.SSGeomList: SSGeom[0].deselectOnLayer() del self.SSGeomList[:] # svuoto la lista # aggiuge il gruppo di selezione con la geometria usata per la selezione self.SSGeomList.append([entitySet, selGeom]) entitySet.selectOnLayer(False) # incremental = False #============================================================================ # addEntitySetGeom #============================================================================ def addEntitySetGeom(self, entitySet, selGeom): # elimino dai gruppi precedenti gli oggetti presenti in entitySet self.removeEntitySet(entitySet) # aggiuge il gruppo di selezione con la geometria usata per la selezione self.SSGeomList.append([entitySet, selGeom]) entitySet.selectOnLayer(True) # incremental = True #============================================================================ # removeEntitySet #============================================================================ def removeEntitySet(self, entitySet): # elimino dai gruppi precedenti gli oggetti presenti in entitySet for SSGeom in self.SSGeomList: SSGeom[0].subtract(entitySet) for SSGeom in self.SSGeomList: SSGeom[0].selectOnLayer(False) # incremental = False #============================================================================ # SSGeomListIsEmpty #============================================================================ def SSGeomListIsEmpty(self): if len(self.SSGeomList) == 0: return True for SSGeom in self.SSGeomList: if SSGeom[0].isEmpty() == False: return False return True #============================================================================ # waitForObjectSel #============================================================================ def waitForObjectSel(self): self.step = 1 # imposto il map tool self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.ASK_FOR_FIRST_PT_RECTANGLE) keyWords = QadMsg.translate("Command_STRETCH", "Polygon") + "/" + \ QadMsg.translate("Command_STRETCH", "Add") + "/" + \ QadMsg.translate("Command_STRETCH", "Remove") if self.AddOnSelection == True: prompt = QadMsg.translate("Command_STRETCH", "Select vertices") else: prompt = QadMsg.translate("Command_STRETCH", "Remove vertices") prompt = prompt + QadMsg.translate( "Command_STRETCH", " to stretch crossed by a selection window or [{0}]: ").format( keyWords) englishKeyWords = "Polygon" + "/" + "Add" + "/" + "Remove" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) #============================================================================ # waitForBasePt #============================================================================ def waitForBasePt(self): self.step = 4 # imposto il map tool self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) keyWords = QadMsg.translate("Command_STRETCH", "Displacement") prompt = QadMsg.translate( "Command_STRETCH", "Specify base point or [{0}] <{0}>: ").format(keyWords) englishKeyWords = "Displacement" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, \ QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) #============================================================================ # run #============================================================================ def run(self, msgMapTool=False, msg=None): if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): self.showMsg( QadMsg.translate( "QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n" )) return True # fine comando #========================================================================= # RICHIESTA SELEZIONE OGGETTI if self.step == 0: # inizio del comando # si appresta ad attendere la selezione degli oggetti da stirare self.waitForObjectSel() return False #========================================================================= # RISPOSTA ALLA SELEZIONE OGGETTI DA STIRARE elif self.step == 1: if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse value = None else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False else: value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if type(value) == unicode: if value == QadMsg.translate("Command_STRETCH", "Polygon") or value == "Polygon": # Seleziona tutti gli oggetti che sono interni al poligono self.MPOLYGONCommand = QadMPOLYGONCommandClass(self.plugIn) # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea # che non verrà salvata su un layer self.MPOLYGONCommand.virtualCmd = True self.MPOLYGONCommand.run(msgMapTool, msg) self.step = 2 return False elif value == QadMsg.translate("Command_SSGET", "Add") or value == "Add": # Passa al metodo Aggiungi: gli oggetti selezionati possono essere aggiunti al gruppo di selezione self.AddOnSelection = True elif value == QadMsg.translate("Command_SSGET", "Remove") or value == "Remove": # Passa al metodo Rimuovi: gli oggetti possono essere rimossi dal gruppo di selezione self.AddOnSelection = False elif type(value) == QgsPointXY: # se é stato selezionato un punto del self.points[:] # svuoto la lista self.points.append(value) # imposto il map tool self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum. FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE) self.getPointMapTool().setStartPoint(value) # si appresta ad attendere un punto self.waitForPoint( QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) self.step = 3 return False else: if self.SSGeomListIsEmpty(): return True # si appresta ad attendere il punto base o lo spostamento self.waitForBasePt() return False # si appresta ad attendere la selezione degli oggetti da stirare self.waitForObjectSel() return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' POLIGONO (da step = 1) elif self.step == 2: # dopo aver atteso un punto si riavvia il comando if self.MPOLYGONCommand.run(msgMapTool, msg) == True: if self.MPOLYGONCommand.polyline.qty() > 0: # cerco tutte le geometrie intersecanti il poligono # e considerando solo layer editabili selSet = getSelSet("CP", self.getPointMapTool(), self.MPOLYGONCommand.PLINECommand.polyline.asPolyline(), \ None, True, True, True, \ True) # se la selezione é avvenuta con shift premuto o se si deve rimuovere il gruppo selSet dal gruppo if self.AddOnSelection == False: self.removeEntitySet(selSet) else: self.setEntitySetGeom( selSet, QgsGeometry.fromPolygonXY([ self.MPOLYGONCommand.PLINECommand.polyline. asPolyline() ])) del self.MPOLYGONCommand self.MPOLYGONCommand = None # si appresta ad attendere la selezione degli oggetti da stirare self.waitForObjectSel() self.getPointMapTool().refreshSnapType( ) # aggiorno lo snapType che può essere variato dal maptool di mpolygon return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' FINESTRA (da step = 1) elif self.step == 3: # dopo aver atteso un punto si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse self.showMsg( QadMsg.translate("Command_STRETCH", "Window not correct.")) # si appresta ad attendere un punto self.waitForPoint( QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) return False else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False shiftKey = self.getPointMapTool().shiftKey value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione shiftKey = False value = msg if type(value) == QgsPointXY: self.points.append(value) # cerco tutte le geometrie intersecanti il rettangolo # e considerando solo layer editabili selSet = getSelSet("C", self.getPointMapTool(), self.points, \ None, True, True, True, \ True) # se si deve rimuovere il gruppo entitySet dal gruppo if self.AddOnSelection == False: self.removeEntitySet(selSet) else: if shiftKey: # se la selezione é avvenuta con shift premuto self.addEntitySetGeom( selSet, QgsGeometry.fromRect( QgsRectangle(self.points[0], self.points[1]))) else: self.setEntitySetGeom( selSet, QgsGeometry.fromRect( QgsRectangle(self.points[0], self.points[1]))) # si appresta ad attendere la selezione degli oggetti da stirare self.waitForObjectSel() return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse pass # opzione di default "spostamento" else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg # imposto il map tool self.getPointMapTool().SSGeomList = self.SSGeomList if value is None or type(value) == unicode: self.basePt.set(0, 0) self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) # si appresta ad attendere un punto msg = QadMsg.translate( "Command_STRETCH", "Specify the displacement from the origin point 0,0 <{0}, {1}>: " ) # msg, inputType, default, keyWords, nessun controllo self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ QadInputTypeEnum.POINT2D, \ self.plugIn.lastOffsetPt, \ "", QadInputModeEnum.NONE) self.step = 5 elif type( value) == QgsPointXY: # se é stato inserito il punto base self.basePt.set(value.x(), value.y()) # imposto il map tool self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(QadMsg.translate("Command_STRETCH", "Specify second point or <use first point as displacement from origin point 0,0>: "), \ QadInputTypeEnum.POINT2D, \ None, \ "", QadInputModeEnum.NONE) self.step = 6 return False #========================================================================= # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg self.plugIn.setLastOffsetPt(value) self.stretchFeatures(value) return True # fine comando #========================================================================= # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse return True # fine comando else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if value is None: newPt = QgsPointXY(self.basePt.x() * 2, self.basePt.y() * 2) self.stretchFeatures(newPt) elif type( value ) == QgsPointXY: # se é stato inserito lo spostamento con un punto self.stretchFeatures(value) return True # fine comando
def processAlgorithm(self, parameters, context, feedback): # gather parameters extent = self.parameterAsExtent(parameters, self.PrmExtent, context, epsg4326) lineDistance = self.parameterAsDouble(parameters, self.PrmLineDistance, context) traceInterval = self.parameterAsDouble(parameters, self.PrmTraceInterval, context) distanceTolerance = self.parameterAsDouble(parameters, self.PrmDistanceTolerance, context) variationTolerance = self.parameterAsDouble(parameters, self.PrmVariationTolerance, context) units = self.parameterAsInt(parameters, self.PrmUnitsOfMeasure, context) measureFactor = conversionToMeters(units) # adjust linear units lineDistance *= measureFactor traceInterval *= measureFactor distanceTolerance *= measureFactor f = QgsFields() f.append(QgsField("trace", QVariant.Int)) f.append(QgsField("variation", QVariant.Double)) # obtain our output sink (sink, dest_id) = self.parameterAsSink(parameters, self.PrmOutputLayer, context, f, QgsWkbTypes.LineString, epsg4326) # Determine the longitude step direction based on sign of the variation in the extent's center, # so that we start on the side of the extent such that we will fill in partial lines at the corner. # TODO: this is not ideal when the extent intersects an agonic line. centerVar = geomag.declination(extent.center().y(), extent.center().x(), 0, date.today()) start = QgsPointXY(extent.xMinimum(), extent.yMinimum()) if centerVar > 0: lineDistance = -lineDistance start.setX(extent.xMaximum()) # We trace between points at a fixed longitude interval, so that the distance-preservation logic below # is straightforward and we can compare points in successive traces easily. traceIntervalDeg = traceInterval * metersToDeg numTraces = math.ceil(extent.height() / traceIntervalDeg) traceY = extent.height() / numTraces lineCount = 0 lastStartPoints = [] # Our major (longitude) loop starts here while not feedback.isCanceled(): # Initialize our line at the start empty = True line = [] startPoints = [] # Add the first point p1 = start if p1.x() >= extent.xMinimum() and p1.x() <= extent.xMaximum(): line.append(p1) empty = False # Now we will trace the line in the field direction until we are out of the rectangle's Y range, # restarting a new line whenever the horizontal step gets out of whack due to latitude change. for t in range(0, numTraces + 1): if feedback.isCanceled(): break y = extent.yMinimum() + (t * traceY) # get the variation at this point variation = geomag.declination(p1.y(), p1.x(), 0, date.today()) if t == 0: lastVariation = variation # record last used variation in this polyline for error computation # determine a 1-meter vector in the direction of magnetic north # and scale this to find a vector taking us from p1 to the next latitude step magN = projectBearing(p1, 1, variation) magN.multiply( (y - p1.y()) / magN.y() ) # note that Y magnitude will be nonzero for reasonable variations p2 = addPoints(p1, magN) if p2.x() >= extent.xMinimum() and p2.x() <= extent.xMaximum(): line.append(p2) empty = False # start a new, longitudinally adjusted line if distance exceeds tolerance if distanceTolerance > 0 and len(lastStartPoints) > t: lastP2 = lastStartPoints[t] (factor, nextX) = self.adjustLongForVariation( lastP2, lineDistance, variation) if abs(p2.x() - nextX) > distanceTolerance * factor: # Flush any line that is in progress if len(line) >= 2: feature = QgsFeature() feature.setAttributes([lineCount, variation]) feature.setGeometry( QgsGeometry.fromPolylineXY(line)) sink.addFeature(feature) # Now start a new line, properly spaced from the previous trace p2 = QgsPointXY(nextX, p2.y()) line = [] if p2.x() >= extent.xMinimum() and p2.x( ) <= extent.xMaximum(): line.append(p2) empty = False # if we didn't do that, and variation error exceeds tolerance, then start a new line in the same spot elif variationTolerance > 0 and abs( variation - lastVariation) > variationTolerance: # Flush any line that is in progress and start a new line in the same spot if len(line) >= 2: feature = QgsFeature() feature.setAttributes([lineCount, variation]) feature.setGeometry(QgsGeometry.fromPolylineXY(line)) sink.addFeature(feature) line = [p2] lastVariation = variation startPoints.append(p2) p1 = p2 # end field tracing loop # Flush any accumulated points to a polyline if len(line) >= 2: feature = QgsFeature() feature.setAttributes([lineCount, variation]) feature.setGeometry(QgsGeometry.fromPolylineXY(line)) sink.addFeature(feature) # If we did not manage to find any points inside the extent on this trace, we're done if empty: break # hold onto our point list for spacing check on the next trace lastStartPoints = startPoints # now advance the start point longitude, correcting for the variation angle variation = geomag.declination(start.y(), start.x(), 0, date.today()) (factor, nextX) = self.adjustLongForVariation(start, lineDistance, variation) start.setX(nextX) lineCount += 1 if lineDistance > 0: feedback.setProgress(100 * (start.x() - extent.xMinimum()) / extent.width()) else: feedback.setProgress(100 * (extent.xMaximum() - start.x()) / extent.width()) # end longitude loop if context.willLoadLayerOnCompletion(dest_id): context.layerToLoadOnCompletionDetails(dest_id).setPostProcessor( StylePostProcessor.create(self)) return {self.PrmOutputLayer: dest_id}
def point_clicked(self, point=None, button=None): # check if active layer is raster if self.raster is None: self.uc.bar_warn("Choose a raster to work with...", dur=3) return # check if coordinates trasformation is required canvas_srs = self.iface.mapCanvas().mapSettings().destinationCrs() if point is None: pos = self.last_point elif not canvas_srs == self.raster.crs(): project = QgsProject.instance() srs_transform = QgsCoordinateTransform(canvas_srs, self.raster.crs(), project) try: pos = srs_transform.transform(point) except QgsCsException as err: self.uc.bar_warn( "Point coordinates transformation failed! Check the raster projection:\n\n{}" .format(repr(err)), dur=5) return else: pos = QgsPointXY(point.x(), point.y()) # keep last clicked point self.last_point = pos # check if the point is within active raster bounds if self.rbounds[0] <= pos.x() <= self.rbounds[2]: self.px = int((pos.x() - self.rbounds[0]) / self.raster.rasterUnitsPerPixelX() ) # - self.gt[0]) / self.gt[1]) else: self.uc.bar_info("Out of x bounds", dur=2) return if self.rbounds[1] <= pos.y() <= self.rbounds[3]: self.py = int((self.rbounds[3] - pos.y()) / self.raster.rasterUnitsPerPixelY() ) # - self.gt[3]) / self.gt[5]) else: self.uc.bar_info("Out of y bounds", dur=2) return # probe current raster value, dict: band_nr -> value vals = self.rdp.identify(pos, QgsRaster.IdentifyFormatValue).results() # for rasters having more that 3 bands, ignore other than 1-3 bands_to_ignore = [i for i in vals.keys() if i > 3] for band_nr in bands_to_ignore: del vals[band_nr] # data types for each band dtypes = [] for nr in range(1, min(4, self.band_count + 1)): # bands data type dtypes.append(self.bands[nr]['qtype']) # check if nodata is defined if self.mode == 'gom' and self.bands[nr]['nodata'] is None: msg = 'NODATA value is not defined for one of the raster\'s bands.\n' msg += 'Please define it in raster properties dialog!' self.uc.show_warn(msg) return # if in probing mode, set band's spinbox value if self.mode == 'probe': val = vals[nr] if is_number( vals[nr]) else self.bands[nr]['nodata'] self.bands[nr]['sbox'].setValue(val) self.bands[nr]['sbox'].setFocus() self.bands[nr]['sbox'].selectAll() if not self.mode == 'probe': old_vals = [ v if v is not None else self.bands[k]['nodata'] for k, v in sorted(vals.items()) ] if self.mode == 'gom': temp_vals = [ self.bands[nr]['nodata'] for nr in sorted(vals.keys()) ] new_vals = [ int(v) if dtypes[i] < 6 else float(v) for i, v in enumerate(temp_vals) ] else: temp_vals = [ self.bands[nr]['sbox'].value() for nr in sorted(vals.keys()) ] new_vals = [ int(v) if dtypes[i] < 6 else float(v) for i, v in enumerate(temp_vals) ] # store all bands' changes to undo list self.undos[self.raster.id()].append( [old_vals, new_vals, self.px, self.py, pos]) # write the new cell value(s) self.change_cell_value(new_vals) if self.band_count > 2: self.mColorButton.setColor( QColor(self.bands[1]['sbox'].value(), self.bands[2]['sbox'].value(), self.bands[3]['sbox'].value()))
class FreehandRasterGeoreferencerLayer(QgsPluginLayer): LAYER_TYPE = "FreehandRasterGeoreferencerLayer" transformParametersChanged = pyqtSignal(tuple) def __init__(self, plugin, filepath, title, screenExtent): QgsPluginLayer.__init__( self, FreehandRasterGeoreferencerLayer.LAYER_TYPE, title) self.plugin = plugin self.iface = plugin.iface self.title = title self.filepath = filepath self.screenExtent = screenExtent self.history = [] # set custom properties self.setCustomProperty("title", title) self.setCustomProperty("filepath", self.filepath) self.setValid(True) self.setTransparency(LayerDefaultSettings.TRANSPARENCY) self.setBlendModeByName(LayerDefaultSettings.BLEND_MODE) # dummy data: real init is done in intializeLayer self.center = QgsPointXY(0, 0) self.rotation = 0.0 self.xScale = 1.0 self.yScale = 1.0 self.error = False self.initializing = False self.initialized = False self.initializeLayer(screenExtent) self._extent = None self.provider = FreehandRasterGeoreferencerLayerProvider(self) def dataProvider(self): # issue with DBManager if the dataProvider of the QgsLayerPlugin # returns None return self.provider def setScale(self, xScale, yScale): self.xScale = xScale self.yScale = yScale def setRotation(self, rotation): rotation = round(rotation, 1) # keep in -180,180 interval if rotation < -180: rotation += 360 if rotation > 180: rotation -= 360 self.rotation = rotation def setCenter(self, center): self.center = center def commitTransformParameters(self): QgsProject.instance().setDirty(True) self._extent = None self.setCustomProperty("xScale", self.xScale) self.setCustomProperty("yScale", self.yScale) self.setCustomProperty("rotation", self.rotation) self.setCustomProperty("xCenter", self.center.x()) self.setCustomProperty("yCenter", self.center.y()) self.transformParametersChanged.emit( (self.xScale, self.yScale, self.rotation, self.center)) def reprojectTransformParameters(self, oldCrs, newCrs): transform = QgsCoordinateTransform(oldCrs, newCrs, QgsProject.instance()) newCenter = transform.transform(self.center) newExtent = transform.transform(self.extent()) # transform the parameters except rotation # TODO rotation could be better handled (maybe check rotation between # old and new extent) # but not really worth the effort ? self.setCrs(newCrs) self.setCenter(newCenter) self.resetScale(newExtent.width(), newExtent.height()) def resetTransformParametersToNewCrs(self): """ Attempts to keep the layer on the same region of the map when the map CRS is changed """ oldCrs = self.crs() newCrs = self.iface.mapCanvas().mapSettings().destinationCrs() self.reprojectTransformParameters(oldCrs, newCrs) self.commitTransformParameters() def setupCrsEvents(self): layerId = self.id() def removeCrsChangeHandler(layerIds): if layerId in layerIds: try: self.iface.mapCanvas().destinationCrsChanged.disconnect( self.resetTransformParametersToNewCrs) except Exception: pass try: QgsProject.instance().disconnect( removeCrsChangeHandler) except Exception: pass self.iface.mapCanvas().destinationCrsChanged.connect( self.resetTransformParametersToNewCrs) QgsProject.instance().layersRemoved.connect( removeCrsChangeHandler) def setupCrs(self): mapCrs = self.iface.mapCanvas().mapSettings().destinationCrs() self.setCrs(mapCrs) self.setupCrsEvents() def repaint(self): self.repaintRequested.emit() def transformParameters(self): return (self.center, self.rotation, self.xScale, self.yScale) def initializeLayer(self, screenExtent=None): if self.error or self.initialized or self.initializing: return if self.filepath is not None: # not safe... self.initializing = True filepath = self.getAbsoluteFilepath() if not os.path.exists(filepath): # TODO integrate with BadLayerHandler ? loadErrorDialog = LoadErrorDialog(filepath) result = loadErrorDialog.exec_() if result == 1: # absolute filepath = loadErrorDialog.lineEditImagePath.text() # to relative if needed self.filepath = utils.toRelativeToQGS(filepath) self.setCustomProperty("filepath", self.filepath) QgsProject.instance().setDirty(True) else: self.error = True del loadErrorDialog fileInfo = QFileInfo(filepath) ext = fileInfo.suffix() if ext == "pdf": s = QSettings() oldValidation = s.value("/Projections/defaultBehavior") s.setValue("/Projections/defaultBehavior", "useGlobal") # for not asking about crs path = fileInfo.filePath() baseName = fileInfo.baseName() layer = QgsRasterLayer(path, baseName) self.image = layer.previewAsImage(QSize(layer.width(),layer.height())) s.setValue("/Projections/defaultBehavior", oldValidation) else: reader = QImageReader(filepath) self.image = reader.read() self.initialized = True self.initializing = False self.setupCrs() if screenExtent: # constructor called from AddLayer action # if not, layer loaded from QGS project file # check if image already has georef info # use GDAL dataset = gdal.Open(filepath, gdal.GA_ReadOnly) georef = None if dataset: georef = dataset.GetGeoTransform() if georef and not self.is_default_geotransform(georef): self.initializeExistingGeoreferencing(dataset, georef) else: # init to default params self.setCenter(screenExtent.center()) self.setRotation(0.0) sw = screenExtent.width() sh = screenExtent.height() self.resetScale(sw, sh) self.commitTransformParameters() def initializeExistingGeoreferencing(self, dataset, georef): # georef can have scaling, rotation or translation rotation = 180 / math.pi * -math.atan2(georef[4], georef[1]) sx = math.sqrt(georef[1] ** 2 + georef[4] ** 2) sy = math.sqrt(georef[2] ** 2 + georef[5] ** 2) i_center_x = self.image.width() / 2 i_center_y = self.image.height() / 2 center = QgsPointXY(georef[0] + georef[1] * i_center_x + georef[2] * i_center_y, georef[3] + georef[4] * i_center_x + georef[5] * i_center_y) qDebug(repr(rotation) + " " + repr((sx, sy)) + " " + repr(center)) self.setRotation(rotation) self.setCenter(center) # keep yScale positive self.setScale(sx, sy) self.commitTransformParameters() crs_wkt = dataset.GetProjection() message_shown = False if crs_wkt: qcrs = QgsCoordinateReferenceSystem(crs_wkt) if qcrs != self.crs(): # reproject try: self.reprojectTransformParameters(qcrs, self.crs()) self.commitTransformParameters() self.showBarMessage( "Transform parameters changed", "Found existing georeferencing in raster but " "its CRS does not match the CRS of the map. " "Reprojected the extent.", Qgis.Warning, 5) message_shown = True except Exception as ex: QgsMessageLog.logMessage(repr(ex)) self.showBarMessage( "CRS does not match", "Found existing georeferencing in raster but " "its CRS does not match the CRS of the map. " "Unable to reproject.", Qgis.Warning, 5) message_shown = True # if no projection info, assume it is the same CRS # as the map and no warning if not message_shown: self.showBarMessage( "Georeferencing loaded", "Found existing georeferencing in raster", Qgis.Info, 3) # zoom (assume the user wants to work on the image) self.iface.mapCanvas().setExtent(self.extent()) def is_default_geotransform(self, georef): """ Check if there is really a transform or if it is just the default made up by GDAL """ return (georef[0] == 0 and georef[3] == 0 and georef[1] == 1 and georef[5] == 1) def resetScale(self, sw, sh): iw = self.image.width() ih = self.image.height() wratio = sw / iw hratio = sh / ih if wratio > hratio: # takes all height of current extent self.setScale(hratio, hratio) else: # all width self.setScale(wratio, wratio) def replaceImage(self, filepath, title): self.title = title self.filepath = filepath # set custom properties self.setCustomProperty("title", title) self.setCustomProperty("filepath", self.filepath) self.setName(title) fileInfo = QFileInfo(filepath) ext = fileInfo.suffix() if ext == "pdf": s = QSettings() oldValidation = s.value("/Projections/defaultBehavior") s.setValue("/Projections/defaultBehavior", "useGlobal") # for not asking about crs path = fileInfo.filePath() baseName = fileInfo.baseName() layer = QgsRasterLayer(path, baseName) self.image = layer.previewAsImage(QSize(layer.width(), layer.height())) s.setValue("/Projections/defaultBehavior", oldValidation) else: reader = QImageReader(filepath) self.image = reader.read() self.repaint() def clone(self): layer = FreehandRasterGeoreferencerLayer(self.plugin, self.filepath, self.title, self.screenExtent) layer.center = self.center layer.rotation = self.rotation layer.xScale = self.xScale layer.yScale = self.yScale layer.commitTransformParameters() return layer def getAbsoluteFilepath(self): if not os.path.isabs(self.filepath): # relative to QGS file qgsPath = QgsProject.instance().fileName() qgsFolder, _ = os.path.split(qgsPath) filepath = os.path.join(qgsFolder, self.filepath) else: filepath = self.filepath return filepath def extent(self): self.initializeLayer() if not self.initialized: qDebug("Not Initialized") return QgsRectangle(0, 0, 1, 1) if self._extent: return self._extent topLeft, topRight, bottomRight, bottomLeft = self.cornerCoordinates() left = min(topLeft.x(), topRight.x(), bottomRight.x(), bottomLeft.x()) right = max(topLeft.x(), topRight.x(), bottomRight.x(), bottomLeft.x()) top = max(topLeft.y(), topRight.y(), bottomRight.y(), bottomLeft.y()) bottom = min(topLeft.y(), topRight.y(), bottomRight.y(), bottomLeft.y()) # recenter + create rectangle self._extent = QgsRectangle(left, bottom, right, top) return self._extent def cornerCoordinates(self): return self.transformedCornerCoordinates(self.center, self.rotation, self.xScale, self.yScale) def transformedCornerCoordinates(self, center, rotation, xScale, yScale): # scale topLeft = QgsPointXY(-self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale) topRight = QgsPointXY(self.image.width() / 2.0 * xScale, self.image.height() / 2.0 * yScale) bottomLeft = QgsPointXY(-self.image.width() / 2.0 * xScale, - self.image.height() / 2.0 * yScale) bottomRight = QgsPointXY(self.image.width() / 2.0 * xScale, - self.image.height() / 2.0 * yScale) # rotate # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) topLeft = self._rotate(topLeft, cosRot, sinRot) topRight = self._rotate(topRight, cosRot, sinRot) bottomRight = self._rotate(bottomRight, cosRot, sinRot) bottomLeft = self._rotate(bottomLeft, cosRot, sinRot) topLeft.set(topLeft.x() + center.x(), topLeft.y() + center.y()) topRight.set(topRight.x() + center.x(), topRight.y() + center.y()) bottomRight.set(bottomRight.x() + center.x(), bottomRight.y() + center.y()) bottomLeft.set(bottomLeft.x() + center.x(), bottomLeft.y() + center.y()) return (topLeft, topRight, bottomRight, bottomLeft) def transformedCornerCoordinatesFromPoint(self, startPoint, rotation, xScale, yScale): # startPoint is a fixed point for this new movement (rotation and # scale) # rotation is the global rotation of the image # xScale is the new xScale factor to be multiplied by self.xScale # idem for yScale # Calculate the coordinate of the center in a startPoint origin # coordinate system and apply scales dX = (self.center.x() - startPoint.x()) * xScale dY = (self.center.y() - startPoint.y()) * yScale # Half width and half height in the current transformation hW = (self.image.width() / 2.0) * self.xScale * xScale hH = (self.image.height() / 2.0) * self.yScale * yScale # Actual rectangle coordinates : pt1 = QgsPointXY(-hW, hH) pt2 = QgsPointXY(hW, hH) pt3 = QgsPointXY(hW, -hH) pt4 = QgsPointXY(-hW, -hH) # Actual rotation from the center # minus sign because rotation is CW in this class and Qt) rotationRad = -self.rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # Second transformation # displacement of the origin pt1 = QgsPointXY(pt1.x() + dX, pt1.y() + dY) pt2 = QgsPointXY(pt2.x() + dX, pt2.y() + dY) pt3 = QgsPointXY(pt3.x() + dX, pt3.y() + dY) pt4 = QgsPointXY(pt4.x() + dX, pt4.y() + dY) # Rotation # minus sign because rotation is CW in this class and Qt) rotationRad = -rotation * math.pi / 180 cosRot = math.cos(rotationRad) sinRot = math.sin(rotationRad) pt1 = self._rotate(pt1, cosRot, sinRot) pt2 = self._rotate(pt2, cosRot, sinRot) pt3 = self._rotate(pt3, cosRot, sinRot) pt4 = self._rotate(pt4, cosRot, sinRot) # translate to startPoint pt1 = QgsPointXY(pt1.x() + startPoint.x(), pt1.y() + startPoint.y()) pt2 = QgsPointXY(pt2.x() + startPoint.x(), pt2.y() + startPoint.y()) pt3 = QgsPointXY(pt3.x() + startPoint.x(), pt3.y() + startPoint.y()) pt4 = QgsPointXY(pt4.x() + startPoint.x(), pt4.y() + startPoint.y()) return (pt1, pt2, pt3, pt4) def moveCenterFromPointRotate(self, startPoint, rotation, xScale, yScale): cornerPoints = self.transformedCornerCoordinatesFromPoint( startPoint, rotation, xScale, yScale) self.center = QgsPointXY((cornerPoints[0].x( ) + cornerPoints[2].x()) / 2, (cornerPoints[0].y() + cornerPoints[2].y()) / 2) def _rotate(self, point, cosRot, sinRot): return QgsPointXY(point.x() * cosRot - point.y() * sinRot, point.x() * sinRot + point.y() * cosRot) def createMapRenderer(self, rendererContext): return FreehandRasterGeoreferencerLayerRenderer(self, rendererContext) def setBlendModeByName(self, modeName): self.blendModeName = modeName blendMode = getattr(QPainter, "CompositionMode_" + modeName, 0) self.setBlendMode(blendMode) self.setCustomProperty("blendMode", modeName) def setTransparency(self, transparency): self.transparency = transparency self.setCustomProperty("transparency", transparency) def draw(self, renderContext): if renderContext.extent().isEmpty(): qDebug("Drawing is skipped because map extent is empty.") return True self.initializeLayer() if not self.initialized: qDebug("Drawing is skipped because nothing to draw.") return True painter = renderContext.painter() painter.save() self.prepareStyle(painter) self.drawRaster(renderContext) painter.restore() return True def drawRaster(self, renderContext): painter = renderContext.painter() self.map2pixel = renderContext.mapToPixel() scaleX = self.xScale / self.map2pixel.mapUnitsPerPixel() scaleY = self.yScale / self.map2pixel.mapUnitsPerPixel() rect = QRectF(QPointF(-self.image.width() / 2.0, - self.image.height() / 2.0), QPointF(self.image.width() / 2.0, self.image.height() / 2.0)) mapCenter = self.map2pixel.transform(self.center) # draw the image on the map canvas painter.translate(QPoint(round(mapCenter.x()), round(mapCenter.y()))) painter.rotate(self.rotation) painter.scale(scaleX, scaleY) painter.drawImage(rect, self.image) painter.setBrush(Qt.NoBrush) painter.setPen(QColor(0, 0, 0)) painter.drawRect(rect) def prepareStyle(self, painter): painter.setOpacity(1.0 - self.transparency / 100.0) def readXml(self, node, context): self.readCustomProperties(node) self.title = self.customProperty("title", "") self.filepath = self.customProperty("filepath", "") self.xScale = float(self.customProperty("xScale", 1.0)) self.yScale = float(self.customProperty("yScale", 1.0)) self.rotation = float(self.customProperty("rotation", 0.0)) xCenter = float(self.customProperty("xCenter", 0.0)) yCenter = float(self.customProperty("yCenter", 0.0)) self.center = QgsPointXY(xCenter, yCenter) self.setTransparency(int(self.customProperty( "transparency", LayerDefaultSettings.TRANSPARENCY))) self.setBlendModeByName(self.customProperty( "blendMode", LayerDefaultSettings.BLEND_MODE)) return True def writeXml(self, node, doc, context): element = node.toElement() self.writeCustomProperties(node, doc) element.setAttribute("type", "plugin") element.setAttribute( "name", FreehandRasterGeoreferencerLayer.LAYER_TYPE) return True def metadata(self): lines = [] fmt = "%s:\t%s" lines.append(fmt % (self.tr("Title"), self.title)) filepath = self.getAbsoluteFilepath() filepath = os.path.normpath(filepath) lines.append(fmt % (self.tr("Path"), filepath)) lines.append(fmt % (self.tr("Image Width"), str(self.image.width()))) lines.append(fmt % (self.tr("Image Height"), str(self.image.height()))) lines.append(fmt % (self.tr("Rotation (CW)"), str(self.rotation))) lines.append(fmt % (self.tr("X center"), str(self.center.x()))) lines.append(fmt % (self.tr("Y center"), str(self.center.y()))) lines.append(fmt % (self.tr("X scale"), str(self.xScale))) lines.append(fmt % (self.tr("Y scale"), str(self.yScale))) return "\n".join(lines) def log(self, msg): qDebug(msg) def dump(self, detail=False, bbox=None): pass def showStatusMessage(self, msg, timeout): self.iface.mainWindow().statusBar().showMessage(msg, timeout) def showBarMessage(self, title, text, level, duration): self.iface.messageBar().pushMessage(title, text, level, duration) def transparencyChanged(self, val): QgsProject.instance().setDirty(True) self.setTransparency(val) self.repaintRequested.emit()
class Qad_pedit_maptool(QadGetPoint): def __init__(self, plugIn): QadGetPoint.__init__(self, plugIn) self.firstPt = None self.mode = None self.layer = None self.polyline = QadPolyline() self.tolerance2ApproxCurve = None self.vertexAt = 0 self.vertexPt = None self.after = True self.basePt = None self.__highlight = QadHighlight(self.canvas) def hidePointMapToolMarkers(self): QadGetPoint.hidePointMapToolMarkers(self) self.__highlight.hide() def showPointMapToolMarkers(self): QadGetPoint.showPointMapToolMarkers(self) self.__highlight.show() def clear(self): QadGetPoint.clear(self) self.__highlight.reset() self.mode = None if self.basePt is not None: del(self.basePt) self.basePt = None def setPolyline(self, polyline, layer): self.polyline.set(polyline) self.layer = layer self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) def setVertexAt(self, vertexAt, after = None): if vertexAt == self.polyline.qty(): self.firstPt = self.polyline.getLinearObjectAt(-1).getEndPt() else: self.firstPt = self.polyline.getLinearObjectAt(vertexAt).getStartPt() self.vertexPt = QgsPointXY(self.firstPt) self.vertexAt = vertexAt self.after = after def canvasMoveEvent(self, event): QadGetPoint.canvasMoveEvent(self, event) self.__highlight.reset() tmpPolyline = None # si richiede un nuovo vertice da inserire if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX: if self.basePt is not None: offsetX = self.tmpPoint.x() - self.basePt.x() offsetY = self.tmpPoint.y() - self.basePt.y() newPt = QgsPointXY(self.vertexPt.x() + offsetX, self.vertexPt.y() + offsetY) else: newPt = QgsPointXY(self.tmpPoint) tmpPolyline = self.polyline.copy() if self.after: # dopo if self.vertexAt == tmpPolyline.qty() and tmpPolyline.isClosed(): tmpPolyline.insertPoint(0, newPt) else: tmpPolyline.insertPoint(self.vertexAt, newPt) else: # prima if self.vertexAt == 0 and tmpPolyline.isClosed(): tmpPolyline.insertPoint(tmpPolyline.qty() - 1, newPt) else: tmpPolyline.insertPoint(self.vertexAt - 1, newPt) elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: newPt = QgsPointXY(self.tmpPoint) tmpPolyline = self.polyline.copy() tmpPolyline.movePoint(self.vertexAt, newPt) if tmpPolyline is not None: pts = tmpPolyline.asPolyline(self.tolerance2ApproxCurve) if self.layer.geometryType() == QgsWkbTypes.PolygonGeometry: geom = QgsGeometry.fromPolygonXY([pts]) else: geom = QgsGeometry.fromPolylineXY(pts) # trasformo la geometria nel crs del layer self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) def activate(self): QadGetPoint.activate(self) self.__highlight.show() def deactivate(self): try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! QadGetPoint.deactivate(self) self.__highlight.hide() except: pass def setMode(self, mode): self.mode = mode # si richiede la selezione di un'entità if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_ENTITY_SEL: self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) # solo layer lineari o poligono editabili che non appartengano a quote layerList = [] for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ layer.isEditable(): if len(QadDimStyles.getDimListByLayer(layer)) == 0: layerList.append(layer) self.layersToCheck = layerList self.setSnapType(QadSnapTypeEnum.DISABLE) # non si richiede niente elif self.mode == Qad_pedit_maptool_ModeEnum.NONE: self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) self.setDrawMode(QadGetPointDrawModeEnum.NONE) # si richiede il primo punto per calcolo distanza di approssimazione # si richiede la posizione più vicina ad un vertice elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_FIRST_TOLERANCE_PT: self.onlyEditableLayers = False self.checkPointLayer = True self.checkLineLayer = True self.checkPolygonLayer = True self.setSnapType() self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) self.setDrawMode(QadGetPointDrawModeEnum.NONE) # noto il primo punto per calcolo distanza di approssimazione si richiede il secondo punto elif self.mode == Qad_pedit_maptool_ModeEnum.FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT or \ self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX or \ self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: self.onlyEditableLayers = False self.checkPointLayer = True self.checkLineLayer = True self.checkPolygonLayer = True self.setSnapType() self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) self.setStartPoint(self.firstPt) # si richiede la posizione più vicina ad un vertice elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX: self.setSnapType(QadSnapTypeEnum.DISABLE) self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) self.setDrawMode(QadGetPointDrawModeEnum.NONE) # si richede il punto base (grip mode) elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_BASE_PT: self.setSnapType() self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) self.setDrawMode(QadGetPointDrawModeEnum.NONE)
def processPoly(source, sink, feedback, maxseglen): layercrs = source.sourceCrs() if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(epsg4326, layercrs, QgsProject.instance()) total = 100.0 / source.featureCount() if source.featureCount() else 0 iterator = source.getFeatures() num_bad = 0 for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break try: if not feature.geometry().isMultipart(): poly = feature.geometry().asPolygon() numpolygons = len(poly) if numpolygons < 1: continue ptset = [] # Iterate through all points in the polygon and if the distance # is greater than the maxseglen, then add additional points. for points in poly: numpoints = len(points) if numpoints < 2: continue # If the input is not 4326 we need to convert it to that and then back to the output CRS ptStart = QgsPointXY(points[0][0], points[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1, numpoints): ptEnd = QgsPointXY(points[x][0], points[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) # Check to see if the distance is greater than the maximum # segment length and if so lets add additional points. if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY(g['lon2'], g['lat2'])) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) ptset.append(pts) if len(ptset) > 0: featureout = QgsFeature() featureout.setGeometry(QgsGeometry.fromPolygonXY(ptset)) featureout.setAttributes(feature.attributes()) sink.addFeature(featureout) else: multipoly = feature.geometry().asMultiPolygon() multiset = [] for poly in multipoly: ptset = [] for points in poly: numpoints = len(points) if numpoints < 2: continue # If the input is not 4326 we need to convert it to that and then back to the output CRS ptStart = QgsPointXY(points[0][0], points[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1, numpoints): ptEnd = QgsPointXY(points[x][0], points[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY( g['lon2'], g['lat2'])) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) ptset.append(pts) multiset.append(ptset) if len(multiset) > 0: featureout = QgsFeature() featureout.setGeometry( QgsGeometry.fromMultiPolygonXY(multiset)) featureout.setAttributes(feature.attributes()) sink.addFeature(featureout) except: num_bad += 1 '''s = traceback.format_exc() feedback.pushInfo(s)''' feedback.setProgress(int(cnt * total)) return num_bad
class QadGRIPMOVECommandClass(QadCommandClass): def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadGRIPMOVECommandClass(self.plugIn) def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.cacheEntitySet = QadCacheEntitySet() self.basePt = QgsPointXY() self.skipToNextGripCommand = False self.copyEntities = False self.nOperationsToUndo = 0 def __del__(self): QadCommandClass.__del__(self) def getPointMapTool(self, drawMode=QadGetPointDrawModeEnum.NONE): if (self.plugIn is not None): if self.PointMapTool is None: self.PointMapTool = Qad_move_maptool(self.plugIn) return self.PointMapTool else: return None #============================================================================ # setSelectedEntityGripPoints #============================================================================ def setSelectedEntityGripPoints(self, entitySetGripPoints): # lista delle entityGripPoint con dei grip point selezionati self.cacheEntitySet.clear() for entityGripPoints in entitySetGripPoints.entityGripPoints: self.cacheEntitySet.appendEntity(entityGripPoints.entity) self.getPointMapTool().cacheEntitySet = self.cacheEntitySet #============================================================================ # move #============================================================================ def move(self, entity, offsetX, offsetY): # verifico se l'entità appartiene ad uno stile di quotatura if entity.whatIs() == "ENTITY": # sposto la geometria dell'entità qadGeom = entity.getQadGeom().copy() # la copio qadGeom.move(offsetX, offsetY) f = entity.getFeature() f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.crs())) if self.copyEntities == False: # plugIn, layer, feature, refresh, check_validity if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: return False else: # plugIn, layer, features, coordTransform, refresh, check_validity if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: return False elif entity.whatIs() == "DIMENTITY": # stiro la quota if self.copyEntities == False: if entity.deleteToLayers(self.plugIn) == False: return False newDimEntity = QadDimEntity(entity) # la copio newDimEntity.move(offsetX, offsetY) if newDimEntity.addToLayers(self.plugIn) == False: return False return True #============================================================================ # moveFeatures #============================================================================ def moveFeatures(self, newPt): offsetX = newPt.x() - self.basePt.x() offsetY = newPt.y() - self.basePt.y() self.plugIn.beginEditCommand("Feature moved", self.cacheEntitySet.getLayerList()) dimElaboratedList = [] # lista delle quotature già elaborate entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) for entity in entityIterator: qadGeom = entity.getQadGeom() # così inizializzo le info qad # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is not None: if appendDimEntityIfNotExisting( dimElaboratedList, dimEntity) == False: # quota già elaborata continue entity = dimEntity if self.move(entity, offsetX, offsetY) == False: self.plugIn.destroyEditCommand() return self.plugIn.endEditCommand() self.nOperationsToUndo = self.nOperationsToUndo + 1 #============================================================================ # waitForMovePoint #============================================================================ def waitForMovePoint(self): self.step = 1 self.plugIn.setLastPoint(self.basePt) # imposto il map tool self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) keyWords = QadMsg.translate("Command_GRIPMOVE", "Base point") + "/" + \ QadMsg.translate("Command_GRIPMOVE", "Copy") + "/" + \ QadMsg.translate("Command_GRIPMOVE", "Undo") + "/" + \ QadMsg.translate("Command_GRIPMOVE", "eXit") prompt = QadMsg.translate( "Command_GRIPMOVE", "Specify move point or [{0}]: ").format(keyWords) englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) #============================================================================ # waitForBasePt #============================================================================ def waitForBasePt(self): self.step = 2 # imposto il map tool self.getPointMapTool().setMode( Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) # si appresta ad attendere un punto self.waitForPoint( QadMsg.translate("Command_GRIPMOVE", "Specify base point: ")) #============================================================================ # run #============================================================================ def run(self, msgMapTool=False, msg=None): if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): self.showMsg( QadMsg.translate( "QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n" )) return True # fine comando #========================================================================= # RICHIESTA SELEZIONE OGGETTI if self.step == 0: # inizio del comando if self.cacheEntitySet.isEmpty( ): # non ci sono oggetti da spostare return True self.showMsg(QadMsg.translate("Command_GRIPMOVE", "\n** MOVE **\n")) # si appresta ad attendere un punto di spostamento self.waitForMovePoint() return False #========================================================================= # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI SPOSTAMENTO elif self.step == 1: ctrlKey = False if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse value = None else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False else: value = self.getPointMapTool().point ctrlKey = self.getPointMapTool().ctrlKey else: # il punto arriva come parametro della funzione value = msg if type(value) == unicode: if value == QadMsg.translate( "Command_GRIPMOVE", "Base point") or value == "Base point": # si appresta ad attendere il punto base self.waitForBasePt() elif value == QadMsg.translate("Command_GRIPMOVE", "Copy") or value == "Copy": # Copia entità lasciando inalterate le originali self.copyEntities = True # si appresta ad attendere un punto di spostamento self.waitForMovePoint() elif value == QadMsg.translate("Command_GRIPMOVE", "Undo") or value == "Undo": if self.nOperationsToUndo > 0: self.nOperationsToUndo = self.nOperationsToUndo - 1 self.plugIn.undoEditCommand() else: self.showMsg( QadMsg.translate( "QAD", "\nThe command has been canceled.")) # si appresta ad attendere un punto di spostamento self.waitForMovePoint() elif value == QadMsg.translate("Command_GRIPMOVE", "eXit") or value == "eXit": return True # fine comando elif type(value) == QgsPointXY: # se é stato selezionato un punto if ctrlKey: self.copyEntities = True self.moveFeatures(value) if self.copyEntities == False: return True # si appresta ad attendere un punto di stiramento self.waitForMovePoint() else: if self.copyEntities == False: self.skipToNextGripCommand = True return True # fine comando return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) elif self.step == 2: # dopo aver atteso un punto if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse pass # opzione di default "spostamento" else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if type(value) == QgsPointXY: # se é stato inserito il punto base self.basePt.set(value.x(), value.y()) # imposto il map tool self.getPointMapTool().basePt = self.basePt # si appresta ad attendere un punto di spostamento self.waitForMovePoint() return False
class QadGRIPSTRETCHCommandClass(QadCommandClass): def instantiateNewCmd(self): """ istanzia un nuovo comando dello stesso tipo """ return QadGRIPSTRETCHCommandClass(self.plugIn) def __init__(self, plugIn): QadCommandClass.__init__(self, plugIn) self.selectedEntityGripPoints = [ ] # lista in cui ogni elemento è una entità + una lista di punti da stirare self.basePt = QgsPointXY() self.skipToNextGripCommand = False self.copyEntities = False self.nOperationsToUndo = 0 def __del__(self): QadCommandClass.__del__(self) def getPointMapTool(self, drawMode=QadGetPointDrawModeEnum.NONE): if (self.plugIn is not None): if self.PointMapTool is None: self.PointMapTool = Qad_gripStretch_maptool(self.plugIn) return self.PointMapTool else: return None #============================================================================ # addToSelectedEntityGripPoints #============================================================================ def addToSelectedEntityGripPoints(self, entityGripPoints): # entità con lista dei grip point i = 0 gripPoints = entityGripPoints.gripPoints gripPointsLen = len(gripPoints) ptList = [] while i < gripPointsLen: gripPoint = gripPoints[i] # grip point selezionato if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: if gripPoint.gripType == qad_grip.QadGripPointTypeEnum.CENTER: ptList.append(gripPoint.getPoint()) elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.LINE_MID_POINT: # aggiungo il vertice precedente e successivo di quello intermedio if i > 0: ptList.append(gripPoints[i - 1].getPoint()) if i < gripPointsLen - 1: ptList.append(gripPoints[i + 1].getPoint()) elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.QUA_POINT: ptList.append(gripPoint.getPoint()) elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.VERTEX or \ gripPoint.gripType == qad_grip.QadGripPointTypeEnum.END_VERTEX: ptList.append(gripPoint.getPoint()) elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.ARC_MID_POINT: ptList.append(gripPoint.getPoint()) i = i + 1 if len(ptList) > 0: self.selectedEntityGripPoints.append( [entityGripPoints.entity, ptList]) #============================================================================ # setSelectedEntityGripPoints #============================================================================ def setSelectedEntityGripPoints(self, entitySetGripPoints): # lista delle entityGripPoint con dei grip point selezionati # ritorna una lista in cui ogni elemento è una entità + una lista di punti da stirare del self.selectedEntityGripPoints[:] # svuoto la lista for entityGripPoints in entitySetGripPoints.entityGripPoints: self.addToSelectedEntityGripPoints(entityGripPoints) self.getPointMapTool().setSelectedEntityGripPoints( self.selectedEntityGripPoints) # input : self.basePt e entitySetGripPoints # cerco in entitySetGripPoints l'entità che ha un solo grip selezionato corrispondente a basePt entityGripPoints, entityGripPoint = entitySetGripPoints.isIntersecting( self.basePt) if entityGripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED and \ len(entityGripPoints.getSelectedGripPoints()) == 1: entity = entityGripPoints.entity # verifico se l'entità appartiene ad uno stile di quotatura if QadDimStyles.isDimEntity(entity): pass else: qadGeom = entity.getQadGeom(entityGripPoint.atGeom, entityGripPoint.atSubGeom) qadGeomType = qadGeom.whatIs() if qadGeomType == "POLYLINE": self.getPointMapTool().prevPart, self.getPointMapTool( ).nextPart = qadGeom.getPrevNextLinearObjectsAtVertex( entityGripPoint.nVertex) elif qadGeomType == "CIRCLE": if qadGeom.isPtOnCircle(entityGripPoint.getPoint()): line = QadLine() line.set(qadGeom.center, entityGripPoint.getPoint()) self.getPointMapTool().prevPart = line elif qadGeomType == "ELLIPSE": if qadGeom.containsPt(entityGripPoint.getPoint()): line = QadLine() line.set(qadGeom.center, entityGripPoint.getPoint()) self.getPointMapTool().prevPart = line #============================================================================ # getSelectedEntityGripPointNdx #============================================================================ def getSelectedEntityGripPointNdx(self, entity): # lista delle entityGripPoint con dei grip point selezionati # cerca la posizione di un'entità nella lista in cui ogni elemento è una entità + una lista di punti da stirare i = 0 tot = len(self.selectedEntityGripPoints) while i < tot: selectedEntityGripPoint = self.selectedEntityGripPoints[i] if selectedEntityGripPoint[0] == entity: return i i = i + 1 return -1 #============================================================================ # stretch #============================================================================ def stretch(self, entity, ptList, offsetX, offsetY, tolerance2ApproxCurve): # entity = entità da stirare # ptList = lista dei punti da stirare # offsetX, offsetY = spostamento da applicare # tolerance2ApproxCurve = tolleranza per ricreare le curve if entity.whatIs() == "DIMENTITY": dimEntity = entity else: # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is None: stretchedGeom = entity.getQadGeom() # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più if stretchedGeom is None: # se non c'è lo salto senza errore return True # stiro la feature stretchedGeom = qad_stretch_fun.stretchQadGeometry(stretchedGeom, ptList, \ offsetX, offsetY) if stretchedGeom is not None: # trasformo la geometria QAD in geometria GSIS nel crs del layer f = entity.getFeature() f.setGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.crs())) if self.copyEntities == False: # plugIn, layer, feature, refresh, check_validity if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: return False else: # plugIn, layer, features, coordTransform, refresh, check_validity if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: return False else: # stiro la quota if self.copyEntities == False: if dimEntity.deleteToLayers(self.plugIn) == False: return False newDimEntity = QadDimEntity(dimEntity) # la copio newDimEntity.stretch(ptList, offsetX, offsetY) if newDimEntity.addToLayers(self.plugIn) == False: return False # non so per quale motivo a volte non si aggiorna la mappa quindi forzo l'aggiornamento self.plugIn.canvas.refresh() return True #============================================================================ # stretchFeatures #============================================================================ def stretchFeatures(self, newPt): # mi ricavo un unico QadEntitySet con le entità selezionate entitySet = QadEntitySet() for selectedEntity in self.selectedEntityGripPoints: entitySet.addEntity(selectedEntity[0]) self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) dimElaboratedList = [] # lista delle quotature già elaborate for selectedEntity in self.selectedEntityGripPoints: entity = selectedEntity[0] ptList = selectedEntity[1] layer = entity.layer tolerance2ApproxCurve = QadVariables.get( QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) offsetX = newPt.x() - self.basePt.x() offsetY = newPt.y() - self.basePt.y() # verifico se l'entità appartiene ad uno stile di quotatura dimEntity = QadDimStyles.getDimEntity(entity) if dimEntity is None: if self.stretch(entity, ptList, offsetX, offsetY, tolerance2ApproxCurve) == False: self.plugIn.destroyEditCommand() return else: found = False for dimElaborated in dimElaboratedList: if dimElaborated == dimEntity: found = True if found == False: # quota non ancora elaborata # aggiungo i layer dei componenti della quota self.plugIn.addLayerListToLastEditCommand( "Feature stretched", [ dimEntity.getSymbolLayer(), dimEntity.getLinearLayer(), dimEntity.getTextualLayer() ]) dimEntitySet = dimEntity.getEntitySet() # creo un'unica lista contenente i grip points di tutti i componenti della quota dimPtlist = [] for layerEntitySet in dimEntitySet.layerEntitySetList: for featureId in layerEntitySet.featureIds: componentDim = QadEntity() componentDim.set(layerEntitySet.layer, featureId) i = self.getSelectedEntityGripPointNdx( componentDim) if i >= 0: dimPtlist.extend( self.selectedEntityGripPoints[i][1]) dimElaboratedList.append(dimEntity) if self.stretch(dimEntity, dimPtlist, offsetX, offsetY, tolerance2ApproxCurve) == False: self.plugIn.destroyEditCommand() return self.plugIn.endEditCommand() self.nOperationsToUndo = self.nOperationsToUndo + 1 #============================================================================ # waitForStretchPoint #============================================================================ def waitForStretchPoint(self): self.step = 1 self.plugIn.setLastPoint(self.basePt) # imposto il map tool self.getPointMapTool().basePt = self.basePt self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ QadMsg.translate("Command_GRIP", "Copy") + "/" + \ QadMsg.translate("Command_GRIP", "Undo") + "/" + \ QadMsg.translate("Command_GRIP", "eXit") prompt = QadMsg.translate( "Command_GRIPSTRETCH", "Specify stretch point or [{0}]: ").format(keyWords) englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" keyWords += "_" + englishKeyWords # si appresta ad attendere un punto o enter o una parola chiave # msg, inputType, default, keyWords, nessun controllo self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ None, \ keyWords, QadInputModeEnum.NONE) #============================================================================ # waitForBasePt #============================================================================ def waitForBasePt(self): self.step = 2 # imposto il map tool self.getPointMapTool().setMode( Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) # si appresta ad attendere un punto self.waitForPoint( QadMsg.translate("Command_GRIPSTRETCH", "Specify base point: ")) #============================================================================ # run #============================================================================ def run(self, msgMapTool=False, msg=None): if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): self.showMsg( QadMsg.translate( "QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n" )) return True # fine comando #========================================================================= # RICHIESTA SELEZIONE OGGETTI if self.step == 0: # inizio del comando if len(self.selectedEntityGripPoints ) == 0: # non ci sono oggetti da stirare return True self.showMsg( QadMsg.translate("Command_GRIPSTRETCH", "\n** STRETCH **\n")) # si appresta ad attendere un punto di stiramento self.waitForStretchPoint() return False #========================================================================= # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI STIRAMENTO elif self.step == 1: ctrlKey = False if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse value = None else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False else: value = self.getPointMapTool().point ctrlKey = self.getPointMapTool().ctrlKey else: # il punto arriva come parametro della funzione value = msg if type(value) == unicode: if value == QadMsg.translate( "Command_GRIP", "Base point") or value == "Base point": # si appresta ad attendere il punto base self.waitForBasePt() elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": # Copia entità lasciando inalterate le originali self.copyEntities = True # si appresta ad attendere un punto di stiramento self.waitForStretchPoint() elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": if self.nOperationsToUndo > 0: self.nOperationsToUndo = self.nOperationsToUndo - 1 self.plugIn.undoEditCommand() else: self.showMsg( QadMsg.translate( "QAD", "\nThe command has been canceled.")) # si appresta ad attendere un punto di stiramento self.waitForStretchPoint() elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": return True # fine comando elif type(value) == QgsPointXY: # se é stato selezionato un punto if ctrlKey: self.copyEntities = True self.stretchFeatures(value) if self.copyEntities == False: return True # si appresta ad attendere un punto di stiramento self.waitForStretchPoint() else: if self.copyEntities == False: self.skipToNextGripCommand = True return True # fine comando return False #========================================================================= # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) elif self.step == 2: # dopo aver atteso un punto if msgMapTool == True: # il punto arriva da una selezione grafica # la condizione seguente si verifica se durante la selezione di un punto # é stato attivato un altro plugin che ha disattivato Qad # quindi stato riattivato il comando che torna qui senza che il maptool # abbia selezionato un punto if self.getPointMapTool( ).point is None: # il maptool é stato attivato senza un punto if self.getPointMapTool( ).rightButton == True: # se usato il tasto destro del mouse pass # opzione di default "spostamento" else: self.setMapTool( self.getPointMapTool()) # riattivo il maptool return False value = self.getPointMapTool().point else: # il punto arriva come parametro della funzione value = msg if type(value) == QgsPointXY: # se é stato inserito il punto base self.basePt.set(value.x(), value.y()) # imposto il map tool self.getPointMapTool().basePt = self.basePt # si appresta ad attendere un punto di stiramento self.waitForStretchPoint() return False
def processLine(source, sink, feedback, discardVertices, maxseglen): layercrs = source.sourceCrs() if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(epsg4326, layercrs, QgsProject.instance()) total = 100.0 / source.featureCount() if source.featureCount() else 0 iterator = source.getFeatures() num_bad = 0 for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break try: if feature.geometry().isMultipart(): seg = feature.geometry().asMultiPolyline() else: seg = [feature.geometry().asPolyline()] numseg = len(seg) if numseg < 1 or len(seg[0]) < 2: continue # Create a new Line Feature fline = QgsFeature() # If the input is not 4326 we need to convert it to that and then back to the output CRS if discardVertices: ptStart = QgsPointXY(seg[0][0][0], seg[0][0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] numpoints = len(seg[numseg - 1]) ptEnd = QgsPointXY(seg[numseg - 1][numpoints - 1][0], seg[numseg - 1][numpoints - 1][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY(g['lon2'], g['lat2'])) pts.append(ptEnd) if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) else: if not feature.geometry().isMultipart(): line = seg[0] numpoints = len(line) ptStart = QgsPointXY(line[0][0], line[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1, numpoints): ptEnd = QgsPointXY(line[x][0], line[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY(g['lon2'], g['lat2'])) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) fline.setGeometry(QgsGeometry.fromPolylineXY(pts)) else: # MultiLineString outseg = [] for line in seg: numpoints = len(line) ptStart = QgsPointXY(line[0][0], line[0][1]) if layercrs != epsg4326: # Convert to 4326 ptStart = transto4326.transform(ptStart) pts = [ptStart] for x in range(1, numpoints): ptEnd = QgsPointXY(line[x][0], line[x][1]) if layercrs != epsg4326: # Convert to 4326 ptEnd = transto4326.transform(ptEnd) l = geod.InverseLine(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) if l.s13 > maxseglen: n = int(math.ceil(l.s13 / maxseglen)) seglen = l.s13 / n for i in range(1, n): s = seglen * i g = l.Position( s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL) pts.append(QgsPointXY( g['lon2'], g['lat2'])) pts.append(ptEnd) ptStart = ptEnd if layercrs != epsg4326: # Convert each point back to the output CRS for x, pt in enumerate(pts): pts[x] = transfrom4326.transform(pt) outseg.append(pts) fline.setGeometry(QgsGeometry.fromMultiPolylineXY(outseg)) fline.setAttributes(feature.attributes()) sink.addFeature(fline) except: num_bad += 1 feedback.setProgress(int(cnt * total)) return num_bad
def measureLine(self, linetoMeasure, clicked_pt, geodetic_measure=True): """This will take a line segment and the clicked point and return the length up till that point for the segment in km. Inspired by shape tools and closest point plugins""" srcCRS = linetoMeasure.sourceCrs() #get CRS from line feature = linetoMeasure.getFeature(0) #get first feature from line wgs84 = KPTool.epsg4326 #define EPSG 4326 projected_point_raw, dist_ext = self.closestPt( linetoMeasure, clicked_pt, True ) #we'll need that point later to compare with projected points of subsegments projected_point_on_line_raw, dist_nonext = self.closestPt( linetoMeasure, clicked_pt, False) #get proj point on non-extended line if srcCRS != wgs84: geomTo4326 = QgsCoordinateTransform( srcCRS, wgs84, QgsProject.instance()) #convert if needed if feature.geometry().isMultipart(): # get nodes out of data ptdata = [feature.geometry().asMultiPolyline()] else: feature = linetoMeasure.getFeature( 1) #for some reason singleline first feature is NULL ptdata = [[feature.geometry().asPolyline()]] for seg in ptdata: if len(seg) < 1: # should never happen self.iface.messageBar().pushMessage( 'Something is strange with your line file', level=Qgis.Critical, duration=2) continue for pts in seg: # now we get all nodes from start to end of segment line numpoints = len(pts) if numpoints < 2: # should never happen self.iface.messageBar().pushMessage( 'Something is strange with your line file', level=Qgis.Critical, duration=2) continue ptStart_raw = QgsPointXY(pts[0].x(), pts[0].y()) #get initial point coords # Calculate the total distance of this line segment distance = 0.0 for x in range(1, numpoints): # from point one (since we preextend segment) start measuring distance cumulative ptEnd_raw = QgsPointXY( pts[x].x(), pts[x].y()) # coordinates of next point P1 = QgsPoint(ptStart_raw.x(), ptStart_raw.y()) P2 = QgsPoint(ptEnd_raw.x(), ptEnd_raw.y()) seg_geom = QgsGeometry.fromPolyline( (P1, P2)) # generate geometry for current subsegment _, mindistpt, _, _ = seg_geom.closestSegmentWithContext( projected_point_raw) TestPoint = QgsPointXY( mindistpt[0], mindistpt[1]) #create projected point on subsegment ptStart = ptStart_raw ptEnd = ptEnd_raw projected_point = projected_point_raw projected_point_on_line = projected_point_on_line_raw if geodetic_measure == True: if srcCRS != wgs84: # Convert to 4326 - just to be safe when using geodetic measure ptStart = geomTo4326.transform(ptStart_raw) ptEnd = geomTo4326.transform(ptEnd_raw) projected_point = geomTo4326.transform( projected_point_raw) projected_point_on_line = geomTo4326.transform( projected_point_on_line_raw) TestPoint = geomTo4326.transform(TestPoint) d_to_click = KPTool.geod.Inverse( ptStart.y(), ptStart.x(), projected_point_on_line.y(), projected_point_on_line.x())['s12'] len_subsegment = KPTool.geod.Inverse( ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x() )['s12'] # geodetic distance between begin and end of subsegment test_distance = KPTool.geod.Inverse( projected_point_on_line.y(), projected_point_on_line.x(), TestPoint.y(), TestPoint.x() )['s12'] # check distance between two on-subsegment-points else: d_to_click = QgsDistanceArea().measureLine( ptStart, projected_point_on_line) len_subsegment = QgsDistanceArea().measureLine( ptStart, ptEnd ) # cartesian distance between begin and end point test_distance = QgsDistanceArea().measureLine( projected_point_on_line, TestPoint ) # check distance between two on-subsegment-points round_test_distance = round( test_distance, 6 ) # round so we can get zero, otherwise it will be a small fraction if round_test_distance != 0: # not yet the last segment to be measured distance += len_subsegment #add to comulative else: # this is the segment, where we have to stop measuring distance += d_to_click break # break loop and give distance so far ptStart_raw = ptEnd_raw # make ready for next pair of points if QgsPoint(projected_point) != QgsPoint( projected_point_on_line): # if our point on the extended line is different, the projected point is before start or after end of line layer if geodetic_measure == True: extra_dist = KPTool.geod.Inverse( projected_point.y(), projected_point.x(), projected_point_on_line.y(), projected_point_on_line.x())['s12'] else: extra_dist = QgsDistanceArea().measureLine( projected_point, projected_point_on_line) if round(distance, 6) == 0: # we are at the start of the line layer, the for loops hasn't done anything, so negative distance to starting node distance = -extra_dist else: # we are at the end of the line segment, so need to add more distance distance = distance + extra_dist out = distance / 1000 # Distance converted to KM return out
def run(sources_layer_path, receivers_layer_path, emission_pts_layer_path, research_ray): sources_layer = QgsVectorLayer(sources_layer_path, "input layer", "ogr") receivers_layer = QgsVectorLayer(receivers_layer_path, "output layer", "ogr") sources_feat_all = sources_layer.dataProvider().getFeatures() receivers_feat_all_dict = {} receivers_feat_all = receivers_layer.dataProvider().getFeatures() receivers_spIndex = QgsSpatialIndex() for receivers_feat in receivers_feat_all: receivers_spIndex.insertFeature(receivers_feat) receivers_feat_all_dict[receivers_feat.id()] = receivers_feat emission_pts_fields = QgsFields() emission_pts_fields.append(QgsField("id_emi", QVariant.Int)) emission_pts_fields.append(QgsField("id_emi_source", QVariant.Int)) emission_pts_fields.append(QgsField("id_source", QVariant.Int)) emission_pts_fields.append(QgsField("d_rTOe", QVariant.Double, len=10, prec=2)) # update for QGIS 3 converting VectorWriter to QgsVectorFileWriter # emission_pts_writer = VectorWriter(emission_pts_layer_path, None, emission_pts_fields, 0, sources_layer.crs()) emission_pts_writer = QgsVectorFileWriter(emission_pts_layer_path, "System", emission_pts_fields, QgsWkbTypes.Point, sources_layer.crs(), "ESRI Shapefile") # initializes ray and emission point id emission_pt_id = 0 for sources_feat in sources_feat_all: # researches the receiver points in a rectangle created by the research_ray # creates the search rectangle rect = QgsRectangle() rect.setXMinimum(sources_feat.geometry().boundingBox().xMinimum() - research_ray) rect.setXMaximum(sources_feat.geometry().boundingBox().xMaximum() + research_ray) rect.setYMinimum(sources_feat.geometry().boundingBox().yMinimum() - research_ray) rect.setYMaximum(sources_feat.geometry().boundingBox().yMaximum() + research_ray) receiver_pts_request = receivers_spIndex.intersects(rect) distance_min = [] for receiver_pts_id in receiver_pts_request: receiver_pts_feat = receivers_feat_all_dict[receiver_pts_id] result = sources_feat.geometry().closestSegmentWithContext(receiver_pts_feat.geometry().asPoint()) distance_min_tmp = sqrt(result[0]) if distance_min_tmp <= research_ray: distance_min.append(distance_min_tmp) # defines segment max length if len(distance_min) >= 1: segment_max = min(distance_min) / 2 if segment_max < 2: segment_max = 2 else: continue # splits the sources line in emission points at a fix distance (minimum distance/2) and create the emission point layer # gets vertex sources_geom = sources_feat.geometry() if sources_geom.isMultipart(): sources_geom.convertToSingleType() sources_feat_vertex_pt_all = sources_geom.asPolyline() emission_pt_id_road = 0 for i in range(0, len(sources_feat_vertex_pt_all)): pt1 = QgsPointXY(sources_feat_vertex_pt_all[i]) add_point_to_layer(emission_pts_writer, pt1, [emission_pt_id, emission_pt_id_road, sources_feat.id(), segment_max]) emission_pt_id = emission_pt_id + 1 emission_pt_id_road = emission_pt_id_road + 1 if i < len(sources_feat_vertex_pt_all) - 1: pt2 = QgsPoint(sources_feat_vertex_pt_all[i + 1]) x1 = pt1.x() y1 = pt1.y() x2 = pt2.x() y2 = pt2.y() if y2 == y1: dx = segment_max dy = 0 m = 0 elif x2 == x1: dx = 0 dy = segment_max else: m = (y2 - y1) / (x2 - x1) dx = sqrt((segment_max ** 2) / (1 + m ** 2)) dy = sqrt(((segment_max ** 2) * (m ** 2)) / (1 + m ** 2)) pt = pt1 while compute_distance(pt, pt2) > segment_max: x_temp = pt.x() y_temp = pt.y() if x_temp < x2: if m > 0: pt = QgsPointXY(x_temp + dx, y_temp + dy) elif m < 0: pt = QgsPointXY(x_temp + dx, y_temp - dy) elif m == 0: pt = QgsPointXY(x_temp + dx, y_temp) elif x_temp > x2: if m > 0: pt = QgsPointXY(x_temp - dx, y_temp - dy) elif m < 0: pt = QgsPointXY(x_temp - dx, y_temp + dy) elif m == 0: pt = QgsPointXY(x_temp - dx, y_temp) elif x_temp == x2: if y2 > y_temp: pt = QgsPointXY(x_temp, y_temp + dy) else: pt = QgsPointXY(x_temp, y_temp - dy) add_point_to_layer(emission_pts_writer, pt, [emission_pt_id, emission_pt_id_road, sources_feat.id(), segment_max]) emission_pt_id = emission_pt_id + 1 emission_pt_id_road = emission_pt_id_road + 1 del emission_pts_writer