humps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/humps.rb +342 -0
- data/lib/humps/version.rb +3 -0
- metadata +47 -0
data/lib/humps.rb
ADDED
@@ -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
|
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: []
|