diff options
Diffstat (limited to 'navit')
28 files changed, 8342 insertions, 361 deletions
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); |