Module: NSWTopo::Feature

Includes:
Log, VectorRender
Defined in:
lib/nswtopo/layer/feature.rb

Constant Summary collapse

CREATE =
%w[features]

Constants included from VectorRender

VectorRender::FONT_SCALED_ATTRIBUTES, VectorRender::MARGIN, VectorRender::SVG_ATTRIBUTES

Constants included from Log

Log::FAILURE, Log::NEUTRAL, Log::SUCCESS, Log::UPDATE

Instance Method Summary collapse

Methods included from VectorRender

#categorise, #create, #drawing_features, #features, #filename, #labeling_features, #params_for, #render, #to_s

Methods included from Log

#log_abort, #log_neutral, #log_success, #log_update, #log_warn

Instance Method Details

#get_featuresObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/nswtopo/layer/feature.rb', line 6

def get_features
  (Array === @features ? @features : [@features]).map do |args|
    case args
    when Hash then args.transform_keys(&:to_sym)
    when String then { source: args }
    else raise "#{@source.basename}: invalid or no features specified"
    end
  end.slice_before do |args|
    !args.delete(:fallback)
  end.map do |fallbacks|
    fallbacks.each.with_object({})
  end.map do |fallbacks|
    args, options = *fallbacks.next
    source, error = args.delete(:source), nil
    source = @path if @path
    log_update "%s: %s" % [@name, options.any? ? "failed to retrieve features, trying fallback source" : "retrieving features"]
    raise "#{@source.basename}: no feature source defined" unless source
    source_path = Pathname(source).expand_path(@source.parent)
    options.merge! args
    collection = case
    when ArcGIS::Service === source
      layer = ArcGIS::Service.new(source).layer(**options.slice(:layer, :where), geometry: @map.neatline(**MARGIN).bbox, decode: true)
      layer.features(**options.slice(:per_page)) do |count, total|
        log_update "%s: retrieved %i of %i feature%s" % [@name, count, total, (?s if total > 1)]
      end.reproject_to(@map.neatline.projection)
    when Shapefile::Source === source_path
      layer = Shapefile::Source.new(source_path).layer(**options.slice(:where, :sql, :layer), geometry: @map.neatline(**MARGIN), projection: @map.neatline.projection)
      layer.features
    else
      raise "#{@source.basename}: invalid feature source: #{source}"
    end
    next collection, options
  rescue ArcGIS::Connection::Error => error
    retry
  rescue StopIteration
    raise error
  end.each do |collection, options|
    rotation_attribute, arithmetic = case options[:rotation]
    when /^90 - (\w+)$/ then [$1, true]
    when String then options[:rotation]
    end

    collection.map! do |feature|
      categories = [*options[:category]].flat_map do |category|
        Hash === category ? [*category] : [category]
      end.map do |attribute, substitutions|
        value = feature.fetch(attribute, attribute)
        substitutions ? substitutions.fetch(value, value) : value
      end

      options[:sizes].tap do |mm, max = 9|
        unit = (mm == true ? 5 : mm)
        case feature
        when GeoJSON::LineString, GeoJSON::MultiLineString
          size = (Math::log2(feature.length) - Math::log2(unit)).ceil rescue 0
          categories << size.clamp(0, max)
        when GeoJSON::Polygon, GeoJSON::MultiPolygon
          size = (0.5 * Math::log2(feature.area) - Math::log2(unit)).ceil rescue 0
          categories << size.clamp(0, max)
        end
      end if options[:sizes]

      rotation = case feature
      when GeoJSON::Point, GeoJSON::MultiPoint
        value = begin
          Float feature.fetch(rotation_attribute)
        rescue KeyError, TypeError, ArgumentError
          0.0
        end
        categories << (value.zero? ? "unrotated" : "rotated")
        arithmetic ? 90 - value : value
      end if rotation_attribute

      labels = Array(options[:label]).map do |attribute|
        feature.fetch(attribute, attribute)
      end.map(&:to_s).reject(&:empty?)

      dual = options[:dual].then do |attribute|
        feature.fetch(attribute, attribute) if attribute
      end

      categories = categories.map(&:to_s).reject(&:empty?).map(&method(:categorise))
      properties = Hash[]
      properties["category"] = categories if categories.any?
      properties["label"] = labels if labels.any?
      properties["dual"] = dual if dual
      properties["draw"] = false if options[:draw] == false
      properties["draw"] = false if @name =~ /[-_]labels$/ && !options.key?(:draw)
      properties["rotation"] = rotation if rotation

      feature.with_properties(properties)
    end
  end.map(&:first).inject(&:merge).with_name(@name)
end