Contents of /trunk/src/geomath.c

Parent Directory Parent Directory | Revision Log Revision Log


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