Commit 851dcb28 authored by hellekin's avatar hellekin

Serialize Positions

parent 803d354f
......@@ -11,7 +11,7 @@
#
# - #geometry stores its longitude and latitude
# - #geo_type SHOULD be 'Point'
# - #bbox and #radius may or may not be indicated.
# - #box and #radius may or may not be indicated.
# - the record may or may not have information at #longitude, #latitude, or
# #elevation, and they may or may not match what's in #geometry (normally,
# they would, but it's not guaranteed.)
......@@ -20,7 +20,7 @@
#
# - #geometry stores the coordinates and type
# - #geo_type SHOULD be 'MultiPoint'
# - if any of #bbox, #longitude, #latitude, or #radius are indicated, they
# - if any of #box, #longitude, #latitude, or #radius are indicated, they
# SHOULD refer to the center of the area covered by the record.
#
# However there's no guarantee (yet) that this is the case.
......@@ -45,6 +45,23 @@ class Position < ApplicationRecord
presence: true,
inclusion: { in: GEO_TYPES }
with_options if: :point?, allow_nil: false do
validates :latitude,
inclusion: { within: -90..90 }
validates :longitude,
inclusion: { within: -180..180 }
end
with_options unless: :point?, allow_nil: true do
validates :latitude,
inclusion: { within: -90..90 }
validates :longitude,
inclusion: { within: -180..180 }
end
validates :radius,
allow_nil: true,
numericality: { greater_than: 0 }
def lat
format('%2.7f', (point? ? geometry.y : latitude))
end
......@@ -62,7 +79,7 @@ class Position < ApplicationRecord
end
def elevation=(value = 0)
super(format('%5.2f', value))
super(format('%5.2f', value || 0))
end
def point?
......
......@@ -3,7 +3,6 @@
class SerializablePosition < JSONAPI::Serializable::Resource
type 'positions'
attribute :id
attribute :box
attribute :elevation
attribute :geo_type
......@@ -15,10 +14,9 @@ class SerializablePosition < JSONAPI::Serializable::Resource
attribute :created_at
attribute :updated_at
belongs_to :agent
belongs_to :user
has_many :locations
link :self do
@url_helpers.api_role_url(@object)
@url_helpers.api_position_url(@object)
end
end
Fabricator(:position) do
geometry "MyText"
geo_type "MyString"
latitude "9.99"
longitude "9.99"
box nil
elevation "9.99"
radius 1
box ""
geo_type "Point"
geometry "POINT (4.3510466 50.8378565)"
latitude "50.8378565"
longitude "4.3510466"
radius nil
end
# == Schema Information
......
require 'rails_helper'
RSpec.describe Position, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
let(:pos) { Fabricate(:position) }
describe 'factory' do
it 'exists and is valid' do
expect(pos).to be_valid
end
end
describe 'associations' do
it 'has many :locations' do
expect(pos).to have_many(:locations)
end
context 'through locations' do
it 'has many :agents' do
expect(pos).to have_many(:agents)
end
it 'has many :entities' do
expect(pos).to have_many(:entities)
end
it 'has many :places' do
expect(pos).to have_many(:places)
end
it 'has many :services' do
expect(pos).to have_many(:services)
end
it 'has many :things' do
expect(pos).to have_many(:things)
end
end
end
describe 'validations' do
describe '#box' do
it 'is optional' do
expect(pos.box).to be_nil
end
context 'when present' do
it 'covers maximum height on Earth' do
# Mount Everest (Chimborazo is higher from Earth's center, but
# elevation is from sea level)
pos.elevation = 8848.00
expect(pos).to be_valid
end
end
end
describe '#geo_type' do
it 'is required' do
expect(pos.geo_type).to be_present
end
it 'is a GeoJSON type' do
expect(Position::GEO_TYPES).to include('Point')
expect(Position::GEO_TYPES).to include('MultiPoint')
expect(Position::GEO_TYPES).to include('LineString')
expect(Position::GEO_TYPES).to include('MultiLineString')
expect(Position::GEO_TYPES).to include('Polygon')
expect(Position::GEO_TYPES).to include('MultiPolygon')
expect(Position::GEO_TYPES).to include(pos.geo_type)
end
end
describe '#geometry' do
it 'is required' do
expect(pos.geometry).to be_present
end
it 'is a GeoJSON feature geometry' do
expect(pos.geometry.class.ancestors).to include(RGeo::Feature::Instance)
end
end
describe '#latitude' do
pending 'is optional'
context 'when a Point' do
it 'is required' do
expect(pos.geo_type).to eql('Point')
expect(pos.latitude).to be_present
end
end
context 'when present' do
it 'is comprised between -90 and +90' do
expect(-90..90).to cover(pos.latitude)
pos.latitude = 90.0000001
expect(pos).not_to be_valid
end
it 'has precision 9, scale 7' do
lat = 12.3456789012345678
pos.geometry = "POINT (#{pos.longitude} #{lat})"
pos.save
expect(pos.latitude).to eql(0.123456789e2)
end
pending 'matches the center of an area'
end
end
describe '#longitude' do
pending 'is optional'
context 'when a Point' do
it 'is required' do
expect(pos.geo_type).to eql('Point')
expect(pos.longitude).to be_present
end
end
context 'when present' do
it 'is comprised between -180 and +180' do
expect(-180..180).to cover(pos.longitude)
pos.longitude = 180.0000001
expect(pos).not_to be_valid
end
it 'has precision 7, scale 10' do
lon = 123.45678901234567
pos.geometry = "POINT (#{lon} #{pos.latitude})"
pos.save
expect(pos.longitude).to eql(0.1234567890e3)
end
pending 'matches the center of an area'
end
end
describe '#radius' do
it 'is optional' do
expect(pos.radius).to be_nil
end
context 'when present' do
before { pos.radius = 500 }
it 'is positive' do
expect(pos).to be_valid
pos.radius = -1
expect(pos).not_to be_valid
end
pending 'describes a circle around a Point'
pending 'is in meters'
end
end
end
end
# == Schema Information
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment