Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 26937)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 26938)
@@ -8,4 +8,7 @@
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.HashMap;
 
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
@@ -22,13 +25,15 @@
 
     /**
-     * Holds the used user agent used for HTTP requests. If this field is
-     * <code>null</code>, the default Java user agent is used.
+     * Holds the HTTP headers. Insert e.g. User-Agent here when default should not be used.
      */
-    public static String USER_AGENT = null;
-    public static String ACCEPT = "text/html, image/png, image/jpeg, image/gif, */*";
+    public Map<String, String> headers = new HashMap<String, String>();
+
+    public int timeoutConnect = 0;
+    public int timeoutRead = 0;
 
     protected TileLoaderListener listener;
 
     public OsmTileLoader(TileLoaderListener listener) {
+        headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*");
         this.listener = listener;
     }
@@ -102,8 +107,11 @@
 
     protected void prepareHttpUrlConnection(HttpURLConnection urlConn) {
-        if (USER_AGENT != null) {
-            urlConn.setRequestProperty("User-agent", USER_AGENT);
+        for(Entry<String, String> e : headers.entrySet()) {
+            urlConn.setRequestProperty(e.getKey(), e.getValue());
         }
-        urlConn.setRequestProperty("Accept", ACCEPT);
+        if(timeoutConnect != 0)
+            urlConn.setConnectTimeout(timeoutConnect);
+        if(timeoutRead != 0)
+            urlConn.setReadTimeout(timeoutRead);
     }
 
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java	(revision 26937)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java	(revision 26938)
@@ -1,4 +1,6 @@
 package org.openstreetmap.gui.jmapviewer.tilesources;
 
+import java.util.Map;
+import java.util.HashMap;
 import java.util.Random;
 import java.util.regex.Pattern;
@@ -9,31 +11,58 @@
     private Random rand = null;
     private String[] randomParts = null;
+    private Map<String, String> headers = new HashMap<String, String>();
     
-    public static final String PATTERN_ZOOM    = "\\{zoom\\}";
+    public static final String PATTERN_ZOOM    = "\\{zoom([+-]\\d+)?\\}";
     public static final String PATTERN_X       = "\\{x\\}";
     public static final String PATTERN_Y       = "\\{y\\}";
     public static final String PATTERN_Y_YAHOO = "\\{!y\\}";
     public static final String PATTERN_SWITCH  = "\\{switch:[^}]+\\}";
+    public static final String PATTERN_HEADER  = "\\{header\\(([^,]+),([^}]+)\\)\\}";
     
     public static final String[] ALL_PATTERNS = {
-        PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_SWITCH
+        PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_SWITCH
     };
     
     public TemplatedTMSTileSource(String name, String url, int maxZoom) {
         super(name, url, maxZoom);
+        handleTemplate();
     }
 
     public TemplatedTMSTileSource(String name, String url, int minZoom, int maxZoom) {
         super(name, url, minZoom, maxZoom);
+        handleTemplate();
+    }
+
+    private void handleTemplate() {
         // Capturing group pattern on switch values
-        Matcher m = Pattern.compile(".*\\{switch:([^}]+)\\}.*").matcher(url);
+        Matcher m = Pattern.compile(".*"+PATTERN_SWITCH+".*").matcher(baseUrl);
         if (m.matches()) {
             rand = new Random();
             randomParts = m.group(1).split(",");
         }
+        Pattern pattern = Pattern.compile(PATTERN_HEADER);
+        StringBuffer output = new StringBuffer();
+        Matcher matcher = pattern.matcher(baseUrl);
+        while (matcher.find()) {
+            headers.put(matcher.group(1),matcher.group(2));
+            matcher.appendReplacement(output, "");
+        }
+        matcher.appendTail(output);
+        baseUrl = output.toString();
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
     }
 
     @Override
     public String getTileUrl(int zoom, int tilex, int tiley) {
+        Matcher m = Pattern.compile(".*"+PATTERN_ZOOM+".*").matcher(this.baseUrl);
+        if (m.matches() && !m.group(1).isEmpty()) {
+            String ofs = m.group(1);
+            if(ofs.startsWith("+"))
+                ofs = ofs.substring(1);
+            zoom += Integer.valueOf(ofs);
+        }
         String r = this.baseUrl
             .replaceAll(PATTERN_ZOOM, Integer.toString(zoom))
