summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormvglasow <michael@vonglasow.com>2021-01-31 15:32:52 +0100
committerGitHub <noreply@github.com>2021-01-31 15:32:52 +0100
commit37f22dbec6aa0a10daedef36d28d4c1de7c1eabe (patch)
tree35c8d6072825facd383c9ed6833ac4c027b7381d
parent91222b892b2f796b8c18dff13eb15f888b276f3b (diff)
parent3aaab0aa1788a660604ae26a5f794f8bbde67337 (diff)
downloadnavit-37f22dbec6aa0a10daedef36d28d4c1de7c1eabe.tar.gz
Merge pull request #1083 from navit-gps/traff_0_8
Add TraFF 0.8 support
-rw-r--r--navit/android/src/org/navitproject/navit/NavitTraff.java361
-rw-r--r--navit/attr.c4
-rw-r--r--navit/navit.c70
-rw-r--r--navit/route.c27
-rw-r--r--navit/route_protected.h1
-rw-r--r--navit/traffic.c31
-rw-r--r--navit/traffic.h24
-rw-r--r--navit/traffic/dummy/traffic_dummy.c1
-rw-r--r--navit/traffic/null/traffic_null.c1
-rw-r--r--navit/traffic/traff_android/traffic_traff_android.c179
-rw-r--r--navit/util.c13
-rw-r--r--navit/xmlconfig.h2
12 files changed, 672 insertions, 42 deletions
diff --git a/navit/android/src/org/navitproject/navit/NavitTraff.java b/navit/android/src/org/navitproject/navit/NavitTraff.java
index c82d7d293..fd499b70b 100644
--- a/navit/android/src/org/navitproject/navit/NavitTraff.java
+++ b/navit/android/src/org/navitproject/navit/NavitTraff.java
@@ -25,11 +25,18 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
import android.util.Log;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
/**
* The TraFF receiver implementation.
@@ -39,11 +46,46 @@ import java.util.List;
*/
public class NavitTraff extends BroadcastReceiver {
+ private static final String ACTION_TRAFF_GET_CAPABILITIES = "org.traffxml.traff.GET_CAPABILITIES";
+ private static final String ACTION_TRAFF_HEARTBEAT = "org.traffxml.traff.HEARTBEAT";
private static final String ACTION_TRAFF_FEED = "org.traffxml.traff.FEED";
private static final String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL";
+ private static final String ACTION_TRAFF_SUBSCRIBE = "org.traffxml.traff.SUBSCRIBE";
+ private static final String ACTION_TRAFF_SUBSCRIPTION_CHANGE = "org.traffxml.traff.SUBSCRIPTION_CHANGE";
+ private static final String ACTION_TRAFF_UNSUBSCRIBE = "org.traffxml.traff.UNSUBSCRIBE";
+ private static final String COLUMN_DATA = "data";
+ private static final String CONTENT_SCHEMA = "content";
+ private static final String[] ERROR_STRINGS = {
+ "unknown (0)",
+ "invalid request (1)",
+ "subscription rejected by the source (2)",
+ "requested area not covered (3)",
+ "requested area partially covered (4)",
+ "subscription ID not recognized by the source (5)",
+ "unknown (6)",
+ "source reported an internal error (7)"
+ };
+ private static final String EXTRA_CAPABILITIES = "capabilities";
private static final String EXTRA_FEED = "feed";
+ private static final String EXTRA_FILTER_LIST = "filter_list";
+ private static final String EXTRA_PACKAGE = "package";
+ private static final String EXTRA_SUBSCRIPTION_ID = "subscription_id";
+ private static final String MIME_TYPE_TRAFF = "vnd.android.cursor.dir/org.traffxml.message";
+ private static final int RESULT_OK = -1;
+ private static final int RESULT_INTERNAL_ERROR = 7;
+ private static final int RESULT_INVALID = 1;
+ private static final int RESULT_SUBSCRIPTION_REJECTED = 2;
+ private static final int RESULT_NOT_COVERED = 3;
+ private static final int RESULT_PARTIALLY_COVERED = 4;
+ private static final int RESULT_SUBSCRIPTION_UNKNOWN = 5;
+ private static final String TAG = "NavitTraff";
private final long mCbid;
+ private final Context context;
+
+ /** Active subscriptions (key is the subscription ID, value is the package ID). */
+ private Map<String, String> subscriptions = new HashMap<String, String>();
+
/**
* Forwards a newly received TraFF feed to the traffic module for processing.
*
@@ -65,39 +107,322 @@ public class NavitTraff extends BroadcastReceiver {
*/
NavitTraff(Context context, long cbid) {
this.mCbid = cbid;
+ this.context = context.getApplicationContext();
+
+ /* An intent filter for TraFF 0.7 events. */
+ IntentFilter traffFilter07 = new IntentFilter();
+ traffFilter07.addAction(ACTION_TRAFF_FEED);
- /* An intent filter for TraFF events. */
- IntentFilter traffFilter = new IntentFilter();
- traffFilter.addAction(ACTION_TRAFF_FEED);
- traffFilter.addAction(ACTION_TRAFF_POLL);
+ /* An intent filter for TraFF 0.8 events. */
+ IntentFilter traffFilter08 = new IntentFilter();
+ traffFilter08.addAction(ACTION_TRAFF_FEED);
+ traffFilter08.addDataScheme(CONTENT_SCHEMA);
+ try {
+ traffFilter08.addDataType(MIME_TYPE_TRAFF);
+ } catch (MalformedMimeTypeException e) {
+ // as long as the constant is a well-formed MIME type, this exception never gets thrown
+ e.printStackTrace();
+ }
- context.registerReceiver(this, traffFilter);
- /* TODO unregister receiver on exit */
+ this.context.registerReceiver(this, traffFilter07);
+ this.context.registerReceiver(this, traffFilter08);
- /* Broadcast a poll intent */
+ /* Broadcast a poll intent to all TraFF 0.7-only receivers */
Intent outIntent = new Intent(ACTION_TRAFF_POLL);
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
- if (receivers != null) {
- for (ResolveInfo receiver : receivers) {
+ PackageManager pm = this.context.getPackageManager();
+ List<ResolveInfo> receivers07 = pm.queryBroadcastReceivers(outIntent, 0);
+ List<ResolveInfo> receivers08 = pm.queryBroadcastReceivers(new Intent(ACTION_TRAFF_GET_CAPABILITIES), 0);
+ if (receivers07 != null) {
+ /* get receivers which support only TraFF 0.7 and poll them */
+ if (receivers08 != null)
+ receivers07.removeAll(receivers08);
+ for (ResolveInfo receiver : receivers07) {
ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
receiver.activityInfo.name);
outIntent = new Intent(ACTION_TRAFF_POLL);
outIntent.setComponent(cn);
- context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION);
+ this.context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION);
+ }
+ }
+ }
+
+ void close() {
+ for (Map.Entry<String, String> subscription : subscriptions.entrySet()) {
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, subscription.getKey());
+ sendTraffIntent(this.context, ACTION_TRAFF_UNSUBSCRIBE, null, extras, subscription.getValue(),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+ this.context.unregisterReceiver(this);
+ }
+
+ void onFilterUpdate(String filterList) {
+ /* change existing subscriptions */
+ for (Map.Entry<String, String> entry : subscriptions.entrySet()) {
+ Log.d(TAG, String.format("changing subscription %s (%s)", entry.getKey(), entry.getValue()));
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, entry.getKey());
+ extras.putString(EXTRA_FILTER_LIST, filterList);
+ sendTraffIntent(context, ACTION_TRAFF_SUBSCRIPTION_CHANGE, null, extras,
+ entry.getValue(),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+
+ /* set up missing subscriptions */
+ PackageManager pm = this.context.getPackageManager();
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(ACTION_TRAFF_GET_CAPABILITIES), 0);
+ if (receivers != null) {
+ /* filter out receivers to which we are already subscribed */
+ Iterator<ResolveInfo> iter = receivers.iterator();
+ while (iter.hasNext()) {
+ ResolveInfo receiver = iter.next();
+ if (subscriptions.containsValue(receiver.activityInfo.applicationInfo.packageName))
+ iter.remove();
+ }
+
+ for (ResolveInfo receiver : receivers) {
+ Log.d(TAG, "subscribing to " + receiver.activityInfo.applicationInfo.packageName);
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_PACKAGE, context.getPackageName());
+ extras.putString(EXTRA_FILTER_LIST, filterList);
+ sendTraffIntent(context, ACTION_TRAFF_SUBSCRIBE, null, extras,
+ receiver.activityInfo.applicationInfo.packageName,
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
}
}
}
@Override
public void onReceive(Context context, Intent intent) {
- if ((intent != null) && (intent.getAction().equals(ACTION_TRAFF_FEED))) {
- String feed = intent.getStringExtra(EXTRA_FEED);
- if (feed == null) {
- Log.w(this.getClass().getSimpleName(), "empty feed, ignoring");
- } else {
- onFeedReceived(mCbid, feed);
+ if (intent != null) {
+ if (intent.getAction().equals(ACTION_TRAFF_FEED)) {
+ Uri uri = intent.getData();
+ if (uri != null) {
+ /* 0.8 feed */
+ String subscriptionId = intent.getStringExtra(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptions.containsKey(subscriptionId))
+ fetchMessages(context, uri);
+ else {
+ /*
+ * If we don’t recognize the subscription, skip processing and unsubscribe.
+ * Note: if EXTRA_PACKAGE is not set, sendTraffIntent() sends the request to every
+ * manifest-declared receiver which handles the request.
+ */
+ Log.d(TAG,
+ String.format("got a feed from %s for unknown subscription %s, URI %s; unsubscribing",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId, uri));
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, subscriptionId);
+ sendTraffIntent(context, ACTION_TRAFF_UNSUBSCRIBE, null, extras,
+ intent.getStringExtra(EXTRA_PACKAGE),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+ } else {
+ /* 0.7 feed */
+ String packageName = intent.getStringExtra(EXTRA_PACKAGE);
+ /*
+ * If the feed comes from a TraFF 0.8+ source and we are subscribed, skip it.
+ * As a side effect of the current implementation, if a “bilingual” TraFF 0.7/0.8
+ * source sends a broadcast feed before we have subscribed to it, we would process
+ * the whole feed first, and then subscribe to a subset of that data.
+ * If that turns out to be an issue, we would need to detect TraFF 0.8-capable
+ * sources and discard broadcast feeds from them.
+ */
+ if ((packageName != null) && subscriptions.containsValue(packageName))
+ return;
+ String feed = intent.getStringExtra(EXTRA_FEED);
+ if (feed == null) {
+ Log.w(this.getClass().getSimpleName(), "empty feed, ignoring");
+ } else {
+ onFeedReceived(mCbid, feed);
+ }
+ } // uri != null
+ } else if (intent.getAction().equals(ACTION_TRAFF_SUBSCRIBE)) {
+ if (this.getResultCode() != RESULT_OK) {
+ Bundle extras = this.getResultExtras(true);
+ if (extras != null)
+ Log.e(this.getClass().getSimpleName(), String.format("subscription to %s failed, %s",
+ extras.getString(EXTRA_PACKAGE), formatTraffError(this.getResultCode())));
+ else
+ Log.e(this.getClass().getSimpleName(), String.format("subscription failed, %s",
+ formatTraffError(this.getResultCode())));
+ return;
+ }
+ Bundle extras = this.getResultExtras(true);
+ String data = this.getResultData();
+ String packageName = extras.getString(EXTRA_PACKAGE);
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptionId == null) {
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription to %s failed: no subscription ID returned", packageName));
+ return;
+ } else if (packageName == null) {
+ Log.e(this.getClass().getSimpleName(), "subscription failed: no package name");
+ return;
+ } else if (data == null) {
+ Log.w(this.getClass().getSimpleName(),
+ String.format("subscription to %s successful (ID: %s) but no content URI was supplied. "
+ + "This is an issue with the source and may result in delayed message retrieval.",
+ packageName, subscriptionId));
+ subscriptions.put(subscriptionId, packageName);
+ return;
+ }
+ Log.d(TAG, "subscription to " + packageName + " successful, ID: " + subscriptionId);
+ subscriptions.put(subscriptionId, packageName);
+ fetchMessages(context, Uri.parse(data));
+ } else if (intent.getAction().equals(ACTION_TRAFF_SUBSCRIPTION_CHANGE)) {
+ if (this.getResultCode() != RESULT_OK) {
+ Bundle extras = this.getResultExtras(true);
+ if (extras != null)
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription change for %s failed: %s",
+ extras.getString(EXTRA_SUBSCRIPTION_ID),
+ formatTraffError(this.getResultCode())));
+ else
+ Log.e(this.getClass().getSimpleName(),
+ String.format("subscription change failed: %s",
+ formatTraffError(this.getResultCode())));
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ String data = this.getResultData();
+ String subscriptionId = extras.getString(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptionId == null) {
+ Log.w(this.getClass().getSimpleName(),
+ "subscription change successful but the source did not specify the subscription ID. "
+ + "This is an issue with the source and may result in delayed message retrieval. "
+ + "URI: " + data);
+ return;
+ } else if (data == null) {
+ Log.w(this.getClass().getSimpleName(),
+ String.format("subscription change for %s successful but no content URI was supplied. "
+ + "This is an issue with the source and may result in delayed message retrieval.",
+ subscriptionId));
+ return;
+ } else if (!subscriptions.containsKey(subscriptionId)) {
+ Log.e(this.getClass().getSimpleName(),
+ "subscription change failed: unknown subscription ID " + subscriptionId);
+ return;
+ }
+ Log.d(TAG, "subscription change for " + subscriptionId + " successful");
+ fetchMessages(context, Uri.parse(data));
+ } else if (intent.getAction().equals(ACTION_TRAFF_UNSUBSCRIBE)) {
+ /*
+ * If we ever unsubscribe for reasons other than that we are shutting down or got a feed for
+ * a subscription we don’t recognize, or if we start keeping a persistent list of
+ * subscriptions, we need to delete the subscription from our list. Until then, there is
+ * nothing to do here: either the subscription isn’t in the list, or we are about to shut
+ * down and the whole list is about to get discarded.
+ */
+ } else if (intent.getAction().equals(ACTION_TRAFF_HEARTBEAT)) {
+ String subscriptionId = intent.getStringExtra(EXTRA_SUBSCRIPTION_ID);
+ if (subscriptions.containsKey(subscriptionId)) {
+ Log.d(TAG,
+ String.format("got a heartbeat from %s for subscription %s; sending result",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId));
+ this.setResult(RESULT_OK, null, null);
+ } else {
+ /*
+ * If we don’t recognize the subscription, skip reply and unsubscribe.
+ * Note: if EXTRA_PACKAGE is not set, sendTraffIntent() sends the request to every
+ * manifest-declared receiver which handles the request.
+ */
+ Log.d(TAG,
+ String.format("got a heartbeat from %s for unknown subscription %s; unsubscribing",
+ intent.getStringExtra(EXTRA_PACKAGE), subscriptionId));
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SUBSCRIPTION_ID, subscriptionId);
+ sendTraffIntent(context, ACTION_TRAFF_UNSUBSCRIBE, null, extras,
+ intent.getStringExtra(EXTRA_PACKAGE),
+ Manifest.permission.ACCESS_COARSE_LOCATION, this);
+ }
+ } // intent.getAction()
+ } // intent != null
+ }
+
+ /**
+ * Fetches messages from a content provider.
+ *
+ * @param context The context to use for the content resolver
+ * @param uri The content provider URI
+ */
+ private void fetchMessages(Context context, Uri uri) {
+ try {
+ Cursor cursor = context.getContentResolver().query(uri, new String[] {COLUMN_DATA}, null, null, null);
+ if (cursor == null)
+ return;
+ if (cursor.getCount() < 1) {
+ cursor.close();
+ return;
}
+ StringBuilder builder = new StringBuilder("<feed>\n");
+ while (cursor.moveToNext())
+ builder.append(cursor.getString(cursor.getColumnIndex(COLUMN_DATA))).append("\n");
+ builder.append("</feed>");
+ cursor.close();
+ onFeedReceived(mCbid, builder.toString());
+ } catch (Exception e) {
+ Log.w(TAG, String.format("Unable to fetch messages from %s", uri.toString()), e);
+ e.printStackTrace();
}
}
+
+ /**
+ * Sends a TraFF intent to a source. This encapsulates most of the low-level Android handling.
+ *
+ * <p>If the recipient specified in {@code packageName} declares multiple receivers for the intent in its
+ * manifest, a separate intent will be delivered to each of them. The intent will not be delivered to
+ * receivers registered at runtime.
+ *
+ * <p>All intents are sent as explicit ordered broadcasts. This means two things:
+ *
+ * <p>Any app which declares a matching receiver in its manifest will be woken up to process the intent.
+ * This works even with certain Android 7 builds which restrict intent delivery to apps which are not
+ * currently running.
+ *
+ * <p>It is safe for the recipient to unconditionally set result data. If the recipient does not set result
+ * data, the result will have a result code of {@link #RESULT_INTERNAL_ERROR}, no data and no extras.
+ *
+ * @param context The context
+ * @param action The intent action.
+ * @param data The intent data (for TraFF, this is the content provider URI), or null
+ * @param extras The extras for the intent
+ * @param packageName The package name for the recipient, or null to deliver the intent to all matching receivers
+ * @param receiverPermission A permission which the recipient must hold, or null if not required
+ * @param resultReceiver A BroadcastReceiver which will receive the result for the intent
+ */
+ /* From traff-consumer-android, by the same author and re-licensed under GPL2 for Navit */
+ public static void sendTraffIntent(Context context, String action, Uri data, Bundle extras, String packageName,
+ String receiverPermission, BroadcastReceiver resultReceiver) {
+ Intent outIntent = new Intent(action);
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
+ if (receivers != null)
+ for (ResolveInfo receiver : receivers) {
+ if ((packageName != null) && !packageName.equals(receiver.activityInfo.applicationInfo.packageName))
+ continue;
+ ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
+ receiver.activityInfo.name);
+ outIntent = new Intent(action);
+ if (data != null)
+ outIntent.setData(data);
+ if (extras != null)
+ outIntent.putExtras(extras);
+ outIntent.setComponent(cn);
+ context.sendOrderedBroadcast(outIntent,
+ receiverPermission,
+ resultReceiver,
+ null, // scheduler,
+ RESULT_INTERNAL_ERROR, // initialCode,
+ null, // initialData,
+ null);
+ }
+ }
+
+ private static String formatTraffError(int code) {
+ if ((code < 0) || (code >= ERROR_STRINGS.length))
+ return String.format("unknown (%d)", code);
+ else
+ return ERROR_STRINGS[code];
+ }
}
diff --git a/navit/attr.c b/navit/attr.c
index de2a508f0..b9d54899f 100644
--- a/navit/attr.c
+++ b/navit/attr.c
@@ -680,6 +680,10 @@ attr_generic_prepend_attr(struct attr **attrs, struct attr *attr) {
*
* If `attrs` does not contain `attr`, this function is a no-op.
*
+ * Attributes are matched based on their `type` and `u.data` members, thus `attr` can be a shallow copy
+ * of the attribute, and can match multiple attributes in the list. The `attr` argument itself is not
+ * changed.
+ *
* @param attrs The attribute list
* @param attr The attribute to remove from the list
*
diff --git a/navit/navit.c b/navit/navit.c
index 0886778f0..d94e123cb 100644
--- a/navit/navit.c
+++ b/navit/navit.c
@@ -1613,8 +1613,6 @@ void navit_set_destination(struct navit *this_, struct pcoord *c, const char *de
}
g_free(destination_file);
- callback_list_call_attr_0(this_->attr_cbl, attr_destination);
-
if (this_->route) {
struct attr attr;
int dstcount;
@@ -1637,10 +1635,12 @@ void navit_set_destination(struct navit *this_, struct pcoord *c, const char *de
g_free(pc);
g_free(destination_file);
}
-
- if (this_->ready == 3 && !(this_->flags & 4))
- navit_draw(this_);
}
+
+ callback_list_call_attr_0(this_->attr_cbl, attr_destination);
+
+ if (this_->route && this_->ready == 3 && !(this_->flags & 4))
+ navit_draw(this_);
}
/**
@@ -1683,15 +1683,30 @@ void navit_set_destinations(struct navit *this_, struct pcoord *c, int count, co
g_free(destination_file);
} else
this_->destination_valid=0;
- callback_list_call_attr_0(this_->attr_cbl, attr_destination);
- if (this_->route) {
+ if (this_->route)
route_set_destinations(this_->route, c, count, async);
- if (this_->ready == 3)
- navit_draw(this_);
- }
+ callback_list_call_attr_0(this_->attr_cbl, attr_destination);
+ if (this_->route && this_->ready == 3)
+ navit_draw(this_);
}
+/**
+ * @brief Retrieves destinations from the route
+ *
+ * Prior to calling this method, you may want to retrieve the number of destinations by calling
+ * {@link navit_get_destination_count(struct navit *)} and assigning a buffer of sufficient capacity.
+ *
+ * If the return value equals `count`, the buffer was either just large enough or too small to hold the
+ * entire list of destinations; there is no way to tell from the result which is the case.
+ *
+ * If the Navit instance does not have a route, the result is 0.
+ *
+ * @param this_ The Navit instance
+ * @param pc Pointer to an array of projected coordinates which will receive the destination coordinates
+ * @param count Capacity of `pc`
+ * @return The number of destinations stored in `pc`, never greater than `count`
+ */
int navit_get_destinations(struct navit *this_, struct pcoord *pc, int count) {
if(!this_->route)
return 0;
@@ -1699,6 +1714,12 @@ int navit_get_destinations(struct navit *this_, struct pcoord *pc, int count) {
}
+/**
+ * @brief Get the destinations count for the route
+ *
+ * @param this The Navit instance
+ * @return destination count for the route, or 0 if the Navit instance has no route
+ */
int navit_get_destination_count(struct navit *this_) {
if(!this_->route)
return 0;
@@ -3685,7 +3706,36 @@ int navit_get_blocked(struct navit *this_) {
void navit_destroy(struct navit *this_) {
dbg(lvl_debug,"enter %p",this_);
+ GList *mapsets;
+ struct map * map;
+ struct attr attr;
graphics_draw_cancel(this_->gra, this_->displaylist);
+
+ mapsets = this_->mapsets;
+ while (mapsets) {
+ GList *maps = NULL;
+ struct mapset_handle *msh;
+ msh = mapset_open(mapsets->data);
+ while (msh && (map = mapset_next(msh, 0))) {
+ /* Add traffic map (identified by the `attr_traffic` attribute) to list of maps to remove */
+ if (map_get_attr(map, attr_traffic, &attr, NULL))
+ maps = g_list_append(maps, map);
+ }
+ mapset_close(msh);
+
+ /* Remove traffic maps, if any */
+ while (maps) {
+ attr.type = attr_map;
+ attr.u.map = maps->data;
+ mapset_remove_attr(mapsets->data, &attr);
+ attr_free_content(&attr);
+ maps = g_list_next(maps);
+ }
+ if (maps)
+ g_list_free(maps);
+ mapsets = g_list_next(mapsets);
+ }
+
callback_list_call_attr_1(this_->attr_cbl, attr_destroy, this_);
attr_list_free(this_->attrs);
diff --git a/navit/route.c b/navit/route.c
index 8e2ef91ec..4c85e3fe9 100644
--- a/navit/route.c
+++ b/navit/route.c
@@ -939,6 +939,17 @@ struct map_selection *route_selection;
/**
* @brief Returns a single map selection
+ *
+ * The boundaries of the selection are determined as follows: First a rectangle spanning `c1` and `c2` is
+ * built (the two coordinates can be any two opposite corners of the rectangle). Then its maximum extension
+ * (height or width) is determined and multiplied with the percentage specified by `rel`. The resulting
+ * amount of padding is added to each edge. After that, the amount specified by `abs` is added to each edge.
+ *
+ * @param order Map order (deepest tile level) to select
+ * @param c1 First coordinate
+ * @param c2 Second coordinate
+ * @param rel Relative padding to add to the selection rectangle, in percent
+ * @param abs Absolute padding to add to the selection rectangle
*/
struct map_selection *
route_rect(int order, struct coord *c1, struct coord *c2, int rel, int abs) {
@@ -1063,7 +1074,7 @@ struct map_selection * route_get_selection(struct route * this_) {
*
* @param sel Start of the list to be destroyed
*/
-static void route_free_selection(struct map_selection *sel) {
+void route_free_selection(struct map_selection *sel) {
struct map_selection *next;
while (sel) {
next=sel->next;
@@ -1130,6 +1141,20 @@ void route_set_destinations(struct route *this, struct pcoord *dst, int count, i
profile(0,"end");
}
+/**
+ * @brief Retrieves destinations from the route
+ *
+ * Prior to calling this method, you may want to retrieve the number of destinations by calling
+ * {@link route_get_destination_count(struct route *)} and assigning a buffer of sufficient capacity.
+ *
+ * If the return value equals `count`, the buffer was either just large enough or too small to hold the
+ * entire list of destinations; there is no way to tell from the result which is the case.
+ *
+ * @param this The route instance
+ * @param pc Pointer to an array of projected coordinates which will receive the destination coordinates
+ * @param count Capacity of `pc`
+ * @return The number of destinations stored in `pc`, never greater than `count`
+ */
int route_get_destinations(struct route *this, struct pcoord *pc, int count) {
int ret=0;
GList *l=this->destinations;
diff --git a/navit/route_protected.h b/navit/route_protected.h
index 586fde91d..3ce6c4663 100644
--- a/navit/route_protected.h
+++ b/navit/route_protected.h
@@ -160,6 +160,7 @@ struct route_graph {
/* prototypes */
struct route_graph * route_get_graph(struct route *this_);
struct map_selection * route_get_selection(struct route * this_);
+void route_free_selection(struct map_selection *sel);
void route_add_traffic_distortion(struct route *this_, struct item *item);
void route_remove_traffic_distortion(struct route *this_, struct item *item);
void route_change_traffic_distortion(struct route *this_, struct item *item);
diff --git a/navit/traffic.c b/navit/traffic.c
index f19560829..2359fea06 100644
--- a/navit/traffic.c
+++ b/navit/traffic.c
@@ -3842,31 +3842,33 @@ static int traffic_message_is_valid(struct traffic_message * this_) {
return 0;
}
if (!this_->receive_time || !this_->update_time) {
- dbg(lvl_debug, "receive_time or update_time not supplied");
+ dbg(lvl_debug, "%s: receive_time or update_time not supplied", this_->id);
return 0;
}
if (!this_->is_cancellation) {
if (!this_->expiration_time && !this_->end_time) {
- dbg(lvl_debug, "not a cancellation, but neither expiration_time nor end_time supplied");
+ dbg(lvl_debug, "%s: not a cancellation, but neither expiration_time nor end_time supplied",
+ this_->id);
return 0;
}
if (!this_->location) {
- dbg(lvl_debug, "not a cancellation, but no location supplied");
+ dbg(lvl_debug, "%s: not a cancellation, but no location supplied", this_->id);
return 0;
}
if (!traffic_location_is_valid(this_->location)) {
- dbg(lvl_debug, "not a cancellation, but location is invalid");
+ dbg(lvl_debug, "%s: not a cancellation, but location is invalid", this_->id);
return 0;
}
if (!this_->event_count || !this_->events) {
- dbg(lvl_debug, "not a cancellation, but no events supplied");
+ dbg(lvl_debug, "%s: not a cancellation, but no events supplied", this_->id);
return 0;
}
for (i = 0; i < this_->event_count; i++)
if (this_->events[i])
has_valid_events |= traffic_event_is_valid(this_->events[i]);
if (!has_valid_events) {
- dbg(lvl_debug, "not a cancellation, but all events (%d in total) are invalid", this_->event_count);
+ dbg(lvl_debug, "%s: not a cancellation, but all events (%d in total) are invalid",
+ this_->id, this_->event_count);
return 0;
}
}
@@ -4626,7 +4628,6 @@ static struct traffic * traffic_new(struct attr *parent, struct attr **attrs) {
navit_object_destroy((struct navit_object *) this_);
return NULL;
}
- navit_object_ref((struct navit_object *) this_);
dbg(lvl_debug,"return %p", this_);
// TODO do this once and cycle through all plugins
@@ -4888,7 +4889,7 @@ static void traffic_xml_end(xml_context *dummy, const char *tag_name, void *data
count,
(struct traffic_event **) children);
if (!traffic_message_is_valid(message)) {
- dbg(lvl_error, "malformed message detected, skipping");
+ dbg(lvl_error, "%s: malformed message detected, skipping", message->id);
traffic_message_destroy(message);
} else
state->messages = g_list_append(state->messages, message);
@@ -4945,7 +4946,9 @@ static void traffic_xml_end(xml_context *dummy, const char *tag_name, void *data
state->si = NULL;
/* TODO preserve unknown (and thus invalid) events if they have maxspeed set */
if (!traffic_event_is_valid(event)) {
- dbg(lvl_debug, "invalid or unknown event detected, skipping");
+ dbg(lvl_debug, "invalid or unknown event %s/%s detected, skipping",
+ traffic_xml_get_attr("class", el->names, el->values),
+ traffic_xml_get_attr("type", el->names, el->values));
traffic_event_destroy(event);
} else
state->events = g_list_append(state->events, event);
@@ -5800,7 +5803,6 @@ struct map * traffic_get_map(struct traffic *this_) {
attrs[4] = NULL;
this_->shared->map = map_new(NULL, attrs);
- navit_object_ref((struct navit_object *) this_->shared->map);
/* populate map with previously stored messages */
filename = g_strjoin(NULL, navit_get_user_data_directory(TRUE), "/traffic.xml", NULL);
@@ -5938,6 +5940,13 @@ void traffic_set_route(struct traffic *this_, struct route *rt) {
this_->shared->rt = rt;
}
+void traffic_destroy(struct traffic *this_) {
+ if (this_->meth.destroy)
+ this_->meth.destroy(this_->priv);
+ attr_list_free(this_->attrs);
+ g_free(this_);
+}
+
struct object_func traffic_func = {
attr_traffic,
(object_func_new)traffic_new,
@@ -5948,7 +5957,7 @@ struct object_func traffic_func = {
(object_func_add_attr)navit_object_add_attr,
(object_func_remove_attr)navit_object_remove_attr,
(object_func_init)NULL,
- (object_func_destroy)navit_object_destroy,
+ (object_func_destroy)traffic_destroy,
(object_func_dup)NULL,
(object_func_ref)navit_object_ref,
(object_func_unref)navit_object_unref,
diff --git a/navit/traffic.h b/navit/traffic.h
index bf0ca907e..f695ad156 100644
--- a/navit/traffic.h
+++ b/navit/traffic.h
@@ -58,6 +58,24 @@ extern "C" {
#endif
/**
+ * @brief Translates a Navit tile order to a minimum road class as used in TraFF.
+ *
+ * This can be used to translate a map selection into a TraFF filter.
+ *
+ * The tile order is the lowest tile level in which an object of a certain type can be placed (higher numbers
+ * correspond to lower levels). Currently, 8 is the maximum order for `highway_city`, `highway_land` and
+ * `street_n_lanes`, equivalent to `MOTORWAY` and `TRUNK`. 10 is the maximum order for `street_4_city` and
+ * `street_4_land` (`SECONDARY`), 12 for `street_3_city` and `street_3_land` (`TERTIARY`). All others can
+ * be placed in any tile level.
+ *
+ * This macro returns `PRIMARY`, `SECONDARY` and `TERTIARY` for the three bins above these cut-off orders,
+ * corresponding to one level below the lowest road class we expect to find there. (Not considering that
+ * low-level roads can be placed into higher-level tiles if they cross a tile boundary of the next lower
+ * level.) Below the lowest cut-off order, the macro returns NULL.
+ */
+#define order_to_min_road_class(x) (x <= 8 ? "PRIMARY" : x <= 10 ? "SECONDARY" : x <= 12 ? "TERTIARY" : NULL)
+
+/**
* @brief Classes for events.
*/
/* If additional event classes are introduced, traffic_event_is_valid() must be adapted to recognize them. */
@@ -239,6 +257,7 @@ struct traffic_message_priv;
*/
struct traffic_methods {
struct traffic_message **(* get_messages)(struct traffic_priv * this_); /**< Retrieves new messages from the traffic plugin */
+ void (*destroy)(struct traffic_priv * this_); /**< Destructor for the traffic plugin */
};
/**
@@ -989,6 +1008,11 @@ void traffic_set_mapset(struct traffic *this_, struct mapset *ms);
*/
void traffic_set_route(struct traffic *this_, struct route *rt);
+/**
+ * @brief Destructor.
+ */
+void traffic_destroy(struct traffic *this_);
+
/* end of prototypes */
#ifdef __cplusplus
}
diff --git a/navit/traffic/dummy/traffic_dummy.c b/navit/traffic/dummy/traffic_dummy.c
index b838752dc..2ab4073d4 100644
--- a/navit/traffic/dummy/traffic_dummy.c
+++ b/navit/traffic/dummy/traffic_dummy.c
@@ -154,6 +154,7 @@ struct traffic_message ** traffic_dummy_get_messages(struct traffic_priv * this_
*/
static struct traffic_methods traffic_dummy_meth = {
traffic_dummy_get_messages,
+ NULL,
};
/**
diff --git a/navit/traffic/null/traffic_null.c b/navit/traffic/null/traffic_null.c
index 94546a666..02fc461c6 100644
--- a/navit/traffic/null/traffic_null.c
+++ b/navit/traffic/null/traffic_null.c
@@ -65,6 +65,7 @@ struct traffic_message ** traffic_null_get_messages(struct traffic_priv * this_)
*/
static struct traffic_methods traffic_null_meth = {
traffic_null_get_messages,
+ NULL,
};
/**
diff --git a/navit/traffic/traff_android/traffic_traff_android.c b/navit/traffic/traff_android/traffic_traff_android.c
index 266f51a0c..e0862ec5c 100644
--- a/navit/traffic/traff_android/traffic_traff_android.c
+++ b/navit/traffic/traff_android/traffic_traff_android.c
@@ -22,7 +22,7 @@
*
* @brief The TraFF plugin for Android
*
- * This plugin receives TraFF feeds via Android broadcasts.
+ * This plugin receives TraFF feeds via Android broadcasts and content providers.
*/
#include <string.h>
@@ -36,13 +36,31 @@
#include "item.h"
#include "attr.h"
#include "coord.h"
+#include "map.h"
+#include "route_protected.h"
+#include "route.h"
+#include "transform.h"
#include "xmlconfig.h"
#include "android.h"
#include "traffic.h"
#include "plugin.h"
#include "callback.h"
+#include "vehicle.h"
#include "debug.h"
#include "navit.h"
+#include "util.h"
+
+/**
+ * @brief Minimum area around the current position for which to retrieve traffic updates.
+ *
+ * 100000 is equivalent to around 50 km on each side of the current position. The actual subscription area
+ * can be larger, allowing for a subscription area to be kept over multiple position updates.
+ *
+ * The actual subscription area around the current location is stored in
+ * {@link struct traffic_priv::position_rect} and updated in
+ * {@link traffic_traff_android_position_callback(struct traffic_priv *, struct navit *, struct vehicle *)}.
+ */
+#define POSITION_RECT_SIZE 100000
/**
* @brief Stores information about the plugin instance.
@@ -50,13 +68,38 @@
struct traffic_priv {
struct navit * nav; /**< The navit instance */
struct callback * cbid; /**< The callback function for TraFF feeds **/
+ int position_valid; /**< Whether Navit currently has a valid position */
+ struct coord_rect * position_rect; /**< Rectangle around last known vehicle position (in `projection_mg`) */
+ struct map_selection * route_map_sel; /**< Map selection for the current route */
jclass NavitTraffClass; /**< The `NavitTraff` class */
jobject NavitTraff; /**< An instance of `NavitTraff` */
};
+void traffic_traff_android_destroy(struct traffic_priv * this_);
struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv * this_);
/**
+ * @brief Destructor.
+ */
+void traffic_traff_android_destroy(struct traffic_priv * this_) {
+ jmethodID cid;
+
+ cid = (*jnienv)->GetMethodID(jnienv, this_->NavitTraffClass, "close", "()V");
+ if (cid == NULL) {
+ dbg(lvl_error,"no method found");
+ return; /* exception thrown */
+ }
+ (*jnienv)->CallVoidMethod(jnienv, this_->NavitTraff, cid);
+
+ if (this_->position_rect)
+ g_free(this_->position_rect);
+ this_->position_rect = NULL;
+ if (this_->route_map_sel)
+ route_free_selection(this_->route_map_sel);
+ this_->route_map_sel = NULL;
+}
+
+/**
* @brief Returns an empty traffic report.
*
* @return Always `NULL`
@@ -70,6 +113,7 @@ struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv
*/
static struct traffic_methods traffic_traff_android_meth = {
traffic_traff_android_get_messages,
+ traffic_traff_android_destroy,
};
@@ -109,12 +153,133 @@ static void traffic_traff_android_on_feed_received(struct traffic_priv * this_,
/**
+ * @brief Sets the route map selection
+ *
+ * @param this_ The instance which will handle the selection update
+ */
+static void traffic_traff_android_set_selection(struct traffic_priv * this_) {
+ struct route * route;
+ struct coord_geo lu, rl;
+ gchar *filter_list;
+ jstring j_filter_list;
+ gchar *min_road_class;
+ jmethodID cid;
+
+ if (this_->route_map_sel)
+ route_free_selection(this_->route_map_sel);
+ this_->route_map_sel = NULL;
+ if (navit_get_destination_count(this_->nav) && (route = (navit_get_route(this_->nav))))
+ this_->route_map_sel = route_get_selection(route);
+
+ /* start building the filter list */
+ filter_list = g_strconcat_printf(NULL, "<filter_list>\n");
+ if (this_->position_rect) {
+ transform_to_geo(projection_mg, &this_->position_rect->lu, &lu);
+ transform_to_geo(projection_mg, &this_->position_rect->rl, &rl);
+ filter_list = g_strconcat_printf(filter_list, " <filter bbox=\"%.5f %.5f %.5f %.5f\"/>\n",
+ rl.lat, lu.lng, lu.lat, rl.lng);
+ }
+ for (struct map_selection * sel = this_->route_map_sel; sel; sel = sel->next) {
+ transform_to_geo(projection_mg, &sel->u.c_rect.lu, &lu);
+ transform_to_geo(projection_mg, &sel->u.c_rect.rl, &rl);
+ min_road_class = order_to_min_road_class(sel->order);
+ if (!min_road_class)
+ filter_list = g_strconcat_printf(filter_list, " <filter bbox=\"%.5f %.5f %.5f %.5f\"/>\n",
+ rl.lat, lu.lng, lu.lat, rl.lng);
+ else
+ filter_list = g_strconcat_printf(filter_list, " <filter min_road_class=\"%s\" bbox=\"%.5f %.5f %.5f %.5f\"/>\n",
+ min_road_class, rl.lat, lu.lng, lu.lat, rl.lng);
+ }
+ /* the trailing \0 is required for NewStringUTF */
+ filter_list = g_strconcat_printf(filter_list, "</filter_list>\0");
+ j_filter_list = (*jnienv)->NewStringUTF(jnienv, filter_list);
+ cid = (*jnienv)->GetMethodID(jnienv, this_->NavitTraffClass, "onFilterUpdate", "(Ljava/lang/String;)V");
+ if (cid)
+ (*jnienv)->CallVoidMethod(jnienv, this_->NavitTraff, cid, j_filter_list);
+ g_free(filter_list);
+}
+
+
+/**
+ * @brief Callback for destination changes
+ *
+ * @param this_ The instance which will handle the destination update
+ */
+static void traffic_traff_android_destination_callback(struct traffic_priv * this_) {
+ traffic_traff_android_set_selection(this_);
+}
+
+
+/**
+ * @brief Callback for navigation status changes
+ *
+ * This callback is necessary to force an update of existing subscriptions when Navit acquires a new
+ * position (after not having had valid position information), as the map selection will change when
+ * the current position becomes known for the first time.
+ *
+ * @param this_ The instance which will handle the navigation status update
+ * @param status The status of the navigation engine (the value of the {@code nav_status} attribute)
+ */
+static void traffic_traff_android_status_callback(struct traffic_priv * this_, int status) {
+ int new_position_valid = (status != 1);
+ if (new_position_valid && !this_->position_valid) {
+ this_->position_valid = new_position_valid;
+ traffic_traff_android_set_selection(this_);
+ } else if (new_position_valid != this_->position_valid)
+ this_->position_valid = new_position_valid;
+}
+
+
+/**
+ * @brief Callback for position changes
+ *
+ * This updates {@link struct traffic_priv::position_rect} if the vehicle has moved far enough from its
+ * center to be within {@link POSITION_RECT_SIZE} of one of its boundaries. The new rectangle is created
+ * with twice that amount of padding, allowing the vehicle to move for at least that distance before the
+ * subscription needs to be updated again.
+ *
+ * @param this_ The instance which will handle the position update
+ * @param navit The Navit instance
+ * @param vehicle The vehicle which delivered the position update and from which the position can be queried
+ */
+static void traffic_traff_android_position_callback(struct traffic_priv * this_, struct navit *navit,
+ struct vehicle *vehicle) {
+ struct attr attr;
+ struct coord c;
+ struct coord_rect cr;
+ jmethodID cid;
+ if (!vehicle_get_attr(vehicle, attr_position_coord_geo, &attr, NULL))
+ return;
+ transform_from_geo(projection_mg, attr.u.coord_geo, &c);
+ cr.lu = c;
+ cr.rl = c;
+ cr.lu.x -= POSITION_RECT_SIZE;
+ cr.rl.x += POSITION_RECT_SIZE;
+ cr.lu.y += POSITION_RECT_SIZE;
+ cr.rl.y -= POSITION_RECT_SIZE;
+ if (!this_->position_rect)
+ this_->position_rect = g_new0(struct coord_rect, 1);
+ if (!coord_rect_contains(this_->position_rect, &cr.lu) || !coord_rect_contains(this_->position_rect, &cr.rl)) {
+ cr.lu.x -= POSITION_RECT_SIZE;
+ cr.rl.x += POSITION_RECT_SIZE;
+ cr.lu.y += POSITION_RECT_SIZE;
+ cr.rl.y -= POSITION_RECT_SIZE;
+ *(this_->position_rect) = cr;
+ traffic_traff_android_set_selection(this_);
+ }
+}
+
+
+/**
* @brief Initializes a traff_android plugin
*
* @return True on success, false on failure
*/
static int traffic_traff_android_init(struct traffic_priv * this_) {
jmethodID cid;
+ struct route * route;
+ struct attr attr;
+ struct navigation * navigation;
if (!android_find_class_global("org/navitproject/navit/NavitTraff", &this_->NavitTraffClass))
return 0;
@@ -131,6 +296,15 @@ static int traffic_traff_android_init(struct traffic_priv * this_) {
if (this_->NavitTraff)
this_->NavitTraff = (*jnienv)->NewGlobalRef(jnienv, this_->NavitTraff);
+ /* register callbacks for position and destination changes */
+ navit_add_callback(this_->nav, callback_new_attr_1(callback_cast(traffic_traff_android_position_callback),
+ attr_position_coord_geo, this_));
+ navit_add_callback(this_->nav, callback_new_attr_1(callback_cast(traffic_traff_android_destination_callback),
+ attr_destination, this_));
+ if ((navigation = navit_get_navigation(this_->nav)))
+ navigation_register_callback(navigation, attr_nav_status,
+ callback_new_attr_1(callback_cast(traffic_traff_android_status_callback), attr_nav_status, this_));
+
return 1;
}
@@ -154,6 +328,9 @@ static struct traffic_priv * traffic_traff_android_new(struct navit *nav, struct
ret = g_new0(struct traffic_priv, 1);
ret->nav = nav;
ret->cbid = callback_new_1(callback_cast(traffic_traff_android_on_feed_received), ret);
+ ret->position_valid = 0;
+ ret->position_rect = NULL;
+ ret->route_map_sel = NULL;
/* TODO populate members, if any */
*meth = traffic_traff_android_meth;
diff --git a/navit/util.c b/navit/util.c
index cf4412938..0dfdab20e 100644
--- a/navit/util.c
+++ b/navit/util.c
@@ -479,6 +479,19 @@ GList *g_hash_to_list_keys(GHashTable *h) {
return ret;
}
+/**
+ * @brief Appends a formatted string and appends it to an existing one.
+ *
+ * Usage is similar to the familiar C functions that take a format string and a variable argument list.
+ *
+ * Return value is a concatenation of `buffer` (unless it is NULL) and `fmt`, with the remaining arguments
+ * inserted into `fmt`.
+ *
+ * @param buffer An existing string, can be null and will be freed by this function
+ * @param fmt A format string (will not be altered)
+ *
+ * @return A newly allocated string, see description. The caller is responsible for freeing the returned string.
+ */
gchar *g_strconcat_printf(gchar *buffer, gchar *fmt, ...) {
gchar *str,*ret;
va_list ap;
diff --git a/navit/xmlconfig.h b/navit/xmlconfig.h
index d5697d53c..bcd0ceec9 100644
--- a/navit/xmlconfig.h
+++ b/navit/xmlconfig.h
@@ -116,7 +116,7 @@ extern struct object_func map_func, mapset_func, navit_func, osd_func, tracking_
layout_func, roadprofile_func, vehicleprofile_func, layer_func, config_func, profile_option_func, script_func, log_func,
speech_func, navigation_func, route_func, traffic_func;
-#define HAS_OBJECT_FUNC(x) ((x) == attr_map || (x) == attr_mapset || (x) == attr_navit || (x) == attr_osd || (x) == attr_trackingo || (x) == attr_vehicle || (x) == attr_maps || (x) == attr_layout || (x) == attr_roadprofile || (x) == attr_vehicleprofile || (x) == attr_layer || (x) == attr_config || (x) == attr_profile_option || (x) == attr_script || (x) == attr_log || (x) == attr_speech || (x) == attr_navigation || (x) == attr_route)
+#define HAS_OBJECT_FUNC(x) ((x) == attr_map || (x) == attr_mapset || (x) == attr_navit || (x) == attr_osd || (x) == attr_trackingo || (x) == attr_vehicle || (x) == attr_maps || (x) == attr_layout || (x) == attr_roadprofile || (x) == attr_vehicleprofile || (x) == attr_layer || (x) == attr_config || (x) == attr_profile_option || (x) == attr_script || (x) == attr_log || (x) == attr_speech || (x) == attr_navigation || (x) == attr_route || (x) == attr_traffic)
#define NAVIT_OBJECT struct object_func *func; int refcount; struct attr **attrs;
struct navit_object {