Class: AbstractFeature

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/abstract_feature.rb

Direct Known Subclasses

AggregateFeature, Feature

Constant Summary collapse

FEATURE_TYPES =
%w(polygon point line)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#make_valid=(value) ⇒ Object

Sets the attribute make_valid

Parameters:

  • value

    the value to set the attribute make_valid to.



12
13
14
# File 'app/models/abstract_feature.rb', line 12

def make_valid=(value)
  @make_valid = value
end

Class Method Details

.area_in_square_meters(geom = 'geom_lowres') ⇒ Object



56
57
58
59
# File 'app/models/abstract_feature.rb', line 56

def self.area_in_square_meters(geom = 'geom_lowres')
  current_scope = all.polygons
  unscoped { SpatialFeatures::Utils.select_db_value(select("ST_Area(ST_Union(#{geom}))").from(current_scope, :features)).to_f }
end

.cache_derivatives(options = {}) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/models/abstract_feature.rb', line 104

def self.cache_derivatives(options = {})
  update_all <<-SQL.squish
    geom         = ST_Transform(geog::geometry, #{detect_srid('geom')}),
    north        = ST_YMax(geog::geometry),
    east         = ST_XMax(geog::geometry),
    south        = ST_YMin(geog::geometry),
    west         = ST_XMin(geog::geometry),
    area         = ST_Area(geog),
    centroid     = ST_PointOnSurface(geog::geometry)
  SQL

  invalid('geom').update_all <<-SQL.squish
    geom         = ST_Buffer(geom, 0)
  SQL

  update_all <<-SQL.squish
    geom_lowres  = ST_SimplifyPreserveTopology(geom, #{options.fetch(:lowres_simplification, lowres_simplification)})
  SQL

  invalid('geom_lowres').update_all <<-SQL.squish
    geom_lowres  = ST_Buffer(geom_lowres, 0)
  SQL
end

.cache_keyObject



28
29
30
# File 'app/models/abstract_feature.rb', line 28

def self.cache_key
  "#{maximum(:id)}-#{count}"
end

.collection_cache_key(_collection, _timestamp_column) ⇒ Object

for Rails >= 5 ActiveRecord collections we override the collection_cache_key to prevent Rails doing its default query on updated_at



24
25
26
# File 'app/models/abstract_feature.rb', line 24

def self.collection_cache_key(_collection, _timestamp_column)
  self.cache_key
end

.geojson(lowres: false, precision: 6, properties: true, srid: 4326, centroids: false) ⇒ Object

default srid is 4326 so output is Google Maps compatible



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/abstract_feature.rb', line 128

def self.geojson(lowres: false, precision: 6, properties: true, srid: 4326, centroids: false) # default srid is 4326 so output is Google Maps compatible
  if centroids
    column = 'centroid'
  elsif lowres
    column = "ST_Transform(geom_lowres, #{srid})"
  else
    column = 'geog'
  end

  properties_sql = <<~SQL if properties
    , 'properties', hstore_to_json(metadata)
  SQL

  sql = <<~SQL
    json_build_object(
      'type', 'FeatureCollection',
      'features', json_agg(
        json_build_object(
          'type', 'Feature',
          'geometry', ST_AsGeoJSON(#{column}, #{precision})::json
          #{properties_sql}
        )
      )
    )
  SQL
  SpatialFeatures::Utils.select_db_value(all.select(sql))
end

.intersecting(other) ⇒ Object



71
72
73
# File 'app/models/abstract_feature.rb', line 71

def self.intersecting(other)
  join_other_features(other).where('ST_Intersects(features.geom_lowres, other_features.geom_lowres)').uniq
end

.invalid(column = 'geog::geometry') ⇒ Object



79
80
81
# File 'app/models/abstract_feature.rb', line 79

def self.invalid(column = 'geog::geometry')
  select("features.*, ST_IsValidReason(#{column}) AS invalid_geometry_message").where.not("ST_IsValid(#{column})")
end

.linesObject



44
45
46
# File 'app/models/abstract_feature.rb', line 44

def self.lines
  where(:feature_type => 'line')
end

.pointsObject



48
49
50
# File 'app/models/abstract_feature.rb', line 48

def self.points
  where(:feature_type => 'point')
end

.polygonsObject



40
41
42
# File 'app/models/abstract_feature.rb', line 40

def self.polygons
  where(:feature_type => 'polygon')
end

.total_intersection_area_in_square_meters(other_features, geom = 'geom_lowres') ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'app/models/abstract_feature.rb', line 61

def self.total_intersection_area_in_square_meters(other_features, geom = 'geom_lowres')
  scope = unscope(:select).select("ST_Union(#{geom}) AS geom").polygons
  other_scope = other_features.polygons

  query = base_class.unscoped.select('ST_Area(ST_Intersection(ST_Union(features.geom), ST_Union(other_features.geom)))')
                  .from(scope, "features")
                  .joins("INNER JOIN (#{other_scope.to_sql}) AS other_features ON ST_Intersects(features.geom, other_features.geom)")
  return SpatialFeatures::Utils.select_db_value(query).to_f
end

.validObject



83
84
85
# File 'app/models/abstract_feature.rb', line 83

def self.valid
  where('ST_IsValid(geog::geometry)')
end

.with_metadata(k, v) ⇒ Object



32
33
34
35
36
37
38
# File 'app/models/abstract_feature.rb', line 32

def self.(k, v)
  if k.present? && v.present?
    where('metadata->? = ?', k, v)
  else
    all
  end
end

.within_distance(other, distance_in_meters) ⇒ Object



75
76
77
# File 'app/models/abstract_feature.rb', line 75

def self.within_distance(other, distance_in_meters)
  join_other_features(other).where('ST_DWithin(features.geom_lowres, other_features.geom_lowres, ?)', distance_in_meters).uniq
end

.within_distance_of_point(lat, lng, distance_in_meters) ⇒ Object



52
53
54
# File 'app/models/abstract_feature.rb', line 52

def self.within_distance_of_point(lat, lng, distance_in_meters)
  where("ST_DWithin(features.geog, ST_Point(:lng, :lat), :distance)", :lat => lat, :lng => lng, :distance => distance_in_meters)
end

.without_caching_derivatives(&block) ⇒ Object



96
97
98
99
100
101
102
# File 'app/models/abstract_feature.rb', line 96

def self.without_caching_derivatives(&block)
  old = automatically_cache_derivatives
  self.automatically_cache_derivatives = false
  block.call
ensure
  self.automatically_cache_derivatives = old
end

Instance Method Details

#cache_derivatives(*args) ⇒ Object



160
161
162
# File 'app/models/abstract_feature.rb', line 160

def cache_derivatives(*args)
  self.class.default_scoped.where(:id => self.id).cache_derivatives(*args)
end

#envelope(buffer_in_meters = 0) ⇒ Object



87
88
89
90
91
92
93
94
# File 'app/models/abstract_feature.rb', line 87

def envelope(buffer_in_meters = 0)
  envelope_json = JSON.parse(self.class.select("ST_AsGeoJSON(ST_Envelope(ST_Buffer(features.geog, #{buffer_in_meters})::geometry)) AS result").where(:id => id).first.result)
  envelope_json = envelope_json["coordinates"].first

  raise "Can't calculate envelope for Feature #{self.id}" if envelope_json.blank?

  return envelope_json.values_at(0,2)
end

#feature_boundsObject



156
157
158
# File 'app/models/abstract_feature.rb', line 156

def feature_bounds
  slice(:north, :east, :south, :west)
end

#geojson(*args) ⇒ Object



170
171
172
# File 'app/models/abstract_feature.rb', line 170

def geojson(*args)
  self.class.where(id: id).geojson(*args)
end

#kml(options = {}) ⇒ Object



164
165
166
167
168
# File 'app/models/abstract_feature.rb', line 164

def kml(options = {})
  geometry = options[:lowres] ? kml_lowres : super()
  geometry = "<MultiGeometry>#{geometry}#{kml_centroid}</MultiGeometry>" if options[:centroid]
  return geometry
end

#make_valid?Boolean

Returns:

  • (Boolean)


174
175
176
# File 'app/models/abstract_feature.rb', line 174

def make_valid?
  @make_valid
end