MTES-MCT / metadata-postgresql

Plume : gestion des métadonnées du patrimoine PostgreSQL
https://mtes-mct.github.io/metadata-postgresql/
GNU Affero General Public License v3.0
1 stars 1 forks source link

Boutons de saisie des géométries #8

Closed alhyss closed 2 years ago

alhyss commented 2 years ago

Les catégories communes Rectangle d'emprise (dcat:bbox), Centroïde (dcat:centroid) et Géométrie (locn:geometry) prennent en valeurs des géométries au format WKT (gsp:wktLiteral) ou GML (gsp:gmlLiteral), soit pas le plus sympathique à saisir à la main...

L'idée serait de s'appuyer sur les fonctionnalités de QGIS pour simplifier la saisie, au moins pour les couches chargées dans QGIS (tout ou partie des options ne seraient pas disponibles pour une ressource sélectionnée dans l'explorateur). Ainsi, on pourrait imaginer un widget annexe QToolButton, placé comme d'habitude à droite du QLineEdit ou QTextEdit et présentant un menu permettant à l'utilisateur de choisir entre plusieurs modes de saisie. Pour les rectangles d'emprises et centroïdes, il pourrait s'agir de Calculer à partir des données et Tracer manuellement. La catégorie Géométrie supposerait plutôt un tracé manuel ou la copie d'un objet existant.

Côté dictionnaire de widgets, cela se manifesterait par de nouvelles clés internes. Clés de stockage : 'geo widget', 'geo menu' et 'geo actions'. Clé de paramétrage : 'geo widget type' (dont la non nullité supposera la création d'un widget d'aide à la saisie des géométries, avec des fonctionnalités associées qui dépendront de la valeur de la clé). La géométrie résultant de l'opération est ensuite simplement rentrée par Plume dans le QLineEdit ou QTextEdit de la catégorie, et le traitement est ensuite exactement le même que pour toutes les autres catégories de métadonnées.

alhyss commented 2 years ago

Je mets en version cible la v1.0 (diffusion générale), considérant qu'il y a plus urgent pour la diffusion au GT, mais de mon côté il n'y a aucune difficulté à implémenter ça. C'est encore plus simple que les widgets de sélection de l'unité. Beaucoup plus de boulot de ton côté par contre, même si on priorise les fonctionnalités en implémentant d'abord les fonctionnalités de calcul et en gardant pour plus tard la saisie manuelle.

Une question importante à court terme pour moi, par contre : quel type choisit-on ? GML ou WKT ? J'étais partie sur du WKT pour l'instant, parce que la syntaxe est plus simple, donc plus appropriée pour la saisie manuelle, mais ce n'est plus aussi important si on propose de l'aide à la saisie. Un critère de choix pourrait être la manière dont QGIS gère le référentiel dans ses WKT. Normalement il est écrit explicitement sous forme d'URI (ex: "<http://www.opengis.net/def/crs/EPSG/0/4326> Point(33.95 -83.38)"^^gsp:wktLiteral) et, à défaut, la géométrie sera présumée être en WGS 84 longitude-latitude (http://www.opengis.net/def/crs/EPSG/0/4979). Il faudrait être sûr que QGIS ne produit pas de WKT sans référentiel explicite avec des coordonnées qui ne seraient pas en WSG 84 longitude-latitude.

alhyss commented 2 years ago

Pour reprendre ton code @WREATCHED :


>>> l = iface.activeLayer()
>>> l.extent()
<QgsRectangle: 646417.32899998605716974 6857521.13564287032932043, 657175.32729998603463173 6867076.03600385971367359>
>>> l.extent().asWktPolygon()
'POLYGON((646417.32899998605716974 6857521.13564287032932043, 657175.32729998603463173 6857521.13564287032932043, 657175.32729998603463173 6867076.03600385971367359, 646417.32899998605716974 6867076.03600385971367359, 646417.32899998605716974 6857521.13564287032932043))'
>>> l.extent().center().asWkt()
'POINT(651796.32814998598769307 6862298.58582336455583572)'

Ma couche porte sur le département de Paris, je suis en Lambert 93, les coordonnées des WKT sont en Lambert 93.

Donc... QgsRectangle.asWktPolygon produit bel et bien des WKT dans le référentiel de coordonnées courant sans l'expliciter dans le WKT. Idem pour QgsPointXY.asWkt. Si ça se confirme, une option serait de récupérer le référentiel et de l'ajouter manuellement au début du WKT. Il y aurait aussi la possibilité de tout convertir en WGS 84 longitude-latitude, mais expliciter le référentiel est peut-être plus simple et plus clair ?

WREATCHED commented 2 years ago

Oui oui, je suis dessus et j'en étais là,

WREATCHED commented 2 years ago

Tu veux ça ou plus l.crs().authid() 'EPSG:2154' l.crs().description() 'RGF93 v1 / Lambert-93'

WREATCHED commented 2 years ago

projCible = 3857 crsCible = QgsCoordinateReferenceSystem() crsCible.createFromSrid(projCible) print(crsCible) crsSource = iface.activeLayer().crs() print(crsSource) transform = QgsCoordinateTransform(crsSource, crsCible, QgsProject.instance()) oldExtent = iface.activeLayer().extent() print(oldExtent) newLayerExtent = transform.transform(iface.activeLayer().extent()) print(newLayerExtent)

Resultat <QgsRectangle: 622659.65276944148354232 6668308.24049644824117422, 627937.73162113758735359 6671308.24049644824117422> <QgsRectangle: 220420.11937167539144866 5960135.80711037013679743, 228114.72040029137860984 5964651.33234825171530247>

Et on mets si tu veux le authid() ou le description() avant

Qu'en penses-tu ?

alhyss commented 2 years ago

Ok ! Merci pour tout ça !

Le plus simple sera d'intégrer la valeur de QgsCoordinateReferenceSystem.authid au WKT. J'ai écrit une fonction qui fait ça, plume.rdf.utils.wkt_with_srid. Elle prend en argument :

>>> from plume.rdf.utils import wkt_with_srid
>>> wkt_with_srid('POINT(651796.32814998598769307 6862298.58582336455583572)',  'EPSG:2154')
'<http://www.opengis.net/def/crs/EPSG/0/2154> POINT(651796.32814998598769307 6862298.58582336455583572)'

NB : Pour le moment, ça ne reconnaît pas tout à fait tous les référentiels pris en charge par QGIS, seulement les registres OGC, EPSG et IGN (pas ESRI, notamment, parce que je ne suis pas allée à la recherche de son espace de nommage).

J'ai l'impression que QGIS n'arrive pas à lire les WKT avec référentiel de coordonnées, donc si un jour on voulait permettre de visualiser les géométries saisies dans les métadonnées j'écrirais la fonction inverse qui déduit le référentiel au format 'Autorité:Code' (que reconnaît QGIS, me semble-t-il) et le WKT sans référentiel à partir d'un WKT avec référentiel.

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/153218330-353ce38d-37b4-4805-9841-de089db33318.mp4

Réponse graphique et interaction avec le mapCanvas de Qgis

alhyss commented 2 years ago

C'est super @WREATCHED ! Il faudra juste que tu passes le WKT et la valeur renvoyée par QgsCoordinateReferenceSystem.authid dans ma fonction plume.rdf.utils.wkt_with_srid avant d'écrire dans le widget, mais sinon c'est vraiment parfait. Je dois vraiment me dépêcher de t'ajouter ce bouton d'aide à la saisie des géométries !

alhyss commented 2 years ago

Ok, je crois que c'est bon de mon côté !

J'ai expliqué dans la doc comment créer les boutons d'aide à la saisie des géométries et surtout comment gérer leurs actions (dans un nouveau fichier juste pour ça). Sans rentrer trop dans le détail ici, tu récupéreras dans la clé 'geo tools' une liste de mots-clés correspondant aux fonctionnalités à proposer dans le bouton. Comme d'habitude, c'est quelque chose que l'administrateur pourra définir via ses modèles de formulaires, et je m'assure évidemment qu'il n'y ait en sortie que des termes valides. On pourra ajouter des mots-clés ultérieurement si on a d'autres idées de fonctionnalités intéressantes - et selon les retours du sous-groupe métadonnées.

Deux choses que j'ai changées depuis que nous en avons parlé :

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/156880274-db9faa57-da17-48c3-8da7-a7115b6be3f4.mp4

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/156935096-b6de96ec-6907-4312-a053-f58a342a0c8b.mp4

alhyss commented 2 years ago

Bravo pour tout ce boulot !

Tu en avais peut-être déjà l'intention, mais je pense que ça vaudra le coup d'expliquer dans les infobulles des actions de tracé manuel comment elles fonctionnent, spécialement pour le polygone. C'est assez intuitif, le principe des clics gauche simples pour les sommets puis un double-clic gauche pour fermer, mais si on peut éviter aux utilisateurs d'avoir à deviner... (d'ailleurs je ne ne suis pas sûre d'avoir bien vu, est-ce que le double-clic ajoute un dernier sommet ou pas ?)

Une interrogation : est-ce que le polygone ne devrait pas disparaître automatiquement après le double-clic, comme les points et les rectangles ?

alhyss commented 2 years ago

J'ai mis à jour la documentation technique (modalités de création de bouton d'aide à la saisie de géométries et description des actions), pour que ce soit davantage en phase avec ce que tu as implémenté.

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/157879357-b2f6e87b-ffbd-47e2-9f4b-efa593b5cc8f.mp4

alhyss commented 2 years ago

Salut @WREATCHED, Je me répète beaucoup, mais c'est vraiment parfait. Beaucoup mieux que ce que j'imaginais au départ. Très bonne idée de gérer le zoom sur la géométrie comme un paramètre ! Et je ne vois aucune raison de ne pas diffuser cette version maintenant qu'elle est aussi aboutie, au contraire.

Une question, parce que tu ne l'as pas montré dans tes vidéos : quand les coordonnées mémorisées dans les métadonnées n'utilisent pas le même référentiel que le canevas, tu as pu gérer la conversion ? Et, réciproquement, pour les fonctionnalités de dessin manuel, c'est bien le référentiel du canevas (et pas celui de la couche) qui est déclaré dans les WKT ?

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/158215750-ea8b6e4b-abcb-437e-bbac-49567634baed.mp4

Toujours du plus

alhyss commented 2 years ago

Je m'étais demandé jusqu'où il fallait aller dans la prise en charge des multiples types de géométries permis par les WKT et j'étais plutôt partie du principe de ne pas aller trop loin tant que le besoin ne se ferait pas ressentir. Est-ce que tu vois des cas où le cercle serait approprié pour représenter l'emprise spatiale d'un jeu de données ?

Après, ce n'est vraiment pas compliqué de mon côté d'autoriser un type supplémentaire. Si c'est plus simple, je peux juste l'implémenter et on verra bien si on a des retours dessus.

WREATCHED commented 2 years ago

Je pensais à qq chose comme une servitude d'un monument historique ou installation classée ou pour déterminer une zone de pollution dans le cadre d'une gestion de crise ...... Maintenant, c'est un plus mais je n'impose rien, mais c'était un exercice pour moi à implémenter. De toute façon, je conserve le code est :

alhyss commented 2 years ago

Quelle est la forme des WKT avec des cercles ? J'ai l'impression qu'il faut passer par du CIRCULARSTRING :

Si c'est ça, alors ma fonction plume.utils.geomtype_from_wkt te renverra 'circularstring' dans ce cas ?

Il y a bien une méthode QgsCircle.toString, mais la fonction ST_GeomFromText de PostGIS n'arrive pas à interpréter les chaînes qu'elle renvoie. Ça ressemble à ça : 'Circle (Center: Point (0.5 0.5), Radius: 0.70, Azimuth: 45)'.

alhyss commented 2 years ago

C'est implémenté avec :

Évidemment, je changerai si le type de géométrie n'est pas le bon.

WREATCHED commented 2 years ago

Ok Merci Je ne t'ai pas répondu car je suis sur la conversion e j'ai enfin le srid du QGSmapCanvas Et je t'appellerai pour le cercle, car c'est plus simple, mais on en parlera au tél si tu veux

WREATCHED commented 2 years ago

image

OUF !! Rien de grave

alhyss commented 2 years ago

Cool ! C'est rassurant, les polygones qui restent des polygones :)

WREATCHED commented 2 years ago

Avec la tronche de travers, bien sûr !!!

WREATCHED commented 2 years ago

Pour être certain de ce que l'on souhaite, Visualisation : reprojection

Saisie : reprojection

OK ou KO ?

alhyss commented 2 years ago

À la saisie il faut que le référentiel déclaré dans les métadonnées (= fourni en argument à plume.rdf.utils.wkt_with_srid) soit celui qui a effectivement été utilisé. À proprement parler il n'y a pas de reprojection, juste la récupération du bon référentiel :

Action Référentiel
calcul côté PostgreSQL celui qui est renvoyé par plume.pg.queries.query_get_geom_srid
calcul côté QGIS celui de la couche
saisie manuelle celui du canevas

Et pour la visualisation, oui, l'idée est de projeter dans le référentiel du canevas, sachant que le référentiel source est fourni par plume.rdf.utils.split_rdf_wkt.

Bref, ça correspond à ce que tu as écrit, sauf pour les calculs côté PostgreSQL.

WREATCHED commented 2 years ago

Bjr Leslie, Je confirme tout ce que tu as écrit, c'est ce que j'ai implémenté, mais mal traduit par écrit, sniff !!!!

Visualisation : reprojection

Saisie :

alhyss commented 2 years ago

Alors tout va bien :)

WREATCHED commented 2 years ago

Be, il y a du taf, Item supplémentaire pour "Cercle", OK Pb zoom sur le point, normal, pas le même objet, donc je suis dessus, Après je regarde deux autres sujets vu hier Polygon pour polygone au lieu de linestring circularstring à gérer. etc...

alhyss commented 2 years ago

Peut-être une mauvaise idée, mais est-ce que tu ne pourrais pas juste gérer le zoom à partir du rectangle d'emprise de la géométrie, quelle que soit sa nature ? Toutes les classes de géométries de QGIS ont au moins une méthode calculateBoundingBox, non ?

WREATCHED commented 2 years ago

J'étais absent une heure, Non, mais pas de souci, je vais comme d'hab trouver et faire. Je te tiens informé

WREATCHED commented 2 years ago

Ouf

** -----

** Circle

** -----

def showCircle(self, rubberBand, x, y) : rubberBand.reset() r = sqrt(x.sqrDist(y)) feature = QgsFeature() feature.setAttributes([x, y, r]) feature.setGeometry(QgsCircle(QgsPoint(x.x(), y.y()), r).toCircularString()) self.rubberBand.addGeometry(feature.geometry(),None) self.rubberBand.show() return

-----

def circle(self): pointsCircle = [ QgsPointXY(self.rubberBand.getPoint(0, i)) for i in range(self.rubberBand.numberOfVertices()) ] geomCircleString = QgsCircularString() equi = len(pointsCircle)/4 p1, p2, p3, p4, p5 = pointsCircle[int(equi0)], pointsCircle[int(equi1)], pointsCircle[int(equi2)], pointsCircle[int(equi3)], pointsCircle[int(equi*4) - 1] geomCircleString.setPoints([QgsPoint(p1), QgsPoint(p2), QgsPoint(p3), QgsPoint(p4), QgsPoint(p5), QgsPoint(p1)]) return geomCircleString.asWkt()

-----

def showCircle(self, pointsCircle):

Instanciation d'une géom QgsCircularString

  geomCircle = QgsCircularString()
  geomCircle.fromWkt(pointsCircle)
  #Lecture de chaque points pour la transformation
  points = geomCircle.points() 
  points = [ transformSourceCibleLayer(self.srid, self.mAuthid, QgsPointXY(p)) for p in points ]
  #Instanciation d'une géom QgsCircularString pour alimenter la géométrie du rubberBand
  feature = QgsFeature()
  geomCircleString = QgsCircularString()
  geomCircleString.setPoints(QgsPoint(p) for p in points)
  feature.setGeometry(geomCircleString)
  self.rubberBand.addGeometry(feature.geometry(),None)
  return 

Te fais une vidéo pour conclure, mais c'est plutôt cool maintenant que c'est implémenté

WREATCHED commented 2 years ago

https://user-images.githubusercontent.com/66324136/159021340-e883bb81-dea9-4dcd-bd4e-d532853b0930.mp4

Je pense que ça va faire un bon produit à montrer version=0.2.10

alhyss commented 2 years ago

Salut @WREATCHED,

J'ai fait quelques essais avec les géométries, et c'est vraiment chouette :)

Quelques petites remarques tout de même, pour rester fidèle à ma réputation :

2022-03-22T08:52:27     WARNING    Traceback (most recent call last):
              File "C:/Users/alhyss/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\plume\bibli_gene_objets.py", line 564, in 
              _mObjetQToolButton.clicked.connect(lambda : action_mObjetQToolButtonGeoToolsShow(self, _mObjetQToolButton, _keyObjet, _valueObjet))
              File "C:/Users/alhyss/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\plume\bibli_gene_objets.py", line 651, in action_mObjetQToolButtonGeoToolsShow
              self.dic_objetMap[__keyObjet] = GeometryMapToolShow(self, __keyObjet, mCanvas, mAuthid, mCoordSaisie, self.dic_geoToolsShow[__keyObjet])
              File "C:/Users/alhyss/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\plume\bibli_plume_tools_map.py", line 339, in __init__
              self.canvas.setExtent(QgsGeometry.fromWkt(self.rubberBand.center().asWkt()).boundingBox())
             AttributeError: 'QgsVertexMarker' object has no attribute 'center'
WREATCHED commented 2 years ago

Hello,

Je te transfère sur le git cette version

alhyss commented 2 years ago

Je clos l'issue. À ce stade, je pense qu'on peut dire que la fonctionnalité est implémentée, et pas qu'à moitié ! S'il y a des bugs ou de petites améliorations à apporter, on pourra les traiter dans de nouvelles issues.