From 03aa29fdb4850057d136b1dbf7d844df2168be62 Mon Sep 17 00:00:00 2001 From: bailuk Date: Thu, 1 Jan 2026 16:11:59 +0100 Subject: [PATCH 1/3] #175 GTK: Add support for brouter API --- .../ch/bailu/aat_gtk/api/BrouterController.kt | 43 +++++++++++++++++++ .../aat_gtk/view/menu/provider/EditorMenu.kt | 2 + .../bailu/aat_gtk/view/toplevel/MainWindow.kt | 4 ++ .../bailu/aat_lib/api/brouter/BrouterApi.kt | 4 ++ .../java/ch/bailu/aat_lib/api/cm/CmApi.kt | 1 + .../aat_lib/api/nominatim/NominatimApi.kt | 4 +- .../api/nominatim/NominatimReverseApi.kt | 3 ++ .../aat_lib/dispatcher/source/EditorSource.kt | 2 +- .../dispatcher/source/FixedOverlaySource.kt | 6 +++ .../gpx/information/InformationUtil.kt | 12 ++++-- 10 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/BrouterController.kt diff --git a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/BrouterController.kt b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/BrouterController.kt new file mode 100644 index 000000000..9beea2f1b --- /dev/null +++ b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/BrouterController.kt @@ -0,0 +1,43 @@ +package ch.bailu.aat_gtk.api + +import ch.bailu.aat_gtk.controller.UiControllerInterface +import ch.bailu.aat_lib.api.brouter.BrouterApi +import ch.bailu.aat_lib.app.AppContext +import ch.bailu.aat_lib.gpx.GpxList +import ch.bailu.aat_lib.gpx.information.GpxInformationProvider +import ch.bailu.aat_lib.gpx.information.InfoID +import ch.bailu.aat_lib.logger.AppLog +import ch.bailu.aat_lib.preferences.map.overlay.SolidBrouterOverlay +import ch.bailu.gtk.gtk.Application +import ch.bailu.gtk.lib.handler.action.ActionHandler + +class BrouterController(app: Application, private val appContext: AppContext, private val gpxInformation: GpxInformationProvider, private val uiController: UiControllerInterface) { + val api = BrouterApi(SolidBrouterOverlay(appContext.dataDirectory)) + + init { + setAction(app, "brouter", { this.onAction() }) + } + + private fun onAction() { + val list = gpxInformation.getInfo().getGpxList() + if (validateList(list) && !api.isTaskRunning(appContext.services)) { + uiController.setOverlayEnabled(InfoID.BROUTER, true) + api.startTask(appContext, list) + } + } + + private fun setAction(app: Application, action: String, onActivate: ()->Unit) { + ActionHandler.get(app, action).apply { + disconnectSignals() + onActivate(onActivate) + } + } + + private fun validateList(list: GpxList): Boolean { + if (list.pointList.size() > 1 && list.pointList.size() <= 20) { + return true + } + AppLog.e(this, "Position count not supported: ${list.pointList.size()}. Must be between 2 and 20") + return false + } +} diff --git a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/EditorMenu.kt b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/EditorMenu.kt index 17ca28a70..6b34333bb 100644 --- a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/EditorMenu.kt +++ b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/EditorMenu.kt @@ -1,5 +1,6 @@ package ch.bailu.aat_gtk.view.menu.provider +import ch.bailu.aat_lib.api.brouter.BrouterApi import ch.bailu.aat_lib.dispatcher.EditorSourceInterface import ch.bailu.aat_lib.gpx.interfaces.GpxType import ch.bailu.aat_lib.resources.Res @@ -13,6 +14,7 @@ class EditorMenu(private val edit: EditorSourceInterface): MenuProviderInterface append(Res.str().edit_save(), "app.editSave") append(Res.str().edit_save_copy(), "app.editCopy") append(Res.str().edit_save_copy_to(), "app.editCopyTo") + append(BrouterApi.NAME, "app.brouter") append(Res.str().edit_inverse(), "app.editInverse") appendSubmenu(Res.str().edit_change_type(), Menu().apply { append("Track", "app.editTypeTrack") diff --git a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/toplevel/MainWindow.kt b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/toplevel/MainWindow.kt index 1b912605b..444f53a4c 100644 --- a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/toplevel/MainWindow.kt +++ b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/toplevel/MainWindow.kt @@ -1,5 +1,6 @@ package ch.bailu.aat_gtk.view.toplevel +import ch.bailu.aat_gtk.api.BrouterController import ch.bailu.aat_gtk.app.GtkAppConfig import ch.bailu.aat_gtk.app.GtkAppContext import ch.bailu.aat_gtk.app.exit @@ -115,6 +116,8 @@ class MainWindow(private val app: Application, private val appContext: AppContex window.onDestroy { exit(dispatcher, 0) } + + BrouterController(app, appContext, editorSource, this) } private fun clearEditor(onCleared: ()->Unit) { @@ -252,6 +255,7 @@ class MainWindow(private val app: Application, private val appContext: AppContex dispatcher.addOverlaySources(appContext, usageTrackers) dispatcher.addSource(FixedOverlaySource.createDraftSource(appContext, usageTrackers)) dispatcher.addSource(FixedOverlaySource.createPoiSource(appContext, usageTrackers)) + dispatcher.addSource(FixedOverlaySource.createBrouterSource(appContext, usageTrackers)) dispatcher.addSource(FixedOverlaySource.createCmSource(appContext, usageTrackers)) dispatcher.addSource(FixedOverlaySource.createNominatimReverseSource(appContext, usageTrackers)) dispatcher.addSource(editorSource) diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/api/brouter/BrouterApi.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/api/brouter/BrouterApi.kt index 45e231508..13a713e1f 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/api/brouter/BrouterApi.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/api/brouter/BrouterApi.kt @@ -39,4 +39,8 @@ class BrouterApi(overlay: SolidOverlayInterface) : Api(overlay) { } return builder.toString() } + + companion object { + const val NAME = "Brouter" + } } diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/api/cm/CmApi.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/api/cm/CmApi.kt index 2325d5de7..bd1c99861 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/api/cm/CmApi.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/api/cm/CmApi.kt @@ -18,5 +18,6 @@ class CmApi(baseDirectory: SolidDataDirectory) : Api(SolidCriticalMapOverlay(bas } companion object { const val ENABLED = false // TODO handle as experimental feature and enable via env variable + const val NAME = "Critical Map" } } diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimApi.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimApi.kt index e3179dec3..ee6c4d298 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimApi.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimApi.kt @@ -39,7 +39,9 @@ abstract class NominatimApi(context: AppContext) : DownloadApi(SolidNominatimOve } companion object { - const val POST = "&format=xml" + const val POST = "&format=xml" + const val NAME = "Nominatim" + private fun toString(b: BoundingBoxE6): String { return if (b.latitudeSpanE6 > 0 && b.longitudeSpanE6 > 0) { "&bounded=1&viewbox=" + diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimReverseApi.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimReverseApi.kt index ff440d297..4e5aba820 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimReverseApi.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/api/nominatim/NominatimReverseApi.kt @@ -21,4 +21,7 @@ class NominatimReverseApi(overlay: SolidOverlayInterface) : Api(overlay) { } } + companion object { + const val NAME = "Reverse" + } } diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/EditorSource.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/EditorSource.kt index dcac55e50..4cba230a9 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/EditorSource.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/EditorSource.kt @@ -19,7 +19,7 @@ class EditorSource(private val appContext: AppContext, usageTracker: UsageTracke EditorSourceInterface { private var target = TargetInterface.NULL - private val edit: EditorHelper = EditorHelper(appContext) + private val edit = EditorHelper(appContext) private val onFileEdited = BroadcastReceiver { args -> if (BroadcastData.has(args, edit.vID)) { requestUpdate() diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/FixedOverlaySource.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/FixedOverlaySource.kt index 647d895a9..727c59591 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/FixedOverlaySource.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/dispatcher/source/FixedOverlaySource.kt @@ -3,6 +3,7 @@ package ch.bailu.aat_lib.dispatcher.source import ch.bailu.aat_lib.app.AppContext import ch.bailu.aat_lib.dispatcher.usage.UsageTrackerInterface import ch.bailu.aat_lib.preferences.StorageInterface +import ch.bailu.aat_lib.preferences.map.overlay.SolidBrouterOverlay import ch.bailu.aat_lib.preferences.map.overlay.SolidCriticalMapOverlay import ch.bailu.aat_lib.preferences.map.overlay.SolidDraftOverlay import ch.bailu.aat_lib.preferences.map.overlay.SolidFixedOverlay @@ -51,6 +52,11 @@ class FixedOverlaySource(context: AppContext, usageTracker: UsageTrackerInterfac return FixedOverlaySource(context, usageTracker, overlay) } + fun createBrouterSource(context: AppContext, usageTracker: UsageTrackerInterface): FileSource { + val overlay = SolidBrouterOverlay(context.dataDirectory) + return FixedOverlaySource(context, usageTracker, overlay) + } + fun createCmSource(context: AppContext, usageTracker: UsageTrackerInterface): FileSource { val overlay = SolidCriticalMapOverlay(context.dataDirectory) return FixedOverlaySource(context, usageTracker, overlay) diff --git a/aat-lib/src/main/java/ch/bailu/aat_lib/gpx/information/InformationUtil.kt b/aat-lib/src/main/java/ch/bailu/aat_lib/gpx/information/InformationUtil.kt index c04234c95..7ecb84287 100644 --- a/aat-lib/src/main/java/ch/bailu/aat_lib/gpx/information/InformationUtil.kt +++ b/aat-lib/src/main/java/ch/bailu/aat_lib/gpx/information/InformationUtil.kt @@ -1,6 +1,9 @@ package ch.bailu.aat_lib.gpx.information +import ch.bailu.aat_lib.api.brouter.BrouterApi import ch.bailu.aat_lib.api.cm.CmApi +import ch.bailu.aat_lib.api.nominatim.NominatimApi +import ch.bailu.aat_lib.api.nominatim.NominatimReverseApi import ch.bailu.aat_lib.preferences.map.overlay.SolidCustomOverlayList import ch.bailu.aat_lib.resources.Res import ch.bailu.aat_lib.resources.ToDo @@ -12,14 +15,14 @@ object InformationUtil { InfoID.POI -> return Res.str().p_mapsforge_poi() InfoID.EDITOR_DRAFT -> return ToDo.translate("Draft") InfoID.TRACKER -> return Res.str().tracker() - InfoID.NOMINATIM -> return "Nominatim" + InfoID.NOMINATIM -> return NominatimApi.NAME InfoID.OVERPASS -> return Res.str().query_overpass() InfoID.EDITOR_OVERLAY -> return ToDo.translate("Editor") InfoID.FILE_VIEW -> return ToDo.translate("Selected File") InfoID.LIST_SUMMARY -> return ToDo.translate("List Summary") - InfoID.CRITICAL_MAP -> return "Critical Map" - InfoID.NOMINATIM_REVERSE -> return "Reverse" - InfoID.BROUTER -> return "Brouter" + InfoID.CRITICAL_MAP -> return CmApi.NAME + InfoID.NOMINATIM_REVERSE -> return NominatimReverseApi.NAME + InfoID.BROUTER -> return BrouterApi.NAME } if (isOverlay(iid)) { return "${ToDo.translate("Overlay")} ${getOverlayIndex(iid)}" @@ -46,6 +49,7 @@ object InformationUtil { add(InfoID.TRACKER) add(InfoID.LIST_SUMMARY) add(InfoID.NOMINATIM_REVERSE) + add(InfoID.BROUTER) if (CmApi.ENABLED) { add(InfoID.CRITICAL_MAP) } From c2bb24d9182a1b7672990570374c8bfeedfd40c1 Mon Sep 17 00:00:00 2001 From: bailuk Date: Thu, 1 Jan 2026 16:29:57 +0100 Subject: [PATCH 2/3] Extract nominatim reverse action to controller --- .../aat_gtk/api/NominatimReverseController.kt | 70 +++++++++++++++++++ .../view/menu/provider/LocationMenu.kt | 60 ++-------------- 2 files changed, 74 insertions(+), 56 deletions(-) create mode 100644 aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/NominatimReverseController.kt diff --git a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/NominatimReverseController.kt b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/NominatimReverseController.kt new file mode 100644 index 000000000..15fc6d6ce --- /dev/null +++ b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/api/NominatimReverseController.kt @@ -0,0 +1,70 @@ +package ch.bailu.aat_gtk.api + +import ch.bailu.aat_gtk.config.Strings +import ch.bailu.aat_gtk.controller.UiControllerInterface +import ch.bailu.aat_gtk.view.menu.MenuHelper +import ch.bailu.aat_lib.api.nominatim.NominatimReverseApi +import ch.bailu.aat_lib.app.AppContext +import ch.bailu.aat_lib.dispatcher.DispatcherInterface +import ch.bailu.aat_lib.dispatcher.TargetInterface +import ch.bailu.aat_lib.gpx.GpxPointNode +import ch.bailu.aat_lib.gpx.attributes.GpxAttributes +import ch.bailu.aat_lib.gpx.attributes.Keys +import ch.bailu.aat_lib.gpx.information.GpxInformation +import ch.bailu.aat_lib.gpx.information.InfoID +import ch.bailu.aat_lib.logger.AppLog +import ch.bailu.aat_lib.preferences.map.overlay.SolidNominatimReverseOverlay +import ch.bailu.gtk.gtk.Application +import org.mapsforge.map.view.MapView + +class NominatimReverseController(app: Application, + private val appContext: AppContext, + private val mapView: MapView, + private val uiController: UiControllerInterface): TargetInterface { + + private val soverlay = SolidNominatimReverseOverlay(appContext.dataDirectory) + private val reverseApi = NominatimReverseApi(soverlay) + private var isUpdated = false + + + init { + MenuHelper.setAction(app, Strings.ACTION_LOCATION_REVERSE) { onQueryAction() } + MenuHelper.setAction(app, Strings.ACTION_LOCATION_REVERSE_CENTER) { + uiController.centerInMap(soverlay.getIID()) + } + } + + + private fun onQueryAction() { + if (!reverseApi.isTaskRunning(appContext.services)) { + reverseApi.startTask( + appContext, + uiController.getMapBounding().center, + mapView.model.mapViewPosition.zoomLevel.toInt() + ) + soverlay.setEnabled(true) + isUpdated = true + } + } + private fun getMessage(attributes: GpxAttributes): String { + return attributes[Keys.toIndex("name")].ifEmpty { + attributes[Keys.toIndex("city")].ifEmpty { + attributes[Keys.toIndex("label")] + } + } + } + + override fun onContentUpdated(iid: Int, info: GpxInformation) { + if (isUpdated) { + val firstNode = info.getGpxList().pointList.first + if (firstNode is GpxPointNode) { + AppLog.i(this, getMessage(firstNode.getAttributes())) + } + isUpdated = false + } + } + + fun addToDispatcher(dispatcher: DispatcherInterface) { + dispatcher.addTarget(this, InfoID.NOMINATIM_REVERSE) + } +} diff --git a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/LocationMenu.kt b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/LocationMenu.kt index 3c3cd22c7..a622bddb9 100644 --- a/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/LocationMenu.kt +++ b/aat-gtk/src/main/kotlin/ch/bailu/aat_gtk/view/menu/provider/LocationMenu.kt @@ -1,5 +1,6 @@ package ch.bailu.aat_gtk.view.menu.provider +import ch.bailu.aat_gtk.api.NominatimReverseController import ch.bailu.aat_gtk.app.GtkAppContext import ch.bailu.aat_gtk.config.Strings import ch.bailu.aat_gtk.controller.ClipboardController @@ -7,18 +8,11 @@ import ch.bailu.aat_gtk.controller.UiControllerInterface import ch.bailu.aat_gtk.view.map.GtkCustomMapView import ch.bailu.aat_gtk.view.menu.MenuHelper import ch.bailu.aat_lib.api.cm.CmApi -import ch.bailu.aat_lib.api.nominatim.NominatimReverseApi import ch.bailu.aat_lib.app.AppContext import ch.bailu.aat_lib.dispatcher.DispatcherInterface -import ch.bailu.aat_lib.gpx.GpxPointNode -import ch.bailu.aat_lib.gpx.attributes.GpxAttributes -import ch.bailu.aat_lib.gpx.attributes.Keys -import ch.bailu.aat_lib.gpx.information.GpxInformation -import ch.bailu.aat_lib.gpx.information.InfoID import ch.bailu.aat_lib.logger.AppLog import ch.bailu.aat_lib.preferences.StorageInterface import ch.bailu.aat_lib.preferences.map.SolidMapGrid -import ch.bailu.aat_lib.preferences.map.overlay.SolidNominatimReverseOverlay import ch.bailu.aat_lib.preferences.system.SolidDataDirectory import ch.bailu.aat_lib.resources.Res import ch.bailu.aat_lib.resources.ToDo @@ -27,7 +21,6 @@ import ch.bailu.gtk.gio.Menu import ch.bailu.gtk.gtk.Application import ch.bailu.gtk.type.Str import org.mapsforge.map.view.MapView -import kotlin.text.ifEmpty class LocationMenu : MenuProviderInterface { override fun createMenu(): Menu { @@ -83,7 +76,9 @@ class LocationMenu : MenuProviderInterface { ) { createActionsClipboard(app, appContext.storage, display, uiController) - createActionsNominatimReverse(app, appContext, mapView, uiController, dispatcher) + NominatimReverseController(app, appContext, mapView, uiController).addToDispatcher( + dispatcher + ) createActionsCm(app, appContext.dataDirectory) } @@ -121,52 +116,5 @@ class LocationMenu : MenuProviderInterface { } } - - private fun createActionsNominatimReverse( - app: Application, - appContext: AppContext, - mapView: MapView, - uiController: UiControllerInterface, - dispatcher: DispatcherInterface - ) { - val soverlay = SolidNominatimReverseOverlay(appContext.dataDirectory) - val reverseApi = NominatimReverseApi(soverlay) - var isUpdated = false - - MenuHelper.setAction(app, Strings.ACTION_LOCATION_REVERSE) { - if (!reverseApi.isTaskRunning(GtkAppContext.services)) { - reverseApi.startTask( - GtkAppContext, - uiController.getMapBounding().center, - mapView.model.mapViewPosition.zoomLevel.toInt() - ) - soverlay.setEnabled(true) - isUpdated = true - } - } - - MenuHelper.setAction(app, Strings.ACTION_LOCATION_REVERSE_CENTER) { - uiController.centerInMap(soverlay.getIID()) - } - - - dispatcher.addTarget({ iid: Int, info: GpxInformation -> - if (isUpdated) { - val firstNode = info.getGpxList().pointList.first - if (firstNode is GpxPointNode) { - AppLog.i(this, getMessage(firstNode.getAttributes())) - } - isUpdated = false - } - }, InfoID.NOMINATIM_REVERSE) - } - - private fun getMessage(attributes: GpxAttributes): String { - return attributes[Keys.toIndex("name")].ifEmpty { - attributes[Keys.toIndex("city")].ifEmpty { - attributes[Keys.toIndex("label")] - } - } - } } } From fed6433f7a265e041b99db832158ea0beb3efa6c Mon Sep 17 00:00:00 2001 From: bailuk Date: Thu, 1 Jan 2026 17:27:44 +0100 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db86491b..a3504f673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,3 +22,4 @@ Lib: Use two decimal precision for average speed Android: Fix sensor scanning state handling for internal and Ble sensors. Android: Fix handling of unpaired power meters GTK: Add support for Nominatim reverse +GTK: Add support for brouter API