فهرست منبع

Améliorations diverses

olivier.massot 7 سال پیش
والد
کامیت
4a6e137cc5

+ 4 - 1
core/cerberus_extend.py

@@ -76,7 +76,10 @@ class ExtendedValidator(cerberus.validator.Validator):
         """
         for item in re.split("\s?-\s?", value or ""):
             if not item in allowed:
-                self._error(field, "Valeur non-autorisée: {}".format(item))
+                if item == '':
+                    self._error(field, "Le champs doit être renseigné")
+                else:
+                    self._error(field, "Valeur non-autorisée: {}".format(item))
 
 class GeoValidator(ExtendedValidator):
     

+ 3 - 3
core/db.py

@@ -107,9 +107,9 @@ class AccessSDb(AccessDb):
         super(AccessSDb, self).__init__(dbpath, uid=uid, pwd=pwd, systemdb=mdwpath, **kwargs)
 
 class OracleDb(CustomDb):
-    dsn = "DRIVER={Oracle dans ORA102};"
-    def __init__(self, dbname, user, pwd, **kwargs):
-        super(OracleDb, self).__init__(dbq=dbname, uid=user, pwd=pwd, **kwargs)
+    dsn = "DRIVER={Microsoft ODBC For Oracle};"
+    def __init__(self, dbq, uid, pwd, **kwargs):
+        super(OracleDb, self).__init__(dbq=dbq, uid=uid, pwd=pwd, **kwargs)
 
 class SqlServerDb(CustomDb):
     dsn = "DRIVER={SQL Server};"

+ 10 - 3
core/gis_.py

@@ -23,7 +23,10 @@ GEOM_NAMES = {0: "(AUCUN)",
                6:"MULTI-POLYGONE",
                }
 
-
+def point_(x, y):
+    point = ogr.Geometry(ogr.wkbPoint)
+    point.AddPoint(x, y)
+    return point
 
 class Datasource():
     DRIVER_NAME = "ESRI Shapefile"
@@ -111,7 +114,10 @@ class Feature():
     
     @property
     def points(self):
-        return [p for p in self.geom.GetPoints()]
+        if self.geom_type != GEOM_POLYGON:
+            return [point_(*p) for p in self.geom.GetPoints()]
+        else:
+            return [point_(*p) for p in self.geom.GetGeometryRef(0).GetPoints()]
 
     def almost_equal(self, feature, buffer_dist=1):
         if feature.geom_type == self.geom_type:
@@ -131,7 +137,8 @@ class Feature():
     def buffered_union(cls, features, buffer_dist=1):
         multi  = ogr.Geometry(ogr.wkbMultiPolygon)
         for f in features:
-            multi.AddGeometry(f.geom.Buffer(buffer_dist))
+            geom = f.geom if issubclass(f.__class__, Feature) else f # au cas où ce ne seraient pas des features, mais directement des geometries
+            multi.AddGeometry(geom.Buffer(buffer_dist))
         return multi.UnionCascaded()
     
 if __name__ == "__main__":

+ 17 - 2
core/mn.py

@@ -2,7 +2,16 @@
 
 @author: olivier.massot, 2018
 '''
-from core.db import PostgresDb
+from core.db import PostgresDb, OracleDb
+
+
+class ANTDb_0(OracleDb):
+    dsn = "DRIVER={Oracle dans OraClient11g_home1};"
+    db = "SIGCG50"
+    user = "SIG_REFERENTIEL"
+    pwd = "Zl20ENUweN6"
+    def __init__(self, **kwargs):
+        super(ANTDb_0, self).__init__(dbq=self.db, uid=self.user, pwd=self.pwd, **kwargs)
 
 class ANTDb(PostgresDb):
     server = "clusterpg.linux.infra.cloud.local"
@@ -11,4 +20,10 @@ class ANTDb(PostgresDb):
     user = "sigr"
     pwd = "T38Msh2R4q"
     def __init__(self, **kwargs):
-        super(ANTDb, self).__init__(server=self.server, dbname=self.db, user=self.user, pwd=self.pwd, **kwargs)
+        super(ANTDb, self).__init__(server=self.server, dbname=self.db, user=self.user, pwd=self.pwd, **kwargs)
+
+
+if __name__ == "__main__":
+    ant_db = ANTDb_0()
+    print([row[0] for row in ant_db.read("SELECT CODE_INSEE FROM SIG_REFERENTIEL.ADM_CD50_COM;")])
+#     print([row[0] for row in ant_db.read("SELECT code_insee FROM sig_referentiel.admn_cd50_com;")])

+ 59 - 34
core/validation.py

@@ -9,10 +9,21 @@ import zipfile
 from path import Path, TempDir
 
 from core import gis_
-from core.cerberus_extend import CerberusErrorHandler, GeoValidator, \
+from core.cerberus_extend import CerberusErrorHandler, \
     _translate_messages, ExtendedValidator
 from schemas.common import SRID
 
+        
+class ValidatorInterruption(BaseException): 
+    pass
+
+class Checkpoint():
+    def __init__(self, name, valid=True):
+        self.name = name
+        self.valid = valid
+
+
+###########    MODELES    ################
 
 class BaseModel():
     filename = ""
@@ -30,15 +41,9 @@ class BaseGeoModel(gis_.Feature):
     
     def __init__(self, feature):
         self.__dict__.update(feature.__dict__)
-        
-class ValidatorInterruption(BaseException): 
-    pass
 
-class Checkpoint():
-    def __init__(self, name, valid=True):
-        self.name = name
-        self.valid = valid
 
+###########    ERREURS DE VALIDATION     ################
 
 VALIDATION_ERROR_LEVELS = {10: "MINEURE", 20: "AVERTISSEMENT", 30: "ERREUR", 40: "CRITIQUE"}
 MINOR = 10
@@ -47,6 +52,7 @@ ERROR = 30
 CRITICAL = 40
 
 class BaseValidationError():
+    order_ = 0
     name = "Erreur"
     level = ERROR
     help = ""
@@ -59,56 +65,75 @@ class BaseValidationError():
         return " - ".join(filter(None, [self.name, self.filename, self.field, self.message]))
 
 # Erreurs dans le chargement des fichiers
-
-class MissingFile(BaseValidationError):
+class InputError(BaseValidationError):
+    order_ = 0
     level = CRITICAL
+    name = "Erreur de chargement"
+
+class MissingFile(InputError):
+    order_ = 1
     name = "Fichier Manquant"
     
-class UnreadableFile(BaseValidationError):
-    level = CRITICAL
+class UnreadableFile(InputError):
+    order_ = 2
     name = "Fichier Illisible"
 
-class WrongSrid(BaseValidationError):
-    level = CRITICAL
+class WrongSrid(InputError):
+    order_ = 3
     name = "Mauvais SRID"
 
 ### Erreurs dans la structure des données
-class DataError(BaseValidationError):
-    name = "Erreur de format"
+class StructureError(BaseValidationError):
+    order_ = 10
+    name = "Erreur de structure"
     level = ERROR
-#     level = CRITICAL
-
-class PositionError(BaseValidationError):
-    name = "Position hors de la zone autorisée"
-    level = CRITICAL
-
-class GeomTypeError(BaseValidationError):
+    
+class GeomTypeError(StructureError):
+    order_ = 12
     name = "Type de géométrie invalide"
     level = CRITICAL
-
-class InvalidGeometry(BaseValidationError):
+    
+class PositionError(StructureError):
+    order_ = 11
+    name = "Position hors de la zone autorisée"
+    
+class InvalidGeometry(StructureError):
+    order_ = 13
     name = "Géométrie invalide"
+    
+class DataError(StructureError):
+    order_ = 14
+    name = "Erreur de format"
 
 # Erreurs dans le contenu, erreurs métiers
-
 class TechnicalValidationError(BaseValidationError):
+    order_ = 20
     level = ERROR
+    name = "Erreur technique"
 
-class DuplicatedPk(TechnicalValidationError):
+class UniqueError(TechnicalValidationError):
+    order_ = 21
     name = "Doublons dans le champs"
 
 class RelationError(TechnicalValidationError):
+    order_ = 22
     level = CRITICAL
     name = "Un objet lié n'existe pas"
 
 class DuplicatedGeom(TechnicalValidationError):
+    order_ = 23
     name = "Doublon graphique"
 
 class MissingItem(TechnicalValidationError):
+    order_ = 24
     name = "Elément manquant"
 
 class DimensionError(TechnicalValidationError):
-    name = "Elément manquant"
+    order_ = 25
+    name = "Elément de dimension"
+
+
+###########    VALIDATION    ################
 
 class BaseValidator():
     schema_name = ""
@@ -179,11 +204,11 @@ class BaseValidator():
         self.checkpoint("Contrôle de la structure des données")
         
         # Validation technique
-        try:
-            self._technical_validation()
-            self.checkpoint("Validation Métier")
-        except:
-            self.checkpoint("Validation Métier [interrompu]")
+#         try:
+        self._technical_validation()
+        self.checkpoint("Validation Métier")
+#         except:
+#             self.checkpoint("Validation Métier [interrompu]")
     
     def _load_files(self, folder):
         """ Charge les données du fichier et les associe à un modèle.
@@ -219,7 +244,7 @@ class BaseValidator():
         
         for err in self.errors:
             if not err.name in report["errors"]:
-                report["errors"][err.name] = {"help": err.help, "list": []}
+                report["errors"][err.name] = {"help": err.help, "order_": err.order_, "list": []}
             
             err_report = {"filename": err.filename or "-",
                           "field": err.field or "-",

+ 5 - 0
resources/questions_20181004.txt

@@ -0,0 +1,5 @@
+* dates: rendre optionnelles?
+
+* Format des noms de cables (et autres codes)
+
+* Ajouter une saisie du code de la zapm partielle? NON

+ 2 - 1
schemas/common.py

@@ -11,4 +11,5 @@ SRID = "3949"
 BUFFER = 2
 
 with mn.ANTDb() as ant_db:
-    INSEE_VALIDES = [row[0] for row in ant_db.read("SELECT code_insee FROM sig_referentiel.admn_cd50_com;")]
+    INSEE_VALIDES = [row[0] for row in ant_db.read("SELECT code_insee FROM sig_referentiel.admn_cd50_com;")]
+    

+ 10 - 10
schemas/netgeo_1_12_doe/models.py

@@ -21,12 +21,12 @@ class Artere(BaseGeoModel):
               'AR_NOEUD_B': {'type': 'string', 'empty': False, 'maxlength': 20}, 
               'AR_NB_FOUR': {'empty': False, 'validator': is_multi_int}, 
               'AR_FOU_DIS': {'empty': False, 'validator': is_int}, 
-              'AR_TYPE_FO': {'type': 'string', 'empty': False, 'multiallowed': ['PVC', 'PEHD', 'SOUS-TUBAGE PEHD', 'SOUS-TUBAGE  SOUPLE', 'FACADE', 'AERIEN', 'ENCORBELLEMENT', 'AUTRE']}, 
-              'AR_DIAM_FO': {'type': 'string', 'empty': False, 'multiallowed': ['10', '14', '18', '25', '28', '32', '40', '45', '60', '80', '150', 'NUL']}, 
-              'AR_PRO_FOU': {'type': 'string', 'empty': False, 'multiallowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'PRIVE', 'ERDF', 'AUTRE (à préciser)']}, 
+              'AR_TYPE_FO': {'type': 'string', 'multiallowed': ['PVC', 'PEHD', 'SOUS-TUBAGE PEHD', 'SOUS-TUBAGE  SOUPLE', 'FACADE', 'AERIEN', 'ENCORBELLEMENT', 'AUTRE']}, 
+              'AR_DIAM_FO': {'type': 'string', 'multiallowed': ['10', '14', '18', '25', '28', '32', '40', '45', '60', '80', '150', 'NUL']}, 
+              'AR_PRO_FOU': {'type': 'string', 'multiallowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'PRIVE', 'ERDF', 'AUTRE (à préciser)']}, 
               'AR_PRO_CAB': {'type': 'string', 'empty': False, 'allowed': ['MANCHE NUMERIQUE']}, 
-              'AR_GEST_FO': {'type': 'string', 'empty': False, 'multiallowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'COLLECTIVITE', 'ORANGE', 'MANCHE FIBRE', 'PRIVE', 'ERDF', 'AUTRE (à préciser)', 'NUL']}, 
-              'AR_UTIL_FO': {'type': 'string', 'empty': False, 'multiallowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'MANCHE FIBRE', 'COLLECTIVITE', 'ORANGE', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
+              'AR_GEST_FO': {'type': 'string', 'multiallowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'COLLECTIVITE', 'ORANGE', 'MANCHE FIBRE', 'PRIVE', 'ERDF', 'AUTRE (à préciser)', 'NUL']}, 
+              'AR_UTIL_FO': {'type': 'string', 'multiallowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'COLLECTIVITE', 'ORANGE', 'MANCHE FIBRE', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
               'AR_DATE_IN': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_DATE_RE': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_REF_PLA': {'type': 'string', 'maxlength': 100}, 
@@ -98,13 +98,13 @@ class Noeud(BaseGeoModel):
               'NO_TYPE_LQ': {'type': 'string', 'maxlength': 10, 'empty': False, 'allowed': ['CHTIR', 'CHRACC', 'POT', 'NRO', 'PM', 'MIMO', 'FAC', 'OUV', 'IMM']}, 
               'NO_TYPE_PH': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['CHAMBRE', 'POTEAU', 'ARMOIRE', 'SHELTER', 'BATIMENT', 'SITE MIMO', 'FACADE', 'OUVRAGE', 'IMMEUBLE']}, 
               'NO_CODE_PH': {'type': 'string', 'maxlength': 20}, 
-              'NO_TECH_PS': {'type': 'string', 'maxlength': 20, 'empty': False, 'multiallowed': ['COAX', 'CUT', 'ECL', 'ELEC', 'VP', 'OPT', 'NC']}, 
+              'NO_TECH_PS': {'type': 'string', 'maxlength': 20, 'multiallowed': ['COAX', 'CUT', 'ECL', 'ELEC', 'VP', 'OPT', 'NC']}, 
               'NO_AMO': {'type': 'string', 'maxlength': 20}, 
               'NO_PLINOX': {'required':False, 'type': 'string', 'maxlength': 3, 'allowed': ['OUI', 'NON']}, 
               'NO_X': {'empty': False, 'validator': is_float}, 
               'NO_Y': {'empty': False, 'validator': is_float}, 
-              'NO_PRO': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'ERDF', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
-              'NO_GEST': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'COLLECTIVITE', 'ORANGE', 'ERDF', 'MANCHE FIBRE', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
+              'NO_PRO': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'ERDF', 'PRIVE', 'ENEDIS', 'AUTRE (à préciser)', 'NUL']}, 
+              'NO_GEST': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['MANCHE NUMERIQUE', 'MANCHE TELECOM', 'COLLECTIVITE', 'ORANGE', 'ERDF', 'ENEDIS', 'MANCHE FIBRE', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
               'NO_HAUT': {'empty': False, 'validator': is_float}, 
               'NO_DATE_IN': {'empty': False, 'validator': is_modern_french_date}, 
               'NO_REF_PLA': {'type': 'string', 'maxlength': 100}, 
@@ -127,9 +127,9 @@ class Tranchee(BaseGeoModel):
               'TR_MOD_POS': {'type': 'string', 'empty': False, 'allowed': ['TRADITIONNEL', 'MICRO TRANCHEE', 'FONCAGE 60', 'FONCAGE 90', 'FONCAGE 120', 'TRANCHEUSE', 'FORAGE URBAIN', 'FORAGE RURAL', 'ENCORBELLEMENT']}, 
               'TR_LONG': {'empty': False, 'validator': is_float}, 
               'TR_LARG': {'empty': False, 'validator': is_float}, 
-              'TR_REVET': {'type': 'string', 'empty': False, 'allowed': ['SABLE', 'BICOUCHE', 'ENROBE', 'BETON', 'PAVE', 'TERRAIN NATUREL']}, 
+              'TR_REVET': {'empty':True, 'type': 'string', 'allowed': ['SABLE', 'BICOUCHE', 'ENROBE', 'BETON', 'PAVE', 'TERRAIN NATUREL']}, 
               'TR_CHARGE': {'empty': False, 'validator': is_float}, 
-              'TR_GRILLAG': {'empty': False, 'validator': is_float}, 
+              'TR_GRILLAG': {'empty':True, 'validator': is_float}, 
               'TR_REMBLAI': {'type': 'string'}, 
               'TR_PLYNOX': {'type': 'string', 'empty': False, 'allowed': ['OUI', 'NON']}, 
               'TR_PRO_VOI': {'type': 'string', 'empty': False, 'allowed': ['COMMUNE', 'COMMUNAUTE DE COMMUNES', 'DEPARTEMENT', 'ETAT', 'PRIVE']}, 

+ 116 - 41
schemas/netgeo_1_12_doe/validator.py

@@ -2,10 +2,13 @@
 
 @author: olivier.massot, 2018
 '''
-from core import mn, gis
+from osgeo import ogr
+
+from core import mn, gis_
 from core.gis_ import Feature
-from core.validation import NetgeoValidator, DuplicatedPk, RelationError, \
-    DuplicatedGeom, MissingItem, DimensionError
+from core.validation import NetgeoValidator, RelationError, \
+    DuplicatedGeom, MissingItem, DimensionError, TechnicalValidationError, \
+    InvalidGeometry, UniqueError
 from schemas.netgeo_1_12_doe.models import Artere, Cable, Equipement, Noeud, \
     Tranchee, Zapbo
 
@@ -26,41 +29,41 @@ class Netgeo112DoeValidator(NetgeoValidator):
             if not noeud.NO_NOM in noeuds:
                 noeuds[noeud.NO_NOM] = noeud
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
+                self.log_error(UniqueError("Doublons dans le champs: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
         
         equipements = {}
         for equipement in self.dataset[Equipement]:
             if not equipement.EQ_NOM in equipements:
                 equipements[equipement.EQ_NOM] = equipement
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
+                self.log_error(UniqueError("Doublons dans le champs: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
                 
         zapbos = {}
         for zapbo in self.dataset[Zapbo]:
             if not zapbo.ID_ZAPBO in zapbos:
                 zapbos[zapbo.ID_ZAPBO] = zapbo
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
+                self.log_error(UniqueError("Doublons dans le champs: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
         
         # contrôle de la validité des géométries
         for artere in arteres:
             if not artere.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(artere), filename=Artere.filename, field="geom"))
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(artere), filename=Artere.filename, field="geom"))
         for tranchee in tranchees:
             if not tranchee.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(tranchee), filename=Tranchee.filename, field="geom"))
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(tranchee), filename=Tranchee.filename, field="geom"))
         for cable in cables:
-            if not cable.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(cable), filename=Cable.filename, field="geom"))
+            if not "baguette" in cable.CA_COMMENT.lower() and not cable.valid:
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(cable), filename=Cable.filename, field="geom"))
         for noeud in noeuds.values():
             if not noeud.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(noeud), filename=Noeud.filename, field="geom"))
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(noeud), filename=Noeud.filename, field="geom"))
         for equipement in equipements.values():
             if not equipement.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(equipement), filename=Equipement.filename, field="geom"))
-        for zapbo in zapbos:
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(equipement), filename=Equipement.filename, field="geom"))
+        for zapbo in zapbos.values():
             if not zapbo.valid:
-                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
+                self.log_error(InvalidGeometry("Géométrie invalide: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
         
         # rattachement les noeuds aux artères     
         for artere in arteres:
@@ -138,33 +141,32 @@ class Netgeo112DoeValidator(NetgeoValidator):
            
         # Arteres: comparer la géométrie à celle des noeuds
         for artere in arteres:
-            point_a, point_b = artere.points[0], artere.points[-1]
-            
-            if not artere.noeud_a or artere.noeud_b:
+            if not artere.noeud_a or not artere.noeud_b:
                 continue
             
-            if point_a not in (artere.noeud_a.geom.points[0], artere.noeud_b.geom.points[0]):
-                self.log_error(DuplicatedGeom("Pas de noeud aux coordonnées attendues ('{}')".format(artere), filename=Artere.filename, field="geom"))
+            buffer_a, buffer_b = artere.points[0].Buffer(1), artere.points[-1].Buffer(1)
+            
+            if not (buffer_a.Contains(artere.noeud_a.points[0]) and buffer_b.Contains(artere.noeud_b.points[0])) \
+               and not (buffer_a.Contains(artere.noeud_b.points[0]) and buffer_b.Contains(artere.noeud_a.points[0])):
+
+                self.log_error(MissingItem("Pas de noeud aux coordonnées attendues ('{}')".format(artere), filename=Artere.filename, field="geom"))
             
-            if point_b not in (artere.noeud_a.geom.points[0], artere.noeud_b.geom.points[0]):
-                self.log_error(DuplicatedGeom("Pas de noeud aux coordonnées attendues ('{}')".format(artere), filename=Artere.filename, field="geom"))
         
         # Cables: comparer la géométrie à celle des equipements (on utilise en fait la geom du noeud correspondant à l'équipement)
         for cable in cables:
-            point_a, point_b = cable.points[0], cable.points[-1]
-            
-            if not cable.equipement_a or cable.equipement_b:
+            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:
                 continue
             
-            if point_a not in (cable.equipement_a.noeud.geom.points[0], cable.equipement_b.noeud.geom.points[0]):
-                self.log_error(DuplicatedGeom("Pas d'equipement aux coordonnées attendues ('{}')".format(cable), filename=Cable.filename, field="geom"))
+            buffer_a, buffer_b = cable.points[0].Buffer(1), cable.points[-1].Buffer(1)
+            
+            if not (buffer_a.Contains(cable.equipement_a.noeud.points[0]) and buffer_b.Contains(cable.equipement_b.noeud.points[0])) \
+               and not (buffer_a.Contains(cable.equipement_b.noeud.points[0]) and buffer_b.Contains(cable.equipement_a.noeud.points[0])):
+            
+                self.log_error(MissingItem("Pas d'equipement aux coordonnées attendues ('{}')".format(cable), filename=Cable.filename, field="geom"))
             
-            if point_b not in (cable.equipement_a.noeud.points[0], cable.equipement_b.noeud.geom.points[0]):
-                self.log_error(DuplicatedGeom("Pas d'equipement aux coordonnées attendues ('{}')".format(cable), filename=Cable.filename, field="geom"))
         
         
         # Verifie que chaque tranchée a au moins une artère
-        # TODO: Ajouter une tolérance de 50cm, et prendre en compte le fait que les cables et artères n'ont pas le même découpage
         arteres_emprise = Feature.buffered_union(arteres, 0.5)
         
         for tranchee in tranchees:
@@ -174,7 +176,7 @@ class Netgeo112DoeValidator(NetgeoValidator):
         
         # Verifie que chaque cable a au moins une artère (sauf si commentaire contient 'baguette')
         for cable in cables:
-            if "baguette" in cable.CA_COMMENT.lower():
+            if "baguette" in cable.CA_COMMENT.lower() or not cable.valid:
                 continue
             if not arteres_emprise.Contains(cable.geom):
                 self.log_error(MissingItem("Cable sans artère ('{}')".format(cable), filename=Cable.filename, field="-"))
@@ -209,24 +211,97 @@ class Netgeo112DoeValidator(NetgeoValidator):
             except (TypeError, ValueError):
                 pass
         
-        # Contrôler l'emprise des ZAPBO
+        ant_db = mn.ANTDb_0()
+        ant_db.execute("alter session set NLS_NUMERIC_CHARACTERS = '.,';") # definit le separateur decimal sur '.'
         
+        # Toutes les zapbo contiennent au moins une prise
+        for zapbo in zapbos.values():
+            sql = """Select SUM(NB_PRISE) AS NB_PRISES FROM SIG_ANT.FTTH_MN_PRISE_LOT z 
+                       WHERE SDO_INSIDE(z.GEOMETRY,
+                                      SDO_GEOMETRY(2003, 3949, SDO_POINT_TYPE(null,null,null), SDO_ELEM_INFO_ARRAY(1,1003,1),  SDO_ORDINATE_ARRAY({}))
+                                         )='TRUE';""".format(", ".join(["{},{}".format(p.GetX(), p.GetY()) for p in zapbo.points]))
+            zapbo.nb_prises = int(ant_db.first(sql).NB_PRISES)
+            if not zapbo.nb_prises:
+                self.log_error(MissingItem("La Zapbo ne contient aucune prise: {}".format(zapbo), filename=Zapbo.filename, field="-"))
         
-        # 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
 
+        # Toutes les prises de la ou les ZAPM impactées sont dans une zapbo
+        zapms = {}
+        # > on déduit la liste des zapms à partir de la position des zapbos
+        for zapbo in zapbos.values():
+            centre = zapbo.geom.Centroid()
+            zapm = ant_db.first("""SELECT z.ID_ZAPM 
+                                  FROM SIG_ANT.FTTH_MN_ZAPM z
+                                  WHERE sdo_contains(z.GEOMETRY, 
+                                                     SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL)) = 'TRUE'
+                               """.format(centre.GetX(), centre.GetY()))
+            try:
+                zapms[zapm.ID_ZAPM].append(zapbo)
+            except KeyError:
+                zapms[zapm.ID_ZAPM] = [zapbo]
+        
+        for id_zapm in zapms:
+            zapm_couverture = Feature.union(zapms[id_zapm])
+            for prise in ant_db.read("""SELECT t.X AS x, t.Y AS y
+                                        FROM SIG_ANT.FTTH_MN_PRISE_LOT z, 
+                                        TABLE(SDO_UTIL.GETVERTICES(z.GEOMETRY)) t 
+                                        WHERE T_ETAT<>'OBSOLETE' AND ID_ZAPM_PARTIELLE='{}';""".format(id_zapm)):
+                point = ogr.Geometry(ogr.wkbPoint)
+                point.AddPoint(prise.x, prise.y)
+                if not zapm_couverture.Contains(point):
+                    self.log_error(MissingItem("Certaines prises de la ZAPM ne sont pas comprises dans une ZAPBO: {}".format(id_zapm), filename=Zapbo.filename, field="-"))
+        
+        # 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
+        
+        for equipement in equipements.values():
+            if not equipement.EQ_TYPE == "PBO":
+                continue
+            
+            #zapbos englobant l'equipement
+            candidates = []
+            for zapbo in zapbos.values():
+                if zapbo.geom.Contains(equipement.geom):
+                    candidates.append(zapbo)
+                    
+            # le pbo doit être contenu dans une zapbo
+            if not candidates:
+                self.log_error(MissingItem("Le PBO n'est contenu dans aucune ZAPBO: {}".format(equipement), filename=Equipement.filename, field="geom"))
+                continue
+            
+            # On se base sur le nom pour trouver la zapbo correspondante
+            try:
+                equipement.zapbo = next((z for z in candidates if equipement.EQ_NOM in z.ID_ZAPBO))
+            except StopIteration:
+                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"))
+                break
+            
+            # Controle du dimensionnement des PBO
+            if equipement.EQ_TYPE_PH == 'PBO 6' and not equipement.zapbo.nb_prises < 6:
+                self.log_error(DimensionError("Le PBO 6 contient plus de 5 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
+        
+            if equipement.EQ_TYPE_PH == 'PBO 12' and not equipement.zapbo.nb_prises >= 6 and equipement.zapbo.nb_prises <= 8:
+                self.log_error(DimensionError("Le PBO 12 contient mois de 6 prises ou plus de 8 prises: {}".format(equipement), filename=Equipement.filename, field="-"))
+        
+            if equipement.zapbo.STATUT == "REC" and not equipement.EQ_STATUT == "REC":
+                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="-"))
+        
+            if equipement.EQ_STATUT == "REC" and not equipement.zapbo.STATUT == "REC" and not equipement.zapbo.ID_ZAPBO[:4].lower() == "att_":
+                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="-"))
         
-        ### Verifs en base
-#         ant_db = mn.ANTDb()
-         
         # Contrôler dans la base si des éléments portant ces codes existent à des emplacements différents
-    
-        # verifier que toutes les prises de la plaque sont inclues dans une zapbo
-    
-    
-    
+        for noeud in noeuds.values():
+            sql = """Select z.ID_FTTH_MN_GR_NOEUD_GEO FROM SIG_ANT.FTTH_MN_GR_NOEUD_GEO z 
+                        WHERE z.NO_NOM='{}' 
+                       AND SDO_GEOM.SDO_DISTANCE(z.GEOMETRY, SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005)>10;
+                       """.format(noeud.NO_NOM, noeud.geom.GetX(), noeud.geom.GetY())
+            if ant_db.exists(sql):
+                self.log_error(DuplicatedGeom("Un noeud portant ce nom existe déjà ailleurs sur le territoire: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
+        
+        
+        
 if __name__ == "__main__":
     from core.constants import MAIN
-    subject = MAIN / "work" / "STURNO_228CP0_APD_180301_OK.zip"
+    subject = MAIN / "work" / "SOGETREL_026AP0_REC_181001_OK"
     report = Netgeo112DoeValidator.submit(subject)
     print(report)
     

+ 1 - 1
templates/report.html

@@ -65,7 +65,7 @@
 	      		{% for typeerr in report['errors'] %}
 	      			{% for err in report['errors'][typeerr]['list'] %}
 		      			<tr>
-			      			<td>{{ typeerr }}</td>
+			      			<td data-sort='{{ report['errors'][typeerr]['order_'] }}'>{{ typeerr }}</td>
 			       			<td>{{ err['filename'] }}</td>
 			       			<td>{{ err['field'] }}</td>
 			       			<td>{{ err['message'] }}</td>

+ 26 - 0
test.py

@@ -0,0 +1,26 @@
+'''
+
+@author: olivier.massot, 2018
+'''
+from core import mn
+from core.gis_ import Datasource
+
+
+ds = Datasource(r"C:\dev\python\datacheck\work\STURNO_178AP1_REC_171121_OK\NOEUD_GEO.shp")
+layer = ds.layer
+noeuds = [f for f in ds.layer]
+
+ant_db = mn.ANTDb_0()
+
+for noeud in noeuds:
+    
+    sql = """Select z.ID_FTTH_MN_GR_NOEUD_GEO FROM SIG_ANT.FTTH_MN_GR_NOEUD_GEO z 
+                WHERE z.NO_NOM='{}' 
+               AND SDO_GEOM.SDO_DISTANCE(z.GEOMETRY, SDO_GEOMETRY(2001, 3949, SDO_POINT_TYPE({}, {}, NULL), NULL, NULL),0.005)>10;
+               """.format(noeud.NO_NOM, noeud.geom.GetX(), noeud.geom.GetY())
+               
+query = ant_db.read(sql)
+
+for row in query:
+    print(row)
+    break

+ 0 - 0
test/__init__.py


+ 0 - 0
test/netgeo_1_12_doe/__init__.py


+ 26 - 0
test/netgeo_1_12_doe/test_validator.py

@@ -0,0 +1,26 @@
+'''
+Created on 3 oct. 2018
+
+@author: olivier.massot
+'''
+import unittest
+
+
+class Test(unittest.TestCase):
+
+
+    def setUp(self):
+        pass
+
+
+    def tearDown(self):
+        pass
+
+
+    def testName(self):
+        pass
+
+
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()