2020-04-28 22:04:01 +03:00
|
|
|
// (c) softland 2020
|
|
|
|
// softlandia@gmail.com
|
|
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
|
|
|
///format strings represent structure of LAS file
|
|
|
|
const (
|
2020-05-19 01:18:23 +03:00
|
|
|
_LasFirstLine = "~Version information\n"
|
|
|
|
_LasVersion = "VERS. %3.1f : glas (c) softlandia@gmail.com\n"
|
2020-02-27 19:21:49 +03:00
|
|
|
_LasWrap = "WRAP. NO : ONE LINE PER DEPTH STEP\n"
|
2020-05-19 01:18:23 +03:00
|
|
|
_LasWellInfoSec = "~Well information\n"
|
2020-02-27 19:21:49 +03:00
|
|
|
_LasMnemonicFormat = "#MNEM.UNIT DATA :DESCRIPTION\n"
|
|
|
|
_LasStrt = " STRT.M %8.3f :START DEPTH\n"
|
|
|
|
_LasStop = " STOP.M %8.3f :STOP DEPTH\n"
|
|
|
|
_LasStep = " STEP.M %8.3f :STEP\n"
|
|
|
|
_LasNull = " NULL. %9.3f :NULL VALUE\n"
|
|
|
|
_LasRkb = " RKB.M %8.3f :KB or GL\n"
|
|
|
|
_LasXcoord = " XWELL.M %8.3f :Well head X coordinate\n"
|
|
|
|
_LasYcoord = " YWELL.M %8.3f :Well head Y coordinate\n"
|
|
|
|
_LasOilComp = " COMP. %-43.43s:OIL COMPANY\n"
|
|
|
|
_LasWell = " WELL. %-43.43s:WELL\n"
|
|
|
|
_LasField = " FLD . %-43.43s:FIELD\n"
|
|
|
|
_LasLoc = " LOC . %-43.43s:LOCATION\n"
|
|
|
|
_LasCountry = " CTRY. %-43.43s:COUNTRY\n"
|
|
|
|
_LasServiceComp = " SRVC. %-43.43s:SERVICE COMPANY\n"
|
|
|
|
_LasDate = " DATE. %-43.43s:DATE\n"
|
|
|
|
_LasAPI = " API . %-43.43s:API NUMBER\n"
|
|
|
|
_LasUwi = " UWI . %-43.43s:UNIVERSAL WELL INDEX\n"
|
|
|
|
_LasCurvSec = "~Curve Information Section\n"
|
|
|
|
_LasCurvFormat = "#MNEM.UNIT :DESCRIPTION\n"
|
|
|
|
_LasCurvDept = " DEPT.M :\n"
|
|
|
|
_LasCurvLine = " %s.%s :\n"
|
|
|
|
_LasDataSec = "~ASCII Log Data\n"
|
|
|
|
|
|
|
|
//secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - dAta
|
|
|
|
lasSecIgnore = 0
|
2020-05-10 04:20:51 +03:00
|
|
|
lasSecVersion = 1
|
2020-02-27 19:21:49 +03:00
|
|
|
lasSecWellInfo = 2
|
|
|
|
lasSecCurInfo = 3
|
|
|
|
lasSecData = 4
|
|
|
|
)
|
|
|
|
|
2020-06-10 19:39:48 +03:00
|
|
|
// HeaderParam - any parameter of LAS
|
|
|
|
type HeaderParam struct {
|
|
|
|
lineNo int // number of line in source file
|
|
|
|
source, // text line from source file contain this parameter
|
|
|
|
name, // name of parameter: STOP, WELL, SP - curve name also
|
|
|
|
Val, // parameter value
|
|
|
|
Unit, // unit of parameter
|
|
|
|
Description string // descrioption of parameter
|
2020-04-10 00:55:08 +03:00
|
|
|
}
|
|
|
|
|
2020-06-10 19:39:48 +03:00
|
|
|
// HeaderSection - contain parameters of Well section
|
|
|
|
type HeaderSection map[string]HeaderParam
|
|
|
|
|
2020-02-27 19:21:49 +03:00
|
|
|
// Las - class to store las file
|
|
|
|
// input code page autodetect
|
|
|
|
// 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
|
2020-04-28 22:04:01 +03:00
|
|
|
//TODO при создании объекта las есть возможность указать кодировку записи, нужна возможность указать явно кодировку чтения
|
2020-02-27 19:21:49 +03:00
|
|
|
type Las struct {
|
2020-04-10 00:55:08 +03:00
|
|
|
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
|
|
|
|
Ver float64 //version 1.0, 1.2 or 2.0
|
|
|
|
Wrap string //YES || NO
|
|
|
|
Strt float64 //start depth
|
|
|
|
Stop float64 //stop depth
|
|
|
|
Step float64 //depth step
|
|
|
|
Null float64 //value interpreted as empty
|
|
|
|
Well string //well name
|
|
|
|
Rkb float64 //altitude KB
|
|
|
|
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
|
|
|
|
ePoints int //expected count (.)
|
|
|
|
nPoints int //actually count (.)
|
|
|
|
oCodepage cpd.IDCodePage //codepage to save, default xlib.CpWindows1251. to special value, specify at make: NewLas(cp...)
|
|
|
|
iDuplicate int //индекс повторящейся мнемоники, увеличивается на 1 при нахождении дубля, начально 0
|
|
|
|
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,
|
|
|
|
WellSec,
|
|
|
|
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-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 {
|
|
|
|
las := new(Las)
|
|
|
|
las.Ver = 2.0
|
|
|
|
las.Wrap = "NO"
|
2020-05-10 04:20:51 +03:00
|
|
|
// до того как прочитаем данные мы не можем знать сколько их фактически, предполагаем что 1000, достаточно большой буфер
|
|
|
|
// избавит от необходимости довыделять память при чтении
|
|
|
|
las.ePoints = ExpPoints
|
2020-02-27 19:21:49 +03:00
|
|
|
las.Logs = make(map[string]LasCurve)
|
2020-06-10 19:39:48 +03:00
|
|
|
las.VerSec = make(HeaderSection)
|
|
|
|
las.WellSec = make(HeaderSection)
|
|
|
|
las.CurSec = make(HeaderSection)
|
|
|
|
las.ParSec = make(HeaderSection)
|
|
|
|
las.OthSec = make(HeaderSection)
|
2020-04-10 00:55:08 +03:00
|
|
|
las.maxWarningCount = MaxWarningCount
|
2020-06-02 20:49:41 +03:00
|
|
|
las.stdNull = StdNull
|
|
|
|
las.Strt = StdNull
|
|
|
|
las.Stop = StdNull
|
|
|
|
las.Step = 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
|
|
|
|
//счётчик повторяющихся мнемоник, увеличивается каждый раз на 1, используется при переименовании мнемоники
|
|
|
|
las.iDuplicate = 0
|
|
|
|
return las
|
|
|
|
}
|
|
|
|
|
2020-04-28 22:04:01 +03:00
|
|
|
// 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 {
|
2020-02-27 19:21:49 +03:00
|
|
|
switch r {
|
|
|
|
case 86: //V
|
2020-05-10 04:20:51 +03:00
|
|
|
return lasSecVersion //version section
|
2020-02-27 19:21:49 +03:00
|
|
|
case 118: //v
|
2020-05-10 04:20:51 +03:00
|
|
|
return lasSecVersion //version section
|
2020-02-27 19:21:49 +03:00
|
|
|
case 87: //W
|
|
|
|
return lasSecWellInfo //well info section
|
|
|
|
case 119: //w
|
|
|
|
return lasSecWellInfo //well info section
|
|
|
|
case 67: //C
|
|
|
|
return lasSecCurInfo //curve section
|
|
|
|
case 99: //c
|
|
|
|
return lasSecCurInfo //curve section
|
|
|
|
case 65: //A
|
|
|
|
return lasSecData //data section
|
|
|
|
case 97: //a
|
|
|
|
return lasSecData //data section
|
|
|
|
default:
|
|
|
|
return lasSecIgnore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsWraped - return true if WRAP == YES
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) IsWraped() bool {
|
|
|
|
return strings.Contains(strings.ToUpper(las.Wrap), "Y") //(strings.Index(strings.ToUpper(o.Wrap), "Y") >= 0)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveWarning - save to file all warning
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) SaveWarning(fileName string) error {
|
|
|
|
if las.Warnings.Count() == 0 {
|
2020-02-27 19:21:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
oFile, err := os.Create(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
las.SaveWarningToFile(oFile)
|
2020-02-27 19:21:49 +03:00
|
|
|
oFile.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveWarningToWriter - store all warning to writer, return count lines writed to
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) SaveWarningToWriter(writer *bufio.Writer) int {
|
|
|
|
n := las.Warnings.Count()
|
2020-02-27 19:21:49 +03:00
|
|
|
if n == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
for _, w := range las.Warnings {
|
2020-02-27 19:21:49 +03:00
|
|
|
writer.WriteString(w.String())
|
|
|
|
writer.WriteString("\n")
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveWarningToFile - store all warning to file, file not close. return count warning writed
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) SaveWarningToFile(oFile *os.File) int {
|
2020-02-27 19:21:49 +03:00
|
|
|
if oFile == nil {
|
|
|
|
return 0
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
if las.Warnings.Count() == 0 {
|
2020-02-27 19:21:49 +03:00
|
|
|
return 0
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
oFile.WriteString("**file: " + las.FileName + "**\n")
|
|
|
|
n := las.Warnings.SaveWarningToFile(oFile)
|
2020-02-27 19:21:49 +03:00
|
|
|
oFile.WriteString("\n")
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2020-04-28 22:04:01 +03:00
|
|
|
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'"})
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-04 00:02:17 +03:00
|
|
|
// GetMnemonic - return Mnemonic from dictionary by Log Name,
|
|
|
|
// if Mnemonic not found return ""
|
|
|
|
// if Dictionary is nil, then return ""
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) GetMnemonic(logName string) string {
|
|
|
|
if (las.LogDic == nil) || (las.VocDic == nil) {
|
2020-03-04 00:02:17 +03:00
|
|
|
return "" //"-"
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
_, ok := (*las.LogDic)[logName]
|
2020-02-27 19:21:49 +03:00
|
|
|
if ok { //GOOD - название каротажа равно мнемонике
|
|
|
|
return logName
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
v, ok := (*las.VocDic)[logName]
|
2020-02-27 19:21:49 +03:00
|
|
|
if ok { //POOR - название загружаемого каротажа найдено в словаре подстановок, мнемоника найдена
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open - load las file
|
2020-04-13 21:31:31 +03:00
|
|
|
// return error on:
|
2020-06-02 20:49:41 +03:00
|
|
|
// - file not exist
|
2020-04-13 21:31:31 +03:00
|
|
|
// - file cannot be decoded to UTF-8
|
|
|
|
// - las is wrapped
|
|
|
|
// - las file not contain Curve section
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) Open(fileName string) (int, error) {
|
|
|
|
var err error
|
|
|
|
las.File, err = os.Open(fileName)
|
|
|
|
if err != nil {
|
2020-06-02 20:49:41 +03:00
|
|
|
return 0, err //FATAL error - file not exist
|
2020-04-28 22:04:01 +03:00
|
|
|
}
|
|
|
|
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 {
|
2020-06-02 20:49:41 +03:00
|
|
|
return 0, err //FATAL error - file cannot be decoded to UTF-8
|
2020-04-28 22:04:01 +03:00
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
// prepare file to read
|
2020-04-28 22:04:01 +03:00
|
|
|
las.scanner = bufio.NewScanner(las.Reader)
|
|
|
|
las.currentLine = 0
|
|
|
|
las.LoadHeader()
|
|
|
|
stdChecker := NewStdChecker()
|
2020-06-02 20:49:41 +03:00
|
|
|
// check for FATAL errors
|
2020-05-10 04:20:51 +03:00
|
|
|
r := stdChecker.check(las)
|
2020-06-02 20:49:41 +03:00
|
|
|
las.storeHeaderWarning(r)
|
|
|
|
if err = r.fatal(); err != nil {
|
2020-05-10 04:20:51 +03:00
|
|
|
return 0, err
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
if r.nullWrong() {
|
|
|
|
las.SetNull(las.stdNull)
|
|
|
|
}
|
2020-06-10 01:07:20 +03:00
|
|
|
if r.strtWrong() {
|
|
|
|
h := las.GetStrtFromData() // return las.Null if cannot find strt in the data section.
|
|
|
|
if h == las.Null {
|
|
|
|
las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprint("__WRN__ STRT parameter on data is wrong setting to 0")})
|
|
|
|
las.setStrt(0)
|
|
|
|
}
|
|
|
|
las.setStrt(h)
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
if r.stepWrong() {
|
2020-06-02 20:49:41 +03:00
|
|
|
h := las.GetStepFromData() // return las.Null if cannot calculate step from data
|
2020-04-28 22:04:01 +03:00
|
|
|
if h == las.Null {
|
2020-06-10 19:39:48 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecWellInfo, las.currentLine, fmt.Sprint("__WRN__ STEP parameter on data is wrong")})
|
2020-04-28 22:04:01 +03:00
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
las.setStep(h)
|
2020-04-28 22:04:01 +03:00
|
|
|
}
|
|
|
|
return las.ReadDataSec(fileName)
|
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// saveHeaderWarning - забирает и сохраняет варнинги от всех проверок
|
|
|
|
func (las *Las) storeHeaderWarning(chkResults CheckResults) {
|
2020-05-10 04:20:51 +03:00
|
|
|
for _, v := range chkResults {
|
|
|
|
las.addWarning(v.warning)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-14 00:32:03 +03:00
|
|
|
/*LoadHeader - read las file and load all section before ~A
|
|
|
|
secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - A data
|
2020-02-27 19:21:49 +03:00
|
|
|
1. читаем строку
|
|
|
|
2. если коммент или пустая в игнор
|
|
|
|
3. если начало секции, определяем какой
|
|
|
|
4. если началась секция данных заканчиваем
|
2020-04-13 21:31:31 +03:00
|
|
|
5. читаем одну строку (это один параметер из известной нам секции)
|
2020-06-10 19:39:48 +03:00
|
|
|
Пока всегда возвращает nil, причин возвращать другое значение пока нет.
|
2020-04-13 21:31:31 +03:00
|
|
|
*/
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) LoadHeader() error {
|
2020-02-27 19:21:49 +03:00
|
|
|
s := ""
|
|
|
|
var err error
|
|
|
|
secNum := 0
|
2020-04-28 22:04:01 +03:00
|
|
|
for i := 0; las.scanner.Scan(); i++ {
|
|
|
|
s = strings.TrimSpace(las.scanner.Text())
|
|
|
|
las.currentLine++
|
2020-02-27 19:21:49 +03:00
|
|
|
if isIgnoredLine(s) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if s[0] == '~' { //start new section
|
2020-04-28 22:04:01 +03:00
|
|
|
secNum = las.selectSection(rune(s[1]))
|
2020-02-27 19:21:49 +03:00
|
|
|
if secNum == lasSecData {
|
2020-06-10 20:05:16 +03:00
|
|
|
break // reached the data section, stop load header
|
2020-06-10 19:39:48 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//if not comment, not empty and not new section => parameter, read it
|
|
|
|
err = las.ReadParameter(s, secNum)
|
|
|
|
if err != nil {
|
|
|
|
las.addWarning(TWarning{directOnRead, secNum, las.currentLine, fmt.Sprintf("param: '%s' error: %v", s, err)})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-10 20:05:16 +03:00
|
|
|
// LoadHeader2 - new version, after test will be replace verion LoadHeader
|
2020-06-10 19:39:48 +03:00
|
|
|
func (las *Las) LoadHeader2() error {
|
|
|
|
s := ""
|
|
|
|
var err error
|
|
|
|
//sec := las.VerSec
|
|
|
|
secNum := 0
|
|
|
|
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 = las.selectSection(rune(s[1]))
|
|
|
|
if secNum == lasSecData {
|
2020-06-10 20:05:16 +03:00
|
|
|
break // reached the data section, stop load header
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
} else {
|
2020-05-10 04:20:51 +03:00
|
|
|
//if not comment, not empty and not new section => parameter, read it
|
|
|
|
err = las.ReadParameter(s, secNum)
|
2020-02-27 19:21:49 +03:00
|
|
|
if err != nil {
|
2020-06-10 19:39:48 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, secNum, las.currentLine, fmt.Sprintf("param: '%s' error: %v", s, err)})
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-14 00:32:03 +03:00
|
|
|
// ReadParameter - read one parameter
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) ReadParameter(s string, secNum int) error {
|
2020-02-27 19:21:49 +03:00
|
|
|
switch secNum {
|
2020-05-10 04:20:51 +03:00
|
|
|
case lasSecVersion:
|
2020-04-28 22:04:01 +03:00
|
|
|
return las.readVersionParam(s)
|
2020-02-27 19:21:49 +03:00
|
|
|
case lasSecWellInfo:
|
2020-04-28 22:04:01 +03:00
|
|
|
return las.ReadWellParam(s)
|
2020-02-27 19:21:49 +03:00
|
|
|
case lasSecCurInfo:
|
2020-04-28 22:04:01 +03:00
|
|
|
return las.readCurveParam(s)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) readVersionParam(s string) error {
|
2020-02-27 19:21:49 +03:00
|
|
|
var err error
|
2020-04-10 00:55:08 +03:00
|
|
|
p := NewLasParam(s)
|
2020-02-27 19:21:49 +03:00
|
|
|
switch p.Name {
|
|
|
|
case "VERS":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Ver, err = strconv.ParseFloat(p.Val, 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
case "WRAP":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Wrap = p.Val
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//ReadWellParam - read parameter from WELL section
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) ReadWellParam(s string) error {
|
2020-02-27 19:21:49 +03:00
|
|
|
var err error
|
2020-04-10 00:55:08 +03:00
|
|
|
p := NewLasParam(s)
|
2020-02-27 19:21:49 +03:00
|
|
|
switch p.Name {
|
|
|
|
case "STRT":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Strt, err = strconv.ParseFloat(p.Val, 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
case "STOP":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Stop, err = strconv.ParseFloat(p.Val, 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
case "STEP":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Step, err = strconv.ParseFloat(p.Val, 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
case "NULL":
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Null, err = strconv.ParseFloat(p.Val, 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
case "WELL":
|
2020-04-28 22:04:01 +03:00
|
|
|
if las.Ver < 2.0 {
|
|
|
|
las.Well = p.Desc
|
2020-02-27 19:21:49 +03:00
|
|
|
} else {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Well = wellNameFromParam(p)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("detected param: %v, unit:%v, value: %v\n", p.Name, p.Unit, p.Val)})
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//ChangeDuplicateLogName - return non duplicated name of log
|
|
|
|
//if input name unique, return input name
|
|
|
|
//if input name not unique, return input name + index duplicate
|
|
|
|
//index duplicate - Las field, increase
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) ChangeDuplicateLogName(name string) string {
|
2020-02-27 19:21:49 +03:00
|
|
|
s := ""
|
2020-04-28 22:04:01 +03:00
|
|
|
if _, ok := las.Logs[name]; ok {
|
|
|
|
las.iDuplicate++
|
|
|
|
s = fmt.Sprintf("%v", las.iDuplicate)
|
2020-02-27 19:21:49 +03:00
|
|
|
name += s
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
//Разбор одной строки с мнемоникой каротажа
|
|
|
|
//Разбираем в переменную l а потом сохраняем в map
|
|
|
|
//Каждый каротаж характеризуется тремя именами
|
|
|
|
//IName - имя каротажа в исходном файле, может повторятся
|
|
|
|
//Name - ключ в map хранилище, повторятся не может. если в исходном есть повторение, то Name строится добавлением к 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-04-10 00:55:08 +03:00
|
|
|
l := NewLasCurve(s)
|
2020-05-10 04:20:51 +03:00
|
|
|
// поскольку этот метод вызывается для каждой кривой в секции ~Curve, то заново определять для каждой кривой количество ожидаемых
|
|
|
|
// точек через las.GetExpectedPointsCount() СТРАННО
|
|
|
|
l.Init(len(las.Logs), las.GetMnemonic(l.Name), las.ChangeDuplicateLogName(l.Name), las.GetExpectedPointsCount(las.Strt, las.Stop, las.Step))
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Logs[l.Name] = l //добавление в хранилище кривой каротажа с колонкой глубин
|
2020-02-27 19:21:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-10 04:20:51 +03:00
|
|
|
// GetExpectedPointsCount - оценка количества точек по параметрам STEP, STRT, STOP
|
|
|
|
// перед чтением данных пытаемся оценить их количество
|
|
|
|
// в правильной ситуации количество должно быть равныи (stop - strt)/step
|
|
|
|
// случаи ошибок:
|
|
|
|
// 1. step == 0
|
|
|
|
// 2. strt == stop
|
|
|
|
// при ошибке вернём предполагаемое (заданное по умолчанию) количество
|
|
|
|
// стандартные случаи:
|
|
|
|
// 1. strt < stop & step > 0
|
|
|
|
// 2. stop < strt & step < 0
|
|
|
|
func (las *Las) GetExpectedPointsCount(strt, stop, step float64) (m int) {
|
|
|
|
if step == 0.0 {
|
2020-04-28 22:04:01 +03:00
|
|
|
return las.ePoints
|
2020-03-08 00:34:07 +03:00
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
d := stop - strt
|
|
|
|
if d == 0 {
|
|
|
|
return las.ePoints
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
m = int(d/step) + 2
|
2020-02-27 19:21:49 +03:00
|
|
|
if m < 0 {
|
|
|
|
m = -m
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
//expandDept - if actually data points exceeds
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) expandDept(d *LasCurve) {
|
2020-02-27 19:21:49 +03:00
|
|
|
//actual number of points more then expected
|
2020-04-28 22:04:01 +03:00
|
|
|
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"})
|
2020-02-27 19:21:49 +03:00
|
|
|
//ожидаем удвоения данных
|
2020-04-28 22:04:01 +03:00
|
|
|
las.ePoints *= 2
|
2020-03-14 00:32:03 +03:00
|
|
|
//expand first log - dept
|
2020-04-28 22:04:01 +03:00
|
|
|
newDept := make([]float64, las.ePoints)
|
2020-05-20 02:31:08 +03:00
|
|
|
copy(newDept, d.D)
|
|
|
|
d.D = newDept
|
2020-02-27 19:21:49 +03:00
|
|
|
|
2020-04-28 22:04:01 +03:00
|
|
|
newLog := make([]float64, las.ePoints)
|
2020-05-20 02:31:08 +03:00
|
|
|
copy(newLog, d.D)
|
|
|
|
d.V = newLog
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Logs[d.Name] = *d
|
2020-02-27 19:21:49 +03:00
|
|
|
|
|
|
|
//loop over other logs
|
2020-04-28 22:04:01 +03:00
|
|
|
n := len(las.Logs)
|
2020-02-27 19:21:49 +03:00
|
|
|
var l *LasCurve
|
|
|
|
for j := 1; j < n; j++ {
|
2020-04-28 22:04:01 +03:00
|
|
|
l, _ = las.logByIndex(j)
|
|
|
|
newDept := make([]float64, las.ePoints)
|
2020-05-20 02:31:08 +03:00
|
|
|
copy(newDept, l.D)
|
|
|
|
l.D = newDept
|
2020-02-27 19:21:49 +03:00
|
|
|
|
2020-04-28 22:04:01 +03:00
|
|
|
newLog := make([]float64, las.ePoints)
|
2020-05-20 02:31:08 +03:00
|
|
|
copy(newLog, l.V)
|
|
|
|
l.V = newLog
|
2020-04-28 22:04:01 +03:00
|
|
|
las.Logs[l.Name] = *l
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 04:20:51 +03:00
|
|
|
func isSeparator(r rune) bool {
|
|
|
|
switch r {
|
|
|
|
case 9, 32:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-27 19:21:49 +03:00
|
|
|
// ReadDataSec - read section of data
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) ReadDataSec(fileName string) (int, error) {
|
2020-02-27 19:21:49 +03:00
|
|
|
var (
|
|
|
|
v float64
|
|
|
|
err error
|
|
|
|
d *LasCurve
|
|
|
|
l *LasCurve
|
|
|
|
dept float64
|
|
|
|
i int
|
|
|
|
)
|
|
|
|
|
2020-05-10 04:20:51 +03:00
|
|
|
// исходя из параметров STRT, STOP и STEP определяем ожидаемое количество строк данных
|
|
|
|
// данную операцию уже выполняли много раз при считывании кривых
|
|
|
|
las.ePoints = las.GetExpectedPointsCount(las.Strt, las.Stop, las.Step)
|
2020-04-28 22:04:01 +03:00
|
|
|
n := len(las.Logs) //количество каротажей, столько колонок данных ожидаем
|
|
|
|
d, _ = las.logByIndex(0) //dept log
|
2020-02-27 19:21:49 +03:00
|
|
|
s := ""
|
2020-04-28 22:04:01 +03:00
|
|
|
for i = 0; las.scanner.Scan(); i++ {
|
|
|
|
las.currentLine++
|
|
|
|
if i == las.ePoints {
|
|
|
|
las.expandDept(d)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
s = strings.TrimSpace(las.scanner.Text())
|
2020-05-10 04:20:51 +03:00
|
|
|
// i счётчик не считанных строк, а фактически считанных данных - счётчик добавлений в слайсы данных
|
2020-04-28 22:04:01 +03:00
|
|
|
//TODO возможно следует завести отдельный счётчик и оставить в покое счётчик цикла
|
2020-02-27 19:21:49 +03:00
|
|
|
if isIgnoredLine(s) {
|
|
|
|
i--
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
//first column is DEPT
|
2020-05-10 04:20:51 +03:00
|
|
|
k := strings.IndexFunc(s, isSeparator)
|
|
|
|
if k < 0 { //line must have n+1 column and n separated spaces block (+1 becouse first column DEPT)
|
2020-04-28 22:04:01 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("line: %d is empty, ignore", las.currentLine)})
|
2020-02-27 19:21:49 +03:00
|
|
|
i--
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
dept, err = strconv.ParseFloat(s[:k], 64)
|
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("first column '%s' not numeric, ignore", s[:k])})
|
2020-02-27 19:21:49 +03:00
|
|
|
i--
|
|
|
|
continue
|
|
|
|
}
|
2020-05-20 02:31:08 +03:00
|
|
|
d.D[i] = dept
|
2020-04-28 22:04:01 +03:00
|
|
|
// проверка шага у первых двух точек данных и сравнение с параметром step
|
|
|
|
//TODO данную проверку следует делать через Checker
|
2020-02-27 19:21:49 +03:00
|
|
|
if i > 1 {
|
2020-05-20 02:31:08 +03:00
|
|
|
if math.Pow(((dept-d.D[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.D[i-1]), las.Step)})
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
// проверка шага между точками [i-1, i] и точками [i-2, i-1] обнаружение немонотонности колонки глубин
|
2020-02-27 19:21:49 +03:00
|
|
|
if i > 2 {
|
2020-05-20 02:31:08 +03:00
|
|
|
if math.Pow(((dept-d.D[i-1])-(d.D[i-1]-d.D[i-2])), 2) > 0.1 {
|
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, fmt.Sprintf("step %5.2f ≠ previously step %5.2f", (dept - d.D[i-1]), (d.D[i-1] - d.D[i-2]))})
|
|
|
|
dept = d.D[i-1] + las.Step
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s = strings.TrimSpace(s[k+1:]) //cut first column
|
|
|
|
//цикл по каротажам
|
|
|
|
for j := 1; j < (n - 1); j++ {
|
2020-05-10 04:20:51 +03:00
|
|
|
iSpace := strings.IndexFunc(s, isSeparator)
|
2020-02-27 19:21:49 +03:00
|
|
|
switch iSpace {
|
2020-05-19 01:18:23 +03:00
|
|
|
case -1: //не все колонки прочитаны, а разделителей уже нет... пробуем игнорировать сроку заполняя оставшиеся каротажи NULLами
|
2020-06-10 01:07:20 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "not all column are read, set log value to NULL"})
|
2020-02-27 19:21:49 +03:00
|
|
|
case 0:
|
2020-04-28 22:04:01 +03:00
|
|
|
v = las.Null
|
2020-02-27 19:21:49 +03:00
|
|
|
case 1:
|
|
|
|
v, err = strconv.ParseFloat(s[:1], 64)
|
|
|
|
default:
|
2020-05-19 01:18:23 +03:00
|
|
|
v, err = strconv.ParseFloat(s[:iSpace], 64)
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
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
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
l, err = las.logByIndex(j)
|
2020-02-27 19:21:49 +03:00
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.nPoints = i
|
|
|
|
return i, errors.New("internal ERROR, func (las *Las) readDataSec()::las.logByIndex(j) return error")
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-05-20 02:31:08 +03:00
|
|
|
l.D[i] = dept
|
|
|
|
l.V[i] = v
|
2020-02-27 19:21:49 +03:00
|
|
|
s = strings.TrimSpace(s[iSpace+1:])
|
|
|
|
}
|
|
|
|
//остаток - последняя колонка
|
|
|
|
v, err = strconv.ParseFloat(s, 64)
|
|
|
|
if err != nil {
|
2020-06-10 01:07:20 +03:00
|
|
|
las.addWarning(TWarning{directOnRead, lasSecData, las.currentLine, "not all column are read, set log value to NULL"})
|
2020-04-28 22:04:01 +03:00
|
|
|
v = las.Null
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
l, err = las.logByIndex(n - 1)
|
2020-02-27 19:21:49 +03:00
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.nPoints = i
|
|
|
|
return i, errors.New("internal ERROR, func (las *Las) readDataSec()::las.logByIndex(j) return error on last column")
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-05-20 02:31:08 +03:00
|
|
|
l.D[i] = dept
|
|
|
|
l.V[i] = v
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
//i - actually readed lines and add (.) to data array
|
|
|
|
//crop logs to actually len
|
2020-05-20 02:31:08 +03:00
|
|
|
err = las.SetActuallyNumberPoints(i)
|
2020-04-10 00:55:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2020-02-27 19:21:49 +03:00
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NumPoints - return actually number of points in data
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) NumPoints() int {
|
|
|
|
return las.nPoints
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-05-20 02:31:08 +03:00
|
|
|
// Dept - return slice of DEPT curve (first column)
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) Dept() []float64 {
|
|
|
|
d, err := las.logByIndex(0)
|
2020-02-27 19:21:49 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-20 02:31:08 +03:00
|
|
|
return d.D
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
|
|
|
|
2020-05-20 02:31:08 +03:00
|
|
|
// SetActuallyNumberPoints - crop all curve to numPoints count of data
|
|
|
|
func (las *Las) SetActuallyNumberPoints(numPoints int) error {
|
2020-02-27 19:21:49 +03:00
|
|
|
if numPoints <= 0 {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.nPoints = 0
|
|
|
|
return errors.New("internal ERROR, func (las *Las) setActuallyNumberPoints(), actually number of points <= 0")
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
if numPoints > len(las.Dept()) {
|
|
|
|
las.nPoints = 0
|
|
|
|
return errors.New("internal ERROR, func (las *Las) setActuallyNumberPoints(), actually number of points > then exist data")
|
2020-02-27 19:21:49 +03:00
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
for _, l := range las.Logs {
|
2020-02-27 19:21:49 +03:00
|
|
|
l.SetLen(numPoints)
|
|
|
|
}
|
2020-04-28 22:04:01 +03:00
|
|
|
las.nPoints = numPoints
|
2020-02-27 19:21:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-10 00:55:08 +03:00
|
|
|
//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?
|
2020-04-28 22:04:01 +03:00
|
|
|
func (las *Las) Save(fileName string, useMnemonic ...bool) error {
|
2020-04-10 00:55:08 +03:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
bufToSave []byte
|
|
|
|
)
|
|
|
|
if len(useMnemonic) > 0 {
|
2020-05-19 01:18:23 +03:00
|
|
|
bufToSave, err = las.SaveToBuf(useMnemonic[0]) //<> bufToSave, err = las.SaveToBuf(true) //TODO las.SaveToBuf(true) not test
|
2020-04-10 00:55:08 +03:00
|
|
|
} else {
|
2020-04-28 22:04:01 +03:00
|
|
|
bufToSave, err = las.SaveToBuf(false)
|
2020-04-10 00:55:08 +03:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !xlib.FileExists(fileName) {
|
|
|
|
err = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("path: '" + filepath.Dir(fileName) + "' can't create >>" + err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(fileName, bufToSave, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("file: '" + fileName + "' can't open to write >>" + err.Error())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 err error
|
|
|
|
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-04-28 22:04:01 +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)
|
|
|
|
|
|
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString("# DEPT |") //готовим строчку с названиями каротажей глубина всегда присутствует
|
|
|
|
var l *LasCurve
|
|
|
|
for i := 1; i < n; i++ { //Пишем названия каротажей
|
2020-04-28 22:04:01 +03:00
|
|
|
l, _ := las.logByIndex(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
|
|
|
|
sb.WriteString(" ")
|
|
|
|
fmt.Fprintf(&sb, "%-8s|", l.Name) //Собираем строчку с названиями каротажей
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(&b, _LasDataSec)
|
|
|
|
//write data
|
|
|
|
fmt.Fprintf(&b, "%s\n", sb.String())
|
2020-04-28 22:04:01 +03:00
|
|
|
dept, _ := las.logByIndex(0)
|
|
|
|
for i := 0; i < las.nPoints; i++ { //loop by dept (.)
|
2020-05-20 02:31:08 +03:00
|
|
|
fmt.Fprintf(&b, "%-9.3f ", dept.D[i])
|
2020-04-10 00:55:08 +03:00
|
|
|
for j := 1; j < n; j++ { //loop by logs
|
2020-04-28 22:04:01 +03:00
|
|
|
l, err = las.logByIndex(j)
|
2020-04-10 00:55:08 +03:00
|
|
|
if err != nil {
|
2020-04-28 22:04:01 +03:00
|
|
|
las.addWarning(TWarning{directOnWrite, lasSecData, i, "logByIndex() return error, log not found, panic"})
|
2020-04-10 00:55:08 +03:00
|
|
|
return nil, errors.New("logByIndex() return error, log not found, panic")
|
|
|
|
}
|
2020-05-20 02:31:08 +03:00
|
|
|
fmt.Fprintf(&b, "%-9.3f ", l.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 {
|
|
|
|
return las.Null
|
|
|
|
}
|
|
|
|
defer iFile.Close()
|
|
|
|
|
|
|
|
_, iScanner, err := xlib.SeekFileStop(las.FileName, "~A")
|
|
|
|
if (err != nil) || (iScanner == nil) {
|
|
|
|
return las.Null
|
|
|
|
}
|
|
|
|
|
|
|
|
s := ""
|
|
|
|
dept1 := 0.0
|
|
|
|
for i := 0; iScanner.Scan(); i++ {
|
|
|
|
s = strings.TrimSpace(iScanner.Text())
|
|
|
|
if (len(s) == 0) || (s[0] == '#') {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
k := strings.IndexRune(s, ' ')
|
|
|
|
if k < 0 {
|
|
|
|
k = len(s)
|
|
|
|
}
|
|
|
|
dept1, err = strconv.ParseFloat(s[:k], 64)
|
|
|
|
if err != nil {
|
|
|
|
return las.Null
|
|
|
|
}
|
|
|
|
return dept1
|
|
|
|
}
|
|
|
|
//если мы попали сюда, то всё грусно, в файле после ~A не нашлось двух строчек с данными... или пустые строчки или комменты
|
|
|
|
// TODO последняя строка "return las.Null" не обрабатывается в тесте
|
|
|
|
return las.Null
|
|
|
|
}
|
|
|
|
|
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-02 20:49:41 +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) {
|
|
|
|
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())
|
|
|
|
if (len(s) == 0) || (s[0] == '#') {
|
|
|
|
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-02 20:49:41 +03:00
|
|
|
return las.Null
|
|
|
|
}
|
|
|
|
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
|
|
|
|
//TODO последняя строка "return las.Null" не обрабатывается в тесте
|
2020-06-02 20:49:41 +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) {
|
|
|
|
las.Step = h
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
|
2020-06-10 01:07:20 +03:00
|
|
|
func (las *Las) setStrt(h float64) {
|
|
|
|
las.Strt = h
|
|
|
|
}
|
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
// IsStrtEmpty - return true if parameter Strt not exist in file
|
|
|
|
func (las *Las) IsStrtEmpty() bool {
|
|
|
|
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 {
|
|
|
|
return las.Stop == StdNull
|
|
|
|
}
|
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 {
|
|
|
|
return las.Step == StdNull
|
|
|
|
}
|
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
|
|
|
|
func (las *Las) SetNull(aNull float64) error {
|
|
|
|
for _, l := range las.Logs { //loop by logs
|
|
|
|
for i := range l.V { //loop by dept step
|
|
|
|
if l.V[i] == las.Null {
|
|
|
|
l.V[i] = aNull
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
las.Null = aNull
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-10 04:20:51 +03:00
|
|
|
|
2020-06-02 20:49:41 +03:00
|
|
|
//logByIndex - return log from map by Index
|
|
|
|
func (las *Las) logByIndex(i int) (*LasCurve, error) {
|
|
|
|
for _, v := range las.Logs {
|
|
|
|
if v.Index == i {
|
|
|
|
return &v, nil
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|
|
|
|
}
|
2020-06-02 20:49:41 +03:00
|
|
|
return nil, fmt.Errorf("log with index: %v not present", i)
|
2020-05-10 04:20:51 +03:00
|
|
|
}
|