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



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

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



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

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



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

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



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

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



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

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



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

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



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

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(lat, lng, distance_in_meters) ⇒ Object



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

def self.within_distance(lat, lng, distance_in_meters)
  # where("ST_DWithin(features.geog, ST_SetSRID( ST_Point( -71.104, 42.315), 4326)::geography, :distance)", :lat => lat, :lng => lng, :distance => 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



93
94
95
96
97
98
99
# File 'app/models/abstract_feature.rb', line 93

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



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

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

#envelope(buffer_in_meters = 0) ⇒ Object



84
85
86
87
88
89
90
91
# File 'app/models/abstract_feature.rb', line 84

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



153
154
155
# File 'app/models/abstract_feature.rb', line 153

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

#geojson(*args) ⇒ Object



167
168
169
# File 'app/models/abstract_feature.rb', line 167

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

#kml(options = {}) ⇒ Object



161
162
163
164
165
# File 'app/models/abstract_feature.rb', line 161

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)


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

def make_valid?
  @make_valid
end