Index: trunk/src/org/openstreetmap/josm/actions/ImageryAdjustAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/ImageryAdjustAction.java	(revision 3845)
+++ trunk/src/org/openstreetmap/josm/actions/ImageryAdjustAction.java	(revision 3847)
@@ -146,4 +146,5 @@
             return;
         oldMapMode = Main.map.mapMode;
+        layer.enableOffsetServer(false);
         super.actionPerformed(e);
     }
Index: trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java	(revision 3845)
+++ trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java	(revision 3847)
@@ -23,12 +23,16 @@
 import javax.swing.JMenuItem;
 import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.ImageryAdjustAction;
 import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.data.imagery.OffsetBookmark;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.io.imagery.OffsetServer;
+import org.openstreetmap.josm.io.imagery.OsmosnimkiOffsetServer;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -58,4 +62,13 @@
 
     protected int sharpenLevel;
+
+    protected boolean offsetServerSupported;
+    protected boolean offsetServerUsed;
+    protected OffsetServerThread offsetServerThread;
+
+    protected OffsetServerThread createoffsetServerThread() {
+        return new OffsetServerThread(new OsmosnimkiOffsetServer(
+                OsmosnimkiOffsetServer.PROP_SERVER_URL.get()));
+    }
 
     public ImageryLayer(ImageryInfo info) {
@@ -63,4 +76,8 @@
         this.info = info;
         this.sharpenLevel = PROP_SHARPEN_LEVEL.get();
+        if (OffsetServer.PROP_SERVER_ENABLED.get()) {
+            offsetServerThread = createoffsetServerThread();
+            offsetServerThread.start();
+        }
     }
 
@@ -129,4 +146,5 @@
         public void actionPerformed(ActionEvent ev) {
             setOffset(b.dx, b.dy);
+            enableOffsetServer(false);
             Main.main.menu.imageryMenu.refreshOffsetMenu();
             Main.map.repaint();
@@ -151,4 +169,18 @@
 
     ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
+    AbstractAction useServerOffsetAction = new AbstractAction(tr("(use server offset)")) {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            enableOffsetServer(true);
+        }
+    };
+
+    public void enableOffsetServer(boolean enable) {
+        offsetServerUsed = enable;
+        if (offsetServerUsed && !offsetServerThread.isAlive()) {
+            offsetServerThread = createoffsetServerThread();
+            offsetServerThread.start();
+        }
+    }
 
     public JMenuItem getOffsetMenuItem() {
@@ -160,7 +192,14 @@
     public JComponent getOffsetMenuItem(JComponent subMenu) {
         JMenuItem adjustMenuItem = new JMenuItem(adjustAction);
-        if (OffsetBookmark.allBookmarks.isEmpty()) return adjustMenuItem;
+        if (OffsetBookmark.allBookmarks.isEmpty() && !offsetServerSupported) return adjustMenuItem;
 
         subMenu.add(adjustMenuItem);
+        if (offsetServerSupported) {
+            JCheckBoxMenuItem item = new JCheckBoxMenuItem(useServerOffsetAction);
+            if (offsetServerUsed) {
+                item.setSelected(true);
+            }
+            subMenu.add(item);
+        }
         subMenu.add(new JSeparator());
         boolean hasBookmarks = false;
@@ -170,5 +209,5 @@
             }
             JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
-            if (b.dx == dx && b.dy == dy) {
+            if (b.dx == dx && b.dy == dy && !offsetServerUsed) {
                 item.setSelected(true);
             }
@@ -176,5 +215,5 @@
             hasBookmarks = true;
         }
-        return hasBookmarks ? subMenu : adjustMenuItem;
+        return (hasBookmarks || offsetServerSupported) ? subMenu : adjustMenuItem;
     }
 
@@ -205,3 +244,48 @@
         g.drawString(text, (img.getWidth() + g.getFontMetrics().stringWidth(text)) / 2, img.getHeight()/2);
     }
+
+    protected class OffsetServerThread extends Thread {
+        OffsetServer offsetServer;
+        EastNorth oldCenter = new EastNorth(Double.NaN, Double.NaN);
+
+        public OffsetServerThread(OffsetServer offsetServer) {
+            this.offsetServer = offsetServer;
+            setDaemon(true);
+        }
+
+        private void updateOffset() {
+            if (Main.map == null || Main.map.mapView == null) return;
+            EastNorth center = Main.map.mapView.getCenter();
+            if (center.equals(oldCenter)) return;
+            oldCenter = center;
+
+            EastNorth offset = offsetServer.getOffset(getInfo(), center);
+            if (offset != null) {
+                setOffset(offset.east(),offset.north());
+            }
+        }
+
+        @Override
+        public void run() {
+            if (!offsetServerSupported) {
+                if (!offsetServer.isLayerSupported(getInfo())) return;
+                offsetServerSupported = true;
+            }
+            offsetServerUsed = true;
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    Main.main.menu.imageryMenu.refreshOffsetMenu();
+                }
+            });
+            try {
+                while (offsetServerUsed) {
+                    updateOffset();
+                    Thread.sleep(1000);
+                }
+            } catch (InterruptedException e) {
+            }
+            offsetServerUsed = false;
+        }
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java	(revision 3845)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/ImageryPreference.java	(revision 3847)
@@ -41,4 +41,6 @@
 import javax.swing.JToolBar;
 import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
@@ -48,11 +50,13 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
 import org.openstreetmap.josm.data.imagery.OffsetBookmark;
-import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.gui.layer.ImageryLayer;
 import org.openstreetmap.josm.gui.layer.TMSLayer;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
 import org.openstreetmap.josm.io.imagery.HTMLGrabber;
+import org.openstreetmap.josm.io.imagery.OffsetServer;
+import org.openstreetmap.josm.io.imagery.OsmosnimkiOffsetServer;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.GBC;
@@ -75,4 +79,6 @@
     private JSlider fadeAmount = new JSlider(0, 100);
     private JComboBox sharpen;
+    private JCheckBox useOffsetServer;
+    private JTextField offsetServerUrl;
 
     // WMS Settings
@@ -147,4 +153,15 @@
             p.add(btnSettingsMigration,GBC.eol().insets(0,5,0,5));
         }
+        this.useOffsetServer = new JCheckBox(tr("Use offset server: "));
+        this.offsetServerUrl = new JTextField();
+        this.useOffsetServer.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                offsetServerUrl.setEnabled(useOffsetServer.isSelected());
+            }
+        });
+        offsetServerUrl.setEnabled(useOffsetServer.isSelected());
+        p.add(this.useOffsetServer, GBC.eol().fill(GBC.HORIZONTAL));
+        p.add(this.offsetServerUrl, GBC.eol().fill(GBC.HORIZONTAL));
         return p;
     }
@@ -269,4 +286,6 @@
         this.fadeAmount.setValue(ImageryLayer.PROP_FADE_AMOUNT.get());
         this.sharpen.setSelectedIndex(Math.max(0, Math.min(2, ImageryLayer.PROP_SHARPEN_LEVEL.get())));
+        this.useOffsetServer.setSelected(OffsetServer.PROP_SERVER_ENABLED.get());
+        this.offsetServerUrl.setText(OsmosnimkiOffsetServer.PROP_SERVER_URL.get());
 
         // WMS Settings
@@ -300,4 +319,6 @@
 
         HTMLGrabber.PROP_BROWSER.put(browser.getEditor().getItem().toString());
+        OffsetServer.PROP_SERVER_ENABLED.put(useOffsetServer.isSelected());
+        OsmosnimkiOffsetServer.PROP_SERVER_URL.put(offsetServerUrl.getText());
 
         if (TMSLayer.PROP_ADD_TO_SLIPPYMAP_CHOOSER.get() != this.addToSlippyMapChosser.isSelected()) {
Index: trunk/src/org/openstreetmap/josm/io/imagery/OffsetServer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/OffsetServer.java	(revision 3847)
+++ trunk/src/org/openstreetmap/josm/io/imagery/OffsetServer.java	(revision 3847)
@@ -0,0 +1,12 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.imagery;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+
+public interface OffsetServer {
+    public static BooleanProperty PROP_SERVER_ENABLED = new BooleanProperty("imagery.offsetserver.enabled",false);
+    abstract boolean isLayerSupported(ImageryInfo info);
+    abstract EastNorth getOffset(ImageryInfo info, EastNorth en);
+}
Index: trunk/src/org/openstreetmap/josm/io/imagery/OsmosnimkiOffsetServer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/OsmosnimkiOffsetServer.java	(revision 3847)
+++ trunk/src/org/openstreetmap/josm/io/imagery/OsmosnimkiOffsetServer.java	(revision 3847)
@@ -0,0 +1,54 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.imagery;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLEncoder;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.preferences.StringProperty;
+
+public class OsmosnimkiOffsetServer implements OffsetServer {
+    public static StringProperty PROP_SERVER_URL = new StringProperty("imagery.offsetserver.url","http://offset.osmosnimki.ru/offset/v0?");
+    private String url;
+
+    public OsmosnimkiOffsetServer(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public boolean isLayerSupported(ImageryInfo info) {
+        try {
+            URL url = new URL(this.url + "action=CheckAvailability&id=" + URLEncoder.encode(info.getFullUrl(), "UTF-8"));
+            final BufferedReader rdr = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream(), "UTF-8"));
+            String response = rdr.readLine();
+            if (response.contains("\"offsets_available\": true")) return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    @Override
+    public EastNorth getOffset(ImageryInfo info, EastNorth en) {
+        LatLon ll = Main.proj.eastNorth2latlon(en);
+        try {
+            URL url = new URL(this.url + "action=GetOffsetForPoint&lat=" + ll.lat() + "&lon=" + ll.lon() + "&id=" + URLEncoder.encode(info.getUrl(), "UTF-8"));
+            final BufferedReader rdr = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream(), "UTF-8"));
+            String s = rdr.readLine();
+            int i = s.indexOf(',');
+            if (i == -1) return null;
+            String sLon = s.substring(1,i);
+            String sLat = s.substring(i+1,s.length()-1);
+            return Main.proj.latlon2eastNorth(new LatLon(Double.valueOf(sLat),Double.valueOf(sLon))).sub(en);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}
