glasio/las.go

846 строки
28 KiB
Go
Исходник Обычный вид История

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 (
_LasFirstLine = "~VERSION INFORMATION\n"
_LasVersion = "VERS. %3.1f :glas (c) softlandia@gmail.com\n"
_LasCodePage = "CPAGE. 1251: code page \n"
_LasWrap = "WRAP. NO : ONE LINE PER DEPTH STEP\n"
_LasWellInfoSec = "~WELL INFORMATION\n"
_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"
_LasCurvLine2 = " %s :\n"
_LasDataSec = "~ASCII Log Data\n"
_LasDataLine = ""
//secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - dAta
lasSecIgnore = 0
lasSecVertion = 1
lasSecWellInfo = 2
lasSecCurInfo = 3
lasSecData = 4
)
2020-04-10 00:55:08 +03:00
// LasWellInfo - contain parameters of Well section
type LasWellInfo struct {
ver float64
wrap string
wellName string
null float64
oCodepage cpd.IDCodePage
}
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
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
//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
// если делать функцией, не методом, то придётся NULL передавать. а оно надо вообще
2020-02-27 19:21:49 +03:00
func (o *Las) GetStepFromData(fileName string) float64 {
iFile, err := os.Open(fileName)
if err != nil {
return o.Null
}
defer iFile.Close()
_, iScanner, err := xlib.SeekFileStop(fileName, "~A")
if (err != nil) || (iScanner == nil) {
return o.Null
}
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 { //data line must have minimum 2 column separated ' ' space
return o.Null
}
dept1, err = strconv.ParseFloat(s[:k], 64)
if err != nil {
return o.Null
}
j++
if j == 2 {
return math.Round((dept1-dept2)*10) / 10
}
dept2 = dept1
}
//если мы попали сюда, то всё грусно, в файле после ~A не нашлось двух строчек с данными... или пустые строчки или комменты
2020-04-10 00:55:08 +03:00
// TODO последняя строка "return o.Null" не обрабатывается в тесте
2020-02-27 19:21:49 +03:00
return o.Null
}
//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
for i := range l.log { //loop by dept step
if l.log[i] == o.Null {
l.log[i] = aNull
}
}
}
o.Null = aNull
return nil
}
//logByIndex - return log from map by Index
func (o *Las) logByIndex(i int) (*LasCurve, error) {
for _, v := range o.Logs {
if v.Index == i {
return &v, nil
}
}
return nil, fmt.Errorf("log with index: %v not present", i)
}
2020-04-10 00:55:08 +03:00
var (
// ExpPoints - ожидаемое количество точек данных, до чтения мы не можем знать сколько точек будет фактически прочитано
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-04-10 00:55:08 +03:00
las.ePoints = 1000
2020-02-27 19:21:49 +03:00
las.Logs = make(map[string]LasCurve)
2020-04-10 00:55:08 +03:00
las.maxWarningCount = MaxWarningCount
las.stdNull = -999.25
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-10 00:55:08 +03:00
// NewLasPar - create new object with parameters
func NewLasPar(lasInfo LasWellInfo) *Las {
las := new(Las)
las.Ver = lasInfo.ver
las.Wrap = lasInfo.wrap
las.Well = lasInfo.wellName
las.stdNull = lasInfo.null
las.oCodepage = lasInfo.oCodepage
las.maxWarningCount = MaxWarningCount
las.ePoints = ExpPoints
las.Logs = make(map[string]LasCurve)
//mnemonic dictionary
las.LogDic = nil
//external log dictionary
las.VocDic = nil
//счётчик повторяющихся мнемоник, увеличивается каждый раз на 1, используется при переименовании мнемоники
las.iDuplicate = 0
return las
}
2020-02-27 19:21:49 +03:00
//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 {
switch r {
case 86: //V
return lasSecVertion //version section
case 118: //v
return lasSecVertion //version section
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
}
}
//make test of loaded well info section
//return error <> nil in one case, if getStepFromData return error
2020-03-08 00:34:07 +03:00
func (o *Las) checkWellInfoSection() error {
2020-02-27 19:21:49 +03:00
if o.Step == 0.0 {
2020-03-08 00:34:07 +03:00
//TODO менять здесь шаг нельзя, здесь только проверка и сохранение извещения о некорректности, менять надо потом
2020-02-27 19:21:49 +03:00
o.Step = o.GetStepFromData(o.FileName) // return o.Null if cannot calculate step from data
if o.Step == o.Null {
2020-03-08 00:34:07 +03:00
return errors.New("invalid STEP parameter and invalid step in data")
2020-02-27 19:21:49 +03:00
}
2020-03-08 00:34:07 +03:00
o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("STEP parameter equal 0, replace to %4.3f", o.Step)})
2020-02-27 19:21:49 +03:00
}
if o.Null == 0.0 {
o.Null = o.stdNull
2020-03-08 00:34:07 +03:00
o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("NULL parameter equal 0, replace to %4.3f", o.Null)})
2020-02-27 19:21:49 +03:00
}
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)})
}
return nil
}
// IsWraped - return true if WRAP == YES
func (o *Las) IsWraped() bool {
2020-04-10 00:55:08 +03:00
return strings.Contains(strings.ToUpper(o.Wrap), "Y") //(strings.Index(strings.ToUpper(o.Wrap), "Y") >= 0)
2020-02-27 19:21:49 +03:00
}
// SaveWarning - save to file all warning
func (o *Las) SaveWarning(fileName string) error {
if o.Warnings.Count() == 0 {
return nil
}
oFile, err := os.Create(fileName)
if err != nil {
return err
}
o.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()
if n == 0 {
return 0
}
for _, w := range o.Warnings {
writer.WriteString(w.String())
writer.WriteString("\n")
}
return n
}
// SaveWarningToFile - store all warning to file, file not close. return count warning writed
func (o *Las) SaveWarningToFile(oFile *os.File) int {
if oFile == nil {
return 0
}
if o.Warnings.Count() == 0 {
return 0
}
oFile.WriteString("**file: " + o.FileName + "**\n")
n := o.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'"})
}
}
}
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-02-27 19:21:49 +03:00
func (o *Las) GetMnemonic(logName string) string {
if (o.LogDic == nil) || (o.VocDic == nil) {
2020-03-04 00:02:17 +03:00
return "" //"-"
2020-02-27 19:21:49 +03:00
}
_, ok := (*o.LogDic)[logName]
if ok { //GOOD - название каротажа равно мнемонике
return logName
}
v, ok := (*o.VocDic)[logName]
if ok { //POOR - название загружаемого каротажа найдено в словаре подстановок, мнемоника найдена
return v
}
return ""
}
// Open - load las file
func (o *Las) Open(fileName string) (int, error) {
//TODO при создании объекта las есть возможность указать кодировку записи, нужна возможность указать явно кодировку чтения
var err error
o.File, err = os.Open(fileName)
if err != nil {
return 0, err
}
defer o.File.Close()
o.FileName = fileName
2020-03-04 00:02:17 +03:00
//create and store Reader, this reader decode to UTF-8
2020-02-27 19:21:49 +03:00
o.Reader, err = cpd.NewReader(o.File)
if err != nil {
return 0, err
}
o.scanner = bufio.NewScanner(o.Reader)
//load header from stored Reader
o.currentLine = 0
2020-03-08 00:34:07 +03:00
o.LoadHeader()
//проверка корректности данных секции WELL INFO перез загрузкой данных
//непоправимая ошибка если невозможно определить корректно шаг
err = o.checkWellInfoSection()
2020-02-27 19:21:49 +03:00
if err != nil {
2020-03-08 00:34:07 +03:00
return 0, err // двойная ошибка, плох параметр STEP и не удалось вычислить STEP по данным, с данными проблема...
2020-02-27 19:21:49 +03:00
}
if o.IsWraped() {
o.addWarning(TWarning{directOnRead, lasSecData, -1, "WRAP = YES, file ignored"})
2020-03-04 00:02:17 +03:00
return 0, nil //TODO здесь должна быть ошибка, мы не добрались до чтения данных
2020-02-27 19:21:49 +03:00
}
if len(o.Logs) <= 0 {
o.addWarning(TWarning{directOnRead, lasSecData, -1, "section ~Curve not exist, file ignored"})
2020-03-04 00:02:17 +03:00
return 0, nil //TODO здесь должна быть ошибка, мы не добрались до чтения данных
2020-02-27 19:21:49 +03:00
}
return o.ReadDataSec(fileName)
}
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. если началась секция данных заканчиваем
5. читаем одну строку (это один параметер из известной нам секции) */
func (o *Las) LoadHeader() error {
s := ""
var err error
secNum := 0
for i := 0; o.scanner.Scan(); i++ {
s = strings.TrimSpace(o.scanner.Text())
o.currentLine++
if isIgnoredLine(s) {
continue
}
if s[0] == '~' { //start new section
secNum = o.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
if err != nil {
o.addWarning(TWarning{directOnRead, secNum, -1, fmt.Sprintf("while process parameter: '%s' occure error: %v", s, err)})
}
}
}
return nil
}
2020-03-14 00:32:03 +03:00
// ReadParameter - read one parameter
2020-02-27 19:21:49 +03:00
func (o *Las) ReadParameter(s string, secNum int) error {
switch secNum {
case lasSecVertion:
return o.readVersionParam(s)
case lasSecWellInfo:
return o.ReadWellParam(s)
case lasSecCurInfo:
return o.readCurveParam(s)
}
return nil
}
func (o *Las) readVersionParam(s string) error {
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":
o.Ver, err = strconv.ParseFloat(p.Val, 64)
case "WRAP":
o.Wrap = p.Val
}
return err
}
//ReadWellParam - read parameter from WELL section
func (o *Las) ReadWellParam(s string) error {
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":
o.Strt, err = strconv.ParseFloat(p.Val, 64)
case "STOP":
o.Stop, err = strconv.ParseFloat(p.Val, 64)
case "STEP":
o.Step, err = strconv.ParseFloat(p.Val, 64)
case "NULL":
o.Null, err = strconv.ParseFloat(p.Val, 64)
case "WELL":
if o.Ver < 2.0 {
o.Well = p.Desc
} else {
2020-04-10 00:55:08 +03:00
//o.Well = p.Val
o.Well = wellNameFromParam(p)
2020-02-27 19:21:49 +03:00
}
}
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)})
}
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
func (o *Las) ChangeDuplicateLogName(name string) string {
s := ""
if _, ok := o.Logs[name]; ok {
o.iDuplicate++
s = fmt.Sprintf("%v", o.iDuplicate)
name += s
}
return name
}
//Разбор одной строки с мнемоникой каротажа
//Разбираем в переменную l а потом сохраняем в map
//Каждый каротаж характеризуется тремя именами
//IName - имя каротажа в исходном файле, может повторятся
//Name - ключ в map хранилище, повторятся не может. если в исходном есть повторение, то Name строится добавлением к IName индекса
2020-03-04 00:02:17 +03:00
//Mnemonic - мнемоника, берётся из словаря, если в словаре не найдено, то ""
2020-02-27 19:21:49 +03:00
func (o *Las) readCurveParam(s string) error {
2020-04-10 00:55:08 +03:00
l := NewLasCurve(s)
2020-02-27 19:21:49 +03:00
l.Init(len(o.Logs), o.GetMnemonic(l.Name), o.ChangeDuplicateLogName(l.Name), o.GetExpectedPointsCount())
2020-04-10 00:55:08 +03:00
o.Logs[l.Name] = l //добавление в хранилище кривой каротажа с колонкой глубин
2020-02-27 19:21:49 +03:00
return nil
}
//GetExpectedPointsCount - оценка количества точек по параметрам STEP, STRT, STOP
func (o *Las) GetExpectedPointsCount() int {
var m int
2020-03-08 00:34:07 +03:00
//TODO нужно обработать все случаи
if o.Step == 0.0 {
2020-04-10 00:55:08 +03:00
return o.ePoints
2020-03-08 00:34:07 +03:00
}
2020-02-27 19:21:49 +03:00
if math.Abs(o.Stop) > math.Abs(o.Strt) {
m = int((o.Stop-o.Strt)/o.Step) + 2
} else {
m = int((o.Strt-o.Stop)/o.Step) + 2
}
if m < 0 {
m = -m
}
2020-04-10 00:55:08 +03:00
if m == 0 {
return o.ePoints
}
2020-02-27 19:21:49 +03:00
return m
}
//expandDept - if actually data points exceeds
func (o *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"})
//ожидаем удвоения данных
2020-04-10 00:55:08 +03:00
o.ePoints *= 2
2020-03-14 00:32:03 +03:00
//expand first log - dept
2020-04-10 00:55:08 +03:00
newDept := make([]float64, o.ePoints)
2020-02-27 19:21:49 +03:00
copy(newDept, d.dept)
d.dept = newDept
2020-04-10 00:55:08 +03:00
newLog := make([]float64, o.ePoints)
2020-02-27 19:21:49 +03:00
copy(newLog, d.dept)
d.log = newLog
o.Logs[d.Name] = *d
//loop over other logs
n := len(o.Logs)
var l *LasCurve
for j := 1; j < n; j++ {
l, _ = o.logByIndex(j)
2020-04-10 00:55:08 +03:00
newDept := make([]float64, o.ePoints)
2020-02-27 19:21:49 +03:00
copy(newDept, l.dept)
l.dept = newDept
2020-04-10 00:55:08 +03:00
newLog := make([]float64, o.ePoints)
2020-02-27 19:21:49 +03:00
copy(newLog, l.log)
l.log = newLog
o.Logs[l.Name] = *l
}
}
// ReadDataSec - read section of data
func (o *Las) ReadDataSec(fileName string) (int, error) {
var (
v float64
err error
d *LasCurve
l *LasCurve
dept float64
i int
)
//исходя из параметров STRT, STOP и STEP определяем ожидаемое количество строк данных
2020-04-10 00:55:08 +03:00
o.ePoints = o.GetExpectedPointsCount()
2020-02-27 19:21:49 +03:00
//o.currentLine++
n := len(o.Logs) //количество каротажей, столько колонок данных ожидаем
d, _ = o.logByIndex(0) //dept log
s := ""
for i = 0; o.scanner.Scan(); i++ {
o.currentLine++
2020-04-10 00:55:08 +03:00
if i == o.ePoints {
2020-02-27 19:21:49 +03:00
o.expandDept(d)
}
s = strings.TrimSpace(o.scanner.Text())
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)})
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])})
i--
continue
}
d.dept[i] = dept
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 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
}
}
s = strings.TrimSpace(s[k+1:]) //cut first column
//цикл по каротажам
for j := 1; j < (n - 1); j++ {
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"})
case 0:
v = o.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
}
l, err = o.logByIndex(j)
if err != nil {
o.nPoints = i
return i, errors.New("internal ERROR, func (o *Las) readDataSec()::o.logByIndex(j) return error")
}
l.dept[i] = dept
l.log[i] = v
s = strings.TrimSpace(s[iSpace+1:])
}
//остаток - последняя колонка
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
}
l, err = o.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")
}
l.dept[i] = dept
l.log[i] = v
}
//i - actually readed lines and add (.) to data array
//crop logs to actually len
2020-04-10 00:55:08 +03:00
err = o.setActuallyNumberPoints(i)
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
func (o *Las) NumPoints() int {
return o.nPoints
}
//Dept - return slice of DEPT curve (first column)
func (o *Las) Dept() []float64 {
d, err := o.logByIndex(0)
if err != nil {
return nil
}
return d.dept
}
func (o *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")
}
if numPoints > len(o.Dept()) {
o.nPoints = 0
return errors.New("internal ERROR, func (o *Las) setActuallyNumberPoints(), actually number of points > then exist data")
}
for _, l := range o.Logs {
l.SetLen(numPoints)
}
o.nPoints = numPoints
return nil
}
2020-04-10 00:55:08 +03:00
/*
func (o *Las) convertStrToOut(s string) string {
r, _ := cpd.NewReaderTo(strings.NewReader(s), o.oCodepage.String())
b, _ := ioutil.ReadAll(r)
return string(b)
}
func (o *Las) SaveToFile(fileName string, useMnemonic ...bool) error {
2020-02-27 19:21:49 +03:00
n := len(o.Logs) //log count
if n <= 0 {
return errors.New("logs not exist")
}
var f *os.File
var err error
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())
}
}
f, err = os.Create(fileName) //Open file to WRITE
if err != nil {
return errors.New("file: '" + fileName + "' can't open to write >>" + err.Error())
}
defer f.Close()
fmt.Fprint(f, _LasFirstLine)
fmt.Fprintf(f, _LasVersion, o.Ver)
fmt.Fprint(f, _LasWrap)
fmt.Fprint(f, _LasWellInfoSec)
fmt.Fprintf(f, _LasStrt, o.Strt)
fmt.Fprintf(f, _LasStop, o.Stop)
fmt.Fprintf(f, _LasStep, o.Step)
fmt.Fprintf(f, _LasNull, o.Null)
fmt.Fprintf(f, _LasWell, o.convertStrToOut(o.Well))
fmt.Fprint(f, _LasCurvSec)
fmt.Fprint(f, _LasCurvDept)
s := "# DEPT |" //готовим строчку с названиями каротажей глубина всегда присутствует
var l *LasCurve
for i := 1; i < n; i++ { //Пишем названия каротажей
l, _ := o.logByIndex(i)
if len(useMnemonic) > 0 {
if len(l.Mnemonic) > 0 {
l.Name = l.Mnemonic
}
}
fmt.Fprintf(f, _LasCurvLine, o.convertStrToOut(l.Name), o.convertStrToOut(l.Unit)) //запись мнемоник в секции ~Curve
s += " " + fmt.Sprintf("%-8s|", l.Name) //Собираем строчку с названиями каротажей
}
fmt.Fprintf(f, _LasDataSec)
//write data
s += "\n"
fmt.Fprintf(f, o.convertStrToOut(s))
dept, _ := o.logByIndex(0)
for i := 0; i < o.nPoints; i++ { //loop by dept (.)
fmt.Fprintf(f, "%-9.3f ", dept.dept[i])
for j := 1; j < n; j++ { //loop by logs
l, err = o.logByIndex(j)
if err != nil {
o.addWarning(TWarning{directOnWrite, lasSecData, i, "logByIndex() return error, log not found, panic"})
return errors.New("logByIndex() return error, log not found, panic")
}
fmt.Fprintf(f, "%-9.3f ", l.log[i])
}
fmt.Fprintln(f)
}
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?
func (o *Las) Save(fileName string, useMnemonic ...bool) error {
var (
err error
bufToSave []byte
)
if len(useMnemonic) > 0 {
bufToSave, err = o.SaveToBuf(true)
} else {
bufToSave, err = o.SaveToBuf(false)
}
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
func (o *Las) SaveToBuf(useMnemonic bool) ([]byte, error) {
n := len(o.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.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.Fprint(&b, _LasCurvSec)
fmt.Fprint(&b, _LasCurvDept)
var sb strings.Builder
sb.WriteString("# DEPT |") //готовим строчку с названиями каротажей глубина всегда присутствует
var l *LasCurve
for i := 1; i < n; i++ { //Пишем названия каротажей
l, _ := o.logByIndex(i)
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())
dept, _ := o.logByIndex(0)
for i := 0; i < o.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)
if err != nil {
o.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 проверяем раньше, других причин нет
bufToSave, _ := ioutil.ReadAll(r)
return bufToSave, nil
}