Commit c91746b1 authored by Jesse Mapel's avatar Jesse Mapel Committed by jlaura
Browse files

Change Points to use XYZ in the DB and automatically update the lat, lon geom (#347)

* Split srid into latitudinal and rectangular

* First pass at point update trigger

* Point trigger now working

* Changed point creation calls to set the apriori and adjusted instead of the geom

* Removed extra srid in triggers

* Removed prints
parent f465818e
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -32,8 +32,7 @@ from autocnet.graph.edge import Edge, NetworkEdge
from autocnet.graph.node import Node, NetworkNode
from autocnet.io import network as io_network
from autocnet.io.db.model import (Images, Keypoints, Matches, Cameras,
                                  Base, Overlay, Edges, Costs,
                                  Points, Measures)
                                  Base, Overlay, Edges, Costs, Measures)
from autocnet.io.db.connection import new_connection, Parent
from autocnet.vis.graph_view import plot_graph, cluster_plot
from autocnet.control import control
+0 −2
Original line number Diff line number Diff line
@@ -502,8 +502,6 @@ class NetworkNode(Node):
        else:
            self.parent = parent

        srid = config['spatial']['srid']

        # Create a session to work in
        session = Session()

+46 −19
Original line number Diff line number Diff line
@@ -24,8 +24,9 @@ from autocnet import engine, Session, config
Base = declarative_base()

# Default to mars if no config is set
spatial = config.get('spatial', {'srid': 949900})
srid = spatial['srid']
spatial = config.get('spatial', {'latitudinal_srid': 949900, 'rectangular_srid': 949980})
latitudinal_srid = spatial['latitudinal_srid']
rectangular_srid = spatial['rectangular_srid']

class BaseMixin(object):
    @classmethod
@@ -115,7 +116,7 @@ class Keypoints(BaseMixin, Base):
    id = Column(Integer, primary_key=True, autoincrement=True)
    image_id = Column(Integer, ForeignKey("images.id", ondelete="CASCADE"))
    convex_hull_image = Column(Geometry('POLYGON'))
    convex_hull_latlon = Column(Geometry('POLYGON', srid=srid))
    convex_hull_latlon = Column(Geometry('POLYGON', srid=latitudinal_srid))
    path = Column(String)
    nkeypoints = Column(Integer)

@@ -158,7 +159,7 @@ class Matches(BaseMixin, Base):
    destination_idx = Column(Integer, nullable=False)
    lat = Column(Float)
    lon = Column(Float)
    _geom = Column("geom", Geometry('POINT', dimension=2, srid=srid, spatial_index=True))
    _geom = Column("geom", Geometry('POINT', dimension=2, srid=latitudinal_srid, spatial_index=True))
    source_x = Column(Float)
    source_y = Column(Float)
    destination_x = Column(Float)
@@ -178,7 +179,7 @@ class Matches(BaseMixin, Base):
    @geom.setter
    def geom(self, geom):
        if geom:  # Supports instances where geom is explicitly set to None.
            self._geom = from_shape(geom, srid=srid)
            self._geom = from_shape(geom, srid=latitudinal_srid)

class Cameras(BaseMixin, Base):
    __tablename__ = 'cameras'
@@ -194,7 +195,7 @@ class Images(BaseMixin, Base):
    path = Column(String)
    serial = Column(String, unique=True)
    active = Column(Boolean, default=True)
    _footprint_latlon = Column("footprint_latlon", Geometry('MultiPolygon', srid=srid, dimension=2, spatial_index=True))
    _footprint_latlon = Column("footprint_latlon", Geometry('MultiPolygon', srid=latitudinal_srid, dimension=2, spatial_index=True))
    footprint_bodyfixed = Column(Geometry('MULTIPOLYGON', dimension=2))
    #footprint_bodyfixed = Column(Geometry('POLYGON',dimension=3))

@@ -229,14 +230,14 @@ class Images(BaseMixin, Base):
        if geom is None:
            self._footprint_latlon = None
        else:
            self._footprint_latlon = from_shape(geom, srid=srid)
            self._footprint_latlon = from_shape(geom, srid=latitudinal_srid)

class Overlay(BaseMixin, Base):
    __tablename__ = 'overlay'
    id = Column(Integer, primary_key=True, autoincrement=True)
    intersections = Column(ARRAY(Integer))
    #geom = Column(Geometry(geometry_type='POLYGON', management=True))  # sqlite
    _geom = Column("geom", Geometry('POLYGON', srid=srid, dimension=2, spatial_index=True))  # postgresql
    _geom = Column("geom", Geometry('POLYGON', srid=latitudinal_srid, dimension=2, spatial_index=True))  # postgresql

    @hybrid_property
    def geom(self):
@@ -246,7 +247,7 @@ class Overlay(BaseMixin, Base):
            return self._geom
    @geom.setter
    def geom(self, geom):
        self._geom = from_shape(geom, srid=srid)
        self._geom = from_shape(geom, srid=latitudinal_srid)

class PointType(enum.IntEnum):
    """
@@ -261,14 +262,10 @@ class Points(BaseMixin, Base):
    id = Column(Integer, primary_key=True, autoincrement=True)
    _pointtype = Column("pointtype", IntEnum(PointType), nullable=False)  # 2, 3, 4 - Could be an enum in the future, map str to int in a decorator
    identifier = Column(String, unique=True)
    _geom = Column("geom", Geometry('POINT', srid=srid, dimension=2, spatial_index=True))
    _geom = Column("geom", Geometry('POINT', srid=latitudinal_srid, dimension=2, spatial_index=True))
    active = Column(Boolean, default=True)
    apriorix = Column(Float)
    aprioriy = Column(Float)
    aprioriz = Column(Float)
    adjustedx = Column(Float)
    adjustedy = Column(Float)
    adjustedz = Column(Float)
    _apriori = Column("apriori", Geometry('POINTZ', srid=rectangular_srid, dimension=3, spatial_index=False))
    _adjusted = Column("adjusted", Geometry('POINTZ', srid=rectangular_srid, dimension=3, spatial_index=False))
    measures = relationship('Measures')
    rms = Column(Float)

@@ -281,8 +278,36 @@ class Points(BaseMixin, Base):

    @geom.setter
    def geom(self, geom):
        if geom:
            self._geom = from_shape(geom, srid=srid)
        raise TypeError("The geom column for Points cannot be set." \
                        " Set the adjusted column to update the geom.")

    @hybrid_property
    def apriori(self):
        try:
            return to_shape(self._apriori)
        except:
            return self._apriori

    @apriori.setter
    def apriori(self, apriori):
        if apriori:
            self._apriori = from_shape(apriori, srid=rectangular_srid)
        else:
            self._apriori = apriori

    @hybrid_property
    def adjusted(self):
        try:
            return to_shape(self._adjusted)
        except:
            return self._adjusted

    @adjusted.setter
    def adjusted(self, adjusted):
        if adjusted:
            self._adjusted = from_shape(adjusted, srid=rectangular_srid)
        else:
            self._adjusted = adjusted

    @hybrid_property
    def pointtype(self):
@@ -333,7 +358,7 @@ class Measures(BaseMixin, Base):
        self._measuretype = v

if Session:
    from autocnet.io.db.triggers import valid_point_function, valid_point_trigger, valid_geom_function, valid_geom_trigger
    from autocnet.io.db.triggers import valid_point_function, valid_point_trigger, update_point_function, update_point_trigger, valid_geom_function, valid_geom_trigger
    # Create the database
    if not database_exists(engine.url):
        create_database(engine.url, template='template_postgis')  # This is a hardcode to the local template
@@ -342,6 +367,8 @@ if Session:
        # based on the point count.
        event.listen(Base.metadata, 'before_create', valid_point_function)
        event.listen(Measures.__table__, 'after_create', valid_point_trigger)
        event.listen(Base.metadata, 'before_create', update_point_function)
        event.listen(Images.__table__, 'after_create', update_point_trigger)
        event.listen(Base.metadata, 'before_create', valid_geom_function)
        event.listen(Images.__table__, 'after_create', valid_geom_trigger)

+26 −2
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ def session(tables, request):
    def cleanup():
        session.rollback()  # Necessary because some tests intentionally fail
        for t in reversed(tables):
            # Skip the srid table
            if t != 'spatial_ref_sys':
                session.execute(f'TRUNCATE TABLE {t} CASCADE')
            # Reset the autoincrementing
            if t in ['Images', 'Cameras', 'Matches', 'Measures']:
@@ -114,8 +116,9 @@ def test_points_exists(tables):
@pytest.mark.parametrize("data", [
    {'id':1, 'pointtype':2},
    {'pointtype':2, 'identifier':'123abc'},
    {'pointtype':3, 'geom':Point(0,0)},
    {'pointtype':2, 'geom':Point(1,1), 'active':True},
    {'pointtype':3, 'apriori':Point(0,0,0)},
    {'pointtype':3, 'adjusted':Point(0,0,0)},
    {'pointtype':2, 'adjusted':Point(1,1,1), 'active':True},
    {'pointtype':2, 'rms':0.001}
])
def test_create_point(session, data):
@@ -123,6 +126,27 @@ def test_create_point(session, data):
    resp = session.query(model.Points).filter(model.Points.id == p.id).first()
    assert p == resp

@pytest.mark.parametrize("data, expected", [
    ({'pointtype':3, 'adjusted':Point(0,-1,0)}, Point(-90, 0)),
    ({'pointtype':3}, None)
])
def test_create_point_geom(session, data, expected):
    p = model.Points.create(session, **data)
    resp = session.query(model.Points).filter(model.Points.id == p.id).first()
    assert resp.geom == expected

@pytest.mark.parametrize("data, new_adjusted, expected", [
    ({'pointtype':3, 'adjusted':Point(0,-1,0)}, None, None),
    ({'pointtype':3, 'adjusted':Point(0,-1,0)}, Point(0,1,0), Point(90, 0)),
    ({'pointtype':3}, Point(0,-1,0), Point(-90, 0))
])
def test_update_point_geom(session, data, new_adjusted, expected):
    p = model.Points.create(session, **data)
    p.adjusted = new_adjusted
    session.commit()
    resp = session.query(model.Points).filter(model.Points.id == p.id).first()
    assert resp.geom == expected

def test_measures_exists(tables):
    assert model.Measures.__tablename__ in tables

+29 −0
Original line number Diff line number Diff line
from sqlalchemy.schema import DDL

from autocnet import config

valid_geom_function = DDL("""
CREATE OR REPLACE FUNCTION validate_geom()
  RETURNS trigger AS
@@ -58,3 +60,30 @@ CREATE TRIGGER active_measure_changes
  FOR EACH ROW
EXECUTE PROCEDURE validate_points();
""")

latitudinal_srid = config['spatial']['latitudinal_srid']

update_point_function = DDL("""
CREATE OR REPLACE FUNCTION update_points()
  RETURNS trigger AS
$BODY$
BEGIN
    NEW.geom = ST_Force_2D(ST_Transform(NEW.adjusted, {}));
    RETURN NEW;
  EXCEPTION WHEN OTHERS THEN
    NEW.geom = Null;
    RETURN NEW;
END;
$BODY$

LANGUAGE plpgsql VOLATILE -- Says the function is implemented in the plpgsql language; VOLATILE says the function has side effects.
COST 100; -- Estimated execution cost of the function.
""".format(latitudinal_srid))

update_point_trigger = DDL("""
CREATE TRIGGER point_inserted
  BEFORE INSERT OR UPDATE
  ON points
  FOR EACH ROW
EXECUTE PROCEDURE update_points();
""")
Loading