validator.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. '''
  2. @author: olivier.massot, 2018
  3. '''
  4. from osgeo import ogr
  5. import pypyodbc
  6. from core import mn
  7. from core.gis_ import Feature
  8. from core.validation import NetgeoValidator, RelationError, UniqueError, \
  9. InvalidGeometry, DuplicatedGeom, MissingItem, DimensionError, \
  10. TechnicalValidationError, PositionError
  11. from schemas.common import TOLERANCE
  12. from schemas.netgeo_2_2_doe.models import Artere, Cable, Equipement, Noeud, \
  13. Tranchee, Zapbo
  14. class Netgeo22DoeValidator(NetgeoValidator):
  15. schema_name = "Netgeo v2.2 DOE"
  16. models = [Artere, Cable, Equipement, Noeud, Tranchee, Zapbo]
  17. def _technical_validation(self):
  18. # construction des index
  19. arteres = self.dataset[Artere]
  20. cables = self.dataset[Cable]
  21. tranchees = self.dataset[Tranchee]
  22. noeuds = {}
  23. for noeud in self.dataset[Noeud]:
  24. if not noeud.NO_NOM in noeuds:
  25. noeuds[noeud.NO_NOM] = noeud
  26. else:
  27. self.log_error(UniqueError("Doublons dans le champs: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
  28. equipements = {}
  29. for equipement in self.dataset[Equipement]:
  30. if not equipement.EQ_NOM in equipements:
  31. equipements[equipement.EQ_NOM] = equipement
  32. else:
  33. self.log_error(UniqueError("Doublons dans le champs: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
  34. zapbos = {}
  35. for zapbo in self.dataset[Zapbo]:
  36. if not zapbo.ID_ZAPBO in zapbos:
  37. zapbos[zapbo.ID_ZAPBO] = zapbo
  38. else:
  39. self.log_error(UniqueError("Doublons dans le champs: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
  40. # contrôle de la validité des géométries
  41. for artere in arteres:
  42. if not artere.valid:
  43. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(artere), filename=Artere.filename, field="geom"))
  44. for tranchee in tranchees:
  45. if not tranchee.valid:
  46. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(tranchee), filename=Tranchee.filename, field="geom"))
  47. for cable in cables:
  48. if not "baguette" in cable.CA_COMMENT.lower() and not cable.valid:
  49. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(cable), filename=Cable.filename, field="geom"))
  50. for noeud in noeuds.values():
  51. if not noeud.valid:
  52. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(noeud), filename=Noeud.filename, field="geom"))
  53. for equipement in equipements.values():
  54. if not equipement.valid:
  55. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(equipement), filename=Equipement.filename, field="geom"))
  56. for zapbo in zapbos.values():
  57. if not zapbo.valid:
  58. self.log_error(InvalidGeometry("Géométrie invalide: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
  59. # rattachement les noeuds aux artères
  60. for artere in arteres:
  61. try:
  62. artere.noeud_a = noeuds[artere.AR_NOEUD_A]
  63. except KeyError:
  64. artere.noeud_a = None
  65. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(artere.AR_NOEUD_A), filename=Artere.filename, field="AR_NOEUD_A"))
  66. try:
  67. artere.noeud_b = noeuds[artere.AR_NOEUD_B]
  68. except KeyError:
  69. artere.noeud_b = None
  70. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(artere.AR_NOEUD_B), filename=Artere.filename, field="AR_NOEUD_A"))
  71. # rattachement des equipements aux cables
  72. for cable in cables:
  73. try:
  74. cable.equipement_a = equipements[cable.CA_EQ_A]
  75. except KeyError:
  76. cable.equipement_a = None
  77. self.log_error(RelationError("L'équipement '{}' n'existe pas".format(cable.CA_EQ_A), filename=Cable.filename, field="CA_EQ_A"))
  78. try:
  79. cable.equipement_b = equipements[cable.CA_EQ_B]
  80. except KeyError:
  81. cable.equipement_b = None
  82. self.log_error(RelationError("L'équipement '{}' n'existe pas".format(cable.CA_EQ_B), filename=Cable.filename, field="CA_EQ_B"))
  83. # rattachement des equipements aux noeuds
  84. for equipement in equipements.values():
  85. try:
  86. equipement.noeud = noeuds[equipement.EQ_NOM_NOE]
  87. except KeyError:
  88. equipement.noeud = None
  89. self.log_error(RelationError("Le noeud '{}' n'existe pas".format(equipement.EQ_NOM_NOE, equipement.EQ_NOM), filename=Equipement.filename, field="EQ_NOM_NOE"))
  90. # verifie que tous les equipements sont l'equipement B d'au moins un cable
  91. equipements_b = [cable.CA_EQ_B for cable in cables]
  92. for eq_id in equipements:
  93. if equipements[eq_id].EQ_TYPE == "BAI":
  94. continue
  95. if not eq_id in equipements_b:
  96. self.log_error(RelationError("L'equipement '{}' n'est l'équipement B d'aucun cable".format(eq_id), filename=Equipement.filename, field="EQ_NOM"))
  97. # controle des doublons graphiques
  98. for i, tranchee in enumerate(tranchees):
  99. for other in tranchees[i+1:]:
  100. if tranchee.geom == other.geom:
  101. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée".format(tranchee), filename=Tranchee.filename, field="geom"))
  102. for i, artere in enumerate(arteres):
  103. for other in arteres[i+1:]:
  104. if artere.geom == other.geom:
  105. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(artere), filename=Artere.filename, field="geom"))
  106. for i, cable in enumerate(cables):
  107. for other in cables[i+1:]:
  108. if cable.geom == other.geom and cable.CA_EQ_A == other.CA_EQ_A and cable.CA_EQ_B == other.CA_EQ_B:
  109. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(cable), filename=Cable.filename, field="geom"))
  110. ls_noeuds = list(noeuds.values())
  111. for i, noeud in enumerate(ls_noeuds):
  112. for other in ls_noeuds[i+1:]:
  113. if noeud.geom == other.geom:
  114. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(noeud), filename=Noeud.filename, field="geom"))
  115. del ls_noeuds
  116. ls_zapbos = list(zapbos.values())
  117. for i, zapbo in enumerate(ls_zapbos):
  118. for other in ls_zapbos[i+1:]:
  119. if zapbo.geom == other.geom:
  120. self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(zapbo), filename=Zapbo.filename, field="geom"))
  121. del ls_zapbos
  122. # Arteres: comparer la géométrie à celle des noeuds
  123. for artere in arteres:
  124. if not artere.noeud_a or not artere.noeud_b:
  125. continue
  126. buffer_a, buffer_b = artere.points[0].Buffer(TOLERANCE), artere.points[-1].Buffer(TOLERANCE)
  127. if not (buffer_a.Contains(artere.noeud_a.points[0]) and buffer_b.Contains(artere.noeud_b.points[0])) \
  128. and not (buffer_a.Contains(artere.noeud_b.points[0]) and buffer_b.Contains(artere.noeud_a.points[0])):
  129. self.log_error(MissingItem("Pas de noeud aux coordonnées attendues ('{}')".format(artere), filename=Artere.filename, field="geom"))
  130. # Cables: comparer la géométrie à celle des equipements (on utilise en fait la geom du noeud correspondant à l'équipement)
  131. for cable in cables:
  132. if not cable.equipement_a or not cable.equipement_b or not cable.valid or not cable.equipement_a.noeud or not cable.equipement_b.noeud:
  133. continue
  134. buffer_a, buffer_b = cable.points[0].Buffer(TOLERANCE), cable.points[-1].Buffer(TOLERANCE)
  135. if not (buffer_a.Contains(cable.equipement_a.noeud.points[0]) and buffer_b.Contains(cable.equipement_b.noeud.points[0])) \
  136. and not (buffer_a.Contains(cable.equipement_b.noeud.points[0]) and buffer_b.Contains(cable.equipement_a.noeud.points[0])):
  137. self.log_error(MissingItem("Pas d'equipement aux coordonnées attendues ('{}')".format(cable), filename=Cable.filename, field="geom"))
  138. del buffer_a, buffer_b
  139. # Verifie que chaque tranchée a au moins une artère
  140. arteres_emprise = Feature.buffered_union(arteres, TOLERANCE)
  141. for tranchee in tranchees:
  142. if not arteres_emprise.Contains(tranchee.geom):
  143. self.log_error(MissingItem("Tranchée sans artère ('{}')".format(tranchee), filename=Tranchee.filename, field="-"))
  144. # Verifie que chaque cable a au moins une artère (sauf si commentaire contient 'baguette')
  145. for cable in cables:
  146. if "baguette" in cable.CA_COMMENT.lower() or not cable.valid:
  147. continue
  148. if not arteres_emprise.Contains(cable.geom):
  149. self.log_error(MissingItem("Cable sans artère ('{}')".format(cable), filename=Cable.filename, field="-"))
  150. del arteres_emprise
  151. # Verifie que chaque artère a au moins un cable (sauf si commentaire contient un de ces mots 'racco client adductio attente bus 'sans cable'')
  152. cables_emprise = Feature.buffered_union(cables, TOLERANCE)
  153. for artere in arteres:
  154. if any(x in artere.AR_COMMENT.lower() for x in ['racco','client','adductio','attente','bus','sans cable']):
  155. continue
  156. if not cables_emprise.Contains(artere.geom):
  157. self.log_error(MissingItem("Artère sans cable ('{}')".format(artere), filename=Artere.filename, field="-"))
  158. del cables_emprise
  159. # Contrôle des dimensions logiques
  160. for artere in arteres:
  161. try:
  162. if not int(artere.AR_FOU_DIS) <= int(artere.AR_NB_FOUR):
  163. self.log_error(DimensionError("Le nombre de fourreaux disponibles doit être inférieur au nombre total ('{}')".format(artere), filename=Artere.filename, field="AR_FOU_DIS"))
  164. except (TypeError, ValueError):
  165. pass
  166. for cable in cables:
  167. try:
  168. if not int(cable.CA_NB_FO_U) <= int(cable.CA_NB_FO):
  169. self.log_error(DimensionError("Le nombre de fourreaux utilisés doit être inférieur au nombre total ('{}')".format(cable), filename=Cable.filename, field="CA_NB_FO_U"))
  170. if not int(cable.CA_NB_FO_D) <= int(cable.CA_NB_FO):
  171. self.log_error(DimensionError("Le nombre de fourreaux disponibles doit être inférieur au nombre total ('{}')".format(cable), filename=Cable.filename, field="CA_NB_FO_D"))
  172. except (TypeError, ValueError):
  173. pass
  174. ant_db = mn.ANTDb_0()
  175. ant_db.execute("alter session set NLS_NUMERIC_CHARACTERS = '.,';") # definit le separateur decimal sur '.'
  176. # Toutes les zapbo contiennent au moins une prise
  177. for zapbo in zapbos.values():
  178. if len(zapbo.points) >= 499:
  179. # passe l'erreur provoquée par la limite au nombre d'arguments en SQL
  180. zapbo.nb_prises = None
  181. continue
  182. sql = """Select SUM(NB_PRISE) AS NB_PRISES FROM SIG_ANT.FTTH_MN_PRISE_LOT z
  183. WHERE SDO_INSIDE(z.GEOMETRY,
  184. SDO_GEOMETRY(2003, 3949, SDO_POINT_TYPE(null,null,null), SDO_ELEM_INFO_ARRAY(1,1003,1), SDO_ORDINATE_ARRAY({}))
  185. )='TRUE';""".format(", ".join(["{},{}".format(p.GetX(), p.GetY()) for p in zapbo.points]))
  186. zapbo.nb_prises = int(ant_db.first(sql).NB_PRISES)
  187. if not zapbo.nb_prises:
  188. self.log_error(MissingItem("La Zapbo ne contient aucune prise: {}".format(zapbo), filename=Zapbo.filename, field="-"))
  189. # Toutes les prises de la ou les ZAPM impactées sont dans une zapbo
  190. zapms = {}
  191. # > on déduit la liste des zapms à partir de la position des zapbos
  192. for zapbo in zapbos.values():
  193. centre = zapbo.geom.Centroid()
  194. zapm = ant_db.first("""SELECT z.ID_ZAPM
  195. FROM SIG_ANT.FTTH_MN_ZAPM z
  196. WHERE sdo_contains(z.GEOMETRY,
  197. SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL)) = 'TRUE'
  198. """.format(centre.GetX(), centre.GetY()))
  199. try:
  200. zapms[zapm.ID_ZAPM].append(zapbo)
  201. except KeyError:
  202. zapms[zapm.ID_ZAPM] = [zapbo]
  203. for id_zapm in zapms:
  204. zapm_couverture = Feature.union(zapms[id_zapm])
  205. for prise in ant_db.read("""SELECT t.X AS x, t.Y AS y
  206. FROM SIG_ANT.FTTH_MN_PRISE_LOT z,
  207. TABLE(SDO_UTIL.GETVERTICES(z.GEOMETRY)) t
  208. WHERE T_ETAT<>'OBSOLETE' AND ID_ZAPM_PARTIELLE='{}';""".format(id_zapm)):
  209. point = ogr.Geometry(ogr.wkbPoint)
  210. point.AddPoint(prise.x, prise.y)
  211. if not zapm_couverture.Contains(point):
  212. self.log_error(MissingItem("Certaines prises de la ZAPM ne sont pas comprises dans une ZAPBO: {}".format(id_zapm), filename=Zapbo.filename, field="-"))
  213. # Verifier que chaque equipement de type PBO est contenu dans une zapbo, et que le nom de la zapbo contient le nom de l'equipement
  214. for equipement in equipements.values():
  215. if not equipement.EQ_TYPE == "PBO":
  216. continue
  217. #zapbos englobant l'equipement
  218. candidates = []
  219. for zapbo in zapbos.values():
  220. if zapbo.geom.Contains(equipement.geom):
  221. candidates.append(zapbo)
  222. # le pbo doit être contenu dans une zapbo
  223. if not candidates:
  224. self.log_error(MissingItem("Le PBO n'est contenu dans aucune ZAPBO: {}".format(equipement), filename=Equipement.filename, field="geom"))
  225. continue
  226. # On se base sur le nom pour trouver la zapbo correspondante
  227. try:
  228. equipement.zapbo = next((z for z in candidates if equipement.EQ_NOM in z.ID_ZAPBO))
  229. except StopIteration:
  230. self.log_error(MissingItem("Le nom du PBO ne coincide avec le nom d'aucune des ZAPBO qui le contient: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
  231. break
  232. # Controle du dimensionnement des PBO
  233. if equipement.zapbo.nb_prises is not None:
  234. if equipement.EQ_TYPE_PH == 'PBO 6' and not equipement.zapbo.nb_prises < 6:
  235. self.log_error(DimensionError("Le PBO 6 contient plus de 5 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
  236. if equipement.EQ_TYPE_PH == 'PBO 12' and not equipement.zapbo.nb_prises >= 6 and equipement.zapbo.nb_prises <= 8:
  237. self.log_error(DimensionError("Le PBO 12 contient mois de 6 prises ou plus de 8 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
  238. if equipement.zapbo.STATUT == "REC" and not equipement.EQ_STATUT == "REC":
  239. self.log_error(TechnicalValidationError("Le statut du PBO n'est pas cohérent avec le statut de sa ZAPBO: {}".format(equipement), filename=Equipement.filename, field="-"))
  240. if equipement.EQ_STATUT == "REC" and not equipement.zapbo.STATUT == "REC" and not equipement.zapbo.ID_ZAPBO[:4].lower() == "att_":
  241. self.log_error(TechnicalValidationError("Le statut du PBO n'est pas cohérent avec le statut de sa ZAPBO: {}".format(equipement), filename=Equipement.filename, field="-"))
  242. # Contrôler dans la base si des éléments portant ces codes existent à des emplacements différents
  243. for noeud in noeuds.values():
  244. sql = """SELECT z.NO_NOM, SDO_GEOM.SDO_DISTANCE(z.GEOMETRY, SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005) AS DIST
  245. FROM SIG_ANT.FTTH_MN_GR_NOEUD_GEO z
  246. WHERE z.NO_NOM='{}';""".format(noeud.geom.GetX(), noeud.geom.GetY(), noeud.NO_NOM)
  247. existing = ant_db.first(sql)
  248. if existing:
  249. if existing.DIST > TOLERANCE and existing.DIST < 20:
  250. self.log_error(PositionError("La position du noeud ne correspond pas à l'existant: {}".format(noeud), filename=Noeud.filename, field="geom"))
  251. elif existing.DIST > 20:
  252. self.log_error(DuplicatedGeom("Un noeud portant ce nom existe déjà ailleurs sur le territoire: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
  253. for zapbo in zapbos.values():
  254. sql = """SELECT z.ID_ZAPBO, SDO_GEOM.SDO_DISTANCE(SDO_GEOM.SDO_CENTROID(z.GEOMETRY,0.005), SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005) AS DIST
  255. FROM SIG_ANT.FTTH_MN_GR_ZAPBO_GEO z
  256. WHERE z.ID_ZAPBO='{}';""".format(zapbo.geom.Centroid().GetX(), zapbo.geom.Centroid().GetY(), zapbo.ID_ZAPBO)
  257. existing = ant_db.first(sql)
  258. if existing:
  259. if existing.DIST > TOLERANCE and existing.DIST < 20:
  260. self.log_error(PositionError("La position de la ZAPBO ne correspond pas à l'existant: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
  261. elif existing.DIST > 20:
  262. self.log_error(DuplicatedGeom("Une ZAPBO portant ce nom existe déjà ailleurs sur le territoire: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
  263. # Contrôle si un equipement portant ce nom existe, mais associé à un noeud différent
  264. for equipement in equipements.values():
  265. sql = """SELECT z.EQ_NOM, z.EQ_NOM_NOEUD
  266. FROM SIG_ANT.FTTH_MN_GR_EQ_PASSIF z
  267. WHERE z.EQ_NOM='{}';""".format(equipement.EQ_NOM)
  268. existing = ant_db.first(sql)
  269. if existing and existing.EQ_NOM_NOEUD != equipement.EQ_NOM_NOE:
  270. self.log_error(DuplicatedGeom("Un équipement portant ce nom ({}) existe déjà et est associé à un noeud différent ({})".format(equipement.NO_NOM, existing.EQ_NOM_NOEUD), filename=Noeud.filename, field="geom"))
  271. if __name__ == "__main__":
  272. from core.constants import MAIN
  273. subject = MAIN / "work" / "STURNO_228CP0_APD_180301_OK"
  274. report = Netgeo22DoeValidator.submit(subject)
  275. print(report)