зеркало из https://github.com/softlandia/laslib.git
This commit is contained in:
Коммит
1ef7ea613e
|
@ -0,0 +1,28 @@
|
|||
~Version Information
|
||||
VERS. 1.20: cp_866 CWLS log ASCII Standard - VERSION 1.20
|
||||
WRAP. NO : One line per depth step
|
||||
~Well Information
|
||||
STRT .M 1.0 : Start depth
|
||||
STOP .M 1.0 : Stop depth
|
||||
STEP .M 0.10 : Step
|
||||
NULL . -9999.00 : Null value
|
||||
WELL . WELL : 12-‘¯«®è ï
|
||||
~Curve Information
|
||||
#----------------------------------------------------------------
|
||||
#MNEM.UNIT API CODE CURVE DESCRIPTION
|
||||
DEPT .M : 1 DEPTH
|
||||
<EFBFBD>‘ .¤.¥¤. :
|
||||
<EFBFBD>‘.ãá«.¥¤. : et
|
||||
<EFBFBD>‘ . :
|
||||
~Parameter Information
|
||||
EKB .M 72.7 : Elevation,Kelly Bushing
|
||||
~ASCII Log Data
|
||||
1.000 -9999.000 -9999.000 2.000
|
||||
1.100 -9999.000 11.535 1.000
|
||||
1.200 -9999.000 -9999.000 5.000
|
||||
1.300 0.762 -9999.000 5.000
|
||||
1.400 0.707 16.053 1.000
|
||||
1.500 0.000 -9999.000 0.000
|
||||
1.600 0.000 -9999.000 -9999.000
|
||||
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,56 @@
|
|||
~Version information
|
||||
VERS. 1.20: CWLS LAS - VERSION 1.20
|
||||
WRAP. NO: One line per depth step
|
||||
~Well information
|
||||
# MNEM.UNIT DATA TYPE INFORMATION
|
||||
# ====.================================: ===================
|
||||
STRT.M 0.000: First depth in file
|
||||
STOP.M 10.000: Last depth in file
|
||||
STEP.M 0.000: Depth increment
|
||||
NULL.M -32768.000: Null values
|
||||
COMP. COMPANY:
|
||||
WELL. WELL: 6
|
||||
FLD . FIELD: <20>ਬ¥à®¥
|
||||
LOC . LOCATION:
|
||||
CNTY. COUNTY:
|
||||
STAT. STATE:
|
||||
CTRY. COUNTRY:
|
||||
SRVC. SERVICE COMPANY:
|
||||
DATE. LOG DATE: 1.01.11
|
||||
API . API NUMBER:
|
||||
~Curve information
|
||||
# MNEM.UNIT API CODE CURVE DESCRIPTION
|
||||
# ====.================================:====================
|
||||
DEPT.M : Depth curve
|
||||
GK : GK
|
||||
NNM : NNM
|
||||
NNB : NNB
|
||||
¯¥à¢ë© : íàòÿæåíè
|
||||
¢â®à®© ª à®â ¦ :
|
||||
~ASCII Log Data
|
||||
1 6.2.24 -0 0 528.072 2215.58
|
||||
2 5.228 -32768 -32768.0 528.07202215.58
|
||||
3 6.224 0 0 528.869 2215.58
|
||||
4 5.975 0 0 528.072 2215.58
|
||||
5 6.473 0 0 528.072 2215.58
|
||||
69 5.975 0 0 527.275 2215.58
|
||||
116 5.477 0 0 526.478 2215.58
|
||||
117 4.73 0 0 526.478 2215.58
|
||||
118 4.73 0 0 525.681 2215.58
|
||||
|
||||
9
|
||||
.8000 2.792 2.444 230.369 3.352 347.843 331.759 363.413 7.326
|
||||
171 6.473 0 0 526.478 2215.58
|
||||
172 4.481 0 0 525.681 2215.58
|
||||
173 4.979 0 0 525.681 2215.58
|
||||
177 6.473 0 0 525.681 2215.58
|
||||
178 6.473 0 0 525.681 2215.58
|
||||
|
||||
179 6.722 0 0 526.478 2215.58
|
||||
182 6.224 0 0 527.275 2215.58
|
||||
183 6.473 0 0 526.478 2215.58
|
||||
184 6.473 0 0 526.478 2215.58
|
||||
185 6.473 0 0 526.478 2215.58
|
||||
186 0 1 1 1 2
|
||||
186 6 4 4 4 4
|
||||
186 6 4 4 4 4
|
|
@ -0,0 +1,720 @@
|
|||
//
|
||||
package laslib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/softlandia/xlib"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
///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 = "~A"
|
||||
_LasDataLine = ""
|
||||
|
||||
//secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - dAta
|
||||
lasSecIgnore = 0
|
||||
lasSecVertion = 1
|
||||
lasSecWellInfo = 2
|
||||
lasSecCurInfo = 3
|
||||
lasSecData = 4
|
||||
)
|
||||
|
||||
//LasLog - class to store one log in Las
|
||||
type LasLog struct {
|
||||
LasParam
|
||||
Index int
|
||||
dept []float64
|
||||
log []float64
|
||||
}
|
||||
|
||||
// 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
|
||||
//TODO warnings - need method to flush slice on file, and clear
|
||||
type Las struct {
|
||||
FileName string //file name from load
|
||||
Ver float64 //version 1.0, 1.2 or 2.0
|
||||
Wrap string //YES || NO
|
||||
CodePage string //1251 //пока не читается
|
||||
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 map[string]LasLog //store all logs
|
||||
LogDic *map[string]string //external dictionary of standart log name - mnemonics
|
||||
VocDic *map[string]string //external vocabulary dictionary of log mnemonic
|
||||
expPoints int //expected count (.)
|
||||
nPoints int //actually count (.)
|
||||
fromCodePage int //codepage input file. autodetect
|
||||
toCodePage int //codepage to save file, default xlib.CpWindows1251. to special value, specify at make: NewLas(cp...)
|
||||
iDuplicate int //индекс повторящейся мнемоники, увеличивается на 1 при нахождении дубля, начально 0
|
||||
currentLine int //index of current line in readed file
|
||||
warnings []TWarning //slice of warnings occure on read or write
|
||||
countWarning int //count of warning, if count > maxWarningCount stop save warning
|
||||
maxWarningCount int //default maximum warning count
|
||||
stdNull float64 //default null value
|
||||
}
|
||||
|
||||
//GetStepFromData - return step from data section
|
||||
//open and read 2 line from section ~A and determine step
|
||||
//close file
|
||||
//return o.Null if error occure
|
||||
func (o *Las) GetStepFromData(fileName string) float64 {
|
||||
iFile, err := os.Open(fileName) // open file to READ
|
||||
if err != nil {
|
||||
return o.Null
|
||||
}
|
||||
defer iFile.Close()
|
||||
|
||||
_, iScanner, err := xlib.SeekFileToString(fileName, "~A")
|
||||
if err != 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 не нашлось двух строчек с данными... или пустые строчки или комменты
|
||||
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
|
||||
}
|
||||
|
||||
//TODO replace to function xStrUtil.ConvertStrCodePage
|
||||
//вызывать после Scanner.Text()
|
||||
func (o *Las) convertStrFromIn(s string) string {
|
||||
switch o.fromCodePage {
|
||||
case xlib.Cp866:
|
||||
s, _, _ = transform.String(charmap.CodePage866.NewDecoder(), s)
|
||||
case xlib.CpWindows1251:
|
||||
s, _, _ = transform.String(charmap.Windows1251.NewDecoder(), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//TODO replace to function xStrUtil.ConvertStrCodePage
|
||||
func (o *Las) convertStrToOut(s string) string {
|
||||
switch o.toCodePage {
|
||||
case xlib.Cp866:
|
||||
s, _, _ = transform.String(charmap.CodePage866.NewEncoder(), s)
|
||||
case xlib.CpWindows1251:
|
||||
s, _, _ = transform.String(charmap.Windows1251.NewEncoder(), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//logByIndex - return log from map by Index
|
||||
func (o *Las) logByIndex(i int) (*LasLog, error) {
|
||||
for _, v := range o.Logs {
|
||||
if v.Index == i {
|
||||
return &v, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("log with index: %v not present", i)
|
||||
}
|
||||
|
||||
//NewLas - make new object Las class
|
||||
//autodetect code page at load file
|
||||
//code page to save by default is xlib.CpWindows1251
|
||||
func NewLas(outputCP ...int) *Las {
|
||||
las := new(Las)
|
||||
las.Logs = make(map[string]LasLog)
|
||||
las.maxWarningCount = 20 //TODO read from Cfg
|
||||
las.stdNull = -999.25 //TODO read from Cfg
|
||||
if len(outputCP) > 0 {
|
||||
las.toCodePage = outputCP[0]
|
||||
} else {
|
||||
las.toCodePage = xlib.CpWindows1251
|
||||
}
|
||||
//mnemonic dictionary
|
||||
las.LogDic = nil
|
||||
//external log dictionary
|
||||
las.VocDic = nil
|
||||
//счётчик повторяющихся мнемоник, увеличивается каждый раз на 1, используется при переименовании мнемоники
|
||||
las.iDuplicate = 0
|
||||
return las
|
||||
}
|
||||
|
||||
//analize first char after ~
|
||||
//~V - section vertion
|
||||
//~W - well info section
|
||||
//~C - curve info section
|
||||
//~A - data section
|
||||
func (o *Las) selectSection(r rune) int {
|
||||
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
|
||||
func (o *Las) testWellInfo() error {
|
||||
if o.Step == 0.0 {
|
||||
o.Step = o.GetStepFromData(o.FileName) // return o.Null if cannot calculate step from data
|
||||
if o.Step == o.Null {
|
||||
return errors.New("invalid STEP parameter, equal 0. and invalid step in data")
|
||||
}
|
||||
o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("invalid STEP parameter, equal 0. replace to %4.3f", o.Step)})
|
||||
}
|
||||
if o.Null == 0.0 {
|
||||
o.Null = o.stdNull
|
||||
o.addWarning(TWarning{directOnRead, lasSecWellInfo, -1, fmt.Sprintf("invalid NULL parameter, equal 0. replace to %4.3f", o.Null)})
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
//IsWrapOn - return true if WRAP == YES
|
||||
func (o *Las) IsWrapOn() bool {
|
||||
return (strings.Index(strings.ToUpper(o.Wrap), "Y") >= 0)
|
||||
}
|
||||
|
||||
//WarningCount - return count of warning
|
||||
func (o *Las) WarningCount() int {
|
||||
return len(o.warnings)
|
||||
}
|
||||
|
||||
//SaveWarning - save to file all warning
|
||||
func (o *Las) SaveWarning(fileName string) error {
|
||||
if o.WarningCount() == 0 {
|
||||
return nil
|
||||
}
|
||||
oFile, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = o.SaveWarningToFile(oFile)
|
||||
oFile.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
//SaveWarningToFile - store all warning to file
|
||||
//file not close
|
||||
func (o *Las) SaveWarningToFile(oFile *os.File) int {
|
||||
if oFile == nil {
|
||||
return 0
|
||||
}
|
||||
n := o.WarningCount()
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
oFile.WriteString("**file: " + o.FileName + "**\n")
|
||||
for i, w := range o.warnings {
|
||||
fmt.Fprintf(oFile, "%d, dir: %d,\tline: %d,\tdesc: %s\n", i, w.direct, w.line, w.desc)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (o *Las) addWarning(w TWarning) {
|
||||
if o.countWarning < o.maxWarningCount {
|
||||
o.warnings = append(o.warnings, w)
|
||||
o.countWarning++
|
||||
if o.countWarning == o.maxWarningCount {
|
||||
o.warnings = append(o.warnings, TWarning{0, 0, 0, "*maximum count* of warning reached, change parameter 'maxWarningCount' in 'glas.ini'"})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//return Mnemonic from dictionary by Log Name
|
||||
//if Mnemonic not found return empty string ""
|
||||
func (o *Las) getMnemonic(logName string) string {
|
||||
if (o.LogDic == nil) || (o.VocDic == nil) {
|
||||
return "-"
|
||||
}
|
||||
v, ok := (*o.LogDic)[logName]
|
||||
if ok { //GOOD - название каротажа равно мнемонике
|
||||
return logName
|
||||
}
|
||||
v, ok = (*o.VocDic)[logName]
|
||||
if ok { //POOR - название каротажа загружаемого файла найдено в словаре подстановок, мнемоника найдена
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//Разбор одной строки с мнемоникой каротажа
|
||||
//Разбираем в переменную l а потом сохраняем в map
|
||||
//Каждый каротаж характеризуется тремя именами
|
||||
//iName - имя каротажа в исходном файле, может повторятся
|
||||
//Name - ключ в map хранилище, повторятся не может. если в исходном есть повторение, то Name строится добавлением к iName индекса
|
||||
//Mnemonic - мнемоника, берётся из словаря. если в словаре не найдено, то оставляем iName
|
||||
func (o *Las) readCurveParam(s string) error {
|
||||
l := LasLog{}
|
||||
err := l.fromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.iName = l.Name
|
||||
l.Mnemonic = o.getMnemonic(l.iName)
|
||||
if _, ok := o.Logs[l.Name]; ok {
|
||||
o.iDuplicate++
|
||||
s = fmt.Sprintf("%v", o.iDuplicate)
|
||||
l.Name += s
|
||||
}
|
||||
l.Index = len(o.Logs)
|
||||
//m := o.getCountPoint()
|
||||
m := o.expPoints
|
||||
l.dept = make([]float64, m)
|
||||
l.log = make([]float64, m)
|
||||
o.Logs[l.Name] = l
|
||||
return nil
|
||||
}
|
||||
|
||||
//оцениваем количество точек в файле
|
||||
func (o *Las) getCountPoint() int {
|
||||
var m int
|
||||
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
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//loadHeader - read las file and load all section before dAta ~A
|
||||
/* secName: 0 - empty, 1 - Version, 2 - Well info, 3 - Curve info, 4 - A data
|
||||
1. читаем строку
|
||||
2. если коммент или пустая в игнор
|
||||
3. если начало секции, определяем какой
|
||||
4. если началась секция данных заканчиваем
|
||||
5. читаем одну строку (это один параметер из известной нам секции) */
|
||||
func (o *Las) loadHeader(fileName string) error {
|
||||
o.FileName = fileName
|
||||
iFile, err := os.Open(fileName) // open file to READ
|
||||
if err != nil {
|
||||
return errors.New("file: " + os.Args[1] + " can't open to read")
|
||||
}
|
||||
defer iFile.Close()
|
||||
s := ""
|
||||
secNum := 0
|
||||
iScanner := bufio.NewScanner(iFile)
|
||||
for i := 0; iScanner.Scan(); i++ {
|
||||
s = strings.TrimSpace(iScanner.Text())
|
||||
if (len(s) == 0) || (s[0] == '#') {
|
||||
continue
|
||||
}
|
||||
s = o.convertStrFromIn(s)
|
||||
if s[0] == '~' { //start new section
|
||||
secNum = o.selectSection(rune(s[1]))
|
||||
if secNum == lasSecCurInfo { //enter to Curve section.
|
||||
//проверка корректности данных секции WELL INFO перез загрузкой кривых и данных
|
||||
err = o.testWellInfo() //STEP != 0, NULL != 0, STRT & STOP
|
||||
if err != nil {
|
||||
return err // двойная ошибка, плох параметр STEP и не удалось вычислить STEP по данным, с данными проблема...
|
||||
}
|
||||
//возможно значение STEP изменилось... оцениваем количество точек и сохраняем
|
||||
o.expPoints = o.getCountPoint()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
//Open - load las file
|
||||
func (o *Las) Open(fileName string) (int, error) {
|
||||
var err error
|
||||
|
||||
if !xlib.FileExists(fileName) {
|
||||
return 0, errors.New("Las.Open() error: file " + fileName + " not exist")
|
||||
}
|
||||
o.fromCodePage, err = xlib.CodePageDetect(fileName, "~A")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//open and close file
|
||||
err = o.loadHeader(fileName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if strings.Index(o.Wrap, "Y") >= 0 {
|
||||
o.addWarning(TWarning{directOnRead, lasSecData, -1, "WRAP = YES, file ignored"})
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
pos, iScanner, err := xlib.SeekFileToString(fileName, "~A")
|
||||
if pos > 0 {
|
||||
//pos in file at line "~A..."
|
||||
o.currentLine = pos
|
||||
return o.readDataSec(iScanner)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//ReadParameter - read one parameter
|
||||
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
|
||||
p := LasParam{}
|
||||
p.fromString(s)
|
||||
switch p.Name {
|
||||
case "VERS":
|
||||
o.Ver, err = strconv.ParseFloat(p.Val, 64)
|
||||
case "WRAP":
|
||||
o.Wrap = p.Val
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*>>>>>>>>>>>>>>>>>>>>>>>> ver 1.0, 1.2
|
||||
_____________STRT, STOP, STEP, NULL
|
||||
STEP .M 0.10 : Step
|
||||
^^^^ ^^^^ ^^^^ ^^^
|
||||
Name Unit Val Desc
|
||||
_____________OTHER PARAMETER IN WELL INFO SECTION
|
||||
WELL . WELL : 12-Karas
|
||||
^^^^ ^^^^ ^^^^ ^^^
|
||||
Name Unit >Val< Desc
|
||||
|
||||
>>>>>>>>>>>>>>>>>>>>>>>> ver 2.0
|
||||
_____________STRT, STOP, STEP, NULL
|
||||
STEP .M 0.10 : Step
|
||||
^^^^ ^^^^ ^^^^ ^^^
|
||||
Name Unit Val Desc
|
||||
_____________OTHER PARAMETER IN WELL INFO SECTION
|
||||
WELL . 12-Karas : WELL
|
||||
^^^^ ^^^^ ^^^^ ^^^
|
||||
Name Unit Val Desc*/
|
||||
|
||||
//ReadWellParam - read parameter from WELL section
|
||||
func (o *Las) ReadWellParam(s string) error {
|
||||
var err error
|
||||
p := LasParam{}
|
||||
err = p.fromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
o.Well = p.Val
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
//expandDept - if actually data points exceeds
|
||||
func (o *Las) expandDept(d *LasLog) {
|
||||
//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"})
|
||||
//ожидаем удвоения данных
|
||||
o.expPoints *= 2
|
||||
//need expand all logs
|
||||
//fmt.Printf("old dept len: %d, cap: %d\n", len(d.dept), cap(d.dept))
|
||||
|
||||
newDept := make([]float64, o.expPoints, o.expPoints)
|
||||
copy(newDept, d.dept)
|
||||
d.dept = newDept
|
||||
|
||||
newLog := make([]float64, o.expPoints, o.expPoints)
|
||||
copy(newLog, d.dept)
|
||||
d.log = newLog
|
||||
o.Logs[d.Name] = *d
|
||||
|
||||
//fmt.Printf("new dept len: %d, cap: %d\n", len(d.dept), cap(d.dept))
|
||||
//loop over other logs
|
||||
n := len(o.Logs)
|
||||
var l *LasLog
|
||||
for j := 1; j < n; j++ {
|
||||
l, _ = o.logByIndex(j)
|
||||
newDept := make([]float64, o.expPoints, o.expPoints)
|
||||
copy(newDept, l.dept)
|
||||
l.dept = newDept
|
||||
|
||||
newLog := make([]float64, o.expPoints, o.expPoints)
|
||||
copy(newLog, l.log)
|
||||
l.log = newLog
|
||||
o.Logs[l.Name] = *l
|
||||
}
|
||||
}
|
||||
|
||||
//~ASCII Log Data
|
||||
// 1419.2000 -9999.000 -9999.000 2.186 2.187
|
||||
// 1419.3000 -9999.000 1.1 2.203 2.205
|
||||
func (o *Las) readDataSec(iScanner *bufio.Scanner) (int, error) {
|
||||
var (
|
||||
//m int
|
||||
v float64
|
||||
err error
|
||||
d *LasLog
|
||||
l *LasLog
|
||||
dept float64
|
||||
i int
|
||||
)
|
||||
o.currentLine++
|
||||
n := len(o.Logs)
|
||||
d, _ = o.logByIndex(0) //dept log
|
||||
s := ""
|
||||
for i = 0; iScanner.Scan(); i++ {
|
||||
o.currentLine++
|
||||
if i == o.expPoints { //o.expPoints - оценённое количество точек исходя из шага
|
||||
o.expandDept(d)
|
||||
}
|
||||
|
||||
s = strings.TrimSpace(iScanner.Text())
|
||||
|
||||
//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
|
||||
}
|
||||
//fmt.Printf("dept len: %d, cap: %d; i = %d, m = %d\n", len(d.dept), cap(d.dept), i, o.expPoints)
|
||||
d.dept[i] = dept
|
||||
if i > 1 {
|
||||
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
|
||||
}
|
||||
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)})
|
||||
}
|
||||
}
|
||||
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
|
||||
o.nPoints = i
|
||||
return i, nil
|
||||
}
|
||||
|
||||
//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 {
|
||||
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.Fprintf(f, _LasFirstLine)
|
||||
fmt.Fprintf(f, _LasVersion, o.Ver)
|
||||
fmt.Fprintf(f, _LasWrap)
|
||||
fmt.Fprintf(f, _LasCodePage)
|
||||
fmt.Fprintf(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.Fprintf(f, _LasCurvSec)
|
||||
fmt.Fprintf(f, _LasCurvDept)
|
||||
|
||||
s := _LasDataSec + " DEPT |" //готовим строчку с названиями каротажей глубина всегда присутствует
|
||||
var l *LasLog
|
||||
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) //Собираем строчку с названиями каротажей
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package laslib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//LasParam - class to store parameter from any section
|
||||
type LasParam struct {
|
||||
iName string
|
||||
Name string
|
||||
Mnemonic string
|
||||
Unit string
|
||||
Val string
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (o *LasParam) fromStringWithUnit(s string, iPoint int) error {
|
||||
//s === 'STEP .M 0.10 : Step'
|
||||
o.Name = strings.TrimSpace(s[:iPoint])
|
||||
s = s[iPoint+1:] // string now store all char after point
|
||||
//s === 'M 0.10 : Step'
|
||||
iSpace := strings.Index(s, " ")
|
||||
if iSpace < 0 {
|
||||
return errors.New("line : '" + s + "' is bad for Parameter, ignore")
|
||||
}
|
||||
o.Unit = ""
|
||||
if iSpace > 0 {
|
||||
o.Unit = s[:iSpace]
|
||||
}
|
||||
//s === ' 0.10 : Step'
|
||||
s = strings.TrimSpace(s[iSpace+1:])
|
||||
//s === '0.10 : Step'
|
||||
iColon := strings.Index(s, ":")
|
||||
if iColon < 0 {
|
||||
return errors.New("line : '" + s + "' is bad for Parameter, must have ':'")
|
||||
}
|
||||
o.Val = strings.TrimSpace(s[:iColon])
|
||||
o.Desc = s[iColon+1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LasParam) fromStringWithoutUnit(s string, iPoint int) error {
|
||||
//s === 'SP : self'
|
||||
o.Name = strings.TrimSpace(s[:iPoint])
|
||||
o.Unit = ""
|
||||
o.Val = ""
|
||||
o.Desc = strings.TrimSpace(s[iPoint:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LasParam) fromString(s string) error {
|
||||
iPoint := strings.Index(s, ".")
|
||||
if iPoint < 0 {
|
||||
iPoint = strings.Index(s, ":")
|
||||
if iPoint < 0 {
|
||||
return errors.New("line : '" + s + "' is bad for Parameter, ignore")
|
||||
}
|
||||
return o.fromStringWithoutUnit(s, iPoint)
|
||||
}
|
||||
return o.fromStringWithUnit(s, iPoint)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//(c) softland 2019
|
||||
//softlandia@gmail.com
|
||||
package laslib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
//CodePageDetect
|
||||
func TestReadWellParam(t *testing.T) {
|
||||
las := NewLas()
|
||||
err := las.ReadWellParam("NULL.\t-999.99 : desc")
|
||||
res, err := CodePageDetect("test_files\\test.txt", "~X~")
|
||||
if err != nil {
|
||||
t.Errorf("<CodePageDetect> on file '%s' return error: %v", "test.txt", err)
|
||||
}
|
||||
if res != Cp866 {
|
||||
t.Errorf("<CodePageDetect> on file '%s' expected 866 got: %s", "test.txt", CodePageAsString(res))
|
||||
}
|
||||
|
||||
res, err = CodePageDetect("test_files\\test.txt")
|
||||
if res != CpWindows1251 {
|
||||
t.Errorf("<CodePageDetect> on file '%s' expected 1251 got: %s", "test.txt", CodePageAsString(res))
|
||||
}
|
||||
|
||||
_, err = CodePageDetect("-.-")
|
||||
if err == nil {
|
||||
t.Errorf("<CodePageDetect> on file '-.-' must return error, but return nil")
|
||||
}
|
||||
|
||||
res, _ = CodePageDetect("test_files\\test2.txt")
|
||||
if res != CpEmpty {
|
||||
t.Errorf("<CodePageDetect> on file 'test2.txt' expect CpEmpty got: %s", CodePageAsString(res))
|
||||
}
|
||||
|
||||
res, err = CodePageDetect("test_files\\test3.txt")
|
||||
if (res != CpEmpty) || (err != nil) {
|
||||
t.Errorf("<CodePageDetect> on file 'test3.txt' expect CpEmpty and no error got: %s and %v", CodePageAsString(res), err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package laslib
|
||||
|
||||
const (
|
||||
defWarningCount = 10
|
||||
warningUNDEF = 0
|
||||
directOnRead = 1
|
||||
directOnWrite = 2
|
||||
)
|
||||
|
||||
//TWarning - class to store warning
|
||||
type TWarning struct {
|
||||
direct int // 0 - undefine (warningUNDEF), 1 - on read (directOnRead), 2 - on write (directOnWrite)
|
||||
section int // 0 - undefine (warningUNDEF), lasSecVertion, lasSecWellInfo, lasSecCurInfo, lasSecData
|
||||
line int // number of line in source file
|
||||
desc string // description of warning
|
||||
}
|
||||
|
||||
//TLasWarnings - class to store and manipulate warnings
|
||||
type TLasWarnings struct {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
~VERSION INFORMATION
|
||||
VERS. 1.2 :glas (c) softlandia@gmail.com
|
||||
WRAP. NO : ONE LINE PER DEPTH STEP
|
||||
CPAGE. 1251: code page
|
||||
~WELL INFORMATION
|
||||
STRT.M 1.000 :START DEPTH
|
||||
STOP.M 1.000 :STOP DEPTH
|
||||
STEP.M 0.100 :STEP
|
||||
NULL. -999.990 :NULL VALUE
|
||||
WELL. 12-Ñïëîøíàÿ :WELL
|
||||
~Curve Information Section
|
||||
DEPT.M :
|
||||
àÏÑ.ä.åä. :
|
||||
àÏÑ1.óñë.åä. :
|
||||
àÏÑ2. :
|
||||
~A DEPT | àÏÑ | àÏÑ1 | àÏÑ2 |
|
||||
1.000 -999.990 -999.990 2.000
|
||||
1.100 -999.990 11.535 1.000
|
||||
1.200 -999.990 -999.990 5.000
|
||||
1.300 0.762 -999.990 5.000
|
||||
1.400 0.707 16.053 1.000
|
||||
1.500 0.000 -999.990 0.000
|
||||
1.600 0.000 -999.990 -999.990
|
|
@ -0,0 +1,28 @@
|
|||
~Version Information
|
||||
VERS. 1.20: cp_866 CWLS log ASCII Standard - VERSION 1.20
|
||||
WRAP. NO : One line per depth step
|
||||
~Well Information
|
||||
STRT .M 1.0 : Start depth
|
||||
STOP .M 1.0 : Stop depth
|
||||
STEP .M 0.10 : Step
|
||||
NULL . -9999.00 : Null value
|
||||
WELL . WELL : 12-‘¯«®è ï
|
||||
~Curve Information
|
||||
#----------------------------------------------------------------
|
||||
#MNEM.UNIT API CODE CURVE DESCRIPTION
|
||||
DEPT .M : 1 DEPTH
|
||||
<EFBFBD>‘ .¤.¥¤. :
|
||||
<EFBFBD>‘.ãá«.¥¤. : et
|
||||
<EFBFBD>‘ . :
|
||||
~Parameter Information
|
||||
EKB .M 72.7 : Elevation,Kelly Bushing
|
||||
~ASCII Log Data
|
||||
1.000 -9999.000 -9999.000 2.000
|
||||
1.100 -9999.000 11.535 1.000
|
||||
1.200 -9999.000 -9999.000 5.000
|
||||
1.300 0.762 -9999.000 5.000
|
||||
1.400 0.707 16.053 1.000
|
||||
1.500 0.000 -9999.000 0.000
|
||||
1.600 0.000 -9999.000 -9999.000
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
**file: 1.las**
|
||||
0, dir: 1, line: -1, desc: detected param: NULL, unit: -9999.00, value:
|
||||
|
||||
1, dir: 1, line: -1, desc: while process parameter: 'NULL . -9999.00 : Null value' occure error: strconv.ParseFloat: parsing "": invalid syntax
|
||||
2, dir: 1, line: -1, desc: invalid NULL parameter, equal 0. replace to -999.250
|
||||
3, dir: 1, line: -1, desc: invalid STRT: 1.000 or STOP: 1.000, will be replace to actually
|
||||
4, dir: 1, line: -1, desc: while process parameter: 'аПС.усл.ед. : et' occure error: line : 'et' is bad for Parameter, must have ':'
|
||||
5, dir: 1, line: 20, desc: not all column readed, set log value to NULL
|
||||
6, dir: 1, line: 21, desc: not all column readed, set log value to NULL
|
||||
7, dir: 1, line: 22, desc: actual number of data lines more than expected, check: STRT, STOP, STEP
|
||||
8, dir: 1, line: 22, desc: expand number of points
|
||||
9, dir: 1, line: 22, desc: not all column readed, set log value to NULL
|
||||
10, dir: 1, line: 23, desc: not all column readed, set log value to NULL
|
||||
11, dir: 1, line: 24, desc: actual number of data lines more than expected, check: STRT, STOP, STEP
|
||||
12, dir: 1, line: 24, desc: expand number of points
|
||||
13, dir: 1, line: 24, desc: not all column readed, set log value to NULL
|
||||
14, dir: 1, line: 25, desc: not all column readed, set log value to NULL
|
||||
15, dir: 1, line: 26, desc: not all column readed, set log value to NULL
|
||||
16, dir: 1, line: 27, desc: line: 27 is empty, ignore
|
||||
17, dir: 1, line: 28, desc: line: 28 is empty, ignore
|
|
@ -0,0 +1,51 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/softlandia/laslib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//test file "1.las"
|
||||
las := laslib.NewLas()
|
||||
n, err := las.Open("1.las")
|
||||
if err != nil {
|
||||
fmt.Printf("on open file: '1.las' error:%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if n == 7 {
|
||||
fmt.Println("read 1.las OK", err)
|
||||
} else {
|
||||
fmt.Printf("TEST read 1.las ERROR, n = %d, must 7\n", n)
|
||||
fmt.Println(err)
|
||||
}
|
||||
las.SaveWarning("1.warning.md")
|
||||
if las.WarningCount() > 0 {
|
||||
fmt.Printf("warning count: %d,\nsave warningto file: 1.warning.md\n", las.WarningCount())
|
||||
}
|
||||
|
||||
err = las.SetNull(-999.99)
|
||||
fmt.Println("set new null value done, error: ", err)
|
||||
|
||||
err = las.Save("-1.las")
|
||||
if err != nil {
|
||||
fmt.Println("TEST save -1.las ERROR: ", err)
|
||||
} else {
|
||||
fmt.Println("TEST save -1.las OK")
|
||||
}
|
||||
|
||||
las = nil
|
||||
las = laslib.NewLas()
|
||||
fmt.Println("reopen file: -1.las")
|
||||
n, err = las.Open("-1.las")
|
||||
if (n == 7) && (las.Null == -999.99) {
|
||||
fmt.Println("TEST read -1.las OK")
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("TEST read -1.las ERROR")
|
||||
fmt.Println("NULL not -999.99 or count dept points != 7")
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче