diff --git a/app/build.gradle b/app/build.gradle index 19d1ad6..003cd15 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,6 +26,9 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + repositories { + maven { url "https://jitpack.io" } + } } dependencies { @@ -47,6 +50,9 @@ dependencies { //osm implementation 'org.osmdroid:osmdroid-android:6.1.8' + //osm bonus pack + implementation 'com.github.MKergall:osmbonuspack:6.6.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' testImplementation 'org.mockito:mockito-core:2.7.22' diff --git a/app/src/main/java/com/a1/nextlocation/MainActivity.java b/app/src/main/java/com/a1/nextlocation/MainActivity.java index 1ac5b68..9f7821e 100644 --- a/app/src/main/java/com/a1/nextlocation/MainActivity.java +++ b/app/src/main/java/com/a1/nextlocation/MainActivity.java @@ -17,6 +17,7 @@ import com.a1.nextlocation.fragments.HomeFragment; import com.a1.nextlocation.fragments.RouteFragment; import com.a1.nextlocation.fragments.SettingsFragment; import com.a1.nextlocation.fragments.StatisticFragment; +import com.a1.nextlocation.network.ApiHandler; import com.a1.nextlocation.recyclerview.CouponListManager; import com.a1.nextlocation.recyclerview.LocationListManager; import com.a1.nextlocation.recyclerview.RouteListManager; diff --git a/app/src/main/java/com/a1/nextlocation/fragments/HomeFragment.java b/app/src/main/java/com/a1/nextlocation/fragments/HomeFragment.java index 8f5ba7d..d21d925 100644 --- a/app/src/main/java/com/a1/nextlocation/fragments/HomeFragment.java +++ b/app/src/main/java/com/a1/nextlocation/fragments/HomeFragment.java @@ -110,8 +110,6 @@ public class HomeFragment extends Fragment { mapView.getOverlays().add(customOverlay); - - // add the zoom controller IMapController mapController = mapView.getController(); mapController.setZoom(15.0); diff --git a/app/src/main/java/com/a1/nextlocation/fragments/RouteFragment.java b/app/src/main/java/com/a1/nextlocation/fragments/RouteFragment.java index 59043de..3ebe08a 100644 --- a/app/src/main/java/com/a1/nextlocation/fragments/RouteFragment.java +++ b/app/src/main/java/com/a1/nextlocation/fragments/RouteFragment.java @@ -2,19 +2,29 @@ package com.a1.nextlocation.fragments; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.a1.nextlocation.R; +import com.a1.nextlocation.data.Location; +import com.a1.nextlocation.data.Route; +import com.a1.nextlocation.json.DirectionsResult; +import com.a1.nextlocation.network.ApiHandler; +import com.a1.nextlocation.network.DirectionsListener; public class RouteFragment extends Fragment { + private static final String TAG = RouteFragment.class.getCanonicalName(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ApiHandler.INSTANCE.addListener(this::onDirectionsAvailable); } @@ -24,4 +34,21 @@ public class RouteFragment extends Fragment { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_route, container, false); } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + +// ApiHandler.INSTANCE.getDirections(8.681436,49.41461,8.687872,49.420318); + Route r = new Route("test"); + r.addLocation(new Location("test",8.681436,49.41461,"route",null)); + r.addLocation(new Location("test",8.687872,49.420318,"route",null)); + ApiHandler.INSTANCE.getDirections(r); + } + + public void onDirectionsAvailable(DirectionsResult result) { + Log.d(TAG, "onDirectionsAvailable: got result! " + result); + + + } } \ No newline at end of file diff --git a/app/src/main/java/com/a1/nextlocation/json/DirectionsResult.java b/app/src/main/java/com/a1/nextlocation/json/DirectionsResult.java new file mode 100644 index 0000000..8cf1c7c --- /dev/null +++ b/app/src/main/java/com/a1/nextlocation/json/DirectionsResult.java @@ -0,0 +1,164 @@ +package com.a1.nextlocation.json; + +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +import org.json.JSONArray; +import org.osmdroid.util.GeoPoint; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +public class DirectionsResult { + private static final String TAG = DirectionsResult.class.getCanonicalName(); + private List steps = new ArrayList<>(); + private double distance; + private double duration; + private double[][] wayPointCoordinates; + + public List getSteps() { + return steps; + } + + public void setSteps(List steps) { + this.steps = steps; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public double getDuration() { + return duration; + } + + public void setDuration(double duration) { + this.duration = duration; + } + + public void addStep(DirectionsStep step) { + this.steps.add(step); + } + + /** + * parses a given json string into this object. It gets all the waypoints and steps and combines them so that every step also has the correct coordinates associated with it + * @param json the json string to parse. + */ + public void parse(String json) { + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonObject feature = JsonParser.parseString(json).getAsJsonObject().get("features").getAsJsonArray().get(0).getAsJsonObject(); + JsonObject properties = feature.get("properties").getAsJsonObject(); + JsonArray wayPointCoordinates = feature.get("geometry").getAsJsonObject().getAsJsonArray("coordinates"); + this.wayPointCoordinates = new double[wayPointCoordinates.size()][2]; + + + // fill the way point coordinates list for later use + for (int i = 0; i < wayPointCoordinates.size(); i++) { + JsonElement j = wayPointCoordinates.get(i); + JsonArray arr = j.getAsJsonArray(); + this.wayPointCoordinates[i][0] = arr.get(0).getAsDouble(); + this.wayPointCoordinates[i][1] = arr.get(1).getAsDouble(); + } + + + JsonArray segments = properties.getAsJsonArray("segments"); + + for (JsonElement element : segments) { + JsonObject segment = element.getAsJsonObject(); + + setDistance(segment.get("distance").getAsDouble()); + setDuration(segment.get("duration").getAsDouble()); + + JsonArray steps = segment.getAsJsonArray("steps"); + + for (JsonElement j : steps) { + + DirectionsStep step = gson.fromJson(j,DirectionsStep.class); + double lat; + double longl; + + // kinda stinky but it works + for (int i = 0; i < 2; i++) { + lat = this.wayPointCoordinates[step.getWay_points().get(i)][0]; + longl = this.wayPointCoordinates[step.getWay_points().get(i)][1]; + step.getWaypoints()[i] = new GeoPoint(lat,longl); + } + + addStep(step); + Log.d(TAG, "parse: added step" + step); + } + } + + } + + public void parseRoute(String json) { + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonArray routes = JsonParser.parseString(json).getAsJsonObject().getAsJsonArray("routes"); + for (JsonElement element : routes) { + JsonObject route = element.getAsJsonObject(); + JsonObject summary = route.getAsJsonObject("summary"); + this.distance = summary.get("distance").getAsDouble(); + this.duration = summary.get("duration").getAsDouble(); + + JsonPrimitive geometry = route.getAsJsonPrimitive("geometry"); + JsonArray wayPointCoordinates = GeometryDecoder.decodeGeometry(geometry.getAsString(),false); + this.wayPointCoordinates = new double[wayPointCoordinates.size()][2]; + + + // fill the way point coordinates list for later use + for (int i = 0; i < wayPointCoordinates.size(); i++) { + JsonElement j = wayPointCoordinates.get(i); + JsonArray arr = j.getAsJsonArray(); + this.wayPointCoordinates[i][0] = arr.get(0).getAsDouble(); + this.wayPointCoordinates[i][1] = arr.get(1).getAsDouble(); + } + + + JsonArray segments = route.getAsJsonArray("segments"); + + for (JsonElement e : segments) { + JsonObject segment = e.getAsJsonObject(); + + setDistance(segment.get("distance").getAsDouble()); + setDuration(segment.get("duration").getAsDouble()); + + JsonArray steps = segment.getAsJsonArray("steps"); + + for (JsonElement j : steps) { + + DirectionsStep step = gson.fromJson(j,DirectionsStep.class); + double lat; + double longl; + + // kinda stinky but it works + for (int i = 0; i < 2; i++) { + lat = this.wayPointCoordinates[step.getWay_points().get(i)][0]; + longl = this.wayPointCoordinates[step.getWay_points().get(i)][1]; + step.getWaypoints()[i] = new GeoPoint(lat,longl); + } + + addStep(step); + Log.d(TAG, "parse: added step" + step); + } + } + + } + + + + } +} diff --git a/app/src/main/java/com/a1/nextlocation/json/DirectionsStep.java b/app/src/main/java/com/a1/nextlocation/json/DirectionsStep.java new file mode 100644 index 0000000..c495cef --- /dev/null +++ b/app/src/main/java/com/a1/nextlocation/json/DirectionsStep.java @@ -0,0 +1,72 @@ +package com.a1.nextlocation.json; + +import org.osmdroid.util.GeoPoint; + +import java.util.ArrayList; + +/** + * pojo class that holds the step object from the api response + */ +public class DirectionsStep { + private double distance; + private double duration; + private String instruction; + private String name; + /** + * these are the actual waypoints that the step refers to. The first is the beginning of the step, and the second is what it leads to. + * The second geopoint is always the first geopoint of the next step in the list of the {@link DirectionsResult} object. + */ + private GeoPoint[] waypoints = new GeoPoint[2]; + /** + * this is a list of the waypoints that are in the response, it is called way_points so it can be automatically serialized with gson + */ + private ArrayList way_points; + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public double getDuration() { + return duration; + } + + public void setDuration(double duration) { + this.duration = duration; + } + + public String getInstruction() { + return instruction; + } + + public void setInstruction(String instruction) { + this.instruction = instruction; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ArrayList getWay_points() { + return way_points; + } + + public void setWay_points(ArrayList way_points) { + this.way_points = way_points; + } + + public GeoPoint[] getWaypoints() { + return waypoints; + } + + public void setWaypoints(GeoPoint[] waypoints) { + this.waypoints = waypoints; + } +} diff --git a/app/src/main/java/com/a1/nextlocation/json/GeometryDecoder.java b/app/src/main/java/com/a1/nextlocation/json/GeometryDecoder.java new file mode 100644 index 0000000..3884e52 --- /dev/null +++ b/app/src/main/java/com/a1/nextlocation/json/GeometryDecoder.java @@ -0,0 +1,63 @@ +package com.a1.nextlocation.json; + +import com.google.gson.JsonArray; + +import org.json.JSONArray; +import org.json.JSONException; + +/** + * source: https://github.com/GIScience/openrouteservice-docs#geometry-decoding + */ +public class GeometryDecoder { + + public static JsonArray decodeGeometry(String encodedGeometry, boolean inclElevation) { + JsonArray geometry = new JsonArray(); + int len = encodedGeometry.length(); + int index = 0; + int lat = 0; + int lng = 0; + int ele = 0; + + while (index < len) { + int result = 1; + int shift = 0; + int b; + do { + b = encodedGeometry.charAt(index++) - 63 - 1; + result += b << shift; + shift += 5; + } while (b >= 0x1f); + lat += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); + + result = 1; + shift = 0; + do { + b = encodedGeometry.charAt(index++) - 63 - 1; + result += b << shift; + shift += 5; + } while (b >= 0x1f); + lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); + + + if(inclElevation){ + result = 1; + shift = 0; + do { + b = encodedGeometry.charAt(index++) - 63 - 1; + result += b << shift; + shift += 5; + } while (b >= 0x1f); + ele += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); + } + + JsonArray location = new JsonArray(); + location.add(lat / 1E5); + location.add(lng / 1E5); + if(inclElevation){ + location.add((float) (ele / 100)); + } + geometry.add(location); + } + return geometry; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/a1/nextlocation/network/ApiHandler.java b/app/src/main/java/com/a1/nextlocation/network/ApiHandler.java index af34416..1b480a8 100644 --- a/app/src/main/java/com/a1/nextlocation/network/ApiHandler.java +++ b/app/src/main/java/com/a1/nextlocation/network/ApiHandler.java @@ -4,14 +4,20 @@ import android.util.Log; import com.a1.nextlocation.data.Location; import com.a1.nextlocation.data.Route; +import com.a1.nextlocation.json.DirectionsResult; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; public enum ApiHandler { @@ -22,21 +28,22 @@ public enum ApiHandler { public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); private final String BASE_URL = "https://api.openrouteservice.org/v2/directions/"; private final String API_KEY = "5b3ce3597851110001cf6248d4eee2099f724255918adc71cc502b2a"; - private final String DIRECTIONS_MODE = "foot_walking"; + private final String DIRECTIONS_MODE = "foot-walking"; + private List listeners = new ArrayList<>(); private OkHttpClient client = new OkHttpClient(); - public Route getDirections(Location startLocation, Location endLocation) { - return getDirections(startLocation.getCoordinates(),endLocation.getCoordinates()); + public void getDirections(Location startLocation, Location endLocation) { + getDirections(startLocation.getCoordinates(),endLocation.getCoordinates()); } - public Route getDirections(double startLat, double startLong, double endLat, double endLong) { - return getDirections(startLat + "," + startLong, endLat + "," + endLong); + public void getDirections(double startLat, double startLong, double endLat, double endLong) { + getDirections(startLat + "," + startLong, endLat + "," + endLong); } - public Route getDirections(String startLocation, String endLocation) { + public void getDirections(String startLocation, String endLocation) { + String requestUrl = BASE_URL + DIRECTIONS_MODE + "?api_key=" + API_KEY + "&start=" +startLocation + "&end=" + endLocation; - AtomicReference res = null; Thread t = new Thread(() -> { Request request = new Request.Builder().url(requestUrl).build(); @@ -45,6 +52,14 @@ public enum ApiHandler { if (response.body() != null) { String responseString = Objects.requireNonNull(response.body()).string(); Log.d(TAG, "getDirections: got response: " + responseString); + + DirectionsResult result = new DirectionsResult(); + result.parse(responseString); + Log.d(TAG, "getDirections: " + result.getSteps().size()); + + for (DirectionsListener listener : listeners) { + listener.onDirectionsAvailable(result); + } } } catch (IOException e) { @@ -54,12 +69,59 @@ public enum ApiHandler { t.start(); - try { - t.join(); - } catch (InterruptedException e) { - e.printStackTrace(); + } + + public void addListener(DirectionsListener listener) { + this.listeners.add(listener); + } + + public void getDirections(Route route) { +// for (int i = 0; i < route.getLocations().size()-1; i+= 2) { +// Location start = route.getLocations().get(i); +// Location end = route.getLocations().get(i+1); +// getDirections(start,end); +// } + + ArrayList allCoords = new ArrayList<>(); + for (Location location : route.getLocations()) { + allCoords.add(location.getCoordinatesAsDoubles()); } - return res.get(); + + String body = "{\"coordinates\":" + new Gson().toJson(allCoords) + "}"; + + + String requestUrl = BASE_URL + DIRECTIONS_MODE + "?api_key=" + API_KEY; + + Thread t = new Thread(() -> { + + RequestBody requestBody = RequestBody.create(body,JSON); + Request request = new Request.Builder() + .url(requestUrl) + .post(requestBody) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.body() != null) { + String responseString = Objects.requireNonNull(response.body()).string(); + Log.d(TAG, "getDirections: got response: " + responseString); + + DirectionsResult result = new DirectionsResult(); + result.parseRoute(responseString); + Log.d(TAG, "getDirections: " + result.getSteps().size()); + + for (DirectionsListener listener : listeners) { + listener.onDirectionsAvailable(result); + } + } + + } catch (IOException e) { + Log.d(TAG, "getDirections: caught exception: " + e.getLocalizedMessage()); + } + }); + + t.start(); + + } diff --git a/app/src/main/java/com/a1/nextlocation/network/DirectionsListener.java b/app/src/main/java/com/a1/nextlocation/network/DirectionsListener.java new file mode 100644 index 0000000..28f2305 --- /dev/null +++ b/app/src/main/java/com/a1/nextlocation/network/DirectionsListener.java @@ -0,0 +1,8 @@ +package com.a1.nextlocation.network; + +import com.a1.nextlocation.data.Route; +import com.a1.nextlocation.json.DirectionsResult; + +public interface DirectionsListener { + void onDirectionsAvailable(DirectionsResult result); +}