Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add nearbyTasksWithinBounds endpoints #1170

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions app/org/maproulette/controllers/api/ChallengeController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,40 @@ class ChallengeController @Inject() (
}
}

/**
* Gets available tasks within a bounding box
*
* @param challengeId The challenge id that is the parent of the tasks that you would be searching for
* @param left The left edge of the bounding box
* @param bottom The bottom edge of the bounding box
* @param right The right edge of the bounding box
* @param top The top edge of the bounding box
* @param limit The maximum number of nearby tasks to return
* @return
*/
def getNearbyTasksWithinBoundingBox(
challengeId: Long,
left: Double,
bottom: Double,
right: Double,
top: Double,
limit: Int
): Action[AnyContent] = Action.async { implicit request =>
this.sessionManager.userAwareRequest { implicit user =>
val results = this.dal
.getNearbyTasksWithinBoundingBox(
User.userOrMocked(user),
challengeId,
left,
bottom,
right,
top,
limit
)
Ok(Json.toJson(results))
}
}

/**
* Archive or unarchive a list of challenges
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,39 @@ class VirtualChallengeController @Inject() (
}
}

/**
* Gets available tasks within a bounding box
*
* @param challengeId The current virtual challenge id
* @param left The left edge of the bounding box
* @param bottom The bottom edge of the bounding box
* @param right The right edge of the bounding box
* @param top The top edge of the bounding box
* @return
*/
def getNearbyTasksWithinBoundingBox(
challengeId: Long,
left: Double,
bottom: Double,
right: Double,
top: Double,
limit: Int
): Action[AnyContent] =
Action.async { implicit request =>
this.sessionManager.userAwareRequest { implicit user =>
val results = this.dal.getNearbyTasksWithinBoundingBox(
User.userOrMocked(user),
challengeId,
left,
bottom,
right,
top,
limit
)
Ok(Json.toJson(results))
}
}

/**
* Gets the geo json for all the tasks associated with the challenge
*
Expand Down
41 changes: 41 additions & 0 deletions app/org/maproulette/models/dal/ChallengeDAL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1903,4 +1903,45 @@ class ChallengeDAL @Inject() (
sqlWithParameters(query, parameters).as(this.withVirtualParentParser.*)
}
}

/**
* Retrieve tasks within a bounding box for a challenge, ordered by proximity
*/
def getNearbyTasksWithinBoundingBox(
user: User,
challengeId: Long,
left: Double,
bottom: Double,
right: Double,
top: Double,
limit: Int = 5
)(
implicit c: Option[Connection] = None
): List[Task] = {
this.permission.hasReadAccess(ChallengeType(), user)(challengeId)

// Get the center point of the bounding box for proximity calculations
val centerLat = (top + bottom) / 2
val centerLon = (left + right) / 2

val query = s"""SELECT tasks.${taskDAL.retrieveColumnsWithReview} FROM tasks
LEFT JOIN locked l ON l.item_id = tasks.id
LEFT OUTER JOIN task_review ON task_review.task_id = tasks.id
WHERE parent_id = $challengeId AND
tasks.location && ST_MakeEnvelope($left, $bottom, $right, $top, 4326) AND
(l.id IS NULL OR l.user_id = ${user.id}) AND
tasks.status IN (0, 3, 6) AND
NOT tasks.id IN (
SELECT task_id FROM status_actions
WHERE osm_user_id = ${user.osmProfile.id} AND created >= NOW() - '1 hour'::INTERVAL)
ORDER BY ST_Distance(
tasks.location,
ST_SetSRID(ST_MakePoint($centerLon, $centerLat), 4326)
), tasks.status, RANDOM()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for ORDER BY ... RANDOM()? Seems like tie-breaking on something deterministic (like tasks.id) would be preferable since it means that for a given query and DB state the results are deterministic (useful I imagine for unit tests).

LIMIT ${this.sqlLimit(limit)}"""

this.withMRTransaction { implicit c =>
SQL(query).as(taskDAL.parser.*)
}
}
}
42 changes: 42 additions & 0 deletions app/org/maproulette/models/dal/VirtualChallengeDAL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,48 @@ class VirtualChallengeDAL @Inject() (
}
}

/**
* Retrieve tasks within a bounding box for a virtual challenge, ordered by proximity
*/
def getNearbyTasksWithinBoundingBox(
user: User,
challengeId: Long,
left: Double,
bottom: Double,
right: Double,
top: Double,
limit: Int = 5
)(
implicit c: Option[Connection] = None
): List[Task] = {
permission.hasReadAccess(VirtualChallengeType(), user)(challengeId)

// Get the center point of the bounding box for proximity calculations
val centerLat = (top + bottom) / 2
val centerLon = (left + right) / 2

val query = s"""SELECT tasks.${taskDAL.retrieveColumnsWithReview} FROM tasks
LEFT JOIN locked l ON l.item_id = tasks.id
LEFT JOIN virtual_challenge_tasks vct on vct.task_id = tasks.id
LEFT OUTER JOIN task_review ON task_review.task_id = tasks.id
WHERE vct.virtual_challenge_id = $challengeId AND
tasks.location && ST_MakeEnvelope($left, $bottom, $right, $top, 4326) AND
(l.id IS NULL OR l.user_id = ${user.id}) AND
tasks.status IN (0, 3, 6) AND
NOT tasks.id IN (
SELECT task_id FROM status_actions
WHERE osm_user_id = ${user.osmProfile.id} AND created >= NOW() - '1 hour'::INTERVAL)
ORDER BY ST_Distance(
tasks.location,
ST_SetSRID(ST_MakePoint($centerLon, $centerLat), 4326)
), tasks.status, RANDOM()
LIMIT ${this.sqlLimit(limit)}"""

this.withMRTransaction { implicit c =>
SQL(query).as(taskDAL.parser.*)
}
}

/**
* Gets the combined geometry of all the tasks that are associated with the virtual challenge
* NOTE* Due to the way this function finds the geometries, it could be quite slow.
Expand Down
33 changes: 33 additions & 0 deletions conf/v2_route/challenge.api
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,39 @@ GET /challenge/:cid/tasks/randomTasks @org.maproulette.controllers
GET /challenge/:cid/tasksNearby/:proximityId @org.maproulette.controllers.api.ChallengeController.getNearbyTasks(cid:Long, proximityId:Long, excludeSelfLocked:Boolean ?=false, limit:Int ?= 5)
###
# tags: [ Challenge ]
# summary: Retrieves available Tasks within a bounding box
# description: Retrieves available tasks within a bounding box
# responses:
# '200':
# description: The list of available tasks within the bounding box
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: '#/components/schemas/org.maproulette.framework.model.Task'
# parameters:
# - name: cid
# in: path
# description: The id of the parent Challenge limiting the tasks to only a descendent of that Challenge.
# - name: left
# in: query
# description: The left edge of the bounding box
# - name: bottom
# in: query
# description: The bottom edge of the bounding box
# - name: right
# in: query
# description: The right edge of the bounding box
# - name: top
# in: query
# description: The top edge of the bounding box
# - name: limit
# in: query
# description: Limit the number of results returned in the response. Default value is 5.
###
GET /challenge/:cid/nearby/box/:left/:bottom/:right/:top @org.maproulette.controllers.api.ChallengeController.getNearbyTasksWithinBoundingBox(cid:Long, left:Double, bottom:Double, right:Double, top:Double, limit:Int ?= 5)
# tags: [ Challenge ]
# summary: Retrieves prioritized random Task
# description: Retrieves a prioritized random Task contained within the current Challenge,
# with higher priority tasks being returned ahead of lower priority tasks
Expand Down
34 changes: 34 additions & 0 deletions conf/v2_route/virtualchallenge.api
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,37 @@ GET /virtualchallenge/clustered/:id @org.maproulette.controllers
# description: Id of task around which geographically closest tasks are desired
###
GET /virtualchallenge/:id/tasksNearby/:proximityId @org.maproulette.controllers.api.VirtualChallengeController.getNearbyTasks(id:Long, proximityId:Long, limit:Int ?= 5)
###
# tags: [ Virtual Challenge ]
# summary: Retrieves available Tasks in Virtual Challenge within a bounding box
# description: Retrieves available tasks within a bounding box
# responses:
# '200':
# description: The list of available tasks within the bounding box
# content:
# application/json:
# schema:
# type: array
# items:
# $ref: '#/components/schemas/org.maproulette.framework.model.Task'
# parameters:
# - name: id
# in: path
# description: The id of the parent Virtual Challenge.
# - name: left
# in: query
# description: The left edge of the bounding box
# - name: bottom
# in: query
# description: The bottom edge of the bounding box
# - name: right
# in: query
# description: The right edge of the bounding box
# - name: top
# in: query
# description: The top edge of the bounding box
# - name: limit
# in: query
# description: Limit the number of results returned in the response. Default value is 5.
###
GET /virtualchallenge/:id/nearby/box/:left/:bottom/:right/:top @org.maproulette.controllers.api.VirtualChallengeController.getNearbyTasksWithinBoundingBox(id:Long, left:Double, bottom:Double, right:Double, top:Double, limit:Int ?= 5)