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

3423 Recording Panorama Dates and Histories from GSV API #3527

Merged
merged 19 commits into from
May 7, 2024
Merged
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
11 changes: 7 additions & 4 deletions app/controllers/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import com.vividsolutions.jts.geom._
import controllers.headers.ProvidesHeader
import controllers.helper.ControllerUtils.sendSciStarterContributions
import formats.json.TaskSubmissionFormats._
import formats.json.PanoHistoryFormats._
import models.amt.AMTAssignmentTable
import models.audit.AuditTaskInteractionTable.secondsAudited
import models.audit._
import models.daos.slick.DBTableDefinitions.{DBUser, UserTable}
import models.gsv.{GSVData, GSVDataTable, GSVLink, GSVLinkTable}
import models.gsv.{GSVData, GSVDataTable, GSVLink, GSVLinkTable, PanoHistory, PanoHistoryTable}
import models.label._
import models.mission.{Mission, MissionTable}
import models.region._
Expand Down Expand Up @@ -221,7 +222,6 @@ class TaskController @Inject() (implicit val env: Environment[User, SessionAuthe
val streetEdgeId: Int = data.auditTask.streetEdgeId
val missionId: Int = data.missionProgress.missionId
val currTime: Timestamp = new Timestamp(data.timestamp)

if (data.auditTask.auditTaskId.isDefined) {
val priorityBefore: StreetEdgePriority = streetPrioritiesFromIds(List(streetEdgeId)).head
userOption match {
Expand Down Expand Up @@ -355,11 +355,11 @@ class TaskController @Inject() (implicit val env: Environment[User, SessionAuthe
// Insert new entry to gsv_data table, or update the last_viewed column if we've already recorded it.
if (GSVDataTable.panoramaExists(pano.gsvPanoramaId)) {
GSVDataTable.updateFromExplore(pano.gsvPanoramaId, pano.lat, pano.lng, pano.cameraHeading,
pano.cameraPitch, expired = false, currTime)
pano.cameraPitch, expired = false, currTime, Some(currTime))
} else {
val gsvData: GSVData = GSVData(pano.gsvPanoramaId, pano.width, pano.height, pano.tileWidth, pano.tileHeight,
pano.captureDate, pano.copyright, pano.lat, pano.lng, pano.cameraHeading, pano.cameraPitch, expired = false,
currTime)
currTime, Some(currTime))
GSVDataTable.save(gsvData)
}
for (link <- pano.links) {
Expand All @@ -368,6 +368,9 @@ class TaskController @Inject() (implicit val env: Environment[User, SessionAuthe
GSVLinkTable.save(gsvLink)
}
}

misaugstad marked this conversation as resolved.
Show resolved Hide resolved
// Save the history of the panoramas at this location.
pano.history.foreach { h => PanoHistoryTable.save(PanoHistory(h.panoId, h.date, pano.gsvPanoramaId)) }
}

// Check for streets in the user's neighborhood that have been audited by other users while they were auditing.
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/ValidationTaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import controllers.headers.ProvidesHeader
import controllers.helper.ControllerUtils.{isAdmin, sendSciStarterContributions}
import controllers.helper.ValidateHelper.{AdminValidateParams, getLabelTypeIdToValidate}
import formats.json.ValidationTaskSubmissionFormats._
import formats.json.PanoHistoryFormats._
import models.amt.AMTAssignmentTable
import models.label._
import models.label.LabelTable.{AdminValidationData, LabelValidationMetadata}
import models.mission.{Mission, MissionTable}
import models.user.{User, UserStatTable}
import models.validation._
import models.gsv.{GSVDataTable, PanoHistory, PanoHistoryTable}
import play.api.libs.json._
import play.api.{Logger, Play}
import play.api.mvc._
Expand Down Expand Up @@ -90,6 +92,15 @@ class ValidationTaskController @Inject() (implicit val env: Environment[User, Se
UserStatTable.updateAccuracy(usersValidated)
}

// Adding the new panorama information to the pano_history table.
data.panoHistories.foreach { panoHistory =>
// First, update the panorama that shows up currently for the current location in the GSVDataTable.
GSVDataTable.updatePanoHistorySaved(panoHistory.currPanoId, Some(new Timestamp(panoHistory.panoHistorySaved)))

// Add all of the panoramas at the current location.
panoHistory.history.foreach { h => PanoHistoryTable.save(PanoHistory(h.panoId, h.date, panoHistory.currPanoId)) }
}

// We aren't always submitting mission progress, so check if data.missionProgress exists.
val returnValue: ValidationTaskPostReturnValue = data.missionProgress match {
case Some(_) =>
Expand Down
20 changes: 20 additions & 0 deletions app/formats/json/PanoHistoryFormats.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package formats.json

import play.api.libs.json.{JsPath, Reads}
import play.api.libs.functional.syntax._

object PanoHistoryFormats {
case class PanoDate(panoId: String, date: String)
case class PanoHistorySubmission(currPanoId: String, history: Seq[PanoDate], panoHistorySaved: Long)

implicit val PanoDateReads: Reads[PanoDate] = (
(JsPath \ "pano_id").read[String] and
(JsPath \ "date").read[String]
)(PanoDate.apply _)

implicit val PanoHistorySubmissionReads: Reads[PanoHistorySubmission] = (
(JsPath \ "curr_pano_id").read[String] and
(JsPath \ "history").read[Seq[PanoDate]] and
(JsPath \ "pano_history_saved").read[Long]
)(PanoHistorySubmission.apply _)
}
7 changes: 4 additions & 3 deletions app/formats/json/TaskSubmissionFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package formats.json
import play.api.libs.json.{JsPath, Reads}
import com.vividsolutions.jts.geom._

import scala.collection.immutable.Seq
import play.api.libs.functional.syntax._
import formats.json.PanoHistoryFormats._

object TaskSubmissionFormats {
case class EnvironmentSubmission(browser: Option[String], browserVersion: Option[String], browserWidth: Option[Int], browserHeight: Option[Int], availWidth: Option[Int], availHeight: Option[Int], screenWidth: Option[Int], screenHeight: Option[Int], operatingSystem: Option[String], language: String, cssZoom: Int)
Expand All @@ -14,7 +14,7 @@ object TaskSubmissionFormats {
case class TaskSubmission(streetEdgeId: Int, taskStart: Long, auditTaskId: Option[Int], completed: Option[Boolean], currentLat: Float, currentLng: Float, startPointReversed: Boolean, currentMissionStart: Option[Point], lastPriorityUpdateTime: Long, requestUpdatedStreetPriority: Boolean)
case class IncompleteTaskSubmission(issueDescription: String, lat: Float, lng: Float)
case class GSVLinkSubmission(targetGsvPanoramaId: String, yawDeg: Double, description: String)
case class GSVPanoramaSubmission(gsvPanoramaId: String, captureDate: String, width: Option[Int], height: Option[Int], tileWidth: Option[Int], tileHeight: Option[Int], lat: Option[Float], lng: Option[Float], cameraHeading: Option[Float], cameraPitch: Option[Float], links: Seq[GSVLinkSubmission], copyright: String)
case class GSVPanoramaSubmission(gsvPanoramaId: String, captureDate: String, width: Option[Int], height: Option[Int], tileWidth: Option[Int], tileHeight: Option[Int], lat: Option[Float], lng: Option[Float], cameraHeading: Option[Float], cameraPitch: Option[Float], links: Seq[GSVLinkSubmission], copyright: String, history: Seq[PanoDate])
case class AuditMissionProgress(missionId: Int, distanceProgress: Option[Float], completed: Boolean, auditTaskId: Option[Int], skipped: Boolean)
case class AuditTaskSubmission(missionProgress: AuditMissionProgress, auditTask: TaskSubmission, labels: Seq[LabelSubmission], interactions: Seq[InteractionSubmission], environment: EnvironmentSubmission, incomplete: Option[IncompleteTaskSubmission], gsvPanoramas: Seq[GSVPanoramaSubmission], amtAssignmentId: Option[Int], userRouteId: Option[Int], timestamp: Long)
case class AMTAssignmentCompletionSubmission(assignmentId: Int, completed: Option[Boolean])
Expand Down Expand Up @@ -116,7 +116,8 @@ object TaskSubmissionFormats {
(JsPath \ "camera_heading").readNullable[Float] and
(JsPath \ "camera_pitch").readNullable[Float] and
(JsPath \ "links").read[Seq[GSVLinkSubmission]] and
(JsPath \ "copyright").read[String]
(JsPath \ "copyright").read[String] and
(JsPath \ "history").read[Seq[PanoDate]]
)(GSVPanoramaSubmission.apply _)

implicit val auditMissionProgressReads: Reads[AuditMissionProgress] = (
Expand Down
8 changes: 4 additions & 4 deletions app/formats/json/ValidationTaskSubmissionFormats.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package formats.json

import controllers.helper.ValidateHelper.AdminValidateParams
import java.sql.Timestamp
import play.api.libs.json.{JsBoolean, JsPath, Reads}
import scala.collection.immutable.Seq
import play.api.libs.json.{JsPath, Reads}
import play.api.libs.functional.syntax._
import formats.json.PanoHistoryFormats._

object ValidationTaskSubmissionFormats {
case class EnvironmentSubmission(missionId: Option[Int], browser: Option[String], browserVersion: Option[String], browserWidth: Option[Int], browserHeight: Option[Int], availWidth: Option[Int], availHeight: Option[Int], screenWidth: Option[Int], screenHeight: Option[Int], operatingSystem: Option[String], language: String, cssZoom: Int)
case class InteractionSubmission(action: String, missionId: Option[Int], gsvPanoramaId: Option[String], lat: Option[Float], lng: Option[Float], heading: Option[Float], pitch: Option[Float], zoom: Option[Float], note: Option[String], timestamp: Long, isMobile: Boolean)
case class LabelValidationSubmission(labelId: Int, missionId: Int, validationResult: Int, oldSeverity: Option[Int], newSeverity: Option[Int], oldTags: List[String], newTags: List[String], canvasX: Option[Int], canvasY: Option[Int], heading: Float, pitch: Float, zoom: Float, canvasHeight: Int, canvasWidth: Int, startTimestamp: Long, endTimestamp: Long, source: String, undone: Option[Boolean])
case class SkipLabelSubmission(labels: Seq[LabelValidationSubmission], adminParams: AdminValidateParams)
case class ValidationMissionProgress(missionId: Int, missionType: String, labelsProgress: Int, labelTypeId: Int, completed: Boolean, skipped: Boolean)
case class ValidationTaskSubmission(interactions: Seq[InteractionSubmission], environment: EnvironmentSubmission, validations: Seq[LabelValidationSubmission], missionProgress: Option[ValidationMissionProgress], adminParams: AdminValidateParams, timestamp: Long)
case class ValidationTaskSubmission(interactions: Seq[InteractionSubmission], environment: EnvironmentSubmission, validations: Seq[LabelValidationSubmission], missionProgress: Option[ValidationMissionProgress], adminParams: AdminValidateParams, panoHistories: Seq[PanoHistorySubmission], timestamp: Long)
case class LabelMapValidationSubmission(labelId: Int, labelType: String, validationResult: Int, oldSeverity: Option[Int], newSeverity: Option[Int], oldTags: List[String], newTags: List[String], canvasX: Option[Int], canvasY: Option[Int], heading: Float, pitch: Float, zoom: Float, canvasHeight: Int, canvasWidth: Int, startTimestamp: Long, endTimestamp: Long, source: String)

implicit val environmentSubmissionReads: Reads[EnvironmentSubmission] = (
Expand Down Expand Up @@ -87,6 +86,7 @@ object ValidationTaskSubmissionFormats {
(JsPath \ "validations").read[Seq[LabelValidationSubmission]] and
(JsPath \ "mission_progress").readNullable[ValidationMissionProgress] and
(JsPath \ "admin_params").read[AdminValidateParams] and
(JsPath \ "pano_histories").read[Seq[PanoHistorySubmission]] and
(JsPath \ "timestamp").read[Long]
)(ValidationTaskSubmission.apply _)

Expand Down
24 changes: 18 additions & 6 deletions app/models/gsv/GSVDataTable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import play.api.Play.current
case class GSVData(gsvPanoramaId: String, width: Option[Int], height: Option[Int], tileWidth: Option[Int],
tileHeight: Option[Int], captureDate: String, copyright: String, lat: Option[Float],
lng: Option[Float], cameraHeading: Option[Float], cameraPitch: Option[Float], expired: Boolean,
lastViewed: java.sql.Timestamp)
lastViewed: java.sql.Timestamp, panoHistorySaved: Option[java.sql.Timestamp])

case class GSVDataSlim(gsvPanoramaId: String, width: Option[Int], height: Option[Int], lat: Option[Float],
lng: Option[Float], cameraHeading: Option[Float], cameraPitch: Option[Float])
Expand All @@ -26,10 +26,11 @@ class GSVDataTable(tag: Tag) extends Table[GSVData](tag, "gsv_data") {
def cameraHeading = column[Option[Float]]("camera_heading", O.Nullable)
def cameraPitch = column[Option[Float]]("camera_pitch", O.Nullable)
def expired = column[Boolean]("expired", O.NotNull)
def lastViewed = column[java.sql.Timestamp]("last_viewed", O.Nullable)
def lastViewed = column[java.sql.Timestamp]("last_viewed", O.NotNull)
def panoHistorySaved = column[Option[java.sql.Timestamp]]("pano_history_saved", O.Nullable)

def * = (gsvPanoramaId, width, height, tileWidth, tileHeight, captureDate, copyright, lat, lng,
cameraHeading, cameraPitch, expired, lastViewed) <>
cameraHeading, cameraPitch, expired, lastViewed, panoHistorySaved) <>
((GSVData.apply _).tupled, GSVData.unapply)
}

Expand Down Expand Up @@ -76,10 +77,10 @@ object GSVDataTable {
/**
* Updates the data from the GSV API for a pano that sometimes changes.
*/
def updateFromExplore(gsvPanoramaId: String, lat: Option[Float], lng: Option[Float], heading: Option[Float], pitch: Option[Float], expired: Boolean, lastViewed: java.sql.Timestamp): Int = db.withSession { implicit session =>
def updateFromExplore(gsvPanoramaId: String, lat: Option[Float], lng: Option[Float], heading: Option[Float], pitch: Option[Float], expired: Boolean, lastViewed: java.sql.Timestamp, panoHistorySaved: Option[java.sql.Timestamp]): Int = db.withSession { implicit session =>
val q = for { pano <- gsvDataRecords if pano.gsvPanoramaId === gsvPanoramaId }
yield (pano.lat, pano.lng, pano.cameraHeading, pano.cameraPitch, pano.expired, pano.lastViewed)
q.update((lat, lng, heading, pitch, expired, lastViewed))
yield (pano.lat, pano.lng, pano.cameraHeading, pano.cameraPitch, pano.expired, pano.lastViewed, pano.panoHistorySaved)
q.update((lat, lng, heading, pitch, expired, lastViewed, panoHistorySaved))
}

/**
Expand All @@ -92,6 +93,17 @@ object GSVDataTable {
gsvDataRecords.filter(_.gsvPanoramaId === panoramaId).list.nonEmpty
}

/**
* This method updates a given panorama's panoHistorySaved field
*
* @param panoramaId Google Street View panorama Id
* @param panoHistorySaved Timestamp that this panorama was last viewed by any user
* @return
*/
def updatePanoHistorySaved(panoramaId: String, panoHistorySaved: Option[java.sql.Timestamp]): Int = db.withSession { implicit session =>
gsvDataRecords.filter(_.gsvPanoramaId === panoramaId).map(_.panoHistorySaved).update(panoHistorySaved)
}

def save(data: GSVData): String = db.withSession { implicit session =>
gsvDataRecords += data
data.gsvPanoramaId
Expand Down
33 changes: 33 additions & 0 deletions app/models/gsv/PanoHistoryTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package models.gsv

import models.utils.MyPostgresDriver.simple._
import play.api.Play.current
import scala.slick.lifted.ForeignKeyQuery

case class PanoHistory(panoId: String, captureDate: String, locationCurrPanoId: String)

class PanoHistoryTable(tag: Tag) extends Table[PanoHistory](tag, "pano_history") {
def panoId: Column[String] = column[String]("pano_id", O.NotNull)
def captureDate: Column[String] = column[String]("capture_date", O.NotNull)
def locationCurrPanoId: Column[String] = column[String]("location_curr_pano_id", O.NotNull)

def * = (panoId, captureDate, locationCurrPanoId) <> ((PanoHistory.apply _).tupled, PanoHistory.unapply)

def locationCurrentPano: ForeignKeyQuery[GSVDataTable, GSVData] = foreignKey("pano_history_gsv_panorama_id_fkey", locationCurrPanoId, TableQuery[GSVDataTable])(_.gsvPanoramaId)
}

object PanoHistoryTable {
val db = play.api.db.slick.DB
val panoHistoryTable = TableQuery[PanoHistoryTable]

/**
* Save a pano history object to the PanoHistory table if it isn't already in the table.
*/
def save(history: PanoHistory): Int = db.withSession { implicit session =>
if (panoHistoryTable.filter(h => h.panoId === history.panoId && h.locationCurrPanoId === history.locationCurrPanoId).list.isEmpty) {
panoHistoryTable += history
} else {
0
}
}
}
15 changes: 15 additions & 0 deletions conf/evolutions/default/226.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# --- !Ups
ALTER TABLE gsv_data ADD COLUMN pano_history_saved TIMESTAMPTZ;

CREATE TABLE pano_history (
pano_id TEXT NOT NULL,
capture_date TEXT NOT NULL,
location_curr_pano_id TEXT NOT NULL,
FOREIGN KEY (location_curr_pano_id) REFERENCES gsv_data(gsv_panorama_id)
);
ALTER TABLE pano_history OWNER TO sidewalk; -- This just helps us give table correct permissions on prod server.

# --- !Downs
DROP TABLE pano_history;

ALTER TABLE gsv_data DROP COLUMN pano_history_saved;
25 changes: 21 additions & 4 deletions public/javascripts/SVLabel/src/SVLabel/data/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ function Form (labelContainer, missionModel, missionContainer, navigationModel,
self.submitData(true);
});

this.convertPanoHistoryFormat = function (prevPanos) {
var history = [];
for (let i = 0; i < prevPanos.length; i++) {
history.push({
pano_id: prevPanos[i].pano,
date: moment(prevPanos[i].Gw).format('YYYY-MM')
});
}
return history;
}

/**
* This method gathers all the data needed for submission.
* @returns {{}}
*/
this.compileSubmissionData = function (task) {
var data = { timestamp: new Date().getTime() };

data.amt_assignment_id = svl.amtAssignmentId;
data.user_route_id = svl.userRouteId;

Expand Down Expand Up @@ -148,8 +158,10 @@ function Form (labelContainer, missionModel, missionContainer, navigationModel,
});
}
}

var panoId = ("location" in panoData && "pano" in panoData.location) ? panoData.location.pano : "";
temp = {
panorama_id: ("location" in panoData && "pano" in panoData.location) ? panoData.location.pano : "",
panorama_id: panoId,
capture_date: "imageDate" in panoData ? panoData.imageDate : "",
width: panoData.tiles.worldSize.width,
height: panoData.tiles.worldSize.height,
Expand All @@ -160,12 +172,17 @@ function Form (labelContainer, missionModel, missionContainer, navigationModel,
camera_heading: panoData.tiles.originHeading,
camera_pitch: -panoData.tiles.originPitch, // camera_pitch is negative origin_pitch.
links: links,
copyright: "copyright" in panoData ? panoData.copyright : ""
copyright: "copyright" in panoData ? panoData.copyright : "",
history: []
};

if (panoData.time !== undefined && panoId !== "") {
temp.history = this.convertPanoHistoryFormat(panoData.time);
}

data.gsv_panoramas.push(temp);
panoramas[i].setProperty("submitted", true);
}

return data;
};

Expand Down
2 changes: 1 addition & 1 deletion public/javascripts/SVLabel/src/SVLabel/mission/Mission.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function Mission(parameters) {
},
_tasksForTheMission = [],
labelCountsAtCompletion;

function _init(parameters) {
if ("missionId" in parameters) setProperty("missionId", parameters.missionId);
if ("missionType" in parameters) setProperty("missionType", parameters.missionType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function MapService (canvas, neighborhoodModel, uiMap, params) {
isInternetExplore: undefined
},
status = {
currentPanoId: undefined,
currPanoId: undefined,
disablePanning: false,
disableWalking : false,
hideNonavailablePanoLinks : false,
Expand Down Expand Up @@ -1111,10 +1111,10 @@ function MapService (canvas, neighborhoodModel, uiMap, params) {

function updateCanvas() {
_canvas.clear();
if (status.currentPanoId !== getPanoId()) {
if (status.currPanoId !== getPanoId()) {
_canvas.setOnlyLabelsOnPanoAsVisible(getPanoId());
}
status.currentPanoId = getPanoId();
status.currPanoId = getPanoId();
_canvas.render();
}

Expand Down Expand Up @@ -1354,7 +1354,6 @@ function MapService (canvas, neighborhoodModel, uiMap, params) {
// If there is no imagery here that we haven't already been stuck in, either try further down the street,
// try with a larger radius, or just jump to a new street if all else fails.
if (status !== GSV_OK || _stuckPanos.includes(streetViewPanoData.location.pano)) {

// If there is room to move forward then try again, recursively calling getPanorama with this callback.
if (turf.length(remainder) > 0) {
// Save the current pano ID as one that doesn't work.
Expand Down
Loading