AI2 Component  (Version nb184)
Navigation.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2019 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.runtime;
7 
11 import static java.util.Arrays.asList;
12 
13 import android.util.Log;
33 import java.io.BufferedOutputStream;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.net.HttpURLConnection;
37 import java.net.URL;
38 import java.util.List;
39 import org.json.JSONException;
40 import org.osmdroid.util.GeoPoint;
41 
47 @SuppressWarnings("TryFinallyCanBeTryWithResources")
48 @DesignerComponent(version = YaVersion.NAVIGATION_COMPONENT_VERSION,
49  category = ComponentCategory.MAPS,
50  description = "Navigation",
51  nonVisible = true,
52  iconName = "images/navigation.png")
53 @UsesPermissions(permissionNames = "android.permission.INTERNET")
54 @UsesLibraries({"osmdroid.jar"})
55 @SimpleObject
56 public class Navigation extends AndroidNonvisibleComponent implements Component {
57 
58  private static final String TAG = "Navigation";
59 
60  public static final String OPEN_ROUTE_SERVICE_URL =
61  "https://api.openrouteservice.org/v2/directions/";
62  private String apiKey;
63  private GeoPoint startLocation;
64  private GeoPoint endLocation;
65  private TransportMethod method;
66  private String serviceUrl = OPEN_ROUTE_SERVICE_URL;
67  private String language = "en";
68  private YailDictionary lastResponse = YailDictionary.makeDictionary();
69 
70  enum TransportMethod {
71  DEFAULT ("foot-walking"),
72  DRIVING ("driving-car"),
73  CYCLING ("cycling-regular"),
74  WALKING ("foot-walking"),
75  WHEELCHAIR ("wheelchair");
76 
77  private final String method;
78 
79  TransportMethod(String method) {
80  this.method = method;
81  }
82 
83  private String method() {
84  return method;
85  }
86  }
87 
93  public Navigation(ComponentContainer container) {
94  super(container.$form());
95  apiKey = "";
96  startLocation = new GeoPoint(0.0, 0.0);
97  endLocation = new GeoPoint(0.0, 0.0);
98  method = TransportMethod.DEFAULT;
99  }
100 
108  @SimpleFunction(description = "Request directions from the routing service.")
109  public void RequestDirections() {
110  if (apiKey.equals("")) {
111  form.dispatchErrorOccurredEvent(this, "Authorization", ErrorMessages.ERROR_INVALID_API_KEY);
112  return;
113  }
114  final GeoPoint startLocation = this.startLocation;
115  final GeoPoint endLocation = this.endLocation;
116  final TransportMethod method = this.method;
117  AsynchUtil.runAsynchronously(new Runnable() {
118  @SuppressWarnings("TryWithIdenticalCatches")
119  @Override
120  public void run() {
121  try {
122  performRequest(startLocation, endLocation, method);
123  } catch (IOException e) {
124  form.dispatchErrorOccurredEvent(Navigation.this, "RequestDirections",
126  } catch (JSONException je) {
127  form.dispatchErrorOccurredEvent(Navigation.this, "RequestDirections",
129  }
130  }
131  });
132  }
133 
137  @SimpleProperty(userVisible = false)
138  public void ServiceURL(String url) {
139  this.serviceUrl = url;
140  }
141 
149  @SimpleProperty(description = "API Key for Open Route Service.")
150  public void ApiKey(String key) {
151  apiKey = key;
152  }
153 
155  defaultValue = "0.0")
157  public void StartLatitude(double latitude) {
158  if (isValidLatitude(latitude)) {
159  startLocation.setLatitude(latitude);
160  } else {
161  getDispatchDelegate().dispatchErrorOccurredEvent(this, "StartLatitude",
163  }
164  }
165 
167  description = "The latitude of the start location.")
168  public double StartLatitude() {
169  return startLocation.getLatitude();
170  }
171 
173  defaultValue = "0.0")
175  public void StartLongitude(double longitude) {
176  if (isValidLongitude(longitude)) {
177  startLocation.setLongitude(longitude);
178  } else {
179  getDispatchDelegate().dispatchErrorOccurredEvent(this, "StartLongitude",
181  }
182  }
183 
185  description = "The longitude of the start location.")
186  public double StartLongitude() {
187  return startLocation.getLongitude();
188  }
189 
190  @SimpleProperty(description = "Set the start location.")
191  public void StartLocation(MapFeature feature) {
192  GeoPoint point = feature.getCentroid();
193  double latitude = point.getLatitude();
194  double longitude = point.getLongitude();
195  if (!isValidLatitude(latitude)) {
196  getDispatchDelegate().dispatchErrorOccurredEvent(this, "SetStartLocation",
198  } else if (!isValidLongitude(longitude)) {
199  getDispatchDelegate().dispatchErrorOccurredEvent(this, "SetStartLocation",
201  } else {
202  startLocation.setCoords(latitude, longitude);
203  }
204  }
205 
207  defaultValue = "0.0")
209  public void EndLatitude(double latitude) {
210  if (isValidLatitude(latitude)) {
211  endLocation.setLatitude(latitude);
212  } else {
213  getDispatchDelegate().dispatchErrorOccurredEvent(this, "EndLatitude",
215  }
216  }
217 
219  description = "The latitude of the end location.")
220  public double EndLatitude() {
221  return endLocation.getLatitude();
222  }
223 
225  defaultValue = "0.0")
227  public void EndLongitude(double longitude) {
228  if (isValidLongitude(longitude)) {
229  endLocation.setLongitude(longitude);
230  } else {
231  getDispatchDelegate().dispatchErrorOccurredEvent(this, "EndLongitude",
233  }
234  }
235 
237  description = "The longitude of the end location.")
238  public double EndLongitude() {
239  return endLocation.getLongitude();
240  }
241 
243  public String TransportationMethod() {
244  return method.method();
245  }
246 
258  defaultValue = "foot-walking")
259  @SimpleProperty(description = "The transportation method used for determining the route.")
260  public void TransportationMethod(String method) {
261  for (TransportMethod t : TransportMethod.values()) {
262  if (method.equals(t.method())) {
263  this.method = t;
264  }
265  }
266  }
267 
268  @SimpleProperty(description = "Set the end location.")
269  public void EndLocation(MapFeature feature) {
270  GeoPoint point = feature.getCentroid();
271  double latitude = point.getLatitude();
272  double longitude = point.getLongitude();
273  if (!isValidLatitude(latitude)) {
274  getDispatchDelegate().dispatchErrorOccurredEvent(this, "SetEndLocation",
276  } else if (!isValidLongitude(longitude)) {
277  getDispatchDelegate().dispatchErrorOccurredEvent(this, "SetEndLocation",
279  } else {
280  endLocation.setCoords(latitude, longitude);
281  }
282  }
283 
289  @SimpleProperty(description = "The language to use for textual directions.")
290  @DesignerProperty(defaultValue = "en")
291  public void Language(String language) {
292  this.language = language;
293  }
294 
296  public String Language() {
297  return language;
298  }
299 
306  @SimpleProperty(description = "Content of the last response as a dictionary.")
307  public YailDictionary ResponseContent() {
308  return lastResponse;
309  }
310 
326  @SimpleEvent(description = "Event triggered when the Openrouteservice returns the directions.")
327  public void GotDirections(YailList directions, YailList points, double distance,
328  double duration) {
329  Log.d(TAG, "GotDirections");
330  EventDispatcher.dispatchEvent(this, "GotDirections", directions, points, distance, duration);
331  }
332 
333  // MARK: Private implementation
334 
335  private void performRequest(GeoPoint start, GeoPoint end, TransportMethod method)
336  throws IOException, JSONException {
337  final String finalURL = serviceUrl + method.method() + "/geojson/";
338  URL url = new URL(finalURL);
339  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
340  connection.setDoInput(true);
341  connection.setDoOutput(true);
342  connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
343  connection.setRequestMethod("POST");
344  connection.setRequestProperty("Authorization", apiKey);
345  try {
346  String coords = "{\"coordinates\": "
347  + JsonUtil.getJsonRepresentation(getCoordinates(start, end)) + ", \"language\": \""
348  + language + "\"}";
349  byte[] postData = coords.getBytes("UTF-8");
350  connection.setFixedLengthStreamingMode(postData.length);
351  BufferedOutputStream out = new BufferedOutputStream(connection.getOutputStream());
352  try {
353  out.write(postData, 0, postData.length);
354  out.flush();
355  } finally {
356  out.close();
357  }
358 
359  if (connection.getResponseCode() != 200) {
360  form.dispatchErrorOccurredEvent(this, "RequestDirections",
361  ErrorMessages.ERROR_ROUTING_SERVICE_ERROR, connection.getResponseCode(),
362  connection.getResponseMessage());
363  return;
364  }
365  final String geoJson = getResponseContent(connection);
366  Log.d(TAG, geoJson);
367  final YailDictionary response = (YailDictionary) JsonUtil.getObjectFromJson(geoJson, true);
368  YailList features = (YailList) response.get("features");
369  if (features.size() > 0) {
370  YailDictionary feature = (YailDictionary) features.getObject(0);
371  YailDictionary summary =
372  (YailDictionary) feature.getObjectAtKeyPath(asList("properties", "summary"));
373  final double distance = (Double) summary.get("distance");
374  final double duration = (Double) summary.get("duration");
375  final YailList directions = YailList.makeList(getDirections(feature));
376  final YailList coordinates = getLineStringCoords(feature);
377 
378  form.runOnUiThread(new Runnable() {
379  @Override
380  public void run() {
381  lastResponse = response;
382  GotDirections(directions, coordinates, distance, duration);
383  }
384  });
385  } else {
386  // No response
387  form.dispatchErrorOccurredEvent(this, "RequestDirections",
388  ErrorMessages.ERROR_NO_ROUTE_FOUND);
389  }
390  } catch (Exception e) {
391  form.dispatchErrorOccurredEvent(this, "RequestDirections",
392  ErrorMessages.ERROR_UNABLE_TO_REQUEST_DIRECTIONS, e.getMessage());
393  e.printStackTrace();
394  } finally {
395  connection.disconnect();
396  }
397  }
398 
399  private static String getResponseContent(HttpURLConnection connection) throws IOException {
400  String encoding = connection.getContentEncoding();
401  if (encoding == null) {
402  encoding = "UTF-8";
403  }
404  Log.d(TAG, Integer.toString(connection.getResponseCode()));
405  InputStreamReader reader = new InputStreamReader(connection.getInputStream(), encoding);
406  try {
407  int contentLength = connection.getContentLength();
408  StringBuilder sb = (contentLength != -1)
409  ? new StringBuilder(contentLength)
410  : new StringBuilder();
411  char[] buf = new char[1024];
412  int read;
413  while ((read = reader.read(buf)) != -1) {
414  sb.append(buf, 0, read);
415  }
416  return sb.toString();
417  } finally {
418  reader.close();
419  }
420  }
421 
422  private Double[][] getCoordinates(GeoPoint startLocation, GeoPoint endLocation) {
423  Double[][] coords = new Double[2][2];
424  coords[0][0] = startLocation.getLongitude();
425  coords[0][1] = startLocation.getLatitude();
426  coords[1][0] = endLocation.getLongitude();
427  coords[1][1] = endLocation.getLatitude();
428  return coords;
429  }
430 
431  private YailList getLineStringCoords(YailDictionary feature) {
432  YailList coords =
433  (YailList) feature.getObjectAtKeyPath(asList("geometry", "coordinates"));
434  return GeoJSONUtil.swapCoordinates(coords);
435  }
436 
437  private List<?> getDirections(YailDictionary feature) {
438  return YailDictionary.walkKeyPath(feature,
439  asList("properties", "segments", ALL, "steps", ALL, "instruction"));
440  }
441 }
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.runtime.Navigation.StartLongitude
void StartLongitude(double longitude)
Definition: Navigation.java:175
com.google.appinventor.components.runtime.Navigation.Navigation
Navigation(ComponentContainer container)
Definition: Navigation.java:93
com.google.appinventor.components.annotations.UsesLibraries
Definition: UsesLibraries.java:21
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.util.MapFactory
Definition: MapFactory.java:30
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_INVALID_LATITUDE
static final int ERROR_INVALID_LATITUDE
Definition: ErrorMessages.java:242
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components.annotations.DesignerProperty
Definition: DesignerProperty.java:25
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_INVALID_LONGITUDE
static final int ERROR_INVALID_LONGITUDE
Definition: ErrorMessages.java:243
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_STRING
static final String PROPERTY_TYPE_STRING
Definition: PropertyTypeConstants.java:237
com.google.appinventor.components
com.google.appinventor.components.runtime.util.JsonUtil
Definition: JsonUtil.java:42
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.annotations.PropertyCategory.BEHAVIOR
BEHAVIOR
Definition: PropertyCategory.java:15
com.google.appinventor.components.runtime.Navigation.StartLatitude
void StartLatitude(double latitude)
Definition: Navigation.java:157
com.google.appinventor.components.runtime.Navigation
Definition: Navigation.java:56
com.google.appinventor.components.annotations.UsesPermissions
Definition: UsesPermissions.java:21
com.google.appinventor.components.runtime.Navigation.EndLongitude
void EndLongitude(double longitude)
Definition: Navigation.java:227
com.google.appinventor.components.runtime.util.MapFactory.MapFeature
Definition: MapFactory.java:588
com.google.appinventor.components.runtime.util.YailDictionary.makeDictionary
static YailDictionary makeDictionary()
Definition: YailDictionary.java:58
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_DEFAULT
static final int ERROR_DEFAULT
Definition: ErrorMessages.java:18
com.google.appinventor.components.runtime.util.GeometryUtil.isValidLongitude
static boolean isValidLongitude(double longitude)
Definition: GeometryUtil.java:454
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.runtime.AndroidNonvisibleComponent
Definition: AndroidNonvisibleComponent.java:17
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.runtime.util.GeometryUtil
Definition: GeometryUtil.java:41
com.google.appinventor.components.runtime.util.AsynchUtil.runAsynchronously
static void runAsynchronously(final Runnable call)
Definition: AsynchUtil.java:23
com.google.appinventor.components.runtime.Navigation.Language
String Language()
Definition: Navigation.java:296
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_INVALID_API_KEY
static final int ERROR_INVALID_API_KEY
Definition: ErrorMessages.java:277
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.runtime.util.AsynchUtil
Definition: AsynchUtil.java:17
com.google
com
com.google.appinventor.components.runtime.util.YailDictionary
Definition: YailDictionary.java:32
com.google.appinventor.components.runtime.ComponentContainer.$form
Form $form()
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_LONGITUDE
static final String PROPERTY_TYPE_LONGITUDE
Definition: PropertyTypeConstants.java:169
com.google.appinventor.components.runtime.util.GeometryUtil.isValidLatitude
static boolean isValidLatitude(double latitude)
Definition: GeometryUtil.java:444
com.google.appinventor.components.runtime.util.JsonUtil.getJsonRepresentation
static String getJsonRepresentation(Object value)
Definition: JsonUtil.java:246
com.google.appinventor.components.runtime.util.YailDictionary.ALL
static final Object ALL
Definition: YailDictionary.java:36
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_NAVIGATION_METHOD
static final String PROPERTY_TYPE_NAVIGATION_METHOD
Definition: PropertyTypeConstants.java:329
com.google.appinventor.components.runtime.util.GeoJSONUtil
Definition: GeoJSONUtil.java:46
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_LATITUDE
static final String PROPERTY_TYPE_LATITUDE
Definition: PropertyTypeConstants.java:94
com.google.appinventor.components.common.PropertyTypeConstants
Definition: PropertyTypeConstants.java:14
com.google.appinventor.components.annotations
com.google.appinventor.components.runtime.Navigation.EndLatitude
void EndLatitude(double latitude)
Definition: Navigation.java:209
com.google.appinventor