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