Skip to content

Commit

Permalink
Finessing of auto callout
Browse files Browse the repository at this point in the history
More work here to include some of the iOS app logic. UserGeometry is no
longer just a data class, but includes functions to request various
distances which are calculated from it's member variables. The main
different with iOS auto callouts, is that on iOS lots of the callouts
get discarded because there's already speech being played.
  • Loading branch information
davecraig committed Jan 30, 2025
1 parent 727f417 commit 87caedf
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,67 @@ class GeoEngine {
/** UserGeometry contains all of the data relating to the location and motion of the user. It's
* aim is simply to reduces the number of arguments to many of the API calls.
*/
data class UserGeometry(val location: LngLatAlt = LngLatAlt(),
var heading: Double = 0.0,
val fovDistance: Double = 50.0,
val inVehicle: Boolean = false,
val inMotion: Boolean = false,
val speed: Double = 0.0)
class UserGeometry(val location: LngLatAlt = LngLatAlt(),
var heading: Double = 0.0,
val fovDistance: Double = 50.0,
val inVehicle: Boolean = false,
val inMotion: Boolean = false,
val speed: Double = 0.0,
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
}

/**
* 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() : UserGeometry {
return UserGeometry(locationProvider.get(),
directionProvider.getCurrentDirection().toDouble(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.scottishtecharmy.soundscape.geoengine.utils.getCompassLabelFacingDire
import org.scottishtecharmy.soundscape.geoengine.utils.getDistanceToFeature
import org.scottishtecharmy.soundscape.geoengine.utils.polygonContainsCoordinates
import org.scottishtecharmy.soundscape.geoengine.utils.removeDuplicateOsmIds
import org.scottishtecharmy.soundscape.geojsonparser.geojson.Feature
import org.scottishtecharmy.soundscape.geojsonparser.geojson.Polygon

class AutoCallout(
Expand Down Expand Up @@ -160,42 +161,74 @@ class AutoCallout(
// Trim history based on location and current time
poiCalloutHistory.trim(userGeometry)

// We want to call out up to the 10 nearest POI that are within 50m range
val pois = gridState.getFeatureTree(TreeId.POIS).generateNearestFeatureCollection(
// We want to start off with a list of the 10 nearest POI that are within search range
val pois = gridState.getFeatureTree(TreeId.SELECTED_SUPER_CATEGORIES).generateNearestFeatureCollection(
userGeometry.location,
50.0,
userGeometry.getSearchDistance(),
10
)

for (feature in pois) {
val uniquelyNamedPOIs = emptyMap<String,Feature>().toMutableMap()
pois.features.filter { feature ->

// Skip the POI if it's coincident with where the current audio beacon is

val name = getTextForFeature(localizedContext, feature)
val category = feature.foreign?.get("category") as String?

// Check the history and if the POI has been called out recently then we skip it
val nearestPoint = getDistanceToFeature(userGeometry.location, feature)
val callout = TrackedCallout(name.text, nearestPoint.point, feature.geometry.type == "Point", name.generic)
if (poiCalloutHistory.find(callout)) {
Log.d(TAG, "Discard ${callout.callout}")
if(category == null) {
true
} else {
results.add(
PositionedString(
if (nearestPoint.distance > userGeometry.getTriggerRange(category)) {
// The POI is farther away than the category allows
true
} else {
// Check the history and if the POI has been called out recently then we skip it
val callout = TrackedCallout(
name.text,
nearestPoint.point,
NativeAudioEngine.EARCON_SENSE_POI,
),
)
// Add the entries to the history
poiCalloutHistory.add(callout)
feature.geometry.type == "Point",
name.generic
)
if (poiCalloutHistory.find(callout)) {
Log.d(TAG, "Discard ${callout.callout}")
// Filter out
true
} else {
if (!uniquelyNamedPOIs.containsKey(name.text)) {
// Don't filter out
uniquelyNamedPOIs[name.text] = feature
results.add(
PositionedString(
name.text,
nearestPoint.point,
NativeAudioEngine.EARCON_SENSE_POI,
),
)
poiCalloutHistory.add(callout)
false
} else {
true
}
}
}
}
}

return results
}

/**
* updateLocation is called whenever the current location changes. It works through the auto
* callout logic to determine which (if any) callouts need to be made. This is based on the iOS
* app logic.
* @param userGeometry The new state of the user location/speed etc.
* @param gridState The current state of the tile data
* @return A list of PositionedString callouts to be spoken
*/
fun updateLocation(userGeometry: UserGeometry,
gridState: GridState) : List<PositionedString> {

// The autoCallout logic comes straight from the iOS app.

// Check that the callout isn't disabled in the settings
if (!sharedPreferences.getBoolean(ALLOW_CALLOUTS_KEY, true)) {
return emptyList()
Expand All @@ -206,9 +239,11 @@ class AutoCallout(
val returnList = runBlocking {
withContext(gridState.treeContext) {
var list = emptyList<PositionedString>()
// buildCalloutForRoadSense

// buildCalloutForRoadSense builds a callout for
val roadSenseCallout = buildCalloutForRoadSense(userGeometry, gridState)
if (roadSenseCallout.isNotEmpty()) {
// If we have som
list = roadSenseCallout
} else {
val intersectionCallout = buildCalloutForIntersections(userGeometry, gridState)
Expand Down Expand Up @@ -237,4 +272,4 @@ class AutoCallout(
companion object {
private const val TAG = "AutoCallout"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ fun getPoiFeatureCollectionBySuperCategory(
feature.foreign?.let { foreign ->
if (foreign["feature_type"] == featureType || foreign["feature_value"] == featureType) {
tempFeatureCollection.addFeature(feature)
feature.foreign?.put("category",superCategory)
}
}
}
Expand Down

0 comments on commit 87caedf

Please sign in to comment.