forked from telus-agcg/ffi-gdal
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added nearblack (GDALNearblack)
- Loading branch information
1 parent
7814cc8
commit 7cf630d
Showing
5 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "nearblack/options" | ||
|
||
module GDAL | ||
module Utils | ||
# Wrapper for nearblack using GDALNearblack C API. | ||
# | ||
# @see https://gdal.org/programs/nearblack.html nearblack utility documentation. | ||
# @see https://gdal.org/api/gdal_utils.html#_CPPv413GDALNearblackPKc12GDALDatasetH12GDALDatasetHPK20GDALNearblackOptionsPi | ||
# GDALNearblack C API. | ||
class Nearblack | ||
# Perform the nearblack (GDALNearblack) operation. | ||
# | ||
# @example Perform nearblack on dataset (for dst_dataset_path). | ||
# src_dataset = GDAL::Dataset.open("source.tif", "r") | ||
# | ||
# dataset = GDAL::Utils::Nearblack.perform(dst_dataset_path: "destination.tif", src_dataset: src_dataset) | ||
# | ||
# # Do something with the dataset. | ||
# puts dataset.raster_x_size | ||
# | ||
# # You must close the dataset when you are done with it. | ||
# dataset.close | ||
# src_dataset.close | ||
# | ||
# @example Perform nearblack on dataset with options (for dst_dataset_path). | ||
# src_dataset = GDAL::Dataset.open("source.tif", "r") | ||
# options = GDAL::Utils::Nearblack::Options.new(options: ["-near", "10"]) | ||
# | ||
# dataset = GDAL::Utils::Nearblack.perform( | ||
# dst_dataset_path: "destination.tif", | ||
# src_dataset: src_dataset, | ||
# options: options | ||
# ) | ||
# | ||
# # Do something with the dataset. | ||
# puts dataset.raster_x_size | ||
# | ||
# # You must close the dataset when you are done with it. | ||
# dataset.close | ||
# src_dataset.close | ||
# | ||
# @example Perform nearblack on dataset (for dst_dataset_path) using block syntax. | ||
# src_dataset = GDAL::Dataset.open("source.tif", "r") | ||
# options = GDAL::Utils::Nearblack::Options.new(options: ["-near", "10"]) | ||
# | ||
# GDAL::Utils::Nearblack.perform( | ||
# dst_dataset_path: "destination.tif", | ||
# src_dataset: src_dataset, | ||
# options: options | ||
# ) do |dataset| | ||
# # Do something with the dataset. | ||
# puts dataset.raster_x_size | ||
# | ||
# # Dataset will be closed automatically. | ||
# end | ||
# src_dataset.close | ||
# | ||
# @example Perform nearblack on dataset (for dst_dataset). | ||
# src_dataset = GDAL::Dataset.open("source.tif", "r") | ||
# dst_dataset = GDAL::Dataset.open("destination.tif", "w") | ||
# | ||
# GDAL::Utils::Nearblack.perform(dst_dataset: dst_dataset, src_dataset: src_dataset) | ||
# | ||
# # You must close the dataset when you are done with it. | ||
# dst_dataset.close | ||
# src_dataset.close | ||
# | ||
# @param dst_dataset_path [String] The path to the destination dataset. | ||
# @param dst_dataset [GDAL::Dataset] The destination dataset. | ||
# @param src_dataset [GDAL::Dataset] The source dataset. | ||
# @param options [GDAL::Utils::Nearblack::Options] Options. | ||
# @yield [GDAL::Dataset] The destination dataset. | ||
# @return [GDAL::Dataset] The destination dataset (only if block is not specified; dataset must be closed). | ||
def self.perform(src_dataset:, dst_dataset: nil, dst_dataset_path: nil, options: Options.new, &block) | ||
if dst_dataset | ||
for_dataset(dst_dataset: dst_dataset, src_dataset: src_dataset, options: options) | ||
else | ||
for_dataset_path(dst_dataset_path: dst_dataset_path, src_dataset: src_dataset, options: options, &block) | ||
end | ||
end | ||
|
||
def self.for_dataset(dst_dataset:, src_dataset:, options: Options.new) | ||
result_dataset_ptr(dst_dataset: dst_dataset, src_dataset: src_dataset, options: options) | ||
|
||
# Return the input dataset as the output dataset (dataset is modified in place). | ||
dst_dataset | ||
end | ||
private_class_method :for_dataset | ||
|
||
def self.for_dataset_path(dst_dataset_path:, src_dataset:, options: Options.new, &block) | ||
dst_dataset_ptr = result_dataset_ptr( | ||
dst_dataset_path: dst_dataset_path, src_dataset: src_dataset, options: options | ||
) | ||
|
||
::GDAL::Dataset.open(dst_dataset_ptr, "w", &block) | ||
end | ||
private_class_method :for_dataset_path | ||
|
||
def self.result_dataset_ptr(src_dataset:, dst_dataset_path: nil, dst_dataset: nil, options: Options.new) | ||
result_code_ptr = ::FFI::MemoryPointer.new(:int) | ||
dst_dataset_ptr = ::FFI::GDAL::Utils.GDALNearblack( | ||
dst_dataset_path, | ||
dst_dataset&.c_pointer, | ||
src_dataset.c_pointer, | ||
options.c_pointer, | ||
result_code_ptr | ||
) | ||
success = result_code_ptr.read_int.zero? | ||
|
||
raise ::GDAL::Error, "GDALNearblack failed." if dst_dataset_ptr.null? || !success | ||
|
||
dst_dataset_ptr | ||
end | ||
private_class_method :result_dataset_ptr | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# frozen_string_literal: true | ||
|
||
module GDAL | ||
module Utils | ||
class Nearblack | ||
# Ruby wrapper for GDALNearblackOptions C API (options for nearblack utility). | ||
# | ||
# @see GDAL::Utils::Nearblack | ||
# @see https://gdal.org/programs/nearblack.html nearblack utility documentation. | ||
class Options | ||
# @private | ||
class AutoPointer < ::FFI::AutoPointer | ||
# @param pointer [FFI::Pointer] | ||
def self.release(pointer) | ||
return unless pointer && !pointer.null? | ||
|
||
::FFI::GDAL::Utils.GDALNearblackOptionsFree(pointer) | ||
end | ||
end | ||
|
||
# @return [AutoPointer] C pointer to the GDALNearblackOptions. | ||
attr_reader :c_pointer | ||
|
||
# @return [Array<String>] The options. | ||
attr_reader :options | ||
|
||
# Create a new instance. | ||
# | ||
# @see https://gdal.org/programs/nearblack.html | ||
# List of available options could be found in nearblack utility documentation. | ||
# | ||
# @example Create a new instance. | ||
# options = GDAL::Utils::Nearblack::Options.new(options: ["-of", "GTiff", "-near", "10"]) | ||
# | ||
# @param options [Array<String>] The options list. | ||
def initialize(options: []) | ||
@options = options | ||
@string_list = ::GDAL::Utils::Helpers::StringList.new(strings: options) | ||
@c_pointer = AutoPointer.new(options_pointer) | ||
end | ||
|
||
private | ||
|
||
attr_reader :string_list | ||
|
||
def options_pointer | ||
::FFI::GDAL::Utils.GDALNearblackOptionsNew(string_list.c_pointer, nil) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# frozen_string_literal: true | ||
|
||
require "spec_helper" | ||
require "gdal" | ||
|
||
RSpec.describe GDAL::Utils::Nearblack::Options do | ||
context "when no options are provided" do | ||
it "returns a new instance of Options" do | ||
subject { described_class.new } | ||
|
||
expect(subject).to be_a(described_class) | ||
expect(subject.c_pointer).to be_a(described_class::AutoPointer) | ||
expect(subject.c_pointer).not_to be_null | ||
end | ||
end | ||
|
||
context "when options are provided" do | ||
subject { described_class.new(options: options) } | ||
|
||
let(:options) { ["-of", "GTiff", "-near", "10"] } | ||
|
||
it "returns a new instance of Options with options" do | ||
expect(subject).to be_a(described_class) | ||
expect(subject.c_pointer).to be_a(described_class::AutoPointer) | ||
expect(subject.c_pointer).not_to be_null | ||
end | ||
end | ||
|
||
context "when incorrect options are provided" do | ||
subject { described_class.new(options: options) } | ||
|
||
let(:options) { ["-unknown123"] } | ||
|
||
it "raises exception" do | ||
expect { subject }.to raise_exception( | ||
GDAL::UnsupportedOperation, "Unknown option name '-unknown123'" | ||
) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# frozen_string_literal: true | ||
|
||
require "spec_helper" | ||
require "gdal" | ||
|
||
RSpec.describe GDAL::Utils::Nearblack do | ||
let(:src_dataset_path) do | ||
path = "../../../../spec/support/images/osgeo/geotiff/GeogToWGS84GeoKey/GeogToWGS84GeoKey5.tif" | ||
File.expand_path(path, __dir__) | ||
end | ||
|
||
let(:src_dataset) { GDAL::Dataset.open(src_dataset_path, "r") } | ||
after { src_dataset.close } | ||
|
||
describe ".perform" do | ||
context "when dst_dataset_path used" do | ||
let(:dst_dataset_path) { "/vsimem/test-#{SecureRandom.uuid}.tif" } | ||
|
||
context "when no options are provided" do | ||
it "returns new dataset" do | ||
new_dataset = described_class.perform(dst_dataset_path: dst_dataset_path, src_dataset: src_dataset) | ||
|
||
expect(new_dataset).to be_a(GDAL::Dataset) | ||
expect(GDAL::Utils::Info.perform(dataset: new_dataset)).not_to include("Block=256x256") | ||
|
||
new_dataset.close | ||
end | ||
|
||
it "returns new dataset in block" do | ||
described_class.perform(dst_dataset_path: dst_dataset_path, src_dataset: src_dataset) do |new_dataset| | ||
expect(new_dataset).to be_a(GDAL::Dataset) | ||
end | ||
end | ||
end | ||
|
||
context "when options are provided" do | ||
it "returns new dataset with options applied" do | ||
options = GDAL::Utils::Nearblack::Options.new(options: ["-co", "TILED=YES", "-near", "10"]) | ||
|
||
new_dataset = described_class.perform( | ||
dst_dataset_path: dst_dataset_path, src_dataset: src_dataset, options: options | ||
) | ||
|
||
expect(new_dataset).to be_a(GDAL::Dataset) | ||
expect(GDAL::Utils::Info.perform(dataset: new_dataset)).to include("Block=256x256") | ||
|
||
new_dataset.close | ||
end | ||
end | ||
|
||
context "when operation fails without GDAL internal exception" do | ||
it "raises exception" do | ||
options = GDAL::Utils::Nearblack::Options.new(options: ["-of", "UnknownFormat123"]) | ||
|
||
expect do | ||
described_class.perform(dst_dataset_path: dst_dataset_path, src_dataset: src_dataset, options: options) | ||
end.to raise_exception( | ||
GDAL::Error, "GDALNearblack failed." | ||
) | ||
end | ||
end | ||
end | ||
|
||
context "when dst_dataset used" do | ||
context "when no options are provided" do | ||
it "returns dst_dataset with changes applied" do | ||
dst_dataset_path = "/vsimem/test-#{SecureRandom.uuid}.tif" | ||
dst_dataset = GDAL::Utils::Translate.perform(dst_dataset_path: dst_dataset_path, src_dataset: src_dataset) | ||
|
||
result_dataset = described_class.perform(dst_dataset: dst_dataset, src_dataset: src_dataset) | ||
expect(result_dataset).to eq(dst_dataset) | ||
|
||
dst_dataset.close | ||
end | ||
end | ||
|
||
context "when operation fails with GDAL internal exception" do | ||
it "raises exception" do | ||
dst_dataset_path = "/vsimem/test-#{SecureRandom.uuid}.tif" | ||
dst_dataset = GDAL::Utils::Translate.perform( | ||
dst_dataset_path: dst_dataset_path, | ||
src_dataset: src_dataset, | ||
options: GDAL::Utils::Translate::Options.new(options: ["-outsize", "50%", "50%"]) | ||
) | ||
|
||
expect do | ||
described_class.perform(dst_dataset: dst_dataset, src_dataset: src_dataset) | ||
end.to raise_exception( | ||
GDAL::Error, "The dimensions of the output dataset don't match the dimensions of the input dataset." | ||
) | ||
|
||
dst_dataset.close | ||
end | ||
end | ||
end | ||
end | ||
end |