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 ADDED
@@ -0,0 +1,2 @@
1
+ Copyright 2012 Wellstorm Development.
2
+ Licensed under the Apache License 2.0
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
+
@@ -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(/"/, "&quot;")
255
+ end
256
+ def escape_text(text)
257
+ text.to_s.strip.sub(/&/, "&amp;").sub(/</, "&lt;")
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: []