2020-04-28 22:04:01 +03:00
|
|
|
// (c) softland 2020
|
|
|
|
// softlandia@gmail.com
|
2020-06-25 01:07:02 +03:00
|
|
|
// main file
|
2020-04-28 22:04:01 +03:00
|
|
|
|
2020-02-27 19:21:49 +03:00
|
|
|
package glasio
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2020-04-10 00:55:08 +03:00
|
|
|
"bytes"
|
2020-02-27 19:21:49 +03:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2020-03-14 00:32:03 +03:00
|
|
|
"io/ioutil"
|
2020-02-27 19:21:49 +03:00
|
|
|
"math"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/softlandia/cpd"
|
|
|
|
|
|
|
|
"github.com/softlandia/xlib"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Las - class to store las file
|
|
|
|
// input code page autodetect
|
|
|
|
// at read file always code page converted to UTF
|
2020-06-28 20:46:04 +03:00
|
|
|
// at save file code page converted to specifyed in Las.oCodepage
|
2020-02-27 19:21:49 +03:00
|
|
|
//TODO add pointer to cfg
|
2020-04-28 22:04:01 +03:00
|
|
|
//TODO при создании объекта las есть возможность указать кодировку записи, нужна возможность указать явно кодировку чтения
|
2020-02-27 19:21:49 +03:00
|
|
|
type Las struct {
|
2020-06-11 00:00:45 +03:00
|
|
|
rows []string // buffer for read source file, only converted to UTF-8 no any othe change
|
|
|
|
FileName string // file name from load
|
|
|
|
File *os.File // the file from which we are reading
|
|
|
|
Reader io.Reader // reader created from File, provides decode from codepage to UTF-8
|
|
|
|
scanner *bufio.Scanner // scanner
|
|
|
|
Logs LasCurves // store all logs
|
|
|
|
LogDic *map[string]string // external dictionary of standart log name - mnemonics
|
|
|
|
VocDic *map[string]string // external vocabulary dictionary of log mnemonic
|
|
|
|
Warnings TLasWarnings // slice of warnings occure on read or write
|
|
|
|
oCodepage cpd.IDCodePage // codepage to save, default xlib.CpWindows1251. to special value, specify at make: NewLas(cp...)
|
|
|
|
currentLine int // index of current line in readed file
|
|
|
|
maxWarningCount int // default maximum warning count
|
|
|
|
stdNull float64 // default null value
|
2020-06-10 19:39:48 +03:00
|
|
|
VerSec,
|
2020-06-25 01:07:02 +03:00
|
|
|
WelSec,
|
2020-06-10 19:39:48 +03:00
|
|
|
CurSec,
|
|
|
|
ParSec,
|
|
|
|
OthSec HeaderSection
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-04-10 00:55:08 +03:00
|
|
|
var (
|
2020-06-10 19:39:48 +03:00
|
|
|
// ExpPoints - первоначальный размер слайсов для хранения данных Logs.D и Logs.V
|
|
|
|
// ожидаемое количество точек данных, до чтения мы не можем знать сколько точек будет фактически прочитано
|
|
|
|
// данные увеличиваются при необходимости и обрезаются после окончания чтения
|
2020-04-10 00:55:08 +03:00
|
|
|
ExpPoints int = 1000
|
|
|
|
// StdNull - пустое значение
|
|
|
|
StdNull float64 = -999.25
|
|
|
|
// MaxWarningCount - слишком много сообщений писать смысла нет
|
|
|
|
MaxWarningCount int = 20
|
|
|
|
)
|
|
|
|
|
2020-06-28 15:35:32 +03:00
|
|
|
//method for get values from header containers ////////////////
|
|
|
|
|
2020-06-28 20:46:04 +03:00
|
|
|
func (las *Las) parFloat(sec HeaderSection, name string, defValue float64) float64 {
|
2020-06-28 15:35:32 +03:00
|
|
|
v := defValue
|
2020-06-28 20:46:04 +03:00
|
|
|
if p, ok := sec.params[name]; ok {
|
2020-06-28 15:35:32 +03:00
|
|
|
v, _ = strconv.ParseFloat(p.Val, 64)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2020-06-28 20:46:04 +03:00
|
|
|
func (las *Las) parStr(sec HeaderSection, name, defValue string) string {
|
2020-06-28 15:35:32 +03:00
|
|
|
v := defValue
|
2020-06-28 20:46:04 +03:00
|
|
|
if p, ok := sec.params[name]; ok {
|
2020-06-28 15:35:32 +03:00
|
|
|
v = p.Val
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
// NULL - return null value of las file as float64
|
|
|
|
// if parameter NULL in las file not exist, then return StdNull (by default -999.25)
|
|
|
|
func (las *Las) NULL() float64 {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parFloat(las.WelSec, "NULL", StdNull)
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// STOP - return depth stop value of las file as float64
|
|
|
|
// if parameter STOP in las file not exist, then return StdNull (by default -999.25)
|
|
|
|
func (las *Las) STOP() float64 {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parFloat(las.WelSec, "STOP", StdNull)
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// STRT - return depth start value of las file as float64
|
|
|
|
// if parameter NULL in las file not exist, then return StdNull (by default -999.25)
|
|
|
|
func (las *Las) STRT() float64 {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parFloat(las.WelSec, "STRT", StdNull)
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// STEP - return depth step value of las file as float64
|
|
|
|
// if parameter not exist, then return StdNull (by default -999.25)
|
|
|
|
func (las *Las) STEP() float64 {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parFloat(las.WelSec, "STEP", StdNull)
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// VERS - return version of las file as float64
|
|
|
|
// if parameter VERS in las file not exist, then return 2.0
|
|
|
|
func (las *Las) VERS() float64 {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parFloat(las.VerSec, "VERS", 2.0)
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// WRAP - return wrap parameter of las file
|
|
|
|
// if parameter not exist, then return "NO"
|
|
|
|
func (las *Las) WRAP() string {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parStr(las.VerSec, "WRAP", "NO")
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// WELL - return well name
|
|
|
|
// if parameter WELL in las file not exist, then return "--"
|
|
|
|
func (las *Las) WELL() string {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.parStr(las.WelSec, "WELL", "")
|
2020-06-28 15:35:32 +03:00
|
|
|
}
|
|
|
|
|
2020-02-27 19:21:49 +03:00
|
|
|
//NewLas - make new object Las class
|
|
|
|
//autodetect code page at load file
|
2020-03-08 00:34:07 +03:00
|
|
|
//code page to save by default is cpd.CP1251
|
2020-02-27 19:21:49 +03:00
|
|
|
func NewLas(outputCP ...cpd.IDCodePage) *Las {
|
2020-06-28 20:46:04 +03:00
|
|
|
las := new(Las)
|
|
|
|
las.rows = make([]string, 0, ExpPoints /*las.nRows*/)
|
2020-06-21 18:00:28 +03:00
|
|
|
las.Logs = make([]LasCurve, 0)
|
2020-06-25 01:07:02 +03:00
|
|
|
las.VerSec = NewVerSection()
|
|
|
|
las.WelSec = NewWelSection()
|
|
|
|
las.CurSec = NewCurSection()
|
|
|
|
las.ParSec = NewParSection()
|
|
|
|
las.OthSec = NewOthSection()
|
2020-04-10 00:55:08 +03:00
|
|
|
las.maxWarningCount = MaxWarningCount
|
2020-06-02 20:49:41 +03:00
|
|
|
las.stdNull = StdNull
|
2020-02-27 19:21:49 +03:00
|
|
|
if len(outputCP) > 0 {
|
|
|
|
las.oCodepage = outputCP[0]
|
|
|
|
} else {
|
|
|
|
las.oCodepage = cpd.CP1251
|
|
|
|
}
|
|
|
|
//mnemonic dictionary
|
|
|
|
las.LogDic = nil
|
|
|
|
//external log dictionary
|
|
|
|
las.VocDic = nil
|
|
|
|
return las
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsWraped - return true if WRAP == YES
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) IsWraped() bool {
|
2020-06-28 20:46:04 +03:00
|
|
|
return strings.Contains(strings.ToUpper(las.WRAP()), "Y")
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-06-11 23:49:11 +03:00
|
|
|
// GetRows - get internal field 'rows'
|
|
|
|
func (las *Las) GetRows() []string {
|
|
|
|
return las.rows
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadRows - reads to buffer 'rows' and return total count of read lines
|
|
|
|
func (las *Las) ReadRows() int {
|
|
|
|
for i := 0; las.scanner.Scan(); i++ {
|
|
|
|
las.rows = append(las.rows, las.scanner.Text())
|
|
|
|
}
|
|
|
|
return len(las.rows)
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
|
|
|
|
2020-06-28 20:46:04 +03:00
|
|
|
// Load - load las from reader
|
|
|
|
// you can make reader from string or othe containers and send as input parameters
|
|
|
|
func (las *Las) Load(reader io.Reader) (int, error) {
|
2020-06-11 00:00:45 +03:00
|
|
|
var err error
|
2020-06-28 20:46:04 +03:00
|
|
|
if reader == nil {
|
|
|
|
return 0, errors.New("Load received nil reader")
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
2020-06-28 20:46:04 +03:00
|
|
|
//create Reader, this reader decode to UTF-8 from reader
|
|
|
|
las.Reader, err = cpd.NewReader(reader)
|
2020-06-11 00:00:45 +03:00
|
|
|
if err != nil {
|
|
|
|
return 0, err //FATAL error - file cannot be decoded to UTF-8
|
|
|
|
}
|
|
|
|
// prepare file to read
|
|
|
|
las.scanner = bufio.NewScanner(las.Reader)
|
2020-06-22 19:29:32 +03:00
|
|
|
las.ReadRows()
|
2020-06-21 18:00:28 +03:00
|
|
|
m, _ := las.LoadHeader()
|
2020-06-11 00:00:45 +03:00
|
|
|
stdChecker := NewStdChecker()
|
|
|
|
// check for FATAL errors
|
|
|
|
r := stdChecker.check(las)
|
|
|
|
las.storeHeaderWarning(r)
|
|
|
|
if err = r.fatal(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if r.nullWrong() {
|
|
|
|
las.SetNull(las.stdNull)
|
|
|
|
}
|
|
|
|
if r.strtWrong() {
|
|
|
|
h := las.GetStrtFromData() // return las.Null if cannot find strt in the data section.
|
2020-06-28 20:46:04 +03:00
|
|
|
if h == las.NULL() {
|
2020-06-11 00:00:45 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprint("__WRN__ STRT parameter on data is wrong setting to 0")})
|
|
|
|
las.setStrt(0)
|
|
|
|
}
|
|
|
|
las.setStrt(h)
|
|
|
|
}
|
|
|
|
if r.stepWrong() {
|
|
|
|
h := las.GetStepFromData() // return las.Null if cannot calculate step from data
|
2020-06-28 20:46:04 +03:00
|
|
|
if h == las.NULL() {
|
2020-06-11 00:00:45 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecWellInfo, las.currentLine, fmt.Sprint("__WRN__ STEP parameter on data is wrong")})
|
|
|
|
}
|
|
|
|
las.setStep(h)
|
|
|
|
}
|
2020-06-22 19:29:32 +03:00
|
|
|
return las.LoadDataSec(m)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-06-28 20:46:04 +03:00
|
|
|
// Open - read las file
|
|
|
|
func (las *Las) Open(fileName string) (int, error) {
|
|
|
|
var err error
|
|
|
|
las.File, err = os.Open(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err //FATAL error - file not exist
|
|
|
|
}
|
|
|
|
defer las.File.Close()
|
|
|
|
las.FileName = fileName
|
|
|
|
return las.Load(las.File)
|
|
|
|
}
|
|
|
|
|
2020-06-21 18:00:28 +03:00
|
|
|
/*LoadHeader - read las file and load all section before ~A
|
|
|
|
returns the row number with which the data section begins, until return nil in any case
|
|
|
|
1. читаем строку
|
|
|
|
2. если коммент или пустая в игнор
|
|
|
|
3. если начало секции, определяем какой
|
|
|
|
4. если началась секция данных заканчиваем
|
|
|
|
5. читаем одну строку (это один параметер из известной нам секции)
|
|
|
|
*/
|
|
|
|
func (las *Las) LoadHeader() (int, error) {
|
2020-06-25 01:07:02 +03:00
|
|
|
var (
|
|
|
|
sec HeaderSection
|
|
|
|
)
|
2020-06-28 20:46:04 +03:00
|
|
|
//secNum := 0
|
2020-06-21 18:00:28 +03:00
|
|
|
las.currentLine = 0
|
2020-06-28 20:46:04 +03:00
|
|
|
for _, s := range las.rows {
|
2020-06-25 01:07:02 +03:00
|
|
|
s = strings.TrimSpace(s)
|
2020-04-28 22:04:01 +03:00
|
|
|
las.currentLine++
|
2020-02-27 19:21:49 +03:00
|
|
|
if isIgnoredLine(s) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if s[0] == '~' { //start new section
|
2020-06-28 20:46:04 +03:00
|
|
|
if las.isDataSection(rune(s[1])) {
|
2020-06-10 20:05:16 +03:00
|
|
|
break // reached the data section, stop load header
|
2020-06-10 19:39:48 +03:00
|
|
|
}
|
2020-06-25 01:07:02 +03:00
|
|
|
sec = las.section(rune(s[1]))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
//not comment, not empty and not new section => parameter, read it
|
2020-06-28 20:46:04 +03:00
|
|
|
p, w := sec.parse(s, las.currentLine)
|
|
|
|
if !w.Empty() {
|
|
|
|
las.addWarning(w)
|
|
|
|
}
|
|
|
|
p.Name = sec.uniqueName(p.Name) //TODO if a duplicate of any parameter is detected, a warning should be generated
|
2020-06-25 01:07:02 +03:00
|
|
|
sec.params[p.Name] = p
|
2020-06-28 20:46:04 +03:00
|
|
|
if sec.name == 'C' { //for ~Curve section need additional actions
|
|
|
|
err := las.readCurveParam(s) //make new curve from "s" and store to container "Logs"
|
|
|
|
if err != nil {
|
|
|
|
las.addWarning(TWarning{directOnRead, 3, las.currentLine, fmt.Sprintf("param: '%s' error: %v", s, err)})
|
|
|
|
}
|
2020-06-10 19:39:48 +03:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 23:49:11 +03:00
|
|
|
return las.currentLine, nil
|
2020-06-10 19:39:48 +03:00
|
|
|
}
|
|
|
|
|
2020-06-28 20:46:04 +03:00
|
|
|
// isDataSection - return true if data section reached
|
|
|
|
func (las *Las) isDataSection(r rune) bool {
|
|
|
|
return (r == 0x41) || (r == 0x61)
|
|
|
|
}
|
|
|
|
|
2020-06-28 15:35:32 +03:00
|
|
|
// ~V - section vertion
|
|
|
|
// ~W - well info section
|
|
|
|
// ~C - curve info section
|
|
|
|
// ~A - data section
|
2020-06-25 01:07:02 +03:00
|
|
|
func (las *Las) section(r rune) HeaderSection {
|
|
|
|
switch r {
|
|
|
|
case 0x56, 0x76: //V, v
|
|
|
|
return las.VerSec //version section
|
|
|
|
case 0x57, 0x77: //W, w
|
2020-06-28 20:46:04 +03:00
|
|
|
if las.VERS() < 2.0 {
|
2020-06-25 01:07:02 +03:00
|
|
|
las.WelSec.parse = welParse12 //change parser, by default using parser 2.0
|
|
|
|
}
|
|
|
|
return las.WelSec //well info section
|
|
|
|
case 0x43, 0x63: //C, c
|
|
|
|
return las.CurSec //curve section
|
|
|
|
case 0x50, 0x70: //P, p
|
|
|
|
return las.ParSec //data section
|
|
|
|
default:
|
|
|
|
return las.OthSec
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 23:49:11 +03:00
|
|
|
// saveHeaderWarning - забирает и сохраняет варнинги от всех проверок
|
|
|
|
func (las *Las) storeHeaderWarning(chkResults CheckResults) {
|
|
|
|
for _, v := range chkResults {
|
|
|
|
las.addWarning(v.warning)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-27 19:21:49 +03:00
|
|
|
//Разбор одной строки с мнемоникой каротажа
|
2020-06-25 01:07:02 +03:00
|
|
|
//Разбираем а потом сохраняем в slice
|
2020-02-27 19:21:49 +03:00
|
|
|
//Каждый каротаж характеризуется тремя именами
|
2020-06-28 20:46:04 +03:00
|
|
|
//Name - имя каротажа, повторятся не может, если есть повторение, то Name строится добавлением к IName индекса
|
2020-02-27 19:21:49 +03:00
|
|
|
//IName - имя каротажа в исходном файле, может повторятся
|
2020-03-04 00:02:17 +03:00
|
|
|
//Mnemonic - мнемоника, берётся из словаря, если в словаре не найдено, то ""
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) readCurveParam(s string) error {
|
2020-06-21 18:00:28 +03:00
|
|
|
l := NewLasCurve(s, las)
|
|
|
|
las.Logs = append(las.Logs, l) //добавление в хранилище кривой каротажа с колонкой глубин
|
2020-02-27 19:21:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-22 19:29:32 +03:00
|
|
|
// тестирование на монотонность трёх последних точек глубин внесённых в контейнер
|
|
|
|
func (las *Las) deptMonotony() CheckRes {
|
|
|
|
if len(las.Logs) == 0 || len(las.Logs[0].D) <= 3 {
|
|
|
|
return CheckRes{"DPTM", TWarning{directOnRead, lasSecWellInfo, las.currentLine, ""}, nil, true}
|
2020-03-08 00:34:07 +03:00
|
|
|
}
|
2020-06-22 19:29:32 +03:00
|
|
|
i := len(las.Logs[0].D) - 1 // индекс последней добавленной в контейнер глубины index of last element in container
|
|
|
|
res := (las.Logs[0].D[i] - las.Logs[0].D[i-1]) == (las.Logs[0].D[i-1] - las.Logs[0].D[i-2])
|
|
|
|
return CheckRes{"DPTM", TWarning{directOnRead, lasSecWellInfo, las.currentLine, "depth not monotony"}, nil, res}
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-06-22 19:29:32 +03:00
|
|
|
// LoadDataSec - read data section from rows
|
|
|
|
func (las *Las) LoadDataSec(m int) (int, error) {
|
2020-02-27 19:21:49 +03:00
|
|
|
var (
|
|
|
|
v float64
|
|
|
|
err error
|
|
|
|
dept float64
|
|
|
|
)
|
2020-06-28 20:46:04 +03:00
|
|
|
n := len(las.Logs) // количество каротажей, столько колонок данных ожидаем
|
|
|
|
dataRows := las.rows[m:] // reslice to lines with data only
|
|
|
|
nullAsStr := strconv.FormatFloat(las.NULL(), 'f', 5, 64) // Null as string
|
2020-06-21 18:00:28 +03:00
|
|
|
las.currentLine = m - 1
|
|
|
|
for _, line := range dataRows {
|
2020-06-11 00:00:45 +03:00
|
|
|
las.currentLine++
|
2020-06-21 18:00:28 +03:00
|
|
|
line = strings.TrimSpace(line) // reslice
|
|
|
|
if isIgnoredLine(line) {
|
2020-06-11 00:00:45 +03:00
|
|
|
continue
|
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
fields := strings.Fields(line) //separators: tab and space
|
|
|
|
//line must have n columns
|
|
|
|
if len(fields) == 0 { // empty line: warning and ignore
|
2020-06-22 19:29:32 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "wow this happened, the line is empty, ignore"})
|
2020-06-11 00:00:45 +03:00
|
|
|
continue
|
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
if len(fields) != n {
|
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("line contains %d columns, expected: %d", len(fields), n)})
|
|
|
|
}
|
2020-06-22 19:29:32 +03:00
|
|
|
// we will analyze the first column separately to check for monotony, and if occure error on parse first column then all line ignore
|
2020-06-21 18:00:28 +03:00
|
|
|
dept, err = strconv.ParseFloat(fields[0], 64)
|
2020-06-11 00:00:45 +03:00
|
|
|
if err != nil {
|
2020-06-21 18:00:28 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("dept:'%s' not numeric, line ignore", fields[0])})
|
2020-06-11 00:00:45 +03:00
|
|
|
continue
|
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
las.Logs[0].D = append(las.Logs[0].D, dept)
|
|
|
|
las.Logs[0].V = append(las.Logs[0].V, dept) //TODO надо подумать про колонку значений для кривой DEPT
|
2020-06-22 19:29:32 +03:00
|
|
|
// проверка монотонности шага
|
|
|
|
if cr := las.deptMonotony(); !cr.res {
|
|
|
|
las.addWarning(cr.warning)
|
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
|
|
|
|
for j := 1; j < n; j++ { // цикл по каротажам
|
|
|
|
s := ""
|
|
|
|
if j >= len(fields) {
|
|
|
|
s = nullAsStr // columns count in current line less then curves count, fill as null value
|
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("for column %d data not present, value set to NULL", j+1)})
|
|
|
|
} else {
|
|
|
|
s = fields[j]
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
v, err = strconv.ParseFloat(s, 64)
|
2020-06-11 00:00:45 +03:00
|
|
|
if err != nil {
|
2020-06-21 18:00:28 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("error convert string: '%s' to number, set to NULL", s)})
|
2020-06-28 20:46:04 +03:00
|
|
|
v = las.NULL()
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
las.Logs[j].D = append(las.Logs[j].D, dept)
|
|
|
|
las.Logs[j].V = append(las.Logs[j].V, v)
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
}
|
|
|
|
return las.NumPoints(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NumPoints - return actually number of points in data
|
|
|
|
func (las *Las) NumPoints() int {
|
|
|
|
if len(las.Logs) == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return len(las.Logs[0].D)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dept - return slice of DEPT curve (first column)
|
|
|
|
func (las *Las) Dept() []float64 {
|
|
|
|
if len(las.Logs) <= 0 {
|
|
|
|
return []float64{}
|
|
|
|
}
|
|
|
|
return las.Logs[0].D
|
|
|
|
}
|
|
|
|
|
|
|
|
//Save - save to file
|
|
|
|
//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 (las *Las) Save(fileName string, useMnemonic ...bool) error {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
bufToSave []byte
|
|
|
|
)
|
|
|
|
if len(useMnemonic) > 0 {
|
|
|
|
bufToSave, err = las.SaveToBuf(useMnemonic[0]) //TODO las.SaveToBuf(true) not test
|
|
|
|
} else {
|
|
|
|
bufToSave, err = las.SaveToBuf(false)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err //TODO случай возврата ошибки из las.SaveToBuf() не тестируется
|
|
|
|
}
|
|
|
|
if !xlib.FileExists(fileName) {
|
|
|
|
err = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
|
2020-06-11 00:00:45 +03:00
|
|
|
if err != nil {
|
2020-06-21 18:00:28 +03:00
|
|
|
return errors.New("path: '" + filepath.Dir(fileName) + "' can't create >>" + err.Error())
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
err = ioutil.WriteFile(fileName, bufToSave, 0644)
|
2020-06-11 00:00:45 +03:00
|
|
|
if err != nil {
|
2020-06-21 18:00:28 +03:00
|
|
|
return errors.New("file: '" + fileName + "' can't open to write >>" + err.Error())
|
2020-06-11 00:00:45 +03:00
|
|
|
}
|
2020-06-21 18:00:28 +03:00
|
|
|
return nil
|
2020-04-10 00:55:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveToBuf - save to file
|
|
|
|
// 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
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) SaveToBuf(useMnemonic bool) ([]byte, error) {
|
|
|
|
n := len(las.Logs) //log count
|
2020-04-10 00:55:08 +03:00
|
|
|
if n <= 0 {
|
|
|
|
return nil, errors.New("logs not exist")
|
|
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
|
|
fmt.Fprint(&b, _LasFirstLine)
|
2020-05-19 01:18:23 +03:00
|
|
|
fmt.Fprintf(&b, _LasVersion, 2.0) //file is always saved in 2.0 format
|
2020-04-10 00:55:08 +03:00
|
|
|
fmt.Fprint(&b, _LasWrap)
|
|
|
|
fmt.Fprint(&b, _LasWellInfoSec)
|
2020-06-28 20:46:04 +03:00
|
|
|
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())
|
2020-04-10 00:55:08 +03:00
|
|
|
fmt.Fprint(&b, _LasCurvSec)
|
|
|
|
fmt.Fprint(&b, _LasCurvDept)
|
|
|
|
|
|
|
|
for i := 1; i < n; i++ { //Пишем названия каротажей
|
2020-06-21 18:00:28 +03:00
|
|
|
l := las.Logs[i]
|
2020-04-10 00:55:08 +03:00
|
|
|
if useMnemonic {
|
|
|
|
if len(l.Mnemonic) > 0 {
|
|
|
|
l.Name = l.Mnemonic
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(&b, _LasCurvLine, l.Name, l.Unit) //запись мнемоник в секции ~Curve
|
|
|
|
}
|
|
|
|
fmt.Fprint(&b, _LasDataSec)
|
2020-06-21 18:00:28 +03:00
|
|
|
fmt.Fprintf(&b, "%s\n", las.Logs.Captions()) //write comment with curves name
|
2020-04-10 00:55:08 +03:00
|
|
|
//write data
|
2020-06-21 18:00:28 +03:00
|
|
|
for i := 0; i < las.NumPoints(); i++ { //loop by dept (.)
|
|
|
|
fmt.Fprintf(&b, "%-10.4f ", las.Logs[0].D[i])
|
2020-04-10 00:55:08 +03:00
|
|
|
for j := 1; j < n; j++ { //loop by logs
|
2020-06-21 18:00:28 +03:00
|
|
|
fmt.Fprintf(&b, "%-10.4f ", las.Logs[j].V[i])
|
2020-04-10 00:55:08 +03:00
|
|
|
}
|
|
|
|
fmt.Fprintln(&b)
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
r, _ := cpd.NewReaderTo(io.Reader(&b), las.oCodepage.String()) //ошибку не обрабатываем, допустимость oCodepage проверяем раньше, других причин нет
|
2020-04-10 00:55:08 +03:00
|
|
|
bufToSave, _ := ioutil.ReadAll(r)
|
|
|
|
return bufToSave, nil
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
|
|
|
|
// IsEmpty - test to not initialize object
|
|
|
|
func (las *Las) IsEmpty() bool {
|
|
|
|
return (las.Logs == nil)
|
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
|
2020-06-10 01:07:20 +03:00
|
|
|
// GetStrtFromData - return strt from data section
|
|
|
|
// read 1 line from section ~A and determine strt
|
|
|
|
// close file
|
|
|
|
// return Null if error occurs
|
|
|
|
func (las *Las) GetStrtFromData() float64 {
|
|
|
|
iFile, err := os.Open(las.FileName)
|
|
|
|
if err != nil {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL() //не обрабатывается в тесте
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
defer iFile.Close()
|
|
|
|
|
|
|
|
_, iScanner, err := xlib.SeekFileStop(las.FileName, "~A")
|
|
|
|
if (err != nil) || (iScanner == nil) {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL() //не обрабатывается в тесте
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
s := ""
|
|
|
|
dept1 := 0.0
|
|
|
|
for i := 0; iScanner.Scan(); i++ {
|
|
|
|
s = strings.TrimSpace(iScanner.Text())
|
|
|
|
if (len(s) == 0) || (s[0] == '#') {
|
2020-06-21 18:00:28 +03:00
|
|
|
continue //не обрабатывается в тесте
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
k := strings.IndexRune(s, ' ')
|
|
|
|
if k < 0 {
|
|
|
|
k = len(s)
|
|
|
|
}
|
|
|
|
dept1, err = strconv.ParseFloat(s[:k], 64)
|
|
|
|
if err != nil {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL() //не обрабатывается в тесте
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
return dept1
|
|
|
|
}
|
|
|
|
//если мы попали сюда, то всё грусно, в файле после ~A не нашлось двух строчек с данными... или пустые строчки или комменты
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL()
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// GetStepFromData - return step from data section
|
|
|
|
// read 2 line from section ~A and determine step
|
|
|
|
// close file
|
|
|
|
// return Null if error occure
|
|
|
|
func (las *Las) GetStepFromData() float64 {
|
|
|
|
iFile, err := os.Open(las.FileName)
|
2020-05-10 04:20:51 +03:00
|
|
|
if err != nil {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL() //не обрабатывается в тесте
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
defer iFile.Close()
|
2020-05-10 04:20:51 +03:00
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
_, iScanner, err := xlib.SeekFileStop(las.FileName, "~A")
|
|
|
|
if (err != nil) || (iScanner == nil) {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL()
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
|
|
|
|
s := ""
|
|
|
|
j := 0
|
|
|
|
dept1 := 0.0
|
|
|
|
dept2 := 0.0
|
|
|
|
for i := 0; iScanner.Scan(); i++ {
|
|
|
|
s = strings.TrimSpace(iScanner.Text())
|
2020-06-21 18:00:28 +03:00
|
|
|
if isIgnoredLine(s) {
|
2020-06-02 20:49:41 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
k := strings.IndexRune(s, ' ')
|
|
|
|
if k < 0 {
|
|
|
|
k = len(s)
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
dept1, err = strconv.ParseFloat(s[:k], 64)
|
|
|
|
if err != nil {
|
2020-06-10 20:05:16 +03:00
|
|
|
// case if the data row in the first position (dept place) contains not a number
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL()
|
2020-06-02 20:49:41 +03:00
|
|
|
}
|
|
|
|
j++
|
|
|
|
if j == 2 {
|
2020-06-10 20:05:16 +03:00
|
|
|
// good case, found two points and determined the step
|
2020-06-02 20:49:41 +03:00
|
|
|
return math.Round((dept1-dept2)*10) / 10
|
|
|
|
}
|
|
|
|
dept2 = dept1
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
2020-06-10 20:05:16 +03:00
|
|
|
//bad case, data section not contain two rows with depth
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.NULL() //не обрабатывается в тесте
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
func (las *Las) setStep(h float64) {
|
2020-06-28 20:46:04 +03:00
|
|
|
las.WelSec.params["STEP"] = HeaderParam{strconv.FormatFloat(h, 'f', -1, 64), "STEP", "", "", "", "step of index", 8}
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
|
2020-06-21 18:00:28 +03:00
|
|
|
func (las *Las) setStrt(strt float64) {
|
2020-06-28 20:46:04 +03:00
|
|
|
las.WelSec.params["STRT"] = HeaderParam{strconv.FormatFloat(strt, 'f', -1, 64), "STRT", "", "", "", "first index value", 6}
|
2020-06-10 01:07:20 +03:00
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// IsStrtEmpty - return true if parameter Strt not exist in file
|
|
|
|
func (las *Las) IsStrtEmpty() bool {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.STRT() == StdNull
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// IsStopEmpty - return true if parameter Stop not exist in file
|
|
|
|
func (las *Las) IsStopEmpty() bool {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.STOP() == StdNull
|
2020-06-02 20:49:41 +03:00
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// IsStepEmpty - return true if parameter Step not exist in file
|
|
|
|
func (las *Las) IsStepEmpty() bool {
|
2020-06-28 20:46:04 +03:00
|
|
|
return las.STEP() == StdNull
|
2020-06-02 20:49:41 +03:00
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// SetNull - change parameter NULL in WELL INFO section and in all logs
|
2020-06-28 20:46:04 +03:00
|
|
|
func (las *Las) SetNull(null float64) {
|
2020-06-02 20:49:41 +03:00
|
|
|
for _, l := range las.Logs { //loop by logs
|
|
|
|
for i := range l.V { //loop by dept step
|
2020-06-28 20:46:04 +03:00
|
|
|
if l.V[i] == las.NULL() {
|
|
|
|
l.V[i] = null
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 20:46:04 +03:00
|
|
|
las.WelSec.params["NULL"] = HeaderParam{strconv.FormatFloat(null, 'f', -1, 64), "NULL", "", "", "", "null value", las.WelSec.params["NULL"].lineNo}
|
2020-06-21 18:00:28 +03:00
|
|
|
}
|
2020-06-28 15:35:32 +03:00
|
|
|
|
|
|
|
// SaveWarning - save to file all warning
|
|
|
|
func (las *Las) SaveWarning(fileName string) error {
|
|
|
|
if las.Warnings.Count() == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
oFile, err := os.Create(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
las.SaveWarningToFile(oFile)
|
|
|
|
oFile.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveWarningToWriter - store all warning to writer, return count lines writed to
|
|
|
|
func (las *Las) SaveWarningToWriter(writer *bufio.Writer) int {
|
|
|
|
n := las.Warnings.Count()
|
|
|
|
if n == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
for _, w := range las.Warnings {
|
|
|
|
writer.WriteString(w.String())
|
|
|
|
writer.WriteString("\n")
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveWarningToFile - store all warning to file, file not close. return count warning writed
|
|
|
|
func (las *Las) SaveWarningToFile(oFile *os.File) int {
|
|
|
|
if oFile == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if las.Warnings.Count() == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
oFile.WriteString("**file: " + las.FileName + "**\n")
|
|
|
|
n := las.Warnings.SaveWarningToFile(oFile)
|
|
|
|
oFile.WriteString("\n")
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
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, -1, "*maximum count* of warning reached, change parameter 'maxWarningCount' in 'glas.ini'"})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMnemonic - return Mnemonic from dictionary by Log Name,
|
|
|
|
// if Mnemonic not found return ""
|
|
|
|
// if Dictionary is nil, then return ""
|
|
|
|
func (las *Las) GetMnemonic(logName string) string {
|
|
|
|
if (las.LogDic == nil) || (las.VocDic == nil) {
|
|
|
|
return "" //"-"
|
|
|
|
}
|
|
|
|
_, ok := (*las.LogDic)[logName]
|
|
|
|
if ok { //GOOD - название каротажа равно мнемонике
|
|
|
|
return logName
|
|
|
|
}
|
|
|
|
v, ok := (*las.VocDic)[logName]
|
|
|
|
if ok { //POOR - название загружаемого каротажа найдено в словаре подстановок, мнемоника найдена
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|