humps 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/humps.rb +342 -0
  2. data/lib/humps/version.rb +3 -0
  3. metadata +47 -0
@@ -0,0 +1,342 @@
1
+ require 'rexml/document'
2
+ require 'open-uri'
3
+
4
+ class Humps
5
+ SRTM_PATH = "/var/gisdata/srtm/"
6
+ NED_PATH = "/var/gisdata/ned/ned_13_arc_second_1_degree_tiles/"
7
+ GI_PATH = "/var/gisdata/gi/"
8
+ ASTER_PATH = "/var/gisdata/aster/"
9
+
10
+
11
+ #fall through various sources in order of importance
12
+ def self.get_ele(x,y)
13
+ return -9999 unless x && y
14
+ if y > 59.9 #this is ghetto, but might be GI related, so high latitude
15
+ #ele = self.bilinear_gi(x, y)
16
+ ele = self.bilinear_aster(x, y)# if ele.nil? or ele < -420
17
+ else
18
+ ele = self.bilinear_ned(x,y)
19
+ ele = self.bilinear_srtm(x, y) if ele.nil? or ele < -420
20
+ ele = self.bilinear_aster(x, y) if ele.nil? or ele < -420
21
+ end
22
+ return ele
23
+ end
24
+
25
+ def self.get_hdr(source, x, y)
26
+ hdr_fn, dem_fn = get_file_names(source, x, y)
27
+ return nil unless hdr_fn && File.exists?(hdr_fn)
28
+
29
+ hdr = {}
30
+ File.read(hdr_fn).split("\n").collect { |a| a.split }.each { |d| hdr[d.first] = d.last }
31
+ return {
32
+ :min_x => hdr['xllcorner'].to_f,
33
+ :min_y => hdr['yllcorner'].to_f,
34
+ :cellsize => hdr['cellsize'].to_f,
35
+ :ncols => hdr['ncols'].to_i,
36
+ :nrows => hdr['nrows'].to_i,
37
+ :data_file => dem_fn
38
+ }
39
+ end
40
+
41
+
42
+
43
+
44
+ ############ ASTER related calls #################
45
+ def self.nn_aster(x,y)
46
+ hdr = self.get_hdr(:aster, x, y)
47
+ return -9999 if hdr.nil?
48
+ return nn(hdr, x, y, 2, 'ss')
49
+ end
50
+
51
+ def self.bilinear_aster(x, y)
52
+ hdr = self.get_hdr(:aster, x, y)
53
+ return -9999 if hdr.nil?
54
+ return bilinear(hdr, x, y, 2, 'ss')
55
+ end
56
+
57
+ def self.cubic_aster(x, y)
58
+ hdr = get_hdr(:aster, x,y)
59
+ return -9999 if hdr.nil?
60
+ return cubic(hdr, x, y, 2, 'ssss')
61
+ end
62
+
63
+
64
+ ####### GI related calls ################
65
+ def self.nn_gi(x,y)
66
+ hdr = self.get_hdr(:gi, x, y)
67
+ return -9999 if hdr.nil?
68
+ return nn(hdr, x, y, 4, 'f')
69
+ end
70
+
71
+ def self.bilinear_gi(x, y)
72
+ hdr = self.get_hdr(:gi, x, y)
73
+ return -9999 if hdr.nil?
74
+ return bilinear(hdr, x, y, 4, 'ff')
75
+ end
76
+
77
+ def self.cubic_gi(x, y)
78
+ hdr = get_hdr(:gi, x,y)
79
+ return -9999 if hdr.nil?
80
+ return cubic(hdr, x, y, 4, 'ffff')
81
+ end
82
+
83
+
84
+ ############# SRTM related calls ##############
85
+ def self.nn_srtm(x, y)
86
+ hdr = self.get_hdr(:srtm, x, y)
87
+ return -9999 if hdr.nil?
88
+ return nn(hdr, x, y, 2, 's')
89
+ end
90
+
91
+ def self.bilinear_srtm(x, y)
92
+ hdr = self.get_hdr(:srtm, x, y)
93
+ return -9999 if hdr.nil?
94
+ return bilinear(hdr, x, y, 2, 'ss')
95
+ end
96
+
97
+ def self.cubic_srtm(x,y)
98
+ hdr = get_hdr(:srtm, x,y)
99
+ return -9999 if hdr.nil?
100
+ return cubic(hdr, x, y, 2, 'ssss')
101
+ end
102
+
103
+
104
+ ######## NED related calls ################
105
+ def self.nn_ned(x,y)
106
+ hdr = get_hdr(:ned, x,y)
107
+ return -9999 if hdr.nil?
108
+ return nn(hdr, x, y, 4, 'ff')
109
+ end
110
+
111
+ def self.bilinear_ned(x, y)
112
+ hdr = get_hdr(:ned, x, y)
113
+ return -9999 if hdr.nil?
114
+ return bilinear(hdr, x, y, 4, 'ff')
115
+ end
116
+
117
+ def self.cubic_ned(x,y)
118
+ hdr = get_hdr(:ned, x,y)
119
+ return -9999 if hdr.nil?
120
+ return cubic(hdr, x, y, 4, 'ffff')
121
+ end
122
+
123
+
124
+ ########## base methods ############33333
125
+ def self.nn(hdr, x, y, bytes, pack)
126
+ begin
127
+ x_offset = ((x-hdr[:min_x]) / hdr[:cellsize]).round
128
+ y_offset = (((hdr[:min_y] + hdr[:cellsize]*(hdr[:ncols]-1)) - y) / hdr[:cellsize]).round
129
+ byte_offset = bytes*(x_offset + hdr[:ncols]*y_offset)
130
+ ele = IO.read(hdr[:data_file], bytes, byte_offset).unpack(pack)[0]
131
+ return (10 * ele).round / 10.0
132
+ rescue
133
+ return -9999
134
+ end
135
+ end
136
+
137
+ def self.bilinear(hdr, x, y, bytes, pack)
138
+ x_offset_abs = (x - hdr[:min_x]) / hdr[:cellsize]
139
+ y_offset_abs = ((hdr[:min_y] + hdr[:cellsize]*(hdr[:ncols]-1)) - y) / hdr[:cellsize]
140
+ x_offset = x_offset_abs.floor
141
+ y_offset = y_offset_abs.floor
142
+ x_index = x_offset_abs - x_offset
143
+ y_index = y_offset_abs - y_offset
144
+ num_bytes_to_read = bytes*2
145
+
146
+ byte_offset1 = bytes*(x_offset + hdr[:ncols] * y_offset)
147
+ byte_offset2 = bytes*(x_offset + hdr[:ncols] * (y_offset+1))
148
+
149
+ #puts "(#{x_offset_abs}, #{y_offset_abs}), (#{x_offset}, #{y_offset}), (#{x_index}, #{y_index})"
150
+
151
+ begin
152
+ a1, a2 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset1).unpack(pack)
153
+ a3, a4 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset2).unpack(pack)
154
+ rescue
155
+ #we wrapped around our DEM edges, let's just get nearest neighbor for simplicity in this rare case
156
+ return self.nn_gi(x, y)
157
+ end
158
+
159
+ #if all values are nodata, then it's not a local fluke/void, let's return nodata value
160
+ #otherwise, not all cells are nodata, which is a local void and probably sea level (passing over bridge)
161
+ return -9999 if(a1 < -420 && a1 == a2 && a1 == a3 && a1 == a4)
162
+ a1 = 0 if a1 < -420
163
+ a2 = 0 if a2 < -420
164
+ a3 = 0 if a3 < -420
165
+ a4 = 0 if a4 < -420
166
+
167
+ res = a1 + (a2 - a1)*x_index + (a3 - a1)*y_index + (a1 - a2 - a3 + a4)*x_index*y_index
168
+ (10 * res).round / 10.0
169
+ end
170
+
171
+ def self.cubic(hdr, x, y, bytes, pack)
172
+ ty = hdr[:min_y] + hdr[:cellsize] * (hdr[:nrows] - 1)
173
+ x_offset_abs = (x - hdr[:min_x]) / hdr[:cellsize]
174
+ y_offset_abs = (ty - y) / hdr[:cellsize]
175
+ x_offset = x_offset_abs.floor
176
+ y_offset = y_offset_abs.floor
177
+ x = x_offset_abs - x_offset
178
+ y = y_offset_abs - y_offset
179
+ num_bytes_to_read = bytes*4
180
+
181
+ byte_offset0 = bytes * (x_offset - 1 + (y_offset-1) * hdr[:ncols])
182
+ byte_offset1 = bytes * (x_offset - 1 + (y_offset ) * hdr[:ncols])
183
+ byte_offset2 = bytes * (x_offset - 1 + (y_offset+1) * hdr[:ncols])
184
+ byte_offset3 = bytes * (x_offset - 1 + (y_offset+2) * hdr[:ncols])
185
+
186
+ #we are performing 4 interpolations in the x direction, to get the four elevation points
187
+ #that will makeup a column of interpolated elevations. This column is then used to
188
+ #do one more interpolation to get the elevation point in center of grid.
189
+ begin
190
+ row0 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset0).unpack(pack).map { |p| p < -420 ? 0 : p }
191
+ row1 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset1).unpack(pack).map { |p| p < -420 ? 0 : p }
192
+ row2 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset2).unpack(pack).map { |p| p < -420 ? 0 : p }
193
+ row3 = IO.read(hdr[:data_file], num_bytes_to_read, byte_offset3).unpack(pack).map { |p| p < -420 ? 0 : p }
194
+ rescue
195
+ #probably wrapped off edge of file
196
+ return self.nn_srtm(x, y)
197
+ end
198
+
199
+ i0 = cubic_interpolate(row0, x)
200
+ i1 = cubic_interpolate(row1, x)
201
+ i2 = cubic_interpolate(row2, x)
202
+ i3 = cubic_interpolate(row3, x)
203
+
204
+ #now we have a column of interpolated elevations, we can do one more cubic interpolation
205
+ #and get a final elevation value.
206
+ #puts row3.inspect
207
+ #puts row2.inspect
208
+ #puts row1.inspect
209
+ #puts row0.inspect
210
+ (10 * self.cubic_interpolate([i0, i1, i2, i3], y)).round / 10.0
211
+ end
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+ def self.usgs_ele(x,y)
228
+ begin
229
+ xml = open("http://gisdata.usgs.gov/XMLWebServices/TNM_Elevation_Service.asmx/getElevation?X_Value=#{x}&Y_Value=#{y}&Elevation_Units=METERS&Source_Layer=&Elevation_Only=true").read
230
+ rescue Timeout::Error
231
+ return false
232
+ end
233
+
234
+ doc = REXML::Document.new(xml)
235
+ #ele = (10.0 * doc.elements['double'].text.to_f).round / 10.0
236
+ ele = doc.elements['double'].text.to_f
237
+ if ele < -1000
238
+ #usgs retrns a very large negative, -1.79e308 or so when can't find a value, and
239
+ #since the lowest place on earth is -422 meters (dead sea), checking < -1000 works
240
+ return -9999
241
+ end
242
+ return (10.0 * ele).round / 10.0 #geto round!
243
+ end
244
+
245
+ def self.geonames_ele(x, y)
246
+ return self.geonames_eles([{'x' => x, 'y' => y}])[0]
247
+ end
248
+
249
+ def self.geonames_eles(points)
250
+ eles = []
251
+ points.each_slice(20) do |pts|
252
+ lats = []
253
+ lngs = []
254
+ pts.each { |p| lats << p['y']; lngs << p['x'] }
255
+
256
+ res = open("http://ws.geonames.org/srtm3?lats=#{lats.join(',')}&lngs=#{lngs.join(',')}")
257
+ eles += res.read.split("\n").map do |e|
258
+ parsed = e.strip.to_i
259
+ parsed < -400 ? -9999 : parsed
260
+ end
261
+ sleep(1)
262
+ end
263
+ return eles
264
+ end
265
+
266
+ def self.get_grid(ll_x, ll_y, ur_x, ur_y, num_x, num_y)
267
+ x_spacing = (ur_x - ll_x) / num_x
268
+ y_spacing = (ur_y - ll_y) / num_y
269
+ grid = {:vertices => []}
270
+ max_ele = -50000;
271
+ min_ele = 50000;
272
+
273
+ (num_x + 1).times do |x|
274
+ lng = ll_x + x_spacing*x
275
+ column = []
276
+ (num_y + 1).times do |y|
277
+ lat = ll_y + y_spacing*y
278
+ ele = get_ele(lng, lat)
279
+
280
+ max_ele = ele if ele > max_ele
281
+ min_ele = ele if ele < min_ele
282
+
283
+ column << {:lng => lng, :lat => lat, :ele => ele}
284
+ end
285
+ grid[:vertices] << column
286
+ end
287
+
288
+ return grid.merge!({:min_ele => min_ele, :max_ele => max_ele})
289
+ end
290
+
291
+
292
+ private
293
+
294
+ def self.cubic_interpolate(row, x)
295
+ a0 = row[3] - row[2] - row[0] + row[1]
296
+ a1 = row[0] - row[1] - a0
297
+ a2 = row[2] - row[0]
298
+
299
+ (a0*x*x*x) + (a1*x*x) + a2*x + row[1]
300
+ end
301
+
302
+ def self.get_file_names(source, x, y)
303
+ case source.to_sym
304
+ when :aster
305
+ #each file is a 1x1 degree chunk, named ASTGTM_[N|S][lat.floor][E|W][lng.floor].flt
306
+ n = y >= 0 ? "N" : "S"
307
+ e = x >= 0 ? "E" : "W"
308
+ #ok, so it took me a while to figure this out, but these aren't too magic
309
+ #of numbers...each tile doesn't start on a clean number. The min-y of
310
+ #a tile may be 46.999861. So we have to offset our number by the remainder.
311
+ x_offset = (x-0.000139).floor.abs
312
+ y_offset = (y-0.000139).floor.abs
313
+ fn_base = "#{ASTER_PATH}ASTGTM_#{n}%02d#{e}%03d_dem" % [y_offset, x_offset]
314
+ when :gi
315
+ #each file is a 1x1 degree chunk, named n[lat.floor]e[lng.floor.flt
316
+ x_offset = (x+0.00005).floor.abs
317
+ y_offset = (y+0.000075).floor.abs
318
+ fn_base = "#{GI_PATH}n#{y_offset}e0%02d" % x_offset
319
+ return fn_base + '.hdr', fn_base + '.flt'
320
+ when :srtm
321
+ #these aren't that magical, we have a 5 degree tile system so we have to
322
+ #do a little math to make sure we chunk by degrees of five. The magic looking
323
+ #offset numbers are because each tile does not start/end on a clean decimal
324
+ #boundary, so we have to offset by the amount of overlap between degrees.
325
+ filex = ((x + 179.999584) / 5.0).ceil
326
+ filey = 24 - ((y + 60.0004168) / 5.0).floor
327
+ fn_base = "#{SRTM_PATH}srtm_%.2d_%.2d" % [filex, filey]
328
+ when :ned
329
+ return nil if x > 0 || y < 0 #we only have a certain range in the NED
330
+ x_offset = (x+0.000555555).abs.ceil
331
+ y_offset = (y+0.000555555).abs.ceil
332
+ fn_base = NED_PATH + "dem#{x_offset}#{y_offset}"
333
+ #key_x = (x.to_i - 1).abs.to_s #file name doesn't have a negative
334
+ #key_y = (y.to_i + 1).to_s
335
+ #the file format is dem[lng.floor.abs][lat.floor].hdr
336
+ #fn_base = NED_PATH + "dem#{key_x + key_y}"
337
+ else
338
+ return nil
339
+ end
340
+ return fn_base + '.hdr', fn_base
341
+ end
342
+ end
@@ -0,0 +1,3 @@
1
+ module Humps
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: humps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Cullen King
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Fast and simple lookups for elevation data in GridFloat DEM files.
15
+ email:
16
+ - cullen@ridewithgps.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/humps.rb
22
+ - lib/humps/version.rb
23
+ homepage: http://cullenking.com
24
+ licenses: []
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project: humps
43
+ rubygems_version: 1.8.10
44
+ signing_key:
45
+ specification_version: 3
46
+ summary: Library for extracting elevations from GridFloat DEMs
47
+ test_files: []