From 775d60b031d5f3b5aed95bf06e62c4c40e00ea69 Mon Sep 17 00:00:00 2001 From: softlandia Date: Tue, 28 Apr 2020 23:04:01 +0400 Subject: [PATCH] v0.1.7 add Checker --- HIST.md | 6 +- data/dic.ini | 13 +- data/mnemonic.ini | 6 + data/more_20_warnings.wrn | 58 ++-- data/test-curve-sec-empty-mnemonic+.las | 2 +- data/test-curve-sec-empty-mnemonic.las | 2 +- data/w1_more_20_warnings.txt | 4 +- las.go | 406 ++++++++++++++---------- las_checker.go | 190 ++++++++++- las_logger.go | 67 +++- las_summary_test.go | 59 +++- las_test.go | 100 ++++-- las_util.go | 62 ++-- las_warning.go | 2 +- logger_test.go | 17 + ver.txt | 2 +- 16 files changed, 713 insertions(+), 283 deletions(-) create mode 100644 logger_test.go diff --git a/HIST.md b/HIST.md index 596106d..d028afc 100644 --- a/HIST.md +++ b/HIST.md @@ -1,4 +1,8 @@ -## ver 0.1.6 // 2020.04.13 ## +## ver 0.1.7 // 2020.04.28 ## + +- внедрена первая версия модуля Checker, пока на слайсах, ожидается переделка на map + +## ver 0.1.6 // 2020.04.13 ## - мелкие улучшения diff --git a/data/dic.ini b/data/dic.ini index 4296746..4c55ed8 100644 --- a/data/dic.ini +++ b/data/dic.ini @@ -86,6 +86,8 @@ Kavernomer= CALI КАВ-9_10 = CALI КАВ26 = CALI КАВ268 = CALI +CAL = CALI +CALvh = CALI DN = BIN DN_ = BIN @@ -94,6 +96,7 @@ Dnom = BIN ДСн = BIN ДСном = BIN Дн = BIN +CAL_nom = BIN ДТП = DTP DТ = DTP @@ -211,6 +214,7 @@ A1M0 = GZ2 A125M01N= GZ2 À2Ì05N = GZ2 +GZ = GZ3 A2* = GZ3 A2*_O = GZ3 A2*_O_O = GZ3 @@ -361,6 +365,7 @@ IK Omm = 6F1I IKOmm = 6F1I ИКомм = 6F1I ИКомм24 = 6F1I +RILD = 6F1I BK = LL3 BK14 = LL3 @@ -383,6 +388,7 @@ BK_Р = LL3 ВК_Р = LL3 бк = LL3 LLD = LL3 +LL8 = LL3 MBK = MLL BMK = MLL @@ -543,6 +549,8 @@ GGP_Р = RHOB ГГКПсем = RHOB ГГКР = RHOB ПЛ_ГГ = RHOB +DEN = RHOB +DENkz = RHOB GGK-bz = GGKbz ГГКбз = GGKbz @@ -571,5 +579,6 @@ Rez = RM ннкбз = RFEN ннкмз = RNEN -Кгл = VCL -КП = POR +Кгл = VCL +КП = POR +So = SOIL diff --git a/data/mnemonic.ini b/data/mnemonic.ini index 35cb26d..678c8d2 100644 --- a/data/mnemonic.ini +++ b/data/mnemonic.ini @@ -36,3 +36,9 @@ GGKmz = ГГКп бз OGZ = ГЗ обр RFEN = ННК бз RNEN = ННК мз +COLL = Признак коллектора +ZONELOG = номер пласта +POR = пористость +SOIL = насыщенность +VCL = глинистость +SAT = признак насыщения \ No newline at end of file diff --git a/data/more_20_warnings.wrn b/data/more_20_warnings.wrn index a0f016f..b9ac504 100644 --- a/data/more_20_warnings.wrn +++ b/data/more_20_warnings.wrn @@ -1,46 +1,42 @@ **file: data\more_20_warnings.las** 0, line: -1, desc: STEP parameter equal 0, replace to 0.000 -1, line: -1, desc: invalid STRT: 0.000 or STOP: 0.000, will be replace to actually -2, line: 24, desc: can't convert string: '6.2.2' to number, set to NULL +1, line: 24, desc: can't convert string: '6.2.2' to number, set to NULL +2, line: 25, desc: not all column readed, set log value to NULL 3, line: 25, desc: not all column readed, set log value to NULL -4, line: 25, desc: not all column readed, set log value to NULL -5, line: 27, desc: actual step 1.00 ≠ global STEP 0.00 +4, line: 26, desc: actual number of data lines more than expected, check: STRT, STOP, STEP +5, line: 26, desc: expand number of points 6, line: 27, desc: can't convert string: '6.2.2' to number, set to NULL -7, line: 29, desc: actual step 1.00 ≠ global STEP 0.00 -8, line: 29, desc: can't convert string: '5.97.' to number, set to NULL -9, line: 31, desc: actual step 1.00 ≠ global STEP 0.00 +7, line: 29, desc: can't convert string: '5.97.' to number, set to NULL +8, line: 30, desc: actual number of data lines more than expected, check: STRT, STOP, STEP +9, line: 30, desc: expand number of points 10, line: 31, desc: can't convert string: '6.47.' to number, set to NULL -11, line: 33, desc: actual step 64.00 ≠ global STEP 0.00 +11, line: 33, desc: actual step 64.00 ≠ global STEP 1.00 12, line: 33, desc: step 64.00 ≠ previously step 1.00 13, line: 33, desc: can't convert string: '5.97.' to number, set to NULL -14, line: 35, desc: actual step 47.00 ≠ global STEP 0.00 +14, line: 35, desc: actual step 47.00 ≠ global STEP 1.00 15, line: 35, desc: step 47.00 ≠ previously step 64.00 16, line: 35, desc: can't convert string: '5.47.' to number, set to NULL -17, line: 37, desc: actual step 1.00 ≠ global STEP 0.00 -18, line: 37, desc: step 1.00 ≠ previously step 47.00 -19, line: 37, desc: can't convert string: '4.7.' to number, set to NULL -20, line: 38, desc: actual step 1.00 ≠ global STEP 0.00 +17, line: 37, desc: step 1.00 ≠ previously step 47.00 +18, line: 37, desc: can't convert string: '4.7.' to number, set to NULL +19, line: 38, desc: actual number of data lines more than expected, check: STRT, STOP, STEP +20, line: 38, desc: expand number of points 21, line: 38, desc: can't convert string: '4.7.' to number, set to NULL 22, line: 40, desc: line: 40 is empty, ignore -23, line: 41, desc: actual step -117.20 ≠ global STEP 0.00 +23, line: 41, desc: actual step -117.20 ≠ global STEP 1.00 24, line: 41, desc: step -117.20 ≠ previously step 1.00 25, line: 41, desc: not all column readed, set log value to NULL -26, line: 42, desc: actual step 170.20 ≠ global STEP 0.00 +26, line: 42, desc: actual step 170.20 ≠ global STEP 1.00 27, line: 42, desc: step 170.20 ≠ previously step -117.20 -28, line: 43, desc: actual step 1.00 ≠ global STEP 0.00 -29, line: 43, desc: step 1.00 ≠ previously step 170.20 -30, line: 44, desc: actual step 1.00 ≠ global STEP 0.00 -31, line: 45, desc: actual step 4.00 ≠ global STEP 0.00 -32, line: 45, desc: step 4.00 ≠ previously step 1.00 -33, line: 46, desc: actual step 1.00 ≠ global STEP 0.00 -34, line: 46, desc: step 1.00 ≠ previously step 4.00 -35, line: 48, desc: actual step 1.00 ≠ global STEP 0.00 -36, line: 49, desc: actual step 3.00 ≠ global STEP 0.00 -37, line: 49, desc: step 3.00 ≠ previously step 1.00 -38, line: 50, desc: actual step 1.00 ≠ global STEP 0.00 -39, line: 50, desc: step 1.00 ≠ previously step 3.00 -40, line: 51, desc: actual step 1.00 ≠ global STEP 0.00 -41, line: 52, desc: actual step 1.00 ≠ global STEP 0.00 -42, line: 53, desc: actual step 1.00 ≠ global STEP 0.00 -43, line: 54, desc: step 0.00 ≠ previously step 1.00 +28, line: 43, desc: step 1.00 ≠ previously step 170.20 +29, line: 45, desc: actual step 4.00 ≠ global STEP 1.00 +30, line: 45, desc: step 4.00 ≠ previously step 1.00 +31, line: 46, desc: step 1.00 ≠ previously step 4.00 +32, line: 49, desc: actual number of data lines more than expected, check: STRT, STOP, STEP +33, line: 49, desc: expand number of points +34, line: 49, desc: actual step 3.00 ≠ global STEP 1.00 +35, line: 49, desc: step 3.00 ≠ previously step 1.00 +36, line: 50, desc: step 1.00 ≠ previously step 3.00 +37, line: 54, desc: actual step 0.00 ≠ global STEP 1.00 +38, line: 54, desc: step 0.00 ≠ previously step 1.00 +39, line: 55, desc: actual step 0.00 ≠ global STEP 1.00 diff --git a/data/test-curve-sec-empty-mnemonic+.las b/data/test-curve-sec-empty-mnemonic+.las index eed4239..9d1e4e4 100644 --- a/data/test-curve-sec-empty-mnemonic+.las +++ b/data/test-curve-sec-empty-mnemonic+.las @@ -18,7 +18,7 @@ UWI . WELL ID : 100091604920W300 D.M : 1 DEPTH A.US/M : 2 SONIC TRANSIT TIME B.K/M3 : 3 BULK DENSITY -C.V/V : 4 NEUTRON POROSITY +SP.V/V : 4 SP -EL- . : -EL-1.m : -EL-2.v/v : diff --git a/data/test-curve-sec-empty-mnemonic.las b/data/test-curve-sec-empty-mnemonic.las index 84fd817..9ba35d9 100644 --- a/data/test-curve-sec-empty-mnemonic.las +++ b/data/test-curve-sec-empty-mnemonic.las @@ -18,7 +18,7 @@ UWI . WELL ID : 100091604920W300 D.M : 1 DEPTH A.US/M : 2 SONIC TRANSIT TIME B.K/M3 : 3 BULK DENSITY -C.V/V : 4 NEUTRON POROSITY +SP.V/V : 4 NEUTRON POROSITY . : .m : .v/v : diff --git a/data/w1_more_20_warnings.txt b/data/w1_more_20_warnings.txt index 8b91799..d3d69d5 100644 --- a/data/w1_more_20_warnings.txt +++ b/data/w1_more_20_warnings.txt @@ -1,6 +1,6 @@ **file: data\more_20_warnings.las** -0, line: -1, desc: STEP parameter equal 0, replace to 0.000 -1, line: -1, desc: invalid STRT: 0.000 or STOP: 0.000, will be replace to actually +0, line: -1, desc: invalid STRT: 0.000 == STOP: 0.000, will be replace to actually +1, line: -1, desc: STEP parameter equal 0, replace to 0.000 2, line: 24, desc: can't convert string: '6.2.2' to number, set to NULL 3, line: 25, desc: not all column readed, set log value to NULL 4, line: 25, desc: not all column readed, set log value to NULL diff --git a/las.go b/las.go index 8a76828..70771a7 100644 --- a/las.go +++ b/las.go @@ -1,3 +1,6 @@ +// (c) softland 2020 +// softlandia@gmail.com + package glasio import ( @@ -72,6 +75,7 @@ type LasWellInfo struct { // at read file always code page converted to UTF // at save file code page converted to specifyed in Las.toCodePage //TODO add pointer to cfg +//TODO при создании объекта las есть возможность указать кодировку записи, нужна возможность указать явно кодировку чтения type Las struct { FileName string //file name from load File *os.File //the file from which we are reading @@ -96,24 +100,23 @@ type Las struct { currentLine int //index of current line in readed file maxWarningCount int //default maximum warning count stdNull float64 //default null value - //iCodepage cpd.IDCodePage //codepage input file - autodetected } // GetStepFromData - return step from data section // read 2 line from section ~A and determine step // close file -// return o.Null if error occure +// return Null if error occure // если делать функцией, не методом, то придётся NULL передавать. а оно надо вообще -func (o *Las) GetStepFromData() float64 { - iFile, err := os.Open(o.FileName) +func (las *Las) GetStepFromData() float64 { + iFile, err := os.Open(las.FileName) if err != nil { - return o.Null + return las.Null } defer iFile.Close() - _, iScanner, err := xlib.SeekFileStop(o.FileName, "~A") + _, iScanner, err := xlib.SeekFileStop(las.FileName, "~A") if (err != nil) || (iScanner == nil) { - return o.Null + return las.Null } s := "" @@ -131,7 +134,7 @@ func (o *Las) GetStepFromData() float64 { } dept1, err = strconv.ParseFloat(s[:k], 64) if err != nil { - return o.Null + return las.Null } j++ if j == 2 { @@ -140,30 +143,30 @@ func (o *Las) GetStepFromData() float64 { dept2 = dept1 } //если мы попали сюда, то всё грусно, в файле после ~A не нашлось двух строчек с данными... или пустые строчки или комменты - // TODO последняя строка "return o.Null" не обрабатывается в тесте - return o.Null + // TODO последняя строка "return las.Null" не обрабатывается в тесте + return las.Null } -func (o *Las) setStep(h float64) { - o.Step = h +func (las *Las) setStep(h float64) { + las.Step = h } //SetNull - change parameter NULL in WELL INFO section and in all logs -func (o *Las) SetNull(aNull float64) error { - for _, l := range o.Logs { //loop by logs +func (las *Las) SetNull(aNull float64) error { + for _, l := range las.Logs { //loop by logs for i := range l.log { //loop by dept step - if l.log[i] == o.Null { + if l.log[i] == las.Null { l.log[i] = aNull } } } - o.Null = aNull + las.Null = aNull return nil } //logByIndex - return log from map by Index -func (o *Las) logByIndex(i int) (*LasCurve, error) { - for _, v := range o.Logs { +func (las *Las) logByIndex(i int) (*LasCurve, error) { + for _, v := range las.Logs { if v.Index == i { return &v, nil } @@ -205,6 +208,7 @@ func NewLas(outputCP ...cpd.IDCodePage) *Las { return las } +/* // NewLasPar - create new object with parameters func NewLasPar(lasInfo LasWellInfo) *Las { las := new(Las) @@ -225,13 +229,14 @@ func NewLasPar(lasInfo LasWellInfo) *Las { las.iDuplicate = 0 return las } +*/ -//analize first char after ~ -//~V - section vertion -//~W - well info section -//~C - curve info section -//~A - data section -func (o *Las) selectSection(r rune) int { +// selectSection - analize first char after ~ +// ~V - section vertion +// ~W - well info section +// ~C - curve info section +// ~A - data section +func (las *Las) selectSection(r rune) int { switch r { case 86: //V return lasSecVertion //version section @@ -302,56 +307,56 @@ func (hc *HeaderCheckRes) addNullWarning() { // - double error on STEP parameter // - las file is WRAP == ON // - Curve section not exist -func (o *Las) checkHeader() (HeaderCheckRes, error) { +func (las *Las) checkHeader() (HeaderCheckRes, error) { res := make(HeaderCheckRes, 0) - if o.Null == 0.0 { + if las.Null == 0.0 { res.addNullWarning() - o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("NULL parameter equal 0, replace to %4.3f", o.Null)}) + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("NULL parameter equal 0, replace to %4.3f", las.Null)}) } - if o.Step == 0.0 { + if las.Step == 0.0 { res.addStepWarning() - o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("STEP parameter equal 0, replace to %4.3f", o.Step)}) + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("STEP parameter equal 0, replace to %4.3f", las.Step)}) } - if math.Abs(o.Stop-o.Strt) < 0.1 { - o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("invalid STRT: %4.3f or STOP: %4.3f, will be replace to actually", o.Strt, o.Stop)}) + if math.Abs(las.Stop-las.Strt) < 0.1 { + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("invalid STRT: %4.3f or STOP: %4.3f, will be replace to actually", las.Strt, las.Stop)}) } - if o.IsWraped() { - o.addWarning(TWarning{directOnRead, lasSecData, -1, "WRAP = YES, file ignored"}) + if las.IsWraped() { + las.addWarning(TWarning{directOnRead, lasSecData, -1, "WRAP = YES, file ignored"}) return res, fmt.Errorf("Wrapped files not support") //return 0, nil } - if len(o.Logs) <= 0 { - o.addWarning(TWarning{directOnRead, lasSecData, -1, "section ~Curve not exist, file ignored"}) + if len(las.Logs) <= 0 { + las.addWarning(TWarning{directOnRead, lasSecData, -1, "section ~Curve not exist, file ignored"}) return res, fmt.Errorf("Curve section not exist") //return 0, nil } return res, nil } // IsWraped - return true if WRAP == YES -func (o *Las) IsWraped() bool { - return strings.Contains(strings.ToUpper(o.Wrap), "Y") //(strings.Index(strings.ToUpper(o.Wrap), "Y") >= 0) +func (las *Las) IsWraped() bool { + return strings.Contains(strings.ToUpper(las.Wrap), "Y") //(strings.Index(strings.ToUpper(o.Wrap), "Y") >= 0) } // SaveWarning - save to file all warning -func (o *Las) SaveWarning(fileName string) error { - if o.Warnings.Count() == 0 { +func (las *Las) SaveWarning(fileName string) error { + if las.Warnings.Count() == 0 { return nil } oFile, err := os.Create(fileName) if err != nil { return err } - o.SaveWarningToFile(oFile) + las.SaveWarningToFile(oFile) oFile.Close() return nil } // SaveWarningToWriter - store all warning to writer, return count lines writed to -func (o *Las) SaveWarningToWriter(writer *bufio.Writer) int { - n := o.Warnings.Count() +func (las *Las) SaveWarningToWriter(writer *bufio.Writer) int { + n := las.Warnings.Count() if n == 0 { return 0 } - for _, w := range o.Warnings { + for _, w := range las.Warnings { writer.WriteString(w.String()) writer.WriteString("\n") } @@ -359,24 +364,24 @@ func (o *Las) SaveWarningToWriter(writer *bufio.Writer) int { } // SaveWarningToFile - store all warning to file, file not close. return count warning writed -func (o *Las) SaveWarningToFile(oFile *os.File) int { +func (las *Las) SaveWarningToFile(oFile *os.File) int { if oFile == nil { return 0 } - if o.Warnings.Count() == 0 { + if las.Warnings.Count() == 0 { return 0 } - oFile.WriteString("**file: " + o.FileName + "**\n") - n := o.Warnings.SaveWarningToFile(oFile) + oFile.WriteString("**file: " + las.FileName + "**\n") + n := las.Warnings.SaveWarningToFile(oFile) oFile.WriteString("\n") return n } -func (o *Las) addWarning(w TWarning) { - if o.Warnings.Count() < o.maxWarningCount { - o.Warnings = append(o.Warnings, w) - if o.Warnings.Count() == o.maxWarningCount { - o.Warnings = append(o.Warnings, TWarning{0, 0, 0, "*maximum count* of warning reached, change parameter 'maxWarningCount' in 'glas.ini'"}) +func (las *Las) addWarning(w TWarning) { + if las.Warnings.Count() < las.maxWarningCount { + las.Warnings = append(las.Warnings, w) + if las.Warnings.Count() == las.maxWarningCount { + las.Warnings = append(las.Warnings, TWarning{0, 0, 0, "*maximum count* of warning reached, change parameter 'maxWarningCount' in 'glas.ini'"}) } } } @@ -384,15 +389,15 @@ func (o *Las) addWarning(w TWarning) { // GetMnemonic - return Mnemonic from dictionary by Log Name, // if Mnemonic not found return "" // if Dictionary is nil, then return "" -func (o *Las) GetMnemonic(logName string) string { - if (o.LogDic == nil) || (o.VocDic == nil) { +func (las *Las) GetMnemonic(logName string) string { + if (las.LogDic == nil) || (las.VocDic == nil) { return "" //"-" } - _, ok := (*o.LogDic)[logName] + _, ok := (*las.LogDic)[logName] if ok { //GOOD - название каротажа равно мнемонике return logName } - v, ok := (*o.VocDic)[logName] + v, ok := (*las.VocDic)[logName] if ok { //POOR - название загружаемого каротажа найдено в словаре подстановок, мнемоника найдена return v } @@ -405,8 +410,58 @@ func (o *Las) GetMnemonic(logName string) string { // - file cannot be decoded to UTF-8 // - las is wrapped // - las file not contain Curve section -func (o *Las) Open(fileName string) (int, error) { - //TODO при создании объекта las есть возможность указать кодировку записи, нужна возможность указать явно кодировку чтения +func (las *Las) Open(fileName string) (int, error) { + var err error + las.File, err = os.Open(fileName) + if err != nil { + return 0, err + } + defer las.File.Close() + las.FileName = fileName + //create and store Reader, this reader decode to UTF-8 + las.Reader, err = cpd.NewReader(las.File) + if err != nil { + return 0, err + } + las.scanner = bufio.NewScanner(las.Reader) + las.currentLine = 0 + las.LoadHeader() + // проверки на фатальные ошибки + wrongChecker := NewWrongChecker() + r := wrongChecker.check(las) + if r.wrapWrong() { + las.addWarning(TWarning{directOnRead, lasSecData, -1, "WRAP = YES, file ignored"}) + return 0, fmt.Errorf("Wrapped files not support") + } + if r.curvesWrong() { + las.addWarning(TWarning{directOnRead, lasSecData, -1, "section ~Curve not exist, file ignored"}) + return 0, fmt.Errorf("Curve section not exist") + } + // фатальные ошибки в заголовке исключены + // делаем стандартные проверки заголовка + stdChecker := NewStdChecker() + r = stdChecker.check(las) + if r.nullWrong() { + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("NULL parameter equal 0, replace to %4.3f", las.Null)}) + las.SetNull(las.stdNull) + } + if r.strtStopWrong() { + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("invalid STRT: %4.3f == STOP: %4.3f, will be replace to actually", las.Strt, las.Stop)}) + } + + if r.stepWrong() { + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("STEP parameter equal 0, replace to %4.3f", las.Step)}) + h := las.GetStepFromData() // return o.Null if cannot calculate step from data + if h == las.Null { + return 0, errors.New("invalid STEP parameter and invalid step in data") + } + las.setStep(h) + } + return las.ReadDataSec(fileName) +} + +/* +func (o *Las) open1(fileName string) (int, error) { var err error o.File, err = os.Open(fileName) if err != nil { @@ -422,12 +477,12 @@ func (o *Las) Open(fileName string) (int, error) { o.scanner = bufio.NewScanner(o.Reader) o.currentLine = 0 o.LoadHeader() + // проверка корректности данных секции WELL INFO перез загрузкой данных res, err := o.checkHeader() // res содержит несколько сообщений связанных с корректностью заголовка las файла if err != nil { - return 0, err // дальше читать файл смысла нет, или с файл переносами или нет секции Curve ... + return 0, err // дальше читать файл смысла нет, или файл с переносами или нет секции Curve ... } - err = nil // обрабатываем изменение параметров las файла по результатам чтения заголовка if res.needUpdateNull() { o.SetNull(o.stdNull) @@ -441,6 +496,7 @@ func (o *Las) Open(fileName string) (int, error) { } return o.ReadDataSec(fileName) } +*/ /*LoadHeader - read las file and load all section before ~A secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - A data @@ -451,25 +507,25 @@ func (o *Las) Open(fileName string) (int, error) { 5. читаем одну строку (это один параметер из известной нам секции) Пока ошибку всегда возвращает nil, причин возвращать другое значение пока нет. */ -func (o *Las) LoadHeader() error { +func (las *Las) LoadHeader() error { s := "" var err error secNum := 0 - for i := 0; o.scanner.Scan(); i++ { - s = strings.TrimSpace(o.scanner.Text()) - o.currentLine++ + for i := 0; las.scanner.Scan(); i++ { + s = strings.TrimSpace(las.scanner.Text()) + las.currentLine++ if isIgnoredLine(s) { continue } if s[0] == '~' { //start new section - secNum = o.selectSection(rune(s[1])) + secNum = las.selectSection(rune(s[1])) if secNum == lasSecData { break // dAta section read after //exit from for } } else { - err = o.ReadParameter(s, secNum) //if not comment, not empty and not new section => parameter, read it + err = las.ReadParameter(s, secNum) //if not comment, not empty and not new section => parameter, read it if err != nil { - o.addWarning(TWarning{directOnRead, secNum, -1, fmt.Sprintf("while process parameter: '%s' occure error: %v", s, err)}) + las.addWarning(TWarning{directOnRead, secNum, -1, fmt.Sprintf("while process parameter: '%s' occure error: %v", s, err)}) } } } @@ -477,53 +533,53 @@ func (o *Las) LoadHeader() error { } // ReadParameter - read one parameter -func (o *Las) ReadParameter(s string, secNum int) error { +func (las *Las) ReadParameter(s string, secNum int) error { switch secNum { case lasSecVertion: - return o.readVersionParam(s) + return las.readVersionParam(s) case lasSecWellInfo: - return o.ReadWellParam(s) + return las.ReadWellParam(s) case lasSecCurInfo: - return o.readCurveParam(s) + return las.readCurveParam(s) } return nil } -func (o *Las) readVersionParam(s string) error { +func (las *Las) readVersionParam(s string) error { var err error p := NewLasParam(s) switch p.Name { case "VERS": - o.Ver, err = strconv.ParseFloat(p.Val, 64) + las.Ver, err = strconv.ParseFloat(p.Val, 64) case "WRAP": - o.Wrap = p.Val + las.Wrap = p.Val } return err } //ReadWellParam - read parameter from WELL section -func (o *Las) ReadWellParam(s string) error { +func (las *Las) ReadWellParam(s string) error { var err error p := NewLasParam(s) switch p.Name { case "STRT": - o.Strt, err = strconv.ParseFloat(p.Val, 64) + las.Strt, err = strconv.ParseFloat(p.Val, 64) case "STOP": - o.Stop, err = strconv.ParseFloat(p.Val, 64) + las.Stop, err = strconv.ParseFloat(p.Val, 64) case "STEP": - o.Step, err = strconv.ParseFloat(p.Val, 64) + las.Step, err = strconv.ParseFloat(p.Val, 64) case "NULL": - o.Null, err = strconv.ParseFloat(p.Val, 64) + las.Null, err = strconv.ParseFloat(p.Val, 64) case "WELL": - if o.Ver < 2.0 { - o.Well = p.Desc + if las.Ver < 2.0 { + las.Well = p.Desc } else { - //o.Well = p.Val - o.Well = wellNameFromParam(p) + //las.Well = p.Val + las.Well = wellNameFromParam(p) } } if err != nil { - o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("detected param: %v, unit:%v, value: %v\n", p.Name, p.Unit, p.Val)}) + las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("detected param: %v, unit:%v, value: %v\n", p.Name, p.Unit, p.Val)}) } return err } @@ -532,11 +588,11 @@ func (o *Las) ReadWellParam(s string) error { //if input name unique, return input name //if input name not unique, return input name + index duplicate //index duplicate - Las field, increase -func (o *Las) ChangeDuplicateLogName(name string) string { +func (las *Las) ChangeDuplicateLogName(name string) string { s := "" - if _, ok := o.Logs[name]; ok { - o.iDuplicate++ - s = fmt.Sprintf("%v", o.iDuplicate) + if _, ok := las.Logs[name]; ok { + las.iDuplicate++ + s = fmt.Sprintf("%v", las.iDuplicate) name += s } return name @@ -548,69 +604,69 @@ func (o *Las) ChangeDuplicateLogName(name string) string { //IName - имя каротажа в исходном файле, может повторятся //Name - ключ в map хранилище, повторятся не может. если в исходном есть повторение, то Name строится добавлением к IName индекса //Mnemonic - мнемоника, берётся из словаря, если в словаре не найдено, то "" -func (o *Las) readCurveParam(s string) error { +func (las *Las) readCurveParam(s string) error { l := NewLasCurve(s) - l.Init(len(o.Logs), o.GetMnemonic(l.Name), o.ChangeDuplicateLogName(l.Name), o.GetExpectedPointsCount()) - o.Logs[l.Name] = l //добавление в хранилище кривой каротажа с колонкой глубин + l.Init(len(las.Logs), las.GetMnemonic(l.Name), las.ChangeDuplicateLogName(l.Name), las.GetExpectedPointsCount()) + las.Logs[l.Name] = l //добавление в хранилище кривой каротажа с колонкой глубин return nil } //GetExpectedPointsCount - оценка количества точек по параметрам STEP, STRT, STOP -func (o *Las) GetExpectedPointsCount() int { +func (las *Las) GetExpectedPointsCount() int { var m int //TODO нужно обработать все случаи - if o.Step == 0.0 { - return o.ePoints + if las.Step == 0.0 { + return las.ePoints } - if math.Abs(o.Stop) > math.Abs(o.Strt) { - m = int((o.Stop-o.Strt)/o.Step) + 2 + if math.Abs(las.Stop) > math.Abs(las.Strt) { + m = int((las.Stop-las.Strt)/las.Step) + 2 } else { - m = int((o.Strt-o.Stop)/o.Step) + 2 + m = int((las.Strt-las.Stop)/las.Step) + 2 } if m < 0 { m = -m } if m == 0 { - return o.ePoints + return las.ePoints } return m } //expandDept - if actually data points exceeds -func (o *Las) expandDept(d *LasCurve) { +func (las *Las) expandDept(d *LasCurve) { //actual number of points more then expected - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, "actual number of data lines more than expected, check: STRT, STOP, STEP"}) - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, "expand number of points"}) + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "actual number of data lines more than expected, check: STRT, STOP, STEP"}) + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "expand number of points"}) //ожидаем удвоения данных - o.ePoints *= 2 + las.ePoints *= 2 //expand first log - dept - newDept := make([]float64, o.ePoints) + newDept := make([]float64, las.ePoints) copy(newDept, d.dept) d.dept = newDept - newLog := make([]float64, o.ePoints) + newLog := make([]float64, las.ePoints) copy(newLog, d.dept) d.log = newLog - o.Logs[d.Name] = *d + las.Logs[d.Name] = *d //loop over other logs - n := len(o.Logs) + n := len(las.Logs) var l *LasCurve for j := 1; j < n; j++ { - l, _ = o.logByIndex(j) - newDept := make([]float64, o.ePoints) + l, _ = las.logByIndex(j) + newDept := make([]float64, las.ePoints) copy(newDept, l.dept) l.dept = newDept - newLog := make([]float64, o.ePoints) + newLog := make([]float64, las.ePoints) copy(newLog, l.log) l.log = newLog - o.Logs[l.Name] = *l + las.Logs[l.Name] = *l } } // ReadDataSec - read section of data -func (o *Las) ReadDataSec(fileName string) (int, error) { +func (las *Las) ReadDataSec(fileName string) (int, error) { var ( v float64 err error @@ -621,45 +677,49 @@ func (o *Las) ReadDataSec(fileName string) (int, error) { ) //исходя из параметров STRT, STOP и STEP определяем ожидаемое количество строк данных - o.ePoints = o.GetExpectedPointsCount() + las.ePoints = las.GetExpectedPointsCount() //o.currentLine++ - n := len(o.Logs) //количество каротажей, столько колонок данных ожидаем - d, _ = o.logByIndex(0) //dept log + n := len(las.Logs) //количество каротажей, столько колонок данных ожидаем + d, _ = las.logByIndex(0) //dept log s := "" - for i = 0; o.scanner.Scan(); i++ { - o.currentLine++ - if i == o.ePoints { - o.expandDept(d) + for i = 0; las.scanner.Scan(); i++ { + las.currentLine++ + if i == las.ePoints { + las.expandDept(d) } - s = strings.TrimSpace(o.scanner.Text()) + s = strings.TrimSpace(las.scanner.Text()) + // i счётчик не строк, а фактически считанных данных - счётчик добавлений в слайсы данных + //TODO возможно следует завести отдельный счётчик и оставить в покое счётчик цикла if isIgnoredLine(s) { i-- continue } //first column is DEPT - k := strings.IndexRune(s, ' ') - if k < 0 { //line must have n+1 column and n separated spaces block (+1 becouse first column DEPT) - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, fmt.Sprintf("line: %d is empty, ignore", o.currentLine)}) + k := strings.IndexRune(s, ' ') //TODO вероятно получим ошибку если данные будут разделены не пробелом а табуляцией или ещё чем-то + if k < 0 { //line must have n+1 column and n separated spaces block (+1 becouse first column DEPT) + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("line: %d is empty, ignore", las.currentLine)}) i-- continue } dept, err = strconv.ParseFloat(s[:k], 64) if err != nil { - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, fmt.Sprintf("first column '%s' not numeric, ignore", s[:k])}) + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("first column '%s' not numeric, ignore", s[:k])}) i-- continue } - d.dept[i] = dept + // проверка шага у первых двух точек данных и сравнение с параметром step + //TODO данную проверку следует делать через Checker if i > 1 { - if math.Pow(((dept-d.dept[i-1])-o.Step), 2) > 0.1 { - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, fmt.Sprintf("actual step %5.2f ≠ global STEP %5.2f", (dept - d.dept[i-1]), o.Step)}) + if math.Pow(((dept-d.dept[i-1])-las.Step), 2) > 0.1 { + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("actual step %5.2f ≠ global STEP %5.2f", (dept - d.dept[i-1]), las.Step)}) } } + // проверка шага между точками [i-1, i] и точками [i-2, i-1] обнаружение немонотонности колонки глубин if i > 2 { if math.Pow(((dept-d.dept[i-1])-(d.dept[i-1]-d.dept[i-2])), 2) > 0.1 { - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, fmt.Sprintf("step %5.2f ≠ previously step %5.2f", (dept - d.dept[i-1]), (d.dept[i-1] - d.dept[i-2]))}) - dept = d.dept[i-1] + o.Step + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("step %5.2f ≠ previously step %5.2f", (dept - d.dept[i-1]), (d.dept[i-1] - d.dept[i-2]))}) + dept = d.dept[i-1] + las.Step } } @@ -669,22 +729,22 @@ func (o *Las) ReadDataSec(fileName string) (int, error) { iSpace := strings.IndexRune(s, ' ') switch iSpace { case -1: //не все колонки прочитаны, а пробелов уже нет... пробуем игнорировать сроку заполняя оставшиеся каротажи NULLами - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, "not all column readed, set log value to NULL"}) + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "not all column readed, set log value to NULL"}) case 0: - v = o.Null + v = las.Null case 1: v, err = strconv.ParseFloat(s[:1], 64) default: v, err = strconv.ParseFloat(s[:iSpace], 64) //strconv.ParseFloat(s[:iSpace-1], 64) } if err != nil { - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, fmt.Sprintf("can't convert string: '%s' to number, set to NULL", s[:iSpace-1])}) - v = o.Null + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("can't convert string: '%s' to number, set to NULL", s[:iSpace-1])}) + v = las.Null } - l, err = o.logByIndex(j) + l, err = las.logByIndex(j) if err != nil { - o.nPoints = i - return i, errors.New("internal ERROR, func (o *Las) readDataSec()::o.logByIndex(j) return error") + las.nPoints = i + return i, errors.New("internal ERROR, func (las *Las) readDataSec()::las.logByIndex(j) return error") } l.dept[i] = dept l.log[i] = v @@ -693,20 +753,21 @@ func (o *Las) ReadDataSec(fileName string) (int, error) { //остаток - последняя колонка v, err = strconv.ParseFloat(s, 64) if err != nil { - o.addWarning(TWarning{directOnRead, lasSecData, o.currentLine, "not all column readed, set log value to NULL"}) - v = o.Null + las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "not all column readed, set log value to NULL"}) + v = las.Null } - l, err = o.logByIndex(n - 1) + l, err = las.logByIndex(n - 1) if err != nil { - o.nPoints = i - return i, errors.New("internal ERROR, func (o *Las) readDataSec()::o.logByIndex(j) return error on last column") + las.nPoints = i + return i, errors.New("internal ERROR, func (las *Las) readDataSec()::las.logByIndex(j) return error on last column") } l.dept[i] = dept l.log[i] = v } //i - actually readed lines and add (.) to data array //crop logs to actually len - err = o.setActuallyNumberPoints(i) + //TODO перенести в Open() + err = las.setActuallyNumberPoints(i) if err != nil { return 0, err } @@ -714,32 +775,32 @@ func (o *Las) ReadDataSec(fileName string) (int, error) { } // NumPoints - return actually number of points in data -func (o *Las) NumPoints() int { - return o.nPoints +func (las *Las) NumPoints() int { + return las.nPoints } //Dept - return slice of DEPT curve (first column) -func (o *Las) Dept() []float64 { - d, err := o.logByIndex(0) +func (las *Las) Dept() []float64 { + d, err := las.logByIndex(0) if err != nil { return nil } return d.dept } -func (o *Las) setActuallyNumberPoints(numPoints int) error { +func (las *Las) setActuallyNumberPoints(numPoints int) error { if numPoints <= 0 { - o.nPoints = 0 - return errors.New("internal ERROR, func (o *Las) setActuallyNumberPoints(), actually number of points <= 0") + las.nPoints = 0 + return errors.New("internal ERROR, func (las *Las) setActuallyNumberPoints(), actually number of points <= 0") } - if numPoints > len(o.Dept()) { - o.nPoints = 0 - return errors.New("internal ERROR, func (o *Las) setActuallyNumberPoints(), actually number of points > then exist data") + if numPoints > len(las.Dept()) { + las.nPoints = 0 + return errors.New("internal ERROR, func (las *Las) setActuallyNumberPoints(), actually number of points > then exist data") } - for _, l := range o.Logs { + for _, l := range las.Logs { l.SetLen(numPoints) } - o.nPoints = numPoints + las.nPoints = numPoints return nil } @@ -821,15 +882,15 @@ func (o *Las) SaveToFile(fileName string, useMnemonic ...bool) error { //rewrite if file exist //if useMnemonic == true then on save using std mnemonic on ~Curve section //TODO las have field filename of readed las file, after save filename must update or not? warning occure on write for what file? -func (o *Las) Save(fileName string, useMnemonic ...bool) error { +func (las *Las) Save(fileName string, useMnemonic ...bool) error { var ( err error bufToSave []byte ) if len(useMnemonic) > 0 { - bufToSave, err = o.SaveToBuf(true) + bufToSave, err = las.SaveToBuf(true) } else { - bufToSave, err = o.SaveToBuf(false) + bufToSave, err = las.SaveToBuf(false) } if err != nil { return err @@ -851,22 +912,22 @@ func (o *Las) Save(fileName string, useMnemonic ...bool) error { // rewrite if file exist // if useMnemonic == true then on save using std mnemonic on ~Curve section // ir return err != nil then fatal error, returned slice is not full corrected -func (o *Las) SaveToBuf(useMnemonic bool) ([]byte, error) { - n := len(o.Logs) //log count +func (las *Las) SaveToBuf(useMnemonic bool) ([]byte, error) { + n := len(las.Logs) //log count if n <= 0 { return nil, errors.New("logs not exist") } var err error var b bytes.Buffer fmt.Fprint(&b, _LasFirstLine) - fmt.Fprintf(&b, _LasVersion, o.Ver) + fmt.Fprintf(&b, _LasVersion, las.Ver) fmt.Fprint(&b, _LasWrap) fmt.Fprint(&b, _LasWellInfoSec) - fmt.Fprintf(&b, _LasStrt, o.Strt) - fmt.Fprintf(&b, _LasStop, o.Stop) - fmt.Fprintf(&b, _LasStep, o.Step) - fmt.Fprintf(&b, _LasNull, o.Null) - fmt.Fprintf(&b, _LasWell, o.Well) + fmt.Fprintf(&b, _LasStrt, las.Strt) + fmt.Fprintf(&b, _LasStop, las.Stop) + fmt.Fprintf(&b, _LasStep, las.Step) + fmt.Fprintf(&b, _LasNull, las.Null) + fmt.Fprintf(&b, _LasWell, las.Well) fmt.Fprint(&b, _LasCurvSec) fmt.Fprint(&b, _LasCurvDept) @@ -874,7 +935,7 @@ func (o *Las) SaveToBuf(useMnemonic bool) ([]byte, error) { sb.WriteString("# DEPT |") //готовим строчку с названиями каротажей глубина всегда присутствует var l *LasCurve for i := 1; i < n; i++ { //Пишем названия каротажей - l, _ := o.logByIndex(i) + l, _ := las.logByIndex(i) if useMnemonic { if len(l.Mnemonic) > 0 { l.Name = l.Mnemonic @@ -888,20 +949,25 @@ func (o *Las) SaveToBuf(useMnemonic bool) ([]byte, error) { fmt.Fprint(&b, _LasDataSec) //write data fmt.Fprintf(&b, "%s\n", sb.String()) - dept, _ := o.logByIndex(0) - for i := 0; i < o.nPoints; i++ { //loop by dept (.) + dept, _ := las.logByIndex(0) + for i := 0; i < las.nPoints; i++ { //loop by dept (.) fmt.Fprintf(&b, "%-9.3f ", dept.dept[i]) for j := 1; j < n; j++ { //loop by logs - l, err = o.logByIndex(j) + l, err = las.logByIndex(j) if err != nil { - o.addWarning(TWarning{directOnWrite, lasSecData, i, "logByIndex() return error, log not found, panic"}) + las.addWarning(TWarning{directOnWrite, lasSecData, i, "logByIndex() return error, log not found, panic"}) return nil, errors.New("logByIndex() return error, log not found, panic") } fmt.Fprintf(&b, "%-9.3f ", l.log[i]) } fmt.Fprintln(&b) } - r, _ := cpd.NewReaderTo(io.Reader(&b), o.oCodepage.String()) //ошибку не обрабатываем, допустимость oCodepage проверяем раньше, других причин нет + r, _ := cpd.NewReaderTo(io.Reader(&b), las.oCodepage.String()) //ошибку не обрабатываем, допустимость oCodepage проверяем раньше, других причин нет bufToSave, _ := ioutil.ReadAll(r) return bufToSave, nil } + +// IsEmpty - test to not initialize object +func (las *Las) IsEmpty() bool { + return (las.Logs == nil) +} diff --git a/las_checker.go b/las_checker.go index cf63f7f..4764687 100644 --- a/las_checker.go +++ b/las_checker.go @@ -1,6 +1,192 @@ +//(c) softland 2020 +//softlandia@gmail.com + package glasio -// Check - -func Check() { +import "fmt" +// CheckRes - результаты проверки, получааем из функции doCheck() +type CheckRes struct { + name string + section string + message string + err error + res bool +} + +func (cr CheckRes) String() string { + return fmt.Sprintf("check name: %s, section: %s, desc: %s, result: %v", cr.name, cr.section, cr.message, cr.res) +} + +type doCheck func(chk Check, las *Las) CheckRes + +// Check - отдельная проверка, обязан реализовать функцию doCheck() +type Check struct { + name string + section string + message string + do doCheck +} + +// checkResults - слайс с результатами всех проверок +type checkResults []CheckRes + +func (crs checkResults) nullWrong() bool { + for _, r := range crs { + if r.name == "NullNot0" { + return true + } + } + return false +} + +func (crs checkResults) stepWrong() bool { + for _, r := range crs { + if r.name == "StepNot0" { + return true + } + } + return false +} + +func (crs checkResults) wrapWrong() bool { + for _, r := range crs { + if r.name == "WrapIsOn" { + return true + } + } + return false +} + +func (crs checkResults) curvesWrong() bool { + for _, r := range crs { + if r.name == "CurvesNotPresent" { + return true + } + } + return false +} + +func (crs checkResults) strtStopWrong() bool { + for _, r := range crs { + if r.name == "StrtStop" { + return true + } + } + return false +} + +// Checker - ПРОВЕРЩИК, содержит в себе всех отдельных проверщиков, +// методом check() вызавает последовательно всех своих проверщиков, +// результаты отправляет в Logger +type Checker []Check + +func (c Checker) check(las *Las) checkResults { + res := make([]CheckRes, 0) + for _, chk := range c { + r := chk.do(chk, las) + if !r.res { + res = append(res, r) + } + } + return res +} + +/****************************/ + +// NewEmptyChecker - создание нового примитивного объекта ПРОВЕРЩИКА +// проверяет только на не пустоту объекта las +func NewEmptyChecker() Checker { + return Checker{ + {chkEmptyName, chkEmptySection, "", emptyCheck}, + } +} + +const ( + chkEmptyName = "LasNotNil" + chkEmptySection = "NoN" +) + +// simpleCheck - return true if las not empty +func emptyCheck(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, nil, !las.IsEmpty()} +} + +/****************************/ + +// NewWrongChecker - создание нового ПРОВЕРЩИКА на ошибочность las файла. +// WRAP = ON +// section ~Curve is empty +func NewWrongChecker() Checker { + return Checker{ + newWrapCheck(), + newNotPresentCurvesCheck(), + } +} + +func newWrapCheck() Check { + return Check{"WrapIsOn", "~V", "WRAP = ON", wrapOn} +} + +func wrapOn(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, fmt.Errorf("Wrapped files not support"), !las.IsWraped()} +} + +func newNotPresentCurvesCheck() Check { + return Check{"CurvesNotPresent", "~C", "Curve section is eptry", curvesIsEmpty} +} + +func curvesIsEmpty(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, fmt.Errorf("Curve section not exist"), len(las.Logs) > 0} +} + +// NewStdChecker - создание нового стандартного ПРОВЕРЩИКА +// проверяет las +// STEP == 0 +// NULL == 0 +// STRT == STOP +// WELL is empty +func NewStdChecker() Checker { + return Checker{ + newStepCheck(), + newNullCheck(), + newStrtStopCheck(), + newWellIsEmptyCheck(), + } +} + +// Step Check +func newStepCheck() Check { + return Check{"StepNot0", "~W", "STEP == 0", stepCheck} +} + +func stepCheck(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, nil, las.Step != 0.0} +} + +// Null Check +func newNullCheck() Check { + return Check{"NullNot0", "~W", "NULL == 0", nullCheck} +} + +func nullCheck(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, nil, las.Null != 0.0} +} + +// STRT == STOP Check +func newStrtStopCheck() Check { + return Check{"StrtStop", "~W", "STRT == STOP", strtStopCheck} +} + +func strtStopCheck(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, nil, las.Strt != las.Stop} +} + +// WELL == "" Check +func newWellIsEmptyCheck() Check { + return Check{"WellNotEmpty", "~W", "WELL == ''", wellIsEmptyCheck} +} + +func wellIsEmptyCheck(chk Check, las *Las) CheckRes { + return CheckRes{chk.name, chk.section, chk.message, nil, len(las.Well) != 0} } diff --git a/las_logger.go b/las_logger.go index 57cca25..c0f77fe 100644 --- a/las_logger.go +++ b/las_logger.go @@ -1,37 +1,50 @@ +//(c) softland 2020 +//softlandia@gmail.com + package glasio import ( "fmt" "os" "sort" + "strings" ) -// LasLog - store log info about the one las, fills up info from las.open() and las.check() -type LasLog struct { +// Logger - store log info about the one las, fills up info from las.open() and las.check() +type Logger struct { las *Las // object from message collected filename string // file to read, used for reporting // имя файла по которому формируется отчёт, используется для оформления сообщений readedNumPoints int // number points readed from file get from las.Open() errorOnOpen error // status from las.Open() msgOpen TLasWarnings // сообщения формируемые в процессе открытия las файла - msgCheck tCheckMsg // информация об особых случаях, получаем из LasChecker + msgCheck tCheckMsg // информация об особых проверках, получаем из LasChecker msgCurve tCurvRprt // информация о кривых хранящихся в LAS файле, записывается в "log.info.md" missMnemonic tMMnemonic // мнемоники найденные в файле и не найденные в словаре } -// NewLasLog - constructor -func NewLasLog(las *Las) LasLog { - var lasLog LasLog - lasLog.las = las - lasLog.filename = las.FileName - lasLog.msgOpen = nil - lasLog.msgCheck = make(tCheckMsg, 0, 10) - lasLog.msgCurve = make(tCurvRprt, 0, 10) - lasLog.missMnemonic = make(tMMnemonic) - return lasLog +// NewLogger - constructor +func NewLogger(las *Las) *Logger { + logger := new(Logger) + logger.las = las + logger.filename = las.FileName + logger.msgOpen = nil + logger.msgCheck = make(tCheckMsg, 0, 10) + logger.msgCurve = make(tCurvRprt, 0, 10) + logger.missMnemonic = make(tMMnemonic) + return logger } +// tCheckMsg - хранит все сообщения о специальных проверках las файла type tCheckMsg []string +func (m tCheckMsg) String() string { + var sb strings.Builder + for _, msg := range m { + sb.WriteString(msg) + } + return sb.String() +} + func (m *tCheckMsg) save(f *os.File) { for _, msg := range *m { f.WriteString(msg) @@ -52,9 +65,20 @@ func (m *tCheckMsg) msgFileOpenWarning(fn string, err error) string { type tCurvRprt []string -func (ir *tCurvRprt) save(f *os.File, filename string) { +func (cr tCurvRprt) String(filename string) string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("##logs in file: '%s'##\n", filename)) + for _, s := range cr { + sb.WriteString(s) + sb.WriteString("\n") + } + sb.WriteString("\n") + return sb.String() +} + +func (cr *tCurvRprt) save(f *os.File, filename string) { fmt.Fprintf(f, "##logs in file: '%s'##\n", filename) - for _, s := range *ir { + for _, s := range *cr { f.WriteString(s) } f.WriteString("\n") @@ -62,6 +86,19 @@ func (ir *tCurvRprt) save(f *os.File, filename string) { type tMMnemonic map[string]string +func (mm tMMnemonic) String() string { + keys := make([]string, 0, len(mm)) + for k := range mm { + keys = append(keys, k) + } + sort.Strings(keys) + var sb strings.Builder + for _, k := range keys { + sb.WriteString(mm[k] + "\n") + } + return sb.String() +} + func (mm *tMMnemonic) save(f *os.File) { keys := make([]string, 0, len(*mm)) for k := range *mm { diff --git a/las_summary_test.go b/las_summary_test.go index 4622cf8..d769efd 100644 --- a/las_summary_test.go +++ b/las_summary_test.go @@ -1,5 +1,5 @@ -//(c) softland 2020 -//softlandia@gmail.com +// (c) softland 2020 +// softlandia@gmail.com package glasio import ( @@ -50,7 +50,7 @@ var dSummaryCheck = []tSummaryCheck{ {fp.Join("data/missing_vers.las"), 2.0, "NO", 1670, 1660, -0.125, -999.25, "WELL", 8, 3, false}, {fp.Join("data/missing_wrap.las"), 1.2, "NO", 1670, 1660, -0.125, -999.25, "ANY ET AL OIL WELL #12", 8, 3, false}, {fp.Join("data/more_20_warnings.las"), 1.2, "NO", 0.0, 0.0, 1.0, -32768.0, "6", 6, 23, true}, //in las file STEP=0.0 but this incorrect, LoadHeader replace STEP to actual from data - {fp.Join("data/no-data-section.las"), 1.2, "NO", 0.0, 0.0, -32768, -32768.0, "6", 31, 0, true}, + {fp.Join("data/no-data-section.las"), 1.2, "NO", 0.0, 0.0, 0.0, -32768.0, "6", 31, 0, true}, //in las file STEP=0.0 but this incorrect, data section contain incorrect step too, result step equal from input {fp.Join("data/sample_bracketed_units.las"), 1.2, "NO", 1670, 1660, -0.125, -999.25, "ANY ET AL OIL WELL #12", 8, 3, true}, {fp.Join("data/test-curve-sec-empty-mnemonic.las"), 1.2, "NO", 1670, 1669.75, -0.125, -999.25, "ANY ET AL OIL WELL #12", 8, 3, true}, {fp.Join("data/UWI_API_leading_zero.las"), 1.2, "NO", 1670, 1660, -0.125, -999.25, "ANY ET AL OIL WELL #12", 8, 3, true}, @@ -91,3 +91,56 @@ func TestCurveSec2(t *testing.T) { assert.False(t, cmpLas(correct, las)) assert.False(t, correct.Logs.Cmp(las.Logs)) } + +func TestLasCheck(t *testing.T) { + lasLog := LasCheck(fp.Join("data/test-curve-sec-empty-mnemonic+.las")) + assert.NotNil(t, lasLog) + s := lasLog.msgOpen.ToString() + assert.Empty(t, s) + s = lasLog.msgCheck.String() + assert.Empty(t, s) + s = lasLog.msgCurve.String(fp.Join("data/test-curve-sec-empty-mnemonic+.las")) + assert.Contains(t, s, "test-curve-sec-empty-mnemonic+.las'##") + + // проверка на возврат nil + lasLog, err := LasDeepCheck(fp.Join("data/test-curve-sec-empty-mnemonic+.las"), fp.Join("data/mnemonic.-"), fp.Join("data/dic.ini")) + assert.Nil(t, lasLog) + lasLog, err = LasDeepCheck(fp.Join("data/test-curve-sec-empty-mnemonic+.las"), fp.Join("data/mnemonic.ini"), fp.Join("data/dic.-")) + assert.Nil(t, lasLog) + + lasLog, err = LasDeepCheck(fp.Join("data/test-curve-sec-empty-mnemonic+.las"), fp.Join("data/mnemonic.ini"), fp.Join("data/dic.ini")) + assert.Nil(t, err) + s = lasLog.msgCurve.String(fp.Join("data/test-curve-sec-empty-mnemonic+.las")) + assert.Contains(t, s, "*input log: B internal: B mnemonic:*") + assert.Contains(t, s, "input log: SP internal: SP mnemonic: SP") + assert.Contains(t, s, "*input log: -EL-2 internal: -EL-2 mnemonic:*") + s = lasLog.missMnemonic.String() + assert.Contains(t, s, "-EL-1") + assert.NotContains(t, s, "SP") + + lasLog, err = LasDeepCheck(fp.Join("data/more_20_warnings.las"), fp.Join("data/mnemonic.ini"), fp.Join("data/dic.ini")) + assert.NotNil(t, lasLog) + assert.Nil(t, err) + s = lasLog.msgOpen.ToString() + assert.Contains(t, s, "STEP parameter equal 0") + assert.Contains(t, s, "invalid STRT: 0.000 == STOP: 0.000, will be replace to actually") + s = lasLog.msgCheck.String() + assert.Empty(t, s) + s = lasLog.msgCurve.String(fp.Join("data/more_20_warnings.las")) + assert.Contains(t, s, "*input log: второй каротаж internal: второй каротаж mnemonic:*") + assert.Contains(t, s, "input log: GK internal: GK mnemonic: GR") + assert.Contains(t, s, "*input log: первый internal: первый mnemonic:*") + s = lasLog.missMnemonic.String() + assert.Contains(t, s, "NNB") + assert.Contains(t, s, "второй каротаж") + assert.NotContains(t, s, "GR") + + // случай если файла нет + lasLog, err = LasDeepCheck(fp.Join("data/-test-curve-sec-empty-mnemonic+.las"), fp.Join("data/mnemonic.ini"), fp.Join("data/dic.ini")) + assert.NotNil(t, lasLog) + assert.NotNil(t, lasLog.errorOnOpen) + + // случай las файл WRAP + lasLog = LasCheck(fp.Join("data/1.2/sample_wrapped.las")) + assert.Contains(t, lasLog.msgCheck.String(), "WRAP=YES") +} diff --git a/las_test.go b/las_test.go index 884656f..0458299 100644 --- a/las_test.go +++ b/las_test.go @@ -131,33 +131,6 @@ func TestLoadLasHeader(t *testing.T) { assert.Nil(t, las) } -/* -func TestLoadHeaderUtf(t *testing.T) { - las := NewLas() - las.iCodepage, _ = cpd.FileCodePageDetect(fp.Join("data/encodings_utf8wbom.las")) - las.LoadHeader(fp.Join("data/encodings_utf8wbom.las")) - assert.Equal(t, 1.2, las.Ver, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed VER: %f, expected %f", las.Ver, 1.2)) - assert.Equal(t, "NO", las.Wrap, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed WRAP: %s, expected %s", las.Wrap, "NO")) - assert.Equal(t, 1670.0, las.Strt, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed STRT: %f, expected %f", las.Strt, 1670.0)) - assert.Equal(t, 1660.0, las.Stop, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed STOP: %f, expected %f", las.Stop, 1660.0)) - assert.Equal(t, -0.1250, las.Step, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed STEP: %f, expected %f", las.Step, -0.1250)) - assert.Equal(t, -999.250, las.Null, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed NULL: %f, expected %f", las.Null, -999.250)) - assert.Equal(t, "Скважина ºᶟᵌᴬń #12", las.Well, fmt.Sprintf(" file 'encodings_utf8wbom.las' readed WELL: %s, expected %s", las.Well, "Скважина ºᶟᵌᴬń #12")) -} - -func TestLoadHeaderUtf16le(t *testing.T) { - las := NewLas() - las.iCodepage, _ = cpd.FileCodePageDetect(fp.Join("data/encodings_utf16lebom.las")) - las.LoadHeader(fp.Join("data/encodings_utf16lebom.las")) - assert.Equal(t, 1.2, las.Ver, fmt.Sprintf("file 'encodings_utf16lebom.las' readed VER: %f, expected %f", las.Ver, 1.2)) - assert.Equal(t, "NO", las.Wrap, fmt.Sprintf("file 'encodings_utf16lebom.las' readed WRAP: %s, expected %s", las.Wrap, "NO")) - assert.Equal(t, 1670.0, las.Strt, fmt.Sprintf("file 'encodings_utf16lebom.las' readed STRT: %f, expected %f", las.Strt, 1670.0)) - assert.Equal(t, 1660.0, las.Stop, fmt.Sprintf("file 'encodings_utf16lebom.las' readed STOP: %f, expected %f", las.Stop, 1660.0)) - assert.Equal(t, -0.1250, las.Step, fmt.Sprintf("file 'encodings_utf16lebom.las' readed STEP: %f, expected %f", las.Step, -0.1250)) - assert.Equal(t, -999.25, las.Null, fmt.Sprintf("file 'encodings_utf16lebom.las' readed NULL: %f, expected %f", las.Null, -999.25)) - assert.Equal(t, "ºᶟᵌᴬń BLOCK", las.Well, fmt.Sprintf("file 'encodings_utf16lebom.las' readed WELL: %s, expected %s", las.Well, "ºᶟᵌᴬń BLOCK")) -}*/ - func TestLasSaveWarning(t *testing.T) { las := NewLas() las.Open(fp.Join("data/more_20_warnings.las")) @@ -176,9 +149,9 @@ type tGetDataStep struct { } var dGetDataStep = []tGetDataStep{ - {fp.Join("data/step-2-data-without-step-case1.las"), -32768.000}, - {fp.Join("data/step-2-data-without-step-case2.las"), -32768.000}, - {fp.Join("data/no-data-section.las"), -32768.000}, + {fp.Join("data/step-2-data-without-step-case1.las"), 0.0}, + {fp.Join("data/step-2-data-without-step-case2.las"), 0.0}, + {fp.Join("data/no-data-section.las"), 0.0}, {fp.Join("data/step-1-normal-case.las"), 1.0}, } @@ -242,8 +215,8 @@ type tSaveLas struct { } var dSaveLas = []tSaveLas{ - {fp.Join("test_files/~1251.las"), cpd.CP1251, -99.99, 0.201, 10.01, 0.01, "Примерная-101 / бис", -0.1}, // filename codepage null strt stop step well name new null + {fp.Join("test_files/~1251.las"), cpd.CP1251, -99.99, 0.201, 10.01, 0.01, "Примерная-101 / бис", -0.1}, {fp.Join("test_files/~koi8.las"), cpd.KOI8R, -99.0, 0.2, 2.0, -0.1, "Примерная-1001 /\"бис\"", -55.55}, {fp.Join("test_files/~866.las"), cpd.CP866, -909.0, 2.21, 12.1, -0.1, "Примерная-101 /\"бис\"", 5555.55}, {fp.Join("test_files/~utf-8.las"), cpd.UTF8, -999.99, 20.21, 1.0, -0.01, "Примерная-101А / бис", -999.25}, @@ -277,6 +250,71 @@ func TestSetNullOnEmptyLas(t *testing.T) { assert.Equal(t, -1000.0, las.Null) } +func TestLasIsEmpty(t *testing.T) { + las := Las{} + assert.True(t, las.IsEmpty()) + las = *NewLas() + assert.False(t, las.IsEmpty()) +} + +type tStdCheckLas struct { + cp cpd.IDCodePage + null float64 + strt float64 + stop float64 + step float64 + well string + testsRes [4]bool // stdCheck contain 4 test +} + +var dCheckLas = []tStdCheckLas{ + //codepage null strt stop step well name проверки step null strt well + {cpd.CP1251, 0.0, 0.201, 10.01, 0.01, "Примерная-101 / бис", [4]bool{true, false, true, true}}, + {cpd.CP1251, -99.99, 0.201, 10.01, 0.0, "Примерная-101 / бис", [4]bool{false, true, true, true}}, + {cpd.KOI8R, 0.0, 0.2, 2.0, 0.0, "Примерная-1001 /\"бис\"", [4]bool{false, false, true, true}}, + {cpd.CP866, 0.0, 0.21, 0.21, 0.1, "Примерная-101 /\"бис\"", [4]bool{true, false, false, true}}, + {cpd.UTF8, 0.0, 0.2, 0.2, 0.0, "", [4]bool{false, false, false, false}}, + {cpd.UTF16LE, 0.0, 20.2, 1.0, -0.0, "", [4]bool{false, false, true, false}}, +} + +func TestLasChecker(t *testing.T) { + chkr := NewEmptyChecker() + assert.NotEqual(t, 0, len(chkr)) + chkr = NewStdChecker() //стандартная проверка на step=0, null=0, strt=stop, well="" + for _, tmp := range dCheckLas { + las := makeSampleLas(tmp.cp, tmp.null, tmp.strt, tmp.stop, tmp.step, tmp.well) + assert.NotEqual(t, 0, len(chkr)) + for i, chk := range chkr { + checkRes := chk.do(chk, las) + assert.Equal(t, tmp.testsRes[i], checkRes.res) + } + } +} + +func TestLasChecker2(t *testing.T) { + stdChecker := NewStdChecker() + + tmp := dCheckLas[0] // в данных одна ошиба, NULL=0 + las := makeSampleLas(tmp.cp, tmp.null, tmp.strt, tmp.stop, tmp.step, tmp.well) + res := stdChecker.check(las) + assert.Equal(t, 1, len(res)) + assert.Equal(t, res[0].name, "NullNot0") + assert.True(t, res.nullWrong()) + assert.False(t, res.stepWrong()) + + tmp = dCheckLas[4] // все 4 ошибки + las = makeSampleLas(tmp.cp, tmp.null, tmp.strt, tmp.stop, tmp.step, tmp.well) + res = stdChecker.check(las) + assert.Equal(t, 4, len(res)) + assert.Equal(t, res[0].name, "StepNot0") + assert.Equal(t, res[3].message, "WELL == ''") + assert.True(t, res.stepWrong()) + + las = makeSampleLas(cpd.CP866, -999.25, 0, 100, 0.2, "well") //правильные данные + res = stdChecker.check(las) //StdChecker должен вернуть пустой слайс + assert.Equal(t, 0, len(res)) +} + func BenchmarkSave1(b *testing.B) { for _, tmp := range dSaveLas { las := makeSampleLas(tmp.cp, tmp.null, tmp.strt, tmp.stop, tmp.step, tmp.well) diff --git a/las_util.go b/las_util.go index 1a2aa73..e67119b 100644 --- a/las_util.go +++ b/las_util.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + fp "path/filepath" "github.com/softlandia/cpd" ) @@ -82,39 +83,56 @@ func LoadLasHeader(fileName string) (*Las, error) { return las, nil } +// LasCheck - read and check las file, return object with all warnings // считывает файл и собирает все сообщения в один объект -func lasOpenCheck(filename string) LasLog { - - las := NewLas() // TODO make special constructor to initialize with global Mnemonic and Dic - //las.LogDic = &Mnemonic // global var - //las.VocDic = &Dic // global var - - LasLog := NewLasLog(las) - - LasLog.readedNumPoints, LasLog.errorOnOpen = las.Open(filename) - LasLog.msgOpen = las.Warnings - +// это базовая проверка las файла, прелесть в том что здесь собираются сообщения от прочтения файла +func LasCheck(filename string) *Logger { + las := NewLas() + n, err := las.Open(filename) + lasLog := NewLogger(las) + lasLog.readedNumPoints = n + lasLog.errorOnOpen = err + lasLog.msgOpen = las.Warnings if las.IsWraped() { - LasLog.msgCheck = append(LasLog.msgCheck, LasLog.msgCheck.msgFileIsWraped(filename)) - //return statLasCheck_WRAP + lasLog.msgCheck = append(lasLog.msgCheck, lasLog.msgCheck.msgFileIsWraped(filename)) } if las.NumPoints() == 0 { - LasLog.msgCheck = append(LasLog.msgCheck, LasLog.msgCheck.msgFileNoData(filename)) - //return statLasCheck_DATA + lasLog.msgCheck = append(lasLog.msgCheck, lasLog.msgCheck.msgFileNoData(filename)) } - if LasLog.errorOnOpen != nil { - LasLog.msgCheck = append(LasLog.msgCheck, LasLog.msgCheck.msgFileOpenWarning(filename, LasLog.errorOnOpen)) + if lasLog.errorOnOpen != nil { + lasLog.msgCheck = append(lasLog.msgCheck, lasLog.msgCheck.msgFileOpenWarning(filename, lasLog.errorOnOpen)) } + las = nil //TODO уверен это грубая ошибка, Logger хранит в себе las НАФИГА мы его тут убиваем... + return lasLog +} +// LasDeepCheck - read and check las file, curve name checked to mnemonic, return object with all warnings +// считывает файл и собирает все сообщения в один объект +func LasDeepCheck(filename, mnemonicFile, vocdicFile string) (*Logger, error) { + lasLog := LasCheck(filename) + //TODO здесь засада, LasCheck сам создаёт и читает las, более того он вообще-то его в себе хранит, + // НО в данном случае нам СТОИТ??? или НЕ СТОИТ??? об этом забывать + // мы ведь вынуждены всё равно прочитать ещё раз las файл + las := NewLas() + Mnemonic, err := LoadStdMnemonicDic(fp.Join(mnemonicFile)) + if err != nil { + return nil, err + } + VocDic, err := LoadStdVocabularyDictionary(fp.Join(vocdicFile)) + if err != nil { + return nil, err + } + las.LogDic = &Mnemonic + las.VocDic = &VocDic + las.Open(filename) //читаем второй раз, когда подключены словари, то чтение идёт иначе ))) for k, v := range las.Logs { if len(v.Mnemonic) == 0 { //v.Mnemonic содержит автоопределённую стандартную мнемонику, если она пустая, значит пропущена, помечаем ** - LasLog.msgCurve = append(LasLog.msgCurve, fmt.Sprintf("*input log: %s \t internal: %s \t mnemonic:%s*\n", v.IName, k, v.Mnemonic)) - LasLog.missMnemonic[v.IName] = v.IName + lasLog.msgCurve = append(lasLog.msgCurve, fmt.Sprintf("*input log: %s \t internal: %s \t mnemonic:%s*\n", v.IName, k, v.Mnemonic)) + lasLog.missMnemonic[v.IName] = v.IName } else { - LasLog.msgCurve = append(LasLog.msgCurve, fmt.Sprintf("input log: %s \t internal: %s \t mnemonic: %s\n", v.IName, k, v.Mnemonic)) + lasLog.msgCurve = append(lasLog.msgCurve, fmt.Sprintf("input log: %s \t internal: %s \t mnemonic: %s\n", v.IName, k, v.Mnemonic)) } } - las = nil - return LasLog + return lasLog, nil } diff --git a/las_warning.go b/las_warning.go index 4add585..ce08045 100644 --- a/las_warning.go +++ b/las_warning.go @@ -64,7 +64,7 @@ func (o TLasWarnings) Count() int { // ToString - make one string from all elements // sep[0] - record separator разделитель записей // sep[1] - field separator разделитель полей -// default separator between field ";" between record "\n" +// default separator between field "," between record "\n" // on empty container return "" func (o *TLasWarnings) ToString(sep ...string) string { if o.Count() == 0 { diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..6c4ef01 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,17 @@ +//(c) softland 2020 +//softlandia@gmail.com +package glasio + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckMeaasges(t *testing.T) { + chMsg := make(tCheckMsg, 2) + chMsg[0] = "message 1" + chMsg[1] = "message 2" + assert.Contains(t, chMsg.String(), "1") + assert.NotContains(t, chMsg.String(), "0") +} diff --git a/ver.txt b/ver.txt index c946ee6..1180819 100644 --- a/ver.txt +++ b/ver.txt @@ -1 +1 @@ -0.1.6 +0.1.7