From 87caedf5debc30fb66d8c17dbfb286e98fdc8bbe Mon Sep 17 00:00:00 2001 From: Dave Craig Date: Thu, 30 Jan 2025 08:30:49 +0000 Subject: [PATCH] Finessing of auto callout 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. --- .../soundscape/geoengine/GeoEngine.kt | 67 +++++++++++++++-- .../geoengine/callouts/AutoCallout.kt | 75 ++++++++++++++----- .../soundscape/geoengine/utils/TileUtils.kt | 1 + 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/GeoEngine.kt b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/GeoEngine.kt index 3d52f8a7..96a18bbc 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/GeoEngine.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/GeoEngine.kt @@ -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(), diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/callouts/AutoCallout.kt b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/callouts/AutoCallout.kt index 413ed3d5..18bd50c1 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/callouts/AutoCallout.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/callouts/AutoCallout.kt @@ -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( @@ -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().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 { - // 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() @@ -206,9 +239,11 @@ class AutoCallout( val returnList = runBlocking { withContext(gridState.treeContext) { var list = emptyList() - // 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) @@ -237,4 +272,4 @@ class AutoCallout( companion object { private const val TAG = "AutoCallout" } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/utils/TileUtils.kt b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/utils/TileUtils.kt index 4c10d242..ec4b8504 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/utils/TileUtils.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/geoengine/utils/TileUtils.kt @@ -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) } } }