diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsUtils.java b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsUtils.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsUtils.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsUtils.java
@@ -21,6 +21,16 @@
 import it.reyboz.bustorino.backend.ServiceType;
 
 abstract public class GtfsUtils {
+
+    public static String stripGtfsPrefix(String routeID){
+        String[] explo = routeID.split(":");
+        //default is
+        String toParse = routeID;
+        if(explo.length>1) {
+            toParse = explo[1];
+        }
+        return toParse;
+    }
     public static Pair<ServiceType, String> getRouteInfoFromGTFS(String routeID){
         String[] explo = routeID.split(":");
         //default is
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -164,8 +164,10 @@
                             val topic = mapTopic(line)
                             if(elms.isEmpty())
                                 respondersMap.remove(line)
-                            else
+                            else {
                                 client.subscribe(topic, QoS.AtMostOnce.value, null, null)
+                                Log.d(DEBUG_TAG, "Resubscribed with topic $topic")
+                            }
                         }
                         Log.d(DEBUG_TAG, "Reconnected to MQTT Mato Client")
 
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -48,14 +48,10 @@
 import it.reyboz.bustorino.data.gtfs.MatoPattern
 import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
 import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
-import it.reyboz.bustorino.map.BusInfoWindow
-import it.reyboz.bustorino.map.BusPositionUtils
+import it.reyboz.bustorino.map.*
 import it.reyboz.bustorino.map.CustomInfoWindow.TouchResponder
-import it.reyboz.bustorino.map.LocationOverlay
-import it.reyboz.bustorino.map.MapViewModel
-import it.reyboz.bustorino.map.MarkerUtils
 import it.reyboz.bustorino.viewmodels.LinesViewModel
-import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import org.osmdroid.config.Configuration
@@ -129,13 +125,15 @@
     private var showOnTopOfLine = true
     private var recyclerInitDone = false
 
+    private var useMQTTPositions = true
+
     //position of live markers
     private val busPositionMarkersByTrip = HashMap<String,Marker>()
     private var busPositionsOverlay = FolderOverlay()
 
     private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
 
-    private val liveBusViewModel: MQTTPositionsViewModel by viewModels()
+    private val liveBusViewModel: LivePositionsViewModel by viewModels()
     @SuppressLint("SetTextI18n")
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
@@ -164,18 +162,25 @@
                 stopsRecyclerView.visibility = View.VISIBLE
 
                 viewModel.setMapShowing(false)
-                liveBusViewModel.stopPositionsListening()
+                liveBusViewModel.stopMatoUpdates()
                 switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_white_30))
             } else{
                 stopsRecyclerView.visibility = View.GONE
                 map.visibility = View.VISIBLE
                 viewModel.setMapShowing(true)
-                liveBusViewModel.requestPosUpdates(lineID)
+                if(useMQTTPositions)
+                    liveBusViewModel.requestMatoPosUpdates(lineID)
+                else
+                    liveBusViewModel.requestGTFSUpdates()
                 switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_list_30))
             }
         }
         viewModel.setRouteIDQuery(lineID)
 
+        val keySourcePositions = getString(R.string.pref_positions_source)
+        useMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
+            .getString(keySourcePositions, "mqtt").contentEquals("mqtt")
+
         viewModel.patternsWithStopsByRouteLiveData.observe(viewLifecycleOwner){
             patterns -> savePatternsToShow(patterns)
         }
@@ -229,6 +234,7 @@
                 //DO NOTHING
                 return@observe
             }
+            val filtdLineID = GtfsUtils.stripGtfsPrefix(lineID)
             //filter buses with direction, show those only with the same direction
             val outmap = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
             val currentPattern = viewingPattern!!.pattern
@@ -236,21 +242,28 @@
             Log.d(DEBUG_TAG, "Got $numUpds updates, current pattern is: ${currentPattern.name}, directionID: ${currentPattern.directionId}")
             val patternsDirections = HashMap<String,Int>()
             for((tripId, pair) in it.entries){
+                //remove trips with wrong line ideas
+                if(pair.first.routeID!=filtdLineID)
+                    continue
 
                 if(pair.second!=null && pair.second?.pattern !=null){
                     val dir = pair.second?.pattern?.directionId
                     if(dir !=null && dir == currentPattern.directionId){
-                        outmap.set(tripId, pair)
+                        outmap[tripId] = pair
                     }
                     patternsDirections.set(tripId,if (dir!=null) dir else -10)
                 } else{
                     outmap[tripId] = pair
                     //Log.d(DEBUG_TAG, "No pattern for tripID: $tripId")
-                    patternsDirections.set(tripId, -10)
+                    patternsDirections[tripId] = -10
                 }
             }
             Log.d(DEBUG_TAG, " Filtered updates are ${outmap.keys.size}") // Original updates directs: $patternsDirections\n
             updateBusPositionsInMap(outmap)
+            //if not using MQTT positions
+            if(!useMQTTPositions){
+                liveBusViewModel.requestDelayedGTFSUpdates(2000)
+            }
         }
 
         //download missing tripIDs
@@ -653,7 +666,16 @@
         Log.d(DEBUG_TAG, "Resetting paused from onResume")
         pausedFragment = false
 
-        liveBusViewModel.requestPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+        val keySourcePositions = getString(R.string.pref_positions_source)
+        useMQTTPositions = PreferenceManager.getDefaultSharedPreferences(requireContext())
+            .getString(keySourcePositions, "mqtt").contentEquals("mqtt")
+
+        //separate paths
+        if(useMQTTPositions)
+            liveBusViewModel.requestMatoPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+        else
+            liveBusViewModel.requestGTFSUpdates()
+
 
         if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
             Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
@@ -677,7 +699,7 @@
 
     override fun onPause() {
         super.onPause()
-        liveBusViewModel.stopPositionsListening()
+        liveBusViewModel.stopMatoUpdates()
         pausedFragment = true
         //save map
         val center = map.mapCenter
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
@@ -50,7 +50,8 @@
 import it.reyboz.bustorino.data.gtfs.MatoPattern;
 import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops;
 import it.reyboz.bustorino.map.*;
-import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel;
+import it.reyboz.bustorino.viewmodels.GtfsPositionsViewModel;
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel;
 import it.reyboz.bustorino.viewmodels.StopsMapViewModel;
 import org.osmdroid.api.IGeoPoint;
 import org.osmdroid.api.IMapController;
@@ -120,8 +121,9 @@
     //the ViewModel from which we get the stop to display in the map
     private StopsMapViewModel stopsViewModel;
 
-    //private GTFSPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class);
-    private MQTTPositionsViewModel positionsViewModel;
+    //private GtfsPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class);
+    private LivePositionsViewModel livePositionsViewModel;
+    private Boolean useMQTTViewModel = true;
 
     private final HashMap<String,Marker> busPositionMarkersByTrip = new HashMap<>();
     private FolderOverlay busPositionsOverlay = null;
@@ -230,6 +232,11 @@
         //set map not done
         hasMapStartFinished = false;
 
+        String keySourcePositions=getString(R.string.pref_positions_source);
+        useMQTTViewModel = (
+                PreferenceManager.getDefaultSharedPreferences(requireContext())
+                        .getString(keySourcePositions,"mqtt").contentEquals("mqtt"));
+
 
         //Start map from bundle
         if (savedInstanceState !=null)
@@ -283,11 +290,12 @@
     public void onAttach(@NonNull Context context) {
         super.onAttach(context);
 
-        //gtfsPosViewModel = new ViewModelProvider(this).get(GTFSPositionsViewModel.class);
-        //viewModel
         ViewModelProvider provider = new ViewModelProvider(this);
-        positionsViewModel = provider.get(MQTTPositionsViewModel.class);
+        //gtfsPosViewModel = provider.get(GtfsPositionsViewModel.class);
+        livePositionsViewModel = provider.get(LivePositionsViewModel.class);
         stopsViewModel = provider.get(StopsMapViewModel.class);
+
+
         if (context instanceof FragmentListenerMain) {
             listenerMain = (FragmentListenerMain) context;
         } else {
@@ -316,7 +324,7 @@
             }
         }
         tripMarkersAnimators.clear();
-        positionsViewModel.stopPositionsListening();
+        if(useMQTTViewModel)   livePositionsViewModel.stopMatoUpdates();
 
     }
 
@@ -347,21 +355,58 @@
         bundle.putDouble(MAP_CURRENT_ZOOM_KEY, map.getZoomLevelDouble());
     }
 
+
     @Override
     public void onResume() {
         super.onResume();
+        //TODO: cleanup duplicate code (maybe merging the positions classes?)
         if(listenerMain!=null) listenerMain.readyGUIfor(FragmentKind.MAP);
-        if(positionsViewModel !=null) {
+        /// choose which to use
+        String keySourcePositions=getString(R.string.pref_positions_source);
+        useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext())
+                .getString(keySourcePositions,"mqtt").contentEquals("mqtt");
+        if(livePositionsViewModel !=null) {
             //gtfsPosViewModel.requestUpdates();
-            positionsViewModel.requestPosUpdates(MQTTMatoClient.LINES_ALL);
+            if(useMQTTViewModel)
+                livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL);
+            else
+                livePositionsViewModel.requestGTFSUpdates();
             //mapViewModel.testCascade();
-            positionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
+            livePositionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
+                Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat);
+                //gtfsPosViewModel.downloadTripsFromMato(dat);
+                MatoTripsDownloadWorker.Companion.downloadTripsFromMato(dat,
+                        requireContext().getApplicationContext(),
+                        "BusTO-MatoTripDownload");
+            });
+        } /*else if(gtfsPosViewModel!=null){
+            gtfsPosViewModel.requestUpdates();
+            gtfsPosViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
                 Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat);
                 //gtfsPosViewModel.downloadTripsFromMato(dat);
                 MatoTripsDownloadWorker.Companion.downloadTripsFromMato(dat,getContext().getApplicationContext(),
                         "BusTO-MatoTripDownload");
             });
         }
+        */
+        else Log.e(DEBUG_TAG, "livePositionsViewModel is null at onResume");
+    }
+
+    private void startRequestsPositions(){
+        if (livePositionsViewModel != null) {
+            //should always be the case
+            livePositionsViewModel.getUpdatesWithTripAndPatterns().observe(getViewLifecycleOwner(), data -> {
+                Log.d(DEBUG_TAG, "Have " + data.size() + " trip updates, has Map start finished: " + hasMapStartFinished);
+                if (hasMapStartFinished) updateBusPositionsInMap(data);
+
+                if(!isDetached() && !useMQTTViewModel)
+                    livePositionsViewModel.requestDelayedGTFSUpdates(3000);
+
+            });
+
+        } else {
+            Log.e(DEBUG_TAG, "PositionsViewModel is null");
+        }
     }
 
     @Override
@@ -543,19 +588,7 @@
             //Log.i(DEBUG_TAG, "Null bus positions overlay,redo");
             busPositionsOverlay = new FolderOverlay();
         }
-
-
-        if(positionsViewModel !=null){
-            //should always be the case
-            positionsViewModel.getUpdatesWithTripAndPatterns().observe(getViewLifecycleOwner(), data->{
-                Log.d(DEBUG_TAG, "Have "+data.size()+" trip updates, has Map start finished: "+hasMapStartFinished);
-                if (hasMapStartFinished) updateBusPositionsInMap(data);
-                //if(!isDetached())
-                //    gtfsPosViewModel.requestDelayedUpdates(4000);
-            });
-        } else {
-            Log.e(DEBUG_TAG, "PositionsViewModel is null");
-        }
+        startRequestsPositions();
         if(stopsViewModel !=null){
 
             stopsViewModel.getStopsInBoundingBox().observe(getViewLifecycleOwner(),
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
@@ -12,7 +12,7 @@
 import androidx.fragment.app.viewModels
 import it.reyboz.bustorino.R
 import it.reyboz.bustorino.backend.mato.MQTTMatoClient
-import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
 
 
 /**
@@ -30,7 +30,7 @@
 
     private lateinit var lineEditText: EditText
 
-    private val mqttViewModel: MQTTPositionsViewModel by viewModels()
+    private val mqttViewModel: LivePositionsViewModel by viewModels()
 
     /*private val requestListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
         override fun onResponse(response: ArrayList<GtfsPositionUpdate>?) {
@@ -91,12 +91,12 @@
                  */
                 subscribed = if(subscribed){
                     //mqttMatoClient.desubscribe(listener)
-                    mqttViewModel.stopPositionsListening()
+                    mqttViewModel.stopMatoUpdates()
                     buttonLaunch.text="Start"
                     false
                 } else{
                     //mqttMatoClient.startAndSubscribe(lineEditText.text.trim().toString(), listener)
-                    mqttViewModel.requestPosUpdates(lineEditText.text.trim().toString())
+                    mqttViewModel.requestMatoPosUpdates(lineEditText.text.trim().toString())
                     buttonLaunch.text="Stop"
                     true
                 }
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
rename from app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt
rename to app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
@@ -32,7 +32,7 @@
 /**
  * View Model for the map. For containing the stops, the trips and whatever
  */
-class GTFSPositionsViewModel(application: Application): AndroidViewModel(application) {
+class GtfsPositionsViewModel(application: Application): AndroidViewModel(application) {
     private val gtfsRepo = GtfsRepository(application)
 
     private val netVolleyManager = NetworkVolleyManager.getInstance(application)
@@ -62,11 +62,7 @@
 
     }
     private val positionRequestErrorListener = Response.ErrorListener {
-        //error listener, it->VolleyError
         Log.e(DEBUG_TI, "Could not download the update, error:\n"+it.stackTrace)
-
-        //TODO: launch again if needed
-
     }
 
     fun requestUpdates(){
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
rename from app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
rename to app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
@@ -20,29 +20,38 @@
 import android.app.Application
 import android.util.Log
 import androidx.lifecycle.*
+import com.android.volley.Response
+import it.reyboz.bustorino.backend.NetworkVolleyManager
+import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
 import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
 import it.reyboz.bustorino.backend.mato.MQTTMatoClient
 import it.reyboz.bustorino.data.GtfsRepository
 import it.reyboz.bustorino.data.MatoPatternsDownloadWorker
 import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
 
-typealias UpdatesMap = HashMap<String, LivePositionUpdate>
-
-class MQTTPositionsViewModel(application: Application): AndroidViewModel(application) {
+class LivePositionsViewModel(application: Application): AndroidViewModel(application) {
 
     private val gtfsRepo = GtfsRepository(application)
 
     //private val updates = UpdatesMap()
     private val updatesLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
+    private val netVolleyManager = NetworkVolleyManager.getInstance(application)
+
 
     private var mqttClient = MQTTMatoClient.getInstance()
 
     private var lineListening = ""
     private var lastTimeReceived: Long = 0
 
-    private val positionListener = MQTTMatoClient.Companion.MQTTMatoListener{
+    private val gtfsRtRequestRunning = MutableLiveData<Boolean>(false)
+
+    /**
+     * Responder to the MQTT Client
+     */
+    private val matoPositionListener = MQTTMatoClient.Companion.MQTTMatoListener{
 
         val mupds = ArrayList<LivePositionUpdate>()
         if(lineListening==MQTTMatoClient.LINES_ALL){
@@ -132,20 +141,20 @@
     }
 
 
-    fun requestPosUpdates(line: String){
+    fun requestMatoPosUpdates(line: String){
         lineListening = line
         viewModelScope.launch {
-            mqttClient.startAndSubscribe(line,positionListener, getApplication())
+            mqttClient.startAndSubscribe(line,matoPositionListener, getApplication())
         }
 
 
         //updatePositions(1000)
     }
 
-    fun stopPositionsListening(){
+    fun stopMatoUpdates(){
         viewModelScope.launch {
             val tt = System.currentTimeMillis()
-            mqttClient.desubscribe(positionListener)
+            mqttClient.desubscribe(matoPositionListener)
             val time = System.currentTimeMillis() -tt
             Log.d(DEBUG_TI, "Took $time ms to unsubscribe")
         }
@@ -157,8 +166,51 @@
             updatesLiveData.postValue(updatesLiveData.value)
         }
     }
+    //Gtfs Real time
+
+
+    private val gtfsPositionsReqListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
+        override fun onResponse(response: ArrayList<LivePositionUpdate>?) {
+            Log.i(DEBUG_TI,"Got response from the GTFS RT server")
+            response?.let {it:ArrayList<LivePositionUpdate> ->
+                if (it.size == 0) {
+                    Log.w(DEBUG_TI,"No position updates from the GTFS RT server")
+                    return
+                }
+                else {
+                    //Log.i(DEBUG_TI, "Posting value to positionsLiveData")
+                    viewModelScope.launch { updatesLiveData.postValue(it) }
+
+                }
+            }
+            gtfsRtRequestRunning.postValue(false)
+
+        }
+
+    }
+    private val positionRequestErrorListener = Response.ErrorListener {
+        Log.e(DEBUG_TI, "Could not download the update, error:\n"+it.stackTrace)
+        gtfsRtRequestRunning.postValue(false)
+    }
+
+    fun requestGTFSUpdates(){
+        if(gtfsRtRequestRunning.value == null || !gtfsRtRequestRunning.value!!) {
+            val request = GtfsRtPositionsRequest(positionRequestErrorListener, gtfsPositionsReqListener)
+            netVolleyManager.requestQueue.add(request)
+            Log.i(DEBUG_TI, "Requested GTFS realtime position updates")
+            gtfsRtRequestRunning.value = true
+        }
+
+    }
+
+    fun requestDelayedGTFSUpdates(timems: Long){
+        viewModelScope.launch {
+            delay(timems)
+            requestGTFSUpdates()
+        }
+    }
 
     companion object{
         private const val DEBUG_TI = "BusTO-MQTTLiveData"
     }
 }
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -206,7 +206,12 @@
     <string name="pref_shown_startup">Schermata da mostrare all\'avvio</string>
     <string name="pref_shown_startup_def_desc">Tocca per cambiare</string>
 
+    <string name="positions_source_pref_title">Fonte posizioni in tempo reale di bus e tram</string>
 
+    <array name="positions_source_sel">
+        <item>MaTO (aggiornate più spesso, può non funzionare)</item>
+        <item>GTFS RT (più stabile)</item>
+    </array>
 
     <!-- lines -->
     <string name="long_press_stop_4_options">Tocca a lungo la fermata per le opzioni</string>
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -23,4 +23,11 @@
         <item>gttjsonfetcher</item>
         <item>fivetscraper</item>
     </array>
+
+    <string name="pref_positions_source">pref_positions_source</string>
+    <array name="positions_source_values">
+        <item>mqtt</item>
+        <item>gtfsrt</item>
+    </array>
+
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -243,6 +243,12 @@
         <item>@string/lines</item>
     </array>
 
+    <string name="positions_source_pref_title">Source of real time positions for buses and trams</string>
+    <array name="positions_source_sel">
+        <item>MaTO (updated more frequently, might be offline)</item>
+        <item>GTFS RT (more stable, less frequently updated)</item>
+    </array>
+
     <string name="remove_all_trips">Remove all GTFS trips info</string>
     <string name="all_trips_removed">All GTFS trips have been removed from the database</string>
 </resources>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -10,6 +10,13 @@
             android:title="@string/pref_shown_startup"
             android:summary="%s"
             />
+
+        <androidx.preference.ListPreference
+                android:key="@string/pref_positions_source"
+                android:entries="@array/positions_source_sel"
+                android:entryValues="@array/positions_source_values"
+                android:title="@string/positions_source_pref_title"
+            />
     </androidx.preference.PreferenceCategory>
 
     <androidx.preference.PreferenceCategory