Skip to content

Commit

Permalink
Refactor UserGeometry into its own file
Browse files Browse the repository at this point in the history
Move UserGeometry out of GeoEngine.
  • Loading branch information
davecraig committed Feb 5, 2025
1 parent e9f4777 commit 9f05dde
Show file tree
Hide file tree
Showing 21 changed files with 193 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,114 +74,7 @@ class GeoEngine {

private val streetPreview = StreetPreview()

/** UserGeometry contains all of the data relating to the location and motion of the user. It's
* aim is to reduces the number of arguments to many of the API calls and to concentrate some of
* the logic around heading choice.
*
* @param phoneHeading is the direction in which the phone is pointing
* @param travelHeading is the direction in which the phone is moving
* @param headHeading is the direction in which the head tracking is pointing
*
* On iOS there were two types of heading prioritization:
* collection: course (travel?), user (phone?), device (head?)
* presentation:user (phone?), course (travel?), device (head?)
*
* It's very hard to follow the Heading code, but because head tracking isn't always (usually)
* present and I think `user` must be the phone direction and `device` the head tracking
* direction.
*
* presentationHeading is used for audio beacons. This makes sense - it's the direction of the
* phone that is used, though I'm surprised it ever uses directly of travel.
* collectionHeading is used for intersections - the direction of travel is most significant
* here.
*
* However, if the user has thrown their phone into their bag, we need to detect this and ignore
* the phone direction.
*/
enum class HeadingMode {
Auto,
Phone,
Travel
}

class UserGeometry(val location: LngLatAlt = LngLatAlt(),
var phoneHeading: Double = 0.0,
val fovDistance: Double = 50.0,
val inVehicle: Boolean = false,
val inMotion: Boolean = false,
val speed: Double = 0.0,
private val headingMode: HeadingMode = HeadingMode.Auto,
private var travelHeading: Double = Double.NaN,
private var headHeading: Double = Double.NaN,
private val inStreetPreview: Boolean = false)
{
private val automotiveRangeMultiplier = 6.0
private val streetPreviewRangeIncrement = 10.0

private fun transform(distance: Double) : Double {
if(inVehicle) return distance * automotiveRangeMultiplier
if(inStreetPreview) return distance + streetPreviewRangeIncrement
return distance
}

fun heading() : Double {
when(headingMode) {
HeadingMode.Auto -> {
if(speed > 0.2 && !travelHeading.isNaN())
return travelHeading

return phoneHeading
}
HeadingMode.Phone -> return phoneHeading
HeadingMode.Travel -> return travelHeading
}
}

/**
* getSearchDistance returns the distance to use when searching for POIs
*/
fun getSearchDistance() : Double {
return transform(50.0)
}

/**
* getTriggerRange returns the distance to use when detecting POIs to call out
*/
fun getTriggerRange(category: String) : Double {
return when(category) {
"object",
"safety" -> transform(10.0)

"place",
"information",
"mobility" -> transform(20.0)

"landmark" -> transform(50.0)

else -> transform(0.0)
}
}

/**
* getTriggerRange returns the distance if a POI is still in proximity after a callout
*/
fun getProximityRange(category: String) : Double {
return when(category) {
"object",
"safety" -> transform(20.0)

"place",
"information",
"mobility" -> transform(30.0)

"landmark" -> transform(100.0)

else -> transform(0.0)
}
}
}

private fun getCurrentUserGeometry(headingMode: HeadingMode) : UserGeometry {
private fun getCurrentUserGeometry(headingMode: UserGeometry.HeadingMode) : UserGeometry {
return UserGeometry(
location = locationProvider.get(),
phoneHeading = directionProvider.getCurrentDirection().toDouble(),
Expand Down Expand Up @@ -278,7 +171,7 @@ class GeoEngine {
// falling back to use the phone direction.
if(!soundscapeService.isAudioEngineBusy()) {
val callouts =
autoCallout.updateLocation(getCurrentUserGeometry(HeadingMode.Auto), gridState)
autoCallout.updateLocation(getCurrentUserGeometry(UserGeometry.HeadingMode.Auto), gridState)
if (callouts.isNotEmpty()) {
// Tell the service that we've got some callouts to tell the user about
soundscapeService.speakCallout(callouts)
Expand Down Expand Up @@ -387,7 +280,7 @@ class GeoEngine {
results = runBlocking {
withContext(gridState.treeContext) {

val userGeometry = getCurrentUserGeometry(HeadingMode.Phone)
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)

// Direction order is: behind(0) left(1) ahead(2) right(3)
val featuresByDirection: Array<Feature?> = arrayOfNulls(4)
Expand Down Expand Up @@ -485,7 +378,7 @@ class GeoEngine {
val list: MutableList<PositionedString> = mutableListOf()

// get device direction
val userGeometry = getCurrentUserGeometry(HeadingMode.Phone)
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)

// Detect if there is a road or an intersection in the FOV
val roadsDescription = getRoadsDescriptionFromFov(
Expand Down Expand Up @@ -569,7 +462,7 @@ class GeoEngine {
val engine = this
CoroutineScope(Job()).launch(gridState.treeContext) {
// Get our current location and figure out what GO means
val userGeometry = getCurrentUserGeometry(HeadingMode.Phone)
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)
val choices = streetPreview.getDirectionChoices(engine, userGeometry.location)
var heading = 0.0
if(choices.isNotEmpty()) {
Expand Down Expand Up @@ -612,7 +505,7 @@ class GeoEngine {
val engine = this
CoroutineScope(Job()).launch(gridState.treeContext) {
// Get our current location and figure out what GO means
val userGeometry = getCurrentUserGeometry(HeadingMode.Phone)
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)
streetPreview.go(userGeometry, engine)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class StreetPreview {
previewState = PreviewState.INITIAL
}

fun go(userGeometry: GeoEngine.UserGeometry, engine: GeoEngine) {
fun go(userGeometry: UserGeometry, engine: GeoEngine) {
when (previewState) {

PreviewState.INITIAL -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.scottishtecharmy.soundscape.geoengine

import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt

/** UserGeometry contains all of the data relating to the location and motion of the user. It's
* aim is to reduces the number of arguments to many of the API calls and to concentrate some of
* the logic around heading choice.
*
* @param phoneHeading is the direction in which the phone is pointing
* @param travelHeading is the direction in which the phone is moving
* @param headHeading is the direction in which the head tracking is pointing
*
* On iOS there were two types of heading prioritization:
* collection: course (travel?), user (phone?), device (head?)
* presentation:user (phone?), course (travel?), device (head?)
*
* It's very hard to follow the Heading code, but because head tracking isn't always (usually)
* present and I think `user` must be the phone direction and `device` the head tracking
* direction.
*
* presentationHeading is used for audio beacons. This makes sense - it's the direction of the
* phone that is used, though I'm surprised it ever uses directly of travel.
* collectionHeading is used for intersections - the direction of travel is most significant
* here.
*
* However, if the user has thrown their phone into their bag, we need to detect this and ignore
* the phone direction.
*/
class UserGeometry(val location: LngLatAlt = LngLatAlt(),
var phoneHeading: Double = 0.0,
val fovDistance: Double = 50.0,
val inVehicle: Boolean = false,
val inMotion: Boolean = false,
val speed: Double = 0.0,
private val headingMode: HeadingMode = HeadingMode.Auto,
private var travelHeading: Double = Double.NaN,
private var headHeading: Double = Double.NaN,
private val inStreetPreview: Boolean = false)
{
private val automotiveRangeMultiplier = 6.0
private val streetPreviewRangeIncrement = 10.0

private fun transform(distance: Double) : Double {
if(inVehicle) return distance * automotiveRangeMultiplier
if(inStreetPreview) return distance + streetPreviewRangeIncrement
return distance
}

fun heading() : Double {
when(headingMode) {
HeadingMode.Auto -> {
if(speed > 0.2 && !travelHeading.isNaN())
return travelHeading

return phoneHeading
}
HeadingMode.Phone -> return phoneHeading
HeadingMode.Travel -> return travelHeading
}
}

/**
* getSearchDistance returns the distance to use when searching for POIs
*/
fun getSearchDistance() : Double {
return transform(50.0)
}

/**
* getTriggerRange returns the distance to use when detecting POIs to call out
*/
fun getTriggerRange(category: String) : Double {
return when(category) {
"object",
"safety" -> transform(10.0)

"place",
"information",
"mobility" -> transform(20.0)

"landmark" -> transform(50.0)

else -> transform(0.0)
}
}

/**
* getTriggerRange returns the distance if a POI is still in proximity after a callout
*/
fun getProximityRange(category: String) : Double {
return when(category) {
"object",
"safety" -> transform(20.0)

"place",
"information",
"mobility" -> transform(30.0)

"landmark" -> transform(100.0)

else -> transform(0.0)
}
}

enum class HeadingMode {
Auto,
Phone,
Travel
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext
import org.scottishtecharmy.soundscape.MainActivity.Companion.ALLOW_CALLOUTS_KEY
import org.scottishtecharmy.soundscape.R
import org.scottishtecharmy.soundscape.audio.NativeAudioEngine
import org.scottishtecharmy.soundscape.geoengine.GeoEngine.UserGeometry
import org.scottishtecharmy.soundscape.geoengine.UserGeometry
import org.scottishtecharmy.soundscape.geoengine.GridState
import org.scottishtecharmy.soundscape.geoengine.PositionedString
import org.scottishtecharmy.soundscape.geoengine.TreeId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package org.scottishtecharmy.soundscape.geoengine.callouts

import android.content.Context
import org.scottishtecharmy.soundscape.R
import org.scottishtecharmy.soundscape.geoengine.GeoEngine
import org.scottishtecharmy.soundscape.geoengine.TreeId
import org.scottishtecharmy.soundscape.geoengine.GridState
import org.scottishtecharmy.soundscape.geoengine.PositionedString
import org.scottishtecharmy.soundscape.geoengine.UserGeometry
import org.scottishtecharmy.soundscape.geoengine.filters.CalloutHistory
import org.scottishtecharmy.soundscape.geoengine.filters.TrackedCallout
import org.scottishtecharmy.soundscape.geoengine.utils.FeatureTree
import org.scottishtecharmy.soundscape.geoengine.utils.RelativeDirections
import org.scottishtecharmy.soundscape.geoengine.utils.Triangle
import org.scottishtecharmy.soundscape.geoengine.utils.checkWhetherIntersectionIsOfInterest
import org.scottishtecharmy.soundscape.geoengine.utils.getFovTriangle
import org.scottishtecharmy.soundscape.geoengine.utils.getIntersectionRoadNames
Expand All @@ -32,7 +31,7 @@ enum class ComplexIntersectionApproach {
}

data class RoadsDescription(val nearestRoad: Feature? = null,
val userGeometry: GeoEngine.UserGeometry = GeoEngine.UserGeometry(),
val userGeometry: UserGeometry = UserGeometry(),
val intersection: Feature? = null,
val intersectionRoads: FeatureCollection = FeatureCollection())

Expand All @@ -49,7 +48,7 @@ data class RoadsDescription(val nearestRoad: Feature? = null,
* intersection.
*/
fun getRoadsDescriptionFromFov(gridState: GridState,
userGeometry: GeoEngine.UserGeometry,
userGeometry: UserGeometry,
approach: ComplexIntersectionApproach
) : RoadsDescription {

Expand Down Expand Up @@ -115,7 +114,7 @@ fun getRoadsDescriptionFromFov(gridState: GridState,

// Create a set of relative direction polygons
val intersectionLocation = intersection!!.geometry as Point
val geometry = GeoEngine.UserGeometry(
val geometry = UserGeometry(
intersectionLocation.coordinates,
nearestRoadBearing,
5.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.scottishtecharmy.soundscape.geoengine.filters

import org.scottishtecharmy.soundscape.geoengine.GeoEngine
import org.scottishtecharmy.soundscape.geoengine.UserGeometry

/**
* This class acts as a filter for throttling the frequency of computation which is initiated by
Expand All @@ -9,16 +9,16 @@ import org.scottishtecharmy.soundscape.geoengine.GeoEngine

open class LocationUpdateFilter(private val minTimeMs: Long, private val minDistance: Double) {

private var lastLocation : GeoEngine.UserGeometry? = null
private var lastLocation : UserGeometry? = null
private var lastTime = 0L

fun update(userGeometry: GeoEngine.UserGeometry)
fun update(userGeometry: UserGeometry)
{
lastTime = System.currentTimeMillis()
lastLocation = userGeometry
}

private fun shouldUpdate(userGeometry: GeoEngine.UserGeometry,
private fun shouldUpdate(userGeometry: UserGeometry,
updateTimeInterval: Long,
updateDistanceInterval: Double)
: Boolean {
Expand All @@ -40,12 +40,12 @@ open class LocationUpdateFilter(private val minTimeMs: Long, private val minDist
return false
}

fun shouldUpdate(userGeometry: GeoEngine.UserGeometry) : Boolean {
fun shouldUpdate(userGeometry: UserGeometry) : Boolean {
return shouldUpdate(userGeometry, minTimeMs, minDistance)
}

private val inVehicleTimeIntervalMultiplier = 4
fun shouldUpdateActivity(userGeometry: GeoEngine.UserGeometry) : Boolean {
fun shouldUpdateActivity(userGeometry: UserGeometry) : Boolean {
if(userGeometry.inVehicle) {
// If travelling in a vehicle then the speed is used to determine how far has to be
// travelled before updating and the time is increased by a multiplier.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.scottishtecharmy.soundscape.geoengine.filters

import android.util.Log
import org.scottishtecharmy.soundscape.geoengine.GeoEngine
import org.scottishtecharmy.soundscape.geoengine.UserGeometry
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt

class TrackedCallout(
Expand Down Expand Up @@ -45,7 +45,7 @@ class CalloutHistory(private val expiryPeriod : Long = 60000) {
history.add(callout)
}

fun trim(userGeometry: GeoEngine.UserGeometry) {
fun trim(userGeometry: UserGeometry) {
val now = System.currentTimeMillis()
// TODO : Remove hardcoded expiry time and distance should be based on category
history.removeAll {
Expand Down
Loading

0 comments on commit 9f05dde

Please sign in to comment.