2 * Copyright (C) 2009-2011 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * SECTION:grits-viewer
20 * @short_description: Virtual globe base class
22 * #GritsViewer is the base class for the virtual globe widget. It handles
23 * everything not directly related to drawing the globe. Plugins and
24 * applications using the viewer should normally talk to the viewer and not care
25 * how it is implemented.
31 #include <gdk/gdkkeysyms.h>
33 #include "grits-marshal.h"
34 #include "grits-viewer.h"
36 #include "grits-util.h"
48 static guint signals[NUM_SIGNALS];
55 static void _grits_viewer_fix_location(GritsViewer *viewer)
57 while (viewer->location[0] < -90) viewer->location[0] += 180;
58 while (viewer->location[0] > 90) viewer->location[0] -= 180;
59 while (viewer->location[1] < -180) viewer->location[1] += 360;
60 while (viewer->location[1] > 180) viewer->location[1] -= 360;
61 viewer->location[2] = ABS(viewer->location[2]);
64 static void _grits_viewer_fix_rotation(GritsViewer *viewer)
66 while (viewer->rotation[0] < -180) viewer->rotation[0] += 360;
67 while (viewer->rotation[0] > 180) viewer->rotation[0] -= 360;
68 while (viewer->rotation[1] < -180) viewer->rotation[1] += 360;
69 while (viewer->rotation[1] > 180) viewer->rotation[1] -= 360;
70 while (viewer->rotation[2] < -180) viewer->rotation[2] += 360;
71 while (viewer->rotation[2] > 180) viewer->rotation[2] -= 360;
74 static gboolean _grits_viewer_queue_draw_cb(gpointer _viewer)
76 GritsViewer *viewer = _viewer;
77 g_mutex_lock(&viewer->draw_lock);
78 gtk_widget_queue_draw(GTK_WIDGET(viewer));
79 viewer->draw_source = 0;
80 g_mutex_unlock(&viewer->draw_lock);
85 static void _grits_viewer_emit_location_changed(GritsViewer *viewer)
87 g_signal_emit(viewer, signals[SIG_LOCATION_CHANGED], 0,
92 static void _grits_viewer_emit_rotation_changed(GritsViewer *viewer)
94 g_signal_emit(viewer, signals[SIG_ROTATION_CHANGED], 0,
99 static void _grits_viewer_emit_time_changed(GritsViewer *viewer)
101 g_signal_emit(viewer, signals[SIG_TIME_CHANGED], 0,
104 static void _grits_viewer_emit_refresh(GritsViewer *viewer)
106 g_signal_emit(viewer, signals[SIG_REFRESH], 0);
108 static void _grits_viewer_emit_offline(GritsViewer *viewer)
110 g_signal_emit(viewer, signals[SIG_OFFLINE], 0,
117 static gboolean on_key_press(GritsViewer *viewer, GdkEventKey *event, gpointer _)
119 g_debug("GritsViewer: on_key_press - key=%x, state=%x, plus=%x",
120 event->keyval, event->state, GDK_KEY_plus);
122 double lat, lon, elev, pan;
123 grits_viewer_get_location(viewer, &lat, &lon, &elev);
124 pan = MIN(elev/(EARTH_R/2), 30);
125 switch (event->keyval) {
126 case GDK_KEY_Left: case GDK_KEY_h: grits_viewer_pan(viewer, 0, -pan, 0); break;
127 case GDK_KEY_Down: case GDK_KEY_j: grits_viewer_pan(viewer, -pan, 0, 0); break;
128 case GDK_KEY_Up: case GDK_KEY_k: grits_viewer_pan(viewer, pan, 0, 0); break;
129 case GDK_KEY_Right: case GDK_KEY_l: grits_viewer_pan(viewer, 0, pan, 0); break;
130 case GDK_KEY_minus: case GDK_KEY_o: grits_viewer_zoom(viewer, 10./9); break;
131 case GDK_KEY_plus: case GDK_KEY_i: grits_viewer_zoom(viewer, 9./10); break;
132 case GDK_KEY_H: grits_viewer_rotate(viewer, 0, 0, -2); break;
133 case GDK_KEY_J: grits_viewer_rotate(viewer, 2, 0, 0); break;
134 case GDK_KEY_K: grits_viewer_rotate(viewer, -2, 0, 0); break;
135 case GDK_KEY_L: grits_viewer_rotate(viewer, 0, 0, 2); break;
140 static gboolean on_scroll(GritsViewer *viewer, GdkEventScroll *event, gpointer _)
142 switch (event->direction) {
143 case GDK_SCROLL_DOWN: grits_viewer_zoom(viewer, 10./9); break;
144 case GDK_SCROLL_UP: grits_viewer_zoom(viewer, 9./10); break;
157 static gboolean on_button_press(GritsViewer *viewer, GdkEventButton *event, gpointer _)
159 g_debug("GritsViewer: on_button_press - %d", event->button);
160 gtk_widget_grab_focus(GTK_WIDGET(viewer));
161 switch (event->button) {
162 case 1: viewer->drag_mode = GRITS_DRAG_PAN; break;
163 case 2: viewer->drag_mode = GRITS_DRAG_ZOOM; break;
164 case 3: viewer->drag_mode = GRITS_DRAG_TILT; break;
165 default: viewer->drag_mode = GRITS_DRAG_NONE; break;
167 viewer->drag_x = event->x;
168 viewer->drag_y = event->y;
172 static gboolean on_button_release(GritsViewer *viewer, GdkEventButton *event, gpointer _)
174 g_debug("GritsViewer: on_button_release");
175 viewer->drag_mode = GRITS_DRAG_NONE;
179 static gboolean on_motion_notify(GritsViewer *viewer, GdkEventMotion *event, gpointer _)
181 gdouble x = viewer->drag_x - event->x;
182 gdouble y = viewer->drag_y - event->y;
183 gdouble lat, lon, elev, scale, rx, ry, rz;
184 grits_viewer_get_location(GRITS_VIEWER(viewer), &lat, &lon, &elev);
185 grits_viewer_get_rotation(GRITS_VIEWER(viewer), &rx, &ry, &rz);
186 scale = (elev/EARTH_R/14.1) * (sin(deg2rad(ABS(rx)))*4+1);
187 switch (viewer->drag_mode) {
188 case GRITS_DRAG_PAN: grits_viewer_pan(viewer, -y*scale*0.782, x*scale, 0); break;
189 case GRITS_DRAG_ZOOM: grits_viewer_zoom(viewer, pow(2, -y/500)); break;
190 case GRITS_DRAG_TILT: grits_viewer_rotate(viewer, y/10, 0, x/10); break;
192 viewer->drag_x = event->x;
193 viewer->drag_y = event->y;
197 static void on_view_changed(GritsViewer *viewer,
198 gdouble _1, gdouble _2, gdouble _3)
200 grits_viewer_queue_draw(viewer);
207 * grits_viewer_setup:
208 * @viewer: the viewer
209 * @plugins: a plugins store
210 * @prefs: a prefs store
212 * This should be called by objects which implement GritsViewer somewhere in their
215 void grits_viewer_setup(GritsViewer *viewer, GritsPlugins *plugins, GritsPrefs *prefs)
217 viewer->plugins = plugins;
218 viewer->prefs = prefs;
219 viewer->offline = grits_prefs_get_boolean(prefs, "grits/offline", NULL);
223 * grits_viewer_set_time:
224 * @viewer: the viewer
225 * @time: the time to set the view to
227 * Set the current time for the view
229 void grits_viewer_set_time(GritsViewer *viewer, time_t time)
231 g_assert(GRITS_IS_VIEWER(viewer));
232 g_debug("GritsViewer: set_time - time=%ld", time);
234 _grits_viewer_emit_time_changed(viewer);
238 * grits_viewer_get_time:
239 * @viewer: the viewer
241 * Get the time that is being viewed
243 * Returns: the current time
245 time_t grits_viewer_get_time(GritsViewer *viewer)
247 g_assert(GRITS_IS_VIEWER(viewer));
248 g_debug("GritsViewer: get_time");
253 * grits_viewer_set_location:
254 * @viewer: the viewer
255 * @lat: the new latitude
256 * @lon: the new longitude
257 * @elev: the new elevation
259 * Set the location for the camera
261 void grits_viewer_set_location(GritsViewer *viewer, gdouble lat, gdouble lon, gdouble elev)
263 g_assert(GRITS_IS_VIEWER(viewer));
264 g_debug("GritsViewer: set_location");
265 viewer->location[0] = lat;
266 viewer->location[1] = lon;
267 viewer->location[2] = elev;
268 _grits_viewer_fix_location(viewer);
269 _grits_viewer_emit_location_changed(viewer);
273 * grits_viewer_get_location:
274 * @viewer: the viewer
275 * @lat: the location to store the latitude
276 * @lon: the location to store the longitude
277 * @elev: the location to store the elevation
279 * Get the location of the camera
281 void grits_viewer_get_location(GritsViewer *viewer, gdouble *lat, gdouble *lon, gdouble *elev)
283 g_assert(GRITS_IS_VIEWER(viewer));
284 //g_debug("GritsViewer: get_location");
285 *lat = viewer->location[0];
286 *lon = viewer->location[1];
287 *elev = viewer->location[2];
292 * @viewer: the viewer
293 * @forward: distance to move forward in meters
294 * @right: distance to move right in meters
295 * @up: distance to move up in meters
297 * Pan the location by a number of meters long the surface.
299 * Bugs: the distances are not in meters
300 * Bugs: panning does not move in strait lines
302 void grits_viewer_pan(GritsViewer *viewer, gdouble forward, gdouble sideways, gdouble up)
304 g_assert(GRITS_IS_VIEWER(viewer));
305 g_debug("GritsViewer: pan - forward=%8.3f, sideways=%8.3f, up=%8.3f",
306 forward, sideways, up);
307 gdouble dist = sqrt(forward*forward + sideways*sideways);
308 gdouble angle1 = deg2rad(viewer->rotation[2]);
309 gdouble angle2 = atan2(sideways, forward);
310 gdouble angle = angle1 + angle2;
311 /* This isn't accurate, but it's usable */
312 viewer->location[0] += dist*cos(angle);
313 viewer->location[1] += dist*sin(angle);
314 viewer->location[2] += up;
315 _grits_viewer_fix_location(viewer);
316 _grits_viewer_emit_location_changed(viewer);
321 * @viewer: the viewer
322 * @scale: the scale to multiple the elevation by
324 * Multiple the elevation by a scale.
326 void grits_viewer_zoom(GritsViewer *viewer, gdouble scale)
328 g_assert(GRITS_IS_VIEWER(viewer));
329 g_debug("GritsViewer: zoom");
330 viewer->location[2] *= scale;
331 _grits_viewer_emit_location_changed(viewer);
335 * grits_viewer_set_rotation:
336 * @viewer: the viewer
337 * @x: rotation new around the x axes
338 * @y: rotation new around the y axes
339 * @z: rotation new around the z axes
341 * Set the rotations in degrees around the x, y, and z axes.
343 void grits_viewer_set_rotation(GritsViewer *viewer, gdouble x, gdouble y, gdouble z)
345 g_assert(GRITS_IS_VIEWER(viewer));
346 g_debug("GritsViewer: set_rotation");
347 viewer->rotation[0] = x;
348 viewer->rotation[1] = y;
349 viewer->rotation[2] = z;
350 _grits_viewer_fix_rotation(viewer);
351 _grits_viewer_emit_rotation_changed(viewer);
355 * grits_viewer_get_rotation:
356 * @viewer: the viewer
357 * @x: rotation around the x axes
358 * @y: rotation around the y axes
359 * @z: rotation around the z axes
361 * Get the rotations in degrees around the x, y, and z axes.
363 void grits_viewer_get_rotation(GritsViewer *viewer, gdouble *x, gdouble *y, gdouble *z)
365 g_assert(GRITS_IS_VIEWER(viewer));
366 //g_debug("GritsViewer: get_rotation");
367 *x = viewer->rotation[0];
368 *y = viewer->rotation[1];
369 *z = viewer->rotation[2];
373 * grits_viewer_rotate:
374 * @viewer: the viewer
375 * @x: rotation around the x axes
376 * @y: rotation around the y axes
377 * @z: rotation around the z axes
379 * Add to the rotation around the x, y, and z axes.
381 void grits_viewer_rotate(GritsViewer *viewer, gdouble x, gdouble y, gdouble z)
383 g_assert(GRITS_IS_VIEWER(viewer));
384 g_debug("GritsViewer: rotate - x=%.0f, y=%.0f, z=%.0f", x, y, z);
385 viewer->rotation[0] += x;
386 viewer->rotation[1] += y;
387 viewer->rotation[2] += z;
388 _grits_viewer_fix_rotation(viewer);
389 _grits_viewer_emit_rotation_changed(viewer);
393 * grits_viewer_refresh:
394 * @viewer: the viewer
396 * Trigger the refresh signal. This will cause any remote data to be checked for
399 void grits_viewer_refresh(GritsViewer *viewer)
401 g_debug("GritsViewer: refresh");
402 _grits_viewer_emit_refresh(viewer);
406 * grits_viewer_set_offline:
407 * @viewer: the viewer
408 * @offline: %TRUE to enter offline mode
410 * Set the offline mode. If @offline is %TRUE, only locally cached data will be
413 void grits_viewer_set_offline(GritsViewer *viewer, gboolean offline)
415 g_assert(GRITS_IS_VIEWER(viewer));
416 g_debug("GritsViewer: set_offline - %d", offline);
417 grits_prefs_set_boolean(viewer->prefs, "grits/offline", offline);
418 viewer->offline = offline;
419 _grits_viewer_emit_offline(viewer);
423 * grits_viewer_get_offline:
424 * @viewer: the viewer
426 * Check if the viewer is in offline mode.
428 * Returns: %TRUE if the viewer is in offline mode.
430 gboolean grits_viewer_get_offline(GritsViewer *viewer)
432 g_assert(GRITS_IS_VIEWER(viewer));
433 g_debug("GritsViewer: get_offline - %d", viewer->offline);
434 return viewer->offline;
438 * grits_viewer_queue_draw:
439 * @viewer: the viewer
441 * Causes the viewer to redraw the screen. This has the safe effect as
442 * gtk_widget_queue_draw, but is thread safe, and probably faster.
444 void grits_viewer_queue_draw(GritsViewer *viewer)
446 g_mutex_lock(&viewer->draw_lock);
447 if (!viewer->draw_source)
448 viewer->draw_source = g_idle_add_full(G_PRIORITY_HIGH,
449 _grits_viewer_queue_draw_cb, viewer, NULL);
450 g_mutex_unlock(&viewer->draw_lock);
453 /***********************************
454 * To be implemented by subclasses *
455 ***********************************/
457 * grits_viewer_center_position:
458 * @viewer: the viewer
460 * @lon: the longitude
461 * @elev: the elevation
463 * Center the viewer on a point. This can be used before drawing operations to
464 * center the items a particular location.
466 void grits_viewer_center_position(GritsViewer *viewer,
467 gdouble lat, gdouble lon, gdouble elev)
469 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
470 if (!klass->center_position)
471 g_warning("GritsViewer: center_position - Unimplemented");
472 klass->center_position(viewer, lat, lon, elev);
476 * grits_viewer_project:
477 * @viewer: the viewer
479 * @lon: the longitude
480 * @elev: the elevation
481 * @px: the project x coordinate
482 * @py: the project y coordinate
483 * @pz: the project z coordinate
485 * Project a latitude, longitude, elevation point to to x, y, and z coordinates
486 * in screen space. Useful for drawing orthographic data over a particular point
487 * in space. E.g. #GritsMarker.
489 void grits_viewer_project(GritsViewer *viewer,
490 gdouble lat, gdouble lon, gdouble elev,
491 gdouble *px, gdouble *py, gdouble *pz)
493 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
495 g_warning("GritsViewer: project - Unimplemented");
496 klass->project(viewer, lat, lon, elev, px, py, pz);
500 * grits_viewer_unproject:
501 * @viewer: the viewer
502 * @x: x coordinate in screen space
503 * @y: y coordinate in screen space
504 * @z: z coordinate in screen space, or -1 to use the value
505 * from the depth buffer at x and y as the z value
507 * @lon: the longitude
508 * @elev: the elevation
510 * Project a x, y point in screen space to a latitude, longitude, and elevation
511 * point. Useful for finding the position of the cursor or another on-screen
512 * object in world coordinates.
514 void grits_viewer_unproject(GritsViewer *viewer,
515 gdouble px, gdouble py, gdouble pz,
516 gdouble *lat, gdouble *lon, gdouble *elev)
518 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
519 if (!klass->unproject)
520 g_warning("GritsViewer: unproject - Unimplemented");
521 klass->unproject(viewer, px, py, pz, lat, lon, elev);
525 * grits_viewer_clear_height_func:
526 * @viewer: the viewer
528 * Clears the height function for the entire viewer. Useful when an elevation
529 * plugin is unloaded.
531 void grits_viewer_clear_height_func(GritsViewer *viewer)
533 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
534 if (!klass->clear_height_func)
535 g_warning("GritsViewer: clear_height_func - Unimplemented");
536 klass->clear_height_func(viewer);
540 * grits_viewer_set_height_func:
541 * @viewer: the viewer
542 * @bounds: the area to set the height function for
543 * @height_func: the height function
544 * @user_data: user data to pass to the height function
545 * @update: %TRUE if the heights inside the bounds should be updated.
547 * Set the height function to be used for a given part of the surface..
549 void grits_viewer_set_height_func(GritsViewer *viewer, GritsBounds *bounds,
550 GritsHeightFunc height_func, gpointer user_data,
553 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
554 if (!klass->set_height_func)
555 g_warning("GritsViewer: set_height_func - Unimplemented");
556 klass->set_height_func(viewer, bounds, height_func, user_data, update);
561 * @viewer: the viewer
562 * @object: the object to add
563 * @level: the level to add the object to
564 * @sort: %TRUE if the object should be depth-sorted prior to being drawn
566 * Objects which are added to the viewer will be drawn on subsequent renderings
567 * if their level of details is adequate.
569 * The @level represents the order the object should be drawn in, this is
570 * unrelated to the objects actual position in the world.
572 * Semi-transparent objects should set @sort to %TRUE so that they are rendered
573 * correctly when they overlap other semi-transparent objects.
575 * The viewer steals the objects reference. Call g_object_ref if you plan on
576 * holding a reference as well.
578 void grits_viewer_add(GritsViewer *viewer, GritsObject *object,
579 gint level, gboolean sort)
581 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
583 g_warning("GritsViewer: add - Unimplemented");
584 object->viewer = viewer;
585 klass->add(viewer, object, level, sort);
589 * grits_viewer_remove:
590 * @viewer: the viewer
591 * @object: the object to remove
593 * Remove an object from the viewer.
595 void grits_viewer_remove(GritsViewer *viewer, GritsObject *object)
597 GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
601 g_warning("GritsViewer: remove - Unimplemented");
602 object->viewer = NULL;
603 klass->remove(viewer, object);
609 G_DEFINE_ABSTRACT_TYPE(GritsViewer, grits_viewer, GTK_TYPE_DRAWING_AREA);
610 static void grits_viewer_init(GritsViewer *viewer)
612 g_debug("GritsViewer: init");
614 viewer->time = time(NULL);
615 viewer->location[0] = 40;
616 viewer->location[1] = -100;
617 viewer->location[2] = EARTH_R;
618 viewer->rotation[0] = 0;
619 viewer->rotation[1] = 0;
620 viewer->rotation[2] = 0;
622 g_mutex_init(&viewer->draw_lock);
624 g_object_set(viewer, "can-focus", TRUE, NULL);
625 gtk_widget_add_events(GTK_WIDGET(viewer),
626 GDK_BUTTON_PRESS_MASK |
627 GDK_BUTTON_RELEASE_MASK |
628 GDK_POINTER_MOTION_MASK |
631 g_signal_connect(viewer, "key-press-event", G_CALLBACK(on_key_press), NULL);
632 g_signal_connect(viewer, "scroll-event", G_CALLBACK(on_scroll), NULL);
634 g_signal_connect(viewer, "button-press-event", G_CALLBACK(on_button_press), NULL);
635 g_signal_connect(viewer, "button-release-event", G_CALLBACK(on_button_release), NULL);
636 g_signal_connect(viewer, "motion-notify-event", G_CALLBACK(on_motion_notify), NULL);
638 g_signal_connect(viewer, "location-changed", G_CALLBACK(on_view_changed), NULL);
639 g_signal_connect(viewer, "rotation-changed", G_CALLBACK(on_view_changed), NULL);
641 static void grits_viewer_dispose(GObject *gobject)
643 g_debug("GritsViewer: dispose");
644 GritsViewer *viewer = GRITS_VIEWER(gobject);
645 g_mutex_lock(&viewer->draw_lock);
646 if (viewer->draw_source)
647 g_source_remove(viewer->draw_source);
648 g_mutex_unlock(&viewer->draw_lock);
649 G_OBJECT_CLASS(grits_viewer_parent_class)->dispose(gobject);
651 static void grits_viewer_finalize(GObject *gobject)
653 g_debug("GritsViewer: finalize");
654 GritsViewer *viewer = GRITS_VIEWER(gobject);
655 g_mutex_clear(&viewer->draw_lock);
656 G_OBJECT_CLASS(grits_viewer_parent_class)->finalize(gobject);
657 g_debug("GritsViewer: finalize - done");
659 static void grits_viewer_class_init(GritsViewerClass *klass)
661 g_debug("GritsViewer: class_init");
662 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
663 gobject_class->dispose = grits_viewer_dispose;
664 gobject_class->finalize = grits_viewer_finalize;
667 * GritsViewer::time-changed:
668 * @viewer: the viewer.
669 * @time: the new time.
671 * The ::time-changed signal is emitted when the viewers current time
674 signals[SIG_TIME_CHANGED] = g_signal_new(
676 G_TYPE_FROM_CLASS(gobject_class),
681 g_cclosure_marshal_VOID__LONG,
687 * GritsViewer::location-changed:
688 * @viewer: the viewer.
689 * @lat: the new latitude.
690 * @lon: the new longitude.
691 * @elev: the new elevation.
693 * The ::location-changed signal is emitted when the viewers camera
696 signals[SIG_LOCATION_CHANGED] = g_signal_new(
698 G_TYPE_FROM_CLASS(gobject_class),
703 grits_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
711 * GritsViewer::rotation-changed:
712 * @viewer: the viewer.
713 * @x: rotation new around the x axes.
714 * @y: rotation new around the y axes.
715 * @z: rotation new around the z axes.
717 * The ::rotation-changed signal is emitted when the viewers cameras
720 signals[SIG_ROTATION_CHANGED] = g_signal_new(
722 G_TYPE_FROM_CLASS(gobject_class),
727 grits_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
735 * GritsViewer::refresh:
736 * @viewer: the viewer.
738 * The ::refresh signal is emitted when a refresh is needed. If you are
739 * using real-time data from a remote server, you should connect to the
740 * refresh signal and update the data when necessary.
742 signals[SIG_REFRESH] = g_signal_new(
744 G_TYPE_FROM_CLASS(gobject_class),
749 g_cclosure_marshal_VOID__VOID,
754 * GritsViewer::offline:
755 * @viewer: the viewer.
756 * @offline: %TRUE if the viewer going offline.
758 * The ::offline signal is emitted when the viewers offline mode
761 signals[SIG_OFFLINE] = g_signal_new(
763 G_TYPE_FROM_CLASS(gobject_class),
768 g_cclosure_marshal_VOID__BOOLEAN,