las2witsml 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +2 -0
- data/README +33 -0
- data/bin/las2witsml +79 -0
- data/lib/las2witsml.rb +23 -0
- data/lib/las_file.rb +235 -0
- data/lib/witsml_file.rb +326 -0
- metadata +52 -0
data/LICENSE
ADDED
data/README
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
las2witsml -- Convert Log ASCII Stanfard (LAS) files to WITSML <log> objects.
|
2
|
+
|
3
|
+
Use this script to convert LAS files to WITSML logs. The script attempts to be forgiving
|
4
|
+
of some commonly encountered variations. It can process most LAS v2 and v3 files.
|
5
|
+
It does not stand on ceremony.
|
6
|
+
|
7
|
+
Usage: wmls [options]
|
8
|
+
-Dvariable=value Replace occurrences of %variable% with value, in the query template
|
9
|
+
-v, --verbose Run verbosely
|
10
|
+
-r, --url url URL of the WITSML service
|
11
|
+
-u, --username USER HTTP user name
|
12
|
+
-p, --password PASS HTTP password
|
13
|
+
-q, --query QUERYFILE Path to file containing query, delete, add or update template
|
14
|
+
-a cap|get|add|update|delete WITSML action; default is 'get'
|
15
|
+
--action
|
16
|
+
-o, --optionsin OPTIONSIN optionsIn string (optional)
|
17
|
+
-h, --help Show this message
|
18
|
+
|
19
|
+
Example:
|
20
|
+
wmls -q query_v1311/get_all_wells.xml -r https://witsml.wellstorm.com/witsml/services/store -u username -p mypassword -a get
|
21
|
+
|
22
|
+
I've included a bunch of sample query templates originally created by Gary Masters of Energistics.
|
23
|
+
|
24
|
+
License: Apache 2.0
|
25
|
+
|
26
|
+
History:
|
27
|
+
|
28
|
+
10 Mar 2011 -- initial commit.
|
29
|
+
16 Oct 2011 -- added -D option to wmls command line tool (0.1.7)
|
30
|
+
01 May 2012 -- added GetCap support (0.1.8)
|
31
|
+
01 May 2012 -- added support for capabilitiesIn parameter to all calls (0.1.9)
|
32
|
+
04 May 2012 -- added support for a headers parameter to all calls (0.1.11)
|
33
|
+
07 May 2012 -- fix headers param to get_cap (default should be {}) (0.1.13)
|
data/bin/las2witsml
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# Copyright 2012 Wellstorm Development LLC
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
require 'optparse'
|
18
|
+
|
19
|
+
require "las_file"
|
20
|
+
require "witsml_file"
|
21
|
+
require "las2witsml"
|
22
|
+
|
23
|
+
options = {:v=>false}
|
24
|
+
|
25
|
+
opts =OptionParser.new do |o|
|
26
|
+
o.banner = "Usage: las2witsml [-n name] [-u uid] [-b uid] [-l uid] [-x version] [-h] [-v] lasfile"
|
27
|
+
|
28
|
+
o.on("-n", "--namelog name", "Name to give the WITSML log; default 'Untitled'") do |v|
|
29
|
+
options[:name] = v
|
30
|
+
end
|
31
|
+
|
32
|
+
o.on("-u", "--uidwell uid", "WITSML uid of the containing well") do |v|
|
33
|
+
options[:uw] = v
|
34
|
+
end
|
35
|
+
o.on("-b", "--uidwellbore uid", "WITSML uid of the containing wellbore") do |v|
|
36
|
+
options[:uwb] = v
|
37
|
+
end
|
38
|
+
o.on("-l", "--uidlog uid", "WITSML uid to assign the log") do |v|
|
39
|
+
options[:ul] = v
|
40
|
+
end
|
41
|
+
o.on("-x", "--version version", "WITSML version to emit, either 1.3.1 or 1.4.1; default 1.4.1") do |v|
|
42
|
+
options[:version] = v
|
43
|
+
end
|
44
|
+
o.on("-v", "--verbose", "Emit diagnostic output on stderr") do |v|
|
45
|
+
options[:v] = v
|
46
|
+
end
|
47
|
+
|
48
|
+
o.on_tail("-h", "--help", "Show this message") do
|
49
|
+
puts o
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
opts.parse!
|
57
|
+
if ( !options[:url] )
|
58
|
+
#puts(opts.help)
|
59
|
+
#exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
ARGV.each do|a|
|
64
|
+
l2w = Las2Witsml.new
|
65
|
+
infile = File.new a, 'r'
|
66
|
+
outfile = $stdout
|
67
|
+
|
68
|
+
case options[:version]
|
69
|
+
when '1.3.1', '1.3.1.1', '1.3.1.0'
|
70
|
+
version = 1311
|
71
|
+
else
|
72
|
+
version = 1410
|
73
|
+
end
|
74
|
+
|
75
|
+
l2w.run infile, outfile, options[:uw] || "", options[:uwb] || "", options[:ul] || "", options[:name] || 'Untitled', version, options[:v] != false
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
data/lib/las2witsml.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#require 'java'
|
2
|
+
#require 'las_file'
|
3
|
+
#require 'witsml_file'
|
4
|
+
class Las2Witsml
|
5
|
+
def run in_stream, out_stream, uid_well, uid_wellbore, uid_log, name_log, version = 1411, verbose=false
|
6
|
+
begin
|
7
|
+
|
8
|
+
las =LasFile.new in_stream
|
9
|
+
las.process verbose
|
10
|
+
$stderr.puts 'processed las file'
|
11
|
+
|
12
|
+
witsml = WitsmlFile.new out_stream, version
|
13
|
+
$stderr.puts 'made witsmlFile' if verbose
|
14
|
+
witsml.from_las_file(las, uid_well, uid_wellbore, uid_log, name_log, verbose)
|
15
|
+
$stderr.puts 'read from lasFile' if verbose
|
16
|
+
out_stream.flush
|
17
|
+
true
|
18
|
+
rescue => e
|
19
|
+
$stderr.puts e.backtrace.join("\n")
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/las_file.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
|
4
|
+
class LogCurveInfo
|
5
|
+
attr_accessor :mnemonic,:unit,:description
|
6
|
+
def initialize(mnemonic, unit = nil, description = nil)
|
7
|
+
@mnemonic = mnemonic; @unit = unit; @description = description
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class LasFile
|
12
|
+
|
13
|
+
|
14
|
+
attr_reader :log_curve_infos,
|
15
|
+
:measured_depth_unit,
|
16
|
+
:start_measured_depth_index, :stop_measured_depth_index,
|
17
|
+
:start_date_time_index, :stop_date_time_index,
|
18
|
+
:null_value, :service_company, :curve_values,
|
19
|
+
:elevation_kelly_bushing, :log_measured_from, :permanent_datum, :above_permanent_datum
|
20
|
+
|
21
|
+
def initialize(io)
|
22
|
+
@log_curve_infos = []
|
23
|
+
@curve_values = []
|
24
|
+
@in = io
|
25
|
+
end
|
26
|
+
|
27
|
+
def process verbose=false
|
28
|
+
while block(verbose); end
|
29
|
+
end
|
30
|
+
|
31
|
+
#each_data_line is an optimization so we don't have to store 8 million
|
32
|
+
# or so ruby objects in memory (uses up 1.6GB).
|
33
|
+
|
34
|
+
def each_data_line verbose=false
|
35
|
+
lines_processed = 0
|
36
|
+
while line = next_line
|
37
|
+
yield(line.split)
|
38
|
+
lines_processed = lines_processed + 1
|
39
|
+
$stderr.puts "processed #{lines_processed} lines" if (verbose && lines_processed % 1000 == 0 )
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def block(verbose)
|
47
|
+
line = next_line
|
48
|
+
section = /^~([a-zA-Z]+).*/.match(line)
|
49
|
+
|
50
|
+
$stderr.puts "processing #{section [1]} section" if verbose
|
51
|
+
case section [1].downcase
|
52
|
+
when 'well', 'w'
|
53
|
+
well_information
|
54
|
+
when 'version', 'v'
|
55
|
+
version_information
|
56
|
+
when 'curve', 'c'
|
57
|
+
curve_information
|
58
|
+
when 'parameter', 'p'
|
59
|
+
parameter_information
|
60
|
+
when 'other', 'o'
|
61
|
+
other_information
|
62
|
+
when 'a'
|
63
|
+
#assume this is the last block. we'll deliver
|
64
|
+
# curve data on demand, in each_data_line
|
65
|
+
#curve_data(verbose) #how we used to process ~A
|
66
|
+
end if section
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def version_information
|
71
|
+
skip_block # we don't care for now
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_depth_unit(unit)
|
75
|
+
if unit then
|
76
|
+
dunit = unit.downcase
|
77
|
+
if (dunit == 'ft' || dunit == 'f' || dunit == 'm' || dunit == 'cm' || dunit == 'in')
|
78
|
+
dunit
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_date_time (data,info, unit='s')
|
84
|
+
begin
|
85
|
+
Time.parse(data) #nicely handles the case of either dd/mm/yy or hh:mm:ss
|
86
|
+
rescue
|
87
|
+
begin
|
88
|
+
offset = data.to_f #for now I'm assuming unit of seconds
|
89
|
+
|
90
|
+
# The Chevron SLB data uses DATE as the units for seconds since 1/1/1970.
|
91
|
+
if unit.downcase == 'date'
|
92
|
+
Time.at(offset)
|
93
|
+
else
|
94
|
+
base = Time.parse(info) # hah! This works for the Scientif Drilling LAS files. They put in comments: 11/30/08 18:00:00.00 (Central Standard Time)"
|
95
|
+
base + offset
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
# ok, ok... nice try, but info did not have a date in it.
|
99
|
+
offset = data.to_f #for now I'm assuming unit of seconds
|
100
|
+
Time.at(offset)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def well_information
|
106
|
+
#['STRT','STOP','STEP','NULL','COMP','WELL','FLD','LOC','PROV','CNTY','STAT','CTRY','SRVC','DATE','UWI','API']
|
107
|
+
while m = /^([^~][^\.]+)\.([^\s]*)(.*):(.*)$/.match(line = next_line)
|
108
|
+
mnemonic, unit, data, info = [*m].slice(1, 4).collect{|s| s.strip}
|
109
|
+
|
110
|
+
case mnemonic
|
111
|
+
when 'STRT'
|
112
|
+
if dunit = is_depth_unit(unit) then
|
113
|
+
@measured_depth_unit = dunit
|
114
|
+
@start_measured_depth_index = data.to_f
|
115
|
+
else
|
116
|
+
@start_date_time_index = parse_date_time(data, info, unit)
|
117
|
+
end
|
118
|
+
when 'STOP'
|
119
|
+
if dunit = is_depth_unit(unit) then
|
120
|
+
@measured_depth_unit = unit
|
121
|
+
@stop_measured_depth_index = data.to_f
|
122
|
+
else
|
123
|
+
@stop_date_time_index = parse_date_time(data, info, unit)
|
124
|
+
|
125
|
+
end
|
126
|
+
when 'STEP'
|
127
|
+
@step_increment = data.to_f
|
128
|
+
when 'NULL'
|
129
|
+
@null_value = data
|
130
|
+
when 'SRVC'
|
131
|
+
@service_company = data
|
132
|
+
end
|
133
|
+
end
|
134
|
+
put_back_line(line)
|
135
|
+
end
|
136
|
+
|
137
|
+
def curve_information
|
138
|
+
while m = /^([^~][^\.]+)\.([^: \t]*)\s*([^:]*):(.*)$/.match(line = next_line)
|
139
|
+
mnemonic, unit, _, description = [*m].slice(1, 4).collect {|s| s.strip} #api_code
|
140
|
+
log_curve_info mnemonic, unit, description
|
141
|
+
end
|
142
|
+
put_back_line(line)
|
143
|
+
end
|
144
|
+
|
145
|
+
def parameter_information
|
146
|
+
# BHT .DEGF 210.00000 :Bottom Hole Temperature (used in calculations)
|
147
|
+
# DFD .LB/G 9.70000 :Drilling Fluid Density
|
148
|
+
# DFL . :Drilling Fluid Loss
|
149
|
+
# DFV .S 54.00000 :Drilling Fluid Viscosity
|
150
|
+
# MATR . LIME :Rock Matrix for Neutron Porosity Corrections
|
151
|
+
# MDEN .G/C3 2.71000 :Matrix Density
|
152
|
+
# RANG . 101W :Range
|
153
|
+
# TOWN . 154N :Township
|
154
|
+
# SECT . 2 :Section
|
155
|
+
# APIN . 33-105-02119 :API Serial Number
|
156
|
+
# FL1 . 285 FNL & 2000 FWL :Field Location Line 1
|
157
|
+
# FL . LOT3 :Field Location
|
158
|
+
# FN . TODD :Field Name
|
159
|
+
# WN . WILLISTON AIRPORT 2-11 :Well Name
|
160
|
+
# CN . BRIGHAM OIL & GAS L.P. :Company Name
|
161
|
+
# RUN . 1 :RUN NUMBER
|
162
|
+
# PDAT . GROUND LEVEL :Permanent Datum
|
163
|
+
# EPD .F 1972.000000 :Elevation of Permanent Datum above Mean Sea Level
|
164
|
+
# LMF . KELLY BUSHING :Logging Measured From (Name of Logging Elevation Reference)
|
165
|
+
# APD .F 19.000000 :Elevation of Depth Reference (LMF) above Permanent Dat
|
166
|
+
|
167
|
+
while m = /^([^~][^\.]+)\.([^\s]*)(.*):(.*)$/.match(line = next_line)
|
168
|
+
mnemonic, unit, data = [*m].slice(1, 4).collect{|s| s.strip}
|
169
|
+
|
170
|
+
case mnemonic
|
171
|
+
when 'RUN'
|
172
|
+
@run_number = data.to_i
|
173
|
+
when 'PDAT'
|
174
|
+
@permanent_datum = data
|
175
|
+
when 'EPD', 'EGL'
|
176
|
+
@elevation_permanent_datum = data.to_f
|
177
|
+
@elevation_unit = unit
|
178
|
+
when 'LMF'
|
179
|
+
@log_measured_from = data
|
180
|
+
when 'APD'
|
181
|
+
@above_permanent_datum = data.to_f
|
182
|
+
@elevation_unit = unit
|
183
|
+
when 'EKB'
|
184
|
+
@elevation_kelly_bushing = data
|
185
|
+
@elevation_unit = unit
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
if @elevation_kelly_bushing
|
190
|
+
@log_measured_from = "KELY BUSHING"
|
191
|
+
end
|
192
|
+
if @elevation_kelly_bushing and @elevation_permanent_datum and not @above_permanent_datum then
|
193
|
+
@above_permanent_datum = @elevation_kelly_bushing.to_f - @elevation_permanent_datum.to_f
|
194
|
+
end
|
195
|
+
if not @elevation_kelly_bushing and @elevation_permanent_datum and @above_permanent_datum then
|
196
|
+
@elevation_kelly_bushing = @above_permanent_datum.to_f + @elevation_permanent_datum.to_f
|
197
|
+
end
|
198
|
+
|
199
|
+
put_back_line(line)
|
200
|
+
end
|
201
|
+
|
202
|
+
def other_information
|
203
|
+
skip_block
|
204
|
+
end
|
205
|
+
|
206
|
+
def next_line
|
207
|
+
if @next_line
|
208
|
+
line = @next_line
|
209
|
+
@next_line = nil
|
210
|
+
else
|
211
|
+
while /^((#.*)|(\s*))$/ =~(line = @in.gets);end
|
212
|
+
end
|
213
|
+
line
|
214
|
+
end
|
215
|
+
|
216
|
+
def put_back_line(line)
|
217
|
+
@next_line = line
|
218
|
+
end
|
219
|
+
|
220
|
+
def skip_block
|
221
|
+
while !(/^~.*$/ =~ (line = @in.readline)); end
|
222
|
+
put_back_line(line)
|
223
|
+
end
|
224
|
+
|
225
|
+
def log_curve_info mnemonic, las_unit, description
|
226
|
+
@log_curve_infos << LogCurveInfo.new(mnemonic, las_unit.downcase, description)
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
|
data/lib/witsml_file.rb
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
#require 'las_file'
|
2
|
+
require 'time'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class WitsmlFile
|
6
|
+
|
7
|
+
UNITS = {'ft'=>'ft',
|
8
|
+
'klbs'=>'klbf',
|
9
|
+
'PSI'=>'psi',
|
10
|
+
'SPM'=>'1Pmin',
|
11
|
+
'RPM'=>'revPmin',
|
12
|
+
'UNITS'=>'ftTlbf',
|
13
|
+
'ft/hr'=>'ftPh' ,
|
14
|
+
'percent'=>'%',
|
15
|
+
'bbl'=>'bbl' ,
|
16
|
+
'ton-miles'=>'tonfUSTmi',
|
17
|
+
'strokes'=>'unitless',
|
18
|
+
'GPM'=>'galUSPmin' ,
|
19
|
+
'hrs'=>'h' ,
|
20
|
+
'amps'=>'A' ,
|
21
|
+
'ppm'=>'ppm',
|
22
|
+
'barrels'=>'bbl',
|
23
|
+
'ft/min'=>'ftPmin',
|
24
|
+
'ksi'=>'kpsi',
|
25
|
+
'cm'=>'cm',
|
26
|
+
'ft per hour'=>'ftPh',
|
27
|
+
'min/ft'=>'minPft'}
|
28
|
+
|
29
|
+
def initialize(out, witsml_version = 1410)
|
30
|
+
@indent = 0
|
31
|
+
@out = out
|
32
|
+
@witsml_version = witsml_version
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def digest_las(las_file)
|
37
|
+
#pre chew the las
|
38
|
+
|
39
|
+
#merge date and time
|
40
|
+
lcis = las_file.log_curve_infos
|
41
|
+
|
42
|
+
#Set these variables, which depend on whether we have a time or a depth log:
|
43
|
+
# new_lcis : possibly modified (to merge date+time) list of logCurveInfos
|
44
|
+
# index_lci: the LCI of the index curve
|
45
|
+
# is_index_index: proc to test the given integer to see whether it is one of the possibly one or two index curve indexes
|
46
|
+
# get_index: proc to extract the index from an array of values
|
47
|
+
|
48
|
+
if las_file.measured_depth_unit then
|
49
|
+
# it's a depth log
|
50
|
+
# use the unaltered array of lcis
|
51
|
+
new_lcis = lcis
|
52
|
+
index_lci = lcis[0]
|
53
|
+
|
54
|
+
is_index_index = lambda {|i| (i == 0) }
|
55
|
+
get_index = lambda { |values| values[0] }
|
56
|
+
|
57
|
+
else
|
58
|
+
|
59
|
+
#it's a time log
|
60
|
+
date_index, time_index, date_fmt = make_time_indexes(lcis)
|
61
|
+
|
62
|
+
# make the new lci for the merged index. Name it 'DATETIME'
|
63
|
+
index_lci = LogCurveInfo.new('DATETIME')
|
64
|
+
|
65
|
+
rest_lcis = lcis.reject {|lci| ['time', 'date'].member?(lci.mnemonic.downcase)}
|
66
|
+
|
67
|
+
# use this new array of lcis now
|
68
|
+
new_lcis = [index_lci] + rest_lcis
|
69
|
+
is_index_index = lambda {|i| (i == time_index || i == date_index) }
|
70
|
+
get_index = lambda do |values|
|
71
|
+
date = values[date_index]
|
72
|
+
#$stderr.puts "date #{date} fmt = #{date_fmt}"
|
73
|
+
|
74
|
+
if date_fmt == '1' then
|
75
|
+
#$stderr.puts "date = #{date}"
|
76
|
+
offset = las_file.start_date_time_index
|
77
|
+
dtf = date.to_f
|
78
|
+
if (dtf < 86400000 && offset) then
|
79
|
+
dt = Time.at(date.to_f + offset.to_f).iso8601
|
80
|
+
else
|
81
|
+
dt = Time.at(date.to_f).iso8601
|
82
|
+
end
|
83
|
+
else
|
84
|
+
#if we have "mmddyy", munge it so the parser works
|
85
|
+
date = date.sub(/(\d\d)(\d\d)(\d\d)/, '\2/\3/\1') if date_fmt == 'yymmdd'
|
86
|
+
time = values[time_index]
|
87
|
+
# Pason does not put colons between time components; SLB does:
|
88
|
+
time = time.sub(/(\d\d)(\d\d)(\d\d)/, '\1:\2:\3') if /\d\d\d\d\d\d/=~ time
|
89
|
+
dt = Time.parse(time + ' ' + date).iso8601
|
90
|
+
end
|
91
|
+
dt
|
92
|
+
end
|
93
|
+
end
|
94
|
+
[new_lcis, index_lci, is_index_index, get_index]
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
def from_las_file(las_file, uid_well='$UIDWELL', uid_wellbore='$UIDWELLBORE', uid='$UID', name='$NAME', verbose=false)
|
101
|
+
new_lcis, _, is_index_index, get_index = digest_las(las_file) #unused index_lci
|
102
|
+
|
103
|
+
if @witsml_version >= 1410
|
104
|
+
ns = 'http://www.witsml.org/schemas/1series'
|
105
|
+
vers = '1.4.1.0'
|
106
|
+
else
|
107
|
+
ns = 'http://www.witsml.org/schemas/131'
|
108
|
+
vers = '1.3.1.1'
|
109
|
+
end
|
110
|
+
|
111
|
+
index_curve = new_lcis[0].mnemonic # assume first column is index
|
112
|
+
add_element('logs',{'xmlns'=>ns, 'version'=>vers}) do
|
113
|
+
add_element('log', {'uidWell' => uid_well, 'uidWellbore'=>uid_wellbore, 'uid' => uid}) do
|
114
|
+
add_text_element 'nameWell', name
|
115
|
+
add_text_element 'nameWellbore', name
|
116
|
+
add_text_element 'name', name
|
117
|
+
#add_text_element 'dataRowCount', las_file.curve_values.length
|
118
|
+
add_text_element 'serviceCompany', las_file.service_company
|
119
|
+
add_text_element 'description', 'Created by Wellstorm LAS Import'
|
120
|
+
|
121
|
+
measured_depth_unit = normalize_unit(las_file.measured_depth_unit);
|
122
|
+
|
123
|
+
if measured_depth_unit then
|
124
|
+
add_text_element 'indexType', 'measured depth'
|
125
|
+
add_text_element 'startIndex', las_file.start_measured_depth_index, {'uom' => measured_depth_unit} if las_file.start_measured_depth_index
|
126
|
+
add_text_element 'endIndex', las_file.stop_measured_depth_index, {'uom' => measured_depth_unit} if las_file.stop_measured_depth_index
|
127
|
+
else
|
128
|
+
add_text_element 'indexType', 'date time'
|
129
|
+
|
130
|
+
|
131
|
+
#puts ("start index in LAS is #{las_file.start_date_time_index}")
|
132
|
+
|
133
|
+
add_text_element 'startDateTimeIndex', las_file.start_date_time_index.iso8601 if las_file.start_date_time_index
|
134
|
+
add_text_element 'endDateTimeIndex', las_file.stop_date_time_index.iso8601 if las_file.stop_date_time_index
|
135
|
+
end
|
136
|
+
#add_text_element 'direction'
|
137
|
+
|
138
|
+
if @witsml_version >= 1410
|
139
|
+
add_text_element 'indexCurve', index_curve
|
140
|
+
else
|
141
|
+
add_text_element 'indexCurve', index_curve, {'columnIndex'=>1}
|
142
|
+
end
|
143
|
+
add_text_element 'nullValue', las_file.null_value
|
144
|
+
|
145
|
+
|
146
|
+
begin
|
147
|
+
# use this new array of curve values we build
|
148
|
+
tempfile = Tempfile.new 'las2witsml'
|
149
|
+
|
150
|
+
#keep track of start/stop for each channel
|
151
|
+
start = []
|
152
|
+
stop = []
|
153
|
+
|
154
|
+
las_file.each_data_line(verbose) do |values|
|
155
|
+
#$stderr.puts values
|
156
|
+
idx = get_index.call(values)
|
157
|
+
tempfile << idx
|
158
|
+
#$stderr.puts "idx #{idx}"
|
159
|
+
values.each_with_index do |v, i|
|
160
|
+
|
161
|
+
start[i] = idx if start[i].nil? and v != las_file.null_value
|
162
|
+
stop[i] = idx if v != las_file.null_value
|
163
|
+
tempfile << ",#{v}" if !is_index_index.call(i)
|
164
|
+
|
165
|
+
# $stderr.puts "put #{v} on tempfile"
|
166
|
+
end
|
167
|
+
tempfile << $/
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
$stderr.puts "done adding lines to tempfile" if verbose
|
171
|
+
|
172
|
+
tempfile.rewind
|
173
|
+
|
174
|
+
$stderr.puts "rewound #{tempfile}" if verbose
|
175
|
+
|
176
|
+
# Now we can build the lcis, surveying each curve for its min/max indexes
|
177
|
+
new_lcis.each_with_index do |lci, index|
|
178
|
+
add_log_curve_info lci, (index + 1), start[index+1], stop[index+1], measured_depth_unit
|
179
|
+
end
|
180
|
+
|
181
|
+
$stderr.puts "added #{new_lcis.length} LCIs" if verbose
|
182
|
+
|
183
|
+
|
184
|
+
# Now add the curve data
|
185
|
+
add_element 'logData' do
|
186
|
+
if @witsml_version >= 1410
|
187
|
+
add_text_element 'mnemonicList', new_lcis.map{|lci| lci.mnemonic}.join(',')
|
188
|
+
add_text_element 'unitList', new_lcis.map{|lci| lci.unit}.join(',')
|
189
|
+
end
|
190
|
+
|
191
|
+
n = 0
|
192
|
+
tempfile.each_line do |values|
|
193
|
+
add_text_element 'data', values
|
194
|
+
n = n + 1
|
195
|
+
$stderr.puts "converted #{n} lines to WITSML" if (verbose && n % 1000 == 0 )
|
196
|
+
end
|
197
|
+
$stderr.puts "converted a total of #{n} lines to WITSML" if verbose
|
198
|
+
end
|
199
|
+
rescue
|
200
|
+
tempfile.close true
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
def add_element name, attrs = {}, one_liner = false
|
209
|
+
@indent.times {@out.write " "}
|
210
|
+
@out.write "<#{name}"
|
211
|
+
attrs.each_pair {|attr, val| @out.write " #{attr}='#{escape_attr(val)}'"}
|
212
|
+
if block_given?
|
213
|
+
@out.write ">"
|
214
|
+
@out.write "\n" if !one_liner
|
215
|
+
@indent += 2
|
216
|
+
yield
|
217
|
+
@indent -= 2
|
218
|
+
if !one_liner
|
219
|
+
@out.write "\n"
|
220
|
+
@indent.times {@out.write " "}
|
221
|
+
end
|
222
|
+
@out.write "</#{name}>\n"
|
223
|
+
else
|
224
|
+
out.write "/>\n"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def add_text_element(name, text, attrs = {})
|
229
|
+
add_element name, attrs, true do
|
230
|
+
@out.write escape_text(text)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def add_log_curve_info(las_lci, column_index, min_index, max_index, measured_depth_unit)
|
235
|
+
add_element('logCurveInfo', {'uid' => las_lci.mnemonic[0..63]}) do
|
236
|
+
add_text_element 'mnemonic', las_lci.mnemonic[0..31]
|
237
|
+
add_text_element 'unit', las_lci.unit.downcase if las_lci.unit
|
238
|
+
|
239
|
+
if measured_depth_unit then
|
240
|
+
add_text_element 'minIndex', min_index, {'uom'=>measured_depth_unit} if min_index
|
241
|
+
add_text_element 'maxIndex', max_index, {'uom'=>measured_depth_unit} if max_index
|
242
|
+
else
|
243
|
+
add_text_element 'minDateTimeIndex', min_index if min_index
|
244
|
+
add_text_element 'maxDateTimeIndex', max_index if max_index
|
245
|
+
end
|
246
|
+
if @witsml_version < 1410
|
247
|
+
add_text_element 'columnIndex', column_index.to_s
|
248
|
+
end
|
249
|
+
add_text_element 'curveDescription',las_lci.description if las_lci.description && las_lci.description.length > 0
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def escape_attr(text)
|
254
|
+
escape_text(text).sub(/'/, "&pos;").sub(/"/, """)
|
255
|
+
end
|
256
|
+
def escape_text(text)
|
257
|
+
text.to_s.strip.sub(/&/, "&").sub(/</, "<")
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
def make_time_indexes(lcis)
|
263
|
+
|
264
|
+
# Typically we see DATE and TIME
|
265
|
+
# We can also see only TIME in which case we expect long integer seconds since 1970
|
266
|
+
# (There's no spec that says that; but this data comes from SLB's IDEAL which uses Unix epoch)
|
267
|
+
# M/D Totco declares one curve named DATE. It has space separated data and time.
|
268
|
+
|
269
|
+
|
270
|
+
date_index = lcis.collect {|lci| lci.mnemonic.downcase}.index('date')
|
271
|
+
time_index = lcis.collect {|lci| lci.mnemonic.downcase}.index('time')
|
272
|
+
|
273
|
+
$stderr.puts("date_index #{date_index}, time_index #{time_index}")
|
274
|
+
|
275
|
+
|
276
|
+
if !time_index then
|
277
|
+
#assume time will be in the next column
|
278
|
+
time_index = (date_index + 1) if date_index
|
279
|
+
end
|
280
|
+
# TODO we never used time_fmt -- why? commenting to suppress warning
|
281
|
+
#time_fmt = lcis[time_index].unit.downcase
|
282
|
+
|
283
|
+
if !date_index then
|
284
|
+
date_index = time_index if time_index
|
285
|
+
date_fmt = '1' # to signify integers since 1/1/1970
|
286
|
+
$stderr.puts 'using integer dates'
|
287
|
+
else
|
288
|
+
date_fmt = lcis[date_index].unit.downcase
|
289
|
+
end
|
290
|
+
|
291
|
+
# at the moment we understand times of format "hhmmss"
|
292
|
+
# and dates "d" or "mmddyy"
|
293
|
+
raise "No TIME or DATE" if (!time_index && !date_index)
|
294
|
+
|
295
|
+
[date_index, time_index, date_fmt]
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
def normalize_unit(las_unit)
|
300
|
+
# translate to witsml unit
|
301
|
+
# n.b. for now we only do depth units!
|
302
|
+
# see https://svn.wellstorm.com/projects/wsp/ticket/274#comment:3
|
303
|
+
|
304
|
+
return las_unit if !las_unit
|
305
|
+
|
306
|
+
map_las_to_witsml_units = {
|
307
|
+
'f'=>'ft',
|
308
|
+
'feet'=>'ft',
|
309
|
+
'foot'=>'ft',
|
310
|
+
'meters'=>'m',
|
311
|
+
'meter'=>'m'}
|
312
|
+
|
313
|
+
# just a few random ones from CWLS web site:
|
314
|
+
# RHOB .K/M3 45 350 02 00 : 2 BULK DENSITY
|
315
|
+
#NPHI .VOL/VO 42 890 00 00 : 3 NEUTRON POROSITY - SANDSTONE
|
316
|
+
#MSFL .OHMM 20 270 01 00 : 4 Rxo RESISTIVITY
|
317
|
+
# SP .MV 07 010 01 00 : 8 SPONTANEOUS POTENTIAL
|
318
|
+
#GR .GAPI 45 310 01 00 : 9 GAMMA RAY
|
319
|
+
#CALI .MM 45 280 01 00 : 10 CALIPER
|
320
|
+
#DRHO .K/M3 45 356 01 00 : 11 DENSITY CORRECTION
|
321
|
+
|
322
|
+
map_las_to_witsml_units[las_unit.downcase] || las_unit.downcase
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: las2witsml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Hugh Winkler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-12 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A command line tool and a library for converting Log ASCII Standard (LAS)
|
15
|
+
files to WITSML <log> objects.
|
16
|
+
email: hugh.winkler@wellstorm.com
|
17
|
+
executables:
|
18
|
+
- las2witsml
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- README
|
23
|
+
- LICENSE
|
24
|
+
- lib/las2witsml.rb
|
25
|
+
- lib/las_file.rb
|
26
|
+
- lib/witsml_file.rb
|
27
|
+
- bin/las2witsml
|
28
|
+
homepage: https://github.com/wellstorm/las2witsml/
|
29
|
+
licenses: []
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.8.10
|
49
|
+
signing_key:
|
50
|
+
specification_version: 3
|
51
|
+
summary: Converts Log ASCII Standard (LAS) files to WITSML <log> objects.
|
52
|
+
test_files: []
|