Source code for fastwater.otm_gpl.parser_kenue

r"""@author Sebastien E. Bourban

    @note ... this work is based on a collaborative effort between
  .________.                                                          ,--.
  |        |                                                      .  (  (
  |,-.    /   HR Wallingford                EDF - LNHE           / \_ \_/ .--.
  /   \  /    Howbery Park,                 6, quai Watier       \   )   /_   )
    ,.  `'     Wallingford, Oxfordshire      78401 Cedex           `-'_  __ `--
  /  \   /    OX10 8BA, United Kingdom      Chatou, France        __/ \ \ `.
 /    `-'|    www.hrwallingford.com         innovation.edf.com   |    )  )  )
!________!                                                        `--'   `--

    @brief
            Tools for handling Kenue native files in python.
            Kenue and its related software (Blue Kenue, Greem Kenue, )
            are property of the NRC Canadian Hydrualics Centre

    @details
            Contains getI2S, getI3S and putI2S, putI3S, which read/write
            python variables into ASCII I2S and I3S files

    @history 26/12/2011 -- Sebastien E. Bourban:
            First trial at parsing I2S and I3S

    @history 09/01/2012 -- Sebastien E. Bourban:
            Addition of XY and XYZ parsing

    @history 13/01/2012 -- Sebastien E. Bourban:
            Creates InS class with associated methods including:
            + removeDuplicates (remove duplicated points based on proximity)
            + make_clockwise (make closed loops clockwise)
            + make_anti_clockwise (make closed loops anti-clockwise)
            + smoothSubdivise (add points and weigthed average move)
            + smoothSubsampleDistance (remove points based on proximity)
            + smoothSubsampleAngle (remove points based on flatness)

    @history 29/02/2012 -- Sebastien E. Bourban:
            The type of a contour can now be checked automatically so that
            mixed set can be stored into one I2S/I3S file. To use this
            feature, type must be absent from the putInS call.
"""
from __future__ import print_function
# _____          ___________________________________________________
# ____/ Imports /__________________________________________________/
#
# ~~> dependencies towards standard python
import re
import numpy as np
from os import path
# ~~> dependencies towards other pytel/modules
from fastwater.otm_gpl.files import get_file_content, put_file_content
from fastwater.otm_gpl.geometry import is_close
from fastwater.otm_gpl.exceptions import TelemacException

# _____                   __________________________________________
# ____/ Global Variables /_________________________________________/
#
KEN_HEADER = re.compile(r'[#:]')

ASC_FILE_TYPE = re.compile(r':FileType\s(?P<type>\b\w\w\w\b)') #\s(?P<after>.*?)\Z')
ASC_ATTRIBUTE_NAME = re.compile(r':AttributeName\s(?P<number>\b(|[^a-zA-Z(,])(?:(\d+)(\b|[^a-zA-Z,)])))(?P<after>.*?)\Z')

VAR_1INT = re.compile(r'(?P<before>[^\'"]*?)\b(?P<number>[+-]?(|[^a-zA-Z(,])(?:(\d+)(\b|[^a-zA-Z,)])))(?P<after>[^.].*?)\Z')
VAR_1DBL = re.compile(r'(?P<number>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))[\s,;]*(?P<after>.*?)\Z')
VAR_2DBL = re.compile(r'(?P<number1>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))[\s,;]*(?P<number2>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))(?P<after>.*?)\Z')
VAR_3DBL = re.compile(r'(?P<number1>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))[\s,;]*(?P<number2>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))[\s,;]*(?P<number3>[+-]?(|[^a-zA-Z(,])(?:(\d+(|\.)\d*[dDeE](\+|\-)?\d+|\d+\.\d+)(\b|[^a-zA-Z,)])))(?P<after>.*?)\Z')
VAR_1STR = re.compile(r'(?P<string>)(".*?")[\s,;]*(?P<after>.*?)\Z')

# _____                  ___________________________________________
# ____/ General Toolbox /__________________________________________/
#

# _____                              _______________________________
# ____/ Principal Class for I2S/I3S /______________________________/
#


[docs]class InS(object): def __init__(self, file_name): self.atrbut = None self.natrbut = 0 self.oatrbut = None self.file_type = 0 self.head = None self.poly = None self.vals = None self.type = None self.npoin = None self.npoly = 0 # file parsing is based on the name of the extension _, tail = path.splitext(file_name) # ~~> Case of a Kenue type i2s/i3s file if tail in ['.i2s', '.i3s']: self.parser_content(file_name) else: raise TelemacException( '\nThe polygon file extension is required to be ' 'either i2s or i3s') # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< # ~~~~ Parse Content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
[docs] def parser_content(self, file_name): # TODO: Read the whole header # ~~ Get all ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ core = get_file_content(file_name) # ~~ Parse head ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ icore = 0 self.atrbut = {} self.natrbut = 0 self.oatrbut = [] while re.match(KEN_HEADER, core[icore].strip()): # ~~> instruction FileType proc = re.match(ASC_FILE_TYPE, core[icore].strip()) if proc: self.file_type = proc.group('type').lower() # ~~> instruction AttributeName proc = re.match(ASC_ATTRIBUTE_NAME, core[icore].strip()) if proc: self.natrbut += 1 if self.natrbut == int(proc.group('number')): self.oatrbut.append(proc.group('after').strip()) self.atrbut.update({self.oatrbut[-1]: []}) else: raise TelemacException( '... Could not read the order of your Attributes: ' '{}'.format(core[icore])) # ... more instruction coming ... icore += 1 self.head = core[0:icore] # /!\ icore starts the body # ~~ Parse body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This is also fairly fast, so you might not need a progress bar self.poly = [] self.vals = [] self.type = [] self.npoin = 0 while icore < len(core): if core[icore].strip() == '': icore += 1 continue # ~~> polygon head proc = re.match(VAR_1INT, core[icore].strip()) if not proc: raise TelemacException( '\nCould not parse the following polyline header: ' '{}'.format(core[icore].strip())) nrec = int(proc.group('number')) after = proc.group('after').strip().split() if len(after) != self.natrbut: if self.natrbut != 0: raise TelemacException( '... Could not find the correct number of attribute:' '{}, {} expected'.format(core[icore].strip(), self.natrbut)) else: self.natrbut = len(after) self.oatrbut = range(1, len(after)+1) self.atrbut = \ dict([(i+1, [after[i]]) for i in range(len(after))]) else: for i in range(len(self.oatrbut)): self.atrbut[self.oatrbut[i]].append(after[i]) xyi = [] val = [] icore += 1 for irec in range(nrec): nbres = core[icore+irec].strip().split() proc = re.match(VAR_1DBL, nbres[0]) if not proc: proc = re.match(VAR_1INT, nbres[0]) if not proc: raise TelemacException( '\nCould not parse the following polyline record: ' '{}'.format(core[icore+irec].strip())) nbres[0] = float(proc.group('number')) procd = re.match(VAR_1DBL, nbres[1]) proci = re.match(VAR_1INT, nbres[1]) if procd: nbres[1] = float(procd.group('number')) elif proci: nbres[1] = float(procd.group('number')) xyi.append(nbres[:2]) val.append(nbres[2:]) if xyi != []: cls = 0 if is_close(xyi[0], xyi[len(xyi)-1], size=10): xyi.pop(len(xyi)-1) val.pop(len(val)-1) cls = 1 self.poly.append(np.asarray(xyi, dtype=np.float)) self.vals.append(np.asarray(val, dtype=np.float)) self.type.append(cls) self.npoin += len(xyi) icore += nrec self.npoly = len(self.poly)
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< # ~~~~ Write-up Content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # Note: # + file parsing is based on the name of the extension #
[docs] def put_content(self, file_name, head=None): # ~~> file extension processing _, tail = path.splitext(file_name) if tail[1:] != self.file_type: if head is not None: head = ['\n'.join(head).replace(':FileType '+self.file_type, ':FileType '+tail[1:])] self.file_type = tail[1:] # ~~> write head if head != []: core = head else: core = [':FileType '+self.file_type+' ASCII EnSim 1.0', ':Application BlueKenue', ':Version 3.2.24', ':WrittenBy sebourban', ':CreationDate Thu, Dec 08, 2011 02:47 PM', ':Name ' + path.basename(file_name), # ':AttributeName 1 level', # ':AttributeType 1 float', # ':AttributeUnits 1 m', ':EndHeader'] # ~~> look for closed lines if self.type == []: for i_p in self.poly: if is_close(i_p[0][:2], i_p[len(i_p)-1][:2]): self.type.append(1) else: self.type.append(0) # ~~> fill-up empty attributes if self.atrbut == {}: self.atrbut = {1: ['ArbitraryName1']} for _ in self.poly: self.atrbut[1].append(0) self.oatrbut = [1] # ~~> fill-up attribute names if head == []: for i, val in enumerate(self.oatrbut): core.insert(-1, ':AttributeName '+repr(i+1)+' '+repr(val)) # ~~ Write body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for i, ipoly, ival, itype in zip(range(len(self.poly)), self.poly, self.vals, self.type): i_l = len(ipoly) if i_l != 0 and not is_close(ipoly[0], ipoly[len(ipoly)-1]): i_l += itype line = repr(i_l) for oatr in self.oatrbut: line += ' ' + str(self.atrbut[oatr][i]) core.append(line) if self.file_type == 'i2s': for xyi in ipoly: core.append(repr(xyi[0])+' '+repr(xyi[1])) if i_l != len(ipoly): core.append(repr(ipoly[0][0])+' '+repr(ipoly[0][1])) elif self.file_type == 'i3s': if np.shape(ival)[1] == 0: for xyi in ipoly: core.append(repr(xyi[0])+' '+repr(xyi[1])+' 0.0') if i_l != len(ipoly): core.append(repr(ipoly[0][0])+' ' + repr(ipoly[0][1])+' 0.0') else: for xyi, val in zip(ipoly, ival): core.append(repr(xyi[0])+' '+repr(xyi[1])+' ' + ' '.join([repr(v) for v in val])) if i_l != len(ipoly): core.append(repr(ipoly[0][0])+' '+repr(ipoly[0][1]) + ' '+' '.join([repr(v) for v in ival[0]])) # ~~ Put all ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ put_file_content(file_name, core)
# _____ ___________________________________________ # ____/ Toolbox for XYZ /__________________________________________/ #
[docs]def get_xyn(file_name): # TODO: Read the whole header, for the time being head is copied # over # TODO: Read multiple variables depending on type and on a list # ~~ Get all ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ core = get_file_content(file_name) # ~~ Parse head ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ icore = 0 file_type = None while re.match(KEN_HEADER, core[icore]): # ~~> instruction FileType proc = re.match(ASC_FILE_TYPE, core[icore]) if proc: file_type = proc.group('type').lower() # ... more instruction coming ... icore += 1 head = core[0:icore] if file_type is None: proc = re.match(VAR_3DBL, core[icore]+' ') if not proc: proc = re.match(VAR_2DBL, core[icore]+' ') if not proc: raise TelemacException( '\nCould not parse the first record: ' '{}'.format(core[icore])) else: file_type = 'xy' else: file_type = 'xyz' # /!\ icore starts the body # ~~ Parse body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This is also fairly fast, so you might not need a progress bar xyz = [] # ; pbar = ProgressBar(maxval=len(core)).start() while icore < len(core): if file_type == 'xy': proc = re.match(VAR_2DBL, core[icore]+' ') if not proc: raise TelemacException( '\nCould not parse the following xyz record: ' '{}'.format(core[icore])) xyz.append([float(proc.group('number1')), float(proc.group('number2'))]) elif file_type == 'xyz': proc = re.match(VAR_3DBL, core[icore]+' ') if not proc: raise TelemacException( '\nCould not parse the following xyz record: ' '{}'.format(core[icore])) xyz.append([float(proc.group('number1')), float(proc.group('number2')), float(proc.group('number3'))]) icore += 1 # pbar.finish() return head, file_type, xyz
[docs]def put_xyn(fle, head, file_type, xyz): # ~~ Write head ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ core = head # if head != []: core = head # <else: core = [':FileType '+file_type+' ASCII EnSim 1.0', # ':Application BlueKenue', ':Version 3.2.24', # ':WrittenBy sebourban', ':CreationDate Thu, Dec 08, 2011 02:47 PM', # ':Name ' + path.basename(file_name), # ':AttributeUnits 1 m', # ':EndHeader' ] # ~~ Write body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if file_type == 'xy': for _ in xyz: core.append(str(xyz[0])+' '+str(xyz[1])) elif file_type == 'xyz': for _ in xyz: core.append(str(xyz[0])+' '+str(xyz[1])+' '+str(xyz[2])) # ~~ Put all ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ put_file_content(fle, core) return