activerecord-postgis-adapter icon indicating copy to clipboard operation
activerecord-postgis-adapter copied to clipboard

Z dimension not saved in db unless X or Y changed

Open pcboy opened this issue 2 years ago • 5 comments

I have a 3D point set in my database.

:lnglat => #<RGeo::Geographic::SphericalPointImpl:0x13f38 "POINT (99.8575 9.243055556 0.0)">,

If I try to manually change the Z only.

:lnglat => #<RGeo::Geographic::SphericalPointImpl:0x14104 "POINT (99.8575 9.243055556 317.0473937988281)">,

I can see it correctly in memory.
But then if I save! my object, nothing is being saved (I can't see any SQL query being made, and no exception triggered).

But now if I change the Y or the X, everything gets saved:

lnglat = RGeo::Geographic.spherical_factory(srid: 4326, has_z_coordinate: true).point(99.8575, 8.243055556, 42)
2.7.3 :016 > sensor.save
D, [2022-05-17T13:51:12.809218 #467861] DEBUG -- :   TRANSACTION (178.5ms)  BEGIN
D, [2022-05-17T13:51:12.986207 #467861] DEBUG -- :   DailySensor Update (176.3ms)  UPDATE "sensors" SET "lnglat" = $1, "updated_at" = $2 WHERE "sensors"."id" = $3  [["lnglat", "00a0000001000010e64058f6e147ae147b40207c71c720431f4045000000000000"], ["updated_at", "2022-05-17 04:51:12.629805"], ["id", "7cfef318-6913-4fb6-b762-639990480d93"]]
D, [2022-05-17T13:51:13.164166 #467861] DEBUG -- :   TRANSACTION (176.7ms)  COMMIT
 => true 

In my schema dump I have:

 lnglat public.geography(PointZ,4326) NOT NULL,

In my rgeo initializer I have:

RGeo::ActiveRecord::SpatialFactoryStore.instance.tap do |config|
  config.register(
    RGeo::Geographic.spherical_factory(srid: 4326, uses_lenient_assertions: true,
                                       has_z_coordinate: true),
    geo_type: 'point',
    has_z: true
  )

  # By default, use the GEOS implementation for spatial columns.
  config.default = RGeo::Geographic.spherical_factory(srid: 4326, uses_lenient_assertions: true)
end

I'm on:

  • Rails 6.1.3.1
  • activerecord-postgis-adapter 7.1.1

Did I miss something obvious? The Z coordinate seem to never be saved unless X or Y changed.

EDIT: If I use ActiveModel::Dirty and manually set my lnglat attribute as dirty with lnglat_will_change! after setting the elevation, lnglat always gets saved properly. So it seems setting the Z value is failing the dirtiness check for some reason.

pcboy avatar May 17 '22 05:05 pcboy

Thanks for the detailed write-up. I'll start looking into this soon.

keithdoggett avatar May 18 '22 17:05 keithdoggett

@pcboy I looked into this and the reason is that in GEOS and RGeo implementations of Z and M coordinates, they are essentially just labels and not used in any computation (including equality checks). Ex.

require 'rgeo'

fac = RGeo::Geographic.spherical_factory(has_z_coordinate: true)
p fac.point(1,2,3) == fac.point(1,2,4)
# => true

fac = RGeo::Geos.factory(has_z_coordinate: true)
p fac.point(1,2,3) == fac.point(1,2,4)
# => true

For now, I think the best solution is to use ActiveModel::Dirty and check for z changes explicitly like you mentioned.

@BuonOmo what are your thoughts on this? Is it worthwhile to look into including z and m in equality checks? It technically would be out of spec with GEOS then, but it is pretty confusing IMO.

keithdoggett avatar May 19 '22 16:05 keithdoggett

I think I'd stick with GEOS spec, to avoid having a heavy CAPI codebase and to keep clarity. This is not a strong opinion though

BuonOmo avatar May 23 '22 06:05 BuonOmo

I also agree that it's best to stick with the spec for the core gem. I wonder if there might be a solution for this repo or rgeo-activerecord where we provide an option to perform a more advanced equality check on z and m fields if present.

keithdoggett avatar May 25 '22 12:05 keithdoggett

@keithdoggett this can be sensible indeed if it is needed by AR to trigger a change.

BuonOmo avatar Jun 08 '22 07:06 BuonOmo