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