summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormvglasow <michael@vonglasow.com>2018-10-13 22:11:49 +0200
committerGitHub <noreply@github.com>2018-10-13 22:11:49 +0200
commitc094186308e90c3a6eb6ff71214929204277afad (patch)
treef5653dc50204ca86a92425f9687ed19468c7885c
parent84ec04ba1ca2f36d591a297fa42a93a5f04de747 (diff)
parent34dd7b4b416fbe4dc6ec036f100e69bae2b00a62 (diff)
downloadnavit-c094186308e90c3a6eb6ff71214929204277afad.tar.gz
Merge pull request #672 from navit-gps/traffic-merge1
First merge for traffic branch
-rwxr-xr-xCMakeLists.txt4
-rw-r--r--README.md38
-rw-r--r--navit/CMakeLists.txt2
-rw-r--r--navit/android.c11
-rw-r--r--navit/android/src/org/navitproject/navit/NavitTraff.java108
-rw-r--r--navit/attr.h1
-rw-r--r--navit/attr_def.h1
-rw-r--r--navit/binding/dbus/binding_dbus.c200
-rw-r--r--navit/gui/internal/gui_internal_html.c9
-rw-r--r--navit/item.c51
-rw-r--r--navit/item.h1
-rw-r--r--navit/navit.c25
-rw-r--r--navit/navit.dtd4
-rw-r--r--navit/plugin.h2
-rw-r--r--navit/plugin_def.h1
-rw-r--r--navit/route.c958
-rw-r--r--navit/route_protected.h184
-rw-r--r--navit/start_real.c2
-rw-r--r--navit/traffic.c5194
-rw-r--r--navit/traffic.h996
-rw-r--r--navit/traffic/dummy/CMakeLists.txt1
-rw-r--r--navit/traffic/dummy/traffic_dummy.c190
-rw-r--r--navit/traffic/null/CMakeLists.txt1
-rw-r--r--navit/traffic/null/traffic_null.c101
-rw-r--r--navit/traffic/traff_android/CMakeLists.txt1
-rw-r--r--navit/traffic/traff_android/traffic_traff_android.c173
-rw-r--r--navit/util.c381
-rw-r--r--navit/util.h7
-rw-r--r--navit/xmlconfig.c90
-rw-r--r--navit/xmlconfig.h8
30 files changed, 8366 insertions, 379 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 681b27b9d..f7fee7cfa 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -499,6 +499,9 @@ add_module(plugin/j1850 "Default" FALSE)
add_module(speech/android "Default" FALSE)
add_module(speech/espeak "Default" FALSE)
add_module(speech/iphone "Default" FALSE)
+add_module(traffic/dummy "Default" TRUE)
+add_module(traffic/null "Default" TRUE)
+add_module(traffic/traff_android "Default" FALSE)
add_module(vehicle/android "Default" FALSE)
add_module(vehicle/iphone "Default" FALSE)
add_module(vehicle/wince "Default" FALSE)
@@ -706,6 +709,7 @@ if(ANDROID)
set_with_reason(graphics/null "Android detected" FALSE)
set_with_reason(graphics/android "Android detected" TRUE)
set_with_reason(speech/android "Android detected" TRUE)
+ set_with_reason(traffic/traff_android "Android detected" TRUE)
set_with_reason(vehicle/android "Android detected" TRUE)
set_with_reason(vehicle/file "Android detected" FALSE)
set_with_reason(vehicle/gpsd "Android detected" FALSE)
diff --git a/README.md b/README.md
index 2af99a32e..f71a5a351 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Navit on Linux based Carputer:
<a href="https://f-droid.org/repository/browse/?fdfilter=navit&fdid=org.navitproject.navit"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Get_it_on_F-Droid.svg/200px-Get_it_on_F-Droid.svg.png" height="100"/></a>
</p>
-NavIT
+Navit
=====
Navit is a open source (GPL) car navigation system with routing engine.
@@ -48,14 +48,14 @@ If you don't know where to start, we recommend you to read the
Interactive Help : http://wiki.navit-project.org/index.php/Interactive_help
-Maps:
-=====
+Maps
+====
The best navigation system is useless without maps. Those three maps
are known to work:
-- OpenStreetMaps: display, routing, but street name search isn't complete
- (see http://wiki.navit-project.org/index.php/OpenStreetMaps)
+- OpenStreetMap: display, routing, but street name search isn't complete
+ (see http://wiki.navit-project.org/index.php/OpenStreetMap)
- Grosser Reiseplaner and compliant maps: full support
(see http://wiki.navit-project.org/index.php/European_maps)
@@ -64,29 +64,31 @@ are known to work:
(see http://wiki.navit-project.org/index.php/Garmin_maps)
-GPS Support:
-============
+GPS Support
+===========
-Navit read the current vehicle position:
-- directly from a file
+Navit reads the current vehicle position:
+- directly from a file or port
- from gpsd (local or remote)
+- from the location service of several mobile platforms
- from udp server (friends tracking) (experimental)
Routing algorithm
=================
-NavIt uses a Dijkstra algorithm for routing. The routing starts at the
-destination by assigning a value to each point directly connected to
-destination point. The value represents the estimated time needed to
-pass this distance.
+Navit uses LPA* (see https://en.wikipedia.org/wiki/Lifelong_Planning_A*), a derivative of the Dijkstra algorithm, for
+routing. Routing starts at the destination by assigning a value to each point directly connected to the destination
+point. The value represents the estimated time needed to reach the destination from that point.
-Now the point with the lowest value is chosen using the Fibonacci
-heap and a value is assigned to connected points whos are
-unevaluated or whos current value ist greater than the new one.
+Now the point with the lowest value is chosen using the Fibonacci heap, and a value is assigned to connected points
+which are unevaluated or whose current value is greater than the new one.
The search is repeated until the origin is found.
-Once the origin is reached, all that needs to be done is to follow the
-points with the lowest values to the destination.
+Once the origin is reached, all that needs to be done is to follow the points with the lowest values to the
+destination.
+LPA* is slightly more complex, as it allows partial re-evaluation of the route graph as segment costs change. This is
+used by the (still experimental) traffic module, which can process traffic reports and tries to find a way around
+traffic problems. Refer to the Wikipedia page for a full description. \ No newline at end of file
diff --git a/navit/CMakeLists.txt b/navit/CMakeLists.txt
index e4a6fbd0c..f6dbe5bb8 100644
--- a/navit/CMakeLists.txt
+++ b/navit/CMakeLists.txt
@@ -9,7 +9,7 @@ set(NAVIT_SRC announcement.c atom.c attr.c cache.c callback.c command.c config_.
event.c file.c geom.c graphics.c gui.c item.c layout.c log.c main.c map.c maps.c
linguistics.c mapset.c maptype.c menu.c messages.c bookmarks.c navit.c navit_nls.c navigation.c osd.c param.c phrase.c plugin.c popup.c
profile.c profile_option.c projection.c roadprofile.c route.c script.c search.c speech.c start_real.c sunriset.c transform.c track.c
- search_houseno_interpol.c util.c vehicle.c vehicleprofile.c xmlconfig.c )
+ search_houseno_interpol.c traffic.c util.c vehicle.c vehicleprofile.c xmlconfig.c )
if(NOT USE_PLUGINS)
list(APPEND NAVIT_SRC ${CMAKE_CURRENT_BINARY_DIR}/builtin.c)
diff --git a/navit/android.c b/navit/android.c
index d595cd43c..9b06ebb44 100644
--- a/navit/android.c
+++ b/navit/android.c
@@ -172,6 +172,17 @@ JNIEXPORT void JNICALL Java_org_navitproject_navit_NavitSensors_SensorCallback(
callback_call_4((struct callback *)id, sensor, &x, &y, &z);
}
+JNIEXPORT void JNICALL Java_org_navitproject_navit_NavitTraff_onFeedReceived(JNIEnv * env, jobject thiz, int id,
+ jstring feed) {
+ const char *s;
+ s = (*env)->GetStringUTFChars(env, feed, NULL);
+ if (id)
+ callback_call_1((struct callback *) id, s);
+ (*env)->ReleaseStringUTFChars(env, feed, s);
+}
+
+
+
// type: 0=town, 1=street, 2=House#
void android_return_search_result(struct jni_object *jni_o, int type, struct pcoord *location, const char *address) {
struct coord_geo geo_location;
diff --git a/navit/android/src/org/navitproject/navit/NavitTraff.java b/navit/android/src/org/navitproject/navit/NavitTraff.java
new file mode 100644
index 000000000..a98b91948
--- /dev/null
+++ b/navit/android/src/org/navitproject/navit/NavitTraff.java
@@ -0,0 +1,108 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2018 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.navitproject.navit;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * @brief The TraFF receiver implementation.
+ *
+ * This class registers the broadcast receiver for TraFF feeds, polls all registered sources once on creation, receives
+ * TraFF feeds and forwards them to the traffic module for processing.
+ */
+public class NavitTraff extends BroadcastReceiver {
+ public static String ACTION_TRAFF_FEED = "org.traffxml.traff.FEED";
+
+ public static String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL";
+
+ public static String EXTRA_FEED = "feed";
+
+ /** Identifier for the callback function. */
+ private int cbid;
+
+ private Context context = null;
+
+ /** An intent filter for TraFF events. */
+ private IntentFilter traffFilter = new IntentFilter();
+
+ /**
+ * @brief Forwards a newly received TraFF feed to the traffic module for processing.
+ *
+ * This is called when a TraFF feed is received.
+ *
+ * @param id The identifier for the native callback implementation
+ * @param feed The TraFF feed
+ */
+ public native void onFeedReceived(int id, String feed);
+
+ /**
+ * @brief Creates a new {@code NavitTraff} instance.
+ *
+ * Creating a new {@code NavitTraff} instance registers a broadcast receiver for TraFF broadcasts and polls all
+ * registered sources once to ensure we have messages which were received by these sources before we started up.
+ *
+ * @param context The context
+ * @param cbid The callback identifier for the native method to call upon receiving a feed
+ */
+ NavitTraff(Context context, int cbid) {
+ this.context = context;
+ this.cbid = cbid;
+
+ traffFilter.addAction(ACTION_TRAFF_FEED);
+ traffFilter.addAction(ACTION_TRAFF_POLL);
+
+ context.registerReceiver(this, traffFilter);
+ /* TODO unregister receiver on exit */
+
+ /* Broadcast a poll intent */
+ 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) {
+ 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);
+ }
+ }
+
+ @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(cbid, feed);
+ }
+ }
+}
diff --git a/navit/attr.h b/navit/attr.h
index c9e1ca30f..dca3a6107 100644
--- a/navit/attr.h
+++ b/navit/attr.h
@@ -91,6 +91,7 @@ enum attr_format {
#define AF_PBH (AF_PEDESTRIAN|AF_BIKE|AF_HORSE)
#define AF_MOTORIZED_FAST (AF_MOTORCYCLE|AF_CAR|AF_HIGH_OCCUPANCY_CAR|AF_TAXI|AF_PUBLIC_BUS|AF_DELIVERY_TRUCK|AF_TRANSPORT_TRUCK|AF_EMERGENCY_VEHICLES)
#define AF_ALL (AF_PBH|AF_MOPED|AF_MOTORIZED_FAST)
+#define AF_DISTORTIONMASK (AF_ALL|AF_ONEWAYMASK)
#define AF_DG_ANY (1<<0)
diff --git a/navit/attr_def.h b/navit/attr_def.h
index 1a0c92ee4..0dffc93e5 100644
--- a/navit/attr_def.h
+++ b/navit/attr_def.h
@@ -478,6 +478,7 @@ ATTR(maps)
ATTR(layout)
ATTR(profile_option)
ATTR(script)
+ATTR(traffic)
ATTR2(0x0008ffff,type_object_end)
ATTR2(0x00090000,type_coord_begin)
ATTR2(0x0009ffff,type_coord_end)
diff --git a/navit/binding/dbus/binding_dbus.c b/navit/binding/dbus/binding_dbus.c
index 35aebf12a..aa776db10 100644
--- a/navit/binding/dbus/binding_dbus.c
+++ b/navit/binding/dbus/binding_dbus.c
@@ -52,6 +52,7 @@
#include "util.h"
#include "transform.h"
#include "event.h"
+#include "traffic.h"
static DBusConnection *connection;
static dbus_uint32_t dbus_serial;
@@ -336,6 +337,11 @@ static DBusHandlerResult dbus_error_navigation_not_configured(DBusConnection *co
"navigation is not configured (no <navigation> element in config file?)");
}
+static DBusHandlerResult dbus_error_traffic_not_configured(DBusConnection *connection, DBusMessage *message) {
+ return dbus_error(connection, message, DBUS_ERROR_FAILED,
+ "traffic is not configured (no <traffic> element in config file?)");
+}
+
static DBusHandlerResult dbus_error_no_data_available(DBusConnection *connection, DBusMessage *message) {
#if 1
return dbus_error(connection, message, DBUS_ERROR_FILE_NOT_FOUND, "no data available");
@@ -1195,6 +1201,185 @@ static DBusHandlerResult request_navit_quit(DBusConnection *connection, DBusMess
return empty_reply(connection, message);
}
+/**
+ * @brief Exports currently active traffic distortions as a GPX file.
+ *
+ * @param connection The DBusConnection object through which a message arrived
+ * @param message The DBusMessage including the `filename` parameter
+ * @returns An empty reply if everything went right, otherwise `DBUS_HANDLER_RESULT_NOT_YET_HANDLED`
+ */
+static DBusHandlerResult request_navit_traffic_export_gpx(DBusConnection *connection, DBusMessage *message) {
+ char * filename;
+ struct navit * navit;
+ DBusMessageIter iter;
+ struct attr attr;
+ struct attr_iter * a_iter;
+ struct traffic * traffic = NULL;
+ FILE *fp;
+ struct traffic_message ** messages;
+ struct traffic_message ** curr_msg;
+ char * wpt_types[] = {"from", "at", "via", "not_via", "to"};
+ struct traffic_point * wpts[5];
+ int i;
+ struct item ** items;
+ struct item ** curr_itm;
+ int dir, lastdir = 0;
+ struct coord c, c_last;
+ struct coord_geo g;
+
+ char *header = "<?xml version='1.0' encoding='UTF-8'?>\n"
+ "<gpx version='1.1' creator='Navit http://navit.sourceforge.net'\n"
+ " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
+ " xmlns:navit='http://www.navit-project.org/schema/navit'\n"
+ " xmlns='http://www.topografix.com/GPX/1/1'\n"
+ " xsi:schemaLocation='http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'>\n";
+ char *trailer = "</gpx>\n";
+
+ navit = object_get_from_message(message, "navit");
+ if (! navit)
+ return dbus_error_invalid_object_path(connection, message);
+
+ dbus_message_iter_init(message, &iter);
+
+ dbus_message_iter_get_basic(&iter, &filename);
+
+ a_iter = navit_attr_iter_new();
+ if (navit_get_attr(navit, attr_traffic, &attr, a_iter))
+ traffic = (struct traffic *) attr.u.navit_object;
+ navit_attr_iter_destroy(a_iter);
+
+ if (!traffic)
+ return dbus_error_traffic_not_configured(connection, message);
+
+ dbg(lvl_debug,"Dumping traffic distortions from dbus to %s", filename);
+
+ fp = fopen(filename, "w");
+ if (!fp) {
+ return dbus_error(connection, message, DBUS_ERROR_FAILED,
+ "could not open file for writing");
+ }
+
+ fprintf(fp, "%s", header);
+
+ messages = traffic_get_stored_messages(traffic);
+
+ for (curr_msg = messages; *curr_msg; curr_msg++) {
+ if (!(*curr_msg)->location)
+ continue;
+ wpts[0] = (*curr_msg)->location->from;
+ wpts[1] = (*curr_msg)->location->at;
+ wpts[2] = (*curr_msg)->location->via;
+ wpts[3] = (*curr_msg)->location->not_via;
+ wpts[4] = (*curr_msg)->location->to;
+ for (i = 0; i <= 4; i++) {
+ if (!wpts[i])
+ continue;
+ fprintf(fp, "<wpt lon='%4.16f' lat='%4.16f'><type>%s</type><name>%s</name></wpt>\n",
+ wpts[i]->coord.lng, wpts[i]->coord.lat, wpt_types[i], (*curr_msg)->id);
+ }
+ }
+
+ for (curr_msg = messages; *curr_msg; curr_msg++) {
+ items = traffic_message_get_items(*curr_msg);
+ for (curr_itm = items; *curr_itm; curr_itm++) {
+ /*
+ * Don’t blindly copy this code unless you know what you are doing.
+ * It is based on various assumptions which hold true for traffic map items, but not necessarily for items
+ * obtained from other maps.
+ */
+ item_coord_rewind(*curr_itm);
+ item_coord_get(*curr_itm, &c, 1);
+ item_attr_rewind(*curr_itm);
+ if (item_attr_get(*curr_itm, attr_flags, &attr)) {
+ if (attr.u.num & AF_ONEWAY)
+ dir = 1;
+ else if (attr.u.num & AF_ONEWAYREV)
+ dir = -1;
+ else
+ dir = 0;
+ } else
+ dir = 0;
+ if ((curr_itm == items) || (c.x != c_last.x) || (c.y != c_last.y) || lastdir != dir) {
+ /*
+ * Start a new route for the first item, or if the last point of the previous item does not coincide
+ * with the first point of the current one. This includes closing the previous route (if any) and
+ * adding the first point.
+ */
+ if (curr_itm != items)
+ fprintf(fp, "</rte>\n");
+ fprintf(fp, "<rte><type>%s</type><name>%s</name>\n",
+ dir ? (dir > 0 ? "forward" : "backward") : "bidirectional", (*curr_msg)->id);
+ transform_to_geo(projection_mg, &c, &g);
+ fprintf(fp,"<rtept lon='%4.16f' lat='%4.16f'></rtept>\n", g.lng, g.lat);
+ }
+ while (item_coord_get(*curr_itm, &c, 1)) {
+ transform_to_geo(projection_mg, &c, &g);
+ fprintf(fp,"<rtept lon='%4.16f' lat='%4.16f'></rtept>\n", g.lng, g.lat);
+ }
+ c_last.x = c.x;
+ c_last.y = c.y;
+ lastdir = dir;
+ }
+ if (curr_itm != items)
+ fprintf(fp, "</rte>\n");
+ g_free(items);
+ }
+
+ fprintf(fp,"%s",trailer);
+
+ fclose(fp);
+
+ g_free(messages);
+
+ return empty_reply(connection, message);
+}
+
+/**
+ * @brief Injects a traffic feed.
+ *
+ * @param connection The DBusConnection object through which a message arrived
+ * @param message The DBusMessage including the `filename` parameter
+ * @returns An empty reply if everything went right, otherwise `DBUS_HANDLER_RESULT_NOT_YET_HANDLED`
+ */
+static DBusHandlerResult request_navit_traffic_inject(DBusConnection *connection, DBusMessage *message) {
+ char * filename;
+ struct navit *navit;
+ DBusMessageIter iter;
+ struct attr * attr;
+ struct attr_iter * a_iter;
+ struct traffic * traffic = NULL;
+ struct traffic_message ** messages;
+
+ navit = object_get_from_message(message, "navit");
+ if (! navit)
+ return dbus_error_invalid_object_path(connection, message);
+
+ dbus_message_iter_init(message, &iter);
+
+ dbus_message_iter_get_basic(&iter, &filename);
+
+ attr = g_new0(struct attr, 1);
+ a_iter = navit_attr_iter_new();
+ if (navit_get_attr(navit, attr_traffic, attr, a_iter))
+ traffic = (struct traffic *) attr->u.navit_object;
+ navit_attr_iter_destroy(a_iter);
+ g_free(attr);
+
+ if (!traffic)
+ return dbus_error_traffic_not_configured(connection, message);
+
+ dbg(lvl_debug, "Processing traffic feed from file %s", filename);
+
+ messages = traffic_get_messages_from_xml_file(traffic, filename);
+ if (messages) {
+ dbg(lvl_debug, "got messages from file %s, processing", filename);
+ traffic_process_messages(traffic, messages);
+ g_free(messages);
+ }
+
+ return empty_reply(connection, message);
+}
+
static DBusHandlerResult request_navit_zoom(DBusConnection *connection, DBusMessage *message) {
int factor;
struct point p, *pp=NULL;
@@ -1292,6 +1477,12 @@ static DBusHandlerResult request_navit_route_export_gpx(DBusConnection *connecti
FILE *fp;
fp = fopen(filename,"w");
+ if (!fp) {
+ map_rect_destroy(mr);
+ return dbus_error(connection, message, DBUS_ERROR_FAILED,
+ "could not open file for writing");
+ }
+
fprintf(fp, "%s", header);
while((item = map_rect_get_item(mr))) {
@@ -1306,6 +1497,8 @@ static DBusHandlerResult request_navit_route_export_gpx(DBusConnection *connecti
fclose(fp);
+ map_rect_destroy(mr);
+
return empty_reply(connection, message);
}
@@ -1369,6 +1562,11 @@ static DBusHandlerResult request_navit_route_export_geojson(DBusConnection *conn
FILE *fp;
fp = fopen(filename,"w");
+ if (!fp) {
+ return dbus_error(connection, message, DBUS_ERROR_FAILED,
+ "could not open file for writing");
+ }
+
fprintf(fp, "%s", header);
int is_first=1;
char * instructions;
@@ -1796,6 +1994,8 @@ struct dbus_method {
{".navit", "set_center", "(iii)", "(projection,longitude,latitude)", "", "", request_navit_set_center},
{".navit", "set_center_screen", "(ii)", "(pixel_x,pixel_y)", "", "", request_navit_set_center_screen},
{".navit", "set_layout", "s", "layoutname", "", "", request_navit_set_layout},
+ {".navit", "traffic_export", "s", "filename", "", "", request_navit_traffic_export_gpx},
+ {".navit", "traffic_inject", "s", "filename", "", "", request_navit_traffic_inject},
{".navit", "zoom", "i(ii)", "factor(pixel_x,pixel_y)", "", "", request_navit_zoom},
{".navit", "zoom", "i", "factor", "", "", request_navit_zoom},
{".navit", "zoom_to_route", "", "", "", "", request_navit_zoom_to_route},
diff --git a/navit/gui/internal/gui_internal_html.c b/navit/gui/internal/gui_internal_html.c
index ca33581be..41dab9f50 100644
--- a/navit/gui/internal/gui_internal_html.c
+++ b/navit/gui/internal/gui_internal_html.c
@@ -445,7 +445,14 @@ static void gui_internal_html_text(xml_context *dummy, const char *text, gsize l
}
void gui_internal_html_parse_text(struct gui_priv *this, char *doc) {
- xml_parse_text(doc, this, gui_internal_html_start, gui_internal_html_end, gui_internal_html_text);
+ int res;
+
+ res = xml_parse_text(doc, this, gui_internal_html_start, gui_internal_html_end, gui_internal_html_text);
+
+ if (!res) {
+ dbg(lvl_error, "FATAL: Failed to parse XML data (looks like incorrect configuration for internal GUI).\n");
+ exit(1);
+ }
}
void gui_internal_html_menu(struct gui_priv *this, const char *document, char *anchor) {
diff --git a/navit/item.c b/navit/item.c
index f50971ee1..4f64a1edb 100644
--- a/navit/item.c
+++ b/navit/item.c
@@ -225,6 +225,57 @@ int item_coord_get_within_selection(struct item *it, struct coord *c, int count,
}
/**
+ * @brief Gets all the coordinates of an item within a specified range
+ *
+ * This will get all the coordinates of the item `i`, starting with `start` and ending with `end`, and
+ * return them in `c`, up to `max` coordinates.
+ *
+ * If `i` does not contain the coordinates in `start`, no coordinates are retrieved and zero is returned.
+ *
+ * If `i` contains the coordinates in `start` but not those in `end`, all coordinates beginning with
+ * `start` are retrieved, ending with the last coordinate of `i` or after `max` coordinates have been
+ * retrieved, whichever occurs first.
+ *
+ * This function is not safe to call after destroying the item's map rect, and doing so may cause errors
+ * with some map implementations.
+ *
+ * @important Make sure that `c` points to a buffer large enough to hold `max` coordinates!
+ *
+ * @param i The item to get the coordinates of
+ * @param c Pointer to memory allocated for holding the coordinates
+ * @param max Maximum number of coordinates to return
+ * @param start First coordinate to get
+ * @param end Last coordinate to get
+ *
+ * @return The number of coordinates stored in `c`
+ */
+int item_coord_get_within_range(struct item *i, struct coord *c, int max,
+ struct coord *start, struct coord *end) {
+ struct map_rect *mr;
+ struct item *item;
+ int rc = 0, p = 0;
+ struct coord c1;
+ mr=map_rect_new(i->map, NULL);
+ if (!mr)
+ return 0;
+ item = map_rect_get_item_byid(mr, i->id_hi, i->id_lo);
+ if (item) {
+ rc = item_coord_get(item, &c1, 1);
+ while (rc && (c1.x != start->x || c1.y != start->y)) {
+ rc = item_coord_get(item, &c1, 1);
+ }
+ while (rc && p < max) {
+ c[p++] = c1;
+ if (c1.x == end->x && c1.y == end->y)
+ break;
+ rc = item_coord_get(item, &c1, 1);
+ }
+ }
+ map_rect_destroy(mr);
+ return p;
+}
+
+/**
* @brief Gets the next coordinates from an item and reprojects them
*
* This function returns a list of coordinates from an item and advances the "coordinate pointer"
diff --git a/navit/item.h b/navit/item.h
index bf65555fc..5374240d2 100644
--- a/navit/item.h
+++ b/navit/item.h
@@ -130,6 +130,7 @@ void item_coord_rewind(struct item *it);
int item_coord_get(struct item *it, struct coord *c, int count);
int item_coord_set(struct item *it, struct coord *c, int count, enum change_mode mode);
int item_coord_get_within_selection(struct item *it, struct coord *c, int count, struct map_selection *sel);
+int item_coord_get_within_range(struct item *i, struct coord *c, int max, struct coord *start, struct coord *end);
int item_coord_get_pro(struct item *it, struct coord *c, int count, enum projection to);
int item_coord_is_node(struct item *it);
void item_attr_rewind(struct item *it);
diff --git a/navit/navit.c b/navit/navit.c
index dad8eb7aa..84af773e2 100644
--- a/navit/navit.c
+++ b/navit/navit.c
@@ -44,6 +44,7 @@
#include "coord.h"
#include "point.h"
#include "transform.h"
+#include "traffic.h"
#include "param.h"
#include "menu.h"
#include "graphics.h"
@@ -1928,6 +1929,9 @@ void navit_init(struct navit *this_) {
struct map *map;
int callback;
char *center_file;
+ struct attr_iter *iter;
+ struct attr *attr;
+ struct traffic * traffic;
dbg(lvl_info,"enter gui %p graphics %p",this_->gui,this_->gra);
@@ -1995,6 +1999,26 @@ void navit_init(struct navit *this_) {
if (this_->route)
tracking_set_route(this_->tracking, this_->route);
}
+
+ attr = g_new0(struct attr, 1);
+ iter = navit_attr_iter_new();
+ map = NULL;
+ while (navit_get_attr(this_, attr_traffic, attr, iter)) {
+ traffic = (struct traffic *) attr->u.navit_object;
+ traffic_set_mapset(traffic, ms);
+ if (this_->route)
+ traffic_set_route(traffic, this_->route);
+ /* add the first map found */
+ if (!map && (map = traffic_get_map(traffic))) {
+ struct attr map_a;
+ map_a.type = attr_map;
+ map_a.u.map = map;
+ mapset_add_attr(ms, &map_a);
+ }
+ }
+ navit_attr_iter_destroy(iter);
+ g_free(attr);
+
if (this_->navigation) {
if ((map=navigation_get_map(this_->navigation))) {
struct attr map_a,active;
@@ -2843,6 +2867,7 @@ int navit_add_attr(struct navit *this_, struct attr *attr) {
break;
case attr_layer:
case attr_script:
+ case attr_traffic:
break;
default:
return 0;
diff --git a/navit/navit.dtd b/navit/navit.dtd
index aa25a1f4c..645816a93 100644
--- a/navit/navit.dtd
+++ b/navit/navit.dtd
@@ -17,7 +17,7 @@
<!ATTLIST log flush_size CDATA #IMPLIED>
<!ATTLIST log flush_time CDATA #IMPLIED>
<!ATTLIST log attr_types CDATA #IMPLIED>
-<!ELEMENT navit (graphics,gui+,log*,osd*,vehicle*,tracking?,vehicleprofile*,route,navigation,speech,mapset+,layer+,layout+) >
+<!ELEMENT navit (graphics,gui+,log*,osd*,traffic*,vehicle*,tracking?,vehicleprofile*,route,navigation,speech,mapset+,layer+,layout+) >
<!ATTLIST navit center CDATA #REQUIRED>
<!ATTLIST navit zoom CDATA #REQUIRED>
<!ATTLIST navit tracking CDATA #REQUIRED>
@@ -65,6 +65,8 @@
<!ELEMENT cursor (itemgra+)>
<!ATTLIST cursor w CDATA #REQUIRED>
<!ATTLIST cursor h CDATA #REQUIRED>
+<!ELEMENT traffic EMPTY>
+<!ATTLIST traffic type CDATA #REQUIRED>
<!ELEMENT tracking ANY>
<!ATTLIST tracking cdf_histsize CDATA #IMPLIED>
<!ELEMENT route EMPTY>
diff --git a/navit/plugin.h b/navit/plugin.h
index 03fb4ad5a..5c9636aa2 100644
--- a/navit/plugin.h
+++ b/navit/plugin.h
@@ -49,6 +49,8 @@ enum plugin_category {
plugin_category_event,
/** Category for plugins which load fonts. */
plugin_category_font,
+ /** Category for plugins which retrieve traffic information. */
+ plugin_category_traffic,
/** Dummy for last entry. */
plugin_category_last,
};
diff --git a/navit/plugin_def.h b/navit/plugin_def.h
index 985160e6f..3140bddc3 100644
--- a/navit/plugin_def.h
+++ b/navit/plugin_def.h
@@ -31,3 +31,4 @@ PLUGIN_CATEGORY(speech, (struct speech_methods *meth, struct attr **attrs, struc
PLUGIN_CATEGORY(vehicle, (struct vehicle_methods *meth, struct callback_list *cbl, struct attr **attrs))
PLUGIN_CATEGORY(event, (struct event_methods *meth))
PLUGIN_CATEGORY(font, (void *meth))
+PLUGIN_CATEGORY(traffic, (struct navit *nav, struct traffic_methods *meth, struct attr **attrs, struct callback_list *cbl))
diff --git a/navit/route.c b/navit/route.c
index 453afa0d1..3933d349e 100644
--- a/navit/route.c
+++ b/navit/route.c
@@ -31,12 +31,54 @@
* It accomplishes this by first building a "route graph". This graph contains segments and
* points.
*
- * After building this graph in route_graph_build(), the function route_graph_flood() assigns every
- * point and segment a "value" which represents the "costs" of traveling from this point to the
- * destination. This is done by Dijkstra's algorithm.
- *
- * When the graph is built a "route path" is created, which is a path in this graph from a given
- * position to the destination determined at time of building the graph.
+ * Routing now relies on the Lifelong Planning A* (LPA*) algorithm, which builds upon the A* algorithm but allows for
+ * partial updates after the cost of some segments has changed. (With A*, one would need to recalculate the entire
+ * route graph from scratch.) A*, in turn, is an extension of the Dijkstra algorithm, with the added improvement that
+ * A* introduces a heuristic (essentially, a lower boundary for the yet-to-be-calculated remainder of the route from a
+ * given point onwards) and chooses the next point to analyze based on the sum of its cost and its heuristic, where
+ * Dijkstra uses simply the cost of the node. This makes A* more efficient than Dijkstra in some scenarios. (Navit,
+ * however, is not one of them, as we currently analyze only a subset of the entire map, and calculating the heuristic
+ * for each node turned out to cost more than it saved in tests.)
+ *
+ * Wikipedia has articles on all three algorithms; refer to these for an in-depth discussion of the algorithms.
+ *
+ * If the heuristic is assumed to be zero in all cases, A* behaves exactly as Dijkstra would. Similarly, LPA* behaves
+ * identically to A* if all segment costs are known prior to route calculation and do not change once route calculation
+ * has started.
+ *
+ * Earlier versions of Navit used Dijkstra for routing. This was upgraded to LPA* when the traffic module was
+ * introduced, as it became necessary to do fast partial recalculations of the route when the traffic situation
+ * changes. Navit’s LPA* implementation differs from the canonical implementation in two important ways:
+ *
+ * \li The heuristic is not used (or assumed to be zero), for the reasons discussed above. However, this is not set in
+ * stone and can be revisited if we find using a heuristic provides a true benefit.
+ * \li Since the destination point may be off-road, Navit may initialize the route graph with multiple candidates for
+ * the destination point, each of which will get a nonzero cost (which may still decrease if routing later determines
+ * that it is cheaper to route from that candidate point to a different candidate point).
+ *
+ * The cost of a node is always the cost to reach the destination, and route calculation is done “backwards”, i.e.
+ * starting at the destination and working its way to the current position (or previous waypoint). This is mandatory
+ * in LPA*, while A* and Dijkstra can also work from the start to the destination. The latter is found in most textbook
+ * descriptions of these algorithms. Navit has always calculated routes from destination to start, even with Dijkstra,
+ * as this made it easier to react to changes in the vehicle position (the start of the route).
+ *
+ * A route graph first needs to be built with `route_graph_build()`, which fetches the segments from the map. Next
+ * `route_graph_init()` is called to initialize the destination point candidates. Then
+ * `route_graph_compute_shortest_path()` is called to assign a `value` to each node, which represents the cost of
+ * traveling from this point to the destination. Each point is also assigned a “next segment” to take in order to reach
+ * the destination from this point. Eventually a “route path” is created, i.e. the sequence of segments from the
+ * current position to the destination are extracted from the route graph and turn instructions are added where
+ * necessary.
+ *
+ * When segment costs change, `route_graph_point_update()` is called for each end point which may have changed. Then
+ * `route_graph_compute_shortest_path()` is called to update parts of the route which may have changed, and finally the
+ * route path is recreated. This is used by the traffic module when traffic reports change the cost of individual
+ * segments.
+ *
+ * A completely new route can be created from an existing graph, which happens e.g. between sections of a route when
+ * waypoints are used. This is done by calling `route_graph_reset()`, which resets all nodes to their initial state.
+ * Then `route_graph_init()` is called, followed by `route_graph_compute_shortest_path()` and eventually creation of
+ * the route path.
*/
#include <stdio.h>
@@ -55,6 +97,7 @@
#include "xmlconfig.h"
#include "map.h"
#include "mapset.h"
+#include "route_protected.h"
#include "route.h"
#include "track.h"
#include "transform.h"
@@ -73,98 +116,13 @@ struct map_priv {
int debug_route=0;
-/**
- * @brief A point in the route graph
- *
- * This represents a point in the route graph. A point usually connects two or more segments,
- * but there are also points which don't do that (e.g. at the end of a dead-end).
- */
-struct route_graph_point {
- struct route_graph_point *hash_next; /**< Pointer to a chained hashlist of all route_graph_points with this hash */
- struct route_graph_segment *start; /**< Pointer to a list of segments of which this point is the start. The links
- * of this linked-list are in route_graph_segment->start_next.*/
- struct route_graph_segment *end; /**< Pointer to a list of segments of which this pointer is the end. The links
- * of this linked-list are in route_graph_segment->end_next. */
- struct route_graph_segment *seg; /**< Pointer to the segment one should use to reach the destination at
- * least costs */
- struct fibheap_el *el; /**< When this point is put on a Fibonacci heap, this is a pointer
- * to this point's heap-element */
- int value; /**< The cost at which one can reach the destination from this point on.
- * {@code INT_MAX} indicates that the destination is unreachable from this
- * point, or that this point has not yet been examined. */
- struct coord c; /**< Coordinates of this point */
- int flags; /**< Flags for this point (eg traffic distortion) */
-};
-
-#define RP_TRAFFIC_DISTORTION 1
-#define RP_TURN_RESTRICTION 2
-#define RP_TURN_RESTRICTION_RESOLVED 4
-
-/**
- * @brief A segment in the route graph or path
- *
- * This is a segment in the route graph or path. A segment represents a driveable way.
- */
-
-struct route_segment_data {
- struct item item; /**< The item (e.g. street) that this segment represents. */
- int flags;
- int len; /**< Length of this segment, in meters */
- /*NOTE: After a segment, various fields may follow, depending on what flags are set. Order of fields:
- 1.) maxspeed Maximum allowed speed on this segment. Present if AF_SPEED_LIMIT is set.
- 2.) offset If the item is segmented (i.e. represented by more than one segment), this
- indicates the position of this segment in the item. Present if AF_SEGMENTED is set.
- */
-};
-
-
-struct size_weight_limit {
- int width;
- int length;
- int height;
- int weight;
- int axle_weight;
-};
#define RSD_OFFSET(x) *((int *)route_segment_data_field_pos((x), attr_offset))
-#define RSD_MAXSPEED(x) *((int *)route_segment_data_field_pos((x), attr_maxspeed))
#define RSD_SIZE_WEIGHT(x) *((struct size_weight_limit *)route_segment_data_field_pos((x), attr_vehicle_width))
#define RSD_DANGEROUS_GOODS(x) *((int *)route_segment_data_field_pos((x), attr_vehicle_dangerous_goods))
/**
- * @brief Data for a segment in the route graph
- */
-struct route_graph_segment_data {
- struct item *item; /**< The item which this segment is part of */
- int offset; /**< If the item passed in "item" is segmented (i.e. divided
- * into several segments), this indicates the position of
- * this segment within the item */
- int flags; /**< Flags for this segment */
- int len; /**< The length of this segment */
- int maxspeed; /**< The maximum speed allowed on this segment in km/h,
- * -1 if not known */
- struct size_weight_limit size_weight; /**< Size and weight limits for this segment */
- int dangerous_goods;
-};
-
-/**
- * @brief A segment in the route graph
- *
- * This is a segment in the route graph. A segment represents a driveable way.
- */
-struct route_graph_segment {
- struct route_graph_segment *next; /**< Linked-list pointer to a list of all route_graph_segments */
- struct route_graph_segment *start_next; /**< Pointer to the next element in the list of segments that start at the
- * same point. Start of this list is in route_graph_point->start. */
- struct route_graph_segment *end_next; /**< Pointer to the next element in the list of segments that end at the
- * same point. Start of this list is in route_graph_point->end. */
- struct route_graph_point *start; /**< Pointer to the point this segment starts at. */
- struct route_graph_point *end; /**< Pointer to the point this segment ends at. */
- struct route_segment_data data; /**< The segment data */
-};
-
-/**
* @brief A traffic distortion
*
* Traffic distortions represent delays or closures on the route, which can occur for a variety of
@@ -264,29 +222,6 @@ struct route {
struct vehicle *v;
};
-/**
- * @brief A complete route graph
- *
- * The route graph holds all routable segments along with the connections between them and the cost of
- * each segment.
- */
-struct route_graph {
- int busy; /**< The graph is being built */
- struct map_selection *sel; /**< The rectangle selection for the graph */
- struct mapset_handle *h; /**< Handle to the mapset */
- struct map *m; /**< Pointer to the currently active map */
- struct map_rect *mr; /**< Pointer to the currently active map rectangle */
- struct vehicleprofile *vehicleprofile; /**< The vehicle profile */
- struct callback *idle_cb; /**< Idle callback to process the graph */
- struct callback *done_cb; /**< Callback when graph is done */
- struct event_idle *idle_ev; /**< The pointer to the idle event */
- struct route_graph_segment
- *route_segments; /**< Pointer to the first route_graph_segment in the linked list of all segments */
- struct route_graph_segment *avoid_seg;
-#define HASH_SIZE 8192
- struct route_graph_point *hash[HASH_SIZE]; /**< A hashtable containing all route_graph_points in this graph */
-};
-
#define HASHCOORD(c) ((((c)->x +(c)->y) * 2654435761UL) & (HASH_SIZE-1))
/**
@@ -309,18 +244,23 @@ struct attr_iter {
static struct route_info * route_find_nearest_street(struct vehicleprofile *vehicleprofile, struct mapset *ms,
struct pcoord *c);
-static struct route_graph_point *route_graph_get_point(struct route_graph *this, struct coord *c);
static void route_graph_update(struct route *this, struct callback *cb, int async);
-static void route_graph_build_done(struct route_graph *rg, int cancel);
static struct route_path *route_path_new(struct route_graph *this, struct route_path *oldpath, struct route_info *pos,
struct route_info *dst, struct vehicleprofile *profile);
-static void route_process_street_graph(struct route_graph *this, struct item *item, struct vehicleprofile *profile);
+static void route_graph_add_street(struct route_graph *this, struct item *item, struct vehicleprofile *profile);
static void route_graph_destroy(struct route_graph *this);
static void route_path_update(struct route *this, int cancel, int async);
static int route_time_seg(struct vehicleprofile *profile, struct route_segment_data *over,
struct route_traffic_distortion *dist);
-static void route_graph_flood(struct route_graph *this, struct route_info *dst, struct vehicleprofile *profile,
- struct callback *cb);
+static void route_graph_compute_shortest_path(struct route_graph * graph, struct vehicleprofile * profile,
+ struct callback *cb);
+static int route_graph_is_path_computed(struct route_graph *this_);
+static struct route_graph_segment *route_graph_get_segment(struct route_graph *graph, struct street_data *sd,
+ struct route_graph_segment *last);
+static int route_value_seg(struct vehicleprofile *profile, struct route_graph_point *from,
+ struct route_graph_segment *over,
+ int dir);
+static void route_graph_init(struct route_graph *this, struct route_info *dst, struct vehicleprofile *profile);
static void route_graph_reset(struct route_graph *this);
@@ -807,7 +747,8 @@ static void route_path_update_done(struct route *this, int new_graph) {
this->link_path=1;
this->current_dst=prev_dst;
route_graph_reset(this->graph);
- route_graph_flood(this->graph, this->current_dst, this->vehicleprofile, this->route_graph_flood_done_cb);
+ route_graph_init(this->graph, this->current_dst, this->vehicleprofile);
+ route_graph_compute_shortest_path(this->graph, this->vehicleprofile, this->route_graph_flood_done_cb);
return;
}
if (!new_graph && this->path2->updated)
@@ -1320,7 +1261,8 @@ void route_remove_waypoint(struct route *this) {
this->reached_destinations_count++;
route_graph_reset(this->graph);
this->current_dst = this->destinations->data;
- route_graph_flood(this->graph, this->current_dst, this->vehicleprofile, this->route_graph_flood_done_cb);
+ route_graph_init(this->graph, this->current_dst, this->vehicleprofile);
+ route_graph_compute_shortest_path(this->graph, this->vehicleprofile, this->route_graph_flood_done_cb);
}
}
@@ -1333,7 +1275,7 @@ void route_remove_waypoint(struct route *this) {
* or {@code NULL} to return the first point
* @return The point at the specified coordinates or NULL if not found
*/
-static struct route_graph_point *route_graph_get_point_next(struct route_graph *this, struct coord *c,
+struct route_graph_point *route_graph_get_point_next(struct route_graph *this, struct coord *c,
struct route_graph_point *last) {
struct route_graph_point *p;
int seen=0,hashval=HASHCOORD(c);
@@ -1357,7 +1299,7 @@ static struct route_graph_point *route_graph_get_point_next(struct route_graph *
* @param c Coordinates to search for
* @return The point at the specified coordinates or NULL if not found
*/
-static struct route_graph_point *route_graph_get_point(struct route_graph *this, struct coord *c) {
+struct route_graph_point * route_graph_get_point(struct route_graph *this, struct coord *c) {
return route_graph_get_point_next(this, c, NULL);
}
@@ -1401,6 +1343,7 @@ static struct route_graph_point *route_graph_point_new(struct route_graph *this,
p->hash_next=this->hash[hashval];
this->hash[hashval]=p;
p->value=INT_MAX;
+ p->dst_val = INT_MAX;
p->c=*f;
return p;
}
@@ -1418,7 +1361,7 @@ static struct route_graph_point *route_graph_point_new(struct route_graph *this,
* @param f The coordinates at which the point should be inserted
* @return The point inserted or NULL on failure
*/
-static struct route_graph_point *route_graph_add_point(struct route_graph *this, struct coord *f) {
+struct route_graph_point * route_graph_add_point(struct route_graph *this, struct coord *f) {
struct route_graph_point *p;
p=route_graph_get_point(this,f);
@@ -1432,7 +1375,7 @@ static struct route_graph_point *route_graph_add_point(struct route_graph *this,
*
* @param this The route graph to delete all points from
*/
-static void route_graph_free_points(struct route_graph *this) {
+void route_graph_free_points(struct route_graph *this) {
struct route_graph_point *curr,*next;
int i;
for (i = 0 ; i < HASH_SIZE ; i++) {
@@ -1447,12 +1390,58 @@ static void route_graph_free_points(struct route_graph *this) {
}
/**
+ * @brief Initializes potential destination nodes.
+ *
+ * This method is normally called after building a fresh route graph, or resetting an existing one. It iterates over
+ * all potential destination nodes (i.e. all nodes which are part of the destination’s street) and initializes them:
+ * The `dst_val` and `rhs` values are set according to their cost to reach the destination.
+ *
+ * @param this The route graph to initialize
+ * @param dst The destination of the route
+ * @param profile The vehicle profile to use for routing. This determines which ways are passable
+ * and how their costs are calculated.
+ */
+static void route_graph_init(struct route_graph *this, struct route_info *dst, struct vehicleprofile *profile) {
+ struct route_graph_segment *s = NULL;
+ int val;
+
+ while ((s = route_graph_get_segment(this, dst->street, s))) {
+ val = route_value_seg(profile, NULL, s, -1);
+ if (val != INT_MAX) {
+ val = val*(100-dst->percent)/100;
+ s->end->seg = s;
+ s->end->dst_seg = s;
+ s->end->rhs = val;
+ s->end->dst_val = val;
+ s->end->el = fh_insertkey(this->heap, MIN(s->end->rhs, s->end->value), s->end);
+ }
+ val = route_value_seg(profile, NULL, s, 1);
+ if (val != INT_MAX) {
+ val = val*dst->percent/100;
+ s->start->seg = s;
+ s->start->dst_seg = s;
+ s->start->rhs = val;
+ s->start->dst_val = val;
+ s->start->el = fh_insertkey(this->heap, MIN(s->start->rhs, s->start->value), s->start);
+ }
+ }
+}
+
+/**
* @brief Resets all nodes
*
* This iterates through all the points in the route graph, resetting them to their initial state.
- * The {@code value} member of each point (cost to reach the destination) is reset to
- * {@code INT_MAX}, the {@code seg} member (cheapest way to destination) is reset to {@code NULL}
- * and the {@code el} member (pointer to element in Fibonacci heap) is also reset to {@code NULL}.
+ * The `value` (cost to reach the destination via `seg`) and `dst_val` (cost to destination if this point is the last
+ * in the route) members of each point are reset to`INT_MAX`, the `seg` member (cheapest way to destination) is reset
+ * to `NULL` and the `el` member (pointer to element in Fibonacci heap) is also reset to `NULL`.
+ *
+ * The Fibonacci heap is also cleared. Inconsistencies between `el` and Fibonacci heap membership are handled
+ * gracefully, i.e. `el` is reset even if it is invalid, and points are removed from the heap regardless of their `el`
+ * value.
+ *
+ * After this method returns, the caller should call
+ * {@link route_graph_init(struct route_graph *, struct route_info *, struct vehicleprofile *)} to initialize potential
+ * end points. After that a route can be calculated.
*
* References to elements of the route graph which were obtained prior to calling this function
* remain valid after it returns.
@@ -1462,15 +1451,23 @@ static void route_graph_free_points(struct route_graph *this) {
static void route_graph_reset(struct route_graph *this) {
struct route_graph_point *curr;
int i;
+
for (i = 0 ; i < HASH_SIZE ; i++) {
curr=this->hash[i];
while (curr) {
curr->value=INT_MAX;
+ curr->dst_val = INT_MAX;
+ curr->rhs = INT_MAX;
curr->seg=NULL;
+ curr->dst_seg = NULL;
curr->el=NULL;
curr=curr->hash_next;
}
}
+
+ while (fh_extractmin(this->heap)) {
+ // no operation, just remove all items (`el` has already been reset)
+ }
}
/**
@@ -1483,7 +1480,7 @@ static void route_graph_reset(struct route_graph *this) {
* @param type Type of the field that should be returned
* @return A pointer to a field of a certain type, or NULL if no such field is present
*/
-static void *route_segment_data_field_pos(struct route_segment_data *seg, enum attr_type type) {
+void * route_segment_data_field_pos(struct route_segment_data *seg, enum attr_type type) {
unsigned char *ptr;
ptr = ((unsigned char*)seg) + sizeof(struct route_segment_data);
@@ -1540,7 +1537,7 @@ static int route_segment_data_size(int flags) {
* @param start The starting point of the segment
* @param data The data for the segment
*/
-static int route_graph_segment_is_duplicate(struct route_graph_point *start, struct route_graph_segment_data *data) {
+int route_graph_segment_is_duplicate(struct route_graph_point *start, struct route_graph_segment_data *data) {
struct route_graph_segment *s;
s=start->start;
while (s) {
@@ -1569,8 +1566,8 @@ static int route_graph_segment_is_duplicate(struct route_graph_point *start, str
* @param offset If the item passed in "item" is segmented (i.e. divided into several segments), this indicates the position of this segment within the item
* @param maxspeed The maximum speed allowed on this segment in km/h. -1 if not known.
*/
-static void route_graph_add_segment(struct route_graph *this, struct route_graph_point *start,
- struct route_graph_point *end, struct route_graph_segment_data *data) {
+void route_graph_add_segment(struct route_graph *this, struct route_graph_point *start,
+ struct route_graph_point *end, struct route_graph_segment_data *data) {
struct route_graph_segment *s;
int size;
@@ -1590,6 +1587,7 @@ static void route_graph_add_segment(struct route_graph *this, struct route_graph
s->data.len=data->len;
s->data.item=*data->item;
s->data.flags=data->flags;
+ s->data.score = data->score;
if (data->flags & AF_SPEED_LIMIT)
RSD_MAXSPEED(&s->data)=data->maxspeed;
@@ -1607,50 +1605,6 @@ static void route_graph_add_segment(struct route_graph *this, struct route_graph
}
/**
- * @brief Gets all the coordinates of an item
- *
- * This will get all the coordinates of the item i and return them in c,
- * up to max coordinates. Additionally it is possible to limit the coordinates
- * returned to all the coordinates of the item between the two coordinates
- * start end end.
- *
- * @important Make sure that whatever c points to has enough memory allocated
- * @important to hold max coordinates!
- *
- * @param i The item to get the coordinates of
- * @param c Pointer to memory allocated for holding the coordinates
- * @param max Maximum number of coordinates to return
- * @param start First coordinate to get
- * @param end Last coordinate to get
- * @return The number of coordinates returned
- */
-static int get_item_seg_coords(struct item *i, struct coord *c, int max,
- struct coord *start, struct coord *end) {
- struct map_rect *mr;
- struct item *item;
- int rc = 0, p = 0;
- struct coord c1;
- mr=map_rect_new(i->map, NULL);
- if (!mr)
- return 0;
- item = map_rect_get_item_byid(mr, i->id_hi, i->id_lo);
- if (item) {
- rc = item_coord_get(item, &c1, 1);
- while (rc && (c1.x != start->x || c1.y != start->y)) {
- rc = item_coord_get(item, &c1, 1);
- }
- while (rc && p < max) {
- c[p++] = c1;
- if (c1.x == end->x && c1.y == end->y)
- break;
- rc = item_coord_get(item, &c1, 1);
- }
- }
- map_rect_destroy(mr);
- return p;
-}
-
-/**
* @brief Returns and removes one segment from a path
*
* @param path The path to take the segment from
@@ -1817,7 +1771,7 @@ static int route_path_add_item_from_graph(struct route_path *this, struct route_
len=dst->lenpos;
}
} else {
- ccnt=get_item_seg_coords(&rgs->data.item, ca, 2047, &rgs->start->c, &rgs->end->c);
+ ccnt=item_coord_get_within_range(&rgs->data.item, ca, 2047, &rgs->start->c, &rgs->end->c);
c=ca;
}
seg_size=sizeof(*segment) + sizeof(struct coord) * (ccnt + extra);
@@ -1865,7 +1819,7 @@ linkold:
*
* @param this The graph to destroy all segments from
*/
-static void route_graph_free_segments(struct route_graph *this) {
+void route_graph_free_segments(struct route_graph *this) {
struct route_graph_segment *curr,*next;
int size;
curr=this->route_segments;
@@ -1888,6 +1842,7 @@ static void route_graph_destroy(struct route_graph *this) {
route_graph_build_done(this, 1);
route_graph_free_points(this);
route_graph_free_segments(this);
+ fh_deleteheap(this->heap);
g_free(this);
}
}
@@ -1979,33 +1934,58 @@ static int route_time_seg(struct vehicleprofile *profile, struct route_segment_d
/**
* @brief Returns the traffic distortion for a segment.
*
+ * If multiple traffic distortions match a segment, the return value will report the lowest speed limit
+ * and greatest delay of all matching segments.
+ *
* @param seg The segment for which the traffic distortion is to be returned
- * @param ret Points to a {@code struct route_traffic_distortion}, whose members will be filled
+ * @param dir The direction of `seg` for which to return traffic distortions. Positive values indicate
+ * travel in the direction of the segment, negative values indicate travel against it.
+ * @param profile The current vehicle profile
+ * @param ret Points to a {@code struct route_traffic_distortion}, whose members will be filled with the
+ * distortion data
*
* @return true if a traffic distortion was found, 0 if not
*/
-static int route_get_traffic_distortion(struct route_graph_segment *seg, struct route_traffic_distortion *ret) {
+static int route_get_traffic_distortion(struct route_graph_segment *seg, int dir, struct vehicleprofile *profile,
+ struct route_traffic_distortion *ret) {
struct route_graph_point *start=seg->start;
struct route_graph_point *end=seg->end;
struct route_graph_segment *tmp,*found=NULL;
- tmp=start->start;
- while (tmp && !found) {
- if (tmp->data.item.type == type_traffic_distortion && tmp->start == start && tmp->end == end)
+ struct route_traffic_distortion result;
+
+ if (!dir) {
+ dbg(lvl_warning, "dir is zero, assuming positive");
+ dir = 1;
+ }
+
+ result.delay = 0;
+ result.maxspeed = INT_MAX;
+
+ for (tmp = start->start; tmp; tmp = tmp->start_next) {
+ if (tmp->data.item.type == type_traffic_distortion && tmp->start == start && tmp->end == end) {
+ if ((tmp->data.flags & (dir > 0 ? profile->flags_forward_mask : profile->flags_reverse_mask)) != profile->flags)
+ continue;
+ if (tmp->data.len > result.delay)
+ result.delay = tmp->data.len;
+ if ((tmp->data.flags & AF_SPEED_LIMIT) && (RSD_MAXSPEED(&tmp->data) < result.maxspeed))
+ result.maxspeed = RSD_MAXSPEED(&tmp->data);
found=tmp;
- tmp=tmp->start_next;
+ }
}
- tmp=start->end;
- while (tmp && !found) {
- if (tmp->data.item.type == type_traffic_distortion && tmp->end == start && tmp->start == end)
+ for (tmp = start->end; tmp; tmp = tmp->end_next) {
+ if (tmp->data.item.type == type_traffic_distortion && tmp->end == start && tmp->start == end) {
+ if ((tmp->data.flags & (dir < 0 ? profile->flags_forward_mask : profile->flags_reverse_mask)) != profile->flags)
+ continue;
+ if (tmp->data.len > result.delay)
+ result.delay = tmp->data.len;
+ if ((tmp->data.flags & AF_SPEED_LIMIT) && (RSD_MAXSPEED(&tmp->data) < result.maxspeed))
+ result.maxspeed = RSD_MAXSPEED(&tmp->data);
found=tmp;
- tmp=tmp->end_next;
+ }
}
if (found) {
- ret->delay=found->data.len;
- if (found->data.flags & AF_SPEED_LIMIT)
- ret->maxspeed=RSD_MAXSPEED(&found->data);
- else
- ret->maxspeed=INT_MAX;
+ ret->delay = result.delay;
+ ret->maxspeed = result.maxspeed;
return 1;
}
return 0;
@@ -2028,6 +2008,7 @@ static int route_through_traffic_allowed(struct vehicleprofile *profile, struct
* checks are done on `from->seg` (the next segment to follow after `over`):
* \li If `from->seg` equals `over` (indicating that, after traversing `over` in direction `dir`, we would immediately
* traverse it again in the opposite direction), `INT_MAX` is returned.
+ * \li If `over` loops back to itself (i.e. its `start` and `end` members are equal), `INT_MAX` is returned.
* \li Otherwise, if `over` does not allow through traffic but `from->seg` does, the through traffic penalty of the
* vehicle profile (`profile`) is applied.
*
@@ -2046,6 +2027,12 @@ static int route_value_seg(struct vehicleprofile *profile, struct route_graph_po
int dir) {
int ret;
struct route_traffic_distortion dist,*distp=NULL;
+ if (!dir) {
+ dbg(lvl_warning, "dir is zero, assuming positive");
+ dir = 1;
+ }
+ if (from && (over->start == over->end))
+ return INT_MAX;
if ((over->data.flags & (dir >= 0 ? profile->flags_forward_mask : profile->flags_reverse_mask)) != profile->flags)
return INT_MAX;
if (dir > 0 && (over->start->flags & RP_TURN_RESTRICTION))
@@ -2054,14 +2041,18 @@ static int route_value_seg(struct vehicleprofile *profile, struct route_graph_po
return INT_MAX;
if (from && from->seg == over)
return INT_MAX;
+ if (over->data.item.type == type_traffic_distortion)
+ return INT_MAX;
if ((over->start->flags & RP_TRAFFIC_DISTORTION) && (over->end->flags & RP_TRAFFIC_DISTORTION) &&
- route_get_traffic_distortion(over, &dist) && dir != 2 && dir != -2) {
+ route_get_traffic_distortion(over, dir, profile, &dist) && dir != 2 && dir != -2) {
+ /* we have a traffic distortion */
distp=&dist;
}
ret=route_time_seg(profile, &over->data, distp);
if (ret == INT_MAX)
return ret;
- if (!route_through_traffic_allowed(profile, over) && from && route_through_traffic_allowed(profile, from->seg))
+ if (!route_through_traffic_allowed(profile, over) && from && from->seg
+ && route_through_traffic_allowed(profile, from->seg))
ret+=profile->through_traffic_penalty;
return ret;
}
@@ -2083,6 +2074,141 @@ static int route_graph_segment_match(struct route_graph_segment *s1, struct rout
}
/**
+ * @brief Adds two route values with protection against integer overflows.
+ *
+ * Unlike regular addition, this function is safe to use if one of the two arguments is `INT_MAX`
+ * (which Navit uses to express that a segment cannot be traversed or a point cannot be reached):
+ * If any of the two arguments is `INT_MAX`, then `INT_MAX` is returned; else the sum of the two
+ * arguments is returned.
+ *
+ * Note that this currently does not cover cases in which both arguments are less than `INT_MAX` but add
+ * up to `val1 + val2 >= INT_MAX`. With Navit’s internal cost definition, `INT_MAX` (2^31) is equivalent
+ * to approximately 7 years, making this unlikely to become a real issue.
+ */
+static int route_value_add(int val1, int val2) {
+ if (val1 == INT_MAX)
+ return INT_MAX;
+ if (val2 == INT_MAX)
+ return INT_MAX;
+ return val1 + val2;
+}
+
+/**
+ * @brief Updates the lookahead value of a point in the route graph and updates its heap membership.
+ *
+ * This recalculates the lookahead value (the `rhs` member) of point `p`, based on the `value` of each neighbor and the
+ * cost to reach that neighbor. If the resulting `p->rhs` differs from `p->value`, `p` is inserted into `heap` using
+ * the lower of the two as its key (if `p` is already a member of `heap`, its key is changed accordingly). If the
+ * resulting `p->rhs` is equal to `p->value` and `p` is a member of `heap`, it is removed.
+ *
+ * This is part of a modified LPA* implementation.
+ *
+ * @param profile The vehicle profile to use for routing. This determines which ways are passable and how their costs
+ * are calculated.
+ * @param p The point to evaluate
+ * @param heap The heap
+ */
+static void route_graph_point_update(struct vehicleprofile *profile, struct route_graph_point * p,
+ struct fibheap * heap) {
+ struct route_graph_segment *s = NULL;
+ int new, val;
+
+ p->rhs = p->dst_val;
+ p->seg = p->dst_seg;
+
+ for (s = p->start; s; s = s->start_next) { /* Iterate over all the segments leading away from our point */
+ val = route_value_seg(profile, s->end, s, 1);
+ if (val != INT_MAX && s->end->seg && item_is_equal(s->data.item, s->end->seg->data.item)) {
+ if (profile->turn_around_penalty2)
+ val += profile->turn_around_penalty2;
+ else
+ val = INT_MAX;
+ }
+ if (val != INT_MAX) {
+ new = route_value_add(val, s->end->value);
+ if (new < p->rhs) {
+ p->rhs = new;
+ p->seg = s;
+ }
+ }
+ }
+
+ for (s = p->end; s; s = s->end_next) { /* Iterate over all the segments leading towards our point */
+ val = route_value_seg(profile, s->start, s, -1);
+ if (val != INT_MAX && s->start->seg && item_is_equal(s->data.item, s->start->seg->data.item)) {
+ if (profile->turn_around_penalty2)
+ val += profile->turn_around_penalty2;
+ else
+ val = INT_MAX;
+ }
+ if (val != INT_MAX) {
+ new = route_value_add(val, s->start->value);
+ if (new < p->rhs) {
+ p->rhs = new;
+ p->seg = s;
+ }
+ }
+ }
+
+ if (p->el) {
+ /* Due to a limitation of the Fibonacci heap implementation, which causes fh_replacekey() to fail if the new
+ * key is greater than the current one, we always remove the point from the heap (and, if locally inconsistent,
+ * re-add it afterwards). */
+ fh_delete(heap, p->el);
+ p->el = NULL;
+ }
+
+ if (p->rhs != p->value)
+ /* The point is locally inconsistent, add (or re-add) it to the heap */
+ p->el = fh_insertkey(heap, MIN(p->rhs, p->value), p);
+}
+
+/**
+ * @brief Expands (i.e. calculates the costs for) the points on the route graph’s heap.
+ *
+ * This calculates the cost for every point on the route graph’s heap, as well as any neighbors affected by the cost
+ * change, and sets the next segment.
+ *
+ * This is part of a modified LPA* implementation.
+ *
+ * @param graph The route graph
+ * @param profile The vehicle profile to use for routing. This determines which ways are passable and how their costs
+ * are calculated.
+ * @param cb The callback function to call when flooding is complete (can be NULL)
+ */
+static void route_graph_compute_shortest_path(struct route_graph * graph, struct vehicleprofile * profile,
+ struct callback *cb) {
+ struct route_graph_point *p_min;
+ struct route_graph_segment *s = NULL;
+
+ while (!route_graph_is_path_computed(graph) && (p_min = fh_extractmin(graph->heap))) {
+ p_min->el = NULL;
+ if (p_min->value > p_min->rhs)
+ /* cost has decreased, update point value */
+ p_min->value = p_min->rhs;
+ else {
+ /* cost has increased, re-evaluate */
+ p_min->value = INT_MAX;
+ route_graph_point_update(profile, p_min, graph->heap);
+ }
+
+ /* in any case, update rhs of predecessors (nodes from which we can reach p_min via a single segment) */
+ for (s = p_min->start; s; s = s->start_next)
+ if ((s->start == s->end) || (s->data.item.type < route_item_first) || (s->data.item.type > route_item_last))
+ continue;
+ else if (route_value_seg(profile, NULL, s, -2) != INT_MAX)
+ route_graph_point_update(profile, s->end, graph->heap);
+ for (s = p_min->end; s; s = s->end_next)
+ if ((s->start == s->end) || (s->data.item.type < route_item_first) || (s->data.item.type > route_item_last))
+ continue;
+ else if (route_value_seg(profile, NULL, s, 2) != INT_MAX)
+ route_graph_point_update(profile, s->start, graph->heap);
+ }
+ if (cb)
+ callback_call_0(cb);
+}
+
+/**
* @brief Sets or clears a traffic distortion for a segment.
*
* This sets a delay (setting speed is not supported) or clears an existing traffic distortion.
@@ -2109,6 +2235,7 @@ static void route_graph_set_traffic_distortion(struct route_graph *this, struct
item.type=type_traffic_distortion;
data.item=&item;
data.len=delay;
+ data.flags = seg->data.flags & AF_DISTORTIONMASK;
s->start->flags |= RP_TRAFFIC_DISTORTION;
s->end->flags |= RP_TRAFFIC_DISTORTION;
route_graph_add_segment(this, s->start, s->end, &data);
@@ -2125,20 +2252,29 @@ static void route_graph_set_traffic_distortion(struct route_graph *this, struct
* @brief Adds a traffic distortion item to the route graph
*
* @param this The route graph to add to
+ * @param profile The vehicle profile to use for cost calculations
* @param item The item to add, must be of {@code type_traffic_distortion}
+ * @param update Whether to update the point (true for LPA*, false for Dijkstra)
*/
-static void route_process_traffic_distortion(struct route_graph *this, struct item *item) {
+static void route_graph_add_traffic_distortion(struct route_graph *this, struct vehicleprofile *profile,
+ struct item *item, int update) {
struct route_graph_point *s_pnt,*e_pnt;
struct coord c,l;
- struct attr delay_attr, maxspeed_attr;
+ struct attr flags_attr, delay_attr, maxspeed_attr;
struct route_graph_segment_data data;
data.item=item;
data.len=0;
- data.flags=0;
data.offset=1;
data.maxspeed = INT_MAX;
+ item_attr_rewind(item);
+ if (item_attr_get(item, attr_flags, &flags_attr))
+ data.flags = flags_attr.u.num & AF_DISTORTIONMASK;
+ else
+ data.flags = 0;
+
+ item_coord_rewind(item);
if (item_coord_get(item, &l, 1)) {
s_pnt=route_graph_add_point(this,&l);
while (item_coord_get(item, &c, 1)) {
@@ -2154,21 +2290,150 @@ static void route_process_traffic_distortion(struct route_graph *this, struct it
if (item_attr_get(item, attr_delay, &delay_attr))
data.len=delay_attr.u.num;
route_graph_add_segment(this, s_pnt, e_pnt, &data);
+ if (update) {
+ if (!(data.flags & AF_ONEWAYREV))
+ route_graph_point_update(profile, s_pnt, this->heap);
+ if (!(data.flags & AF_ONEWAY))
+ route_graph_point_update(profile, e_pnt, this->heap);
+ }
+ }
+}
+
+/**
+ * @brief Removes a traffic distortion item from the route graph
+ *
+ * Removing a traffic distortion which is not in the graph is a no-op.
+ *
+ * @param this The route graph to remove from
+ * @param profile The vehicle profile to use for cost calculations
+ * @param item The item to remove, must be of {@code type_traffic_distortion}
+ */
+static void route_graph_remove_traffic_distortion(struct route_graph *this, struct vehicleprofile *profile,
+ struct item *item) {
+ struct route_graph_point *s_pnt = NULL, *e_pnt = NULL;
+ struct coord c, l;
+ struct route_graph_segment *curr;
+
+ item_coord_rewind(item);
+ if (item_coord_get(item, &l, 1)) {
+ s_pnt = route_graph_get_point(this, &l);
+ while (item_coord_get(item, &c, 1))
+ l = c;
+ e_pnt = route_graph_get_point(this, &l);
+ }
+ if (s_pnt && e_pnt) {
+#if 1
+ curr = s_pnt->start;
+ s_pnt->flags &= ~RP_TRAFFIC_DISTORTION;
+ for (curr = s_pnt->start; curr; curr = curr->start_next) {
+ if ((curr->end == e_pnt) && item_is_equal(curr->data.item, *item))
+ curr->data.item.type = type_none;
+ else if (curr->data.item.type == type_traffic_distortion)
+ s_pnt->flags |= RP_TRAFFIC_DISTORTION;
+ }
+
+ e_pnt->flags &= ~RP_TRAFFIC_DISTORTION;
+ for (curr = e_pnt->end; curr; curr = curr->end_next)
+ if (curr->data.item.type == type_traffic_distortion)
+ e_pnt->flags |= RP_TRAFFIC_DISTORTION;
+#else
+ struct route_graph_segment *found = NULL, *prev;
+ /* this frees up memory but is slower */
+ /* remove from global list */
+ curr = this->route_segments;
+ prev = NULL;
+ while (curr && !found) {
+ if ((curr->start == s_pnt) && (curr->end == e_pnt) && (curr->data.item == item)) {
+ if (prev)
+ prev->next = curr->next;
+ else
+ this->route_segments = curr->next;
+ found = curr;
+ } else {
+ prev = curr;
+ curr = prev->next;
+ }
+ }
+
+ if (!found)
+ return;
+
+ /* remove from s_pnt list */
+ curr = s_pnt->start;
+ prev = NULL;
+ s_pnt->flags &= ~RP_TRAFFIC_DISTORTION;
+ while (curr) {
+ if (curr == found) {
+ if (prev)
+ prev->start_next = curr->start_next;
+ else
+ s_pnt->start = curr->start_next;
+ } else {
+ if (curr->data.item.type == type_traffic_distortion)
+ s_pnt->flags |= RP_TRAFFIC_DISTORTION;
+ prev = curr;
+ }
+ curr = prev->start_next;
+ }
+
+ /* remove from e_pnt list */
+ curr = e_pnt->end;
+ prev = NULL;
+ e_pnt->flags &= ~RP_TRAFFIC_DISTORTION;
+ while (curr) {
+ if (curr == found) {
+ if (prev)
+ prev->end_next = curr->end_next;
+ else
+ s_pnt->end = curr->end_next;
+ } else {
+ if (curr->data.item.type == type_traffic_distortion)
+ e_pnt->flags |= RP_TRAFFIC_DISTORTION;
+ prev = curr;
+ }
+ curr = prev->end_next;
+ }
+
+ size = sizeof(struct route_graph_segment) - sizeof(struct route_segment_data)
+ + route_segment_data_size(found->data.flags);
+ g_slice_free1(size, found);
+#endif
+
+ /* TODO figure out if we need to update both points */
+ route_graph_point_update(profile, s_pnt, this->heap);
+ route_graph_point_update(profile, e_pnt, this->heap);
}
}
/**
+ * @brief Changes a traffic distortion item in the route graph
+ *
+ * Attempting to change an idem which is not in the route graph will add it.
+ *
+ * @param this The route graph to change
+ * @param profile The vehicle profile to use for cost calculations
+ * @param item The item to change, must be of {@code type_traffic_distortion}
+ */
+static void route_graph_change_traffic_distortion(struct route_graph *this, struct vehicleprofile *profile,
+ struct item *item) {
+ /* TODO is there a more elegant way of doing this? */
+ route_graph_remove_traffic_distortion(this, profile, item);
+ route_graph_add_traffic_distortion(this, profile, item, 1);
+}
+
+/**
* @brief Adds a turn restriction item to the route graph
*
* @param this The route graph to add to
* @param item The item to add, must be of `type_street_turn_restriction_no` or `type_street_turn_restriction_only`
*/
-static void route_process_turn_restriction(struct route_graph *this, struct item *item) {
+void route_graph_add_turn_restriction(struct route_graph *this, struct item *item) {
struct route_graph_point *pnt[4];
struct coord c[5];
int i,count;
struct route_graph_segment_data data;
+ item_coord_rewind(item);
count=item_coord_get(item, c, 5);
if (count != 3 && count != 4) {
dbg(lvl_debug,"wrong count %d",count);
@@ -2183,6 +2448,7 @@ static void route_process_turn_restriction(struct route_graph *this, struct item
data.item=item;
data.flags=0;
data.len=0;
+ data.score = 0;
route_graph_add_segment(this, pnt[0], pnt[1], &data);
route_graph_add_segment(this, pnt[1], pnt[2], &data);
#if 1
@@ -2205,7 +2471,7 @@ static void route_process_turn_restriction(struct route_graph *this, struct item
* @param item The item to add
* @param profile The vehicle profile currently in use
*/
-static void route_process_street_graph(struct route_graph *this, struct item *item, struct vehicleprofile *profile) {
+static void route_graph_add_street(struct route_graph *this, struct item *item, struct vehicleprofile *profile) {
#ifdef AVOID_FLOAT
int len=0;
#else
@@ -2213,6 +2479,8 @@ static void route_process_street_graph(struct route_graph *this, struct item *it
#endif
int segmented = 0;
struct roadprofile *roadp;
+ int default_flags_value = AF_ALL;
+ int *default_flags;
struct route_graph_point *s_pnt,*e_pnt; /* Start and end point */
struct coord c,l; /* Current and previous point */
struct attr attr;
@@ -2228,23 +2496,18 @@ static void route_process_street_graph(struct route_graph *this, struct item *it
return;
}
+ item_coord_rewind(item);
if (item_coord_get(item, &l, 1)) {
- int default_flags_value=AF_ALL;
- int *default_flags=item_get_default_flags(item->type);
- if (! default_flags)
- default_flags=&default_flags_value;
+ if (!(default_flags = item_get_default_flags(item->type)))
+ default_flags = &default_flags_value;
if (item_attr_get(item, attr_flags, &attr)) {
data.flags = attr.u.num;
- if (data.flags & AF_SEGMENTED)
- segmented = 1;
+ segmented = (data.flags & AF_SEGMENTED);
} else
data.flags = *default_flags;
-
- if (data.flags & AF_SPEED_LIMIT) {
- if (item_attr_get(item, attr_maxspeed, &attr))
- data.maxspeed = attr.u.num;
- }
+ if ((data.flags & AF_SPEED_LIMIT) && (item_attr_get(item, attr_maxspeed, &attr)))
+ data.maxspeed = attr.u.num;
if (data.flags & AF_DANGEROUS_GOODS) {
if (item_attr_get(item, attr_vehicle_dangerous_goods, &attr))
data.dangerous_goods = attr.u.num;
@@ -2347,128 +2610,87 @@ static struct route_graph_segment *route_graph_get_segment(struct route_graph *g
}
/**
- * @brief Calculates the routing costs for each point
+ * @brief Whether cost (re)calculation of route graph points has reached the start point
*
- * This function is the heart of routing. It assigns each point in the route graph a
- * cost at which one can reach the destination from this point on. Additionally it assigns
- * each point a segment one should follow from this point on to reach the destination at the
- * stated costs.
+ * This method serves as the exit criterion for cost calculation in our LPA* implementation. When it returns true, it
+ * means that calculation of node cost has proceeded far enough to determine the cost of, and cheapest path from, the
+ * start point.
*
- * This function uses Dijkstra's algorithm to do the routing. To understand it you should have a look
- * at this algorithm.
+ * The current implementation returns true only when the heap is empty, i.e. all points have been calculated. This is
+ * not optimal in terms of efficiency, as the cost of the start point and the cheapest path from there no longer
+ * change during the last few cycles. Future versions may report true before the heap is completely empty, as soon as
+ * the cost of the start point and the cheapest path are final. However, this needs to be considered for recalculations
+ * which happen when the vehicle leaves the cheapest path: right now, any point in the route graph has its final cost
+ * and cheapest path, thus no recalculation is needed if the vehicle leaves the cheapest path. In the future, however,
+ * a (partial) recalculation may be needed if the vehicle deviates from the cheapest path.
*
- * References to elements of the route graph which were obtained prior to calling this function
- * remain valid after it returns.
+ * @param this_ The route graph
*
- * @param this_ The route graph to flood
- * @param dst The destination of the route
- * @param profile The vehicle profile to use for routing. This determines which ways are passable
- * and how their costs are calculated.
- * @param cb The callback function to call when flooding is complete
+ * @return true if calculation is complete, false if not
*/
-static void route_graph_flood(struct route_graph *this, struct route_info *dst, struct vehicleprofile *profile,
- struct callback *cb) {
- struct route_graph_point *p_min;
- struct route_graph_segment *s=NULL;
- int min,new,val;
- struct fibheap *heap; /* This heap will hold all points with "temporarily" calculated costs */
+static int route_graph_is_path_computed(struct route_graph *this_) {
+ /* TODO refine exit criterion */
+ if (!fh_min(this_->heap))
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * @brief Triggers partial recalculation of the route, based on the existing route graph.
+ *
+ * This is currently used when traffic distortions have been added, changed or removed. Future versions may also use
+ * it if the current position has changed to a portion of the route graph which has not been flooded (which is
+ * currently not necessary because the route graph is always flooded completely).
+ *
+ * This tends to be faster than full recalculation, as only a subset of all points in the graph needs to be evaluated.
+ *
+ * If segment costs have changed (as is the case with traffic distortions), all affected segments must have been added
+ * to, removed from or updated in the route graph before this method is called.
+ *
+ * After recalculation, the route path is updated.
+ *
+ * The function uses a modified LPA* algorithm for recalculations. Most modifications were made for compatibility with
+ * the algorithm used for the initial routing:
+ * \li The `value` of a node represents the cost to reach the destination and thus decreases along the route
+ * (eliminating the need for recalculations as the vehicle moves within the route graph)
+ * \li The heuristic is always assumed to be zero (which would turn A* into Dijkstra, the basis of the main routing
+ * algorithm, and makes our keys one-dimensional)
+ * \li Currently, each pass evaluates all locally inconsistent points, leaving an empty heap at the end (though this
+ * may change in the future).
+ *
+ * @param this_ The route
+ */
+/* TODO This is absolutely not thread-safe and will wreak havoc if run concurrently with route_graph_flood(). This is
+ * not an issue as long as the two never overlap: Currently both this function and route_graph_flood() run without
+ * interruption until they finish, and are both on the main thread. If that changes, we need to revisit this. */
+void route_recalculate_partial(struct route *this_) {
+ struct attr route_status;
- heap = fh_makekeyheap();
+ /* do nothing if we don’t have a route graph */
+ if (!route_has_graph(this_))
+ return;
- while ((s=route_graph_get_segment(this, dst->street, s))) {
- val=route_value_seg(profile, NULL, s, -1);
- if (val != INT_MAX) {
- val=val*(100-dst->percent)/100;
- s->end->seg=s;
- s->end->value=val;
- s->end->el=fh_insertkey(heap, s->end->value, s->end);
- }
- val=route_value_seg(profile, NULL, s, 1);
- if (val != INT_MAX) {
- val=val*dst->percent/100;
- s->start->seg=s;
- s->start->value=val;
- s->start->el=fh_insertkey(heap, s->start->value, s->start);
- }
- }
- for (;;) {
- p_min=fh_extractmin(heap); /* Starting Dijkstra by selecting the point with the minimum costs on the heap */
- if (! p_min) /* There are no more points with temporarily calculated costs, Dijkstra has finished */
- break;
- min=p_min->value;
- if (debug_route)
- printf("extract p=%p free el=%p min=%d, 0x%x, 0x%x\n", p_min, p_min->el, min, p_min->c.x, p_min->c.y);
- p_min->el=NULL; /* This point is permanently calculated now, we've taken it out of the heap */
- s=p_min->start;
- while (s) { /* Iterating all the segments leading away from our point to update the points at their ends */
- val=route_value_seg(profile, p_min, s, -1);
- if (val != INT_MAX && item_is_equal(s->data.item,p_min->seg->data.item)) {
- if (profile->turn_around_penalty2)
- val+=profile->turn_around_penalty2;
- else
- val=INT_MAX;
- }
- if (val != INT_MAX) {
- new=min+val;
- if (debug_route)
- printf("begin %d len %d vs %d (0x%x,0x%x)\n",new,val,s->end->value, s->end->c.x, s->end->c.y);
- if (new < s->end->value) { /* We've found a less costly way to reach the end of s, update it */
- s->end->value=new;
- s->end->seg=s;
- if (! s->end->el) {
- if (debug_route)
- printf("insert_end p=%p el=%p val=%d ", s->end, s->end->el, s->end->value);
- s->end->el=fh_insertkey(heap, new, s->end);
- if (debug_route)
- printf("el new=%p\n", s->end->el);
- } else {
- if (debug_route)
- printf("replace_end p=%p el=%p val=%d\n", s->end, s->end->el, s->end->value);
- fh_replacekey(heap, s->end->el, new);
- }
- }
- if (debug_route)
- printf("\n");
- }
- s=s->start_next;
- }
- s=p_min->end;
- while (s) { /* Doing the same as above with the segments leading towards our point */
- val=route_value_seg(profile, p_min, s, 1);
- if (val != INT_MAX && item_is_equal(s->data.item,p_min->seg->data.item)) {
- if (profile->turn_around_penalty2)
- val+=profile->turn_around_penalty2;
- else
- val=INT_MAX;
- }
- if (val != INT_MAX) {
- new=min+val;
- if (debug_route)
- printf("end %d len %d vs %d (0x%x,0x%x)\n",new,val,s->start->value,s->start->c.x, s->start->c.y);
- if (new < s->start->value) {
- s->start->value=new;
- s->start->seg=s;
- if (! s->start->el) {
- if (debug_route)
- printf("insert_start p=%p el=%p val=%d ", s->start, s->start->el, s->start->value);
- s->start->el=fh_insertkey(heap, new, s->start);
- if (debug_route)
- printf("el new=%p\n", s->start->el);
- } else {
- if (debug_route)
- printf("replace_start p=%p el=%p val=%d\n", s->start, s->start->el, s->start->value);
- fh_replacekey(heap, s->start->el, new);
- }
- }
- if (debug_route)
- printf("\n");
- }
- s=s->end_next;
- }
- }
- fh_deleteheap(heap);
- callback_call_0(cb);
- dbg(lvl_debug,"return");
+ /* if the route graph is still being built, it will be calculated from scratch after that, nothing to do here */
+ if (this_->graph->busy)
+ return;
+
+ /* exit if there is no need to recalculate */
+ if (route_graph_is_path_computed(this_->graph))
+ return;
+
+ route_status.type = attr_route_status;
+
+ route_status.u.num = route_status_building_graph;
+ route_set_attr(this_, &route_status);
+
+ printf("Expanding points which have changed\n");
+
+ route_graph_compute_shortest_path(this_->graph, this_->vehicleprofile, NULL);
+
+ printf("Point expansion complete, recalculating route path\n");
+
+ route_path_update_done(this_, 0);
}
/**
@@ -2639,7 +2861,8 @@ static struct route_path *route_path_new(struct route_graph *this, struct route_
this->avoid_seg=s;
route_graph_set_traffic_distortion(this, this->avoid_seg, profile->turn_around_penalty);
route_graph_reset(this);
- route_graph_flood(this, dst, profile, NULL);
+ route_graph_init(this, dst, profile);
+ route_graph_compute_shortest_path(this, profile, NULL);
return route_path_new(this, oldpath, pos, dst, profile);
}
}
@@ -2753,6 +2976,7 @@ static void route_graph_clone_segment(struct route_graph *this, struct route_gra
data.len=s->data.len+1;
data.maxspeed=-1;
data.dangerous_goods=0;
+ data.score = s->data.score;
if (s->data.flags & AF_SPEED_LIMIT)
data.maxspeed=RSD_MAXSPEED(&s->data);
if (s->data.flags & AF_SEGMENTED)
@@ -2847,13 +3071,17 @@ static void route_graph_process_restrictions(struct route_graph *this) {
/**
* @brief Releases all resources needed to build the route graph.
*
- * If {@code cancel} is false, this function will start processing restrictions and ultimately call
- * the route graph's {@code done_cb} callback.
+ * If `cancel` is false, this function will start processing restrictions and ultimately call the route
+ * graph's `done_cb` callback.
+ *
+ * The traffic module will always call this method with `cancel` set to true, as it does not process
+ * restrictions and has no callback. Inside the routing module, `cancel` will be true if, and only if,
+ * navigation has been aborted.
*
* @param rg Points to the route graph
* @param cancel True if the process was aborted before completing, false if it completed normally
*/
-static void route_graph_build_done(struct route_graph *rg, int cancel) {
+void route_graph_build_done(struct route_graph *rg, int cancel) {
dbg(lvl_debug,"cancel=%d",cancel);
if (rg->idle_ev)
event_remove_idle(rg->idle_ev);
@@ -2869,7 +3097,8 @@ static void route_graph_build_done(struct route_graph *rg, int cancel) {
rg->sel=NULL;
if (! cancel) {
route_graph_process_restrictions(rg);
- callback_call_0(rg->done_cb);
+ if (rg->done_cb)
+ callback_call_0(rg->done_cb);
}
rg->busy=0;
}
@@ -2889,11 +3118,11 @@ static void route_graph_build_idle(struct route_graph *rg, struct vehicleprofile
}
}
if (item->type == type_traffic_distortion)
- route_process_traffic_distortion(rg, item);
+ route_graph_add_traffic_distortion(rg, profile, item, 0);
else if (item->type == type_street_turn_restriction_no || item->type == type_street_turn_restriction_only)
- route_process_turn_restriction(rg, item);
+ route_graph_add_turn_restriction(rg, item);
else
- route_process_street_graph(rg, item, profile);
+ route_graph_add_street(rg, item, profile);
count--;
}
}
@@ -2927,6 +3156,7 @@ static struct route_graph *route_graph_build(struct mapset *ms, struct coord *c,
ret->h=mapset_open(ms);
ret->done_cb=done_cb;
ret->busy=1;
+ ret->heap = fh_makekeyheap();
if (route_graph_build_next_map(ret)) {
if (async) {
ret->idle_cb=callback_new_2(callback_cast(route_graph_build_idle), ret, profile);
@@ -2939,7 +3169,8 @@ static struct route_graph *route_graph_build(struct mapset *ms, struct coord *c,
}
static void route_graph_update_done(struct route *this, struct callback *cb) {
- route_graph_flood(this->graph, this->current_dst, this->vehicleprofile, cb);
+ route_graph_init(this->graph, this->current_dst, this->vehicleprofile);
+ route_graph_compute_shortest_path(this->graph, this->vehicleprofile, cb);
}
/**
@@ -3216,7 +3447,7 @@ static int rm_attr_get(void *priv_data, enum attr_type attr_type, struct attr *a
return 0;
case attr_maxspeed:
mr->attr_next = attr_street_item;
- if (seg && seg->data->flags & AF_SPEED_LIMIT) {
+ if (seg && (seg->data->flags & AF_SPEED_LIMIT)) {
attr->u.num=RSD_MAXSPEED(seg->data);
} else {
@@ -3249,6 +3480,8 @@ static int rm_attr_get(void *priv_data, enum attr_type attr_type, struct attr *a
return 0;
return 1;
case attr_time:
+ /* TODO This ignores access flags on traffic distortions, but the attribute does not seem
+ * to be used anywhere */
mr->attr_next=attr_speed;
if (seg)
attr->u.num=route_time_seg(route->vehicleprofile, seg->data, NULL);
@@ -3256,6 +3489,8 @@ static int rm_attr_get(void *priv_data, enum attr_type attr_type, struct attr *a
return 0;
return 1;
case attr_speed:
+ /* TODO This ignores access flags on traffic distortions, but the attribute does not seem
+ * to be used anywhere */
mr->attr_next=attr_label;
if (seg)
attr->u.num=route_seg_speed(route->vehicleprofile, seg->data, NULL);
@@ -3450,9 +3685,11 @@ static int rp_attr_get(void *priv_data, enum attr_type attr_type, struct attr *a
&seg->data, NULL), seg->start, seg->end);
attr->u.str = mr->str;
return 1;
+ break;
default:
return 0;
}
+ break;
default:
mr->attr_next=attr_none;
attr->type=attr_none;
@@ -3686,11 +3923,13 @@ static struct item *rm_get_item(struct map_rect_priv *mr) {
id=route->pos;
break;
}
+ /* FALLTHRU */
case type_route_start:
case type_route_start_reverse:
mr->seg=NULL;
mr->dest=mr->mpriv->route->destinations;
+ /* FALLTHRU */
default:
if (mr->item.type == type_waypoint)
mr->dest=g_list_next(mr->dest);
@@ -3727,6 +3966,7 @@ static struct item *rm_get_item(struct map_rect_priv *mr) {
id=&(mr->mpriv->route->destinations);
if (mr->mpriv->route->destinations)
break;
+ /* FALLTHRU */
case type_route_end:
return NULL;
}
@@ -3822,6 +4062,30 @@ static struct map *route_get_map_helper(struct route *this_, struct map **map, c
}
/**
+ * @brief Adds a traffic distortion item to the route
+ *
+ * @param this_ The route
+ * @param item The item to add, must be of {@code type_traffic_distortion}
+ */
+void route_add_traffic_distortion(struct route *this_, struct item *item) {
+ if (route_has_graph(this_))
+ route_graph_add_traffic_distortion(this_->graph, this_->vehicleprofile, item, 1);
+}
+
+/**
+ * @brief Changes a traffic distortion item on the route
+ *
+ * Attempting to change an idem which is not in the route graph will add it.
+ *
+ * @param this_ The route
+ * @param item The item to change, must be of {@code type_traffic_distortion}
+ */
+void route_change_traffic_distortion(struct route *this_, struct item *item) {
+ if (route_has_graph(this_))
+ route_graph_change_traffic_distortion(this_->graph, this_->vehicleprofile, item);
+}
+
+/**
* @brief Returns a new map containing the route path
*
* This function returns a new map containing the route path.
@@ -3861,6 +4125,15 @@ enum route_path_flags route_get_flags(struct route *this_) {
}
/**
+ * @brief Retrieves the route graph.
+ *
+ * @return The route graph, or NULL if the route has no valid graph
+ */
+struct route_graph * route_get_graph(struct route *this_) {
+ return this_->graph;
+}
+
+/**
* @brief Whether the route has a valid graph.
*
* @return True if the route has a graph, false if not.
@@ -3869,6 +4142,19 @@ int route_has_graph(struct route *this_) {
return (this_->graph != NULL);
}
+/**
+ * @brief Removes a traffic distortion item from the route
+ *
+ * Removing a traffic distortion which is not in the route graph is a no-op.
+ *
+ * @param this_ The route
+ * @param item The item to remove, must be of {@code type_traffic_distortion}
+ */
+void route_remove_traffic_distortion(struct route *this_, struct item *item) {
+ if (route_has_graph(this_))
+ route_graph_remove_traffic_distortion(this_->graph, this_->vehicleprofile, item);
+}
+
void route_set_projection(struct route *this_, enum projection pro) {
}
diff --git a/navit/route_protected.h b/navit/route_protected.h
new file mode 100644
index 000000000..9b7bf62f6
--- /dev/null
+++ b/navit/route_protected.h
@@ -0,0 +1,184 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2008 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * @file route_private.h
+ *
+ * @brief Contains protected exports for route.c
+ *
+ * This file contains code that is exported by route.c. Unlike the exports in route.h, exports in this
+ * file are intended only for use by modules which are closely related to routing. They are not part of
+ * the public route API.
+ */
+
+#ifndef NAVIT_ROUTE_PROTECTED_H
+#define NAVIT_ROUTE_PROTECTED_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define RP_TRAFFIC_DISTORTION 1
+#define RP_TURN_RESTRICTION 2
+#define RP_TURN_RESTRICTION_RESOLVED 4
+
+#define RSD_MAXSPEED(x) *((int *)route_segment_data_field_pos((x), attr_maxspeed))
+
+/**
+ * @brief A point in the route graph
+ *
+ * This represents a point in the route graph. A point usually connects two or more segments,
+ * but there are also points which don't do that (e.g. at the end of a dead-end).
+ */
+struct route_graph_point {
+ struct route_graph_point *hash_next; /**< Pointer to a chained hashlist of all route_graph_points with this hash */
+ struct route_graph_segment *start; /**< Pointer to a list of segments of which this point is the start. The links
+ * of this linked-list are in route_graph_segment->start_next.*/
+ struct route_graph_segment *end; /**< Pointer to a list of segments of which this pointer is the end. The links
+ * of this linked-list are in route_graph_segment->end_next. */
+ struct route_graph_segment *seg; /**< Pointer to the segment one should use to reach the destination at
+ * least costs */
+ struct fibheap_el *el; /**< When this point is put on a Fibonacci heap, this is a pointer
+ * to this point's heap element */
+ int value; /**< The cost at which one can reach the destination from this point on.
+ * {@code INT_MAX} indicates that the destination is unreachable from this
+ * point, or that this point has not yet been examined. */
+ int rhs; /**< Lookahead value based on neighbors’ `value`; used for recalculation and
+ * equal to `value` after the route graph has been flooded. */
+ int dst_val; /**< For points close to the destination, this is the cost of the point if it
+ * is the last in the graph; `INT_MAX` for all other points. */
+ struct route_graph_segment *dst_seg; /**< For points close to the destination, this is the segment over which the
+ * destination can be reached directly */
+ struct coord c; /**< Coordinates of this point */
+ int flags; /**< Flags for this point (e.g. traffic distortion) */
+};
+
+/**
+ * @brief A segment in the route graph or path
+ *
+ * This is a segment in the route graph or path. A segment represents a driveable way.
+ */
+struct route_segment_data {
+ struct item item; /**< The item (e.g. street) that this segment represents. */
+ int flags; /**< Flags e.g. for access, restrictions, segmentation or roundabouts. */
+ int len; /**< Length of this segment, in meters */
+ int score; /**< Used by the traffic module to give preference to some
+ * segments over others */
+ /*NOTE: After a segment, various fields may follow, depending on what flags are set. Order of fields:
+ 1.) maxspeed Maximum allowed speed on this segment. Present if AF_SPEED_LIMIT is set.
+ 2.) offset If the item is segmented (i.e. represented by more than one segment), this
+ indicates the position of this segment in the item. Present if AF_SEGMENTED is set.
+ */
+};
+
+/**
+ * @brief Size and weight limits for a route segment
+ */
+struct size_weight_limit {
+ int width;
+ int length;
+ int height;
+ int weight;
+ int axle_weight;
+};
+
+/**
+ * @brief Data for a segment in the route graph
+ */
+struct route_graph_segment_data {
+ struct item *item; /**< The item which this segment is part of */
+ int offset; /**< If the item passed in "item" is segmented (i.e. divided
+ * into several segments), this indicates the position of
+ * this segment within the item */
+ int flags; /**< Flags for this segment */
+ int len; /**< The length of this segment */
+ int maxspeed; /**< The maximum speed allowed on this segment in km/h,
+ * -1 if not known */
+ struct size_weight_limit size_weight; /**< Size and weight limits for this segment */
+ int dangerous_goods;
+ int score; /**< Used by the traffic module to give preference to some
+ * segments over others */
+};
+
+/**
+ * @brief A segment in the route graph
+ *
+ * This is a segment in the route graph. A segment represents a driveable way.
+ */
+struct route_graph_segment {
+ struct route_graph_segment *next; /**< Linked-list pointer to a list of all route_graph_segments */
+ struct route_graph_segment *start_next; /**< Pointer to the next element in the list of segments that start at the
+ * same point. Start of this list is in route_graph_point->start. */
+ struct route_graph_segment *end_next; /**< Pointer to the next element in the list of segments that end at the
+ * same point. Start of this list is in route_graph_point->end. */
+ struct route_graph_point *start; /**< Pointer to the point this segment starts at. */
+ struct route_graph_point *end; /**< Pointer to the point this segment ends at. */
+ struct route_segment_data data; /**< The segment data */
+};
+
+/**
+ * @brief A complete route graph
+ *
+ * The route graph holds all routable segments along with the connections between them and the cost of
+ * each segment.
+ */
+struct route_graph {
+ int busy; /**< The graph is being built */
+ struct map_selection *sel; /**< The rectangle selection for the graph */
+ struct mapset_handle *h; /**< Handle to the mapset */
+ struct map *m; /**< Pointer to the currently active map */
+ struct map_rect *mr; /**< Pointer to the currently active map rectangle */
+ struct vehicleprofile *vehicleprofile; /**< The vehicle profile */
+ struct callback *idle_cb; /**< Idle callback to process the graph */
+ struct callback *done_cb; /**< Callback when graph is done */
+ struct event_idle *idle_ev; /**< The pointer to the idle event */
+ struct route_graph_segment *route_segments; /**< Pointer to the first route_graph_segment in the linked list of all segments */
+ struct route_graph_segment *avoid_seg;
+ struct fibheap *heap; /**< Priority queue for points to be expanded */
+#define HASH_SIZE 8192
+ struct route_graph_point *hash[HASH_SIZE]; /**< A hashtable containing all route_graph_points in this graph */
+};
+
+
+/* prototypes */
+struct route_graph * route_get_graph(struct route *this_);
+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);
+struct route_graph_point * route_graph_add_point(struct route_graph *this, struct coord *f);
+void route_graph_add_turn_restriction(struct route_graph *this, struct item *item);
+void route_graph_free_points(struct route_graph *this);
+struct route_graph_point *route_graph_get_point(struct route_graph *this, struct coord *c);
+struct route_graph_point *route_graph_get_point_next(struct route_graph *this, struct coord *c,
+ struct route_graph_point *last);
+void route_graph_add_segment(struct route_graph *this, struct route_graph_point *start,
+ struct route_graph_point *end, struct route_graph_segment_data *data);
+int route_graph_segment_is_duplicate(struct route_graph_point *start, struct route_graph_segment_data *data);
+void route_graph_free_segments(struct route_graph *this);
+void route_graph_build_done(struct route_graph *rg, int cancel);
+void route_recalculate_partial(struct route *this_);
+void * route_segment_data_field_pos(struct route_segment_data *seg, enum attr_type type);
+/* end of prototypes */
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/navit/start_real.c b/navit/start_real.c
index fd631916f..c3f8b5622 100644
--- a/navit/start_real.c
+++ b/navit/start_real.c
@@ -46,6 +46,7 @@
#include "atom.h"
#include "command.h"
#include "geom.h"
+#include "traffic.h"
#ifdef HAVE_API_WIN32_CE
#include <windows.h>
#include <winbase.h>
@@ -110,6 +111,7 @@ int main_real(int argc, char * const* argv) {
search_init();
linguistics_init();
geom_init();
+ traffic_init();
config_file=NULL;
#ifdef HAVE_GETOPT_H
opterr=0; //don't bomb out on errors.
diff --git a/navit/traffic.c b/navit/traffic.c
new file mode 100644
index 000000000..74304e3cf
--- /dev/null
+++ b/navit/traffic.c
@@ -0,0 +1,5194 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2017 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/** @file
+ * @brief Contains code related to processing traffic messages into map items.
+ *
+ * Currently the only map items supported are traffic distortions. More may be added in the future.
+ *
+ * Traffic distortions are used by Navit to route around traffic problems.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef _POSIX_C_SOURCE
+#include <sys/types.h>
+#endif
+#include <sys/time.h>
+#include "glib_slice.h"
+#include "config.h"
+#include "navit.h"
+#include "util.h"
+#include "coord.h"
+#include "item.h"
+#include "map.h"
+#include "mapset.h"
+#include "route_protected.h"
+#include "route.h"
+#include "transform.h"
+#include "xmlconfig.h"
+#include "traffic.h"
+#include "plugin.h"
+#include "fib.h"
+#include "event.h"
+#include "callback.h"
+#include "vehicleprofile.h"
+#include "debug.h"
+
+#undef TRAFFIC_DEBUG
+
+/** Flag to indicate new messages have been received */
+#define MESSAGE_UPDATE_MESSAGES 1 << 0
+
+/** Flag to indicate segments have changed */
+#define MESSAGE_UPDATE_SEGMENTS 1 << 1
+
+/** The penalty applied to an off-road link */
+#define PENALTY_OFFROAD 4
+
+/** The maximum penalty applied to points with non-matching attributes */
+#define PENALTY_POINT_MATCH 16
+
+/** Flag to indicate expired messages should be purged */
+#define PROCESS_MESSAGES_PURGE_EXPIRED 1 << 0
+
+/** Flag to indicate the message store should not be exported */
+#define PROCESS_MESSAGES_NO_DUMP_STORE 1 << 1
+
+/** The lowest order of items to consider */
+#define ROUTE_ORDER 18
+
+/** The buffer zone around the enclosing rectangle used in route calculations, absolute distance */
+#define ROUTE_RECT_DIST_ABS 1000
+
+/** The buffer zone around the enclosing rectangle used in route calculations, relative to rect size */
+#define ROUTE_RECT_DIST_REL 0
+
+/** Time slice for idle loops, in milliseconds */
+#define TIME_SLICE 40
+
+/**
+ * @brief Private data shared between all traffic instances.
+ */
+struct traffic_shared_priv {
+ GList * messages; /**< Currently active messages */
+ GList * message_queue; /**< Queued messages, waiting to be processed */
+ // TODO messages by ID? In a later phase…
+};
+
+/**
+ * @brief A traffic plugin instance.
+ *
+ * If multiple traffic plugins are loaded, each will have its own `struct traffic` instance.
+ */
+struct traffic {
+ NAVIT_OBJECT
+ struct navit *navit; /**< The navit instance */
+ struct traffic_shared_priv *shared; /**< Private data shared between all instances */
+ struct traffic_priv *priv; /**< Private data used by the plugin */
+ struct traffic_methods meth; /**< Methods implemented by the plugin */
+ struct callback * callback; /**< The callback function for the idle loop */
+ struct event_timeout * timeout; /**< The timeout event that triggers the loop function */
+ struct callback *idle_cb; /**< Idle callback to process new messages */
+ struct event_idle *idle_ev; /**< The pointer to the idle event */
+ struct mapset *ms; /**< The mapset used for routing */
+ struct route *rt; /**< The route to notify of traffic changes */
+ struct map *map; /**< The traffic map, in which traffic distortions are stored */
+};
+
+struct traffic_location_priv {
+ struct coord_geo * sw; /*!< Southwestern corner of rectangle enclosing all points.
+ * Calculated by Navit from the points of the location. */
+ struct coord_geo * ne; /*!< Northeastern corner of rectangle enclosing all points.
+ * Calculated by Navit from the points of the location. */
+};
+
+struct traffic_message_priv {
+ struct item **items; /**< The items for this message in the traffic map */
+};
+
+/**
+ * @brief Private data for the traffic map.
+ *
+ * If multiple traffic plugins are loaded, the map is shared between all of them.
+ */
+struct map_priv {
+ GList * items; /**< The map items */
+ // TODO items by start/end coordinates? In a later phase…
+};
+
+/**
+ * @brief Implementation-specific map rect data
+ */
+struct map_rect_priv {
+ struct map_priv *mpriv; /**< The map to which this map rect refers */
+ struct item *item; /**< The current item, i.e. the last item returned by the `map_rect_get_item` method */
+ GList * next_item; /**< `GList` entry for the next item to be returned by `map_rect_get_item` */
+};
+
+/**
+ * @brief Message-specific map private data
+ *
+ * This structure is needed to handle segments referenced by multiple messages. When a message changes,
+ * is cancelled or expires, the data of the remaining messages is used to determine the new attributes
+ * for the segment.
+ */
+struct item_msg_priv {
+ char * message_id; /**< Message ID for the associated message */
+ int speed; /**< The expected speed in km/h (`INT_MAX` for unlimited, 0 indicates
+ * that the road is closed) */
+ int delay; /**< Expected delay for this segment, in 1/10 s */
+ struct attr ** attrs; /**< Additional attributes to add to the segment */
+};
+
+/**
+ * @brief Implementation-specific item data for traffic map items
+ */
+struct item_priv {
+ struct map_rect_priv * mr; /**< The private data for the map rect from which the item was obtained */
+ struct attr **attrs; /**< The attributes for the item, `NULL`-terminated */
+ struct coord *coords; /**< The coordinates for the item */
+ int coord_count; /**< The number of elements in `coords` */
+ int refcount; /**< How many references to this item exist */
+ GList * message_data; /**< Message-specific data, see `struct item_msg_priv` */
+ struct attr **next_attr; /**< The next attribute of `item` to be returned by the `item_attr_get` method */
+ unsigned int
+ next_coord; /**< The index of the next coordinate of `item` to be returned by the `item_coord_get` method */
+ struct route *rt; /**< The route to which the item has been added */
+};
+
+/**
+ * @brief Data for segments affected by a traffic message.
+ *
+ * Speed can be specified in three different ways:
+ * \li `speed` replaces the maximum speed of the segment, if lower
+ * \li `speed_penalty` subtracts the specified amount from the maximum speed of the segment
+ * \li `speed_factor` is the percentage of the maximum speed of the segment to be assumed
+ *
+ * Where more than one of these values is set, the lowest speed applies.
+ */
+struct seg_data {
+ enum item_type type; /**< The item type; currently only `type_traffic_distortion` is supported */
+ int speed; /**< The expected speed in km/h (`INT_MAX` for unlimited, 0 indicates
+ * that the road is closed) */
+ int speed_penalty; /**< Difference between expected speed and the posted speed limit of
+ * the segment (0 for none); the resulting maximum speed is never
+ * less than 5 km/h */
+ int speed_factor; /**< Expected speed expressed as a percentage of the posted limit (100
+ * for full speed) */
+ int delay; /**< Expected delay for all segments combined, in 1/10 s */
+ enum location_dir dir; /**< Directionality */
+ int flags; /**< Access flags (modes of transportation to which the message applies) */
+ struct attr ** attrs; /**< Additional attributes to add to the segments */
+};
+
+struct point_data {
+ struct route_graph_point * p; /**< The point in the route graph */
+ int score; /**< The attribute matching score */
+};
+
+/**
+ * @brief State for the XML parser.
+ *
+ * Several members of this struct are used to cache traffic data model objects until they can be
+ * incorporated in a message.
+ *
+ * All `struct traffic_point` members are reset to NULL when the `location` member is set. Likewise, the
+ * `si` member is reset to NULL when a new event is added. The `location` and `events` members are reset
+ * to NULL when a message is created.
+ */
+struct xml_state {
+ GList * messages; /**< Messages read so far */
+ GList * tagstack; /**< Currently open tags (order is bottom to top) */
+ int is_valid; /**< Whether `tagstack` represents a hierarchy of elements we recognize */
+ int is_opened; /**< True if we have just opened an element;
+ * false if child elements have been opened and closed since */
+ struct traffic_point * at; /**< The point for a point location, NULL for linear locations. */
+ struct traffic_point * from; /**< The start of a linear location, or a point before `at`. */
+ struct traffic_point * to; /**< The end of a linear location, or a point after `at`. */
+ struct traffic_point * via; /**< A point between `from` and `to`. Required on ring roads
+ * unless `not_via` is used; cannot be used together with `at`. */
+ struct traffic_point * not_via; /**< A point NOT between `from` and `to`. Required on ring roads
+ * unless `via` is used; cannot be used together with `at`. */
+ struct traffic_location * location; /**< The location to which the next message refers. */
+ GList * si; /**< Supplementary information items for the next event. */
+ GList * events; /**< The events for the next message. */
+};
+
+/**
+ * @brief Data for an XML element
+ *
+ * `names` and `values` are always two separate arrays for this struct, regardless of what is indicated by
+ * `XML_ATTR_DISTANCE`.
+ */
+struct xml_element {
+ char * tag_name; /**< The tag name */
+ char ** names; /**< Attribute names */
+ char ** values; /**< Attribute values (indices correspond to `names`) */
+ char * text; /**< Character data (NULL-terminated) */
+};
+
+static struct seg_data * seg_data_new(void);
+static struct item * tm_add_item(struct map *map, enum item_type type, int id_hi, int id_lo,
+ int flags, struct attr **attrs, struct coord *c, int count, char * id);
+#ifdef TRAFFIC_DEBUG
+static void tm_dump_item_to_textfile(struct item * item);
+#endif
+static void tm_destroy(struct map_priv *priv);
+static void tm_coord_rewind(void *priv_data);
+static void tm_item_destroy(struct item * item);
+static struct item * tm_item_ref(struct item * item);
+static struct item * tm_item_unref(struct item * item);
+static void tm_item_update_attrs(struct item * item, struct route * route);
+static int tm_coord_get(void *priv_data, struct coord *c, int count);
+static void tm_attr_rewind(void *priv_data);
+static int tm_attr_get(void *priv_data, enum attr_type attr_type, struct attr *attr);
+static int tm_type_set(void *priv_data, enum item_type type);
+static struct route_graph * traffic_location_get_route_graph(struct traffic_location * this_,
+ struct mapset * ms);
+static int traffic_location_match_attributes(struct traffic_location * this_, struct item *item);
+static int traffic_message_add_segments(struct traffic_message * this_, struct mapset * ms, struct seg_data * data,
+ struct map *map, struct route * route);
+static void traffic_location_populate_route_graph(struct traffic_location * this_, struct route_graph * rg,
+ struct mapset * ms);
+static void traffic_dump_messages_to_xml(struct traffic * this_);
+static void traffic_loop(struct traffic * this_);
+static struct traffic * traffic_new(struct attr *parent, struct attr **attrs);
+static int traffic_process_messages_int(struct traffic * this_, int flags);
+static void traffic_message_dump_to_stderr(struct traffic_message * this_);
+static struct seg_data * traffic_message_parse_events(struct traffic_message * this_);
+static struct route_graph_point * traffic_route_flood_graph(struct route_graph * rg,
+ struct coord * c_start, struct coord * c_dst, struct route_graph_point * start_existing);
+
+static struct item_methods methods_traffic_item = {
+ tm_coord_rewind,
+ tm_coord_get,
+ tm_attr_rewind,
+ tm_attr_get,
+ NULL,
+ NULL,
+ NULL,
+ tm_type_set,
+};
+
+/**
+ * @brief Creates a Boolean value from its string representation.
+ *
+ * If the string equals `true`, `yes` or can be parsed to a nonzero integer, the result is true.
+ *
+ * If the string equals `false`, `no` or begins with the digit 0 and returns zero when parsed to an
+ * integer, the result is false.
+ *
+ * If NULL is supplied, or if the string does not match any known value, the result is the default value.
+ *
+ * String comparison is case-insensitive.
+ *
+ * Since true is always represented by a return value of 1, passing a `deflt` other than 0 or 1 allows
+ * the caller to determine if the string could be parsed correctly.
+ *
+ * @param string The string representation
+ * @param deflt The default value to return if `string` is not a valid representation of a Boolean value.
+ *
+ * @return The corresponding `enum event_class`, or `event_class_invalid` if `string` does not match a
+ * known identifier
+ */
+static int boolean_new(const char * string, int deflt) {
+ if (!string)
+ return deflt;
+ if (!g_ascii_strcasecmp(string, "yes") || !g_ascii_strcasecmp(string, "true") || atoi(string))
+ return 1;
+ if (!g_ascii_strcasecmp(string, "no") || !g_ascii_strcasecmp(string, "false") || ((string[0] == '0') && !atoi(string)))
+ return 0;
+ return deflt;
+}
+
+/**
+ * @brief Creates a new `struct seg_data` and initializes it with default values.
+ */
+static struct seg_data * seg_data_new(void) {
+ struct seg_data * ret = g_new0(struct seg_data, 1);
+ ret->type = type_traffic_distortion;
+ ret->speed = INT_MAX;
+ ret->speed_factor = 100;
+ return ret;
+}
+
+/**
+ * @brief Creates a timestamp from its ISO8601 representation.
+ *
+ * @param string The ISO8601 timestamp
+ *
+ * @return The timestamp, or 0 if `string` is NULL.
+ */
+static time_t time_new(char * string) {
+ if (!string)
+ return 0;
+ return iso8601_to_time(string);
+}
+
+/**
+ * @brief Whether two `struct seg_data` contain the same data.
+ *
+ * @return true if `l` and `r` are equal, false if not. Two NULL values are considered equal; a NULL value and a
+ * non-NULL value are not.
+ */
+static int seg_data_equals(struct seg_data * l, struct seg_data * r) {
+ struct attr ** attrs;
+ struct attr * attr;
+
+ if (!l && !r)
+ return 0;
+ else if (!l || !r)
+ return 1;
+ if (l->type != r->type)
+ return 0;
+ if (l->speed != r->speed)
+ return 0;
+ if (l->speed_penalty != r->speed_penalty)
+ return 0;
+ if (l->speed_factor != r->speed_factor)
+ return 0;
+ if (l->delay != r->delay)
+ return 0;
+ if (l->dir != r->dir)
+ return 0;
+ if (l->flags != r->flags)
+ return 0;
+ if (!l->attrs && !r->attrs)
+ return 1;
+ if (!l->attrs || !r->attrs)
+ return 0;
+ /* FIXME this will break if multiple attributes of the same type are present and have different values */
+ for (attrs = l->attrs; attrs; attrs++) {
+ attr = attr_search(r->attrs, NULL, (*attrs)->type);
+ if (!attr || (attr->u.data != (*attrs)->u.data))
+ return 0;
+ }
+ for (attrs = r->attrs; attrs; attrs++) {
+ attr = attr_search(l->attrs, NULL, (*attrs)->type);
+ if (!attr || (attr->u.data != (*attrs)->u.data))
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * @brief Adds message data to a traffic map item.
+ *
+ * This method checks if the item already has data for the message specified in `msgid`. If so, the
+ * existing data is updated, else a new entry is added.
+ *
+ * Data changes also trigger an update of the affected item’s attributes.
+ *
+ * @param item The item (its `priv_data` member must point to a `struct item_priv`)
+ * @param msgid The message ID
+ * @param speed The maximum speed for the segment (`INT_MAX` if none given)
+ * @param delay The delay for the segment, in tenths of seconds (0 for none)
+ * @param attrs Additional attributes specified by the message
+ * @param route The route affected by the changes
+ *
+ * @return true if data was changed, false if not
+ */
+static int tm_item_add_message_data(struct item * item, char * msgid, int speed, int delay, struct attr ** attrs,
+ struct route * route) {
+ int ret = 0;
+ struct item_priv * priv_data = item->priv_data;
+ GList * msglist;
+ struct item_msg_priv * msgdata;
+
+ for (msglist = priv_data->message_data; msglist; msglist = g_list_next(msglist)) {
+ msgdata = (struct item_msg_priv *) msglist->data;
+ if (!strcmp(msgdata->message_id, msgid))
+ break;
+ }
+
+ if (msglist) {
+ /* we have an existing item, update it */
+ ret |= ((msgdata->speed != speed) || (msgdata->delay != delay));
+ msgdata->speed = speed;
+ msgdata->delay = delay;
+ /* TODO attrs */
+ } else {
+ ret = 1;
+ /* we need to insert a new item */
+ msgdata = g_new0(struct item_msg_priv, 1);
+ msgdata->message_id = g_strdup(msgid);
+ msgdata->speed = speed;
+ msgdata->delay = delay;
+ /* TODO attrs */
+ priv_data->message_data = g_list_append(priv_data->message_data, msgdata);
+ }
+
+ if (ret)
+ tm_item_update_attrs(item, route);
+
+ return ret;
+}
+
+/**
+ * @brief Destroys a traffic map item.
+ *
+ * This function should never be called directly. Instead, be sure to obtain all references by calling
+ * `tm_item_ref()` and destroying them by calling `tm_item_unref()`.
+ *
+ * @param item The item (its `priv_data` member must point to a `struct item_priv`)
+ */
+static void tm_item_destroy(struct item * item) {
+ struct item_priv * priv_data = item->priv_data;
+ GList * msglist;
+ struct item_msg_priv * msgdata;
+
+ attr_list_free(priv_data->attrs);
+ g_free(priv_data->coords);
+
+ for (msglist = priv_data->message_data; msglist; msglist = g_list_remove(msglist, msglist->data)) {
+ msgdata = (struct item_msg_priv *) msglist->data;
+ g_free(msgdata->message_id);
+ attr_list_free(msgdata->attrs);
+ g_free(msgdata);
+ }
+
+ g_free(item->priv_data);
+ g_free(item);
+}
+
+/**
+ * @brief References a traffic map item.
+ *
+ * Storing a reference to a traffic map item should always be done by calling this function, passing the
+ * item as its argument. This will return the item and increase its reference count by one.
+ *
+ * Never store a pointer to a traffic item not obtained via this function. Doing so may have undesired
+ * side effects as the item will not be aware of the reference to it, and the reference may unexpectedly
+ * become invalid, leading to a segmentation fault.
+ *
+ * @param item The item (its `priv_data` member must point to a `struct item_priv`)
+ *
+ * @return The item. `NULL` will be returned if the argument is `NULL` or points to an item whose
+ * `priv_data` member is `NULL`.
+ */
+static struct item * tm_item_ref(struct item * item) {
+ if (!item)
+ return NULL;
+ if (!item->priv_data)
+ return NULL;
+ ((struct item_priv *) item->priv_data)->refcount++;
+ return item;
+}
+
+/**
+ * @brief Unreferences a traffic map item.
+ *
+ * This must be called when destroying a reference to a traffic map item. It will decrease the reference
+ * count of the item by one, and destroy the item if the last reference to is is removed.
+ *
+ * The map itself (and only the map) holds weak references to its items, which are not considered in the
+ * reference count. Consequently, when the reference count reaches zero, the item is also removed from
+ * the map.
+ *
+ * Unreferencing an item with a zero reference count (which is only possible for an item which has never
+ * been referenced since its creation) is equivalent to dropping the last reference, i.e. it will destroy
+ * the item.
+ *
+ * When the last reference is removed (or an item with a zero reference count is unreferenced) and the item’s `rt`
+ * member is set (indicating the route to which the item was added), the item is removed from that route.
+ *
+ * If the unreference operation is successful, this function returns `NULL`. This allows one-line
+ * operations such as:
+ *
+ * {@code some_item = tm_item_unref(some_item);}
+ *
+ * @param item The item (its `priv_data` member must point to a `struct item_priv`)
+ *
+ * @return `NULL` if the item was unreferenced successfully, `item` if it points to an item whose
+ * `priv_data` member is `NULL`.
+ */
+static struct item * tm_item_unref(struct item * item) {
+ struct item_priv * priv_data;
+ struct map_rect * mr;
+ struct item * mapitem;
+ if (!item)
+ return item;
+ if (!item->priv_data)
+ return item;
+ priv_data = (struct item_priv *) item->priv_data;
+ priv_data->refcount--;
+ if (priv_data->refcount <= 0) {
+ if (priv_data->rt)
+ route_remove_traffic_distortion(priv_data->rt, item);
+ mr = map_rect_new(item->map, NULL);
+ do {
+ mapitem = map_rect_get_item(mr);
+ } while (mapitem && (mapitem != item));
+ if (mapitem)
+ item_type_set(mapitem, type_none);
+ map_rect_destroy(mr);
+ tm_item_destroy(item);
+ }
+ return NULL;
+}
+
+/**
+ * @brief Updates the attributes of an item.
+ *
+ * This method must be called after changing the message data associated with an item, i.e. adding,
+ * removing or modifying message data.
+ *
+ * @param item The item
+ * @param route The route affected by the changes
+ */
+static void tm_item_update_attrs(struct item * item, struct route * route) {
+ struct item_priv * priv_data = (struct item_priv *) item->priv_data;
+ GList * msglist;
+ struct item_msg_priv * msgdata;
+ int speed = INT_MAX;
+ int delay = 0;
+ struct attr * attr = NULL;
+ int has_changes = 0;
+
+ for (msglist = priv_data->message_data; msglist; msglist = g_list_next(msglist)) {
+ msgdata = (struct item_msg_priv *) msglist->data;
+ if (msgdata->speed < speed)
+ speed = msgdata->speed;
+ if (msgdata->delay < delay)
+ delay = msgdata->delay;
+ /* TODO attrs */
+ }
+
+ if (!priv_data->attrs)
+ priv_data->attrs = g_new0(struct attr *, 1);
+
+ /* TODO maxspeed vs. delay:
+ * Currently both values are interpreted as being cumulative, which may give erroneous results.
+ * Consider a segment with a length of 1000 m and a maxspeed of 120 km/h, thus having a cost of 30 s.
+ * One message reports a maxspeed of 60 km/h and no delay, increasing the cost to 60 s. A second
+ * message reports no maxspeed but a delay of 30 s, also increasing the cost to 60 s. Both messages
+ * together would be interpreted as reducing the maxspeed to 60 km/h and adding a delay of 30 s,
+ * resulting in a cost of 90 s for the segment.
+ */
+ if (speed < INT_MAX) {
+ attr = attr_search(priv_data->attrs, NULL, attr_maxspeed);
+ if (!attr) {
+ attr = g_new0(struct attr, 1);
+ attr->type = attr_maxspeed;
+ attr->u.num = speed;
+ priv_data->attrs = attr_generic_add_attr(priv_data->attrs, attr);
+ g_free(attr);
+ attr = NULL;
+ has_changes = 1;
+ } else if (speed < attr->u.num) {
+ has_changes = 1;
+ attr->u.num = speed;
+ } else if (speed > attr->u.num) {
+ has_changes = 1;
+ attr->u.num = speed;
+ }
+ } else {
+ while ((attr = attr_search(priv_data->attrs, NULL, attr_maxspeed)))
+ priv_data->attrs = attr_generic_remove_attr(priv_data->attrs, attr);
+ }
+
+ if (delay) {
+ attr = attr_search(priv_data->attrs, NULL, attr_delay);
+ if (!attr) {
+ attr = g_new0(struct attr, 1);
+ attr->type = attr_delay;
+ attr->u.num = delay;
+ priv_data->attrs = attr_generic_add_attr(priv_data->attrs, attr);
+ g_free(attr);
+ attr = NULL;
+ has_changes = 1;
+ } else if (delay > attr->u.num) {
+ has_changes = 1;
+ attr->u.num = delay;
+ } else if (delay < attr->u.num) {
+ has_changes = 1;
+ attr->u.num = delay;
+ }
+ } else {
+ while (1) {
+ attr = attr_search(priv_data->attrs, NULL, attr_delay);
+ if (!attr)
+ break;
+ priv_data->attrs = attr_generic_remove_attr(priv_data->attrs, attr);
+ }
+ }
+
+ if (has_changes) {
+ if (!priv_data->rt) {
+ priv_data->rt = route;
+ route_add_traffic_distortion(priv_data->rt, item);
+ } else
+ route_change_traffic_distortion(priv_data->rt, item);
+ }
+}
+
+/**
+ * @brief Returns an item from the map which matches the supplied data.
+ *
+ * Comparison criteria are as follows:
+ *
+ * \li The item type must match
+ * \li Start and end coordinates must match (inverted coordinates will also match)
+ * \li If `attr_flags` is supplied in `attrs`, the item must have this attribute and the rules listed
+ * below are applied
+ * \li Flags in `AF_ALL` must match
+ * \li Flags in `AF_ONEWAYMASK` must be set either on both sides or neither side
+ * \li If set, flags in `AF_ONEWAYMASK` must effectively match (equal for same direction, inverted for
+ * opposite directions)
+ * \li Other attributes are currently ignored
+ *
+ * This is due to the way different reports for the same segment are handled:
+ *
+ * \li If multiple reports with the same access flags exist, one item is created; speed and delay are
+ * evaluated across all currently active reports in `tm_item_update_attrs()` (lowest speed and longest
+ * delay wins)
+ * \li If multiple reports exist and access flags differ, one item is created for each set of flags;
+ * items are deduplicated in `route_get_traffic_distortion()`
+ *
+ * @param mr A map rectangle in the traffic map
+ * @param type Type of the item
+ * @param attrs The attributes for the item
+ * @param c Points to an array of coordinates for the item
+ * @param count Number of items in `c`
+ */
+static struct item * tm_find_item(struct map_rect *mr, enum item_type type, struct attr **attrs,
+ struct coord *c, int count) {
+ struct item * ret = NULL;
+ struct item * curr;
+ struct item_priv * curr_priv;
+ struct attr wanted_flags_attr, curr_flags_attr;
+
+ while ((curr = map_rect_get_item(mr)) && !ret) {
+ if (curr->type != type)
+ continue;
+ if (attr_generic_get_attr(attrs, NULL, attr_flags, &wanted_flags_attr, NULL)) {
+ if (!item_attr_get(curr, attr_flags, &curr_flags_attr))
+ continue;
+ if ((wanted_flags_attr.u.num & AF_ALL) != (curr_flags_attr.u.num & AF_ALL))
+ continue;
+ continue;
+ } else
+ wanted_flags_attr.type = attr_none;
+ curr_priv = curr->priv_data;
+ if (curr_priv->coords[0].x == c[0].x && curr_priv->coords[0].y == c[0].y
+ && curr_priv->coords[curr_priv->coord_count-1].x == c[count-1].x
+ && curr_priv->coords[curr_priv->coord_count-1].y == c[count-1].y) {
+ if (wanted_flags_attr.type == attr_none) {
+ /* no flag comparison, match */
+ } else if ((wanted_flags_attr.u.num & AF_ONEWAYMASK) != (curr_flags_attr.u.num & AF_ONEWAYMASK))
+ /* different oneway restrictions, no match */
+ continue;
+ ret = curr;
+ } else if (curr_priv->coords[0].x == c[count-1].x
+ && curr_priv->coords[0].y == c[count-1].y
+ && curr_priv->coords[curr_priv->coord_count-1].x == c[0].x
+ && curr_priv->coords[curr_priv->coord_count-1].y == c[0].y) {
+ if (wanted_flags_attr.type == attr_none) {
+ /* no flag comparison, match */
+ } else if (!(wanted_flags_attr.u.num & AF_ONEWAYMASK) && !(curr_flags_attr.u.num & AF_ONEWAYMASK)) {
+ /* two bidirectional distortions, match */
+ } else if (wanted_flags_attr.u.num & curr_flags_attr.u.num & AF_ONEWAYMASK) {
+ /* oneway in opposite directions, no match */
+ continue;
+ } else if ((wanted_flags_attr.u.num ^ AF_ONEWAYMASK) & curr_flags_attr.u.num & AF_ONEWAYMASK) {
+ /* oneway in same direction, match */
+ } else {
+ continue;
+ }
+ ret = curr;
+ }
+ }
+ return ret;
+}
+
+#ifdef TRAFFIC_DEBUG
+/**
+ * @brief Dumps an item to a textfile map.
+ *
+ * This method writes the item to a textfile map named `distortion.txt` in the default data folder.
+ * This map can be added to the active mapset in order for the distortions to be rendered on the map and
+ * considered for routing.
+ *
+ * All data passed to this method is safe to free after the method returns, and doing so is the
+ * responsibility of the caller.
+ *
+ * @param item The item
+ */
+static void tm_dump_item_to_textfile(struct item * item) {
+ struct item_priv * ip = (struct item_priv *) item->priv_data;
+ struct attr **attrs = ip->attrs;
+ struct coord *c = ip->coords;
+ int i;
+ char * attr_text;
+
+ /* add the configuration directory to the name of the file to use */
+ char *dist_filename = g_strjoin(NULL, navit_get_user_data_directory(TRUE),
+ "/distortion.txt", NULL);
+ if (dist_filename) {
+ FILE *map = fopen(dist_filename,"a");
+ if (map) {
+ fprintf(map, "type=%s", item_to_name(item->type));
+ while (*attrs) {
+ attr_text = attr_to_text(*attrs, NULL, 0);
+ /* FIXME this may not work properly for all attribute types */
+ fprintf(map, " %s=%s", attr_to_name((*attrs)->type), attr_text);
+ g_free(attr_text);
+ attrs++;
+ }
+ fprintf(map, "\n");
+
+ for (i = 0; i < ip->coord_count; i++) {
+ fprintf(map,"0x%x 0x%x\n", c[i].x, c[i].y);
+ }
+ fclose(map);
+ } else {
+ dbg(lvl_error,"could not open file for distortions !!");
+
+ } /* else - if (map) */
+ g_free(dist_filename); /* free the file name */
+ } /* if (dist_filename) */
+}
+
+/**
+ * @brief Dumps the traffic map to a textfile map.
+ *
+ * This method writes all items to a textfile map named `distortion.txt` in the default data folder.
+ * This map can be added to the active mapset in order for the distortions to be rendered on the map and
+ * considered for routing.
+ *
+ * @param map The traffic map
+ */
+static void tm_dump_to_textfile(struct map * map) {
+ /* external method, verifies the public API as well as internal structure */
+ struct map_rect * mr;
+ struct item * item;
+
+ mr = map_rect_new(map, NULL);
+ while ((item = map_rect_get_item(mr)))
+ tm_dump_item_to_textfile(item);
+ map_rect_destroy(mr);
+}
+#endif
+
+/**
+ * @brief Adds an item to the map.
+ *
+ * If a matching item is already in the map, that item will be returned.
+ *
+ * All data passed to this method is safe to free after the method returns, and doing so is the
+ * responsibility of the caller.
+ *
+ * @param map The traffic map
+ * @param type Type of the item
+ * @param id_hi First part of the ID of the item (item IDs have two parts)
+ * @param id_lo Second part of the ID of the item
+ * @param flags Flags used as a matching criterion, and added to newly-created items
+ * @param attrs The attributes for the item
+ * @param c Points to an array of coordinates for the item
+ * @param count Number of items in `c`
+ * @param id Message ID for the associated message
+ *
+ * @return The map item
+ */
+static struct item * tm_add_item(struct map *map, enum item_type type, int id_hi, int id_lo,
+ int flags, struct attr **attrs, struct coord *c, int count, char * id) {
+ struct item * ret = NULL;
+ struct item_priv * priv_data;
+ struct map_rect * mr;
+ struct attr ** int_attrs = NULL;
+ struct attr flags_attr;
+
+ flags_attr.type = attr_flags;
+ flags_attr.u.num = flags;
+ int_attrs = attr_generic_set_attr(attr_list_dup(attrs), &flags_attr);
+
+ mr = map_rect_new(map, NULL);
+ ret = tm_find_item(mr, type, int_attrs, c, count);
+ if (!ret) {
+ ret = map_rect_create_item(mr, type);
+ ret->id_hi = id_hi;
+ ret->id_lo = id_lo;
+ ret->map = map;
+ ret->meth = &methods_traffic_item;
+ priv_data = (struct item_priv *) ret->priv_data;
+ priv_data->attrs = int_attrs;
+ priv_data->coords = g_memdup(c, sizeof(struct coord) * count);
+ priv_data->coord_count = count;
+ priv_data->next_attr = int_attrs;
+ priv_data->next_coord = 0;
+ } else if (int_attrs) {
+ /* free up our copy of the attribute list if we’re not attaching it to a new item */
+ attr_list_free(int_attrs);
+ }
+ map_rect_destroy(mr);
+ //tm_dump_item(ret);
+ return ret;
+}
+
+/**
+ * @brief Destroys (closes) the traffic map.
+ *
+ * @param priv The private data for the traffic map instance
+ */
+static void tm_destroy(struct map_priv *priv) {
+ g_free(priv);
+}
+
+/**
+ * @brief Opens a new map rectangle on the traffic map.
+ *
+ * This function opens a new map rectangle on the route graph's map.
+ *
+ * @param priv The traffic graph map's private data
+ * @param sel The map selection (to restrict search to a rectangle, order and/or item types)
+ * @return A new map rect's private data
+ */
+static struct map_rect_priv * tm_rect_new(struct map_priv *priv, struct map_selection *sel) {
+ struct map_rect_priv * mr;
+ dbg(lvl_debug,"enter");
+ mr=g_new0(struct map_rect_priv, 1);
+ mr->mpriv = priv;
+ mr->next_item = priv->items;
+ /* all other pointers are initially NULL */
+ return mr;
+}
+
+/**
+ * @brief Destroys a map rectangle on the traffic map.
+ */
+static void tm_rect_destroy(struct map_rect_priv *mr) {
+ /* just free the map_rect_priv, all its members are pointers to data "owned" by others */
+ g_free(mr);
+}
+
+/**
+ * @brief Returns the next item from the traffic map
+ *
+ * @param mr The map rect to search for items
+ *
+ * @return The next item, or `NULL` if the last item has already been retrieved.
+ */
+static struct item * tm_get_item(struct map_rect_priv *mr) {
+ struct item * ret = NULL;
+ struct item_priv * ip;
+
+ if (mr->item) {
+ ip = (struct item_priv *) mr->item->priv_data;
+ ip->mr = NULL;
+ }
+ if (mr->next_item) {
+ ret = (struct item *) mr->next_item->data;
+ ip = (struct item_priv *) ret->priv_data;
+ ip->mr = mr;
+ tm_attr_rewind(ret->priv_data);
+ tm_coord_rewind(ret->priv_data);
+ mr->next_item = g_list_next(mr->next_item);
+ }
+
+ mr->item = ret;
+ return ret;
+}
+
+/**
+ * @brief Returns the next item with the supplied ID from the traffic map
+ *
+ * @param mr The map rect to search for items
+ * @param id_hi The high-order portion of the ID
+ * @param id_lo The low-order portion of the ID
+ *
+ * @return The next item matching the ID; `NULL` if there are no matching items or the last matching
+ * item has already been retrieved.
+ */
+static struct item * tm_get_item_byid(struct map_rect_priv *mr, int id_hi, int id_lo) {
+ struct item *ret = NULL;
+ do {
+ ret = tm_get_item(mr);
+ } while (ret && (ret->id_lo != id_lo || ret->id_hi != id_hi));
+ return ret;
+}
+
+/**
+ * @brief Creates a new item of the specified type and inserts it into the map.
+ *
+ * @param mr The map rect in which to create the item
+ * @param type The type of item to create
+ *
+ * @return The new item. The item is of type `type` and has an allocated `priv_data` member; all other
+ * members of both structs are `NULL`.
+ */
+static struct item * tm_rect_create_item(struct map_rect_priv *mr, enum item_type type) {
+ struct map_priv * map_priv = mr->mpriv;
+ struct item * ret = NULL;
+ struct item_priv * priv_data;
+
+ priv_data = g_new0(struct item_priv, 1);
+
+ ret = g_new0(struct item, 1);
+ ret->type = type;
+ ret->priv_data = priv_data;
+ map_priv->items = g_list_append(map_priv->items, ret);
+
+ return ret;
+}
+
+/**
+ * @brief Rewinds the coordinates of the currently selected item.
+ *
+ * After rewinding, the next call to the `tm_coord_get()` will return the first coordinate of the
+ * current item.
+ *
+ * @param priv_data The item's private data
+ */
+static void tm_coord_rewind(void *priv_data) {
+ struct item_priv * ip = priv_data;
+
+ ip->next_coord = 0;
+}
+
+/**
+ * @brief Returns the coordinates of a traffic item.
+ *
+ * @param priv_data The item's private data
+ * @param c Pointer to a `struct coord` array where coordinates will be stored
+ * @param count The maximum number of coordinates to retrieve (must be less than or equal to the number
+ * of items `c` can hold)
+ * @return The number of coordinates retrieved
+ */
+static int tm_coord_get(void *priv_data, struct coord *c, int count) {
+ struct item_priv * ip = priv_data;
+ int ret = count;
+
+ if (!ip)
+ return 0;
+ if (ip->next_coord >= ip->coord_count)
+ return 0;
+ if (ip->next_coord + count > ip->coord_count)
+ ret = ip->coord_count - ip->next_coord;
+ memcpy(c, &ip->coords[ip->next_coord], ret * sizeof(struct coord));
+ ip->next_coord += ret;
+ return ret;
+}
+
+/**
+ * @brief Rewinds the attributes of the currently selected item.
+ *
+ * After rewinding, the next call to `tm_attr_get()` will return the first attribute.
+ *
+ * @param priv_data The item's private data
+ */
+static void tm_attr_rewind(void *priv_data) {
+ struct item_priv * ip = priv_data;
+
+ ip->next_attr = ip->attrs;
+}
+
+/**
+ * @brief Returns the next attribute of a traffic item which matches the specified type.
+ *
+ * @param priv_data The item's private data
+ * @param attr_type The attribute type to retrieve, or `attr_any` to retrieve the next attribute,
+ * regardless of type
+ * @param attr Receives the attribute
+ *
+ * @return True on success, false on failure
+ */
+static int tm_attr_get(void *priv_data, enum attr_type attr_type, struct attr *attr) {
+ struct item_priv * ip = priv_data;
+ int ret = 0;
+
+ if (!ip->next_attr)
+ return 0;
+ while (*(ip->next_attr) && !ret) {
+ ret = (attr_type == attr_any) || (attr_type == (*(ip->next_attr))->type);
+ if (ret)
+ attr_dup_content(*(ip->next_attr), attr);
+ ip->next_attr++;
+ }
+ return ret;
+}
+
+/**
+ * @brief Sets the type of a traffic item.
+ *
+ * @param priv_data The item's private data
+ * @param type The new type for the item. Setting it to `type_none` deletes the item from the map.
+ *
+ * @return 0 on failure, nonzero on success
+ */
+static int tm_type_set(void *priv_data, enum item_type type) {
+ struct item_priv * ip = priv_data;
+
+ if (!ip->mr || !ip->mr->item || (ip->mr->item->priv_data != priv_data)) {
+ dbg(lvl_error, "this function can only be called for the last item retrieved from its map rect");
+ return 0;
+ }
+
+ if (type == type_none) {
+ /* if we have multiple occurrences of this item in the list, move forward beyond the last one */
+ while (ip->mr->next_item && (ip->mr->next_item->data == ip->mr->item))
+ ip->mr->next_item = g_list_next(ip->mr->next_item);
+
+ /* remove the item from the map and set last retrieved item to NULL */
+ ip->mr->mpriv->items = g_list_remove_all(ip->mr->mpriv->items, ip->mr->item);
+ ip->mr->item = NULL;
+ } else {
+ ip->mr->item->type = type;
+ }
+
+ return 1;
+}
+
+static struct map_methods traffic_map_meth = {
+ projection_mg, /* pro: The projection used for that type of map */
+ "utf-8", /* charset: The charset this map uses. */
+ tm_destroy, /* map_destroy: Destroy ("close") a map. */
+ tm_rect_new, /* map_rect_new: Create a new map rect on the map. */
+ tm_rect_destroy, /* map_rect_destroy: Destroy a map rect */
+ tm_get_item, /* map_rect_get_item: Return the next item from a map rect */
+ tm_get_item_byid, /* map_rect_get_item_byid: Get an item with a specific ID from a map rect, can be NULL */
+ NULL, /* map_search_new: Start a new search on the map, can be NULL */
+ NULL, /* map_search_destroy: Destroy a map search struct, ignored if `map_search_new` is NULL */
+ NULL, /* map_search_get_item: Get the next item of a search on the map */
+ tm_rect_create_item, /* map_rect_create_item: Create a new item in the map */
+ NULL, /* map_get_attr */
+ NULL, /* map_set_attr */
+};
+
+/**
+ * @brief Whether the contents of an event are valid.
+ *
+ * This identifies any malformed events in which mandatory members are not set.
+ *
+ * @return true if the event is valid, false if it is malformed
+ */
+static int traffic_event_is_valid(struct traffic_event * this_) {
+ if (!this_->event_class || !this_->type) {
+ dbg(lvl_debug, "event_class (%d) or type (%d) are unknown", this_->event_class, this_->type);
+ return 0;
+ }
+ switch (this_->event_class) {
+ case event_class_congestion:
+ if ((this_->type < event_congestion_cleared) || (this_->type >= event_delay_clearance)) {
+ dbg(lvl_debug, "illegal type (%d) for event_class_congestion", this_->type);
+ return 0;
+ }
+ break;
+ case event_class_delay:
+ if ((this_->type < event_delay_clearance)
+ || (this_->type >= event_restriction_access_restrictions_lifted)) {
+ dbg(lvl_debug, "illegal type (%d) for event_class_delay", this_->type);
+ return 0;
+ }
+ break;
+ case event_class_restriction:
+ if ((this_->type < event_restriction_access_restrictions_lifted)
+ || (this_->type > event_restriction_speed_limit_lifted)) {
+ dbg(lvl_debug, "illegal type (%d) for event_class_restriction", this_->type);
+ return 0;
+ }
+ break;
+ default:
+ dbg(lvl_debug, "unknown event class %d", this_->event_class);
+ return 0;
+ }
+ if (this_->si_count && !this_->si) {
+ dbg(lvl_debug, "si_count=%d but no supplementary information", this_->si_count);
+ return 0;
+ }
+ /* TODO check SI */
+ return 1;
+}
+
+/**
+ * @brief Determines the degree to which the attributes of a location and a map item match.
+ *
+ * The result of this method is used to match a location to a map item. Its result is a score—the higher
+ * the score, the better the match.
+ *
+ * To calculate the score, all supplied attributes are examined and points are given for each attribute
+ * which is defined for the location. An exact match adds 4 points, a partial match adds 2. Values of 1
+ * and 3 are added where additional granularity is needed. The number of points attained is divided by
+ * the maximum number of points attainable, and the result is returned as a percentage value.
+ *
+ * If no points can be attained (because no attributes which must match are supplied), the score is 100
+ * for any item supplied.
+ *
+ * @param this_ The location
+ * @param item The map item
+ *
+ * @return The score, as a percentage value
+ */
+static int traffic_location_match_attributes(struct traffic_location * this_, struct item *item) {
+ int score = 0;
+ int maxscore = 0;
+ struct attr attr;
+
+ /* road type */
+ if ((this_->road_type != type_line_unspecified)) {
+ maxscore += 400;
+ if (item->type == this_->road_type)
+ score += 400;
+ else
+ switch (this_->road_type) {
+ /* motorway */
+ case type_highway_land:
+ if (item->type == type_highway_city)
+ score += 300;
+ else if (item->type == type_street_n_lanes)
+ score += 200;
+ break;
+ case type_highway_city:
+ if (item->type == type_highway_land)
+ score += 300;
+ else if (item->type == type_street_n_lanes)
+ score += 200;
+ break;
+ /* trunk */
+ case type_street_n_lanes:
+ if ((item->type == type_highway_land) || (item->type == type_highway_city)
+ || (item->type == type_street_4_land)
+ || (item->type == type_street_4_city))
+ score += 200;
+ break;
+ /* primary */
+ case type_street_4_land:
+ if (item->type == type_street_4_city)
+ score += 300;
+ else if ((item->type == type_street_n_lanes)
+ || (item->type == type_street_3_land))
+ score += 200;
+ else if (item->type == type_street_3_city)
+ score += 100;
+ break;
+ case type_street_4_city:
+ if (item->type == type_street_4_land)
+ score += 300;
+ else if ((item->type == type_street_n_lanes)
+ || (item->type == type_street_3_city))
+ score += 200;
+ else if (item->type == type_street_3_land)
+ score += 100;
+ break;
+ /* secondary */
+ case type_street_3_land:
+ if (item->type == type_street_3_city)
+ score += 300;
+ else if ((item->type == type_street_4_land)
+ || (item->type == type_street_2_land))
+ score += 200;
+ else if ((item->type == type_street_4_city)
+ || (item->type == type_street_2_city))
+ score += 100;
+ break;
+ case type_street_3_city:
+ if (item->type == type_street_3_land)
+ score += 300;
+ else if ((item->type == type_street_4_city)
+ || (item->type == type_street_2_city))
+ score += 200;
+ else if ((item->type == type_street_4_land)
+ || (item->type == type_street_2_land))
+ score += 100;
+ break;
+ /* tertiary */
+ case type_street_2_land:
+ if (item->type == type_street_2_city)
+ score += 300;
+ else if (item->type == type_street_3_land)
+ score += 200;
+ else if (item->type == type_street_3_city)
+ score += 100;
+ break;
+ case type_street_2_city:
+ if (item->type == type_street_2_land)
+ score += 300;
+ else if (item->type == type_street_3_city)
+ score += 200;
+ else if (item->type == type_street_3_land)
+ score += 100;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* road_ref */
+ if (this_->road_ref) {
+ maxscore += 400;
+ if (item_attr_get(item, attr_street_name_systematic, &attr))
+ score += (400 * (MAX_MISMATCH - compare_name_systematic(this_->road_ref, attr.u.str))) / MAX_MISMATCH;
+ }
+
+ /* road_name */
+ if (this_->road_name) {
+ maxscore += 200;
+ if (item_attr_get(item, attr_street_name, &attr)) {
+ // TODO crude comparison in need of refinement
+ if (!strcmp(this_->road_name, attr.u.str))
+ score += 200;
+ }
+ }
+
+ // TODO direction
+ // TODO destination
+
+ // TODO ramps
+
+ if (!maxscore)
+ return 100;
+ return (score * 100) / maxscore;
+}
+
+/**
+ * @brief Determines the degree to which the attributes of a point and a map item match.
+ *
+ * The result of this method is used to match a location to a map item. Its result is a score—the higher
+ * the score, the better the match.
+ *
+ * To calculate the score, all supplied attributes are examined and points are given for each attribute
+ * which is defined for the location. An exact match adds 4 points, a partial match adds 2. Values of 1
+ * and 3 are added where additional granularity is needed. The number of points attained is divided by
+ * the maximum number of points attainable, and the result is returned as a percentage value.
+ *
+ * If no points can be attained (because no attributes which must match are supplied), the score is 0
+ * for any item supplied.
+ *
+ * @param this_ The traffic point
+ * @param item The map item
+ *
+ * @return The score, as a percentage value
+ */
+static int traffic_point_match_attributes(struct traffic_point * this_, struct item *item) {
+ int score = 0;
+ int maxscore = 0;
+ struct attr attr;
+
+ /* junction_ref */
+ if (this_->junction_ref) {
+ maxscore += 400;
+ if (item_attr_get(item, attr_ref, &attr))
+ score += (400 * (MAX_MISMATCH - compare_name_systematic(this_->junction_ref, attr.u.str))) / MAX_MISMATCH;
+ }
+
+ /* junction_name */
+ if (this_->junction_name) {
+ if (item_attr_get(item, attr_label, &attr)) {
+ maxscore += 400;
+ // TODO crude comparison in need of refinement
+ if (!strcmp(this_->junction_name, attr.u.str))
+ score += 400;
+ }
+ }
+
+ // TODO tmc_table, point->tmc_id
+
+ if (!maxscore)
+ return 0;
+ return (score * 100) / maxscore;
+}
+
+/**
+ * @brief Determines the degree to which the attributes of a point match those of the segments connecting to it.
+ *
+ * The result of this method is used to match a location to a map item. Its result is a score—the higher the score, the
+ * better the match.
+ *
+ * To calculate the score, this method iterates over all segments which begin or end at `p`. The `junction_name`
+ * member of the location is compared against the name of the segment, and the highest score for any segment is
+ * returned. Currently the name must match completely, resulting in a score of 100, while everything else is considered
+ * a mismatch (with a score of 0). Future versions may introduce support for partial matches, with the score indicating
+ * the quality of the match.
+ *
+ * Segments which are part of the route are treated in a different manner, as the direction in which the segment is
+ * traversed (not the direction of the segment itself) is taken into account: When evaluating the start point of the
+ * route, only the first point (whose `seg` member points to the segment) will match; the opposite is true when the end
+ * point of the route is evaluated. This ensures the matched segment ends up being part of the route.
+ *
+ * If no points can be attained (because no attributes which must match are supplied), the score is 0 for any point.
+ *
+ * @param this_ The traffic point
+ * @param p The point shared by all segments to examine
+ * @param start The first point of the path
+ * @param match_start True to evaluate for the start point of a route, false for the end point
+ *
+ * @return The score, as a percentage value
+ */
+static int traffic_point_match_segment_attributes(struct traffic_point * this_, struct route_graph_point *p,
+ struct route_graph_point * start, int match_start) {
+
+ /* Iterator for route graph points */
+ struct route_graph_point *p_iter = start;
+
+ /* The predecessor pf `p`in the route graph */
+ struct route_graph_point *p_prev = NULL;
+
+ /* Whether we have a match for the start of a route segment, the end of a route segment or an off-route segment */
+ int has_start_match = 0, has_end_match = 0, has_offroute_match = 0;
+
+ /* The route segment being examined */
+ struct route_graph_segment *s;
+
+ /* Map rect for retrieving item data */
+ struct map_rect *mr;
+
+ /* The item being examined */
+ struct item * item;
+
+ /* The attribute being examined */
+ struct attr attr;
+
+ if (!this_->junction_name)
+ /* nothing to compare, score is 0 */
+ return 0;
+
+ /* find predecessor of p, if any */
+ while (p_iter && (p_iter != p)) {
+ if (!p_iter->seg) {
+ p_prev = NULL;
+ break;
+ }
+ p_prev = p_iter;
+ if (p_iter == p_iter->seg->start)
+ p_iter = p_iter->seg->end;
+ else
+ p_iter = p_iter->seg->start;
+ }
+
+ if (!p_prev && (p != start)) {
+ /* not a point on the route */
+ return 0;
+ }
+ /* check if we have a match for the start of a route segment */
+ if (p->seg) {
+ mr = map_rect_new(p->seg->data.item.map, NULL);
+ if ((item = map_rect_get_item_byid(mr, p->seg->data.item.id_hi, p->seg->data.item.id_lo))
+ && item_attr_get(item, attr_street_name, &attr)
+ // TODO crude comparison in need of refinement
+ && !strcmp(this_->junction_name, attr.u.str))
+ has_start_match = 1;
+ map_rect_destroy(mr);
+ }
+
+ /* check if we have a match for the end of a route segment */
+ if (p_prev && p_prev->seg) {
+ mr = map_rect_new(p_prev->seg->data.item.map, NULL);
+ if ((item = map_rect_get_item_byid(mr, p_prev->seg->data.item.id_hi, p_prev->seg->data.item.id_lo))
+ && item_attr_get(item, attr_street_name, &attr)
+ // TODO crude comparison in need of refinement
+ && !strcmp(this_->junction_name, attr.u.str))
+ has_end_match = 1;
+ map_rect_destroy(mr);
+ }
+
+ /* we cannot have multiple matches in different categories */
+ if (has_start_match && has_end_match) {
+ return 0;
+ }
+
+ /* check if we have a match for an off-route segment */
+ for (s = p->start; s && !has_offroute_match; s = s->start_next) {
+ if ((p->seg == s) || (p_prev && (p_prev->seg == s)))
+ /* segments is on the route, skip */
+ continue;
+ mr = map_rect_new(s->data.item.map, NULL);
+ if ((item = map_rect_get_item_byid(mr, s->data.item.id_hi, s->data.item.id_lo))
+ && item_attr_get(item, attr_street_name, &attr)
+ // TODO crude comparison in need of refinement
+ && !strcmp(this_->junction_name, attr.u.str))
+ has_offroute_match = 1;
+ map_rect_destroy(mr);
+ }
+
+ for (s = p->end; s && !has_offroute_match; s = s->end_next) {
+ if ((p->seg == s) || (p_prev && (p_prev->seg == s)))
+ /* segments is on the route, skip */
+ continue;
+ mr = map_rect_new(s->data.item.map, NULL);
+ if ((item = map_rect_get_item_byid(mr, s->data.item.id_hi, s->data.item.id_lo))
+ && item_attr_get(item, attr_street_name, &attr)
+ // TODO crude comparison in need of refinement
+ && !strcmp(this_->junction_name, attr.u.str))
+ has_offroute_match = 1;
+ map_rect_destroy(mr);
+ }
+
+ if (has_offroute_match) {
+ if (has_start_match || has_end_match) {
+ /* we cannot have multiple matches in different categories */
+ return 0;
+ }
+ } else {
+ if ((match_start && !has_start_match) || (!match_start && !has_end_match)) {
+ /* no match in requested category */
+ return 0;
+ }
+ }
+
+ return 100;
+}
+
+/**
+ * @brief Returns the cost of the segment in the given direction.
+ *
+ * The cost is calculated based on the length of the segment and a penalty which depends on the score.
+ * A segment with the maximum score of 100 is not penalized, i.e. its cost is equal to its length. A
+ * segment with a zero score is penalized with a factor of `PENALTY_OFFROAD`. For scores in between, a
+ * penalty factor between 1 and `PENALTY_OFFROAD` is applied.
+ *
+ * If the segment is impassable in the given direction, the cost is always `INT_MAX`.
+ *
+ * @param over The segment
+ * @param dir The direction (positive numbers indicate positive direction)
+ *
+ * @return The cost of the segment
+ */
+static int traffic_route_get_seg_cost(struct route_graph_segment *over, int dir) {
+ if (over->data.flags & (dir >= 0 ? AF_ONEWAYREV : AF_ONEWAY))
+ return INT_MAX;
+ if (dir > 0 && (over->start->flags & RP_TURN_RESTRICTION))
+ return INT_MAX;
+ if (dir < 0 && (over->end->flags & RP_TURN_RESTRICTION))
+ return INT_MAX;
+ if ((over->data.item.type < route_item_first) || (over->data.item.type > route_item_last))
+ return INT_MAX;
+
+ return over->data.len * (100 - over->data.score) * (PENALTY_OFFROAD - 1) / 100 + over->data.len;
+}
+
+/**
+ * @brief Determines the “point triple” for a traffic location.
+ *
+ * Each traffic location is defined by up to three points:
+ * \li a start and end point, and an optional auxiliary point in between
+ * \li a single point, with one or two auxiliary points (one before, one after)
+ * \li a start and end point, and a third point which is outside the location
+ *
+ * This method determines these three points, puts them in the order in which they are encountered and
+ * returns a bit field indicating the end points. If a point in the array is NULL or refers to an
+ * auxiliary point, its corresponding bit is not set. The following values are returned:
+ * \li 2: Point location, the middle point is the actual point
+ * \li 3: Point-to-point location from the first to the second point; the third point is an auxiliary
+ * point outside the location
+ * \li 5: Point-to-point location from the first to the last point; the second point (if not NULL) is an
+ * auxiliary point located in between
+ * \li 6: Point-to-point location from the second to the third point; the first point is an auxiliary
+ * point outside the location
+ *
+ * @param this_ The location
+ * @param coords Points to an array which will receive pointers to the coordinates. The array must be
+ * able to store three pointers.
+ *
+ * @return A bit field indicating the end points for the location
+ */
+static int traffic_location_get_point_triple(struct traffic_location * this_, struct coord_geo ** coords) {
+ /* Which members of coords are the end points */
+ int ret = 0;
+
+ /* Projected coordinates */
+ struct coord c_from, c_to, c_not_via;
+
+ if (this_->at) {
+ coords[0] = this_->from ? &this_->from->coord : NULL;
+ coords[1] = &this_->at->coord;
+ coords[2] = this_->to ? &this_->to->coord : NULL;
+ ret = 1 << 1;
+ } else if (this_->via) {
+ coords[0] = this_->from ? &this_->from->coord : NULL;
+ coords[1] = &this_->via->coord;
+ coords[2] = this_->to ? &this_->to->coord : NULL;
+ ret = (1 << 2) | (1 << 0);
+ } else if (this_->not_via) {
+ /*
+ * If not_via is set, we calculate a route either for not_via-from-to or for from-to-not_via,
+ * then trim the ends. The order of points is determined by the distance between not_via and the
+ * other two points.
+ */
+ if (!this_->from || !this_->to) {
+ coords[0] = NULL;
+ coords[1] = NULL;
+ coords[2] = NULL;
+ return ret;
+ }
+ transform_from_geo(projection_mg, &this_->from->coord, &c_from);
+ transform_from_geo(projection_mg, &this_->to->coord, &c_to);
+ transform_from_geo(projection_mg, &this_->not_via->coord, &c_not_via);
+ if (transform_distance(projection_mg, &c_from, &c_not_via)
+ < transform_distance(projection_mg, &c_to, &c_not_via)) {
+ coords[0] = &this_->not_via->coord;
+ coords[1] = &this_->from->coord;
+ coords[2] = &this_->to->coord;
+ } else {
+ coords[0] = &this_->from->coord;
+ coords[1] = &this_->to->coord;
+ coords[2] = &this_->not_via->coord;
+ }
+ } else {
+ coords[0] = this_->from ? &this_->from->coord : NULL;
+ coords[1] = NULL;
+ coords[2] = this_->to ? &this_->to->coord : NULL;
+ ret = (1 << 2) | (1 << 0);
+ }
+ return ret;
+}
+
+/**
+ * @brief Sets the rectangle enclosing all points of a location
+ *
+ * @param this_ The traffic location
+ * @param coords The point triple, can be NULL
+ */
+static void traffic_location_set_enclosing_rect(struct traffic_location * this_, struct coord_geo ** coords) {
+ struct coord_geo * sw;
+ struct coord_geo * ne;
+ struct coord_geo * int_coords[] = {NULL, NULL, NULL};
+ int i;
+
+ if (this_->priv->sw && this_->priv->ne)
+ return;
+
+ if (!coords) {
+ coords = &int_coords[0];
+ traffic_location_get_point_triple(this_, coords);
+ }
+
+ if (!this_->priv->sw) {
+ sw = g_new0(struct coord_geo, 1);
+ sw->lat = INT_MAX;
+ sw->lng = INT_MAX;
+ for (i = 0; i < 3; i++)
+ if (coords[i]) {
+ if (coords[i]->lat < sw->lat)
+ sw->lat = coords[i]->lat;
+ if (coords[i]->lng < sw->lng)
+ sw->lng = coords[i]->lng;
+ }
+ this_->priv->sw = sw;
+ }
+
+ if (!this_->priv->ne) {
+ ne = g_new0(struct coord_geo, 1);
+ ne->lat = -INT_MAX;
+ ne->lng = -INT_MAX;
+ for (i = 0; i < 3; i++)
+ if (coords[i]) {
+ if (coords[i]->lat > ne->lat)
+ ne->lat = coords[i]->lat;
+ if (coords[i]->lng > ne->lng)
+ ne->lng = coords[i]->lng;
+ }
+ this_->priv->ne = ne;
+ }
+}
+
+/**
+ * @brief Opens a map rectangle around the end points of the traffic location.
+ *
+ * Prior to calling this function, the caller must ensure `rg->m` points to the map to be used, and the enclosing
+ * rectangle for the traffic location has been set (e.g. by calling `traffic_location_set_enclosing_rect()`).
+ *
+ * @param this_ The traffic location
+ * @param rg The route graph
+ *
+ * @return NULL on failure, the map selection on success
+ */
+static struct map_rect * traffic_location_open_map_rect(struct traffic_location * this_, struct route_graph * rg) {
+ /* Corners of the enclosing rectangle, in Mercator coordinates */
+ struct coord c1, c2;
+
+ transform_from_geo(map_projection(rg->m), this_->priv->sw, &c1);
+ transform_from_geo(map_projection(rg->m), this_->priv->ne, &c2);
+
+ rg->sel = route_rect(ROUTE_ORDER, &c1, &c2, ROUTE_RECT_DIST_REL, ROUTE_RECT_DIST_ABS);
+
+ if (!rg->sel)
+ return NULL;
+ rg->mr = map_rect_new(rg->m, rg->sel);
+ if (!rg->mr) {
+ map_selection_destroy(rg->sel);
+ rg->sel = NULL;
+ }
+ return rg->mr;
+}
+
+/**
+ * @brief Populates a route graph.
+ *
+ * This adds all routable segments in the enclosing rectangle of the location (plus a safety margin) to
+ * the route graph.
+ *
+ * @param rg The route graph
+ * @param ms The mapset to read the ramps from
+ */
+static void traffic_location_populate_route_graph(struct traffic_location * this_, struct route_graph * rg,
+ struct mapset * ms) {
+ /* The item being processed */
+ struct item *item;
+
+ /* Mercator coordinates of current and previous point */
+ struct coord c, l;
+
+ /* Data for the route graph segment */
+ struct route_graph_segment_data data;
+
+ /* The length of the current segment */
+#ifdef AVOID_FLOAT
+ int len;
+#else
+ double len;
+#endif
+
+ /* Whether the current item is segmented */
+ int segmented;
+
+ /* Default value assumed for access flags if we cannot get flags for the item, nor for the item type */
+ int default_flags_value = AF_ALL;
+
+ /* Default flags assumed for the current item type */
+ int *default_flags;
+
+ /* Holds an attribute retrieved from the current item */
+ struct attr attr;
+
+ /* Start and end point of the current way or segment */
+ struct route_graph_point *s_pnt, *e_pnt;
+
+ traffic_location_set_enclosing_rect(this_, NULL);
+
+ rg->h = mapset_open(ms);
+
+ while ((rg->m = mapset_next(rg->h, 2))) {
+ if (!traffic_location_open_map_rect(this_, rg))
+ continue;
+ while ((item = map_rect_get_item(rg->mr))) {
+ if (item->type == type_street_turn_restriction_no || item->type == type_street_turn_restriction_only)
+ route_graph_add_turn_restriction(rg, item);
+ else if ((item->type < route_item_first) || (item->type > route_item_last))
+ continue;
+ if (item_get_default_flags(item->type)) {
+
+ item_coord_rewind(item);
+ if (item_coord_get(item, &l, 1)) {
+ data.score = traffic_location_match_attributes(this_, item);
+ data.flags=0;
+ data.offset=1;
+ data.maxspeed=-1;
+ data.item=item;
+ len = 0;
+ segmented = 0;
+
+ if (!(default_flags = item_get_default_flags(item->type)))
+ default_flags = &default_flags_value;
+ if (item_attr_get(item, attr_flags, &attr)) {
+ data.flags = attr.u.num;
+ segmented = (data.flags & AF_SEGMENTED);
+ } else
+ data.flags = *default_flags;
+
+ if ((data.flags & AF_SPEED_LIMIT) && (item_attr_get(item, attr_maxspeed, &attr)))
+ data.maxspeed = attr.u.num;
+
+ /* clear flags we're not copying here */
+ data.flags &= ~(AF_DANGEROUS_GOODS | AF_SIZE_OR_WEIGHT_LIMIT);
+
+ s_pnt = route_graph_add_point(rg, &l);
+
+ if (!segmented) {
+ while (item_coord_get(item, &c, 1)) {
+ len += transform_distance(map_projection(item->map), &l, &c);
+ l = c;
+ }
+ e_pnt = route_graph_add_point(rg, &l);
+ dbg_assert(len >= 0);
+ data.len = len;
+ if (!route_graph_segment_is_duplicate(s_pnt, &data))
+ route_graph_add_segment(rg, s_pnt, e_pnt, &data);
+ } else {
+ int isseg, rc;
+ int sc = 0;
+ do {
+ isseg = item_coord_is_node(item);
+ rc = item_coord_get(item, &c, 1);
+ if (rc) {
+ len += transform_distance(map_projection(item->map), &l, &c);
+ l = c;
+ if (isseg) {
+ e_pnt = route_graph_add_point(rg, &l);
+ data.len = len;
+ if (!route_graph_segment_is_duplicate(s_pnt, &data))
+ route_graph_add_segment(rg, s_pnt, e_pnt, &data);
+ data.offset++;
+ s_pnt = route_graph_add_point(rg, &l);
+ len = 0;
+ }
+ }
+ } while(rc);
+ e_pnt = route_graph_add_point(rg, &l);
+ dbg_assert(len >= 0);
+ sc++;
+ data.len = len;
+ if (!route_graph_segment_is_duplicate(s_pnt, &data))
+ route_graph_add_segment(rg, s_pnt, e_pnt, &data);
+ }
+ }
+ }
+ }
+ map_selection_destroy(rg->sel);
+ rg->sel = NULL;
+ map_rect_destroy(rg->mr);
+ rg->mr = NULL;
+ }
+ route_graph_build_done(rg, 0);
+}
+
+/**
+ * @brief Builds a new route graph for traffic location matching.
+ *
+ * Traffic location matching is done by using a modified routing algorithm to identify the segments
+ * affected by a traffic message.
+ *
+ * @param this_ The location to match to the map
+ * @param ms The mapset to use for the route graph
+ *
+ * @return A route graph. The caller is responsible for destroying the route graph and all related data
+ * when it is no longer needed.
+ */
+static struct route_graph * traffic_location_get_route_graph(struct traffic_location * this_,
+ struct mapset * ms) {
+ struct route_graph *rg;
+
+ traffic_location_set_enclosing_rect(this_, NULL);
+
+ rg = g_new0(struct route_graph, 1);
+
+ rg->done_cb = NULL;
+ rg->busy = 1;
+
+ /* build the route graph */
+ traffic_location_populate_route_graph(this_, rg, ms);
+
+ return rg;
+}
+
+/**
+ * @brief Whether two traffic points are equal.
+ *
+ * Comparison is done solely on coordinates and requires a precise match. This can result in two points
+ * being reported as not equal, even though the locations using these points may translate to the same
+ * segments later.
+ *
+ * @return true if `l` and `r` are equal, false if not
+ */
+static int traffic_point_equals(struct traffic_point * l, struct traffic_point * r) {
+ if (l->coord.lat != r->coord.lat)
+ return 0;
+ if (l->coord.lng != r->coord.lng)
+ return 0;
+ return 1;
+}
+
+/**
+ * @brief Whether two traffic locations are equal.
+ *
+ * Only directionality, the `ramps` member and reference points are considered for comparison; auxiliary
+ * data (such as road names, road types and additional TMC information) is ignored.
+ *
+ * When in doubt, this function errs on the side of inequality, i.e. when equivalence cannot be reliably
+ * determined, the locations will be reported as not equal, even though they may translate to the same
+ * segments later.
+ *
+ * @return true if `l` and `r` are equal, false if not
+ */
+static int traffic_location_equals(struct traffic_location * l, struct traffic_location * r) {
+ /* directionality and ramps must match for locations to be considered equal */
+ if (l->directionality != r->directionality)
+ return 0;
+ if (l->ramps != r->ramps)
+ return 0;
+
+ /* locations must have the same points set to be considered equal */
+ if (!l->from != !r->from)
+ return 0;
+ if (!l->to != !r->to)
+ return 0;
+ if (!l->at != !r->at)
+ return 0;
+ if (!l->via != !r->via)
+ return 0;
+ if (!l->not_via != !r->not_via)
+ return 0;
+
+ /* both locations have the same points set, compare them */
+ if (l->from && !traffic_point_equals(l->from, r->from))
+ return 0;
+ if (l->to && !traffic_point_equals(l->to, r->to))
+ return 0;
+ if (l->at && !traffic_point_equals(l->at, r->at))
+ return 0;
+ if (l->via && !traffic_point_equals(l->via, r->via))
+ return 0;
+ if (l->not_via && !traffic_point_equals(l->not_via, r->not_via))
+ return 0;
+
+ /* No differences found, consider locations equal */
+ return 1;
+}
+
+/**
+ * @brief Determines the path between two reference points in a route graph.
+ *
+ * The reference points `from` and `to` are the beginning and end of the path and do not necessarily
+ * coincide with the `from` and `to` members of the location. For a point location with an auxiliary
+ * point, one will instead be the `at` member of the location; when examining the opposite direction of
+ * a bidirectional location, `from` and `to` will be swapped with respect to the location.
+ *
+ * The coordinates contained in the reference points are typically approximate, i.e. they do not
+ * precisely coincide with a point in the route graph.
+ *
+ * When this function returns, the route graph will be flooded, i.e. every point will have a cost
+ * assigned to it and the `seg` member for each point will be set, indicating the next segment on which
+ * to proceed in order to reach the destination. For the last point in the graph, `seg` will be `NULL`.
+ * Unlike in common routing, the last point will have a nonzero cost if `to` does not coincide with a
+ * point in the route graph.
+ *
+ * The cost of each node represents the cost to reach `to`. The cost is calculated in
+ * `traffic_route_get_seg_cost()` for actual segments, and distance (with a penalty factor) for the
+ * offroad connection from the last point in the graph to `to`.
+ *
+ * To obtain the path, start with the return value. Its `seg` member points to the next segment. Either
+ * the `start` or the `end` value of that segment will coincide with the point currently being examined;
+ * the other of the two is the point at the other end. Repeat this until you reach a point whose `seg`
+ * member is `NULL`.
+ *
+ * This function can be run multiple times against the same route graph but with different reference
+ * points. It is safe to call with `NULL` passed for one or both reference points, in which case `NULL`
+ * will be returned.
+ *
+ * The caller is responsible for freeing up the data structures passed to this function when they are no
+ * longer needed.
+ *
+ * @param rg The route graph
+ * @param c_start Start coordinates
+ * @param c_dst Destination coordinates
+ * @param start_existing Start point of an existing route (whose points will not be used)
+ *
+ * @return The point in the route graph at which the path begins, or `NULL` if no path was found.
+ */
+static struct route_graph_point * traffic_route_flood_graph(struct route_graph * rg,
+ struct coord * c_start, struct coord * c_dst, struct route_graph_point * start_existing) {
+ struct route_graph_point * ret;
+
+ int i;
+
+ GList * existing = NULL;
+
+ /* This heap will hold all points with "temporarily" calculated costs */
+ struct fibheap *heap;
+
+ /* Cost of the start position */
+ int start_value;
+
+ /* The point currently being examined */
+ struct route_graph_point *p;
+
+ /* Cost of point being examined, other end of segment being examined, segment */
+ int min, new, val;
+
+ /* The segment currently being examined */
+ struct route_graph_segment *s = NULL;
+
+ if (!c_start || !c_dst)
+ return NULL;
+
+ /* store points of existing route */
+ if (start_existing) {
+ p = start_existing;
+ while (p) {
+ /* Do not exclude the last point (seg==NULL) from the heap as that may result in the existing route not
+ * being joined properly to the new one */
+ if (p->seg)
+ existing = g_list_prepend(existing, p);
+ if (!p->seg)
+ p = NULL;
+ else if (p == p->seg->start)
+ p = p->seg->end;
+ else
+ p = p->seg->start;
+ }
+ }
+
+ /* prime the route graph */
+ heap = fh_makekeyheap();
+
+ start_value = PENALTY_OFFROAD * transform_distance(projection_mg, c_start, c_dst);
+ ret = NULL;
+
+ dbg(lvl_debug, "start flooding route graph, start_value=%d", start_value);
+
+ for (i = 0; i < HASH_SIZE; i++) {
+ p = rg->hash[i];
+ while (p) {
+ if (!g_list_find(existing, p)) {
+ if (!(p->flags & RP_TURN_RESTRICTION)) {
+ p->value = PENALTY_OFFROAD * transform_distance(projection_mg, &p->c, c_dst);
+ p->el = fh_insertkey(heap, p->value, p);
+ } else {
+ /* ignore points which are part of turn restrictions */
+ p->value = INT_MAX;
+ p->el = NULL;
+ }
+ p->seg = NULL;
+ }
+ p = p->hash_next;
+ }
+ }
+
+ /* flood the route graph */
+ for (;;) {
+ p = fh_extractmin(heap); /* Starting Dijkstra by selecting the point with the minimum costs on the heap */
+ if (!p) /* There are no more points with temporarily calculated costs, Dijkstra has finished */
+ break;
+
+ dbg(lvl_debug, "p=%p, value=%d", p, p->value);
+
+ min = p->value;
+ p->el = NULL; /* This point is permanently calculated now, we've taken it out of the heap */
+ s = p->start;
+ while (s) { /* Iterating all the segments leading away from our point to update the points at their ends */
+ val = traffic_route_get_seg_cost(s, -1);
+
+ dbg(lvl_debug, " negative segment, val=%d", val);
+
+ if (val != INT_MAX) {
+ new = min + val;
+ if (new < s->end->value) { /* We've found a less costly way to reach the end of s, update it */
+ s->end->value = new;
+ s->end->seg = s;
+ if (!s->end->el) {
+ s->end->el = fh_insertkey(heap, new, s->end);
+ } else {
+ fh_replacekey(heap, s->end->el, new);
+ }
+ new += PENALTY_OFFROAD * transform_distance(projection_mg, &s->end->c, c_start);
+ if (new < start_value) { /* We've found a less costly way from the start point, update */
+ start_value = new;
+ ret = s->end;
+ }
+ }
+ }
+ s = s->start_next;
+ }
+ s = p->end;
+ while (s) { /* Doing the same as above with the segments leading towards our point */
+ val = traffic_route_get_seg_cost(s, 1);
+
+ dbg(lvl_debug, " positive segment, val=%d", val);
+
+ if (val != INT_MAX) {
+ new = min + val;
+ if (new < s->start->value) {
+ s->start->value = new;
+ s->start->seg = s;
+ if (!s->start->el) {
+ s->start->el = fh_insertkey(heap, new, s->start);
+ } else {
+ fh_replacekey(heap, s->start->el, new);
+ }
+ new += PENALTY_OFFROAD * transform_distance(projection_mg, &s->start->c, c_start);
+ if (new < start_value) {
+ start_value = new;
+ ret = s->start;
+ }
+ }
+ }
+ s = s->end_next;
+ }
+ }
+
+ fh_deleteheap(heap);
+ g_list_free(existing);
+ return ret;
+}
+
+/**
+ * @brief Extends the route beyond its end point.
+ *
+ * This function follows the road beginning at `end`, stopping at the next junction. It can be called
+ * again on the result, again extending it to the next junction.
+ *
+ * To follow the road, each segment is compared to `last` and the segment whose attributes match it is
+ * chosen, provided such a segment can be determined without ambiguity.
+ *
+ * When the function returns, all points added to the route will have their `seg` member set. To append
+ * the new stretch to the route, set the `seg` member of its last point to the return value. After that,
+ * the extended route can be walked in the usual manner.
+ *
+ * The value of each new point is the value of its predecessor on the route mins the length of the
+ * segment which links the two points. Point values thus continue to decrease along the route, allowing
+ * comparisons or difference calculations to be performed on the extended route. Note that this may
+ * result in points having negative values.
+ *
+ * @param rg The flooded route graph
+ * @param last The last segment in the current route graph (either the `start` or the `end` member of
+ * this segment must be equal to the `end` argument)
+ * @param end The last point of the current route graph (the `seg` member of this point must be NULL)
+ *
+ * @return The next segment in the route, or `NULL` if the route cannot be extended.
+ */
+static struct route_graph_segment * traffic_route_append(struct route_graph *rg,
+ struct route_graph_segment * last, struct route_graph_point * end) {
+ struct route_graph_segment * ret = NULL, * s = last, * s_cmp, * s_next;
+ struct route_graph_point * p = end;
+ int num_seg;
+ int id_match;
+ int is_ambiguous;
+
+ if (!end) {
+ dbg(lvl_error, "end point cannot be NULL");
+ return NULL;
+ }
+ if (end->seg) {
+ dbg(lvl_error, "end point cannot have a next segment");
+ return NULL;
+ }
+
+ if ((end != last->end) && (end != last->start)) {
+ dbg(lvl_error, "last segment must begin or end at end point");
+ return NULL;
+ }
+
+ while (1) {
+ num_seg = 0;
+ id_match = 0;
+ is_ambiguous = 0;
+ s_next = NULL;
+ for (s_cmp = p->start; s_cmp; s_cmp = s_cmp->start_next) {
+ num_seg++;
+ if ((s_cmp == s) || (s_cmp->data.flags & AF_ONEWAYREV) || (s_cmp->end->flags & RP_TURN_RESTRICTION))
+ continue;
+ if (item_is_equal_id(s_cmp->data.item, s->data.item)) {
+ s_next = s_cmp;
+ id_match = 1;
+ } else if ((s_cmp->data.item.type == s->data.item.type) && !id_match && !is_ambiguous) {
+ if (s_next) {
+ s_next = NULL;
+ is_ambiguous = 1;
+ } else
+ s_next = s_cmp;
+ }
+ }
+ for (s_cmp = p->end; s_cmp; s_cmp = s_cmp->end_next) {
+ num_seg++;
+ if ((s_cmp == s) || (s_cmp->data.flags & AF_ONEWAY) || (s_cmp->end->flags & RP_TURN_RESTRICTION))
+ continue;
+ if (item_is_equal_id(s_cmp->data.item, s->data.item)) {
+ s_next = s_cmp;
+ id_match = 1;
+ } else if ((s_cmp->data.item.type == s->data.item.type) && !id_match && !is_ambiguous) {
+ if (s_next) {
+ s_next = NULL;
+ is_ambiguous = 1;
+ } else
+ s_next = s_cmp;
+ }
+ }
+
+ /* cancel if we are past end and have hit a junction */
+ if ((p != end) && (num_seg > 2))
+ break;
+
+ /* update links and move one step further */
+ if (p != end)
+ p->seg = s_next;
+ else
+ ret = s_next;
+ if (s_next) {
+ if (p == s_next->start) {
+ s_next->end->value = p->value - s_next->data.len;
+ p = s_next->end;
+ } else {
+ s_next->start->value = p->value - s_next->data.len;
+ p = s_next->start;
+ }
+ s = s_next;
+ } else
+ break;
+ }
+ p->seg = NULL;
+ dbg(lvl_debug, "return, last=%p, ret=%p", last, ret);
+ return ret;
+}
+
+/**
+ * @brief Extends the route beyond its start point.
+ *
+ * This function follows the road leading towards `start` backwards, stopping at the next junction. It
+ * can be called again on the result, again extending it to the next junction.
+ *
+ * To follow the road, each segment is compared to `start->seg` and the segment whose attributes match
+ * it is chosen, provided such a segment can be determined without ambiguity.
+ *
+ * When the function returns, all points added to the route will have their `seg` member set so the
+ * extended route can be walked in the usual manner.
+ *
+ * @param rg The flooded route graph
+ * @param start The current start of the route
+ *
+ * @return The start of the extended route, or `NULL` if the route cannot be extended (in which case
+ * `start` continues to be the start of the route).
+ */
+static struct route_graph_point * traffic_route_prepend(struct route_graph * rg,
+ struct route_graph_point * start) {
+ struct route_graph_point * ret = start;
+ struct route_graph_segment * s = start->seg, * s_cmp, * s_prev = NULL;
+ int num_seg;
+ int id_match;
+ int is_ambiguous;
+
+ dbg(lvl_debug, "At %p (%d), start", start, start ? start->value : -1);
+ if (!start)
+ return NULL;
+
+ while (s) {
+ num_seg = 0;
+ id_match = 0;
+ is_ambiguous = 0;
+ for (s_cmp = ret->start; s_cmp; s_cmp = s_cmp->start_next) {
+ num_seg++;
+ if (s_cmp == s)
+ continue;
+ if (s_cmp->data.flags & AF_ONEWAY)
+ continue;
+ if (s_cmp->end->seg != s_cmp)
+ continue;
+ if (item_is_equal_id(s_cmp->data.item, s->data.item)) {
+ s_prev = s_cmp;
+ id_match = 1;
+ } else if ((s_cmp->data.item.type == s->data.item.type) && !id_match && !is_ambiguous) {
+ if (s_prev) {
+ s_prev = NULL;
+ is_ambiguous = 1;
+ } else
+ s_prev = s_cmp;
+ }
+ }
+ for (s_cmp = ret->end; s_cmp; s_cmp = s_cmp->end_next) {
+ num_seg++;
+ if (s_cmp == s)
+ continue;
+ if (s_cmp->data.flags & AF_ONEWAYREV)
+ continue;
+ if (s_cmp->start->seg != s_cmp)
+ continue;
+ if (item_is_equal_id(s_cmp->data.item, s->data.item)) {
+ s_prev = s_cmp;
+ id_match = 1;
+ } else if ((s_cmp->data.item.type == s->data.item.type) && !id_match && !is_ambiguous) {
+ if (s_prev) {
+ s_prev = NULL;
+ is_ambiguous = 1;
+ } else
+ s_prev = s_cmp;
+ }
+ }
+
+ /* cancel if we are past start and ret is a junction */
+ if ((ret != start) && (num_seg > 2))
+ break;
+
+ /* move s and ret one step further and update links */
+ s = s_prev;
+ if (s) {
+ if (ret == s->start) {
+ ret = s->end;
+ dbg(lvl_debug, "At %p (%d -> %d)", ret, ret->value, s->start->value + s->data.len);
+ ret->value = s->start->value + s->data.len;
+ } else {
+ ret = s->start;
+ dbg(lvl_debug, "At %p (%d -> %d)", ret, ret->value, s->end->value + s->data.len);
+ ret->value = s->end->value + s->data.len;
+ }
+ ret->seg = s;
+ s_prev = NULL;
+ }
+ }
+ dbg(lvl_debug, "return, start=%p, ret=%p", start, ret);
+ return ret;
+}
+
+/**
+ * @brief Returns one of the traffic location’s points.
+ *
+ * @param this_ The traffic location
+ * @param point The point of the traffic location to retrieve (0 = from, 1 = at, 2 = to, 16 = start, 17 = end)
+ *
+ * @return The matched points, or NULL if the requested point does not exist
+ */
+static struct traffic_point * traffic_location_get_point(struct traffic_location * this_, int point) {
+ /* The point from the location to match */
+ struct traffic_point * trpoint = NULL;
+
+ switch(point) {
+ case 0:
+ trpoint = this_->from;
+ break;
+ case 1:
+ trpoint = this_->at;
+ break;
+ case 2:
+ trpoint = this_->to;
+ break;
+ case 16:
+ trpoint = this_->from ? this_->from : this_->at;
+ break;
+ case 17:
+ trpoint = this_->to ? this_->to : this_->at;
+ break;
+ default:
+ break;
+ }
+
+ return trpoint;
+}
+
+/**
+ * @brief Compares a given point to the traffic location and returns a score.
+ *
+ * This method obtains all points at coordinates `c` from the map_rect used to build the route graph, compares their
+ * attributes to those supplied with the location, assigns a match score from 0 (no matching attributes) to 100 (all
+ * supplied attributes match) and returns the highest score obtained. If no matching point is found, 0 is returned.
+ *
+ * @param this_ The traffic location
+ * @param p The route graph point to examine for matches
+ * @param point The point of the traffic location to use for matching (0 = from, 1 = at, 2 = to, 16 = start, 17 = end)
+ * @param rg The route graph
+ * @param start The first point of the path
+ * @param match_start True to evaluate for the start point of a route, false for the end point
+ * @param ms The mapset to read the items from
+ *
+ * @return A score from 0 (worst) to 100 (best).
+ */
+static int traffic_location_get_point_match(struct traffic_location * this_, struct route_graph_point * p, int point,
+ struct route_graph * rg, struct route_graph_point * start, int match_start, struct mapset * ms) {
+ int ret = 0;
+
+ /* The point from the location to match */
+ struct traffic_point * trpoint = NULL;
+
+ /* The attribute matching score for the current item */
+ int score;
+
+ trpoint = traffic_location_get_point(this_, point);
+
+ if (!trpoint)
+ return 0;
+
+ /* First examine route graph points and connected segments */
+ score = traffic_point_match_segment_attributes(trpoint, p, start, match_start);
+ if (ret < score)
+ ret = score;
+ return ret;
+}
+
+/**
+ * @brief Returns points from the route graph which match a traffic location.
+ *
+ * This method obtains point items from the map_rect from which the route graph was built and compares
+ * their attributes to those supplied with the location. Each point is assigned a match score, from 0
+ * (no matching attributes) to 100 (all supplied attributes match), and a list of all points with a
+ * nonzero score is returned.
+ *
+ * Points which have no corresponding map item (i.e. points which have no additional attributes) are not included in
+ * the result and must be analyzed separately if needed.
+ *
+ * @param this_ The traffic location
+ * @param point The point of the traffic location to use for matching (0 = from, 1 = at, 2 = to, 16 = start, 17 = end)
+ * @param rg The route graph
+ * @param start The first point of the path
+ * @param match_start True to evaluate for the start point of a route, false for the end point
+ * @param ms The mapset to read the items from
+ *
+ * @return The matched points as a `GList`. The `data` member of each item points to a `struct point_data` for the point.
+ */
+static GList * traffic_location_get_matching_points(struct traffic_location * this_, int point,
+ struct route_graph * rg, struct route_graph_point * start, int match_start, struct mapset * ms) {
+ GList * ret = NULL;
+
+ /* The point from the location to match */
+ struct traffic_point * trpoint = NULL;
+
+ /* The item being processed */
+ struct item *item;
+
+ /* Mercator coordinates of current and previous point */
+ struct coord c;
+
+ /* The corresponding point in the route graph */
+ struct route_graph_point * p;
+
+ /* The attribute matching score for the current item */
+ int score;
+
+ /* Data for the current point */
+ struct point_data * data;
+
+ trpoint = traffic_location_get_point(this_, point);
+
+ if (!trpoint)
+ return NULL;
+
+ traffic_location_set_enclosing_rect(this_, NULL);
+
+ rg->h = mapset_open(ms);
+
+ while ((rg->m = mapset_next(rg->h, 2))) {
+ if (!traffic_location_open_map_rect(this_, rg))
+ continue;
+ while ((item = map_rect_get_item(rg->mr))) {
+ /* exclude non-point items */
+ if ((item->type < type_town_label) || (item->type >= type_line))
+ continue;
+
+ /* exclude items from which we can't obtain a coordinate pair */
+ if (!item_coord_get(item, &c, 1))
+ continue;
+
+ /* exclude items not in the route graph (points with turn restrictions are ignored) */
+ p = route_graph_get_point(rg, &c);
+ while (p && (p->flags & RP_TURN_RESTRICTION))
+ p = route_graph_get_point_next(rg, &c, p);
+ if (!p)
+ continue;
+
+ /* determine score */
+ score = traffic_point_match_attributes(trpoint, item);
+
+ /* exclude items with a zero score */
+ if (!score)
+ continue;
+
+ dbg(lvl_debug, "adding item, score: %d", score);
+
+ do {
+ if (!(p->flags & RP_TURN_RESTRICTION)) {
+ data = g_new0(struct point_data, 1);
+ data->score = score;
+ data->p = p;
+
+ ret = g_list_append(ret, data);
+ }
+ } while ((p = route_graph_get_point_next(rg, &c, p)));
+ }
+ map_selection_destroy(rg->sel);
+ rg->sel = NULL;
+ map_rect_destroy(rg->mr);
+ rg->mr = NULL;
+ }
+ route_graph_build_done(rg, 1);
+
+ return ret;
+}
+
+/**
+ * @brief Whether the contents of a location are valid.
+ *
+ * This identifies any malformed locations in which mandatory members are not set.
+ *
+ * @return true if the locations is valid, false if it is malformed
+ */
+static int traffic_location_is_valid(struct traffic_location * this_) {
+ if (!this_->at && !(this_->from && this_->to))
+ return 0;
+ return 1;
+}
+
+/**
+ * @brief Whether the current point is a candidate for low-res endpoint matching.
+ *
+ * @param this_ The point to examine
+ * @param s_prev The route segment leading to `this_` (NULL for the start point)
+ */
+static int route_graph_point_is_endpoint_candidate(struct route_graph_point *this_,
+ struct route_graph_segment *s_prev) {
+ int ret;
+
+ /* Whether we are at a junction of 3 or more segments */
+ int is_junction;
+
+ /* Segment used for comparison */
+ struct route_graph_segment *s_cmp;
+
+ /* Current segment */
+ struct route_graph_segment *s = this_->seg;
+
+ if (!s_prev || !s)
+ /* the first and last points are always candidates */
+ ret = 1;
+ else
+ /* detect tunnel portals */
+ ret = ((s->data.flags & AF_UNDERGROUND) != (s_prev->data.flags & AF_UNDERGROUND));
+ if (!ret) {
+ /* detect junctions */
+ is_junction = (s && s_prev) ? 0 : -1;
+ for (s_cmp = this_->start; s_cmp; s_cmp = s_cmp->start_next) {
+ if ((s_cmp != s) && (s_cmp != s_prev))
+ is_junction += 1;
+ }
+ for (s_cmp = this_->end; s_cmp; s_cmp = s_cmp->end_next) {
+ if ((s_cmp != s) && (s_cmp != s_prev))
+ is_junction += 1;
+ }
+ ret = (is_junction > 0);
+ }
+ return ret;
+}
+
+/**
+ * @brief Generates segments affected by a traffic message.
+ *
+ * This translates the approximate coordinates in the `from`, `at`, `to`, `via` and `not_via` members of
+ * the location to one or more map segments, using both the raw coordinates and the auxiliary information
+ * contained in the location. Each segment is stored in the map, if not already present, and a link is
+ * stored with the message.
+ *
+ * @param this_ The traffic message
+ * @param ms The mapset to use for matching
+ * @param data Data for the segments added to the map
+ * @param map The traffic map
+ * @param route The route affected by the changes
+ *
+ * @return `true` if the locations were matched successfully, `false` if there was a failure.
+ */
+static int traffic_message_add_segments(struct traffic_message * this_, struct mapset * ms, struct seg_data * data,
+ struct map *map, struct route * route) {
+ int i;
+
+ struct coord_geo * coords[] = {NULL, NULL, NULL};
+ struct coord * pcoords[] = {NULL, NULL, NULL};
+
+ /* How many point pairs coords contains (number of members minus one) */
+ int point_pairs = -1;
+
+ /* Which members of coords are the end points */
+ int endpoints = 0;
+
+ /* The direction (positive or negative) */
+ int dir = 1;
+
+ /* Start point for the route path */
+ struct route_graph_point * p_start = NULL;
+
+ /* Current and previous segment */
+ struct route_graph_segment *s = NULL;
+ struct route_graph_segment *s_prev;
+
+ /* Iterator for the route path */
+ struct route_graph_point *p_iter;
+
+ /* route graph for simplified routing */
+ struct route_graph *rg;
+
+ /* Coordinate count for matched segment */
+ int ccnt;
+
+ /* Coordinates of matched segment and pointer into it, order as read from map */
+ struct coord *c, ca[2048];
+
+ /* Coordinates of matched segment, sorted */
+ struct coord *cd, *cs;
+
+ /* Speed calculated in various ways */
+ int maxspeed, speed, penalized_speed, factor_speed;
+
+ /* Delay for the current segment */
+ int delay;
+
+ /* Number of new segments and existing segments */
+ int count = 0, prev_count;
+
+ /* Length of location */
+ int len;
+
+ /* The message's previous list of items */
+ struct item ** prev_items;
+
+ /* The next item in the message's list of items */
+ struct item ** next_item;
+
+ /* Flags for the next item to add */
+ int flags;
+
+ /* The last item added */
+ struct item * item;
+
+ /* Projected coordinates of start and end points of the actual location
+ * (if at is set, both point to the same coordinates) */
+ struct coord * c_from, * c_to;
+
+ /* Matched points */
+ GList * points;
+ GList * points_iter;
+
+ /* The corresponding point data */
+ struct point_data * pd;
+
+ /* The match score of the current point */
+ int score;
+
+ /* Current and minimum cost to reference point */
+ int val, minval;
+
+ /* Start of extended route */
+ struct route_graph_point * start_new;
+
+ /* Last segment of the route (before extension) */
+ struct route_graph_segment * s_last = NULL;
+
+ /* Aligned points */
+ struct route_graph_point * p_from;
+ struct route_graph_point * p_to;
+
+ dbg(lvl_debug, "*****checkpoint ADD-1");
+ if (!data) {
+ dbg(lvl_error, "no data for segments, aborting");
+ return 0;
+ }
+
+ if (this_->location->ramps != location_ramps_none)
+ /* TODO Ramps, not supported yet */
+ return 0;
+
+ /* Main carriageway */
+
+ dbg(lvl_debug, "*****checkpoint ADD-2");
+ /* get point triple and enclosing rectangle */
+ endpoints = traffic_location_get_point_triple(this_->location, &coords[0]);
+ if (!endpoints) {
+ dbg(lvl_error, "invalid location (mandatory points missing)");
+ return 0;
+ }
+ traffic_location_set_enclosing_rect(this_->location, &coords[0]);
+ for (i = 0; i < 3; i++)
+ if (coords[i]) {
+ pcoords[i] = g_new0(struct coord, 1);
+ transform_from_geo(projection_mg, coords[i], pcoords[i]);
+ point_pairs++;
+ }
+
+ if (this_->location->at && !(this_->location->from || this_->location->to))
+ /* TODO Point location with no auxiliary points, not supported yet */
+ return 0;
+
+ dbg(lvl_debug, "*****checkpoint ADD-3");
+ rg = traffic_location_get_route_graph(this_->location, ms);
+
+ /* transform coordinates */
+ c_from = (endpoints & 4) ? pcoords[0] : pcoords[1];
+ c_to = (endpoints & 1) ? pcoords[2] : pcoords[1];
+
+ /* determine segments */
+ dbg(lvl_debug, "*****checkpoint ADD-4 (loop start)");
+ while (1) { /* once for each direction (loop logic at the end) */
+ dbg(lvl_debug, "*****checkpoint ADD-4.1");
+ if (point_pairs == 1) {
+ if (dir > 0)
+ p_start = traffic_route_flood_graph(rg,
+ pcoords[0] ? pcoords[0] : pcoords[1],
+ pcoords[2] ? pcoords[2] : pcoords[1], NULL);
+ else
+ p_start = traffic_route_flood_graph(rg,
+ pcoords[2] ? pcoords[2] : pcoords[1],
+ pcoords[0] ? pcoords[0] : pcoords[1], NULL);
+ dbg(lvl_debug, "*****checkpoint ADD-4.1.1");
+ } else if (point_pairs == 2) {
+ /*
+ * If we have more than two points, create the route in two stages (from the first to the second point,
+ * then from the second to the third point) and concatenate them. This could easily be extended to any
+ * number of points, provided they are spaced sufficiently far apart to calculate a route between each pair
+ * of subsequent points.
+ * This will create a kind of “Frankenstein route” in which the cost of points does not decrease
+ * continuously but has an upward leap as we pass the middle point. This is not an issue as long as we do
+ * not do any further processing based on point cost (which we currently don’t).
+ * If the route needs to be extended beyond the start point, this has to be done after the first stage,
+ * as doing so relies on the route graph for that stage.
+ */
+ /* TODO handle cases in which the route goes through the "third" point
+ * (this should not happen; if it does, we need to detect and fix it) */
+ if (dir > 0)
+ p_start = traffic_route_flood_graph(rg, pcoords[0], pcoords[1], NULL);
+ else
+ p_start = traffic_route_flood_graph(rg, pcoords[2], pcoords[1], NULL);
+ if ((this_->location->fuzziness == location_fuzziness_low_res)
+ || this_->location->at || this_->location->not_via) {
+ /* extend start to next junction */
+ start_new = traffic_route_prepend(rg, p_start);
+ if (start_new)
+ p_start = start_new;
+ }
+ if (dir > 0)
+ traffic_route_flood_graph(rg, pcoords[1], pcoords[2], p_start);
+ else
+ traffic_route_flood_graph(rg, pcoords[1], pcoords[0], p_start);
+ dbg(lvl_debug, "*****checkpoint ADD-4.1.2");
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2");
+ /* tweak ends (find the point where the ramp touches the main road) */
+ if ((this_->location->fuzziness == location_fuzziness_low_res)
+ || this_->location->at || this_->location->not_via) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.1");
+ /* tweak end point */
+ if (this_->location->at)
+ points = traffic_location_get_matching_points(this_->location, 1, rg, p_start, 0, ms);
+ else if (dir > 0)
+ points = traffic_location_get_matching_points(this_->location, 2, rg, p_start, 0, ms);
+ else
+ points = traffic_location_get_matching_points(this_->location, 0, rg, p_start, 0, ms);
+ if (!p_start) {
+ dbg(lvl_error, "end point not found on map");
+ for (points_iter = points; points_iter; points_iter = g_list_next(points_iter))
+ g_free(points_iter->data);
+ g_list_free(points);
+ route_graph_free_points(rg);
+ route_graph_free_segments(rg);
+ g_free(rg);
+ for (i = 0; i < 3; i++)
+ g_free(pcoords[i]);
+ return 0;
+ }
+ s = p_start ? p_start->seg : NULL;
+ p_iter = p_start;
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.2");
+ /* extend end to next junction */
+ for (s = p_start ? p_start->seg : NULL; s; s = p_iter->seg) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.2.1, s=%p, p_iter=%p (%d)", s, p_iter, p_iter ? p_iter->value : INT_MAX);
+ s_last = s;
+ if (s->start == p_iter)
+ p_iter = s->end;
+ else
+ p_iter = s->start;
+ }
+ s = traffic_route_append(rg, s_last, p_iter);
+ p_iter->seg = s;
+
+ s = p_start ? p_start->seg : NULL;
+ s_prev = NULL;
+ p_iter = p_start;
+ minval = INT_MAX;
+ p_to = NULL;
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.3");
+ while (p_iter) {
+ if (route_graph_point_is_endpoint_candidate(p_iter, s_prev)) {
+ score = traffic_location_get_point_match(this_->location, p_iter,
+ this_->location->at ? 1 : (dir > 0) ? 2 : 0,
+ rg, p_start, 0, ms);
+ pd = NULL;
+ for (points_iter = points; points_iter && (score < 100); points_iter = g_list_next(points_iter)) {
+ pd = (struct point_data *) points_iter->data;
+ if ((pd->p == p_iter) && (pd->score > score))
+ score = pd->score;
+ }
+ val = transform_distance(projection_mg, &p_iter->c, (dir > 0) ? c_to : c_from);
+ val += (val * (100 - score) * (PENALTY_POINT_MATCH) / 100);
+ if (val < minval) {
+ minval = val;
+ p_to = p_iter;
+ dbg(lvl_debug, "candidate end point found, point %p, value %d (score %d)", p_iter, val, score);
+ }
+ }
+
+ if (!s)
+ p_iter = NULL;
+ else {
+ p_iter = (s->start == p_iter) ? s->end : s->start;
+ s_prev = s;
+ s = p_iter->seg;
+ }
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.4");
+ for (points_iter = points; points_iter; points_iter = g_list_next(points_iter))
+ g_free(points_iter->data);
+ g_list_free(points);
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.5");
+ /* tweak start point */
+ if (this_->location->at)
+ points = traffic_location_get_matching_points(this_->location, 1, rg, p_start, 1, ms);
+ else if (dir > 0)
+ points = traffic_location_get_matching_points(this_->location, 0, rg, p_start, 1, ms);
+ else
+ points = traffic_location_get_matching_points(this_->location, 2, rg, p_start, 1, ms);
+ s_prev = NULL;
+ minval = INT_MAX;
+ p_from = NULL;
+
+ struct coord_geo wgs;
+ transform_to_geo(projection_mg, &(p_start->c), &wgs);
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.6, p_start=%p\nhttps://www.openstreetmap.org?mlat=%f&mlon=%f/#map=13",
+ p_start, wgs.lat, wgs.lng);
+ if (point_pairs == 1) {
+ /* extend start to next junction (if we have more than two points, this has already been done) */
+ start_new = traffic_route_prepend(rg, p_start);
+ if (start_new)
+ p_start = start_new;
+ }
+
+ s = p_start ? p_start->seg : NULL;
+ p_iter = p_start;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.7");
+ while (p_iter) {
+ transform_to_geo(projection_mg, &(p_iter->c), &wgs);
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.7, p_iter=%p (value=%d)\nhttps://www.openstreetmap.org?mlat=%f&mlon=%f/#map=13",
+ p_iter, p_iter->value, wgs.lat, wgs.lng);
+ if (route_graph_point_is_endpoint_candidate(p_iter, s_prev)) {
+ score = traffic_location_get_point_match(this_->location, p_iter,
+ this_->location->at ? 1 : (dir > 0) ? 0 : 2,
+ rg, p_start, 1, ms);
+ pd = NULL;
+ for (points_iter = points; points_iter && (score < 100); points_iter = g_list_next(points_iter)) {
+ pd = (struct point_data *) points_iter->data;
+ if ((pd->p == p_iter) && (pd->score > score))
+ score = pd->score;
+ }
+ val = transform_distance(projection_mg, &p_iter->c, (dir > 0) ? c_from : c_to);
+ /* TODO does attribute matching make sense for the start segment? */
+ val += (val * (100 - score) * (PENALTY_POINT_MATCH) / 100);
+ if (val < minval) {
+ minval = val;
+ p_from = p_iter;
+ dbg(lvl_debug, "candidate start point found, point %p, value %d (score %d)",
+ p_iter, val, score);
+ }
+ }
+
+ if (!s)
+ p_iter = NULL;
+ else {
+ p_iter = (s->start == p_iter) ? s->end : s->start;
+ s_prev = s;
+ s = p_iter->seg;
+ }
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.8");
+ for (points_iter = points; points_iter; points_iter = g_list_next(points_iter))
+ g_free(points_iter->data);
+ g_list_free(points);
+
+ if (!p_from)
+ p_from = p_start;
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9");
+ /* ensure we have at least one segment */
+ if ((p_from == p_to) || !p_from->seg) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.1");
+ p_iter = p_start;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2");
+ while (p_iter->seg) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.1, p_iter=%p, p_iter->seg=%p", p_iter, p_iter ? p_iter->seg : NULL);
+ if (p_iter == p_iter->seg->start) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.2 (p_iter == p_iter->seg->start)");
+ /* compare to the last point: because p_to may be NULL here, we're comparing to
+ * p_from instead, which at this point is guaranteed to be non-NULL and either
+ * equal to p_to or without a successor, making it the designated end point. */
+ if (p_iter->seg->end == p_from)
+ break;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.3");
+ p_iter = p_iter->seg->end;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.4");
+ } else {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.2 (p_iter != p_iter->seg->start)");
+ if (p_iter->seg->start == p_from)
+ break;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.3");
+ p_iter = p_iter->seg->start;
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.2.4");
+ }
+ }
+ if (p_from->seg) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.3, p_from->seg is non-NULL");
+ /* decide between predecessor and successor of the point, based on proximity */
+ p_to = (p_from == p_from->seg->end) ? p_from->seg->start : p_from->seg->end;
+ if (transform_distance(projection_mg, &p_to->c, pcoords[1] ? pcoords[1] : pcoords[2])
+ > transform_distance(projection_mg, &p_iter->c, pcoords[1] ? pcoords[1] : pcoords[2])) {
+ p_to = p_from;
+ p_from = p_iter;
+ }
+ } else {
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.9.3, p_from->seg is NULL");
+ /* p_from has no successor, the segment goes from its predecessor to p_from */
+ p_to = p_from;
+ p_from = p_iter;
+ }
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.2.10");
+ /* if we have identified a last point, drop everything after it from the path */
+ if (p_to)
+ p_to->seg = NULL;
+
+ /* set first point to be the start point */
+ if (p_from != p_start) {
+ dbg(lvl_debug, "changing p_start from %p to %p", p_start, p_from);
+ }
+ p_start = p_from;
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.3");
+ /* calculate route */
+ s = p_start ? p_start->seg : NULL;
+ p_iter = p_start;
+
+ if (!s)
+ dbg(lvl_error, "no segments");
+
+ /* count segments and calculate length */
+ prev_count = count;
+ count = 0;
+ len = 0;
+ dbg(lvl_debug, "*****checkpoint ADD-4.4");
+ while (s) {
+ dbg(lvl_debug, "*****checkpoint ADD-4.4.1 (#%d, p_iter=%p, s=%p, next %p)",
+ count, p_iter, s, (s->start == p_iter) ? s->end : s->start);
+ count++;
+ len += s->data.len;
+ if (s->start == p_iter)
+ p_iter = s->end;
+ else
+ p_iter = s->start;
+ s = p_iter->seg;
+ }
+ dbg(lvl_debug, "*****checkpoint ADD-4.5");
+
+ /* add segments */
+
+ s = p_start ? p_start->seg : NULL;
+ p_iter = p_start;
+
+ if (this_->priv->items) {
+ prev_items = this_->priv->items;
+ this_->priv->items = g_new0(struct item *, count + prev_count + 1);
+ memcpy(this_->priv->items, prev_items, sizeof(struct item *) * prev_count);
+ next_item = this_->priv->items + prev_count;
+ g_free(prev_items);
+ } else {
+ this_->priv->items = g_new0(struct item *, count + 1);
+ next_item = this_->priv->items;
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.6 (loop start)");
+ while (s) {
+ ccnt = item_coord_get_within_range(&s->data.item, ca, 2047, &s->start->c, &s->end->c);
+ c = ca;
+ cs = g_new0(struct coord, ccnt);
+ cd = cs;
+
+ speed = data->speed;
+ if ((data->speed != INT_MAX) || data->speed_penalty || (data->speed_factor != 100)) {
+ if (s->data.flags & AF_SPEED_LIMIT) {
+ maxspeed = RSD_MAXSPEED(&s->data);
+ } else {
+ switch (s->data.item.type) {
+ case type_highway_land:
+ case type_street_n_lanes:
+ maxspeed = 100;
+ break;
+ case type_highway_city:
+ case type_street_4_land:
+ maxspeed = 80;
+ break;
+ case type_street_3_land:
+ maxspeed = 70;
+ break;
+ case type_street_2_land:
+ maxspeed = 65;
+ break;
+ case type_street_1_land:
+ maxspeed = 60;
+ break;
+ case type_street_4_city:
+ maxspeed = 50;
+ break;
+ case type_ramp:
+ case type_street_3_city:
+ case type_street_unkn:
+ maxspeed = 40;
+ break;
+ case type_street_2_city:
+ case type_track_paved:
+ maxspeed = 30;
+ break;
+ case type_track:
+ case type_cycleway:
+ maxspeed = 20;
+ break;
+ case type_roundabout:
+ case type_street_1_city:
+ case type_street_0:
+ case type_living_street:
+ case type_street_service:
+ case type_street_parking_lane:
+ case type_path:
+ case type_track_ground:
+ case type_track_gravelled:
+ case type_track_unpaved:
+ case type_track_grass:
+ case type_bridleway:
+ maxspeed = 10;
+ break;
+ case type_street_pedestrian:
+ case type_footway:
+ case type_steps:
+ maxspeed = 5;
+ break;
+ default:
+ maxspeed = 50;
+ }
+ }
+ penalized_speed = maxspeed - data->speed_penalty;
+ if (penalized_speed < 5)
+ penalized_speed = 5;
+ factor_speed = maxspeed * data->speed_factor / 100;
+ if (speed > penalized_speed)
+ speed = penalized_speed;
+ if (speed > factor_speed)
+ speed = factor_speed;
+ }
+
+ if (data->delay)
+ delay = data->delay * s->data.len / len;
+ else
+ delay = data->delay;
+
+ for (i = 0; i < ccnt; i++) {
+ *cd++ = *c++;
+ }
+
+ if (s->start == p_iter) {
+ /* forward direction */
+ p_iter = s->end;
+ flags = data->flags | (s->data.flags & AF_ONEWAYMASK)
+ | (data->dir == location_dir_one ? AF_ONEWAY : 0);
+ } else {
+ /* backward direction */
+ p_iter = s->start;
+ flags = data->flags | (s->data.flags & AF_ONEWAYMASK)
+ | (data->dir == location_dir_one ? AF_ONEWAYREV : 0);
+ }
+
+
+ item = tm_add_item(map, type_traffic_distortion, s->data.item.id_hi, s->data.item.id_lo, flags, data->attrs, cs, ccnt,
+ this_->id);
+
+ tm_item_add_message_data(item, this_->id, speed, delay, data->attrs, route);
+
+ g_free(cs);
+
+ *next_item = tm_item_ref(item);
+ next_item++;
+
+ s = p_iter->seg;
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-4.7");
+ if ((this_->location->directionality == location_dir_one) || (dir < 0))
+ break;
+
+ dir = -1;
+ }
+
+ dbg(lvl_debug, "*****checkpoint ADD-5");
+ route_graph_free_points(rg);
+ route_graph_free_segments(rg);
+ g_free(rg);
+
+ for (i = 0; i < 3; i++)
+ g_free(pcoords[i]);
+
+ dbg(lvl_debug, "*****checkpoint ADD-6");
+ return 1;
+}
+
+/**
+ * @brief Prints a dump of a message to debug output.
+ *
+ * @param this_ The message to dump
+ */
+static void traffic_message_dump_to_stderr(struct traffic_message * this_) {
+ int i, j;
+ char * point_names[5] = {"From", "At", "Via", "Not via", "To"};
+ struct traffic_point * points[5];
+ char * timestamp = NULL;
+
+ if (!this_) {
+ dbg(lvl_debug, "(null)");
+ return;
+ }
+
+ if (this_->location) {
+ points[0] = this_->location->from;
+ points[1] = this_->location->at;
+ points[2] = this_->location->via;
+ points[3] = this_->location->not_via;
+ points[4] = this_->location->to;
+ } else
+ memset(&points, 0, sizeof(struct traffic_point *) * 5);
+
+ dbg(lvl_debug, "id='%s', is_cancellation=%d, is_forecast=%d",
+ this_->id, this_->is_cancellation, this_->is_forecast);
+ if (this_->receive_time) {
+ timestamp = time_to_iso8601(this_->receive_time);
+ dbg(lvl_debug, " First received: %s (%ld)", timestamp, this_->receive_time);
+ g_free(timestamp);
+ }
+ if (this_->update_time) {
+ timestamp = time_to_iso8601(this_->update_time);
+ dbg(lvl_debug, " Last updated: %s (%ld)", timestamp, this_->update_time);
+ g_free(timestamp);
+ }
+ if (this_->start_time) {
+ timestamp = time_to_iso8601(this_->start_time);
+ dbg(lvl_debug, " Start time: %s (%ld)", timestamp, this_->start_time);
+ g_free(timestamp);
+ }
+ if (this_->end_time) {
+ timestamp = time_to_iso8601(this_->end_time);
+ dbg(lvl_debug, " End time: %s (%ld)", timestamp, this_->end_time);
+ g_free(timestamp);
+ }
+ if (this_->expiration_time) {
+ timestamp = time_to_iso8601(this_->expiration_time);
+ dbg(lvl_debug, " Expires: %s (%ld)", timestamp, this_->expiration_time);
+ g_free(timestamp);
+ }
+
+ /* dump replaced message IDs */
+ dbg(lvl_debug, " replaced_count=%d",
+ this_->replaced_count);
+ for (i = 0; i < this_->replaced_count; i++) {
+ dbg(lvl_debug, " Replaces: '%s'", this_->replaces[i]);
+ }
+
+ /* dump location */
+ if (this_->location) {
+ dbg(lvl_debug, " Location: road_type='%s', road_ref='%s', road_name='%s'",
+ item_to_name(this_->location->road_type), this_->location->road_ref,
+ this_->location->road_name);
+ dbg(lvl_debug, " directionality=%d, destination='%s', direction='%s'",
+ this_->location->directionality, this_->location->destination, this_->location->direction);
+ dbg(lvl_debug, " fuzziness=%s, ramps=%s, tmc_table='%s', tmc_direction=%+d",
+ location_fuzziness_to_string(this_->location->fuzziness),
+ location_ramps_to_string(this_->location->ramps), this_->location->tmc_table,
+ this_->location->tmc_direction);
+ for (i = 0; i < 5; i++) {
+ if (points[i]) {
+ dbg(lvl_debug, " %s: lat=%.5f, lng=%.5f",
+ point_names[i], points[i]->coord.lat, points[i]->coord.lng);
+ dbg(lvl_debug, " junction_name='%s', junction_ref='%s', tmc_id='%s'",
+ points[i]->junction_name, points[i]->junction_ref, points[i]->tmc_id);
+ } else {
+ dbg(lvl_debug, " %s: (null)",
+ point_names[i]);
+ }
+ }
+ } else {
+ dbg(lvl_debug, " Location: null");
+ }
+
+ /* dump events */
+ dbg(lvl_debug, " event_count=%d",
+ this_->event_count);
+ for (i = 0; i < this_->event_count; i++) {
+ dbg(lvl_debug, " Event: event_class=%s, type=%s, length=%d m, speed=%d km/h",
+ event_class_to_string(this_->events[i]->event_class),
+ event_type_to_string(this_->events[i]->type),
+ this_->events[i]->length, this_->events[i]->speed);
+ /* TODO quantifier */
+
+ /* dump supplementary information */
+ dbg(lvl_debug, " si_count=%d",
+ this_->events[i]->si_count);
+ for (j = 0; j < this_->events[i]->si_count; j++) {
+ dbg(lvl_debug, " Supplementary Information: si_class=%s, type=%s",
+ si_class_to_string(this_->events[i]->si[j]->si_class),
+ si_type_to_string(this_->events[i]->si[j]->type));
+ /* TODO quantifier */
+ }
+ }
+}
+
+/**
+ * @brief Whether the contents of a message are valid.
+ *
+ * This identifies any malformed messages in which mandatory members are not set.
+ *
+ * @return true if the message is valid, false if it is malformed
+ */
+static int traffic_message_is_valid(struct traffic_message * this_) {
+ int i;
+ int has_valid_events = 0;
+
+ if (!this_->id || !this_->id[0]) {
+ dbg(lvl_debug, "ID is NULL or empty");
+ return 0;
+ }
+ if (!this_->receive_time || !this_->update_time) {
+ dbg(lvl_debug, "receive_time or update_time not supplied");
+ 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");
+ return 0;
+ }
+ if (!this_->location) {
+ dbg(lvl_debug, "not a cancellation, but no location supplied");
+ return 0;
+ }
+ if (!traffic_location_is_valid(this_->location)) {
+ dbg(lvl_debug, "not a cancellation, but location is invalid");
+ return 0;
+ }
+ if (!this_->event_count || !this_->events) {
+ dbg(lvl_debug, "not a cancellation, but no events supplied");
+ 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);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ * @brief Parses the events of a traffic message.
+ *
+ * @param message The message to parse
+ *
+ * @return A `struct seg_data`, or `NULL` if the message contains no usable information
+ */
+static struct seg_data * traffic_message_parse_events(struct traffic_message * this_) {
+ struct seg_data * ret = NULL;
+
+ int i, j;
+ int has_flags = 0;
+ int flags = 0;
+
+ /* Default assumptions, used only if no explicit values are given */
+ int speed = INT_MAX;
+ int speed_penalty = 0;
+ int speed_factor = 100;
+ int delay = 0;
+
+ for (i = 0; i < this_->event_count; i++) {
+ if (this_->events[i]->speed != INT_MAX) {
+ if (!ret)
+ ret = seg_data_new();
+ if (ret->speed > this_->events[i]->speed)
+ ret->speed = this_->events[i]->speed;
+ }
+ if (this_->events[i]->event_class == event_class_congestion) {
+ switch (this_->events[i]->type) {
+ case event_congestion_heavy_traffic:
+ case event_congestion_traffic_building_up:
+ case event_congestion_traffic_heavier_than_normal:
+ case event_congestion_traffic_much_heavier_than_normal:
+ /* Heavy traffic: assume 10 km/h below the posted limit, unless explicitly specified */
+ if ((this_->events[i]->speed == INT_MAX) && (speed_penalty < 10))
+ speed_penalty = 10;
+ break;
+ case event_congestion_slow_traffic:
+ case event_congestion_traffic_congestion:
+ case event_congestion_traffic_problem:
+ /* Slow traffic or unspecified congestion: assume half the posted limit, unless explicitly specified */
+ if ((this_->events[i]->speed == INT_MAX) && (speed_factor > 50))
+ speed_factor = 50;
+ break;
+ case event_congestion_queue:
+ /* Queuing traffic: assume 20 km/h, unless explicitly specified */
+ if ((this_->events[i]->speed == INT_MAX) && (speed > 20))
+ speed = 20;
+ break;
+ case event_congestion_stationary_traffic:
+ case event_congestion_long_queue:
+ /* Stationary traffic or long queues: assume 5 km/h, unless explicitly specified */
+ if ((this_->events[i]->speed == INT_MAX) && (speed > 5))
+ speed = 5;
+ break;
+ default:
+ break;
+ }
+ } else if (this_->events[i]->event_class == event_class_delay) {
+ switch (this_->events[i]->type) {
+ case event_delay_delay:
+ case event_delay_long_delay:
+ /* Delay or long delay: assume 30 minutes, unless explicitly specified */
+ if (this_->events[i]->quantifier) {
+ if (!ret)
+ ret = seg_data_new();
+ if (ret->delay < this_->events[i]->quantifier->u.q_duration)
+ ret->delay = this_->events[i]->quantifier->u.q_duration;
+ } else if (delay < 18000)
+ delay = 18000;
+ break;
+ case event_delay_very_long_delay:
+ /* Very long delay: assume 1 hour, unless explicitly specified */
+ if (this_->events[i]->quantifier) {
+ if (!ret)
+ ret = seg_data_new();
+ if (ret->delay < this_->events[i]->quantifier->u.q_duration)
+ ret->delay = this_->events[i]->quantifier->u.q_duration;
+ } else if (delay < 36000)
+ delay = 36000;
+ break;
+ case event_delay_several_hours:
+ case event_delay_uncertain_duration:
+ /* Delay of several hours or uncertain duration: assume 3 hours */
+ if (delay < 108000)
+ delay = 108000;
+ break;
+ default:
+ break;
+ }
+ } else if (this_->events[i]->event_class == event_class_restriction) {
+ switch (this_->events[i]->type) {
+ case event_restriction_blocked:
+ case event_restriction_blocked_ahead:
+ case event_restriction_carriageway_blocked:
+ case event_restriction_carriageway_closed:
+ case event_restriction_closed:
+ case event_restriction_closed_ahead:
+ if (!ret)
+ ret = seg_data_new();
+ ret->speed = 0;
+ break;
+ case event_restriction_intermittent_closures:
+ case event_restriction_batch_service:
+ case event_restriction_single_alternate_line_traffic:
+ /* Assume 30% of the posted limit for all of these cases */
+ if (speed_factor > 30)
+ speed_factor = 30;
+ break;
+ case event_restriction_lane_blocked:
+ case event_restriction_lane_closed:
+ case event_restriction_reduced_lanes:
+ /* Assume speed is reduced proportionally to number of lanes, and never higher than 80 */
+ speed = 80;
+ /* TODO determine actual numbers of lanes */
+ speed_factor = 67;
+ break;
+ case event_restriction_contraflow:
+ /* Contraflow: assume 80, unless explicitly specified */
+ speed = 80;
+ break;
+ /* restriction_speed_limit is not in the list: either it comes with a maxspeed attribute, which gets
+ * evaluated regardless of the event it comes with, and if it doesn’t come with one, it carries no
+ * useful information. */
+ default:
+ break;
+ }
+ }
+
+ for (j = 0; j < this_->events[i]->si_count; j++) {
+ switch (this_->events[i]->si[j]->type) {
+ case si_vehicle_all:
+ /* For all vehicles */
+ flags |= AF_ALL;
+ has_flags = 1;
+ break;
+ case si_vehicle_bus:
+ /* For buses only */
+ /* TODO what about other (e.g. chartered) buses? */
+ flags |= AF_PUBLIC_BUS;
+ has_flags = 1;
+ break;
+ case si_vehicle_car:
+ /* For cars only */
+ flags |= AF_CAR;
+ has_flags = 1;
+ break;
+ case si_vehicle_car_with_caravan:
+ /* For cars with caravans only */
+ /* TODO no matching flag */
+ has_flags = 1;
+ break;
+ case si_vehicle_car_with_trailer:
+ /* For cars with trailers only */
+ /* TODO no matching flag */
+ has_flags = 1;
+ break;
+ case si_vehicle_hazmat:
+ /* For hazardous loads only */
+ flags |= AF_DANGEROUS_GOODS;
+ has_flags = 1;
+ break;
+ case si_vehicle_hgv:
+ /* For heavy trucks only */
+ flags |= AF_TRANSPORT_TRUCK | AF_DELIVERY_TRUCK;
+ has_flags = 1;
+ break;
+ case si_vehicle_motor:
+ /* For all motor vehicles */
+ flags |= AF_MOTORIZED_FAST | AF_MOPED;
+ has_flags = 1;
+ break;
+ case si_vehicle_with_trailer:
+ /* For vehicles with trailers only */
+ /* TODO no matching flag */
+ has_flags = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* if no vehicle type is specified in supplementary information, assume all */
+ if (!has_flags)
+ flags = AF_ALL;
+
+ if (!ret)
+ ret = seg_data_new();
+
+ /* use implicit values if no explicit ones are given */
+ if ((speed != INT_MAX) || speed_penalty || (speed_factor != 100) || delay) {
+ if (ret->speed == INT_MAX) {
+ ret->speed = speed;
+ ret->speed_penalty = speed_penalty;
+ ret->speed_factor = speed_factor;
+ }
+ if (!ret->delay)
+ ret->delay = delay;
+ }
+
+ ret->dir = this_->location->directionality;
+ ret->flags = flags;
+
+ return ret;
+}
+
+/**
+ * @brief Removes message data from the items associated with a message.
+ *
+ * Removing message data also triggers an update of the affected items’ attributes.
+ *
+ * It is possible to skip items associated with a particular message from being removed by passing that
+ * message as the `new` argument. This is used for message updates, as this function is called after the
+ * items associated with both the old and the new message have already been updated. Skipping items
+ * referenced by `new` ensures that message data is only stripped from items which are no longer being
+ * referenced by the updated message.
+ *
+ * If the IDs of `old` and `new` differ, `new` is ignored.
+ *
+ * @param old The message whose data it so be removed from its associated items
+ * @param new If non-NULL, items referenced by this message will be skipped, see description
+ * @param route The route affected by the changes
+ */
+static void traffic_message_remove_item_data(struct traffic_message * old, struct traffic_message * new,
+ struct route * route) {
+ int i, j;
+ int skip;
+ struct item_priv * ip;
+ GList * msglist;
+ struct item_msg_priv * msgdata;
+
+ if (new && strcmp(old->id, new->id))
+ new = NULL;
+
+ for (i = 0; old->priv->items && old->priv->items[i]; i++) {
+ skip = 0;
+ if (new)
+ for (j = 0; new->priv->items && new->priv->items[j] && !skip; j++)
+ skip |= (old->priv->items[i] == new->priv->items[j]);
+ if (!skip) {
+ ip = (struct item_priv *) old->priv->items[i]->priv_data;
+ for (msglist = ip->message_data; msglist; ) {
+ msgdata = (struct item_msg_priv *) msglist->data;
+ msglist = g_list_next(msglist);
+ if (!strcmp(msgdata->message_id, old->id)) {
+ ip->message_data = g_list_remove(ip->message_data, msgdata);
+ g_free(msgdata->message_id);
+ g_free(msgdata);
+ }
+ }
+ tm_item_update_attrs(old->priv->items[i], route);
+ }
+ }
+}
+
+/**
+ * @brief Ensures the traffic instance points to valid shared data.
+ *
+ * This method first examines all registered traffic instances to see if one of them has the `shared`
+ * member set. If that is the case, the current instance copies the `shared` pointer of the other
+ * instance. Otherwise a new `struct traffic_shared_priv` is created and its address stored in `shared`.
+ *
+ * Calling this method on a traffic instance with a non-NULL `shared` member has no effect.
+ *
+ * @param this_ The traffic instance
+ */
+static void traffic_set_shared(struct traffic *this_) {
+ struct attr_iter *iter;
+ struct attr attr;
+ struct traffic * traffic;
+
+ dbg(lvl_debug, "enter");
+
+ if (!this_->shared) {
+ iter = navit_attr_iter_new();
+ while (navit_get_attr(this_->navit, attr_traffic, &attr, iter)) {
+ traffic = (struct traffic *) attr.u.navit_object;
+ if (traffic->shared)
+ this_->shared = traffic->shared;
+ }
+ navit_attr_iter_destroy(iter);
+ }
+
+ if (!this_->shared) {
+ this_->shared = g_new0(struct traffic_shared_priv, 1);
+ }
+}
+
+/**
+ * @brief Dumps all currently active traffic messages to an XML file.
+ */
+static void traffic_dump_messages_to_xml(struct traffic * this_) {
+ /* add the configuration directory to the name of the file to use */
+ char *traffic_filename = g_strjoin(NULL, navit_get_user_data_directory(TRUE),
+ "/traffic.xml", NULL);
+ GList * msgiter;
+ struct traffic_message * message;
+ char * strval;
+ char * point_names[5] = {"from", "at", "via", "not_via", "to"};
+ struct traffic_point * points[5];
+ int i, j;
+
+ if (traffic_filename) {
+ FILE *f = fopen(traffic_filename,"w");
+ if (f) {
+ fprintf(f, "<navit_messages>\n");
+ for (msgiter = this_->shared->messages; msgiter; msgiter = g_list_next(msgiter)) {
+ message = (struct traffic_message *) msgiter->data;
+ points[0] = message->location->from;
+ points[1] = message->location->at;
+ points[2] = message->location->via;
+ points[3] = message->location->not_via;
+ points[4] = message->location->to;
+
+ strval = time_to_iso8601(message->receive_time);
+ fprintf(f, " <message id=\"%s\" receive_time=\"%s\"", message->id, strval);
+ g_free(strval);
+ strval = time_to_iso8601(message->update_time);
+ fprintf(f, " update_time=\"%s\"", strval);
+ g_free(strval);
+ if (message->start_time) {
+ strval = time_to_iso8601(message->start_time);
+ fprintf(f, " start_time=\"%s\"", strval);
+ g_free(strval);
+ }
+ if (message->end_time) {
+ strval = time_to_iso8601(message->end_time);
+ fprintf(f, " end_time=\"%s\"", strval);
+ g_free(strval);
+ }
+ if (message->expiration_time) {
+ strval = time_to_iso8601(message->expiration_time);
+ fprintf(f, " expiration_time=\"%s\"", strval);
+ g_free(strval);
+ }
+ if (message->is_forecast)
+ fprintf(f, " forecast=\"%d\"", message->is_forecast);
+ fprintf(f, ">\n");
+
+ fprintf(f, " <location directionality=\"%s\"",
+ message->location->directionality == location_dir_one ? "ONE_DIRECTION" : "BOTH_DIRECTIONS");
+ if (message->location->fuzziness)
+ fprintf(f, " fuzziness=\"%s\"", location_fuzziness_to_string(message->location->fuzziness));
+ if (message->location->ramps)
+ fprintf(f, " ramps=\"%s\"", location_ramps_to_string(message->location->ramps));
+ if (message->location->road_type != type_line_unspecified)
+ fprintf(f, " road_type=\"%s\"", item_to_name(message->location->road_type));
+ if (message->location->road_ref)
+ fprintf(f, " road_ref=\"%s\"", message->location->road_ref);
+ if (message->location->road_name)
+ fprintf(f, " road_name=\"%s\"", message->location->road_name);
+ if (message->location->destination)
+ fprintf(f, " destination=\"%s\"", message->location->destination);
+ if (message->location->direction)
+ fprintf(f, " direction=\"%s\"", message->location->direction);
+ if ((message->location->directionality == location_dir_one)
+ && message->location->tmc_direction)
+ fprintf(f, " tmc_direction=\"%+d\"", message->location->tmc_direction);
+ if (message->location->tmc_table)
+ fprintf(f, " tmc_table=\"%s\"", message->location->tmc_table);
+ fprintf(f, ">\n");
+
+ for (i = 0; i < 5; i++)
+ if (points[i]) {
+ fprintf(f, " <%s", point_names[i]);
+ if (points[i]->junction_name)
+ fprintf(f, " junction_name=\"%s\"", points[i]->junction_name);
+ if (points[i]->junction_ref)
+ fprintf(f, " junction_ref=\"%s\"", points[i]->junction_ref);
+ if (points[i]->tmc_id)
+ fprintf(f, " tmc_id=\"%s\"", points[i]->tmc_id);
+ fprintf(f, ">");
+ fprintf(f, "%+f %+f", points[i]->coord.lat, points[i]->coord.lng);
+ fprintf(f, "</%s>\n", point_names[i]);
+ }
+
+ fprintf(f, " </location>\n");
+
+ fprintf(f, " <events>\n");
+ for (i = 0; i < message->event_count; i++) {
+ fprintf(f, " <event class=\"%s\" type=\"%s\"",
+ event_class_to_string(message->events[i]->event_class),
+ event_type_to_string(message->events[i]->type));
+ if (message->events[i]->length >= 0)
+ fprintf(f, " length=\"%d\"", message->events[i]->length);
+ if (message->events[i]->speed != INT_MAX)
+ fprintf(f, " speed=\"%d\"", message->events[i]->speed);
+ /* TODO message->events[i]->quantifier */
+ fprintf(f, ">\n");
+
+ for (j = 0; j < message->events[i]->si_count; j++) {
+ fprintf(f, " <supplementary_info class=\"%s\" type=\"%s\"",
+ si_class_to_string(message->events[i]->si[j]->si_class),
+ si_type_to_string(message->events[i]->si[j]->type));
+ /* TODO message->events[i]->si[j]->quantifier */
+ fprintf(f, "/>\n");
+ }
+
+ fprintf(f, " </event>\n");
+ }
+ fprintf(f, " </events>\n");
+ fprintf(f, " </message>\n");
+ }
+ fprintf(f, "</navit_messages>\n");
+ fclose(f);
+ } else {
+ dbg(lvl_error,"could not open file for traffic messages");
+
+ } /* else - if (f) */
+ g_free(traffic_filename); /* free the file name */
+ } /* if (traffic_filename) */
+}
+
+/**
+ * @brief Processes new traffic messages.
+ *
+ * This is the internal backend for `traffic_process_messages()`. It is also used internally.
+ *
+ * The behavior of this function can be controlled via flags.
+ *
+ * `PROCESS_MESSAGES_PURGE_EXPIRED` causes expired messages to be purged from the message store after
+ * new messages have been processed. It is intended to be used with timer-triggered calls.
+ *
+ * `PROCESS_MESSAGES_NO_DUMP_STORE` prevents saving of the message store to disk, intended to be used
+ * when reading stored message data on startup.
+ *
+ * Traffic messages are always read from `this->shared->message_queue`. It can be empty, which makes sense e.g. when
+ * the `PROCESS_MESSAGES_PURGE_EXPIRED` flag is used, to just purge expired messages.
+ *
+ * @param this_ The traffic instance
+ * @param flags Flags, see description
+ *
+ * @return A combination of flags, `MESSAGE_UPDATE_MESSAGES` indicating that new messages were processed
+ * and `MESSAGE_UPDATE_SEGMENTS` that segments were changed
+ */
+/* TODO what if the update for a still-valid message expires in the past? */
+static int traffic_process_messages_int(struct traffic * this_, int flags) {
+ /* Start and current time */
+ struct timeval start, now;
+
+ /* Current message */
+ struct traffic_message * message;
+
+ /* Return value */
+ int ret = 0;
+
+ /* Number of messages processed so far */
+ int i = 0;
+
+ /* Iterator over messages */
+ GList * msg_iter;
+
+ /* Stored message being compared */
+ struct traffic_message * stored_msg;
+
+ /* Messages to remove */
+ GList * msgs_to_remove = NULL;
+
+ /* Pointer into messages[i]->replaces */
+ char ** replaces;
+
+ /* Attributes for traffic distortions generated from the current traffic message */
+ struct seg_data * data;
+
+ /* Message replaced by the current one whose segments can be reused */
+ struct traffic_message * swap_candidate;
+
+ /* Temporary store for swapping locations and items */
+ struct traffic_location * swap_location;
+ struct item ** swap_items;
+
+ /* Time elapsed since start */
+ double msec = 0;
+
+ if (this_->shared->message_queue)
+ dbg(lvl_debug, "*****enter, %d messages in queue", g_list_length(this_->shared->message_queue));
+
+ gettimeofday(&start, NULL);
+ for (; this_->shared->message_queue && (msec < TIME_SLICE);
+ this_->shared->message_queue = g_list_remove(this_->shared->message_queue, message)) {
+ message = (struct traffic_message *) this_->shared->message_queue->data;
+ i++;
+ if (message->expiration_time < time(NULL)) {
+ dbg(lvl_debug, "message is no longer valid, ignoring");
+ traffic_message_destroy(message);
+ } else {
+ dbg(lvl_debug, "*****checkpoint PROCESS-1, id='%s'", message->id);
+ ret |= MESSAGE_UPDATE_MESSAGES;
+
+ for (msg_iter = this_->shared->messages; msg_iter; msg_iter = g_list_next(msg_iter)) {
+ stored_msg = (struct traffic_message *) msg_iter->data;
+ if (!strcmp(stored_msg->id, message->id))
+ msgs_to_remove = g_list_append(msgs_to_remove, stored_msg);
+ else
+ for (replaces = ((struct traffic_message *) this_->shared->message_queue->data)->replaces; replaces; replaces++)
+ if (!strcmp(stored_msg->id, *replaces) && !g_list_find(msgs_to_remove, message))
+ msgs_to_remove = g_list_append(msgs_to_remove, stored_msg);
+ }
+
+ if (!message->is_cancellation) {
+ dbg(lvl_debug, "*****checkpoint PROCESS-2");
+ /* if the message is not just a cancellation, store it and match it to the map */
+ data = traffic_message_parse_events(message);
+ swap_candidate = NULL;
+
+ dbg(lvl_debug, "*****checkpoint PROCESS-3");
+ /* check if any of the replaced messages has the same location and segment data */
+ for (msg_iter = msgs_to_remove; msg_iter && !swap_candidate; msg_iter = g_list_next(msg_iter)) {
+ stored_msg = (struct traffic_message *) msg_iter->data;
+ if (seg_data_equals(data, traffic_message_parse_events(stored_msg))
+ && traffic_location_equals(message->location, stored_msg->location))
+ swap_candidate = stored_msg;
+ }
+
+ if (swap_candidate) {
+ dbg(lvl_debug, "*****checkpoint PROCESS-4, swap candidate found");
+ /* reuse location and segments if we are replacing a matching message */
+ swap_location = message->location;
+ swap_items = message->priv->items;
+ message->location = swap_candidate->location;
+ message->priv->items = swap_candidate->priv->items;
+ swap_candidate->location = swap_location;
+ swap_candidate->priv->items = swap_items;
+ } else {
+ dbg(lvl_debug, "*****checkpoint PROCESS-4, need to find matching segments");
+ /* else find matching segments from scratch */
+ traffic_message_add_segments(message, this_->ms, data, this_->map, this_->rt);
+ ret |= MESSAGE_UPDATE_SEGMENTS;
+ }
+
+ g_free(data);
+
+ /* store message */
+ this_->shared->messages = g_list_append(this_->shared->messages, message);
+ dbg(lvl_debug, "*****checkpoint PROCESS-5");
+ }
+
+ /* delete replaced messages */
+ if (msgs_to_remove) {
+ dbg(lvl_debug, "*****checkpoint PROCESS (messages to remove, start)");
+ for (msg_iter = msgs_to_remove; msg_iter; msg_iter = g_list_next(msg_iter)) {
+ stored_msg = (struct traffic_message *) msg_iter->data;
+ if (stored_msg->priv->items)
+ ret |= MESSAGE_UPDATE_SEGMENTS;
+ this_->shared->messages = g_list_remove_all(this_->shared->messages, stored_msg);
+ traffic_message_remove_item_data(stored_msg, message, this_->rt);
+ traffic_message_destroy(stored_msg);
+ }
+
+ g_list_free(msgs_to_remove);
+ msgs_to_remove = NULL;
+ dbg(lvl_debug, "*****checkpoint PROCESS (messages to remove, end)");
+ }
+
+ traffic_message_dump_to_stderr(message);
+
+ if (message->is_cancellation)
+ traffic_message_destroy(message);
+
+ dbg(lvl_debug, "*****checkpoint PROCESS-6");
+ }
+ gettimeofday(&now, NULL);
+ msec = (now.tv_usec - start.tv_usec) / ((double)1000) + (now.tv_sec - start.tv_sec) * 1000;
+ }
+
+ if (i)
+ dbg(lvl_debug, "processed %d message(s), %d still in queue", i, g_list_length(this_->shared->message_queue));
+
+ if (this_->shared->message_queue) {
+ /* if we're in the middle of the queue, trigger a redraw (if needed) and exit */
+ if ((ret & MESSAGE_UPDATE_SEGMENTS) && (navit_get_ready(this_->navit) == 3))
+ navit_draw_async(this_->navit, 1);
+ return ret;
+ } else {
+ /* last pass, remove our idle event and callback */
+ if (this_->idle_ev)
+ event_remove_idle(this_->idle_ev);
+ if (this_->idle_cb)
+ callback_destroy(this_->idle_cb);
+ this_->idle_ev = NULL;
+ this_->idle_cb = NULL;
+ }
+
+ if (flags & PROCESS_MESSAGES_PURGE_EXPIRED) {
+ /* find and remove expired messages */
+ for (msg_iter = this_->shared->messages; msg_iter; msg_iter = g_list_next(msg_iter)) {
+ stored_msg = (struct traffic_message *) msg_iter->data;
+ if (stored_msg->expiration_time < time(NULL))
+ msgs_to_remove = g_list_append(msgs_to_remove, stored_msg);
+ }
+
+ if (msgs_to_remove) {
+ for (msg_iter = msgs_to_remove; msg_iter; msg_iter = g_list_next(msg_iter)) {
+ stored_msg = (struct traffic_message *) msg_iter->data;
+ if (stored_msg->priv->items)
+ ret |= MESSAGE_UPDATE_SEGMENTS;
+ this_->shared->messages = g_list_remove_all(this_->shared->messages, stored_msg);
+ traffic_message_remove_item_data(stored_msg, NULL, this_->rt);
+ traffic_message_destroy(stored_msg);
+ }
+
+ dbg(lvl_debug, "%d message(s) expired", g_list_length(msgs_to_remove));
+
+ g_list_free(msgs_to_remove);
+ }
+ }
+
+ if (ret && !(flags & PROCESS_MESSAGES_NO_DUMP_STORE)) {
+#ifdef TRAFFIC_DEBUG
+ /* dump map if messages have been added, deleted or expired */
+ tm_dump_to_textfile(this_->map);
+#endif
+
+ /* dump message store if new messages have been received */
+ traffic_dump_messages_to_xml(this_);
+ }
+
+ /* TODO see comment on route_recalculate_partial about thread-safety */
+ route_recalculate_partial(this_->rt);
+
+ /* trigger redraw if segments have changed */
+ if ((ret & MESSAGE_UPDATE_SEGMENTS) && (navit_get_ready(this_->navit) == 3))
+ navit_draw_async(this_->navit, 1);
+
+ return ret;
+}
+
+/**
+ * @brief The loop function for the traffic module.
+ *
+ * This function polls backends for new messages and processes them by inserting, removing or modifying
+ * traffic distortions and triggering route recalculations as needed.
+ */
+static void traffic_loop(struct traffic * this_) {
+ struct traffic_message ** messages;
+ struct traffic_message ** cur_msg;
+
+ messages = this_->meth.get_messages(this_->priv);
+ for (cur_msg = messages; cur_msg && *cur_msg; cur_msg++)
+ this_->shared->message_queue = g_list_append(this_->shared->message_queue, *cur_msg);
+ g_free(messages);
+
+ /* make sure traffic_process_messages_int runs at least once to ensure purging of expired messages */
+ if (this_->shared->message_queue) {
+ if (this_->idle_ev)
+ event_remove_idle(this_->idle_ev);
+ if (this_->idle_cb)
+ callback_destroy(this_->idle_cb);
+ this_->idle_cb = callback_new_2(callback_cast(traffic_process_messages_int),
+ this_, PROCESS_MESSAGES_PURGE_EXPIRED);
+ this_->idle_ev = event_add_idle(50, this_->idle_cb);
+ } else
+ traffic_process_messages_int(this_, PROCESS_MESSAGES_PURGE_EXPIRED);
+}
+
+/**
+ * @brief Instantiates the traffic plugin
+ *
+ * At a minimum, `attrs` must contain a `type` attribute matching one of the available traffic plugins.
+ *
+ * @param parent The parent, usually the Navit instance
+ * @param attrs The attributes for the plugin
+ *
+ * @return A `traffic` instance.
+ */
+static struct traffic * traffic_new(struct attr *parent, struct attr **attrs) {
+ struct traffic *this_;
+ struct traffic_priv *(*traffic_new)(struct navit *nav, struct traffic_methods *meth,
+ struct attr **attrs, struct callback_list *cbl);
+ struct attr *attr;
+
+ attr = attr_search(attrs, NULL, attr_type);
+ if (!attr) {
+ dbg(lvl_error, "type missing");
+ return NULL;
+ }
+ dbg(lvl_debug, "type='%s'", attr->u.str);
+ traffic_new = plugin_get_category_traffic(attr->u.str);
+ dbg(lvl_debug, "new=%p", traffic_new);
+ if (!traffic_new) {
+ dbg(lvl_error, "wrong type '%s'", attr->u.str);
+ return NULL;
+ }
+ this_ = (struct traffic *) navit_object_new(attrs, &traffic_func, sizeof(struct traffic));
+ if (parent->type == attr_navit)
+ this_->navit = parent->u.navit;
+ else {
+ dbg(lvl_error, "wrong parent type '%s', only navit is permitted", attr_to_name(parent->type));
+ navit_object_destroy((struct navit_object *) this_);
+ return NULL;
+ }
+
+ this_->priv = traffic_new(parent->u.navit, &this_->meth, this_->attrs, NULL);
+ dbg(lvl_debug, "get_messages=%p", this_->meth.get_messages);
+ dbg(lvl_debug, "priv=%p", this_->priv);
+ if (!this_->priv) {
+ dbg(lvl_error, "plugin initialization failed");
+ 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
+ this_->callback = callback_new_1(callback_cast(traffic_loop), this_);
+ this_->timeout = event_add_timeout(1000, 1, this_->callback); // TODO make interval configurable
+
+ this_->map = NULL;
+
+ if (!this_->shared)
+ traffic_set_shared(this_);
+
+ return this_;
+}
+
+/**
+ * @brief Creates a new XML element structure.
+ *
+ * Note that the structure of `names` and `values` may differ between XML libraries. Behavior is indicated by the
+ * `XML_ATTR_DISTANCE` constant.
+ *
+ * If `XML_ATTR_DISTANCE == 1`, `names` and `values` are two separate arrays, and `values[n]` is the value that
+ * corresponds to `names[n]`.
+ *
+ * If `XML_ATTR_DISTANCE == 2`, attribute names and values are kept in a single array in which names and values
+ * alternate, names first. In this case, `names` points to the array while `values` points to its second element, i.e.
+ * the first value. In this case, `value` is invalid for an empty array, and dereferencing it may segfault.
+ *
+ * @param tag_name The tag name
+ * @param names Attribute names
+ * @param values Attribute values
+ */
+static struct xml_element * traffic_xml_element_new(const char *tag_name, const char **names,
+ const char **values) {
+ struct xml_element * ret = g_new0(struct xml_element, 1);
+ const char ** in;
+ char ** out;
+
+ ret->tag_name = g_strdup(tag_name);
+ if (names) {
+ ret->names = g_new0(char *, g_strv_length((gchar **) names) / XML_ATTR_DISTANCE + 1);
+ in = names;
+ out = ret->names;
+ while (*in) {
+ *out++ = g_strdup(*in);
+ in += XML_ATTR_DISTANCE;
+ }
+ }
+ /* extra check for mixed name-value array */
+ if (names && *names && values) {
+#if XML_ATTR_DISTANCE == 1
+ ret->values = g_new0(char *, g_strv_length((gchar **) values) + 1);
+#else
+ ret->values = g_new0(char *, g_strv_length((gchar **) values) / XML_ATTR_DISTANCE + 2);
+#endif
+ in = values;
+ out = ret->values;
+ while (*in) {
+ *out++ = g_strdup(*in++);
+#if XML_ATTR_DISTANCE > 1
+ if (*in)
+ in++;
+#endif
+ }
+ }
+ return ret;
+}
+
+/**
+ * @brief Frees up an XML element structure.
+ *
+ * This will free up the memory used by the struct and all its members.
+ */
+static void traffic_xml_element_destroy(struct xml_element * this_) {
+ void ** iter;
+
+ g_free(this_->tag_name);
+ if (this_->names) {
+ for (iter = (void **) this_->names; *iter; iter++)
+ g_free(*iter);
+ g_free(this_->names);
+ }
+ if (this_->values) {
+ for (iter = (void **) this_->values; *iter; iter++)
+ g_free(*iter);
+ g_free(this_->values);
+ }
+ g_free(this_->text);
+ g_free(this_);
+}
+
+/**
+ * @brief Retrieves the value of an XML attribute.
+ *
+ * @param name The name of the attribute to retrieve
+ * @param names All attribute names
+ * @param values Attribute values (indices correspond to `names`)
+ *
+ * @return If `names` contains `name`, the corresponding value is returned, else NULL
+ */
+static char * traffic_xml_get_attr(const char * attr, char ** names, char ** values) {
+ int i;
+ for (i = 0; names[i] && values[i]; i++) {
+ if (!g_ascii_strcasecmp(attr, names[i]))
+ return values[i];
+ }
+ return NULL;
+}
+
+/**
+ * @brief Whether the tag stack represents a hierarchy of elements which is recognized.
+ *
+ * @param state The XML parser state
+ *
+ * @return True if the stack is valid, false if invalid. An empty stack is considered invalid.
+ */
+static int traffic_xml_is_tagstack_valid(struct xml_state * state) {
+ int ret = 0;
+ GList * tagiter;
+ struct xml_element * el, * el_parent;
+
+ for (tagiter = g_list_last(state->tagstack); tagiter; tagiter = g_list_previous(tagiter)) {
+ el = (struct xml_element *) tagiter->data;
+ el_parent = tagiter->next ? tagiter->next->data : NULL;
+
+ if (!g_ascii_strcasecmp(el->tag_name, "navit_messages")
+ || !g_ascii_strcasecmp(el->tag_name, "feed"))
+ ret = !tagiter->next;
+ else if (!g_ascii_strcasecmp((char *) el->tag_name, "message"))
+ ret = (!el_parent
+ || !g_ascii_strcasecmp(el_parent->tag_name, "navit_messages")
+ || !g_ascii_strcasecmp(el_parent->tag_name, "feed"));
+ else if (!g_ascii_strcasecmp(el->tag_name, "events")
+ || !g_ascii_strcasecmp(el->tag_name, "location")
+ || !g_ascii_strcasecmp(el->tag_name, "merge"))
+ ret = (el_parent && !g_ascii_strcasecmp(el_parent->tag_name, "message"));
+ else if (!g_ascii_strcasecmp(el->tag_name, "event"))
+ ret = (el_parent && !g_ascii_strcasecmp(el_parent->tag_name, "events"));
+ else if (!g_ascii_strcasecmp(el->tag_name, "from")
+ || !g_ascii_strcasecmp(el->tag_name, "to")
+ || !g_ascii_strcasecmp(el->tag_name, "at")
+ || !g_ascii_strcasecmp(el->tag_name, "via")
+ || !g_ascii_strcasecmp(el->tag_name, "not_via"))
+ ret = (el_parent && !g_ascii_strcasecmp(el_parent->tag_name, "location"));
+ else if (!g_ascii_strcasecmp(el->tag_name, "supplementary_info"))
+ ret = (el_parent && !g_ascii_strcasecmp(el_parent->tag_name, "event"));
+ else if (!g_ascii_strcasecmp(el->tag_name, "replaces"))
+ ret = (el_parent && !g_ascii_strcasecmp(el_parent->tag_name, "merge"));
+ else
+ ret = 0;
+
+ if (!ret)
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Callback function which gets called when an opening tag is encountered.
+ *
+ * @param tag_name The tag name
+ * @param names Attribute names
+ * @param values Attribute values (indices correspond to `names`)
+ * @param data Points to a `struct xml_state` holding parser state
+ */
+static void traffic_xml_start(xml_context *dummy, const char *tag_name, const char **names,
+ const char **values, void *data, GError **error) {
+ struct xml_state * state = (struct xml_state *) data;
+ struct xml_element * el;
+
+ el = traffic_xml_element_new(tag_name, names, values);
+ state->tagstack = g_list_prepend(state->tagstack, el);
+ state->is_opened = 1;
+ state->is_valid = traffic_xml_is_tagstack_valid(state);
+ if (!state->is_valid)
+ return;
+
+ dbg(lvl_debug, "OPEN: %s", tag_name);
+
+ if (!g_ascii_strcasecmp((char *) tag_name, "supplementary_info")) {
+ state->si = g_list_append(state->si, traffic_suppl_info_new(
+ si_class_new(traffic_xml_get_attr("class", el->names, el->values)),
+ si_type_new(traffic_xml_get_attr("type", el->names, el->values)),
+ /* TODO quantifier */
+ NULL));
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "replaces")) {
+ /* TODO */
+ }
+
+ /*
+ * No handling necessary for:
+ *
+ * navit_messages: No attributes, everything handled in children's callbacks
+ * feed: No attributes, everything handled in children's callbacks
+ * message: Everything handled in end callback
+ * events: No attributes, everything handled in children's callbacks
+ * location: Everything handled in end callback
+ * event: Everything handled in end callback
+ * merge: No attributes, everything handled in children's callbacks
+ * from, to, at, via, not_via: Everything handled in end callback
+ */
+}
+
+/**
+ * @brief Callback function which gets called when a closing tag is encountered.
+ *
+ * @param tag_name The tag name
+ * @param data Points to a `struct xml_state` holding parser state
+ */
+static void traffic_xml_end(xml_context *dummy, const char *tag_name, void *data, GError **error) {
+ struct xml_state * state = (struct xml_state *) data;
+ struct xml_element * el = state->tagstack ? (struct xml_element *) state->tagstack->data : NULL;
+ struct traffic_message * message;
+ struct traffic_point ** point = NULL;
+
+ /* Iterator and child element count */
+ int i, count;
+
+ /* Child elements */
+ void ** children = NULL;
+
+ /* Iterator for children in GList */
+ GList * iter;
+
+ /* Some elements we need to check for null */
+ char * tmc_direction;
+ char * length;
+ char * speed;
+
+ /* New traffic event */
+ struct traffic_event * event = NULL;
+
+ float lat, lon;
+
+ if (state->is_valid) {
+ dbg(lvl_debug, " END: %s", tag_name);
+
+ if (!g_ascii_strcasecmp((char *) tag_name, "message")) {
+ count = g_list_length(state->events);
+ if (count) {
+ children = (void **) g_new0(struct traffic_event *, count);
+ iter = state->events;
+ for (i = 0; iter && (i < count); i++) {
+ children[i] = iter->data;
+ iter = g_list_next(iter);
+ }
+ }
+ message = traffic_message_new(traffic_xml_get_attr("id", el->names, el->values),
+ time_new(traffic_xml_get_attr("receive_time", el->names, el->values)),
+ time_new(traffic_xml_get_attr("update_time", el->names, el->values)),
+ time_new(traffic_xml_get_attr("expiration_time", el->names, el->values)),
+ time_new(traffic_xml_get_attr("start_time", el->names, el->values)),
+ time_new(traffic_xml_get_attr("end_time", el->names, el->values)),
+ boolean_new(traffic_xml_get_attr("cancellation", el->names, el->values), 0),
+ boolean_new(traffic_xml_get_attr("forecast", el->names, el->values), 0),
+ /* TODO replaces */
+ 0, NULL,
+ state->location,
+ count,
+ (struct traffic_event **) children);
+ if (!traffic_message_is_valid(message)) {
+ dbg(lvl_error, "malformed message detected, skipping");
+ traffic_message_destroy(message);
+ } else
+ state->messages = g_list_append(state->messages, message);
+ g_free(children);
+ state->location = NULL;
+ g_list_free(state->events);
+ state->events = NULL;
+ /* TODO replaces */
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "location")) {
+ tmc_direction = traffic_xml_get_attr("tmc_direction", el->names, el->values);
+ state->location = traffic_location_new(state->at, state->from,
+ state->to, state->via, state->not_via,
+ traffic_xml_get_attr("destination", el->names, el->values),
+ traffic_xml_get_attr("direction", el->names, el->values),
+ location_dir_new(traffic_xml_get_attr("directionality", el->names, el->values)),
+ location_fuzziness_new(traffic_xml_get_attr("fuzziness", el->names, el->values)),
+ location_ramps_new(traffic_xml_get_attr("ramps", el->names, el->values)),
+ item_type_from_road_type(traffic_xml_get_attr("road_type", el->names, el->values),
+ /* TODO revisit default for road_is_urban */
+ boolean_new(traffic_xml_get_attr("road_is_urban", el->names, el->values), 0)),
+ traffic_xml_get_attr("road_name", el->names, el->values),
+ traffic_xml_get_attr("road_ref", el->names, el->values),
+ traffic_xml_get_attr("tmc_table", el->names, el->values),
+ tmc_direction ? atoi(tmc_direction) : 0);
+ state->from = NULL;
+ state->to = NULL;
+ state->at = NULL;
+ state->via = NULL;
+ state->not_via = NULL;
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "event")) {
+ count = g_list_length(state->si);
+ if (count) {
+ children = (void **) g_new0(struct traffic_suppl_info *, count);
+ iter = state->si;
+ for (i = 0; iter && (i < count); i++) {
+ children[i] = iter->data;
+ iter = g_list_next(iter);
+ }
+ }
+ length = traffic_xml_get_attr("length", el->names, el->values);
+ speed = traffic_xml_get_attr("speed", el->names, el->values);
+ event = traffic_event_new(event_class_new(traffic_xml_get_attr("class", el->names, el->values)),
+ event_type_new(traffic_xml_get_attr("type", el->names, el->values)),
+ length ? atoi(length) : -1,
+ speed ? atoi(speed) : INT_MAX,
+ /* TODO quantifier */
+ NULL,
+ count,
+ (struct traffic_suppl_info **) children);
+ g_free(children);
+ g_list_free(state->si);
+ 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");
+ traffic_event_destroy(event);
+ } else
+ state->events = g_list_append(state->events, event);
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "from")) {
+ point = &state->from;
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "to")) {
+ point = &state->to;
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "at")) {
+ point = &state->at;
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "via")) {
+ point = &state->via;
+ } else if (!g_ascii_strcasecmp((char *) tag_name, "not_via")) {
+ point = &state->not_via;
+ }
+
+ /*
+ * No handling necessary for:
+ *
+ * navit_messages: No attributes, everything handled in children's callbacks
+ * feed: No attributes, everything handled in children's callbacks
+ * events: No attributes, everything handled in children's callbacks
+ * merge: No attributes, everything handled in children's callbacks
+ * replaces: Leaf node, handled in start callback
+ * supplementary_info: Leaf node, handled in start callback
+ */
+
+ if (point) {
+ /* we have a location point (from, at, to, via or not_via) to process */
+ if (sscanf(el->text, "%f %f", &lat, &lon) == 2) {
+ *point = traffic_point_new(lon, lat,
+ traffic_xml_get_attr("junction_name", el->names, el->values),
+ traffic_xml_get_attr("junction_ref", el->names, el->values),
+ traffic_xml_get_attr("tmc_id", el->names, el->values));
+ } else {
+ dbg(lvl_error, "%s has no valid lat/lon pair, skipping", tag_name);
+ }
+ }
+ }
+
+ if (el && !g_ascii_strcasecmp(tag_name, el->tag_name)) {
+ traffic_xml_element_destroy(el);
+ state->tagstack = g_list_remove(state->tagstack, state->tagstack->data);
+ }
+ state->is_opened = 0;
+}
+
+/**
+ * @brief Callback function which gets called when character data is encountered.
+ *
+ * @param text The character data (note that the data is not NULL-terminated!)
+ * @param len The number of characters in `text`
+ * @param data Points to a `struct xml_state` holding parser state
+ */
+static void traffic_xml_text(xml_context *dummy, const char *text, gsize len, void *data, GError **error) {
+ struct xml_state * state = (struct xml_state *) data;
+ char * text_sz = g_strndup(text, len);
+ struct xml_element * el = state->tagstack ? (struct xml_element *) state->tagstack->data : NULL;
+
+ dbg(lvl_debug, " TEXT: '%s'", text_sz);
+ if (state->is_valid && state->is_opened) {
+ /* this will work only for leaf nodes, which is not an issue at the moment as the only nodes
+ * with actual text data are leaf nodes */
+ el->text = g_strndup(text, len);
+ }
+ g_free(text_sz);
+}
+
+enum event_class event_class_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "CONGESTION"))
+ return event_class_congestion;
+ if (!g_ascii_strcasecmp(string, "DELAY"))
+ return event_class_delay;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION"))
+ return event_class_restriction;
+ }
+ return event_class_invalid;
+}
+
+const char * event_class_to_string(enum event_class this_) {
+ switch (this_) {
+ case event_class_congestion:
+ return "CONGESTION";
+ case event_class_delay:
+ return "DELAY";
+ case event_class_restriction:
+ return "RESTRICTION";
+ default:
+ return "INVALID";
+ }
+}
+
+enum event_type event_type_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "CONGESTION_CLEARED"))
+ return event_congestion_cleared;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_FORECAST_WITHDRAWN"))
+ return event_congestion_forecast_withdrawn;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_HEAVY_TRAFFIC"))
+ return event_congestion_heavy_traffic;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_LONG_QUEUE"))
+ return event_congestion_long_queue;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_NONE"))
+ return event_congestion_none;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_NORMAL_TRAFFIC"))
+ return event_congestion_normal_traffic;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_QUEUE"))
+ return event_congestion_queue;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_QUEKE_LIKELY"))
+ return event_congestion_queue_likely;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_SLOW_TRAFFIC"))
+ return event_congestion_slow_traffic;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_STATIONARY_TRAFFIC"))
+ return event_congestion_stationary_traffic;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_STATIONARY_TRAFFIC_LIKELY"))
+ return event_congestion_stationary_traffic_likely;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_BUILDING_UP"))
+ return event_congestion_traffic_building_up;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_CONGESTION"))
+ return event_congestion_traffic_congestion;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_EASING"))
+ return event_congestion_traffic_easing;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_FLOWING_FREELY"))
+ return event_congestion_traffic_flowing_freely;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_HEAVIER_THAN_NORMAL"))
+ return event_congestion_traffic_heavier_than_normal;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_LIGHTER_THAN_NORMAL"))
+ return event_congestion_traffic_lighter_than_normal;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_MUCH_HEAVIER_THAN_NORMAL"))
+ return event_congestion_traffic_much_heavier_than_normal;
+ if (!g_ascii_strcasecmp(string, "CONGESTION_TRAFFIC_PROBLEM"))
+ return event_congestion_traffic_problem;
+ if (!g_ascii_strcasecmp(string, "DELAY_CLEARANCE"))
+ return event_delay_clearance;
+ if (!g_ascii_strcasecmp(string, "DELAY_DELAY"))
+ return event_delay_delay;
+ if (!g_ascii_strcasecmp(string, "DELAY_DELAY_POSSIBLE"))
+ return event_delay_delay_possible;
+ if (!g_ascii_strcasecmp(string, "DELAY_FORECAST_WITHDRAWN"))
+ return event_delay_forecast_withdrawn;
+ if (!g_ascii_strcasecmp(string, "DELAY_LONG_DELAY"))
+ return event_delay_long_delay;
+ if (!g_ascii_strcasecmp(string, "DELAY_SEVERAL_HOURS"))
+ return event_delay_several_hours;
+ if (!g_ascii_strcasecmp(string, "DELAY_UNCERTAIN_DURATION"))
+ return event_delay_uncertain_duration;
+ if (!g_ascii_strcasecmp(string, "DELAY_VERY_LONG_DELAY"))
+ return event_delay_very_long_delay;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ACCESS_RESTRICTIONS_LIFTED"))
+ return event_restriction_access_restrictions_lifted;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ALL_CARRIAGEWAYS_CLEARED"))
+ return event_restriction_all_carriageways_cleared;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ALL_CARRIAGEWAYS_REOPENED"))
+ return event_restriction_all_carriageways_reopened;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_BATCH_SERVICE"))
+ return event_restriction_batch_service;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_BLOCKED"))
+ return event_restriction_blocked;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_BLOCKED_AHEAD"))
+ return event_restriction_blocked_ahead;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_CARRIAGEWAY_BLOCKED"))
+ return event_restriction_carriageway_blocked;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_CARRIAGEWAY_CLOSED"))
+ return event_restriction_carriageway_closed;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_CONTRAFLOW"))
+ return event_restriction_contraflow;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_CLOSED"))
+ return event_restriction_closed;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_CLOSED_AHEAD"))
+ return event_restriction_closed_ahead;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ENTRY_BLOCKED"))
+ return event_restriction_entry_blocked;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ENTRY_REOPENED"))
+ return event_restriction_entry_reopened;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_INTERMITTENT_CLOSURES"))
+ return event_restriction_intermittent_closures;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_LANE_BLOCKED"))
+ return event_restriction_lane_blocked;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_LANE_CLOSED"))
+ return event_restriction_lane_closed;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_OPEN"))
+ return event_restriction_open;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_RAMP_BLOCKED"))
+ return event_restriction_ramp_blocked;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_RAMP_CLOSED"))
+ return event_restriction_ramp_closed;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_RAMP_REOPENED"))
+ return event_restriction_ramp_reopened;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_REDUCED_LANES"))
+ return event_restriction_reduced_lanes;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_REOPENED"))
+ return event_restriction_reopened;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_ROAD_CLEARED"))
+ return event_restriction_road_cleared;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_SINGLE_ALTERNATE_LINE_TRAFFIC"))
+ return event_restriction_single_alternate_line_traffic;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_SPEED_LIMIT"))
+ return event_restriction_speed_limit;
+ if (!g_ascii_strcasecmp(string, "RESTRICTION_SPEED_LIMIT_LIFTED"))
+ return event_restriction_speed_limit_lifted;
+ }
+ return event_invalid;
+}
+
+const char * event_type_to_string(enum event_type this_) {
+ switch (this_) {
+ case event_congestion_cleared:
+ return "CONGESTION_CLEARED";
+ case event_congestion_forecast_withdrawn:
+ return "CONGESTION_FORECAST_WITHDRAWN";
+ case event_congestion_heavy_traffic:
+ return "CONGESTION_HEAVY_TRAFFIC";
+ case event_congestion_long_queue:
+ return "CONGESTION_LONG_QUEUE";
+ case event_congestion_none:
+ return "CONGESTION_NONE";
+ case event_congestion_normal_traffic:
+ return "CONGESTION_NORMAL_TRAFFIC";
+ case event_congestion_queue:
+ return "CONGESTION_QUEUE";
+ case event_congestion_queue_likely:
+ return "CONGESTION_QUEUE_LIKELY";
+ case event_congestion_slow_traffic:
+ return "CONGESTION_SLOW_TRAFFIC";
+ case event_congestion_stationary_traffic:
+ return "CONGESTION_STATIONARY_TRAFFIC";
+ case event_congestion_stationary_traffic_likely:
+ return "CONGESTION_STATIONARY_TRAFFIC_LIKELY";
+ case event_congestion_traffic_building_up:
+ return "CONGESTION_TRAFFIC_BUILDING_UP";
+ case event_congestion_traffic_congestion:
+ return "CONGESTION_TRAFFIC_CONGESTION";
+ case event_congestion_traffic_easing:
+ return "CONGESTION_TRAFFIC_EASING";
+ case event_congestion_traffic_flowing_freely:
+ return "CONGESTION_TRAFFIC_FLOWING_FREELY";
+ case event_congestion_traffic_heavier_than_normal:
+ return "CONGESTION_TRAFFIC_HEAVIER_THAN_NORMAL";
+ case event_congestion_traffic_lighter_than_normal:
+ return "CONGESTION_TRAFFIC_LIGHTER_THAN_NORMAL";
+ case event_congestion_traffic_much_heavier_than_normal:
+ return "CONGESTION_TRAFFIC_MUCH_HEAVIER_THAN_NORMAL";
+ case event_congestion_traffic_problem:
+ return "CONGESTION_TRAFFIC_PROBLEM";
+ case event_delay_clearance:
+ return "DELAY_CLEARANCE";
+ case event_delay_delay:
+ return "DELAY_DELAY";
+ case event_delay_delay_possible:
+ return "DELAY_DELAY_POSSIBLE";
+ case event_delay_forecast_withdrawn:
+ return "DELAY_FORECAST_WITHDRAWN";
+ case event_delay_long_delay:
+ return "DELAY_LONG_DELAY";
+ case event_delay_several_hours:
+ return "DELAY_SEVERAL_HOURS";
+ case event_delay_uncertain_duration:
+ return "DELAY_UNCERTAIN_DURATION";
+ case event_delay_very_long_delay:
+ return "DELAY_VERY_LONG_DELAY";
+ case event_restriction_access_restrictions_lifted:
+ return "RESTRICTION_ACCESS_RESTRICTIONS_LIFTED";
+ case event_restriction_all_carriageways_cleared:
+ return "RESTRICTION_ALL_CARRIAGEWAYS_CLEARED";
+ case event_restriction_all_carriageways_reopened:
+ return "RESTRICTION_ALL_CARRIAGEWAYS_REOPENED";
+ case event_restriction_batch_service:
+ return "RESTRICTION_BATCH_SERVICE";
+ case event_restriction_blocked:
+ return "RESTRICTION_BLOCKED";
+ case event_restriction_blocked_ahead:
+ return "RESTRICTION_BLOCKED_AHEAD";
+ case event_restriction_carriageway_blocked:
+ return "RESTRICTION_CARRIAGEWAY_BLOCKED";
+ case event_restriction_carriageway_closed:
+ return "RESTRICTION_CARRIAGEWAY_CLOSED";
+ case event_restriction_closed:
+ return "RESTRICTION_CLOSED";
+ case event_restriction_closed_ahead:
+ return "RESTRICTION_CLOSED_AHEAD";
+ case event_restriction_contraflow:
+ return "RESTRICTION_CONTRAFLOW";
+ case event_restriction_entry_blocked:
+ return "RESTRICTION_ENTRY_BLOCKED";
+ case event_restriction_entry_reopened:
+ return "RESTRICTION_ENTRY_REOPENED";
+ case event_restriction_exit_blocked:
+ return "RESTRICTION_EXIT_BLOCKED";
+ case event_restriction_exit_reopened:
+ return "RESTRICTION_EXIT_REOPENED";
+ case event_restriction_intermittent_closures:
+ return "RESTRICTION_INTERMITTENT_CLOSURES";
+ case event_restriction_lane_blocked:
+ return "RESTRICTION_LANE_BLOCKED";
+ case event_restriction_lane_closed:
+ return "RESTRICTION_LANE_CLOSED";
+ case event_restriction_open:
+ return "RESTRICTION_OPEN";
+ case event_restriction_ramp_blocked:
+ return "RESTRICTION_RAMP_BLOCKED";
+ case event_restriction_ramp_closed:
+ return "RESTRICTION_RAMP_CLOSED";
+ case event_restriction_ramp_reopened:
+ return "RESTRICTION_RAMP_REOPENED";
+ case event_restriction_reduced_lanes:
+ return "RESTRICTION_REDUCED_LANES";
+ case event_restriction_reopened:
+ return "RESTRICTION_REOPENED";
+ case event_restriction_road_cleared:
+ return "RESTRICTION_ROAD_CLEARED";
+ case event_restriction_single_alternate_line_traffic:
+ return "RESTRICTION_SINGLE_ALTERNATE_LINE_TRAFFIC";
+ case event_restriction_speed_limit:
+ return "RESTRICTION_SPEED_LIMIT";
+ case event_restriction_speed_limit_lifted:
+ return "RESTRICTION_SPEED_LIMIT_LIFTED";
+ default:
+ return "INVALID";
+ }
+}
+
+enum item_type item_type_from_road_type(char * string, int is_urban) {
+ enum item_type ret = type_line_unspecified;
+
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "MOTORWAY"))
+ return is_urban ? type_highway_city : type_highway_land;
+ if (!g_ascii_strcasecmp(string, "TRUNK"))
+ return type_street_n_lanes;
+ if (!g_ascii_strcasecmp(string, "PRIMARY"))
+ return is_urban ? type_street_4_city : type_street_4_land;
+ if (!g_ascii_strcasecmp(string, "SECONDARY"))
+ return is_urban ? type_street_3_city : type_street_3_land;
+ if (!g_ascii_strcasecmp(string, "TERTIARY"))
+ return is_urban ? type_street_2_city : type_street_2_land;
+
+ ret = item_from_name(string);
+ }
+ if ((ret < route_item_first) || (ret > route_item_last))
+ return type_line_unspecified;
+ return ret;
+}
+
+enum location_dir location_dir_new(char * string) {
+ if (string && !g_ascii_strcasecmp(string, "ONE_DIRECTION"))
+ return location_dir_one;
+ return location_dir_both;
+}
+
+enum location_fuzziness location_fuzziness_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "LOW_RES"))
+ return location_fuzziness_low_res;
+ if (!g_ascii_strcasecmp(string, "END_UNKNOWN"))
+ return location_fuzziness_end_unknown;
+ if (!g_ascii_strcasecmp(string, "START_UNKNOWN"))
+ return location_fuzziness_start_unknown;
+ if (!g_ascii_strcasecmp(string, "EXTENT_UNKNOWN"))
+ return location_fuzziness_extent_unknown;
+ }
+ return location_fuzziness_none;
+}
+
+const char * location_fuzziness_to_string(enum location_fuzziness this_) {
+ switch (this_) {
+ case location_fuzziness_low_res:
+ return "LOW_RES";
+ case location_fuzziness_end_unknown:
+ return "END_UNKNOWN";
+ case location_fuzziness_start_unknown:
+ return "START_UNKNOWN";
+ case location_fuzziness_extent_unknown:
+ return "EXTENT_UNKNOWN";
+ default:
+ return NULL;
+ }
+}
+
+enum location_ramps location_ramps_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "ALL_RAMPS"))
+ return location_ramps_all;
+ if (!g_ascii_strcasecmp(string, "ENTRY_RAMP"))
+ return location_ramps_entry;
+ if (!g_ascii_strcasecmp(string, "EXIT_RAMP"))
+ return location_ramps_exit;
+ }
+ return location_ramps_none;
+}
+
+const char * location_ramps_to_string(enum location_ramps this_) {
+ switch (this_) {
+ case location_ramps_none:
+ return "NONE";
+ case location_ramps_all:
+ return "ALL_RAMPS";
+ case location_ramps_entry:
+ return "ENTRY_RAMP";
+ case location_ramps_exit:
+ return "EXIT_RAMP";
+ default:
+ return NULL;
+ }
+}
+
+enum si_class si_class_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "PLACE"))
+ return si_class_place;
+ if (!g_ascii_strcasecmp(string, "TENDENCY"))
+ return si_class_tendency;
+ if (!g_ascii_strcasecmp(string, "VEHICLE"))
+ return si_class_vehicle;
+ }
+ return si_class_invalid;
+}
+
+const char * si_class_to_string(enum si_class this_) {
+ switch (this_) {
+ case si_class_place:
+ return "PLACE";
+ case si_class_tendency:
+ return "TENDENCY";
+ case si_class_vehicle:
+ return "VEHICLE";
+ default:
+ return "INVALID";
+ }
+}
+
+enum si_type si_type_new(char * string) {
+ if (string) {
+ if (!g_ascii_strcasecmp(string, "S_PLACE_BRIDGE"))
+ return si_place_bridge;
+ if (!g_ascii_strcasecmp(string, "S_PLACE_RAMP"))
+ return si_place_ramp;
+ if (!g_ascii_strcasecmp(string, "S_PLACE_ROADWORKS"))
+ return si_place_roadworks;
+ if (!g_ascii_strcasecmp(string, "S_PLACE_TUNNEL"))
+ return si_place_tunnel;
+ if (!g_ascii_strcasecmp(string, "S_TENDENCY_QUEUE_DECREASING"))
+ return si_tendency_queue_decreasing;
+ if (!g_ascii_strcasecmp(string, "S_TENDENCY_QUEUE_INCREASING"))
+ return si_tendency_queue_increasing;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_ALL"))
+ return si_vehicle_all;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_BUS"))
+ return si_vehicle_bus;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_CAR"))
+ return si_vehicle_car;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_CAR_WITH_CARAVAN"))
+ return si_vehicle_car_with_caravan;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_CAR_WITH_TRAILER"))
+ return si_vehicle_car_with_trailer;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_HAZMAT"))
+ return si_vehicle_hazmat;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_HGV"))
+ return si_vehicle_hgv;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_MOTOR"))
+ return si_vehicle_motor;
+ if (!g_ascii_strcasecmp(string, "S_VEHICLE_WITH_TRAILER"))
+ return si_vehicle_with_trailer;
+ }
+ return si_invalid;
+}
+
+const char * si_type_to_string(enum si_type this_) {
+ switch (this_) {
+ case si_place_bridge:
+ return "S_PLACE_BRIDGE";
+ case si_place_ramp:
+ return "S_PLACE_RAMP";
+ case si_place_roadworks:
+ return "S_PLACE_ROADWORKS";
+ case si_place_tunnel:
+ return "S_PLACE_TUNNEL";
+ case si_tendency_queue_decreasing:
+ return "S_TENDENCY_QUEUE_DECREASING";
+ case si_tendency_queue_increasing:
+ return "S_TENDENCY_QUEUE_INCREASING";
+ case si_vehicle_all:
+ return "S_VEHICLE_ALL";
+ case si_vehicle_bus:
+ return "S_VEHICLE_BUS";
+ case si_vehicle_car:
+ return "S_VEHICLE_CAR";
+ case si_vehicle_car_with_caravan:
+ return "S_VEHICLE_CAR_WITH_CARAVAN";
+ case si_vehicle_car_with_trailer:
+ return "S_VEHICLE_CAR_WITH_TRAILER";
+ case si_vehicle_hazmat:
+ return "S_VEHICLE_HAZMAT";
+ case si_vehicle_hgv:
+ return "S_VEHICLE_HGV";
+ case si_vehicle_motor:
+ return "S_VEHICLE_MOTOR";
+ case si_vehicle_with_trailer:
+ return "S_VEHICLE_WITH_TRAILER";
+ default:
+ return "INVALID";
+ }
+}
+
+struct traffic_point * traffic_point_new(float lon, float lat, char * junction_name, char * junction_ref,
+ char * tmc_id) {
+ struct traffic_point * ret;
+
+ ret = g_new0(struct traffic_point, 1);
+ ret->coord.lat = lat;
+ ret->coord.lng = lon;
+ ret->junction_name = junction_name ? g_strdup(junction_name) : NULL;
+ ret->junction_ref = junction_ref ? g_strdup(junction_ref) : NULL;
+ ret->tmc_id = tmc_id ? g_strdup(tmc_id) : NULL;
+ return ret;
+}
+
+struct traffic_point * traffic_point_new_short(float lon, float lat) {
+ return traffic_point_new(lon, lat, NULL, NULL, NULL);
+}
+
+void traffic_point_destroy(struct traffic_point * this_) {
+ if (this_->junction_name)
+ g_free(this_->junction_name);
+ if (this_->junction_ref)
+ g_free(this_->junction_ref);
+ if (this_->tmc_id)
+ g_free(this_->tmc_id);
+ g_free(this_);
+}
+
+// TODO split CID/LTN?
+struct traffic_location * traffic_location_new(struct traffic_point * at, struct traffic_point * from,
+ struct traffic_point * to, struct traffic_point * via, struct traffic_point * not_via,
+ char * destination, char * direction, enum location_dir directionality,
+ enum location_fuzziness fuzziness, enum location_ramps ramps, enum item_type road_type,
+ char * road_name, char * road_ref, char * tmc_table, int tmc_direction) {
+ struct traffic_location * ret;
+
+ ret = g_new0(struct traffic_location, 1);
+ ret->at = at;
+ ret->from = from;
+ ret->to = to;
+ ret->via = via;
+ ret->not_via = not_via;
+ ret->destination = destination ? g_strdup(destination) : NULL;
+ ret->direction = direction ? g_strdup(direction) : NULL;
+ ret->directionality = directionality;
+ ret->fuzziness = fuzziness;
+ ret->ramps = ramps;
+ ret->road_type = road_type;
+ ret->road_name = road_name ? g_strdup(road_name) : NULL;
+ ret->road_ref = road_ref ? g_strdup(road_ref) : NULL;
+ ret->tmc_table = tmc_table ? g_strdup(tmc_table) : NULL;
+ ret->tmc_direction = tmc_direction;
+ ret->priv = g_new0(struct traffic_location_priv, 1);
+ ret->priv->sw = NULL;
+ ret->priv->ne = NULL;
+ return ret;
+}
+
+struct traffic_location * traffic_location_new_short(struct traffic_point * at, struct traffic_point * from,
+ struct traffic_point * to, struct traffic_point * via, struct traffic_point * not_via,
+ enum location_dir directionality, enum location_fuzziness fuzziness) {
+ return traffic_location_new(at, from, to, via, not_via, NULL, NULL, directionality, fuzziness,
+ location_ramps_none, type_line_unspecified, NULL, NULL, NULL, 0);
+}
+
+void traffic_location_destroy(struct traffic_location * this_) {
+ if (this_->at)
+ traffic_point_destroy(this_->at);
+ if (this_->from)
+ traffic_point_destroy(this_->from);
+ if (this_->to)
+ traffic_point_destroy(this_->to);
+ if (this_->via)
+ traffic_point_destroy(this_->via);
+ if (this_->not_via)
+ traffic_point_destroy(this_->not_via);
+ if (this_->destination)
+ g_free(this_->destination);
+ if (this_->direction)
+ g_free(this_->direction);
+ if (this_->road_name)
+ g_free(this_->road_name);
+ if (this_->road_ref)
+ g_free(this_->road_ref);
+ if (this_->tmc_table)
+ g_free(this_->tmc_table);
+ if (this_->priv->sw)
+ g_free(this_->priv->sw);
+ if (this_->priv->ne)
+ g_free(this_->priv->ne);
+ g_free(this_->priv);
+ g_free(this_);
+}
+
+struct traffic_suppl_info * traffic_suppl_info_new(enum si_class si_class, enum si_type type,
+ struct quantifier * quantifier) {
+ struct traffic_suppl_info * ret;
+ ret = g_new0(struct traffic_suppl_info, 1);
+ ret->si_class = si_class;
+ ret->type = type;
+ ret->quantifier = quantifier ? g_memdup(quantifier, sizeof(struct quantifier)) : NULL;
+ return ret;
+}
+
+void traffic_suppl_info_destroy(struct traffic_suppl_info * this_) {
+ if (this_->quantifier)
+ g_free(this_->quantifier);
+ g_free(this_);
+}
+
+struct traffic_event * traffic_event_new(enum event_class event_class, enum event_type type,
+ int length, int speed, struct quantifier * quantifier, int si_count, struct traffic_suppl_info ** si) {
+ struct traffic_event * ret;
+
+ ret = g_new0(struct traffic_event, 1);
+ ret->event_class = event_class;
+ ret->type = type;
+ ret->length = length;
+ ret->speed = speed;
+ ret->quantifier = quantifier ? g_memdup(quantifier, sizeof(struct quantifier)) : NULL;
+ if (si_count && si) {
+ ret->si_count = si_count;
+ ret->si = g_memdup(si, sizeof(struct traffic_suppl_info *) * si_count);
+ } else {
+ ret->si_count = 0;
+ ret->si = NULL;
+ }
+ return ret;
+}
+
+struct traffic_event * traffic_event_new_short(enum event_class event_class, enum event_type type) {
+ return traffic_event_new(event_class, type, -1, INT_MAX, NULL, 0, NULL);
+}
+
+void traffic_event_destroy(struct traffic_event * this_) {
+ int i;
+
+ if (this_->quantifier)
+ g_free(this_->quantifier);
+ if (this_->si && this_->si_count) {
+ for (i = 0; i < this_->si_count; i++)
+ traffic_suppl_info_destroy(this_->si[i]);
+ g_free(this_->si);
+ }
+ g_free(this_);
+}
+
+void traffic_event_add_suppl_info(struct traffic_event * this_, struct traffic_suppl_info * si) {
+ struct traffic_suppl_info ** si_new;
+
+ if (this_->si_count && this_->si) {
+ si_new = g_new0(struct traffic_suppl_info *, this_->si_count + 1);
+ memcpy(si_new, this_->si, sizeof(struct traffic_suppl_info *) * this_->si_count);
+ si_new[this_->si_count] = si;
+ g_free(this_->si);
+ this_->si = si_new;
+ this_->si_count++;
+ } else {
+ this_->si = g_new0(struct traffic_suppl_info *, 1);
+ this_->si[0] = si;
+ this_->si_count = 1;
+ }
+}
+
+struct traffic_suppl_info * traffic_event_get_suppl_info(struct traffic_event * this_, int index) {
+ if (this_->si && (index < this_->si_count))
+ return this_->si[index];
+ else
+ return NULL;
+}
+
+struct traffic_message * traffic_message_new(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, time_t start_time, time_t end_time, int is_cancellation, int is_forecast,
+ int replaced_count, char ** replaces, struct traffic_location * location, int event_count,
+ struct traffic_event ** events) {
+ struct traffic_message * ret;
+
+ ret = g_new0(struct traffic_message, 1);
+ ret->id = g_strdup(id);
+ ret->receive_time = receive_time;
+ ret->update_time = update_time;
+ ret->expiration_time = expiration_time;
+ ret->start_time = start_time;
+ ret->end_time = end_time;
+ ret->is_cancellation = is_cancellation;
+ ret->is_forecast = is_forecast;
+ if (replaced_count && replaces) {
+ ret->replaced_count = replaced_count;
+ ret->replaces = g_memdup(replaces, sizeof(char *) * replaced_count);
+ } else {
+ ret->replaced_count = 0;
+ ret->replaces = NULL;
+ }
+ ret->location = location;
+ if (event_count && events) {
+ ret->event_count = event_count;
+ ret->events = g_memdup(events, sizeof(struct traffic_event *) * event_count);
+ }
+ ret->priv = g_new0(struct traffic_message_priv, 1);
+ ret->priv->items = NULL;
+ return ret;
+}
+
+struct traffic_message * traffic_message_new_short(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, int is_forecast, struct traffic_location * location,
+ int event_count, struct traffic_event ** events) {
+ return traffic_message_new(id, receive_time, update_time, expiration_time, 0, 0, 0,
+ is_forecast, 0, NULL, location, event_count, events);
+}
+
+struct traffic_message * traffic_message_new_single_event(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, int is_forecast, struct traffic_location * location,
+ enum event_class event_class, enum event_type type) {
+ struct traffic_event * event;
+ struct traffic_event ** events;
+
+ event = traffic_event_new_short(event_class, type);
+ events = g_new0(struct traffic_event *, 1);
+ events[0] = event;
+ return traffic_message_new_short(id, receive_time, update_time, expiration_time, is_forecast,
+ location, 1, events);
+ g_free(events);
+}
+
+struct traffic_message * traffic_message_new_cancellation(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, struct traffic_location * location) {
+ return traffic_message_new(id, receive_time, update_time, expiration_time, 0, 0, 1,
+ 0, 0, NULL, location, 0, NULL);
+}
+
+void traffic_message_destroy(struct traffic_message * this_) {
+ int i;
+ struct item ** items;
+
+ g_free(this_->id);
+ if (this_->replaces) {
+ for (i = 0; i < this_->replaced_count; i++)
+ g_free(this_->replaces[i]);
+ g_free(this_->replaces);
+ }
+ if (this_->location)
+ traffic_location_destroy(this_->location);
+ if (this_->events && this_->event_count) {
+ for (i = 0; i < this_->event_count; i++)
+ traffic_event_destroy(this_->events[i]);
+ g_free(this_->events);
+ }
+ if (this_->priv->items) {
+ for (items = this_->priv->items; *items; items++)
+ *items = tm_item_unref(*items);
+ g_free(this_->priv->items);
+ }
+ g_free(this_->priv);
+ g_free(this_);
+}
+
+void traffic_message_add_event(struct traffic_message * this_, struct traffic_event * event) {
+ struct traffic_event ** events_new;
+
+ events_new = g_new0(struct traffic_event *, this_->event_count + 1);
+ memcpy(events_new, this_->events, sizeof(struct traffic_event *) * this_->event_count);
+ events_new[this_->event_count] = event;
+ g_free(this_->events);
+ this_->events = events_new;
+ this_->event_count++;
+}
+
+struct traffic_event * traffic_message_get_event(struct traffic_message * this_, int index) {
+ if (this_->events && (index < this_->event_count))
+ return this_->events[index];
+ else
+ return NULL;
+}
+
+struct item ** traffic_message_get_items(struct traffic_message * this_) {
+ struct item ** ret;
+ struct item ** in;
+ int i;
+ if (!this_->priv->items) {
+ ret = g_new0(struct item *, 1);
+ return ret;
+ }
+ in = this_->priv->items;
+ for (i = 1; *in; i++)
+ in++;
+ ret = g_new0(struct item *, i);
+ memcpy(ret, this_->priv->items, sizeof(struct item *) * i);
+ return ret;
+}
+
+/**
+ * @brief Registers a new traffic map plugin
+ *
+ * @param meth Receives the map methods
+ * @param attrs The attributes for the map
+ * @param cbl
+ *
+ * @return A pointer to a `map_priv` structure for the map
+ */
+static struct map_priv * traffic_map_new(struct map_methods *meth, struct attr **attrs, struct callback_list *cbl) {
+ struct map_priv *ret;
+
+ ret = g_new0(struct map_priv, 1);
+ *meth = traffic_map_meth;
+
+ return ret;
+}
+
+void traffic_init(void) {
+ dbg(lvl_debug, "enter");
+ plugin_register_category_map("traffic", traffic_map_new);
+}
+
+struct map * traffic_get_map(struct traffic *this_) {
+ struct attr_iter *iter;
+ struct attr *attr;
+ struct traffic * traffic;
+ char * filename;
+ struct traffic_message ** messages;
+ struct traffic_message ** cur_msg;
+
+ if (!this_->map) {
+ /* see if any of the other instances has already created a map */
+ attr = g_new0(struct attr, 1);
+ iter = navit_attr_iter_new();
+ while (navit_get_attr(this_->navit, attr_traffic, attr, iter)) {
+ traffic = (struct traffic *) attr->u.navit_object;
+ if (traffic->map)
+ this_->map = traffic->map;
+ }
+ navit_attr_iter_destroy(iter);
+ g_free(attr);
+ }
+
+ if (!this_->map) {
+ /* no map yet, create a new one */
+ struct attr *attrs[4];
+ struct attr a_type,data,a_description;
+ a_type.type = attr_type;
+ a_type.u.str = "traffic";
+ data.type = attr_data;
+ data.u.str = "";
+ a_description.type = attr_description;
+ a_description.u.str = "Traffic";
+
+ attrs[0] = &a_type;
+ attrs[1] = &data;
+ attrs[2] = &a_description;
+ attrs[3] = NULL;
+
+ this_->map = map_new(NULL, attrs);
+ navit_object_ref((struct navit_object *) this_->map);
+
+ /* populate map with previously stored messages */
+ filename = g_strjoin(NULL, navit_get_user_data_directory(TRUE), "/traffic.xml", NULL);
+ messages = traffic_get_messages_from_xml_file(this_, filename);
+ g_free(filename);
+
+ if (messages) {
+ for (cur_msg = messages; *cur_msg; cur_msg++)
+ this_->shared->message_queue = g_list_append(this_->shared->message_queue, *cur_msg);
+ g_free(messages);
+ if (this_->shared->message_queue) {
+ if (!this_->idle_cb)
+ this_->idle_cb = callback_new_2(callback_cast(traffic_process_messages_int),
+ this_, PROCESS_MESSAGES_NO_DUMP_STORE);
+ if (!this_->idle_ev)
+ this_->idle_ev = event_add_idle(50, this_->idle_cb);
+ }
+ }
+ }
+
+ return this_->map;
+}
+
+/**
+ * @brief Reads previously stored traffic messages from parsed XML data.
+ *
+ * @param state The XML parser state after parsing the XML data
+ *
+ * @return A `NULL`-terminated pointer array. Each element points to one `struct traffic_message`.
+ * `NULL` is returned (rather than an empty pointer array) if there are no messages to report.
+ */
+static struct traffic_message ** traffic_get_messages_from_parsed_xml(struct xml_state * state) {
+ struct traffic_message ** ret = NULL;
+ int i, count;
+ GList * msg_iter;
+
+ count = g_list_length(state->messages);
+ if (count)
+ ret = g_new0(struct traffic_message *, count + 1);
+ msg_iter = state->messages;
+ for (i = 0; i < count; i++) {
+ ret[i] = (struct traffic_message *) msg_iter->data;
+ msg_iter = g_list_next(msg_iter);
+ }
+ g_list_free(state->messages);
+ return ret;
+}
+
+struct traffic_message ** traffic_get_messages_from_xml_file(struct traffic * this_, char * filename) {
+ struct traffic_message ** ret = NULL;
+ struct xml_state state;
+ int read_success = 0;
+
+ if (filename) {
+ memset(&state, 0, sizeof(struct xml_state));
+ read_success = xml_parse_file(filename, &state, traffic_xml_start, traffic_xml_end, traffic_xml_text);
+ if (read_success) {
+ ret = traffic_get_messages_from_parsed_xml(&state);
+ } else {
+ dbg(lvl_error,"could not retrieve stored traffic messages");
+ }
+ } /* if (traffic_filename) */
+ return ret;
+}
+
+struct traffic_message ** traffic_get_messages_from_xml_string(struct traffic * this_, char * xml) {
+ struct traffic_message ** ret = NULL;
+ struct xml_state state;
+ int read_success = 0;
+
+ if (xml) {
+ memset(&state, 0, sizeof(struct xml_state));
+ read_success = xml_parse_text(xml, &state, traffic_xml_start, traffic_xml_end, traffic_xml_text);
+ if (read_success) {
+ ret = traffic_get_messages_from_parsed_xml(&state);
+ } else {
+ dbg(lvl_error,"no data supplied");
+ }
+ } /* if (xml) */
+ return ret;
+}
+
+struct traffic_message ** traffic_get_stored_messages(struct traffic *this_) {
+ struct traffic_message ** ret = g_new0(struct traffic_message *, g_list_length(this_->shared->messages) + 1);
+ struct traffic_message ** out = ret;
+ GList * in = this_->shared->messages;
+
+ while (in) {
+ *out = (struct traffic_message *) in->data;
+ in = g_list_next(in);
+ out++;
+ }
+
+ return ret;
+}
+
+void traffic_process_messages(struct traffic * this_, struct traffic_message ** messages) {
+ struct traffic_message ** cur_msg;
+
+ for (cur_msg = messages; cur_msg && *cur_msg; cur_msg++)
+ this_->shared->message_queue = g_list_append(this_->shared->message_queue, *cur_msg);
+ if (this_->shared->message_queue) {
+ if (this_->idle_ev)
+ event_remove_idle(this_->idle_ev);
+ if (this_->idle_cb)
+ callback_destroy(this_->idle_cb);
+ this_->idle_cb = callback_new_2(callback_cast(traffic_process_messages_int), this_, 0);
+ this_->idle_ev = event_add_idle(50, this_->idle_cb);
+ }
+}
+
+void traffic_set_mapset(struct traffic *this_, struct mapset *ms) {
+ this_->ms = ms;
+}
+
+void traffic_set_route(struct traffic *this_, struct route *rt) {
+ this_->rt = rt;
+}
+
+struct object_func traffic_func = {
+ attr_traffic,
+ (object_func_new)traffic_new,
+ (object_func_get_attr)navit_object_get_attr,
+ (object_func_iter_new)navit_object_attr_iter_new,
+ (object_func_iter_destroy)navit_object_attr_iter_destroy,
+ (object_func_set_attr)navit_object_set_attr,
+ (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_dup)NULL,
+ (object_func_ref)navit_object_ref,
+ (object_func_unref)navit_object_unref,
+};
diff --git a/navit/traffic.h b/navit/traffic.h
new file mode 100644
index 000000000..f1c8f8b34
--- /dev/null
+++ b/navit/traffic.h
@@ -0,0 +1,996 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2017 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/** @file
+ *
+ * @brief Contains exported code for traffic.c, the traffic module
+ *
+ * This file contains types and function prototypes exported from the traffic module, which enables
+ * Navit to route around traffic problems.
+ *
+ * The traffic module consists of two parts:
+ *
+ * The traffic core interacts with the Navit core and converts traffic messages into traffic
+ * distortions (future versions may add support for other traffic information).
+ *
+ * The traffic backends obtain traffic information from a source of their choice (e.g. from a TMC
+ * receiver or a network service), translate them into Navit data structures and report them to the
+ * traffic plugin.
+ *
+ * Traffic messages and related structures are considered immutable once created (when information
+ * changes, the old message is replaced with a new one). For this reason, there are very few data
+ * manipulation methods. Those that exist are intended for the creation of new messages rather than
+ * for extensive manipulation.
+ *
+ * As a rule, responsibility for freeing up any `traffic_*` instances normally lies with the
+ * traffic plugin, which frees messages as they expire or are replaced. Since this also frees all child
+ * data structures, traffic backends will seldom need to call any of the destructors. The only case in
+ * which this would be necessary is if a backend has instantiated an object which is not going to be
+ * used (i.e. attached to a parent object or, in the case of `traffic_message`, reported to the
+ * traffic plugin: these need to be freed up manually by calling the destructor of the topmost object in
+ * the hierarchy.
+ *
+ * Any other references passed in functions (including pointer arrays and `quantifier` instances)
+ * must be freed up by the caller. This is safe to do as soon as the function returns.
+ */
+
+#ifndef NAVIT_TRAFFIC_H
+#define NAVIT_TRAFFIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Classes for events.
+ */
+/* If additional event classes are introduced, traffic_event_is_valid() must be adapted to recognize them. */
+enum event_class {
+ event_class_invalid = 0, /*!< Invalid event which should be ignored */
+ event_class_congestion, /*!< Traffic congestion, typically indicating the approximate speed */
+ event_class_delay, /*!< Delays, typically indicating the amount of extra waiting time */
+ event_class_restriction, /*!< Temporary traffic restrictions, such as road or lane closures or size,
+ * weight or access restrictions */
+};
+
+/**
+ * @brief Event types.
+ */
+/* If additional events are introduced, remember to do the following:
+ * - If the events belong to an existing class, insert them right after the last existing event for that class.
+ * - If the events belong to a new class, insert them at the end of the list.
+ * - Always keep events of the same class together.
+ * - After adding events (of any class) at the end of the list, adapt traffic_event_is_valid() to recognize them. */
+enum event_type {
+ event_invalid = 0, /*!< Invalid event which should be ignored */
+ event_congestion_cleared, /*!< Traffic congestion cleared */
+ event_congestion_forecast_withdrawn, /*!< Traffic congestion forecast withdrawn */
+ event_congestion_heavy_traffic, /*!< Heavy traffic with average speeds of `speed` */
+ event_congestion_long_queue, /*!< Long queues with average speeds of `speed` */
+ event_congestion_none, /*!< No problems to report */
+ event_congestion_normal_traffic, /*!< Traffic has returned to normal */
+ event_congestion_queue, /*!< Queuing traffic with average speeds of `speed` */
+ event_congestion_queue_likely, /*!< Danger of queuing traffic with average speeds
+ * of `speed` */
+ event_congestion_slow_traffic, /*!< Slow traffic with average speeds of `speed` */
+ event_congestion_stationary_traffic, /*!< Stationary traffic (frequent standstills) */
+ event_congestion_stationary_traffic_likely, /*!< Danger of stationary traffic */
+ event_congestion_traffic_building_up, /*!< Traffic building up with average speeds of
+ * `speed` */
+ event_congestion_traffic_congestion, /*!< Traffic congestion with average speeds of
+ * `speed` */
+ event_congestion_traffic_easing, /*!< Traffic easing */
+ event_congestion_traffic_flowing_freely, /*!< Traffic flowing freely with average speeds
+ * of `speed` */
+ event_congestion_traffic_heavier_than_normal, /*!< Traffic heavier than normal with average
+ * speeds of `speed` */
+ event_congestion_traffic_lighter_than_normal, /*!< Traffic lighter than normal with average
+ * speeds of `speed` */
+ event_congestion_traffic_much_heavier_than_normal, /*!< Traffic very much heavier than normal with
+ * average speeds of `speed` (increased density
+ * but no significant decrease in speed) */
+ event_congestion_traffic_problem, /*!< Traffic problem */
+ event_delay_clearance, /*!< Delays cleared */
+ event_delay_delay, /*!< Delays up to `q_timespan` */
+ event_delay_delay_possible, /*!< Delays up to `q_timespan` possible */
+ event_delay_forecast_withdrawn, /*!< Delay forecast withdrawn */
+ event_delay_long_delay, /*!< Long delays up to `q_timespan` */
+ event_delay_several_hours, /*!< Delays of several hours */
+ event_delay_uncertain_duration, /*!< Delays of uncertain duration */
+ event_delay_very_long_delay, /*!< Very long delays up to `q_timespan` */
+ event_restriction_access_restrictions_lifted, /*!< Traffic restrictions lifted: reopened for all
+ * traffic, other restrictions (overtaking etc.)
+ * remain in place */
+ event_restriction_all_carriageways_cleared, /*!< All carriageways cleared */
+ event_restriction_all_carriageways_reopened, /*!< All carriageways reopened */
+ event_restriction_batch_service, /*!< Batch service (to limit the amount of traffic
+ * passing through a section, unlike single
+ * alternate line traffic) */
+ event_restriction_blocked, /*!< Blocked (refers to the entire road; separate
+ * codes exist for blockages of individual lanes
+ * or carriageways) */
+ event_restriction_blocked_ahead, /*!< Blocked ahead (at a point beyond the
+ * indicated location) */
+ event_restriction_carriageway_blocked, /*!< Carriageway blocked (main carriageway, unless
+ * otherwise indicated in supplementary information) */
+ event_restriction_carriageway_closed, /*!< Carriageway closed (main carriageway, unless
+ * otherwise indicated in supplementary information) */
+ event_restriction_contraflow, /*!< Contraflow */
+ event_restriction_closed, /*!< Closed until `q_time` (refers to the entire
+ * road; separate codes exist for closures of
+ * individual lanes or carriageways) */
+ event_restriction_closed_ahead, /*!< Closed ahead (at a point beyond the indicated
+ * location) */
+ event_restriction_entry_blocked, /*!< `q_int` th entry slip road blocked */
+ event_restriction_entry_reopened, /*!< Entry reopened */
+ event_restriction_exit_blocked, /*!< `q_int` th exit slip road blocked */
+ event_restriction_exit_reopened, /*!< Exit reopened */
+ event_restriction_intermittent_closures, /*!< Intermittent short term closures */
+ event_restriction_lane_blocked, /*!< `q:int` lanes blocked */
+ event_restriction_lane_closed, /*!< `q:int` lanes closed */
+ event_restriction_open, /*!< Open */
+ event_restriction_ramp_blocked, /*!< Ramps blocked */
+ event_restriction_ramp_closed, /*!< Ramps closed */
+ event_restriction_ramp_reopened, /*!< Ramps reopened */
+ event_restriction_reduced_lanes, /*!< Carriageway reduced from `q_ints[1]` lanes to `q_int[0]`
+ * lanes (quantifiers are currently not implemented for this
+ * event type) */
+ event_restriction_reopened, /*!< Reopened */
+ event_restriction_road_cleared, /*!< Road cleared */
+ event_restriction_single_alternate_line_traffic, /*!< Single alternate line traffic (because the
+ * affected stretch of road can only be used in
+ * one direction at a time, different from batch
+ * service) */
+ event_restriction_speed_limit, /*!< Speed limit `speed` in force */
+ event_restriction_speed_limit_lifted, /*!< Speed limit lifted */
+};
+
+/**
+ * @brief The directionality of a location.
+ */
+enum location_dir {
+ location_dir_one = 1, /*!< Indicates a unidirectional location. */
+ location_dir_both = 2, /*!< Indicates a bidirectional location. */
+};
+
+/**
+ * @brief The fuzziness of a location.
+ */
+enum location_fuzziness {
+ location_fuzziness_none = 0, /*!< No fuzziness information is given. */
+ location_fuzziness_low_res, /*!< Locations are constrained to a predefined table; the actual
+ * extent of the condition may be shorter than indicated. */
+ location_fuzziness_end_unknown, /*!< The end is unknown, e.g. a traffic jam reported by a driver
+ * who has just entered it. */
+ location_fuzziness_start_unknown, /*!< The start is unknown, e.g. a traffic jam reported by a driver
+ who has just passed the obstruction which caused it. */
+ location_fuzziness_extent_unknown, /*!< Start and end are unknown, e.g. a traffic jam reported by a
+ driver who is in the middle of it. */
+};
+
+/**
+ * @brief Whether a location refers to the main carriageway or the ramps.
+ */
+enum location_ramps {
+ location_ramps_none = 0, /*!< The location refers to the carriageways of the main road. */
+ location_ramps_all, /*!< The location refers to the entry and exit ramps, not the main carriageway. */
+ location_ramps_entry, /*!< The location refers to the entry ramps only, not the main carriageway. */
+ location_ramps_exit, /*!< The location refers to the exit ramps only, not the main carriageway. */
+};
+
+/**
+ * @brief Classes for supplementary information items.
+ */
+enum si_class {
+ si_class_invalid = 0, /*!< Invalid supplementary information item which should be ignored */
+ si_class_place, /*!< Qualifiers specifying the place(s) to which the event refers */
+ si_class_tendency, /*!< Traffic density development */
+ si_class_vehicle, /*!< Specifies categories of vehicles to which the event applies */
+};
+
+/**
+ * @brief Supplementary information types.
+ */
+enum si_type {
+ si_invalid = 0, /*!< Invalid supplementary information item which should be ignored */
+ si_place_bridge, /*!< On bridges */
+ si_place_ramp, /*!< On ramps (entry/exit) */
+ si_place_roadworks, /*!< In the roadworks area */
+ si_place_tunnel, /*!< In tunnels */
+ si_tendency_queue_decreasing, /*!< Traffic queue length decreasing (average rate in optional `q_speed`) */
+ si_tendency_queue_increasing, /*!< Traffic queue length increasing (average rate in optional `q_speed`) */
+ si_vehicle_all, /*!< For all vehicles */
+ si_vehicle_bus, /*!< For buses only (TODO currently supported for public buses only) */
+ si_vehicle_car, /*!< For cars only */
+ si_vehicle_car_with_caravan, /*!< For cars with caravans only (TODO currently not supported) */
+ si_vehicle_car_with_trailer, /*!< For cars with trailers only (TODO currently not supported) */
+ si_vehicle_hazmat, /*!< For hazardous loads only */
+ si_vehicle_hgv, /*!< For heavy trucks only */
+ si_vehicle_motor, /*!< For all motor vehicles */
+ si_vehicle_with_trailer, /*!< For vehicles with trailers only (TODO currently not supported) */
+};
+
+struct traffic_priv;
+struct traffic_location_priv;
+struct traffic_message_priv;
+
+/**
+ * @brief Holds all functions a traffic plugin has to implement to be usable
+ *
+ * This structure holds pointers to a traffic plugin's functions which navit's core will call
+ * to communicate with the plugin.
+ */
+struct traffic_methods {
+ struct traffic_message **(* get_messages)(struct traffic_priv * this_); /**< Retrieves new messages from the traffic plugin */
+};
+
+/**
+ * @brief A point on the road.
+ *
+ * This can either be a point location or an endpoint of a linear location. It specifies a coordinate
+ * pair and can optionally be supplemented with a junction name and/or number where applicable.
+ */
+struct traffic_point {
+ struct coord_geo coord; /*!< The coordinates of this point, as supplied by the source. These may
+ * deviate somewhat from the coordinates on the map. */
+ char * junction_name; /*!< The name of the motorway junction this point refers to. */
+ char * junction_ref; /*!< The reference number of the motorway junction this point refers to. */
+ char * tmc_id; /*!< The TMC identifier of the point, if the location was obtained via TMC.
+ * This can be an LCID (12345) or a combination of an LCID and an offset
+ * (12345+2, i.e. an offset of 2 points in positive direction from 12345).
+ * The offset is typically used with the secondary location in TMC. */
+};
+
+/**
+ * @brief Location data for a traffic message.
+ *
+ * Locations can be either point or linear locations.
+ *
+ * Linear locations are indicated by a pair of points and refer to a stretch of road. The entire
+ * location must be part of the same road, i.e. either the road name or the road reference must be the
+ * same for all affected segments, or all segments must be of the same type and be of a higher order
+ * than any road connecting to them directly.
+ *
+ * Point locations are indicated by a single point, as well as one or two auxiliary points to indicate
+ * direction. Auxiliary points can be omitted if `tmc_table`, `tmc_direction` and
+ * `at->tmc_id` are supplied. However, this will only work if the map has accurate TMC data for
+ * the location, thus it is recommended to supply an auxiliary point nonetheless.
+ *
+ * The order of points is as a driver would encounter them, i.e. first `from`, then `at`,
+ * finally `to`.
+ */
+struct traffic_location {
+ struct traffic_point * at; /*!< The point for a point location, NULL for linear locations. */
+ struct traffic_point * from; /*!< The start of a linear location, or a point before `at`. */
+ struct traffic_point * to; /*!< The end of a linear location, or a point after `at`. */
+ struct traffic_point * via; /*!< A point between `from` and `to`. Required on ring roads
+ * unless `not_via` is used; cannot be used together with `at`. */
+ struct traffic_point * not_via; /*!< A point NOT between `from` and `to`. Required on ring roads
+ * unless `via` is used; cannot be used together with `at`. */
+ char * destination; /*!< A destination, preferably the one given on road signs,
+ * indicating that the message applies only to traffic going in
+ * that direction. Do not use for bidirectional locations. */
+ char * direction; /*!< A compass direction indicating the direction of travel which
+ * this location refers to. Do not use where ambiguous. */
+ enum location_dir directionality; /*!< Indicates whether the message refers to one or both directions
+ * of travel. */
+ enum location_fuzziness fuzziness; /*!< Indicates how precisely the end points are known. */
+ enum location_ramps ramps; /*!< Any value other than `location_ramps_none` implies
+ * that only the specified ramps are affected while the main
+ * road is not. In that case, the `road*` fields refer to
+ * the main road served by the ramp, not the ramp itself. This
+ * is mainly intended for compatibility with TMC, where
+ * junctions with all their ramps are represented by a single
+ * point. Other sources should use coordinate pairs instead. */
+ enum item_type road_type; /*!< The importance of the road within the road network, must be a
+ * road item type. Use `line_unspecified` if not known or
+ * not consistent. */
+ char * road_name; /*!< A road name, if consistent throughout the location. */
+ char * road_ref; /*!< A road number, if consistent throughout the location. */
+ char * tmc_table; /*!< For messages received via TMC, the country identifier (CID)
+ * and location table number (LTN or TABCD) for the location
+ * table to be used for location lookup. The CID is the decimal
+ * number stored in the COUNTRIES and LOCATIONDATASETS tables,
+ * not the hexadecimal code from the PI (known as CCD in TMC). */
+ int tmc_direction; /*!< For messages received via TMC, the direction of the road to
+ * which this message applies (positive or negative). Ignored
+ * for bidirectional messages. */
+ struct traffic_location_priv * priv; /*!< Internal data, not exposed via the API */
+};
+
+/**
+ * @brief A quantifier, which can be used with events and supplementary information.
+ */
+/*
+ * For now, these are various integer types, but other types may be added in the future.
+ */
+struct quantifier {
+ union {
+ int q_duration; /*!< A duration in 1/10 of a second. */
+ int q_int; /*!< An integer. */
+ int q_speed; /*!< A speed in km/h. */
+ unsigned int q_time; /*!< A timestamp in epoch time (seconds elapsed since Jan 1, 1970, 00:00 UTC). */
+ } u;
+};
+
+/**
+ * @brief Extra information supplied with a traffic event.
+ */
+struct traffic_suppl_info {
+ enum si_class si_class; /*!< The supplementary information class (generic category). */
+ enum si_type type; /*!< The supplementary information type, which can be mapped to a
+ * string to be displayed to the user. */
+ struct quantifier * quantifier; /*!< Additional quantifier for supplementary information types
+ * allowing this. Data type and meaning depends on the event type. */
+};
+
+/**
+ * @brief A traffic event.
+ *
+ * An event refers to a condition, its cause or its effect.
+ */
+struct traffic_event {
+ enum event_class event_class; /*!< The event class (generic category). */
+ enum event_type type; /*!< The event type, which can be mapped to a string to be displayed
+ * to the user. */
+ int length; /*!< The length of the affected route in meters. */
+ int speed; /*!< The speed in km/h at which vehicles can expect to pass through the
+ * affected stretch of road (either a temporary speed limit or
+ * average speed in practice, whichever is less), `INT_MAX` if
+ * not set or unknown. */
+ struct quantifier * quantifier; /*!< Additional quantifier for events allowing this. Data type and
+ * meaning depends on the event type. */
+ int si_count; /*!< Number of supplementary information items in `si_count`. */
+ struct traffic_suppl_info ** si; /*!< Points to an array of pointers to supplementary information items. */
+};
+
+/**
+ * @brief A traffic message.
+ *
+ * A message is the atomic element of traffic information, referring to a particular condition at a
+ * given location.
+ *
+ * If no updates are received for a message, it should be discarded after both `expiration_time`
+ * and `end_time` (if specified) have elapsed.
+ */
+struct traffic_message {
+ char * id; /*!< An identifier, which remains stable over the entire lifecycle of the
+ * message. The colon (:) is a reserved character to separate different
+ * levels of source identifiers from each other and from the local
+ * message identifier. */
+ time_t receive_time; /*!< When the message was first received by the source, should be kept
+ * stable across all updates. */
+ time_t update_time; /*!< When the last update to this message was received by the source. */
+ time_t expiration_time; /*!< How long the message should be considered valid.*/
+ time_t start_time; /*!< When the condition is expected to begin (optional, 0 if not set). */
+ time_t end_time; /*!< How long the condition is expected to last (optional, 0 if not set). */
+ int is_cancellation; /*!< If true, this message is a cancellation message, indicating that
+ * existing messages with the same ID should be deleted or no longer
+ * considered current. All other attributes of a cancellation message
+ * should be ignored. */
+ int is_forecast; /*!< If false, the message describes a current situation. If true, it
+ * describes an expected situation in the future. */
+ int replaced_count; /*!< The number of entries in `replaces`. */
+ char ** replaces; /*!< Points to an array of identifiers of messages which the current
+ * message replaces. */
+ struct traffic_location * location; /*!< The location to which this message refers. */
+ int event_count; /*!< The number of events in `events`. */
+ struct traffic_event ** events; /*!< Points to an array of pointers to the events for this message. */
+ struct traffic_message_priv * priv; /*!< Internal data, not exposed via the API */
+};
+
+struct map;
+struct mapset;
+struct traffic;
+
+/**
+ * @brief Creates an event class from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum event_class`, or `event_class_invalid` if `string` does not match a
+ * known identifier
+ */
+enum event_class event_class_new(char * string);
+
+/**
+ * @brief Translates an event class to its string representation.
+ *
+ * @return The string representation of the event class
+ */
+const char * event_class_to_string(enum event_class this_);
+
+/**
+ * @brief Creates an event type from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum event_type`, or `event_invalid` if `string` does not match a known
+ * identifier
+ */
+enum event_type event_type_new(char * string);
+
+/**
+ * @brief Translates an event type to its string representation.
+ *
+ * @return The string representation of the event type
+ */
+const char * event_type_to_string(enum event_type this_);
+
+/**
+ * @brief Creates an item type from a road type.
+ *
+ * This is guaranteed to return either a routable type (i.e. `route_item_first <= type <= route_item_last`)
+ * or `type_line_unspecified`. The latter is also returned if `string` refers to a Navit item type which
+ * is not routable.
+ *
+ * @param string A TraFF road type or the string representation of a Navit item type
+ * @param is_urban Whether the road is in a built-up area (ignored if `string` is a Navit item type)
+ *
+ * @return The corresponding `enum item_type`, or `type_line_unspecified` if `string` does not match a
+ * known and routable identifier
+ */
+enum item_type item_type_from_road_type(char * string, int is_urban);
+
+/**
+ * @brief Creates a location directionality from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum location_dir`, or `location_dir_both` if `string` does
+ * not match a known identifier
+ */
+enum location_dir location_dir_new(char * string);
+
+/**
+ * @brief Creates a location fuzziness from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum location_fuzziness`, or `location_fuzziness_none` if `string` does
+ * not match a known identifier
+ */
+enum location_fuzziness location_fuzziness_new(char * string);
+
+/**
+ * @brief Translates location fuzziness to its string representation.
+ *
+ * @return The string representation of the location fuzziness, or NULL for `location_fuzziness_none`
+ */
+const char * location_fuzziness_to_string(enum location_fuzziness this_);
+
+/**
+ * @brief Creates an `enum location_ramps` from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum location_ramps`, or `location_ramps_none` if `string` does
+ * not match a known identifier
+ */
+enum location_ramps location_ramps_new(char * string);
+
+/**
+ * @brief Translates an `enum location_ramps` to its string representation.
+ *
+ * @return The string representation
+ */
+const char * location_ramps_to_string(enum location_ramps this_);
+
+/**
+ * @brief Creates a supplementary information class from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum si_class`, or `si_class_invalid` if `string` does not match a
+ * known identifier
+ */
+enum si_class si_class_new(char * string);
+
+/**
+ * @brief Translates a supplementary information class to its string representation.
+ *
+ * @return The string representation of the supplementary information class
+ */
+const char * si_class_to_string(enum si_class this_);
+
+/**
+ * @brief Creates a supplementary information type from its string representation.
+ *
+ * @param string The string representation (case is ignored)
+ *
+ * @return The corresponding `enum si_type`, or `si_invalid` if `string` does not match a known
+ * identifier
+ */
+enum si_type si_type_new(char * string);
+
+/**
+ * @brief Translates a supplementary information type to its string representation.
+ *
+ * @return The string representation of the supplementary information type
+ */
+const char * si_type_to_string(enum si_type this_);
+
+/**
+ * @brief Creates a new `traffic_point`.
+ *
+ * It is the responsibility of the caller to destroy all references passed to this function. This can be
+ * done immediately after the function returns.
+ *
+ * @param lon The longitude, as reported by the source, in GPS coordinates
+ * @param lat The latitude, as reported by the source, in GPS coordinates
+ * @param junction_name The name of the motorway junction this point refers to, NULL if not applicable
+ * @param junction_ref The reference number of the motorway junction this point refers to, NULL if not applicable
+ * @param tmc_id The TMC identifier of the point, if the location was obtained via TMC, or NULL if not applicable
+ */
+struct traffic_point * traffic_point_new(float lon, float lat, char * junction_name, char * junction_ref,
+ char * tmc_id);
+
+/**
+ * @brief Creates a new `traffic_point`.
+ *
+ * This is the short version of the constructor, which sets only mandatory members. Other members can be
+ * set after the instance is created.
+ *
+ * @param lon The longitude, as reported by the source, in GPS coordinates
+ * @param lat The latitude, as reported by the source, in GPS coordinates
+ */
+struct traffic_point * traffic_point_new_short(float lon, float lat);
+
+/**
+ * @brief Destroys a `traffic_point`.
+ *
+ * This will release the memory used by the `traffic_point` and all related data.
+ *
+ * A `traffic_point` is usually destroyed together with its parent `traffic_location`, thus
+ * it is usually not necessary to call this destructor directly.
+ *
+ * @param this_ The point
+ */
+void traffic_point_destroy(struct traffic_point * this_);
+
+/**
+ * @brief Creates a new `traffic_location`.
+ *
+ * The `traffic_point` instances are destroyed when the `traffic_location` is destroyed, and
+ * therefore cannot be shared between multiple `traffic_location` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function. This
+ * can be done immediately after the function returns.
+ *
+ * If `at` is non-NULL, the location is a point location, and `from` and `to` are
+ * interpreted as auxiliary locations.
+ *
+ * Of `from` and `to`, one is mandatory for a unidirectional point location; both are
+ * mandatory for a linear location.
+ *
+ * `ramps` is mainly intended for compatibility with TMC, where junctions with all their ramps are
+ * represented by a single point. Other sources should use coordinate pairs instead.
+ *
+ * @param at The coordinates for a point location, NULL for a linear location
+ * @param from The start of a linear location, or a point before `at`
+ * @param to The end of a linear location, or a point after `at`
+ * @param via A point between `from` and `to`, needed only on ring roads
+ * @param not_via A point not between `from` and `to`, needed only on ring roads
+ * @param destination A destination, preferably the one given on road signs, indicating that the message
+ * applies only to traffic going in that direction; can be NULL, do not use for bidirectional locations
+ * @param direction A compass direction indicating the direction of travel which this location refers to;
+ * can be NULL, do not use where ambiguous
+ * @param directionality Whether the location is unidirectional or bidirectional
+ * @param fuzziness A precision indicator for `from` and `to`
+ * @param ramps Whether the main carriageway or the ramps are affected
+ * @param road_type The importance of the road within the road network, must be a road item type,
+ * `type_line_unspecified` if not known or not consistent
+ * @param road_name A road name, if consistent throughout the location; NULL if not known or inconsistent
+ * @param road_ref A road number, if consistent throughout the location; NULL if not known or inconsistent
+ * @param tmc_table For messages received via TMC, the CID and LTN; NULL otherwise
+ * @param tmc_direction For messages received via TMC, the direction of the road; ignored for
+ * bidirectional or non-TMC messages
+ */
+// TODO split CID/LTN?
+struct traffic_location * traffic_location_new(struct traffic_point * at, struct traffic_point * from,
+ struct traffic_point * to, struct traffic_point * via, struct traffic_point * not_via,
+ char * destination, char * direction, enum location_dir directionality,
+ enum location_fuzziness fuzziness, enum location_ramps ramps, enum item_type road_type,
+ char * road_name, char * road_ref, char * tmc_table, int tmc_direction);
+
+/**
+ * @brief Creates a new `traffic_location`.
+ *
+ * This is the short version of the constructor, which sets only mandatory members. Other members can be
+ * set after the instance is created.
+ *
+ * The `traffic_point` instances are destroyed when the `traffic_location` is destroyed, and
+ * therefore cannot be shared between multiple `traffic_location` instances.
+ *
+ * If `at` is non-NULL, the location is a point location, and `from` and `to` are
+ * interpreted as auxiliary locations.
+ *
+ * Of `from` and `to`, one is mandatory for a unidirectional point location; both are
+ * mandatory for a linear location.
+ *
+ * @param at The coordinates for a point location, NULL for a linear location
+ * @param from The start of a linear location, or a point before `at`
+ * @param to The end of a linear location, or a point after `at`
+ * @param via A point between `from` and `to`, needed only on ring roads
+ * @param not_via A point not between `from` and `to`, needed only on ring roads
+ * @param directionality Whether the location is unidirectional or bidirectional
+ * @param fuzziness A precision indicator for `from` and `to`
+ */
+struct traffic_location * traffic_location_new_short(struct traffic_point * at, struct traffic_point * from,
+ struct traffic_point * to, struct traffic_point * via, struct traffic_point * not_via,
+ enum location_dir directionality, enum location_fuzziness fuzziness);
+
+/**
+ * @brief Destroys a `traffic_location`.
+ *
+ * This will release the memory used by the `traffic_location` and all related data.
+ *
+ * A `traffic_location` is usually destroyed together with its parent `traffic_message`, thus
+ * it is usually not necessary to call this destructor directly.
+ *
+ * @param this_ The location
+ */
+void traffic_location_destroy(struct traffic_location * this_);
+
+/**
+ * @brief Creates a new `traffic_suppl_info`.
+ *
+ * It is the responsibility of the caller to destroy all references passed to this function. This can be
+ * done immediately after the function returns.
+ *
+ * @param si_class The supplementary information class (generic category)
+ * @param type The supplementary information type, which can be mapped to a string to be displayed to
+ * the user
+ * @param quantifier Additional quantifier for supplementary information types allowing this, or NULL
+ */
+struct traffic_suppl_info * traffic_suppl_info_new(enum si_class si_class, enum si_type type,
+ struct quantifier * quantifier);
+
+/**
+ * @brief Destroys a `traffic_suppl_info`.
+ *
+ * This will release the memory used by the `traffic_suppl_info` and all related data.
+ *
+ * A `traffic_suppl_info` is usually destroyed together with its parent `traffic_event`, thus
+ * it is usually not necessary to call this destructor directly.
+ *
+ * @param this_ The supplementary information item
+ */
+void traffic_suppl_info_destroy(struct traffic_suppl_info * this_);
+
+/**
+ * @brief Creates a new `traffic_event`.
+ *
+ * The `traffic_suppl_info` instances are destroyed when the `traffic_event` is destroyed, and
+ * therefore cannot be shared between multiple `traffic_event` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function
+ * (including the `si` buffer but not the `traffic_suppl_info` instances). This can be done
+ * immediately after the function returns.
+ *
+ * @param event_class The event class (generic category)
+ * @param type The event type, which can be mapped to a string to be displayed to the user
+ * @param length The length of the affected route in meters, -1 if not known
+ * @param speed The speed in km/h at which vehicles can expect to pass through the affected stretch of
+ * road (either a temporary speed limit or average speed in practice, whichever is less); INT_MAX if unknown
+ * @param quantifier Additional quantifier for supplementary information types allowing this, or NULL
+ * @param si_count Number of supplementary information items in `si_count`
+ * @param si Points to an array of pointers to supplementary information items
+ */
+struct traffic_event * traffic_event_new(enum event_class event_class, enum event_type type,
+ int length, int speed, struct quantifier * quantifier, int si_count, struct traffic_suppl_info ** si);
+
+/**
+ * @brief Creates a new `traffic_event`.
+ *
+ * This is the short version of the constructor, which sets only mandatory members. Other members can be
+ * set after the instance is created.
+ *
+ * @param event_class The event class (generic category)
+ * @param type The event type, which can be mapped to a string to be displayed to the user
+ */
+struct traffic_event * traffic_event_new_short(enum event_class event_class, enum event_type type);
+
+/**
+ * @brief Destroys a `traffic_event`.
+ *
+ * This will release the memory used by the `traffic_event` and all related data.
+ *
+ * A `traffic_event` is usually destroyed together with its parent `traffic_message`, thus
+ * it is usually not necessary to call this destructor directly.
+ *
+ * @param this_ The event
+ */
+void traffic_event_destroy(struct traffic_event * this_);
+
+/**
+ * @brief Adds a supplementary information item to an event.
+ *
+ * The `traffic_suppl_info` instance is destroyed when the `traffic_event` is destroyed, and
+ * therefore cannot be shared between multiple `traffic_event` instances.
+ *
+ * @param this_ The event
+ * @param si The supplementary information item
+ */
+void traffic_event_add_suppl_info(struct traffic_event * this_, struct traffic_suppl_info * si);
+
+/**
+ * @brief Retrieves a supplementary information item associated with an event.
+ *
+ * @param this_ The event
+ * @param index The index of the supplementary information item, zero-based
+ * @return The supplementary information item at the specified position, or NULL if out of bounds
+ */
+struct traffic_suppl_info * traffic_event_get_suppl_info(struct traffic_event * this_, int index);
+
+/**
+ * @brief Creates a new `traffic_message`.
+ *
+ * The `traffic_event` and `traffic_location` instances are destroyed when the
+ * `traffic_message` is destroyed, and therefore cannot be shared between multiple
+ * `traffic_message` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function
+ * (including the `events` buffer but not the `traffic_event` instances). This can be done
+ * immediately after the function returns.
+ *
+ * @param id The message identifier; existing messages with the same identifier will be replaced by the
+ * new message
+ * @param receive_time When the message was first received by the source, should be kept stable across
+ * all updates
+ * @param update_time When the last update to this message was received by the source
+ * @param expiration_time How long the message should be considered valid
+ * @param start_time When the condition is expected to begin (optional, 0 if not set)
+ * @param end_time How long the condition is expected to last (optional, 0 if not set)
+ * @param isCancellation If true, create a cancellation message (existing messages with the same ID
+ * should be deleted or no longer considered current, and all other attributes ignored)
+ * @param isForecast If false, the message describes a current situation; if true, it describes an
+ * expected situation in the future
+ * @param replaced_count The number of entries in `replaces`
+ * @param replaces Points to an array of identifiers of messages which the current message replaces
+ * @param location The location to which this message refers
+ * @param event_count The number of events in `events`
+ * @param events Points to an array of pointers to the events for this message
+ */
+struct traffic_message * traffic_message_new(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, time_t start_time, time_t end_time, int is_cancellation, int is_Forecast,
+ int replaced_count, char ** replaces, struct traffic_location * location, int event_count,
+ struct traffic_event ** events);
+
+/**
+ * @brief Creates a new `traffic_message`.
+ *
+ * This is the short version of the constructor, which sets only mandatory members. Other members can be
+ * set after the instance is created.
+ *
+ * The `traffic_event` and `traffic_location` instances are destroyed when the
+ * `traffic_message` is destroyed, and therefore cannot be shared between multiple
+ * `traffic_message` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function
+ * (including the `events` buffer but not the `traffic_event` instances). This can be done
+ * immediately after the function returns.
+ *
+ * @param id The message identifier; existing messages with the same identifier will be replaced by the
+ * new message
+ * @param receive_time When the message was first received by the source, should be kept stable across
+ * all updates
+ * @param update_time When the last update to this message was received by the source
+ * @param expiration_time How long the message should be considered valid
+ * @param is_forecast If false, the message describes a current situation; if true, it describes an
+ * expected situation in the future
+ * @param location The location to which this message refers
+ * @param event_count The number of events in `events`
+ * @param events Points to an array of pointers to the events for this message
+ */
+struct traffic_message * traffic_message_new_short(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, int is_forecast, struct traffic_location * location,
+ int event_count, struct traffic_event ** events);
+
+/**
+ * @brief Creates a new single-event `traffic_message`.
+ *
+ * This is a convenience constructor, which sets only mandatory members. Other members can be
+ * set after the instance is created.
+ *
+ * The `traffic_location` instances are destroyed when the `traffic_message` is destroyed,
+ * and therefore cannot be shared between multiple `traffic_message` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function. This
+ * can be done immediately after the function returns.
+ *
+ * @param id The message identifier; existing messages with the same identifier will be replaced by the
+ * new message
+ * @param receive_time When the message was first received by the source, should be kept stable across
+ * all updates
+ * @param update_time When the last update to this message was received by the source
+ * @param expiration_time How long the message should be considered valid
+ * @param is_forecast If false, the message describes a current situation; if true, it describes an
+ * expected situation in the future
+ * @param location The location to which this message refers
+ * @param event_class The event class (generic category)
+ * @param type The event type, which can be mapped to a string to be displayed to the user
+ */
+struct traffic_message * traffic_message_new_single_event(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, int is_forecast, struct traffic_location * location,
+ enum event_class event_class, enum event_type type);
+
+/**
+ * @brief Creates a new cancellation `traffic_message`.
+ *
+ * This is a convenience constructor, which creates a cancellation message, without the need to supply
+ * members which are not required for cancellation messages. Upon receiving a cancellation message,
+ * existing messages with the same ID should be deleted or no longer considered current, and all other
+ * attributes ignored.
+ *
+ * The `traffic_location` instances are destroyed when the `traffic_message` is destroyed,
+ * and therefore cannot be shared between multiple `traffic_message` instances.
+ *
+ * It is the responsibility of the caller to destroy all other references passed to this function. This
+ * can be done immediately after the function returns.
+ *
+ * @param id The message identifier; existing messages with the same identifier will be replaced by the
+ * new message
+ * @param receive_time When the message was first received by the source, should be kept stable across
+ * all updates
+ * @param update_time When the last update to this message was received by the source
+ * @param expiration_time How long the message should be considered valid
+ * @param location The location to which this message refers
+ */
+struct traffic_message * traffic_message_new_cancellation(char * id, time_t receive_time, time_t update_time,
+ time_t expiration_time, struct traffic_location * location);
+
+/**
+ * @brief Destroys a `traffic_message`.
+ *
+ * This will release the memory used by the `traffic_message` and all related data.
+ *
+ * A `traffic_message` is usually destroyed by the traffic plugin, thus it is usually not
+ * necessary to call this destructor directly.
+ *
+ * @param this_ The message
+ */
+void traffic_message_destroy(struct traffic_message * this_);
+
+/**
+ * @brief Adds an event to a message.
+ *
+ * The `traffic_event` instance is destroyed when the `traffic_message` is destroyed, and
+ * therefore cannot be shared between multiple `traffic_message` instances.
+ *
+ * @param this_ The message
+ * @param event The event to add to this message
+ */
+void traffic_message_add_event(struct traffic_message * this_, struct traffic_event * event);
+
+/**
+ * @brief Retrieves an event associated with a message.
+ *
+ * @param this_ The message
+ * @param index The index of the event, zero-based
+ * @return The event at the specified position, or NULL if out of bounds
+ */
+struct traffic_event * traffic_message_get_event(struct traffic_message * this_, int index);
+
+/**
+ * @brief Returns the items associated with a message.
+ *
+ * Note that no map rectangle is required to obtain traffic items. This behavior is particular to traffic items, which
+ * do not rely on a map rectangle. Items obtained from other maps may behave differently.
+ *
+ * @param this_ The message
+ *
+ * @return Items as a NULL-terminated array. The caller is responsible for freeing the array (not its elements) when it
+ * is no longer needed. This method will always return a valid pointer—if no items are associated with the message, an
+ * empty array (with just one single NULL element) will be returned. No particular order is guaranteed for the items.
+ */
+struct item ** traffic_message_get_items(struct traffic_message * this_);
+
+/**
+ * @brief Initializes the traffic plugin.
+ *
+ * This function is called once on startup.
+ */
+void traffic_init(void);
+
+/**
+ * @brief Reads previously stored traffic messages from an XML file.
+ *
+ * @param this_ The traffic instance
+ * @param filename The full path to the XML file to parse
+ *
+ * @return A `NULL`-terminated pointer array. Each element points to one `struct traffic_message`.
+ * `NULL` is returned (rather than an empty pointer array) if there are no messages to report.
+ */
+struct traffic_message ** traffic_get_messages_from_xml_file(struct traffic * this_, char * filename);
+
+/**
+ * @brief Reads traffic messages from an XML string.
+ *
+ * @param this_ The traffic instance
+ * @param filename The XML document to parse, as a string
+ *
+ * @return A `NULL`-terminated pointer array. Each element points to one `struct traffic_message`.
+ * `NULL` is returned (rather than an empty pointer array) if there are no messages to report.
+ */
+struct traffic_message ** traffic_get_messages_from_xml_string(struct traffic * this_, char * xml);
+
+/**
+ * @brief Returns the map for the traffic plugin.
+ *
+ * The map is created by the first traffic plugin loaded. If multiple traffic plugin instances are
+ * active at the same time, they share the map created by the first instance.
+ *
+ * @param this_ The traffic plugin instance
+ *
+ * @return The traffic map
+ */
+struct map * traffic_get_map(struct traffic *this_);
+
+/**
+ * @brief Returns currently active traffic messages.
+ *
+ * If multiple plugin instances are active, this will give the same result for any plugin, as traffic messages are
+ * shared between instances.
+ *
+ * @param this_ The traffic plugin instance
+ *
+ * @return A null-terminated array of traffic messages. The caller is responsible for freeing the array (not its
+ * elements) when it is no longer needed. This method will always return a valid pointer—if the message store is empty,
+ * an empty array (with just one single NULL element) will be returned.
+ */
+struct traffic_message ** traffic_get_stored_messages(struct traffic *this_);
+
+/**
+ * @brief Processes new traffic messages.
+ *
+ * Calling this method delivers new messages in a “push” manner (as opposed to the “pull” fashion of
+ * calling a plugin method).
+ *
+ * Messages which are past their expiration timestamp are skipped, and the flags in the return value
+ * are set only if at least one valid message is found.
+ *
+ * @param this_ The traffic instance
+ * @param messages The new messages
+ */
+void traffic_process_messages(struct traffic * this_, struct traffic_message ** messages);
+
+/**
+ * @brief Sets the mapset for the traffic plugin.
+ *
+ * This sets the mapset from which the segments affected by a traffic report will be retrieved.
+ *
+ * @param this_ The traffic plugin instance
+ * @param ms The mapset
+ */
+void traffic_set_mapset(struct traffic *this_, struct mapset *ms);
+
+/**
+ * @brief Sets the route for the traffic plugin.
+ *
+ * This sets the route which may get notified by the traffic plugin if traffic distortions change.
+ */
+void traffic_set_route(struct traffic *this_, struct route *rt);
+
+/* end of prototypes */
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/navit/traffic/dummy/CMakeLists.txt b/navit/traffic/dummy/CMakeLists.txt
new file mode 100644
index 000000000..83df03b51
--- /dev/null
+++ b/navit/traffic/dummy/CMakeLists.txt
@@ -0,0 +1 @@
+module_add_library(traffic_dummy traffic_dummy.c)
diff --git a/navit/traffic/dummy/traffic_dummy.c b/navit/traffic/dummy/traffic_dummy.c
new file mode 100644
index 000000000..b838752dc
--- /dev/null
+++ b/navit/traffic/dummy/traffic_dummy.c
@@ -0,0 +1,190 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2017 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * @file traffic_dummy.c
+ *
+ * @brief A dummy traffic plugin.
+ *
+ * This is a dummy plugin to test the traffic framework.
+ */
+
+#include <string.h>
+#include <time.h>
+
+#ifdef _POSIX_C_SOURCE
+#include <sys/types.h>
+#endif
+#include "glib_slice.h"
+#include "config.h"
+#include "coord.h"
+#include "item.h"
+#include "xmlconfig.h"
+#include "traffic.h"
+#include "plugin.h"
+#include "debug.h"
+
+/**
+ * @brief Stores information about the plugin instance.
+ */
+struct traffic_priv {
+ struct navit * nav; /*!< The navit instance */
+ int reports_requested; /*!< How many reports have been requested */
+};
+
+struct traffic_message ** traffic_dummy_get_messages(struct traffic_priv * this_);
+
+/**
+ * @brief Returns a dummy traffic report.
+ *
+ * This method will report two messages when first called: The messages indicate queuing traffic on the
+ * A9 Munich–Nuremberg between Neufahrn and Allershausen, and slow traffic on the A96 Lindau–Munich
+ * between Gräfelfing and München-Laim.
+ *
+ * The 10th call will report an update message for the A9 (with a recent timestamp but otherwise the same
+ * data) and a cancellation message for the A96.
+ *
+ * They mimic TMC messages in that coordinates are approximate, TMC identifiers are supplied for the
+ * locations and extra data fields which can be inferred from the TMC location table are filled. The
+ * timestamps indicate a message that has just been received for the first time, i.e. its “first
+ * received” and “last updated” timestamps match and are recent. Expiration is after 20 seconds for
+ * messages in the first feed and 10 seconds for messages in the first feed (far below the lowest
+ * expiration timespan permitted in TMC).
+ *
+ * All other calls to this method will return `NULL`, indicating that there are no messages to report.
+ *
+ * @return A `NULL`-terminated pointer array. Each element points to one `struct traffic_message`.
+ * `NULL` is returned (rather than an empty pointer array) if there are no messages to report.
+ */
+struct traffic_message ** traffic_dummy_get_messages(struct traffic_priv * this_) {
+ struct traffic_message ** messages;
+ struct traffic_point * from;
+ struct traffic_point * to;
+ struct traffic_point * at;
+ struct traffic_point * via;
+ struct traffic_location * location;
+
+ this_->reports_requested++;
+
+ switch (this_->reports_requested) {
+ case 10:
+ messages = g_new0(struct traffic_message *, 6);
+
+ from = traffic_point_new(11.6208, 48.3164, "Neufahrn", "68", "12732-4");
+ to = traffic_point_new(11.5893, 48.429, "Allershausen", "67", "12732");
+ location = traffic_location_new(NULL, from, to, NULL, NULL, "Nürnberg", NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_highway_land, NULL, "A9", "58:1", -1);
+ messages[0] = traffic_message_new_single_event("dummy:A9-68-67", time(NULL), time(NULL),
+ time(NULL) + 86400, 0, location, event_class_congestion, event_congestion_queue);
+
+ from = traffic_point_new(11.4481, 48.1266, "Gräfelfing", "36b", "12961-2");
+ to = traffic_point_new(11.5028, 48.1258, "München-Laim", "38", "12961");
+ location = traffic_location_new(NULL, from, to, NULL, NULL, "München", NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_highway_land, NULL, "A96", "58:1", -1);
+ messages[1] = traffic_message_new_single_event("dummy:A96-36b-38", time(NULL), time(NULL),
+ time(NULL) + 86400, 0, location, event_class_congestion, event_congestion_slow_traffic);
+
+ from = traffic_point_new(11.6143, 48.15255, "Effnertunnel", NULL, "60922");
+ to = traffic_point_new(11.53225, 48.13255, "Trappentreutunnel", NULL, "35333");
+ via = traffic_point_new(11.5728, 48.178, "Petueltunnel", NULL, "29829");
+ location = traffic_location_new(NULL, from, to, via, NULL, NULL, NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_line_unspecified, NULL, "B2R", "58:1", 1);
+ messages[2] = traffic_message_new_single_event("dummy:B2R-N", time(NULL), time(NULL),
+ time(NULL) + 86400, 0, location, event_class_congestion, event_congestion_slow_traffic);
+
+ from = traffic_point_new(11.6143, 48.15255, "Effnertunnel", NULL, "60922");
+ to = traffic_point_new(11.53225, 48.13255, "Trappentreutunnel", NULL, "35333");
+ via = traffic_point_new(11.55085, 48.11225, "Brudermühltunnel", NULL, "35329");
+ location = traffic_location_new(NULL, from, to, via, NULL, NULL, NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_line_unspecified, NULL, "B2R", "58:1", -1);
+ messages[3] = traffic_message_new_single_event("dummy:B2R-S", time(NULL), time(NULL),
+ time(NULL) + 86400, 0, location, event_class_congestion, event_congestion_slow_traffic);
+
+ from = traffic_point_new(11.6208, 48.3164, "Neufahrn", "68", "12727+5");
+ at = traffic_point_new(11.6405, 48.2435, "Garching-Süd", "71", "12727");
+ location = traffic_location_new(at, from, NULL, NULL, NULL, NULL, NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_highway_land, NULL, "A9", "58:1", 1);
+ messages[4] = traffic_message_new_single_event("dummy:A9-71-S", time(NULL), time(NULL),
+ time(NULL) + 86400, 0, location, event_class_congestion, event_congestion_slow_traffic);
+ break;
+
+ case 20:
+ messages = g_new0(struct traffic_message *, 4);
+
+ from = traffic_point_new(11.6208, 48.3164, "Neufahrn", "68", "12732-4");
+ to = traffic_point_new(11.5893, 48.429, "Allershausen", "67", "12732");
+ location = traffic_location_new(NULL, from, to, NULL, NULL, "Nürnberg", NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_highway_land, NULL, "A9", "58:1", -1);
+ messages[0] = traffic_message_new_single_event("dummy:A9-68-67", time(NULL) - 10, time(NULL),
+ time(NULL) + 10, 0, location, event_class_congestion, event_congestion_queue);
+
+ from = traffic_point_new(11.4481, 48.1266, "Gräfelfing", "36b", "12961-2");
+ to = traffic_point_new(11.5028, 48.1258, "München-Laim", "38", "12961");
+ location = traffic_location_new(NULL, from, to, NULL, NULL, "München", NULL, location_dir_one,
+ location_fuzziness_low_res, location_ramps_none, type_highway_land, NULL, "A96", "58:1", -1);
+ messages[1] = traffic_message_new_cancellation("dummy:A96-36b-38", time(NULL) - 10, time(NULL),
+ time(NULL) + 10, location);
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return messages;
+}
+
+/**
+ * @brief The methods implemented by this plugin
+ */
+static struct traffic_methods traffic_dummy_meth = {
+ traffic_dummy_get_messages,
+};
+
+/**
+ * @brief Registers a new dummy traffic plugin
+ *
+ * @param nav The navit instance
+ * @param meth Receives the traffic methods
+ * @param attrs The attributes for the map
+ * @param cbl
+ *
+ * @return A pointer to a `traffic_priv` structure for the plugin instance
+ */
+static struct traffic_priv * traffic_dummy_new(struct navit *nav, struct traffic_methods *meth,
+ struct attr **attrs, struct callback_list *cbl) {
+ struct traffic_priv *ret;
+
+ dbg(lvl_debug, "enter");
+
+ ret = g_new0(struct traffic_priv, 1);
+ *meth = traffic_dummy_meth;
+
+ return ret;
+}
+
+/**
+ * @brief Initializes the traffic plugin.
+ *
+ * This function is called once on startup.
+ */
+void plugin_init(void) {
+ dbg(lvl_debug, "enter");
+
+ plugin_register_category_traffic("dummy", traffic_dummy_new);
+}
diff --git a/navit/traffic/null/CMakeLists.txt b/navit/traffic/null/CMakeLists.txt
new file mode 100644
index 000000000..c5edbe691
--- /dev/null
+++ b/navit/traffic/null/CMakeLists.txt
@@ -0,0 +1 @@
+module_add_library(traffic_null traffic_null.c)
diff --git a/navit/traffic/null/traffic_null.c b/navit/traffic/null/traffic_null.c
new file mode 100644
index 000000000..94546a666
--- /dev/null
+++ b/navit/traffic/null/traffic_null.c
@@ -0,0 +1,101 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2017 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * @file traffic_null.c
+ *
+ * @brief A null traffic plugin.
+ *
+ * This plugin was mainly designed to test the traffic framework. It acts like a traffic plugin but will
+ * never report any messages. This allows us to have the full traffic functionality without having an
+ * actual source for traffic messages; useful for injecting messages via DBus.
+ */
+
+#include <string.h>
+#include <time.h>
+
+#ifdef _POSIX_C_SOURCE
+#include <sys/types.h>
+#endif
+#include "glib_slice.h"
+#include "config.h"
+#include "coord.h"
+#include "item.h"
+#include "xmlconfig.h"
+#include "traffic.h"
+#include "plugin.h"
+#include "debug.h"
+
+/**
+ * @brief Stores information about the plugin instance.
+ */
+struct traffic_priv {
+ struct navit * nav; /*!< The navit instance */
+};
+
+struct traffic_message ** traffic_null_get_messages(struct traffic_priv * this_);
+
+/**
+ * @brief Returns an empty traffic report.
+ *
+ * @return Always `NULL`
+ */
+struct traffic_message ** traffic_null_get_messages(struct traffic_priv * this_) {
+ return NULL;
+}
+
+/**
+ * @brief The methods implemented by this plugin
+ */
+static struct traffic_methods traffic_null_meth = {
+ traffic_null_get_messages,
+};
+
+/**
+ * @brief Registers a new null traffic plugin
+ *
+ * @param nav The navit instance
+ * @param meth Receives the traffic methods
+ * @param attrs The attributes for the map
+ * @param cbl
+ *
+ * @return A pointer to a `traffic_priv` structure for the plugin instance
+ */
+static struct traffic_priv * traffic_null_new(struct navit *nav, struct traffic_methods *meth,
+ struct attr **attrs, struct callback_list *cbl) {
+ struct traffic_priv *ret;
+
+ dbg(lvl_debug, "enter");
+
+ ret = g_new0(struct traffic_priv, 1);
+ *meth = traffic_null_meth;
+
+ return ret;
+}
+
+/**
+ * @brief Initializes the traffic plugin.
+ *
+ * This function is called once on startup.
+ */
+void plugin_init(void) {
+ dbg(lvl_debug, "enter");
+
+ plugin_register_category_traffic("null", traffic_null_new);
+}
diff --git a/navit/traffic/traff_android/CMakeLists.txt b/navit/traffic/traff_android/CMakeLists.txt
new file mode 100644
index 000000000..5e5f06df1
--- /dev/null
+++ b/navit/traffic/traff_android/CMakeLists.txt
@@ -0,0 +1 @@
+module_add_library(traffic_traff_android traffic_traff_android.c)
diff --git a/navit/traffic/traff_android/traffic_traff_android.c b/navit/traffic/traff_android/traffic_traff_android.c
new file mode 100644
index 000000000..0bc07ad7f
--- /dev/null
+++ b/navit/traffic/traff_android/traffic_traff_android.c
@@ -0,0 +1,173 @@
+/**
+ * Navit, a modular navigation system.
+ * Copyright (C) 2005-2018 Navit Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * @file traffic_traff_android.c
+ *
+ * @brief The TraFF plugin for Android
+ *
+ * This plugin receives TraFF feeds via Android broadcasts.
+ */
+
+#include <string.h>
+#include <time.h>
+
+#ifdef _POSIX_C_SOURCE
+#include <sys/types.h>
+#endif
+#include "glib_slice.h"
+#include "config.h"
+#include "item.h"
+#include "attr.h"
+#include "coord.h"
+#include "xmlconfig.h"
+#include "android.h"
+#include "traffic.h"
+#include "plugin.h"
+#include "callback.h"
+#include "debug.h"
+
+/**
+ * @brief Stores information about the plugin instance.
+ */
+struct traffic_priv {
+ struct navit * nav; /**< The navit instance */
+ struct callback * cbid; /**< The callback function for TraFF feeds **/
+ jclass NavitTraffClass; /**< The `NavitTraff` class */
+ jobject NavitTraff; /**< An instance of `NavitTraff` */
+};
+
+struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv * this_);
+
+/**
+ * @brief Returns an empty traffic report.
+ *
+ * @return Always `NULL`
+ */
+struct traffic_message ** traffic_traff_android_get_messages(struct traffic_priv * this_) {
+ return NULL;
+}
+
+/**
+ * @brief The methods implemented by this plugin
+ */
+static struct traffic_methods traffic_traff_android_meth = {
+ traffic_traff_android_get_messages,
+};
+
+
+/**
+ * @brief Called when a new TraFF feed is received.
+ *
+ * @param this_ Private data for the module instance
+ * @param feed Feed data in string form
+ */
+static void traffic_traff_android_on_feed_received(struct traffic_priv * this_, char * feed) {
+ struct attr * attr;
+ struct attr_iter * a_iter;
+ struct traffic * traffic = NULL;
+ struct traffic_message ** messages;
+
+ dbg(lvl_debug, "enter");
+ attr = g_new0(struct attr, 1);
+ a_iter = navit_attr_iter_new();
+ if (navit_get_attr(this_->nav, attr_traffic, attr, a_iter))
+ traffic = (struct traffic *) attr->u.navit_object;
+ navit_attr_iter_destroy(a_iter);
+ g_free(attr);
+
+ if (!traffic) {
+ dbg(lvl_error, "failed to obtain traffic instance");
+ return;
+ }
+
+ dbg(lvl_debug, "processing traffic feed:\n%s", feed);
+ messages = traffic_get_messages_from_xml_string(traffic, feed);
+ if (messages) {
+ dbg(lvl_debug, "got messages from feed, processing");
+ traffic_process_messages(traffic, messages);
+ g_free(messages);
+ }
+}
+
+
+/**
+ * @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;
+
+ if (!android_find_class_global("org/navitproject/navit/NavitTraff", &this_->NavitTraffClass))
+ return 0;
+ cid = (*jnienv)->GetMethodID(jnienv, this_->NavitTraffClass, "<init>", "(Landroid/content/Context;I)V");
+ if (cid == NULL) {
+ dbg(lvl_error,"no method found");
+ return 0; /* exception thrown */
+ }
+ this_->NavitTraff=(*jnienv)->NewObject(jnienv, this_->NavitTraffClass, cid, android_activity,
+ (int) this_->cbid);
+ dbg(lvl_debug,"result=%p", this_->NavitTraff);
+ if (!this_->NavitTraff)
+ return 0;
+ if (this_->NavitTraff)
+ this_->NavitTraff = (*jnienv)->NewGlobalRef(jnienv, this_->NavitTraff);
+
+ return 1;
+}
+
+
+/**
+ * @brief Registers a new traff_android traffic plugin
+ *
+ * @param nav The navit instance
+ * @param meth Receives the traffic methods
+ * @param attrs The attributes for the map
+ * @param cbl
+ *
+ * @return A pointer to a `traffic_priv` structure for the plugin instance
+ */
+static struct traffic_priv * traffic_traff_android_new(struct navit *nav, struct traffic_methods *meth,
+ struct attr **attrs, struct callback_list *cbl) {
+ struct traffic_priv *ret;
+
+ dbg(lvl_debug, "enter");
+
+ ret = g_new0(struct traffic_priv, 1);
+ ret->nav = nav;
+ ret->cbid = callback_new_1(callback_cast(traffic_traff_android_on_feed_received), ret);
+ /* TODO populate members, if any */
+ *meth = traffic_traff_android_meth;
+
+ traffic_traff_android_init(ret);
+
+ return ret;
+}
+
+/**
+ * @brief Initializes the traffic plugin.
+ *
+ * This function is called once on startup.
+ */
+void plugin_init(void) {
+ dbg(lvl_debug, "enter");
+
+ plugin_register_category_traffic("traff_android", traffic_traff_android_new);
+}
diff --git a/navit/util.c b/navit/util.c
index 51bb4db8d..9398e316c 100644
--- a/navit/util.c
+++ b/navit/util.c
@@ -93,6 +93,245 @@ int navit_utf8_strcasecmp(const char *s1, const char *s2) {
return cmpres;
}
+/**
+ * @brief Trims all leading and trailing whitespace characters from a string.
+ *
+ * Whitespace characters are all up to and including 0x20.
+ *
+ * This function operates in-place, i.e. `s` will be modified.
+ *
+ * @param s The string to trim
+ */
+static void strtrim(char *s) {
+ char *tmp = g_strdup(s);
+ char *in = tmp;
+ while (strlen(in) && (in[0] <= 0x20))
+ in++;
+ while (strlen(in) && (in[strlen(in) - 1] <= 0x20))
+ in[strlen(in) - 1] = 0;
+ strcpy(s, in);
+ g_free(tmp);
+}
+
+/**
+ * @brief Parser states for `parse_for_systematic_comparison()`.
+ */
+enum parse_state {
+ parse_state_whitespace,
+ parse_state_numeric,
+ parse_state_alpha,
+};
+
+/**
+ * @brief Parses a string for systematic comparison.
+ *
+ * This is a helper function for `compare_name_systematic()`.
+ *
+ * The string is broken down into numeric and non-numeric parts. Whitespace characters are discarded
+ * unless they are surrounded by string characters, in which case the whole unit is treated as one
+ * string part. All strings are converted to lowercase and leading zeroes stripped from numbers.
+ *
+ * @param s The string to parse
+ *
+ * @return A buffer containing the parsed string, parts delimited by a null character, the last part
+ * followed by a double null character.
+ */
+static char * parse_for_systematic_comparison(const char *s) {
+ char *ret = g_malloc0(strlen(s) * 2 + 1);
+ const char *in = s;
+ char *out = ret;
+ char *part;
+ enum parse_state state = parse_state_whitespace;
+ int i = 0;
+ char c;
+
+ dbg(lvl_debug, "enter\n");
+
+ while (i < strlen(in)) {
+ c = in[i];
+ if ((c <= 0x20) || (c == ',') || (c == '-') || (c == '.') || (c == '/')) {
+ /* whitespace */
+ if (state == parse_state_numeric) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ state = parse_state_whitespace;
+ } else
+ i++;
+ } else if ((c >= '0') && (c <= '9')) {
+ /* numeric */
+ if (state == parse_state_alpha) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ strtrim(part);
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ } else
+ i++;
+ state = parse_state_numeric;
+ } else {
+ /* alpha */
+ if (state == parse_state_numeric) {
+ part = g_malloc0(i + 1);
+ strncpy(part, in, i);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ out += strlen(part) + 1;
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ in += i;
+ i = 1;
+ } else
+ i++;
+ state = parse_state_alpha;
+ }
+ }
+
+ if (strlen(in) > 0) {
+ if (state == parse_state_numeric) {
+ part = g_malloc0(strlen(in) + 1);
+ strcpy(part, in);
+ sprintf(part, "%d", atoi(part));
+ strcpy(out, part);
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ } else if (state == parse_state_alpha) {
+ part = g_malloc0(strlen(in) + 1);
+ strcpy(part, in);
+ strtrim(part);
+ strcpy(out, part);
+ dbg(lvl_debug, "part='%s'\n", part);
+ g_free(part);
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Compares two name_systematic strings.
+ *
+ * A name_systematic string is typically used for road reference numbers (A 4, I-51, SP526). This
+ * function performs a fuzzy comparison: Each string is broken down into numeric and non-numeric parts.
+ * Then both strings are compared part by part. The following rules apply:
+ *
+ * \li Semicolons denote sequences of strings, and the best match between any pair of strings from `s1` and `s2` is
+ * returned.
+ * \li Whitespace bordering on a number is discarded.
+ * \li Whitespace surrounded by string characters is treated as one string with the surrounding characters.
+ * \li If one string has more parts than the other, the shorter string is padded with null parts.
+ * \li null equals null.
+ * \li null does not equal non-null.
+ * \li Numeric parts are compared as integers, hence `'042'` equals `'42'`.
+ * \li Comparison of string parts is case-insensitive.
+ *
+ * Partial matches are currently determined by determining each part of one string with each part of the other. Each
+ * part of one string that is matched by at least one part of the other increases the score. Order is currently not
+ * taken into account, i.e. `'42A'` and `'A-42A'` are both considered full (not partial) matches for `'A42'`. Future
+ * versions may change this.
+ *
+ * @param s1 The first string
+ * @param s2 The second string
+ *
+ * @return 0 if both strings match, nonzero if they do not. `MAX_MISMATCH` indicates a complete mismatch; values in
+ * between indicate partial matches (lower values correspond to better matches).
+ */
+int compare_name_systematic(const char *s1, const char *s2) {
+ int ret = MAX_MISMATCH;
+ int tmp;
+ int elements = 0, matches = 0;
+ char *l = NULL, *r = NULL, *l0, *r0;
+
+ if (!s1 || !s1[0]) {
+ if (!s2 || !s2[0])
+ return 0;
+ else
+ return MAX_MISMATCH;
+ } else if (!s2 || !s2[0])
+ return MAX_MISMATCH;
+
+ /* break up strings at semicolons and parse each separately, return 0 if any two match */
+ if (strchr(s1, ';')) {
+ l = g_strdup(s1);
+ for (l0 = strtok(l, ";"); l0; l0 = strtok(NULL, ";")) {
+ tmp = compare_name_systematic(l0, s2);
+ if (tmp < ret)
+ ret = tmp;
+ if (!ret)
+ break;
+ }
+ g_free(l);
+ return ret;
+ } else if (strchr(s2, ';')) {
+ r = g_strdup(s2);
+ for (r0 = strtok(r, ";"); r0; r0 = strtok(NULL, ";")) {
+ tmp = compare_name_systematic(s1, r0);
+ if (tmp < ret)
+ ret = tmp;
+ if (!ret)
+ break;
+ }
+ g_free(r);
+ return ret;
+ }
+
+ /* s1 and s2 are single strings (no semicolons) */
+ l0 = parse_for_systematic_comparison(s1);
+ r0 = parse_for_systematic_comparison(s2);
+
+ /* count left-hand elements and all left-hand elements matched by a right-hand element */
+ for (l = l0; l[0]; l += strlen(l) + 1) {
+ elements++;
+ for (r = r0; r[0]; r += strlen(r) + 1) {
+ if (atoi(l) || (l[0] == '0')) {
+ if ((atoi(r) || (r[0] == '0')) && (atoi(l) == atoi(r))) {
+ matches++;
+ break;
+ }
+ } else if (!strcasecmp(l, r)) {
+ matches++;
+ break;
+ }
+ }
+ }
+
+ /* same in the opposite direction */
+ for (r = r0; r[0]; r += strlen(r) + 1) {
+ elements++;
+ for (l = l0; l[0]; l += strlen(l) + 1) {
+ if (atoi(l) || (l[0] == '0')) {
+ if ((atoi(r) || (r[0] == '0')) && (atoi(l) == atoi(r))) {
+ matches++;
+ break;
+ }
+ } else if (!strcasecmp(l, r)) {
+ matches++;
+ break;
+ }
+ }
+ }
+
+ g_free(l0);
+ g_free(r0);
+
+ ret = ((elements - matches) * MAX_MISMATCH) / elements;
+
+ dbg(lvl_debug, "'%s' %s '%s', ret=%d",
+ s1, ret ? (ret == MAX_MISMATCH ? "does NOT match" : "PARTIALLY matches") : "matches", s2, ret);
+
+ return ret;
+}
+
static void hash_callback(gpointer key, gpointer value, gpointer user_data) {
GList **l=user_data;
*l=g_list_prepend(*l, value);
@@ -338,28 +577,152 @@ unsigned int iso8601_to_secs(char *iso8601) {
}
/**
+ * @brief Converts a `tm` structure to `time_t`
+ *
+ * Returns the value of type `time_t` that represents the UTC time described by the `tm` structure
+ * pointed to by `pt` (which may be modified).
+ *
+ * This function performs the reverse translation that `gmtime()` does. As this functionality is absent
+ * in the standard library, it is emulated by calling `mktime()`, converting its output into both GMT
+ * and local time, comparing the results and calling `mktime()` again with an input adjusted for the
+ * offset in the opposite direction. This ensures maximum portability.
+ *
+ * The values of the `tm_wday` and `tm_yday` members of `pt` are ignored, and the values of the other
+ * members are interpreted even if out of their valid ranges (see `struct tm`). For example, `tm_mday`
+ * may contain values above 31, which are interpreted accordingly as the days that follow the last day
+ * of the selected month.
+ *
+ * A call to this function automatically adjusts the values of the members of `pt` if they are off-range
+ * or—in the case of `tm_wday` and `tm_yday`—if their values are inconsistent with the other members.
+ *
+ */
+time_t mkgmtime(struct tm * pt) {
+ time_t ret;
+
+ /* Input, GMT and local time */
+ struct tm * pti, * pgt, * plt;
+
+ pti = g_memdup(pt, sizeof(struct tm));
+
+ ret = mktime(pti);
+
+ pgt = g_memdup(gmtime(&ret), sizeof(struct tm));
+ plt = g_memdup(localtime(&ret), sizeof(struct tm));
+
+ pti->tm_year = pt->tm_year - pgt->tm_year + plt->tm_year;
+ pti->tm_mon = pt->tm_mon - pgt->tm_mon + plt->tm_mon;
+ pti->tm_mday = pt->tm_mday - pgt->tm_mday + plt->tm_mday;
+ pti->tm_hour = pt->tm_hour - pgt->tm_hour + plt->tm_hour;
+ pti->tm_min = pt->tm_min - pgt->tm_min + plt->tm_min;
+ pti->tm_sec = pt->tm_sec - pgt->tm_sec + plt->tm_sec;
+
+ ret = mktime(pti);
+
+ dbg(lvl_debug, "time %ld (%02d-%02d-%02d %02d:%02d:%02d)\n", ret, pti->tm_year, pti->tm_mon, pti->tm_mday,
+ pti->tm_hour, pti->tm_min, pti->tm_sec);
+
+ g_free(pti);
+ g_free(pgt);
+ g_free(plt);
+
+ return ret;
+}
+
+/**
+ * @brief Converts an ISO 8601-style time string into `time_t`.
+ */
+time_t iso8601_to_time(char * iso8601) {
+ /* Date/time fields (YYYY-MM-DD-hh-mm-ss) */
+ int val[8];
+
+ int i = 0;
+
+ /* Start of next integer portion and current position */
+ char *start = iso8601, *pos = iso8601;
+
+ /* Time struct */
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(struct tm));
+
+ while (*pos && i < 6) {
+ if (*pos < '0' || *pos > '9') {
+ val[i++] = atoi(start);
+ if (i == 6)
+ break;
+ pos++;
+ start = pos;
+ }
+ if (*pos)
+ pos++;
+ }
+ val[6] = 0;
+ val[7] = 0;
+ if (*pos && i == 6) {
+ if (pos[1] && pos[2] && (!pos[3] || pos[3] == ':')) {
+ val[6] = atoi(pos);
+ if (pos[3] == ':') {
+ pos += 3;
+ val[7] = (val[6] < 0) ? -atoi(pos) : atoi(pos);
+ }
+ } else if (pos[1] && pos[2] && pos[3] && pos[4]) {
+ val[6] = atoi(pos) / 100;
+ val[7] = atoi(pos) % 100;
+ }
+ }
+
+ tm.tm_year = val[0] - 1900;
+ tm.tm_mon = val[1] - 1;
+ tm.tm_mday = val[2];
+ tm.tm_hour = val[3] - val[6];
+ tm.tm_min = val[4] - val[7];
+ tm.tm_sec = val[5];
+
+ dbg(lvl_debug, "time %s (%02d-%02d-%02d %02d:%02d:%02d)\n", iso8601, tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ return mkgmtime(&tm);
+}
+
+/**
+ * @brief Converts time to ISO8601 format.
+ *
+ * The caller is responsible for freeing the return value of this function when it is no longer needed.
+ *
+ * @param time The time, as returned by `time()` and related functions
+ *
+ * @return Time in ISO8601 format
+ */
+char * time_to_iso8601(time_t time) {
+ char *timep=NULL;
+ char buffer[32];
+ struct tm *tm;
+
+ tm = gmtime(&time);
+ if (tm) {
+ strftime(buffer, sizeof(buffer), "%Y-%m-%dT%TZ", tm);
+ timep=g_strdup(buffer);
+ }
+ return timep;
+}
+
+/**
* @brief Outputs local system time in ISO 8601 format.
*
* @return Time in ISO 8601 format
*/
char *current_to_iso8601(void) {
- char *timep=NULL;
#ifdef HAVE_API_WIN32_BASE
+ char *timep=NULL;
SYSTEMTIME ST;
GetSystemTime(&ST);
timep=g_strdup_printf("%d-%02d-%02dT%02d:%02d:%02dZ",ST.wYear,ST.wMonth,ST.wDay,ST.wHour,ST.wMinute,ST.wSecond);
+ return timep;
#else
- char buffer[32];
time_t tnow;
- struct tm *tm;
tnow = time(0);
- tm = gmtime(&tnow);
- if (tm) {
- strftime(buffer, sizeof(buffer), "%Y-%m-%dT%TZ", tm);
- timep=g_strdup(buffer);
- }
+ return time_to_iso8601(tnow);
#endif
- return timep;
}
diff --git a/navit/util.h b/navit/util.h
index 7aa6449e6..131173ff6 100644
--- a/navit/util.h
+++ b/navit/util.h
@@ -21,12 +21,16 @@
#define NAVIT_types_H
#include <ctype.h>
+#include <time.h>
#include "config.h"
+#define MAX_MISMATCH 100
+
void strtoupper(char *dest, const char *src);
void strtolower(char *dest, const char *src);
unsigned int uint_sqrt(unsigned int n);
int navit_utf8_strcasecmp(const char *s1, const char *s2);
+int compare_name_systematic(const char *s1, const char *s2);
GList * g_hash_to_list(GHashTable *h);
GList * g_hash_to_list_keys(GHashTable *h);
gchar * g_strconcat_printf(gchar *buffer, gchar *fmt, ...);
@@ -38,6 +42,9 @@ char * newSysString(const char *toconvert);
#endif
#endif
unsigned int iso8601_to_secs(char *iso8601);
+time_t mkgmtime(struct tm * pt);
+time_t iso8601_to_time(char * iso8601);
+char * time_to_iso8601(time_t time);
char * current_to_iso8601(void);
#if defined(_MSC_VER) || (!defined(HAVE_GETTIMEOFDAY) && defined(HAVE_API_WIN32_BASE))
diff --git a/navit/xmlconfig.c b/navit/xmlconfig.c
index 69a4209e5..451a308e2 100644
--- a/navit/xmlconfig.c
+++ b/navit/xmlconfig.c
@@ -279,6 +279,8 @@ object_func_lookup(enum attr_type type) {
return &tracking_func;
case attr_speech:
return &speech_func;
+ case attr_traffic:
+ return &traffic_func;
case attr_vehicle:
return &vehicle_func;
case attr_vehicleprofile:
@@ -332,7 +334,7 @@ static char *element_fixmes[]= {
};
static void initStatic(void) {
- elements=g_new0(struct element_func,44); //43 is a number of elements + ending NULL element
+ elements=g_new0(struct element_func, 45); //44 is a number of elements + ending NULL element
elements[0].name="config";
elements[0].parent=NULL;
@@ -547,6 +549,11 @@ static void initStatic(void) {
elements[42].parent="navit";
elements[42].func=NULL;
elements[42].type=attr_script;
+
+ elements[43].name="traffic";
+ elements[43].parent="navit";
+ elements[43].func=NULL;
+ elements[43].type=attr_traffic;
}
/**
@@ -1009,35 +1016,96 @@ static void parse_node_text(ezxml_t node, void *data, void (*start)(void *, cons
}
#endif
-void xml_parse_text(const char *document, void *data,
- void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **),
- void (*end)(xml_context *, const char *, void *, GError **),
- void (*text)(xml_context *, const char *, gsize, void *, GError **)) {
+/**
+ * @brief Parses an XML file.
+ *
+ * @param filename The XML file to parse
+ * @param data Points to a user-defined data structure which will be passed to each of the callbacks
+ * passed in the following arguments
+ * @param start Callback which will be called when an open tag is encountered
+ * @param end Callback which will be called when a close tag is encountered
+ * @param text Callback which will be called when character data is encountered
+ *
+ * @return True on success, false on failure.
+ */
+int xml_parse_file(char *filename, void *data,
+ void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **),
+ void (*end)(xml_context *, const char *, void *, GError **),
+ void (*text)(xml_context *, const char *, gsize, void *, GError **)) {
+ int ret = 0;
+#if !USE_EZXML
+ gchar *contents;
+ gsize len;
+
+ if (g_file_get_contents(filename, &contents, &len, NULL)) {
+ dbg(lvl_debug, "XML data:\n%s\n", contents);
+ ret = xml_parse_text(contents, data, start, end, text);
+ g_free(contents);
+ } else {
+ dbg(lvl_error,"could not open XML file");
+ }
+#else
+ FILE *f;
+ ezxml_t root;
+
+ f = fopen(filename,"rb");
+ if (f) {
+ root = ezxml_parse_fp(f);
+ fclose(f);
+ if (root) {
+ parse_node_text(root, data, start, end, text);
+ ezxml_free(root);
+ ret = 1;
+ }
+ } else {
+ dbg(lvl_error,"could not open XML file");
+ }
+#endif
+ return ret;
+}
+
+/**
+ * @brief Parses XML text.
+ *
+ * @param document The XML data to parse
+ * @param data Points to a user-defined data structure which will be passed to each of the callbacks
+ * passed in the following arguments
+ * @param start Callback which will be called when an open tag is encountered
+ * @param end Callback which will be called when a close tag is encountered
+ * @param text Callback which will be called when character data is encountered
+ *
+ * @return True on success, false on failure.
+ */
+int xml_parse_text(const char *document, void *data,
+ void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **),
+ void (*end)(xml_context *, const char *, void *, GError **),
+ void (*text)(xml_context *, const char *, gsize, void *, GError **)) {
#if !USE_EZXML
GMarkupParser parser = { start, end, text, NULL, NULL};
xml_context *context;
gboolean result;
- context = g_markup_parse_context_new (&parser, 0, data, NULL);
if (!document) {
- dbg(lvl_error, "FATAL: No XML data supplied (looks like incorrect configuration for internal GUI).");
- exit(1);
+ dbg(lvl_error, "FATAL: No XML data supplied.");
+ return 0;
}
+ context = g_markup_parse_context_new (&parser, 0, data, NULL);
result = g_markup_parse_context_parse (context, document, strlen(document), NULL);
+ g_markup_parse_context_free (context);
if (!result) {
dbg(lvl_error, "FATAL: Cannot parse data as XML: '%s'", document);
- exit(1);
+ return 0;
}
- g_markup_parse_context_free (context);
#else
char *str=g_strdup(document);
ezxml_t root = ezxml_parse_str(str, strlen(str));
if (!root)
- return;
+ return 0;
parse_node_text(root, data, start, end, text);
ezxml_free(root);
g_free(str);
#endif
+ return 1;
}
diff --git a/navit/xmlconfig.h b/navit/xmlconfig.h
index 483f215d0..56f118eb2 100644
--- a/navit/xmlconfig.h
+++ b/navit/xmlconfig.h
@@ -112,7 +112,7 @@ struct object_func {
* default behavior, can be NULL for some object types */
};
-extern struct object_func map_func, mapset_func, navit_func, osd_func, tracking_func, vehicle_func, maps_func, layout_func, roadprofile_func, vehicleprofile_func, layer_func, config_func, profile_option_func, script_func, log_func, speech_func, navigation_func, route_func;
+extern struct object_func map_func, mapset_func, navit_func, osd_func, tracking_func, vehicle_func, maps_func, 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)
@@ -139,7 +139,11 @@ typedef GError xmlerror;
/* prototypes */
enum attr_type;
struct object_func *object_func_lookup(enum attr_type type);
-void xml_parse_text(const char *document, void *data, void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **), void (*end)(xml_context *, const char *, void *, GError **), void (*text)(xml_context*, const char *, gsize, void *, GError **));
+int xml_parse_file(char *filename, void *data,
+ void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **),
+ void (*end)(xml_context *, const char *, void *, GError **),
+ void (*text)(xml_context *, const char *, gsize, void *, GError **));
+int xml_parse_text(const char *document, void *data, void (*start)(xml_context *, const char *, const char **, const char **, void *, GError **), void (*end)(xml_context *, const char *, void *, GError **), void (*text)(xml_context*, const char *, gsize, void *, GError **));
gboolean config_load(const char *filename, xmlerror **error);
//static void xinclude(GMarkupParseContext *context, const gchar **attribute_names, const gchar **attribute_values, struct xmldocument *doc_old, xmlerror **error);