Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
map.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Henrik Andersson.
4 Copyright (C) 2012 Jean-Sébastien Pédron.
5 Copyright (C) 2012-2018 Tobias Ellinghaus.
6 Copyright (C) 2013-2014, 2016-2017, 2019-2021 Pascal Obry.
7 Copyright (C) 2013-2017 Roman Lebedev.
8 Copyright (C) 2013-2014 Ronny Kahl.
9 Copyright (C) 2014-2017 Jérémy Rosen.
10 Copyright (C) 2014 parafin.
11 Copyright (C) 2017, 2021 luzpaz.
12 Copyright (C) 2017 Sven Claussner.
13 Copyright (C) 2018 Rikard Öxler.
14 Copyright (C) 2019-2021 Aldric Renaudin.
15 Copyright (C) 2019 Heiko Bauke.
16 Copyright (C) 2019-2021 Philippe Weyland.
17 Copyright (C) 2020 Chris Elston.
18 Copyright (C) 2020, 2022 Diederik Ter Rahe.
19 Copyright (C) 2020-2021 Hubert Kowalski.
20 Copyright (C) 2020-2021 Ralf Brown.
21 Copyright (C) 2021 Paolo Benvenuto.
22 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Miloš Komarčević.
25 Copyright (C) 2022 Nicolas Auffray.
26 Copyright (C) 2023 Ricky Moon.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41
42#include "common/collection.h"
43#include "common/paths.h" // DT_PATH_MAX
44#include "common/act_on.h"
46#include "metadata/gpx.h"
47#include "metadata/geo.h"
49#include "common/selection.h"
50#include "common/times.h"
51#include "common/utility.h"
52#include "common/conf.h"
53#include "control/control.h"
55
56#include "gui/drag_and_drop.h"
57#include "widgets/draw.h"
58#include "views/view.h"
59#include "views/view_api.h"
60#include <gdk/gdkkeysyms.h>
61#include "control/signal.h"
62#include "gui/application.h"
63#include "gui/window_manager.h"
65
66
67DT_MODULE(1)
68
69typedef struct dt_geo_position_t
70{
71 double x, y;
73 int32_t imgid;
75
76typedef struct dt_map_image_t
77{
78 int32_t imgid;
79 double latitude;
80 double longitude;
81 int group;
85 OsmGpsMapImage *image;
88 cairo_surface_t *surface;
91
124
125#define UNCLASSIFIED -1
126#define NOISE -2
127
128#define CORE_POINT 1
129#define NOT_CORE_POINT 0
130
131static const int thumb_size = 128, thumb_border = 2, image_pin_size = 13, place_pin_size = 72;
132static const int cross_size = 16, max_size = 1024;
133static const uint32_t thumb_frame_color = 0x000000aa;
134static const uint32_t thumb_frame_sel_color = 0xffffffee;
135static const uint32_t thumb_frame_gpx_color = 0xff000099;
136static const uint32_t pin_outer_color = 0x0000aaaa;
137static const uint32_t pin_inner_color = 0xffffffee;
138static const uint32_t pin_line_color = 0x000000ff;
139
146
147/* proxy function to center map view on location at a zoom level */
148static void _view_map_center_on_location(const dt_view_t *view, gdouble lon, gdouble lat, gdouble zoom);
149/* proxy function to center map view on a bounding box */
150static void _view_map_center_on_bbox(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2);
151/* proxy function to show or hide the osd */
152static void _view_map_show_osd(const dt_view_t *view);
153/* proxy function to set the map source */
154static void _view_map_set_map_source(const dt_view_t *view, OsmGpsMapSource_t map_source);
155/* wrapper for setting the map source in the GObject */
156static void _view_map_set_map_source_g_object(const dt_view_t *view, OsmGpsMapSource_t map_source);
157/* proxy function to check if preferences have changed */
158static void _view_map_check_preference_changed(gpointer instance, gpointer user_data);
159/* proxy function to add a marker to the map */
160static GObject *_view_map_add_marker(const dt_view_t *view, dt_geo_map_display_t type, GList *points);
161/* proxy function to remove a marker from the map */
162static gboolean _view_map_remove_marker(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker);
163/* proxy function to add a location to the map */
164static void _view_map_add_location(const dt_view_t *view, dt_map_location_data_t *g, const guint locid);
165/* proxy function to remove a location from the map */
166static void _view_map_location_action(const dt_view_t *view, const int action);
167/* proxy function to provide a drag context icon */
168static void _view_map_drag_set_icon(const dt_view_t *self, GdkDragContext *context,
169 const int32_t imgid, const int count);
170
171/* callback when the collection changes */
172static void _view_map_collection_changed(gpointer instance, dt_collection_change_t query_change,
173 dt_collection_properties_t changed_property, gpointer imgs, int next,
174 gpointer user_data);
175/* callback when the selection changes */
176static void _view_map_selection_changed(gpointer instance, gpointer user_data);
177/* callback when images geotags change */
178static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer user_data);
179/* update the geotag information on location tag */
181/* callback when an image is selected in filmstrip, centers map */
182static void _view_map_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data);
183/* callback when a drag starts from filmstrip, selects the dragged image first */
184static void _view_map_filmstrip_drag_begin_callback(gpointer instance, int32_t imgid, gpointer user_data);
185/* callback when an image is dropped from filmstrip */
186static void _drag_and_drop_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
187 GtkSelectionData *selection_data, guint target_type, guint time,
188 gpointer data);
189/* callback when the user drags images FROM the map */
190static void _view_map_dnd_get_callback(GtkWidget *widget, GdkDragContext *context,
191 GtkSelectionData *selection_data, guint target_type, guint time,
192 dt_view_t *self);
193/* callback that readds the images to the map */
194static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self);
195/* callback that handles mouse scroll */
196static gboolean _view_map_scroll_event(GtkWidget *w, GdkEventScroll *event, dt_view_t *self);
197/* callback that handles clicks on the map */
198static gboolean _view_map_button_press_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self);
199static gboolean _view_map_button_release_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self);
200/* callback when the mouse is moved */
201static gboolean _view_map_motion_notify_callback(GtkWidget *w, GdkEventMotion *e, dt_view_t *self);
202static gboolean _view_map_drag_motion_callback(GtkWidget *widget, GdkDragContext *dc,
203 gint x, gint y, guint time, dt_view_t *self);
204static gboolean _view_map_dnd_failed_callback(GtkWidget *widget, GdkDragContext *drag_context,
205 GtkDragResult result, dt_view_t *self);
206// find the images clusters on the map
207static void _dbscan(dt_geo_position_t *points, unsigned int num_points, double epsilon,
208 unsigned int minpts);
209static gboolean _view_map_prefs_changed(dt_map_t *lib);
210
211/* center map to on the baricenter of the image list */
212static gboolean _view_map_center_on_image_list(dt_view_t *self);
213/* center map on the given image */
214static void _view_map_center_on_image(dt_view_t *self, const int32_t imgid);
215
217static gboolean _view_map_remove_polygon(const dt_view_t *view, OsmGpsMapPolygon *polygon);
218static OsmGpsMapImage *_view_map_draw_location(dt_map_t *lib, dt_location_draw_t *ld,
219 const gboolean main);
221
222static void _free_map_image(gpointer data)
223{
224 dt_map_image_t *entry = (dt_map_image_t *)data;
225 if(IS_NULL_PTR(entry)) return;
227 dt_free(entry);
228}
229
230int key_pressed(dt_view_t *self, GdkEventKey *event)
231{
232 if(!gtk_window_is_active(GTK_WINDOW(dt_gui_get_ui()->main_window))) return FALSE;
233
234 switch(event->keyval)
235 {
236 case GDK_KEY_Escape:
237 dt_ctl_switch_mode_to("lighttable");
238 return TRUE;
239 default:
240 break;
241 }
242
243 return FALSE;
244}
245
246const char *name(const dt_view_t *self)
247{
248 return _("Map");
249}
250
251uint32_t view(const dt_view_t *self)
252{
253 return DT_VIEW_MAP;
254}
255
256#ifndef HAVE_OSMGPSMAP_110_OR_NEWER
257// the following functions were taken from libosmgpsmap
258// Copyright (C) Marcus Bauer 2008 <marcus.bauer@gmail.com>
259// Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
260// Copyright (C) 2014 Martijn Goedhart <goedhart.martijn@gmail.com>
261
262#if FLT_RADIX == 2
263 #define LOG2(x) (ilogb(x))
264#else
265 #define LOG2(x) ((int)floor(log2(abs(x))))
266#endif
267
268#define TILESIZE 256
269
270static float deg2rad(float deg)
271{
272 return (deg * M_PI / 180.0);
273}
274
275static int latlon2zoom(int pix_height, int pix_width, float lat1, float lat2, float lon1, float lon2)
276{
277 const float lat1_m = atanh(sinf(lat1));
278 const float lat2_m = atanh(sinf(lat2));
279 const int zoom_lon = LOG2((double)(2 * pix_width * M_PI) / (TILESIZE * (lon2 - lon1)));
280 const int zoom_lat = LOG2((double)(2 * pix_height * M_PI) / (TILESIZE * (lat2_m - lat1_m)));
281 return MIN(zoom_lon, zoom_lat);
282}
283
284#undef LOG2
285#undef TILESIZE
286
287// Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
288// Copyright (C) Marcus Bauer 2008 <marcus.bauer@gmail.com>
289// Copyright (C) John Stowers 2009 <john.stowers@gmail.com>
290// Copyright (C) Till Harbaum 2009 <till@harbaum.org>
291//
292// Contributions by
293// Everaldo Canuto 2009 <everaldo.canuto@gmail.com>
294static void osm_gps_map_zoom_fit_bbox(OsmGpsMap *map, float latitude1, float latitude2, float longitude1, float longitude2)
295{
296 GtkAllocation allocation;
297 gtk_widget_get_allocation(GTK_WIDGET (map), &allocation);
298 const int zoom = latlon2zoom(allocation.height, allocation.width,
299 deg2rad(latitude1), deg2rad(latitude2),
300 deg2rad(longitude1), deg2rad(longitude2));
301 osm_gps_map_set_center(map, (latitude1 + latitude2) / 2, (longitude1 + longitude2) / 2);
302 osm_gps_map_set_zoom(map, zoom);
303}
304#endif // HAVE_OSMGPSMAP_110_OR_NEWER
305
306static void _toast_log_lat_lon(const float lat, const float lon)
307{
308 gchar *latitude = dt_util_latitude_str(lat);
309 gchar *longitude = dt_util_longitude_str(lon);
310 dt_toast_log("%s %s",latitude, longitude);
311 dt_free(latitude);
312 dt_free(longitude);
313}
314
315static GdkPixbuf *_view_map_images_count(const int nb_images, const gboolean same_loc,
316 double *count_width, double *count_height)
317{
318 gchar *text = g_strdup_printf("%d", nb_images);
319 const int w = DT_PIXEL_APPLY_DPI(thumb_size + 2 * thumb_border);
320 const int h = DT_PIXEL_APPLY_DPI(image_pin_size);
321
322 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
323 cairo_t *cr = cairo_create(cst);
324 /* fill background */
326 cairo_paint(cr);
327
330 cairo_set_font_size(cr, 12 * (1 + (dt_gui_get_global()->dpi_factor - 1) / 2));
331 cairo_text_extents_t te;
332 cairo_text_extents(cr, text, &te);
333 *count_width = te.width + 4 * te.x_bearing;
334 *count_height = te.height + 2;
335 cairo_move_to(cr, te.x_bearing, te.height + 1);
336
337 cairo_show_text(cr, text);
338 cairo_destroy(cr);
339 uint8_t *data = cairo_image_surface_get_data(cst);
340 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
341 const size_t size = (size_t)w * h * 4;
342 uint8_t *buf = (uint8_t *)malloc(size);
343 memcpy(buf, data, size);
344 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
345 (GdkPixbufDestroyNotify)free, NULL);
346 cairo_surface_destroy(cst);
347 dt_free(text);
348 return pixbuf;
349}
350
351static GdkPixbuf *_init_image_pin()
352{
353 const size_t w = DT_PIXEL_APPLY_DPI(thumb_size + 2 * thumb_border);
354 const size_t h = DT_PIXEL_APPLY_DPI(image_pin_size);
355 g_return_val_if_fail(w > 0 && h > 0, NULL);
356
357 float r, g, b, a;
358 r = ((thumb_frame_color & 0xff000000) >> 24) / 255.0;
359 g = ((thumb_frame_color & 0x00ff0000) >> 16) / 255.0;
360 b = ((thumb_frame_color & 0x0000ff00) >> 8) / 255.0;
361 a = ((thumb_frame_color & 0x000000ff) >> 0) / 255.0;
362
363 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
364 cairo_t *cr = cairo_create(cst);
365 cairo_set_source_rgba(cr, r, g, b, a);
366 dtgtk_cairo_paint_map_pin(cr, (h-w)/2, 0, w, h, 0, NULL); // keep the pin on left
367 cairo_destroy(cr);
368 uint8_t *data = cairo_image_surface_get_data(cst);
369 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
370 const size_t size = (size_t)w * h * 4;
371 uint8_t *buf = (uint8_t *)malloc(size);
372 memcpy(buf, data, size);
373 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
374 (GdkPixbufDestroyNotify)free, NULL);
375 cairo_surface_destroy(cst);
376 return pixbuf;
377}
378
379static GdkPixbuf *_init_place_pin()
380{
381 const size_t w = DT_PIXEL_APPLY_DPI(place_pin_size);
382 const size_t h = DT_PIXEL_APPLY_DPI(place_pin_size);
383 g_return_val_if_fail(w > 0 && h > 0, NULL);
384
385 float r, g, b, a;
386
387 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
388 cairo_t *cr = cairo_create(cst);
389
390 // outer shape
391 r = ((pin_outer_color & 0xff000000) >> 24) / 255.0;
392 g = ((pin_outer_color & 0x00ff0000) >> 16) / 255.0;
393 b = ((pin_outer_color & 0x0000ff00) >> 8) / 255.0;
394 a = ((pin_outer_color & 0x000000ff) >> 0) / 255.0;
395 cairo_set_source_rgba(cr, r, g, b, a);
396 cairo_arc(cr, 0.5 * w, 0.333 * h, 0.333 * h - 2, 150.0 * (M_PI / 180.0), 30.0 * (M_PI / 180.0));
397 cairo_line_to(cr, 0.5 * w, h - 2);
398 cairo_close_path(cr);
399 cairo_fill_preserve(cr);
400
401 r = ((pin_line_color & 0xff000000) >> 24) / 255.0;
402 g = ((pin_line_color & 0x00ff0000) >> 16) / 255.0;
403 b = ((pin_line_color & 0x0000ff00) >> 8) / 255.0;
404 a = ((pin_line_color & 0x000000ff) >> 0) / 255.0;
405 cairo_set_source_rgba(cr, r, g, b, a);
406 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1));
407 cairo_stroke(cr);
408
409 // inner circle
410 r = ((pin_inner_color & 0xff000000) >> 24) / 255.0;
411 g = ((pin_inner_color & 0x00ff0000) >> 16) / 255.0;
412 b = ((pin_inner_color & 0x0000ff00) >> 8) / 255.0;
413 a = ((pin_inner_color & 0x000000ff) >> 0) / 255.0;
414 cairo_set_source_rgba(cr, r, g, b, a);
415 cairo_arc(cr, 0.5 * w, 0.333 * h, 0.17 * h, 0, 2.0 * M_PI);
416 cairo_fill(cr);
417
418 cairo_destroy(cr);
419 uint8_t *data = cairo_image_surface_get_data(cst);
420 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
421 size_t size = (size_t)w * h * 4;
422 uint8_t *buf = (uint8_t *)malloc(size);
423 memcpy(buf, data, size);
424 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
425 (GdkPixbufDestroyNotify)free, NULL);
426 cairo_surface_destroy(cst);
427 return pixbuf;
428}
429
430static GdkPixbuf *_draw_ellipse(const float dlongitude, const float dlatitude,
431 const gboolean main)
432{
433 const int dlon = dlongitude > max_size ? max_size :
434 dlongitude < cross_size ? cross_size : dlongitude;
435 const int dlat = dlatitude > max_size ? max_size :
436 dlatitude < cross_size ? cross_size : dlatitude;
437 const gboolean landscape = dlon > dlat ? TRUE : FALSE;
438 const float ratio = dlon > dlat ? (float)dlat / (float)dlon : (float)dlon / (float)dlat;
439 const int w = DT_PIXEL_APPLY_DPI(2.0 * (landscape ? dlon : dlat));
440 const int h = w;
441 const int d = DT_PIXEL_APPLY_DPI(main ? 2 : 1);
442 const int cross = DT_PIXEL_APPLY_DPI(cross_size);
443 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
444 cairo_t *cr = cairo_create(cst);
445
446 cairo_set_line_width(cr, d);
447 const int color_hi = dlon == max_size || dlon == cross_size
451
452 cairo_matrix_t save_matrix;
453 cairo_get_matrix(cr, &save_matrix);
454 cairo_translate(cr, 0.5 * w, 0.5 * h);
455 cairo_scale(cr, landscape ? 1 : ratio, landscape ? ratio : 1);
456 cairo_translate(cr, -0.5 * w, -0.5 * h);
457
459 cairo_arc(cr, 0.5 * w, 0.5 * h, 0.5 * h - d - d, 0, 2.0 * M_PI);
460
461 cairo_set_matrix(cr, &save_matrix);
462 cairo_stroke(cr);
463 cairo_move_to(cr, 0.5 * w + d, 0.5 * h - cross);
464 cairo_line_to(cr, 0.5 * w + d, 0.5 * h + cross);
465 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h - d);
466 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h - d);
467 cairo_stroke(cr);
468
469 cairo_get_matrix(cr, &save_matrix);
470 cairo_translate(cr, 0.5 * w, 0.5 * h);
471 cairo_scale(cr, landscape ? 1 : ratio, landscape ? ratio : 1);
472 cairo_translate(cr, -0.5 * w, -0.5 * h);
473
474 dt_widget_set_source_rgb(cr, color_hi);
475 cairo_arc(cr, 0.5 * w, 0.5 * h, 0.5 * h - d, 0, 2.0 * M_PI);
476
477 cairo_set_matrix(cr, &save_matrix);
478
479 cairo_stroke(cr);
480 cairo_move_to(cr, 0.5 * w, 0.5 * h - cross);
481 cairo_line_to(cr, 0.5 * w, 0.5 * h + cross);
482 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h );
483 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h );
484 cairo_stroke(cr);
485
486 cairo_destroy(cr);
487 uint8_t *data = cairo_image_surface_get_data(cst);
488 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
489 size_t size = (size_t)w * h * 4;
490 uint8_t *buf = (uint8_t *)malloc(size);
491 memcpy(buf, data, size);
492 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
493 (GdkPixbufDestroyNotify)free, NULL);
494 cairo_surface_destroy(cst);
495 return pixbuf;
496}
497
498static GdkPixbuf *_draw_rectangle(const float dlongitude, const float dlatitude,
499 const gboolean main)
500{
501 const int dlon = dlongitude > max_size ? max_size :
502 dlongitude < cross_size ? cross_size : dlongitude;
503 const int dlat = dlatitude > max_size ? max_size :
504 dlatitude < cross_size ? cross_size : dlatitude;
505 const int w = DT_PIXEL_APPLY_DPI(2.0 * dlon);
506 const int h = DT_PIXEL_APPLY_DPI(2.0 * dlat);
507 const int d = DT_PIXEL_APPLY_DPI(main ? 2 : 1);
508 const int cross = DT_PIXEL_APPLY_DPI(cross_size);
509 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
510 cairo_t *cr = cairo_create(cst);
511
512 cairo_set_line_width(cr,d);
514 cairo_move_to(cr, d + d, d + d);
515 cairo_line_to(cr, w - d - d, d + d);
516 cairo_line_to(cr, w - d - d, h - d - d);
517 cairo_line_to(cr, d + d, h - d - d);
518 cairo_line_to(cr, d + d, d + d);
519 cairo_move_to(cr, 0.5 * w + d, 0.5 * h - cross);
520 cairo_line_to(cr, 0.5 * w + d, 0.5 * h + cross);
521 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h - d);
522 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h - d);
523 cairo_stroke(cr);
524
525 dt_widget_set_source_rgb(cr, dlon == max_size || dlon == cross_size ||
526 dlat == max_size || dlat == cross_size
530 cairo_move_to(cr, d, d);
531 cairo_line_to(cr, w - d, d);
532 cairo_line_to(cr, w - d, h - d);
533 cairo_line_to(cr, d, h - d);
534 cairo_line_to(cr, d, d);
535 cairo_move_to(cr, 0.5 * w, 0.5 * h - cross);
536 cairo_line_to(cr, 0.5 * w, 0.5 * h + cross);
537 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h );
538 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h );
539 cairo_stroke(cr);
540
541 cairo_destroy(cr);
542 uint8_t *data = cairo_image_surface_get_data(cst);
543 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
544 size_t size = (size_t)w * h * 4;
545 uint8_t *buf = (uint8_t *)malloc(size);
546 memcpy(buf, data, size);
547 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
548 (GdkPixbufDestroyNotify)free, NULL);
549 cairo_surface_destroy(cst);
550 return pixbuf;
551}
552
553void expose(dt_view_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
554{
555 dt_map_t *lib = (dt_map_t *)self->data;
556 g_list_free(lib->incoming_selection);
557 lib->incoming_selection = NULL;
558 if(lib->entering)
559 {
560 // we need to ensure there's no remaining things on canvas.
561 // otherwise they can appear on map move
562 lib->entering = FALSE;
563 cairo_set_source_rgb(cri, 0, 0, 0);
564 cairo_paint(cri);
565 }
566}
567
568static void _view_changed(gpointer instance, dt_view_t *old_view,
569 dt_view_t *new_view, dt_view_t *self)
570{
571 if(old_view == self)
572 {
574 }
575}
576
577void init(dt_view_t *self)
578{
579 self->data = calloc(1, sizeof(dt_map_t));
580
581 dt_map_t *lib = (dt_map_t *)self->data;
582
584 {
585 lib->image_pin = _init_image_pin();
586 lib->place_pin = _init_place_pin();
588 lib->thumb_lat_angle = 0.01, lib->thumb_lon_angle = 0.01;
589 lib->time_out = 0, lib->timeout_event_source = 0;
590 lib->loc.main.id = 0, lib->loc.main.location = NULL, lib->loc.time_out = 0;
591 lib->loc.others = NULL;
592 lib->last_hovered_entry = NULL;
593
594 OsmGpsMapSource_t map_source
595 = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; // open street map should be a nice default ...
596 const char *old_map_source = dt_conf_get_string_const("plugins/map/map_source");
597 if(old_map_source && old_map_source[0] != '\0')
598 {
599 // find the number of the stored map_source
600 for(int i = 0; i <= OSM_GPS_MAP_SOURCE_LAST; i++)
601 {
602 const gchar *new_map_source = osm_gps_map_source_get_friendly_name(i);
603 if(!g_strcmp0(old_map_source, new_map_source))
604 {
605 if(osm_gps_map_source_is_valid(i)) map_source = i;
606 break;
607 }
608 }
609 }
610 else
611 dt_conf_set_string("plugins/map/map_source", osm_gps_map_source_get_friendly_name(map_source));
612
613 lib->map_source = map_source;
614
615 lib->map = g_object_new(OSM_TYPE_GPS_MAP, "map-source", OSM_GPS_MAP_SOURCE_NULL, "proxy-uri",
616 g_getenv("http_proxy"), NULL);
617
618 g_object_ref(lib->map); // we want to keep map alive until explicit destroy
619
620 lib->osd = g_object_new(OSM_TYPE_GPS_MAP_OSD, "show-scale", TRUE, "show-coordinates", TRUE, "show-dpad",
621 TRUE, "show-zoom", TRUE,
622#ifdef HAVE_OSMGPSMAP_NEWER_THAN_110
623 "show-copyright", TRUE,
624#endif
625 NULL);
626
627 if(dt_conf_get_bool("plugins/map/show_map_osd"))
628 {
629 osm_gps_map_layer_add(OSM_GPS_MAP(lib->map), lib->osd);
630 }
631
632 gtk_drag_dest_set(GTK_WIDGET(lib->map), GTK_DEST_DEFAULT_ALL,
633 target_list_internal, n_targets_internal, GDK_ACTION_MOVE);
634 g_signal_connect(GTK_WIDGET(lib->map), "scroll-event", G_CALLBACK(_view_map_scroll_event), self);
635 g_signal_connect(GTK_WIDGET(lib->map), "drag-data-received", G_CALLBACK(_drag_and_drop_received), self);
636 g_signal_connect(GTK_WIDGET(lib->map), "drag-data-get", G_CALLBACK(_view_map_dnd_get_callback), self);
637 g_signal_connect(GTK_WIDGET(lib->map), "drag-failed", G_CALLBACK(_view_map_dnd_failed_callback), self);
638
639 g_signal_connect(GTK_WIDGET(lib->map), "changed", G_CALLBACK(_view_map_changed_callback), self);
640 g_signal_connect_after(G_OBJECT(lib->map), "button-press-event",
641 G_CALLBACK(_view_map_button_press_callback), self);
642 g_signal_connect_after(G_OBJECT(lib->map), "button-release-event",
643 G_CALLBACK(_view_map_button_release_callback), self);
644 g_signal_connect(G_OBJECT(lib->map), "motion-notify-event", G_CALLBACK(_view_map_motion_notify_callback),
645 self);
646 g_signal_connect(G_OBJECT(lib->map), "drag-motion", G_CALLBACK(_view_map_drag_motion_callback),
647 self);
648 }
649
650 /* build the query string */
651
652 /* connect collection changed signal */
654 G_CALLBACK(_view_map_collection_changed), (gpointer)self);
655 /* connect selection changed signal */
657 G_CALLBACK(_view_map_selection_changed), (gpointer)self);
658 /* connect preference changed signal */
660 G_CALLBACK(_view_map_check_preference_changed), (gpointer)self);
661
663 G_CALLBACK(_view_changed), (gpointer)self);
664
666 G_CALLBACK(_view_map_geotag_changed), (gpointer)self);
667}
668
670{
671 dt_map_t *lib = (dt_map_t *)self->data;
672
678
680 {
681 g_object_unref(G_OBJECT(lib->image_pin));
682 g_object_unref(G_OBJECT(lib->place_pin));
683 g_object_unref(G_OBJECT(lib->osd));
684 osm_gps_map_image_remove_all(lib->map);
685 if(lib->points)
686 {
687 dt_free(lib->points);
688 }
689 if(lib->images)
690 {
691 g_slist_free_full(lib->images, _free_map_image);
692 lib->images = NULL;
693 }
694 if(lib->loc.main.id)
695 {
697 lib->loc.main.id = 0;
698 }
699 if(lib->loc.others)
700 {
701 for(GList *other = lib->loc.others; other; other = g_list_next(other))
702 {
705 // polygons are freed only from others list
707 }
708 g_list_free_full(lib->loc.others, dt_free_gpointer);
709 lib->loc.others = NULL;
710 }
711 // Release the extra strong reference taken in init(). The widget tree keeps
712 // its own ownership, this only drops our view-level keepalive reference.
713 g_object_unref(G_OBJECT(lib->map));
714 lib->map = NULL;
715 }
716 dt_free(self->data);
717}
718
719void configure(dt_view_t *self, int wd, int ht)
720{
721 // dt_capture_t *lib=(dt_capture_t*)self->data;
722}
723
725{
726 dt_map_t *lib = (dt_map_t *)self->data;
727 g_list_free(lib->incoming_selection);
729 return 0;
730}
731
741
742// updating collection when mouse scrolls to resize the location is too demanding
743// so wait for scrolling stop
744static gboolean _view_map_signal_change_delayed(gpointer user_data)
745{
746 dt_view_t *self = (dt_view_t *)user_data;
747 dt_map_t *lib = (dt_map_t *)self->data;
748 if(lib->loc.time_out)
749 {
750 lib->loc.time_out--;
751 if(!lib->loc.time_out)
752 {
754 return FALSE;
755 }
756 }
757 return TRUE;
758}
759
760static void _view_map_signal_change_wait(dt_view_t *self, const int time_out)
761{
762 dt_map_t *lib = (dt_map_t *)self->data;
763 if(time_out)
764 {
765 if(lib->loc.time_out)
766 {
767 lib->loc.time_out = time_out;
768 }
769 else
770 {
771 lib->loc.time_out = time_out;
772 g_timeout_add(100, _view_map_signal_change_delayed, self);
773 }
774 }
776}
777
778static gboolean _view_map_redraw(gpointer user_data)
779{
780 dt_view_t *self = (dt_view_t *)user_data;
781 dt_map_t *lib = (dt_map_t *)self->data;
782 g_signal_emit_by_name(lib->map, "changed");
783 return FALSE; // remove the function again
784}
785
786static void _view_map_angles(dt_map_t *lib, const gint pixels, const float lat,
787 const float lon, float *dlat_min, float *dlon_min)
788{
789 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat, lon);
790 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(0.0, 0.0);
791 gint pixel_x = 0, pixel_y = 0;
792 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &pixel_x, &pixel_y);
793 osm_gps_map_convert_screen_to_geographic(lib->map, pixel_x + pixels,
794 pixel_y + pixels, pt1);
795 float lat1 = 0.0, lon1 = 0.0;
796 osm_gps_map_point_get_degrees(pt1, &lat1, &lon1);
797 osm_gps_map_point_free(pt0);
798 osm_gps_map_point_free(pt1);
799 *dlat_min = lat - lat1;
800 *dlon_min = lon1 - lon;
801}
802
803// when map is moving we often get incorrect (even negative) values.
804// keep the last positive values here to limit wrong effects (still not perfect)
805static void _view_map_thumb_angles(dt_map_t *lib, const float lat, const float lon,
806 float *dlat_min, float *dlon_min)
807{
808 _view_map_angles(lib, thumb_size, lat, lon, dlat_min, dlon_min);
809 if(*dlat_min > 0.0 && *dlon_min > 0.0)
810 {
811 lib->thumb_lat_angle = *dlat_min;
812 lib->thumb_lon_angle = *dlon_min;
813 }
814 else // something got wrong, keep the last positive values
815 {
816 *dlat_min = lib->thumb_lat_angle ;
817 *dlon_min = lib->thumb_lon_angle;
818 }
819}
820
821static float _view_map_angles_to_pixels(const dt_map_t *lib, const float lat0,
822 const float lon0, const float angle)
823{
824 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat0, lon0);
825 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(lat0 + angle, lon0 + angle);
826 gint px0 = 0, py0 = 0;
827 gint px1 = 0, py1 = 0;
828 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &px0, &py0);
829 osm_gps_map_convert_geographic_to_screen(lib->map, pt1, &px1, &py1);
830 osm_gps_map_point_free(pt0);
831 osm_gps_map_point_free(pt1);
832 return abs(px1 - px0);
833}
834
835static double _view_map_get_angles_ratio(const dt_map_t *lib, const float lat0,
836 const float lon0)
837{
838 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat0, lon0);
839 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(lat0 + 2.0, lon0 + 2.0);
840 gint px0 = 0, py0 = 0;
841 gint px1 = 0, py1 = 0;
842 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &px0, &py0);
843 osm_gps_map_convert_geographic_to_screen(lib->map, pt1, &px1, &py1);
844 osm_gps_map_point_free(pt0);
845 osm_gps_map_point_free(pt1);
846 double ratio = 1.;
847 if((px1 - px0) > 0)
848 ratio = (float)abs(py1 - py0) / (float)(px1 - px0);
849 return ratio;
850}
851
852static GdkPixbuf *_draw_location(dt_map_t *lib, int *width, int *height,
854 const gboolean main)
855{
856 float pixel_lon = _view_map_angles_to_pixels(lib, data->lat, data->lon, data->delta1);
857 float pixel_lat = pixel_lon * data->delta2 * data->ratio / data->delta1;
858 GdkPixbuf *draw = NULL;
860 {
861 draw = _draw_ellipse(pixel_lon, pixel_lat, main);
862 if(pixel_lon > pixel_lat)
863 pixel_lat = pixel_lon;
864 else
865 pixel_lon = pixel_lat;
866 }
867 else if(data->shape == MAP_LOCATION_SHAPE_RECTANGLE)
868 draw = _draw_rectangle(pixel_lon, pixel_lat, main);
869
870 if(width) *width = (int)pixel_lon;
871 if(height) *height = (int)pixel_lat;
872 return draw;
873}
874
876{
877 for(GList *other = lib->loc.others; other; other = g_list_next(other))
878 {
880 if(d->id == locid)
881 return d;
882 }
883 return NULL;
884}
885
886GList *_others_location(GList *others, const int locid)
887{
888 for(GList *other = others; other; other = g_list_next(other))
889 {
891 if(d->id == locid)
892 return other;
893 }
894 return NULL;
895}
896
897static OsmGpsMapImage *_view_map_draw_location(dt_map_t *lib, dt_location_draw_t *ld,
898 const gboolean main)
899{
901 {
902 GdkPixbuf *draw = _draw_location(lib, NULL, NULL, &ld->data, main);
903 OsmGpsMapImage *location = NULL;
904 if(draw)
905 {
906 location = osm_gps_map_image_add_with_alignment(lib->map, ld->data.lat, ld->data.lon, draw, 0.5, 0.5);
907 g_object_unref(draw);
908 }
909 return location;
910 }
911 else
912 {
913 if(IS_NULL_PTR(ld->data.polygons) && ld == &lib->loc.main)
914 {
916 if(d)
917 {
918 ld->data.polygons = d->data.polygons;
919 ld->data.plg_pts = d->data.plg_pts;
920 }
921 }
922 if(IS_NULL_PTR(ld->data.polygons))
924 OsmGpsMapPolygon *location = _view_map_add_polygon_location(lib, ld);
925 return (OsmGpsMapImage *)location;
926 }
927}
928
930{
931 if(ld->location)
932 {
934 osm_gps_map_image_remove(lib->map, ld->location);
935 else
936 osm_gps_map_polygon_remove(lib->map, (OsmGpsMapPolygon *)ld->location);
937 ld->location = NULL;
938 }
939}
940
941static void _view_map_delete_other_location(dt_map_t *lib, GList *other)
942{
946 lib->loc.others = g_list_remove_link(lib->loc.others, other);
947 dt_free(other->data);
948 g_list_free(other);
949 other = NULL;
950}
951
953{
954 if(lib->loc.main.id && (ld != &lib->loc.main))
955 {
956 // save the data to others locations (including polygons)
958 if(IS_NULL_PTR(d))
959 {
960 d = g_malloc0(sizeof(dt_location_draw_t));
961 lib->loc.others = g_list_append(lib->loc.others, d);
962 }
963 if(d)
964 {
965 // copy everything except the drawn location
966 memcpy(d, &lib->loc.main, sizeof(dt_location_draw_t));
967 d->location = NULL;
968 if(dt_conf_get_bool("plugins/map/showalllocations"))
969 d->location = _view_map_draw_location(lib, d, FALSE);
970 }
971 }
973
974 if(ld && ld->id)
975 {
976 memcpy(&lib->loc.main, ld, sizeof(dt_location_draw_t));
979 if(d && d->location)
981 }
982}
983
985{
986 // clean up other locations
987 {
988 for(GList *other = lib->loc.others; other; other = g_list_next(other))
989 {
992 }
993 }
994 if(dt_conf_get_bool("plugins/map/showalllocations"))
995 {
997
998 for(GList *other = others; other; other = g_list_next(other))
999 {
1001 GList *other2 = _others_location(lib->loc.others, d->id);
1002 // add the new ones
1003 if(IS_NULL_PTR(other2))
1004 {
1005 d = (dt_location_draw_t *)other->data;
1006 lib->loc.others = g_list_append(lib->loc.others, other->data);
1007 other->data = NULL; // to avoid it gets freed
1008 if(d->data.shape == MAP_LOCATION_SHAPE_POLYGONS)
1009 {
1010 if(d->id == lib->loc.main.id)
1011 {
1012 d->data.polygons = lib->loc.main.data.polygons;
1013 d->data.plg_pts = lib->loc.main.data.plg_pts;
1014 }
1015 if(IS_NULL_PTR(d->data.polygons))
1017 }
1018 }
1019 else
1020 d = (dt_location_draw_t *)other2->data;
1021 // display again location except polygons and the selected one
1022 if((!lib->loc.main.id || lib->loc.main.id != d->id) && IS_NULL_PTR(d->location))
1023 d->location = _view_map_draw_location(lib, d, FALSE);
1024 }
1025 // free the new list
1026 g_list_free_full(others, dt_free_gpointer);
1027 others = NULL;
1028 }
1029}
1030
1032{
1033 dt_map_t *lib = (dt_map_t *)self->data;
1034 if(lib->loc.main.id > 0)
1035 {
1036 // update coordinates
1040 }
1041}
1042
1043static GdkPixbuf *_draw_image(dt_map_image_t *entry, int *width, int *height,
1044 const int group_count, const gboolean group_same_loc,
1045 const uint32_t frame, const int thumbnail, dt_view_t *self)
1046{
1047 dt_map_t *lib = (dt_map_t *)self->data;
1048
1049 GdkPixbuf *thumb = NULL, *source = NULL, *count = NULL;
1050 const float _thumb_border = DT_PIXEL_APPLY_DPI(thumb_border);
1051 const float _pin_size = DT_PIXEL_APPLY_DPI(image_pin_size);
1052
1053 if(thumbnail == DT_MAP_THUMB_THUMB)
1054 {
1057 &entry->surface, GTK_WIDGET(lib->map), DT_THUMBTABLE_ZOOM_FIT);
1058 if(res != DT_VIEW_SURFACE_OK || IS_NULL_PTR(entry->surface)) return NULL;
1059
1060 const int raw_w = cairo_image_surface_get_width(entry->surface);
1061 const int raw_h = cairo_image_surface_get_height(entry->surface);
1062 double sx = 1.0, sy = 1.0;
1063 cairo_surface_get_device_scale(entry->surface, &sx, &sy);
1064 /* Map markers are plain pixbufs, so we flatten the fetched Cairo surface
1065 * once into logical coordinates. The surface already carries device scale,
1066 * so dividing by that gives the correct on-map thumbnail size without
1067 * extra HiDPI compensation in the marker code. */
1068 const int w = MAX(1, (int)round((double)raw_w / sx));
1069 const int h = MAX(1, (int)round((double)raw_h / sy));
1070 if(raw_w == w && raw_h == h)
1071 {
1072 source = gdk_pixbuf_get_from_surface(entry->surface, 0, 0, w, h);
1073 if(IS_NULL_PTR(source)) goto map_changed_failure;
1074 }
1075 else
1076 {
1077 cairo_surface_t *logical = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
1078 cairo_t *cr = cairo_create(logical);
1079 cairo_set_source_surface(cr, entry->surface, 0, 0);
1080 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1081 cairo_paint(cr);
1082 cairo_destroy(cr);
1083 source = gdk_pixbuf_get_from_surface(logical, 0, 0, w, h);
1084 cairo_surface_destroy(logical);
1085 if(IS_NULL_PTR(source)) goto map_changed_failure;
1086 }
1087
1088 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w + 2 * _thumb_border,
1089 h + 2 * _thumb_border + _pin_size);
1090 if(IS_NULL_PTR(thumb)) goto map_changed_failure;
1091 gdk_pixbuf_fill(thumb, frame);
1092 gdk_pixbuf_copy_area(source, 0, 0, w, h, thumb, _thumb_border, _thumb_border);
1093 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w + 2 * _thumb_border,
1094 _pin_size, thumb, 0, h + 2 * _thumb_border);
1095 if(group_count)
1096 {
1097 double count_height, count_width;
1098 count = _view_map_images_count(group_count, group_same_loc, &count_width, &count_height);
1099 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb,
1100 _thumb_border, h - count_height + _thumb_border);
1101 }
1102 if(width) *width = w;
1103 if(height) *height = h;
1104 }
1105 else if(thumbnail == DT_MAP_THUMB_COUNT)
1106 {
1107 double count_height, count_width;
1108 count = _view_map_images_count(group_count, group_same_loc,
1109 &count_width, &count_height);
1110 if(!count) goto map_changed_failure;
1111 const int w = count_width + 2 * _thumb_border;
1112 const int h = count_height + 2 * _thumb_border + _pin_size;
1113 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
1114 if(IS_NULL_PTR(thumb)) goto map_changed_failure;
1115 gdk_pixbuf_fill(thumb, frame);
1116 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb,
1117 _thumb_border, _thumb_border);
1118 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w, _pin_size, thumb,
1119 0, count_height + 2 * _thumb_border);
1120 if(width) *width = count_width;
1121 if(height) *height = count_height;
1122 }
1123map_changed_failure:
1124 if(source) g_object_unref(source);
1125 if(count) g_object_unref(count);
1126 return thumb;
1127}
1128
1129static GdkPixbuf *_draw_transient_image(const int32_t imgid, int *width, int *height, const int group_count,
1130 const gboolean group_same_loc, const uint32_t frame,
1131 const int thumbnail, dt_view_t *self)
1132{
1133 dt_map_t *lib = (dt_map_t *)self->data;
1134 GdkPixbuf *thumb = NULL, *source = NULL, *count = NULL;
1135 cairo_surface_t *surface = NULL;
1136 const float _thumb_border = DT_PIXEL_APPLY_DPI(thumb_border);
1137 const float _pin_size = DT_PIXEL_APPLY_DPI(image_pin_size);
1138
1139 if(thumbnail == DT_MAP_THUMB_THUMB)
1140 {
1143 if(res != DT_VIEW_SURFACE_OK || IS_NULL_PTR(surface)) goto map_changed_failure;
1144
1145 const int raw_w = cairo_image_surface_get_width(surface);
1146 const int raw_h = cairo_image_surface_get_height(surface);
1147 double sx = 1.0, sy = 1.0;
1148 cairo_surface_get_device_scale(surface, &sx, &sy);
1149 const int w = MAX(1, (int)round((double)raw_w / sx));
1150 const int h = MAX(1, (int)round((double)raw_h / sy));
1151 if(raw_w == w && raw_h == h)
1152 {
1153 source = gdk_pixbuf_get_from_surface(surface, 0, 0, w, h);
1154 if(IS_NULL_PTR(source)) goto map_changed_failure;
1155 }
1156 else
1157 {
1158 cairo_surface_t *logical = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
1159 cairo_t *cr = cairo_create(logical);
1160 cairo_set_source_surface(cr, surface, 0, 0);
1161 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1162 cairo_paint(cr);
1163 cairo_destroy(cr);
1164 source = gdk_pixbuf_get_from_surface(logical, 0, 0, w, h);
1165 cairo_surface_destroy(logical);
1166 if(IS_NULL_PTR(source)) goto map_changed_failure;
1167 }
1168
1169 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w + 2 * _thumb_border,
1170 h + 2 * _thumb_border + _pin_size);
1171 if(IS_NULL_PTR(thumb)) goto map_changed_failure;
1172 gdk_pixbuf_fill(thumb, frame);
1173 gdk_pixbuf_copy_area(source, 0, 0, w, h, thumb, _thumb_border, _thumb_border);
1174 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w + 2 * _thumb_border,
1175 _pin_size, thumb, 0, h + 2 * _thumb_border);
1176 if(group_count)
1177 {
1178 double count_height, count_width;
1179 count = _view_map_images_count(group_count, group_same_loc, &count_width, &count_height);
1180 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb,
1181 _thumb_border, h - count_height + _thumb_border);
1182 }
1183 if(width) *width = w;
1184 if(height) *height = h;
1185 }
1186 else if(thumbnail == DT_MAP_THUMB_COUNT)
1187 {
1188 double count_height, count_width;
1189 count = _view_map_images_count(group_count, group_same_loc, &count_width, &count_height);
1190 if(!count) goto map_changed_failure;
1191 const int w = count_width + 2 * _thumb_border;
1192 const int h = count_height + 2 * _thumb_border + _pin_size;
1193 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
1194 if(IS_NULL_PTR(thumb)) goto map_changed_failure;
1195 gdk_pixbuf_fill(thumb, frame);
1196 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb, _thumb_border, _thumb_border);
1197 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w, _pin_size, thumb, 0, count_height + 2 * _thumb_border);
1198 if(width) *width = count_width;
1199 if(height) *height = count_height;
1200 }
1201
1202map_changed_failure:
1203 if(surface) cairo_surface_destroy(surface);
1204 if(source) g_object_unref(source);
1205 if(count) g_object_unref(count);
1206 return thumb;
1207}
1208
1209static gboolean _view_map_draw_image(dt_map_image_t *entry, const int thumbnail, dt_view_t *self)
1210{
1211 dt_map_t *lib = (dt_map_t *)self->data;
1212 gboolean needs_redraw = FALSE;
1213 if(entry->image && entry->thumbnail != thumbnail)
1214 {
1215 osm_gps_map_image_remove(lib->map, entry->image);
1216 entry->image = NULL;
1217 }
1218 if(IS_NULL_PTR(entry->image))
1219 {
1220 GdkPixbuf *thumb = _draw_image(entry, &entry->width, &entry->height,
1221 entry->group_count, entry->group_same_loc,
1223 thumbnail, self);
1224 if(thumb)
1225 {
1226 entry->thumbnail = thumbnail;
1227 entry->image = osm_gps_map_image_add_with_alignment(lib->map, entry->latitude,
1228 entry->longitude,
1229 thumb, 0, 1);
1230 g_object_unref(thumb);
1231 }
1232 else
1233 needs_redraw = TRUE;
1234 }
1235 return needs_redraw;
1236}
1237
1238// scan the images list and draw the missing ones
1239// if launched to be executed repeatedly, return FALSE when it is done
1240static gboolean _view_map_draw_images(gpointer user_data)
1241{
1242 dt_view_t *self = (dt_view_t *)user_data;
1243 dt_map_t *lib = (dt_map_t *)self->data;
1244 gboolean needs_redraw = FALSE;
1245 int img_drawn = 0;
1246 for(GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1247 {
1248 needs_redraw = _view_map_draw_image((dt_map_image_t *)iter->data, lib->thumbnail, self);
1249 img_drawn++;
1250 // we limit the number of displayed images as required
1251 if(lib->thumbnail == DT_MAP_THUMB_THUMB && img_drawn >= lib->max_images_drawn)
1252 break;
1253 }
1254 if(!needs_redraw)
1255 lib->timeout_event_source = 0;
1256 return needs_redraw;
1257}
1258
1260{
1261 OsmGpsMapPoint bb[2];
1262 osm_gps_map_get_bbox(lib->map, &bb[0], &bb[1]);
1263 dt_map_box_t box = {0.0};
1264
1265 osm_gps_map_point_get_degrees(&bb[0], &box.lat1, &box.lon1);
1266 osm_gps_map_point_get_degrees(&bb[1], &box.lat2, &box.lon2);
1267 box.lat1 = CLAMP(box.lat1, -90.0, 90.0);
1268 box.lat2 = CLAMP(box.lat2, -90.0, 90.0);
1269 box.lon1 = CLAMP(box.lon1, -180.0, 180.0);
1270 box.lon2 = CLAMP(box.lon2, -180.0, 180.0);
1271 memcpy(bbox, &box, sizeof(dt_map_box_t));
1272}
1273
1274static void _view_map_changed_callback_delayed(gpointer user_data)
1275{
1276 dt_view_t *self = (dt_view_t *)user_data;
1277 dt_map_t *lib = (dt_map_t *)self->data;
1278 gboolean needs_redraw = FALSE;
1279 // called for its side effects: it refreshes lib->max_images_drawn and lib->thumbnail from
1280 // conf. Its return value used to say whether the main query needed rebuilding; there is no
1281 // main query here any more, and the other caller still reads it.
1283
1284 if(!lib->timeout_event_source)
1285 {
1286 /* remove the old images */
1287 if(lib->images)
1288 {
1289 // we can't use osm_gps_map_image_remove_all() because we want to keep the marker
1290 for(GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1291 {
1292 dt_map_image_t *image = (dt_map_image_t *)iter->data;
1293 if(image->image)
1294 osm_gps_map_image_remove(lib->map, image->image);
1295 }
1296 g_slist_free_full(lib->images, _free_map_image);
1297 lib->images = NULL;
1298 }
1299 }
1300
1302 {
1303 // not a redraw
1304 /* get bounding box coords */
1305 _view_map_get_bounding_box(lib, &lib->bbox);
1306
1307 /* get map view state and store */
1308 int zoom;
1309 float center_lat, center_lon;
1310 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "latitude", &center_lat, "longitude", &center_lon, NULL);
1311 dt_conf_set_float("plugins/map/longitude", center_lon);
1312 dt_conf_set_float("plugins/map/latitude", center_lat);
1313 dt_conf_set_int("plugins/map/zoom", zoom);
1314
1315 /* the collected images inside the viewport, in the longitude order dbscan needs */
1316 int img_count = 0;
1318 lib->bbox.lon1, lib->bbox.lon2, lib->bbox.lat1, lib->bbox.lat2, &img_count);
1319
1320 if(lib->points)
1321 {
1322 dt_free(lib->points);
1323 }
1324 lib->nb_points = img_count;
1325 if(img_count > 0)
1326 lib->points = (dt_geo_position_t *)calloc(img_count, sizeof(dt_geo_position_t));
1327 dt_geo_position_t *p = lib->points;
1328
1329 /* make the image list: same points, in radians, ready to be clustered */
1330 int i = 0;
1331 for(i = 0; p && i < img_count; i++)
1332 {
1333 p[i].imgid = geo[i].imgid;
1334 p[i].x = geo[i].longitude * M_PI / 180;
1335 p[i].y = geo[i].latitude * M_PI / 180;
1336 p[i].cluster_id = UNCLASSIFIED;
1337 }
1338 dt_free(geo);
1339
1340 if(p)
1341 {
1342 const float epsilon_factor = dt_conf_get_int("plugins/map/epsilon_factor");
1343 const int min_images = dt_conf_get_int("plugins/map/min_images_per_group");
1344 // zoom varies from 0 (156412 m/pixel) to 20 (0.149 m/pixel)
1345 // https://wiki.openstreetmap.org/wiki/Zoom_levels
1346 // each time zoom increases by 1 the size is divided by 2
1347 // epsilon factor = 100 => epsilon covers more or less a thumbnail surface
1348 #define R 6371 // earth radius (km)
1349 double epsilon = thumb_size * (((unsigned int)(156412000 >> zoom))
1350 * epsilon_factor * 0.01 * 0.000001 / R);
1351
1352 dt_times_t start;
1353 dt_get_times(&start);
1354 _dbscan(p, img_count, epsilon, min_images);
1355 dt_show_times(&start, "[map] dbscan calculation");
1356
1357 // set the clusters
1358 GList *sel_imgs = dt_act_on_get_images();
1359 int group = -1;
1360 for(i = 0; i< img_count; i++)
1361 {
1362 if(p[i].cluster_id == NOISE)
1363 {
1364 dt_map_image_t *entry = (dt_map_image_t *)calloc(1, sizeof(dt_map_image_t));
1366 entry->imgid = p[i].imgid;
1367 entry->group = p[i].cluster_id;
1368 entry->group_count = 1;
1369 entry->longitude = p[i].x * 180 / M_PI;
1370 entry->latitude = p[i].y * 180 / M_PI;
1371 entry->group_same_loc = TRUE;
1372 if(sel_imgs)
1373 entry->selected_in_group = g_list_find((GList *)sel_imgs,
1374 GINT_TO_POINTER(entry->imgid))
1375 ? TRUE : FALSE;
1376 lib->images = g_slist_prepend(lib->images, entry);
1377 }
1378 else if(p[i].cluster_id > group)
1379 {
1380 group = p[i].cluster_id;
1381 dt_map_image_t *entry = (dt_map_image_t *)calloc(1, sizeof(dt_map_image_t));
1383 entry->imgid = p[i].imgid;
1384 entry->group = p[i].cluster_id;
1385 entry->group_same_loc = TRUE;
1386 entry->selected_in_group = (sel_imgs && g_list_find((GList *)sel_imgs,
1387 GINT_TO_POINTER(p[i].imgid)))
1388 ? TRUE : FALSE;
1389 const double lon = p[i].x, lat = p[i].y;
1390 for(int j = 0; j < img_count; j++)
1391 {
1392 if(p[j].cluster_id == group)
1393 {
1394 entry->group_count++;
1395 entry->longitude += p[j].x;
1396 entry->latitude += p[j].y;
1397 if(entry->group_same_loc && (p[j].x != lon || p[j].y != lat))
1398 {
1399 entry->group_same_loc = FALSE;
1400 }
1401 if(sel_imgs && !entry->selected_in_group)
1402 {
1403 if(g_list_find((GList *)sel_imgs, GINT_TO_POINTER(p[j].imgid)))
1404 entry->selected_in_group = TRUE;
1405 }
1406 }
1407 }
1408 entry->latitude = entry->latitude * 180 / M_PI / entry->group_count;
1409 entry->longitude = entry->longitude * 180 / M_PI / entry->group_count;
1410 lib->images = g_slist_prepend(lib->images, entry);
1411 }
1412 }
1413 g_list_free(sel_imgs);
1414 sel_imgs = NULL;
1415 }
1416
1417 needs_redraw = _view_map_draw_images(self);
1420 }
1421
1422 // not exactly thread safe, but should be good enough for updating the display
1423 if(needs_redraw && lib->timeout_event_source == 0)
1424 {
1425 lib->timeout_event_source = g_timeout_add(100, _view_map_draw_images, self); // try again later on
1426 }
1427}
1428
1429static gboolean _view_map_changed_callback_wait(gpointer user_data)
1430{
1431 dt_view_t *self = (dt_view_t *)user_data;
1432 dt_map_t *lib = (dt_map_t *)self->data;
1433 if(lib->time_out)
1434 {
1435 lib->time_out--;
1436 if(!lib->time_out)
1437 {
1439 return FALSE;
1440 }
1441 }
1442 return TRUE;
1443}
1444
1445static int first_times = 3;
1446
1447static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self)
1448{
1449 dt_map_t *lib = (dt_map_t *)self->data;
1450 // ugly but it avoids to display not well controlled maps at init time
1451 if(first_times)
1452 {
1453 first_times--;;
1454 return;
1455 }
1456
1457 // "changed" event can be high frequency. As calculation is heavy we don't to repeat it.
1458 if(!lib->time_out)
1459 {
1460 g_timeout_add(100, _view_map_changed_callback_wait, self);
1461 }
1462 lib->time_out = 2;
1463}
1464
1466 const double y)
1467{
1468 dt_map_t *lib = (dt_map_t *)self->data;
1469
1470 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1471 {
1472 dt_map_image_t *entry = (dt_map_image_t *)iter->data;
1473 OsmGpsMapImage *image = entry->image;
1474 if(image)
1475 {
1476 OsmGpsMapPoint *pt = (OsmGpsMapPoint *)osm_gps_map_image_get_point(image);
1477 gint img_x = 0, img_y = 0;
1478 osm_gps_map_convert_geographic_to_screen(lib->map, pt, &img_x, &img_y);
1480 if(x >= img_x && x <= img_x + entry->width && y <= img_y && y >= img_y - entry->height)
1481 {
1482 return entry;
1483 }
1484 }
1485 }
1486 return NULL;
1487}
1488
1489static GList *_view_map_get_imgs_at_pos(dt_view_t *self, const float x, const float y,
1490 int *offset_x, int *offset_y, const gboolean first_on)
1491{
1492 dt_map_t *lib = (dt_map_t *)self->data;
1493 GList *imgs = NULL;
1494 int32_t imgid = UNKNOWN_IMAGE;
1495 dt_map_image_t *entry = NULL;
1496
1497 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1498 {
1499 entry = (dt_map_image_t *)iter->data;
1500 OsmGpsMapImage *image = entry->image;
1501 if(image)
1502 {
1503 OsmGpsMapPoint *pt = (OsmGpsMapPoint *)osm_gps_map_image_get_point(image);
1504 gint img_x = 0, img_y = 0;
1505 osm_gps_map_convert_geographic_to_screen(lib->map, pt, &img_x, &img_y);
1507 if(x >= img_x && x <= img_x + entry->width && y <= img_y && y >= img_y - entry->height)
1508 {
1509 imgid = entry->imgid;
1510 *offset_x = (int)x - img_x;
1511 *offset_y = (int)y - img_y - DT_PIXEL_APPLY_DPI(image_pin_size);
1512 break;
1513 }
1514 }
1515 }
1516
1517 if(imgid != UNKNOWN_IMAGE && !first_on && entry->group_count > 1 && lib->points)
1518 {
1519 dt_geo_position_t *p = lib->points;
1520 int count = 1;
1521 for(int i = 0; i < lib->nb_points; i++)
1522 {
1523 if(p[i].cluster_id == entry->group && p[i].imgid != imgid)
1524 {
1525 imgs = g_list_prepend(imgs, GINT_TO_POINTER(p[i].imgid));
1526 count++;
1527 if(count >= entry->group_count)
1528 {
1529 break;
1530 }
1531 }
1532 }
1533 }
1534 if(imgid != UNKNOWN_IMAGE)
1535 // it's necessary to have the visible image as the first one of the list
1536 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
1537 return imgs;
1538}
1539
1540gint _find_image_in_images(gconstpointer a, gconstpointer b)
1541{
1542 dt_map_image_t *entry = (dt_map_image_t *)a;
1543 return entry->imgid == GPOINTER_TO_INT(b) ? 0 : 1;
1544}
1545
1546static gboolean _display_next_image(dt_view_t *self, dt_map_image_t *entry, const gboolean next)
1547{
1548 dt_map_t *lib = (dt_map_t *)self->data;
1549 if(IS_NULL_PTR(entry)) return FALSE;
1550
1551 if(entry->group_count == 1)
1552 {
1553 if(entry->image)
1554 {
1555 osm_gps_map_image_remove(lib->map, entry->image);
1556 entry->image = NULL;
1557 }
1560 return TRUE;
1561 }
1562
1563 dt_geo_position_t *p = lib->points;
1564 int index = -1;
1565 for(int i = 0; i < lib->nb_points; i++)
1566 {
1567 if(p[i].imgid == GPOINTER_TO_INT(entry->imgid))
1568 {
1569 if(next)
1570 {
1571 for(int j = i + 1; j < lib->nb_points; j++)
1572 {
1573 if(p[j].cluster_id == entry->group)
1574 {
1575 index = j;
1576 break;
1577 }
1578 }
1579 if(index == -1)
1580 {
1581 for(int j = 0; j < i; j++)
1582 {
1583 if(p[j].cluster_id == entry->group)
1584 {
1585 index = j;
1586 break;
1587 }
1588 }
1589 }
1590 }
1591 else
1592 {
1593 for(int j = i - 1; j >= 0; j--)
1594 {
1595 if(p[j].cluster_id == entry->group)
1596 {
1597 index = j;
1598 break;
1599 }
1600 }
1601 if(index == -1)
1602 {
1603 for(int j = lib->nb_points - 1; j > i; j--)
1604 {
1605 if(p[j].cluster_id == entry->group)
1606 {
1607 index = j;
1608 break;
1609 }
1610 }
1611 }
1612 }
1613 break;
1614 }
1615 }
1616 if(index == -1) return FALSE;
1617 entry->imgid = p[index].imgid;
1618 if(entry->image)
1619 {
1620 osm_gps_map_image_remove(lib->map, entry->image);
1621 entry->image = NULL;
1622 }
1625 return TRUE;
1626}
1627
1628static void _view_map_drag_set_icon(const dt_view_t *self, GdkDragContext *context,
1629 const int32_t imgid, const int count)
1630{
1631 dt_map_t *lib = (dt_map_t *)self->data;
1632 int height;
1633 GdkPixbuf *thumb = _draw_transient_image(imgid, NULL, &height, count, TRUE, thumb_frame_sel_color,
1634 lib->thumbnail, (dt_view_t *)self);
1635 if(thumb)
1636 {
1637 GtkWidget *image = gtk_image_new_from_pixbuf(thumb);
1638
1639 gtk_widget_show(image);
1640 gtk_drag_set_icon_widget(context, image, lib->start_drag_offset_x,
1642 g_object_unref(thumb);
1643 }
1644}
1645
1646static gboolean _view_map_drag_motion_callback(GtkWidget *widget, GdkDragContext *dc,
1647 gint x, gint y, guint time, dt_view_t *self)
1648{
1649 dt_map_t *lib = (dt_map_t *)self->data;
1650 OsmGpsMapPoint *p = osm_gps_map_point_new_degrees(0.0, 0.0);
1651 osm_gps_map_convert_screen_to_geographic(lib->map, x, y, p);
1652 float lat, lon;
1653 osm_gps_map_point_get_degrees(p, &lat, &lon);
1654 osm_gps_map_point_free(p);
1656 return FALSE;
1657}
1658
1659static gboolean _view_map_motion_notify_callback(GtkWidget *widget, GdkEventMotion *e, dt_view_t *self)
1660{
1661 dt_map_t *lib = (dt_map_t *)self->data;
1662 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, (GdkEventButton *)e);
1663 float lat, lon;
1664 osm_gps_map_point_get_degrees(p, &lat, &lon);
1666
1667 if(lib->loc.drag && lib->loc.main.id > 0 &&
1668 (abs(lib->start_drag_x - (int)ceil(e->x_root)) +
1669 abs(lib->start_drag_y - (int)ceil(e->y_root))) > DT_PIXEL_APPLY_DPI(8))
1670 {
1671 lib->loc.drag = FALSE;
1672 osm_gps_map_image_remove(lib->map, lib->loc.main.location);
1673 GtkTargetList *targets = gtk_target_list_new(target_list_internal, n_targets_internal);
1674
1675 GdkDragContext *context =
1676 gtk_drag_begin_with_coordinates(GTK_WIDGET(lib->map), targets,
1677 GDK_ACTION_MOVE, 1,
1678 (GdkEvent *)e, -1, -1);
1679
1680 int width;
1681 int height;
1682 GdkPixbuf *location = _draw_location(lib, &width, &height, &lib->loc.main.data, TRUE);
1683 if(location)
1684 {
1685 GtkWidget *image = gtk_image_new_from_pixbuf(location);
1686 gtk_widget_set_name(image, "map-drag-icon");
1687 gtk_widget_show(image);
1688 gtk_drag_set_icon_widget(context, image,
1691 g_object_unref(location);
1692 }
1693 gtk_target_list_unref(targets);
1694 return TRUE;
1695 }
1696
1697 if(lib->start_drag && lib->selected_images &&
1698 (abs(lib->start_drag_x - (int)ceil(e->x_root)) +
1699 abs(lib->start_drag_y - (int)ceil(e->y_root))) > DT_PIXEL_APPLY_DPI(8))
1700 {
1701 const int nb = g_list_length(lib->selected_images);
1702 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1703 {
1704 dt_map_image_t *entry = (dt_map_image_t *)iter->data;
1705 if(entry->image)
1706 {
1707 GList *sel_img = lib->selected_images;
1708 if(entry->imgid == GPOINTER_TO_INT(sel_img->data))
1709 {
1710 if(entry->group_count == nb)
1711 {
1712 osm_gps_map_image_remove(lib->map, entry->image);
1713 entry->image = NULL;
1714 }
1715 else
1716 _display_next_image(self, entry, TRUE);
1717 break;
1718 }
1719 }
1720 }
1721
1722 const int group_count = g_list_length(lib->selected_images);
1723
1724 lib->start_drag = FALSE;
1725 GtkTargetList *targets = gtk_target_list_new(target_list_all, n_targets_all);
1726 GdkDragContext *context = gtk_drag_begin_with_coordinates(GTK_WIDGET(lib->map), targets,
1727 GDK_ACTION_MOVE, 1,
1728 (GdkEvent *)e, -1, -1);
1729 _view_map_drag_set_icon(self, context, GPOINTER_TO_INT(lib->selected_images->data),
1730 group_count);
1731 gtk_target_list_unref(targets);
1732 return TRUE;
1733 }
1734
1735
1736 dt_map_image_t *entry = _view_map_get_entry_at_pos(self, e->x, e->y);
1737 if(entry)
1738 {
1739 // show image information if image is hovered
1741 // if count is displayed shows the thumbnail
1742 if(lib->thumbnail == DT_MAP_THUMB_COUNT)
1743 {
1745 lib->last_hovered_entry = entry;
1746 return TRUE;
1747 }
1748 }
1749 else
1750 {
1752 if(lib->last_hovered_entry)
1753 {
1754 // _view_map_draw_image(lib->last_hovered_entry) would be faster but is not safe
1756 lib->last_hovered_entry = NULL;
1757 }
1758 }
1759 return FALSE;
1760}
1761
1762static gboolean _zoom_and_center(const gint x, const gint y, const int direction, dt_view_t *self)
1763{
1764 // try to keep the center of zoom at the mouse position
1765 dt_map_t *lib = (dt_map_t *)self->data;
1766 int zoom, max_zoom;
1767 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "max-zoom", &max_zoom, NULL);
1768
1769 OsmGpsMapPoint bb[2];
1770 osm_gps_map_get_bbox(lib->map, &bb[0], &bb[1]);
1771 gint x0, x1, y0, y1;
1772 osm_gps_map_convert_geographic_to_screen(lib->map, &bb[0], &x0, &y0);
1773 osm_gps_map_convert_geographic_to_screen(lib->map, &bb[1], &x1, &y1);
1774
1775 gint nx, ny;
1776 if(direction == GDK_SCROLL_UP && zoom < max_zoom)
1777 {
1778 zoom++;
1779 nx = (x0 + x1 + 2 * x) / 4;
1780 ny = (y0 + y1 + 2 * y) / 4;
1781 }
1782 else if(direction == GDK_SCROLL_DOWN && zoom > 0)
1783 {
1784 zoom--;
1785 nx = x0 + x1 - x;
1786 ny = y0 + y1 - y;
1787 }
1788 else return FALSE;
1789
1790 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
1791 osm_gps_map_convert_screen_to_geographic(lib->map, nx, ny, pt);
1792 float nlat, nlon;
1793 osm_gps_map_point_get_degrees(pt, &nlat, &nlon);
1794 osm_gps_map_point_free(pt);
1795 osm_gps_map_set_center_and_zoom(lib->map, nlat, nlon, zoom);
1796
1797 return TRUE;
1798}
1799
1800static gboolean _view_map_scroll_event(GtkWidget *w, GdkEventScroll *event, dt_view_t *self)
1801{
1802 dt_map_t *lib = (dt_map_t *)self->data;
1803 // check if the click was on image(s) or just some random position
1804 dt_map_image_t *entry = _view_map_get_entry_at_pos(self, event->x, event->y);
1805 if(entry)
1806 {
1807 if(_display_next_image(self, entry, event->direction == GDK_SCROLL_DOWN))
1808 return TRUE;
1809 }
1810
1811 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, (GdkEventButton *)event);
1812 float lat, lon;
1813 osm_gps_map_point_get_degrees(p, &lat, &lon);
1814 if(lib->loc.main.id > 0)
1815 {
1817 {
1818 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
1819 {
1820 if(event->direction == GDK_SCROLL_DOWN)
1821 lib->loc.main.data.delta1 *= 1.1;
1822 else
1823 lib->loc.main.data.delta1 /= 1.1;
1824 }
1825 else if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
1826 {
1827 if(event->direction == GDK_SCROLL_DOWN)
1828 lib->loc.main.data.delta2 *= 1.1;
1829 else
1830 lib->loc.main.data.delta2 /= 1.1;
1831 }
1832 else
1833 {
1834 if(event->direction == GDK_SCROLL_DOWN)
1835 {
1836 lib->loc.main.data.delta1 *= 1.1;
1837 lib->loc.main.data.delta2 *= 1.1;
1838 }
1839 else
1840 {
1841 lib->loc.main.data.delta1 /= 1.1;
1842 lib->loc.main.data.delta2 /= 1.1;
1843 }
1844 }
1847 _view_map_signal_change_wait(self, 5); // wait 5/10 sec after last scroll
1848 return TRUE;
1849 }
1850 else // scroll on the map. try to keep the map where it is
1851 {
1853 return _zoom_and_center(event->x, event->y, event->direction, self);
1854 }
1855 }
1856 else
1857 {
1859 return _zoom_and_center(event->x, event->y, event->direction, self);
1860 }
1861 return FALSE;
1862}
1863
1864static gboolean _view_map_button_press_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
1865{
1866 dt_map_t *lib = (dt_map_t *)self->data;
1867 if(lib->selected_images)
1868 {
1869 g_list_free(lib->selected_images);
1870 lib->selected_images = NULL;
1871 }
1872 if(e->button == 1)
1873 {
1874 // check if the click was in a location form - crtl gives priority to images
1875 if(lib->loc.main.id > 0 && (lib->loc.main.data.shape != MAP_LOCATION_SHAPE_POLYGONS)
1876 && !dt_modifier_is(e->state, DT_PRIMARY_MASK))
1877 {
1878
1879 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, e);
1880 float lat, lon;
1881 osm_gps_map_point_get_degrees(p, &lat, &lon);
1883 {
1884 if(!dt_modifier_is(e->state, GDK_SHIFT_MASK))
1885 {
1886 lib->start_drag_x = ceil(e->x_root);
1887 lib->start_drag_y = ceil(e->y_root);
1888 lib->loc.drag = TRUE;
1889 return TRUE;
1890 }
1891 }
1892 }
1893 // check if another location is clicked - ctrl gives priority to images
1894 if (!dt_modifier_is(e->state, DT_PRIMARY_MASK) &&
1895 dt_conf_get_bool("plugins/map/showalllocations"))
1896 {
1897 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, e);
1898 float lat, lon;
1899 osm_gps_map_point_get_degrees(p, &lat, &lon);
1900 for(GList *other = lib->loc.others; other; other = g_list_next(other))
1901 {
1903 if(dt_map_location_included(lon, lat, &d->data))
1904 {
1910 return TRUE;
1911 }
1912 }
1913 }
1914 // check if the click was on image(s) or just some random position
1915 lib->selected_images = _view_map_get_imgs_at_pos(self, e->x, e->y,
1917 !dt_modifier_is(e->state, GDK_SHIFT_MASK));
1918 if(lib->selected_images)
1919 {
1920 const int32_t imgid = GPOINTER_TO_INT(lib->selected_images->data);
1924 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
1925 }
1926 if(e->type == GDK_BUTTON_PRESS)
1927 {
1928 if(lib->selected_images)
1929 {
1930 lib->start_drag_x = ceil(e->x_root);
1931 lib->start_drag_y = ceil(e->y_root);
1932 lib->start_drag = TRUE;
1933 return TRUE;
1934 }
1935 else
1936 {
1937 return FALSE;
1938 }
1939 }
1940 if(e->type == GDK_2BUTTON_PRESS)
1941 {
1942 if(lib->selected_images)
1943 {
1944 // open the image in darkroom
1945 dt_control_set_mouse_over_id(GPOINTER_TO_INT(lib->selected_images->data));
1946 dt_ctl_switch_mode_to("darkroom");
1947 return TRUE;
1948 }
1949 else
1950 {
1951 // zoom into that position
1952 float longitude, latitude;
1953 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
1954 osm_gps_map_convert_screen_to_geographic(lib->map, e->x, e->y, pt);
1955 osm_gps_map_point_get_degrees(pt, &latitude, &longitude);
1956 osm_gps_map_point_free(pt);
1957 int zoom, max_zoom;
1958 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "max-zoom", &max_zoom, NULL);
1959 zoom = MIN(zoom + 1, max_zoom);
1960 _view_map_center_on_location(self, longitude, latitude, zoom);
1961 }
1962 return TRUE;
1963 }
1964 }
1965 return FALSE;
1966}
1967
1968static gboolean _view_map_button_release_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
1969{
1970 dt_map_t *lib = (dt_map_t *)self->data;
1971 lib->start_drag = FALSE;
1972 lib->start_drag_offset_x = 0;
1973 lib->start_drag_offset_y = 0;
1974 lib->loc.drag = FALSE;
1975 return FALSE;
1976}
1977
1978static gboolean _view_map_display_selected(gpointer user_data)
1979{
1980 dt_view_t *self = (dt_view_t *)user_data;
1981 dt_map_t *lib = (dt_map_t *)self->data;
1982 gboolean done = FALSE;
1983 const GList *active = dt_view_active_images_get_all();
1984
1985 // Active images are the per-view working set once we leave lighttable.
1986 // Compute the bbox from that list directly instead of reading the global
1987 // selection table that gets intentionally cleared on entry.
1988 if(active)
1989 {
1990 double max_longitude = -INFINITY;
1991 double max_latitude = -INFINITY;
1992 double min_longitude = INFINITY;
1993 double min_latitude = INFINITY;
1994 int count = 0;
1995
1996 for(const GList *iter = active; iter; iter = g_list_next((GList *)iter))
1997 {
1998 const int32_t imgid = GPOINTER_TO_INT(iter->data);
1999 dt_image_geoloc_t geoloc;
2000 dt_image_get_location(imgid, &geoloc);
2001 if(isnan(geoloc.longitude) || isnan(geoloc.latitude)) continue;
2002
2003 max_longitude = fmax(max_longitude, geoloc.longitude);
2004 min_longitude = fmin(min_longitude, geoloc.longitude);
2005 max_latitude = fmax(max_latitude, geoloc.latitude);
2006 min_latitude = fmin(min_latitude, geoloc.latitude);
2007 count++;
2008 }
2009
2010 if(count > 0)
2011 {
2012 max_longitude = CLAMP(max_longitude, -180, 180);
2013 min_longitude = CLAMP(min_longitude, -180, 180);
2014 max_latitude = CLAMP(max_latitude, -90, 90);
2015 min_latitude = CLAMP(min_latitude, -90, 90);
2016 _view_map_center_on_bbox(self, min_longitude, min_latitude, max_longitude, max_latitude);
2017 done = TRUE;
2018 }
2019 }
2020
2021 // collection ?
2022 if(!done)
2023 {
2024 done = _view_map_center_on_image_list(self);
2025 }
2026
2027 // last map view
2028 if(!done)
2029 {
2030 /* if nothing to show restore last zoom,location in map */
2031 float lon = dt_conf_get_float("plugins/map/longitude");
2032 lon = CLAMP(lon, -180, 180);
2033 float lat = dt_conf_get_float("plugins/map/latitude");
2034 lat = CLAMP(lat, -90, 90);
2035 const int zoom = dt_conf_get_int("plugins/map/zoom");
2036 osm_gps_map_set_center_and_zoom(lib->map, lat, lon, zoom);
2037 }
2038 return FALSE; // don't call again
2039}
2040
2041void enter(dt_view_t *self)
2042{
2043 dt_map_t *lib = (dt_map_t *)self->data;
2044
2045 lib->selected_images = NULL;
2046 lib->start_drag = FALSE;
2047 lib->start_drag_offset_x = 0;
2048 lib->start_drag_offset_y = 0;
2049 lib->loc.drag = FALSE;
2050 lib->entering = TRUE;
2051
2054
2055 dt_thumbtable_show(dt_gui_get_ui()->thumbtable_filmstrip);
2056 dt_thumbtable_update_parent(dt_gui_get_ui()->thumbtable_filmstrip);
2057
2059 if(lib->incoming_selection)
2060 {
2062 lib->incoming_selection = NULL;
2063 }
2064
2065 /* set the correct map source */
2067
2068 /* The map widget is explicitly removed on leave so we need to reattach it on
2069 * every entry before it can receive allocations and paint over the center. */
2070 if(!gtk_widget_get_parent(GTK_WIDGET(lib->map)))
2071 {
2072 gtk_overlay_add_overlay(GTK_OVERLAY(dt_ui_center_base(dt_gui_get_ui())), GTK_WIDGET(lib->map));
2073 }
2074 gtk_widget_show_all(GTK_WIDGET(lib->map));
2075
2076 /* setup proxy functions */
2087
2088 vm->proxy.map.view = self;
2089
2090 /* Map opts into single-click filmstrip commits explicitly so generic
2091 * thumbtable activation can keep its double-click semantics. */
2093 G_CALLBACK(_view_map_filmstrip_activate_callback), self);
2095 G_CALLBACK(_view_map_filmstrip_drag_begin_callback), self);
2096
2097 const int32_t active_imgid = dt_view_active_images_get_first();
2098 dt_control_set_mouse_over_id(active_imgid);
2099 dt_control_set_keyboard_over_id(active_imgid);
2100 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
2101 g_timeout_add(250, _view_map_display_selected, self);
2102}
2103
2104void leave(dt_view_t *self)
2105{
2108
2109 dt_thumbtable_hide(dt_gui_get_ui()->thumbtable_filmstrip);
2110
2111 /* disable the map source again. no need to risk network traffic while we are not in map mode. */
2112 _view_map_set_map_source_g_object(self, OSM_GPS_MAP_SOURCE_NULL);
2113
2114 /* disconnect from filmstrip image activate */
2116 (gpointer)self);
2118 (gpointer)self);
2119 dt_map_t *lib = (dt_map_t *)self->data;
2121
2122 if(lib->selected_images)
2123 {
2124 g_list_free(lib->selected_images);
2125 lib->selected_images = NULL;
2126 }
2127 gtk_widget_hide(GTK_WIDGET(lib->map));
2128 if(gtk_widget_get_parent(GTK_WIDGET(lib->map)) == dt_ui_center_base(dt_gui_get_ui()))
2129 gtk_container_remove(GTK_CONTAINER(dt_ui_center_base(dt_gui_get_ui())), GTK_WIDGET(lib->map));
2130
2131 /* reset proxy */
2133}
2134
2135/* Merged in gui/actions/edit.h without the callback blocks
2136* TODO: investigate how bad it is to fire the callbacks all the time.
2137* FIXME : handle peculiar stuff in the actual callbacks if needed,
2138* while the higher-level callback dispatching is kept standard throughout the app.
2139* Current design is stupid.
2140static void _view_map_undo_callback(dt_action_t *action)
2141{
2142 dt_view_t *self = dt_action_view(action);
2143 dt_map_t *lib = (dt_map_t *)self->data;
2144
2145 // let current map view unchanged (avoid to center the map on collection)
2146 dt_control_signal_block_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_geotag_changed), self);
2147 dt_control_signal_block_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_collection_changed), self);
2148 dt_undo_do_undo(dt_undo_get_global(), DT_UNDO_MAP);
2149 dt_control_signal_unblock_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_collection_changed), self);
2150 dt_control_signal_unblock_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_geotag_changed), self);
2151 g_signal_emit_by_name(lib->map, "changed");
2152}
2153
2154static void _view_map_redo_callback(dt_action_t *action)
2155{
2156 dt_view_t *self = dt_action_view(action);
2157 dt_map_t *lib = (dt_map_t *)self->data;
2158
2159 // let current map view unchanged (avoid to center the map on collection)
2160 dt_control_signal_block_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_geotag_changed), self);
2161 dt_control_signal_block_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_collection_changed), self);
2162 dt_undo_do_redo(dt_undo_get_global(), DT_UNDO_MAP);
2163 dt_control_signal_unblock_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_collection_changed), self);
2164 dt_control_signal_unblock_by_func(dt_control_signal_get_global(), G_CALLBACK(_view_map_geotag_changed), self);
2165 g_signal_emit_by_name(lib->map, "changed");
2166}
2167*/
2168
2169static void _view_map_center_on_location(const dt_view_t *view, gdouble lon, gdouble lat, gdouble zoom)
2170{
2171 dt_map_t *lib = (dt_map_t *)view->data;
2172 osm_gps_map_set_center_and_zoom(lib->map, lat, lon, zoom);
2173}
2174
2175static void _view_map_center_on_bbox(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
2176{
2177 dt_map_t *lib = (dt_map_t *)view->data;
2178 osm_gps_map_zoom_fit_bbox(lib->map, lat1, lat2, lon1, lon2);
2179}
2180
2182{
2183 dt_map_t *lib = (dt_map_t *)view->data;
2184 const gboolean enabled = dt_conf_get_bool("plugins/map/show_map_osd");
2185 if(enabled)
2186 osm_gps_map_layer_add(OSM_GPS_MAP(lib->map), lib->osd);
2187 else
2188 osm_gps_map_layer_remove(OSM_GPS_MAP(lib->map), lib->osd);
2189
2190 g_signal_emit_by_name(lib->map, "changed");
2191}
2192
2193static void _view_map_set_map_source_g_object(const dt_view_t *view, OsmGpsMapSource_t map_source)
2194{
2195 dt_map_t *lib = (dt_map_t *)view->data;
2196
2197 GValue value = {
2198 0,
2199 };
2200 g_value_init(&value, G_TYPE_INT);
2201 g_value_set_int(&value, map_source);
2202 g_object_set_property(G_OBJECT(lib->map), "map-source", &value);
2203 g_value_unset(&value);
2204}
2205
2206static void _view_map_set_map_source(const dt_view_t *view, OsmGpsMapSource_t map_source)
2207{
2208 dt_map_t *lib = (dt_map_t *)view->data;
2209
2210 if(map_source == lib->map_source) return;
2211
2212 lib->map_source = map_source;
2213 dt_conf_set_string("plugins/map/map_source", osm_gps_map_source_get_friendly_name(map_source));
2215}
2216
2217static OsmGpsMapImage *_view_map_add_pin(const dt_view_t *view, GList *points)
2218{
2219 dt_map_t *lib = (dt_map_t *)view->data;
2221 return osm_gps_map_image_add_with_alignment(lib->map, p->lat, p->lon, lib->place_pin, 0.5, 1);
2222}
2223
2224static gboolean _view_map_remove_pin(const dt_view_t *view, OsmGpsMapImage *pin)
2225{
2226 dt_map_t *lib = (dt_map_t *)view->data;
2227 return osm_gps_map_image_remove(lib->map, pin);
2228}
2229
2230static void _track_add_point(OsmGpsMapTrack *track, OsmGpsMapPoint *point, OsmGpsMapPoint *prev_point)
2231{
2232 float lat, lon, prev_lat, prev_lon;
2233 osm_gps_map_point_get_degrees(point, &lat, &lon);
2234 osm_gps_map_point_get_degrees(prev_point, &prev_lat, &prev_lon);
2235 double d, delta;
2236 gboolean short_distance = TRUE;
2237 if(fabs(lat - prev_lat) > DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC
2238 || fabs(lon - prev_lon) > DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC)
2239 {
2240 short_distance = FALSE;
2242 prev_lat, prev_lon,
2243 &d, &delta);
2244 }
2245
2246 if(short_distance || d < DT_MINIMUM_DISTANCE_FOR_GEODESIC)
2247 {
2248 osm_gps_map_track_add_point(track, point);
2249 }
2250 else
2251 {
2252 /* the line must be splitted in order to seek the geodesic line */
2253 OsmGpsMapPoint *ith_point;
2254 double f, ith_lat, ith_lon;
2255 const int n_segments = ceil(d / DT_MINIMUM_DISTANCE_FOR_GEODESIC);
2256 gboolean first_time = TRUE;
2257 for(int i = 1; i <= n_segments; i ++)
2258 {
2259 f = (double)i / n_segments;
2260 dt_gpx_geodesic_intermediate_point(prev_lat, prev_lon,
2261 lat, lon,
2262 delta,
2263 first_time,
2264 f,
2265 &ith_lat, &ith_lon);
2266
2267 ith_point = osm_gps_map_point_new_degrees (ith_lat, ith_lon);
2268 osm_gps_map_track_add_point(track, ith_point);
2269 osm_gps_map_point_free(ith_point);
2270 first_time = FALSE;
2271 }
2272 }
2273}
2274
2275#ifdef HAVE_OSMGPSMAP_110_OR_NEWER
2276static OsmGpsMapPolygon *_view_map_add_polygon(const dt_view_t *view, GList *points)
2277{
2278 dt_map_t *lib = (dt_map_t *)view->data;
2279 OsmGpsMapPolygon *poly = osm_gps_map_polygon_new();
2280 OsmGpsMapTrack* track = osm_gps_map_track_new();
2281
2282 // angles for 1 pixel;
2283 float dlat, dlon;
2284 _view_map_angles(lib, 1, (lib->bbox.lat1 + lib->bbox.lat2) * 0.5 ,
2285 (lib->bbox.lon1 + lib->bbox.lon2) * 0.5 , &dlat, &dlon);
2286
2287 float prev_lat = 0.0;
2288 float prev_lon = 0.0;
2289 for(GList *iter = points; iter; iter = g_list_next(iter))
2290 {
2292 if((fabs(p->lat - prev_lat) > dlat) || (fabs(p->lon - prev_lon) > dlon))
2293 {
2294 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2295 osm_gps_map_track_add_point(track, point);
2296 prev_lat = p->lat;
2297 prev_lon = p->lon;
2298 }
2299 }
2300
2301 g_object_set(poly, "track", track, (gchar *)0);
2302 g_object_set(poly, "editable", FALSE, (gchar *)0);
2303 g_object_set(poly, "shaded", FALSE, (gchar *)0);
2304
2305 osm_gps_map_polygon_add(lib->map, poly);
2306
2307 return poly;
2308}
2309
2310static OsmGpsMapPolygon *_view_map_add_polygon_location(dt_map_t *lib, dt_location_draw_t *ld)
2311{
2312 OsmGpsMapPolygon *poly = osm_gps_map_polygon_new();
2313 OsmGpsMapTrack* track = osm_gps_map_track_new();
2314 g_object_set(track, "line-width" , 2.0, "alpha", 0.9, (gchar *)0);
2315
2316 // angles for 1 pixel;
2317 float dlat, dlon;
2318 _view_map_angles(lib, 1, ld->data.lat, ld->data.lon, &dlat, &dlon);
2319 int zoom;
2320 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, NULL);
2321 // zoom = 20 => mod = 21 ; zoom = 0 => mod = 1
2322 const int mod2 = zoom + 1;
2323
2324 // keep a bit of points around the bounding box
2327 const float mlon = (bbox.lon2 - bbox.lon1) * 0.5;
2328 const float mlat = (bbox.lat1 - bbox.lat2) * 0.5;
2329 bbox.lon1 = CLAMP(bbox.lon1 - mlon, -180.0, 180);
2330 bbox.lon2 = CLAMP(bbox.lon2 + mlon, -180.0, 180);
2331 bbox.lat1 = CLAMP(bbox.lat1 + mlat, -90.0, 90);
2332 bbox.lat2 = CLAMP(bbox.lat2 - mlat, -90.0, 90);
2333
2334 int i = 0;
2335 float prev_lat = 0.0;
2336 float prev_lon = 0.0;
2337 for(GList *iter = ld->data.polygons; iter; iter = g_list_next(iter), i++)
2338 {
2340 if(p->lat <= bbox.lat1 && p->lat >= bbox.lat2 &&
2341 p->lon >= bbox.lon1 && p->lon <= bbox.lon2)
2342 {
2343 if((fabs(p->lat - prev_lat) > dlat) || (fabs(p->lon - prev_lon) > dlon))
2344 {
2345 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2346 osm_gps_map_track_add_point(track, point);
2347 prev_lat = p->lat;
2348 prev_lon = p->lon;
2349 }
2350 }
2351 else if(!(i % mod2))
2352 {
2353 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2354 osm_gps_map_track_add_point(track, point);
2355 }
2356 }
2357
2358 g_object_set(poly, "track", track, (gchar *)0);
2359 g_object_set(poly, "editable", FALSE, (gchar *)0);
2360 g_object_set(poly, "shaded", FALSE, (gchar *)0);
2361
2362 osm_gps_map_polygon_add(lib->map, poly);
2363
2364 return poly;
2365}
2366
2367static gboolean _view_map_remove_polygon(const dt_view_t *view, OsmGpsMapPolygon *polygon)
2368{
2369 dt_map_t *lib = (dt_map_t *)view->data;
2370 return osm_gps_map_polygon_remove(lib->map, polygon);
2371}
2372#endif
2373
2374static OsmGpsMapTrack *_view_map_add_track(const dt_view_t *view, GList *points)
2375{
2376 dt_map_t *lib = (dt_map_t *)view->data;
2377
2378 OsmGpsMapTrack* track = osm_gps_map_track_new();
2379
2380 OsmGpsMapPoint* prev_point;
2381 gboolean first_point = TRUE;
2382 for(GList *iter = points; iter; iter = g_list_next(iter))
2383 {
2385 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2386 if(first_point)
2387 {
2388 osm_gps_map_track_add_point(track, point);
2389 }
2390 else
2391 {
2392 _track_add_point(track, point, prev_point);
2393 osm_gps_map_point_free(prev_point);
2394 }
2395 prev_point = &(*point);
2396 if(!g_list_next(iter))
2397 osm_gps_map_point_free(prev_point);
2398 first_point = FALSE;
2399 }
2400
2401 g_object_set(track, "editable", FALSE, (gchar *)0);
2402
2403 osm_gps_map_track_add(lib->map, track);
2404
2405 return track;
2406}
2407
2408static gboolean _view_map_remove_track(const dt_view_t *view, OsmGpsMapTrack *track)
2409{
2410 dt_map_t *lib = (dt_map_t *)view->data;
2411 return osm_gps_map_track_remove(lib->map, track);
2412}
2413
2414static OsmGpsMapImage *_view_map_draw_single_image(const dt_view_t *view, GList *points)
2415{
2416 dt_map_t *lib = (dt_map_t *)view->data;
2417 struct {int32_t imgid; float latitude; float longitude; int count;} *p;
2418 p = points->data;
2419 GdkPixbuf *thumb = _draw_transient_image(p->imgid, NULL, NULL, p->count, TRUE,
2421 OsmGpsMapImage *image = NULL;
2422 if(thumb)
2423 {
2424 image = osm_gps_map_image_add_with_alignment(lib->map, p->latitude, p->longitude, thumb, 0, 1);
2425 g_object_unref(thumb);
2426 }
2427 return image;
2428}
2429
2430static GObject *_view_map_add_marker(const dt_view_t *view, dt_geo_map_display_t type, GList *points)
2431{
2432 switch(type)
2433 {
2434 case MAP_DISPLAY_POINT: return G_OBJECT(_view_map_add_pin(view, points));
2435 case MAP_DISPLAY_TRACK: return G_OBJECT(_view_map_add_track(view, points));
2436#ifdef HAVE_OSMGPSMAP_110_OR_NEWER
2437 case MAP_DISPLAY_POLYGON: return G_OBJECT(_view_map_add_polygon(view, points));
2438#endif
2439 case MAP_DISPLAY_THUMB: return G_OBJECT(_view_map_draw_single_image(view, points));
2440 default: return NULL;
2441 }
2442}
2443
2444static gboolean _view_map_remove_marker(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker)
2445{
2446 dt_map_t *lib = (dt_map_t *)view->data;
2447 if(type == MAP_DISPLAY_NONE) return FALSE;
2448
2449 switch(type)
2450 {
2451 case MAP_DISPLAY_POINT: return _view_map_remove_pin(view, OSM_GPS_MAP_IMAGE(marker));
2452 case MAP_DISPLAY_TRACK: return _view_map_remove_track(view, OSM_GPS_MAP_TRACK(marker));
2453#ifdef HAVE_OSMGPSMAP_110_OR_NEWER
2454 case MAP_DISPLAY_POLYGON: return _view_map_remove_polygon(view, OSM_GPS_MAP_POLYGON(marker));
2455#endif
2456 case MAP_DISPLAY_THUMB: return osm_gps_map_image_remove(lib->map, OSM_GPS_MAP_IMAGE(marker));
2457 default: return FALSE;
2458 }
2459}
2460
2461static void _view_map_add_location(const dt_view_t *view, dt_map_location_data_t *g, const guint locid)
2462{
2463 dt_map_t *lib = (dt_map_t *)view->data;
2464 dt_location_draw_t loc_main;
2465 loc_main.id = locid;
2466 if(g)
2467 {
2468 if(g->delta1 != 0.0 && g->delta2 != 0.0)
2469 {
2470 // existing location
2471 memcpy(&loc_main.data, g, sizeof(dt_map_location_data_t));
2472 const double max_lon = CLAMP(g->lon + g->delta1, -180, 180);
2473 const double min_lon = CLAMP(g->lon - g->delta1, -180, 180);
2474 const double max_lat = CLAMP(g->lat + g->delta2, -90, 90);
2475 const double min_lat = CLAMP(g->lat - g->delta2, -90, 90);
2476 if(max_lon > min_lon && max_lat > min_lat)
2477 {
2478 // only if new box not imcluded in the current map box
2479 if(g->lon < lib->bbox.lon1 || g->lon > lib->bbox.lon2 ||
2480 g->lat > lib->bbox.lat1 || g->lat < lib->bbox.lat2)
2481 _view_map_center_on_bbox(view, min_lon, max_lat, max_lon, min_lat);
2482 _view_map_draw_main_location(lib, &loc_main);
2483 }
2484 }
2485 else
2486 {
2487 // this is a new location
2488 loc_main.data.shape = g->shape;
2489 if(g->shape == MAP_LOCATION_SHAPE_POLYGONS)
2490 {
2492 loc_main.data.polygons = dt_map_location_convert_polygons(g->polygons, &bbox, &loc_main.data.plg_pts);
2494 loc_main.data.lon = (bbox.lon1 + bbox.lon2) * 0.5;
2495 loc_main.data.lat = (bbox.lat1 + bbox.lat2) * 0.5;
2496 loc_main.data.ratio = 1;
2497 loc_main.data.delta1 = (bbox.lon2 - bbox.lon1) * 0.5;
2498 loc_main.data.delta2 = (bbox.lat1 - bbox.lat2) * 0.5;
2499 }
2500 else
2501 {
2502 // create the location on the center of the map
2503 float lon, lat;
2504 g_object_get(G_OBJECT(lib->map), "latitude", &lat, "longitude", &lon, NULL);
2505 loc_main.data.lon = lon, loc_main.data.lat = lat;
2506 // get a radius angle equivalent to thumb dimension to start with for delta1
2507 float dlat, dlon;
2508 _view_map_thumb_angles(lib, loc_main.data.lat, loc_main.data.lon, &dlat, &dlon);
2509 loc_main.data.ratio = _view_map_get_angles_ratio(lib, loc_main.data.lat, loc_main.data.lon);
2510 loc_main.data.delta1 = dlon;
2511 loc_main.data.delta2 = dlon / loc_main.data.ratio;
2512 }
2513 _view_map_draw_main_location(lib, &loc_main);
2515 }
2516 }
2517}
2518
2519static void _view_map_location_action(const dt_view_t *view, const int action)
2520{
2521 dt_map_t *lib = (dt_map_t *)view->data;
2522 if(action == MAP_LOCATION_ACTION_REMOVE && lib->loc.main.id)
2523 {
2524 GList *other = _others_location(lib->loc.others, lib->loc.main.id);
2525 if(other)
2527 // remove the main location
2529 lib->loc.main.id = 0;
2530 }
2532}
2533
2534
2535static void _view_map_check_preference_changed(gpointer instance, gpointer user_data)
2536{
2537 dt_view_t *view = (dt_view_t *)user_data;
2538 dt_map_t *lib = (dt_map_t *)view->data;
2539
2540 if(_view_map_prefs_changed(lib)) g_signal_emit_by_name(lib->map, "changed");
2541}
2542
2543static void _view_map_collection_changed(gpointer instance, dt_collection_change_t query_change,
2544 dt_collection_properties_t changed_property, gpointer imgs, int next,
2545 gpointer user_data)
2546{
2547 dt_view_t *self = (dt_view_t *)user_data;
2548 dt_map_t *lib = (dt_map_t *)self->data;
2549 // avoid to centre the map on collection while a location is active
2550 if(dt_view_manager_get_global()->proxy.map.view && !lib->loc.main.id)
2551 {
2553 }
2554
2555 if(dt_conf_get_bool("plugins/map/filter_images_drawn"))
2556 {
2557 // only redraw when map mode is currently active, otherwise enter() does the magic
2558 if(dt_view_manager_get_global()->proxy.map.view) g_signal_emit_by_name(lib->map, "changed");
2559 }
2560}
2561
2562static void _view_map_selection_changed(gpointer instance, gpointer user_data)
2563{
2564 dt_view_t *self = (dt_view_t *)user_data;
2565 dt_map_t *lib = (dt_map_t *)self->data;
2566
2567 /* This handler stays connected for the app's whole lifetime, not just while map is the active
2568 view (see init()). active_images is a global, shared "currently displayed/edited image"
2569 marker other views (darkroom, Studio Capture) rely on: resyncing it to the selection here
2570 regardless of which view is active stomps on whatever they just set, e.g. wiping the
2571 filmstrip's highlight the instant any other view calls dt_selection_select_single(), since
2572 its internal clear+select briefly raises this signal with an empty selection.
2573 Only touch it when map is actually the active view, exactly like the redraw below. */
2575 {
2577 GList *selection = dt_selection_get_list(dt_selection_get_global());
2578 if(selection) dt_view_active_images_set(selection, FALSE);
2579
2580 const int32_t imgid = dt_selection_get_first_id(dt_selection_get_global());
2583 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
2584 g_signal_emit_by_name(lib->map, "changed");
2585 }
2586}
2587
2588static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer user_data)
2589{
2590 // if locid <> NULL this event doesn't concern geotag but location
2591 if(!locid)
2592 {
2593 dt_view_t *self = (dt_view_t *)user_data;
2594 dt_map_t *lib = (dt_map_t *)self->data;
2595 if(dt_view_manager_get_global()->proxy.map.view) g_signal_emit_by_name(lib->map, "changed");
2596 }
2597}
2598
2599static void _view_map_center_on_image(dt_view_t *self, const int32_t imgid)
2600{
2601 if(imgid)
2602 {
2603 const dt_map_t *lib = (dt_map_t *)self->data;
2604 dt_image_geoloc_t geoloc;
2605 dt_image_get_location(imgid, &geoloc);
2606
2607 if(!isnan(geoloc.longitude) && !isnan(geoloc.latitude))
2608 {
2609 int zoom;
2610 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, NULL);
2611 _view_map_center_on_location(self, geoloc.longitude, geoloc.latitude, zoom);
2612 }
2613 }
2614}
2615
2617{
2618 const dt_map_t *lib = (dt_map_t *)self->data;
2619
2620 dt_image_geo_bounds_t bounds = { 0 };
2622
2623 double max_longitude = bounds.max_longitude;
2624 double max_latitude = bounds.max_latitude;
2625 double min_longitude = bounds.min_longitude;
2626 double min_latitude = bounds.min_latitude;
2627 int count = bounds.count;
2628
2629 if(count>0)
2630 {
2631 max_longitude = CLAMP(max_longitude, -180, 180);
2632 min_longitude = CLAMP(min_longitude, -180, 180);
2633 max_latitude = CLAMP(max_latitude, -90, 90);
2634 min_latitude = CLAMP(min_latitude, -90, 90);
2635
2636 _view_map_center_on_bbox(self, min_longitude, min_latitude, max_longitude, max_latitude);
2637
2638 // Now the zoom is set we can use the thumb angle to give some room
2639 max_longitude = CLAMP(max_longitude + 1.0 * lib->thumb_lon_angle, -180, 180);
2640 min_longitude = CLAMP(min_longitude - 0.2 * lib->thumb_lon_angle, -180, 180);
2641 max_latitude = CLAMP(max_latitude + 1.0 * lib->thumb_lat_angle, -90, 90);
2642 min_latitude = CLAMP(min_latitude - 0.2 * lib->thumb_lat_angle, -90, 90);
2643
2644 _view_map_center_on_bbox(self, min_longitude, min_latitude, max_longitude, max_latitude);
2645
2646 return TRUE;
2647 }
2648 else
2649 return FALSE;
2650}
2651
2652static void _view_map_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
2653{
2654 dt_view_t *self = (dt_view_t *)user_data;
2656 g_idle_add((GSourceFunc)dt_thumbtable_scroll_to_selection, dt_gui_get_ui()->thumbtable_filmstrip);
2657 _view_map_center_on_image(self, imgid);
2658}
2659
2660static void _view_map_filmstrip_drag_begin_callback(gpointer instance, int32_t imgid, gpointer user_data)
2661{
2665}
2666
2667static void _drag_and_drop_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2668 GtkSelectionData *selection_data, guint target_type, guint time,
2669 gpointer data)
2670{
2671 dt_view_t *self = (dt_view_t *)data;
2672 dt_map_t *lib = (dt_map_t *)self->data;
2673 gboolean success = FALSE;
2674 if(!IS_NULL_PTR(selection_data) && target_type == DND_TARGET_IMGID)
2675 {
2676 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2677 if(imgs_nb)
2678 {
2679 uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2680 if(imgs_nb == 1 && imgt[0] == -1)
2681 {
2682 // move of location
2683 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
2684 osm_gps_map_convert_screen_to_geographic(lib->map, x, y, pt);
2685 float lat, lon;
2686 osm_gps_map_point_get_degrees(pt, &lat, &lon);
2687 lib->loc.main.data.lat = lat, lib->loc.main.data.lon = lon;
2688 const float prev_ratio = lib->loc.main.data.ratio;
2690 lib->loc.main.data.lon);
2691 lib->loc.main.data.delta2 = lib->loc.main.data.delta2 * prev_ratio / lib->loc.main.data.ratio;
2692 osm_gps_map_point_free(pt);
2696 success = TRUE;
2697 }
2698 else
2699 {
2700 GList *imgs = NULL;
2701 for(int i = 0; i < imgs_nb; i++)
2702 {
2703 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2704 }
2705 float longitude, latitude;
2706 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
2707 osm_gps_map_convert_screen_to_geographic(lib->map, x - lib->start_drag_offset_x,
2708 y - lib->start_drag_offset_y, pt);
2709 osm_gps_map_point_get_degrees(pt, &latitude, &longitude);
2710 osm_gps_map_point_free(pt);
2711 // TODO redraw the image group
2712 // it seems that at this time osm_gps_map doesn't answer before dt_image_set_locations(). Locked in some way ?
2713 const dt_image_geoloc_t geoloc = { longitude, latitude, NAN };
2715 dt_image_set_locations(imgs, &geoloc, TRUE);
2718 g_signal_emit_by_name(lib->map, "changed");
2719 success = TRUE;
2720 }
2721 }
2722 }
2723 gtk_drag_finish(context, success, FALSE, time);
2724}
2725
2726static void _view_map_dnd_get_callback(GtkWidget *widget, GdkDragContext *context,
2727 GtkSelectionData *selection_data, guint target_type, guint time,
2728 dt_view_t *self)
2729{
2730 dt_map_t *lib = (dt_map_t *)self->data;
2731 g_assert(!IS_NULL_PTR(selection_data));
2732 switch(target_type)
2733 {
2734 case DND_TARGET_IMGID:
2735 {
2736 if(lib->selected_images)
2737 {
2738 // drag & drop of images
2739 const guint imgs_nb = g_list_length(lib->selected_images);
2740 if(imgs_nb)
2741 {
2742 uint32_t *imgs = malloc(sizeof(uint32_t) * imgs_nb);
2743 int i = 0;
2744 for(GList *l = lib->selected_images; l; l = g_list_next(l))
2745 {
2746 imgs[i++] = GPOINTER_TO_INT(l->data);
2747 }
2748 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2749 _DWORD, (guchar *)imgs, imgs_nb * sizeof(uint32_t));
2750 dt_free(imgs);
2751 }
2752 }
2753 else if(lib->loc.main.id > 0)
2754 {
2755 // move of location
2756 uint32_t *imgs = malloc(sizeof(uint32_t));
2757 imgs[0] = -1;
2758 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2759 _DWORD, (guchar *)imgs, sizeof(uint32_t));
2760 dt_free(imgs);
2761 }
2762 }
2763 break;
2764 default: // return the location of the file as a last resort
2765 case DND_TARGET_URI:
2766 {
2767 if(lib->selected_images)
2768 {
2769 const int32_t imgid = GPOINTER_TO_INT(lib->selected_images->data);
2770 gchar pathname[DT_PATH_MAX] = { 0 };
2771 gboolean from_cache = TRUE;
2772 dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
2773 gchar *uri = g_strdup_printf("file://%s", pathname); // TODO: should we add the host?
2774 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data), _BYTE,
2775 (guchar *)uri, strlen(uri));
2776 dt_free(uri);
2777 }
2778 break;
2779 }
2780 }
2781}
2782
2783static gboolean _view_map_dnd_failed_callback(GtkWidget *widget, GdkDragContext *drag_context,
2784 GtkDragResult result, dt_view_t *self)
2785{
2786 dt_map_t *lib = (dt_map_t *)self->data;
2787 g_signal_emit_by_name(lib->map, "changed");
2788
2789 return TRUE;
2790}
2791
2792static gboolean _view_map_prefs_changed(dt_map_t *lib)
2793{
2794 gboolean prefs_changed = FALSE;
2795
2796 lib->max_images_drawn = dt_conf_get_int("plugins/map/max_images_drawn");
2797 if(lib->max_images_drawn == 0) lib->max_images_drawn = 100;
2798
2799 gboolean filter_images_drawn = dt_conf_get_bool("plugins/map/filter_images_drawn");
2800 if(lib->filter_images_drawn != filter_images_drawn) prefs_changed = TRUE;
2801
2802 const char *thumbnail = dt_conf_get_string_const("plugins/map/images_thumbnail");
2803 lib->thumbnail = !g_strcmp0(thumbnail, "thumbnail") ? DT_MAP_THUMB_THUMB :
2804 !g_strcmp0(thumbnail, "count") ? DT_MAP_THUMB_COUNT : DT_MAP_THUMB_NONE;
2805
2806 return prefs_changed;
2807}
2808
2809// starting point taken from https://github.com/gyaikhom/dbscan
2810// Copyright 2015 Gagarine Yaikhom (MIT License)
2811
2812typedef struct epsilon_neighbours_t
2813{
2814 unsigned int num_members;
2815 unsigned int index[];
2816} epsilon_neighbours_t;
2817
2818typedef struct dt_dbscan_t
2819{
2820 dt_geo_position_t *points;
2821 unsigned int num_points;
2822 double epsilon;
2823 unsigned int minpts;
2824 epsilon_neighbours_t *seeds;
2825 epsilon_neighbours_t *spreads;
2826 unsigned int index;
2827 unsigned int cluster_id;
2828} dt_dbscan_t;
2829
2830static dt_dbscan_t db;
2831
2832static void _get_epsilon_neighbours(epsilon_neighbours_t *en, unsigned int index)
2833{
2834 // points are ordered by longitude
2835 // limit the exploration to epsilon east and west
2836 // west
2837 for(int i = index; i < db.num_points; ++i)
2838 {
2839 if(i == index || db.points[i].cluster_id >= 0)
2840 continue;
2841 if((db.points[i].x - db.points[index].x) > db.epsilon)
2842 break;
2843 if(fabs(db.points[i].y - db.points[index].y) > db.epsilon)
2844 continue;
2845 else
2846 {
2847 en->index[en->num_members] = i;
2848 en->num_members++;
2849 }
2850 }
2851 // east
2852 for(int i = index; i >= 0; --i)
2853 {
2854 if(i == (int)index || db.points[i].cluster_id >= 0)
2855 continue;
2856 if((db.points[index].x - db.points[i].x) > db.epsilon)
2857 break;
2858 if(fabs(db.points[index].y - db.points[i].y) > db.epsilon)
2859 continue;
2860 else
2861 {
2862 en->index[en->num_members] = i;
2863 en->num_members++;
2864 }
2865 }
2866}
2867
2868static void _dbscan_spread(unsigned int index)
2869{
2870 db.spreads->num_members = 0;
2871 _get_epsilon_neighbours(db.spreads, index);
2872
2873 for(unsigned int i = 0; i < db.spreads->num_members; i++)
2874 {
2875 dt_geo_position_t *d = &db.points[db.spreads->index[i]];
2876 if(d->cluster_id == NOISE || d->cluster_id == UNCLASSIFIED)
2877 {
2878 db.seeds->index[db.seeds->num_members] = db.spreads->index[i];
2879 db.seeds->num_members++;
2880 d->cluster_id = db.cluster_id;
2881 }
2882 }
2883}
2884
2885static int _dbscan_expand(unsigned int index)
2886{
2887 int return_value = NOT_CORE_POINT;
2888 db.seeds->num_members = 0;
2889 _get_epsilon_neighbours(db.seeds, index);
2890
2891 if (db.seeds->num_members < db.minpts)
2892 db.points[index].cluster_id = NOISE;
2893 else
2894 {
2895 db.points[index].cluster_id = db.cluster_id;
2896 for(int i = 0; i < db.seeds->num_members; i++)
2897 {
2898 db.points[db.seeds->index[i]].cluster_id = db.cluster_id;
2899 }
2900
2901 for(int i = 0; i < db.seeds->num_members; i++)
2902 {
2903 _dbscan_spread(db.seeds->index[i]);
2904 }
2905 return_value = CORE_POINT;
2906 }
2907 return return_value;
2908}
2909
2910static void _dbscan(dt_geo_position_t *points, unsigned int num_points,
2911 double epsilon, unsigned int minpts)
2912{
2913 db.points = points;
2914 db.num_points = num_points;
2915 db.epsilon = epsilon;
2916 // remove the pivot from target
2917 db.minpts = minpts > 1 ? minpts - 1 : minpts;
2918 db.cluster_id = 0;
2919 db.seeds = (epsilon_neighbours_t *)malloc(sizeof(db.seeds->num_members)
2920 + num_points * sizeof(db.seeds->index[0]));
2921 db.spreads = (epsilon_neighbours_t *)malloc(sizeof(db.spreads->num_members)
2922 + num_points * sizeof(db.spreads->index[0]));
2923
2924 if(db.seeds && db.spreads)
2925 {
2926 for(unsigned int i = 0; i < db.num_points; ++i)
2927 {
2928 if(db.points[i].cluster_id == UNCLASSIFIED)
2929 {
2930 if(_dbscan_expand(i) == CORE_POINT)
2931 {
2932 ++db.cluster_id;
2933 }
2934 }
2935 }
2936 dt_free(db.seeds);
2937 dt_free(db.spreads);
2938 }
2939}
2940
2941// clang-format off
2942// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2943// vim: shiftwidth=2 expandtab tabstop=2 cindent
2944// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2945// clang-format on
void dt_accels_connect_accels(dt_accels_t *accels)
Actually enable accelerators after having loaded user config.
void dt_accels_connect_active_group(dt_accels_t *accels, const gchar *group)
Connect the contextual active accels group to the window. Views can declare their own set of contextu...
void dt_accels_disconnect_active_group(dt_accels_t *accels)
Disconnect the contextual active accels group from the window.
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GList * dt_act_on_get_images()
Definition act_on.c:38
struct dt_accels_t * dt_gui_get_accels(void)
struct dt_ui_t * dt_gui_get_ui(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
dt_collection_properties_t
Definition collection.h:120
dt_collection_change_t
Definition collection.h:160
static const float x
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float delta
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_image_get_location(const int32_t imgid, dt_image_geoloc_t *geoloc)
void dt_image_set_locations(const GList *imgs, const dt_image_geoloc_t *geoloc, const gboolean undo_on)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:701
void dt_toast_log(const char *msg,...)
Definition control.c:871
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:1020
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
gboolean enabled
–doc was passed on the command line
GtkWidget * view
the tree view
static const GtkTargetEntry target_list_all[]
#define _DWORD
static const GtkTargetEntry target_list_internal[]
@ DND_TARGET_URI
@ DND_TARGET_IMGID
static const guint n_targets_internal
#define _BYTE
static const guint n_targets_all
static void dt_draw_cairo_to_gdk_pixbuf(uint8_t *data, unsigned int width, unsigned int height)
Definition draw.h:479
const int res
Definition dtpthread.h:351
dt_geo_map_display_t
Definition geo.h:26
@ MAP_DISPLAY_POLYGON
Definition geo.h:30
@ MAP_DISPLAY_THUMB
Definition geo.h:31
@ MAP_DISPLAY_NONE
Definition geo.h:27
@ MAP_DISPLAY_POINT
Definition geo.h:28
@ MAP_DISPLAY_TRACK
Definition geo.h:29
void dt_gpx_geodesic_intermediate_point(const double lat1, const double lon1, const double lat2, const double lon2, const double delta, const gboolean first_time, double f, double *lat, double *lon)
Definition gpx.c:494
void dt_gpx_geodesic_distance(double lat1, double lon1, double lat2, double lon2, double *d, double *delta)
Definition gpx.c:473
#define DT_MINIMUM_DISTANCE_FOR_GEODESIC
Definition gpx.h:33
#define DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC
Definition gpx.h:34
#define UNKNOWN_IMAGE
Definition image.h:78
gboolean dt_image_repository_get_collected_geo_bounds(dt_image_geo_bounds_t *bounds)
Bounding box of every collected image carrying coordinates.
dt_image_geo_point_t * dt_image_repository_get_collected_geo_points(const double lon1, const double lon2, const double lat1, const double lat2, int *count)
Collected images inside the box, ordered by longitude ascending.
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
_lib_location_type_t type
Definition location.c:1
dt_map_box_t bbox
Definition location.c:4
float lat
Definition location.c:3
float lon
Definition location.c:2
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static GObject * _view_map_add_marker(const dt_view_t *view, dt_geo_map_display_t type, GList *points)
Definition map.c:2430
dt_map_thumb_t
Definition map.c:141
@ DT_MAP_THUMB_COUNT
Definition map.c:143
@ DT_MAP_THUMB_THUMB
Definition map.c:142
@ DT_MAP_THUMB_NONE
Definition map.c:144
void init(dt_view_t *self)
Definition map.c:577
static void _view_map_filmstrip_drag_begin_callback(gpointer instance, int32_t imgid, gpointer user_data)
Definition map.c:2660
static void _dbscan(dt_geo_position_t *points, unsigned int num_points, double epsilon, unsigned int minpts)
static gboolean _display_next_image(dt_view_t *self, dt_map_image_t *entry, const gboolean next)
Definition map.c:1546
static void _view_map_check_preference_changed(gpointer instance, gpointer user_data)
Definition map.c:2535
static GdkPixbuf * _draw_image(dt_map_image_t *entry, int *width, int *height, const int group_count, const gboolean group_same_loc, const uint32_t frame, const int thumbnail, dt_view_t *self)
Definition map.c:1043
#define TILESIZE
Definition map.c:268
static gboolean _view_map_dnd_failed_callback(GtkWidget *widget, GdkDragContext *drag_context, GtkDragResult result, dt_view_t *self)
static void _view_map_get_bounding_box(dt_map_t *lib, dt_map_box_t *bbox)
Definition map.c:1259
#define NOISE
Definition map.c:126
GList * _others_location(GList *others, const int locid)
Definition map.c:886
static const uint32_t pin_outer_color
Definition map.c:136
static void _view_map_center_on_image(dt_view_t *self, const int32_t imgid)
Definition map.c:2599
static void _view_map_show_osd(const dt_view_t *view)
Definition map.c:2181
void leave(dt_view_t *self)
Definition map.c:2104
static void _view_map_collection_changed(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, gpointer user_data)
Definition map.c:2543
static void _view_map_set_map_source(const dt_view_t *view, OsmGpsMapSource_t map_source)
Definition map.c:2206
static GdkPixbuf * _draw_ellipse(const float dlongitude, const float dlatitude, const gboolean main)
Definition map.c:430
static gboolean _view_map_changed_callback_wait(gpointer user_data)
Definition map.c:1429
static void _view_map_add_location(const dt_view_t *view, dt_map_location_data_t *g, const guint locid)
Definition map.c:2461
dt_location_draw_t * _others_location_draw(dt_map_t *lib, const int locid)
Definition map.c:875
static const uint32_t thumb_frame_color
Definition map.c:133
static gboolean _view_map_draw_image(dt_map_image_t *entry, const int thumbnail, dt_view_t *self)
Definition map.c:1209
static void _view_map_delete_other_location(dt_map_t *lib, GList *other)
Definition map.c:941
static void _view_map_draw_other_locations(dt_map_t *lib, dt_map_box_t *bbox)
Definition map.c:984
void cleanup(dt_view_t *self)
Definition map.c:669
static gboolean _view_map_remove_marker(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker)
Definition map.c:2444
static int latlon2zoom(int pix_height, int pix_width, float lat1, float lat2, float lon1, float lon2)
Definition map.c:275
static gboolean _view_map_drag_motion_callback(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time, dt_view_t *self)
Definition map.c:1646
static void _view_map_signal_change_wait(dt_view_t *self, const int time_out)
Definition map.c:760
static gboolean _view_map_remove_track(const dt_view_t *view, OsmGpsMapTrack *track)
Definition map.c:2408
static dt_map_image_t * _view_map_get_entry_at_pos(dt_view_t *self, const double x, const double y)
Definition map.c:1465
#define LOG2(x)
Definition map.c:265
static OsmGpsMapImage * _view_map_draw_single_image(const dt_view_t *view, GList *points)
Definition map.c:2414
#define CORE_POINT
Definition map.c:128
static gboolean _view_map_button_press_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
Definition map.c:1864
static OsmGpsMapImage * _view_map_draw_location(dt_map_t *lib, dt_location_draw_t *ld, const gboolean main)
Definition map.c:897
static GdkPixbuf * _draw_transient_image(const int32_t imgid, int *width, int *height, const int group_count, const gboolean group_same_loc, const uint32_t frame, const int thumbnail, dt_view_t *self)
Definition map.c:1129
static gboolean _view_map_motion_notify_callback(GtkWidget *w, GdkEventMotion *e, dt_view_t *self)
Definition map.c:1659
static void _view_map_angles(dt_map_t *lib, const gint pixels, const float lat, const float lon, float *dlat_min, float *dlon_min)
Definition map.c:786
static const int thumb_size
Definition map.c:131
static GdkPixbuf * _init_image_pin()
Definition map.c:351
static GdkPixbuf * _view_map_images_count(const int nb_images, const gboolean same_loc, double *count_width, double *count_height)
Definition map.c:315
static const int place_pin_size
Definition map.c:131
#define R
static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer user_data)
Definition map.c:2588
static gboolean _view_map_prefs_changed(dt_map_t *lib)
static gboolean _view_map_remove_polygon(const dt_view_t *view, OsmGpsMapPolygon *polygon)
int key_pressed(dt_view_t *self, GdkEventKey *event)
Definition map.c:230
static const uint32_t pin_line_color
Definition map.c:138
static void _track_add_point(OsmGpsMapTrack *track, OsmGpsMapPoint *point, OsmGpsMapPoint *prev_point)
Definition map.c:2230
static void _view_map_location_action(const dt_view_t *view, const int action)
Definition map.c:2519
static void _view_map_update_location_geotag(dt_view_t *self)
Definition map.c:1031
static const int cross_size
Definition map.c:132
static void _toast_log_lat_lon(const float lat, const float lon)
Definition map.c:306
static OsmGpsMapTrack * _view_map_add_track(const dt_view_t *view, GList *points)
Definition map.c:2374
void configure(dt_view_t *self, int wd, int ht)
Definition map.c:719
#define UNCLASSIFIED
Definition map.c:125
static gboolean _view_map_display_selected(gpointer user_data)
Definition map.c:1978
static void _view_map_center_on_bbox(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
Definition map.c:2175
static void _view_map_drag_set_icon(const dt_view_t *self, GdkDragContext *context, const int32_t imgid, const int count)
Definition map.c:1628
static void _view_changed(gpointer instance, dt_view_t *old_view, dt_view_t *new_view, dt_view_t *self)
Definition map.c:568
static void _view_map_draw_main_location(dt_map_t *lib, dt_location_draw_t *ld)
Definition map.c:952
static void osm_gps_map_zoom_fit_bbox(OsmGpsMap *map, float latitude1, float latitude2, float longitude1, float longitude2)
Definition map.c:294
static GdkPixbuf * _draw_rectangle(const float dlongitude, const float dlatitude, const gboolean main)
Definition map.c:498
static double _view_map_get_angles_ratio(const dt_map_t *lib, const float lat0, const float lon0)
Definition map.c:835
static void _view_map_changed_callback_delayed(gpointer user_data)
Definition map.c:1274
static const int thumb_border
Definition map.c:131
static float _view_map_angles_to_pixels(const dt_map_t *lib, const float lat0, const float lon0, const float angle)
Definition map.c:821
static const int image_pin_size
Definition map.c:131
static void _view_map_set_map_source_g_object(const dt_view_t *view, OsmGpsMapSource_t map_source)
Definition map.c:2193
static void _view_map_thumb_angles(dt_map_t *lib, const float lat, const float lon, float *dlat_min, float *dlon_min)
Definition map.c:805
static void _view_map_signal_change_raise(gpointer user_data)
Definition map.c:732
void enter(dt_view_t *self)
Definition map.c:2041
static gboolean _view_map_remove_pin(const dt_view_t *view, OsmGpsMapImage *pin)
Definition map.c:2224
static void _view_map_remove_location(dt_map_t *lib, dt_location_draw_t *ld)
Definition map.c:929
static gboolean _view_map_signal_change_delayed(gpointer user_data)
Definition map.c:744
static float deg2rad(float deg)
Definition map.c:270
static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self)
Definition map.c:1447
static void _drag_and_drop_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer data)
Definition map.c:2667
void expose(dt_view_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition map.c:553
static OsmGpsMapImage * _view_map_add_pin(const dt_view_t *view, GList *points)
Definition map.c:2217
static void _view_map_selection_changed(gpointer instance, gpointer user_data)
Definition map.c:2562
gint _find_image_in_images(gconstpointer a, gconstpointer b)
Definition map.c:1540
static void _free_map_image(gpointer data)
Definition map.c:222
static gboolean _zoom_and_center(const gint x, const gint y, const int direction, dt_view_t *self)
Definition map.c:1762
static gboolean _view_map_draw_images(gpointer user_data)
Definition map.c:1240
static GdkPixbuf * _draw_location(dt_map_t *lib, int *width, int *height, dt_map_location_data_t *data, const gboolean main)
Definition map.c:852
static const int max_size
Definition map.c:132
static OsmGpsMapPolygon * _view_map_add_polygon_location(dt_map_t *lib, dt_location_draw_t *ld)
static gboolean _view_map_center_on_image_list(dt_view_t *self)
Definition map.c:2616
static gboolean _view_map_redraw(gpointer user_data)
Definition map.c:778
static void _view_map_filmstrip_activate_callback(gpointer instance, int32_t imgid, gpointer user_data)
Definition map.c:2652
static int first_times
Definition map.c:1445
static gboolean _view_map_scroll_event(GtkWidget *w, GdkEventScroll *event, dt_view_t *self)
Definition map.c:1800
static GdkPixbuf * _init_place_pin()
Definition map.c:379
static gboolean _view_map_button_release_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
Definition map.c:1968
int try_enter(dt_view_t *self)
Definition map.c:724
static const uint32_t pin_inner_color
Definition map.c:137
static void _view_map_dnd_get_callback(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint target_type, guint time, dt_view_t *self)
Definition map.c:2726
static const uint32_t thumb_frame_gpx_color
Definition map.c:135
static GList * _view_map_get_imgs_at_pos(dt_view_t *self, const float x, const float y, int *offset_x, int *offset_y, const gboolean first_on)
Definition map.c:1489
#define NOT_CORE_POINT
Definition map.c:129
static void _view_map_center_on_location(const dt_view_t *view, gdouble lon, gdouble lat, gdouble zoom)
Definition map.c:2169
static const uint32_t thumb_frame_sel_color
Definition map.c:134
@ MAP_LOCATION_ACTION_REMOVE
@ MAP_LOCATION_SHAPE_POLYGONS
@ MAP_LOCATION_SHAPE_RECTANGLE
@ MAP_LOCATION_SHAPE_ELLIPSE
#define M_PI
Definition math.h:47
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
gboolean dt_map_location_update_images(dt_location_draw_t *ld)
GList * dt_map_location_convert_polygons(void *polygons, dt_map_box_t *bbox, int *nb_pts)
void dt_map_location_set_data(const guint locid, const dt_map_location_data_t *g)
void dt_map_location_get_polygons(dt_location_draw_t *ld)
gboolean dt_map_location_included(const float lon, const float lat, dt_map_location_data_t *g)
void dt_map_location_free_polygons(dt_location_draw_t *ld)
GList * dt_map_location_get_locations_on_map(const dt_map_box_t *const bbox)
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const char * name
Definition pdf.h:90
int main()
Definition prova.c:47
int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
Definition selection.c:103
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:185
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:297
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:512
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:506
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:139
@ DT_SIGNAL_PREFERENCES_CHANGE
This signal is raised after preferences have been changed no parameters no return.
Definition signal.h:276
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:133
@ DT_SIGNAL_VIEWMANAGER_FILMSTRIP_ACTIVATE
This signal is raised when a thumb is single-clicked in the filmstrip. Views that want filmstrip clic...
Definition signal.h:106
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:130
@ DT_SIGNAL_VIEWMANAGER_FILMSTRIP_DRAG_BEGIN
This signal is raised when a drag starts from the filmstrip. Views that need filmstrip drags to commi...
Definition signal.h:114
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:125
@ DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:84
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
int cluster_id
Definition map.c:72
int32_t imgid
Definition map.c:73
double x
Definition map.c:71
The extent of the collection's geotagged images.
One geotagged image, in degrees as stored.
double latitude
Definition image.h:332
double longitude
Definition image.h:332
dt_map_location_data_t data
float lon1
Definition geo.h:41
float lat2
Definition geo.h:44
float lat1
Definition geo.h:42
float lon2
Definition geo.h:43
double latitude
Definition map.c:79
int group
Definition map.c:81
double longitude
Definition map.c:80
dt_view_image_surface_fetcher_t fetcher
Definition map.c:89
cairo_surface_t * surface
Definition map.c:88
gboolean group_same_loc
Definition map.c:83
gboolean selected_in_group
Definition map.c:84
OsmGpsMapImage * image
Definition map.c:85
int group_count
Definition map.c:82
gint width
Definition map.c:86
int thumbnail
Definition map.c:87
int32_t imgid
Definition map.c:78
gint height
Definition map.c:86
Definition map.c:93
gboolean drag
Definition map.c:119
OsmGpsMapLayer * osd
Definition map.c:97
int start_drag_y
Definition map.c:105
dt_map_box_t bbox
Definition map.c:111
struct dt_map_t::@66 loc
OsmGpsMapSource_t map_source
Definition map.c:96
int nb_points
Definition map.c:100
gboolean drop_filmstrip_activated
Definition map.c:108
int start_drag_offset_y
Definition map.c:106
int max_images_drawn
Definition map.c:110
dt_map_image_t * last_hovered_entry
Definition map.c:115
GList * others
Definition map.c:121
float thumb_lat_angle
Definition map.c:107
GdkPixbuf * image_pin
Definition map.c:101
float thumb_lon_angle
Definition map.c:107
int timeout_event_source
Definition map.c:113
gboolean entering
Definition map.c:94
int time_out
Definition map.c:112
dt_location_draw_t main
Definition map.c:118
gboolean filter_images_drawn
Definition map.c:109
GList * incoming_selection
Definition map.c:102
GList * selected_images
Definition map.c:103
OsmGpsMap * map
Definition map.c:95
dt_geo_position_t * points
Definition map.c:99
int start_drag_offset_x
Definition map.c:106
int start_drag_x
Definition map.c:105
GSList * images
Definition map.c:98
GdkPixbuf * place_pin
Definition map.c:101
gboolean start_drag
Definition map.c:104
int thumbnail
Definition map.c:114
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:119
GObject *(* add_marker)(const dt_view_t *view, dt_geo_map_display_t type, GList *points)
Definition view.h:251
gboolean(* redraw)(gpointer user_data)
Definition view.h:255
void(* center_on_location)(const dt_view_t *view, gdouble lon, gdouble lat, double zoom)
Definition view.h:247
gboolean(* remove_marker)(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker)
Definition view.h:252
void(* add_location)(const dt_view_t *view, dt_map_location_data_t *p, const guint posid)
Definition view.h:253
void(* location_action)(const dt_view_t *view, const int action)
Definition view.h:254
void(* set_map_source)(const dt_view_t *view, OsmGpsMapSource_t map_source)
Definition view.h:250
struct dt_view_t * view
Definition view.h:237
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@64 map
void(* show_osd)(const dt_view_t *view)
Definition view.h:249
void(* center_on_bbox)(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
Definition view.h:248
GModule *void * data
Definition view.h:159
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
int dt_thumbtable_scroll_to_selection(dt_thumbtable_t *table)
Scroll to show selected content.
Definition thumbtable.c:457
void dt_thumbtable_update_parent(dt_thumbtable_t *table)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:77
static void dt_thumbtable_show(dt_thumbtable_t *table)
Show the thumbnail table widget.
Definition thumbtable.h:395
static void dt_thumbtable_hide(dt_thumbtable_t *table)
Hide the thumbnail table widget.
Definition thumbtable.h:410
void dt_show_times(const dt_times_t *start, const char *prefix)
Definition darktable.c:2138
static void dt_get_times(dt_times_t *t)
Definition times.h:50
gchar * dt_util_longitude_str(float longitude)
Definition utility.c:572
gchar * dt_util_latitude_str(float latitude)
Definition utility.c:554
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:847
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:836
dt_view_surface_value_t dt_view_image_get_surface_async(dt_view_image_surface_fetcher_t *fetcher, int32_t imgid, int width, int height, cairo_surface_t **target, GtkWidget *widget, int zoom)
Definition view.c:892
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1266
GList * dt_view_active_images_get_all()
Definition view.c:1302
dt_view_surface_value_t dt_view_image_get_surface(int32_t imgid, int width, int height, cairo_surface_t **surface, int zoom)
Definition view.c:1214
void dt_view_active_images_set(GList *images, gboolean raise)
Definition view.c:1314
int32_t dt_view_active_images_get_first()
Definition view.c:1307
@ DT_VIEW_MAP
Definition view.h:80
dt_view_surface_value_t
Definition view.h:104
@ DT_VIEW_SURFACE_OK
Definition view.h:105
void dt_widget_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
@ DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
@ DT_GUI_COLOR_MAP_COUNT_BG
@ DT_GUI_COLOR_MAP_LOC_SHAPE_LOW
@ DT_GUI_COLOR_MAP_COUNT_SAME_LOC
@ DT_GUI_COLOR_MAP_COUNT_DIFF_LOC
@ DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_map_pin(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
GtkWidget * dt_ui_center_base(dt_ui_t *ui)