diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
index a2da413..79ddce7 100644
--- a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
@@ -1,308 +1,341 @@
/*
BusTO (backend components)
Copyright (C) 2016 Ludovico Pavesi
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.backend;
import android.location.Location;
+import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import it.reyboz.bustorino.util.LinesNameSorter;
import org.osmdroid.api.IGeoPoint;
import java.net.URLEncoder;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class Stop implements Comparable {
// remove "final" in case you need to set these from outside the parser\scrapers\fetchers
public final @NonNull String ID;
private @Nullable String name;
private @Nullable String username;
public @Nullable String location;
public @Nullable Route.Type type;
private @Nullable List routesThatStopHere;
private final @Nullable Double lat;
private final @Nullable Double lon;
// leave this non-final
private @Nullable String routesThatStopHereString = null;
private @Nullable String absurdGTTPlaceName = null;
//
public @Nullable String gtfsID = null;
/**
* Hey, look, method overloading!
*/
public Stop(final @Nullable String name, final @NonNull String ID, @Nullable final String location, @Nullable final Route.Type type, @Nullable final List routesThatStopHere) {
this.ID = ID;
this.name = name;
this.username = null;
this.location = (location != null && location.length() == 0) ? null : location;
this.type = type;
this.routesThatStopHere = routesThatStopHere;
this.lat = null;
this.lon = null;
}
/**
* Hey, look, method overloading!
*/
public Stop(final @NonNull String ID) {
this.ID = ID;
this.name = null;
this.username = null;
this.location = null;
this.type = null;
this.routesThatStopHere = null;
this.lat = null;
this.lon = null;
}
/**
* Constructor that sets EVERYTHING.
*/
public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName,
@Nullable String location, @Nullable Route.Type type, @Nullable List routesThatStopHere,
@Nullable Double lat, @Nullable Double lon, @Nullable String gtfsID) {
this.ID = ID;
this.name = name;
this.username = userName;
this.location = location;
this.type = type;
this.routesThatStopHere = routesThatStopHere;
this.lat = lat;
this.lon = lon;
this.gtfsID = gtfsID;
}
public @Nullable String routesThatStopHereToString() {
// M E M O I Z A T I O N
if(this.routesThatStopHereString != null) {
return this.routesThatStopHereString;
}
// no string yet? build it!
return buildRoutesString();
}
@Nullable
public String getAbsurdGTTPlaceName() {
return absurdGTTPlaceName;
}
public void setAbsurdGTTPlaceName(@NonNull String absurdGTTPlaceName) {
this.absurdGTTPlaceName = absurdGTTPlaceName;
}
public void setRoutesThatStopHere(@Nullable List routesThatStopHere) {
this.routesThatStopHere = routesThatStopHere;
}
protected void setRoutesThatStopHereString(String routesStopping){
this.routesThatStopHereString = routesStopping;
}
@Nullable
protected List getRoutesThatStopHere(){
return routesThatStopHere;
}
protected @Nullable String buildRoutesString() {
// no routes => no string
if(this.routesThatStopHere == null || this.routesThatStopHere.size() == 0) {
return null;
}
StringBuilder sb = new StringBuilder();
Collections.sort(routesThatStopHere,new LinesNameSorter());
int i, lenMinusOne = routesThatStopHere.size() - 1;
for (i = 0; i < lenMinusOne; i++) {
sb.append(routesThatStopHere.get(i)).append(", ");
}
// last one:
sb.append(routesThatStopHere.get(i));
this.routesThatStopHereString = sb.toString();
return this.routesThatStopHereString;
}
@Override
public int compareTo(@NonNull Stop other) {
int res;
int thisAsInt = networkTools.failsafeParseInt(this.ID);
int otherAsInt = networkTools.failsafeParseInt(other.ID);
// numeric stop IDs
if(thisAsInt != 0 && otherAsInt != 0) {
return thisAsInt - otherAsInt;
} else {
// non-numeric
res = this.ID.compareTo(other.ID);
if (res != 0) {
return res;
}
}
// try with name, then
if(this.name != null && other.name != null) {
res = this.name.compareTo(other.name);
}
// and give up
return res;
}
/**
* Sets a name.
*
* @param name stop name as string (not null)
*/
public final void setStopName(@NonNull String name) {
this.name = name;
}
/**
* Sets user name. Empty string is converted to null.
*
* @param name a string of non-zero length, or null
*/
public final void setStopUserName(@Nullable String name) {
if(name == null) {
this.username = null;
} else if(name.length() == 0) {
this.username = null;
} else {
this.username = name;
}
}
/**
* Returns stop name or username (if set).
* - empty string means "already searched everywhere, can't find it"
* - null means "didn't search, yet. Maybe you should try."
* - string means "here's the name.", obviously.
*
* @return string if known, null if still unknown
*/
public final @Nullable String getStopDisplayName() {
if(this.username == null) {
return this.name;
} else {
return this.username;
}
}
/**
* Same as getStopDisplayName, only returns default name.
* I'd use an @see tag, but Android Studio is incapable of understanding that getStopDefaultName
* refers to the method exactly above this one and not some arcane and esoteric unknown symbol.
*/
public final @Nullable String getStopDefaultName() {
return this.name;
}
/**
* Same as getStopDisplayName, only returns user name.
* Also, never an empty string.
*/
public final @Nullable String getStopUserName() {
return this.username;
}
/**
* Gets username and name from other stop if they exist, sets itself accordingly.
*
* @param other another Stop
* @return did we actually set/change anything?
*/
public final boolean mergeNameFrom(Stop other) {
boolean ret = false;
if(other.name != null) {
if(this.name == null || !this.name.equals(other.name)) {
this.name = other.name;
ret = true;
}
}
if(other.username != null) {
if(this.username == null || !this.username.equals(other.username)) {
this.username = other.username;
ret = true;
}
}
return ret;
}
public final @Nullable String getGeoURL() {
if(this.lat == null || this.lon == null) {
return null;
}
// Android documentation suggests US for machine readable output (use dot as decimal separator)
return String.format(Locale.US, "geo:%f,%f", this.lat, this.lon);
}
public final @Nullable String getGeoURLWithAddress() {
String url = getGeoURL();
if(url == null) {
return null;
}
if(this.location != null) {
try {
String addThis = "?q=".concat(URLEncoder.encode(this.location, "utf-8"));
return url.concat(addThis);
} catch (Exception ignored) {}
}
return url;
}
@Nullable
public Double getLatitude() {
return lat;
}
@Nullable
public Double getLongitude() {
return lon;
}
public Double getDistanceFromLocation(IGeoPoint loc){
return getDistanceFromLocation(loc.getLatitude(), loc.getLongitude());
}
public Double getDistanceFromLocation(double latitude, double longitude){
if(this.lat!=null && this.lon !=null)
return utils.measuredistanceBetween(this.lat,this.lon,latitude, longitude);
else return Double.POSITIVE_INFINITY;
- }}
+ }
+
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString("ID", ID);
+ bundle.putString("name", name);
+ bundle.putString("username", username);
+ bundle.putString("location", location);
+ bundle.putString("type", (type != null) ? type.name() : null);
+ bundle.putStringArrayList("routesThatStopHere", (routesThatStopHere != null) ? new ArrayList<>(routesThatStopHere) : null);
+ if (lat != null) bundle.putDouble("lat", lat);
+ if (lon != null) bundle.putDouble("lon", lon);
+ if (gtfsID !=null) bundle.putString("gtfsID", gtfsID);
+ return bundle;
+ }
+
+ public static Stop fromBundle(Bundle bundle) {
+ String ID = bundle.getString("ID");
+ if (ID == null) throw new IllegalArgumentException("ID cannot be null");
+ String name = bundle.getString("name");
+ String username = bundle.getString("username");
+ String location = bundle.getString("location");
+ String typeStr = bundle.getString("type");
+ Route.Type type = (typeStr != null) ? Route.Type.valueOf(typeStr) : null;
+ List routesThatStopHere = bundle.getStringArrayList("routesThatStopHere");
+ Double lat = bundle.containsKey("lat") ? bundle.getDouble("lat") : null;
+ Double lon = bundle.containsKey("lon") ? bundle.getDouble("lon") : null;
+ String gtfsId = bundle.getString("gtfsID");
+
+ return new Stop(ID, name, username, location, type, routesThatStopHere, lat, lon, gtfsId);
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
index 536a00e..0a0eca4 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
@@ -1,152 +1,337 @@
package it.reyboz.bustorino.fragments
+import android.annotation.SuppressLint
+import android.content.Context
+import android.location.Location
import android.os.Bundle
+import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.viewModels
+import com.google.gson.Gson
+import com.google.gson.JsonObject
import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.map.Styles
+import it.reyboz.bustorino.viewmodels.StopsMapViewModel
import org.maplibre.android.MapLibre
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.maps.MapView
import org.maplibre.android.geometry.LatLng
+import org.maplibre.android.location.LocationComponent
+import org.maplibre.android.location.LocationComponentActivationOptions
+import org.maplibre.android.location.LocationComponentOptions
+import org.maplibre.android.location.engine.LocationEngineRequest
+import org.maplibre.android.location.modes.CameraMode
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.OnMapReadyCallback
+import org.maplibre.android.maps.Style
+import org.maplibre.android.style.layers.PropertyFactory
+import org.maplibre.android.style.layers.SymbolLayer
+import org.maplibre.android.style.sources.GeoJsonSource
+import org.maplibre.geojson.Feature
+import org.maplibre.geojson.FeatureCollection
+import org.maplibre.geojson.Point
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [MapLibreFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class MapLibreFragment : Fragment(), OnMapReadyCallback {
- // TODO: Rename and change types of parameters
+
//private var param1: String? = null
//private var param2: String? = null
// Declare a variable for MapView
private lateinit var mapView: MapView
+ private lateinit var locationComponent: LocationComponent
+ private var lastLocation: Location? = null
+ private val stopsViewModel: StopsMapViewModel by viewModels()
+ private val gson = Gson()
+ private var stopsShowing = ArrayList(0)
+
+
protected var map: MapLibreMap? = null
+ // Sources for stops and buses
+ private lateinit var stopsSource: GeoJsonSource
+ private lateinit var busesSource: GeoJsonSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
*/
MapLibre.getInstance(requireContext())
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
- val rootView = inflater.inflate(R.layout.fragment_map_libre, container, false)
+ val rootView = inflater.inflate(R.layout.fragment_map_libre,
+ container, false)
// Init layout view
// Init the MapView
mapView = rootView.findViewById(R.id.libreMapView)
mapView.getMapAsync(this) //{ //map ->
//map.setStyle("https://demotiles.maplibre.org/style.json") }
return rootView
}
override fun onMapReady(mapReady: MapLibreMap) {
this.map = mapReady
- mapReady.cameraPosition = CameraPosition.Builder().target(LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)).zoom(9.0).build()
+ mapReady.cameraPosition = CameraPosition.Builder().target(LatLng(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)).zoom(
+ 15.0).build()
activity?.run {
- //TODO: copy from TransportR
- //val mapStyle = makeStyleUrl("jawg-terrain")
- /*if (mapStyle != null && mapReady.style?.uri != mapStyle) {
- mapReady.setStyle(mapStyle, ::onMapStyleLoaded) //callback
+ mapReady.setStyle(Styles.CARTO_VOYAGER ) { style ->
+ setupSources(style)
+ setupLayers(style)
+
+ // Start observing data
+ observeViewModels()
+ initLocation(style, mapReady, requireContext())
+ }
+
+ mapReady.addOnCameraIdleListener {
+ map?.let {
+ stopsViewModel.loadStopsInLatLngBounds(it.projection.visibleRegion.latLngBounds)
+ }
+
}
- */
- mapReady.setStyle(OPENSTREETMAP_HOT_URL )
//makeStyleMapBoxUrl(false))
+
+ }
+
+ }
+ @SuppressLint("MissingPermission")
+ private fun initLocation(style: Style, map: MapLibreMap, context: Context){
+ locationComponent = map.locationComponent
+ val locationComponentOptions =
+ LocationComponentOptions.builder(context)
+ .pulseEnabled(true)
+ .build()
+ val locationComponentActivationOptions =
+ buildLocationComponentActivationOptions(style, locationComponentOptions, context)
+ locationComponent.activateLocationComponent(locationComponentActivationOptions)
+ locationComponent.isLocationComponentEnabled = true
+ locationComponent.cameraMode = CameraMode.TRACKING //CameraMode.TRACKING
+ locationComponent.forceLocationUpdate(lastLocation)
+ }
+ private fun buildLocationComponentActivationOptions(
+ style: Style,
+ locationComponentOptions: LocationComponentOptions,
+ context: Context
+ ): LocationComponentActivationOptions {
+ return LocationComponentActivationOptions
+ .builder(context, style)
+ .locationComponentOptions(locationComponentOptions)
+ .useDefaultLocationEngine(true)
+ .locationEngineRequest(
+ LocationEngineRequest.Builder(750)
+ .setFastestInterval(750)
+ .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
+ .build()
+ )
+ .build()
+ }
+
+ /**
+ * Setup the Map Layers
+ */
+ private fun setupLayers(style: Style) {
+ // add icon
+ ResourcesCompat.getDrawable(resources,R.drawable.bus_stop, activity?.theme)
+ ?.let { style.addImage(STOP_IMAGE_ID, it) }
+ // Stops layer
+ val stopsLayer = SymbolLayer(STOPS_LAYER_ID, STOPS_SOURCE_ID).apply {
+ withProperties(
+ PropertyFactory.iconImage(STOP_IMAGE_ID),
+ PropertyFactory.iconAllowOverlap(true),
+ PropertyFactory.iconIgnorePlacement(true)
+
+ )
+ }
+ style.addLayer(stopsLayer)
+
+ /*
+ // TODO when adding the buses
+ // Buses layer
+ val busesLayer = SymbolLayer(BUSES_LAYER_ID, BUSES_SOURCE_ID).apply {
+ withProperties(
+ PropertyFactory.iconImage("bus"),
+ PropertyFactory.iconSize(1.0f),
+ PropertyFactory.iconAllowOverlap(true),
+ PropertyFactory.iconRotate(Expression.get("bearing"))
+ )
+ }
+ style.addLayer(busesLayer)
+
+ */
+ }
+
+ /**
+ * Setup data sources for the map
+ */
+ private fun setupSources(style: Style) {
+ // Stops source
+ stopsSource = GeoJsonSource(STOPS_SOURCE_ID,)
+ style.addSource(stopsSource)
+
+ // Buses source
+ // TODO when adding the buses
+ //busesSource = GeoJsonSource(BUSES_SOURCE_ID)
+ //style.addSource(busesSource)
+ }
+
+
+ /**
+ * Incremental updates of the layers
+ */
+ fun updateLayerIncrementally(newPoints: List, layerSourceId: String) {
+ val source = map?.style?.getSourceAs(layerSourceId) ?: return
+
+ //source.querySourceFeatures(null)
+ // Get existing features
+ val existingFeatures = source.querySourceFeatures(null).toMutableList()
+
+ // Add new features
+ val newFeatures = newPoints.map { point ->
+ Feature.fromGeometry(point)
}
+ existingFeatures.addAll(newFeatures)
+
+ // Update source
+ source.setGeoJson(FeatureCollection.fromFeatures(existingFeatures))
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
+ private fun observeViewModels() {
+ // Observe stops
+ stopsViewModel.stopsToShow.observe(viewLifecycleOwner) { stops ->
+ stopsShowing = stops
+ displayStops(stops)
+ }
+ }
+
+ /**
+ * Add the stops to the layers
+ */
+ private fun displayStops(stops: List?) {
+ if (stops == null) return
+
+ val features = stops.mapNotNull { stop ->
+ stop.latitude?.let { lat ->
+ stop.longitude?.let { lon ->
+ Feature.fromGeometry(
+ Point.fromLngLat(lat, lon),
+ JsonObject().apply {
+ addProperty("id", stop.ID)
+ addProperty("name", stop.stopDefaultName)
+ addProperty("routes", stop.routesThatStopHereToString()) // Add routes array to JSON object
+ }
+ )
+ }
+ }
+ }
+ Log.d("MapLibreFrag","Have put ${features.size} stops to display")
+
+ stopsSource.setGeoJson(FeatureCollection.fromFeatures(features))
+ }
+
companion object {
+ private const val STOPS_SOURCE_ID = "stops-source"
+ private const val STOPS_LAYER_ID = "stops-layer"
+ private const val BUSES_SOURCE_ID = "buses-source"
+ private const val BUSES_LAYER_ID = "buses-layer"
+ private const val STOP_IMAGE_ID ="bus-stop-icon"
private const val DEFAULT_CENTER_LAT = 45.0708
private const val DEFAULT_CENTER_LON = 7.6858
- private const val POSITION_FOUND_ZOOM = 18.3
+ private const val POSITION_FOUND_ZOOM = 16.5
+ private const val NO_POSITION_ZOOM = 17.1
private const val ACCESS_TOKEN="KxO8lF4U3kiO63m0c7lzqDCDrMUVg1OA2JVzRXxxmYSyjugr1xpe4W4Db5rFNvbQ"
- const val NO_POSITION_ZOOM = 17.1
private const val MAPLIBRE_URL = "https://api.jawg.io/styles/"
- private const val OPENSTREETMAP_URL = "https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json"
- private const val OPENSTREETMAP_HOT_URL="https://raw.githubusercontent.com/fabmazz/maps/refs/heads/main/src/assets/json/openStreetMap-hot.json"
+
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MapLibreFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
MapLibreFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
private fun makeStyleUrl(style: String = "jawg-streets") =
"${MAPLIBRE_URL+ style}.json?access-token=${ACCESS_TOKEN}"
private fun makeStyleMapBoxUrl(dark: Boolean) =
if(dark)
"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
else //"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
"https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
+ const val OPENFREEMAP_LIBERY = "https://tiles.openfreemap.org/styles/liberty"
+
+ const val OPENFREEMAP_BRIGHT = "https://tiles.openfreemap.org/styles/bright"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/Styles.kt b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt
new file mode 100644
index 0000000..d797f66
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/Styles.kt
@@ -0,0 +1,50 @@
+package it.reyboz.bustorino.map
+import org.maplibre.android.maps.Style
+
+object Styles {
+ const val DEMOTILES = "https://demotiles.maplibre.org/style.json"
+
+ const val VERSATILES = "https://tiles.versatiles.org/assets/styles/colorful.json"
+
+ const val AMERICANA = "https://americanamap.org/style.json"
+
+ const val OPENFREEMAP_LIBERY = "https://tiles.openfreemap.org/styles/liberty"
+
+ const val OPENFREEMAP_BRIGHT = "https://tiles.openfreemap.org/styles/bright"
+
+ const val AWS_OPEN_DATA_STANDARD_LIGHT =
+ "https://maps.geo.us-east-2.amazonaws.com/maps/v0/maps/OpenDataStyle/style-descriptor?key=v1.public.eyJqdGkiOiI1NjY5ZTU4My0yNWQwLTQ5MjctODhkMS03OGUxOTY4Y2RhMzgifR_7GLT66TNRXhZJ4KyJ-GK1TPYD9DaWuc5o6YyVmlikVwMaLvEs_iqkCIydspe_vjmgUVsIQstkGoInXV_nd5CcmqRMMa-_wb66SxDdbeRDvmmkpy2Ow_LX9GJDgL2bbiCws0wupJPFDwWCWFLwpK9ICmzGvNcrPbX5uczOQL0N8V9iUvziA52a1WWkZucIf6MUViFRf3XoFkyAT15Ll0NDypAzY63Bnj8_zS8bOaCvJaQqcXM9lrbTusy8Ftq8cEbbK5aMFapXRjug7qcrzUiQ5sr0g23qdMvnKJQFfo7JuQn8vwAksxrQm6A0ByceEXSfyaBoVpFcTzEclxUomhY.NjAyMWJkZWUtMGMyOS00NmRkLThjZTMtODEyOTkzZTUyMTBi"
+
+ private fun protomaps(style: String): String {
+ return "https://api.protomaps.com/styles/v2/${style}.json?key=e761cc7daedf832a"
+ }
+
+ private fun makeStyleMapBoxUrl(dark: Boolean) =
+ if (dark)
+ "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
+ else //"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
+ "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
+
+ val PROTOMAPS_LIGHT = protomaps("light")
+
+ val PROTOMAPS_DARK = protomaps("dark")
+
+ val PROTOMAPS_GRAYSCALE = protomaps("grayscale")
+
+ val PROTOMAPS_WHITE = protomaps("white")
+
+ val PROTOMAPS_BLACK = protomaps("black")
+
+ val CARTO_DARK = makeStyleMapBoxUrl(true)
+
+ val CARTO_VOYAGER = makeStyleMapBoxUrl(false)
+
+ fun getPredefinedStyleWithFallback(name: String): String {
+ try {
+ val style = Style.getPredefinedStyle(name)
+ return style
+ } catch (e: Exception) {
+ return OPENFREEMAP_LIBERY
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
index 9d07f4f..3dd8948 100644
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
@@ -1,45 +1,73 @@
package it.reyboz.bustorino.viewmodels
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.map
-import it.reyboz.bustorino.backend.Result
import it.reyboz.bustorino.backend.Stop
-import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.NextGenDB
import it.reyboz.bustorino.data.OldDataRepository
-import it.reyboz.bustorino.data.gtfs.GtfsDatabase
+import org.maplibre.android.geometry.LatLngBounds
import org.osmdroid.util.BoundingBox
-import java.util.ArrayList
import java.util.concurrent.Executors
+import kotlin.collections.ArrayList
class StopsMapViewModel(application: Application): AndroidViewModel(application) {
private val executor = Executors.newFixedThreadPool(2)
private val oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+ val stopsToShow = MutableLiveData(ArrayList())
+ private var stopsShownIDs = HashSet()
+
val stopsInBoundingBox = MutableLiveData>()
private val callback =
OldDataRepository.Callback> { res ->
if(res.isSuccess){
stopsInBoundingBox.postValue(res.result)
Log.d(DEBUG_TAG, "Setting value of stops in bounding box")
}
}
+ private val addStopsCallback =
+ OldDataRepository.Callback> { res ->
+ if(res.isSuccess) res.result?.let{ newStops ->
+ val stopsAdd = stopsToShow.value ?: ArrayList()
+ for (s in newStops){
+ if (s.ID !in stopsShownIDs){
+ stopsShownIDs.add(s.ID)
+ stopsAdd.add(s)
+ }
+ }
+
+ stopsToShow.postValue(stopsAdd)
+ Log.d(DEBUG_TAG, "Loaded ${stopsAdd.size} stops in total")
+ }
+ }
+
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")
+ oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast, callback)
+ }
+ }
+ fun loadStopsInLatLngBounds(bb: LatLngBounds?){
+ bb?.let {
+ Log.d(DEBUG_TAG, "Launching stop request")
+ oldRepo.requestStopsInArea(it.latitudeSouth, it.latitudeNorth, it.longitudeWest, it.longitudeEast,
+ addStopsCallback)
+ }
+ }
companion object{
private const val DEBUG_TAG = "BusTOStopMapViewModel"
}
}
\ No newline at end of file