Skip to content

Commit

Permalink
fix bounding box table data fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
CollinBeczak committed Feb 25, 2025
1 parent 52f06e2 commit 16c1fa8
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 113 deletions.
21 changes: 11 additions & 10 deletions app/org/maproulette/controllers/api/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -516,16 +516,17 @@ class TaskController @Inject() (
*/
def bulkStatusChange(newStatus: Int): Action[AnyContent] = Action.async { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
SearchParameters.withSearch { p =>
var params = p
params.location match {
case Some(l) => // do nothing, already have bounding box
case None =>
// No bounding box, so search everything
params = p.copy(location = Some(SearchLocation(-180, -90, 180, 90)))
}
val (count, tasks) = this.taskClusterService.getTasksInBoundingBox(user, params, Paging(-1))
val challengeIds = params.challengeParams.challengeIds.getOrElse(List()).distinct.sorted
SearchParameters.withSearch { params =>
val (count, tasks) = this.taskClusterService.getTasksInBoundingBox(
user,
params,
Paging(-1),
false,
"",
"",
Some(SearchLocation(-180, -90, 180, 90))
)
val challengeIds = params.challengeParams.challengeIds.getOrElse(List()).distinct.sorted

// Update challenge status to building
challengeIds.foreach(challengeId => {
Expand Down
6 changes: 3 additions & 3 deletions app/org/maproulette/framework/controller/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ class TaskController @Inject() (
includeTags: Boolean = false
): Action[AnyContent] = Action.async { implicit request =>
this.sessionManager.userAwareRequest { implicit user =>
SearchParameters.withSearch { p =>
val params = p.copy(location = Some(SearchLocation(left, bottom, right, top)))
SearchParameters.withSearch { params =>
val (count, result) = this.taskClusterService.getTasksInBoundingBox(
User.userOrMocked(user),
params,
Paging(limit, page),
excludeLocked,
sort,
order
order,
Some(SearchLocation(left, bottom, right, top))
)

val resultJson = this.insertExtraTaskJSON(result, includeGeometries, includeTags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,18 +564,20 @@ class TaskReviewController @Inject() (
def removeReviewRequest(ids: String, asMetaReview: Boolean = false): Action[AnyContent] =
Action.async { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
SearchParameters.withSearch { p =>
SearchParameters.withSearch { params =>
implicit val taskIds = Utils.toLongList(ids) match {
case Some(l) if !l.isEmpty => l
case None => {
val params = p.location match {
case Some(l) => p
case None =>
// No bounding box, so search everything
p.copy(location = Some(SearchLocation(-180, -90, 180, 90)))
}
val (count, tasks) =
this.serviceManager.taskCluster.getTasksInBoundingBox(user, params, Paging(-1))
this.serviceManager.taskCluster.getTasksInBoundingBox(
user,
params,
Paging(-1),
false,
"",
"",
Some(SearchLocation(-180, -90, 180, 90))
)
tasks.map(task => task.id)
}
}
Expand Down
100 changes: 73 additions & 27 deletions app/org/maproulette/framework/repository/TaskClusterRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,6 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL:
SQL(sql.toString).as(this.pointParser.*)
}
}
// sql query use to select list of ClusteredPoint data
private val selectTaskMarkersSQL = s"""
SELECT tasks.id, tasks.name, tasks.parent_id, c.name, tasks.instruction, tasks.status, tasks.mapped_on,
tasks.completed_time_spent, tasks.completed_by,
tasks.bundle_id, tasks.is_bundle_primary, tasks.cooperative_work_json::TEXT as cooperative_work,
task_review.review_status, task_review.review_requested_by, task_review.reviewed_by, task_review.reviewed_at,
task_review.review_started_at, task_review.meta_review_status, task_review.meta_reviewed_by,
task_review.meta_reviewed_at, task_review.additional_reviewers,
ST_AsGeoJSON(tasks.location) AS location, priority,
CASE WHEN task_review.review_started_at IS NULL
THEN 0
ELSE EXTRACT(epoch FROM (task_review.reviewed_at - task_review.review_started_at)) END
AS reviewDuration
FROM tasks
${joinClause.toString()}
"""

/**
* Querys tasks in a bounding box
Expand All @@ -147,17 +131,80 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL:
def queryTasksInBoundingBox(
query: Query,
order: Order,
paging: Paging
paging: Paging,
location: Option[SearchLocation]
): (Int, List[ClusteredPoint]) = {
this.withMRTransaction { implicit c =>
val count =
query.build(s"""
SELECT count(*) FROM tasks
${joinClause.toString()}
""").as(SqlParser.int("count").single)
// Extract the location filter if provided
val locationClause = location match {
case Some(loc) =>
s"tasks.location && ST_MakeEnvelope(${loc.left}, ${loc.bottom}, ${loc.right}, ${loc.top}, 4326)"
case None => "TRUE"
}

// Create a modified query that only selects task IDs
val filteredTasksCTE = s"""
WITH filtered_tasks AS (
SELECT tasks.id
FROM tasks
INNER JOIN challenges c ON c.id = tasks.parent_id
INNER JOIN projects p ON p.id = c.parent_id
LEFT OUTER JOIN task_review ON task_review.task_id = tasks.id
WHERE ${query.filter.sql()}
)
"""

// Count query using the CTE
val countQuery = s"""
${filteredTasksCTE}
SELECT count(*) FROM filtered_tasks
"""

val count = SQL(countQuery).as(SqlParser.int("count").single)

val resultsQuery = query.copy(order = order, paging = paging).build(selectTaskMarkersSQL)
val results = resultsQuery.as(this.pointParser.*)
// Main query using the CTE with ordering and paging
val orderClause = order.sql()

// Explicitly construct the full query with LIMIT and OFFSET
val mainQuery = s"""
${filteredTasksCTE}
SELECT tasks.id,
tasks.name,
tasks.parent_id,
c.name,
tasks.instruction,
tasks.status,
tasks.mapped_on,
tasks.completed_time_spent,
tasks.completed_by,
tasks.bundle_id,
tasks.is_bundle_primary,
tasks.cooperative_work_json::TEXT as cooperative_work,
task_review.review_status,
task_review.review_requested_by,
task_review.reviewed_by,
task_review.reviewed_at,
task_review.review_started_at,
task_review.meta_review_status,
task_review.meta_reviewed_by,
task_review.meta_reviewed_at,
task_review.additional_reviewers,
ST_AsGeoJSON(tasks.location) AS location,
priority,
CASE
WHEN task_review.review_started_at IS NULL THEN 0
ELSE EXTRACT(epoch FROM (task_review.reviewed_at - task_review.review_started_at))
END AS reviewDuration
FROM filtered_tasks
INNER JOIN tasks ON tasks.id = filtered_tasks.id
INNER JOIN challenges c ON c.id = tasks.parent_id
INNER JOIN projects p ON p.id = c.parent_id
LEFT OUTER JOIN task_review ON task_review.task_id = tasks.id
WHERE ${locationClause}
${orderClause} LIMIT ${paging.limit} OFFSET ${paging.page}
"""

val results = SQL(mainQuery).as(this.pointParser.*)

(count, results)
}
Expand Down Expand Up @@ -199,6 +246,7 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL:

// Main query using the CTE
val mainQuery = s"""
${filteredTasksCTE}
SELECT tasks.id,
tasks.name,
tasks.parent_id,
Expand Down Expand Up @@ -235,9 +283,7 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL:
LIMIT ${limit}
"""

val finalSQL = filteredTasksCTE + mainQuery

SQL(finalSQL).as(this.pointParser.*)
SQL(mainQuery).on(query.parameters(): _*).as(this.pointParser.*)
}
}

Expand Down
70 changes: 31 additions & 39 deletions app/org/maproulette/framework/service/TaskClusterService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,9 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository)
paging: Paging = Paging(Config.DEFAULT_LIST_SIZE, 0),
ignoreLocked: Boolean = false,
sort: String = "",
orderDirection: String = "ASC"
): (Int, List[ClusteredPoint]) = {
val query = buildQueryForBoundingBox(user, params, ignoreLocked)
this.repository.queryTasksInBoundingBox(query, this.getOrder(sort, orderDirection), paging)
}

/**
* This function will retrieve task marker data in a given bounded area with optimized performance.
* Uses filtering techniques to limit data retrieved and improve query execution time.
*
* @param user The user making the request
* @param params The search parameters including bounding box information
* @param limit Maximum number of points to return
* @param ignoreLocked Whether to include locked tasks (by other users) or not
* @return The list of ClusteredPoints found within the bounding box
*/
def getTaskMarkerDataInBoundingBox(
user: User,
params: SearchParameters,
limit: Int,
ignoreLocked: Boolean = false,
orderDirection: String = "ASC",
location: Option[SearchLocation] = None
): List[ClusteredPoint] = {
): (Int, List[ClusteredPoint]) = {
// Create a pre-filtered query for basic task conditions
val baseQuery = this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false))

Expand Down Expand Up @@ -124,32 +104,41 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository)
}

// Execute the optimized query
this.repository.queryTaskMarkerDataInBoundingBox(finalQuery, location, limit)
this.repository.queryTasksInBoundingBox(
finalQuery,
this.getOrder(sort, orderDirection),
paging,
location
)
}

/**
* Builds a query to retrieve tasks within a bounding box, applying search parameters.
* This function will retrieve task marker data in a given bounded area with optimized performance.
* Uses filtering techniques to limit data retrieved and improve query execution time.
*
* @param user The user making the request
* @param params Search parameters including location or bounding geometries
* @param ignoreLocked Whether to exclude tasks locked by other users
* @return The constructed query
* @param params The search parameters including bounding box information
* @param limit Maximum number of points to return
* @param ignoreLocked Whether to include locked tasks (by other users) or not
* @return The list of ClusteredPoints found within the bounding box
*/
private def buildQueryForBoundingBox(
def getTaskMarkerDataInBoundingBox(
user: User,
params: SearchParameters,
ignoreLocked: Boolean
): Query = {
ensureBoundingBox(params)
var query = this.filterOutLocked(
user,
this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false)),
ignoreLocked
)
limit: Int,
ignoreLocked: Boolean = false,
location: Option[SearchLocation] = None
): List[ClusteredPoint] = {
// Create a pre-filtered query for basic task conditions
val baseQuery = this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false))

// Apply locking filters
val lockedFilteredQuery = this.filterOutLocked(user, baseQuery, ignoreLocked)

params.taskParams.excludeTaskIds match {
// Add exclusion filter if needed
val finalQuery = params.taskParams.excludeTaskIds match {
case Some(excludedIds) if excludedIds.nonEmpty =>
query.addFilterGroup(
lockedFilteredQuery.addFilterGroup(
FilterGroup(
List(
BaseParameter(
Expand All @@ -163,8 +152,11 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository)
)
)
)
case _ => query
case _ => lockedFilteredQuery
}

// Execute the optimized query
this.repository.queryTaskMarkerDataInBoundingBox(finalQuery, location, limit)
}

/**
Expand Down
Loading

0 comments on commit 16c1fa8

Please sign in to comment.