/* * Navit, a modular navigation system. * Copyright (C) 2005-2009 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 vehicle.c * @defgroup vehicle-plugins vehicle plugins * @ingroup plugins * @brief Generic components of the vehicle object. * * This file implements the generic vehicle interface, i.e. everything which is * not specific to a single data source. * * @author Navit Team * @date 2005-2014 */ #include #include #include #include #include /* for sqrt from coord.h */ #include "config.h" #include "debug.h" #include "coord.h" #include "item.h" #include "xmlconfig.h" #include "log.h" #include "plugin.h" #include "transform.h" #include "util.h" #include "event.h" #include "coord.h" #include "transform.h" #include "projection.h" #include "point.h" #include "graphics.h" #include "callback.h" #include "color.h" #include "layout.h" #include "vehicle.h" #include "navit_nls.h" struct vehicle { NAVIT_OBJECT struct vehicle_methods meth; struct vehicle_priv *priv; struct callback_list *cbl; struct log *nmea_log, *gpx_log; char *gpx_desc; // cursor struct cursor *cursor; int cursor_fixed; struct callback *animate_callback; struct event_timeout *animate_timer; struct point cursor_pnt; int need_resize; int real_w; int real_h; struct graphics *gra; struct graphics_gc *bg; struct transformation *trans; int angle; int speed; int sequence; GHashTable *log_to_cb; }; struct object_func vehicle_func; static void vehicle_set_default_name(struct vehicle *this); static void vehicle_draw_do(struct vehicle *this_); static void vehicle_log_nmea(struct vehicle *this_, struct log *log); static void vehicle_log_gpx(struct vehicle *this_, struct log *log); static void vehicle_log_textfile(struct vehicle *this_, struct log *log); static void vehicle_log_binfile(struct vehicle *this_, struct log *log); static int vehicle_add_log(struct vehicle *this_, struct log *log); /** * @brief Creates a new vehicle * * @param parent * @param attrs Points to a null-terminated array of pointers to the attributes * for the new vehicle type. * * @return The newly created vehicle object */ struct vehicle * vehicle_new(struct attr *parent, struct attr **attrs) { struct vehicle *this_; struct attr *source; struct vehicle_priv *(*vehicletype_new) (struct vehicle_methods * meth, struct callback_list * cbl, struct attr ** attrs); char *type, *colon; struct pcoord center; dbg(lvl_debug, "enter"); source = attr_search(attrs, attr_source); if (!source) { dbg(lvl_error, "incomplete vehicle definition: missing attribute 'source'"); return NULL; } type = g_strdup(source->u.str); colon = strchr(type, ':'); if (colon) *colon = '\0'; dbg(lvl_debug, "source='%s' type='%s'", source->u.str, type); vehicletype_new = plugin_get_category_vehicle(type); if (!vehicletype_new) { dbg(lvl_error, "invalid source '%s': unknown type '%s'", source->u.str, type); g_free(type); return NULL; } g_free(type); this_ = g_new0(struct vehicle, 1); this_->func=&vehicle_func; navit_object_ref((struct navit_object *)this_); this_->cbl = callback_list_new(); this_->priv = vehicletype_new(&this_->meth, this_->cbl, attrs); if (!this_->priv) { dbg(lvl_error, "vehicletype_new failed"); callback_list_destroy(this_->cbl); g_free(this_); return NULL; } this_->attrs=attr_list_dup(attrs); center.pro=projection_screen; center.x=0; center.y=0; this_->trans=transform_new(¢er, 16, 0); vehicle_set_default_name(this_); dbg(lvl_debug, "leave"); this_->log_to_cb=g_hash_table_new(NULL,NULL); return this_; } /** * @brief Destroys a vehicle * * @param this_ The vehicle to destroy */ void vehicle_destroy(struct vehicle *this_) { dbg(lvl_debug,"enter"); if (this_->animate_callback) { callback_destroy(this_->animate_callback); event_remove_timeout(this_->animate_timer); } transform_destroy(this_->trans); this_->meth.destroy(this_->priv); callback_list_destroy(this_->cbl); attr_list_free(this_->attrs); if (this_->bg) graphics_gc_destroy(this_->bg); if (this_->gra) graphics_free(this_->gra); g_free(this_); } /** * Creates an attribute iterator to be used with vehicles */ struct attr_iter * vehicle_attr_iter_new(void * unused) { return (struct attr_iter *)g_new0(void *,1); } /** * Destroys a vehicle attribute iterator * * @param iter a vehicle attr_iter */ void vehicle_attr_iter_destroy(struct attr_iter *iter) { g_free(iter); } /** * Generic get function * * @param this_ Pointer to a vehicle structure * @param type The attribute type to look for * @param attr Pointer to a {@code struct attr} to store the attribute * @param iter A vehicle attr_iter. This is only used for generic attributes; for attributes specific to the vehicle object it is ignored. * @return True for success, false for failure */ int vehicle_get_attr(struct vehicle *this_, enum attr_type type, struct attr *attr, struct attr_iter *iter) { int ret; if (type == attr_log_gpx_desc) { attr->u.str = this_->gpx_desc; return 1; } if (this_->meth.position_attr_get) { ret=this_->meth.position_attr_get(this_->priv, type, attr); if (ret) return ret; } return attr_generic_get_attr(this_->attrs, NULL, type, attr, iter); } /** * Generic set function * * @param this_ A vehicle * @param attr The attribute to set * @return False on success, true on failure */ int vehicle_set_attr(struct vehicle *this_, struct attr *attr) { int ret=1; if (attr->type == attr_log_gpx_desc) { g_free(this_->gpx_desc); this_->gpx_desc = g_strdup(attr->u.str); } else if (this_->meth.set_attr) ret=this_->meth.set_attr(this_->priv, attr); /* attr_profilename probably is never used by vehicle itself but it's used to control the routing engine. So any vehicle should allow to set and read it. */ if(attr->type == attr_profilename) ret=1; if (ret == 1 && attr->type != attr_navit && attr->type != attr_pdl_gps_update) this_->attrs=attr_generic_set_attr(this_->attrs, attr); return ret != 0; } /** * Generic add function * * @param this_ A vehicle * @param attr The attribute to add * * @return true if the attribute was added, false if not. */ int vehicle_add_attr(struct vehicle *this_, struct attr *attr) { int ret=1; switch (attr->type) { case attr_callback: callback_list_add(this_->cbl, attr->u.callback); break; case attr_log: ret=vehicle_add_log(this_, attr->u.log); break; // currently supporting oldstyle cursor config. case attr_cursor: this_->cursor_fixed=1; vehicle_set_cursor(this_, attr->u.cursor, 1); break; default: break; } if (ret) this_->attrs=attr_generic_add_attr(this_->attrs, attr); return ret; } /** * @brief Generic remove function. * * Used to remove a callback from the vehicle. * @param this_ A vehicle * @param attr */ int vehicle_remove_attr(struct vehicle *this_, struct attr *attr) { struct callback *cb; switch (attr->type) { case attr_callback: callback_list_remove(this_->cbl, attr->u.callback); break; case attr_log: cb=g_hash_table_lookup(this_->log_to_cb, attr->u.log); if (!cb) return 0; g_hash_table_remove(this_->log_to_cb, attr->u.log); callback_list_remove(this_->cbl, cb); break; default: this_->attrs=attr_generic_remove_attr(this_->attrs, attr); return 0; } return 1; } /** * Sets the cursor of a vehicle. * * @param this_ A vehicle * @param cursor A cursor * @author Ralph Sennhauser (10/2009) */ void vehicle_set_cursor(struct vehicle *this_, struct cursor *cursor, int overwrite) { dbg(lvl_debug,"enter this_=%p cursot=%p overwrit=%d, this_->cursor_fixed=%d, this_->gra=%p", this_, cursor, overwrite, this_->cursor_fixed, this_->gra); if (this_->cursor_fixed && !overwrite) return; if (this_->animate_callback) { event_remove_timeout(this_->animate_timer); this_->animate_timer=NULL; // dangling pointer! prevent double freeing. callback_destroy(this_->animate_callback); this_->animate_callback=NULL; // dangling pointer! prevent double freeing. } if (cursor && cursor->interval) { this_->animate_callback=callback_new_2(callback_cast(vehicle_draw_do), this_, 0); this_->animate_timer=event_add_timeout(cursor->interval, 1, this_->animate_callback); } /* we changed the cursor, so the overlay (if existing) may need a resize */ this_->need_resize=1; this_->cursor=cursor; /* if the graphics was already created, but a NULL cursor was set, we need to disable * otherwise stale overlay */ if(this_->gra) { if(this_->cursor) graphics_overlay_disable(this_->gra, 0); else graphics_overlay_disable(this_->gra, 1); } /* vehicle_draw will care for the graphics */ } /** * Draws a vehicle on top of a graphics. * * @param this_ The vehicle * @param gra The graphics * @param pnt Screen coordinates of the vehicle. * @param angle The angle relative to the map. * @param speed The speed of the vehicle. */ void vehicle_draw(struct vehicle *this_, struct graphics *gra, struct point *pnt, int angle, int speed) { struct point sc; if (angle < 0) angle+=360; dbg(lvl_debug,"enter this=%p gra=%p pnt=%p dir=%d speed=%d", this_, gra, pnt, angle, speed); dbg(lvl_debug,"point %d,%d", pnt->x, pnt->y); this_->cursor_pnt=*pnt; this_->angle=angle; this_->speed=speed; if (!this_->cursor) return; if((this_->need_resize) || !(this_->gra)) { /* recalculate real size of the required overlay */ navit_float radius; /* get the radius of the out circle. Pythagoras greets */ radius = navit_sqrt((this_->cursor->w * this_->cursor->w) + (this_->cursor->h * this_->cursor->h)); /* since we rotate the rectangle around the center to indicate direction, the overlay needs to be at least the * radius of the out circle big. The +1 compensates the rounding error. */ this_->real_w = (int)radius +1; this_->real_h = (int)radius +1; /* set transform center to the middle of the cursor */ sc.x = this_->real_w/2; sc.y = this_->real_h/2; transform_set_screen_center(this_->trans, &sc); } /* move the cursor point from te center to the top left*/ this_->cursor_pnt.x-=(this_->real_w/2); this_->cursor_pnt.y-=(this_->real_h/2); dbg(lvl_debug,"real point %d,%d real size %d,%d", this_->cursor_pnt.x, this_->cursor_pnt.y, this_->real_w, this_->real_h); if (!this_->gra) { struct color c; this_->need_resize=0; this_->gra=graphics_overlay_new(gra, &this_->cursor_pnt, this_->real_w, this_->real_h, 0); if (this_->gra) { graphics_init(this_->gra); this_->bg=graphics_gc_new(this_->gra); c.r=0; c.g=0; c.b=0; c.a=0; graphics_gc_set_foreground(this_->bg, &c); graphics_background_gc(this_->gra, this_->bg); } } else if (this_->need_resize) { /* seems the cursor was changed. Need to resize */ this_->need_resize=0; graphics_overlay_resize(this_->gra, &this_->cursor_pnt, this_->real_w, this_->real_h, 0); } vehicle_draw_do(this_); } int vehicle_get_cursor_data(struct vehicle *this, struct point *pnt, int *angle, int *speed) { *pnt=this->cursor_pnt; *angle=this->angle; *speed=this->speed; return 1; } static void vehicle_set_default_name(struct vehicle *this_) { struct attr default_name; if (!attr_search(this_->attrs, attr_name)) { default_name.type=attr_name; // Safe cast: attr_generic_set_attr does not modify its parameter. default_name.u.str=(char*)_("Unnamed vehicle"); this_->attrs=attr_generic_set_attr(this_->attrs, &default_name); dbg(lvl_error, "Incomplete vehicle definition: missing attribute 'name'. Default name set."); } } static void vehicle_draw_do(struct vehicle *this_) { struct point p; struct cursor *cursor=this_->cursor; int speed=this_->speed; int angle=this_->angle; int sequence=this_->sequence; struct attr **attr; char *label=NULL; int match=0; if (!this_->cursor || !this_->cursor->attrs || !this_->gra) return; attr=this_->attrs; while (attr && *attr) { if ((*attr)->type == attr_name) label=(*attr)->u.str; attr++; } transform_set_yaw(this_->trans, -this_->angle); graphics_draw_mode(this_->gra, draw_mode_begin); p.x=0; p.y=0; /* clear old content by overwriting with an rectangle */ graphics_draw_rectangle(this_->gra, this_->bg, &p, this_->real_w, this_->real_h); attr=cursor->attrs; while (*attr) { if ((*attr)->type == attr_itemgra) { struct itemgra *itm=(*attr)->u.itemgra; dbg(lvl_debug,"speed %d-%d %d", itm->speed_range.min, itm->speed_range.max, speed); if (speed >= itm->speed_range.min && speed <= itm->speed_range.max && angle >= itm->angle_range.min && angle <= itm->angle_range.max && sequence >= itm->sequence_range.min && sequence <= itm->sequence_range.max) { graphics_draw_itemgra(this_->gra, itm, this_->trans, label); } if (sequence < itm->sequence_range.max) match=1; } ++attr; } graphics_draw_drag(this_->gra, &this_->cursor_pnt); graphics_draw_mode(this_->gra, draw_mode_end); if (this_->animate_callback) { ++this_->sequence; if (cursor->sequence_range && cursor->sequence_range->max < this_->sequence) this_->sequence=cursor->sequence_range->min; if (! match && ! cursor->sequence_range) this_->sequence=0; } } /** * @brief Writes to an NMEA log. * * @param this_ The vehicle supplying data * @param log The log to write to */ static void vehicle_log_nmea(struct vehicle *this_, struct log *log) { struct attr pos_attr; if (!this_->meth.position_attr_get) return; if (!this_->meth.position_attr_get(this_->priv, attr_position_nmea, &pos_attr)) return; log_write(log, pos_attr.u.str, strlen(pos_attr.u.str), 0); } /** * Add a tag to the extensions section of a GPX trackpoint. * * @param tag The tag to add * @param logstr Pointer to a pointer to a string to be inserted into the log. * When calling this function, {@code *logstr} must point to the substring into which the new tag is * to be inserted. If {@code *logstr} is NULL, a new string will be created for the extensions section. * Upon returning, {@code *logstr} will point to the new string with the additional tag inserted. */ void vehicle_log_gpx_add_tag(char *tag, char **logstr) { char *ext_start="\t\n"; char *ext_end="\t\n"; char *trkpt_end=""; char *start=NULL,*end=NULL; if (!*logstr) { start=g_strdup(ext_start); end=g_strdup(ext_end); } else { char *str=strstr(*logstr, ext_start); int len; if (str) { len=str-*logstr+strlen(ext_start); start=g_strdup(*logstr); start[len]='\0'; end=g_strdup(str+strlen(ext_start)); } else { str=strstr(*logstr, trkpt_end); len=str-*logstr; end=g_strdup_printf("%s%s",ext_end,str); str=g_strdup(*logstr); str[len]='\0'; start=g_strdup_printf("%s%s",str,ext_start); g_free(str); } } *logstr=g_strdup_printf("%s%s%s",start,tag,end); g_free(start); g_free(end); } /** * @brief Writes a trackpoint to a GPX log. * * @param this_ The vehicle supplying data * @param log The log to write to */ static void vehicle_log_gpx(struct vehicle *this_, struct log *log) { struct attr attr,*attrp, fix_attr; enum attr_type *attr_types; char *logstr; char *extensions="\t\n"; if (!this_->meth.position_attr_get) return; if (log_get_attr(log, attr_attr_types, &attr, NULL)) attr_types=attr.u.attr_types; else attr_types=NULL; if (this_->meth.position_attr_get(this_->priv, attr_position_fix_type, &fix_attr)) { if ( fix_attr.u.num == 0 ) return; } if (!this_->meth.position_attr_get(this_->priv, attr_position_coord_geo, &attr)) return; logstr=g_strdup_printf("\n",attr.u.coord_geo->lat,attr.u.coord_geo->lng); if (attr_types && attr_types_contains_default(attr_types, attr_position_time_iso8601, 0)) { if (this_->meth.position_attr_get(this_->priv, attr_position_time_iso8601, &attr)) { logstr=g_strconcat_printf(logstr,"\t\n",attr.u.str); } else { char *timep = current_to_iso8601(); logstr=g_strconcat_printf(logstr,"\t\n",timep); g_free(timep); } } if (this_->gpx_desc) { logstr=g_strconcat_printf(logstr,"\t%s\n",this_->gpx_desc); g_free(this_->gpx_desc); this_->gpx_desc = NULL; } if (attr_types_contains_default(attr_types, attr_position_height,0) && this_->meth.position_attr_get(this_->priv, attr_position_height, &attr)) logstr=g_strconcat_printf(logstr,"\t%.6f\n",*attr.u.numd); // magnetic variation in degrees; we might use position_magnetic_direction and position_direction to figure it out // Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message (field 11, which vehicle_wince.c ignores) // GPS name (arbitrary) // comment // Source of data // Link to additional information (URL) // Text of GPS symbol name // Type (classification) // Type of GPS fix {'none'|'2d'|'3d'|'dgps'|'pps'}, leave out if unknown. Similar to position_fix_type but more detailed. if (attr_types_contains_default(attr_types, attr_position_sats_used,0) && this_->meth.position_attr_get(this_->priv, attr_position_sats_used, &attr)) logstr=g_strconcat_printf(logstr,"\t%d\n",attr.u.num); if (attr_types_contains_default(attr_types, attr_position_hdop,0) && this_->meth.position_attr_get(this_->priv, attr_position_hdop, &attr)) logstr=g_strconcat_printf(logstr,"\t%.6f\n",*attr.u.numd); // , Vertical and position dilution of precision, no corresponding attribute if (attr_types_contains_default(attr_types, attr_position_direction,0) && this_->meth.position_attr_get(this_->priv, attr_position_direction, &attr)) logstr=g_strconcat_printf(logstr,"\t%.1f\n",*attr.u.numd); if (attr_types_contains_default(attr_types, attr_position_speed, 0) && this_->meth.position_attr_get(this_->priv, attr_position_speed, &attr)) logstr=g_strconcat_printf(logstr,"\t%.2f\n",(*attr.u.numd / 3.6)); if (attr_types_contains_default(attr_types, attr_profilename, 0) && (attrp=attr_search(this_->attrs, attr_profilename))) { logstr=g_strconcat_printf(logstr,"%s\t\t%s\n",extensions,attrp->u.str); extensions=""; } if (attr_types_contains_default(attr_types, attr_position_radius, 0) && this_->meth.position_attr_get(this_->priv, attr_position_radius, &attr)) { logstr=g_strconcat_printf(logstr,"%s\t\t%.2f\n",extensions,*attr.u.numd); extensions=""; } if (!strcmp(extensions,"")) { logstr=g_strconcat_printf(logstr,"\t\n"); } logstr=g_strconcat_printf(logstr,"\n"); callback_list_call_attr_1(this_->cbl, attr_log_gpx, &logstr); log_write(log, logstr, strlen(logstr), 0); g_free(logstr); } /** * @brief Writes to a text log. * * @param this_ The vehicle supplying data * @param log The log to write to */ static void vehicle_log_textfile(struct vehicle *this_, struct log *log) { struct attr pos_attr,fix_attr; char *logstr; if (!this_->meth.position_attr_get) return; if (this_->meth.position_attr_get(this_->priv, attr_position_fix_type, &fix_attr)) { if (fix_attr.u.num == 0) return; } if (!this_->meth.position_attr_get(this_->priv, attr_position_coord_geo, &pos_attr)) return; logstr=g_strdup_printf("%f %f type=trackpoint\n", pos_attr.u.coord_geo->lng, pos_attr.u.coord_geo->lat); callback_list_call_attr_1(this_->cbl, attr_log_textfile, &logstr); log_write(log, logstr, strlen(logstr), 0); } /** * @brief Writes to a binary log. * * @param this_ The vehicle supplying data * @param log The log to write to */ static void vehicle_log_binfile(struct vehicle *this_, struct log *log) { struct attr pos_attr, fix_attr; int *buffer; int *buffer_new; int len,limit=1024,done=0,radius=25; struct coord c; enum log_flags flags; if (!this_->meth.position_attr_get) return; if (this_->meth.position_attr_get(this_->priv, attr_position_fix_type, &fix_attr)) { if (fix_attr.u.num == 0) return; } if (!this_->meth.position_attr_get(this_->priv, attr_position_coord_geo, &pos_attr)) return; transform_from_geo(projection_mg, pos_attr.u.coord_geo, &c); if (!c.x || !c.y) return; while (!done) { buffer=log_get_buffer(log, &len); if (! buffer || !len) { buffer_new=g_malloc(5*sizeof(int)); buffer_new[0]=2; buffer_new[1]=type_track; buffer_new[2]=0; } else { buffer_new=g_malloc((buffer[0]+3)*sizeof(int)); memcpy(buffer_new, buffer, (buffer[0]+1)*sizeof(int)); } dbg(lvl_debug,"c=0x%x,0x%x",c.x,c.y); buffer_new[buffer_new[0]+1]=c.x; buffer_new[buffer_new[0]+2]=c.y; buffer_new[0]+=2; buffer_new[2]+=2; if (buffer_new[2] > limit) { int count=buffer_new[2]/2; struct coord *out=g_alloca(sizeof(struct coord)*(count)); struct coord *in=(struct coord *)(buffer_new+3); int count_out=transform_douglas_peucker(in, count, radius, out); memcpy(in, out, count_out*2*sizeof(int)); buffer_new[0]+=(count_out-count)*2; buffer_new[2]+=(count_out-count)*2; flags=log_flag_replace_buffer|log_flag_force_flush|log_flag_truncate; } else { flags=log_flag_replace_buffer|log_flag_keep_pointer|log_flag_keep_buffer|log_flag_force_flush; done=1; } log_write(log, (char *)buffer_new, (buffer_new[0]+1)*sizeof(int), flags); } } /** * @brief Registers a new log to receive data. * * @param this_ The vehicle supplying data * @param log The log to write to * * @return False if the log is of an unknown type, true otherwise (including when {@code attr_type} is missing). */ static int vehicle_add_log(struct vehicle *this_, struct log *log) { struct callback *cb; struct attr type_attr; if (!log_get_attr(log, attr_type, &type_attr, NULL)) return 1; if (!strcmp(type_attr.u.str, "nmea")) { cb=callback_new_attr_2(callback_cast(vehicle_log_nmea), attr_position_coord_geo, this_, log); } else if (!strcmp(type_attr.u.str, "gpx")) { char *header = "\n" "\n" "\n" "\n"; char *trailer = "\n\n\n"; log_set_header(log, header, strlen(header)); log_set_trailer(log, trailer, strlen(trailer)); cb=callback_new_attr_2(callback_cast(vehicle_log_gpx), attr_position_coord_geo, this_, log); } else if (!strcmp(type_attr.u.str, "textfile")) { char *header = "type=track\n"; log_set_header(log, header, strlen(header)); cb=callback_new_attr_2(callback_cast(vehicle_log_textfile), attr_position_coord_geo, this_, log); } else if (!strcmp(type_attr.u.str, "binfile")) { cb=callback_new_attr_2(callback_cast(vehicle_log_binfile), attr_position_coord_geo, this_, log); } else return 0; g_hash_table_insert(this_->log_to_cb, log, cb); callback_list_add(this_->cbl, cb); return 1; } struct object_func vehicle_func = { attr_vehicle, (object_func_new)vehicle_new, (object_func_get_attr)vehicle_get_attr, (object_func_iter_new)vehicle_attr_iter_new, (object_func_iter_destroy)vehicle_attr_iter_destroy, (object_func_set_attr)vehicle_set_attr, (object_func_add_attr)vehicle_add_attr, (object_func_remove_attr)vehicle_remove_attr, (object_func_init)NULL, (object_func_destroy)vehicle_destroy, (object_func_dup)NULL, (object_func_ref)navit_object_ref, (object_func_unref)navit_object_unref, };