Contents of /trunk/src/geomath.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 198 - (hide annotations)
Thu Nov 19 12:38:03 2009 UTC (14 years, 6 months ago) by harbaum
File MIME type: text/plain
File size: 14219 byte(s)
more geotoad
1 harbaum 1 /*
2     * Copyright (C) 2008 Till Harbaum <till@harbaum.org>.
3     *
4     * This file is part of GPXView.
5     *
6     * GPXView is free software: you can redistribute it and/or modify
7     * it under the terms of the GNU General Public License as published by
8     * the Free Software Foundation, either version 3 of the License, or
9     * (at your option) any later version.
10     *
11     * GPXView is distributed in the hope that it will be useful,
12     * but WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU General Public License for more details.
15     *
16     * You should have received a copy of the GNU General Public License
17     * along with GPXView. If not, see <http://www.gnu.org/licenses/>.
18     */
19    
20     #include "gpxview.h"
21     #include <math.h>
22    
23 harbaum 198 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5)
24     #include <hildon/hildon-entry.h>
25     #endif
26    
27 harbaum 1 #define STR_NAN "----"
28    
29     typedef struct {
30     appdata_t *appdata;
31    
32     GtkWidget *lon1, *lat1, *dist1, *dir1;
33     GtkWidget *lon2, *lat2;
34    
35     GtkWidget *distance, *proj_lat, *proj_lon;
36     GtkWidget *middle_lat, *middle_lon;
37     } math_dialog_state_t;
38    
39     static gboolean mark(GtkWidget *widget, gboolean valid) {
40     gtk_widget_set_state(widget, valid?GTK_STATE_NORMAL:TAG_STATE);
41     return valid;
42     }
43    
44     /* a entry that is colored red when being "active" */
45     static GtkWidget *gtk_red_entry_new(void) {
46     GdkColor color;
47 harbaum 198 #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5)
48 harbaum 1 GtkWidget *widget = gtk_entry_new();
49 harbaum 198 #else
50     GtkWidget *widget = hildon_entry_new(HILDON_SIZE_AUTO);
51     #endif
52 harbaum 1 gdk_color_parse("#ff0000", &color);
53     gtk_widget_modify_text(widget, TAG_STATE, &color);
54     return widget;
55     }
56    
57     /* a comboboxentry that is colored red when being "active" */
58     static GtkWidget *gtk_red_combo_box_entry_new_text(void) {
59     GdkColor color;
60     GtkWidget *widget = gtk_combo_box_entry_new_text();
61     gdk_color_parse("#ff0000", &color);
62     gtk_widget_modify_text(GTK_BIN(widget)->child, TAG_STATE, &color);
63     return widget;
64     }
65    
66     /* a label that is colored red when being "active" */
67     static GtkWidget *gtk_red_label_new(char *str) {
68     GdkColor color;
69 harbaum 198 GtkWidget *widget = left_label_new(str);
70 harbaum 1 gdk_color_parse("#ff0000", &color);
71     gtk_widget_modify_fg(widget, TAG_STATE, &color);
72     return widget;
73     }
74    
75     static GtkWidget *gtk_dir_entry_new(float val) {
76     char str[16];
77     GtkWidget *w = gtk_red_entry_new();
78    
79     snprintf(str, sizeof(str), _("%.1f°"), val);
80     gtk_entry_set_text(GTK_ENTRY(w), str);
81    
82     return w;
83     }
84    
85     float pos_lat_eval_combo(GtkWidget *widget) {
86     char *p = (char*)gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
87     float val = pos_parse_lat(p);
88     mark(widget, !isnan(val));
89    
90     return val;
91     }
92    
93     static float distance_eval(GtkWidget *widget, math_dialog_state_t *state) {
94     char *p = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
95     float val = distance_parse(p, state->appdata->imperial);
96     mark(widget, !isnan(val));
97    
98     return val;
99     }
100    
101     float direction_eval(GtkWidget *widget) {
102     char *p = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
103     float val;
104     if(sscanf(p, _("%f°"), &val) != 1)
105     val = NAN;
106    
107     mark(widget, !isnan(val));
108    
109     return val;
110     }
111    
112     static void on_calc_clicked(GtkButton *button, gpointer user_data) {
113     math_dialog_state_t *state = (math_dialog_state_t*)user_data;
114     pos_t pos1, pos2;
115     gboolean pos1_ok = FALSE, pos2_ok = FALSE;
116     float dist1;
117     gboolean dist1_ok = FALSE;
118     float dir1;
119     gboolean dir1_ok = FALSE;
120    
121     /* parse input */
122     pos1.lat = lat_get(state->lat1);
123     pos1.lon = lon_get(state->lon1);
124     if(!isnan(pos1.lat) && !isnan(pos1.lon)) pos1_ok = TRUE;
125    
126     pos2.lat = lat_get(state->lat2);
127     pos2.lon = lon_get(state->lon2);
128     if(!isnan(pos2.lat) && !isnan(pos2.lon)) pos2_ok = TRUE;
129    
130     dist1 = distance_eval(state->dist1, state);
131     if(!isnan(dist1)) dist1_ok = TRUE;
132    
133     dir1 = direction_eval(state->dir1);
134     if(!isnan(dir1)) dir1_ok = TRUE;
135    
136     /* ------------------- do all calculations ------------------- */
137    
138    
139     /* ------------------- distance of coo1 and coo2 ------------------- */
140     if(mark(state->distance, pos1_ok && pos2_ok)) {
141     char str[32];
142     float dist = gpx_pos_get_distance(pos1, pos2, state->appdata->imperial);
143     distance_str(str, sizeof(str), dist, state->appdata->imperial);
144    
145     gtk_label_set_text(GTK_LABEL(state->distance), str);
146     } else
147     gtk_label_set_text(GTK_LABEL(state->distance), STR_NAN);
148    
149     // N 53° 09.033' W 001° 50.666' 100km / 30° = N 53° 55.616, W001° 04.850
150     /* ------------------- coordinate projection ---------------- */
151     mark(state->proj_lat, pos1_ok && dist1_ok && dir1_ok);
152     if(mark(state->proj_lon, pos1_ok && dist1_ok && dir1_ok)) {
153     pos_t pro;
154    
155     /* get great circle radius in miles/kilometers */
156     float gcrad = state->appdata->imperial?3959.0:6371.0;
157    
158     // from: http://www.movable-type.co.uk/scripts/latlong.html
159     pro.lat = asin(sin(pos1.lat/180*M_PI) * cos(dist1/gcrad) +
160     cos(pos1.lat/180*M_PI) * sin(dist1/gcrad) *
161     cos(dir1/180*M_PI) )/M_PI*180;
162     pro.lon = pos1.lon + atan2(sin(dir1/180*M_PI)*sin(dist1/gcrad)*
163     cos(pos1.lat/180*M_PI),
164     cos(dist1/gcrad)-sin(pos1.lat/180*M_PI)*
165     sin(pro.lat/180*M_PI))/M_PI*180;
166     pro.lon = fmodf(pro.lon+180,360) - 180; // normalise to -180...+180
167    
168     char str[16];
169     pos_lat_str(str, sizeof(str), pro.lat);
170     gtk_label_set_text(GTK_LABEL(state->proj_lat), str);
171     pos_lon_str(str, sizeof(str), pro.lon);
172     gtk_label_set_text(GTK_LABEL(state->proj_lon), str);
173    
174     if(!isnan(pro.lat) && !isnan(pro.lon))
175     state->appdata->geomath = pro;
176     } else {
177     gtk_label_set_text(GTK_LABEL(state->proj_lat), STR_NAN);
178     gtk_label_set_text(GTK_LABEL(state->proj_lon), STR_NAN);
179     }
180    
181     #if 0
182     /* ------------ middle between both points ------------- */
183     mark(state->middle_lat, pos1_ok && pos2_ok);
184     if(mark(state->middle_lon, pos1_ok && pos2_ok)) {
185     pos_t res;
186    
187     float dlon = fabs(pos2.lon - pos1.lon)/180*M_PI;
188    
189     /* http://mathforum.org/library/drmath/view/51822.html */
190     res.lon = 180/M_PI * atan(cos(pos2.lat/180*M_PI)*sin(pos2.lon/180*M_PI)/
191     (cos(pos1.lat/180*M_PI)+cos(pos2.lat/180*M_PI)*cos(pos2.lon/180*M_PI)));
192     res.lat = 180/M_PI * atan((sin(pos1.lat/180*M_PI)+sin(pos2.lat/180*M_PI))/
193     sqrt(pow(cos(pos2.lat/180*M_PI)+cos(pos2.lat/180*M_PI)*cos(dlon),2)+
194     pow(cos(pos2.lat/180*M_PI)*sin(dlon),2)));
195    
196     char str1[32], str2[32];
197     pos_lat_str(str1,sizeof(str1),res.lat);
198     pos_lon_str(str2,sizeof(str2),res.lon);
199     printf("pos = %s/%s\n", str1, str2);
200     } else {
201     gtk_label_set_text(GTK_LABEL(state->middle_lat), STR_NAN);
202     gtk_label_set_text(GTK_LABEL(state->middle_lon), STR_NAN);
203     }
204     #endif
205     }
206    
207     static gint waypoint_changed_event(GtkWidget *widget, gpointer data ) {
208     math_dialog_state_t *state = (math_dialog_state_t*)data;
209     int wpt_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
210     pos_t *pos = NULL;
211    
212     if(wpt_idx < 0)
213     return FALSE;
214    
215     if(wpt_idx == 0)
216     pos = gps_get_pos(state->appdata);
217     else if(wpt_idx == 1) {
218 harbaum 198 #ifdef ENABLE_OSM_GPS_MAP
219     if(!isnan(state->appdata->map.pos.lat) &&
220     !isnan(state->appdata->map.pos.lon))
221     pos = &state->appdata->map.pos;
222     #endif
223     } else if(wpt_idx == 2) {
224 harbaum 1 pos_t cache_pos = gpx_cache_pos(state->appdata->cur_cache);
225     pos = &cache_pos;
226     } else {
227     wpt_t *wpt = state->appdata->cur_cache->wpt;
228 harbaum 198 while(wpt_idx > 3) {
229 harbaum 1 g_assert(wpt != NULL);
230     wpt = wpt->next;
231     wpt_idx--;
232     }
233     pos = &wpt->pos;
234     }
235    
236     if(pos) {
237     char str[32];
238     pos_lat_str(str,sizeof(str),pos->lat);
239     gtk_entry_set_text(GTK_ENTRY(state->lat1), str);
240     pos_lon_str(str,sizeof(str),pos->lon);
241     gtk_entry_set_text(GTK_ENTRY(state->lon1), str);
242     } else {
243     gtk_entry_set_text(GTK_ENTRY(state->lat1), STR_NAN);
244     gtk_entry_set_text(GTK_ENTRY(state->lon1), STR_NAN);
245     }
246    
247     mark(state->lat1, pos != NULL);
248     mark(state->lon1, pos != NULL);
249    
250     return FALSE;
251     }
252    
253 harbaum 198 static void callback_modified_lat(GtkWidget *widget, gpointer data ) {
254     float i = pos_parse_lat((char*)gtk_entry_get_text(GTK_ENTRY(widget)));
255     mark(widget, !isnan(i));
256     }
257    
258 harbaum 1 void geomath_dialog(appdata_t *appdata) {
259     static pos_t pos1 = { 0.0, 0.0 }, pos2 = { 0.0, 0.0 };
260     static float dist1 = 0.0;
261     static float dir1 = 0.0;
262     static gboolean is_imperial = FALSE;
263    
264     math_dialog_state_t state;
265     char str[32];
266    
267     /* this is quite ugly. It would be nice to run the entire system on */
268     /* one specific system (e.g. metric) and only convert for in- and output */
269     if(!appdata->imperial && is_imperial)
270     dist1 *= 6371.0/3959.0; /* we just switched to metric */
271     if(appdata->imperial && !is_imperial)
272     dist1 *= 3959.0/6371.0; /* we just switched to imperial */
273     is_imperial = appdata->imperial;
274    
275     state.appdata = appdata;
276    
277     #ifdef USE_MAEMO
278     if(appdata->cur_cache)
279     printf("current cache is %s\n", appdata->cur_cache->id);
280     else
281     printf("no current cache\n");
282     #endif
283    
284     GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Geomath"),
285     GTK_WINDOW(appdata->window),
286     // GTK_DIALOG_NO_SEPARATOR |
287     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
288     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
289     NULL);
290    
291     #if defined(USE_MAEMO) && defined(HILDON_HELP)
292     hildon_help_dialog_help_enable(GTK_DIALOG(dialog), HELP_ID_GEOMATH,
293     appdata->osso_context);
294     #endif
295    
296     gtk_window_set_default_size(GTK_WINDOW(dialog), DIALOG_WIDTH, DIALOG_HEIGHT);
297    
298     /* do geomath dialog */
299     GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
300    
301     /* ------------------------- input area ------------------------- */
302     GtkWidget *table = gtk_table_new(5, 3, FALSE);
303     gtk_table_set_col_spacing(GTK_TABLE(table), 1, 20);
304    
305 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Coordinate 1")), 1, 2, 0, 1);
306     gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Coordinate 2")), 2, 3, 0, 1);
307     gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Latitude:")), 0, 1, 1, 2);
308 harbaum 1
309     GtkWidget *cbox = gtk_red_combo_box_entry_new_text();
310     gtk_combo_box_append_text(GTK_COMBO_BOX(cbox), _("GPS"));
311 harbaum 198 gtk_combo_box_append_text(GTK_COMBO_BOX(cbox), _("Map"));
312 harbaum 1
313     if(appdata->cur_cache) {
314     gtk_combo_box_append_text(GTK_COMBO_BOX(cbox), appdata->cur_cache->id);
315     wpt_t *wpt = appdata->cur_cache->wpt;
316     while(wpt) {
317     gtk_combo_box_append_text(GTK_COMBO_BOX(cbox), wpt->id);
318     wpt = wpt->next;
319     }
320     }
321    
322     state.lat1 = GTK_BIN(cbox)->child;
323 harbaum 198 g_signal_connect(G_OBJECT(state.lat1), "changed",
324     G_CALLBACK(callback_modified_lat), NULL);
325 harbaum 1 pos_lat_str(str, sizeof(str), pos1.lat);
326     gtk_entry_set_text(GTK_ENTRY(state.lat1), str);
327    
328     gtk_signal_connect(GTK_OBJECT(cbox), "changed",
329     (GtkSignalFunc)waypoint_changed_event, &state);
330     gtk_table_attach_defaults(GTK_TABLE(table), cbox, 1,2,1,2);
331    
332    
333 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), state.lat2 = lat_entry_new(pos2.lat), 2, 3, 1, 2);
334     gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Longitude:")), 0, 1, 2, 3);
335     gtk_table_attach_defaults(GTK_TABLE(table), state.lon1 = lon_entry_new(pos1.lon), 1, 2, 2, 3);
336     gtk_table_attach_defaults(GTK_TABLE(table), state.lon2 = lon_entry_new(pos2.lon), 2, 3, 2, 3);
337     gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Distance:")), 0, 1, 3, 4);
338 harbaum 1 gtk_table_attach_defaults(GTK_TABLE(table), state.dist1 = dist_entry_new(dist1, appdata->imperial), 1, 2, 3, 4);
339 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Direction:")), 0, 1, 4, 5);
340     gtk_table_attach_defaults(GTK_TABLE(table), state.dir1 = gtk_dir_entry_new(dir1), 1, 2, 4, 5);
341 harbaum 1
342     gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0);
343    
344     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 0);
345    
346     /* ------------------------- do-it-button ------------------------- */
347    
348     GtkWidget *button = gtk_button_new_with_label(_("Calculate!"));
349 harbaum 198 g_signal_connect(button, "clicked", (GCallback)on_calc_clicked, &state);
350    
351 harbaum 133 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR == 5)
352     hildon_gtk_widget_set_theme_size(button,
353     (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH));
354 harbaum 198 gtk_table_attach(GTK_TABLE(table), button, 2,3,3,5, GTK_EXPAND, GTK_EXPAND, 0, 0);
355     #else
356     /* in non-maemo5 the button has its own row */
357     hbox = gtk_hbox_new(FALSE, 0);
358 harbaum 1 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
359     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, FALSE, 0);
360 harbaum 198 #endif
361 harbaum 1
362     /* ------------------------- output area ------------------------- */
363    
364     table = gtk_table_new(3, 3, FALSE);
365    
366 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Distance = ")),
367 harbaum 1 0, 1, 0, 1);
368     gtk_table_attach_defaults(GTK_TABLE(table), state.distance = gtk_red_label_new(STR_NAN),
369     1, 3, 0, 1);
370    
371 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Projection = ")),
372 harbaum 1 0, 1, 1, 2);
373     gtk_table_attach_defaults(GTK_TABLE(table), state.proj_lat = gtk_red_label_new(STR_NAN),
374     1, 2, 1, 2);
375     gtk_table_attach_defaults(GTK_TABLE(table), state.proj_lon = gtk_red_label_new(STR_NAN),
376     2, 3, 1, 2);
377     #if 0
378 harbaum 198 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Middle = ")),
379 harbaum 1 0, 1, 2, 3);
380     gtk_table_attach_defaults(GTK_TABLE(table), state.middle_lat = gtk_red_label_new(STR_NAN),
381     1, 2, 2, 3);
382     gtk_table_attach_defaults(GTK_TABLE(table), state.middle_lon = gtk_red_label_new(STR_NAN),
383     2, 3, 2, 3);
384     #endif
385    
386     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), table, TRUE, TRUE, 0);
387    
388    
389     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
390    
391     gtk_widget_show_all(dialog);
392     gtk_dialog_run(GTK_DIALOG(dialog));
393    
394     /* copy values back to local static variables so they re-appear if */
395     /* the dialog is re-opened, convert illegal values (NAN) to 0 */
396    
397     pos1.lat = lat_get(state.lat1); if(isnan(pos1.lat)) pos1.lat=0;
398     pos1.lon = lon_get(state.lon1); if(isnan(pos1.lon)) pos1.lon=0;
399     pos2.lat = lat_get(state.lat2); if(isnan(pos2.lat)) pos2.lat=0;
400     pos2.lon = lon_get(state.lon2); if(isnan(pos2.lon)) pos2.lon=0;
401     dist1 = distance_eval(state.dist1, &state); if(isnan(dist1)) dist1=0;
402     dir1 = direction_eval(state.dir1); if(isnan(dir1)) dir1=0;
403    
404     gtk_widget_destroy(dialog);
405     }