]> git.street.me.uk Git - andy/viking.git/blobdiff - src/vikmapslayer.c
SF Features#129: Advanced Maps Layer drawing configuration.
[andy/viking.git] / src / vikmapslayer.c
index ce3a5d708625975952fb443bf9900590e862dcfc..80b969acbcb4a45b1f4634b7031e59b58fba0990 100644 (file)
@@ -53,6 +53,8 @@
 #include "preferences.h"
 #include "vikmapslayer.h"
 #include "icons/icons.h"
+#include "metatile.h"
+#include "ui_util.h"
 
 #ifdef HAVE_SQLITE3_H
 #include "sqlite3.h"
@@ -70,6 +72,13 @@ static gdouble MIN_SHRINKFACTOR = 0.0312499; /* zoom 32 viewing 1-tiles */
 #define VIK_SETTINGS_MAP_REAL_MIN_SHRINKFACTOR "maps_real_min_shrinkfactor"
 static gdouble REAL_MIN_SHRINKFACTOR = 0.0039062499; /* if shrinkfactor is between MAX and REAL_MAX, will only check for existence */
 
+#define VIK_SETTINGS_MAP_SCALE_INC_UP "maps_scale_inc_up"
+static guint SCALE_INC_UP = 2;
+#define VIK_SETTINGS_MAP_SCALE_INC_DOWN "maps_scale_inc_down"
+static guint SCALE_INC_DOWN = 4;
+#define VIK_SETTINGS_MAP_SCALE_SMALLER_ZOOM_FIRST "maps_scale_smaller_zoom_first"
+static gboolean SCALE_SMALLER_ZOOM_FIRST = TRUE;
+
 /****** MAP TYPES ******/
 
 static GList *__map_types = NULL;
@@ -312,6 +321,18 @@ void maps_layer_init ()
 
   if ( a_settings_get_double ( VIK_SETTINGS_MAP_REAL_MIN_SHRINKFACTOR, &gdtmp ) )
     REAL_MIN_SHRINKFACTOR = gdtmp;
+
+  gint gitmp = 0;
+  if ( a_settings_get_integer ( VIK_SETTINGS_MAP_SCALE_INC_UP, &gitmp ) )
+    SCALE_INC_UP = gitmp;
+
+  if ( a_settings_get_integer ( VIK_SETTINGS_MAP_SCALE_INC_DOWN, &gitmp ) )
+    SCALE_INC_DOWN = gitmp;
+
+  gboolean gbtmp = TRUE;
+  if ( a_settings_get_boolean ( VIK_SETTINGS_MAP_SCALE_SMALLER_ZOOM_FIRST, &gbtmp ) )
+    SCALE_SMALLER_ZOOM_FIRST = gbtmp;
+
 }
 
 /****************************************/
@@ -652,8 +673,8 @@ static void maps_layer_change_param ( GtkWidget *widget, ui_change_values values
     case PARAM_MAPTYPE: {
       // Get new value
       VikLayerParamData vlpd = a_uibuilder_widget_get_value ( widget, values[UI_CHG_PARAM] );
-      // Is it *not* the OSM On Disk Tile Layout or the MBTiles type
-      gboolean sensitive = ( 21 != vlpd.u && 23 != vlpd.u);
+      // Is it *not* the OSM On Disk Tile Layout or the MBTiles type or the OSM Metatiles type
+      gboolean sensitive = ( 21 != vlpd.u && 23 != vlpd.u && 24 != vlpd.u );
       GtkWidget **ww1 = values[UI_CHG_WIDGETS];
       GtkWidget **ww2 = values[UI_CHG_LABELS];
       GtkWidget *w1 = ww1[PARAM_ONLYMISSING];
@@ -819,33 +840,6 @@ static VikMapsLayer *maps_layer_unmarshall( guint8 *data, gint len, VikViewport
 /****** DRAWING ******/
 /*********************/
 
-static GdkPixbuf *pixbuf_set_alpha ( GdkPixbuf *pixbuf, guint8 alpha )
-{
-  guchar *pixels;
-  gint width, height, iii, jjj;
-
-  if ( ! gdk_pixbuf_get_has_alpha ( pixbuf ) )
-  {
-    GdkPixbuf *tmp = gdk_pixbuf_add_alpha(pixbuf,FALSE,0,0,0);
-    g_object_unref(G_OBJECT(pixbuf));
-    pixbuf = tmp;
-    if ( !pixbuf )
-      return NULL;
-  }
-
-  pixels = gdk_pixbuf_get_pixels(pixbuf);
-  width = gdk_pixbuf_get_width(pixbuf);
-  height = gdk_pixbuf_get_height(pixbuf);
-
-  /* r,g,b,a,r,g,b,a.... */
-  for (iii = 0; iii < width; iii++) for (jjj = 0; jjj < height; jjj++)
-  {
-    pixels += 3;
-    *pixels++ = alpha;
-  }
-  return pixbuf;
-}
-
 static GdkPixbuf *pixbuf_shrink ( GdkPixbuf *pixbuf, gdouble xshrinkfactor, gdouble yshrinkfactor )
 {
   GdkPixbuf *tmp;
@@ -964,11 +958,56 @@ static GdkPixbuf *get_mbtiles_pixbuf ( VikMapsLayer *vml, gint xx, gint yy, gint
   return pixbuf;
 }
 
+static GdkPixbuf *get_pixbuf_from_metatile ( VikMapsLayer *vml, gint xx, gint yy, gint zz )
+{
+  const int tile_max = METATILE_MAX_SIZE;
+  char err_msg[PATH_MAX];
+  char *buf;
+  int len;
+  int compressed;
+
+  buf = malloc(tile_max);
+  if (!buf) {
+      return NULL;
+  }
+
+  err_msg[0] = 0;
+  len = metatile_read(vml->cache_dir, xx, yy, zz, buf, tile_max, &compressed, err_msg);
+
+  if (len > 0) {
+    if (compressed) {
+      // Not handled yet - I don't think this is used often - so implement later if necessary
+      g_warning ( "Compressed metatiles not implemented:%s\n", __FUNCTION__);
+      return NULL;
+    }
+
+    // Convert these buf bytes into a pixbuf via these streaming operations
+    GdkPixbuf *pixbuf = NULL;
+
+    GInputStream *stream = g_memory_input_stream_new_from_data ( buf, len, NULL );
+    GError *error = NULL;
+    pixbuf = gdk_pixbuf_new_from_stream ( stream, NULL, &error );
+    if (error || (!pixbuf)) {
+      g_warning ( "%s: %s", __FUNCTION__, error->message );
+      g_error_free ( error );
+    }
+    g_input_stream_close ( stream, NULL, NULL );
+
+    free(buf);
+    return pixbuf;
+  }
+  else {
+    g_warning ( "FAILED:%s %s", __FUNCTION__, err_msg);
+    return NULL;
+  }
+}
+
+
 static GdkPixbuf *pixbuf_apply_settings ( GdkPixbuf *pixbuf, VikMapsLayer *vml, MapCoord *mapcoord, gdouble xshrinkfactor, gdouble yshrinkfactor )
 {
   // Apply alpha setting
   if ( pixbuf && vml->alpha < 255 )
-    pixbuf = pixbuf_set_alpha ( pixbuf, vml->alpha );
+    pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
 
   if ( pixbuf && ( xshrinkfactor != 1.0 || yshrinkfactor != 1.0 ) )
     pixbuf = pixbuf_shrink ( pixbuf, xshrinkfactor, yshrinkfactor );
@@ -1030,6 +1069,11 @@ static GdkPixbuf *get_pixbuf( VikMapsLayer *vml, guint16 id, const gchar* mapnam
         // return now to avoid file tests that aren't appropriate for this map type
         return pixbuf;
       }
+      else if ( vik_map_source_is_osm_meta_tiles(map) ) {
+        pixbuf = get_pixbuf_from_metatile ( vml, mapcoord->x, mapcoord->y, (17 - mapcoord->scale) );
+        pixbuf = pixbuf_apply_settings ( pixbuf, vml, mapcoord, xshrinkfactor, yshrinkfactor );
+        return pixbuf;
+      }
       else
         get_filename ( vml->cache_dir, VIK_MAPS_CACHE_LAYOUT_OSM, id, NULL,
                        mapcoord->scale, mapcoord->z, mapcoord->x, mapcoord->y, filename_buf, buf_len,
@@ -1105,6 +1149,68 @@ static gboolean should_start_autodownload(VikMapsLayer *vml, VikViewport *vvp)
   return TRUE;
 }
 
+/**
+ *
+ */
+gboolean try_draw_scale_down (VikMapsLayer *vml, VikViewport *vvp, MapCoord ulm, gint xx, gint yy, gint tilesize_x_ceil, gint tilesize_y_ceil,
+                              gdouble xshrinkfactor, gdouble yshrinkfactor, guint id, const gchar *mapname, gchar *path_buf, guint max_path_len)
+{
+  GdkPixbuf *pixbuf;
+  int scale_inc;
+  for (scale_inc = 1; scale_inc < SCALE_INC_DOWN; scale_inc ++) {
+    // Try with smaller zooms
+    int scale_factor = 1 << scale_inc;  /*  2^scale_inc */
+    MapCoord ulm2 = ulm;
+    ulm2.x = ulm.x / scale_factor;
+    ulm2.y = ulm.y / scale_factor;
+    ulm2.scale = ulm.scale + scale_inc;
+    pixbuf = get_pixbuf ( vml, id, mapname, &ulm2, path_buf, max_path_len, xshrinkfactor * scale_factor, yshrinkfactor * scale_factor );
+    if ( pixbuf ) {
+      gint src_x = (ulm.x % scale_factor) * tilesize_x_ceil;
+      gint src_y = (ulm.y % scale_factor) * tilesize_y_ceil;
+      vik_viewport_draw_pixbuf ( vvp, pixbuf, src_x, src_y, xx, yy, tilesize_x_ceil, tilesize_y_ceil );
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ *
+ */
+gboolean try_draw_scale_up (VikMapsLayer *vml, VikViewport *vvp, MapCoord ulm, gint xx, gint yy, gint tilesize_x_ceil, gint tilesize_y_ceil,
+                            gdouble xshrinkfactor, gdouble yshrinkfactor, guint id, const gchar *mapname, gchar *path_buf, guint max_path_len)
+{
+  GdkPixbuf *pixbuf;
+  // Try with bigger zooms
+  int scale_dec;
+  for (scale_dec = 1; scale_dec < SCALE_INC_UP; scale_dec ++) {
+    int pict_x, pict_y;
+    int scale_factor = 1 << scale_dec;  /*  2^scale_dec */
+    MapCoord ulm2 = ulm;
+    ulm2.x = ulm.x * scale_factor;
+    ulm2.y = ulm.y * scale_factor;
+    ulm2.scale = ulm.scale - scale_dec;
+    for (pict_x = 0; pict_x < scale_factor; pict_x ++) {
+      for (pict_y = 0; pict_y < scale_factor; pict_y ++) {
+        MapCoord ulm3 = ulm2;
+        ulm3.x += pict_x;
+        ulm3.y += pict_y;
+        pixbuf = get_pixbuf ( vml, id, mapname, &ulm3, path_buf, max_path_len, xshrinkfactor / scale_factor, yshrinkfactor / scale_factor );
+        if ( pixbuf ) {
+          gint src_x = 0;
+          gint src_y = 0;
+          gint dest_x = xx + pict_x * (tilesize_x_ceil / scale_factor);
+          gint dest_y = yy + pict_y * (tilesize_y_ceil / scale_factor);
+          vik_viewport_draw_pixbuf ( vvp, pixbuf, src_x, src_y, dest_x, dest_y, tilesize_x_ceil / scale_factor, tilesize_y_ceil / scale_factor );
+          return TRUE;
+        }
+      }
+    }
+  }
+  return FALSE;
+}
+
 static void maps_layer_draw_section ( VikMapsLayer *vml, VikViewport *vvp, VikCoord *ul, VikCoord *br )
 {
   MapCoord ulm, brm;
@@ -1234,49 +1340,24 @@ static void maps_layer_draw_section ( VikMapsLayer *vml, VikViewport *vvp, VikCo
               vik_viewport_draw_line ( vvp, black_gc, xx+tilesize_x_ceil, yy, xx, yy+tilesize_y_ceil );
             }
           } else {
-            int scale_inc;
-            for (scale_inc = 0; scale_inc < 4; scale_inc ++) {
-              /* try with correct then smaller zooms */
-              int scale_factor = 1 << scale_inc;  /*  2^scale_inc */
-              MapCoord ulm2 = ulm;
-              ulm2.x = ulm.x / scale_factor;
-              ulm2.y = ulm.y / scale_factor;
-              ulm2.scale = ulm.scale + scale_inc;
-              pixbuf = get_pixbuf ( vml, id, mapname, &ulm2, path_buf, max_path_len, xshrinkfactor * scale_factor, yshrinkfactor * scale_factor );
-              if ( pixbuf ) {
-                gint src_x = (ulm.x % scale_factor) * tilesize_x_ceil;
-                gint src_y = (ulm.y % scale_factor) * tilesize_y_ceil;
-#ifdef DEBUG
-                printf("maps_layer_draw_section - x=%d, y=%d, z=%d, src_x=%d, src_y=%d, xx=%d, yy=%d - %x\n", ulm.x, ulm.y, ulm.scale, src_x, src_y, (int)xx, (int)yy, vvp);
-#endif
-                vik_viewport_draw_pixbuf ( vvp, pixbuf, src_x, src_y, xx, yy, tilesize_x_ceil, tilesize_y_ceil );
-                break;
-              }
+            // Try correct scale first
+            int scale_factor = 1;
+            pixbuf = get_pixbuf ( vml, id, mapname, &ulm, path_buf, max_path_len, xshrinkfactor * scale_factor, yshrinkfactor * scale_factor );
+            if ( pixbuf ) {
+              gint src_x = (ulm.x % scale_factor) * tilesize_x_ceil;
+              gint src_y = (ulm.y % scale_factor) * tilesize_y_ceil;
+              vik_viewport_draw_pixbuf ( vvp, pixbuf, src_x, src_y, xx, yy, tilesize_x_ceil, tilesize_y_ceil );
             }
-            if ( !pixbuf ) {
-              /* retry with bigger zooms */
-              int scale_dec;
-              for (scale_dec = 1; scale_dec < 2; scale_dec ++) {
-                int pict_x, pict_y;
-                int scale_factor = 1 << scale_dec;  /*  2^scale_dec */
-                MapCoord ulm2 = ulm;
-                ulm2.x = ulm.x * scale_factor;
-                ulm2.y = ulm.y * scale_factor;
-                ulm2.scale = ulm.scale - scale_dec;
-                for (pict_x = 0; pict_x < scale_factor; pict_x ++) {
-                  for (pict_y = 0; pict_y < scale_factor; pict_y ++) {
-                    MapCoord ulm3 = ulm2;
-                    ulm3.x += pict_x;
-                    ulm3.y += pict_y;
-                    pixbuf = get_pixbuf ( vml, id, mapname, &ulm3, path_buf, max_path_len, xshrinkfactor / scale_factor, yshrinkfactor / scale_factor );
-                    if ( pixbuf ) {
-                      gint src_x = 0;
-                      gint src_y = 0;
-                      gint dest_x = xx + pict_x * (tilesize_x_ceil / scale_factor);
-                      gint dest_y = yy + pict_y * (tilesize_y_ceil / scale_factor);
-                      vik_viewport_draw_pixbuf ( vvp, pixbuf, src_x, src_y, dest_x, dest_y, tilesize_x_ceil / scale_factor, tilesize_y_ceil / scale_factor );
-                    }
-                  }
+            else {
+              // Otherwise try different scales
+              if ( SCALE_SMALLER_ZOOM_FIRST ) {
+                if ( !try_draw_scale_down(vml,vvp,ulm,xx,yy,tilesize_x_ceil,tilesize_y_ceil,xshrinkfactor,yshrinkfactor,id,mapname,path_buf,max_path_len) ) {
+                  try_draw_scale_up(vml,vvp,ulm,xx,yy,tilesize_x_ceil,tilesize_y_ceil,xshrinkfactor,yshrinkfactor,id,mapname,path_buf,max_path_len);
+                }
+              }
+              else {
+                if ( !try_draw_scale_up(vml,vvp,ulm,xx,yy,tilesize_x_ceil,tilesize_y_ceil,xshrinkfactor,yshrinkfactor,id,mapname,path_buf,max_path_len) ) {
+                  try_draw_scale_down(vml,vvp,ulm,xx,yy,tilesize_x_ceil,tilesize_y_ceil,xshrinkfactor,yshrinkfactor,id,mapname,path_buf,max_path_len);
                 }
               }
             }
@@ -1368,114 +1449,139 @@ static void weak_ref_cb(gpointer ptr, GObject * dead_vml)
   g_mutex_unlock(mdi->mutex);
 }
 
+static gboolean is_in_area (VikMapSource *map, MapCoord mc)
+{
+  VikCoord vc;
+  vik_map_source_mapcoord_to_center_coord ( map, &mc, &vc );
+
+  struct LatLon tl;
+  tl.lat = vik_map_source_get_lat_max(map);
+  tl.lon = vik_map_source_get_lon_min(map);
+  struct LatLon br;
+  br.lat = vik_map_source_get_lat_min(map);
+  br.lon = vik_map_source_get_lon_max(map);
+  VikCoord vctl;
+  vik_coord_load_from_latlon (&vctl, VIK_COORD_LATLON, &tl);
+  VikCoord vcbr;
+  vik_coord_load_from_latlon (&vcbr, VIK_COORD_LATLON, &br);
+
+  return vik_coord_inside ( &vc, &vctl, &vcbr );
+}
+
 static int map_download_thread ( MapDownloadInfo *mdi, gpointer threaddata )
 {
   void *handle = vik_map_source_download_handle_init(MAPS_LAYER_NTH_TYPE(mdi->maptype));
   guint donemaps = 0;
+  MapCoord mcoord = mdi->mapcoord;
   gint x, y;
   for ( x = mdi->x0; x <= mdi->xf; x++ )
   {
+    mcoord.x = x;
     for ( y = mdi->y0; y <= mdi->yf; y++ )
     {
-      gboolean remove_mem_cache = FALSE;
-      gboolean need_download = FALSE;
-
-      get_filename ( mdi->cache_dir, mdi->cache_layout,
-                     vik_map_source_get_uniq_id(MAPS_LAYER_NTH_TYPE(mdi->maptype)),
-                     vik_map_source_get_name(MAPS_LAYER_NTH_TYPE(mdi->maptype)),
-                     mdi->mapcoord.scale, mdi->mapcoord.z, x, y, mdi->filename_buf, mdi->maxlen,
-                     vik_map_source_get_file_extension(MAPS_LAYER_NTH_TYPE(mdi->maptype)) );
-
-      donemaps++;
-      int res = a_background_thread_progress ( threaddata, ((gdouble)donemaps) / mdi->mapstoget ); /* this also calls testcancel */
-      if (res != 0) {
-        vik_map_source_download_handle_cleanup(MAPS_LAYER_NTH_TYPE(mdi->maptype), handle);
-        return -1;
-      }
+      mcoord.y = y;
+      // Only attempt to download a tile from supported areas
+      if ( is_in_area ( MAPS_LAYER_NTH_TYPE(mdi->maptype), mcoord ) )
+      {
+        gboolean remove_mem_cache = FALSE;
+        gboolean need_download = FALSE;
+
+        get_filename ( mdi->cache_dir, mdi->cache_layout,
+                       vik_map_source_get_uniq_id(MAPS_LAYER_NTH_TYPE(mdi->maptype)),
+                       vik_map_source_get_name(MAPS_LAYER_NTH_TYPE(mdi->maptype)),
+                       mdi->mapcoord.scale, mdi->mapcoord.z, x, y, mdi->filename_buf, mdi->maxlen,
+                       vik_map_source_get_file_extension(MAPS_LAYER_NTH_TYPE(mdi->maptype)) );
+
+        donemaps++;
+        int res = a_background_thread_progress ( threaddata, ((gdouble)donemaps) / mdi->mapstoget ); /* this also calls testcancel */
+        if (res != 0) {
+          vik_map_source_download_handle_cleanup(MAPS_LAYER_NTH_TYPE(mdi->maptype), handle);
+          return -1;
+        }
 
-      if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE ) {
-        need_download = TRUE;
-        remove_mem_cache = TRUE;
+        if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE ) {
+          need_download = TRUE;
+          remove_mem_cache = TRUE;
 
-      } else {  /* in case map file already exists */
-        switch (mdi->redownload) {
-          case REDOWNLOAD_NONE:
-            continue;
+        } else {  /* in case map file already exists */
+          switch (mdi->redownload) {
+            case REDOWNLOAD_NONE:
+              continue;
 
-          case REDOWNLOAD_BAD:
-          {
-            /* see if this one is bad or what */
-            GError *gx = NULL;
-            GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file ( mdi->filename_buf, &gx );
-            if (gx || (!pixbuf)) {
-              g_remove ( mdi->filename_buf );
-              need_download = TRUE;
-              remove_mem_cache = TRUE;
-              g_error_free ( gx );
+            case REDOWNLOAD_BAD:
+            {
+              /* see if this one is bad or what */
+              GError *gx = NULL;
+              GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file ( mdi->filename_buf, &gx );
+              if (gx || (!pixbuf)) {
+                g_remove ( mdi->filename_buf );
+                need_download = TRUE;
+                remove_mem_cache = TRUE;
+                g_error_free ( gx );
 
-            } else {
-              g_object_unref ( pixbuf );
+              } else {
+                g_object_unref ( pixbuf );
+              }
+              break;
             }
-            break;
-          }
 
-          case REDOWNLOAD_NEW:
-            need_download = TRUE;
-            remove_mem_cache = TRUE;
-            break;
+            case REDOWNLOAD_NEW:
+              need_download = TRUE;
+              remove_mem_cache = TRUE;
+              break;
 
-          case REDOWNLOAD_ALL:
-            /* FIXME: need a better way than to erase file in case of server/network problem */
-            g_remove ( mdi->filename_buf );
-            need_download = TRUE;
-            remove_mem_cache = TRUE;
-            break;
+            case REDOWNLOAD_ALL:
+              /* FIXME: need a better way than to erase file in case of server/network problem */
+              g_remove ( mdi->filename_buf );
+              need_download = TRUE;
+              remove_mem_cache = TRUE;
+              break;
 
-          case DOWNLOAD_OR_REFRESH:
-            remove_mem_cache = TRUE;
-            break;
+            case DOWNLOAD_OR_REFRESH:
+              remove_mem_cache = TRUE;
+              break;
 
-          default:
-            g_warning ( "redownload state %d unknown\n", mdi->redownload);
+            default:
+              g_warning ( "redownload state %d unknown\n", mdi->redownload);
+          }
         }
-      }
-
-      mdi->mapcoord.x = x; mdi->mapcoord.y = y;
 
-      if (need_download) {
-        DownloadResult_t dr = vik_map_source_download( MAPS_LAYER_NTH_TYPE(mdi->maptype), &(mdi->mapcoord), mdi->filename_buf, handle);
-        switch ( dr ) {
-          case DOWNLOAD_HTTP_ERROR:
-          case DOWNLOAD_CONTENT_ERROR: {
-            // TODO: ?? count up the number of download errors somehow...
-            gchar* msg = g_strdup_printf ( "%s: %s", vik_maps_layer_get_map_label (mdi->vml), _("Failed to download tile") );
-            vik_window_statusbar_update ( (VikWindow*)VIK_GTK_WINDOW_FROM_LAYER(mdi->vml), msg, VIK_STATUSBAR_INFO );
-            g_free (msg);
-            break;
-          }
-          case DOWNLOAD_FILE_WRITE_ERROR: {
-            gchar* msg = g_strdup_printf ( "%s: %s", vik_maps_layer_get_map_label (mdi->vml), _("Unable to save tile") );
-            vik_window_statusbar_update ( (VikWindow*)VIK_GTK_WINDOW_FROM_LAYER(mdi->vml), msg, VIK_STATUSBAR_INFO );
-            g_free (msg);
-            break;
+        mdi->mapcoord.x = x; mdi->mapcoord.y = y;
+
+        if (need_download) {
+          DownloadResult_t dr = vik_map_source_download( MAPS_LAYER_NTH_TYPE(mdi->maptype), &(mdi->mapcoord), mdi->filename_buf, handle);
+          switch ( dr ) {
+            case DOWNLOAD_HTTP_ERROR:
+            case DOWNLOAD_CONTENT_ERROR: {
+              // TODO: ?? count up the number of download errors somehow...
+              gchar* msg = g_strdup_printf ( "%s: %s", vik_maps_layer_get_map_label (mdi->vml), _("Failed to download tile") );
+              vik_window_statusbar_update ( (VikWindow*)VIK_GTK_WINDOW_FROM_LAYER(mdi->vml), msg, VIK_STATUSBAR_INFO );
+              g_free (msg);
+              break;
+            }
+            case DOWNLOAD_FILE_WRITE_ERROR: {
+              gchar* msg = g_strdup_printf ( "%s: %s", vik_maps_layer_get_map_label (mdi->vml), _("Unable to save tile") );
+              vik_window_statusbar_update ( (VikWindow*)VIK_GTK_WINDOW_FROM_LAYER(mdi->vml), msg, VIK_STATUSBAR_INFO );
+              g_free (msg);
+              break;
+            }
+            case DOWNLOAD_SUCCESS:
+            case DOWNLOAD_NOT_REQUIRED:
+            default:
+              break;
           }
-          case DOWNLOAD_SUCCESS:
-          case DOWNLOAD_NOT_REQUIRED:
-          default:
-            break;
         }
-      }
 
-      g_mutex_lock(mdi->mutex);
-      if (remove_mem_cache)
-          a_mapcache_remove_all_shrinkfactors ( x, y, mdi->mapcoord.z, vik_map_source_get_uniq_id(MAPS_LAYER_NTH_TYPE(mdi->maptype)), mdi->mapcoord.scale );
-      if (mdi->refresh_display && mdi->map_layer_alive) {
-        /* TODO: check if it's on visible area */
-        vik_layer_emit_update ( VIK_LAYER(mdi->vml) ); // NB update display from background
+        g_mutex_lock(mdi->mutex);
+        if (remove_mem_cache)
+            a_mapcache_remove_all_shrinkfactors ( x, y, mdi->mapcoord.z, vik_map_source_get_uniq_id(MAPS_LAYER_NTH_TYPE(mdi->maptype)), mdi->mapcoord.scale );
+        if (mdi->refresh_display && mdi->map_layer_alive) {
+          /* TODO: check if it's on visible area */
+          vik_layer_emit_update ( VIK_LAYER(mdi->vml) ); // NB update display from background
+        }
+        g_mutex_unlock(mdi->mutex);
+        mdi->mapcoord.x = mdi->mapcoord.y = 0; /* we're temporarily between downloads */
       }
-      g_mutex_unlock(mdi->mutex);
-      mdi->mapcoord.x = mdi->mapcoord.y = 0; /* we're temporarily between downloads */
-
     }
   }
   vik_map_source_download_handle_cleanup(MAPS_LAYER_NTH_TYPE(mdi->maptype), handle);
@@ -1542,21 +1648,29 @@ static void start_download_thread ( VikMapsLayer *vml, VikViewport *vvp, const V
 
     mdi->mapstoget = 0;
 
+    MapCoord mcoord = mdi->mapcoord;
+
     if ( mdi->redownload ) {
       mdi->mapstoget = (mdi->xf - mdi->x0 + 1) * (mdi->yf - mdi->y0 + 1);
     } else {
       /* calculate how many we need */
       for ( a = mdi->x0; a <= mdi->xf; a++ )
       {
+        mcoord.x = a;
         for ( b = mdi->y0; b <= mdi->yf; b++ )
         {
-          get_filename ( mdi->cache_dir, mdi->cache_layout,
-                         vik_map_source_get_uniq_id(map),
-                         vik_map_source_get_name(map),
-                         ulm.scale, ulm.z, a, b, mdi->filename_buf, mdi->maxlen,
-                         vik_map_source_get_file_extension(map) );
-          if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE )
-            mdi->mapstoget++;
+          mcoord.y = b;
+          // Only count tiles from supported areas
+          if ( is_in_area (map, mcoord) )
+          {
+            get_filename ( mdi->cache_dir, mdi->cache_layout,
+                           vik_map_source_get_uniq_id(map),
+                           vik_map_source_get_name(map),
+                           ulm.scale, ulm.z, a, b, mdi->filename_buf, mdi->maxlen,
+                           vik_map_source_get_file_extension(map) );
+            if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE )
+              mdi->mapstoget++;
+          }
         }
       }
     }
@@ -1637,15 +1751,22 @@ static void maps_layer_download_section ( VikMapsLayer *vml, VikViewport *vvp, V
 
   mdi->mapstoget = 0;
 
+  MapCoord mcoord = mdi->mapcoord;
+
   for (i = mdi->x0; i <= mdi->xf; i++) {
+    mcoord.x = i;
     for (j = mdi->y0; j <= mdi->yf; j++) {
-      get_filename ( mdi->cache_dir, mdi->cache_layout,
-                     vik_map_source_get_uniq_id(map),
-                     vik_map_source_get_name(map),
-                     ulm.scale, ulm.z, i, j, mdi->filename_buf, mdi->maxlen,
-                     vik_map_source_get_file_extension(map) );
-      if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE )
-            mdi->mapstoget++;
+      mcoord.y = j;
+      // Only count tiles from supported areas
+      if ( is_in_area (map, mcoord) ) {
+        get_filename ( mdi->cache_dir, mdi->cache_layout,
+                       vik_map_source_get_uniq_id(map),
+                       vik_map_source_get_name(map),
+                       ulm.scale, ulm.z, i, j, mdi->filename_buf, mdi->maxlen,
+                       vik_map_source_get_file_extension(map) );
+        if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE )
+              mdi->mapstoget++;
+      }
     }
   }
 
@@ -1749,6 +1870,12 @@ static void maps_layer_tile_info ( VikMapsLayer *vml )
       source = g_strdup ( _("Not available") );
 #endif
     }
+    else if ( vik_map_source_is_osm_meta_tiles ( map ) ) {
+      char path[PATH_MAX];
+      xyz_to_meta(path, sizeof(path), vml->cache_dir, ulm.x, ulm.y, 17-ulm.scale );
+      source = g_strdup ( path );
+      filename = g_strdup ( path );
+    }
     else {
       guint max_path_len = strlen(vml->cache_dir) + 40;
       filename = g_malloc ( max_path_len * sizeof(char) );
@@ -1929,7 +2056,7 @@ static void maps_layer_redownload_all_onscreen_maps ( menu_array_values values )
   download_onscreen_maps( values, REDOWNLOAD_ALL);
 }
 
-static void maps_layers_about ( gpointer vml_vvp[2] )
+static void maps_layer_about ( gpointer vml_vvp[2] )
 {
   VikMapsLayer *vml = vml_vvp[0];
   VikMapSource *map = MAPS_LAYER_NTH_TYPE(vml->maptype);
@@ -1989,33 +2116,39 @@ static gint maps_layer_how_many_maps ( VikMapsLayer *vml, VikViewport *vvp, VikC
   }
   else {
     /* calculate how many we need */
+    MapCoord mcoord = mdi->mapcoord;
     for (i = mdi->x0; i <= mdi->xf; i++) {
+      mcoord.x = i;
       for (j = mdi->y0; j <= mdi->yf; j++) {
-        get_filename ( mdi->cache_dir, mdi->cache_layout,
-                       vik_map_source_get_uniq_id(map),
-                       vik_map_source_get_name(map),
-                       ulm.scale, ulm.z, i, j, mdi->filename_buf, mdi->maxlen,
-                       vik_map_source_get_file_extension(map) );
-        if ( mdi->redownload == REDOWNLOAD_NEW ) {
-          // Assume the worst - always a new file
-          // Absolute value would requires server lookup - but that is too slow
-          mdi->mapstoget++;
-        }
-        else {
-          if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE ) {
-            // Missing
+        mcoord.y = j;
+        // Only count tiles from supported areas
+        if ( is_in_area ( map, mcoord ) ) {
+          get_filename ( mdi->cache_dir, mdi->cache_layout,
+                         vik_map_source_get_uniq_id(map),
+                         vik_map_source_get_name(map),
+                         ulm.scale, ulm.z, i, j, mdi->filename_buf, mdi->maxlen,
+                         vik_map_source_get_file_extension(map) );
+          if ( mdi->redownload == REDOWNLOAD_NEW ) {
+            // Assume the worst - always a new file
+            // Absolute value would require a server lookup - but that is too slow
             mdi->mapstoget++;
           }
           else {
-            if ( mdi->redownload == REDOWNLOAD_BAD ) {
-              /* see if this one is bad or what */
-              GError *gx = NULL;
-              GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file ( mdi->filename_buf, &gx );
-              if (gx || (!pixbuf)) {
-                mdi->mapstoget++;
+            if ( g_file_test ( mdi->filename_buf, G_FILE_TEST_EXISTS ) == FALSE ) {
+              // Missing
+              mdi->mapstoget++;
+            }
+            else {
+              if ( mdi->redownload == REDOWNLOAD_BAD ) {
+                /* see if this one is bad or what */
+                GError *gx = NULL;
+                GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file ( mdi->filename_buf, &gx );
+                if (gx || (!pixbuf)) {
+                  mdi->mapstoget++;
+                }
+                break;
+                // Other download cases already considered or just ignored
               }
-              break;
-              // Other download cases already considered or just ignored
             }
           }
         }
@@ -2243,7 +2376,7 @@ static void maps_layer_add_menu_items ( VikMapsLayer *vml, GtkMenu *menu, VikLay
   gtk_widget_show ( item );
 
   item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
-  g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(maps_layers_about), values );
+  g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(maps_layer_about), values );
   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
   gtk_widget_show ( item );
 }