Skip to content

Commit

Permalink
Merge pull request #3418 from ProjectSidewalk/develop
Browse files Browse the repository at this point in the history
v7.16.1
  • Loading branch information
misaugstad authored Nov 1, 2023
2 parents 50d5556 + 9ad4241 commit 3df55ad
Show file tree
Hide file tree
Showing 36 changed files with 528 additions and 316 deletions.
36 changes: 35 additions & 1 deletion app/controllers/AdminController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import controllers.headers.ProvidesHeader
import controllers.helper.ControllerUtils.parseIntegerList
import formats.json.LabelFormat
import formats.json.TaskFormats._
import formats.json.UserRoleSubmissionFormats._
import formats.json.AdminUpdateSubmissionFormats._
import formats.json.LabelFormat._
import javassist.NotFoundException
import models.attribute.{GlobalAttribute, GlobalAttributeTable}
Expand Down Expand Up @@ -555,6 +555,40 @@ class AdminController @Inject() (implicit val env: Environment[User, SessionAuth
}
)
}

/**
* Updates the org in the database for the given user.
*/
def setUserOrg = UserAwareAction.async(BodyParsers.parse.json) { implicit request =>
val submission = request.body.validate[UserOrgSubmission]

submission.fold(
errors => {
Future.successful(BadRequest(Json.obj("status" -> "Error", "message" -> JsError.toFlatJson(errors))))
},
submission => {
val userId: UUID = UUID.fromString(submission.userId)
val newOrgId: Int = submission.orgId

if (isAdmin(request.identity)) {
// Remove any previous org and add the new org. Will add the ability to be in multiple orgs in the future.
val currentOrg: Option[Int] = UserOrgTable.getAllOrgs(userId).headOption
if (currentOrg.nonEmpty) {
UserOrgTable.remove(userId, currentOrg.get)
}
val confirmedOrgId: Int = UserOrgTable.save(userId, newOrgId)

if (confirmedOrgId == newOrgId) {
Future.successful(Ok(Json.obj("user_id" -> userId, "org_id" -> newOrgId)))
} else {
Future.successful(BadRequest("Error saving org"))
}
} else {
Future.failed(new AuthenticationException("User is not an administrator"))
}
}
)
}

/** Clears all cached values stored in the EhCachePlugin, which is Play's default cache plugin. */
def clearPlayCache() = UserAwareAction.async { implicit request =>
Expand Down
71 changes: 50 additions & 21 deletions app/controllers/ProjectSidewalkAPIController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import formats.json.APIFormats
import java.sql.Timestamp
import java.time.Instant
import javax.inject.Inject
import models.attribute.{GlobalAttributeForAPI, GlobalAttributeTable, GlobalAttributeWithLabelForAPI}
import models.attribute.{GlobalAttributeForAPI, GlobalAttributeTable, GlobalAttributeWithLabelForAPI, ConfigTable, MapParams}
import org.locationtech.jts.geom.{Coordinate => JTSCoordinate}
import math._
import models.region._
Expand Down Expand Up @@ -125,13 +125,15 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
* @param filetype
* @return
*/
def getAccessAttributesWithLabelsV2(lat1: Double, lng1: Double, lat2: Double, lng2: Double, severity: Option[String], filetype: Option[String]) = UserAwareAction.async { implicit request =>
def getAccessAttributesWithLabelsV2(lat1: Option[Double], lng1: Option[Double], lat2: Option[Double], lng2: Option[Double],
severity: Option[String], filetype: Option[String]) = UserAwareAction.async { implicit request =>
apiLogging(request.remoteAddress, request.identity, request.toString)

val minLat:Float = min(lat1, lat2).toFloat
val maxLat:Float = max(lat1, lat2).toFloat
val minLng:Float = min(lng1, lng2).toFloat
val maxLng:Float = max(lng1, lng2).toFloat
val cityMapParams: MapParams = ConfigTable.getCityMapParams
val minLat: Double = min(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val maxLat: Double = max(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val minLng: Double = min(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))
val maxLng: Double = max(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))

// In CSV format.
if (filetype.isDefined && filetype.get == "csv") {
Expand Down Expand Up @@ -198,14 +200,16 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
* @param filetype
* @return
*/
def getAccessAttributesV2(lat1: Double, lng1: Double, lat2: Double, lng2: Double, severity: Option[String],
filetype: Option[String]) = UserAwareAction.async { implicit request =>
def getAccessAttributesV2(lat1: Option[Double], lng1: Option[Double], lat2: Option[Double], lng2: Option[Double],
severity: Option[String], filetype: Option[String]) = UserAwareAction.async { implicit request =>
apiLogging(request.remoteAddress, request.identity, request.toString)

val minLat:Float = min(lat1, lat2).toFloat
val maxLat:Float = max(lat1, lat2).toFloat
val minLng:Float = min(lng1, lng2).toFloat
val maxLng:Float = max(lng1, lng2).toFloat
val cityMapParams: MapParams = ConfigTable.getCityMapParams
val minLat:Double = min(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val maxLat:Double = max(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val minLng:Double = min(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))
val maxLng:Double = max(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))

// In CSV format.
if (filetype.isDefined && filetype.get == "csv") {
val accessAttributesfile = new java.io.File("access_attributes.csv")
Expand Down Expand Up @@ -308,9 +312,17 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
* @param filetype
* @return
*/
def getAccessScoreNeighborhoodsV2(lat1: Double, lng1: Double, lat2: Double, lng2: Double, filetype: Option[String]) = UserAwareAction.async { implicit request =>
def getAccessScoreNeighborhoodsV2(lat1: Option[Double], lng1: Option[Double], lat2: Option[Double], lng2: Option[Double],
filetype: Option[String]) = UserAwareAction.async { implicit request =>
apiLogging(request.remoteAddress, request.identity, request.toString)
val coordinates = Array(min(lat1, lat2), max(lat1, lat2), min(lng1, lng2), max(lng1, lng2))

val cityMapParams: MapParams = ConfigTable.getCityMapParams
val minLat: Double = min(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val maxLat: Double = max(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val minLng: Double = min(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))
val maxLng: Double = max(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))
val coordinates = Array(minLat, maxLat, minLng, maxLng)

// In CSV format.
if (filetype.isDefined && filetype.get == "csv") {
val file: java.io.File = getAccessScoreNeighborhoodsCSV(version = 2, coordinates)
Expand Down Expand Up @@ -364,14 +376,14 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
val coordinates: Array[Coordinate] = geom.getCoordinates
val coordStr: String = "\"[" + coordinates.map(c => "(" + c.x + "," + c.y + ")").mkString(",") + "]\""
if (region.coverage > 0.0D) {
writer.println(region.name + "," + region.regionID + "," + region.score + "," + coordStr + "," +
writer.println("\"" + region.name + "\"," + region.regionID + "," + region.score + "," + coordStr + "," +
region.coverage + "," + region.attributeScores(0) + "," + region.attributeScores(1) + "," +
region.attributeScores(2) + "," + region.attributeScores(3) + "," + region.significanceScores(0) + "," +
region.significanceScores(1) + "," + region.significanceScores(2) + "," + region.significanceScores(3) + "," +
region.avgImageCaptureDate.map(_.toString).getOrElse("NA") + "," +
region.avgLabelDate.map(_.toString).getOrElse("NA"))
} else {
writer.println(region.name + "," + region.regionID + "," + "NA" + "," + coordStr + "," + 0.0 + "," + "NA" +
writer.println("\"" + region.name + "\"," + region.regionID + "," + "NA" + "," + coordStr + "," + 0.0 + "," + "NA" +
"," + "NA" + "," + "NA" + "," + "NA" + "," + region.significanceScores(0) + "," +
region.significanceScores(1) + "," + region.significanceScores(2) + "," + region.significanceScores(3) + "," +
"NA" + "," + "NA")
Expand All @@ -395,7 +407,7 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
val clusteredLabelLocations: List[LabelLocation] = clusterLabelLocations(labelLocations)
clusteredLabelLocations.map(l => AttributeForAccessScore(l.lat, l.lng, l.labelType, new Timestamp(0), new Timestamp(0), 1, 1))
case 2 =>
val globalAttributes: List[GlobalAttributeForAPI] = GlobalAttributeTable.getGlobalAttributesInBoundingBox(coordinates(0).toFloat, coordinates(2).toFloat, coordinates(1).toFloat, coordinates(3).toFloat, None)
val globalAttributes: List[GlobalAttributeForAPI] = GlobalAttributeTable.getGlobalAttributesInBoundingBox(coordinates(0), coordinates(2), coordinates(1), coordinates(3), None)
globalAttributes.map(l => AttributeForAccessScore(l.lat, l.lng, l.labelType, l.avgImageCaptureDate, l.avgLabelDate, l.imageCount, l.labelCount))
}
labelsForScore
Expand Down Expand Up @@ -545,9 +557,16 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
* @param lng2 Second longitude value for the bounding box
* @return The access score for the given neighborhood
*/
def getAccessScoreStreetsV2(lat1: Double, lng1: Double, lat2: Double, lng2: Double, filetype: Option[String]) = UserAwareAction.async { implicit request =>
def getAccessScoreStreetsV2(lat1: Option[Double], lng1: Option[Double], lat2: Option[Double], lng2: Option[Double], filetype: Option[String]) = UserAwareAction.async { implicit request =>
apiLogging(request.remoteAddress, request.identity, request.toString)
val streetAccessScores: List[AccessScoreStreet] = getAccessScoreStreetsGeneric(lat1, lng1, lat2, lng2, version = 2)

val cityMapParams: MapParams = ConfigTable.getCityMapParams
val minLat: Double = min(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val maxLat: Double = max(lat1.getOrElse(cityMapParams.lat1), lat2.getOrElse(cityMapParams.lat2))
val minLng: Double = min(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))
val maxLng: Double = max(lng1.getOrElse(cityMapParams.lng1), lng2.getOrElse(cityMapParams.lng2))

val streetAccessScores: List[AccessScoreStreet] = getAccessScoreStreetsGeneric(minLat, minLng, maxLat, maxLng, version = 2)
// In CSV format.
if (filetype.isDefined && filetype.get == "csv") {
val file = new java.io.File("access_score_streets.csv")
Expand Down Expand Up @@ -675,6 +694,13 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
val srid = 4326
val factory: GeometryFactory = new GeometryFactory(pm, srid)

// Generate and store (point, label) pairs
val points: List[(Point, AttributeForAccessScore)] = labelLocations.map { ll =>
val p: Point = factory.createPoint(new Coordinate(ll.lng.toDouble, ll.lat.toDouble))
(p, ll)
}

// Evaluate scores for each street
val streetAccessScores = streets.map { edge =>
// Expand each edge a little bit and count the number of accessibility attributes.
val buffer: Geometry = edge.street.geom.buffer(radius)
Expand All @@ -690,8 +716,9 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
var imageAgeSum: Float = 0
var nLabels: Int = 0
var nImages: Int = 0
labelLocations.foreach { ll =>
val p: Point = factory.createPoint(new Coordinate(ll.lng.toDouble, ll.lat.toDouble))
// Update street cluster values for each point that's close enough to the street
points.foreach { pointPair =>
val (p: Point, ll: AttributeForAccessScore) = pointPair
if (p.within(buffer) && labelCounter.contains(ll.labelType)) {
labelCounter(ll.labelType) += 1
imageAgeSum += ll.avgImageCaptureDate.getTime * ll.imageCount
Expand Down Expand Up @@ -839,6 +866,7 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
val writer = new java.io.PrintStream(sidewalkStatsFile)

val stats: ProjectSidewalkStats = LabelTable.getOverallStatsForAPI(filterLowQuality)
writer.println(s"Launch Date, ${stats.launchDate}")
writer.println(s"KM Explored,${stats.kmExplored}")
writer.println(s"KM Explored Without Overlap,${stats.kmExploreNoOverlap}")
writer.println(s"Total User Count,${stats.nUsers}")
Expand All @@ -862,6 +890,7 @@ class ProjectSidewalkAPIController @Inject()(implicit val env: Environment[User,
writer.println(s"$labType Disagreed Count,${accStats.nDisagree}")
writer.println(s"$labType Accuracy,${accStats.accuracy.map(_.toString).getOrElse("NA")}")
}

writer.close()
Future.successful(Ok.sendFile(content = sidewalkStatsFile, onClose = () => sidewalkStatsFile.delete()))
} else { // In JSON format.
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,10 @@ class TaskController @Inject() (implicit val env: Environment[User, SessionAuthe
for (label: LabelSubmission <- data.labels) {
val labelTypeId: Int = LabelTypeTable.labelTypeToId(label.labelType)

val existingLabel: Option[Label] = if (label.temporaryLabelId.isDefined && userOption.isDefined) {
LabelTable.find(label.temporaryLabelId.get, userOption.get.userId)
val existingLabel: Option[Label] = if (userOption.isDefined) {
LabelTable.find(label.temporaryLabelId, userOption.get.userId)
} else {
Logger.error("Received label with Null temporary_label_id or user_id")
Logger.error("Received label with Null user_id")
None
}

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/UserProfileController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class UserProfileController @Inject() (implicit val env: Environment[User, Sessi
case Some(user) =>
val userId: UUID = user.userId
if (user.role.getOrElse("") != "Anonymous") {
val allUserOrgs: List[Int] = UserOrgTable.getAllOrgs(userId);
val allUserOrgs: List[Int] = UserOrgTable.getAllOrgs(userId)
if (allUserOrgs.headOption.isEmpty) {
UserOrgTable.save(userId, orgId)
} else if (allUserOrgs.head != orgId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import java.util.UUID
import play.api.libs.json.{Reads, JsPath}
import play.api.libs.functional.syntax._

object UserRoleSubmissionFormats {
object AdminUpdateSubmissionFormats {
case class UserRoleSubmission(userId: String, roleId: String)
case class UserOrgSubmission(userId: String, orgId: Int)

implicit val userRoleSubmissionReads: Reads[UserRoleSubmission] = (
(JsPath \ "user_id").read[String] and
(JsPath \ "role_id").read[String]
)(UserRoleSubmission.apply _)

implicit val userOrgSubmissionReads: Reads[UserOrgSubmission] = (
(JsPath \ "user_id").read[String] and
(JsPath \ "org_id").read[Int]
)(UserOrgSubmission.apply _)
}
1 change: 1 addition & 0 deletions app/formats/json/APIFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object APIFormats {

def projectSidewalkStatsToJson(stats: ProjectSidewalkStats): JsObject = {
Json.obj(
"launch_date" -> stats.launchDate,
"km_explored" -> stats.kmExplored,
"km_explored_no_overlap" -> stats.kmExploreNoOverlap,
"user_counts" -> Json.obj(
Expand Down
2 changes: 1 addition & 1 deletion app/formats/json/LabelFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object LabelFormat {
(__ \ "gsv_panorama_id").write[String] and
(__ \ "label_type_id").write[Int] and
(__ \ "deleted").write[Boolean] and
(__ \ "temporary_label_id").writeNullable[Int] and
(__ \ "temporary_label_id").write[Int] and
(__ \ "time_created").write[Timestamp] and
(__ \ "tutorial").write[Boolean] and
(__ \ "street_edge_id").write[Int] and
Expand Down
2 changes: 1 addition & 1 deletion app/formats/json/TaskFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object TaskFormats {
(__ \ "task_start").write[Timestamp] and
(__ \ "task_end").write[Timestamp] and
(__ \ "label_id").writeNullable[Int] and
(__ \ "temporary_label_id").writeNullable[Int] and
(__ \ "temporary_label_id").write[Int] and
(__ \ "label_type").writeNullable[String]
)(unlift(AuditTaskWithALabel.unapply _))
}
4 changes: 2 additions & 2 deletions app/formats/json/TaskSubmissionFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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)
case class InteractionSubmission(action: String, gsvPanoramaId: Option[String], lat: Option[Float], lng: Option[Float], heading: Option[Float], pitch: Option[Float], zoom: Option[Int], note: Option[String], temporaryLabelId: Option[Int], timestamp: Long)
case class LabelPointSubmission(panoX: Int, panoY: Int, canvasX: Int, canvasY: Int, heading: Float, pitch: Float, zoom: Int, lat: Option[Float], lng: Option[Float], computationMethod: Option[String])
case class LabelSubmission(gsvPanoramaId: String, auditTaskId: Int, labelType: String, deleted: Boolean, severity: Option[Int], temporary: Boolean, description: Option[String], tagIds: Seq[Int], point: LabelPointSubmission, temporaryLabelId: Option[Int], timeCreated: Option[Long], tutorial: Boolean)
case class LabelSubmission(gsvPanoramaId: String, auditTaskId: Int, labelType: String, deleted: Boolean, severity: Option[Int], temporary: Boolean, description: Option[String], tagIds: Seq[Int], point: LabelPointSubmission, temporaryLabelId:Int, timeCreated: Option[Long], tutorial: Boolean)
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)
Expand Down Expand Up @@ -79,7 +79,7 @@ object TaskSubmissionFormats {
(JsPath \ "description").readNullable[String] and
(JsPath \ "tag_ids").read[Seq[Int]] and
(JsPath \ "label_point").read[LabelPointSubmission] and
(JsPath \ "temporary_label_id").readNullable[Int] and
(JsPath \ "temporary_label_id").read[Int] and
(JsPath \ "time_created").readNullable[Long] and
(JsPath \ "tutorial").read[Boolean]
)(LabelSubmission.apply _)
Expand Down
Loading

0 comments on commit 3df55ad

Please sign in to comment.