module NSWTopo::Spot
def candidates
def candidates @candidates ||= Dir.mktmppath do |temp_dir| raw_path = temp_dir / "raw.tif" dem_hr_path = temp_dir / "dem.hr.tif" dem_lr_path = temp_dir / "dem.lr.tif" if @smooth.zero? get_dem temp_dir, dem_hr_path else get_dem temp_dir, raw_path blur_dem raw_path, dem_hr_path end low_resolution = 0.5 * @extent OS.gdalwarp "-r", "med", "-tr", low_resolution, low_resolution, dem_hr_path, dem_lr_path mask = pixels_knolls(dem_lr_path).map(&:first).to_set pixels, knolls = pixels_knolls(dem_hr_path) do |col, row| !mask.include? [(col * @mm_per_px / low_resolution).floor, (row * @mm_per_px / low_resolution).floor] end.entries.transpose raise "no elevation data found in map area" unless pixels locations = raster_locations dem_hr_path, pixels elevations = raster_values dem_hr_path, pixels locations.zip(elevations, knolls).map do |coordinates, elevation, knoll| GeoJSON::Point[coordinates, "label" => elevation.round] do |feature| feature.extend Candidate, ordering feature.knoll, feature.elevation, feature.conflicts = knoll, elevation, Set[] end end end end
def get_features
def get_features selected, remaining = [], AVLTree.new spatial_index = RTree.load(candidates, &:bounds) candidates.each.with_index do |candidate, index| log_update "%s: examining candidates: %.1f%%" % [@name, 100.0 * index / candidates.length] spatial_index.search(candidate.bounds, @spacing).each do |other| next if other == candidate next if (candidate.coordinates - other.coordinates).norm > @spacing candidate.conflicts << other end end.each do |candidate| remaining << candidate end while chosen = remaining.first log_update "%s: choosing candidates: %i remaining" % [@name, remaining.count] selected << chosen removals = Set[chosen] | chosen.conflicts removals.each do |candidate| remaining.delete candidate end.map(&:conflicts).inject(&:|).subtract(removals).each do |other| remaining.delete other other.conflicts.subtract removals remaining.insert other end end GeoJSON::Collection.new projection: @map.projection, features: selected end
def margin
def margin { mm: 3 * @smooth } end
def ordering
def ordering @ordering ||= case @prefer when "knolls" then Candidate::PreferKnolls when "saddles" then Candidate::PreferSaddles else Candidate::PreferNeither end end
def pixels_knolls(dem_path, &block)
def pixels_knolls(dem_path, &block) Enumerator.new do |yielder| log_update "%s: calculating aspect map" % @name aspect_path = dem_path.sub_ext ".bil" OS.gdaldem "aspect", dem_path, aspect_path, "-trigonometric" aspect = ESRIHdr.new aspect_path, -9999 offsets = [-1..1, -1..1].map(&:entries).inject(&:product).map do |row, col| row * aspect.ncols + col - 1 end.values_at(0,3,6,7,8,5,2,1,0) aspect.nrows.times do |row| log_update "%s: finding flat areas: %.1f%%" % [@name, 100.0 * (row + 1) / aspect.nrows] aspect.ncols.times do |col| offsets.map!(&:next) next if row < 1 || col < 1 || row >= aspect.nrows - 1 || col >= aspect.ncols - 1 next if block_given? && block.call(col, row) ccw, cw = offsets.each_cons(2).inject([true, true]) do |(ccw, cw), (o1, o2)| break unless ccw || cw a1, a2 = aspect.values.values_at o1, o2 break unless a1 && a2 (a2 - a1) % 360 < 180 ? [ccw, false] : [false, cw] end yielder << [[col, row], true] if ccw yielder << [[col, row], false] if cw end end end end
def raster_locations(path, pixels)
def raster_locations(path, pixels) OS.gdaltransform "-output_xy", path do |stdin| pixels.each { |pixel| stdin.puts "%i %i" % pixel } end.each_line.map do |line| line.chomp.split(?\s).map(&:to_f) end end
def raster_values(path, pixels)
def raster_values(path, pixels) OS.gdallocationinfo "-valonly", path do |stdin| pixels.each { |pixel| stdin.puts "%i %i" % pixel } end.each_line.map do |line| Float(line) rescue nil end end