Exemple #1
0
class TaskGeometry(db.Model):
    """The collection of geometries (1+) belonging to a task"""

    __tablename__ = 'task_geometries'
    id = db.Column(db.Integer, nullable=False, unique=True, primary_key=True)
    osmid = db.Column(db.BigInteger)
    task_id = db.Column(db.Integer,
                        db.ForeignKey('tasks.id',
                                      onupdate="cascade",
                                      ondelete="cascade"),
                        nullable=False)
    geom = db.Column(Geometry, nullable=False)

    def __init__(self, shape, osmid=None):
        self.osmid = osmid
        self.geom = from_shape(shape)

    @hybrid_property
    def geometry(self):
        """Return the task geometry collection as a Shapely object"""

        return to_shape(self.geom)

    @geometry.setter
    def geometry(self, shape):
        """Set the task geometry collection from a Shapely object"""

        self.geom = from_shape(shape)

    geometry = synonym('geom', descriptor=geometry)
Exemple #2
0
class Action(db.Model):
    """An action on a task"""

    __tablename__ = 'actions'

    id = db.Column(db.Integer,
                   unique=True,
                   primary_key=True,
                   nullable=False,
                   autoincrement=True)
    timestamp = db.Column(
        db.DateTime,
        # store the timestamp as naive UTC time
        default=datetime.now(pytz.utc).replace(tzinfo=None),
        nullable=False)
    user_id = db.Column(
        db.Integer,
        db.ForeignKey('users.id', onupdate="cascade", ondelete="cascade"))
    task_id = db.Column(
        db.Integer,
        db.ForeignKey('tasks.id', onupdate="cascade", ondelete="cascade"))
    status = db.Column(db.String(), nullable=False)
    editor = db.Column(db.String())

    __table_args__ = (db.Index('idx_action_timestamp', timestamp),
                      db.Index('idx_action_userid', user_id),
                      db.Index('idx_action_taskid',
                               task_id), db.Index('idx_action_status', status))

    def __repr__(self):
        return "<Action %s set on %s>" % (self.status, self.timestamp)

    def __init__(self, status, user_id=None, editor=None):
        self.status = status
        # store the timestamp as naive UTC time
        self.timestamp = datetime.now(pytz.utc).replace(tzinfo=None)
        if user_id:
            self.user_id = user_id
        if editor:
            self.editor = editor
Exemple #3
0
class Task(db.Model):
    """A MapRoulette task"""

    __tablename__ = 'tasks'

    id = db.Column(db.Integer,
                   Sequence('tasks_id_seq'),
                   unique=True,
                   nullable=False)
    identifier = db.Column(db.String(72), primary_key=True, nullable=False)
    challenge_slug = db.Column(db.String,
                               db.ForeignKey('challenges.slug',
                                             onupdate="cascade",
                                             ondelete="cascade"),
                               primary_key=True)
    random = db.Column(db.Float, default=getrandom, nullable=False)
    manifest = db.Column(db.String)  # not used for now
    location = db.Column(Geometry)
    geometries = db.relationship("TaskGeometry",
                                 cascade='all,delete-orphan',
                                 passive_deletes=True,
                                 backref=db.backref("task"))
    actions = db.relationship("Action",
                              cascade='all,delete-orphan',
                              passive_deletes=True,
                              backref=db.backref("task"))
    status = db.Column(db.String)
    instruction = db.Column(db.String)
    # note that spatial indexes seem to be created automagically
    __table_args__ = (db.Index('idx_id',
                               id), db.Index('idx_identifer', identifier),
                      db.Index('idx_challenge',
                               challenge_slug), db.Index('idx_random', random))

    # geometries should always be provided for new tasks, defaulting to None so
    # we can handle task updates and two step initialization of tasks
    def __init__(self,
                 challenge_slug,
                 identifier,
                 geometries=[],
                 instruction=None,
                 status='created'):
        self.challenge_slug = challenge_slug
        self.identifier = identifier
        self.instruction = instruction
        self.geometries = geometries
        self.append_action(Action('created'))

    def __repr__(self):
        return '<Task {identifier}>'.format(identifier=self.identifier)

    def __str__(self):
        return self.identifier

    @hybrid_method
    def has_status(self, statuses):
        if not type(statuses) == list:
            statuses = [statuses]
        return self.status in statuses

    @has_status.expression
    def has_status(cls, statuses):
        if not type(statuses) == list:
            statuses = [statuses]
        return cls.status.in_(statuses)

    def update(self, new_values, geometries, commit=True):
        """This updates a task based on a dict with new values"""
        for k, v in new_values.iteritems():
            # if a status is set, append an action
            if k == 'status':
                self.append_action(Action(v))
            elif not hasattr(self, k):
                app.logger.debug('task does not have %s' % (k, ))
                return False
            setattr(self, k, v)

        self.geometries = geometries

        # set the location for this task, as a representative point of the
        # combined geometries.
        if self.location is None:
            self.set_location()

        db.session.add(self)
        if commit:
            try:
                db.session.commit()
            except Exception as e:
                app.logger.warn(e.message)
                db.session.rollback()
                raise e
        return True

    @property
    def is_within(self, lon, lat, radius):
        return self.location.intersects(Point(lon, lat).buffer(radius))

    def append_action(self, action):
        self.actions.append(action)
        # duplicate the action status string in the tasks table to save lookups
        self.status = action.status
        try:
            db.session.commit()
        except Exception as e:
            app.logger.warn(e.message)
            db.session.rollback()
            raise e

    def set_location(self):
        """Set the location of a task as a cheaply calculated
        representative point of the combined geometries."""
        # set the location field, which is a representative point
        # for the task's geometries
        # first, get all individual coordinates for the geometries
        coordinates = []
        for geometry in self.geometries:
            coordinates.extend(list(to_shape(geometry.geom).coords))
        # then, set the location to a representative point
        # (cheaper than centroid)
        if len(coordinates) > 0:
            self.location = from_shape(
                MultiPoint(coordinates).representative_point(), srid=4326)