diff --git a/app/build.gradle b/app/build.gradle --- a/app/build.gradle +++ b/app/build.gradle @@ -97,8 +97,6 @@ implementation 'org.jsoup:jsoup:1.15.3' implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' implementation 'com.android.volley:volley:1.2.1' - - implementation 'org.osmdroid:osmdroid-android:6.1.18' //maplibre implementation 'org.maplibre.gl:android-sdk:11.8.6' implementation 'org.maplibre.gl:android-sdk-turf:6.0.1' diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -718,8 +718,8 @@ void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){ final FragmentManager fm = getSupportFragmentManager(); final FragmentTransaction ft = fm.beginTransaction(); - final MapLibreFragment fragment = MapLibreFragment.Companion.newInstance(stop); - ft.replace(R.id.mainActContentFrame, fragment, MapFragmentKt.FRAGMENT_TAG); + final MapLibreFragment fragment = MapLibreFragment.newInstance(stop); + ft.replace(R.id.mainActContentFrame, fragment, MapLibreFragment.FRAGMENT_TAG); if (addToBackStack) ft.addToBackStack(null); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); diff --git a/app/src/main/java/it/reyboz/bustorino/backend/GPSPoint.java b/app/src/main/java/it/reyboz/bustorino/backend/GPSPoint.java --- a/app/src/main/java/it/reyboz/bustorino/backend/GPSPoint.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/GPSPoint.java @@ -1,8 +1,7 @@ package it.reyboz.bustorino.backend; -import org.osmdroid.api.IGeoPoint; -public class GPSPoint implements IGeoPoint { +public class GPSPoint { public final double latitude; public final double longitude; @@ -12,22 +11,18 @@ this.longitude = longitude; } - @Override public int getLatitudeE6() { return (int) (latitude*1e6d); } - @Override public int getLongitudeE6() { return (int) (longitude*1e6d); } - @Override public double getLatitude() { return latitude; } - @Override public double getLongitude() { return longitude; } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/RealtimeVehicle.java b/app/src/main/java/it/reyboz/bustorino/backend/RealtimeVehicle.java --- a/app/src/main/java/it/reyboz/bustorino/backend/RealtimeVehicle.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/RealtimeVehicle.java @@ -1,24 +1,23 @@ package it.reyboz.bustorino.backend; -import org.osmdroid.util.GeoPoint; public class RealtimeVehicle { - private final GeoPoint location; + private final GPSPoint location; private final float bearing; public final int updateTimestamp; private final String vehicleLabel; private String routeID; - public RealtimeVehicle(GeoPoint location, float bearing, int updateTimestamp, String vehicleLabel) { + public RealtimeVehicle(GPSPoint location, float bearing, int updateTimestamp, String vehicleLabel) { this.location = location; this.bearing = bearing; this.updateTimestamp = updateTimestamp; this.vehicleLabel = vehicleLabel; } - public GeoPoint getLocation() { + public GPSPoint getLocation() { return location; } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java --- a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java @@ -24,7 +24,7 @@ import androidx.annotation.Nullable; import it.reyboz.bustorino.util.LinesNameSorter; -import org.osmdroid.api.IGeoPoint; +import org.jetbrains.annotations.NotNull; import java.net.URLEncoder; import java.util.ArrayList; @@ -313,7 +313,7 @@ } - public Double getDistanceFromLocation(IGeoPoint loc){ + public Double getDistanceFromLocation(@NotNull GPSPoint loc){ return getDistanceFromLocation(loc.getLatitude(), loc.getLongitude()); } public Double getDistanceFromLocation(double latitude, double longitude){ diff --git a/app/src/main/java/it/reyboz/bustorino/backend/utils.java b/app/src/main/java/it/reyboz/bustorino/backend/utils.java --- a/app/src/main/java/it/reyboz/bustorino/backend/utils.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/utils.java @@ -50,6 +50,8 @@ public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){ + + final double phi1 = Math.toRadians(lat1); final double phi2 = Math.toRadians(lat2); 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 @@ -514,7 +514,7 @@ this.map = mapReady val context = requireContext() - val mjson = Styles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json") + val mjson = MapLibreStyles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json") activity?.run { val builder = Style.Builder().fromJson(mjson!!) diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java +++ /dev/null @@ -1,837 +0,0 @@ -/* - BusTO - Fragments components - Copyright (C) 2020 Andrea Ugo - Copyright (C) 2021 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.fragments; - -import android.Manifest; -import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; -import android.content.Context; - -import android.graphics.drawable.Drawable; -import android.location.Location; -import android.location.LocationManager; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.res.ResourcesCompat; -import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; - -import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate; -import it.reyboz.bustorino.backend.mato.MQTTMatoClient; -import it.reyboz.bustorino.backend.utils; -import it.reyboz.bustorino.data.gtfs.MatoPattern; -import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops; -import it.reyboz.bustorino.map.*; -import it.reyboz.bustorino.viewmodels.LivePositionsViewModel; -import it.reyboz.bustorino.viewmodels.StopsMapViewModel; -import org.osmdroid.api.IGeoPoint; -import org.osmdroid.api.IMapController; -import org.osmdroid.config.Configuration; -import org.osmdroid.events.DelayedMapListener; -import org.osmdroid.events.MapListener; -import org.osmdroid.events.ScrollEvent; -import org.osmdroid.events.ZoomEvent; -import org.osmdroid.tileprovider.tilesource.TileSourceFactory; -import org.osmdroid.util.BoundingBox; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.FolderOverlay; -import org.osmdroid.views.overlay.Marker; -import org.osmdroid.views.overlay.infowindow.InfoWindow; -import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; - -import java.util.*; - -import kotlin.Pair; -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.middleware.GeneralActivity; -import it.reyboz.bustorino.util.Permissions; - -import static it.reyboz.bustorino.fragments.SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE; - -public class MapFragment extends ScreenBaseFragment { - - //private static final String TAG = "Busto-MapActivity"; - private static final String MAP_CURRENT_ZOOM_KEY = "map-current-zoom"; - private static final String MAP_CENTER_LAT_KEY = "map-center-lat"; - private static final String MAP_CENTER_LON_KEY = "map-center-lon"; - private static final String FOLLOWING_LOCAT_KEY ="following"; - - public static final String BUNDLE_LATIT = "lat"; - public static final String BUNDLE_LONGIT = "lon"; - public static final String BUNDLE_NAME = "name"; - public static final String BUNDLE_ID = "ID"; - public static final String BUNDLE_ROUTES_STOPPING = "routesStopping"; - - public static final String FRAGMENT_TAG="BusTOMapFragment"; - - - private static final double DEFAULT_CENTER_LAT = 45.0708; - private static final double DEFAULT_CENTER_LON = 7.6858; - private static final double POSITION_FOUND_ZOOM = 18.3; - public static final double NO_POSITION_ZOOM = 17.1; - - private static final String DEBUG_TAG=FRAGMENT_TAG; - - protected FragmentListenerMain listenerMain; - - private HashSet shownStops = null; - - - private MapView map = null; - public Context ctx; - private LocationOverlay mLocationOverlay = null; - private FolderOverlay stopsFolderOverlay = null; - private Bundle savedMapState = null; - protected ImageButton btCenterMap; - protected ImageButton btFollowMe; - - protected CoordinatorLayout coordLayout; - private boolean hasMapStartFinished = false; - private boolean followingLocation = false; - - //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 LivePositionsViewModel livePositionsViewModel; - private Boolean useMQTTViewModel = true; - - private final HashMap busPositionMarkersByTrip = new HashMap<>(); - private FolderOverlay busPositionsOverlay = null; - - private final HashMap tripMarkersAnimators = new HashMap<>(); - - protected final CustomInfoWindow.TouchResponder responder = new CustomInfoWindow.TouchResponder() { - @Override - public void onActionUp(@NonNull String stopID, @Nullable String stopName) { - if (listenerMain!= null){ - Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: "+stopID); - listenerMain.requestArrivalsForStopID(stopID); - } - } - }; - protected final LocationOverlay.OverlayCallbacks locationCallbacks = new LocationOverlay.OverlayCallbacks() { - @Override - public void onDisableFollowMyLocation() { - updateGUIForLocationFollowing(false); - followingLocation=false; - } - - @Override - public void onEnableFollowMyLocation() { - updateGUIForLocationFollowing(true); - followingLocation=true; - } - }; - - private final ActivityResultLauncher positionRequestLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { - if (result == null){ - Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?"); - } - else if(Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_COARSE_LOCATION)) && - Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))){ - - map.getOverlays().remove(mLocationOverlay); - startLocationOverlay(true, map); - if(getContext()==null || getContext().getSystemService(Context.LOCATION_SERVICE)==null) - return; - LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); - @SuppressLint("MissingPermission") - Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (userLocation != null) { - map.getController().setZoom(POSITION_FOUND_ZOOM); - GeoPoint startPoint = new GeoPoint(userLocation); - setLocationFollowing(true); - map.getController().setCenter(startPoint); - } - } - else Log.w(DEBUG_TAG,"No location permission"); - }); - - public MapFragment() { - } - public static MapFragment getInstance(){ - return new MapFragment(); - } - public static MapFragment getInstance(@NonNull Stop stop){ - MapFragment fragment= new MapFragment(); - Bundle args = new Bundle(); - args.putDouble(BUNDLE_LATIT, stop.getLatitude()); - args.putDouble(BUNDLE_LONGIT, stop.getLongitude()); - args.putString(BUNDLE_NAME, stop.getStopDisplayName()); - args.putString(BUNDLE_ID, stop.ID); - args.putString(BUNDLE_ROUTES_STOPPING, stop.routesThatStopHereToString()); - fragment.setArguments(args); - - return fragment; - } - //public static MapFragment getInstance(@NonNull Stop stop){ - // return getInstance(stop.getLatitude(), stop.getLongitude(), stop.getStopDisplayName(), stop.ID); - //} - - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - //use the same layout as the activity - View root = inflater.inflate(R.layout.fragment_map, container, false); - if (getContext() == null){ - throw new IllegalStateException(); - } - ctx = getContext().getApplicationContext(); - Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx)); - map = root.findViewById(R.id.map); - map.setTileSource(TileSourceFactory.MAPNIK); - //map.setTilesScaledToDpi(true); - map.setFlingEnabled(true); - - // add ability to zoom with 2 fingers - map.setMultiTouchControls(true); - - btCenterMap = root.findViewById(R.id.centerMapImageButton); - btFollowMe = root.findViewById(R.id.followUserImageButton); - coordLayout = root.findViewById(R.id.coord_layout); - - //setup FolderOverlay - stopsFolderOverlay = new FolderOverlay(); - //setup Bus Markers Overlay - busPositionsOverlay = new FolderOverlay(); - //reset shown bus updates - busPositionMarkersByTrip.clear(); - tripMarkersAnimators.clear(); - //set map not done - hasMapStartFinished = false; - - String keySourcePositions=getString(R.string.pref_positions_source); - useMQTTViewModel = ( - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(keySourcePositions,LIVE_POSITIONS_PREF_MQTT_VALUE).contentEquals(LIVE_POSITIONS_PREF_MQTT_VALUE)); - - - //Start map from bundle - if (savedInstanceState !=null) - startMap(getArguments(), savedInstanceState); - else startMap(getArguments(), savedMapState); - //set listeners - map.addMapListener(new DelayedMapListener(new MapListener() { - - @Override - public boolean onScroll(ScrollEvent paramScrollEvent) { - requestStopsToShow(); - //Log.d(DEBUG_TAG, "Scrolling"); - //if (moveTriggeredByCode) moveTriggeredByCode =false; - //else setLocationFollowing(false); - return true; - } - - @Override - public boolean onZoom(ZoomEvent event) { - requestStopsToShow(); - return true; - } - - })); - - - btCenterMap.setOnClickListener(v -> { - //Log.i(TAG, "centerMap clicked "); - if(Permissions.bothLocationPermissionsGranted(getContext())) { - final GeoPoint myPosition = mLocationOverlay.getMyLocation(); - map.getController().animateTo(myPosition); - } else - Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT) - .show(); - }); - - btFollowMe.setOnClickListener(v -> { - //Log.i(TAG, "btFollowMe clicked "); - if(Permissions.bothLocationPermissionsGranted(getContext())) - setLocationFollowing(!followingLocation); - else - Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT) - .show(); - }); - - - return root; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - - ViewModelProvider provider = new ViewModelProvider(this); - //gtfsPosViewModel = provider.get(GtfsPositionsViewModel.class); - livePositionsViewModel = provider.get(LivePositionsViewModel.class); - stopsViewModel = provider.get(StopsMapViewModel.class); - - - if (context instanceof FragmentListenerMain) { - listenerMain = (FragmentListenerMain) context; - } else { - throw new RuntimeException(context.toString() - + " must implement FragmentListenerMain"); - } - } - @Override - public void onDetach() { - super.onDetach(); - listenerMain = null; - //stop animations - - // setupOnAttached = true; - Log.w(DEBUG_TAG, "Fragment detached"); - } - - @Override - public void onPause() { - super.onPause(); - Log.w(DEBUG_TAG, "On pause called mapfrag"); - saveMapState(); - for (ObjectAnimator animator : tripMarkersAnimators.values()) { - if(animator!=null && animator.isRunning()){ - animator.cancel(); - } - } - tripMarkersAnimators.clear(); - if(useMQTTViewModel) livePositionsViewModel.stopMatoUpdates(); - - } - - /** - * Save the map state inside the fragment - * (calls saveMapState(bundle)) - */ - private void saveMapState(){ - savedMapState = new Bundle(); - saveMapState(savedMapState); - } - - /** - * Save the state of the map to restore it to a later time - * @param bundle the bundle in which to save the data - */ - private void saveMapState(Bundle bundle){ - Log.d(DEBUG_TAG, "Saving state, location following: "+followingLocation); - bundle.putBoolean(FOLLOWING_LOCAT_KEY, followingLocation); - if (map == null){ - //The map is null, it can happen? - Log.e(DEBUG_TAG, "Cannot save map center, map is null"); - return; - } - final IGeoPoint loc = map.getMapCenter(); - bundle.putDouble(MAP_CENTER_LAT_KEY, loc.getLatitude()); - bundle.putDouble(MAP_CENTER_LON_KEY, loc.getLongitude()); - 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); - /// choose which to use - String keySourcePositions=getString(R.string.pref_positions_source); - useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(keySourcePositions,LIVE_POSITIONS_PREF_MQTT_VALUE).contentEquals( - LIVE_POSITIONS_PREF_MQTT_VALUE); - if(livePositionsViewModel !=null) { - //gtfsPosViewModel.requestUpdates(); - if(useMQTTViewModel) - livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL); - else - livePositionsViewModel.requestGTFSUpdates(); - //mapViewModel.testCascade(); - livePositionsViewModel.isLastWorkResultGood().observe(this, d -> - Log.d(DEBUG_TAG, "Last trip download result is "+d)); - livePositionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> { - Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat); - livePositionsViewModel.downloadTripsFromMato(dat); - /*MatoTripsDownloadWorker.Companion.requestMatoTripsDownload(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"); - - //rerequest stop - stopsViewModel.requestStopsInBoundingBox(map.getBoundingBox()); - } - - 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 - public void onSaveInstanceState(@NonNull Bundle outState) { - saveMapState(outState); - - super.onSaveInstanceState(outState); - } - - //own methods - - /** - * Switch following the location on and off - * @param value true if we want to follow location - */ - public void setLocationFollowing(Boolean value){ - followingLocation = value; - if(mLocationOverlay==null || getContext() == null || map ==null) - //nothing else to do - return; - if (value){ - mLocationOverlay.enableFollowLocation(); - } else { - mLocationOverlay.disableFollowLocation(); - } - } - - /** - * Do all the stuff you need to do on the gui, when parameter is changed to value - * @param following value - */ - protected void updateGUIForLocationFollowing(boolean following){ - if (following) - btFollowMe.setImageResource(R.drawable.ic_follow_me_on); - else - btFollowMe.setImageResource(R.drawable.ic_follow_me); - - } - - /** - * Build the location overlay. Enable only when - * a) we know we have the permission - * b) the location map is set - */ - private void startLocationOverlay(boolean enableLocation, MapView map){ - if(getActivity()== null) throw new IllegalStateException("Cannot enable LocationOverlay now"); - // Location Overlay - // from OpenBikeSharing (THANK GOD) - Log.d(DEBUG_TAG, "Starting position overlay"); - GpsMyLocationProvider imlp = new GpsMyLocationProvider(getActivity().getBaseContext()); - imlp.setLocationUpdateMinDistance(5); - imlp.setLocationUpdateMinTime(2000); - - final LocationOverlay overlay = new LocationOverlay(imlp,map, locationCallbacks); - if (enableLocation) overlay.enableMyLocation(); - overlay.setOptionsMenuEnabled(true); - - //map.getOverlays().add(this.mLocationOverlay); - this.mLocationOverlay = overlay; - map.getOverlays().add(mLocationOverlay); - } - - public void startMap(Bundle incoming, Bundle savedInstanceState) { - //Check that we're attached - GeneralActivity activity = getActivity() instanceof GeneralActivity ? (GeneralActivity) getActivity() : null; - if(getContext()==null|| activity==null){ - //we are not attached - Log.e(DEBUG_TAG, "Calling startMap when not attached"); - return; - }else{ - Log.d(DEBUG_TAG, "Starting map from scratch"); - } - //clear previous overlays - map.getOverlays().clear(); - - - //parse incoming bundle - GeoPoint marker = null; - String name = null; - String ID = null; - String routesStopping = ""; - if (incoming != null) { - double lat = incoming.getDouble(BUNDLE_LATIT); - double lon = incoming.getDouble(BUNDLE_LONGIT); - marker = new GeoPoint(lat, lon); - name = incoming.getString(BUNDLE_NAME); - ID = incoming.getString(BUNDLE_ID); - routesStopping = incoming.getString(BUNDLE_ROUTES_STOPPING, ""); - } - - - //ask for location permission - if(!Permissions.bothLocationPermissionsGranted(activity)){ - if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){ - //TODO: show dialog for permission rationale - Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show(); - } - positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS); - - } - - shownStops = new HashSet<>(); - // move the map on the marker position or on a default view point: Turin, Piazza Castello - // and set the start zoom - IMapController mapController = map.getController(); - GeoPoint startPoint = null; - startLocationOverlay(Permissions.bothLocationPermissionsGranted(activity), - map); - // set the center point - if (marker != null) { - //startPoint = marker; - mapController.setZoom(POSITION_FOUND_ZOOM); - setLocationFollowing(false); - // put the center a little bit off (animate later) - startPoint = new GeoPoint(marker); - startPoint.setLatitude(marker.getLatitude()+ utils.angleRawDifferenceFromMeters(20)); - startPoint.setLongitude(marker.getLongitude()-utils.angleRawDifferenceFromMeters(20)); - //don't need to do all the rest since we want to show a point - } else if (savedInstanceState != null && savedInstanceState.containsKey(MAP_CURRENT_ZOOM_KEY)) { - mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY)); - mapController.setCenter(new GeoPoint(savedInstanceState.getDouble(MAP_CENTER_LAT_KEY), - savedInstanceState.getDouble(MAP_CENTER_LON_KEY))); - Log.d(DEBUG_TAG, "Location following from savedInstanceState: "+savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); - setLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)); - } else { - Log.d(DEBUG_TAG, "No position found from intent or saved state"); - boolean found = false; - LocationManager locationManager = - (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); - //check for permission - if (locationManager != null && Permissions.bothLocationPermissionsGranted(activity)) { - - @SuppressLint("MissingPermission") - Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - - if (userLocation != null) { - double distan = utils.measuredistanceBetween(userLocation.getLatitude(), userLocation.getLongitude(), - DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON); - if (distan < 100_000.0) { - mapController.setZoom(POSITION_FOUND_ZOOM); - startPoint = new GeoPoint(userLocation); - found = true; - setLocationFollowing(true); - } - } - } - if(!found){ - startPoint = new GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON); - mapController.setZoom(NO_POSITION_ZOOM); - setLocationFollowing(false); - } - } - - // set the minimum zoom level - map.setMinZoomLevel(15.0); - //add contingency check (shouldn't happen..., but) - - if (startPoint != null) { - mapController.setCenter(startPoint); - } - - - //add stops overlay - //map.getOverlays().add(mLocationOverlay); - map.getOverlays().add(this.stopsFolderOverlay); - - Log.d(DEBUG_TAG, "Requesting stops load"); - // This is not necessary, by setting the center we already move - // the map and we trigger a stop request - //requestStopsToShow(); - if (marker != null) { - // make a marker with the info window open for the searched marker - //TODO: make Stop Bundle-able - Marker stopMarker = makeMarker(marker, ID , name, routesStopping,true); - map.getController().animateTo(marker); - } - //add the overlays with the bus stops - if(busPositionsOverlay == null){ - //Log.i(DEBUG_TAG, "Null bus positions overlay,redo"); - busPositionsOverlay = new FolderOverlay(); - } - startRequestsPositions(); - if(stopsViewModel !=null){ - - stopsViewModel.getStopsInBoundingBox().observe(getViewLifecycleOwner(), - this::showStopsMarkers - ); - } else Log.d(DEBUG_TAG, "Cannot observe new stops in map, stopsViewModel is null"); - map.getOverlays().add(this.busPositionsOverlay); - //set map as started - hasMapStartFinished = true; - } - - /** - * Start a request to load the stops that are in the current view - * from the database - */ - private void requestStopsToShow(){ - // get the top, bottom, left and right screen's coordinate - BoundingBox bb = map.getBoundingBox(); - Log.d(DEBUG_TAG, "Requesting stops in bounding box, stopViewModel is null "+(stopsViewModel==null)); - if(stopsViewModel!=null){ - stopsViewModel.requestStopsInBoundingBox(bb); - } - /*double latFrom = bb.getLatSouth(); - double latTo = bb.getLatNorth(); - double lngFrom = bb.getLonWest(); - double lngTo = bb.getLonEast(); - if (stopFetcher!= null && stopFetcher.getStatus()!= AsyncTask.Status.FINISHED) - stopFetcher.cancel(true); - stopFetcher = new AsyncStopFetcher(this); - stopFetcher.execute( - new AsyncStopFetcher.BoundingBoxLimit(lngFrom,lngTo,latFrom, latTo)); - - */ - } - - private void updateBusMarker(final Marker marker, final LivePositionUpdate posUpdate, @Nullable boolean justCreated){ - GeoPoint position; - final String updateID = posUpdate.getTripID(); - if(!justCreated){ - position = marker.getPosition(); - if(posUpdate.getLatitude()!=position.getLatitude() || posUpdate.getLongitude()!=position.getLongitude()){ - GeoPoint newpos = new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude()); - ObjectAnimator valueAnimator = MarkerUtils.makeMarkerAnimator( - map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200); - valueAnimator.setAutoCancel(true); - tripMarkersAnimators.put(updateID,valueAnimator); - valueAnimator.start(); - } - //marker.setPosition(new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude())); - } else { - - position = new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude()); - marker.setPosition(position); - } - - if(posUpdate.getBearing()!=null) - marker.setRotation(posUpdate.getBearing()*(-1.f)); - } - - private void updateBusPositionsInMap(HashMap> tripsPatterns){ - Log.d(DEBUG_TAG, "Updating positions of the buses"); - //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay(); - final ArrayList noPatternsTrips = new ArrayList<>(); - for(String tripID: tripsPatterns.keySet()) { - final Pair pair = tripsPatterns.get(tripID); - if (pair == null) continue; - final LivePositionUpdate update = pair.getFirst(); - final TripAndPatternWithStops tripWithPatternStops = pair.getSecond(); - - - //check if Marker is already created - if (busPositionMarkersByTrip.containsKey(tripID)){ - //need to change the position of the marker - final Marker marker = busPositionMarkersByTrip.get(tripID); - assert marker!=null; - updateBusMarker(marker, update, false); - if(marker.getInfoWindow()!=null && marker.getInfoWindow() instanceof BusInfoWindow){ - BusInfoWindow window = (BusInfoWindow) marker.getInfoWindow(); - if(tripWithPatternStops != null) { - //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID); - window.setPatternAndDraw(tripWithPatternStops.getPattern()); - } - - } - } else{ - //marker is not there, need to make it - if(map==null) Log.e(DEBUG_TAG, "Creating marker with null map, things will explode"); - final Marker marker = new Marker(map); - - /*final Drawable mDrawable = DrawableUtils.Companion.getScaledDrawableResources( - getResources(), - R.drawable.point_heading_icon, - R.dimen.map_icons_size, R.dimen.map_icons_size); - - */ - //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID()); - final Drawable mdraw = ResourcesCompat.getDrawable(getResources(),R.drawable.map_bus_position_icon, null); - /*final Drawable mdraw = DrawableUtils.Companion.writeOnDrawable(getResources(), - R.drawable.point_heading_icon, - R.color.white, - route,12); - - */ - assert mdraw != null; - //mdraw.setBounds(0,0,28,28); - marker.setIcon(mdraw); - if(tripWithPatternStops == null){ - noPatternsTrips.add(tripID); - } - MatoPattern markerPattern = null; - if(tripWithPatternStops != null && tripWithPatternStops.getPattern()!=null) - markerPattern = tripWithPatternStops.getPattern(); - marker.setInfoWindow(new BusInfoWindow(map, update, markerPattern , false, (pattern) -> { })); - marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER); - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER); - - updateBusMarker(marker, update, true); - // the overlay is null when it's not attached yet?5 - // cannot recreate it because it becomes null very soon - // if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay(); - //save the marker - if(busPositionsOverlay!=null) { - busPositionsOverlay.add(marker); - busPositionMarkersByTrip.put(tripID, marker); - } - - } - } - if(noPatternsTrips.size()>0){ - Log.i(DEBUG_TAG, "These trips have no matching pattern: "+noPatternsTrips); - } - } - - /** - * Add stops as Markers on the map - * @param stops the list of stops that must be included - */ - protected void showStopsMarkers(List stops){ - if (getContext() == null || stops == null){ - //we are not attached - return; - } - boolean good = true; - - for (Stop stop : stops) { - if (shownStops.contains(stop.ID)){ - continue; - } - if(stop.getLongitude()==null || stop.getLatitude()==null) - continue; - - shownStops.add(stop.ID); - if(!map.isShown()){ - if(good) - Log.d(DEBUG_TAG, "Need to show stop but map is not shown, probably detached already"); - good = false; - continue; - } else if(map.getRepository() == null){ - Log.e(DEBUG_TAG, "Map view repository is null"); - } - GeoPoint marker = new GeoPoint(stop.getLatitude(), stop.getLongitude()); - - Marker stopMarker = makeMarker(marker, stop, false); - stopsFolderOverlay.add(stopMarker); - if (!map.getOverlays().contains(stopsFolderOverlay)) { - Log.w(DEBUG_TAG, "Map doesn't have folder overlay"); - } - good=true; - } - //Log.d(DEBUG_TAG,"We have " +stopsFolderOverlay.getItems().size()+" stops in the folderOverlay"); - //force redraw of markers - map.invalidate(); - } - - public Marker makeMarker(GeoPoint geoPoint, Stop stop, boolean isStartMarker){ - return makeMarker(geoPoint,stop.ID, - stop.getStopDefaultName(), - stop.routesThatStopHereToString(), isStartMarker); - } - - public Marker makeMarker(GeoPoint geoPoint, String stopID, String stopName, - String routesStopping, boolean isStartMarker) { - - // add a marker - final Marker marker = new Marker(map); - - // set custom info window as info window - CustomInfoWindow popup = new CustomInfoWindow(map, stopID, stopName, routesStopping, - responder, R.layout.linedetail_stop_infowindow, R.color.red_darker); - marker.setInfoWindow(popup); - - // make the marker clickable - marker.setOnMarkerClickListener((thisMarker, mapView) -> { - if (thisMarker.isInfoWindowOpen()) { - // on second click - Log.w(DEBUG_TAG, "Pressed on the click marker"); - } else { - // on first click - - // hide all opened info window - InfoWindow.closeAllInfoWindowsOn(map); - // show this particular info window - thisMarker.showInfoWindow(); - // move the map to its position - map.getController().animateTo(thisMarker.getPosition()); - } - - return true; - }); - - // set its position - marker.setPosition(geoPoint); - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER); - // add to it an icon - //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker)); - - marker.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.bus_stop, ctx.getTheme())); - // add to it a title - marker.setTitle(stopName); - // set the description as the ID - marker.setSnippet(stopID); - - // show popup info window of the searched marker - if (isStartMarker) { - marker.showInfoWindow(); - //map.getController().animateTo(marker.getPosition()); - } - - return marker; - } - - @Nullable - @Override - public View getBaseViewForSnackBar() { - return coordLayout; - } - -} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragmentKt.kt +++ /dev/null @@ -1,750 +0,0 @@ -/* - BusTO - Fragments components - Copyright (C) 2020 Andrea Ugo - Copyright (C) 2021 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.fragments - -import android.Manifest -import android.animation.ObjectAnimator -import android.annotation.SuppressLint -import android.content.Context -import android.location.LocationManager -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageButton -import android.widget.Toast -import androidx.activity.result.ActivityResultCallback -import androidx.activity.result.contract.ActivityResultContracts -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.content.res.ResourcesCompat -import androidx.fragment.app.viewModels -import androidx.preference.PreferenceManager -import it.reyboz.bustorino.R -import it.reyboz.bustorino.backend.Stop -import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate -import it.reyboz.bustorino.backend.mato.MQTTMatoClient -import it.reyboz.bustorino.backend.utils -import it.reyboz.bustorino.data.gtfs.MatoPattern -import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops -import it.reyboz.bustorino.map.BusInfoWindow -import it.reyboz.bustorino.map.CustomInfoWindow -import it.reyboz.bustorino.map.CustomInfoWindow.TouchResponder -import it.reyboz.bustorino.map.LocationOverlay -import it.reyboz.bustorino.map.LocationOverlay.OverlayCallbacks -import it.reyboz.bustorino.map.MarkerUtils -import it.reyboz.bustorino.middleware.GeneralActivity -import it.reyboz.bustorino.util.Permissions -import it.reyboz.bustorino.viewmodels.LivePositionsViewModel -import it.reyboz.bustorino.viewmodels.StopsMapViewModel -import org.osmdroid.config.Configuration -import org.osmdroid.events.DelayedMapListener -import org.osmdroid.events.MapListener -import org.osmdroid.events.ScrollEvent -import org.osmdroid.events.ZoomEvent -import org.osmdroid.tileprovider.tilesource.TileSourceFactory -import org.osmdroid.util.GeoPoint -import org.osmdroid.views.MapView -import org.osmdroid.views.overlay.FolderOverlay -import org.osmdroid.views.overlay.Marker -import org.osmdroid.views.overlay.infowindow.InfoWindow -import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider - -open class MapFragmentKt : ScreenBaseFragment() { - protected var listenerMain: FragmentListenerMain? = null - private var shownStops: HashSet? = null - private lateinit var map: MapView - var ctx: Context? = null - private lateinit var mLocationOverlay: LocationOverlay - private lateinit var stopsFolderOverlay: FolderOverlay - private var savedMapState: Bundle? = null - protected lateinit var btCenterMap: ImageButton - protected lateinit var btFollowMe: ImageButton - protected var coordLayout: CoordinatorLayout? = null - private var hasMapStartFinished = false - private var followingLocation = false - - //the ViewModel from which we get the stop to display in the map - private val stopsViewModel: StopsMapViewModel by viewModels() - - //private GtfsPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class); - private val livePositionsViewModel: LivePositionsViewModel by viewModels() - private var useMQTTViewModel = true - private val busPositionMarkersByTrip = HashMap() - private var busPositionsOverlay: FolderOverlay? = null - private val tripMarkersAnimators = HashMap() - protected val responder = TouchResponder { stopID, stopName -> - if (listenerMain != null) { - Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: $stopID") - listenerMain!!.requestArrivalsForStopID(stopID) - } - } - protected val locationCallbacks: OverlayCallbacks = object : OverlayCallbacks { - override fun onDisableFollowMyLocation() { - updateGUIForLocationFollowing(false) - followingLocation = false - } - - override fun onEnableFollowMyLocation() { - updateGUIForLocationFollowing(true) - followingLocation = true - } - } - private val positionRequestLauncher = - registerForActivityResult, Map>( - ActivityResultContracts.RequestMultiplePermissions(), - ActivityResultCallback { result -> - if (result == null) { - Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?") - } else if (java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_COARSE_LOCATION] - && java.lang.Boolean.TRUE == result[Manifest.permission.ACCESS_FINE_LOCATION]) { - // We can use the position, restart location overlay - map.overlays.remove(mLocationOverlay) - startLocationOverlay(true, map) - if (context == null || requireContext().getSystemService(Context.LOCATION_SERVICE) == null) - return@ActivityResultCallback ///@registerForActivityResult - val locationManager = - requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager - @SuppressLint("MissingPermission") val userLocation = - locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) - if (userLocation != null) { - map!!.controller.setZoom(POSITION_FOUND_ZOOM) - val startPoint = GeoPoint(userLocation) - setLocationFollowing(true) - map!!.controller.setCenter(startPoint) - } - } else Log.w(DEBUG_TAG, "No location permission") - }) - - //public static MapFragment getInstance(@NonNull Stop stop){ - // return getInstance(stop.getLatitude(), stop.getLongitude(), stop.getStopDisplayName(), stop.ID); - //} - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - //use the same layout as the activity - val root = inflater.inflate(R.layout.fragment_map, container, false) - val context = requireContext() - ctx = context.applicationContext - Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(context)) - map = root.findViewById(R.id.map) - map.setTileSource(TileSourceFactory.MAPNIK) - //map.setTilesScaledToDpi(true); - map.setFlingEnabled(true) - - // add ability to zoom with 2 fingers - map.setMultiTouchControls(true) - btCenterMap = root.findViewById(R.id.centerMapImageButton) - btFollowMe = root.findViewById(R.id.followUserImageButton) - coordLayout = root.findViewById(R.id.coord_layout) - - //setup FolderOverlay - stopsFolderOverlay = FolderOverlay() - //setup Bus Markers Overlay - busPositionsOverlay = FolderOverlay() - //reset shown bus updates - busPositionMarkersByTrip.clear() - tripMarkersAnimators.clear() - //set map not done - hasMapStartFinished = false - val keySourcePositions = getString(R.string.pref_positions_source) - useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(keySourcePositions, SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE) - .contentEquals(SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE) - - - //Start map from bundle - if (savedInstanceState != null) startMap(arguments, savedInstanceState) else startMap( - arguments, savedMapState - ) - //set listeners - map.addMapListener(DelayedMapListener(object : MapListener { - override fun onScroll(paramScrollEvent: ScrollEvent): Boolean { - requestStopsToShow() - //Log.d(DEBUG_TAG, "Scrolling"); - //if (moveTriggeredByCode) moveTriggeredByCode =false; - //else setLocationFollowing(false); - return true - } - - override fun onZoom(event: ZoomEvent): Boolean { - requestStopsToShow() - return true - } - })) - btCenterMap.setOnClickListener(View.OnClickListener { v: View? -> - //Log.i(TAG, "centerMap clicked "); - if (Permissions.bothLocationPermissionsGranted(context)) { - val myPosition = mLocationOverlay!!.myLocation - map.getController().animateTo(myPosition) - } else Toast.makeText(context, R.string.enable_position_message_map, Toast.LENGTH_SHORT) - .show() - }) - btFollowMe.setOnClickListener(View.OnClickListener { v: View? -> - //Log.i(TAG, "btFollowMe clicked "); - if (Permissions.bothLocationPermissionsGranted(context)) setLocationFollowing(!followingLocation) else Toast.makeText( - context, R.string.enable_position_message_map, Toast.LENGTH_SHORT - ) - .show() - }) - return root - } - - override fun onAttach(context: Context) { - super.onAttach(context) - listenerMain = if (context is FragmentListenerMain) { - context - } else { - throw RuntimeException( - context.toString() - + " must implement FragmentListenerMain" - ) - } - } - - override fun onDetach() { - super.onDetach() - listenerMain = null - - Log.w(DEBUG_TAG, "Fragment detached") - } - - override fun onPause() { - super.onPause() - Log.w(DEBUG_TAG, "On pause called mapfrag") - saveMapState() - for (animator in tripMarkersAnimators.values) { - if (animator != null && animator.isRunning) { - animator.cancel() - } - } - tripMarkersAnimators.clear() - if (useMQTTViewModel) livePositionsViewModel.stopMatoUpdates() - } - - /** - * Save the map state inside the fragment - * (calls saveMapState(bundle)) - */ - private fun saveMapState() { - savedMapState = Bundle() - saveMapState(savedMapState!!) - } - - /** - * Save the state of the map to restore it to a later time - * @param bundle the bundle in which to save the data - */ - private fun saveMapState(bundle: Bundle) { - Log.d(DEBUG_TAG, "Saving state, location following: $followingLocation") - bundle.putBoolean(FOLLOWING_LOCAT_KEY, followingLocation) - if (map == null) { - //The map is null, it can happen? - Log.e(DEBUG_TAG, "Cannot save map center, map is null") - return - } - val loc = map!!.mapCenter - bundle.putDouble(MAP_CENTER_LAT_KEY, loc.latitude) - bundle.putDouble(MAP_CENTER_LON_KEY, loc.longitude) - bundle.putDouble(MAP_CURRENT_ZOOM_KEY, map!!.zoomLevelDouble) - } - - override fun onResume() { - super.onResume() - //TODO: cleanup duplicate code (maybe merging the positions classes?) - if (listenerMain != null) listenerMain!!.readyGUIfor(FragmentKind.MAP) - /// choose which to use - val keySourcePositions = getString(R.string.pref_positions_source) - useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(keySourcePositions, SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE) - .contentEquals( - SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE - ) - //gtfsPosViewModel.requestUpdates(); - if (useMQTTViewModel) livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL) - else livePositionsViewModel.requestGTFSUpdates() - //mapViewModel.testCascade(); - livePositionsViewModel.isLastWorkResultGood.observe(this) { d: Boolean -> - Log.d( - DEBUG_TAG, "Last trip download result is $d" - ) - } - livePositionsViewModel.tripsGtfsIDsToQuery.observe(this) { dat: List -> - Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: $dat") - livePositionsViewModel.downloadTripsFromMato(dat) - } - - //rerequest stop - stopsViewModel!!.requestStopsInBoundingBox(map!!.boundingBox) - } - - private fun startRequestsPositions() { - if (livePositionsViewModel != null) { - //should always be the case - livePositionsViewModel!!.updatesWithTripAndPatterns.observe(viewLifecycleOwner) { data: HashMap> -> - 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 fun onSaveInstanceState(outState: Bundle) { - saveMapState(outState) - super.onSaveInstanceState(outState) - } - //own methods - /** - * Switch following the location on and off - * @param value true if we want to follow location - */ - fun setLocationFollowing(value: Boolean) { - followingLocation = value - if (mLocationOverlay == null || context == null || map == null) //nothing else to do - return - if (value) { - mLocationOverlay!!.enableFollowLocation() - } else { - mLocationOverlay!!.disableFollowLocation() - } - } - - /** - * Do all the stuff you need to do on the gui, when parameter is changed to value - * @param following value - */ - protected fun updateGUIForLocationFollowing(following: Boolean) { - if (following) btFollowMe!!.setImageResource(R.drawable.ic_follow_me_on) else btFollowMe!!.setImageResource( - R.drawable.ic_follow_me - ) - } - - /** - * Build the location overlay. Enable only when - * a) we know we have the permission - * b) the location map is set - */ - private fun startLocationOverlay(enableLocation: Boolean, map: MapView?) { - checkNotNull(activity) { "Cannot enable LocationOverlay now" } - // Location Overlay - // from OpenBikeSharing (THANK GOD) - Log.d(DEBUG_TAG, "Starting position overlay") - val imlp = GpsMyLocationProvider(requireActivity().baseContext) - imlp.locationUpdateMinDistance = 5f - imlp.locationUpdateMinTime = 2000 - val overlay = LocationOverlay(imlp, map, locationCallbacks) - if (enableLocation) overlay.enableMyLocation() - overlay.isOptionsMenuEnabled = true - - //map.getOverlays().add(this.mLocationOverlay); - mLocationOverlay = overlay - map!!.overlays.add(mLocationOverlay) - } - - fun startMap(incoming: Bundle?, savedInstanceState: Bundle?) { - //Check that we're attached - val activity = if (activity is GeneralActivity) activity as GeneralActivity? else null - if (context == null || activity == null) { - //we are not attached - Log.e(DEBUG_TAG, "Calling startMap when not attached") - return - } else { - Log.d(DEBUG_TAG, "Starting map from scratch") - } - //clear previous overlays - map!!.overlays.clear() - - - //parse incoming bundle - var marker: GeoPoint? = null - var name: String? = null - var ID: String? = null - var routesStopping: String? = "" - if (incoming != null) { - val lat = incoming.getDouble(BUNDLE_LATIT) - val lon = incoming.getDouble(BUNDLE_LONGIT) - marker = GeoPoint(lat, lon) - name = incoming.getString(BUNDLE_NAME) - ID = incoming.getString(BUNDLE_ID) - routesStopping = incoming.getString(BUNDLE_ROUTES_STOPPING, "") - } - - - //ask for location permission - if (!Permissions.bothLocationPermissionsGranted(activity)) { - if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) { - //TODO: show dialog for permission rationale - Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT) - .show() - } - positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS) - } - shownStops = HashSet() - // move the map on the marker position or on a default view point: Turin, Piazza Castello - // and set the start zoom - val mapController = map!!.controller - var startPoint: GeoPoint? = null - startLocationOverlay( - Permissions.bothLocationPermissionsGranted(activity), - map - ) - // set the center point - if (marker != null) { - //startPoint = marker; - mapController.setZoom(POSITION_FOUND_ZOOM) - setLocationFollowing(false) - // put the center a little bit off (animate later) - startPoint = GeoPoint(marker) - startPoint.latitude = marker.latitude + utils.angleRawDifferenceFromMeters(20.0) - startPoint.longitude = marker.longitude - utils.angleRawDifferenceFromMeters(20.0) - //don't need to do all the rest since we want to show a point - } else if (savedInstanceState != null && savedInstanceState.containsKey(MAP_CURRENT_ZOOM_KEY)) { - mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY)) - mapController.setCenter( - GeoPoint( - savedInstanceState.getDouble(MAP_CENTER_LAT_KEY), - savedInstanceState.getDouble(MAP_CENTER_LON_KEY) - ) - ) - Log.d( - DEBUG_TAG, - "Location following from savedInstanceState: " + savedInstanceState.getBoolean( - FOLLOWING_LOCAT_KEY - ) - ) - setLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY)) - } else { - Log.d(DEBUG_TAG, "No position found from intent or saved state") - var found = false - val locationManager = - requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager - //check for permission - if (Permissions.bothLocationPermissionsGranted(activity)) { - @SuppressLint("MissingPermission") val userLocation = - locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) - if (userLocation != null) { - val distan = utils.measuredistanceBetween( - userLocation.latitude, userLocation.longitude, - DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON - ) - if (distan < 100000.0) { - mapController.setZoom(POSITION_FOUND_ZOOM) - startPoint = GeoPoint(userLocation) - found = true - setLocationFollowing(true) - } - } - } - if (!found) { - startPoint = GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON) - mapController.setZoom(NO_POSITION_ZOOM) - setLocationFollowing(false) - } - } - - // set the minimum zoom level - map!!.minZoomLevel = 15.0 - //add contingency check (shouldn't happen..., but) - if (startPoint != null) { - mapController.setCenter(startPoint) - } - - - //add stops overlay - //map.getOverlays().add(mLocationOverlay); - map!!.overlays.add(stopsFolderOverlay) - Log.d(DEBUG_TAG, "Requesting stops load") - // This is not necessary, by setting the center we already move - // the map and we trigger a stop request - //requestStopsToShow(); - if (marker != null) { - // make a marker with the info window open for the searched marker - //TODO: make Stop Bundle-able - val stopMarker = makeMarker(marker, ID, name, routesStopping, true) - map!!.controller.animateTo(marker) - } - //add the overlays with the bus stops - if (busPositionsOverlay == null) { - //Log.i(DEBUG_TAG, "Null bus positions overlay,redo"); - busPositionsOverlay = FolderOverlay() - } - startRequestsPositions() - if (stopsViewModel != null) { - stopsViewModel!!.stopsInBoundingBox.observe(viewLifecycleOwner) { stops: List? -> - showStopsMarkers( - stops - ) - } - } else Log.d(DEBUG_TAG, "Cannot observe new stops in map, stopsViewModel is null") - map!!.overlays.add(busPositionsOverlay) - //set map as started - hasMapStartFinished = true - } - - /** - * Start a request to load the stops that are in the current view - * from the database - */ - private fun requestStopsToShow() { - // get the top, bottom, left and right screen's coordinate - val bb = map!!.boundingBox - Log.d( - DEBUG_TAG, - "Requesting stops in bounding box, stopViewModel is null " + (false) - ) - stopsViewModel.requestStopsInBoundingBox(bb) - - } - - private fun updateBusMarker( - marker: Marker?, - posUpdate: LivePositionUpdate, - justCreated: Boolean - ) { - val position: GeoPoint - val updateID = posUpdate.tripID - if (!justCreated) { - position = marker!!.position - if (posUpdate.latitude != position.latitude || posUpdate.longitude != position.longitude) { - val newpos = GeoPoint(posUpdate.latitude, posUpdate.longitude) - val valueAnimator = MarkerUtils.makeMarkerAnimator( - map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200 - ) - valueAnimator.setAutoCancel(true) - tripMarkersAnimators[updateID] = valueAnimator - valueAnimator.start() - } - //marker.setPosition(new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude())); - } else { - position = GeoPoint(posUpdate.latitude, posUpdate.longitude) - marker!!.position = position - } - marker.rotation = posUpdate.bearing?.let { it*-1f } ?: 0.0f - } - - private fun updateBusPositionsInMap(tripsPatterns: HashMap>) { - Log.d(DEBUG_TAG, "Updating positions of the buses") - //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay(); - val noPatternsTrips = ArrayList() - for (tripID in tripsPatterns.keys) { - val (update, tripWithPatternStops) = tripsPatterns[tripID] ?: continue - - - //check if Marker is already created - if (busPositionMarkersByTrip.containsKey(tripID)) { - //need to change the position of the marker - val marker = busPositionMarkersByTrip[tripID]!! - updateBusMarker(marker, update, false) - if (marker.infoWindow != null && marker.infoWindow is BusInfoWindow) { - val window = marker.infoWindow as BusInfoWindow - if (tripWithPatternStops != null) { - //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID); - window.setPatternAndDraw(tripWithPatternStops.pattern) - } - } - } else { - //marker is not there, need to make it - val marker = Marker(map) - - /*final Drawable mDrawable = DrawableUtils.Companion.getScaledDrawableResources( - getResources(), - R.drawable.point_heading_icon, - R.dimen.map_icons_size, R.dimen.map_icons_size); - - */ - //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID()); - val mdraw = - ResourcesCompat.getDrawable(resources, R.drawable.map_bus_position_icon, null)!! - //mdraw.setBounds(0,0,28,28); - marker.icon = mdraw - if (tripWithPatternStops == null) { - noPatternsTrips.add(tripID) - } - var markerPattern: MatoPattern? = null - if (tripWithPatternStops != null && tripWithPatternStops.pattern != null) markerPattern = - tripWithPatternStops.pattern - marker.infoWindow = - BusInfoWindow(map!!, update, markerPattern, false) { pattern: MatoPattern? -> } - marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) - updateBusMarker(marker, update, true) - // the overlay is null when it's not attached yet?5 - // cannot recreate it because it becomes null very soon - // if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay(); - //save the marker - if (busPositionsOverlay != null) { - busPositionsOverlay!!.add(marker) - busPositionMarkersByTrip[tripID] = marker - } - } - } - if (noPatternsTrips.size > 0) { - Log.i(DEBUG_TAG, "These trips have no matching pattern: $noPatternsTrips") - } - } - - /** - * Add stops as Markers on the map - * @param stops the list of stops that must be included - */ - protected fun showStopsMarkers(stops: List?) { - if (context == null || stops == null) { - //we are not attached - return - } - var good = true - for (stop in stops) { - if (shownStops!!.contains(stop.ID)) { - continue - } - if (stop.longitude == null || stop.latitude == null) continue - shownStops!!.add(stop.ID) - if (!map!!.isShown) { - if (good) Log.d( - DEBUG_TAG, - "Need to show stop but map is not shown, probably detached already" - ) - good = false - continue - } else if (map!!.repository == null) { - Log.e(DEBUG_TAG, "Map view repository is null") - } - val marker = GeoPoint(stop.latitude!!, stop.longitude!!) - val stopMarker = makeMarker(marker, stop, false) - stopsFolderOverlay!!.add(stopMarker) - if (!map!!.overlays.contains(stopsFolderOverlay)) { - Log.w(DEBUG_TAG, "Map doesn't have folder overlay") - } - good = true - } - //Log.d(DEBUG_TAG,"We have " +stopsFolderOverlay.getItems().size()+" stops in the folderOverlay"); - //force redraw of markers - map!!.invalidate() - } - - fun makeMarker(geoPoint: GeoPoint?, stop: Stop, isStartMarker: Boolean): Marker { - return makeMarker( - geoPoint, stop.ID, - stop.stopDefaultName, - stop.routesThatStopHereToString(), isStartMarker - ) - } - - fun makeMarker( - geoPoint: GeoPoint?, stopID: String?, stopName: String?, - routesStopping: String?, isStartMarker: Boolean - ): Marker { - - // add a marker - val marker = Marker(map) - - // set custom info window as info window - val popup = CustomInfoWindow( - map, stopID, stopName, routesStopping, - responder, R.layout.linedetail_stop_infowindow, R.color.red_darker - ) - marker.infoWindow = popup - - // make the marker clickable - marker.setOnMarkerClickListener { thisMarker: Marker, mapView: MapView? -> - if (thisMarker.isInfoWindowOpen) { - // on second click - Log.w(DEBUG_TAG, "Pressed on the click marker") - } else { - // on first click - - // hide all opened info window - InfoWindow.closeAllInfoWindowsOn(map) - // show this particular info window - thisMarker.showInfoWindow() - // move the map to its position - map!!.controller.animateTo(thisMarker.position) - } - true - } - - // set its position - marker.position = geoPoint - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) - // add to it an icon - //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker)); - marker.icon = ResourcesCompat.getDrawable(resources, R.drawable.bus_stop, ctx!!.theme) - // add to it a title - marker.title = stopName - // set the description as the ID - marker.snippet = stopID - - // show popup info window of the searched marker - if (isStartMarker) { - marker.showInfoWindow() - //map.getController().animateTo(marker.getPosition()); - } - return marker - } - - override fun getBaseViewForSnackBar(): View? { - return coordLayout - } - - companion object { - //private static final String TAG = "Busto-MapActivity"; - private const val MAP_CURRENT_ZOOM_KEY = "map-current-zoom" - private const val MAP_CENTER_LAT_KEY = "map-center-lat" - private const val MAP_CENTER_LON_KEY = "map-center-lon" - private const val FOLLOWING_LOCAT_KEY = "following" - const val BUNDLE_LATIT = "lat" - const val BUNDLE_LONGIT = "lon" - const val BUNDLE_NAME = "name" - const val BUNDLE_ID = "ID" - const val BUNDLE_ROUTES_STOPPING = "routesStopping" - const val FRAGMENT_TAG = "BusTOMapFragment" - private const val DEFAULT_CENTER_LAT = 45.0708 - private const val DEFAULT_CENTER_LON = 7.6858 - private const val POSITION_FOUND_ZOOM = 18.3 - const val NO_POSITION_ZOOM = 17.1 - private const val DEBUG_TAG = FRAGMENT_TAG - - @JvmStatic - fun getInstance(): MapFragmentKt { - return MapFragmentKt() - } - @JvmStatic - fun getInstance(stop: Stop): MapFragmentKt { - val fragment = MapFragmentKt() - val args = Bundle() - args.putDouble(MapFragment.BUNDLE_LATIT, stop.latitude!!) - args.putDouble(MapFragment.BUNDLE_LONGIT, stop.longitude!!) - args.putString(MapFragment.BUNDLE_NAME, stop.stopDisplayName) - args.putString(MapFragment.BUNDLE_ID, stop.ID) - args.putString(MapFragment.BUNDLE_ROUTES_STOPPING, stop.routesThatStopHereToString()) - fragment.arguments = args - - return fragment - } - } -} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt @@ -7,12 +7,10 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.Context -import android.content.Intent import android.graphics.Color import android.location.Location import android.location.LocationListener import android.location.LocationManager -import android.net.Uri import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -32,7 +30,6 @@ import androidx.fragment.app.viewModels import androidx.preference.PreferenceManager import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.gson.Gson import com.google.gson.JsonObject import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.Stop @@ -42,12 +39,11 @@ import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops import it.reyboz.bustorino.fragments.SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE import it.reyboz.bustorino.map.MapLibreUtils -import it.reyboz.bustorino.map.Styles +import it.reyboz.bustorino.map.MapLibreStyles import it.reyboz.bustorino.util.Permissions import it.reyboz.bustorino.util.ViewUtils import it.reyboz.bustorino.viewmodels.LivePositionsViewModel import it.reyboz.bustorino.viewmodels.StopsMapViewModel -import org.maplibre.android.MapLibre import org.maplibre.android.camera.CameraPosition import org.maplibre.android.camera.CameraUpdateFactory import org.maplibre.android.geometry.LatLng @@ -56,14 +52,9 @@ import org.maplibre.android.location.LocationComponentOptions import org.maplibre.android.location.modes.CameraMode import org.maplibre.android.maps.MapLibreMap -import org.maplibre.android.maps.MapView -import org.maplibre.android.maps.OnMapReadyCallback import org.maplibre.android.maps.Style import org.maplibre.android.plugins.annotation.Symbol -import org.maplibre.android.plugins.annotation.SymbolManager -import org.maplibre.android.plugins.annotation.SymbolOptions import org.maplibre.android.style.expressions.Expression -import org.maplibre.android.style.layers.Property import org.maplibre.android.style.layers.Property.* import org.maplibre.android.style.layers.PropertyFactory import org.maplibre.android.style.layers.SymbolLayer @@ -289,7 +280,7 @@ override fun onMapReady(mapReady: MapLibreMap) { this.map = mapReady val context = requireContext() - val mjson = Styles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) + val mjson = MapLibreStyles.getJsonStyleFromAsset(context, PreferencesHolder.getMapLibreStyleFile(context)) //ViewUtils.loadJsonFromAsset(requireContext(),"map_style_good.json") activity?.run { @@ -1125,6 +1116,8 @@ private const val DEBUG_TAG = "BusTO-MapLibreFrag" private const val STOP_ACTIVE_IMG = "Stop-active" + const val FRAGMENT_TAG = "BusTOMapFragment" + private const val LOCATION_PERMISSION_REQUEST_CODE = 981202 /** diff --git a/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt b/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - BusTO - Map components - Copyright (C) 2023 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.map - -import android.annotation.SuppressLint -import android.view.View.* -import android.widget.ImageView -import android.widget.TextView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.view.marginEnd -import it.reyboz.bustorino.R -import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate -import it.reyboz.bustorino.backend.gtfs.GtfsUtils -import it.reyboz.bustorino.backend.utils -import it.reyboz.bustorino.data.gtfs.MatoPattern -import org.osmdroid.views.MapView -import org.osmdroid.views.overlay.infowindow.BasicInfoWindow - -@SuppressLint("ClickableViewAccessibility") -class BusInfoWindow(map: MapView, - private val routeName: String, - private val vehicleLabel: String, - var pattern: MatoPattern?, - val showClose: Boolean, - private val touchUp: onTouchUp - ): - BasicInfoWindow(R.layout.bus_info_window,map) { - - init { - mView.setOnTouchListener { view, motionEvent -> - touchUp.onActionUp(pattern) - close() - //mView.performClick() - true - - } - } - constructor(map: MapView, update: LivePositionUpdate, pattern: MatoPattern?, showClose: Boolean, touchUp: onTouchUp, ): - this(map, - GtfsUtils.getLineNameFromGtfsID(update.routeID), - update.vehicle, - pattern, - showClose, - touchUp - ) - - - override fun onOpen(item: Any?) { - // super.onOpen(item) - val titleView = mView.findViewById(R.id.businfo_title) - val descrView = mView.findViewById(R.id.businfo_description) - val subdescrView = mView.findViewById(R.id.businfo_subdescription) - - val iconClose = mView.findViewById(R.id.closeIcon) - - //val nameRoute = GtfsUtils.getLineNameFromGtfsID(update.lineGtfsId) - - titleView.text = (mView.resources.getString(R.string.line_fill, routeName) - ) - subdescrView.text = vehicleLabel - //mView.resources.getString(R.string.vehicle_fill, vehicleLabel) - - - if(pattern!=null){ - descrView.text = pattern!!.headsign - descrView.visibility = VISIBLE - } else{ - descrView.visibility = GONE - } - if(!showClose){ - iconClose.visibility = GONE - val ctx = titleView.context - val layPars = (titleView.layoutParams as ConstraintLayout.LayoutParams).apply { - marginStart= 0 //utils.convertDipToPixelsInt(ctx, 8.0)//8.dpToPixels() - topMargin=utils.convertDipToPixelsInt(ctx, 4.0) - marginEnd=0 - bottomMargin=0 - } - //titleView.layoutParams = layPars - } - } - - fun setPatternAndDraw(pattern: MatoPattern?){ - if(pattern==null){ - return - } - this.pattern = pattern - if(isOpen){ - onOpen(pattern) - } - } - - fun interface onTouchUp{ - fun onActionUp(pattern: MatoPattern?) - } -} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt b/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package it.reyboz.bustorino.map - -import android.animation.ObjectAnimator -import android.util.Log -import androidx.core.content.res.ResourcesCompat -import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate -import it.reyboz.bustorino.data.gtfs.MatoPattern -import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops -import org.osmdroid.util.GeoPoint -import org.osmdroid.views.MapView -import org.osmdroid.views.overlay.Marker - -class BusPositionUtils { - companion object{ - @JvmStatic - public fun updateBusPositionMarker(map: MapView, marker: Marker?, posUpdate: LivePositionUpdate, - tripMarkersAnimators: HashMap, - justCreated: Boolean) { - val position: GeoPoint - val updateID = posUpdate.tripID - if (!justCreated) { - position = marker!!.position - if (posUpdate.latitude != position.latitude || posUpdate.longitude != position.longitude) { - val newpos = GeoPoint(posUpdate.latitude, posUpdate.longitude) - val valueAnimator = MarkerUtils.makeMarkerAnimator( - map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200 - ) - valueAnimator.setAutoCancel(true) - tripMarkersAnimators.put(updateID, valueAnimator) - valueAnimator.start() - } - //marker.setPosition(new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude())); - } else { - position = GeoPoint(posUpdate.latitude, posUpdate.longitude) - marker!!.position = position - } - //if (posUpdate.bearing != null) marker.rotation = posUpdate.bearing * -1f - marker.rotation = posUpdate.bearing?.let { it*-1f } ?: 0.0f - } - } -} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java b/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - BusTO - Map components - Copyright (C) 2020 Andrea Ugo - Copyright (C) 2021 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.map; - -import android.annotation.SuppressLint; -import android.os.Build; -import android.view.MotionEvent; -import android.view.View; - -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import androidx.core.content.ContextCompat; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.infowindow.BasicInfoWindow; - -import it.reyboz.bustorino.R; - -public class CustomInfoWindow extends BasicInfoWindow { - //TODO: Make the action on the Click customizable - private final TouchResponder touchResponder; - private final String stopID, name, routesStopping; - - private final int colorResID; - //final DisplayMetrics metrics; - - @Override - public void onOpen(Object item) { - super.onOpen(item); - TextView descr_textView = mView.findViewById(R.id.bubble_description); - CharSequence text = descr_textView.getText(); - TextView titleTV = mView.findViewById(R.id.bubble_title); - titleTV.setTextColor(ContextCompat.getColor(mView.getContext(),colorResID)); - //Log.d("BusTO-MapInfoWindow", "Descrip: "+text+", title "+(titleTV==null? "null": titleTV.getText())); - - if (text==null || !text.toString().isEmpty()){ - descr_textView.setVisibility(View.VISIBLE); - } else - descr_textView.setVisibility(View.GONE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mView.setElevation(3.2f); - } - TextView subDescriptTextView = mView.findViewById(R.id.bubble_subdescription); - if (routesStopping!=null && !routesStopping.isEmpty()){ - subDescriptTextView.setText(routesStopping); - subDescriptTextView.setVisibility(View.VISIBLE); - } - - //check if there is a close image - ImageView image = mView.findViewById(R.id.closeIcon); - if (image != null) { - image.setOnClickListener( view -> close()); - } - - } - public CustomInfoWindow(MapView mapView, String stopID, String name, String routesStopping, - TouchResponder responder){ - - this(mapView, stopID, name, routesStopping, responder,R.layout.map_popup, R.color.red_darker); - } - - @SuppressLint("ClickableViewAccessibility") - public CustomInfoWindow(MapView mapView, - String stopID, - String name, - String routesStopping, - TouchResponder responder, - int layoutId, - int colorResId) { - // get the personalized layout - super(layoutId, mapView); - touchResponder =responder; - this.stopID = stopID; - this.name = name; - this.routesStopping = routesStopping; - colorResID = colorResId; - - //metrics = Resources.getSystem().getDisplayMetrics(); - - // make clickable - mView.setOnTouchListener((View v, MotionEvent e) -> { - if (e.getAction() == MotionEvent.ACTION_UP) { - // on click - touchResponder.onActionUp(this.stopID, this.name); - } - return true; - }); - - - } - - public interface TouchResponder{ - /** - * React to a click on the stop View - * @param stopID the stop id - * @param stopName the stop name - */ - void onActionUp(@NonNull String stopID, @Nullable String stopName); - } -} diff --git a/app/src/main/java/it/reyboz/bustorino/map/GeoPointInterpolator.java b/app/src/main/java/it/reyboz/bustorino/map/LatLngInterpolator.java rename from app/src/main/java/it/reyboz/bustorino/map/GeoPointInterpolator.java rename to app/src/main/java/it/reyboz/bustorino/map/LatLngInterpolator.java --- a/app/src/main/java/it/reyboz/bustorino/map/GeoPointInterpolator.java +++ b/app/src/main/java/it/reyboz/bustorino/map/LatLngInterpolator.java @@ -3,7 +3,8 @@ /* Copyright 2013 Google Inc. Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0.html */ -import org.osmdroid.util.GeoPoint; + +import org.maplibre.android.geometry.LatLng; import static java.lang.Math.asin; import static java.lang.Math.atan2; @@ -14,21 +15,21 @@ import static java.lang.Math.toDegrees; import static java.lang.Math.toRadians; -public interface GeoPointInterpolator { - public GeoPoint interpolate(float fraction, GeoPoint a, GeoPoint b); +public interface LatLngInterpolator { + public LatLng interpolate(float fraction, LatLng a, LatLng b); - public class Linear implements GeoPointInterpolator { + public class Linear implements LatLngInterpolator { @Override - public GeoPoint interpolate(float fraction, GeoPoint a, GeoPoint b) { + public LatLng interpolate(float fraction, LatLng a, LatLng b) { double lat = (b.getLatitude() - a.getLatitude()) * fraction + a.getLatitude(); double lng = (b.getLongitude() - a.getLongitude()) * fraction + a.getLongitude(); - return new GeoPoint(lat, lng); + return new LatLng(lat, lng); } } - public class LinearFixed implements GeoPointInterpolator { + public class LinearFixed implements LatLngInterpolator { @Override - public GeoPoint interpolate(float fraction, GeoPoint a, GeoPoint b) { + public LatLng interpolate(float fraction, LatLng a, LatLng b) { double lat = (b.getLatitude() - a.getLatitude()) * fraction + a.getLatitude(); double lngDelta = b.getLongitude() - a.getLongitude(); @@ -37,15 +38,15 @@ lngDelta -= Math.signum(lngDelta) * 360; } double lng = lngDelta * fraction + a.getLongitude(); - return new GeoPoint(lat, lng); + return new LatLng(lat, lng); } } - public class Spherical implements GeoPointInterpolator { + public class Spherical implements LatLngInterpolator { /* From github.com/googlemaps/android-maps-utils */ @Override - public GeoPoint interpolate(float fraction, GeoPoint from, GeoPoint to) { + public LatLng interpolate(float fraction, LatLng from, LatLng to) { // http://en.wikipedia.org/wiki/Slerp double fromLat = toRadians(from.getLatitude()); double fromLng = toRadians(from.getLongitude()); @@ -71,7 +72,7 @@ // Converts interpolated vector back to polar. double lat = atan2(z, sqrt(x * x + y * y)); double lng = atan2(y, x); - return new GeoPoint(toDegrees(lat), toDegrees(lng)); + return new LatLng(toDegrees(lat), toDegrees(lng)); } private double computeAngleBetween(double fromLat, double fromLng, double toLat, double toLng) { diff --git a/app/src/main/java/it/reyboz/bustorino/map/LocationOverlay.java b/app/src/main/java/it/reyboz/bustorino/map/LocationOverlay.java deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/map/LocationOverlay.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - BusTO - Map components - Copyright (C) 2021 Fabio Mazza - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package it.reyboz.bustorino.map; - - -import android.content.Context; -import android.util.Log; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; -import org.osmdroid.views.overlay.mylocation.IMyLocationProvider; -import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; - -public class LocationOverlay extends MyLocationNewOverlay { - - private final static String DEBUG_TAG = "BusTOLocationOverlay"; - final OverlayCallbacks callbacks; - - public LocationOverlay(MapView mapView, OverlayCallbacks callbacks) { - super(mapView); - this.callbacks = callbacks; - } - - public LocationOverlay(IMyLocationProvider myLocationProvider, MapView mapView, OverlayCallbacks callbacks) { - super(myLocationProvider, mapView); - this.callbacks = callbacks; - } - - @Override - public void enableFollowLocation() { - super.enableFollowLocation(); - callbacks.onEnableFollowMyLocation(); - } - - @Override - public void disableFollowLocation() { - - super.disableFollowLocation(); - callbacks.onDisableFollowMyLocation(); - } - - public static LocationOverlay createLocationOverlay(boolean enableLocation, MapView map, Context context, OverlayCallbacks locationCallbacks){ - if(context== null) { - Log.d(DEBUG_TAG, "Cannot start location overlay, context is null"); - return null; - } - // Location Overlay - // from OpenBikeSharing (THANK GOD) - Log.d(DEBUG_TAG, "Starting position overlay"); - GpsMyLocationProvider imlp = new GpsMyLocationProvider(context.getApplicationContext()); - imlp.setLocationUpdateMinDistance(5); - imlp.setLocationUpdateMinTime(2000); - - final LocationOverlay overlay = new LocationOverlay(imlp,map, locationCallbacks); - if (enableLocation) overlay.enableMyLocation(); - //overlay.setOptionsMenuEnabled(true); - - return overlay; - } - - public interface OverlayCallbacks{ - /** - * Called right after disableFollowMyLocation - */ - void onDisableFollowMyLocation(); - - /** - * Called right after enableFollowMyLocation - */ - void onEnableFollowMyLocation(); - } -} diff --git a/app/src/main/java/it/reyboz/bustorino/map/Styles.kt b/app/src/main/java/it/reyboz/bustorino/map/MapLibreStyles.kt rename from app/src/main/java/it/reyboz/bustorino/map/Styles.kt rename to app/src/main/java/it/reyboz/bustorino/map/MapLibreStyles.kt --- a/app/src/main/java/it/reyboz/bustorino/map/Styles.kt +++ b/app/src/main/java/it/reyboz/bustorino/map/MapLibreStyles.kt @@ -3,7 +3,7 @@ import it.reyboz.bustorino.util.ViewUtils import org.maplibre.android.maps.Style -object Styles { +object MapLibreStyles { const val DEMOTILES = "https://demotiles.maplibre.org/style.json" const val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json" diff --git a/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java b/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java deleted file mode 100644 --- a/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -package it.reyboz.bustorino.map; - -import android.animation.ObjectAnimator; -import android.animation.TypeEvaluator; -import android.graphics.drawable.Drawable; -import android.util.Log; -import android.util.Property; - - -import android.view.animation.LinearInterpolator; -import it.reyboz.bustorino.R; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.Marker; -import org.osmdroid.views.overlay.infowindow.InfoWindow; - -public class MarkerUtils { - - public static final int LINEAR_ANIMATION = 1; - - /* Copyright 2013 Google Inc. - Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0.html */ - public static ObjectAnimator makeMarkerAnimator(final MapView map, Marker marker, GeoPoint finalPosition, int animationType, int durationMs) { - - GeoPointInterpolator interpolator; - switch (animationType){ - case LINEAR_ANIMATION: - interpolator = new GeoPointInterpolator.Linear(); - break; - default: - throw new IllegalArgumentException("Value "+animationType+ " for animationType is invalid"); - } - TypeEvaluator typeEvaluator = (fraction, startValue, endValue) -> - interpolator.interpolate(fraction, startValue, endValue); - Property property = Property.of(Marker.class, GeoPoint.class, "position"); - ObjectAnimator animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, finalPosition); - switch (animationType){ - case LINEAR_ANIMATION: - - animator.setInterpolator(new LinearInterpolator()); - default: - } - animator.setDuration(durationMs); - //animator.start(); - return animator; - } - - public static Marker makeMarker(GeoPoint geoPoint, String stopID, String stopName, - String routesStopping, - MapView map, - CustomInfoWindow.TouchResponder responder, - Drawable icon, - int infoWindowLayout, - int titleColorId) { - - // add a marker - final Marker marker = new Marker(map); - - // set custom info window as info window - CustomInfoWindow popup = new CustomInfoWindow(map, stopID, stopName, routesStopping, responder, infoWindowLayout, titleColorId); - marker.setInfoWindow(popup); - - // make the marker clickable - marker.setOnMarkerClickListener((thisMarker, mapView) -> { - if (thisMarker.isInfoWindowOpen()) { - // on second click - Log.w("BusTO-OsmMap", "Pressed on the click marker"); - } else { - // on first click - - // hide all opened info window - InfoWindow.closeAllInfoWindowsOn(map); - // show this particular info window - thisMarker.showInfoWindow(); - // move the map to its position - map.getController().animateTo(thisMarker.getPosition()); - } - - return true; - }); - - // set its position - marker.setPosition(geoPoint); - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM); - // add to it an icon - //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker)); - - marker.setIcon(icon); - // add to it a title - marker.setTitle(stopName); - // set the description as the ID - marker.setSnippet(stopID); - - // show popup info window of the searched marker - /*if (isStartMarker) { - marker.showInfoWindow(); - //map.getController().animateTo(marker.getPosition()); - }*/ - - return marker; - } -} diff --git a/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java b/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java --- a/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java +++ b/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java @@ -21,11 +21,7 @@ import androidx.core.util.Pair; import android.util.Log; -import it.reyboz.bustorino.backend.Passaggio; -import it.reyboz.bustorino.backend.Route; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.backend.utils; -import org.osmdroid.api.IGeoPoint; +import it.reyboz.bustorino.backend.*; import java.util.Collections; import java.util.Comparator; @@ -39,7 +35,7 @@ latPos = latitude; longPos = longitude; } - public RoutePositionSorter(IGeoPoint position){ + public RoutePositionSorter(GPSPoint position){ this(position.getLatitude(), position.getLongitude()); } diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt @@ -9,7 +9,6 @@ import it.reyboz.bustorino.data.NextGenDB import it.reyboz.bustorino.data.OldDataRepository import org.maplibre.android.geometry.LatLngBounds -import org.osmdroid.util.BoundingBox import java.util.concurrent.Executors import kotlin.collections.ArrayList @@ -60,12 +59,14 @@ return ArrayList(allStopsLoaded.values) } - fun requestStopsInBoundingBox(bb: BoundingBox) { + /*fun requestStopsInBoundingBox(bb: BoundingBox) { bb.let { Log.d(DEBUG_TAG, "Launching stop request") oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback) } } + + */ fun requestStopsInLatLng(bb: LatLngBounds) { bb.let { Log.d(DEBUG_TAG, "Launching stop request") 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 @@ -285,13 +285,13 @@ Source of real time positions for buses and trams - + MaTO (updated more frequently, might be offline) + GTFS RT (more stable, less frequently updated) @string/positions_source_mato_descr @string/positions_source_gtfsrt_descr - MaTO (updated more frequently, might be offline) - GTFS RT (more stable, less frequently updated) + Style of the map Versatiles (vector) OSM legacy (raster, lighter) @@ -299,8 +299,6 @@ @string/map_style_versatiles @string/map_style_legacy_raster - MaTO (updated more frequently, might be offline) - GTFS RT (more stable, less frequently updated) Remove trips data (free up space) All GTFS trips have been removed from the database