Parent Directory | Revision Log
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 | } |