Reputation: 477
RGeo provides built in methods for POINT features, for example getter methods lat()
and lon()
to pull latitude and longitude values from a POINT object. Unfortunately, these don't work as setters. For example:
point = RGeo::Geographic.spherical_factory(:srid => 4326).point(3,5) // => #<RGeo::Geographic::SphericalPointImpl:0x817e521c "POINT (3.0 5.0)">
I can do this:
point.lat // => 5.0
point.lon // => 3.0
But I can't do:
point.lat = 4 // => NoMethodError: undefined method `lat=' for #<RGeo::Geographic::SphericalPointImpl:0x00000104024770>
Any suggestions as to how to implement setter methods? Would you do it in the Model or extend the Feature class?
Upvotes: 7
Views: 5000
Reputation: 146
I ended up doing something like this in my model:
class MyModel < ActiveRecord::Base
attr_accessor :longitude, :latitude
attr_accessible :longitude, :latitude
validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true
validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true
before_save :update_gps_location
def update_gps_location
if longitude.present? || latitude.present?
long = longitude || self.gps_location.longitude
lat = latitude || self.gps_location.latitude
self.gps_location = RGeo::Geographic.spherical_factory(srid: 4326).point(long, lat)
end
end
end
Then you can just update the position like so:
my_model.update_attributes(longitude: -122, latitude: 37)
I didn't load up longitude/latitude in an after_initialize block because in my app we never need to read the data, only write it. You could easily add that though.
Credit to this answer for the validations.
Upvotes: 0
Reputation: 839
I'm the author of RGeo, so you can consider this answer authoritative on that basis.
In short, PLEASE AVOID DOING THIS. RGeo objects intentionally have no setter methods because they are meant to be immutable objects. This is so that they can be cached, used as hash keys, used across threads, etc. Some of the RGeo calculations assume that the value of a feature object will never change, so making changes like this could have unexpected and unpredictable consequences.
If you really want a "changed" value, create a new object. For example:
p1 = my_create_a_point()
p2 = p1.factory.point(p1.lon + 20.0, p2.lat)
Upvotes: 33
Reputation: 477
I have found something that works, although there might be more elegant solutions.
In my Location
model I have added theses methods:
after_initialize :init
def init
self.latlon ||= Location.rgeo_factory_for_column(:latlon).point(0, 0)
end
def latitude
self.latlon.lat
end
def latitude=(value)
lon = self.latlon.lon
self.latlon = Location.rgeo_factory_for_column(:latlon).point(lon, value)
end
def longitude
self.latlon.lon
end
def longitude=(value)
lat = self.latlon.lat
self.latlon = Location.rgeo_factory_for_column(:latlon).point(value, lat)
end
Upvotes: 3