GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 33.2% 75 / 0 / 226
Functions: 53.6% 15 / 0 / 28
Branches: 22.6% 28 / 0 / 124

shumate/shumate-viewport.c
Line Branch Exec Source
1 /* shumate-viewport.c: Viewport
2 *
3 * Copyright (C) 2008 OpenedHand
4 * Copyright (C) 2011-2013 Jiri Techet <techet@gmail.com>
5 * Copyright (C) 2019 Marcus Lundblad <ml@update.uu.se>
6 * Copyright (C) 2020 Collabora, Ltd. (https://www.collabora.com)
7 * Copyright (C) 2020 Corentin Noël <corentin.noel@collabora.com>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
23 *
24 * Written by: Chris Lord <chris@openedhand.com>
25 */
26
27 #include "shumate-viewport.h"
28 #include "shumate-location.h"
29
30 /**
31 * ShumateViewport:
32 *
33 * The object holding the coordinate, zoom-level, and rotation state of the current view.
34 *
35 * As the object implements [iface@Shumate.Location], the latitude and longitude are
36 * accessible via the interface methods.
37 */
38
39 #define DEFAULT_MIN_ZOOM 0
40 #define DEFAULT_MAX_ZOOM 20
41
42 struct _ShumateViewport
43 {
44 GObject parent_instance;
45
46 double lon;
47 double lat;
48
49 double zoom_level;
50 guint min_zoom_level;
51 guint max_zoom_level;
52 double rotation;
53
54 ShumateMapSource *ref_map_source;
55 };
56
57 static void shumate_viewport_shumate_location_interface_init (ShumateLocationInterface *iface);
58
59
4/6
shumate_viewport_class_intern_init:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
shumate_viewport_get_type:
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 7 taken 246 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 7 not taken.
261 G_DEFINE_TYPE_WITH_CODE (ShumateViewport, shumate_viewport, G_TYPE_OBJECT,
60 G_IMPLEMENT_INTERFACE (SHUMATE_TYPE_LOCATION, shumate_viewport_shumate_location_interface_init));
61
62 /* Remember to update shumate_viewport_copy() when adding properties */
63 enum
64 {
65 PROP_ZOOM_LEVEL = 1,
66 PROP_MIN_ZOOM_LEVEL,
67 PROP_MAX_ZOOM_LEVEL,
68 PROP_REFERENCE_MAP_SOURCE,
69 PROP_ROTATION,
70 N_PROPERTIES,
71
72 PROP_LONGITUDE,
73 PROP_LATITUDE,
74 };
75
76 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
77
78 enum {
79 CHANGED,
80 LAST_SIGNAL
81 };
82
83 static guint signals[LAST_SIGNAL] = { 0, };
84
85 static double
86 ✗ shumate_viewport_get_latitude (ShumateLocation *location)
87 {
88 ✗ ShumateViewport *self = (ShumateViewport *)location;
89
90 ✗ g_assert (SHUMATE_IS_VIEWPORT (self));
91
92 ✗ return self->lat;
93 }
94
95 static double
96 ✗ shumate_viewport_get_longitude (ShumateLocation *location)
97 {
98 ✗ ShumateViewport *self = (ShumateViewport *)location;
99
100 ✗ g_assert (SHUMATE_IS_VIEWPORT (self));
101
102 ✗ return self->lon;
103 }
104
105 static void
106 ✗ shumate_viewport_set_location (ShumateLocation *location,
107 double latitude,
108 double longitude)
109 {
110 ✗ ShumateViewport *self = (ShumateViewport *)location;
111
112 ✗ g_assert (SHUMATE_IS_VIEWPORT (self));
113
114 ✗ self->lon = CLAMP (longitude, SHUMATE_MIN_LONGITUDE, SHUMATE_MAX_LONGITUDE);
115 ✗ self->lat = CLAMP (latitude, SHUMATE_MIN_LATITUDE, SHUMATE_MAX_LATITUDE);
116 ✗ g_object_notify (G_OBJECT (self), "longitude");
117 ✗ g_object_notify (G_OBJECT (self), "latitude");
118 ✗ g_signal_emit (self, signals[CHANGED], 0);
119 ✗ }
120
121 static void
122 ✗ shumate_viewport_get_property (GObject *object,
123 guint prop_id,
124 GValue *value,
125 GParamSpec *pspec)
126 {
127 ✗ ShumateViewport *self = SHUMATE_VIEWPORT (object);
128
129 ✗ switch (prop_id)
130 {
131 ✗ case PROP_ZOOM_LEVEL:
132 ✗ g_value_set_double (value, self->zoom_level);
133 ✗ break;
134
135 ✗ case PROP_MIN_ZOOM_LEVEL:
136 ✗ g_value_set_uint (value, self->min_zoom_level);
137 ✗ break;
138
139 ✗ case PROP_MAX_ZOOM_LEVEL:
140 ✗ g_value_set_uint (value, self->max_zoom_level);
141 ✗ break;
142
143 ✗ case PROP_REFERENCE_MAP_SOURCE:
144 ✗ g_value_set_object (value, self->ref_map_source);
145 ✗ break;
146
147 ✗ case PROP_ROTATION:
148 ✗ g_value_set_double (value, self->rotation);
149 ✗ break;
150
151 ✗ case PROP_LONGITUDE:
152 ✗ g_value_set_double (value, self->lon);
153 ✗ break;
154
155 ✗ case PROP_LATITUDE:
156 ✗ g_value_set_double (value, self->lat);
157 ✗ break;
158
159 ✗ default:
160 ✗ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161 ✗ break;
162 }
163 ✗ }
164
165 static void
166 ✗ shumate_viewport_set_property (GObject *object,
167 guint prop_id,
168 const GValue *value,
169 GParamSpec *pspec)
170 {
171 ✗ ShumateViewport *self = SHUMATE_VIEWPORT (object);
172
173 ✗ switch (prop_id)
174 {
175 ✗ case PROP_ZOOM_LEVEL:
176 ✗ shumate_viewport_set_zoom_level (self, g_value_get_double (value));
177 ✗ break;
178
179 ✗ case PROP_MIN_ZOOM_LEVEL:
180 ✗ shumate_viewport_set_min_zoom_level (self, g_value_get_uint (value));
181 ✗ break;
182
183 ✗ case PROP_MAX_ZOOM_LEVEL:
184 ✗ shumate_viewport_set_max_zoom_level (self, g_value_get_uint (value));
185 ✗ break;
186
187 ✗ case PROP_REFERENCE_MAP_SOURCE:
188 ✗ shumate_viewport_set_reference_map_source (self, g_value_get_object (value));
189 ✗ break;
190
191 ✗ case PROP_ROTATION:
192 ✗ shumate_viewport_set_rotation (self, g_value_get_double (value));
193 ✗ break;
194
195 ✗ case PROP_LONGITUDE:
196 ✗ self->lon = CLAMP (g_value_get_double (value), SHUMATE_MIN_LONGITUDE, SHUMATE_MAX_LONGITUDE);
197 ✗ g_object_notify (object, "longitude");
198 ✗ break;
199
200 ✗ case PROP_LATITUDE:
201 ✗ self->lat = CLAMP (g_value_get_double (value), SHUMATE_MIN_LATITUDE, SHUMATE_MAX_LATITUDE);
202 ✗ g_object_notify (object, "latitude");
203 ✗ break;
204
205 ✗ default:
206 ✗ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
207 ✗ break;
208 }
209 ✗ }
210
211 static void
212 9 shumate_viewport_dispose (GObject *object)
213 {
214 9 ShumateViewport *self = SHUMATE_VIEWPORT (object);
215
216
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 9 times.
9 g_clear_object (&self->ref_map_source);
217
218 9 G_OBJECT_CLASS (shumate_viewport_parent_class)->dispose (object);
219 9 }
220
221 static void
222 5 shumate_viewport_class_init (ShumateViewportClass *klass)
223 {
224 5 GObjectClass *object_class = G_OBJECT_CLASS (klass);
225
226 5 object_class->get_property = shumate_viewport_get_property;
227 5 object_class->set_property = shumate_viewport_set_property;
228 5 object_class->dispose = shumate_viewport_dispose;
229
230 /**
231 * ShumateViewport:zoom-level:
232 *
233 * The level of zoom of the content.
234 */
235 10 obj_properties[PROP_ZOOM_LEVEL] =
236 5 g_param_spec_double ("zoom-level", NULL, NULL,
237 0, 30, 3,
238 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
239
240 /**
241 * ShumateViewport:min-zoom-level:
242 *
243 * The lowest allowed level of zoom of the content.
244 */
245 10 obj_properties[PROP_MIN_ZOOM_LEVEL] =
246 5 g_param_spec_uint ("min-zoom-level", NULL, NULL,
247 0, 20, DEFAULT_MIN_ZOOM,
248 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
249
250 /**
251 * ShumateViewport:max-zoom-level:
252 *
253 * The highest allowed level of zoom of the content.
254 */
255 10 obj_properties[PROP_MAX_ZOOM_LEVEL] =
256 5 g_param_spec_uint ("max-zoom-level", NULL, NULL,
257 0, 30, DEFAULT_MAX_ZOOM,
258 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
259
260 /**
261 * ShumateViewport:reference-map-source:
262 *
263 * The reference #ShumateMapSource being displayed
264 */
265 10 obj_properties[PROP_REFERENCE_MAP_SOURCE] =
266 5 g_param_spec_object ("reference-map-source", NULL, NULL,
267 SHUMATE_TYPE_MAP_SOURCE,
268 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
269
270 /**
271 * ShumateViewport:rotation:
272 *
273 * The rotation of the map view, in radians clockwise from up being due north
274 */
275 10 obj_properties[PROP_ROTATION] =
276 5 g_param_spec_double ("rotation", NULL, NULL,
277 0, G_PI * 2.0, 0,
278 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
279
280 5 g_object_class_install_properties (object_class,
281 N_PROPERTIES,
282 obj_properties);
283
284 5 g_object_class_override_property (object_class,
285 PROP_LONGITUDE,
286 "longitude");
287
288 5 g_object_class_override_property (object_class,
289 PROP_LATITUDE,
290 "latitude");
291
292 /**
293 * ShumateViewport::changed:
294 * @self: the [class@Viewport] emitting the signal
295 * @event: a [class@SymbolEvent] with details about the clicked symbol.
296 *
297 * Emitted when the viewport position (location, zoom, rotation, etc.) changes. Changes to multiple properties at once
298 * may be combined into a single signal emission.
299 *
300 * Since: 1.6
301 */
302 10 signals[CHANGED] =
303 5 g_signal_new ("changed",
304 G_OBJECT_CLASS_TYPE (object_class),
305 G_SIGNAL_RUN_LAST,
306 0, NULL, NULL,
307 NULL,
308 G_TYPE_NONE,
309 0);
310 5 }
311
312 static void
313 15 shumate_viewport_init (ShumateViewport *self)
314 {
315 /* We need to set these here, otherwise the max_zoom >= min_zoom check in
316 * the setter functions may fail if they're not called in the right order. */
317 15 self->min_zoom_level = DEFAULT_MIN_ZOOM;
318 15 self->max_zoom_level = DEFAULT_MAX_ZOOM;
319 15 }
320
321 static void
322 5 shumate_viewport_shumate_location_interface_init (ShumateLocationInterface *iface)
323 {
324 5 iface->get_latitude = shumate_viewport_get_latitude;
325 5 iface->get_longitude = shumate_viewport_get_longitude;
326 5 iface->set_location = shumate_viewport_set_location;
327 5 }
328
329 /**
330 * shumate_viewport_new:
331 *
332 * Creates a new #ShumateViewport
333 *
334 * Returns: A new #ShumateViewport
335 */
336 ShumateViewport *
337 15 shumate_viewport_new (void)
338 {
339 15 return g_object_new (SHUMATE_TYPE_VIEWPORT, NULL);
340 }
341
342 /**
343 * shumate_viewport_set_zoom_level:
344 * @self: a #ShumateViewport
345 * @zoom_level: the zoom level
346 *
347 * Set the zoom level
348 */
349 void
350 31 shumate_viewport_set_zoom_level (ShumateViewport *self,
351 double zoom_level)
352 {
353
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 31 times.
31 g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
354
355
2/2
✓ Branch 5 → 6 taken 29 times.
✓ Branch 5 → 7 taken 2 times.
31 zoom_level = CLAMP (zoom_level, self->min_zoom_level, self->max_zoom_level);
356
357
2/2
✓ Branch 7 → 8 taken 28 times.
✓ Branch 7 → 10 taken 3 times.
31 if (self->zoom_level == zoom_level)
358 return;
359
360 28 self->zoom_level = zoom_level;
361 28 g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_ZOOM_LEVEL]);
362 28 g_signal_emit (self, signals[CHANGED], 0);
363 }
364
365 /**
366 * shumate_viewport_get_zoom_level:
367 * @self: a #ShumateViewport
368 *
369 * Get the current zoom level
370 *
371 * Returns: the current zoom level
372 */
373 double
374 17 shumate_viewport_get_zoom_level (ShumateViewport *self)
375 {
376
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 17 times.
17 g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), 0U);
377
378 17 return self->zoom_level;
379 }
380
381 /**
382 * shumate_viewport_set_max_zoom_level:
383 * @self: a #ShumateViewport
384 * @max_zoom_level: the maximal zoom level
385 *
386 * Set the maximal zoom level
387 */
388 void
389 30 shumate_viewport_set_max_zoom_level (ShumateViewport *self,
390 guint max_zoom_level)
391 {
392
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 30 times.
30 g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
393
2/2
✓ Branch 5 → 6 taken 28 times.
✓ Branch 5 → 7 taken 2 times.
30 g_return_if_fail (max_zoom_level >= self->min_zoom_level);
394
395
2/2
✓ Branch 6 → 8 taken 18 times.
✓ Branch 6 → 11 taken 10 times.
28 if (self->max_zoom_level == max_zoom_level)
396 return;
397
398 18 self->max_zoom_level = max_zoom_level;
399 18 g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_MAX_ZOOM_LEVEL]);
400
401
2/2
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 16 times.
18 if (self->zoom_level > max_zoom_level)
402 2 shumate_viewport_set_zoom_level (self, max_zoom_level);
403 }
404
405 /**
406 * shumate_viewport_get_max_zoom_level:
407 * @self: a #ShumateViewport
408 *
409 * Get the maximal zoom level
410 *
411 * Returns: the maximal zoom level
412 */
413 guint
414 8 shumate_viewport_get_max_zoom_level (ShumateViewport *self)
415 {
416
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), 0U);
417
418 8 return self->max_zoom_level;
419 }
420
421 /**
422 * shumate_viewport_set_min_zoom_level:
423 * @self: a #ShumateViewport
424 * @min_zoom_level: the minimal zoom level
425 *
426 * Set the minimal zoom level
427 */
428 void
429 28 shumate_viewport_set_min_zoom_level (ShumateViewport *self,
430 guint min_zoom_level)
431 {
432
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 28 times.
28 g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
433
2/2
✓ Branch 5 → 6 taken 26 times.
✓ Branch 5 → 7 taken 2 times.
28 g_return_if_fail (min_zoom_level <= self->max_zoom_level);
434
435
2/2
✓ Branch 6 → 8 taken 16 times.
✓ Branch 6 → 11 taken 10 times.
26 if (self->min_zoom_level == min_zoom_level)
436 return;
437
438 16 self->min_zoom_level = min_zoom_level;
439 16 g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_MIN_ZOOM_LEVEL]);
440
441
2/2
✓ Branch 9 → 10 taken 4 times.
✓ Branch 9 → 11 taken 12 times.
16 if (self->zoom_level < min_zoom_level)
442 4 shumate_viewport_set_zoom_level (self, min_zoom_level);
443 }
444
445 /**
446 * shumate_viewport_get_min_zoom_level:
447 * @self: a #ShumateViewport
448 *
449 * Get the minimal zoom level
450 *
451 * Returns: the minimal zoom level
452 */
453 guint
454 8 shumate_viewport_get_min_zoom_level (ShumateViewport *self)
455 {
456
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), 0U);
457
458 8 return self->min_zoom_level;
459 }
460
461 /**
462 * shumate_viewport_set_reference_map_source:
463 * @self: a #ShumateViewport
464 * @map_source: (nullable): a #ShumateMapSource or %NULL to set none.
465 *
466 * Set the reference map source
467 */
468 void
469 ✗ shumate_viewport_set_reference_map_source (ShumateViewport *self,
470 ShumateMapSource *map_source)
471 {
472 ✗ g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
473 ✗ g_return_if_fail (map_source == NULL || SHUMATE_IS_MAP_SOURCE (map_source));
474
475 ✗ if (g_set_object (&self->ref_map_source, map_source))
476 {
477 ✗ if (map_source != NULL)
478 {
479 ✗ shumate_viewport_set_max_zoom_level (self, shumate_map_source_get_max_zoom_level (map_source));
480 ✗ shumate_viewport_set_min_zoom_level (self, shumate_map_source_get_min_zoom_level (map_source));
481 }
482
483 ✗ g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_REFERENCE_MAP_SOURCE]);
484 }
485
486 }
487
488 /**
489 * shumate_viewport_get_reference_map_source:
490 * @self: a #ShumateViewport
491 *
492 * Get the reference map source
493 *
494 * Returns: (transfer none) (nullable): the reference #ShumateMapSource or %NULL
495 * when none has been set.
496 */
497 ShumateMapSource *
498 105 shumate_viewport_get_reference_map_source (ShumateViewport *self)
499 {
500
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 105 times.
105 g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), NULL);
501
502 105 return self->ref_map_source;
503 }
504
505 /**
506 * shumate_viewport_set_rotation:
507 * @self: a #ShumateViewport
508 * @rotation: the rotation
509 *
510 * Sets the rotation
511 */
512 void
513 ✗ shumate_viewport_set_rotation (ShumateViewport *self,
514 double rotation)
515 {
516 ✗ g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
517
518 ✗ rotation = fmod (rotation, G_PI * 2.0);
519 ✗ if (rotation < 0)
520 ✗ rotation += G_PI * 2.0;
521
522 ✗ if (self->rotation == rotation)
523 return;
524
525 ✗ self->rotation = rotation;
526 ✗ g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_ROTATION]);
527 ✗ g_signal_emit (self, signals[CHANGED], 0);
528 }
529
530 /**
531 * shumate_viewport_get_rotation:
532 * @self: a #ShumateViewport
533 *
534 * Gets the current rotation
535 *
536 * Returns: the current rotation
537 */
538 double
539 ✗ shumate_viewport_get_rotation (ShumateViewport *self)
540 {
541 ✗ g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), 0);
542
543 ✗ return self->rotation;
544 }
545
546 static void
547 ✗ rotate_around_center (double *x, double *y, double width, double height, double angle)
548 {
549 /* Rotate (x, y) around (width / 2, height / 2) */
550
551 ✗ double old_x = *x;
552 ✗ double old_y = *y;
553 ✗ double center_x = width / 2.0;
554 ✗ double center_y = height / 2.0;
555
556 ✗ *x = cos(angle) * (old_x - center_x) - sin(angle) * (old_y - center_y) + center_x;
557 ✗ *y = sin(angle) * (old_x - center_x) + cos(angle) * (old_y - center_y) + center_y;
558 ✗ }
559
560 static double
561 ✗ positive_mod (double i, double n)
562 {
563 ✗ return fmod (fmod (i, n) + n, n);
564 }
565
566 /**
567 * shumate_viewport_widget_coords_to_location:
568 * @self: a #ShumateViewport
569 * @widget: a #GtkWidget that uses @self as viewport
570 * @x: the x coordinate
571 * @y: the y coordinate
572 * @latitude: (out): return location for the latitude
573 * @longitude: (out): return location for the longitude
574 *
575 * Gets the latitude and longitude corresponding to a position on @widget.
576 */
577 void
578 ✗ shumate_viewport_widget_coords_to_location (ShumateViewport *self,
579 GtkWidget *widget,
580 double x,
581 double y,
582 double *latitude,
583 double *longitude)
584 {
585 ✗ double center_x, center_y;
586 ✗ double width, height;
587 ✗ double tile_size;
588 ✗ double map_width, map_height;
589
590 ✗ g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
591 ✗ g_return_if_fail (GTK_IS_WIDGET (widget));
592 ✗ g_return_if_fail (latitude != NULL);
593 ✗ g_return_if_fail (longitude != NULL);
594
595 ✗ if (!self->ref_map_source)
596 {
597 ✗ g_critical ("A reference map source is required.");
598 ✗ return;
599 }
600
601 ✗ width = gtk_widget_get_width (widget);
602 ✗ height = gtk_widget_get_height (widget);
603 ✗ rotate_around_center (&x, &y, width, height, -self->rotation);
604
605 ✗ tile_size = shumate_map_source_get_tile_size_at_zoom (self->ref_map_source, self->zoom_level);
606 ✗ map_width = tile_size * shumate_map_source_get_column_count (self->ref_map_source, self->zoom_level);
607 ✗ map_height = tile_size * shumate_map_source_get_row_count (self->ref_map_source, self->zoom_level);
608
609 ✗ center_x = shumate_map_source_get_x (self->ref_map_source, self->zoom_level, self->lon) - width/2 + x;
610 ✗ center_y = shumate_map_source_get_y (self->ref_map_source, self->zoom_level, self->lat) - height/2 + y;
611
612 ✗ center_x = positive_mod (center_x, map_width);
613 ✗ center_y = positive_mod (center_y, map_height);
614
615 ✗ *latitude = shumate_map_source_get_latitude (self->ref_map_source, self->zoom_level, center_y);
616 ✗ *longitude = shumate_map_source_get_longitude (self->ref_map_source, self->zoom_level, center_x);
617 }
618
619 /**
620 * shumate_viewport_location_to_widget_coords:
621 * @self: a #ShumateViewport
622 * @widget: a #GtkWidget that uses @self as viewport
623 * @latitude: the latitude
624 * @longitude: the longitude
625 * @x: (out): return value for the x coordinate
626 * @y: (out): return value for the y coordinate
627 *
628 * Gets the position on @widget that correspond to the given latitude and
629 * longitude.
630 */
631 void
632 ✗ shumate_viewport_location_to_widget_coords (ShumateViewport *self,
633 GtkWidget *widget,
634 double latitude,
635 double longitude,
636 double *x,
637 double *y)
638 {
639 ✗ double center_latitude, center_longitude;
640 ✗ double width, height;
641
642 ✗ g_return_if_fail (SHUMATE_IS_VIEWPORT (self));
643 ✗ g_return_if_fail (GTK_IS_WIDGET (widget));
644 ✗ g_return_if_fail (x != NULL);
645 ✗ g_return_if_fail (y != NULL);
646
647 ✗ if (!self->ref_map_source)
648 {
649 ✗ g_critical ("A reference map source is required.");
650 ✗ return;
651 }
652
653 ✗ width = gtk_widget_get_width (widget);
654 ✗ height = gtk_widget_get_height (widget);
655
656 ✗ *x = shumate_map_source_get_x (self->ref_map_source, self->zoom_level, longitude);
657 ✗ *y = shumate_map_source_get_y (self->ref_map_source, self->zoom_level, latitude);
658
659 ✗ center_latitude = shumate_location_get_latitude (SHUMATE_LOCATION (self));
660 ✗ center_longitude = shumate_location_get_longitude (SHUMATE_LOCATION (self));
661 ✗ *x -= shumate_map_source_get_x (self->ref_map_source, self->zoom_level, center_longitude) - width/2;
662 ✗ *y -= shumate_map_source_get_y (self->ref_map_source, self->zoom_level, center_latitude) - height/2;
663
664 ✗ rotate_around_center (x, y, width, height, self->rotation);
665 }
666
667 /**
668 * shumate_viewport_copy:
669 * @self: a [class@Viewport]
670 *
671 * Creates a copy of the viewport.
672 *
673 * Returns: (transfer full): a [class@Viewport] with the same values as @self
674 */
675 ShumateViewport *
676 ✗ shumate_viewport_copy (ShumateViewport *self)
677 {
678 ✗ g_return_val_if_fail (SHUMATE_IS_VIEWPORT (self), NULL);
679
680 ✗ return g_object_new (SHUMATE_TYPE_VIEWPORT,
681 "latitude", self->lat,
682 "longitude", self->lon,
683 "min-zoom-level", self->min_zoom_level,
684 "max-zoom-level", self->max_zoom_level,
685 "rotation", self->rotation,
686 "reference-map-source", self->ref_map_source,
687 "zoom-level", self->zoom_level,
688 NULL);
689 }
690