Browse Source

Améliorations des validations geom

olivier.massot 7 years ago
parent
commit
8739cb055d

+ 18 - 14
core/cerberus_extend.py

@@ -8,7 +8,7 @@ import re
 
 
 import cerberus
 import cerberus
 
 
-from core import gis
+from core import gis, gis_
 
 
 
 
 def is_french_date(field, value, error):
 def is_french_date(field, value, error):
@@ -30,9 +30,14 @@ def is_int(field, value, error):
     try:
     try:
         if  float(value) != int(value):
         if  float(value) != int(value):
             error(field, 'Doit être un nombre entier: {}'.format(value))
             error(field, 'Doit être un nombre entier: {}'.format(value))
-    except ValueError:
+    except (TypeError, ValueError):
         error(field, 'Doit être un nombre entier: {}'.format(value))
         error(field, 'Doit être un nombre entier: {}'.format(value))
 
 
+def is_multi_int(field, value, error):
+    
+    if not re.match(r"\d(\s?-\s?\d)*", str(value)):
+        error(field, 'Doit être un nombre entier, ou une liste de nombres séparés par des tirets: {}'.format(value))
+        
 def is_float(field, value, error):
 def is_float(field, value, error):
     try:
     try:
         value = locale.atof(str(value))
         value = locale.atof(str(value))
@@ -69,28 +74,27 @@ class ExtendedValidator(cerberus.validator.Validator):
         The rule's arguments are validated against this schema:
         The rule's arguments are validated against this schema:
         {'type': 'list'}
         {'type': 'list'}
         """
         """
-        for item in re.split("\s?-\s?", value):
+        for item in re.split("\s?-\s?", value or ""):
             if not item in allowed:
             if not item in allowed:
                 self._error(field, "Valeur non-autorisée: {}".format(item))
                 self._error(field, "Valeur non-autorisée: {}".format(item))
-                
 
 
 class GeoValidator(ExtendedValidator):
 class GeoValidator(ExtendedValidator):
     
     
-    def _validate_geometry(self, geom, field, value):
-        """ Contrôle la géométrie d'un objet
+    def _validate_inside_box(self, bounding_box, field, value):
+        """ Contrôle l'inclusion de la bounding box de l'entité dans la box donneé
 
 
         The rule's arguments are validated against this schema:
         The rule's arguments are validated against this schema:
-        {'type': 'list', 'items': [{'type':'integer'},{'type': 'list'}]}
+        {'type': 'list'}
         """
         """
-        shapeType, bbox = geom
-        
-        if value.shapeType != shapeType:
-            self._error(field, "Le type de géométrie est {} (attendu: {})".format(gis.SHAPE_NAMES[value.shapeType], gis.SHAPE_NAMES[shapeType]))
-               
-        xmin, ymin, xmax, ymax = bbox
+#         geom_type, bounding_box = geom
+#         
+#         if value.geom_type != geom_type:
+#             self._error(field, "Le type de géométrie est {} (attendu: {})".format(value.geom_name, gis_.GEOM_NAMES[geom_type]))
+    
+        xmin, ymin, xmax, ymax = bounding_box
         
         
         try:
         try:
-            x1, y1, x2, y2 = value.bbox
+            x1, y1, x2, y2 = value.bounding_box
             
             
             if any(x < xmin or x > xmax for x in (x1, x2)) or \
             if any(x < xmin or x > xmax for x in (x1, x2)) or \
                any(y < ymin or y > ymax for y in (y1, y2)):
                any(y < ymin or y > ymax for y in (y1, y2)):

+ 63 - 7
core/gis_.py

@@ -2,9 +2,10 @@
 
 
 @author: olivier.massot, 2018
 @author: olivier.massot, 2018
 '''
 '''
-from osgeo import ogr
+from osgeo import ogr, osr
 from path import Path
 from path import Path
 
 
+
 GEOM_UNKNOWN = 0
 GEOM_UNKNOWN = 0
 GEOM_POINT = 1
 GEOM_POINT = 1
 GEOM_LINE = 2
 GEOM_LINE = 2
@@ -23,6 +24,7 @@ GEOM_NAMES = {0: "(AUCUN)",
                }
                }
 
 
 
 
+
 class Datasource():
 class Datasource():
     DRIVER_NAME = "ESRI Shapefile"
     DRIVER_NAME = "ESRI Shapefile"
     def __init__(self, filename, readonly=True):
     def __init__(self, filename, readonly=True):
@@ -73,16 +75,24 @@ class Layer():
     def __getitem__(self, i):
     def __getitem__(self, i):
         return Feature(self._ogr_layer.GetFeature(i))
         return Feature(self._ogr_layer.GetFeature(i))
 
 
+
 class Feature():
 class Feature():
     def __init__(self, ogr_feature):
     def __init__(self, ogr_feature):
         self._ogr_feature = ogr_feature
         self._ogr_feature = ogr_feature
         
         
         self.geom = self._ogr_feature.GetGeometryRef()
         self.geom = self._ogr_feature.GetGeometryRef()
         self.geom_type = self.geom.GetGeometryType()
         self.geom_type = self.geom.GetGeometryType()
-        self.bounding_box = self.geom.GetEnvelope()
+        
+        xmin, xmax, ymin, ymax = self.geom.GetEnvelope()
+        self.bounding_box = (xmin, ymin, xmax, ymax)
         
         
         _def = ogr_feature.GetDefnRef()
         _def = ogr_feature.GetDefnRef()
-        self.fields = [LayerField(_def.GetFieldDefn(i)) for i in range(_def.GetFieldCount())]
+        for i in range(_def.GetFieldCount()):
+            field = _def.GetFieldDefn(i)
+            value = ogr_feature[i]
+            if value is None and field.GetType() == ogr.OFTString:
+                value = ""
+            self.__setattr__(field.GetName(), value)
         
         
     @property
     @property
     def geom_name(self):
     def geom_name(self):
@@ -92,15 +102,38 @@ class Feature():
         layerDefinition = self._ogr_layer.GetLayerDefn()
         layerDefinition = self._ogr_layer.GetLayerDefn()
         return [LayerField(layerDefinition.GetFieldDefn(i)) for i in range(layerDefinition.GetFieldCount())]
         return [LayerField(layerDefinition.GetFieldDefn(i)) for i in range(layerDefinition.GetFieldCount())]
     
     
+    @property
+    def valid(self):
+        return self.geom.IsValid()
+    
     def __repr__(self):
     def __repr__(self):
         return "<Feature: type={}, bounding_box={}>".format(self.geom_name, self.bounding_box)
         return "<Feature: type={}, bounding_box={}>".format(self.geom_name, self.bounding_box)
     
     
+    @property
     def points(self):
     def points(self):
         return [p for p in self.geom.GetPoints()]
         return [p for p in self.geom.GetPoints()]
-    
-    def __getattr__(self, key):
-        return self._ogr_feature.__getattr__(key)
 
 
+    def almost_equal(self, feature, buffer_dist=1):
+        if feature.geom_type == self.geom_type:
+            buffer = self.geom.Buffer(buffer_dist)
+            if buffer.Contains(feature.geom):
+                return True
+        return False
+    
+    @classmethod
+    def union(cls, features):
+        g = features[0].geom.Clone()
+        for f in features[1:]:
+            g = g.Union(f.geom)
+        return g
+    
+    @classmethod
+    def buffered_union(cls, features, buffer_dist=1):
+        multi  = ogr.Geometry(ogr.wkbMultiPolygon)
+        for f in features:
+            multi.AddGeometry(f.geom.Buffer(buffer_dist))
+        return multi.UnionCascaded()
+    
 if __name__ == "__main__":
 if __name__ == "__main__":
     ds = Datasource(r"C:\dev\python\datacheck\work\STURNO_178AP1_REC_171121_OK\ARTERE_GEO.shp")
     ds = Datasource(r"C:\dev\python\datacheck\work\STURNO_178AP1_REC_171121_OK\ARTERE_GEO.shp")
     layer = ds.layer
     layer = ds.layer
@@ -108,8 +141,31 @@ if __name__ == "__main__":
 #     for f in layer:
 #     for f in layer:
 #         print(f)
 #         print(f)
     f = layer[4]
     f = layer[4]
+    f2 = layer[5]
+    f3 = layer[4]
     print(f)
     print(f)
-    print(f.fields)
     print(f.geom)
     print(f.geom)
     print(f.AR_ID_INSE)
     print(f.AR_ID_INSE)
+    
+    gf = Feature.union(list(layer))
+    print(gf)
+#     gf = Feature.buffered_union(list(layer), 0.5)
+#     print(gf)
+    
+    # save to file
+    result_file = Path(r"C:\dev\python\datacheck\work\result.shp")
+    driver = ogr.GetDriverByName("ESRI Shapefile")
+    if result_file.exists():
+        driver.DeleteDataSource(result_file)
+        
+    data_source = driver.CreateDataSource(result_file)
+    srs = osr.SpatialReference()
+    srs.ImportFromEPSG(3949)
+    
+    layer = data_source.CreateLayer("result", srs, gf.GetGeometryType())
+    feature = ogr.Feature(layer.GetLayerDefn())
+    feature.SetGeometry(gf)
+    layer.CreateFeature(feature)
+    
+    
     
     

+ 59 - 22
core/validation.py

@@ -8,9 +8,9 @@ import zipfile
 
 
 from path import Path, TempDir
 from path import Path, TempDir
 
 
-from core import gis
+from core import gis_
 from core.cerberus_extend import CerberusErrorHandler, GeoValidator, \
 from core.cerberus_extend import CerberusErrorHandler, GeoValidator, \
-    _translate_messages
+    _translate_messages, ExtendedValidator
 from schemas.common import SRID
 from schemas.common import SRID
 
 
 
 
@@ -21,11 +21,16 @@ class BaseModel():
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
         self.__dict__.update(kwargs)
         self.__dict__.update(kwargs)
         
         
-class BaseGeoModel(BaseModel):
-    def __init__(self, geom, **kwargs):
-        super(BaseGeoModel, self).__init__(**kwargs)
-        self.geom = geom
-
+class BaseGeoModel(gis_.Feature):
+    filename = ""
+    pk = ""
+    geom_type = 0
+    bounding_box = (0,0,1,1)
+    schema = {}
+    
+    def __init__(self, feature):
+        self.__dict__.update(feature.__dict__)
+        
 class ValidatorInterruption(BaseException): 
 class ValidatorInterruption(BaseException): 
     pass
     pass
 
 
@@ -73,6 +78,17 @@ class DataError(BaseValidationError):
     level = ERROR
     level = ERROR
 #     level = CRITICAL
 #     level = CRITICAL
 
 
+class PositionError(BaseValidationError):
+    name = "Position hors de la zone autorisée"
+    level = CRITICAL
+
+class GeomTypeError(BaseValidationError):
+    name = "Type de géométrie invalide"
+    level = CRITICAL
+
+class InvalidGeometry(BaseValidationError):
+    name = "Géométrie invalide"
+
 # Erreurs dans le contenu, erreurs métiers
 # Erreurs dans le contenu, erreurs métiers
 
 
 class TechnicalValidationError(BaseValidationError):
 class TechnicalValidationError(BaseValidationError):
@@ -177,7 +193,7 @@ class BaseValidator():
     def _structure_validation(self):
     def _structure_validation(self):
         
         
         for model in self.models:
         for model in self.models:
-            v = GeoValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
+            v = ExtendedValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
             
             
             for item in self.dataset[model]:
             for item in self.dataset[model]:
 
 
@@ -228,22 +244,43 @@ class NetgeoValidator(BaseValidator):
             
             
             self.dataset[model] = []
             self.dataset[model] = []
             try:
             try:
-                with gis.ShapeFile(path_, srid=SRID) as sf:
-                    fields = sf.fields()
+                
+                ds = gis_.Datasource(path_)
+                layer = ds.layer
+                
+                if layer.srid != SRID:
+                    self.log_error(WrongSrid("Mauvaise projection: {} (attendu: {})".format(layer.srid, SRID)))
+                
+                for feature in layer:
                     
                     
-                    for record in sf.records():
-                        
-                        data = dict(zip(fields, record.record))
+                    item = model(feature)
                     
                     
-                        item = model(record.shape, **data)
-                        
-                        self.dataset[model].append(item)
+                    self.dataset[model].append(item)
                         
                         
-            except gis.ShapeError as e:
-                self.log_error(UnreadableFile(str(e)))
-            except gis.SridError:
-                self.log_error(WrongSrid(str(e)))
-    
-    
+            except IOError:
+                self.log_error(UnreadableFile("Fichier illisible: {}".format(path_.name)))
     
     
+    def _structure_validation(self):
+        
+        for model in self.models:
+            v = ExtendedValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
+            xmin, ymin, xmax, ymax = model.bounding_box
+            
+            for item in self.dataset[model]:
+
+                # geom type
+                if item.geom_type != model.geom_type:
+                    self.log_error(GeomTypeError("Type de géométrie invalide: {} (attendu: {})".format(item.geom_name, gis_.GEOM_NAMES[model.geom_type]), filename=model.filename, field="geom"))
+
+                # bounding box
+                x1, y1, x2, y2 = item.bounding_box
+                if any(x < xmin or x > xmax for x in (x1, x2)) or \
+                   any(y < ymin or y > ymax for y in (y1, y2)):
+                    self.log_error(PositionError("Situé hors de l'emprise autorisée", filename=model.filename, field="geom"))
+
+                v.validate(item.__dict__)
+               
+                for field, verrors in v.errors.items():
+                    for err in verrors:
+                        self.log_error(DataError(_translate_messages(err), filename=model.filename, field=field))
     
     

+ 20 - 0
resources/questions_20180927.txt

@@ -0,0 +1,20 @@
+tolérance: 2m
+
+pas de doublons dans le fichier pour: noeuds, tranchées
+
+pas de doublons avec la base pour: noeuds
+
+Vérifier que toutes les prises de la plaque sont inclus dans une zapbo (et une seule)
+
+controles graphiques:
+- une tranchée a forcément au moins une artère
+- pas de cable sans artère sauf exception (commentaire contient "baguette")
+- pas d'artère sans cable, sauf exception (commentaire contient "racco client adductio attente bus 'sans cable'")
+
+Vérifier que AR_FOU_DIS <= AR_NB_FOUR
+Vérifier que CA_NB_FO_U <= CA_NB_FO
+Vérifier que CA_NB_FO_D <= CA_NB_FO
+
+Dates: vérifier que les dates sont dans comprise entre 01/01/2014 et today()
+
+!!! Pour les equipements, utiliser la géométrie du noeud correspondant (cf: EQ_NOM_NOE)

+ 18 - 0
resources/questions_20181003.txt

@@ -0,0 +1,18 @@
+
+questions:
+
+* Format des noms de cables (et autres codes)
+
+* Noeud. 	NO_TECH_PS > Longueur max de 4? cas des valeurs multiples
+
+* règle: "Le cable doublon qui alimente les mêmes equipements A et B qu'un autre cable"
+> il peut y avoir des doublons graphiques de cables, mais ils ne doivent pas avoir le même equipement de départ et d'arrivée.
+
+* AR_UTIL_FO: ajouter manche fibre à la liste? OUI
+* AR_PRO_FOU: ajouter PRIVE, ERDF? OUI
+
+* AR_DIAM_FO: ajouter 80? OUI
+
+* AR_NB_FOUR: entier plutôt que texte? => Non, séparateur tirets
+
+* NO_GEST, NO_PRO: ajouter ENEDIS? OUI

+ 1 - 1
schemas/common.py

@@ -6,7 +6,7 @@ from core import mn
 
 
 
 
 XMIN, XMAX, YMIN, YMAX = 1341999, 1429750, 8147750, 8294000
 XMIN, XMAX, YMIN, YMAX = 1341999, 1429750, 8147750, 8294000
-SRID = 3949
+SRID = "3949"
 
 
 BUFFER = 2
 BUFFER = 2
 
 

+ 26 - 21
schemas/netgeo_1_12_doe/models.py

@@ -2,31 +2,31 @@
 
 
 @author: olivier.massot, 2018
 @author: olivier.massot, 2018
 '''
 '''
-from core import gis
+from core import gis_
 from core.cerberus_extend import is_int, is_float, \
 from core.cerberus_extend import is_int, is_float, \
-    is_modern_french_date
+    is_modern_french_date, is_multi_int
 from core.validation import BaseGeoModel
 from core.validation import BaseGeoModel
 from schemas.common import INSEE_VALIDES, XMIN, YMIN, XMAX, YMAX
 from schemas.common import INSEE_VALIDES, XMIN, YMIN, XMAX, YMAX
 
 
 
 
 class Artere(BaseGeoModel):
 class Artere(BaseGeoModel):
     filename = "artere_geo.shp"
     filename = "artere_geo.shp"
-    schema = {'geom': {'geometry': (gis.POLYLINE, (XMIN, YMIN, XMAX, YMAX))}, 
-              'AR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+    geom_type = gis_.GEOM_LINE
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'AR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
               'AR_LONG': {'empty': False, 'validator': is_float},
               'AR_LONG': {'empty': False, 'validator': is_float},
               'AR_ETAT': {'type': 'string', 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'AR_ETAT': {'type': 'string', 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'AR_OCCP': {'type': 'string', 'empty': False, 'allowed': ['0', '1.1', '1.2', '2', '3', '4']}, 
               'AR_OCCP': {'type': 'string', 'empty': False, 'allowed': ['0', '1.1', '1.2', '2', '3', '4']}, 
               'AR_NOEUD_A': {'type': 'string', 'empty': False, 'maxlength': 20}, 
               'AR_NOEUD_A': {'type': 'string', 'empty': False, 'maxlength': 20}, 
               'AR_NOEUD_B': {'type': 'string', 'empty': False, 'maxlength': 20}, 
               'AR_NOEUD_B': {'type': 'string', 'empty': False, 'maxlength': 20}, 
-              'AR_NB_FOUR': {'empty': False, 'validator': is_int}, 
-#               'AR_NB_FOUR': {'type': 'string', 'maxlength': 10}, 
+              'AR_NB_FOUR': {'empty': False, 'validator': is_multi_int}, 
               'AR_FOU_DIS': {'empty': False, 'validator': is_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_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', 'NUL']}, 
-              'AR_PRO_FOU': {'type': 'string', 'empty': False, 'multiallowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'AUTRE (à préciser)']}, 
+              '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_PRO_CAB': {'type': 'string', 'empty': False, 'allowed': ['MANCHE NUMERIQUE']}, 
               '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_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', 'COLLECTIVITE', 'ORANGE', 'PRIVE', '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_DATE_IN': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_DATE_IN': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_DATE_RE': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_DATE_RE': {'empty': False, 'validator': is_modern_french_date}, 
               'AR_REF_PLA': {'type': 'string', 'maxlength': 100}, 
               'AR_REF_PLA': {'type': 'string', 'maxlength': 100}, 
@@ -41,8 +41,9 @@ class Artere(BaseGeoModel):
 
 
 class Cable(BaseGeoModel):
 class Cable(BaseGeoModel):
     filename = "cable_geo.shp"
     filename = "cable_geo.shp"
-    schema = {'geom': {'geometry': (gis.POLYLINE, (XMIN, YMIN, XMAX, YMAX))}, 
-              'CA_NUMERO': {'type': 'string', 'maxlength': 17}, 
+    geom_type = gis_.GEOM_LINE
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'CA_NUMERO': {'type': 'string', 'maxlength': 17}, 
               'CA_TYPE': {'type': 'string', 'maxlength': 10, 'empty': False, 'allowed': ['AERIEN', 'IMMEUBLE', 'FACADE', 'MIXTE', 'SOUTERRAIN']}, 
               'CA_TYPE': {'type': 'string', 'maxlength': 10, 'empty': False, 'allowed': ['AERIEN', 'IMMEUBLE', 'FACADE', 'MIXTE', 'SOUTERRAIN']}, 
               'CA_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'CA_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'CA_LONG': {'validator': is_float}, 
               'CA_LONG': {'validator': is_float}, 
@@ -65,9 +66,10 @@ class Cable(BaseGeoModel):
     
     
 class Equipement(BaseGeoModel):
 class Equipement(BaseGeoModel):
     filename = "equipement_passif.shp"
     filename = "equipement_passif.shp"
-    schema = {'geom': {'geometry': (gis.POINT, (XMIN, YMIN, XMAX, YMAX))}, 
-              'EQ_NOM': {'type': 'string', 'maxlength': 10, 'contains_any_of': ['PBO', 'BPE', 'BAI']}, 
-              'EQ_NOM_NOE': {'type': 'string', 'maxlength': 14}, 
+    geom_type = gis_.GEOM_POINT
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'EQ_NOM': {'type': 'string', 'maxlength': 10, 'contains_any_of': ['PBO', 'BPE', 'BAI']}, 
+              'EQ_NOM_NOE': {'type': 'string', 'maxlength': 30}, 
               'EQ_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'EQ_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'EQ_OCCP': {'type': 'string', 'maxlength': 3, 'empty': False, 'allowed': ['0', '1.1', '1.2', '2', '3', '4']}, 
               'EQ_OCCP': {'type': 'string', 'maxlength': 3, 'empty': False, 'allowed': ['0', '1.1', '1.2', '2', '3', '4']}, 
               'EQ_TYPE': {'type': 'string', 'empty': False, 'allowed': ['PBO', 'PBOE', 'BPE', 'BAI']}, 
               'EQ_TYPE': {'type': 'string', 'empty': False, 'allowed': ['PBO', 'PBOE', 'BPE', 'BAI']}, 
@@ -85,8 +87,9 @@ class Equipement(BaseGeoModel):
 
 
 class Noeud(BaseGeoModel):
 class Noeud(BaseGeoModel):
     filename = "noeud_geo.shp"
     filename = "noeud_geo.shp"
-    schema = {'geom': {'geometry': (gis.POINT, (XMIN, YMIN, XMAX, YMAX))},
-              'NO_NOM': {'type': 'string', 'maxlength': 20}, 
+    geom_type = gis_.GEOM_POINT
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'NO_NOM': {'type': 'string', 'maxlength': 30}, 
               'NO_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
               'NO_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
               'NO_VOIE': {'type': 'string', 'maxlength': 100}, 
               'NO_VOIE': {'type': 'string', 'maxlength': 100}, 
               'NO_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'NO_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
@@ -97,7 +100,7 @@ class Noeud(BaseGeoModel):
               'NO_CODE_PH': {'type': 'string', 'maxlength': 20}, 
               '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, 'empty': False, 'multiallowed': ['COAX', 'CUT', 'ECL', 'ELEC', 'VP', 'OPT', 'NC']}, 
               'NO_AMO': {'type': 'string', 'maxlength': 20}, 
               'NO_AMO': {'type': 'string', 'maxlength': 20}, 
-              'NO_PLINOX': {'type': 'string', 'maxlength': 3, 'empty': False, 'allowed': ['OUI', 'NON']}, 
+              'NO_PLINOX': {'required':False, 'type': 'string', 'maxlength': 3, 'allowed': ['OUI', 'NON']}, 
               'NO_X': {'empty': False, 'validator': is_float}, 
               'NO_X': {'empty': False, 'validator': is_float}, 
               'NO_Y': {'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_PRO': {'type': 'string', 'maxlength': 20, 'empty': False, 'allowed': ['MANCHE NUMERIQUE', 'COLLECTIVITE', 'ORANGE', 'ERDF', 'PRIVE', 'AUTRE (à préciser)', 'NUL']}, 
@@ -116,8 +119,9 @@ class Noeud(BaseGeoModel):
     
     
 class Tranchee(BaseGeoModel):
 class Tranchee(BaseGeoModel):
     filename = "tranchee_geo.shp"
     filename = "tranchee_geo.shp"
-    schema = {'geom': {'geometry': (gis.POLYLINE, (XMIN, YMIN, XMAX, YMAX))}, 
-              'TR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+    geom_type = gis_.GEOM_LINE
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'TR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
               'TR_VOIE': {'type': 'string', 'maxlength': 200}, 
               'TR_VOIE': {'type': 'string', 'maxlength': 200}, 
               'TR_TYP_IMP': {'type': 'string', 'empty': False, 'allowed': ['ACCOTEMENT STABILISE', 'ACCOTEMENT NON STABILISE', 'CHAUSSEE LOURDE', 'CHAUSSEE LEGERE', 'FOSSE', 'TROTTOIR', 'ESPACE VERT', 'ENCORBELLEMENT']}, 
               'TR_TYP_IMP': {'type': 'string', 'empty': False, 'allowed': ['ACCOTEMENT STABILISE', 'ACCOTEMENT NON STABILISE', 'CHAUSSEE LOURDE', 'CHAUSSEE LEGERE', 'FOSSE', 'TROTTOIR', 'ESPACE VERT', 'ENCORBELLEMENT']}, 
               'TR_MOD_POS': {'type': 'string', 'empty': False, 'allowed': ['TRADITIONNEL', 'MICRO TRANCHEE', 'FONCAGE 60', 'FONCAGE 90', 'FONCAGE 120', 'TRANCHEUSE', 'FORAGE URBAIN', 'FORAGE RURAL', 'ENCORBELLEMENT']}, 
               'TR_MOD_POS': {'type': 'string', 'empty': False, 'allowed': ['TRADITIONNEL', 'MICRO TRANCHEE', 'FONCAGE 60', 'FONCAGE 90', 'FONCAGE 120', 'TRANCHEUSE', 'FORAGE URBAIN', 'FORAGE RURAL', 'ENCORBELLEMENT']}, 
@@ -143,8 +147,9 @@ class Tranchee(BaseGeoModel):
     
     
 class Zapbo(BaseGeoModel):
 class Zapbo(BaseGeoModel):
     filename = "zapbo_geo.shp"
     filename = "zapbo_geo.shp"
-    schema = {'geom': {'geometry': (gis.POLYGON, (XMIN, YMIN, XMAX, YMAX))}, 
-              'ID_ZAPBO': {'type': 'string', 'maxlength': 30, 'contains_any_of': ['PBO', 'BPE']}, 
+    geom_type = gis_.GEOM_POLYGON
+    bounding_box = (XMIN,YMIN,XMAX,YMAX)
+    schema = {'ID_ZAPBO': {'type': 'string', 'maxlength': 30, 'contains_any_of': ['PBO', 'BPE']}, 
               'COMMENTAIR': {'type': 'string', 'maxlength': 254, 'empty': True}, 
               'COMMENTAIR': {'type': 'string', 'maxlength': 254, 'empty': True}, 
               'STATUT': {'type': 'string', 'empty': False, 'allowed': ['APS', 'APD', 'EXE', 'REC']}}
               'STATUT': {'type': 'string', 'empty': False, 'allowed': ['APS', 'APD', 'EXE', 'REC']}}
     
     

+ 75 - 55
schemas/netgeo_1_12_doe/validator.py

@@ -3,6 +3,7 @@
 @author: olivier.massot, 2018
 @author: olivier.massot, 2018
 '''
 '''
 from core import mn, gis
 from core import mn, gis
+from core.gis_ import Feature
 from core.validation import NetgeoValidator, DuplicatedPk, RelationError, \
 from core.validation import NetgeoValidator, DuplicatedPk, RelationError, \
     DuplicatedGeom, MissingItem, DimensionError
     DuplicatedGeom, MissingItem, DimensionError
 from schemas.netgeo_1_12_doe.models import Artere, Cable, Equipement, Noeud, \
 from schemas.netgeo_1_12_doe.models import Artere, Cable, Equipement, Noeud, \
@@ -25,22 +26,41 @@ class Netgeo112DoeValidator(NetgeoValidator):
             if not noeud.NO_NOM in noeuds:
             if not noeud.NO_NOM in noeuds:
                 noeuds[noeud.NO_NOM] = noeud
                 noeuds[noeud.NO_NOM] = noeud
             else:
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(noeud.NO_NOM), filename=Noeud.filename, field="NO_NOM"))
+                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
         
         
         equipements = {}
         equipements = {}
         for equipement in self.dataset[Equipement]:
         for equipement in self.dataset[Equipement]:
             if not equipement.EQ_NOM in equipements:
             if not equipement.EQ_NOM in equipements:
                 equipements[equipement.EQ_NOM] = equipement
                 equipements[equipement.EQ_NOM] = equipement
             else:
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(equipement.NO_NOM), filename=Equipement.filename, field="EQ_NOM"))
+                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(equipement), filename=Equipement.filename, field="EQ_NOM"))
                 
                 
         zapbos = {}
         zapbos = {}
         for zapbo in self.dataset[Zapbo]:
         for zapbo in self.dataset[Zapbo]:
             if not zapbo.ID_ZAPBO in zapbos:
             if not zapbo.ID_ZAPBO in zapbos:
                 zapbos[zapbo.ID_ZAPBO] = zapbo
                 zapbos[zapbo.ID_ZAPBO] = zapbo
             else:
             else:
-                self.log_error(DuplicatedPk("Doublons dans le champs: {}".format(zapbo.NO_NOM), filename=Zapbo.filename, field="ID_ZAPBO"))
+                self.log_error(DuplicatedPk("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"))
+        for tranchee in tranchees:
+            if not tranchee.valid:
+                self.log_error(DuplicatedPk("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"))
+        for noeud in noeuds.values():
+            if not noeud.valid:
+                self.log_error(DuplicatedPk("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:
+            if not zapbo.valid:
+                self.log_error(DuplicatedPk("Géométrie invalide: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
         
         
         # rattachement les noeuds aux artères     
         # rattachement les noeuds aux artères     
         for artere in arteres:
         for artere in arteres:
@@ -81,113 +101,113 @@ class Netgeo112DoeValidator(NetgeoValidator):
         # verifie que tous les equipements sont l'equipement B d'au moins un cable
         # verifie que tous les equipements sont l'equipement B d'au moins un cable
         equipements_b = [cable.CA_EQ_B for cable in cables]
         equipements_b = [cable.CA_EQ_B for cable in cables]
         for eq_id in equipements:
         for eq_id in equipements:
+            if equipements[eq_id].EQ_TYPE == "BAI":
+                continue
             if not eq_id in equipements_b:
             if not eq_id in equipements_b:
-                self.log_error(RelationError("L'equipement '{}' n'est l'équipement B d'aucun cable".format(noeud.NO_NOM), filename=Equipement.filename, field="EQ_NOM"))
-
-#         if self.critical_happened():
-#             return
+                self.log_error(RelationError("L'equipement '{}' n'est l'équipement B d'aucun cable".format(eq_id), filename=Equipement.filename, field="EQ_NOM"))
 
 
         # controle des doublons graphiques
         # controle des doublons graphiques
         for i, tranchee in enumerate(tranchees):
         for i, tranchee in enumerate(tranchees):
             for other in tranchees[i+1:]:
             for other in tranchees[i+1:]:
-                if gis.same_geom(tranchee.geom, other.geom):
+                if tranchee.geom == other.geom:
                     self.log_error(DuplicatedGeom("Une entité graphique est dupliquée".format(tranchee), filename=Tranchee.filename, field="geom"))
                     self.log_error(DuplicatedGeom("Une entité graphique est dupliquée".format(tranchee), filename=Tranchee.filename, field="geom"))
                     
                     
         for i, artere in enumerate(arteres):
         for i, artere in enumerate(arteres):
             for other in arteres[i+1:]:
             for other in arteres[i+1:]:
-                if gis.same_geom(artere.geom, other.geom):
-                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('')".format(artere), filename=Artere.filename, field="geom"))
-                    
+                if artere.geom == other.geom:
+                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(artere), filename=Artere.filename, field="geom"))
+
         for i, cable in enumerate(cables):
         for i, cable in enumerate(cables):
             for other in cables[i+1:]:
             for other in cables[i+1:]:
-                if gis.same_geom(cable.geom, other.geom):
-                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('')".format(cable), filename=Cable.filename, field="geom"))
+                if cable.geom == other.geom and cable.CA_EQ_A == other.CA_EQ_A and cable.CA_EQ_B == other.CA_EQ_B:
+                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(cable), filename=Cable.filename, field="geom"))
         
         
         ls_noeuds = list(noeuds.values())
         ls_noeuds = list(noeuds.values())
         for i, noeud in enumerate(ls_noeuds):
         for i, noeud in enumerate(ls_noeuds):
             for other in ls_noeuds[i+1:]:
             for other in ls_noeuds[i+1:]:
-                if gis.same_geom(noeud.geom, other.geom):
-                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('')".format(noeud), filename=Noeud.filename, field="geom"))
+                if noeud.geom == other.geom:
+                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(noeud), filename=Noeud.filename, field="geom"))
         del ls_noeuds
         del ls_noeuds
         
         
         ls_zapbos = list(zapbos.values())
         ls_zapbos = list(zapbos.values())
         for i, zapbo in enumerate(ls_zapbos):
         for i, zapbo in enumerate(ls_zapbos):
             for other in ls_zapbos[i+1:]:
             for other in ls_zapbos[i+1:]:
-                if gis.same_geom(zapbo.geom, other.geom):
-                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('')".format(zapbo), filename=Zapbo.filename, field="geom"))
+                if zapbo.geom == other.geom:
+                    self.log_error(DuplicatedGeom("Une entité graphique est dupliquée ('{}')".format(zapbo), filename=Zapbo.filename, field="geom"))
         del ls_zapbos
         del ls_zapbos
            
            
         # Arteres: comparer la géométrie à celle des noeuds
         # Arteres: comparer la géométrie à celle des noeuds
         for artere in arteres:
         for artere in arteres:
-            point_a, point_b = artere.geom.points[0], artere.geom.points[-1]
+            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 artere.noeud_b:
                 continue
                 continue
             
             
             if point_a not in (artere.noeud_a.geom.points[0], artere.noeud_b.geom.points[0]):
             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"))
+                self.log_error(DuplicatedGeom("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]):
             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"))
+                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)
         # Cables: comparer la géométrie à celle des equipements (on utilise en fait la geom du noeud correspondant à l'équipement)
         for cable in cables:
         for cable in cables:
-            point_a, point_b = cable.geom.points[0], cable.geom.points[-1]
+            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 cable.equipement_b:
                 continue
                 continue
             
             
             if point_a not in (cable.equipement_a.noeud.geom.points[0], cable.equipement_b.noeud.geom.points[0]):
             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"))
+                self.log_error(DuplicatedGeom("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]):
             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"))
+                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
         # 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:
         for tranchee in tranchees:
-            found = False
-            for artere in arteres:
-                if tranchee.geom.bbox == artere.geom.bbox:
-                    found = True
-                    break
-            if not found:
-                self.log_error(MissingItem("Pas de tranchée correspondant à l'artère ('')".format(artere), filename=Artere.filename, field="-"))
+            if not arteres_emprise.Contains(tranchee.geom):
+                self.log_error(MissingItem("Tranchée sans artère ('{}')".format(tranchee), filename=Tranchee.filename, field="-"))
+        
         
         
         # Verifie que chaque cable a au moins une artère (sauf si commentaire contient 'baguette')
         # Verifie que chaque cable a au moins une artère (sauf si commentaire contient 'baguette')
         for cable in cables:
         for cable in cables:
-            found = False
             if "baguette" in cable.CA_COMMENT.lower():
             if "baguette" in cable.CA_COMMENT.lower():
                 continue
                 continue
-            for artere in arteres:
-                if tranchee.geom.bbox == artere.geom.bbox:
-                    found = True
-                    break
-            if not found:
-                self.log_error(MissingItem("Pas d'artère correspondante au cable ('')".format(cable), filename=Cable.filename, field="-"))
+            if not arteres_emprise.Contains(cable.geom):
+                self.log_error(MissingItem("Cable sans artère ('{}')".format(cable), filename=Cable.filename, field="-"))
+        
+        del arteres_emprise
         
         
         # 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'')
         # 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'')
-        for tranchee in tranchees:
-            found = False
-            if any(x in cable.CA_COMMENT.lower() for x in ['racco','client','adductio','attente','bus','sans cable']):
-                continue
-            for artere in arteres:
-                if tranchee.geom.bbox == artere.geom.bbox:
-                    found = True
-                    break
-            if not found:
-                self.log_error(MissingItem("Pas de cable correspondant à l'artère ('')".format(artere), filename=Artere.filename, field="-"))
+        cables_emprise = Feature.buffered_union(cables, 0.5)
         
         
+        for artere in arteres:
+            if any(x in artere.AR_COMMENT.lower() for x in ['racco','client','adductio','attente','bus','sans cable']):
+                continue
+            if not cables_emprise.Contains(artere.geom):
+                self.log_error(MissingItem("Artère sans cable ('{}')".format(artere), filename=Artere.filename, field="-"))
+                
+        del cables_emprise
+                
         # Contrôle des dimensions logiques
         # Contrôle des dimensions logiques
         for artere in arteres:
         for artere in arteres:
-            if not int(artere.AR_FOU_DIS) <= int(artere.AR_NB_FOUR):
-                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"))
+            try:
+                if not int(artere.AR_FOU_DIS) <= int(artere.AR_NB_FOUR):
+                    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"))
+            except (TypeError, ValueError):
+                pass
         
         
         for cable in cables:
         for cable in cables:
-            if not int(cable.CA_NB_FO_U) <= int(artere.CA_NB_FO):
-                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"))
-            if not int(cable.CA_NB_FO_D) <= int(artere.CA_NB_FO):
-                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"))
+            try:
+                if not int(cable.CA_NB_FO_U) <= int(cable.CA_NB_FO):
+                    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"))
+                if not int(cable.CA_NB_FO_D) <= int(cable.CA_NB_FO):
+                    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"))
+            except (TypeError, ValueError):
+                pass
         
         
         # Contrôler l'emprise des ZAPBO
         # Contrôler l'emprise des ZAPBO
         
         
@@ -196,8 +216,8 @@ class Netgeo112DoeValidator(NetgeoValidator):
 
 
         
         
         ### Verifs en base
         ### Verifs en base
-        ant_db = mn.ANTDb()
-        
+#         ant_db = mn.ANTDb()
+         
         # Contrôler dans la base si des éléments portant ces codes existent à des emplacements différents
         # 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
         # verifier que toutes les prises de la plaque sont inclues dans une zapbo