Class: GDAL::RasterBandClassifier

Inherits:
Object
  • Object
show all
Includes:
Logger
Defined in:
lib/gdal/extensions/raster_band_classifier.rb

Overview

Takes a list of Ranges of color values and remaps them. Note that these values are directly written to the raster band, overwriting all existing values.

Examples:

classifier = GDAL::RasterBandClassifier.new(raster_band)
ranges = [
  { range: 0...20, map_to: 1 },
  { range: 20...50, map_to: 2 },
  { range: 50...250, map_to: 3 }
]
classifier.add_ranges(ranges)
classifier.classify!(ranges)

Constant Summary collapse

MIN_GAP_PERCENTAGE =
0.005

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raster_band) ⇒ RasterBandClassifier

Returns a new instance of RasterBandClassifier.

Parameters:



30
31
32
33
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 30

def initialize(raster_band)
  @raster_band = raster_band
  @ranges = []
end

Instance Attribute Details

#rangesObject (readonly)

Returns the value of attribute ranges.



27
28
29
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 27

def ranges
  @ranges
end

Instance Method Details

#add_range(range, map_to_value) ⇒ Object

Parameters:

  • range (Range)

    The range of values to map to a new value.

  • map_to_value (Number)


37
38
39
40
41
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 37

def add_range(range, map_to_value)
  raise "range must be a Ruby Range" unless range.is_a? Range

  @ranges << { range: range, map_to: map_to_value }
end

#add_ranges(range_array) ⇒ Object

Parameters:

  • range_array (Array<Hash{range => Range, map_to => Number}>)


44
45
46
47
48
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 44

def add_ranges(range_array)
  range_array.each do |range|
    add_range range[:range], range[:map_to]
  end
end

#classify!Object

Uses the ranges that have been added to remap ranges to map_to values. Note that this will overwrite the associated RasterBand with these values, so if you don’t want to overwrite the Dataset you’re working with, you should copy it first.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 94

def classify!
  band_pixels = @raster_band.to_nna
  new_band_pixels = band_pixels.clone
  data_pixels = if nodata_value
                  nodata_is_nan? ? ~band_pixels.isnan : band_pixels.ne(nodata_value)
                else
                  Numo::Bit.cast(band_pixels.new_ones)
                end

  @ranges.each do |r|
    new_band_pixels[data_pixels & band_pixels.le(r[:range].max) & band_pixels.ge(r[:range].min)] = r[:map_to]
  end

  mask_nan(new_band_pixels, data_pixels) if nodata_is_nan?
  @raster_band.write_xy_narray(new_band_pixels)
end

#equal_count_ranges(range_count) ⇒ Array<Hash>?

Uses the max value of the associated RasterBand and range_count to calculate evenly-weighted ranges. If there are remainder values at the max end of the values, those get lumped in with the last range.

Parameters:

  • range_count (Integer)

    The number of ranges to create.

Returns:

  • (Array<Hash>, nil)


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 56

def equal_count_ranges(range_count)
  pixels = @raster_band.to_nna
  masked_pixels = masked_pixels(pixels)

  return [] if masked_pixels.empty?

  sorted_and_masked_pixels = masked_pixels.to_a.sort
  range_size = (sorted_and_masked_pixels.size / range_count).to_i

  log "Masked pixel count/total pixel count: #{sorted_and_masked_pixels.size}/#{pixels.size}"
  log "Min pixel value: #{sorted_and_masked_pixels.min}"
  log "Max pixel value: #{sorted_and_masked_pixels.max}"
  log "Range size: #{range_size}"

  break_values = [*Array.new(range_count) { |i| sorted_and_masked_pixels[range_size * i] }.uniq,
                  sorted_and_masked_pixels.max]
  ensure_min_gap(break_values)
  log "Break values: #{break_values}"

  return if range_count != 1 && break_values.uniq.size - 1 != range_count

  breakpoint_calculator = lambda do |range_number|
    min = break_values[range_number]
    max = break_values[range_number + 1]

    range_for_type(min, max)
  end

  Array.new(range_count) do |i|
    range = breakpoint_calculator.call(i)
    { range: range, map_to: (i + 1).to_data_type(@raster_band.data_type) }
  end
end

#nodata_is_nan?Boolean

Returns True if NODATA is NaN.

Returns:

  • (Boolean)

    True if NODATA is NaN.



117
118
119
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 117

def nodata_is_nan?
  nodata_value.is_a?(Float) && nodata_value.nan?
end

#nodata_valueNumeric

Returns NODATA value for the @raster_band.

Returns:

  • (Numeric)

    NODATA value for the @raster_band.



112
113
114
# File 'lib/gdal/extensions/raster_band_classifier.rb', line 112

def nodata_value
  @raster_band.no_data_value[:value]
end