Browse Source

Structure ok

omassot 7 years ago
parent
commit
0a98e99a91

+ 5 - 5
__init__.py

@@ -22,15 +22,15 @@
  ***************************************************************************/
  This script initializes the plugin, making it known to QGIS.
 """
+import os
+import sys
 
+sys.path.insert(0, os.path.join(__file__, os.pardir))
+sys.path.insert(0, os.path.join(os.path.join(__file__, os.pardir), "lib"))
 
 # noinspection PyPep8Naming
 def classFactory(iface):  # pylint: disable=invalid-name
     """Load MnCheck class from file MnCheck.
-
-    :param iface: A QGIS interface instance.
-    :type iface: QgsInterface
     """
-    #
-    from .mncheck import MnCheck
+    from main import MnCheck   # @UnresolvedImport
     return MnCheck(iface)

+ 0 - 2
core/cerberus_extend.py

@@ -8,8 +8,6 @@ import re
 
 import cerberus
 
-from core import gis, gis_
-
 
 def is_french_date(field, value, error):
     try:

+ 6 - 3
core/constants.py

@@ -6,6 +6,9 @@ from path import Path
 
 MAIN = Path(__file__).parent.parent.abspath()
 
-# Dirs
-WRKDIR = MAIN / "work"
-RSCDIR = MAIN / "resources"
+RSCDIR = MAIN / "ui" / "rsc"
+
+LOGDIR = Path("%appdata%").expandvars() / "mncheck"
+LOGDIR.mkdir_p()
+
+LOGCONF = MAIN / "core" / "logging.yaml"

+ 0 - 75
core/gis.py

@@ -1,75 +0,0 @@
-'''
-
-@author: olivier.massot, sept. 2018
-'''
-import shapefile
-
-POINT = 1
-POLYLINE = 3
-POLYGON = 5
-SHAPE_NAMES = {0: "(AUCUN)", 
-               1: "POINT", 
-               3:"POLYLIGNE", 
-               5:"POLYGONE",
-               8: "MULTI-POINT",
-               11: "POINT-Z",
-               13: "POLYLIGNE-Z",
-               15: "POLYGONE-Z",
-               18: "MULTIPOINT-Z",
-               21: "POINT-M",
-               23: "POLYLIGNE-M",
-               25: "POLYGONE-M",
-               28: "MULTIPOINT-M",
-               31: "MULTI-PATCH",
-               }
-
-def same_geom(geom1, geom2, buffer = 0):
-    """ retourne vrai si les deux géométries sont identiques """
-    if geom1.shapeType == geom2.shapeType:
-        if geom1.shapeType in (3,5,8,13,15,18,23,25,28,31):
-            return geom1.bbox == geom2.bbox
-        else:
-            return geom1 == geom2
-        
-    return False
-
-class ShapeError(IOError): 
-    pass
-class SridError(IOError): 
-    pass
-
-class ShapeFile():
-    def __init__(self, path_, srid=None):
-        self.path_ = path_
-        self.srid = srid
-    
-    def __enter__(self):
-        if not self.path_.isfile():
-            raise FileNotFoundError("Fichier introuvable")
-     
-        try:
-            self.sf = shapefile.Reader(self.path_)
-        except shapefile.ShapefileException:
-            raise ShapeError("Fichier Shape illisible")
-        
-        if not self.sf.shapeType in (POINT, POLYLINE, POLYGON):
-            raise ShapeError("Type de géométrie non reconnue")
-
-#         if self.srid is not None:
-#             if not self.sf.srid == self.srid:
-#                 raise SridError("Le SRID du fichier doit être '{}'".format(self.srid))
-
-        return self
-    
-    def shape(self):
-        return self.sf.shapeType
-    
-    def fields(self):
-        return [f[0] for f in self.sf.fields if f[0] != 'DeletionFlag']
-        
-    def records(self):
-        for record in self.sf.shapeRecords():
-            yield record
-        
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        del self.sf

+ 0 - 178
core/gis_.py

@@ -1,178 +0,0 @@
-'''
-
-@author: olivier.massot, 2018
-'''
-from osgeo import ogr, osr
-from path import Path
-
-
-GEOM_UNKNOWN = 0
-GEOM_POINT = 1
-GEOM_LINE = 2
-GEOM_POLYGON = 3
-GEOM_MULTIPOINT = 4
-GEOM_MULTILINE = 5
-GEOM_MULTIPOLYGON = 6
-
-GEOM_NAMES = {0: "(AUCUN)", 
-               1: "POINT", 
-               2: "LIGNE", 
-               3: "POLYGONE", 
-               4: "MULTI-POINT", 
-               5:"MULTI-LIGNE", 
-               6:"MULTI-POLYGONE",
-               }
-
-def point_(x, y):
-    point = ogr.Geometry(ogr.wkbPoint)
-    point.AddPoint(x, y)
-    return point
-
-class Datasource():
-    DRIVER_NAME = "ESRI Shapefile"
-    def __init__(self, filename, readonly=True):
-        self.filename = Path(filename)
-        driver = ogr.GetDriverByName(self.DRIVER_NAME)
-        self._ogr_datasource = driver.Open(filename, 0 if readonly else 1)
-        if not self._ogr_datasource:
-            raise IOError("Unable to read the file {}".format(filename))
-    
-    @property
-    def layer(self):
-        return Layer(self._ogr_datasource.GetLayer())
-    
-    
-class LayerField():
-    def __init__(self, ogr_field):
-        self.name =  ogr_field.GetName()
-        self.type_ = ogr_field.GetType()
-        self.type_name = ogr_field.GetFieldTypeName(self.type_)
-        self.width = ogr_field.GetWidth()
-        self.precision = ogr_field.GetPrecision()
-    
-    def __repr__(self):
-        return "<Field: nom={}, type={}, longueur={}>".format(self.name, self.type_name, self.width)
-    
-class Layer():
-    def __init__(self, ogr_layer):
-        self._ogr_layer = ogr_layer
-        
-        self.name = ogr_layer.GetName()
-        self.srid = ogr_layer.GetSpatialRef().GetAttrValue('AUTHORITY',1)
-        self.geom_type = ogr_layer.GetGeomType()
-        _layer_def = self._ogr_layer.GetLayerDefn()
-        self.fields = [LayerField(_layer_def.GetFieldDefn(i)) for i in range(_layer_def.GetFieldCount())]
-    
-    @property
-    def geom_name(self):
-        return GEOM_NAMES[self._ogr_layer.GetGeomType()]
-    
-    def __len__(self):
-        return self._ogr_layer.GetFeatureCount()
-    
-    def __iter__(self):
-        for f in self._ogr_layer.__iter__():
-            yield Feature(f)
-        self._ogr_layer.ResetReading()
-
-    def __getitem__(self, i):
-        return Feature(self._ogr_layer.GetFeature(i))
-
-
-class Feature():
-    def __init__(self, ogr_feature):
-        self._ogr_feature = ogr_feature
-        
-        self.geom = self._ogr_feature.GetGeometryRef()
-        self.geom_type = self.geom.GetGeometryType()
-        
-        xmin, xmax, ymin, ymax = self.geom.GetEnvelope()
-        self.bounding_box = (xmin, ymin, xmax, ymax)
-        
-        _def = ogr_feature.GetDefnRef()
-        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
-    def geom_name(self):
-        return GEOM_NAMES[self.geom.GetGeometryType()]
-    
-    def fields(self):
-        layerDefinition = self._ogr_layer.GetLayerDefn()
-        return [LayerField(layerDefinition.GetFieldDefn(i)) for i in range(layerDefinition.GetFieldCount())]
-    
-    @property
-    def valid(self):
-        return self.geom.IsValid()
-    
-    def __repr__(self):
-        return "<Feature: type={}, bounding_box={}>".format(self.geom_name, self.bounding_box)
-    
-    @property
-    def points(self):
-        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:
-            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:
-            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__":
-    ds = Datasource(r"C:\dev\python\datacheck\work\STURNO_178AP1_REC_171121_OK\ARTERE_GEO.shp")
-    layer = ds.layer
-    print(layer.fields)
-#     for f in layer:
-#         print(f)
-    f = layer[4]
-    f2 = layer[5]
-    f3 = layer[4]
-    print(f)
-    print(f.geom)
-    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)
-    
-    
-    

+ 36 - 0
core/logconf.py

@@ -0,0 +1,36 @@
+'''
+Created on 6 juil. 2017
+
+@author: olivier.massot
+'''
+import logging.config
+import sys
+import traceback
+
+import yaml
+
+from core.constants import LOGDIR, LOGCONF
+
+SYS_EXCEPT_HOOK = sys.excepthook
+
+def start(name="main", level=0, filename=""):
+    # charge la configuration du logging depuis le fichier 'logging.yaml'
+    with open(LOGCONF, 'rt') as f:
+        conf = yaml.load(f)
+
+    if level:
+        conf["loggers"][name]["level"] = level
+
+    if not filename:
+        filename = LOGDIR / r'{}.log'.format(name)
+    conf["handlers"]["file"]["filename"] = filename
+
+    logging.config.dictConfig(conf)
+
+    logger = logging.getLogger(name)
+    def _excepthook(typ, value, trace):
+        """ Remplace la gestion d'erreur standard, pour logger aussi les erreurs non gérées """
+        logger.error("{}\n{}\n{}".format(typ.__name__, value, ''.join(traceback.format_tb(trace))))
+        SYS_EXCEPT_HOOK(typ, value, trace)
+    sys.excepthook = _excepthook
+

+ 37 - 0
core/logging.yaml

@@ -0,0 +1,37 @@
+version: 1
+disable_existing_loggers: no
+formatters:
+    simple:
+        format: "%(asctime)s - %(levelname)s - %(message)s"
+    complete:
+        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+    short:
+        format: "%(levelname)s - %(message)s"
+    message_only:
+        format: "%(message)s"
+        
+handlers:
+    console:
+        class: logging.StreamHandler
+        level: INFO
+        formatter: message_only
+        stream: ext://sys.stdout
+    file:
+        class: logging.handlers.RotatingFileHandler
+        level: DEBUG
+        formatter: complete
+        filename: debug.log
+        maxBytes: 1000000
+        backupCount: 1
+        encoding: utf8
+
+loggers:
+    mncheck:
+        level: DEBUG
+        handlers: [console, file]
+        propagate: no
+
+root:
+    level: DEBUG
+    handlers: [console]
+    propagate: yes

+ 0 - 34
core/sqlformatter.py

@@ -1,34 +0,0 @@
-'''
-
-    Formatter special pour les requetes SQL
-
-    usage:
-
-        Sql = SqlFormatter()
-
-        my_query = Sql.format("SELECT {} FROM {}", "my_field", "tblname")
-
-@author: olivier.massot
-'''
-import string
-
-
-class SqlFormatter(string.Formatter):
-
-    def format_field(self, value, format_spec):
-
-        if value is None:
-            return "Null"
-
-        if format_spec == "date":
-            return "#{:%Y-%m-%d %H:%M:%S}#".format(value)
-        elif format_spec == "text":
-            return "'{}'".format(SqlFormatter._escape(str(value)))
-        elif format_spec == "float":
-            return str(value).replace(",", ".")
-        else:
-            return SqlFormatter._escape(value.__format__(format_spec))
-
-    @staticmethod
-    def _escape(instr):
-            return instr.replace("'", "''").replace("\"", "''").replace("\t", " ")

+ 91 - 0
main.py

@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+"""
+/***************************************************************************
+ MnCheck
+                                 A QGIS plugin
+ Contrôle des données FTTH format MN
+ Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
+                              -------------------
+        begin                : 2018-12-07
+        git sha              : $Format:%H$
+        copyright            : (C) 2018 by Manche Numérique 2019
+        email                : olivier.massot@manchenumerique.fr
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+"""
+
+import logging
+
+from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import QAction
+
+from core import logconf
+from core.constants import MAIN
+from ui.dlg_main import DlgMain
+
+__VERSION__ = "0.1.0"
+
+logger = logging.getLogger("mncheck")
+logconf.start("mncheck", logging.DEBUG)
+
+class MnCheck:
+    def __init__(self, iface):
+        self.iface = iface
+        self.plugin_dir = MAIN
+        self.dlg = DlgMain()
+
+        self.actions = []
+        self.menu = '&MnCheck'
+        self.toolbar = self.iface.addToolBar('MnCheck')
+        self.toolbar.setObjectName('MnCheck')
+
+    def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, 
+                   add_to_toolbar=True, status_tip=None, whats_this=None, parent=None):
+        icon = QIcon(icon_path)
+        action = QAction(icon, text, parent)
+        action.triggered.connect(callback)
+        action.setEnabled(enabled_flag)
+
+        if status_tip is not None:
+            action.setStatusTip(status_tip)
+
+        if whats_this is not None:
+            action.setWhatsThis(whats_this)
+
+        if add_to_toolbar:
+            self.toolbar.addAction(action)
+
+        if add_to_menu:
+            self.iface.addPluginToMenu(self.menu, action)
+
+        self.actions.append(action)
+
+        return action
+
+    def initGui(self):
+        """Create the menu entries and toolbar icons inside the QGIS GUI."""
+        icon_path = MAIN / 'icon.png'
+        self.add_action(icon_path, text='MnCheck', callback=self.run, parent=self.iface.mainWindow())
+
+    def unload(self):
+        """Removes the plugin menu item and icon from QGIS GUI."""
+        for action in self.actions:
+            self.iface.removePluginMenu('&MnCheck', action)
+            self.iface.removeToolBarIcon(action)
+        del self.toolbar
+
+    def run(self):
+        """Run method that performs all the real work"""
+        self.dlg.show()
+        result = self.dlg.exec_()
+
+        if result:
+            pass

+ 0 - 197
mncheck.py

@@ -1,197 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-/***************************************************************************
- MnCheck
-                                 A QGIS plugin
- Contrôle des données FTTH format MN
- Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-                              -------------------
-        begin                : 2018-12-07
-        git sha              : $Format:%H$
-        copyright            : (C) 2018 by Manche Numérique 2019
-        email                : olivier.massot@manchenumerique.fr
- ***************************************************************************/
-
-/***************************************************************************
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- ***************************************************************************/
-"""
-import os.path
-
-from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QAction
-
-from .ui.dlg_main import DlgMain
-
-from .resources import *
-
-
-# Initialize Qt resources from file resources.py
-# Import the code for the dialog
-class MnCheck:
-    """QGIS Plugin Implementation."""
-
-    def __init__(self, iface):
-        """Constructor.
-
-        :param iface: An interface instance that will be passed to this class
-            which provides the hook by which you can manipulate the QGIS
-            application at run time.
-        :type iface: QgsInterface
-        """
-        # Save reference to the QGIS interface
-        self.iface = iface
-        # initialize plugin directory
-        self.plugin_dir = os.path.dirname(__file__)
-        # initialize locale
-        locale = QSettings().value('locale/userLocale')[0:2]
-        locale_path = os.path.join(
-            self.plugin_dir,
-            'i18n',
-            'MnCheck_{}.qm'.format(locale))
-
-        if os.path.exists(locale_path):
-            self.translator = QTranslator()
-            self.translator.load(locale_path)
-
-            if qVersion() > '4.3.3':
-                QCoreApplication.installTranslator(self.translator)
-
-        # Create the dialog (after translation) and keep reference
-        self.dlg = DlgMain()
-
-        # Declare instance attributes
-        self.actions = []
-        self.menu = self.tr(u'&MnCheck')
-        # TODO: We are going to let the user set this up in a future iteration
-        self.toolbar = self.iface.addToolBar(u'MnCheck')
-        self.toolbar.setObjectName(u'MnCheck')
-
-    # noinspection PyMethodMayBeStatic
-    def tr(self, message):
-        """Get the translation for a string using Qt translation API.
-
-        We implement this ourselves since we do not inherit QObject.
-
-        :param message: String for translation.
-        :type message: str, QString
-
-        :returns: Translated version of message.
-        :rtype: QString
-        """
-        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
-        return QCoreApplication.translate('MnCheck', message)
-
-
-    def add_action(
-        self,
-        icon_path,
-        text,
-        callback,
-        enabled_flag=True,
-        add_to_menu=True,
-        add_to_toolbar=True,
-        status_tip=None,
-        whats_this=None,
-        parent=None):
-        """Add a toolbar icon to the toolbar.
-
-        :param icon_path: Path to the icon for this action. Can be a resource
-            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
-        :type icon_path: str
-
-        :param text: Text that should be shown in menu items for this action.
-        :type text: str
-
-        :param callback: Function to be called when the action is triggered.
-        :type callback: function
-
-        :param enabled_flag: A flag indicating if the action should be enabled
-            by default. Defaults to True.
-        :type enabled_flag: bool
-
-        :param add_to_menu: Flag indicating whether the action should also
-            be added to the menu. Defaults to True.
-        :type add_to_menu: bool
-
-        :param add_to_toolbar: Flag indicating whether the action should also
-            be added to the toolbar. Defaults to True.
-        :type add_to_toolbar: bool
-
-        :param status_tip: Optional text to show in a popup when mouse pointer
-            hovers over the action.
-        :type status_tip: str
-
-        :param parent: Parent widget for the new action. Defaults None.
-        :type parent: QWidget
-
-        :param whats_this: Optional text to show in the status bar when the
-            mouse pointer hovers over the action.
-
-        :returns: The action that was created. Note that the action is also
-            added to self.actions list.
-        :rtype: QAction
-        """
-
-        icon = QIcon(icon_path)
-        action = QAction(icon, text, parent)
-        action.triggered.connect(callback)
-        action.setEnabled(enabled_flag)
-
-        if status_tip is not None:
-            action.setStatusTip(status_tip)
-
-        if whats_this is not None:
-            action.setWhatsThis(whats_this)
-
-        if add_to_toolbar:
-            self.toolbar.addAction(action)
-
-        if add_to_menu:
-            self.iface.addPluginToMenu(
-                self.menu,
-                action)
-
-        self.actions.append(action)
-
-        return action
-
-    def initGui(self):
-        """Create the menu entries and toolbar icons inside the QGIS GUI."""
-
-        icon_path = ':/plugins/mncheck/icon.png'
-        self.add_action(
-            icon_path,
-            text=self.tr(u'MnCheck'),
-            callback=self.run,
-            parent=self.iface.mainWindow())
-
-
-    def unload(self):
-        """Removes the plugin menu item and icon from QGIS GUI."""
-        for action in self.actions:
-            self.iface.removePluginMenu(
-                self.tr(u'&MnCheck'),
-                action)
-            self.iface.removeToolBarIcon(action)
-        # remove the toolbar
-        del self.toolbar
-
-
-    def run(self):
-        """Run method that performs all the real work"""
-        # show the dialog
-        self.dlg.show()
-        # Run the dialog event loop
-        result = self.dlg.exec_()
-        # See if OK was pressed
-        if result:
-            # Do something useful here - delete the line containing pass and
-            # substitute with your code.
-            pass

+ 0 - 2
requirements.txt

@@ -1,2 +0,0 @@
-path.py>=11.1.0
-cerberus

+ 0 - 10
schemas/common.py

@@ -2,18 +2,8 @@
 
 @author: olivier.massot, 2018
 '''
-from core import mn
-
 
 XMIN, XMAX, YMIN, YMAX = 1341999, 1429750, 8147750, 8294000
 SRID = "3949"
 
 TOLERANCE = 1
-
-with mn.ANTDb_0() as ant_db:
-    INSEE_VALIDES = [row[0] for row in ant_db.read("SELECT CODE_INSEE FROM SIG_REFERENTIEL.ADM_CD50_COM;")]
-
-# # version Postgres:
-# 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;")]
-    

+ 3 - 0
schemas/mn1_rec/__init__.py

@@ -0,0 +1,3 @@
+
+from schemas.mn1_rec.validator import Mn1RecValidator
+validator = Mn1RecValidator

+ 4 - 4
schemas/netgeo_1_12_doe/models.py → schemas/mn1_rec/models.py

@@ -6,14 +6,14 @@ from core import gis_
 from core.cerberus_extend import is_int, is_float, \
     is_modern_french_date, is_multi_int
 from core.validation import BaseGeoModel
-from schemas.common import INSEE_VALIDES, XMIN, YMIN, XMAX, YMAX
+from schemas.common import XMIN, YMIN, XMAX, YMAX
 
 
 class Artere(BaseGeoModel):
     filename = "artere_geo.shp"
     geom_type = gis_.GEOM_LINE
     bounding_box = (XMIN,YMIN,XMAX,YMAX)
-    schema = {'AR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+    schema = {'AR_ID_INSE': {'type': 'string', 'empty': False, 'regex': r'50\d{3}'}, 
               'AR_LONG': {'empty': False, 'validator': is_float},
               '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']}, 
@@ -90,7 +90,7 @@ class Noeud(BaseGeoModel):
     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, 'regex': r'50\d{3}'}, 
               'NO_VOIE': {'type': 'string', 'maxlength': 100}, 
               'NO_ETAT': {'type': 'string', 'maxlength': 1, 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
               'NO_OCCP': {'type': 'string', 'maxlength': 3, 'empty': False, 'allowed': ['0', '1.1', '1.2', '2', '3', '4']}, 
@@ -121,7 +121,7 @@ class Tranchee(BaseGeoModel):
     filename = "tranchee_geo.shp"
     geom_type = gis_.GEOM_LINE
     bounding_box = (XMIN,YMIN,XMAX,YMAX)
-    schema = {'TR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+    schema = {'TR_ID_INSE': {'type': 'string', 'empty': False, 'regex': r'50\d{3}'}, 
               '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_MOD_POS': {'type': 'string', 'empty': False, 'allowed': ['TRADITIONNEL', 'MICRO TRANCHEE', 'FONCAGE 60', 'FONCAGE 90', 'FONCAGE 120', 'TRANCHEUSE', 'FORAGE URBAIN', 'FORAGE RURAL', 'ENCORBELLEMENT']}, 

+ 0 - 0
schemas/netgeo_1_12_doe/readme.md → schemas/mn1_rec/readme.md


+ 4 - 87
schemas/netgeo_1_12_doe/validator.py → schemas/mn1_rec/validator.py

@@ -2,20 +2,16 @@
 
 @author: olivier.massot, 2018
 '''
-from osgeo import ogr
-
-from core import mn
-from core.gis_ import Feature
 from core.validation import NetgeoValidator, RelationError, \
     DuplicatedGeom, MissingItem, DimensionError, TechnicalValidationError, \
-    InvalidGeometry, UniqueError, PositionError
+    InvalidGeometry, UniqueError
 from schemas.common import TOLERANCE
-from schemas.netgeo_1_12_doe.models import Artere, Cable, Equipement, Noeud, \
+from schemas.mn1_rec.models import Artere, Cable, Equipement, Noeud, \
     Tranchee, Zapbo
 
 
-class Netgeo112DoeValidator(NetgeoValidator):
-    schema_name = "Netgeo v1.12 DOE"
+class Mn1RecValidator(NetgeoValidator):
+    schema_name = "Mn1 REC"
     models = [Artere, Cable, Equipement, Noeud, Tranchee, Zapbo]
     
     def _technical_validation(self):
@@ -212,53 +208,6 @@ class Netgeo112DoeValidator(NetgeoValidator):
             except (TypeError, ValueError):
                 pass
         
-        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():
-            
-            if len(zapbo.points) >= 499:
-                # passe l'erreur provoquée par la limite au nombre d'arguments en SQL
-                zapbo.nb_prises = None
-                continue
-            
-            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]))
-            nb_prises = ant_db.first(sql).NB_PRISES         
-            zapbo.nb_prises = int(nb_prises) if nb_prises is not None else 0
-            
-            if not zapbo.nb_prises:
-                self.log_error(MissingItem("La Zapbo ne contient aucune prise: {}".format(zapbo), filename=Zapbo.filename, field="-"))
-        
-
-        # 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
         
@@ -300,39 +249,7 @@ class Netgeo112DoeValidator(NetgeoValidator):
             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="-"))
         
-        # Contrôler dans la base si des éléments portant ces codes existent à des emplacements différents
-        for noeud in noeuds.values():
-            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 
-                     FROM SIG_ANT.FTTH_MN_GR_NOEUD_GEO z 
-                     WHERE z.NO_NOM='{}';""".format(noeud.geom.GetX(), noeud.geom.GetY(), noeud.NO_NOM)
-            existing = ant_db.first(sql)
-            if existing:
-                if existing.DIST > TOLERANCE and existing.DIST < 20:
-                    self.log_error(PositionError("La position du noeud ne correspond pas à l'existant: {}".format(noeud), filename=Noeud.filename, field="geom"))
-                elif existing.DIST > 20:
-                    self.log_error(DuplicatedGeom("Un noeud portant ce nom existe déjà ailleurs sur le territoire: {}".format(noeud), filename=Noeud.filename, field="NO_NOM"))
-        
-        for zapbo in zapbos.values():
-            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 
-                     FROM SIG_ANT.FTTH_MN_GR_ZAPBO_GEO z 
-                     WHERE z.ID_ZAPBO='{}';""".format(zapbo.geom.Centroid().GetX(), zapbo.geom.Centroid().GetY(), zapbo.ID_ZAPBO)
-            existing = ant_db.first(sql)
-            if existing:
-                if existing.DIST > TOLERANCE and existing.DIST < 20:
-                    self.log_error(PositionError("La position de la ZAPBO ne correspond pas à l'existant: {}".format(zapbo), filename=Zapbo.filename, field="geom"))
-                elif existing.DIST > 20:
-                    self.log_error(DuplicatedGeom("Une ZAPBO portant ce nom existe déjà ailleurs sur le territoire: {}".format(zapbo), filename=Zapbo.filename, field="ID_ZAPBO"))
 
-        # Contrôle si un equipement portant ce nom existe, mais associé à un noeud différent
-        for equipement in equipements.values():
-            sql = """SELECT z.EQ_NOM, z.EQ_NOM_NOEUD
-                     FROM SIG_ANT.FTTH_MN_GR_EQ_PASSIF z 
-                     WHERE z.EQ_NOM='{}';""".format(equipement.EQ_NOM)
-            existing = ant_db.first(sql)
-            if existing and existing.EQ_NOM_NOEUD != equipement.EQ_NOM_NOE:
-                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"))
-        
-        
 if __name__ == "__main__":
     from core.constants import MAIN
     subject = MAIN / "work" / "SOGETREL_026AP0_REC_181001_OK"

+ 4 - 0
schemas/mn2_rec/__init__.py

@@ -0,0 +1,4 @@
+from schemas.mn2_rec.validator import Mn2RecValidator
+
+
+validator = Mn2RecValidator

+ 5 - 5
schemas/netgeo_2_2_doe/models.py → schemas/mn2_rec/models.py

@@ -6,7 +6,7 @@ from core import gis_
 from core.cerberus_extend import is_int, is_float, \
     is_modern_french_date, is_multi_int
 from core.validation import BaseGeoModel
-from schemas.common import INSEE_VALIDES, XMIN, YMIN, XMAX, YMAX
+from schemas.common import XMIN, YMIN, XMAX, YMAX
 
 STATUTS = ['EN ETUDE', 'EN REALISATION', 'EN SERVICE', 'HORS SERVICE']
 
@@ -14,9 +14,9 @@ class Artere(BaseGeoModel):
     filename = "artere_geo.shp"
     geom_type = gis_.GEOM_LINE
     bounding_box = (XMIN,YMIN,XMAX,YMAX)
-    schema = {'AR_CODE': {'type': 'string', 'maxlength': 26}, 
+    schema = {'AR_CODE': {'type': 'string', 'maxlength': 26},
               'AR_NOM': {'type': 'string', 'maxlength': 26}, 
-              'AR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+              'AR_ID_INSE': {'type': 'string', 'empty': False, 'regex': r'50\d{3}'}, 
               'AR_LONG': {'empty': False, 'validator': is_float},
               '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']}, 
@@ -117,7 +117,7 @@ class Noeud(BaseGeoModel):
     bounding_box = (XMIN,YMIN,XMAX,YMAX)
     schema = {'NO_CODE': {'type': 'string', 'maxlength': 18}, 
               'NO_NOM': {'type': 'string', 'maxlength': 30}, 
-              'NO_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+              'NO_ID_INSE': {'type': 'string', 'empty': False, 'regex': r'50\d{3}'}, 
               'NO_VOIE': {'type': 'string', 'maxlength': 100}, 
               'NO_EMPRISE': {'type': 'string', 'maxlength': 10}, 
               'NO_ETAT': {'type': 'string', 'empty': False, 'allowed': ['0', '1', '2', '3', '4']}, 
@@ -152,7 +152,7 @@ class Tranchee(BaseGeoModel):
     bounding_box = (XMIN,YMIN,XMAX,YMAX)
     schema = {'TR_CODE': {'type': 'string', 'maxlength': 23}, 
               'TR_NOM': {'type': 'string', 'maxlength': 23}, 
-              'TR_ID_INSE': {'type': 'string', 'empty': False, 'allowed': INSEE_VALIDES}, 
+              'TR_ID_INSE': {'type': 'string', 'empty': False, 'regex': r'50\d{3}'}, 
               '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_MOD_POS': {'type': 'string', 'empty': False, 'allowed': ['TRADITIONNEL', 'MICRO TRANCHEE', 'FONCAGE 60', 'FONCAGE 90', 'FONCAGE 120', 'TRANCHEUSE', 'FORAGE URBAIN', 'FORAGE RURAL', 'ENCORBELLEMENT']}, 

+ 3 - 8
schemas/netgeo_2_2_doe/validator.py → schemas/mn2_rec/validator.py

@@ -2,21 +2,16 @@
 
 @author: olivier.massot, 2018
 '''
-from osgeo import ogr
-import pypyodbc
-
-from core import mn
 from core.gis_ import Feature
 from core.validation import NetgeoValidator, RelationError, UniqueError, \
     InvalidGeometry, DuplicatedGeom, MissingItem, DimensionError, \
     TechnicalValidationError, PositionError
 from schemas.common import TOLERANCE
-from schemas.netgeo_2_2_doe.models import Artere, Cable, Equipement, Noeud, \
+from schemas.mn2_rec.models import Artere, Cable, Equipement, Noeud, \
     Tranchee, Zapbo
 
-
-class Netgeo22DoeValidator(NetgeoValidator):
-    schema_name = "Netgeo v2.2 DOE"
+class Mn2RecValidator(NetgeoValidator):
+    schema_name = "MN2 REC"
     models = [Artere, Cable, Equipement, Noeud, Tranchee, Zapbo]
     
     def _technical_validation(self):

+ 0 - 0
schemas/mn3_exe/__init__.py


+ 0 - 0
schemas/mn3_pro/__init__.py


+ 0 - 0
schemas/mn3_rec/__init__.py


+ 0 - 3
schemas/netgeo_1_12_doe/__init__.py

@@ -1,3 +0,0 @@
-
-from schemas.netgeo_1_12_doe.validator import Netgeo112DoeValidator
-validator = Netgeo112DoeValidator

+ 0 - 4
schemas/netgeo_2_2_doe/__init__.py

@@ -1,4 +0,0 @@
-from schemas.netgeo_2_2_doe.validator import Netgeo22DoeValidator
-
-
-validator = Netgeo22DoeValidator

+ 90 - 13
ui/dlg_main.py

@@ -21,22 +21,99 @@
  *                                                                         *
  ***************************************************************************/
 """
+import importlib
+import logging
 
-import os
-
-from PyQt5 import uic
 from PyQt5 import QtWidgets
+from PyQt5 import uic
+from PyQt5.Qt import QIcon, QPixmap
+
+from core.constants import MAIN, RSCDIR
+
+logger = logging.getLogger("mncheck")
+
+Ui_Main, _ = uic.loadUiType(MAIN / 'ui'/ 'dlg_main.ui')
+
+SCHEMAS = ["mn1_rec", "mn2_rec", "mn3_pro", "mn3_exe", "mn3_rec"]
+
 
-FORM_CLASS, _ = uic.loadUiType(os.path.join(
-    os.path.dirname(__file__), 'dlg_main.ui'))
 
-class DlgMain(QtWidgets.QDialog, FORM_CLASS):
+class DlgMain(QtWidgets.QDialog):
     def __init__(self, parent=None):
-        """Constructor."""
         super(DlgMain, self).__init__(parent)
-        # Set up the user interface from Designer.
-        # After setupUI you can access any designer object by doing
-        # self.<objectname>, and you can use autoconnect slots - see
-        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
-        # #widgets-and-dialogs-with-auto-connect
-        self.setupUi(self)
+        self.createWidgets()
+
+    def createWidgets(self):
+        """ set up the interface """
+        self.ui = Ui_Main()
+        self.ui.setupUi(self)
+        
+        self.ui.stack.setCurrentIndex(0)
+        
+        self.ui.btn_play.setIcon(QIcon(RSCDIR / "play.png"))
+        self.ui.btn_play.clicked.connect(self.run)
+        
+        self.ui.btn_help.setIcon(QIcon(RSCDIR / "question.png"))
+        self.ui.btn_help.clicked.connect(self.show_help)
+        
+        self.ui.btn_settings.setIcon(QIcon(RSCDIR / "settings.png"))
+        self.ui.btn_settings.clicked.connect(self.show_settings)
+        
+        self.ui.lbl_hourglass.setPixmap(QPixmap(RSCDIR / "hourglass.png"))
+        self.ui.lbl_ok.setPixmap(QPixmap(RSCDIR / "ok_32.png"))
+
+        self.ui.cbb_schemas.addItem("Schéma MN v1", 0)
+        self.ui.cbb_schemas.addItem("Schéma MN v2", 1)
+        self.ui.cbb_schemas.addItem("Schéma MN v3 PRO", 2)
+        self.ui.cbb_schemas.addItem("Schéma MN v3 EXE", 3)
+        self.ui.cbb_schemas.addItem("Schéma MN v3 REC", 4)
+#         self.ui.cbb_schemas.addItem("GraceTHD v2.1", 5)
+        self.ui.cbb_schemas.currentIndexChanged.connect(self.update_layers_list)
+        
+        self.update_layers_list()
+    
+    
+    def update_layers_list(self):
+        
+        schema_lib_name = SCHEMAS[int(self.ui.cbb_schemas.itemData(self.ui.cbb_schemas.currentIndex()))]
+        
+        schema_lib = importlib.import_module("schemas." + schema_lib_name)
+
+        logger.info("Selection du schema '{%s}'", schema_lib)
+
+    def run(self):
+        pass
+        
+#         try:
+#             f = request.FILES['dossier']
+#         except KeyError:
+#             return render(request, "index.html", {"validation_error": "Aucun fichier sélectionné"})
+#         
+#         filename = secure_filename(f.name)
+#         
+#         if Path(filename).ext != ".zip":
+#             return render(request, "index.html", {"validation_error": "Le fichier doit être un fichier .ZIP ({})".format(Path(filename).ext)})
+#         
+#         schema_lib_name = request.POST['schema']
+#         schema_lib = importlib.import_module("schemas." + schema_lib_name)
+#         
+#     #     try:
+#         with TemporaryDirectory(dir=MAIN / "upload") as d:
+#             filename = Path(d) / filename
+#             with open(filename, 'wb+') as destination:
+#                 for chunk in f.chunks():
+#                     destination.write(chunk)
+#             report = schema_lib.validator.submit(filename)
+#     #     except Exception as e:
+#     #         return render_template("index.html", validation_error=str(e))
+#     
+#     return render(request, "report.html", {"report": report})
+    
+    def show_help(self):
+        pass
+    
+    def show_settings(self):
+        pass
+
+
+

+ 22 - 3
ui/dlg_main.ui

@@ -27,10 +27,10 @@
    <item>
     <widget class="QStackedWidget" name="stack">
      <property name="currentIndex">
-      <number>3</number>
+      <number>0</number>
      </property>
      <widget class="QWidget" name="page_1">
-      <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0">
+      <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0,0">
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <property name="leftMargin">
@@ -71,6 +71,19 @@
        <item>
         <layout class="QVBoxLayout" name="layers_layout"/>
        </item>
+       <item>
+        <spacer name="verticalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout">
          <property name="topMargin">
@@ -91,6 +104,9 @@
          </item>
          <item>
           <widget class="QPushButton" name="btn_help">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
            <property name="minimumSize">
             <size>
              <width>100</width>
@@ -113,6 +129,9 @@
          </item>
          <item>
           <widget class="QPushButton" name="btn_settings">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
            <property name="minimumSize">
             <size>
              <width>100</width>
@@ -219,7 +238,7 @@
           </spacer>
          </item>
          <item>
-          <widget class="QLabel" name="label_2">
+          <widget class="QLabel" name="lbl_ok">
            <property name="text">
             <string/>
            </property>