Contents of /trunk/src/geomath.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 214 - (show annotations)
Thu Nov 26 10:05:23 2009 UTC (14 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 11846 byte(s)
Unified coo tool
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 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5)
24 #include <hildon/hildon-entry.h>
25 #endif
26
27 #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 GtkWidget *widget = entry_new();
48 gdk_color_parse("#ff0000", &color);
49 gtk_widget_modify_text(widget, TAG_STATE, &color);
50 return widget;
51 }
52
53 /* a label that is colored red when being "active" */
54 static GtkWidget *gtk_red_label_new(char *str) {
55 GdkColor color;
56 GtkWidget *widget = left_label_new(str);
57 gdk_color_parse("#ff0000", &color);
58 gtk_widget_modify_fg(widget, TAG_STATE, &color);
59 return widget;
60 }
61
62 static GtkWidget *gtk_dir_entry_new(float val) {
63 char str[16];
64 GtkWidget *w = gtk_red_entry_new();
65
66 snprintf(str, sizeof(str), _("%.1f°"), val);
67 gtk_entry_set_text(GTK_ENTRY(w), str);
68
69 return w;
70 }
71
72 float pos_lat_eval_combo(GtkWidget *widget) {
73 char *p = (char*)gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
74 float val = pos_parse_lat(p);
75 mark(widget, !isnan(val));
76
77 return val;
78 }
79
80 static float distance_eval(GtkWidget *widget, math_dialog_state_t *state) {
81 char *p = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
82 float val = distance_parse(p, state->appdata->imperial);
83 mark(widget, !isnan(val));
84
85 return val;
86 }
87
88 float direction_eval(GtkWidget *widget) {
89 char *p = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
90 float val;
91 if(sscanf(p, _("%f°"), &val) != 1)
92 val = NAN;
93
94 mark(widget, !isnan(val));
95
96 return val;
97 }
98
99 static void on_calc_clicked(GtkButton *button, gpointer user_data) {
100 math_dialog_state_t *state = (math_dialog_state_t*)user_data;
101 pos_t pos1, pos2;
102 gboolean pos1_ok = FALSE, pos2_ok = FALSE;
103 float dist1;
104 gboolean dist1_ok = FALSE;
105 float dir1;
106 gboolean dir1_ok = FALSE;
107
108 /* parse input */
109 pos1.lat = lat_get(state->lat1);
110 pos1.lon = lon_get(state->lon1);
111 if(!isnan(pos1.lat) && !isnan(pos1.lon)) pos1_ok = TRUE;
112
113 pos2.lat = lat_get(state->lat2);
114 pos2.lon = lon_get(state->lon2);
115 if(!isnan(pos2.lat) && !isnan(pos2.lon)) pos2_ok = TRUE;
116
117 dist1 = distance_eval(state->dist1, state);
118 if(!isnan(dist1)) dist1_ok = TRUE;
119
120 dir1 = direction_eval(state->dir1);
121 if(!isnan(dir1)) dir1_ok = TRUE;
122
123 /* ------------------- do all calculations ------------------- */
124
125
126 /* ------------------- distance of coo1 and coo2 ------------------- */
127 if(mark(state->distance, pos1_ok && pos2_ok)) {
128 char str[32];
129 float dist = gpx_pos_get_distance(pos1, pos2, state->appdata->imperial);
130 distance_str(str, sizeof(str), dist, state->appdata->imperial);
131
132 gtk_label_set_text(GTK_LABEL(state->distance), str);
133 } else
134 gtk_label_set_text(GTK_LABEL(state->distance), STR_NAN);
135
136 // N 53° 09.033' W 001° 50.666' 100km / 30° = N 53° 55.616, W001° 04.850
137 /* ------------------- coordinate projection ---------------- */
138 mark(state->proj_lat, pos1_ok && dist1_ok && dir1_ok);
139 if(mark(state->proj_lon, pos1_ok && dist1_ok && dir1_ok)) {
140 pos_t pro;
141
142 /* get great circle radius in miles/kilometers */
143 float gcrad = state->appdata->imperial?3959.0:6371.0;
144
145 // from: http://www.movable-type.co.uk/scripts/latlong.html
146 pro.lat = asin(sin(pos1.lat/180*M_PI) * cos(dist1/gcrad) +
147 cos(pos1.lat/180*M_PI) * sin(dist1/gcrad) *
148 cos(dir1/180*M_PI) )/M_PI*180;
149 pro.lon = pos1.lon + atan2(sin(dir1/180*M_PI)*sin(dist1/gcrad)*
150 cos(pos1.lat/180*M_PI),
151 cos(dist1/gcrad)-sin(pos1.lat/180*M_PI)*
152 sin(pro.lat/180*M_PI))/M_PI*180;
153 pro.lon = fmodf(pro.lon+180,360) - 180; // normalise to -180...+180
154
155 char str[16];
156 pos_lat_str(str, sizeof(str), pro.lat);
157 gtk_label_set_text(GTK_LABEL(state->proj_lat), str);
158 pos_lon_str(str, sizeof(str), pro.lon);
159 gtk_label_set_text(GTK_LABEL(state->proj_lon), str);
160
161 if(!isnan(pro.lat) && !isnan(pro.lon))
162 state->appdata->geomath = pro;
163 } else {
164 gtk_label_set_text(GTK_LABEL(state->proj_lat), STR_NAN);
165 gtk_label_set_text(GTK_LABEL(state->proj_lon), STR_NAN);
166 }
167
168 #if 0
169 /* ------------ middle between both points ------------- */
170 mark(state->middle_lat, pos1_ok && pos2_ok);
171 if(mark(state->middle_lon, pos1_ok && pos2_ok)) {
172 pos_t res;
173
174 float dlon = fabs(pos2.lon - pos1.lon)/180*M_PI;
175
176 /* http://mathforum.org/library/drmath/view/51822.html */
177 res.lon = 180/M_PI * atan(cos(pos2.lat/180*M_PI)*sin(pos2.lon/180*M_PI)/
178 (cos(pos1.lat/180*M_PI)+cos(pos2.lat/180*M_PI)*cos(pos2.lon/180*M_PI)));
179 res.lat = 180/M_PI * atan((sin(pos1.lat/180*M_PI)+sin(pos2.lat/180*M_PI))/
180 sqrt(pow(cos(pos2.lat/180*M_PI)+cos(pos2.lat/180*M_PI)*cos(dlon),2)+
181 pow(cos(pos2.lat/180*M_PI)*sin(dlon),2)));
182
183 char str1[32], str2[32];
184 pos_lat_str(str1,sizeof(str1),res.lat);
185 pos_lon_str(str2,sizeof(str2),res.lon);
186 printf("pos = %s/%s\n", str1, str2);
187 } else {
188 gtk_label_set_text(GTK_LABEL(state->middle_lat), STR_NAN);
189 gtk_label_set_text(GTK_LABEL(state->middle_lon), STR_NAN);
190 }
191 #endif
192 }
193
194 void geomath_dialog(appdata_t *appdata) {
195 static pos_t pos1 = { 0.0, 0.0 }, pos2 = { 0.0, 0.0 };
196 static float dist1 = 0.0;
197 static float dir1 = 0.0;
198 static gboolean is_imperial = FALSE;
199
200 math_dialog_state_t state;
201
202 /* this is quite ugly. It would be nice to run the entire system on */
203 /* one specific system (e.g. metric) and only convert for in- and output */
204 if(!appdata->imperial && is_imperial)
205 dist1 *= 6371.0/3959.0; /* we just switched to metric */
206 if(appdata->imperial && !is_imperial)
207 dist1 *= 3959.0/6371.0; /* we just switched to imperial */
208 is_imperial = appdata->imperial;
209
210 state.appdata = appdata;
211
212 #ifdef USE_MAEMO
213 if(appdata->cur_cache)
214 printf("current cache is %s\n", appdata->cur_cache->id);
215 else
216 printf("no current cache\n");
217 #endif
218
219 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Geomath"),
220 GTK_WINDOW(appdata->window),
221 // GTK_DIALOG_NO_SEPARATOR |
222 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
223 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
224 NULL);
225
226 #if defined(USE_MAEMO) && defined(HILDON_HELP)
227 hildon_help_dialog_help_enable(GTK_DIALOG(dialog), HELP_ID_GEOMATH,
228 appdata->osso_context);
229 #endif
230
231 gtk_window_set_default_size(GTK_WINDOW(dialog), DIALOG_WIDTH, DIALOG_HEIGHT);
232
233 /* do geomath dialog */
234 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
235
236 /* ------------------------- input area ------------------------- */
237 GtkWidget *table = gtk_table_new(5, 5, FALSE);
238 gtk_table_set_col_spacing(GTK_TABLE(table), 2, 20);
239
240 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Latitude:")), 0, 1, 1, 2);
241 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Longitude:")), 0, 1, 2, 3);
242
243 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Coordinate 1")), 1, 3, 0, 1);
244 gtk_table_attach_defaults(GTK_TABLE(table), state.lat1 = lat_entry_new(pos1.lat), 1, 2, 1, 2);
245 gtk_table_attach_defaults(GTK_TABLE(table), state.lon1 = lon_entry_new(pos1.lon), 1, 2, 2, 3);
246 gtk_table_attach_defaults(GTK_TABLE(table), coo_popup(appdata, state.lat1, state.lon1), 2, 3, 1, 2);
247
248 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Coordinate 2")), 3, 5, 0, 1);
249 gtk_table_attach_defaults(GTK_TABLE(table), state.lat2 = lat_entry_new(pos2.lat), 3, 4, 1, 2);
250 gtk_table_attach_defaults(GTK_TABLE(table), state.lon2 = lon_entry_new(pos2.lon), 3, 4, 2, 3);
251 gtk_table_attach_defaults(GTK_TABLE(table), coo_popup(appdata, state.lat2, state.lon2), 4, 5, 1, 2);
252
253 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Distance:")), 0, 1, 3, 4);
254 gtk_table_attach_defaults(GTK_TABLE(table), state.dist1 = dist_entry_new(dist1, appdata->imperial), 1, 3, 3, 4);
255
256 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Direction:")), 0, 1, 4, 5);
257 gtk_table_attach_defaults(GTK_TABLE(table), state.dir1 = gtk_dir_entry_new(dir1), 1, 3, 4, 5);
258
259 gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0);
260
261 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 0);
262
263 /* ------------------------- do-it-button ------------------------- */
264
265 GtkWidget *button = gtk_button_new_with_label(_("Calculate!"));
266 g_signal_connect(button, "clicked", (GCallback)on_calc_clicked, &state);
267
268 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR == 5)
269 hildon_gtk_widget_set_theme_size(button,
270 (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH));
271 gtk_table_attach(GTK_TABLE(table), button, 3,5,3,5, GTK_EXPAND, GTK_EXPAND, 0, 0);
272 #else
273 /* in non-maemo5 the button has its own row */
274 hbox = gtk_hbox_new(FALSE, 0);
275 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
276 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, FALSE, 0);
277 #endif
278
279 /* ------------------------- output area ------------------------- */
280
281 table = gtk_table_new(3, 3, FALSE);
282
283 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Distance = ")),
284 0, 1, 0, 1);
285 gtk_table_attach_defaults(GTK_TABLE(table), state.distance = gtk_red_label_new(STR_NAN),
286 1, 3, 0, 1);
287
288 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Projection = ")),
289 0, 1, 1, 2);
290 gtk_table_attach_defaults(GTK_TABLE(table), state.proj_lat = gtk_red_label_new(STR_NAN),
291 1, 2, 1, 2);
292 gtk_table_attach_defaults(GTK_TABLE(table), state.proj_lon = gtk_red_label_new(STR_NAN),
293 2, 3, 1, 2);
294 #if 0
295 gtk_table_attach_defaults(GTK_TABLE(table), left_label_new(_("Middle = ")),
296 0, 1, 2, 3);
297 gtk_table_attach_defaults(GTK_TABLE(table), state.middle_lat = gtk_red_label_new(STR_NAN),
298 1, 2, 2, 3);
299 gtk_table_attach_defaults(GTK_TABLE(table), state.middle_lon = gtk_red_label_new(STR_NAN),
300 2, 3, 2, 3);
301 #endif
302
303 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), table, TRUE, TRUE, 0);
304
305
306 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
307
308 gtk_widget_show_all(dialog);
309 gtk_dialog_run(GTK_DIALOG(dialog));
310
311 /* copy values back to local static variables so they re-appear if */
312 /* the dialog is re-opened, convert illegal values (NAN) to 0 */
313
314 pos1.lat = lat_get(state.lat1); if(isnan(pos1.lat)) pos1.lat=0;
315 pos1.lon = lon_get(state.lon1); if(isnan(pos1.lon)) pos1.lon=0;
316 pos2.lat = lat_get(state.lat2); if(isnan(pos2.lat)) pos2.lat=0;
317 pos2.lon = lon_get(state.lon2); if(isnan(pos2.lon)) pos2.lon=0;
318 dist1 = distance_eval(state.dist1, &state); if(isnan(dist1)) dist1=0;
319 dir1 = direction_eval(state.dir1); if(isnan(dir1)) dir1=0;
320
321 gtk_widget_destroy(dialog);
322 }