ffi-gdal 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +57 -0
- data/ffi-gdal.gemspec +28 -0
- data/lib/ext/cpl_error_symbols.rb +37 -0
- data/lib/ext/to_bool.rb +13 -0
- data/lib/ffi/gdal/cpl_conv.rb +151 -0
- data/lib/ffi/gdal/cpl_error.rb +91 -0
- data/lib/ffi/gdal/cpl_string.rb +113 -0
- data/lib/ffi/gdal/cpl_vsi.rb +119 -0
- data/lib/ffi/gdal/gdal_color_entry.rb +13 -0
- data/lib/ffi/gdal/gdal_gcp.rb +18 -0
- data/lib/ffi/gdal/ogr_api.rb +28 -0
- data/lib/ffi/gdal/ogr_core.rb +199 -0
- data/lib/ffi/gdal/ogr_srs_api.rb +48 -0
- data/lib/ffi/gdal/version.rb +5 -0
- data/lib/ffi/gdal.rb +607 -0
- data/lib/ffi-gdal/color_table.rb +59 -0
- data/lib/ffi-gdal/dataset.rb +347 -0
- data/lib/ffi-gdal/driver.rb +151 -0
- data/lib/ffi-gdal/exceptions.rb +17 -0
- data/lib/ffi-gdal/geo_transform.rb +137 -0
- data/lib/ffi-gdal/major_object.rb +71 -0
- data/lib/ffi-gdal/raster_attribute_table.rb +78 -0
- data/lib/ffi-gdal/raster_band.rb +571 -0
- data/lib/ffi-gdal/version_info.rb +48 -0
- data/lib/ffi-gdal.rb +12 -0
- data/linkies.rb +35 -0
- data/meow.rb +144 -0
- data/readie.rb +90 -0
- data/rubby.rb +224 -0
- data/spec/ext/cpl_error_symbols_spec.rb +79 -0
- data/spec/ffi-gdal/integration/color_table_info_spec.rb +60 -0
- data/spec/ffi-gdal/integration/dataset_info_spec.rb +95 -0
- data/spec/ffi-gdal/integration/driver_info_spec.rb +60 -0
- data/spec/ffi-gdal/integration/geo_transform_info_spec.rb +66 -0
- data/spec/ffi-gdal/integration/raster_attribute_table_info_spec.rb +23 -0
- data/spec/ffi-gdal/integration/raster_band_info_spec.rb +333 -0
- data/spec/ffi-gdal/unit/version_info_spec.rb +48 -0
- data/spec/ffi-gdal_spec.rb +6 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/integration_help.rb +1 -0
- data/spec/support/shared_examples/major_object_examples.rb +68 -0
- data/things.rb +84 -0
- metadata +216 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
require_relative '../ffi/gdal'
|
2
|
+
require_relative '../ffi-gdal'
|
3
|
+
require_relative 'driver'
|
4
|
+
require_relative 'geo_transform'
|
5
|
+
require_relative 'raster_band'
|
6
|
+
require_relative 'exceptions'
|
7
|
+
require_relative 'major_object'
|
8
|
+
|
9
|
+
|
10
|
+
module GDAL
|
11
|
+
|
12
|
+
# A set of associated raster bands and info common to them all. It's also
|
13
|
+
# responsible for the georeferencing transform and coordinate system
|
14
|
+
# definition of all bands.
|
15
|
+
class Dataset
|
16
|
+
include FFI::GDAL
|
17
|
+
include MajorObject
|
18
|
+
|
19
|
+
ACCESS_FLAGS = {
|
20
|
+
'r' => :GA_ReadOnly,
|
21
|
+
'w' => :GA_Update
|
22
|
+
}
|
23
|
+
|
24
|
+
# @param path [String] Path to the file that contains the dataset.
|
25
|
+
# @param access_flag [String] 'r' or 'w'.
|
26
|
+
def self.open(path, access_flag)
|
27
|
+
file_path = ::File.expand_path(path)
|
28
|
+
pointer = FFI::GDAL.GDALOpen(file_path, ACCESS_FLAGS[access_flag])
|
29
|
+
raise OpenFailure.new(file_path) if pointer.null?
|
30
|
+
|
31
|
+
new(pointer)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Computes NDVI from the red and near-infrared bands in the dataset. Raises
|
35
|
+
# a GDAL::RequiredBandNotFound if one of those band types isn't found.
|
36
|
+
#
|
37
|
+
# @param source [String] Path to the dataset that contains the red and NIR
|
38
|
+
# bands.
|
39
|
+
# @param destination [String] Path to output the new dataset to.
|
40
|
+
# @param driver_name [String] The type of dataset to create.
|
41
|
+
def self.extract_ndvi(source, destination, driver_name: 'GTiff')
|
42
|
+
extract_8bit(source, destination, driver_name) do |original, ndvi_dataset|
|
43
|
+
red = original.red_band
|
44
|
+
nir = original.undefined_band
|
45
|
+
|
46
|
+
if red.nil?
|
47
|
+
fail RequiredBandNotFound, 'Red band not found.'
|
48
|
+
elsif nir.nil?
|
49
|
+
fail RequiredBandNotFound, 'Near-infrared'
|
50
|
+
end
|
51
|
+
|
52
|
+
the_array = original.calculate_ndvi(red.to_a, nir.to_a)
|
53
|
+
|
54
|
+
ndvi_band = ndvi_dataset.raster_band(1)
|
55
|
+
ndvi_band.write_array(the_array)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.extract_gndvi(source, destination, driver_name: 'GTiff')
|
60
|
+
extract_8bit(source, destination, driver_name) do |original, gndvi_dataset|
|
61
|
+
green = original.green_band
|
62
|
+
nir = original.undefined_band
|
63
|
+
|
64
|
+
if green.nil?
|
65
|
+
fail RequiredBandNotFound, 'Green band not found.'
|
66
|
+
elsif nir.nil?
|
67
|
+
fail RequiredBandNotFound, 'Near-infrared'
|
68
|
+
end
|
69
|
+
|
70
|
+
the_array = original.calculate_ndvi(green.to_a, nir.to_a)
|
71
|
+
|
72
|
+
gndvi_band = gndvi_dataset.raster_band(1)
|
73
|
+
gndvi_band.write_array(the_array)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.extract_nir(source, destination, driver_name: 'GTiff')
|
78
|
+
extract_8bit(source, destination, driver_name) do |original, nir_dataset|
|
79
|
+
nir = original.undefined_band
|
80
|
+
fail RequiredBandNotFound, 'Near-infrared' if nir.nil?
|
81
|
+
|
82
|
+
nir_band = nir_dataset.raster_band(1)
|
83
|
+
nir_band.write_array(nir.to_a)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.extract_natural_color(source, destination, driver_name: 'GTiff')
|
88
|
+
original_dataset = open(source, 'r')
|
89
|
+
geo_transform = original_dataset.geo_transform
|
90
|
+
projection = original_dataset.projection
|
91
|
+
rows = original_dataset.raster_y_size
|
92
|
+
columns = original_dataset.raster_x_size
|
93
|
+
|
94
|
+
driver = GDAL::Driver.by_name(driver_name)
|
95
|
+
driver.create_dataset(destination, columns, rows, bands: 3) do |new_dataset|
|
96
|
+
new_dataset.geo_transform = geo_transform
|
97
|
+
new_dataset.projection = projection
|
98
|
+
original_red_band = original_dataset.red_band
|
99
|
+
original_green_band = original_dataset.green_band
|
100
|
+
original_blue_band = original_dataset.blue_band
|
101
|
+
|
102
|
+
new_red_band = new_dataset.raster_band(1)
|
103
|
+
new_red_band.write_array(original_red_band.to_a)
|
104
|
+
|
105
|
+
new_green_band = new_dataset.raster_band(2)
|
106
|
+
new_green_band.write_array(original_green_band.to_a)
|
107
|
+
|
108
|
+
new_blue_band = new_dataset.raster_band(3)
|
109
|
+
new_blue_band.write_array(original_blue_band.to_a)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.extract_8bit(source, destination, driver_name)
|
114
|
+
dataset = open(source, 'r')
|
115
|
+
geo_transform = dataset.geo_transform
|
116
|
+
projection = dataset.projection
|
117
|
+
rows = dataset.raster_y_size
|
118
|
+
columns = dataset.raster_x_size
|
119
|
+
|
120
|
+
driver = GDAL::Driver.by_name(driver_name)
|
121
|
+
driver.create_dataset(destination, columns, rows) do |new_dataset|
|
122
|
+
new_dataset.geo_transform = geo_transform
|
123
|
+
new_dataset.projection = projection
|
124
|
+
|
125
|
+
yield dataset, new_dataset
|
126
|
+
end
|
127
|
+
end
|
128
|
+
private_class_method :extract_8bit
|
129
|
+
|
130
|
+
# @param dataset_pointer [FFI::Pointer] Pointer to the dataset in memory.
|
131
|
+
def initialize(dataset_pointer)
|
132
|
+
@gdal_dataset = dataset_pointer
|
133
|
+
@last_known_file_list = []
|
134
|
+
@open = true
|
135
|
+
close_me = -> { self.close }
|
136
|
+
ObjectSpace.define_finalizer self, close_me
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [FFI::Pointer] Pointer to the GDALDatasetH that's represented by
|
140
|
+
# this Ruby object.
|
141
|
+
def c_pointer
|
142
|
+
@gdal_dataset
|
143
|
+
end
|
144
|
+
|
145
|
+
# Close the dataset.
|
146
|
+
def close
|
147
|
+
@last_known_file_list = file_list
|
148
|
+
GDALClose(@gdal_dataset)
|
149
|
+
@open = false
|
150
|
+
end
|
151
|
+
|
152
|
+
# Tries to reopen the dataset using the first item from #file_list before
|
153
|
+
# the dataset was closed.
|
154
|
+
#
|
155
|
+
# @param access_flag [String]
|
156
|
+
# @return [Boolean]
|
157
|
+
def reopen(access_flag)
|
158
|
+
@gdal_dataset = GDALOpen(@last_known_file_list.first, access_flag)
|
159
|
+
|
160
|
+
@open = true unless @gdal_dataset.null?
|
161
|
+
end
|
162
|
+
|
163
|
+
# @return [Boolean]
|
164
|
+
def open?
|
165
|
+
@open
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [GDAL::Driver] The driver to be used for working with this
|
169
|
+
# dataset.
|
170
|
+
def driver
|
171
|
+
return @driver if @driver
|
172
|
+
|
173
|
+
@driver = if @gdal_dataset && !null?
|
174
|
+
Driver.new(dataset: @gdal_dataset)
|
175
|
+
else
|
176
|
+
Driver.new
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Fetches all files that form the dataset.
|
181
|
+
# @return [Array<String>]
|
182
|
+
def file_list
|
183
|
+
list_pointer = GDALGetFileList(c_pointer)
|
184
|
+
file_list = list_pointer.get_array_of_string(0)
|
185
|
+
CSLDestroy(list_pointer)
|
186
|
+
|
187
|
+
file_list
|
188
|
+
end
|
189
|
+
|
190
|
+
# @return [Fixnum]
|
191
|
+
def raster_x_size
|
192
|
+
return nil if null?
|
193
|
+
|
194
|
+
GDALGetRasterXSize(@gdal_dataset)
|
195
|
+
end
|
196
|
+
|
197
|
+
# @return [Fixnum]
|
198
|
+
def raster_y_size
|
199
|
+
return nil if null?
|
200
|
+
|
201
|
+
GDALGetRasterYSize(@gdal_dataset)
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [Fixnum]
|
205
|
+
def raster_count
|
206
|
+
return 0 if null?
|
207
|
+
|
208
|
+
GDALGetRasterCount(@gdal_dataset)
|
209
|
+
end
|
210
|
+
|
211
|
+
# @param raster_index [Fixnum]
|
212
|
+
# @return [GDAL::RasterBand]
|
213
|
+
def raster_band(raster_index)
|
214
|
+
@raster_bands ||= Array.new(raster_count)
|
215
|
+
|
216
|
+
if @raster_bands[raster_index] && !@raster_bands[raster_index].null?
|
217
|
+
return @raster_bands[raster_index]
|
218
|
+
end
|
219
|
+
|
220
|
+
@raster_bands[raster_index] =
|
221
|
+
GDAL::RasterBand.new(@gdal_dataset, band_id: raster_index)
|
222
|
+
end
|
223
|
+
|
224
|
+
# @return [String]
|
225
|
+
def projection
|
226
|
+
return '' if null?
|
227
|
+
|
228
|
+
GDALGetProjectionRef(@gdal_dataset)
|
229
|
+
end
|
230
|
+
|
231
|
+
# @param new_projection [String]
|
232
|
+
# @return [Boolean]
|
233
|
+
def projection=(new_projection)
|
234
|
+
cpl_err = GDALSetProjection(@gdal_dataset, new_projection)
|
235
|
+
|
236
|
+
cpl_err.to_bool
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [Symbol]
|
240
|
+
def access_flag
|
241
|
+
return nil if null?
|
242
|
+
|
243
|
+
flag = GDALGetAccess(@gdal_dataset)
|
244
|
+
|
245
|
+
GDALAccess[flag]
|
246
|
+
end
|
247
|
+
|
248
|
+
# @return [GDAL::GeoTransform]
|
249
|
+
def geo_transform
|
250
|
+
@geo_transform ||= GeoTransform.new(@gdal_dataset)
|
251
|
+
end
|
252
|
+
|
253
|
+
# @param new_transform [GDAL::GeoTransform]
|
254
|
+
# @return [GDAL::GeoTransform]
|
255
|
+
def geo_transform=(new_transform)
|
256
|
+
new_pointer = new_transform.c_pointer.dup
|
257
|
+
cpl_err = GDALSetGeoTransform(@gdal_dataset, new_pointer)
|
258
|
+
cpl_err.to_bool
|
259
|
+
|
260
|
+
@geo_transform = GeoTransform.new(@gdal_dataset, geo_transform_pointer: new_pointer)
|
261
|
+
end
|
262
|
+
|
263
|
+
# @return [Fixnum]
|
264
|
+
def gcp_count
|
265
|
+
return 0 if null?
|
266
|
+
|
267
|
+
GDALGetGCPCount(@gdal_dataset)
|
268
|
+
end
|
269
|
+
|
270
|
+
# @return [String]
|
271
|
+
def gcp_projection
|
272
|
+
return '' if null?
|
273
|
+
|
274
|
+
GDALGetGCPProjection(@gdal_dataset)
|
275
|
+
end
|
276
|
+
|
277
|
+
# @return [FFI::GDAL::GDALGCP]
|
278
|
+
def gcps
|
279
|
+
return GDALGCP.new if null?
|
280
|
+
|
281
|
+
gcp_array_pointer = GDALGetGCPs(@gdal_dataset)
|
282
|
+
|
283
|
+
if gcp_array_pointer.null?
|
284
|
+
GDALGCP.new
|
285
|
+
else
|
286
|
+
GDALGCP.new(gcp_array_pointer)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Iterates raster bands from 1 to #raster_count and yields them to the given
|
291
|
+
# block.
|
292
|
+
def each_band
|
293
|
+
1.upto(raster_count) do |i|
|
294
|
+
yield(raster_band(i))
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Returns the first raster band for which the block returns true. Ex.
|
299
|
+
#
|
300
|
+
# dataset.find_band do |band|
|
301
|
+
# band.color_interpretation == :GCI_RedBand
|
302
|
+
# end
|
303
|
+
#
|
304
|
+
# @return [GDAL::RasterBand]
|
305
|
+
def find_band
|
306
|
+
each_band do |band|
|
307
|
+
result = yield(band)
|
308
|
+
return band if result
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# @param red_band_array [NArray]
|
313
|
+
# @param nir_band_array [NArray]
|
314
|
+
# @return [NArray]
|
315
|
+
def calculate_ndvi(red_band_array, nir_band_array)
|
316
|
+
(nir_band_array - red_band_array) / (nir_band_array + red_band_array)
|
317
|
+
end
|
318
|
+
|
319
|
+
# @return [GDAL::RasterBand]
|
320
|
+
def red_band
|
321
|
+
find_band do |band|
|
322
|
+
band.color_interpretation == :GCI_RedBand
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# @return [GDAL::RasterBand]
|
327
|
+
def green_band
|
328
|
+
find_band do |band|
|
329
|
+
band.color_interpretation == :GCI_GreenBand
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# @return [GDAL::RasterBand]
|
334
|
+
def blue_band
|
335
|
+
find_band do |band|
|
336
|
+
band.color_interpretation == :GCI_BlueBand
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# @return [GDAL::RasterBand]
|
341
|
+
def undefined_band
|
342
|
+
find_band do |band|
|
343
|
+
band.color_interpretation == :GCI_Undefined
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative '../ffi/gdal'
|
2
|
+
require_relative 'major_object'
|
3
|
+
require 'multi_xml'
|
4
|
+
require 'log_switch'
|
5
|
+
|
6
|
+
|
7
|
+
module GDAL
|
8
|
+
class Driver
|
9
|
+
include FFI::GDAL
|
10
|
+
include MajorObject
|
11
|
+
include LogSwitch::Mixin
|
12
|
+
|
13
|
+
GDAL_DOCS_URL = 'http://gdal.org'
|
14
|
+
|
15
|
+
# @return [Fixnum]
|
16
|
+
def self.driver_count
|
17
|
+
FFI::GDAL.GDALGetDriverCount
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [GDAL::Driver]
|
21
|
+
def self.by_name(name)
|
22
|
+
new(name: name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a new GDAL::Driver object based on the mutually exclusive given
|
26
|
+
# parameters. Pass in only one of the allowed parameters.
|
27
|
+
#
|
28
|
+
# @param file_path [String] File to get the driver for.
|
29
|
+
# @param name [String] Name of the registered GDALDriver.
|
30
|
+
# @param index [Fixnum] Index of the registered driver. Must be less than
|
31
|
+
# GDAL::Driver.driver_count.
|
32
|
+
# @param dataset [FFI::Pointer] Pointer to the GDALDataset.
|
33
|
+
def initialize(file_path: file_path, name: name, index: index, dataset: dataset)
|
34
|
+
@gdal_driver_handle = if file_path
|
35
|
+
GDALIdentifyDriver(::File.expand_path(file_path), nil)
|
36
|
+
elsif name
|
37
|
+
GDALGetDriverByName(name)
|
38
|
+
elsif index
|
39
|
+
count = self.class.driver_count
|
40
|
+
raise "index must be between 0 and #{count - 1}." if index > count
|
41
|
+
|
42
|
+
GDALGetDriver(index)
|
43
|
+
elsif dataset
|
44
|
+
GDALGetDatasetDriver(dataset)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def c_pointer
|
49
|
+
@gdal_driver_handle
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def short_name
|
54
|
+
return '' unless @gdal_driver_handle
|
55
|
+
|
56
|
+
GDALGetDriverShortName(@gdal_driver_handle)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String]
|
60
|
+
def long_name
|
61
|
+
return '' unless @gdal_driver_handle
|
62
|
+
|
63
|
+
GDALGetDriverLongName(@gdal_driver_handle)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
def help_topic
|
68
|
+
return '' unless @gdal_driver_handle
|
69
|
+
|
70
|
+
"#{GDAL_DOCS_URL}/#{GDALGetDriverHelpTopic(@gdal_driver_handle)}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Lists and describes the options that can be used when calling
|
74
|
+
# GDAL::Dataset.create or GDAL::Dataset.create_copy.
|
75
|
+
#
|
76
|
+
# @return [Array]
|
77
|
+
def creation_option_list
|
78
|
+
return [] unless @gdal_driver_handle
|
79
|
+
|
80
|
+
creation_option_list_xml = GDALGetDriverCreationOptionList(@gdal_driver_handle)
|
81
|
+
MultiXml.parse(creation_option_list_xml)['CreationOptionList']['Option']
|
82
|
+
end
|
83
|
+
|
84
|
+
# Copy all of the associated files of a dataset from one file to another.
|
85
|
+
#
|
86
|
+
# @param new_name [String]
|
87
|
+
# @param old_name [String]
|
88
|
+
# @return true on success, false on warning.
|
89
|
+
# @raise [GDAL::CPLErrFailure] If failures.
|
90
|
+
def copy_dataset_files(new_name, old_name)
|
91
|
+
cpl_err = GDALCopyDatasetFiles(@gdal_driver_handle, new_name, old_name)
|
92
|
+
|
93
|
+
cpl_err.to_bool
|
94
|
+
end
|
95
|
+
|
96
|
+
# Create a new Dataset with this driver. Legal arguments depend on the
|
97
|
+
# driver and can't be retrieved programmatically.
|
98
|
+
#
|
99
|
+
# @param filename [String]
|
100
|
+
# @param x_size [Fixnum] Width of created raster in pixels.
|
101
|
+
# @param y_size [Fixnum] Height of created raster in pixels.
|
102
|
+
# @param bands [Fixnum]
|
103
|
+
# @param type [FFI::GDAL::GDALDataType]
|
104
|
+
# @return [GDAL::Dataset] Returns the *closed* dataset. You'll need to
|
105
|
+
# reopen it if you with to continue working with it.
|
106
|
+
# @todo Implement options.
|
107
|
+
def create_dataset(filename, x_size, y_size, bands: 1, type: :GDT_Byte, **options)
|
108
|
+
log "creating dataset with size #{x_size},#{y_size}"
|
109
|
+
|
110
|
+
dataset_pointer = GDALCreate(@gdal_driver_handle,
|
111
|
+
filename,
|
112
|
+
x_size,
|
113
|
+
y_size,
|
114
|
+
bands,
|
115
|
+
type,
|
116
|
+
nil
|
117
|
+
)
|
118
|
+
|
119
|
+
raise CreateFail if dataset_pointer.null?
|
120
|
+
|
121
|
+
dataset = Dataset.new(dataset_pointer)
|
122
|
+
yield(dataset) if block_given?
|
123
|
+
dataset.close
|
124
|
+
|
125
|
+
dataset
|
126
|
+
end
|
127
|
+
|
128
|
+
# Delete the dataset represented by +file_name+. Depending on the driver,
|
129
|
+
# this could mean deleting associated files, database objects, etc.
|
130
|
+
#
|
131
|
+
# @param file_name [String]
|
132
|
+
# @return true on success, false on warning.
|
133
|
+
# @raise [GDAL::CPLErrFailure] If failures.
|
134
|
+
def delete_dataset(file_name)
|
135
|
+
cpl_err = GDALDeleteDataset(@gdal_driver_handle, file_name)
|
136
|
+
|
137
|
+
cpl_err.to_bool
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param new_name [String]
|
141
|
+
# @param old_name [String]
|
142
|
+
# @return true on success, false on warning.
|
143
|
+
# @raise [GDAL::CPLErrFailure] If failures.
|
144
|
+
def rename_dataset(new_name, old_name)
|
145
|
+
cpl_err = GDALRenameDataset(@gdal_driver_handle, new_name, old_name)
|
146
|
+
|
147
|
+
|
148
|
+
cpl_err.to_bool
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GDAL
|
2
|
+
class OpenFailure < StandardError
|
3
|
+
def initialize(file, msg=nil)
|
4
|
+
message = msg || "Unabled to open file '#{file}'. Perhaps an unsupported file format?"
|
5
|
+
super(message)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class CPLErrFailure < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class CreateFail < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class RequiredBandNotFound < StandardError
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative '../ffi/gdal'
|
2
|
+
|
3
|
+
|
4
|
+
module GDAL
|
5
|
+
class GeoTransform
|
6
|
+
include FFI::GDAL
|
7
|
+
|
8
|
+
attr_accessor :gdal_dataset
|
9
|
+
|
10
|
+
# @param gdal_dataset [FFI::Pointer]
|
11
|
+
def initialize(dataset, geo_transform_pointer: nil)
|
12
|
+
@gdal_dataset = if dataset.nil?
|
13
|
+
FFI::MemoryPointer.new(:pointer)
|
14
|
+
elsif dataset.is_a? GDAL::Dataset
|
15
|
+
dataset.c_pointer
|
16
|
+
else
|
17
|
+
dataset
|
18
|
+
end
|
19
|
+
|
20
|
+
@gdal_geo_transform = if geo_transform_pointer
|
21
|
+
geo_transform_pointer
|
22
|
+
else
|
23
|
+
container_pointer = FFI::MemoryPointer.new(:double, 6)
|
24
|
+
GDALGetGeoTransform(@gdal_dataset, container_pointer).to_ruby
|
25
|
+
container_pointer
|
26
|
+
end
|
27
|
+
|
28
|
+
to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
def c_pointer
|
32
|
+
@gdal_geo_transform
|
33
|
+
end
|
34
|
+
|
35
|
+
def null?
|
36
|
+
@gdal_geo_transform.null?
|
37
|
+
end
|
38
|
+
|
39
|
+
# All attributes as an Array, in the order:
|
40
|
+
# * x_origin
|
41
|
+
# * pixel_width
|
42
|
+
# * x_rotation
|
43
|
+
# * y_origin
|
44
|
+
# * y_rotation
|
45
|
+
# * pixel_height
|
46
|
+
#
|
47
|
+
# @return [Array]
|
48
|
+
def to_a
|
49
|
+
[
|
50
|
+
x_origin,
|
51
|
+
pixel_width,
|
52
|
+
x_rotation,
|
53
|
+
y_origin,
|
54
|
+
y_rotation,
|
55
|
+
pixel_height
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
# X-coordinate of the center of the upper left pixel.
|
60
|
+
# In wikipedia's World Map definition, this is "C".
|
61
|
+
#
|
62
|
+
# @return [Float]
|
63
|
+
def x_origin
|
64
|
+
return nil if null?
|
65
|
+
|
66
|
+
@gdal_geo_transform[0].read_double
|
67
|
+
end
|
68
|
+
|
69
|
+
# AKA X-pixel size.
|
70
|
+
# In wikipedia's World Map definition, this is "A".
|
71
|
+
#
|
72
|
+
# @return [Float]
|
73
|
+
def pixel_width
|
74
|
+
return nil if null?
|
75
|
+
|
76
|
+
@gdal_geo_transform[1].read_double
|
77
|
+
end
|
78
|
+
|
79
|
+
# Rotation about the x-axis.
|
80
|
+
# In wikipedia's World Map definition, this is "B".
|
81
|
+
#
|
82
|
+
# @return [Float]
|
83
|
+
def x_rotation
|
84
|
+
return nil if null?
|
85
|
+
|
86
|
+
@gdal_geo_transform[2].read_double
|
87
|
+
end
|
88
|
+
|
89
|
+
# Y-coordinate of the center of the upper left pixel.
|
90
|
+
# In wikipedia's World Map definition, this is "F".
|
91
|
+
#
|
92
|
+
# @return [Float]
|
93
|
+
def y_origin
|
94
|
+
return nil if null?
|
95
|
+
|
96
|
+
@gdal_geo_transform[3].read_double
|
97
|
+
end
|
98
|
+
|
99
|
+
# Rotation about the y-axis.
|
100
|
+
# In wikipedia's World Map definition, this is "D".
|
101
|
+
#
|
102
|
+
# @return [Float]
|
103
|
+
def y_rotation
|
104
|
+
return nil if null?
|
105
|
+
|
106
|
+
@gdal_geo_transform[4].read_double
|
107
|
+
end
|
108
|
+
|
109
|
+
# AKA Y-pixel size.
|
110
|
+
# In wikipedia's World Map definition, this is "E".
|
111
|
+
#
|
112
|
+
# @return [Float]
|
113
|
+
def pixel_height
|
114
|
+
return nil if null?
|
115
|
+
|
116
|
+
@gdal_geo_transform[5].read_double
|
117
|
+
end
|
118
|
+
|
119
|
+
# The calculated UTM easting of the pixel on the map.
|
120
|
+
#
|
121
|
+
# @return [Float]
|
122
|
+
def x_projection(x_pixel, y_pixel)
|
123
|
+
return nil if null?
|
124
|
+
|
125
|
+
(pixel_width * x_pixel) + (x_rotation * y_pixel) + x_origin
|
126
|
+
end
|
127
|
+
|
128
|
+
# The calculated UTM northing of the pixel on the map.
|
129
|
+
#
|
130
|
+
# @return [Float]
|
131
|
+
def y_projection(x_pixel, y_pixel)
|
132
|
+
return nil if null?
|
133
|
+
|
134
|
+
(y_rotation * x_pixel) + (pixel_height * y_pixel) + y_origin
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../ffi/gdal'
|
2
|
+
|
3
|
+
|
4
|
+
module GDAL
|
5
|
+
module MajorObject
|
6
|
+
include FFI::GDAL
|
7
|
+
|
8
|
+
# @return [Array<String>]
|
9
|
+
def metadata_domain_list
|
10
|
+
# I don't quite get it, but if #GDALGetMetadataDomainList isn't called
|
11
|
+
# twice, the last domain in the list sometimes doesn't get read.
|
12
|
+
GDALGetMetadataDomainList(c_pointer)
|
13
|
+
list_pointer = GDALGetMetadataDomainList(c_pointer)
|
14
|
+
return [] if list_pointer.null?
|
15
|
+
|
16
|
+
strings = list_pointer.get_array_of_string(0)
|
17
|
+
|
18
|
+
strings.compact.delete_if(&:empty?)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param domain [String] Name of the domain to get metadata for.
|
22
|
+
# @return [Hash]
|
23
|
+
def metadata_for_domain(domain='')
|
24
|
+
m = GDALGetMetadata(c_pointer, domain)
|
25
|
+
return {} if m.null?
|
26
|
+
|
27
|
+
data_array = m.get_array_of_string(0)
|
28
|
+
|
29
|
+
data_array.each_with_object({}) do |key_value_pair, obj|
|
30
|
+
key, value = key_value_pair.split('=', 2)
|
31
|
+
|
32
|
+
begin
|
33
|
+
obj[key] = MultiXml.parse(value)
|
34
|
+
rescue MultiXml::ParseError
|
35
|
+
obj[key] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param name [String]
|
41
|
+
# @param domain [String]
|
42
|
+
# @return [String]
|
43
|
+
def metadata_item(name, domain='')
|
44
|
+
GDALGetMetadataItem(c_pointer, name, domain)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Hash{domain => Array<String>}]
|
48
|
+
def all_metadata
|
49
|
+
sub_metadata = metadata_domain_list.each_with_object({}) do |subdomain, obj|
|
50
|
+
metadata_array = metadata_for_domain(subdomain)
|
51
|
+
obj[subdomain] = metadata_array
|
52
|
+
end
|
53
|
+
|
54
|
+
{ DEFAULT: metadata_for_domain }.merge(sub_metadata)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def description
|
59
|
+
GDALGetDescription(c_pointer)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param new_description [String]
|
63
|
+
def description=(new_description)
|
64
|
+
GDALSetDescription(c_pointer, new_description.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
def null?
|
68
|
+
c_pointer.null?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|