ffi-gdal 0.0.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.
- 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
|