Index: /applications/editors/josm/plugins/cadastre-fr/build.xml
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/build.xml	(revision 33636)
+++ /applications/editors/josm/plugins/cadastre-fr/build.xml	(revision 33637)
@@ -10,5 +10,5 @@
     -->
     <property name="plugin.author" value="Pieren"/>
-    <property name="plugin.class" value="cadastre_fr.CadastrePlugin"/>
+    <property name="plugin.class" value="org.openstreetmap.josm.plugins.fr.cadastre.CadastrePlugin"/>
     <property name="plugin.description" value="A special handler for the French land registry WMS server."/>
     <property name="plugin.icon" value="images/preferences/cadastrewms.png"/>
Index: /applications/editors/josm/plugins/cadastre-fr/src/META-INF/MANIFEST.MF
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/META-INF/MANIFEST.MF	(revision 33636)
+++ /applications/editors/josm/plugins/cadastre-fr/src/META-INF/MANIFEST.MF	(revision 33637)
@@ -1,4 +1,4 @@
 Manifest-Version: 1.0
-Plugin-Class: cadastre_fr.CadastrePlugin
+Plugin-Class: org.openstreetmap.josm.plugins.fr.cadastre.CadastrePlugin
 Plugin-Description: A special handler for the French land registry WMS server
 Plugin-Version: 2.6
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Address.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Address.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Address.java	(revision 33637)
@@ -0,0 +1,540 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Cursor;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class Address extends MapMode {
+
+    // perhaps make all these tags configurable in the future
+    private String tagHighway = "highway";
+    private String tagHighwayName = "name";
+    private String tagHouseNumber = "addr:housenumber";
+    private String tagHouseStreet = "addr:street";
+    private String tagBuilding = "building";
+    private String relationAddrType = "associatedStreet";
+    private String relationAddrName = "name";
+    private String relationAddrStreetRole = "street";
+    private String relationMemberHouse = "house";
+
+    private JRadioButton plusOne = new JRadioButton("+1", false);
+    private JRadioButton plusTwo = new JRadioButton("+2", true); // enable this by default
+    private JRadioButton minusOne = new JRadioButton("-1", false);
+    private JRadioButton minusTwo = new JRadioButton("-2", false);
+    final JCheckBox tagPolygon = new JCheckBox(tr("on polygon"));
+
+    JDialog dialog;
+    JButton clearButton;
+    final JTextField inputNumber = new JTextField();
+    final JTextField inputStreet = new JTextField();
+    JLabel link = new JLabel();
+    private transient Way selectedWay;
+    private boolean shift;
+    private boolean ctrl;
+
+    /**
+     * Constructs a new {@code Address} map mode.
+     */
+    public Address() {
+        super(tr("Add address"), "buildings",
+                tr("Helping tool for tag address"),
+                // CHECKSTYLE.OFF: LineLength
+                Shortcut.registerShortcut("mapmode:cadastre-fr-buildings", tr("Mode: {0}", tr("CadastreFR - Buildings")), KeyEvent.VK_E, Shortcut.DIRECT),
+                // CHECKSTYLE.ON: LineLength
+                getCursor());
+    }
+
+    @Override public void enterMode() {
+        super.enterMode();
+        if (dialog == null) {
+            createDialog();
+        }
+        dialog.setVisible(true);
+        Main.map.mapView.addMouseListener(this);
+    }
+
+    @Override public void exitMode() {
+        if (Main.map.mapView != null) {
+            super.exitMode();
+            Main.map.mapView.removeMouseListener(this);
+        }
+        // kill the window completely to fix an issue on some linux distro and full screen mode.
+        if (dialog != null) {
+            dialog.dispose();
+            dialog = null;
+        }
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+        if (e.getButton() != MouseEvent.BUTTON1)
+            return;
+        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
+        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
+        MapView mv = Main.map.mapView;
+        Point mousePos = e.getPoint();
+        List<Way> mouseOnExistingWays = new ArrayList<>();
+        List<Way> mouseOnExistingBuildingWays = new ArrayList<>();
+        Node currentMouseNode = mv.getNearestNode(mousePos, OsmPrimitive::isSelectable);
+        if (currentMouseNode != null) {
+            // click on existing node
+            setNewSelection(currentMouseNode);
+            String num = currentMouseNode.get(tagHouseNumber);
+            if (num != null
+                    && currentMouseNode.get(tagHouseStreet) == null
+                    && findWayInRelationAddr(currentMouseNode) == null
+                    && !inputStreet.getText().isEmpty()) {
+                // house number already present but not linked to a street
+                Collection<Command> cmds = new LinkedList<>();
+                addStreetNameOrRelation(currentMouseNode, cmds);
+                Command c = new SequenceCommand("Add node address", cmds);
+                Main.main.undoRedo.add(c);
+                setNewSelection(currentMouseNode);
+            } else {
+                if (num != null) {
+                    try {
+                        // add new address
+                        Integer.parseInt(num);
+                        inputNumber.setText(num);
+                        applyInputNumberChange();
+                    } catch (NumberFormatException en) {
+                        Main.warn("Unable to parse house number \"" + num + "\"");
+                    }
+                }
+                if (currentMouseNode.get(tagHouseStreet) != null) {
+                    if (Main.pref.getBoolean("cadastrewms.addr.dontUseRelation", false)) {
+                        inputStreet.setText(currentMouseNode.get(tagHouseStreet));
+                        if (ctrl) {
+                            Collection<Command> cmds = new LinkedList<>();
+                            addAddrToPrimitive(currentMouseNode, cmds);
+                            if (num == null)
+                                applyInputNumberChange();
+                        }
+                        setSelectedWay((Way) null);
+                    }
+                } else {
+                    // check if the node belongs to an associatedStreet relation
+                    Way wayInRelationAddr = findWayInRelationAddr(currentMouseNode);
+                    if (wayInRelationAddr == null) {
+                        // node exists but doesn't carry address information : add tags like a new node
+                        if (ctrl) {
+                            applyInputNumberChange();
+                        }
+                        Collection<Command> cmds = new LinkedList<>();
+                        addAddrToPrimitive(currentMouseNode, cmds);
+                    } else {
+                        inputStreet.setText(wayInRelationAddr.get(tagHighwayName));
+                        setSelectedWay(wayInRelationAddr);
+                    }
+                }
+            }
+        } else {
+            List<WaySegment> wss = mv.getNearestWaySegments(mousePos, OsmPrimitive::isSelectable);
+            for (WaySegment ws : wss) {
+                if (ws.way.get(tagHighway) != null && ws.way.get(tagHighwayName) != null)
+                    mouseOnExistingWays.add(ws.way);
+                else if (ws.way.get(tagBuilding) != null && ws.way.get(tagHouseNumber) == null)
+                    mouseOnExistingBuildingWays.add(ws.way);
+            }
+            if (mouseOnExistingWays.size() == 1) {
+                // clicked on existing highway => set new street name
+                inputStreet.setText(mouseOnExistingWays.get(0).get(tagHighwayName));
+                setSelectedWay(mouseOnExistingWays.get(0));
+                inputNumber.setText("");
+                setNewSelection(mouseOnExistingWays.get(0));
+            } else if (mouseOnExistingWays.isEmpty()) {
+                // clicked a non highway and not a node => add the new address
+                if (inputStreet.getText().isEmpty() || inputNumber.getText().isEmpty()) {
+                    Toolkit.getDefaultToolkit().beep();
+                } else {
+                    Collection<Command> cmds = new LinkedList<>();
+                    if (ctrl) {
+                        applyInputNumberChange();
+                    }
+                    if (tagPolygon.isSelected()) {
+                        addAddrToPolygon(mouseOnExistingBuildingWays, cmds);
+                    } else {
+                        Node n = createNewNode(e, cmds);
+                        addAddrToPrimitive(n, cmds);
+                    }
+                }
+            }
+        }
+    }
+
+    private Way findWayInRelationAddr(Node n) {
+        List<OsmPrimitive> l = n.getReferrers();
+        for (OsmPrimitive osm : l) {
+            if (osm instanceof Relation && osm.hasKey("type") && osm.get("type").equals(relationAddrType)) {
+                for (RelationMember rm : ((Relation) osm).getMembers()) {
+                    if (rm.getRole().equals(relationAddrStreetRole)) {
+                        OsmPrimitive osp = rm.getMember();
+                        if (osp instanceof Way && osp.hasKey(tagHighwayName)) {
+                            return (Way) osp;
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private void addAddrToPolygon(List<Way> mouseOnExistingBuildingWays, Collection<Command> cmds) {
+        for (Way w:mouseOnExistingBuildingWays) {
+            addAddrToPrimitive(w, cmds);
+        }
+    }
+
+    private void addAddrToPrimitive(OsmPrimitive osm, Collection<Command> cmds) {
+        // add the current tag addr:housenumber in node and member in relation (if so configured)
+        if (shift) {
+            try {
+                revertInputNumberChange();
+            } catch (NumberFormatException en) {
+                Main.warn("Unable to parse house number \"" + inputNumber.getText() + "\"");
+            }
+        }
+        cmds.add(new ChangePropertyCommand(osm, tagHouseNumber, inputNumber.getText()));
+        addStreetNameOrRelation(osm, cmds);
+        try {
+            applyInputNumberChange();
+            Command c = new SequenceCommand("Add node address", cmds);
+            Main.main.undoRedo.add(c);
+            setNewSelection(osm);
+        } catch (NumberFormatException en) {
+            Main.warn("Unable to parse house number \"" + inputNumber.getText() + "\"");
+        }
+    }
+
+    private Relation findRelationAddr(Way w) {
+        List<OsmPrimitive> l = w.getReferrers();
+        for (OsmPrimitive osm : l) {
+            if (osm instanceof Relation && osm.hasKey("type") && osm.get("type").equals(relationAddrType)) {
+                return (Relation) osm;
+            }
+        }
+        return null;
+    }
+
+    private void addStreetNameOrRelation(OsmPrimitive osm, Collection<Command> cmds) {
+        if (Main.pref.getBoolean("cadastrewms.addr.dontUseRelation", false)) {
+            cmds.add(new ChangePropertyCommand(osm, tagHouseStreet, inputStreet.getText()));
+        } else if (selectedWay != null) {
+            Relation selectedRelation = findRelationAddr(selectedWay);
+            // add the node to its relation
+            if (selectedRelation != null) {
+                RelationMember rm = new RelationMember(relationMemberHouse, osm);
+                Relation newRel = new Relation(selectedRelation);
+                newRel.addMember(rm);
+                cmds.add(new ChangeCommand(selectedRelation, newRel));
+            } else {
+                // create new relation
+                Relation newRel = new Relation();
+                newRel.put("type", relationAddrType);
+                newRel.put(relationAddrName, selectedWay.get(tagHighwayName));
+                newRel.addMember(new RelationMember(relationAddrStreetRole, selectedWay));
+                newRel.addMember(new RelationMember(relationMemberHouse, osm));
+                cmds.add(new AddCommand(newRel));
+            }
+        }
+    }
+
+    private static Node createNewNode(MouseEvent e, Collection<Command> cmds) {
+        // DrawAction.mouseReleased() but without key modifiers
+        Node n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
+        cmds.add(new AddCommand(n));
+        List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive::isSelectable);
+        Map<Way, List<Integer>> insertPoints = new HashMap<>();
+        for (WaySegment ws : wss) {
+            List<Integer> is;
+            if (insertPoints.containsKey(ws.way)) {
+                is = insertPoints.get(ws.way);
+            } else {
+                is = new ArrayList<>();
+                insertPoints.put(ws.way, is);
+            }
+
+            is.add(ws.lowerIndex);
+        }
+        Set<Pair<Node, Node>> segSet = new HashSet<>();
+        ArrayList<Way> replacedWays = new ArrayList<>();
+        ArrayList<Way> reuseWays = new ArrayList<>();
+        for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
+            Way w = insertPoint.getKey();
+            List<Integer> is = insertPoint.getValue();
+            Way wnew = new Way(w);
+            pruneSuccsAndReverse(is);
+            for (int i : is) {
+                segSet.add(Pair.sort(new Pair<>(w.getNode(i), w.getNode(i+1))));
+            }
+            for (int i : is) {
+                wnew.addNode(i + 1, n);
+            }
+            cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
+            replacedWays.add(insertPoint.getKey());
+            reuseWays.add(wnew);
+        }
+        adjustNode(segSet, n);
+
+        return n;
+    }
+
+    private static void adjustNode(Collection<Pair<Node, Node>> segs, Node n) {
+
+        switch (segs.size()) {
+        case 0:
+            return;
+        case 2:
+            // This computes the intersection between
+            // the two segments and adjusts the node position.
+            Iterator<Pair<Node, Node>> i = segs.iterator();
+            Pair<Node, Node> seg = i.next();
+            EastNorth A = seg.a.getEastNorth();
+            EastNorth B = seg.b.getEastNorth();
+            seg = i.next();
+            EastNorth C = seg.a.getEastNorth();
+            EastNorth D = seg.b.getEastNorth();
+
+            double u = det(B.east() - A.east(), B.north() - A.north(), C.east() - D.east(), C.north() - D.north());
+
+            // Check for parallel segments and do nothing if they are
+            // In practice this will probably only happen when a way has been duplicated
+
+            if (u == 0) return;
+
+            // q is a number between 0 and 1
+            // It is the point in the segment where the intersection occurs
+            // if the segment is scaled to lenght 1
+
+            double q = det(B.north() - C.north(), B.east() - C.east(), D.north() - C.north(), D.east() - C.east()) / u;
+            EastNorth intersection = new EastNorth(
+                    B.east() + q * (A.east() - B.east()),
+                    B.north() + q * (A.north() - B.north()));
+
+            int snapToIntersectionThreshold
+            = Main.pref.getInteger("edit.snap-intersection-threshold", 10);
+
+            // only adjust to intersection if within snapToIntersectionThreshold pixel of mouse click; otherwise
+            // fall through to default action.
+            // (for semi-parallel lines, intersection might be miles away!)
+            if (Main.map.mapView.getPoint(n).distance(Main.map.mapView.getPoint(intersection)) < snapToIntersectionThreshold) {
+                n.setEastNorth(intersection);
+                return;
+            }
+
+        default:
+            EastNorth P = n.getEastNorth();
+            seg = segs.iterator().next();
+            A = seg.a.getEastNorth();
+            B = seg.b.getEastNorth();
+            double a = P.distanceSq(B);
+            double b = P.distanceSq(A);
+            double c = A.distanceSq(B);
+            q = (a - b + c) / (2*c);
+            n.setEastNorth(new EastNorth(B.east() + q * (A.east() - B.east()), B.north() + q * (A.north() - B.north())));
+        }
+    }
+
+    static double det(double a, double b, double c, double d) {
+        return a * d - b * c;
+    }
+
+    private static void pruneSuccsAndReverse(List<Integer> is) {
+        HashSet<Integer> is2 = new HashSet<>();
+        for (int i : is) {
+            if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
+                is2.add(i);
+            }
+        }
+        is.clear();
+        is.addAll(is2);
+        Collections.sort(is);
+        Collections.reverse(is);
+    }
+
+    private static Cursor getCursor() {
+        try {
+            return ImageProvider.getCursor("crosshair", null);
+        } catch (RuntimeException e) {
+            Main.warn(e);
+        }
+        return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
+    }
+
+    private void applyInputNumberChange() {
+        Integer num = Integer.parseInt(inputNumber.getText());
+        if (plusOne.isSelected())
+            num = num + 1;
+        if (plusTwo.isSelected())
+            num = num + 2;
+        if (minusOne.isSelected() && num > 1)
+            num = num - 1;
+        if (minusTwo.isSelected() && num > 2)
+            num = num - 2;
+        inputNumber.setText(num.toString());
+    }
+
+    private void revertInputNumberChange() {
+        Integer num = Integer.parseInt(inputNumber.getText());
+        if (plusOne.isSelected())
+            num = num - 1;
+        if (plusTwo.isSelected())
+            num = num - 2;
+        if (minusOne.isSelected() && num > 1)
+            num = num + 1;
+        if (minusTwo.isSelected() && num > 2)
+            num = num + 2;
+        inputNumber.setText(num.toString());
+    }
+
+    private void createDialog() {
+        ImageIcon iconLink = ImageProvider.get(null, "Mf_relation");
+        link.setIcon(iconLink);
+        link.setEnabled(false);
+        JPanel p = new JPanel(new GridBagLayout());
+        JLabel number = new JLabel(tr("Next no"));
+        JLabel street = new JLabel(tr("Street"));
+        p.add(number, GBC.std().insets(0, 0, 0, 0));
+        p.add(inputNumber, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
+        p.add(street, GBC.std().insets(0, 0, 0, 0));
+        JPanel p2 = new JPanel(new GridBagLayout());
+        inputStreet.setEditable(false);
+        p2.add(inputStreet, GBC.std().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
+        p2.add(link, GBC.eol().insets(10, 0, 0, 0));
+        p.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
+        clearButton = new JButton("Clear");
+        clearButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                inputNumber.setText("");
+                inputStreet.setText("");
+                setSelectedWay((Way) null);
+            }
+        });
+        ButtonGroup bgIncremental = new ButtonGroup();
+        bgIncremental.add(plusOne);
+        bgIncremental.add(plusTwo);
+        bgIncremental.add(minusOne);
+        bgIncremental.add(minusTwo);
+        p.add(minusOne, GBC.std().insets(10, 0, 10, 0));
+        p.add(plusOne, GBC.std().insets(0, 0, 10, 0));
+        tagPolygon.setSelected(Main.pref.getBoolean("cadastrewms.addr.onBuilding", false));
+        tagPolygon.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent arg0) {
+                Main.pref.put("cadastrewms.addr.onBuilding", tagPolygon.isSelected());
+            }
+        });
+        p.add(tagPolygon, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
+        p.add(minusTwo, GBC.std().insets(10, 0, 10, 0));
+        p.add(plusTwo, GBC.std().insets(0, 0, 10, 0));
+        p.add(clearButton, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
+
+        final Object[] options = {};
+        final JOptionPane pane = new JOptionPane(p,
+                JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION,
+                null, options, null);
+        dialog = pane.createDialog(Main.parent, tr("Enter addresses"));
+        dialog.setModal(false);
+        dialog.setAlwaysOnTop(true);
+        dialog.addComponentListener(new ComponentAdapter() {
+            protected void rememberGeometry() {
+                Main.pref.put("cadastrewms.addr.bounds", dialog.getX()+","+dialog.getY()+","+dialog.getWidth()+","+dialog.getHeight());
+            }
+
+            @Override public void componentMoved(ComponentEvent e) {
+                rememberGeometry();
+            }
+
+            @Override public void componentResized(ComponentEvent e) {
+                rememberGeometry();
+            }
+        });
+        dialog.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent arg) {
+                Main.map.selectMapMode((MapMode) Main.map.getDefaultButtonAction());
+            }
+        });
+        String bounds = Main.pref.get("cadastrewms.addr.bounds", null);
+        if (bounds != null) {
+            String[] b = bounds.split(",");
+            dialog.setBounds(new Rectangle(
+                    Integer.parseInt(b[0]), Integer.parseInt(b[1]), Integer.parseInt(b[2]), Integer.parseInt(b[3])));
+        }
+    }
+
+    private void setSelectedWay(Way w) {
+        this.selectedWay = w;
+        if (w == null) {
+            link.setEnabled(false);
+        } else
+            link.setEnabled(true);
+        link.repaint();
+    }
+
+    private static void setNewSelection(OsmPrimitive osm) {
+        DataSet ds = Main.getLayerManager().getEditDataSet();
+        Collection<OsmPrimitive> newSelection = new LinkedList<>(ds.getSelected());
+        newSelection.clear();
+        newSelection.add(osm);
+        ds.setSelected(osm);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheControl.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheControl.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheControl.java	(revision 33637)
@@ -0,0 +1,235 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+
+/**
+ * This class handles the WMS layer cache mechanism. The design is oriented for a good performance (no
+ * wait status on GUI, fast saving even in big file). A separate thread is created for each WMS
+ * layer to not suspend the GUI until disk I/O is terminated (a file for the cache can take
+ * several MB's). If the cache file already exists, new images are just appended to the file
+ * (performance). Since we use the ObjectStream methods, it is required to modify the standard
+ * ObjectOutputStream in order to have objects appended readable (otherwise a stream header
+ * is inserted before each append and an exception is raised at objects read).
+ */
+public class CacheControl implements Runnable {
+
+    public static final String C_LAMBERT_CC_9Z = "CC";
+
+    public static final String C_UTM20N = "UTM";
+
+    public static class ObjectOutputStreamAppend extends ObjectOutputStream {
+        public ObjectOutputStreamAppend(OutputStream out) throws IOException {
+            super(out);
+        }
+
+        @Override
+        protected void writeStreamHeader() throws IOException {
+            reset();
+        }
+    }
+
+    public static boolean cacheEnabled = true;
+
+    public static int cacheSize = 500;
+
+    public WMSLayer wmsLayer;
+
+    private ArrayList<GeorefImage> imagesToSave = new ArrayList<>();
+    private Lock imagesLock = new ReentrantLock();
+
+    public boolean isCachePipeEmpty() {
+        imagesLock.lock();
+        boolean ret = imagesToSave.isEmpty();
+        imagesLock.unlock();
+        return ret;
+    }
+
+    public CacheControl(WMSLayer wmsLayer) {
+        cacheEnabled = Main.pref.getBoolean("cadastrewms.enableCaching", true);
+        this.wmsLayer = wmsLayer;
+        try {
+            cacheSize = Integer.parseInt(Main.pref.get("cadastrewms.cacheSize", String.valueOf(CadastrePreferenceSetting.DEFAULT_CACHE_SIZE)));
+        } catch (NumberFormatException e) {
+            cacheSize = CadastrePreferenceSetting.DEFAULT_CACHE_SIZE;
+        }
+        File path = new File(CadastrePlugin.cacheDir);
+        if (!path.exists())
+            path.mkdirs();
+        else // check directory capacity
+            checkDirSize(path);
+        new Thread(this).start();
+    }
+
+    private static void checkDirSize(File path) {
+        if (cacheSize != 0) {
+            long size = 0;
+            long oldestFileDate = Long.MAX_VALUE;
+            int oldestFile = 0;
+            File[] files = path.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                size += files[i].length();
+                if (files[i].lastModified() < oldestFileDate) {
+                    oldestFile = i;
+                    oldestFileDate = files[i].lastModified();
+                }
+            }
+            if (size > (long) cacheSize*1024*1024) {
+                Main.info("Delete oldest file  \""+ files[oldestFile].getName()
+                        + "\" in cache dir to stay under the limit of " + cacheSize + " MB.");
+                files[oldestFile].delete();
+                checkDirSize(path);
+            }
+        }
+    }
+
+    public boolean loadCacheIfExist() {
+        if (!CadastrePlugin.isCadastreProjection()) {
+            CadastrePlugin.askToChangeProjection();
+        }
+        File file = new File(CadastrePlugin.cacheDir + wmsLayer.getName() + "." + WMSFileExtension());
+        if (file.exists()) {
+            int reply = GuiHelper.runInEDTAndWaitAndReturn(new Callable<Integer>() {
+                @Override
+                public Integer call() throws Exception {
+                    JOptionPane pane = new JOptionPane(
+                            tr("Location \"{0}\" found in cache.\n"+
+                            "Load cache first ?\n"+
+                            "(No = new cache)", wmsLayer.getName()),
+                            JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, null);
+                    // this below is a temporary workaround to fix the "always on top" issue
+                    JDialog dialog = pane.createDialog(Main.parent, tr("Select Feuille"));
+                    CadastrePlugin.prepareDialog(dialog);
+                    dialog.setVisible(true);
+                    return (Integer) pane.getValue();
+                    // till here
+                }
+            });
+
+            if (reply == JOptionPane.OK_OPTION && loadCache(file, wmsLayer.getLambertZone())) {
+                return true;
+            } else {
+                delete(file);
+            }
+        }
+        return false;
+    }
+
+    public void deleteCacheFile() {
+        delete(new File(CadastrePlugin.cacheDir + wmsLayer.getName() + "." + WMSFileExtension()));
+    }
+
+    private static void delete(File file) {
+        Main.info("Delete file "+file);
+        if (file.exists())
+            file.delete();
+        while (file.exists()) { // wait until file is really gone (otherwise appends to existing one)
+            CadastrePlugin.safeSleep(500);
+        }
+    }
+
+    public boolean loadCache(File file, int currentLambertZone) {
+        boolean successfulRead = false;
+        try (
+            FileInputStream fis = new FileInputStream(file);
+            ObjectInputStream ois = new ObjectInputStream(fis);
+        ) {
+            successfulRead = wmsLayer.read(file, ois, currentLambertZone);
+        } catch (IOException | ClassNotFoundException ex) {
+            Main.error(ex);
+            GuiHelper.runInEDTAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error loading file.\nProbably an old version of the cache file."),
+                            tr("Error"), JOptionPane.ERROR_MESSAGE);
+                }
+            });
+            return false;
+        }
+        if (successfulRead && wmsLayer.isRaster()) {
+            // serialized raster bufferedImage hangs-up on Java6. Recreate them here
+            wmsLayer.getImage(0).image = RasterImageModifier.fixRasterImage(wmsLayer.getImage(0).image);
+        }
+        return successfulRead;
+    }
+
+    public synchronized void saveCache(GeorefImage image) {
+        imagesLock.lock();
+        this.imagesToSave.add(image);
+        this.notifyAll();
+        imagesLock.unlock();
+    }
+
+    /**
+     * Thread saving the grabbed images in background.
+     */
+    @Override
+    public synchronized void run() {
+        for (;;) {
+            imagesLock.lock();
+            int size = imagesToSave.size();
+            imagesLock.unlock();
+            if (size > 0) {
+                File file = new File(CadastrePlugin.cacheDir + wmsLayer.getName() + "." + WMSFileExtension());
+                try {
+                    if (file.exists()) {
+                        try (ObjectOutputStreamAppend oos = new ObjectOutputStreamAppend(
+                                new BufferedOutputStream(new FileOutputStream(file, true)))) {
+                            for (int i = 0; i < size; i++) {
+                                oos.writeObject(imagesToSave.get(i));
+                            }
+                        }
+                    } else {
+                        try (ObjectOutputStream oos = new ObjectOutputStream(
+                                new BufferedOutputStream(new FileOutputStream(file)))) {
+                            wmsLayer.write(file, oos);
+                            for (int i = 0; i < size; i++) {
+                                oos.writeObject(imagesToSave.get(i));
+                            }
+                        }
+                    }
+                } catch (IOException e) {
+                    Main.error(e);
+                }
+                imagesLock.lock();
+                for (int i = 0; i < size; i++) {
+                    imagesToSave.remove(0);
+                }
+                imagesLock.unlock();
+            }
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Main.error(e);
+            }
+        }
+    }
+
+    private String WMSFileExtension() {
+        String ext = String.valueOf(wmsLayer.getLambertZone() + 1);
+        if (CadastrePlugin.isLambert_cc9())
+            ext = C_LAMBERT_CC_9Z + ext;
+        else if (CadastrePlugin.isUtm_france_dom())
+            ext = C_UTM20N + ext;
+        return ext;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert4ZoneFilter.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert4ZoneFilter.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert4ZoneFilter.java	(revision 33637)
@@ -0,0 +1,55 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.util.Locale;
+
+import javax.swing.filechooser.FileFilter;
+
+public final class CacheFileLambert4ZoneFilter extends FileFilter {
+
+    /**
+     * Derived from ExtensionFileFilter writen by imi
+     */
+    private final String extension;
+    private final String description;
+
+    static final CacheFileLambert4ZoneFilter[] filters = {
+        new CacheFileLambert4ZoneFilter("1", tr("Lambert Zone {0} cache file (.{0})", 1)),
+        new CacheFileLambert4ZoneFilter("2", tr("Lambert Zone {0} cache file (.{0})", 2)),
+        new CacheFileLambert4ZoneFilter("3", tr("Lambert Zone {0} cache file (.{0})", 3)),
+        new CacheFileLambert4ZoneFilter("4", tr("Lambert Zone {0} cache file (.{0})", 4))
+        };
+
+    /**
+     * Construct an extension file filter by giving the extension to check after.
+     *
+     */
+    private CacheFileLambert4ZoneFilter(String extension, String description) {
+        this.extension = extension;
+        this.description = description;
+    }
+
+    public boolean acceptName(String filename) {
+        String name = filename.toLowerCase(Locale.FRANCE);
+        for (String ext : extension.split(",")) {
+            if (name.endsWith("." + ext))
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean accept(File pathname) {
+        if (pathname.isDirectory())
+            return true;
+        return acceptName(pathname.getName());
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert9ZoneFilter.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert9ZoneFilter.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileLambert9ZoneFilter.java	(revision 33637)
@@ -0,0 +1,60 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public final class CacheFileLambert9ZoneFilter extends FileFilter {
+
+    /**
+     * Derived from ExtensionFileFilter writen by imi
+     */
+    private final String extension;
+    private final String description;
+
+    public static CacheFileLambert9ZoneFilter[] filters = {
+        new CacheFileLambert9ZoneFilter("cc1", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 1)),
+        new CacheFileLambert9ZoneFilter("cc2", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 2)),
+        new CacheFileLambert9ZoneFilter("cc3", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 3)),
+        new CacheFileLambert9ZoneFilter("cc4", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 4)),
+        new CacheFileLambert9ZoneFilter("cc5", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 5)),
+        new CacheFileLambert9ZoneFilter("cc6", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 6)),
+        new CacheFileLambert9ZoneFilter("cc7", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 7)),
+        new CacheFileLambert9ZoneFilter("cc8", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 8)),
+        new CacheFileLambert9ZoneFilter("cc9", tr("Lambert CC9 Zone {0} cache file (.CC{0})", 9))
+        };
+
+    /**
+     * Construct an extension file filter by giving the extension to check after.
+     *
+     */
+    private CacheFileLambert9ZoneFilter(String extension, String description) {
+        this.extension = extension;
+        this.description = description;
+    }
+
+    public boolean acceptName(String filename) {
+        String name = filename.toLowerCase();
+        for (String ext : extension.split(",")) {
+            if (name.endsWith("." + ext))
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean accept(File pathname) {
+        if (pathname.isDirectory())
+            return true;
+        return acceptName(pathname.getName());
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileUTM20NFilter.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileUTM20NFilter.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CacheFileUTM20NFilter.java	(revision 33637)
@@ -0,0 +1,55 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+public final class CacheFileUTM20NFilter extends FileFilter {
+
+    /**
+     * Derived from ExtensionFileFilter writen by imi
+     */
+    private final String extension;
+    private final String description;
+
+    public static CacheFileUTM20NFilter[] filters = {
+        new CacheFileUTM20NFilter("utm1", tr("Guadeloupe Fort-Marigot cache file (.UTM1)")),
+        new CacheFileUTM20NFilter("utm2", tr("Guadeloupe Ste-Anne cache file (.UTM2)")),
+        new CacheFileUTM20NFilter("utm3", tr("Martinique Fort Desaix cache file (.UTM3)")),
+        new CacheFileUTM20NFilter("utm4", tr("Reunion RGR92 cache file (.UTM4)"))
+        };
+
+    /**
+     * Construct an extension file filter by giving the extension to check after.
+     *
+     */
+    private CacheFileUTM20NFilter(String extension, String description) {
+        this.extension = extension;
+        this.description = description;
+    }
+
+    public boolean acceptName(String filename) {
+        String name = filename.toLowerCase();
+        for (String ext : extension.split(",")) {
+            if (name.endsWith("." + ext))
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean accept(File pathname) {
+        if (pathname.isDirectory())
+            return true;
+        return acceptName(pathname.getName());
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreGrabber.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreGrabber.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreGrabber.java	(revision 33637)
@@ -0,0 +1,93 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.io.OsmTransferException;
+
+public class CadastreGrabber {
+
+    private CadastreInterface wmsInterface = new CadastreInterface();
+
+    public GeorefImage grab(WMSLayer wmsLayer, EastNorth lambertMin, EastNorth lambertMax)
+            throws IOException, OsmTransferException {
+        try {
+            URL url = null;
+            if (wmsLayer.isRaster())
+                url = getURLRaster(wmsLayer, lambertMin, lambertMax);
+            else
+                url = getURLVector(lambertMin, lambertMax);
+            BufferedImage img = grab(url);
+            if (img == null)
+                throw new OsmTransferException(url.toString());
+            ImageModifier imageModified;
+            if (wmsLayer.isRaster())
+                imageModified = new RasterImageModifier(img);
+            else
+                imageModified = new VectorImageModifier(img, false);
+            return new GeorefImage(imageModified.getBufferedImage(), lambertMin, lambertMax, wmsLayer);
+        } catch (MalformedURLException e) {
+            throw (IOException) new IOException(tr("CadastreGrabber: Illegal url.")).initCause(e);
+        }
+    }
+
+    private static URL getURLRaster(WMSLayer wmsLayer, EastNorth lambertMin, EastNorth lambertMax) throws MalformedURLException {
+        // GET /scpc/wms?version=1.1&request=GetMap&layers=CDIF:PMC@QH4480001701&format=image/png&bbox=-1186,0,13555,8830&width=576&height=345&exception=application/vnd.ogc.se_inimage&styles= HTTP/1.1
+        final int cRasterX = CadastrePlugin.imageWidth; // keep width constant and adjust width to original image proportions
+        String str = CadastreInterface.BASE_URL+"/scpc/wms?version=1.1&request=GetMap";
+        str += "&layers=CDIF:PMC@";
+        str += wmsLayer.getCodeCommune();
+        str += "&format=image/png";
+        //str += "&format=image/jpeg";
+        str += "&bbox=";
+        str += wmsLayer.eastNorth2raster(lambertMin, lambertMax);
+        str += "&width="+cRasterX+"&height="; // maximum allowed by wms server (576/345, 800/378, 1000/634)
+        str += (int) (cRasterX*(wmsLayer.communeBBox.max.getY() - wmsLayer.communeBBox.min.getY())/(wmsLayer.communeBBox.max.getX() - wmsLayer.communeBBox.min.getX()));
+        str += "&exception=application/vnd.ogc.se_inimage&styles="; // required for raster images
+        Main.info("URL="+str);
+        return new URL(str.replace(" ", "%20"));
+    }
+
+    private static URL buildURLVector(String layers, String styles,
+            int width, int height,
+            EastNorth lambertMin, EastNorth lambertMax) throws MalformedURLException {
+        String str = CadastreInterface.BASE_URL+"/scpc/wms?version=1.1&request=GetMap";
+        str += "&layers="+ layers;
+        str += "&format=image/png";
+        str += "&bbox="+lambertMin.east()+",";
+        str += lambertMin.north() + ",";
+        str += lambertMax.east() + ",";
+        str += lambertMax.north();
+        str += "&width="+width+"&height="+height;
+        str += "&exception=application/vnd.ogc.se_inimage"; // works also without (but slower ?)
+        str += "&styles=" + styles;
+        Main.info("URL="+str);
+        return new URL(str.replace(" ", "%20"));
+    }
+
+    private static URL getURLVector(EastNorth lambertMin, EastNorth lambertMax) throws MalformedURLException {
+        return buildURLVector(CadastrePlugin.grabLayers, CadastrePlugin.grabStyles,
+                CadastrePlugin.imageWidth, CadastrePlugin.imageHeight,
+                lambertMin, lambertMax);
+    }
+
+    private BufferedImage grab(URL url) throws IOException, OsmTransferException {
+        try (InputStream is = wmsInterface.getContent(url)) {
+            return ImageIO.read(is);
+        }
+    }
+
+    public CadastreInterface getWmsInterface() {
+        return wmsInterface;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreInterface.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreInterface.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreInterface.java	(revision 33637)
@@ -0,0 +1,607 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.validation.util.Entities;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.io.ProgressInputStream;
+import org.openstreetmap.josm.tools.GBC;
+
+public class CadastreInterface {
+    public boolean downloadCanceled;
+    private HttpURLConnection urlConn;
+
+    private String cookie;
+    private String interfaceRef;
+    private String lastWMSLayerName;
+    private URL searchFormURL;
+    private List<String> listOfCommunes = new ArrayList<>();
+    private List<String> listOfTA = new ArrayList<>();
+    static class PlanImage {
+        String name;
+        String ref;
+        PlanImage(String name, String ref) {
+            this.name = name;
+            this.ref = ref;
+        }
+    }
+
+    private List<PlanImage> listOfFeuilles = new ArrayList<>();
+    private long cookieTimestamp;
+
+    static final String BASE_URL = "https://www.cadastre.gouv.fr";
+    static final String C_IMAGE_FORMAT = "Cette commune est au format ";
+    static final String C_COMMUNE_LIST_START = "<select name=\"codeCommune\"";
+    static final String C_COMMUNE_LIST_END = "</select>";
+    static final String C_OPTION_LIST_START = "<option value=\"";
+    static final String C_OPTION_LIST_END = "</option>";
+    static final String C_BBOX_COMMUN_START = "new GeoBox(";
+    static final String C_BBOX_COMMUN_END = ")";
+
+    static final String C_INTERFACE_VECTOR = "afficherCarteCommune.do";
+    static final String C_INTERFACE_RASTER_TA = "afficherCarteTa.do";
+    static final String C_INTERFACE_RASTER_FEUILLE = "afficherCarteFeuille.do";
+    static final String C_IMAGE_LINK_START = "<a href=\"#\" class=\"raster\" onClick=\"popup('afficherCarteFeuille.do?f=";
+    static final String C_TA_IMAGE_LINK_START = "<a href=\"#\" class=\"raster\" onClick=\"popup('afficherCarteTa.do?f=";
+    static final String C_IMAGE_NAME_START = ">Feuille ";
+    static final String C_TA_IMAGE_NAME_START = "Tableau d'assemblage <strong>";
+
+    static final long COOKIE_EXPIRATION = 30 * 60 * 1000L; // 30 minutes expressed in milliseconds
+
+    static final int RETRIES_GET_COOKIE = 10; // 10 times every 3 seconds means 30 seconds trying to get a cookie
+
+    public boolean retrieveInterface(WMSLayer wmsLayer) throws DuplicateLayerException, WMSException {
+        if (wmsLayer.getName().isEmpty())
+            return false;
+        boolean isCookieExpired = isCookieExpired();
+        if (wmsLayer.getName().equals(lastWMSLayerName) && !isCookieExpired)
+            return true;
+        if (!wmsLayer.getName().equals(lastWMSLayerName))
+            interfaceRef = null;
+        // open the session with the French Cadastre web front end
+        downloadCanceled = false;
+        try {
+            if (cookie == null || isCookieExpired) {
+                getCookie();
+                interfaceRef = null;
+            }
+            if (cookie == null)
+                throw new WMSException(tr("Cannot open a new client session.\nServer in maintenance or temporary overloaded."));
+            if (interfaceRef == null) {
+                    getInterface(wmsLayer);
+                    this.lastWMSLayerName = wmsLayer.getName();
+            }
+            openInterface();
+        } catch (IOException e) {
+            Main.error(e);
+            GuiHelper.runInEDT(() ->
+                JOptionPane.showMessageDialog(Main.parent,
+                    tr("Town/city {0} not found or not available\n" +
+                            "or action canceled", wmsLayer.getLocation())));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     *
+     * @return true if a cookie is delivered by WMS and false is WMS is not opening a client session
+     *         (too many clients or in maintenance)
+     */
+    private void getCookie() throws IOException {
+        boolean success = false;
+        int retries = RETRIES_GET_COOKIE;
+        try {
+            searchFormURL = new URL(BASE_URL + "/scpc/accueil.do");
+            while (!success && retries > 0) {
+                urlConn = (HttpURLConnection) searchFormURL.openConnection();
+                urlConn.setRequestProperty("Connection", "close");
+                urlConn.setRequestMethod("GET");
+                urlConn.connect();
+                if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
+                    Main.info("GET "+searchFormURL);
+                    BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), StandardCharsets.UTF_8));
+                    while (in.readLine() != null) {
+                        // read the buffer otherwise we sent POST too early
+                    }
+                    success = true;
+                    // See https://bugs.openjdk.java.net/browse/JDK-8036017
+                    // When a cookie handler is setup, "Set-Cookie" header returns empty values
+                    CookieHandler cookieHandler = CookieHandler.getDefault();
+                    if (cookieHandler != null) {
+                        if (handleCookie(cookieHandler.get(searchFormURL.toURI(), new HashMap<String, List<String>>()).get("Cookie").get(0))) {
+                            break;
+                        }
+                    } else {
+                        String headerName;
+                        for (int i = 1; (headerName = urlConn.getHeaderFieldKey(i)) != null; i++) {
+                            if ("Set-Cookie".equals(headerName) && handleCookie(urlConn.getHeaderField(i))) {
+                                break;
+                            }
+                        }
+                    }
+                } else {
+                    Main.warn("Request to home page failed. Http error:"+urlConn.getResponseCode()+". Try again "+retries+" times");
+                    CadastrePlugin.safeSleep(3000);
+                    retries--;
+                }
+            }
+        } catch (MalformedURLException | URISyntaxException e) {
+            throw new IOException("Illegal url.", e);
+        }
+    }
+
+    private boolean handleCookie(String pCookie) {
+        cookie = pCookie;
+        if (cookie == null || cookie.isEmpty()) {
+            Main.warn("received empty cookie");
+            cookie = null;
+        } else {
+            int index = cookie.indexOf(';');
+            if (index > -1) {
+                cookie = cookie.substring(0, index);
+            }
+            cookieTimestamp = new Date().getTime();
+            Main.info("received cookie=" + cookie + " at " + new Date(cookieTimestamp));
+        }
+        return cookie != null;
+    }
+
+    public void resetCookie() {
+        lastWMSLayerName = null;
+        cookie = null;
+    }
+
+    public boolean isCookieExpired() {
+        long now = new Date().getTime();
+        if ((now - cookieTimestamp) > COOKIE_EXPIRATION) {
+            Main.info("cookie received at "+new Date(cookieTimestamp)+" expired (now is "+new Date(now)+")");
+            return true;
+        }
+        return false;
+    }
+
+    public void resetInterfaceRefIfNewLayer(String newWMSLayerName) {
+        if (!newWMSLayerName.equals(lastWMSLayerName)) {
+            interfaceRef = null;
+            cookie = null; // new since WMS server requires that we come back to the main form
+        }
+    }
+
+    private void setCookie() {
+        this.urlConn.setRequestProperty("Cookie", this.cookie);
+    }
+
+    public void setCookie(HttpURLConnection urlConn) {
+        urlConn.setRequestProperty("Cookie", this.cookie);
+    }
+
+    private void getInterface(WMSLayer wmsLayer) throws IOException, DuplicateLayerException {
+        // first attempt : search for given name without codeCommune
+        interfaceRef = postForm(wmsLayer, "");
+        // second attempt either from known codeCommune (e.g. from cache) or from ComboBox
+        if (interfaceRef == null) {
+            if (!wmsLayer.getCodeCommune().isEmpty()) {
+                // codeCommune is already known (from previous request or from cache on disk)
+                interfaceRef = postForm(wmsLayer, wmsLayer.getCodeCommune());
+            } else {
+                if (listOfCommunes.size() > 1) {
+                    // commune unknown, prompt the list of communes from server and try with codeCommune
+                    String selected = selectMunicipalityDialog();
+                    if (selected != null) {
+                        String newCodeCommune = selected.substring(1, selected.indexOf('>') - 2);
+                        String newLocation = selected.substring(selected.indexOf('>') + 1, selected.lastIndexOf(" - "));
+                        wmsLayer.setCodeCommune(newCodeCommune);
+                        wmsLayer.setLocation(newLocation);
+                        Main.pref.put("cadastrewms.codeCommune", newCodeCommune);
+                        Main.pref.put("cadastrewms.location", newLocation);
+                    }
+                    checkLayerDuplicates(wmsLayer);
+                    interfaceRef = postForm(wmsLayer, wmsLayer.getCodeCommune());
+                }
+                if (listOfCommunes.size() == 1 && wmsLayer.isRaster()) {
+                    // commune known but raster format. Select "Feuille" (non-georeferenced image) from list.
+                    int res = selectFeuilleDialog();
+                    if (res != -1) {
+                        wmsLayer.setCodeCommune(listOfFeuilles.get(res).name);
+                        checkLayerDuplicates(wmsLayer);
+                        interfaceRef = buildRasterFeuilleInterfaceRef(wmsLayer.getCodeCommune());
+                    }
+                }
+            }
+        }
+
+        if (interfaceRef == null)
+            throw new IOException("Town/city " + wmsLayer.getLocation() + " not found.");
+    }
+
+    private void openInterface() throws IOException {
+        try {
+            // finally, open the interface on server side giving access to the wms server
+            URL interfaceURL = new URL(BASE_URL + "/scpc/"+interfaceRef);
+            urlConn = (HttpURLConnection) interfaceURL.openConnection();
+            urlConn.setRequestMethod("GET");
+            setCookie();
+            urlConn.connect();
+            if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                throw new IOException("Cannot open Cadastre interface. GET response:"+urlConn.getResponseCode());
+            }
+            Main.info("GET "+interfaceURL);
+            BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), StandardCharsets.UTF_8));
+            // read the buffer otherwise we sent POST too early
+            StringBuilder lines = new StringBuilder();
+            String ln;
+            while ((ln = in.readLine()) != null) {
+                if (Main.isDebugEnabled()) {
+                    lines.append(ln);
+                }
+            }
+            if (Main.isDebugEnabled()) {
+                Main.debug(lines.toString());
+            }
+        } catch (MalformedURLException e) {
+            throw (IOException) new IOException(
+                "CadastreGrabber: Illegal url.").initCause(e);
+        }
+    }
+
+    /**
+     * Post the form with the commune name and check the returned answer which is embedded
+     * in HTTP XML packets. This function doesn't use an XML parser yet but that would be a good idea
+     * for the next releases.
+     * Two possibilities :
+     * - either the commune name matches and we receive an URL starting with "afficherCarteCommune.do" or
+     * - we don't receive a single answer but a list of possible values. This answer looks like:
+     *   <select name="codeCommune" class="long erreur" id="codeCommune">
+     *   <option value="">Choisir</option>
+     *   <option value="50061" >COLMARS - 04370</option>
+     *   <option value="QK066" >COLMAR - 68000</option>
+     *   </select>
+     * The returned string is the interface name used in further requests, e.g. "afficherCarteCommune.do?c=QP224"
+     * where QP224 is the code commune known by the WMS (or "afficherCarteTa.do?c=..." for raster images).
+     *
+     * @return retURL url to available code commune in the cadastre; "" if not found
+     */
+    private String postForm(WMSLayer wmsLayer, String codeCommune) throws IOException {
+        try {
+            listOfCommunes.clear();
+            listOfTA.clear();
+            // send a POST request with a city/town/village name
+            String content = "numerovoie=";
+            content += "&indiceRepetition=";
+            content += "&nomvoie=";
+            content += "&lieuDit=";
+            if (codeCommune.isEmpty()) {
+                content += "&ville=" + java.net.URLEncoder.encode(wmsLayer.getLocation(), "UTF-8");
+                content += "&codePostal=";
+            } else {
+                content += "&codeCommune=" + codeCommune;
+            }
+            content += "&codeDepartement=";
+            content += wmsLayer.getDepartement();
+            content += "&nbResultatParPage=10";
+            content += "&x=0&y=0";
+            searchFormURL = new URL(BASE_URL + "/scpc/rechercherPlan.do");
+            urlConn = (HttpURLConnection) searchFormURL.openConnection();
+            urlConn.setRequestMethod("POST");
+            urlConn.setDoOutput(true);
+            urlConn.setDoInput(true);
+            setCookie();
+            try (OutputStream wr = urlConn.getOutputStream()) {
+                wr.write(content.getBytes(StandardCharsets.UTF_8));
+                Main.info("POST "+content);
+                wr.flush();
+            }
+            String ln;
+            StringBuilder sb = new StringBuilder();
+            try (BufferedReader rd = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), StandardCharsets.UTF_8))) {
+                while ((ln = rd.readLine()) != null) {
+                    sb.append(ln);
+                }
+            }
+            String lines = sb.toString();
+            urlConn.disconnect();
+            if (lines != null) {
+                if (lines.indexOf(C_IMAGE_FORMAT) != -1) {
+                    int i = lines.indexOf(C_IMAGE_FORMAT);
+                    int j = lines.indexOf('.', i);
+                    wmsLayer.setRaster("image".equals(lines.substring(i+C_IMAGE_FORMAT.length(), j)));
+                }
+                if (!wmsLayer.isRaster() && lines.indexOf(C_INTERFACE_VECTOR) != -1) {  // "afficherCarteCommune.do"
+                    // shall be something like: interfaceRef = "afficherCarteCommune.do?c=X2269";
+                    lines = lines.substring(lines.indexOf(C_INTERFACE_VECTOR), lines.length());
+                    lines = lines.substring(0, lines.indexOf('\''));
+                    lines = Entities.unescape(lines);
+                    Main.info("interface ref.:"+lines);
+                    return lines;
+                } else if (wmsLayer.isRaster() && lines.indexOf(C_INTERFACE_RASTER_TA) != -1) { // "afficherCarteTa.do"
+                    // list of values parsed in listOfFeuilles (list all non-georeferenced images)
+                    lines = getFeuillesList();
+                    if (!downloadCanceled) {
+                        parseFeuillesList(lines);
+                        if (!listOfFeuilles.isEmpty()) {
+                            int res = selectFeuilleDialog();
+                            if (res != -1) {
+                                wmsLayer.setCodeCommune(listOfFeuilles.get(res).name);
+                                checkLayerDuplicates(wmsLayer);
+                                interfaceRef = buildRasterFeuilleInterfaceRef(wmsLayer.getCodeCommune());
+                                wmsLayer.setCodeCommune(listOfFeuilles.get(res).ref);
+                                lines = buildRasterFeuilleInterfaceRef(listOfFeuilles.get(res).ref);
+                                lines = Entities.unescape(lines);
+                                Main.info("interface ref.:"+lines);
+                                return lines;
+                            }
+                        }
+                    }
+                    return null;
+                } else if (lines.indexOf(C_COMMUNE_LIST_START) != -1 && lines.indexOf(C_COMMUNE_LIST_END) != -1) {
+                    // list of values parsed in listOfCommunes
+                    int i = lines.indexOf(C_COMMUNE_LIST_START);
+                    int j = lines.indexOf(C_COMMUNE_LIST_END, i);
+                    parseCommuneList(lines.substring(i, j));
+                }
+            }
+        } catch (MalformedURLException e) {
+            throw (IOException) new IOException("Illegal url.").initCause(e);
+        } catch (DuplicateLayerException e) {
+            Main.error(e);
+        }
+        return null;
+    }
+
+    private void parseCommuneList(String input) {
+        if (input.indexOf(C_OPTION_LIST_START) != -1) {
+            while (input.indexOf("<option value=\"") != -1) {
+                int i = input.indexOf(C_OPTION_LIST_START);
+                int j = input.indexOf(C_OPTION_LIST_END, i+C_OPTION_LIST_START.length());
+                int k = input.indexOf('"', i+C_OPTION_LIST_START.length());
+                if (j != -1 && k > (i + C_OPTION_LIST_START.length())) {
+                    String lov = input.substring(i+C_OPTION_LIST_START.length()-1, j);
+                    if (lov.indexOf('>') != -1) {
+                        Main.info("parse "+lov);
+                        listOfCommunes.add(lov);
+                    } else
+                        Main.error("unable to parse commune string:"+lov);
+                }
+                input = input.substring(j+C_OPTION_LIST_END.length());
+            }
+        }
+    }
+
+    private String getFeuillesList() {
+        // get all images in one html page
+        String ln = null;
+        StringBuilder lines = new StringBuilder();
+        HttpURLConnection urlConn2 = null;
+        try {
+            URL getAllImagesURL = new URL(BASE_URL + "/scpc/listerFeuillesParcommune.do?keepVolatileSession=&offset=2000");
+            urlConn2 = (HttpURLConnection) getAllImagesURL.openConnection();
+            setCookie(urlConn2);
+            urlConn2.connect();
+            Main.info("GET "+getAllImagesURL);
+            try (BufferedReader rd = new BufferedReader(new InputStreamReader(urlConn2.getInputStream(), StandardCharsets.UTF_8))) {
+                while ((ln = rd.readLine()) != null) {
+                    lines.append(ln);
+                }
+            }
+            urlConn2.disconnect();
+        } catch (IOException e) {
+            listOfFeuilles.clear();
+            Main.error(e);
+        }
+        return lines.toString();
+    }
+
+    private void parseFeuillesList(String input) {
+        listOfFeuilles.clear();
+        // get "Tableau d'assemblage"
+        String inputTA = input;
+        if (Main.pref.getBoolean("cadastrewms.useTA", false)) {
+            while (inputTA.indexOf(C_TA_IMAGE_LINK_START) != -1) {
+                inputTA = inputTA.substring(inputTA.indexOf(C_TA_IMAGE_LINK_START) + C_TA_IMAGE_LINK_START.length());
+                String refTA = inputTA.substring(0, inputTA.indexOf('\''));
+                String nameTA = inputTA.substring(inputTA.indexOf(C_TA_IMAGE_NAME_START) + C_TA_IMAGE_NAME_START.length());
+                nameTA = nameTA.substring(0, nameTA.indexOf('<'));
+                listOfFeuilles.add(new PlanImage(nameTA, refTA));
+            }
+        }
+        // get "Feuilles"
+        while (input.indexOf(C_IMAGE_LINK_START) != -1) {
+            input = input.substring(input.indexOf(C_IMAGE_LINK_START)+C_IMAGE_LINK_START.length());
+            String refFeuille = input.substring(0, input.indexOf('\''));
+            String nameFeuille = input.substring(
+                    input.indexOf(C_IMAGE_NAME_START)+C_IMAGE_NAME_START.length(),
+                    input.indexOf(" -"));
+            listOfFeuilles.add(new PlanImage(nameFeuille, refFeuille));
+        }
+    }
+
+    private String selectMunicipalityDialog() {
+        JPanel p = new JPanel(new GridBagLayout());
+        String[] communeList = new String[listOfCommunes.size() + 1];
+        communeList[0] = tr("Choose from...");
+        for (int i = 0; i < listOfCommunes.size(); i++) {
+            communeList[i + 1] = listOfCommunes.get(i).substring(listOfCommunes.get(i).indexOf('>')+1);
+        }
+        JComboBox<String> inputCommuneList = new JComboBox<>(communeList);
+        p.add(inputCommuneList, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 0, 0, 0));
+        JOptionPane pane = new JOptionPane(p, JOptionPane.INFORMATION_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null);
+        // this below is a temporary workaround to fix the "always on top" issue
+        JDialog dialog = pane.createDialog(Main.parent, tr("Select commune"));
+        CadastrePlugin.prepareDialog(dialog);
+        dialog.setVisible(true);
+        // till here
+        if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
+            return null;
+        return listOfCommunes.get(inputCommuneList.getSelectedIndex()-1);
+    }
+
+    private int selectFeuilleDialog() {
+        JPanel p = new JPanel(new GridBagLayout());
+        List<String> imageNames = new ArrayList<>();
+        for (PlanImage src : listOfFeuilles) {
+            imageNames.add(src.name);
+        }
+        JComboBox<String> inputFeuilleList = new JComboBox<>(imageNames.toArray(new String[]{}));
+        p.add(inputFeuilleList, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 0, 0, 0));
+        JOptionPane pane = new JOptionPane(p, JOptionPane.INFORMATION_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null);
+        // this below is a temporary workaround to fix the "always on top" issue
+        JDialog dialog = pane.createDialog(Main.parent, tr("Select Feuille"));
+        CadastrePlugin.prepareDialog(dialog);
+        dialog.setVisible(true);
+        // till here
+        if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
+            return -1;
+        return inputFeuilleList.getSelectedIndex();
+    }
+
+    private static String buildRasterFeuilleInterfaceRef(String codeCommune) {
+        return C_INTERFACE_RASTER_FEUILLE + "?f=" + codeCommune;
+    }
+
+    /**
+     * Retrieve the bounding box size in pixels of the whole commune (point 0,0 at top, left corner)
+     * and store it in given wmsLayer
+     * In case of raster image, we also check in the same http request if the image is already georeferenced
+     * and store the result in the wmsLayer as well.
+     * @param wmsLayer the WMSLayer where the commune data and images are stored
+     */
+    public void retrieveCommuneBBox(WMSLayer wmsLayer) throws IOException {
+        if (interfaceRef == null)
+            return;
+        // send GET opening normally the small window with the commune overview
+        String content = BASE_URL + "/scpc/" + interfaceRef;
+        content += "&dontSaveLastForward&keepVolatileSession=";
+        searchFormURL = new URL(content);
+        urlConn = (HttpURLConnection) searchFormURL.openConnection();
+        urlConn.setRequestMethod("GET");
+        setCookie();
+        urlConn.connect();
+        if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
+            throw new IOException("Cannot get Cadastre response.");
+        }
+        Main.info("GET "+searchFormURL);
+        String ln;
+        StringBuilder sb = new StringBuilder();
+        try (BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), StandardCharsets.UTF_8))) {
+            while ((ln = in.readLine()) != null) {
+                sb.append(ln);
+            }
+        }
+        urlConn.disconnect();
+        String line = sb.toString();
+        parseBBoxCommune(wmsLayer, line);
+        if (wmsLayer.isRaster() && !wmsLayer.isAlreadyGeoreferenced()) {
+            parseGeoreferences(wmsLayer, line);
+        }
+    }
+
+    private static void parseBBoxCommune(WMSLayer wmsLayer, String input) {
+        if (input.indexOf(C_BBOX_COMMUN_START) != -1) {
+            input = input.substring(input.indexOf(C_BBOX_COMMUN_START));
+            int i = input.indexOf(',');
+            double minx = Double.parseDouble(input.substring(C_BBOX_COMMUN_START.length(), i));
+            int j = input.indexOf(',', i+1);
+            double miny = Double.parseDouble(input.substring(i+1, j));
+            int k = input.indexOf(',', j+1);
+            double maxx = Double.parseDouble(input.substring(j+1, k));
+            int l = input.indexOf(C_BBOX_COMMUN_END, k+1);
+            double maxy = Double.parseDouble(input.substring(k+1, l));
+            wmsLayer.setCommuneBBox(new EastNorthBound(new EastNorth(minx, miny), new EastNorth(maxx, maxy)));
+        }
+    }
+
+    private static void parseGeoreferences(WMSLayer wmsLayer, String input) {
+        /* commented since cadastre WMS changes mid july 2013
+         * until new GeoBox coordinates parsing is solved */
+//        if (input.lastIndexOf(cBBoxCommunStart) != -1) {
+//            input = input.substring(input.lastIndexOf(cBBoxCommunStart));
+//            input = input.substring(input.indexOf(cBBoxCommunEnd)+cBBoxCommunEnd.length());
+//            int i = input.indexOf(",");
+//            int j = input.indexOf(",", i+1);
+//            String str = input.substring(i+1, j);
+//            double unknown_yet = tryParseDouble(str);
+//            int j_ = input.indexOf(",", j+1);
+//            double angle = Double.parseDouble(input.substring(j+1, j_));
+//            int k = input.indexOf(",", j_+1);
+//            double scale_origin = Double.parseDouble(input.substring(j_+1, k));
+//            int l = input.indexOf(",", k+1);
+//            double dpi = Double.parseDouble(input.substring(k+1, l));
+//            int m = input.indexOf(",", l+1);
+//            double fX = Double.parseDouble(input.substring(l+1, m));
+//            int n = input.indexOf(",", m+1);
+//            double fY = Double.parseDouble(input.substring(m+1, n));
+//            int o = input.indexOf(",", n+1);
+//            double X0 = Double.parseDouble(input.substring(n+1, o));
+//            int p = input.indexOf(",", o+1);
+//            double Y0 = Double.parseDouble(input.substring(o+1, p));
+//            if (X0 != 0.0 && Y0 != 0) {
+//                wmsLayer.setAlreadyGeoreferenced(true);
+//                wmsLayer.fX = fX;
+//                wmsLayer.fY = fY;
+//                wmsLayer.angle = angle;
+//                wmsLayer.X0 = X0;
+//                wmsLayer.Y0 = Y0;
+//            }
+//            Main.info("parse georef:"+unknown_yet+","+angle+","+scale_origin+","+dpi+","+fX+","+fY+","+X0+","+Y0);
+//        }
+    }
+
+    private static void checkLayerDuplicates(WMSLayer wmsLayer) throws DuplicateLayerException {
+        if (Main.map != null) {
+            for (Layer l : Main.getLayerManager().getLayers()) {
+                if (l instanceof WMSLayer && l.getName().equals(wmsLayer.getName()) && (!l.equals(wmsLayer))) {
+                    Main.info("Try to grab into a new layer when "+wmsLayer.getName()+" is already opened.");
+                    // remove the duplicated layer
+                    Main.getLayerManager().removeLayer(wmsLayer);
+                    throw new DuplicateLayerException();
+                }
+            }
+        }
+    }
+
+    public void cancel() {
+        if (urlConn != null) {
+            urlConn.setConnectTimeout(1);
+            urlConn.setReadTimeout(1);
+        }
+        downloadCanceled = true;
+        lastWMSLayerName = null;
+    }
+
+    public InputStream getContent(URL url) throws IOException, OsmTransferException {
+        urlConn = (HttpURLConnection) url.openConnection();
+        urlConn.setRequestProperty("Connection", "close");
+        urlConn.setRequestMethod("GET");
+        setCookie();
+        return new ProgressInputStream(urlConn, NullProgressMonitor.INSTANCE);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePlugin.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePlugin.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePlugin.java	(revision 33637)
@@ -0,0 +1,517 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.io.session.SessionReader.registerSessionLayerImporter;
+import static org.openstreetmap.josm.io.session.SessionWriter.registerSessionLayerExporter;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JDialog;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.data.projection.AbstractProjection;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.gui.IconToggleButton;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.map.MapPreference;
+import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+/**
+ * Plugin to access the French Cadastre WMS server at <a href="https://www.cadastre.gouv.fr">
+ * www.cadastre.gouv.fr</a>.<br>
+ * This WMS server requires some specific handling like retrieving a cookie for a
+ * limitation in case of no activity, or the request to the server shall provide
+ * a city/town/village code.
+ *
+ * @author Pieren &lt;pieren3@gmail.com&gt;,
+ *         &lt;matthieu.lochegnies@gmail.com&gt; for the extension to codeCommune
+ *
+ * @version 2.6
+ * <br>History:
+ * <br>0.1 17-Jun-2008 first prototype using a first Lambert projection impl. in core
+ * <br>0.2 22-Jun-2008 first stable version
+ * <br>0.3 24-Jun-2008 add code departement
+ * <br>0.4 06-Jul-2008 - add images scales, icons, menu items disabling
+ * <br>                - remove dependencies of wmsplugin
+ * <br>                - add option to force a Lambert zone (for median locations)
+ * <br>                - add auto-sourcing
+ * <br>0.5 16-Aug-2008 - add transparency in layer (allowing multiple wms layers displayed together)
+ * <br>                - no overlapping of grabbed images if transparency is enabled
+ * <br>                - set one layer per location
+ * <br>                - use utf-8 charset in POST request to server
+ * <br>                - improve the preferences setting dialog
+ * <br>                - cancel the current download is now possible
+ * <br>                - add automatic images caching and load on request (+ manage cache directory size)
+ * <br>                - enable auto-sourcing only if a WMS layer is used
+ * <br>0.6 18-Aug-2008 - suppress the null-exception message after the dialog 'open a layer first'
+ * <br>                - process the overlapping images when cache is loaded from disk
+ * <br>                - save the last 'new location request' text again in preferences
+ * <br>                - avoid duplicate layers with same name
+ * <br>                - set text input for new locations in upper case
+ * <br>                - the cache directory is configurable in "cadastrewms.cacheDir"
+ * <br>                - improve configuration change updates
+ * <br>0.7 24-Aug-2008 - mask images only if transparency enabled
+ * <br>                - validate projection name by Lambert.toString() method
+ * <br>0.8 25-Jan-2009 - display returned list of communes if direct name is not recognized by server
+ * <br>                - new possible grab factor of 100 square meters fixed size
+ * <br>                - minor fixes due to changes in JOSM core classes
+ * <br>                - first draft of raster image support
+ * <br>0.9 05-Feb-2009 - grab vectorized full commune bbox, save in file, convert to OSM way
+ *                       and simplify
+ * <br>1.0 18-Feb-2009 - fix various bugs in color management and preference dialog
+ * <br>                - increase PNG picture size requested to WMS (800x1000)
+ * <br>                - set 4th grab scale fixed size configurable (from 25 to 1000 meters)
+ * <br>1.1 11-Jun-2009 - fixed a null exception error when trying to displace a vectorized layer
+ * <br>                - propose to use shortcut F11 for grabbing
+ * <br>1.2 16-Aug-2009 - implementation of raster image grabbing, cropping and georeferencing (not the
+ * <br>                  overview rasters (Tableau d'assemblage) but directly small units (Feuille)
+ * <br>1.3 23-Aug-2009 - improve georeferencing action cancellation
+ * <br>                - fixed bug of raster image loaded from cache not working on Java1.6
+ * <br>                - improve mouse click bounce detection during georeferencing process
+ * <br>1.4 25-Oct-2009 - add support for new Lambert CC 9 Zones projection
+ * <br>                - add optional crosspieces display on raster image layers
+ * <br>                - add automatic raster images georeferencing when WMS provides data
+ * <br>                - re-implement manual adjustment mode in raster image layer
+ * <br>1.5 21-Nov-2009 - major changes in projection in core : no magical zone prediction anymore for
+ *                       Lambert 4 and 9 zones; grid translation implemented for Lambert 4 zones;
+ *                       support of subprojections in preferences for zones setting and UTM20N
+ * <br>                - removed autosourcing of empty new nodes
+ * <br>1.6 28-Nov-2009 - Fix minor issues if Grab is called without layer (possible since projection rework)
+ * <br>1.7 12-Dec-2009 - Change URL's changes for cookie and downgrade imgs resolution due to WMS changes
+ * <br>1.8 11-Mar-2010 - filter the mouse button 1 during georeferencing
+ * <br>                - retry if getting a new cookie failed (10 times during 30 seconds)
+ * <br>                - cookie expiration automatically detected and renewed (after 30 minutes)
+ * <br>                - proper WMS layer cleanup at destruction (workaround for memory leak)
+ * <br>                - new cache format (v3) storing original image and cropped image bbox + angle
+ * <br>                - new cache format (v4) storing original image size for later rotation
+ * <br>                - cache files read compatible with previous formats
+ * <br>                - raster image rotation issues fixed, now using shift+ctrl key instead of ctrl
+ * <br>                - raster image adjustment using default system menu modifier (ctrl for windows) for Mac support
+ * <br>                - image resolution configurable (high, medium, low) like the online interface
+ * <br>                - layer selection configurable for vectorized images
+ * <br>                - improved download cancellation
+ * <br>                - from Erik Amzallag:
+ * <br>                -     possibility to modify the auto-sourcing text just before upload
+ * <br>                - from Clément Ménier:
+ * <br>                -     new option allowing an auto-selection of the first cadastre layer for grab
+ * <br>                -     non-modal JDialog in MenuActionGrabPlanImage
+ * <br>                -     new options in the image filter (bilinear, bicubic)
+ * <br>1.9 05-Apr-2010 - added a scroll bar in preferences
+ * <br>                - download cancellation improved
+ * <br>                - last deployment for Java1.5 compatibility
+ * <br>2.0 07-Jul-2010 - update projection for "La Reunion" departement to RGR92, UTM40S.
+ * <br>                - add 'departement' as option in the municipality selection
+ * <br>                - fixed bug in cache directory size control (and disabled by default)
+ * <br>                - add map mode for addressing
+ * <br>                - from Nicolas Dumoulin:
+ * <br>                -     add "tableau d'assemblage" in raster images for georeferencing (as option)
+ * <br>2.1 14-Jan-2011 - add GrabThread moving the grab to a separate thread
+ * <br>                - the divided BBox mode starts from the central square and loads the next in a spiral
+ * <br>                - move the grabber from CadastrPlugin singleton to each wmsLayer instance to allow grabbing
+ *                       of multiple municipalities in parallel.
+ * <br>2.2 01-Jul-2011 - replace deprecated Main.proj by newest Main.getProjection()
+ * <br>                - fix list of raster images (Feuilles) parsing failing after a Cadastre server change/maintenance
+ * <br>2.3 11-Jan-2013 - add various improvements from Don-Vip (Vincent Privat) trac #8175, #8229 and #5626.
+ * <br>2.4 27-Jun-2013 - fix raster image georeferencing issues. Add new MenuActionRefineGeoRef for a new georeferencing
+ *                       of already referenced plan image.
+ * <br>2.5 06-Aug-2013 - fix transparency issue on new raster images. Temporary disable georeferences parsing not
+ *                       working on new cadastre WMS.
+ * <br>                - workaround on address help tool when switching to full screen
+ * <br>                - improvement when clicking on existing node address street in mode relation
+ * <br>                - option to simplify raster images in 2 bits colors (like images served in the past).
+ * <br>2.6 10-Sep-2013 - add JOSM "sessions" feature support (list of layers stored in a file)
+ */
+public class CadastrePlugin extends Plugin {
+    static String VERSION = "2.6";
+
+    static JMenu cadastreJMenu;
+
+    public static String source = "";
+
+    // true if the checkbox "auto-sourcing" is set in the plugin menu
+    public static boolean autoSourcing = false;
+
+    // true when the plugin is first used, e.g. grab from WMS or download cache file
+    public static boolean pluginUsed = false;
+
+    public static String cacheDir = null;
+
+    public static boolean alterColors = false;
+
+    public static boolean backgroundTransparent = false;
+
+    public static float transparency = 1.0f;
+
+    public static boolean drawBoundaries = false;
+
+    public static int imageWidth, imageHeight;
+
+    public static String grabLayers, grabStyles = null;
+
+    private static boolean menuEnabled = false;
+
+    private static String LAYER_BULDINGS = "CDIF:LS2";
+    private static String STYLE_BUILDING = "LS2_90";
+    private static String LAYER_WATER = "CDIF:LS3";
+    private static String STYLE_WATER = "LS3_90";
+    private static String LAYER_SYMBOL = "CDIF:LS1";
+    private static String STYLE_SYMBOL = "LS1_90";
+    private static String LAYER_PARCELS = "CDIF:PARCELLE";
+    private static String STYLE_PARCELS = "PARCELLE_90";
+    private static String LAYER_NUMERO = "CDIF:NUMERO";
+    private static String STYLE_NUMERO = "NUMERO_90";
+    private static String LAYER_LABEL = "CDIF:PT3,CDIF:PT2,CDIF:PT1";
+    private static String STYLE_LABEL = "PT3_90,PT2_90,PT1_90";
+    private static String LAYER_LIEUDIT = "CDIF:LIEUDIT";
+    private static String STYLE_LIEUDIT = "LIEUDIT_90";
+    private static String LAYER_SECTION = "CDIF:SUBSECTION,CDIF:SECTION";
+    private static String STYLE_SECTION = "SUBSECTION_90,SECTION_90";
+    private static String LAYER_COMMUNE = "CDIF:COMMUNE";
+    private static String STYLE_COMMUNE = "COMMUNE_90";
+
+    /**
+     * Creates the plugin and setup the default settings if necessary.
+     * @param info plugin information
+     */
+    public CadastrePlugin(PluginInformation info) {
+        super(info);
+        Main.info("Pluging cadastre-fr v"+VERSION+" started...");
+        initCacheDir();
+
+        refreshConfiguration();
+
+        UploadAction.registerUploadHook(new CheckSourceUploadHook());
+
+        registerSessionLayerExporter(WMSLayer.class, CadastreSessionExporter.class);
+        registerSessionLayerImporter("cadastre-fr", CadastreSessionImporter.class);
+    }
+
+    private static void initCacheDir() {
+        if (Main.pref.get("cadastrewms.cacheDir").isEmpty()) {
+            cacheDir = new File(Main.pref.getCacheDirectory(), "cadastrewms").getAbsolutePath();
+        } else {
+            cacheDir = Main.pref.get("cadastrewms.cacheDir");
+        }
+        if (cacheDir.charAt(cacheDir.length()-1) != File.separatorChar)
+            cacheDir += File.separatorChar;
+        Main.info("current cache directory: "+cacheDir);
+    }
+
+    public static void refreshMenu() {
+        MainMenu menu = Main.main.menu;
+
+        if (cadastreJMenu == null) {
+            cadastreJMenu = menu.addMenu("Cadastre", tr("Cadastre"), KeyEvent.VK_C, menu.getDefaultMenuPos(), ht("/Plugin/CadastreFr"));
+            JosmAction grab = new MenuActionGrab();
+            JMenuItem menuGrab = new JMenuItem(grab);
+            KeyStroke ks = grab.getShortcut().getKeyStroke();
+            if (ks != null) {
+                menuGrab.setAccelerator(ks);
+            }
+            JMenuItem menuActionGrabPlanImage = new JMenuItem(new MenuActionGrabPlanImage());
+            JMenuItem menuSettings = new JMenuItem(new MenuActionNewLocation());
+            final JCheckBoxMenuItem menuSource = new JCheckBoxMenuItem(tr("Auto sourcing"));
+            menuSource.setSelected(autoSourcing);
+            menuSource.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(ActionEvent ev) {
+                    Main.pref.put("cadastrewms.autosourcing", menuSource.isSelected());
+                    autoSourcing = menuSource.isSelected();
+                }
+            });
+
+            //JMenuItem menuResetCookie = new JMenuItem(new MenuActionResetCookie());
+            //JMenuItem menuLambertZone = new JMenuItem(new MenuActionLambertZone());
+            JMenuItem menuLoadFromCache = new JMenuItem(new MenuActionLoadFromCache());
+            // temporary disabled:
+            //JMenuItem menuActionBoundaries = new JMenuItem(new MenuActionBoundaries());
+            //JMenuItem menuActionBuildings = new JMenuItem(new MenuActionBuildings());
+
+            cadastreJMenu.add(menuGrab);
+            cadastreJMenu.add(menuActionGrabPlanImage);
+            cadastreJMenu.add(menuSettings);
+            cadastreJMenu.add(menuSource);
+            //cadastreJMenu.add(menuResetCookie); not required any more
+            //cadastreJMenu.add(menuLambertZone);
+            //if (Main.pref.getBoolean("cadastrewms.buildingsMenu", false))
+            //    cadastreJMenu.add(menuActionBuildings);
+            cadastreJMenu.add(menuLoadFromCache);
+            // all SVG features disabled until official WMS is released
+            //cadastreJMenu.add(menuActionBoundaries);
+            cadastreJMenu.add(new JMenuItem(new MenuActionOpenPreferences()));
+        }
+        setEnabledAll(menuEnabled);
+    }
+
+    public static void refreshConfiguration() {
+        source = checkSourceMillesime();
+        autoSourcing = Main.pref.getBoolean("cadastrewms.autosourcing", true);
+        alterColors = Main.pref.getBoolean("cadastrewms.alterColors");
+        drawBoundaries = Main.pref.getBoolean("cadastrewms.drawBoundaries", false);
+        if (alterColors) {
+            backgroundTransparent = Main.pref.getBoolean("cadastrewms.backgroundTransparent");
+            transparency = Float.parseFloat(Main.pref.get("cadastrewms.brightness", "1.0f"));
+        } else {
+            backgroundTransparent = false;
+            transparency = 1.0f;
+        }
+        String currentResolution = Main.pref.get("cadastrewms.resolution", "high");
+        if (currentResolution.equals("high")) {
+            imageWidth = 1000; imageHeight = 800;
+        } else if (currentResolution.equals("medium")) {
+            imageWidth = 800; imageHeight = 600;
+        } else {
+            imageWidth = 600; imageHeight = 400;
+        }
+        refreshLayersURL();
+        refreshMenu();
+    }
+
+    private static void refreshLayersURL() {
+        grabLayers = "";
+        grabStyles = "";
+        int countLayers = 0;
+        if (Main.pref.getBoolean("cadastrewms.layerWater", true)) {
+            grabLayers += LAYER_WATER + ",";
+            grabStyles += STYLE_WATER + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerBuilding", true)) {
+            grabLayers += LAYER_BULDINGS + ",";
+            grabStyles += STYLE_BUILDING + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerSymbol", true)) {
+            grabLayers += LAYER_SYMBOL + ",";
+            grabStyles += STYLE_SYMBOL + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerParcel", true)) {
+            grabLayers += LAYER_PARCELS + ",";
+            grabStyles += STYLE_PARCELS + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerNumero", true)) {
+            grabLayers += LAYER_NUMERO + ",";
+            grabStyles += STYLE_NUMERO + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerLabel", true)) {
+            grabLayers += LAYER_LABEL + ",";
+            grabStyles += STYLE_LABEL + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerLieudit", true)) {
+            grabLayers += LAYER_LIEUDIT + ",";
+            grabStyles += STYLE_LIEUDIT + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerSection", true)) {
+            grabLayers += LAYER_SECTION + ",";
+            grabStyles += STYLE_SECTION + ",";
+            countLayers++;
+        }
+        if (Main.pref.getBoolean("cadastrewms.layerCommune", true)) {
+            grabLayers += LAYER_COMMUNE + ",";
+            grabStyles += STYLE_COMMUNE + ",";
+            countLayers++;
+        }
+        if (countLayers > 2) { // remove the last ','
+            grabLayers = grabLayers.substring(0, grabLayers.length()-1);
+            grabStyles = grabStyles.substring(0, grabStyles.length()-1);
+        } else {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please enable at least two WMS layers in the cadastre-fr "
+                    + "plugin configuration.\nLayers ''Building'' and ''Parcel'' added by default."));
+            Main.pref.put("cadastrewms.layerBuilding", true);
+            Main.pref.put("cadastrewms.layerParcel", true);
+            grabLayers += LAYER_BULDINGS + "," + LAYER_PARCELS;
+            grabStyles += STYLE_BUILDING + "," + STYLE_PARCELS;
+        }
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new CadastrePreferenceSetting();
+    }
+
+    private static void setEnabledAll(boolean isEnabled) {
+        for (int i = 0; i < cadastreJMenu.getItemCount(); i++) {
+            JMenuItem item = cadastreJMenu.getItem(i);
+            if (item != null)
+                if (item.getText().equals(MenuActionGrabPlanImage.NAME) /*||
+                    item.getText().equals(MenuActionGrab.name) ||
+                    item.getText().equals(MenuActionBoundaries.name) ||
+                    item.getText().equals(MenuActionBuildings.name)*/) {
+                    item.setEnabled(isEnabled);
+                }
+        }
+        menuEnabled = isEnabled;
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (cadastreJMenu != null) {
+            if (oldFrame == null && newFrame != null) {
+                setEnabledAll(true);
+                Main.map.addMapMode(new IconToggleButton(new WMSAdjustAction()));
+                Main.map.addMapMode(new IconToggleButton(new Address()));
+            } else if (oldFrame != null && newFrame == null) {
+                setEnabledAll(false);
+                //Lambert.layoutZone = -1;
+                //LambertCC9Zones.layoutZone = -1;
+            }
+        }
+    }
+
+    public static boolean isLambert() {
+        String code = Main.getProjection().toCode();
+        return Arrays.asList(ProjectionPreference.lambert.allCodes()).contains(code);
+    }
+
+    public static boolean isUtm_france_dom() {
+        String code = Main.getProjection().toCode();
+        return Arrays.asList(ProjectionPreference.utm_france_dom.allCodes()).contains(code);
+    }
+
+    public static boolean isLambert_cc9() {
+        String code = Main.getProjection().toCode();
+        return Arrays.asList(ProjectionPreference.lambert_cc9.allCodes()).contains(code);
+    }
+
+    public static boolean isCadastreProjection() {
+        return isLambert() || isUtm_france_dom() || isLambert_cc9();
+    }
+
+    public static int getCadastreProjectionLayoutZone() {
+        int zone = -1;
+        Projection proj = Main.getProjection();
+        if (proj instanceof AbstractProjection) {
+            Integer code = ((AbstractProjection) proj).getEpsgCode();
+            if (code != null) {
+                if (code >= 3942 && code <= 3950) {                 // LambertCC9Zones
+                    zone = code - 3942;
+                } else if (code >= 27561 && 27564 <= code) {        // Lambert
+                    zone = code - 27561;
+                } else {                                            // UTM_France_DOM
+                    Map<Integer, Integer> utmfr = new HashMap<>();
+                    utmfr.put(2969, 0);
+                    utmfr.put(2970, 1);
+                    utmfr.put(2973, 2);
+                    utmfr.put(2975, 3);
+                    utmfr.put(2972, 4);
+                    if (utmfr.containsKey(code)) {
+                        zone = utmfr.get(code);
+                    }
+                }
+            }
+        }
+        return zone;
+    }
+
+    public static void safeSleep(long milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {
+            Main.debug(e);
+        }
+    }
+
+    // See OptionPaneUtil
+    // FIXME: this is a temporary solution.
+    public static void prepareDialog(JDialog dialog) {
+        if (Main.pref.getBoolean("window-handling.option-pane-always-on-top", true)) {
+            try {
+                dialog.setAlwaysOnTop(true);
+            } catch (SecurityException e) {
+                Main.warn(tr("Warning: failed to put option pane dialog always on top. Exception was: {0}", e.toString()));
+            }
+        }
+        dialog.setModal(true);
+        dialog.toFront();
+        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+    }
+
+    /**
+     * Adds the WMSLayer following this rule:<br/>
+     * - if a WMSLayer exists place this new layer just before this layer<br/>
+     * - Otherwise place it at the bottom
+     * @param wmsLayer the wmsLayer to add
+     */
+    public static void addWMSLayer(WMSLayer wmsLayer) {
+        if (Main.map != null && Main.map.mapView != null) {
+            int wmsNewLayerPos = Main.getLayerManager().getLayers().size();
+            for (Layer l : Main.getLayerManager().getLayersOfType(WMSLayer.class)) {
+                int wmsPos = Main.getLayerManager().getLayers().indexOf(l);
+                if (wmsPos < wmsNewLayerPos) wmsNewLayerPos = wmsPos;
+            }
+            Main.getLayerManager().addLayer(wmsLayer);
+            // Move the layer to its new position
+            Main.map.mapView.moveLayer(wmsLayer, wmsNewLayerPos);
+        } else
+            Main.getLayerManager().addLayer(wmsLayer);
+    }
+
+    private static String checkSourceMillesime() {
+        java.util.Calendar calendar = java.util.Calendar.getInstance();
+        int currentYear = calendar.get(java.util.Calendar.YEAR);
+        String src = Main.pref.get("cadastrewms.source",
+            "cadastre-dgi-fr source : Direction G\u00e9n\u00e9rale des Imp\u00f4ts - Cadastre. Mise \u00e0 jour : AAAA");
+        String srcYear = src.substring(src.lastIndexOf(" ")+1);
+        Integer year = null;
+        try {
+            year = Integer.decode(srcYear);
+        } catch (NumberFormatException e) {
+            Main.debug(e);
+        }
+        if (srcYear.equals("AAAA") || (year != null && year < currentYear)) {
+            Main.info("Replace source year "+srcYear+" by current year "+currentYear);
+            src = src.substring(0, src.lastIndexOf(" ")+1)+currentYear;
+            Main.pref.put("cadastrewms.source", src);
+        }
+        return src;
+    }
+
+    /**
+     * Ask to change projection if current one is not suitable for French cadastre.
+     */
+    public static void askToChangeProjection() {
+        GuiHelper.runInEDTAndWait(new Runnable() {
+            @Override
+            public void run() {
+                if (JOptionPane.showConfirmDialog(Main.parent,
+                        tr("To enable the cadastre WMS plugin, change\n"
+                                + "the current projection to one of the cadastre\n"
+                                + "projections and retry"),
+                                tr("Change the current projection"), JOptionPane.OK_CANCEL_OPTION)
+                    == JOptionPane.OK_OPTION) {
+                    PreferenceDialog p = new PreferenceDialog(Main.parent);
+                    p.selectPreferencesTabByClass(MapPreference.class);
+                    p.getTabbedPane().getSetting(ProjectionPreference.class).selectProjection(ProjectionPreference.lambert_cc9);
+                    p.setVisible(true);
+                }
+            }
+        });
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePreferenceSetting.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePreferenceSetting.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastrePreferenceSetting.java	(revision 33637)
@@ -0,0 +1,466 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.I18n;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Preference settings for the French Cadastre plugin
+ *
+ * @author Pieren &lt;pieren3@gmail.com&gt;
+ */
+public class CadastrePreferenceSetting extends DefaultTabPreferenceSetting {
+
+    static final int TRANS_MIN = 1;
+    static final int TRANS_MAX = 10;
+    private JSlider sliderTrans = new JSlider(JSlider.HORIZONTAL, TRANS_MIN, TRANS_MAX, TRANS_MAX);
+
+    private JTextField sourcing = new JTextField(20);
+
+    private JCheckBox alterColors = new JCheckBox(tr("Replace original background by JOSM background color."));
+
+    private JCheckBox reversGrey = new JCheckBox(tr("Reverse grey colors (for black backgrounds)."));
+
+    private JCheckBox transparency = new JCheckBox(tr("Set background transparent."));
+
+    private JCheckBox drawBoundaries = new JCheckBox(tr("Draw boundaries of downloaded data."));
+
+    private JComboBox<String> imageInterpolationMethod = new JComboBox<>();
+
+    private JCheckBox disableImageCropping = new JCheckBox(tr("Disable image cropping during georeferencing."));
+
+    private JCheckBox enableTableauAssemblage = new JCheckBox(tr("Use \"Tableau d''assemblage\""));
+
+    private JCheckBox simplify2BitsColors = new JCheckBox(tr("Replace grey shades by white color only"));
+
+    private JCheckBox autoFirstLayer = new JCheckBox(tr("Select first WMS layer in list."));
+
+    private JCheckBox dontUseRelation = new JCheckBox(tr("Don''t use relation for addresses (but \"addr:street\" on elements)."));
+
+    private JRadioButton grabMultiplier1 = new JRadioButton("", true);
+
+    private JRadioButton grabMultiplier2 = new JRadioButton("", true);
+
+    private JRadioButton grabMultiplier3 = new JRadioButton("", true);
+
+    private JRadioButton grabMultiplier4 = new JRadioButton("", true);
+
+    private JRadioButton crosspiece1 = new JRadioButton(tr("off"));
+
+    private JRadioButton crosspiece2 = new JRadioButton(tr("25 m"));
+
+    private JRadioButton crosspiece3 = new JRadioButton(tr("50 m"));
+
+    private JRadioButton crosspiece4 = new JRadioButton(tr("100 m"));
+
+    private JRadioButton grabRes1 = new JRadioButton(tr("high"));
+    private JRadioButton grabRes2 = new JRadioButton(tr("medium"));
+    private JRadioButton grabRes3 = new JRadioButton(tr("low"));
+
+    private JCheckBox layerLS3 = new JCheckBox(tr("water"));
+    private JCheckBox layerLS2 = new JCheckBox(tr("building"));
+    private JCheckBox layerLS1 = new JCheckBox(tr("symbol"));
+    private JCheckBox layerParcel = new JCheckBox(tr("parcel"));
+    private JCheckBox layerLabel = new JCheckBox(tr("parcel number"));
+    private JCheckBox layerNumero = new JCheckBox(tr("address"));
+    private JCheckBox layerLieudit = new JCheckBox(tr("locality"));
+    private JCheckBox layerSection = new JCheckBox(tr("section"));
+    private JCheckBox layerCommune = new JCheckBox(tr("commune"));
+
+    static final int DEFAULT_SQUARE_SIZE = 100;
+    private JTextField grabMultiplier4Size = new JTextField(5);
+
+    private JCheckBox enableCache = new JCheckBox(tr("Enable automatic caching."));
+
+    static final int DEFAULT_CACHE_SIZE = 0; // disabled by default
+    JLabel jLabelCacheSize = new JLabel(tr("Max. cache size (in MB)"));
+    private JTextField cacheSize = new JTextField(20);
+
+    static final String DEFAULT_RASTER_DIVIDER = "7";
+    private JTextField rasterDivider = new JTextField(10);
+
+    static final int DEFAULT_CROSSPIECES = 0;
+
+    static final String DEFAULT_GRAB_MULTIPLIER = Scale.SQUARE_100M.value;
+
+    /**
+     * Constructs a new {@code CadastrePreferenceSetting}.
+     */
+    public CadastrePreferenceSetting() {
+        super("cadastrewms.png", I18n.tr("French cadastre WMS"),
+            tr("A special handler of the French cadastre wms at www.cadastre.gouv.fr" + "<BR><BR>"
+                + "Please read the Terms and Conditions of Use here (in French): <br>"
+                + "<a href=\"http://www.cadastre.gouv.fr/scpc/html/CU_01_ConditionsGenerales_fr.html\"> "
+                + "http://www.cadastre.gouv.fr/scpc/html/CU_01_ConditionsGenerales_fr.html</a> <BR>"
+                + "before any upload of data created by this plugin.")
+        );
+    }
+
+    @Override
+    public void addGui(final PreferenceTabbedPane gui) {
+        JPanel cadastrewmsMast = gui.createPreferenceTab(this);
+
+        JPanel cadastrewms = new JPanel(new GridBagLayout());
+        cadastrewms.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+
+        // option to automatically set the source tag when uploading
+        sourcing.setText(CadastrePlugin.source);
+        sourcing.setToolTipText(tr("<html>Value of key \"source\" when autosourcing is enabled</html>"));
+        JLabel jLabelSource = new JLabel(tr("Source"));
+        cadastrewms.add(jLabelSource, GBC.eop().insets(0, 0, 0, 0));
+        cadastrewms.add(sourcing, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
+
+        // option to alter the original colors of the wms images
+        alterColors.setSelected(Main.pref.getBoolean("cadastrewms.alterColors", false));
+        alterColors.setToolTipText(tr("Replace the original white background by the background color defined in JOSM preferences."));
+        cadastrewms.add(alterColors, GBC.eop().insets(0, 0, 0, 0));
+
+        // option to reverse the grey colors (to see texts background)
+        reversGrey.setSelected(Main.pref.getBoolean("cadastrewms.invertGrey", false));
+        reversGrey.setToolTipText(
+                tr("Invert the original black and white colors (and all intermediate greys). Useful for texts on dark backgrounds."));
+        cadastrewms.add(reversGrey, GBC.eop().insets(0, 0, 0, 0));
+
+        // option to enable transparency
+        transparency.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                sliderTrans.setEnabled(transparency.isSelected());
+            }
+        });
+        transparency.setSelected(Main.pref.getBoolean("cadastrewms.backgroundTransparent", false));
+        transparency.setToolTipText(tr("Allows multiple layers stacking"));
+        cadastrewms.add(transparency, GBC.eop().insets(0, 0, 0, 0));
+
+        // slider for transparency level
+        sliderTrans.setSnapToTicks(true);
+        sliderTrans.setToolTipText(tr("Set WMS layers transparency. Right is opaque, left is transparent."));
+        sliderTrans.setMajorTickSpacing(10);
+        sliderTrans.setMinorTickSpacing(1);
+        sliderTrans.setValue((int) (Float.parseFloat(Main.pref.get("cadastrewms.brightness", "1.0f"))*10));
+        sliderTrans.setPaintTicks(true);
+        sliderTrans.setPaintLabels(false);
+        sliderTrans.setEnabled(transparency.isSelected());
+        cadastrewms.add(sliderTrans, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 250, 0));
+
+        // option to draw boundaries of downloaded data
+        drawBoundaries.setSelected(Main.pref.getBoolean("cadastrewms.drawBoundaries", false));
+        drawBoundaries.setToolTipText(tr("Draw a rectangle around downloaded data from WMS server."));
+        cadastrewms.add(drawBoundaries, GBC.eop().insets(0, 0, 0, 5));
+
+        // option to select the single grabbed image resolution
+        JLabel jLabelRes = new JLabel(tr("Image resolution:"));
+        cadastrewms.add(jLabelRes, GBC.std().insets(0, 5, 10, 0));
+        ButtonGroup bgResolution = new ButtonGroup();
+        grabRes1.setToolTipText(tr("High resolution (1000x800)"));
+        grabRes2.setToolTipText(tr("Medium resolution (800x600)"));
+        grabRes3.setToolTipText(tr("Low resolution (600x400)"));
+        bgResolution.add(grabRes1);
+        bgResolution.add(grabRes2);
+        bgResolution.add(grabRes3);
+        String currentResolution = Main.pref.get("cadastrewms.resolution", "high");
+        if (currentResolution.equals("high"))
+            grabRes1.setSelected(true);
+        if (currentResolution.equals("medium"))
+            grabRes2.setSelected(true);
+        if (currentResolution.equals("low"))
+            grabRes3.setSelected(true);
+        cadastrewms.add(grabRes1, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(grabRes2, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(grabRes3, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
+
+        // option to select image zooming interpolation method
+        JLabel jLabelImageZoomInterpolation = new JLabel(tr("Image filter interpolation:"));
+        cadastrewms.add(jLabelImageZoomInterpolation, GBC.std().insets(0, 0, 10, 0));
+        imageInterpolationMethod.addItem(tr("Nearest-Neighbor (fastest) [ Default ]"));
+        imageInterpolationMethod.addItem(tr("Bilinear (fast)"));
+        imageInterpolationMethod.addItem(tr("Bicubic (slow)"));
+        String savedImageInterpolationMethod = Main.pref.get("cadastrewms.imageInterpolation", "standard");
+        if (savedImageInterpolationMethod.equals("bilinear"))
+            imageInterpolationMethod.setSelectedIndex(1);
+        else if (savedImageInterpolationMethod.equals("bicubic"))
+            imageInterpolationMethod.setSelectedIndex(2);
+        else
+            imageInterpolationMethod.setSelectedIndex(0);
+        cadastrewms.add(imageInterpolationMethod, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 200, 5));
+
+        // separator
+        cadastrewms.add(new JSeparator(SwingConstants.HORIZONTAL), GBC.eol().fill(GBC.HORIZONTAL));
+
+        // the vectorized images multiplier
+        JLabel jLabelScale = new JLabel(tr("Vector images grab multiplier:"));
+        cadastrewms.add(jLabelScale, GBC.std().insets(0, 5, 10, 0));
+        ButtonGroup bgGrabMultiplier = new ButtonGroup();
+        ActionListener multiplierActionListener = new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent actionEvent) {
+              AbstractButton button = (AbstractButton) actionEvent.getSource();
+              grabMultiplier4Size.setEnabled(button == grabMultiplier4);
+            }
+          };
+        grabMultiplier1.setIcon(ImageProvider.get("preferences", "unsel_box_1"));
+        grabMultiplier1.setSelectedIcon(ImageProvider.get("preferences", "sel_box_1"));
+        grabMultiplier1.addActionListener(multiplierActionListener);
+        grabMultiplier1.setToolTipText(tr("Grab one image full screen"));
+        grabMultiplier2.setIcon(ImageProvider.get("preferences", "unsel_box_2"));
+        grabMultiplier2.setSelectedIcon(ImageProvider.get("preferences", "sel_box_2"));
+        grabMultiplier2.addActionListener(multiplierActionListener);
+        grabMultiplier2.setToolTipText(tr("Grab smaller images (higher quality but use more memory)"));
+        grabMultiplier3.setIcon(ImageProvider.get("preferences", "unsel_box_3"));
+        grabMultiplier3.setSelectedIcon(ImageProvider.get("preferences", "sel_box_3"));
+        grabMultiplier3.addActionListener(multiplierActionListener);
+        grabMultiplier3.setToolTipText(tr("Grab smaller images (higher quality but use more memory)"));
+        grabMultiplier4.setIcon(ImageProvider.get("preferences", "unsel_box_4"));
+        grabMultiplier4.setSelectedIcon(ImageProvider.get("preferences", "sel_box_4"));
+        grabMultiplier4.addActionListener(multiplierActionListener);
+        grabMultiplier4.setToolTipText(tr("Fixed size square (default is 100m)"));
+        bgGrabMultiplier.add(grabMultiplier1);
+        bgGrabMultiplier.add(grabMultiplier2);
+        bgGrabMultiplier.add(grabMultiplier3);
+        bgGrabMultiplier.add(grabMultiplier4);
+        String currentScale = Main.pref.get("cadastrewms.scale", DEFAULT_GRAB_MULTIPLIER);
+        if (currentScale.equals(Scale.X1.value))
+            grabMultiplier1.setSelected(true);
+        if (currentScale.equals(Scale.X2.value))
+            grabMultiplier2.setSelected(true);
+        if (currentScale.equals(Scale.X3.value))
+            grabMultiplier3.setSelected(true);
+        if (currentScale.equals(Scale.SQUARE_100M.value))
+            grabMultiplier4.setSelected(true);
+        cadastrewms.add(grabMultiplier1, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(grabMultiplier2, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(grabMultiplier3, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(grabMultiplier4, GBC.std().insets(5, 0, 5, 0));
+        int squareSize = getNumber("cadastrewms.squareSize", DEFAULT_SQUARE_SIZE);
+        grabMultiplier4Size.setText(String.valueOf(squareSize));
+        grabMultiplier4Size.setToolTipText(tr("Fixed size (from 25 to 1000 meters)"));
+        grabMultiplier4Size.setEnabled(currentScale.equals(Scale.SQUARE_100M.value));
+        cadastrewms.add(grabMultiplier4Size, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
+
+        // WMS layers selection
+        JLabel jLabelLayers = new JLabel(tr("Layers:"));
+        cadastrewms.add(jLabelLayers, GBC.std().insets(0, 5, 10, 0));
+        layerLS3.setSelected(Main.pref.getBoolean("cadastrewms.layerWater", true));
+        layerLS3.setToolTipText(tr("Sea, rivers, swimming pools."));
+        cadastrewms.add(layerLS3, GBC.std().insets(5, 0, 5, 0));
+        layerLS2.setSelected(Main.pref.getBoolean("cadastrewms.layerBuilding", true));
+        layerLS2.setToolTipText(tr("Buildings, covers, underground constructions."));
+        cadastrewms.add(layerLS2, GBC.std().insets(5, 0, 5, 0));
+        layerLS1.setSelected(Main.pref.getBoolean("cadastrewms.layerSymbol", true));
+        layerLS1.setToolTipText(tr("Symbols like cristian cross."));
+        cadastrewms.add(layerLS1, GBC.std().insets(5, 0, 5, 0));
+        layerParcel.setSelected(Main.pref.getBoolean("cadastrewms.layerParcel", true));
+        layerParcel.setToolTipText(tr("Parcels."));
+        cadastrewms.add(layerParcel, GBC.eop().insets(5, 0, 5, 0));
+        layerLabel.setSelected(Main.pref.getBoolean("cadastrewms.layerLabel", true));
+        layerLabel.setToolTipText(tr("Parcels numbers, street names."));
+        cadastrewms.add(layerLabel, GBC.std().insets(70, 0, 5, 0));
+        layerNumero.setSelected(Main.pref.getBoolean("cadastrewms.layerNumero", true));
+        layerNumero.setToolTipText(tr("Address, houses numbers."));
+        cadastrewms.add(layerNumero, GBC.std().insets(5, 0, 5, 0));
+        layerLieudit.setSelected(Main.pref.getBoolean("cadastrewms.layerLieudit", true));
+        layerLieudit.setToolTipText(tr("Locality, hamlet, place."));
+        cadastrewms.add(layerLieudit, GBC.std().insets(5, 0, 5, 0));
+        layerSection.setSelected(Main.pref.getBoolean("cadastrewms.layerSection", true));
+        layerSection.setToolTipText(tr("Cadastral sections and subsections."));
+        cadastrewms.add(layerSection, GBC.std().insets(5, 0, 5, 0));
+        layerCommune.setSelected(Main.pref.getBoolean("cadastrewms.layerCommune", true));
+        layerCommune.setToolTipText(tr("Municipality administrative borders."));
+        cadastrewms.add(layerCommune, GBC.eop().insets(5, 0, 5, 0));
+
+        // separator
+        cadastrewms.add(new JSeparator(SwingConstants.HORIZONTAL), GBC.eol().fill(GBC.HORIZONTAL));
+
+        // for raster images (not vectorized), image grab divider (from 1 to 12)
+        String savedRasterDivider = Main.pref.get("cadastrewms.rasterDivider", DEFAULT_RASTER_DIVIDER);
+        JLabel jLabelRasterDivider = new JLabel(tr("Raster images grab multiplier:"));
+        rasterDivider.setText(savedRasterDivider);
+        rasterDivider.setToolTipText("Raster image grab division, from 1 to 12; 12 is very high definition");
+        cadastrewms.add(jLabelRasterDivider, GBC.std().insets(0, 5, 10, 0));
+        cadastrewms.add(rasterDivider, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 200, 5));
+        // option to disable image cropping during raster image georeferencing
+        disableImageCropping.setSelected(Main.pref.getBoolean("cadastrewms.noImageCropping", false));
+        disableImageCropping.setToolTipText(tr("Disable image cropping during georeferencing."));
+        cadastrewms.add(disableImageCropping, GBC.std().insets(0, 0, 10, 0));
+        // option to add the "Tableau d'assemblage" in list of sheets to grab
+        enableTableauAssemblage.setSelected(Main.pref.getBoolean("cadastrewms.useTA", false));
+        enableTableauAssemblage.setToolTipText(tr("Add the \"Tableau(x) d''assemblage\" in the list of cadastre sheets to grab."));
+        cadastrewms.add(enableTableauAssemblage, GBC.eop().insets(0, 0, 0, 0));
+        // option to use 2 bits colors only
+        simplify2BitsColors.setSelected(Main.pref.getBoolean("cadastrewms.raster2bitsColors", false));
+        simplify2BitsColors.setToolTipText(tr("Replace greyscale by white color (smaller files and memory usage)."));
+        cadastrewms.add(simplify2BitsColors, GBC.eop().insets(0, 0, 0, 0));
+        // the crosspiece display
+        JLabel jLabelCrosspieces = new JLabel(tr("Display crosspieces:"));
+        cadastrewms.add(jLabelCrosspieces, GBC.std().insets(0, 0, 10, 0));
+        ButtonGroup bgCrosspieces = new ButtonGroup();
+        int crosspieces = getNumber("cadastrewms.crosspieces", DEFAULT_CROSSPIECES);
+        if (crosspieces == 0) crosspiece1.setSelected(true);
+        if (crosspieces == 1) crosspiece2.setSelected(true);
+        if (crosspieces == 2) crosspiece3.setSelected(true);
+        if (crosspieces == 3) crosspiece4.setSelected(true);
+        bgCrosspieces.add(crosspiece1);
+        bgCrosspieces.add(crosspiece2);
+        bgCrosspieces.add(crosspiece3);
+        bgCrosspieces.add(crosspiece4);
+        cadastrewms.add(crosspiece1, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(crosspiece2, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(crosspiece3, GBC.std().insets(5, 0, 5, 0));
+        cadastrewms.add(crosspiece4, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
+
+        // separator
+        cadastrewms.add(new JSeparator(SwingConstants.HORIZONTAL), GBC.eol().fill(GBC.HORIZONTAL));
+
+        // option to enable automatic caching
+        enableCache.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                jLabelCacheSize.setEnabled(enableCache.isSelected());
+                cacheSize.setEnabled(enableCache.isSelected());
+            }
+        });
+        enableCache.setSelected(Main.pref.getBoolean("cadastrewms.enableCaching", true));
+        enableCache.setToolTipText(tr("Allows an automatic caching"));
+        cadastrewms.add(enableCache, GBC.eop().insets(0, 0, 0, 0));
+
+        // option to fix the cache size(in MB)
+        int size = getNumber("cadastrewms.cacheSize", DEFAULT_CACHE_SIZE);
+        cacheSize.setText(String.valueOf(size));
+        cacheSize.setToolTipText(tr("Oldest files are automatically deleted when this size is exceeded"));
+        cadastrewms.add(jLabelCacheSize, GBC.std().insets(20, 0, 0, 0));
+        cadastrewms.add(cacheSize, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 200, 5));
+
+        // separator
+        cadastrewms.add(new JSeparator(SwingConstants.HORIZONTAL), GBC.eol().fill(GBC.HORIZONTAL));
+
+        // option to select the first WMS layer
+        autoFirstLayer.setSelected(Main.pref.getBoolean("cadastrewms.autoFirstLayer", false));
+        autoFirstLayer.setToolTipText(tr("Automatically selects the first WMS layer if multiple layers exist when grabbing."));
+        cadastrewms.add(autoFirstLayer, GBC.eop().insets(0, 0, 0, 0));
+
+        // separator
+        cadastrewms.add(new JSeparator(SwingConstants.HORIZONTAL), GBC.eol().fill(GBC.HORIZONTAL));
+
+        // option to use or not relations in addresses
+        dontUseRelation.setSelected(Main.pref.getBoolean("cadastrewms.addr.dontUseRelation", false));
+        dontUseRelation.setToolTipText(tr("Enable this to use the tag \"add:street\" on nodes."));
+        cadastrewms.add(dontUseRelation, GBC.eop().insets(0, 0, 0, 0));
+
+        // end of dialog, scroll bar
+        cadastrewms.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
+        JScrollPane scrollpane = new JScrollPane(cadastrewms);
+        scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+        cadastrewmsMast.add(scrollpane, GBC.eol().fill(GBC.BOTH));
+    }
+
+    @Override
+    public boolean ok() {
+        Main.pref.put("cadastrewms.source", sourcing.getText());
+        CadastrePlugin.source = sourcing.getText();
+        Main.pref.put("cadastrewms.alterColors", alterColors.isSelected());
+        Main.pref.put("cadastrewms.invertGrey", reversGrey.isSelected());
+        Main.pref.put("cadastrewms.backgroundTransparent", transparency.isSelected());
+        Main.pref.put("cadastrewms.brightness", Float.toString((float) sliderTrans.getValue()/10));
+        Main.pref.put("cadastrewms.drawBoundaries", drawBoundaries.isSelected());
+        if (grabRes1.isSelected())
+            Main.pref.put("cadastrewms.resolution", "high");
+        else if (grabRes2.isSelected())
+            Main.pref.put("cadastrewms.resolution", "medium");
+        else if (grabRes3.isSelected())
+            Main.pref.put("cadastrewms.resolution", "low");
+        if (imageInterpolationMethod.getSelectedIndex() == 2)
+            Main.pref.put("cadastrewms.imageInterpolation", "bicubic");
+        else if (imageInterpolationMethod.getSelectedIndex() == 1)
+            Main.pref.put("cadastrewms.imageInterpolation", "bilinear");
+        else
+            Main.pref.put("cadastrewms.imageInterpolation", "standard");
+        if (grabMultiplier1.isSelected())
+            Main.pref.put("cadastrewms.scale", Scale.X1.toString());
+        else if (grabMultiplier2.isSelected())
+            Main.pref.put("cadastrewms.scale", Scale.X2.toString());
+        else if (grabMultiplier3.isSelected())
+            Main.pref.put("cadastrewms.scale", Scale.X3.toString());
+        else {
+            Main.pref.put("cadastrewms.scale", Scale.SQUARE_100M.toString());
+            try {
+                int squareSize = Integer.parseInt(grabMultiplier4Size.getText());
+                if (squareSize >= 25 && squareSize <= 1000)
+                    Main.pref.put("cadastrewms.squareSize", grabMultiplier4Size.getText());
+            } catch (NumberFormatException e) {
+                Main.debug(e);
+            }
+        }
+        Main.pref.put("cadastrewms.layerWater", layerLS3.isSelected());
+        Main.pref.put("cadastrewms.layerBuilding", layerLS2.isSelected());
+        Main.pref.put("cadastrewms.layerSymbol", layerLS1.isSelected());
+        Main.pref.put("cadastrewms.layerParcel", layerParcel.isSelected());
+        Main.pref.put("cadastrewms.layerLabel", layerLabel.isSelected());
+        Main.pref.put("cadastrewms.layerNumero", layerNumero.isSelected());
+        Main.pref.put("cadastrewms.layerLieudit", layerLieudit.isSelected());
+        Main.pref.put("cadastrewms.layerSection", layerSection.isSelected());
+        Main.pref.put("cadastrewms.layerCommune", layerCommune.isSelected());
+        try {
+            int i = Integer.parseInt(rasterDivider.getText());
+            if (i > 0 && i < 13)
+                Main.pref.put("cadastrewms.rasterDivider", String.valueOf(i));
+        } catch (NumberFormatException e) {
+            Main.debug(e);
+        }
+        Main.pref.put("cadastrewms.noImageCropping", disableImageCropping.isSelected());
+        Main.pref.put("cadastrewms.useTA", enableTableauAssemblage.isSelected());
+        Main.pref.put("cadastrewms.raster2bitsColors", simplify2BitsColors.isSelected());
+        if (crosspiece1.isSelected()) Main.pref.put("cadastrewms.crosspieces", "0");
+        else if (crosspiece2.isSelected()) Main.pref.put("cadastrewms.crosspieces", "1");
+        else if (crosspiece3.isSelected()) Main.pref.put("cadastrewms.crosspieces", "2");
+        else if (crosspiece4.isSelected()) Main.pref.put("cadastrewms.crosspieces", "3");
+        Main.pref.put("cadastrewms.enableCaching", enableCache.isSelected());
+
+        // spread data into objects instead of restarting the application
+        try {
+            CacheControl.cacheSize = Integer.parseInt(cacheSize.getText());
+            Main.pref.put("cadastrewms.cacheSize", String.valueOf(CacheControl.cacheSize));
+        } catch (NumberFormatException e) {
+            Main.debug(e);
+        }
+        Main.pref.put("cadastrewms.autoFirstLayer", autoFirstLayer.isSelected());
+        CacheControl.cacheEnabled = enableCache.isSelected();
+        Main.pref.put("cadastrewms.addr.dontUseRelation", dontUseRelation.isSelected());
+        CadastrePlugin.refreshConfiguration();
+        CadastrePlugin.refreshMenu();
+
+        return false;
+    }
+
+    private int getNumber(String pref_parameter, int def_value) {
+        try {
+            return Integer.parseInt(Main.pref.get(pref_parameter, String.valueOf(def_value)));
+        } catch (NumberFormatException e) {
+            return def_value;
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionExporter.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionExporter.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionExporter.java	(revision 33637)
@@ -0,0 +1,81 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.io.session.SessionLayerExporter;
+import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
+import org.openstreetmap.josm.tools.GBC;
+import org.w3c.dom.Element;
+
+public class CadastreSessionExporter implements SessionLayerExporter {
+
+    private WMSLayer layer;
+    private JCheckBox export;
+
+    public CadastreSessionExporter(WMSLayer layer) {
+        this.layer = layer;
+    }
+
+    @Override
+    public Collection<Layer> getDependencies() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Component getExportPanel() {
+        final JPanel p = new JPanel(new GridBagLayout());
+        export = new JCheckBox();
+        export.setSelected(true);
+        final JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT);
+        lbl.setToolTipText(layer.getToolTipText());
+        p.add(export, GBC.std());
+        p.add(lbl, GBC.std());
+        p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
+        return p;
+    }
+
+    @Override
+    public boolean shallExport() {
+        return export.isSelected();
+    }
+
+    @Override
+    public boolean requiresZip() {
+        return false;
+    }
+
+    @Override
+    public Element export(ExportSupport support) throws IOException {
+        Element layerEl = support.createElement("layer");
+        layerEl.setAttribute("type", "cadastre-fr");
+        layerEl.setAttribute("version", "0.1");
+
+        Element file = support.createElement("file");
+        layerEl.appendChild(file);
+
+        URI uri = layer.getAssociatedFile().toURI();
+        URL url = null;
+        try {
+            url = uri.toURL();
+        } catch (MalformedURLException e) {
+            throw new IOException(e);
+        }
+        file.appendChild(support.createTextNode(url.toString()));
+        return layerEl;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionImporter.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionImporter.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CadastreSessionImporter.java	(revision 33637)
@@ -0,0 +1,65 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLDecoder;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.session.SessionLayerImporter;
+import org.openstreetmap.josm.io.session.SessionReader.ImportSupport;
+import org.w3c.dom.Element;
+
+public class CadastreSessionImporter implements SessionLayerImporter {
+
+    @Override
+    public Layer load(Element elem, ImportSupport support,
+            ProgressMonitor progressMonitor) throws IOException,
+            IllegalDataException {
+        String version = elem.getAttribute("version");
+        if (!"0.1".equals(version)) {
+            throw new IllegalDataException(tr("Version ''{0}'' of meta data for imagery layer is not supported. Expected: 0.1", version));
+        }
+        try {
+            XPathFactory xPathFactory = XPathFactory.newInstance();
+            XPath xpath = xPathFactory.newXPath();
+            XPathExpression fileExp = xpath.compile("file/text()");
+            String fileStr = (String) fileExp.evaluate(elem, XPathConstants.STRING);
+            if (fileStr == null || fileStr.isEmpty()) {
+                throw new IllegalDataException(tr("File name expected for layer no. {0}", support.getLayerIndex()));
+            }
+
+            fileStr = URLDecoder.decode(fileStr, "UTF-8");
+            fileStr = fileStr.substring(fileStr.indexOf(":/")+2);
+            String filename = fileStr.substring(fileStr.lastIndexOf('/')+1, fileStr.length());
+            String ext = (filename.lastIndexOf('.') == -1) ? "" : filename.substring(filename.lastIndexOf('.')+1, filename.length());
+            // create layer and load cache
+            if (ext.length() == 3 && ext.substring(0, CacheControl.C_LAMBERT_CC_9Z.length()).equals(CacheControl.C_LAMBERT_CC_9Z))
+                ext = ext.substring(2);
+            else if (ext.length() == 4 && ext.substring(0, CacheControl.C_UTM20N.length()).equals(CacheControl.C_UTM20N))
+                ext = ext.substring(3);
+            else if (ext.length() == 2 || ext.length() > 4)
+                throw new IllegalDataException(tr("Unexpected file extension. {0}", ext));
+
+            int layoutZone = Integer.parseInt(ext)-1;
+            WMSLayer wmsLayer = new WMSLayer("", "", layoutZone);
+            File file = new File(fileStr);
+            wmsLayer.grabThread.getCacheControl().loadCache(file, layoutZone);
+            return wmsLayer;
+
+        } catch (XPathExpressionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CheckSourceUploadHook.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CheckSourceUploadHook.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/CheckSourceUploadHook.java	(revision 33637)
@@ -0,0 +1,89 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.upload.UploadHook;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * This hook is called at JOSM upload and will check if new nodes and ways provide
+ * a tag "source=". If not and if auto-sourcing is enabled, it will add
+ * automatically a tag "source"="Cadastre..." as defined in the plugin preferences.
+ */
+public class CheckSourceUploadHook implements UploadHook {
+
+    /**
+     * Add the tag "source" if it doesn't exist for all new Nodes and Ways before uploading
+     */
+    @Override
+    public boolean checkUpload(APIDataSet apiDataSet) {
+        if (CadastrePlugin.autoSourcing && CadastrePlugin.pluginUsed && !apiDataSet.getPrimitivesToAdd().isEmpty()) {
+            Collection<OsmPrimitive> sel = new HashSet<>();
+            for (OsmPrimitive osm : apiDataSet.getPrimitivesToAdd()) {
+                if ((osm instanceof Way && (osm.getKeys().size() == 0 || !tagSourceExist(osm)))
+                 || (osm instanceof Node && osm.getKeys().size() > 0 && !tagSourceExist(osm))) {
+                    sel.add(osm);
+                }
+            }
+            if (!sel.isEmpty()) {
+                displaySource(sel);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check whenever one of the keys of the object is "source"
+     * @return true if one of keys is "source"
+     */
+    private boolean tagSourceExist(OsmPrimitive osm) {
+        for (String key : osm.keySet()) {
+            if (key.equals("source")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Displays a screen with the list of objects which will be tagged with
+     * source="cadastre.." if it is approved.
+     * @param sel the list of elements added without a key "source"
+     */
+    private void displaySource(Collection<OsmPrimitive> sel) {
+        if (!sel.isEmpty()) {
+            JPanel p = new JPanel(new GridBagLayout());
+            OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();
+            p.add(new JLabel(tr("Add \"source=...\" to elements?")), GBC.eol());
+            JTextField tf = new JTextField(CadastrePlugin.source);
+            p.add(tf, GBC.eol());
+            JList<OsmPrimitive> l = new JList<>(sel.toArray(new OsmPrimitive[0]));
+            l.setCellRenderer(renderer);
+            l.setVisibleRowCount(l.getModel().getSize() < 6 ? l.getModel().getSize() : 10);
+            p.add(new JScrollPane(l), GBC.eol().fill());
+            boolean bContinue = JOptionPane.showConfirmDialog(Main.parent, p, tr("Add \"source=...\" to elements?"),
+                   JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
+            if (bContinue)
+                Main.main.undoRedo.add(new ChangePropertyCommand(sel, "source", tf.getText()));
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGBuilding.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGBuilding.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGBuilding.java	(revision 33637)
@@ -0,0 +1,283 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmTransferException;
+
+public class DownloadSVGBuilding extends PleaseWaitRunnable {
+
+    private WMSLayer wmsLayer;
+    private CadastreInterface wmsInterface;
+    private String svg;
+    private static EastNorthBound currentView;
+    private EastNorthBound viewBox;
+    private static String errorMessage;
+
+    /**
+     * Constructs a new {@code DownloadSVGBuilding}.
+     */
+    public DownloadSVGBuilding(WMSLayer wmsLayer) {
+        super(tr("Downloading {0}", wmsLayer.getName()));
+
+        this.wmsLayer = wmsLayer;
+        this.wmsInterface = wmsLayer.grabber.getWmsInterface();
+    }
+
+    @Override
+    public void realRun() throws IOException, OsmTransferException {
+        progressMonitor.indeterminateSubTask(tr("Contacting WMS Server..."));
+        errorMessage = null;
+        try {
+            if (wmsInterface.retrieveInterface(wmsLayer)) {
+                svg = grabBoundary(currentView);
+                if (svg == null)
+                    return;
+                getViewBox(svg);
+                if (viewBox == null)
+                    return;
+                createBuildings(svg);
+            }
+        } catch (DuplicateLayerException e) {
+            Main.warn("removed a duplicated layer");
+        } catch (WMSException e) {
+            errorMessage = e.getMessage();
+            wmsLayer.grabber.getWmsInterface().resetCookie();
+        }
+    }
+
+    @Override
+    protected void cancel() {
+        wmsLayer.grabber.getWmsInterface().cancel();
+    }
+
+    @Override
+    protected void finish() {
+        // Do nothing
+    }
+
+    private boolean getViewBox(String svg) {
+        double[] box = new SVGParser().getViewBox(svg);
+        if (box != null) {
+            viewBox = new EastNorthBound(new EastNorth(box[0], box[1]),
+                    new EastNorth(box[0]+box[2], box[1]+box[3]));
+            return true;
+        }
+        Main.warn("Unable to parse SVG data (viewBox)");
+        return false;
+    }
+
+    /**
+     *  The svg contains more than one commune boundary defined by path elements. So detect
+     *  which path element is the best fitting to the viewBox and convert it to OSM objects
+     */
+    private void createBuildings(String svg) {
+        String[] SVGpaths = new SVGParser().getClosedPaths(svg);
+        ArrayList<ArrayList<EastNorth>> eastNorths = new ArrayList<>();
+
+        // convert SVG nodes to eastNorth coordinates
+        for (int i = 0; i < SVGpaths.length; i++) {
+            ArrayList<EastNorth> eastNorth = new ArrayList<>();
+            createNodes(SVGpaths[i], eastNorth);
+            if (eastNorth.size() > 2)
+                eastNorths.add(eastNorth);
+        }
+
+        // create nodes and closed ways
+        DataSet svgDataSet = new DataSet();
+        for (ArrayList<EastNorth> path : eastNorths) {
+            Way wayToAdd = new Way();
+            for (EastNorth eastNorth : path) {
+                Node nodeToAdd = new Node(Main.getProjection().eastNorth2latlon(eastNorth));
+                // check if new node is not already created by another new path
+                Node nearestNewNode = checkNearestNode(nodeToAdd, svgDataSet.getNodes());
+                if (nodeToAdd.equals(nearestNewNode))
+                    svgDataSet.addPrimitive(nearestNewNode);
+                wayToAdd.addNode(nearestNewNode); // either a new node or an existing one
+            }
+            wayToAdd.addNode(wayToAdd.getNode(0)); // close the way
+            svgDataSet.addPrimitive(wayToAdd);
+        }
+
+        // TODO remove small boxes (4 nodes with less than 1 meter distance)
+        /*
+        for (Way w : svgDataSet.ways)
+            if (w.nodes.size() == 5)
+                for (int i = 0; i < w.nodes.size()-2; i++) {
+                    if (w.nodes.get(i).eastNorth.distance(w.nodes.get(i+1).eastNorth))
+                }*/
+
+        // simplify ways and check if we can reuse existing OSM nodes
+//        for (Way wayToAdd : svgDataSet.getWays())
+//            new SimplifyWay().simplifyWay(wayToAdd, svgDataSet, 0.5);
+        // check if the new way or its nodes is already in OSM layer
+        for (Node n : svgDataSet.getNodes()) {
+            Node nearestNewNode = checkNearestNode(n, Main.getLayerManager().getEditDataSet().getNodes());
+            if (nearestNewNode != n) {
+                // replace the SVG node by the OSM node
+                for (Way w : svgDataSet.getWays()) {
+                    int replaced = 0;
+                    for (Node node : w.getNodes()) {
+                        if (node == n) {
+                            node = nearestNewNode;
+                            replaced++;
+                        }
+                    }
+                    if (w.getNodesCount() == replaced)
+                        w.setDeleted(true);
+                }
+                n.setDeleted(true);
+            }
+
+        }
+
+        Collection<Command> cmds = new LinkedList<>();
+        for (Node node : svgDataSet.getNodes()) {
+            if (!node.isDeleted())
+                cmds.add(new AddCommand(node));
+        }
+        for (Way way : svgDataSet.getWays()) {
+            if (!way.isDeleted())
+                cmds.add(new AddCommand(way));
+        }
+        Main.main.undoRedo.add(new SequenceCommand(tr("Create buildings"), cmds));
+        Main.map.repaint();
+    }
+
+    private void createNodes(String SVGpath, ArrayList<EastNorth> eastNorth) {
+        // looks like "M981283.38 368690.15l143.81 72.46 155.86 ..."
+        String[] coor = SVGpath.split("[MlZ ]"); //coor[1] is x, coor[2] is y
+        double dx = Double.parseDouble(coor[1]);
+        double dy = Double.parseDouble(coor[2]);
+        for (int i = 3; i < coor.length; i += 2) {
+            if (coor[i].isEmpty()) {
+                eastNorth.clear(); // some paths are just artifacts
+                return;
+            }
+            double east = dx += Double.parseDouble(coor[i]);
+            double north = dy += Double.parseDouble(coor[i+1]);
+            eastNorth.add(new EastNorth(east, north));
+        }
+        // flip the image (svg using a reversed Y coordinate system)
+        double pivot = viewBox.min.getY() + (viewBox.max.getY() - viewBox.min.getY()) / 2;
+        for (int i = 0; i < eastNorth.size(); i++) {
+            EastNorth en = eastNorth.get(i);
+            eastNorth.set(i, new EastNorth(en.east(), 2 * pivot - en.north()));
+        }
+        return;
+    }
+
+    /**
+     * Check if node can be reused.
+     * @param nodeToAdd the candidate as new node
+     * @return the already existing node (if any), otherwise the new node candidate.
+     */
+    private static Node checkNearestNode(Node nodeToAdd, Collection<Node> nodes) {
+        double epsilon = 0.05; // smallest distance considering duplicate node
+        for (Node n : nodes) {
+            if (!n.isDeleted() && !n.isIncomplete()) {
+                double dist = n.getEastNorth().distance(nodeToAdd.getEastNorth());
+                if (dist < epsilon) {
+                    return n;
+                }
+            }
+        }
+        return nodeToAdd;
+    }
+
+    private String grabBoundary(EastNorthBound bbox) throws IOException, OsmTransferException {
+        try {
+            URL url = null;
+            url = getURLsvg(bbox);
+            return grabSVG(url);
+        } catch (MalformedURLException e) {
+            throw (IOException) new IOException(tr("CadastreGrabber: Illegal url.")).initCause(e);
+        }
+    }
+
+    private static URL getURLsvg(EastNorthBound bbox) throws MalformedURLException {
+        String str = CadastreInterface.BASE_URL+"/scpc/wms?version=1.1&request=GetMap";
+        str += "&layers=";
+        str += "CDIF:LS2";
+        str += "&format=image/svg";
+        str += "&bbox="+bbox.min.east()+",";
+        str += bbox.min.north() + ",";
+        str += bbox.max.east() + ",";
+        str += bbox.max.north();
+        str += "&width="+CadastrePlugin.imageWidth+"&height="+CadastrePlugin.imageHeight;
+        str += "&exception=application/vnd.ogc.se_inimage";
+        str += "&styles=";
+        str += "LS2_90";
+        Main.info("URL="+str);
+        return new URL(str.replace(" ", "%20"));
+    }
+
+    private String grabSVG(URL url) throws IOException, OsmTransferException {
+        File file = new File(CadastrePlugin.cacheDir + "building.svg");
+        String svg = "";
+        try (InputStream is = wmsInterface.getContent(url)) {
+            if (file.exists())
+                file.delete();
+            try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, true));
+                 InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
+                 BufferedReader br = new BufferedReader(isr)) {
+                String line;
+                while (null != (line = br.readLine())) {
+                    line += "\n";
+                    bos.write(line.getBytes(StandardCharsets.UTF_8));
+                    svg += line;
+                }
+            }
+        } catch (IOException e) {
+            Main.error(e);
+        }
+        return svg;
+    }
+
+    public static void download(WMSLayer wmsLayer) {
+        MapView mv = Main.map.mapView;
+        currentView = new EastNorthBound(mv.getEastNorth(0, mv.getHeight()),
+                mv.getEastNorth(mv.getWidth(), 0));
+        if ((currentView.max.east() - currentView.min.east()) > 1000 ||
+                (currentView.max.north() - currentView.min.north() > 1000)) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("To avoid cadastre WMS overload,\nbuilding import size is limited to 1 km2 max."));
+            return;
+        }
+        if (CadastrePlugin.autoSourcing == false) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Please, enable auto-sourcing and check cadastre millesime."));
+            return;
+        }
+        Main.worker.execute(new DownloadSVGBuilding(wmsLayer));
+        if (errorMessage != null)
+            JOptionPane.showMessageDialog(Main.parent, errorMessage);
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGTask.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGTask.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadSVGTask.java	(revision 33637)
@@ -0,0 +1,224 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmTransferException;
+
+/**
+ * Grab the SVG administrative boundaries of the active commune layer (cadastre),
+ * isolate the SVG path of the concerned commune (other municipalities are also
+ * downloaded in the SVG data), convert to OSM nodes and way plus simplify.
+ * Thanks to Frederic Rodrigo for his help.
+ */
+public class DownloadSVGTask extends PleaseWaitRunnable {
+
+    private WMSLayer wmsLayer;
+    private CadastreInterface wmsInterface;
+    private String svg;
+    private EastNorthBound viewBox;
+    private static String errorMessage;
+
+    /**
+     * Constructs a new {@code DownloadSVGTask}.
+     */
+    public DownloadSVGTask(WMSLayer wmsLayer) {
+        super(tr("Downloading {0}", wmsLayer.getName()));
+
+        this.wmsLayer = wmsLayer;
+        this.wmsInterface = wmsLayer.grabber.getWmsInterface();
+    }
+
+    @Override
+    public void realRun() throws IOException, OsmTransferException {
+        progressMonitor.indeterminateSubTask(tr("Contacting WMS Server..."));
+        errorMessage = null;
+        try {
+            if (wmsInterface.retrieveInterface(wmsLayer)) {
+                svg = grabBoundary(wmsLayer.getCommuneBBox());
+                if (svg == null)
+                    return;
+                progressMonitor.indeterminateSubTask(tr("Extract SVG ViewBox..."));
+                getViewBox(svg);
+                if (viewBox == null)
+                    return;
+                progressMonitor.indeterminateSubTask(tr("Extract best fitting boundary..."));
+                createWay(svg);
+            }
+        } catch (DuplicateLayerException e) {
+            Main.warn("removed a duplicated layer");
+        } catch (WMSException e) {
+            errorMessage = e.getMessage();
+            wmsLayer.grabber.getWmsInterface().resetCookie();
+        }
+    }
+
+    @Override
+    protected void cancel() {
+        wmsLayer.grabber.getWmsInterface().cancel();
+    }
+
+    @Override
+    protected void finish() {
+        // Do nothing
+    }
+
+    private boolean getViewBox(String svg) {
+        double[] box = new SVGParser().getViewBox(svg);
+        if (box != null) {
+            viewBox = new EastNorthBound(new EastNorth(box[0], box[1]),
+                    new EastNorth(box[0]+box[2], box[1]+box[3]));
+            return true;
+        }
+        Main.warn("Unable to parse SVG data (viewBox)");
+        return false;
+    }
+
+    /**
+     *  The svg contains more than one commune boundary defined by path elements. So detect
+     *  which path element is the best fitting to the viewBox and convert it to OSM objects
+     */
+    private void createWay(String svg) {
+        String[] SVGpaths = new SVGParser().getClosedPaths(svg);
+        ArrayList<Double> fitViewBox = new ArrayList<>();
+        ArrayList<ArrayList<EastNorth>> eastNorths = new ArrayList<>();
+        for (int i = 0; i < SVGpaths.length; i++) {
+            ArrayList<EastNorth> eastNorth = new ArrayList<>();
+            fitViewBox.add(createNodes(SVGpaths[i], eastNorth));
+            eastNorths.add(eastNorth);
+        }
+        // the smallest fitViewBox indicates the best fitting path in viewBox
+        Double min = Collections.min(fitViewBox);
+        int bestPath = fitViewBox.indexOf(min);
+        List<Node> nodeList = new ArrayList<>();
+        for (EastNorth eastNorth : eastNorths.get(bestPath)) {
+            nodeList.add(new Node(Main.getProjection().eastNorth2latlon(eastNorth)));
+        }
+        Way wayToAdd = new Way();
+        Collection<Command> cmds = new LinkedList<>();
+        for (Node node : nodeList) {
+            cmds.add(new AddCommand(node));
+            wayToAdd.addNode(node);
+        }
+        wayToAdd.addNode(wayToAdd.getNode(0)); // close the circle
+
+        // simplify the way
+//        double threshold = Double.parseDouble(Main.pref.get("cadastrewms.simplify-way-boundary", "1.0"));
+//        new SimplifyWay().simplifyWay(wayToAdd, Main.main.getCurrentDataSet(), threshold);
+
+        cmds.add(new AddCommand(wayToAdd));
+        Main.main.undoRedo.add(new SequenceCommand(tr("Create boundary"), cmds));
+        Main.map.repaint();
+    }
+
+    private double createNodes(String SVGpath, ArrayList<EastNorth> eastNorth) {
+        // looks like "M981283.38 368690.15l143.81 72.46 155.86 ..."
+        String[] coor = SVGpath.split("[MlZ ]"); //coor[1] is x, coor[2] is y
+        double dx = Double.parseDouble(coor[1]);
+        double dy = Double.parseDouble(coor[2]);
+        double minY = Double.MAX_VALUE;
+        double minX = Double.MAX_VALUE;
+        double maxY = Double.MIN_VALUE;
+        double maxX = Double.MIN_VALUE;
+        for (int i = 3; i < coor.length; i += 2) {
+            double east = dx += Double.parseDouble(coor[i]);
+            double north = dy += Double.parseDouble(coor[i+1]);
+            eastNorth.add(new EastNorth(east, north));
+            minX = minX > east ? east : minX;
+            minY = minY > north ? north : minY;
+            maxX = maxX < east ? east : maxX;
+            maxY = maxY < north ? north : maxY;
+        }
+        // flip the image (svg using a reversed Y coordinate system)
+        double pivot = viewBox.min.getY() + (viewBox.max.getY() - viewBox.min.getY()) / 2;
+        for (int i = 0; i < eastNorth.size(); i++) {
+            EastNorth en = eastNorth.get(i);
+            eastNorth.set(i, new EastNorth(en.east(), 2 * pivot - en.north()));
+        }
+        return Math.abs(minX - viewBox.min.getX())+Math.abs(maxX - viewBox.max.getX())
+        +Math.abs(minY - viewBox.min.getY())+Math.abs(maxY - viewBox.max.getY());
+    }
+
+    private String grabBoundary(EastNorthBound bbox) throws IOException, OsmTransferException {
+        try {
+            return grabSVG(getURLsvg(bbox));
+        } catch (MalformedURLException e) {
+            throw (IOException) new IOException(tr("CadastreGrabber: Illegal url.")).initCause(e);
+        }
+    }
+
+    private static URL getURLsvg(EastNorthBound bbox) throws MalformedURLException {
+        String str = CadastreInterface.BASE_URL+"/scpc/wms?version=1.1&request=GetMap";
+        str += "&layers=";
+        str += "CDIF:COMMUNE";
+        str += "&format=image/svg";
+        str += "&bbox="+bbox.min.east()+",";
+        str += bbox.min.north() + ",";
+        str += bbox.max.east() + ",";
+        str += bbox.max.north();
+        str += "&width="+CadastrePlugin.imageWidth+"&height="+CadastrePlugin.imageHeight;
+        str += "&styles=";
+        str += "COMMUNE_90";
+        Main.info("URL="+str);
+        return new URL(str.replace(" ", "%20"));
+    }
+
+    private String grabSVG(URL url) throws IOException, OsmTransferException {
+        File file = new File(CadastrePlugin.cacheDir + "boundary.svg");
+        String svg = "";
+        try (InputStream is = wmsInterface.getContent(url)) {
+            if (file.exists())
+                file.delete();
+            try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, true));
+                 InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
+                 BufferedReader br = new BufferedReader(isr)) {
+                String line;
+                while (null != (line = br.readLine())) {
+                    line += "\n";
+                    bos.write(line.getBytes(StandardCharsets.UTF_8));
+                    svg += line;
+                }
+            }
+        } catch (IOException e) {
+            Main.error(e);
+        }
+        return svg;
+    }
+
+    public static void download(WMSLayer wmsLayer) {
+        if (!CadastrePlugin.autoSourcing) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Please, enable auto-sourcing and check cadastre millesime."));
+            return;
+        }
+        Main.worker.execute(new DownloadSVGTask(wmsLayer));
+        if (errorMessage != null)
+            JOptionPane.showMessageDialog(Main.parent, errorMessage);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSPlanImage.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSPlanImage.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSPlanImage.java	(revision 33637)
@@ -0,0 +1,126 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+
+public class DownloadWMSPlanImage {
+
+    private Future<Task> task = null;
+    private WMSLayer wmsLayer;
+    private Bounds bounds;
+    private static boolean dontGeoreference = false;
+    private static String errorMessage;
+
+    private class Task extends PleaseWaitRunnable {
+        Task(WMSLayer wmsLayer, Bounds bounds) {
+            super(tr("Downloading {0}", wmsLayer.getName()));
+        }
+
+        @Override
+        public void realRun() throws IOException {
+            progressMonitor.indeterminateSubTask(tr("Contacting cadastre WMS ..."));
+            errorMessage = null;
+            try {
+                if (wmsLayer.grabber.getWmsInterface().retrieveInterface(wmsLayer)) {
+                    if (!wmsLayer.getImages().isEmpty()) {
+                        JOptionPane pane = new JOptionPane(tr("Image already loaded"), JOptionPane.INFORMATION_MESSAGE);
+                        // this below is a temporary workaround to fix the "always on top" issue
+                        JDialog dialog = pane.createDialog(Main.parent, "");
+                        CadastrePlugin.prepareDialog(dialog);
+                        dialog.setVisible(true);
+                        // till here
+                        dontGeoreference = true;
+                    } else if (wmsLayer.grabber.getWmsInterface().downloadCanceled) {
+                        // do nothing
+                    } else {
+                        // first time we grab an image for this layer
+                        if (CacheControl.cacheEnabled) {
+                            if (wmsLayer.grabThread.getCacheControl().loadCacheIfExist()) {
+                                dontGeoreference = true;
+                                wmsLayer.invalidate();
+                                return;
+                            }
+                        }
+                        if (wmsLayer.isRaster()) {
+                            // set raster image commune bounding box based on current view (before adjustment)
+                            wmsLayer.grabber.getWmsInterface().retrieveCommuneBBox(wmsLayer);
+                            wmsLayer.setRasterBounds(bounds);
+                            // grab new images from wms server into active layer
+                            wmsLayer.grab(bounds);
+                            if (wmsLayer.grabber.getWmsInterface().downloadCanceled) {
+                                wmsLayer.clearImages();
+                                wmsLayer.invalidate();
+                            } else {
+                                // next steps follow in method finish() when download is terminated
+                                wmsLayer.joinBufferedImages();
+                            }
+                        } else {
+                            /*JOptionPane.showMessageDialog(Main.parent,tr("Municipality vectorized !\n"+
+                                    "Use the normal Cadastre Grab menu."));*/
+                            JOptionPane pane = new JOptionPane(
+                                    tr("Municipality vectorized !\nUse the normal Cadastre Grab menu."),
+                                    JOptionPane.INFORMATION_MESSAGE);
+                            // this below is a temporary workaround to fix the "always on top" issue
+                            JDialog dialog = pane.createDialog(Main.parent, "");
+                            CadastrePlugin.prepareDialog(dialog);
+                            dialog.setVisible(true);
+                            // till here
+                        }
+                    }
+                }
+            } catch (DuplicateLayerException e) {
+                // we tried to grab onto a duplicated layer (removed)
+                Main.warn("removed a duplicated layer");
+            } catch (WMSException e) {
+                errorMessage = e.getMessage();
+                wmsLayer.grabber.getWmsInterface().resetCookie();
+            }
+        }
+
+        @Override
+        protected void cancel() {
+            wmsLayer.grabber.getWmsInterface().cancel();
+            dontGeoreference = true;
+        }
+
+        @Override
+        protected void finish() {
+        }
+    }
+
+    public void download(WMSLayer wmsLayer) {
+        MapView mv = Main.map.mapView;
+        Bounds bounds = new Bounds(mv.getLatLon(0, mv.getHeight()), mv.getLatLon(mv.getWidth(), 0));
+        dontGeoreference = false;
+
+        //Main.worker.execute(new DownloadWMSPlanImage(wmsLayer, bounds));
+        Task t = new Task(wmsLayer, bounds);
+        this.wmsLayer = wmsLayer;
+        this.bounds = bounds;
+        task = Main.worker.submit(t, t);
+        if (errorMessage != null)
+            JOptionPane.showMessageDialog(Main.parent, errorMessage);
+    }
+
+    public boolean waitFinished() {
+        if (task != null) {
+            try {
+                task.get();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return dontGeoreference;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSVectorImage.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSVectorImage.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DownloadWMSVectorImage.java	(revision 33637)
@@ -0,0 +1,89 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+
+public class DownloadWMSVectorImage extends PleaseWaitRunnable {
+
+    private WMSLayer wmsLayer;
+    private Bounds bounds;
+    private static String errorMessage;
+
+    public DownloadWMSVectorImage(WMSLayer wmsLayer, Bounds bounds) {
+        super(tr("Downloading {0}", wmsLayer.getName()));
+
+        this.wmsLayer = wmsLayer;
+        this.bounds = bounds;
+    }
+
+    @Override
+    public void realRun() throws IOException {
+        progressMonitor.indeterminateSubTask(tr("Contacting WMS Server..."));
+        errorMessage = null;
+        try {
+            if (wmsLayer.grabber.getWmsInterface().retrieveInterface(wmsLayer)) {
+                if (!wmsLayer.hasImages()) {
+                    // first time we grab an image for this layer
+                    if (CacheControl.cacheEnabled) {
+                        if (wmsLayer.grabThread.getCacheControl().loadCacheIfExist()) {
+                            Main.map.mapView.zoomTo(wmsLayer.getFirstViewFromCacheBBox().toBounds());
+                            return;
+                        }
+                    }
+                    if (wmsLayer.isRaster()) {
+                        // set raster image commune bounding box based on current view (before adjustment)
+                        JOptionPane.showMessageDialog(Main.parent,
+                                tr("This commune is not vectorized.\nPlease use the other menu entry to georeference a \"Plan image\""));
+                        Main.getLayerManager().removeLayer(wmsLayer);
+                        wmsLayer = null;
+                        return;
+                    } else {
+                        // set vectorized commune bounding box by opening the standard web window
+                        wmsLayer.grabber.getWmsInterface().retrieveCommuneBBox(wmsLayer);
+                    }
+                }
+                // grab new images from wms server into active layer
+                wmsLayer.grab(bounds);
+            } else if (!wmsLayer.hasImages()) {
+              // failed to contact WMS of find this commune. Remove layer if empty.
+              Main.getLayerManager().removeLayer(wmsLayer);
+            }
+        } catch (DuplicateLayerException e) {
+            // we tried to grab onto a duplicated layer (removed)
+            Main.warn("removed a duplicated layer");
+        } catch (WMSException e) {
+            Main.warn(e);
+            errorMessage = e.getMessage();
+            wmsLayer.grabber.getWmsInterface().resetCookie();
+        }
+    }
+
+    @Override
+    protected void cancel() {
+        wmsLayer.grabber.getWmsInterface().cancel();
+        if (wmsLayer != null)
+            wmsLayer.grabThread.setCanceled(true);
+    }
+
+    @Override
+    protected void finish() {
+    }
+
+    public static void download(WMSLayer wmsLayer) {
+        MapView mv = Main.map.mapView;
+        Bounds bounds = new Bounds(mv.getLatLon(0, mv.getHeight()), mv.getLatLon(mv.getWidth(), 0));
+
+        Main.worker.execute(new DownloadWMSVectorImage(wmsLayer, bounds));
+        if (errorMessage != null)
+            JOptionPane.showMessageDialog(Main.parent, errorMessage);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DuplicateLayerException.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DuplicateLayerException.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/DuplicateLayerException.java	(revision 33637)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+class DuplicateLayerException extends Exception {
+    private static final long serialVersionUID = 1L;
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/EastNorthBound.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/EastNorthBound.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/EastNorthBound.java	(revision 33637)
@@ -0,0 +1,42 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.io.Serializable;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+
+public class EastNorthBound implements Serializable {
+
+    private static final long serialVersionUID = 8451650309216472069L;
+
+    public EastNorth min, max;
+    public EastNorthBound(EastNorth min, EastNorth max) {
+        this.min = min;
+        this.max = max;
+    }
+
+    public boolean contains(EastNorth eastNorth) {
+        if (eastNorth.east() < min.east() || eastNorth.north() < min.north())
+            return false;
+        if (eastNorth.east() > max.east() || eastNorth.north() > max.north())
+            return false;
+        return true;
+    }
+
+    public EastNorthBound interpolate(EastNorthBound en2, double proportion) {
+        EastNorthBound enb = new EastNorthBound(this.min.interpolate(en2.min, proportion),
+                this.max.interpolate(en2.max, proportion));
+        return enb;
+    }
+
+    public Bounds toBounds() {
+        return new Bounds(Main.getProjection().eastNorth2latlon(min), Main.getProjection().eastNorth2latlon(max));
+    }
+
+    @Override public String toString() {
+        return "EastNorthBound[" + min.east() + "," + min.north() + "," + max.east() + "," + max.north() + "]";
+    }
+}
+
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GeorefImage.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GeorefImage.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GeorefImage.java	(revision 33637)
@@ -0,0 +1,376 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+
+public class GeorefImage implements Serializable, ImageObserver, Cloneable {
+    private static final long serialVersionUID = 1L;
+
+    // bbox of the georeferenced image (the nice horizontal and vertical box)
+    public EastNorth min;
+    public EastNorth max;
+    // bbox of the georeferenced original image (raster only) (inclined if rotated and before cropping)
+    // P[0] is bottom,left then next are clockwise.
+    public EastNorth[] orgRaster = new EastNorth[4];
+    // bbox of the georeferenced original image (raster only) after cropping
+    public EastNorth[] orgCroppedRaster = new EastNorth[4];
+    // angle with georeferenced original image after rotation (raster images only)(in radian)
+    public double angle = 0;
+    public int imageOriginalHeight = 0;
+    public int imageOriginalWidth = 0;
+
+    public BufferedImage image;
+    public WMSLayer wmsLayer;
+
+    private double pixelPerEast;
+    private double pixelPerNorth;
+
+    public GeorefImage(BufferedImage img, EastNorth min, EastNorth max, WMSLayer wmsLayer) {
+        image = Objects.requireNonNull(img);
+
+        this.min = Objects.requireNonNull(min);
+        this.max = Objects.requireNonNull(max);
+        this.orgRaster[0] = min;
+        this.orgRaster[1] = new EastNorth(min.east(), max.north());
+        this.orgRaster[2] = max;
+        this.orgRaster[3] = new EastNorth(max.east(), min.north());
+        this.orgCroppedRaster[0] = min;
+        this.orgCroppedRaster[1] = new EastNorth(min.east(), max.north());
+        this.orgCroppedRaster[2] = max;
+        this.orgCroppedRaster[3] = new EastNorth(max.east(), min.north());
+        // img can be null for a hack used in overlapping detection
+        this.imageOriginalHeight = (img == null ? 1 : img.getHeight());
+        this.imageOriginalWidth = (img == null ? 1 : img.getWidth());
+        this.wmsLayer = wmsLayer;
+        updatePixelPer();
+    }
+
+    public static GraphicsConfiguration getDefaultConfiguration() {
+        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+        GraphicsDevice gd = ge.getDefaultScreenDevice();
+        return gd.getDefaultConfiguration();
+    }
+
+    /**
+     * Recalculate the new bounding box of the image based on the four points provided as parameters.
+     * The new bbox defined in [min.max] will retain the extreme values of both boxes.
+     * @param p1 one of the bounding box corner
+     * @param p2 one of the bounding box corner
+     * @param p3 one of the bounding box corner
+     * @param p4 one of the bounding box corner
+     */
+    private EastNorthBound computeNewBounding(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
+        EastNorth[] pt = new EastNorth[4];
+        pt[0] = p1;
+        pt[1] = p2;
+        pt[2] = p3;
+        pt[3] = p4;
+        double smallestEast = Double.MAX_VALUE;
+        double smallestNorth = Double.MAX_VALUE;
+        double highestEast = Double.MIN_VALUE;
+        double highestNorth = Double.MIN_VALUE;
+        for (int i = 0; i <= 3; i++) {
+            smallestEast = Math.min(pt[i].east(), smallestEast);
+            smallestNorth = Math.min(pt[i].north(), smallestNorth);
+            highestEast = Math.max(pt[i].east(), highestEast);
+            highestNorth = Math.max(pt[i].north(), highestNorth);
+        }
+        return new EastNorthBound(new EastNorth(smallestEast, smallestNorth),
+                new EastNorth(highestEast, highestNorth));
+    }
+
+    public boolean contains(EastNorth en) {
+        return min.east() <= en.east() && en.east() <= max.east() && min.north() <= en.north()
+                && en.north() <= max.north();
+    }
+
+    public void paint(Graphics2D g, NavigatableComponent nc, boolean backgroundTransparent, float transparency,
+            boolean drawBoundaries) {
+        if (image == null || min == null || max == null)
+            return;
+
+        // apply offsets defined manually when vector images are translated manually (not saved in cache)
+        double dx = 0, dy = 0;
+        if (wmsLayer != null) {
+            dx = wmsLayer.deltaEast;
+            dy = wmsLayer.deltaNorth;
+        }
+        Point minPt = nc.getPoint(new EastNorth(min.east()+dx, min.north()+dy));
+        Point maxPt = nc.getPoint(new EastNorth(max.east()+dx, max.north()+dy));
+
+        if (!g.hitClip(minPt.x, maxPt.y, maxPt.x - minPt.x, minPt.y - maxPt.y))
+            return;
+
+        if (backgroundTransparent && transparency < 1.0f)
+            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency));
+        if (drawBoundaries) {
+            if (orgCroppedRaster == null) {
+                // this is the old cache format where only [min,max] bbox is stored
+                g.setColor(Color.green);
+                g.drawRect(minPt.x, maxPt.y, maxPt.x - minPt.x, minPt.y - maxPt.y);
+            } else {
+                Point[] croppedPoint = new Point[5];
+                for (int i = 0; i < 4; i++) {
+                    croppedPoint[i] = nc.getPoint(
+                            new EastNorth(orgCroppedRaster[i].east()+dx, orgCroppedRaster[i].north()+dy));
+                }
+                croppedPoint[4] = croppedPoint[0];
+                for (int i = 0; i < 4; i++) {
+                    g.setColor(Color.green);
+                    g.drawLine(croppedPoint[i].x, croppedPoint[i].y, croppedPoint[i+1].x, croppedPoint[i+1].y);
+                }
+                /*
+                //Uncomment this section to display the original image size (before cropping)
+                Point[] orgPoint = new Point[5];
+                for (int i=0; i<4; i++)
+                    orgPoint[i] = nc.getPoint(orgRaster[i]);
+                orgPoint[4] = orgPoint[0];
+                for (int i=0; i<4; i++) {
+                  g.setColor(Color.red);
+                  g.drawLine(orgPoint[i].x, orgPoint[i].y, orgPoint[i+1].x, orgPoint[i+1].y);
+                }
+                */
+            }
+        }
+            g.drawImage(image, minPt.x, maxPt.y, maxPt.x, minPt.y, // dest
+                        0, 0, image.getWidth(), image.getHeight(), // src
+                        null);
+        if (backgroundTransparent && transparency < 1.0f)
+            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
+    }
+
+    /**
+     * Is the given bbox overlapping this image ?
+     */
+    public boolean overlap(GeorefImage georefImage) {
+        if (this.contains(georefImage.min) || this.contains(georefImage.max))
+            return true;
+        if (this.contains(new EastNorth(georefImage.min.east(), georefImage.max.north()))
+                || this.contains(new EastNorth(georefImage.max.east(), georefImage.min.north())))
+            return true;
+        return false;
+    }
+
+    /**
+     * Make all pixels masked by the given georefImage transparent in this image
+     */
+    public void withdraw(GeorefImage georefImage) {
+        double minMaskEast = (georefImage.min.east() > this.min.east()) ? georefImage.min.east() : this.min.east();
+        double maxMaskEast = (georefImage.max.east() < this.max.east()) ? georefImage.max.east() : this.max.east();
+        double minMaskNorth = (georefImage.min.north() > this.min.north()) ? georefImage.min.north() : this.min.north();
+        double maxMaskNorth = (georefImage.max.north() < this.max.north()) ? georefImage.max.north() : this.max.north();
+        if ((maxMaskNorth - minMaskNorth) > 0 && (maxMaskEast - minMaskEast) > 0) {
+            double pxPerEast = (max.east() - min.east()) / image.getWidth();
+            double pxPerNorth = (max.north() - min.north()) / image.getHeight();
+            int minXMaskPixel = (int) ((minMaskEast - min.east()) / pxPerEast);
+            int minYMaskPixel = (int) ((max.north() - maxMaskNorth) / pxPerNorth);
+            int widthXMaskPixel = Math.abs((int) ((maxMaskEast - minMaskEast) / pxPerEast));
+            int heightYMaskPixel = Math.abs((int) ((maxMaskNorth - minMaskNorth) / pxPerNorth));
+            Graphics g = image.getGraphics();
+            for (int x = minXMaskPixel; x < minXMaskPixel + widthXMaskPixel; x++) {
+                for (int y = minYMaskPixel; y < minYMaskPixel + heightYMaskPixel; y++) {
+                    image.setRGB(x, y, VectorImageModifier.cadastreBackgroundTransp);
+                }
+            }
+            g.dispose();
+        }
+    }
+
+    /**
+     * Method required by BufferedImage serialization.
+     * Save only primitives to keep cache independent of software changes.
+     */
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        if (WMSLayer.currentFormat >= 2) {
+            max = new EastNorth(in.readDouble(), in.readDouble());
+            min = new EastNorth(in.readDouble(), in.readDouble());
+        }
+        orgRaster = null;
+        orgCroppedRaster = null;
+        if (WMSLayer.currentFormat >= 3) {
+            orgRaster = new EastNorth[4];
+            orgCroppedRaster = new EastNorth[4];
+            angle = in.readDouble();
+            orgRaster[0] = new EastNorth(in.readDouble(), in.readDouble());
+            orgRaster[1] = new EastNorth(in.readDouble(), in.readDouble());
+            orgRaster[2] = new EastNorth(in.readDouble(), in.readDouble());
+            orgRaster[3] = new EastNorth(in.readDouble(), in.readDouble());
+            orgCroppedRaster[0] = new EastNorth(in.readDouble(), in.readDouble());
+            orgCroppedRaster[1] = new EastNorth(in.readDouble(), in.readDouble());
+            orgCroppedRaster[2] = new EastNorth(in.readDouble(), in.readDouble());
+            orgCroppedRaster[3] = new EastNorth(in.readDouble(), in.readDouble());
+        }
+        if (WMSLayer.currentFormat >= 4) {
+            imageOriginalHeight = in.readInt();
+            imageOriginalWidth = in.readInt();
+        }
+        image = ImageIO.read(ImageIO.createImageInputStream(in));
+        updatePixelPer();
+    }
+
+    /**
+     * Method required by BufferedImage serialization.
+     * Use only primitives for stability in time (not influenced by josm-core changes).
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeDouble(max.getX()); out.writeDouble(max.getY());
+        out.writeDouble(min.getX()); out.writeDouble(min.getY());
+        if (orgRaster == null) { // just in case we save an old format layer already cached
+            orgRaster = new EastNorth[4];
+            orgCroppedRaster = new EastNorth[4];
+        }
+        out.writeDouble(angle);
+        out.writeDouble(orgRaster[0].getX()); out.writeDouble(orgRaster[0].getY());
+        out.writeDouble(orgRaster[1].getX()); out.writeDouble(orgRaster[1].getY());
+        out.writeDouble(orgRaster[2].getX()); out.writeDouble(orgRaster[2].getY());
+        out.writeDouble(orgRaster[3].getX()); out.writeDouble(orgRaster[3].getY());
+        out.writeDouble(orgCroppedRaster[0].getX()); out.writeDouble(orgCroppedRaster[0].getY());
+        out.writeDouble(orgCroppedRaster[1].getX()); out.writeDouble(orgCroppedRaster[1].getY());
+        out.writeDouble(orgCroppedRaster[2].getX()); out.writeDouble(orgCroppedRaster[2].getY());
+        out.writeDouble(orgCroppedRaster[3].getX()); out.writeDouble(orgCroppedRaster[3].getY());
+        // Write image as a format 3 if cache was loaded with this format to avoid incompatibilities.
+        if (WMSLayer.currentFormat >= 4) {
+            out.writeInt(imageOriginalHeight);
+            out.writeInt(imageOriginalWidth);
+        }
+        ImageIO.write(image, "png", ImageIO.createImageOutputStream(out));
+    }
+
+    private void updatePixelPer() {
+        pixelPerEast = image.getWidth()/(max.east()-min.east());
+        pixelPerNorth = image.getHeight()/(max.north()-min.north());
+    }
+
+    public double getPixelPerEast() {
+        return pixelPerEast;
+    }
+
+    public double getPixelPerNorth() {
+        return pixelPerNorth;
+    }
+
+    @Override
+    public String toString() {
+        return "GeorefImage[min=" + min + ", max=" + max + ", image" + image + "]";
+    }
+
+    /*
+     * Following methods are used for affine transformation of two points p1 and p2
+     */
+    /**
+     * Add a translation (dx, dy) to this image min,max coordinates
+     * @param dx delta added to X image coordinate
+     * @param dy delta added to Y image coordinate
+     */
+    public void shear(double dx, double dy) {
+        min = new EastNorth(min.east() + dx, min.north() + dy);
+        max = new EastNorth(max.east() + dx, max.north() + dy);
+        for (int i = 0; i < 4; i++) {
+            orgRaster[i] = new EastNorth(orgRaster[i].east() + dx, orgRaster[i].north() + dy);
+            orgCroppedRaster[i] = new EastNorth(orgCroppedRaster[i].east() + dx, orgCroppedRaster[i].north() + dy);
+        }
+    }
+
+    /**
+     * Change this image scale by moving the min,max coordinates around an anchor
+     */
+    public void scale(EastNorth anchor, double proportion) {
+        min = anchor.interpolate(min, proportion);
+        max = anchor.interpolate(max, proportion);
+        for (int i = 0; i < 4; i++) {
+            orgRaster[i] = anchor.interpolate(orgRaster[i], proportion);
+            orgCroppedRaster[i] = anchor.interpolate(orgCroppedRaster[i], proportion);
+        }
+        updatePixelPer();
+    }
+
+    /**
+     * Rotate this image and its min/max coordinates around anchor point
+     * @param anchor anchor of rotation
+     * @param old_ang previous angle of image before rotation (0 the first time)(in radian)
+     * @param delta_ang angle of rotation (in radian)
+     */
+    public void rotate(EastNorth anchor, double delta_ang) {
+        if (orgRaster == null || orgCroppedRaster == null)
+            return;
+        // rotate the bounding boxes coordinates first
+        for (int i = 0; i < 4; i++) {
+            orgRaster[i] = orgRaster[i].rotate(anchor, delta_ang);
+            orgCroppedRaster[i] = orgCroppedRaster[i].rotate(anchor, delta_ang);
+        }
+        // rotate the image now
+        double sin = Math.abs(Math.sin(angle+delta_ang)), cos = Math.abs(Math.cos(angle+delta_ang));
+        int w = imageOriginalWidth, h = imageOriginalHeight;
+        int neww = (int) Math.floor(w*cos+h*sin);
+        int newh = (int) Math.floor(h*cos+w*sin);
+        GraphicsConfiguration gc = getDefaultConfiguration();
+        BufferedImage result = gc.createCompatibleImage(neww, newh, image.getTransparency());
+        Graphics2D g = result.createGraphics();
+        g.translate((neww-image.getWidth())/2, (newh-image.getHeight())/2);
+        g.rotate(delta_ang, image.getWidth()/2, image.getHeight()/2);
+        g.drawRenderedImage(image, null);
+        g.dispose();
+        image = result;
+        EastNorthBound enb = computeNewBounding(orgCroppedRaster[0], orgCroppedRaster[1], orgCroppedRaster[2], orgCroppedRaster[3]);
+        min = enb.min;
+        max = enb.max;
+        angle += delta_ang;
+    }
+
+    /**
+     * Crop the image based on new bbox coordinates adj1 and adj2 (for raster images only).
+     * @param adj1 is the new corner bottom, left
+     * @param adj2 is the new corner top, right
+     */
+    public void crop(EastNorth adj1, EastNorth adj2) {
+        // s1 and s2 have 0,0 at top, left where all EastNorth coord. have 0,0 at bottom, left
+        int sx1 = (int) ((adj1.getX() - min.getX())*getPixelPerEast());
+        int sy1 = (int) ((max.getY() - adj2.getY())*getPixelPerNorth());
+        int sx2 = (int) ((adj2.getX() - min.getX())*getPixelPerEast());
+        int sy2 = (int) ((max.getY() - adj1.getY())*getPixelPerNorth());
+        int newWidth = Math.abs(sx2 - sx1);
+        int newHeight = Math.abs(sy2 - sy1);
+        BufferedImage new_img = new BufferedImage(newWidth, newHeight, image.getType());
+        Graphics g = new_img.getGraphics();
+        g.drawImage(image, 0, 0, newWidth-1, newHeight-1,
+                sx1, sy1, sx2, sy2,
+                this);
+        image = new_img;
+        this.min = adj1;
+        this.max = adj2;
+        this.orgCroppedRaster[0] = min;
+        this.orgCroppedRaster[1] = new EastNorth(min.east(), max.north());
+        this.orgCroppedRaster[2] = max;
+        this.orgCroppedRaster[3] = new EastNorth(max.east(), min.north());
+        this.imageOriginalWidth = newWidth;
+        this.imageOriginalHeight = newHeight;
+        updatePixelPer();
+    }
+
+    @Override
+    public boolean imageUpdate(Image img, int infoflags, int x, int y,
+            int width, int height) {
+        return false;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GrabThread.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GrabThread.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/GrabThread.java	(revision 33637)
@@ -0,0 +1,235 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.io.OsmTransferException;
+
+public class GrabThread extends Thread {
+
+    private boolean canceled;
+
+    private CadastreGrabber grabber;
+
+    private WMSLayer wmsLayer;
+
+    private Lock lockImagesToGrag = new ReentrantLock();
+
+    private ArrayList<EastNorthBound> imagesToGrab = new ArrayList<>();
+
+    private CacheControl cacheControl = null;
+
+    private EastNorthBound currentGrabImage;
+
+    private Lock lockCurrentGrabImage = new ReentrantLock();
+
+    /**
+     * Call directly grabber for raster images or prepare thread for vector images
+     */
+    public void addImages(ArrayList<EastNorthBound> moreImages) {
+        lockImagesToGrag.lock();
+        imagesToGrab.addAll(moreImages);
+        lockImagesToGrag.unlock();
+        synchronized (this) {
+            this.notify();
+        }
+        Main.info("Added " + moreImages.size() + " to the grab thread");
+        if (wmsLayer.isRaster()) {
+            waitNotification();
+        }
+    }
+
+    public int getImagesToGrabSize() {
+        lockImagesToGrag.lock();
+        int size = imagesToGrab.size();
+        lockImagesToGrag.unlock();
+        return size;
+    }
+
+    public ArrayList<EastNorthBound> getImagesToGrabCopy() {
+        ArrayList<EastNorthBound> copyList = new ArrayList<>();
+        lockImagesToGrag.lock();
+        for (EastNorthBound img : imagesToGrab) {
+            EastNorthBound imgCpy = new EastNorthBound(img.min, img.max);
+            copyList.add(imgCpy);
+        }
+        lockImagesToGrag.unlock();
+        return copyList;
+    }
+
+    public void clearImagesToGrab() {
+        lockImagesToGrag.lock();
+        imagesToGrab.clear();
+        lockImagesToGrag.unlock();
+    }
+
+    @Override
+    public void run() {
+        for (;;) {
+            while (getImagesToGrabSize() > 0) {
+                lockImagesToGrag.lock();
+                lockCurrentGrabImage.lock();
+                currentGrabImage = imagesToGrab.get(0);
+                lockCurrentGrabImage.unlock();
+                imagesToGrab.remove(0);
+                lockImagesToGrag.unlock();
+                if (canceled) {
+                    break;
+                } else {
+                    GeorefImage newImage;
+                    try {
+                        Main.map.repaint(); // paint the current grab box
+                        newImage = grabber.grab(wmsLayer, currentGrabImage.min, currentGrabImage.max);
+                    } catch (IOException e) {
+                        Main.warn("Download action canceled by user or server did not respond");
+                        setCanceled(true);
+                        break;
+                    } catch (OsmTransferException e) {
+                        Main.error("OSM transfer failed");
+                        setCanceled(true);
+                        break;
+                    }
+                    if (grabber.getWmsInterface().downloadCanceled) {
+                        Main.info("Download action canceled by user");
+                        setCanceled(true);
+                        break;
+                    }
+                    try {
+                        if (CadastrePlugin.backgroundTransparent) {
+                            wmsLayer.imagesLock.lock();
+                            for (GeorefImage img : wmsLayer.getImages()) {
+                                if (img.overlap(newImage))
+                                    // mask overlapping zone in already grabbed image
+                                    img.withdraw(newImage);
+                                else
+                                    // mask overlapping zone in new image only when new image covers completely the
+                                    // existing image
+                                    newImage.withdraw(img);
+                            }
+                            wmsLayer.imagesLock.unlock();
+                        }
+                        wmsLayer.addImage(newImage);
+                        wmsLayer.invalidate();
+                        saveToCache(newImage);
+                    } catch (NullPointerException e) {
+                        Main.info("Layer destroyed. Cancel grab thread");
+                        setCanceled(true);
+                    }
+                }
+            }
+            Main.info("grab thread list empty");
+            lockCurrentGrabImage.lock();
+            currentGrabImage = null;
+            lockCurrentGrabImage.unlock();
+            if (canceled) {
+                clearImagesToGrab();
+                canceled = false;
+            }
+            if (wmsLayer.isRaster()) {
+                notifyWaiter();
+            }
+            waitNotification();
+        }
+    }
+
+    public void saveToCache(GeorefImage image) {
+        if (CacheControl.cacheEnabled && !wmsLayer.isRaster()) {
+            getCacheControl().saveCache(image);
+        }
+    }
+
+    public void saveNewCache() {
+        if (CacheControl.cacheEnabled) {
+            getCacheControl().deleteCacheFile();
+            wmsLayer.imagesLock.lock();
+            for (GeorefImage image : wmsLayer.getImages()) {
+                getCacheControl().saveCache(image);
+            }
+            wmsLayer.imagesLock.unlock();
+        }
+    }
+
+    public void cancel() {
+        clearImagesToGrab();
+        if (cacheControl != null) {
+            while (!cacheControl.isCachePipeEmpty()) {
+                Main.info("Try to close a WMSLayer which is currently saving in cache : wait 1 sec.");
+                CadastrePlugin.safeSleep(1000);
+            }
+        }
+    }
+
+    public CacheControl getCacheControl() {
+        if (cacheControl == null)
+            cacheControl = new CacheControl(wmsLayer);
+        return cacheControl;
+    }
+
+    public GrabThread(WMSLayer wmsLayer) {
+        this.wmsLayer = wmsLayer;
+    }
+
+    public void paintBoxesToGrab(Graphics g, MapView mv) {
+        if (getImagesToGrabSize() > 0) {
+            ArrayList<EastNorthBound> imagesToGrab = getImagesToGrabCopy();
+            for (EastNorthBound img : imagesToGrab) {
+                paintBox(g, mv, img, Color.red);
+            }
+        }
+        lockCurrentGrabImage.lock();
+        if (currentGrabImage != null) {
+            paintBox(g, mv, currentGrabImage, Color.orange);
+        }
+        lockCurrentGrabImage.unlock();
+    }
+
+    private void paintBox(Graphics g, MapView mv, EastNorthBound img, Color color) {
+        Point[] croppedPoint = new Point[5];
+        croppedPoint[0] = mv.getPoint(img.min);
+        croppedPoint[1] = mv.getPoint(new EastNorth(img.min.east(), img.max.north()));
+        croppedPoint[2] = mv.getPoint(img.max);
+        croppedPoint[3] = mv.getPoint(new EastNorth(img.max.east(), img.min.north()));
+        croppedPoint[4] = croppedPoint[0];
+        for (int i = 0; i < 4; i++) {
+            g.setColor(color);
+            g.drawLine(croppedPoint[i].x, croppedPoint[i].y, croppedPoint[i+1].x, croppedPoint[i+1].y);
+        }
+    }
+
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    public void setCanceled(boolean canceled) {
+        this.canceled = canceled;
+    }
+
+    public CadastreGrabber getGrabber() {
+        return grabber;
+    }
+
+    public void setGrabber(CadastreGrabber grabber) {
+        this.grabber = grabber;
+    }
+
+    private synchronized void notifyWaiter() {
+        this.notify();
+    }
+
+    private synchronized void waitNotification() {
+        try {
+            wait();
+        } catch (InterruptedException e) {
+            Main.error(e);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/ImageModifier.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/ImageModifier.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/ImageModifier.java	(revision 33637)
@@ -0,0 +1,178 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.Color;
+import java.awt.Transparency;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+import java.util.Objects;
+
+public abstract class ImageModifier {
+    /**
+     * Current background color used by cadastre.gouv.fr
+     */
+    //public static int cadastreBackgroundTransp = 1; // original white but transparent
+
+    protected int parcelColor = Color.RED.getRGB();
+
+    protected BufferedImage bufferedImage;
+
+    public static int[] cRoofColors = new int[] {-197380, -592138};
+    public static int[] cBuilingFootColors = new int[] {-256};
+
+    protected BufferedImage convert1(BufferedImage src) {
+        IndexColorModel icm = new IndexColorModel(
+            1, 2,
+            new byte[] {(byte) 0, (byte) 0xFF},
+            new byte[] {(byte) 0, (byte) 0xFF},
+            new byte[] {(byte) 0, (byte) 0xFF}
+        );
+
+        BufferedImage dest = new BufferedImage(
+            src.getWidth(), src.getHeight(),
+            BufferedImage.TYPE_BYTE_BINARY,
+            icm
+            );
+
+        ColorConvertOp cco = new ColorConvertOp(
+            src.getColorModel().getColorSpace(),
+            dest.getColorModel().getColorSpace(),
+            null
+            );
+
+        cco.filter(src, dest);
+
+        return dest;
+      }
+
+    /**
+     * Converts the source image to 4-bit colour
+     * using the default 16-colour palette:
+     * <ul>
+     *  <li>black</li><li>dark red</li><li>dark green</li>
+     *  <li>dark yellow</li><li>dark blue</li><li>dark magenta</li>
+     *  <li>dark cyan</li><li>dark grey</li><li>light grey</li>
+     *  <li>red</li><li>green</li><li>yellow</li><li>blue</li>
+     *  <li>magenta</li><li>cyan</li><li>white</li>
+     * </ul>
+     * No transparency.
+     * @param src the source image to convert
+     * @return a copy of the source image with a 4-bit colour depth, with the default colour pallette
+     */
+    protected BufferedImage convert4(BufferedImage src) {
+        int[] cmap = new int[] {
+          0x000000, 0x800000, 0x008000, 0x808000,
+          0x000080, 0x800080, 0x008080, 0x808080,
+          0xC0C0C0, 0xFF0000, 0x00FF00, 0xFFFF00,
+          0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF
+        };
+        return convert4(src, cmap);
+      }
+
+      /**
+       * Converts the source image to 4-bit colour
+       * using the given colour map.  No transparency.
+       * @param src the source image to convert
+       * @param cmap the colour map, which should contain no more than 16 entries
+       * The entries are in the form RRGGBB (hex).
+       * @return a copy of the source image with a 4-bit colour depth, with the custom colour pallette
+       */
+    protected BufferedImage convert4(BufferedImage src, int[] cmap) {
+        IndexColorModel icm = new IndexColorModel(
+            4, cmap.length, cmap, 0, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
+            );
+        BufferedImage dest = new BufferedImage(
+            src.getWidth(), src.getHeight(),
+            BufferedImage.TYPE_BYTE_BINARY,
+            icm
+            );
+        ColorConvertOp cco = new ColorConvertOp(
+            src.getColorModel().getColorSpace(),
+            dest.getColorModel().getColorSpace(),
+            null
+            );
+        cco.filter(src, dest);
+
+        return dest;
+      }
+
+    protected BufferedImage convert8(BufferedImage src) {
+        BufferedImage dest = new BufferedImage(
+            src.getWidth(), src.getHeight(),
+            BufferedImage.TYPE_BYTE_INDEXED
+            );
+        ColorConvertOp cco = new ColorConvertOp(
+            src.getColorModel().getColorSpace(),
+            dest.getColorModel().getColorSpace(),
+            null
+            );
+        cco.filter(src, dest);
+        return dest;
+      }
+
+    public boolean isBuildingColor(int rgb, boolean ignoreParcelColor) {
+        for (int i = 0; i < cBuilingFootColors.length; i++) {
+            if (rgb == cBuilingFootColors[i])
+                    return true;
+        }
+        if (ignoreParcelColor && (rgb == parcelColor))
+            return true;
+        return false;
+    }
+
+    public boolean isRoofColor(int rgb, boolean ignoreParcelColor) {
+        for (int i = 0; i < cRoofColors.length; i++) {
+            if (rgb == cRoofColors[i])
+                    return true;
+        }
+        if (ignoreParcelColor && (rgb == parcelColor))
+            return true;
+        return false;
+    }
+
+    public boolean isParcelColor(BufferedImage img, int x, int y) {
+        int rgb = img.getRGB(x, y);
+        return (rgb == parcelColor);
+    }
+
+    public boolean isBuildingOrRoofColor(BufferedImage img, int x, int y, boolean ignoreParcelColor) {
+        int rgb = img.getRGB(x, y);
+        boolean ret = isBuildingColor(rgb, ignoreParcelColor) || isRoofColor(rgb, ignoreParcelColor);
+        return ret;
+    }
+
+    public boolean isBuildingOrRoofColor(BufferedImage img, int x, int y, boolean colorType, boolean ignoreParcelColor) {
+        int rgb = img.getRGB(x, y);
+        boolean ret;
+        if (colorType)
+            ret = isBuildingColor(rgb, ignoreParcelColor);
+        else
+            ret = isRoofColor(rgb, ignoreParcelColor);
+        return ret;
+    }
+
+    /**
+     * Checks if the rgb value is the black background color
+     */
+    public boolean isBackgroundColor(BufferedImage img, int x, int y) {
+        return (img.getRGB(x, y) == -1);
+    }
+
+    /**
+     * Returns the buffered image.
+     * @return the buffered image
+     */
+    public final BufferedImage getBufferedImage() {
+        return bufferedImage;
+    }
+
+    /**
+     * Sets the buffered image.
+     * @param bufferedImage the buffered image
+     */
+    protected final void setBufferedImage(BufferedImage bufferedImage) {
+        this.bufferedImage = Objects.requireNonNull(bufferedImage);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionBoundaries.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionBoundaries.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionBoundaries.java	(revision 33637)
@@ -0,0 +1,40 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class MenuActionBoundaries extends JosmAction {
+
+    public static final String NAME = "Administrative boundary";
+
+    private static final long serialVersionUID = 1L;
+    private WMSLayer wmsLayer = null;
+
+    /**
+     * Constructs a new {@code MenuActionBoundaries}.
+     */
+    public MenuActionBoundaries() {
+        super(tr(NAME), "cadastre_small", tr("Extract commune boundary"), null, false);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        wmsLayer = WMSDownloadAction.getLayer();
+        if (wmsLayer != null) {
+            if (wmsLayer.isRaster()) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("Only on vectorized layers"), tr("Error"),
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+            DownloadSVGTask.download(wmsLayer);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionCancelGrab.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionCancelGrab.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionCancelGrab.java	(revision 33637)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.actions.JosmAction;
+
+@SuppressWarnings("serial")
+public class MenuActionCancelGrab extends JosmAction {
+
+    public static final String NAME = marktr("Cancel current grab");
+
+    private WMSLayer wmsLayer;
+
+    /**
+     * Constructs a new {@code MenuActionCancelGrab}.
+     * @param wmsLayer WMS layer
+     */
+    public MenuActionCancelGrab(WMSLayer wmsLayer) {
+        super(tr(NAME), null, tr("Cancel current grab (only vector images)"), null, false);
+        this.wmsLayer = wmsLayer;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        if (wmsLayer.grabThread.getImagesToGrabSize() > 0) {
+            wmsLayer.grabThread.cancel();
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrab.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrab.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrab.java	(revision 33637)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Action calling the wms grabber for cadastre.gouv.fr
+ */
+public class MenuActionGrab extends JosmAction {
+
+    public static final String NAME = marktr("Cadastre grab");
+
+    /**
+     * Constructs a new {@code MenuActionGrab}.
+     */
+    public MenuActionGrab() {
+        super(tr(NAME), "cadastre_small", tr("Download Image from French Cadastre WMS"),
+                Shortcut.registerShortcut("cadastre:grab", tr("Cadastre: {0}", tr("Download Image from French Cadastre WMS")),
+                KeyEvent.VK_F10, Shortcut.DIRECT), false, "cadastrefr/grab", true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (Main.map != null) {
+            if (CadastrePlugin.isCadastreProjection()) {
+                WMSLayer wmsLayer = WMSDownloadAction.getLayer();
+                if (wmsLayer != null)
+                    DownloadWMSVectorImage.download(wmsLayer);
+            } else {
+                CadastrePlugin.askToChangeProjection();
+            }
+        } else
+            new MenuActionNewLocation().actionPerformed(e);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrabPlanImage.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrabPlanImage.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionGrabPlanImage.java	(revision 33637)
@@ -0,0 +1,91 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class MenuActionGrabPlanImage extends JosmAction implements Runnable {
+
+    /**
+     * Action calling the wms grabber for non georeferenced images called "plan image"
+     */
+    private static final long serialVersionUID = 1L;
+
+    public static final String NAME = marktr("Georeference an image");
+
+    private DownloadWMSPlanImage downloadWMSPlanImage;
+    private WMSLayer wmsLayer;
+    private RasterImageGeoreferencer rasterImageGeoreferencer;
+
+    /**
+     * Constructs a new {@code MenuActionGrabPlanImage}.
+     */
+    public MenuActionGrabPlanImage() {
+        super(tr(NAME), "cadastre_small", tr("Grab non-georeferenced image"), null, false, "cadastrefr/grabplanimage", true);
+        rasterImageGeoreferencer = new RasterImageGeoreferencer();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (wmsLayer == null || Main.map == null || Main.map.mapView == null) return;
+        if (!rasterImageGeoreferencer.isRunning()) return;
+        if (Main.getLayerManager().containsLayer(wmsLayer))
+            return;
+        JOptionPane.showMessageDialog(Main.parent, tr("Georeferencing interrupted"));
+        rasterImageGeoreferencer.actionInterrupted();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent ae) {
+        if (Main.map != null) {
+            if (CadastrePlugin.isCadastreProjection()) {
+                wmsLayer = new MenuActionNewLocation().addNewLayer(new ArrayList<WMSLayer>());
+                if (wmsLayer == null) return;
+                downloadWMSPlanImage = new DownloadWMSPlanImage();
+                downloadWMSPlanImage.download(wmsLayer);
+                // download sub-images of the cadastre scan and join them into one single
+                Main.worker.execute(this);
+            } else {
+                CadastrePlugin.askToChangeProjection();
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        // wait until plan image is fully loaded and joined into one single image
+        boolean loadedFromCache = downloadWMSPlanImage.waitFinished();
+        if (loadedFromCache) {
+            Main.map.repaint();
+        } else if (wmsLayer.getImages().size() == 0) {
+            // action canceled or image loaded from cache (and already georeferenced)
+            rasterImageGeoreferencer.actionInterrupted();
+        } else {
+            int reply = JOptionPane.CANCEL_OPTION;
+            if (wmsLayer.isAlreadyGeoreferenced()) {
+                reply = JOptionPane.showConfirmDialog(null,
+                        tr("This image contains georeference data.\n"+
+                                "Do you want to use them ?"),
+                        null,
+                        JOptionPane.YES_NO_OPTION);
+            }
+            if (reply == JOptionPane.OK_OPTION) {
+                rasterImageGeoreferencer.transformGeoreferencedImg();
+            } else {
+                rasterImageGeoreferencer.addListener();
+                if (Main.pref.getBoolean("cadastrewms.noImageCropping", false) == false)
+                    rasterImageGeoreferencer.startCropping(wmsLayer);
+                else
+                    rasterImageGeoreferencer.startGeoreferencing(wmsLayer);
+            }
+        }
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionLoadFromCache.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionLoadFromCache.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionLoadFromCache.java	(revision 33637)
@@ -0,0 +1,113 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.layer.Layer;
+
+public class MenuActionLoadFromCache extends JosmAction {
+    private static final long serialVersionUID = 1L;
+
+    public static final String name = marktr("Load layer from cache");
+
+    /**
+     * Constructs a new {@code MenuActionLoadFromCache}.
+     */
+    public MenuActionLoadFromCache() {
+        super(tr(name), "cadastre_small", tr("Load location from cache (only if cache is enabled)"), null, false, "cadastrefr/loadfromcache", true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        JFileChooser fc = createAndOpenFileChooser();
+        if (fc == null)
+            return;
+
+        File[] files = fc.getSelectedFiles();
+        int layoutZone = CadastrePlugin.getCadastreProjectionLayoutZone();
+        nextFile:
+        for (File file : files) {
+            if (file.exists()) {
+                String filename = file.getName();
+                String ext = (filename.lastIndexOf('.') == -1) ? "" : filename.substring(filename.lastIndexOf('.')+1, filename.length());
+                if ((ext.length() == 3 && ext.substring(0, CacheControl.C_LAMBERT_CC_9Z.length()).equals(CacheControl.C_LAMBERT_CC_9Z) &&
+                    !(CadastrePlugin.isLambert_cc9()))
+                    || (ext.length() == 4 && ext.substring(0, CacheControl.C_UTM20N.length()).equals(CacheControl.C_UTM20N) &&
+                            !(CadastrePlugin.isUtm_france_dom()))
+                    || (ext.length() == 1) && !(CadastrePlugin.isLambert())) {
+                        JOptionPane.showMessageDialog(Main.parent, tr("{0} not allowed with the current projection", filename),
+                                tr("Error"), JOptionPane.ERROR_MESSAGE);
+                        continue;
+                } else {
+                    String location = filename.substring(0, filename.lastIndexOf('.'));
+                    if (ext.length() == 3 && ext.substring(0, CacheControl.C_LAMBERT_CC_9Z.length()).equals(CacheControl.C_LAMBERT_CC_9Z))
+                        ext = ext.substring(2);
+                    else if (ext.length() == 4 && ext.substring(0, CacheControl.C_UTM20N.length()).equals(CacheControl.C_UTM20N))
+                        ext = ext.substring(3);
+                    // check the extension and its compatibility with current projection
+                    try {
+                        int cacheZone = Integer.parseInt(ext) - 1;
+                        if (cacheZone >= 0 && cacheZone <= 9) {
+                            if (cacheZone != layoutZone) {
+                                JOptionPane.showMessageDialog(Main.parent,
+                                        tr("Cannot load cache {0} which is not compatible with current projection zone", filename),
+                                        tr("Error"), JOptionPane.ERROR_MESSAGE);
+                                continue nextFile;
+                            } else
+                                Main.info("Load cache " + filename);
+                        }
+                    } catch (NumberFormatException ex) {
+                        JOptionPane.showMessageDialog(Main.parent, tr("Selected file {0} is not a cache file from this plugin (invalid extension)", filename), tr("Error"), JOptionPane.ERROR_MESSAGE);
+                        continue nextFile;
+                    }
+                    // check if the selected cache is not already displayed
+                    if (Main.map != null) {
+                        for (Layer l : Main.getLayerManager().getLayers()) {
+                            if (l instanceof WMSLayer && l.getName().equals(location)) {
+                                JOptionPane.showMessageDialog(Main.parent, tr("The location {0} is already on screen. Cache not loaded.", filename), tr("Error"), JOptionPane.ERROR_MESSAGE);
+                                continue nextFile;
+                            }
+                        }
+                    }
+                    // create layer and load cache
+                    WMSLayer wmsLayer = new WMSLayer("", "", Integer.parseInt(ext)-1);
+                    if (wmsLayer.grabThread.getCacheControl().loadCache(file, layoutZone)) {
+                        CadastrePlugin.addWMSLayer(wmsLayer);
+                    }
+                }
+            }
+        }
+
+    }
+
+    protected static JFileChooser createAndOpenFileChooser() {
+        JFileChooser fc = new JFileChooser(new File(CadastrePlugin.cacheDir));
+        fc.setMultiSelectionEnabled(true);
+        int layoutZone = CadastrePlugin.getCadastreProjectionLayoutZone();
+        if (layoutZone != -1) {
+            if (CadastrePlugin.isLambert())
+                fc.addChoosableFileFilter(CacheFileLambert4ZoneFilter.filters[layoutZone]);
+            else if (CadastrePlugin.isLambert_cc9())
+                fc.addChoosableFileFilter(CacheFileLambert9ZoneFilter.filters[layoutZone]);
+            else if (CadastrePlugin.isUtm_france_dom())
+                fc.addChoosableFileFilter(CacheFileUTM20NFilter.filters[layoutZone]);
+        }
+        fc.setAcceptAllFileFilterUsed(false);
+
+        int answer = fc.showOpenDialog(Main.parent);
+        if (answer != JFileChooser.APPROVE_OPTION)
+            return null;
+
+        return fc;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionNewLocation.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionNewLocation.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionNewLocation.java	(revision 33637)
@@ -0,0 +1,134 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.GBC;
+
+public class MenuActionNewLocation extends JosmAction {
+
+    private static final long serialVersionUID = 1L;
+
+    // CHECKSTYLE.OFF: LineLength
+    // CHECKSTYLE.OFF: SingleSpaceSeparator
+
+    private static final String[] departements = {
+        "", tr("(optional)"),
+        "001", "01 - Ain",                 "002", "02 - Aisne",              "003", "03 - Allier",                "004", "04 - Alpes de Haute-Provence", "005", "05 - Hautes-Alpes",
+        "006", "06 - Alpes-Maritimes",     "007", "07 - Ard\u00eache",       "008", "08 - Ardennes",              "009", "09 - Ari\u00e8ge",             "010", "10 - Aube",
+        "011", "11 - Aude",                "012", "12 - Aveyron",            "013", "13 - Bouches-du-Rh\u00f4ne", "014", "14 - Calvados",                "015", "15 - Cantal",
+        "016", "16 - Charente",            "017", "17 - Charente-Maritime",  "018", "18 - Cher",                  "019", "19 - Corr\u00e8ze",
+        "02A", "2A - Corse-du-Sud",        "02B", "2B - Haute-Corse",
+        "021", "21 - C\u00f4te-d'Or",      "022", "22 - C\u00f4tes d'Armor", "023", "23 - Creuse",                "024", "24 - Dordogne",                "025", "25 - Doubs",
+        "026", "26 - Dr\u00f4me",          "027", "27 - Eure",               "028", "28 - Eure-et-Loir",          "029", "29 - Finist\u00e8re",          "030", "30 - Gard",
+        "031", "31 - Haute-Garonne",       "032", "32 - Gers",               "033", "33 - Gironde",               "034", "34 - H\u00e9rault",            "035", "35 - Ille-et-Vilaine",
+        "036", "36 - Indre",               "037", "37 - Indre-et-Loire",     "038", "38 - Is\u00e8re",            "039", "39 - Jura",                    "040", "40 - Landes",
+        "041", "41 - Loir-et-Cher",        "042", "42 - Loire",              "043", "43 - Haute-Loire",           "044", "44 - Loire-Atlantique",        "045", "45 - Loiret",
+        "046", "46 - Lot",                 "047", "47 - Lot-et-Garonne",     "048", "48 - Loz\u00e8re",           "049", "49 - Maine-et-Loire",          "050", "50 - Manche",
+        "051", "51 - Marne",               "052", "52 - Haute-Marne",        "053", "53 - Mayenne",               "054", "54 - Meurthe-et-Moselle",      "055", "55 - Meuse",
+        "056", "56 - Morbihan",            "057", "57 - Moselle",            "058", "58 - Ni\u00e8vre",           "059", "59 - Nord",                    "060", "60 - Oise",
+        "061", "61 - Orne",                "062", "62 - Pas-de-Calais",      "063", "63 - Puy-de-D\u00f4me",      "064", "64 - Pyr\u00e9n\u00e9es-Atlantiques", "065", "65 - Hautes-Pyr\u00e9n\u00e9es",
+        "066", "66 - Pyr\u00e9n\u00e9es-Orientales", "067", "67 - Bas-Rhin", "068", "68 - Haut-Rhin",             "069", "69 - Rh\u00f4ne",              "070", "70 - Haute-Sa\u00f4ne",
+        "071", "71 - Sa\u00f4ne-et-Loire", "072", "72 - Sarthe",             "073", "73 - Savoie",                "074", "74 - Haute-Savoie",            "075", "75 - Paris",
+        "076", "76 - Seine-Maritime",      "077", "77 - Seine-et-Marne",     "078", "78 - Yvelines",              "079", "79 - Deux-S\u00e8vres",        "080", "80 - Somme",
+        "081", "81 - Tarn",                "082", "82 - Tarn-et-Garonne",    "083", "83 - Var",                   "084", "84 - Vaucluse",                "085", "85 - Vend\u00e9e",
+        "086", "86 - Vienne",              "087", "87 - Haute-Vienne",       "088", "88 - Vosges",                "089", "89 - Yonne",                   "090", "90 - Territoire de Belfort",
+        "091", "91 - Essonne",             "092", "92 - Hauts-de-Seine",     "093", "93 - Seine-Saint-Denis",     "094", "94 - Val-de-Marne",            "095", "95 - Val-d'Oise",
+        "971", "971 - Guadeloupe",         "972", "972 - Martinique",        "973", "973 - Guyane",               "974", "974 - R\u00e9union"
+    };
+
+    // CHECKSTYLE.ON: SingleSpaceSeparator
+    // CHECKSTYLE.ON: LineLength
+
+    public MenuActionNewLocation() {
+        super(tr("Change location"), "cadastre_small", tr("Set a new location for the next request"), null, false,
+                "cadastrefr/newlocation", true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        WMSLayer wmsLayer = addNewLayer(new ArrayList<WMSLayer>());
+        if (wmsLayer != null)
+            DownloadWMSVectorImage.download(wmsLayer);
+    }
+
+    public WMSLayer addNewLayer(ArrayList<WMSLayer> existingLayers) {
+        String location = "";
+        String codeDepartement = "";
+        String codeCommune = "";
+        JLabel labelSectionNewLocation = new JLabel(tr("Add a new municipality layer"));
+        JPanel p = new JPanel(new GridBagLayout());
+        JLabel labelLocation = new JLabel(tr("Commune"));
+        final JTextField inputTown = new JTextField(Main.pref.get("cadastrewms.location"));
+        inputTown.setToolTipText(tr("<html>Enter the town,village or city name.<br>"
+                + "Use the syntax and punctuation known by www.cadastre.gouv.fr .</html>"));
+        JLabel labelDepartement = new JLabel(tr("Departement"));
+        final JComboBox<String> inputDepartement = new JComboBox<>();
+        for (int i = 1; i < departements.length; i += 2) {
+            inputDepartement.addItem(departements[i]);
+        }
+        inputDepartement.setToolTipText(tr("<html>Departement number (optional)</html>"));
+        if (!Main.pref.get("cadastrewms.codeDepartement").equals("")) {
+            for (int i = 0; i < departements.length; i += 2) {
+                if (departements[i].equals(Main.pref.get("cadastrewms.codeDepartement")))
+                    inputDepartement.setSelectedIndex(i/2);
+            }
+        }
+        p.add(labelSectionNewLocation, GBC.eol());
+        p.add(labelLocation, GBC.std().insets(10, 0, 0, 0));
+        p.add(inputTown, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
+        p.add(labelDepartement, GBC.std().insets(10, 0, 0, 0));
+        p.add(inputDepartement, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
+        JOptionPane pane = new JOptionPane(p, JOptionPane.INFORMATION_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null) {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void selectInitialValue() {
+                inputTown.requestFocusInWindow();
+                inputTown.selectAll();
+            }
+        };
+        pane.createDialog(Main.parent, tr("Add new layer")).setVisible(true);
+        if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
+            return null;
+
+        WMSLayer wmsLayer = null;
+        if (!inputTown.getText().equals("")) {
+            location = inputTown.getText().toUpperCase();
+            codeDepartement = departements[inputDepartement.getSelectedIndex()*2];
+            Main.pref.put("cadastrewms.location", location);
+            Main.pref.put("cadastrewms.codeCommune", codeCommune);
+            Main.pref.put("cadastrewms.codeDepartement", codeDepartement);
+            if (Main.map != null) {
+                for (Layer l : Main.getLayerManager().getLayers()) {
+                    if (l instanceof WMSLayer && l.getName().equalsIgnoreCase(location)) {
+                        return null;
+                    }
+                }
+            }
+            // add the layer if it doesn't exist
+            int zone = CadastrePlugin.getCadastreProjectionLayoutZone();
+            wmsLayer = new WMSLayer(location, codeCommune, zone);
+            wmsLayer.setDepartement(codeDepartement);
+            CadastrePlugin.addWMSLayer(wmsLayer);
+            Main.info("Add new layer with Location:" + inputTown.getText());
+        } else if (existingLayers != null && existingLayers.size() > 0 && Main.getLayerManager().getActiveLayer() instanceof WMSLayer) {
+            wmsLayer = (WMSLayer) Main.getLayerManager().getActiveLayer();
+        }
+
+        return wmsLayer;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionOpenPreferences.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionOpenPreferences.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionOpenPreferences.java	(revision 33637)
@@ -0,0 +1,31 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+
+public class MenuActionOpenPreferences extends JosmAction {
+    private static final long serialVersionUID = 1L;
+
+    public static final String NAME = marktr("Preferences");
+
+    /**
+     * Constructs a new {@code MenuActionOpenPreferences}.
+     */
+    public MenuActionOpenPreferences() {
+        super(tr(NAME), "cadastre_small", tr("Open Cadastre Preferences"), null, false, "cadastrefr/openpreferences", true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        PreferenceDialog p = new PreferenceDialog(Main.parent);
+        p.selectPreferencesTabByClass(CadastrePreferenceSetting.class);
+        p.setVisible(true);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionRefineGeoRef.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionRefineGeoRef.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionRefineGeoRef.java	(revision 33637)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class MenuActionRefineGeoRef extends JosmAction {
+
+    public static final String NAME = marktr("Refine georeferencing");
+
+    private WMSLayer wmsLayer;
+    private RasterImageGeoreferencer rasterImageGeoreferencer;
+
+    /**
+     * Constructs a new {@code MenuActionRefineGeoRef}.
+     * @param wmsLayer WMS layer
+     */
+    public MenuActionRefineGeoRef(WMSLayer wmsLayer) {
+        super(tr(NAME), null, tr("Improve georeferencing (only raster images)"), null, false);
+        this.wmsLayer = wmsLayer;
+        rasterImageGeoreferencer = new RasterImageGeoreferencer();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        if (!wmsLayer.isRaster()) {
+            Main.info("MenuActionRefineGeoRef called for unexpected layer type");
+            return;
+        }
+        if (CadastrePlugin.isCadastreProjection()) {
+            //wmsLayer = WMSDownloadAction.getLayer();
+        } else {
+            CadastrePlugin.askToChangeProjection();
+        }
+        rasterImageGeoreferencer.addListener();
+        rasterImageGeoreferencer.startGeoreferencing(wmsLayer);
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionResetCookie.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionResetCookie.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionResetCookie.java	(revision 33637)
@@ -0,0 +1,25 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class MenuActionResetCookie extends JosmAction {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public MenuActionResetCookie() {
+        super(tr("Reset cookie"), "cadastre_small", tr("Get a new cookie (session timeout)"), null, false);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        //CadastrePlugin.cadastreGrabber.getWmsInterface().resetCookie();
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionSaveRasterAs.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionSaveRasterAs.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/MenuActionSaveRasterAs.java	(revision 33637)
@@ -0,0 +1,139 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.coverage.grid.GridCoverageFactory;
+import org.geotools.coverage.grid.io.AbstractGridFormat;
+import org.geotools.gce.geotiff.GeoTiffFormat;
+import org.geotools.gce.geotiff.GeoTiffWriteParams;
+import org.geotools.gce.geotiff.GeoTiffWriter;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.referencing.CRS;
+import org.opengis.parameter.GeneralParameterValue;
+import org.opengis.parameter.ParameterValueGroup;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class MenuActionSaveRasterAs extends JosmAction {
+
+    public static final String NAME = marktr("Save image as...");
+
+    private static final long serialVersionUID = 1L;
+
+    private WMSLayer wmsLayer;
+
+    public class FiltrePng extends FileFilter {
+        @Override
+        public boolean accept(File file) {
+            if (file.isDirectory()) {
+                return true;
+            }
+            return file.getName().toLowerCase(Locale.ENGLISH).endsWith(".png");
+        }
+
+        @Override
+        public String getDescription() {
+            return tr("PNG files (*.png)");
+        }
+    }
+
+    public class FiltreTiff extends FileFilter {
+        @Override
+        public boolean accept(File file) {
+            if (file.isDirectory()) {
+                return true;
+            }
+            return file.getName().toLowerCase(Locale.ENGLISH).endsWith(".tif");
+        }
+
+        @Override
+        public String getDescription() {
+            return tr("GeoTiff files (*.tif)");
+        }
+    }
+
+    FiltreTiff filtreTiff = new FiltreTiff();
+    FiltrePng filtrePng = new FiltrePng();
+
+    /**
+     * Constructs a new {@code MenuActionSaveRasterAs}.
+     * @param wmsLayer WMS layer
+     */
+    public MenuActionSaveRasterAs(WMSLayer wmsLayer) {
+        super(tr(NAME), "save", tr("Export image (only raster images)"), null, false);
+        this.wmsLayer = wmsLayer;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        File file;
+        JFileChooser fc = new JFileChooser();
+        fc.addChoosableFileFilter(filtreTiff);
+        fc.addChoosableFileFilter(filtrePng);
+        fc.setFileFilter(filtreTiff);
+        int returnVal = fc.showSaveDialog(Main.parent);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+            file = fc.getSelectedFile();
+            BufferedImage bi = wmsLayer.getImage(0).image;
+            if (fc.getFileFilter().equals(filtrePng)) {
+                if (!file.getName().endsWith(".png"))
+                    file = new File(file.getParent(), file.getName()+".png");
+                try {
+                    ImageIO.write(bi, "png", file);
+/*
+                    FileOutputStream flux = new FileOutputStream(file);
+                    BufferedOutputStream fluxBuf = new BufferedOutputStream(flux);
+                    JPEGImageEncoder codec = JPEGCodec.createJPEGEncoder(fluxBuf, JPEGCodec.getDefaultJPEGEncodeParam(bi));
+                    codec.encode(bi);
+                    fluxBuf.close();
+*/
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            } else if (fc.getFileFilter().equals(filtreTiff)) {
+                boolean alpha = bi.getColorModel().hasAlpha();
+                Main.info("image with alpha channel : " + alpha);
+                try {
+                    double x = wmsLayer.getImage(0).min.east();
+                    double y = wmsLayer.getImage(0).min.north();
+                    Envelope2D bbox = new Envelope2D(CRS.decode("EPSG:27561"),
+                            x, y,
+                            wmsLayer.getImage(0).max.east()-x, wmsLayer.getImage(0).max.north()-y);
+                    GridCoverageFactory factory = new GridCoverageFactory();
+                    GridCoverage2D coverage = factory.create("tiff", bi, bbox);
+                    final File output = new File(file.getParent(), file.getName()+".tif");
+                    GeoTiffWriter gtwriter = new GeoTiffWriter(output);
+                    GeoTiffWriteParams wp = new GeoTiffWriteParams();
+                    wp.setCompressionMode(GeoTiffWriteParams.MODE_EXPLICIT);
+                    wp.setCompressionType("LZW");
+                    wp.setCompressionQuality(0.75F);
+                    final GeoTiffFormat format = new GeoTiffFormat();
+                    final ParameterValueGroup params = format.getWriteParameters();
+                    params.parameter(
+                                    AbstractGridFormat.GEOTOOLS_WRITE_PARAMS.getName().toString())
+                                    .setValue(wp);
+
+                    gtwriter.write(coverage, params.values().toArray(new GeneralParameterValue[1]));
+                    gtwriter.dispose();
+                    coverage.dispose(true);
+                } catch (Exception e) {
+                    Main.error(e);
+                }
+            }
+        }
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageGeoreferencer.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageGeoreferencer.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageGeoreferencer.java	(revision 33637)
@@ -0,0 +1,363 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.tools.GBC;
+
+public class RasterImageGeoreferencer implements MouseListener {
+
+    private int countMouseClicked = 0;
+    private int mode = 0;
+    private int cGetCorners = 1;
+    private int cGetLambertCrosspieces = 2;
+    private EastNorth ea1;
+    private EastNorth ea2;
+    private long mouseClickedTime = 0;
+    private WMSLayer wmsLayer;
+    private EastNorth georefpoint1;
+    private EastNorth georefpoint2;
+    private boolean ignoreMouseClick = false;
+    private boolean clickOnTheMap = false;
+
+    /**
+     * The time which needs to pass between two clicks during georeferencing, in milliseconds
+     */
+    private int initialClickDelay;
+
+    public void addListener() {
+        Main.map.mapView.addMouseListener(this);
+    }
+
+    /**
+    *
+    * @return false if all operations are canceled
+    */
+   public boolean startCropping(WMSLayer wmsLayer) {
+       this.wmsLayer = wmsLayer;
+       mode = cGetCorners;
+       countMouseClicked = 0;
+       initialClickDelay = Main.pref.getInteger("cadastrewms.georef-click-delay", 200);
+       mouseClickedTime = System.currentTimeMillis();
+       Object[] options = {"OK", "Cancel"};
+       int ret = JOptionPane.showOptionDialog(null,
+               tr("Click first corner for image cropping\n(two points required)"),
+               tr("Image cropping"),
+               JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE,
+               null, options, options[0]);
+       if (ret == JOptionPane.OK_OPTION) {
+           mouseClickedTime = System.currentTimeMillis();
+       } else
+           if (canceledOrRestartCurrAction("image cropping"))
+               return startCropping(wmsLayer);
+       return true;
+   }
+
+   /**
+    *
+    * @return false if all operations are canceled
+    */
+  public boolean startGeoreferencing(WMSLayer wmsLayer) {
+      this.wmsLayer = wmsLayer;
+      countMouseClicked = 0;
+      mode = cGetLambertCrosspieces;
+      initialClickDelay = Main.pref.getInteger("cadastrewms.georef-click-delay", 200);
+      mouseClickedTime = System.currentTimeMillis();
+      Object[] options = {"OK", "Cancel"};
+      int ret = JOptionPane.showOptionDialog(null,
+              tr("Click first Lambert crosspiece for georeferencing\n(two points required)"),
+              tr("Image georeferencing"),
+              JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE,
+              null, options, options[0]);
+      if (ret == JOptionPane.OK_OPTION) {
+          mouseClickedTime = System.currentTimeMillis();
+      } else
+          if (canceledOrRestartCurrAction("georeferencing"))
+              return startGeoreferencing(wmsLayer);
+      return true;
+  }
+
+  public boolean isRunning() {
+      return (countMouseClicked != 0 || mode != 0);
+  }
+
+  @Override
+  public void mouseClicked(MouseEvent e) {
+      if (System.currentTimeMillis() - mouseClickedTime < initialClickDelay) {
+          Main.info("mouse click bounce detected");
+          return; // mouse click anti-bounce
+      } else
+          mouseClickedTime = System.currentTimeMillis();
+      if (e.getButton() != MouseEvent.BUTTON1)
+          return;
+      if (ignoreMouseClick) return; // In case we are currently just allowing zooming to read lambert coordinates
+      EastNorth ea = Main.getProjection().latlon2eastNorth(Main.map.mapView.getLatLon(e.getX(), e.getY()));
+      Main.info("click:"+countMouseClicked+" ,"+ea+", mode:"+mode);
+      if (clickOnTheMap) {
+          clickOnTheMap = false;
+          handleNewCoordinates(ea.east(), ea.north());
+      } else {
+          // ignore clicks outside the image
+          if (ea.east() < wmsLayer.getImage(0).min.east() || ea.east() > wmsLayer.getImage(0).max.east()
+                  || ea.north() < wmsLayer.getImage(0).min.north() || ea.north() > wmsLayer.getImage(0).max.north()) {
+              Main.info("ignore click outside the image");
+              return;
+          }
+          countMouseClicked++;
+          if (mode == cGetCorners) {
+              if (countMouseClicked == 1) {
+                  ea1 = ea;
+                  continueCropping();
+              }
+              if (countMouseClicked == 2) {
+                  wmsLayer.cropImage(ea1, ea);
+                  wmsLayer.invalidate();
+                  startGeoreferencing(wmsLayer);
+              }
+          } else if (mode == cGetLambertCrosspieces) {
+              if (countMouseClicked == 1) {
+                  ea1 = ea;
+                  inputLambertPosition(); // This will automatically asks for second point and continue the georeferencing
+              }
+              if (countMouseClicked == 2) {
+                  ea2 = ea;
+                  inputLambertPosition(); // This will automatically ends the georeferencing
+              }
+          }
+      }
+  }
+
+  /**
+   *
+   * @return false if all operations are canceled
+   */
+ private boolean canceledOrRestartCurrAction(String action) {
+     Object[] options = {"Cancel", "Retry"};
+     int selectedValue = JOptionPane.showOptionDialog(null,
+             tr("Do you want to cancel completely\n"+
+                     "or just retry "+action+" ?"), "",
+             JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+             null, options, options[0]);
+     countMouseClicked = 0;
+     if (selectedValue == 0) { // "Cancel"
+         // remove layer
+         Main.getLayerManager().removeLayer(wmsLayer);
+         wmsLayer = null;
+         Main.map.mapView.removeMouseListener(this);
+         return false;
+     }
+     return true;
+ }
+
+ /**
+  * Use point org1 as anchor for scale, then move org1 to dst1, then rotate org2 on dst2
+  * around org1/dst1 anchor
+  * @param org1 first point at original coordinate system (the grabbed image)
+  * @param org2 second point
+  * @param dst1 first point at final destination coordinate system (the real east/north coordinate system)
+  * @param dst2 second point
+  */
+ private void affineTransform(EastNorth org1, EastNorth org2, EastNorth dst1, EastNorth dst2) {
+     // handle an NPE case I'm not able to reproduce
+     if (org1 == null || org2 == null || dst1 == null || dst2 == null) {
+         JOptionPane.showMessageDialog(Main.parent,
+                 tr("Ooops. I failed to catch all coordinates\n"+
+                    "correctly. Retry please."));
+         Main.warn("failed to transform: one coordinate missing:"
+                    +"org1="+org1+", org2="+org2+", dst1="+dst1+", dst2="+dst2);
+         return;
+     }
+     double angle = dst1.heading(dst2) - org1.heading(org2);
+     double proportion = dst1.distance(dst2)/org1.distance(org2);
+     // move
+     double dx = dst1.getX() - org1.getX();
+     double dy = dst1.getY() - org1.getY();
+     wmsLayer.getImage(0).shear(dx, dy);
+     // rotate : dst1 is anchor for rotation and scale
+     wmsLayer.getImage(0).rotate(dst1, angle);
+     // scale image from anchor dst1
+     wmsLayer.getImage(0).scale(dst1, proportion);
+ }
+
+ /**
+  * Ends the georeferencing by computing the affine transformation
+  */
+ private void endGeoreferencing() {
+     Main.map.mapView.removeMouseListener(this);
+     affineTransform(ea1, ea2, georefpoint1, georefpoint2);
+     wmsLayer.grabThread.saveNewCache();
+     wmsLayer.invalidate();
+     actionCompleted();
+     clickOnTheMap = false;
+     ignoreMouseClick = false;
+ }
+
+ private void inputLambertPosition() {
+     JLabel labelEnterPosition = new JLabel(
+             tr("Enter cadastre east,north position"));
+     JLabel labelWarning = new JLabel(
+             tr("(Warning: verify north with arrow !!)"));
+     JPanel p = new JPanel(new GridBagLayout());
+     JLabel labelEast = new JLabel(tr("East"));
+     JLabel labelNorth = new JLabel(tr("North"));
+     final JTextField inputEast = new JTextField();
+     final JTextField inputNorth = new JTextField();
+     p.add(labelEnterPosition, GBC.eol());
+     p.add(labelWarning, GBC.eol());
+     p.add(labelEast, GBC.std().insets(0, 0, 10, 0));
+     p.add(inputEast, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 5, 0, 5));
+     p.add(labelNorth, GBC.std().insets(0, 0, 10, 0));
+     p.add(inputNorth, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 5, 0, 5));
+     final Object[] options = {tr("OK"),
+             tr("Cancel"),
+             tr("I use the mouse")};
+     final JOptionPane pane = new JOptionPane(p,
+             JOptionPane.INFORMATION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION,
+             null, options, options[0]);
+     String number;
+     if (countMouseClicked == 1)
+         number = "first";
+     else
+         number = "second";
+     JDialog dialog = pane.createDialog(Main.parent, tr(
+             "Set {0} Lambert coordinates", number));
+     dialog.setModal(false);
+     ignoreMouseClick = true; // To avoid mouseClicked from being called
+                              // during coordinates reading
+     dialog.setAlwaysOnTop(true);
+     dialog.setVisible(true);
+     pane.addPropertyChangeListener(new PropertyChangeListener() {
+         @Override
+         public void propertyChange(PropertyChangeEvent evt) {
+             if (JOptionPane.VALUE_PROPERTY.equals(evt.getPropertyName())) {
+                 ignoreMouseClick = false;
+                 // Cancel
+                 if (pane.getValue().equals(options[1])) {
+                     if (canceledOrRestartCurrAction("georeferencing"))
+                         startGeoreferencing(wmsLayer);
+                 }
+                 // Click on the map
+                 if (pane.getValue().equals(options[2])) {
+                     clickOnTheMap = true;
+                 } else {
+                 // OK (coordinates manually entered)
+                     clickOnTheMap = false;
+                     if (inputEast.getText().length() != 0
+                             && inputNorth.getText().length() != 0) {
+                         double e, n;
+                         try {
+                             e = Double.parseDouble(inputEast.getText());
+                             n = Double.parseDouble(inputNorth.getText());
+                         } catch (NumberFormatException ex) {
+                             return;
+                         }
+                         handleNewCoordinates(e, n);
+                     }
+                 }
+             }
+         }
+     });
+ }
+
+ /**
+ *
+ * @return false if all operations are canceled
+ */
+private boolean continueCropping() {
+    Object[] options = {"OK", "Cancel"};
+    int ret = JOptionPane.showOptionDialog(null,
+            tr("Click second corner for image cropping"),
+            tr("Image cropping"),
+            JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE,
+            null, options, options[0]);
+    if (ret != JOptionPane.OK_OPTION) {
+        if (canceledOrRestartCurrAction("image cropping"))
+            return startCropping(wmsLayer);
+    }
+    return true;
+}
+
+public void transformGeoreferencedImg() {
+    georefpoint1 = new EastNorth(wmsLayer.X0, wmsLayer.Y0);
+    georefpoint2 = new EastNorth(wmsLayer.X0+wmsLayer.fX*wmsLayer.communeBBox.max.getX(),
+            wmsLayer.Y0+wmsLayer.fY*wmsLayer.communeBBox.max.getX());
+    ea1 = new EastNorth(wmsLayer.getImage(0).min.east(), wmsLayer.getImage(0).max.north());
+    EastNorth ea2 = wmsLayer.getImage(0).max;
+    affineTransform(ea1, ea2, georefpoint1, georefpoint2);
+    wmsLayer.grabThread.saveNewCache();
+    wmsLayer.invalidate();
+}
+
+
+ /**
+ *
+ * @return false if all operations are canceled
+ */
+private boolean continueGeoreferencing() {
+    Object[] options = {"OK", "Cancel"};
+    int ret = JOptionPane.showOptionDialog(null,
+            tr("Click second Lambert crosspiece for georeferencing"),
+            tr("Image georeferencing"),
+            JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE,
+            null, options, options[0]);
+    if (ret != JOptionPane.OK_OPTION) {
+        if (canceledOrRestartCurrAction("georeferencing"))
+            return startGeoreferencing(wmsLayer);
+    }
+    return true;
+}
+
+ private void handleNewCoordinates(double e, double n) {
+     if (countMouseClicked == 1) {
+         georefpoint1 = new EastNorth(e, n);
+         continueGeoreferencing();
+     } else {
+         georefpoint2 = new EastNorth(e, n);
+         endGeoreferencing();
+     }
+ }
+
+ private void actionCompleted() {
+     countMouseClicked = 0;
+     mode = 0;
+     mouseClickedTime = System.currentTimeMillis();
+ }
+
+ public void actionInterrupted() {
+     actionCompleted();
+     if (wmsLayer != null) {
+         Main.getLayerManager().removeLayer(wmsLayer);
+         wmsLayer = null;
+     }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent arg0) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent arg0) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent arg0) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent arg0) {
+ }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageModifier.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageModifier.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/RasterImageModifier.java	(revision 33637)
@@ -0,0 +1,106 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.IndexColorModel;
+
+import org.openstreetmap.josm.Main;
+
+public class RasterImageModifier extends ImageModifier {
+
+    private int cadastreBackground = -1; // white
+
+    public static int cadastreBackgroundTransp = 16777215; // original white but transparent
+
+    private boolean transparencyEnabled = false;
+
+    public RasterImageModifier(BufferedImage bi) {
+        setBufferedImage(bi);
+        transparencyEnabled = Main.pref.getBoolean("cadastrewms.backgroundTransparent");
+        if (transparencyEnabled)
+            makeTransparent();
+        if (Main.pref.getBoolean("cadastrewms.invertGrey"))
+            invertGrey();
+    }
+
+    /**
+     * Invert black/white/grey pixels (to change original black characters to white).
+     */
+    private void invertGrey() {
+        int w = bufferedImage.getWidth();
+        int h = bufferedImage.getHeight();
+        for (int x = 0; x < w; x++) {
+            for (int y = 0; y < h; y++) {
+                int pixel = bufferedImage.getRGB(x, y);
+                if ((!transparencyEnabled && pixel != cadastreBackground)
+                        || (transparencyEnabled && pixel != cadastreBackgroundTransp)) {
+                    bufferedImage.setRGB(x, y, reverseIfGrey(pixel));
+                }
+            }
+        }
+    }
+
+    /**
+     * Reverse the grey value if the pixel is grey (light grey becomes dark grey)
+     * Used for texts.
+     */
+    private int reverseIfGrey(int pixel) {
+        Color col = new Color(pixel);
+        int r = col.getRed();
+        int g = col.getGreen();
+        int b = col.getBlue();
+        if ((b == r) && (b == g)) {
+            pixel = (0x00 << 32) + ((byte) (255 - r) << 16) + ((byte) (255 - r) << 8) + ((byte) (255 - r));
+        }
+        return pixel;
+    }
+
+    private void makeTransparent() {
+        if (bufferedImage.getColorModel() instanceof ComponentColorModel ||
+            bufferedImage.getColorModel() instanceof IndexColorModel) {
+            int width = bufferedImage.getWidth();
+            int height = bufferedImage.getHeight();
+            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            // converting grey scale colors to black/white is configurable (use less resources but is less readable)
+            boolean simplifyColors = Main.pref.getBoolean("cadastrewms.raster2bitsColors", false);
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    int rgb = bufferedImage.getRGB(x, y);
+                    Color c = new Color(rgb);
+                    int r = c.getRed();
+                    int g = c.getGreen();
+                    int b = c.getBlue();
+                    Color maskedColor;
+                    if (rgb == cadastreBackground) {
+                        maskedColor = simplifyColors ? new Color(0xff, 0xff, 0xff, 0x00) :
+                            new Color(r, g, b, 0x00); // transparent
+                    } else {
+                        maskedColor = simplifyColors ? new Color(0, 0, 0, 0xFF) :
+                            new Color(r, g, b, 0xFF); // opaque
+                    }
+                    bi.setRGB(x, y, maskedColor.getRGB());
+                }
+            }
+            setBufferedImage(bi);
+        }
+        return;
+    }
+
+    /**
+     * Temporary fix for Java6 which doesn't de-serialize correctly cached image on disk.
+     * Recreate a new raster image based on what is loaded/serialized from disk cache.
+     * @return new image
+     */
+    public static BufferedImage fixRasterImage(BufferedImage img) {
+        int width = img.getWidth();
+        int height = img.getHeight();
+        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        int[] rgbArray = new int[width * height];
+        img.getRGB(0, 0, width, height, rgbArray, 0, width);
+        bi.setRGB(0, 0, width, height, rgbArray, 0, width);
+        return bi;
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SVGParser.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SVGParser.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SVGParser.java	(revision 33637)
@@ -0,0 +1,64 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.util.ArrayList;
+
+/**
+ * This class is not intended to be a real SVG parser. It's also not using existing
+ * xml parsers. It's just extracting the required strings from an SVG file coming
+ * from the French land registry cadastre.gouv.fr
+ *
+ */
+public class SVGParser {
+
+    private String cViewBoxStart = "viewBox=\"";
+    private String cViewBoxEnd = "\"";
+    private String cPathStart = "<path d=\"";
+    private String cClosedPathEnd = "\"/>";
+
+    /**
+     * The SVG viewBox looks like this:
+     *   viewBox="969780.0 320377.11 5466.130000000005 2846.429999999993"
+     * @param svg the SVG XML data
+     * @return double [x,y,dx,dy] of viewBox; null if parsing failed
+     */
+    public double[] getViewBox(String svg) {
+        int s = svg.indexOf(cViewBoxStart)+cViewBoxStart.length();
+        int e = svg.indexOf(cViewBoxEnd, s);
+        if (s != -1 && e != -1) {
+            try {
+                String str = svg.substring(s, e);
+                String[] viewBox = str.split(" ");
+                double[] dbox = new double[4];
+                for (int i = 0; i < 4; i++) {
+                    dbox[i] = Double.parseDouble(viewBox[i]);
+                }
+                return dbox;
+            } catch (Exception ex) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Closed SVG paths are finishing with a "Z" at the end of the moves list.
+     */
+    public String[] getClosedPaths(String svg) {
+        ArrayList<String> path = new ArrayList<>();
+        int i = 0;
+        while (svg.indexOf(cPathStart, i) != -1) {
+            int s = svg.indexOf(cPathStart, i) + cViewBoxStart.length();
+            int e = svg.indexOf(cClosedPathEnd, s);
+            if (s != -1 && e != -1) {
+                String onePath = svg.substring(s, e);
+                if (onePath.indexOf("Z") != -1) // only closed SVG path
+                    path.add(onePath);
+            } else
+                break;
+            i = e;
+        }
+        return path.toArray(new String[ path.size() ]);
+    }
+
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Scale.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Scale.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/Scale.java	(revision 33637)
@@ -0,0 +1,31 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+/**
+ * List of possible grab factors each time we call the grab action.
+ * X1 means that only one bounding box is grabbed where X2 means that the current
+ * view is split in 2x2 bounding boxes and X3 is 3x3 boxes.
+ * SQUARE_100M is a special value where bounding boxes have a fixed size of 100x100 meters
+ * and east,north are rounded to the lowest 100 meter as well, thus none of the bounding boxes
+ * are overlapping each others.
+ */
+public enum Scale {
+    X1("1"),
+    X2("2"),
+    X3("3"),
+    SQUARE_100M("4");
+
+    /**
+     * value is the string equivalent stored in the preferences file
+     */
+    public final String value;
+
+    Scale(String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SimplifyWay.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SimplifyWay.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/SimplifyWay.java	(revision 33637)
@@ -0,0 +1,85 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.projection.Ellipsoid;
+
+/**
+ * Imported from plugin UtilsPlugin
+ * @author
+ *
+ */
+public class SimplifyWay {
+    public void simplifyWay(Way w/*, DataSet dataSet*/, double threshold) {
+        Way wnew = new Way(w);
+
+        simplifyWayRange(wnew, 0, wnew.getNodesCount() - 1, threshold);
+        w.setNodes(wnew.getNodes());
+    }
+
+    public void simplifyWayRange(Way wnew, int from, int to, double thr) {
+        if (to - from >= 2) {
+            ArrayList<Node> ns = new ArrayList<>();
+            simplifyWayRange(wnew, from, to, ns, thr);
+            List<Node> nodes = wnew.getNodes();
+            for (int j = to - 1; j > from; j--) {
+                nodes.remove(j);
+            }
+            nodes.addAll(from+1, ns);
+            wnew.setNodes(nodes);
+        }
+    }
+
+    /**
+     * Takes an interval [from,to] and adds nodes from (from,to) to ns.
+     * (from and to are indices of wnew.nodes.)
+     */
+    public void simplifyWayRange(Way wnew, int from, int to, ArrayList<Node> ns, double thr) {
+        Node fromN = wnew.getNode(from), toN = wnew.getNode(to);
+
+        int imax = -1;
+        double xtemax = 0;
+        for (int i = from + 1; i < to; i++) {
+            Node n = wnew.getNode(i);
+            double xte = Math.abs(Ellipsoid.WGS84.a
+                    * xtd(fromN.getCoor().lat() * Math.PI / 180, fromN.getCoor().lon() * Math.PI / 180, toN.getCoor().lat() * Math.PI
+                            / 180, toN.getCoor().lon() * Math.PI / 180, n.getCoor().lat() * Math.PI / 180, n.getCoor().lon() * Math.PI
+                            / 180));
+            if (xte > xtemax) {
+                xtemax = xte;
+                imax = i;
+            }
+        }
+
+        if (imax != -1 && xtemax >= thr) {
+            simplifyWayRange(wnew, from, imax, ns, thr);
+            ns.add(wnew.getNode(imax));
+            simplifyWayRange(wnew, imax, to, ns, thr);
+        }
+    }
+
+    /* From Aviaton Formulary v1.3
+     * http://www.edwilliams.org/avform.htm
+     */
+    public static double dist(double lat1, double lon1, double lat2, double lon2) {
+        return 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) + Math.cos(lat1) * Math.cos(lat2)
+                * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
+    }
+
+    public static double course(double lat1, double lon1, double lat2, double lon2) {
+        return Math.atan2(Math.sin(lon1 - lon2) * Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
+                * Math.cos(lat2) * Math.cos(lon1 - lon2))
+                % (2 * Math.PI);
+    }
+
+    public static double xtd(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3) {
+        double dist_AD = dist(lat1, lon1, lat3, lon3);
+        double crs_AD = course(lat1, lon1, lat3, lon3);
+        double crs_AB = course(lat1, lon1, lat2, lon2);
+        return Math.asin(Math.sin(dist_AD) * Math.sin(crs_AD - crs_AB));
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/VectorImageModifier.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/VectorImageModifier.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/VectorImageModifier.java	(revision 33637)
@@ -0,0 +1,113 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ColorHelper;
+
+public class VectorImageModifier extends ImageModifier {
+
+    private int cadastreBackground = -1; // white
+
+    public static int cadastreBackgroundTransp = 1; // original white but transparent
+
+    private int backgroundPixel = 0;
+
+    public VectorImageModifier() {
+        super();
+    }
+
+    public VectorImageModifier(BufferedImage bi, boolean monocolor) {
+        setBufferedImage(bi);
+        if (Main.pref.getBoolean("cadastrewms.backgroundTransparent"))
+            makeTransparent();
+        else if (Main.pref.getBoolean("cadastrewms.alterColors"))
+            replaceBackground();
+        if (Main.pref.getBoolean("cadastrewms.invertGrey"))
+            invertGrey();
+        if (monocolor)
+            setBufferedImage(convert8(convert4(bufferedImage)));
+    }
+
+    /**
+     * Replace the background color by the josm color.background color.
+     */
+    private void replaceBackground() {
+        int w = bufferedImage.getWidth();
+        int h = bufferedImage.getHeight();
+        int josmBackgroundColor = ColorHelper.html2color(Main.pref.get("color.background", "#000000")).getRGB();
+        for (int x = 0; x < w; x++) {
+            for (int y = 0; y < h; y++) {
+                int pixel = bufferedImage.getRGB(x, y);
+                if (pixel == cadastreBackground) {
+                    bufferedImage.setRGB(x, y, josmBackgroundColor);
+                }
+            }
+        }
+        // The cadastre has now a new background (for invertGrey())
+        cadastreBackground = josmBackgroundColor;
+    }
+
+    /**
+     * Invert black/white/grey pixels (to change original black characters to white).
+     */
+    private void invertGrey() {
+        int w = bufferedImage.getWidth();
+        int h = bufferedImage.getHeight();
+        for (int x = 0; x < w; x++) {
+            for (int y = 0; y < h; y++) {
+                int pixel = bufferedImage.getRGB(x, y);
+                if (pixel != cadastreBackground) {
+                    bufferedImage.setRGB(x, y, reverseIfGrey(pixel));
+                }
+            }
+        }
+    }
+
+    /**
+     * Reverse the grey value if the pixel is grey (light grey becomes dark grey)
+     * Used for texts.
+     */
+    private int reverseIfGrey(int pixel) {
+        Color col = new Color(pixel);
+        int r = col.getRed();
+        int g = col.getGreen();
+        int b = col.getBlue();
+        if ((b == r) && (b == g)) {
+            pixel = ((byte) col.getAlpha() << 24) + ((byte) (255 - r) << 16) + ((byte) (255 - r) << 8) + ((byte) (255 - r));
+        }
+        // Maybe we should try to find a better formula to avoid discontinuity when text is drawn on a colored item
+        // (building, ...). One could use the conversion to and from HSB to only change the brightness not the color.
+        // Note: the color palette does not have the inverse colors so it may be very weird!
+        return pixel;
+    }
+
+    private void makeTransparent() {
+        ColorModel colorModel = bufferedImage.getColorModel();
+        if (bufferedImage.getColorModel() instanceof IndexColorModel) {
+            // vector image (IndexColorModel)
+            IndexColorModel icm = (IndexColorModel) colorModel;
+            WritableRaster raster = bufferedImage.getRaster();
+            // pixel is offset in ICM's palette
+            backgroundPixel = 1; // default Cadastre background sample
+            int size = icm.getMapSize();
+            byte[] reds = new byte[size];
+            byte[] greens = new byte[size];
+            byte[] blues = new byte[size];
+            icm.getReds(reds);
+            icm.getGreens(greens);
+            icm.getBlues(blues);
+            // The cadastre background has now an alpha to 0 (for invertGrey())
+            cadastreBackground = 0x00ffffff;
+            IndexColorModel icm2 = new IndexColorModel(colorModel.getPixelSize(), size, reds, greens, blues,
+                    backgroundPixel);
+            setBufferedImage(new BufferedImage(icm2, raster, bufferedImage.isAlphaPremultiplied(), null));
+        }
+        return;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSAdjustAction.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSAdjustAction.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSAdjustAction.java	(revision 33637)
@@ -0,0 +1,197 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics2D;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class WMSAdjustAction extends MapMode implements
+        MouseListener, MouseMotionListener {
+
+    private static final long serialVersionUID = 1L;
+    private WMSLayer modifiedLayer = null;
+    private boolean rasterMoved;
+    private EastNorth prevEastNorth;
+    enum Mode { moveXY, moveZ, rotate }
+
+    private static Mode mode = null;
+    private static EastNorth[] croppedRaster = new EastNorth[5];;
+
+    /**
+     * Constructs a new {@code WMSAdjustAction} map mode.
+     */
+    public WMSAdjustAction() {
+        super(tr("Adjust WMS"), "adjustxywms",
+                        tr("Adjust the position of the WMS layer (saved for raster images only)"),
+                        ImageProvider.getCursor("normal", "move"));
+    }
+
+    @Override public void enterMode() {
+        if (Main.map != null) {
+            if (Main.getLayerManager().getActiveLayer() instanceof WMSLayer) {
+                modifiedLayer = (WMSLayer) Main.getLayerManager().getActiveLayer();
+                super.enterMode();
+                Main.map.mapView.addMouseListener(this);
+                Main.map.mapView.addMouseMotionListener(this);
+                rasterMoved = false;
+                modifiedLayer.adjustModeEnabled = true;
+            } else {
+                // This mode works only if active layer is a cadastre layer
+                if (Boolean.TRUE.equals(getValue("active"))) {
+                    exitMode();
+                }
+                Main.map.selectMapMode((MapMode) Main.map.getDefaultButtonAction());
+            }
+        }
+    }
+
+    @Override public void exitMode() {
+        super.exitMode();
+        Main.map.mapView.removeMouseListener(this);
+        Main.map.mapView.removeMouseMotionListener(this);
+        if (rasterMoved && CacheControl.cacheEnabled && modifiedLayer.isRaster()) {
+            int reply = JOptionPane.showConfirmDialog(null,
+                    "Save the changes in cache ?",
+                    "Update cache",
+                    JOptionPane.YES_NO_OPTION);
+            if (reply == JOptionPane.OK_OPTION) {
+                saveModifiedLayers();
+            }
+        }
+        rasterMoved = false;
+        if (modifiedLayer != null) {
+            modifiedLayer.adjustModeEnabled = false;
+            modifiedLayer = null;
+        }
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+        if (e.getButton() != MouseEvent.BUTTON1)
+            return;
+        requestFocusInMapView();
+        boolean ctrl = (e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0;
+        // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
+        boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
+        if (shift && !ctrl && modifiedLayer.isRaster())
+            mode = Mode.moveZ;
+        else if (shift && ctrl && modifiedLayer.isRaster())
+            mode = Mode.rotate;
+        else
+            mode = Mode.moveXY;
+        rasterMoved = true;
+        prevEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
+        Main.map.mapView.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+    }
+
+    @Override public void mouseDragged(MouseEvent e) {
+        EastNorth newEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
+        if (mode == Mode.rotate) {
+            rotateFrameOnly(prevEastNorth, newEastNorth);
+        } else {
+            if (mode == Mode.moveXY) {
+                displace(prevEastNorth, newEastNorth);
+            } else if (mode == Mode.moveZ) {
+                resize(newEastNorth);
+            }
+            prevEastNorth = newEastNorth;
+        }
+        if (modifiedLayer != null) {
+            modifiedLayer.invalidate();
+        }
+    }
+
+    public static void paintAdjustFrames(Graphics2D g, final MapView mv) {
+        if (mode == Mode.rotate && croppedRaster != null) {
+            g.setColor(Color.red);
+            for (int i = 0; i < 4; i++) {
+                g.drawLine(mv.getPoint(croppedRaster[i]).x,
+                        mv.getPoint(croppedRaster[i]).y,
+                        mv.getPoint(croppedRaster[i+1]).x,
+                        mv.getPoint(croppedRaster[i+1]).y);
+            }
+        }
+    }
+
+    private void displace(EastNorth start, EastNorth end) {
+        modifiedLayer.displace(end.east()-start.east(), end.north()-start.north());
+    }
+
+    private void resize(EastNorth newEastNorth) {
+        EastNorth center = modifiedLayer.getRasterCenter();
+        double dPrev = prevEastNorth.distance(center.east(), center.north());
+        double dNew = newEastNorth.distance(center.east(), center.north());
+        modifiedLayer.resize(center, dNew/dPrev);
+    }
+
+    private void rotate(EastNorth start, EastNorth end) {
+        EastNorth pivot = modifiedLayer.getRasterCenter();
+        double startAngle = Math.atan2(start.east()-pivot.east(), start.north()-pivot.north());
+        double endAngle = Math.atan2(end.east()-pivot.east(), end.north()-pivot.north());
+        double rotationAngle = endAngle - startAngle;
+        modifiedLayer.rotate(pivot, rotationAngle);
+    }
+
+    private void rotateFrameOnly(EastNorth start, EastNorth end) {
+        if (start != null && end != null) {
+            EastNorth pivot = modifiedLayer.getRasterCenter();
+            double startAngle = Math.atan2(start.east()-pivot.east(), start.north()-pivot.north());
+            double endAngle = Math.atan2(end.east()-pivot.east(), end.north()-pivot.north());
+            double rotationAngle = endAngle - startAngle;
+            if (modifiedLayer.getImage(0).orgCroppedRaster != null) {
+                for (int i = 0; i < 4; i++) {
+                    croppedRaster[i] = modifiedLayer.getImage(0).orgCroppedRaster[i].rotate(pivot, rotationAngle);
+                }
+                croppedRaster[4] = croppedRaster[0];
+            }
+        }
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+        if (mode == Mode.rotate) {
+            EastNorth newEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
+            rotate(prevEastNorth, newEastNorth);
+            if (modifiedLayer != null) {
+                modifiedLayer.invalidate();
+            }
+        }
+        Main.map.mapView.setCursor(Cursor.getDefaultCursor());
+        prevEastNorth = null;
+        mode = null;
+    }
+
+    @Override
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    @Override
+    public void mouseExited(MouseEvent e) {
+    }
+
+    @Override
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    @Override public void mouseClicked(MouseEvent e) {
+    }
+
+    private void saveModifiedLayers() {
+        modifiedLayer.grabThread.saveNewCache();
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSDownloadAction.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSDownloadAction.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSDownloadAction.java	(revision 33637)
@@ -0,0 +1,52 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+//import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+//import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.layer.Layer;
+
+public class WMSDownloadAction /*extends JosmAction */ {
+
+//    public WMSDownloadAction(String layerName) {
+//        super(layerName, "wmsmenu", tr("Download WMS tile from {0}",layerName), null, false);
+//    }
+//
+//    public void actionPerformed(ActionEvent e) {
+//        DownloadWMSVectorImage.download(getLayer());
+//    }
+
+    public static WMSLayer getLayer() {
+        // check if we already have a layer created. if not, create; if yes, reuse.
+        ArrayList<WMSLayer> existingWMSlayers = new ArrayList<>();
+        if (Main.map != null) {
+            Layer activeLayer = Main.getLayerManager().getActiveLayer();
+            if (activeLayer instanceof WMSLayer)
+                return (WMSLayer) activeLayer;
+            for (Layer l : Main.getLayerManager().getLayers()) {
+                if (l instanceof WMSLayer) {
+                    existingWMSlayers.add((WMSLayer) l);
+                }
+            }
+            if (existingWMSlayers.size() == 1)
+                return existingWMSlayers.get(0);
+            if (existingWMSlayers.size() == 0)
+                return new MenuActionNewLocation().addNewLayer(existingWMSlayers);
+            if (Main.pref.getBoolean("cadastrewms.autoFirstLayer", false)) {
+                return existingWMSlayers.get(0);
+            } else {
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("More than one WMS layer present\nSelect one of them first, then retry"));
+            }
+        } else {
+            return new MenuActionNewLocation().addNewLayer(existingWMSlayers);
+        }
+        return null;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSException.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSException.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSException.java	(revision 33637)
@@ -0,0 +1,17 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+class WMSException extends Exception {
+    private String message;
+    private static final long serialVersionUID = 1L;
+
+    WMSException(String message) {
+        super();
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+}
Index: /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSLayer.java
===================================================================
--- /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSLayer.java	(revision 33637)
+++ /applications/editors/josm/plugins/cadastre-fr/src/org/openstreetmap/josm/plugins/fr/cadastre/WMSLayer.java	(revision 33637)
@@ -0,0 +1,729 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.fr.cadastre;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+
+/**
+ * This is a layer that grabs the current screen from the French cadastre WMS
+ * server. The data fetched this way is tiled and managed to the disc to reduce
+ * server load.
+ */
+public class WMSLayer extends Layer implements ImageObserver {
+
+    private int lambertZone = -1;
+
+    public CadastreGrabber grabber = new CadastreGrabber();
+
+    protected static final Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(
+            CadastrePlugin.class.getResource("/images/cadastre_small.png")));
+
+    private Vector<GeorefImage> images = new Vector<>();
+
+    public Lock imagesLock = new ReentrantLock();
+
+    /**
+     * v1 to v2 = not supported
+     * v2 to v3 = add 4 more EastNorth coordinates in GeorefImages
+     * v3 to v4 = add original raster image width and height
+     */
+    protected final int serializeFormatVersion = 4;
+
+    public static int currentFormat;
+
+    private ArrayList<EastNorthBound> dividedBbox = new ArrayList<>();
+
+    private String location = "";
+
+    private String departement = "";
+
+    private String codeCommune = "";
+
+    public EastNorthBound communeBBox = new EastNorthBound(new EastNorth(0, 0), new EastNorth(0, 0));
+
+    private boolean isRaster;
+    private boolean isAlreadyGeoreferenced;
+    public double X0, Y0, angle, fX, fY;
+
+    // bbox of the georeferenced raster image (the nice horizontal and vertical box)
+    private EastNorth rasterMin;
+    private EastNorth rasterMax;
+    private double rasterRatio;
+
+    // offset for vector images temporarily shifted (correcting Cadastre artifacts), in pixels
+    public double deltaEast;
+    public double deltaNorth;
+
+    private Action saveAsPng;
+
+    private Action cancelGrab;
+
+    private Action refineGeoRef;
+
+    class ResetOffsetActionMenu extends JosmAction {
+        ResetOffsetActionMenu() {
+            super(tr("Reset offset"), null, tr("Reset offset (only vector images)"), null, false);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent arg0) {
+            deltaEast = 0;
+            deltaNorth = 0;
+            invalidate();
+        }
+    }
+
+    public boolean adjustModeEnabled;
+
+    public GrabThread grabThread;
+
+    /**
+     * Constructs a new {@code WMSLayer}.
+     */
+    public WMSLayer() {
+        this(tr("Blank Layer"), "", -1);
+    }
+
+    public WMSLayer(String location, String codeCommune, int lambertZone) {
+        super(buildName(location, codeCommune));
+        this.location = location;
+        this.codeCommune = codeCommune;
+        this.lambertZone = lambertZone;
+        grabThread = new GrabThread(this);
+        grabThread.start();
+        // enable auto-sourcing option
+        CadastrePlugin.pluginUsed = true;
+    }
+
+    @Override
+    public void destroy() {
+        // if the layer is currently saving the images in the cache, wait until it's finished
+        if (grabThread != null)
+                grabThread.cancel();
+        grabThread = null;
+        super.destroy();
+        images = null;
+        dividedBbox = null;
+        Main.info("Layer "+location+" destroyed");
+    }
+
+    private static String buildName(String location, String codeCommune) {
+        String ret = location.toUpperCase(Locale.FRANCE);
+        if (codeCommune != null && !codeCommune.isEmpty())
+            ret += "(" + codeCommune + ")";
+        return ret;
+    }
+
+    private String rebuildName() {
+        return buildName(this.location.toUpperCase(Locale.FRANCE), this.codeCommune);
+    }
+
+    public void grab(Bounds b) throws IOException {
+        grabThread.setCanceled(false);
+        grabThread.setGrabber(grabber);
+        // if it is the first layer, use the communeBBox as grab bbox (and not divided)
+        if (Main.getLayerManager().getLayers().size() == 1) {
+            final Bounds bounds = this.getCommuneBBox().toBounds();
+            GuiHelper.runInEDTAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    Main.map.mapView.zoomTo(bounds);
+                }
+            });
+            divideBbox(bounds, 1);
+        } else {
+            if (isRaster) {
+                divideBbox(new Bounds(Main.getProjection().eastNorth2latlon(rasterMin), Main.getProjection().eastNorth2latlon(rasterMax)),
+                        Integer.parseInt(Main.pref.get("cadastrewms.rasterDivider", CadastrePreferenceSetting.DEFAULT_RASTER_DIVIDER)));
+            } else
+                divideBbox(b,
+                        Integer.parseInt(Main.pref.get("cadastrewms.scale", CadastrePreferenceSetting.DEFAULT_GRAB_MULTIPLIER)));
+        }
+        grabThread.addImages(dividedBbox);
+    }
+
+    /**
+     * Divides the bounding box in smaller squares. Their size (and quantity) is configurable in Preferences.
+     *
+     * @param b      the original bbox, usually the current bbox on screen
+     * @param factor 1 = source bbox 1:1
+     *               2 = source bbox divided by 2x2 smaller boxes
+     *               3 = source bbox divided by 3x3 smaller boxes
+     *               4 = configurable size from preferences (100 meters per default) rounded
+     *                   allowing grabbing of next contiguous zone
+     */
+    private void divideBbox(Bounds b, int factor) {
+        EastNorth lambertMin = Main.getProjection().latlon2eastNorth(b.getMin());
+        EastNorth lambertMax = Main.getProjection().latlon2eastNorth(b.getMax());
+        double minEast = lambertMin.east()+deltaEast;
+        double minNorth = lambertMin.north()+deltaNorth;
+        double dEast = (lambertMax.east() - minEast) / factor;
+        double dNorth = (lambertMax.north() - minNorth) / factor;
+        dividedBbox.clear();
+        if (factor < 4 || isRaster) {
+            for (int xEast = 0; xEast < factor; xEast++) {
+                for (int xNorth = 0; xNorth < factor; xNorth++) {
+                    dividedBbox.add(new EastNorthBound(new EastNorth(minEast + xEast * dEast, minNorth + xNorth * dNorth),
+                                new EastNorth(minEast + (xEast + 1) * dEast, minNorth + (xNorth + 1) * dNorth)));
+                }
+            }
+        } else {
+            // divide to fixed size squares
+            // grab all square in a spiral starting from the center (usually the most interesting place)
+            int c = Integer.parseInt(Main.pref.get("cadastrewms.squareSize", String.valueOf(CadastrePreferenceSetting.DEFAULT_SQUARE_SIZE)));
+            lambertMin = lambertMin.add(-minEast % c, -minNorth % c);
+            lambertMax = lambertMax.add(c - lambertMax.east() % c, c - lambertMax.north() % c);
+            EastNorth mid = lambertMax.getCenter(lambertMin);
+            mid = mid.add(-1, 1); // in case the boxes side is a pair, select the one one top,left to follow the rotation
+            mid = mid.add(-mid.east() % c, -mid.north() % c);
+            int x = (int) (lambertMax.east() -lambertMin.east())/c;
+            int y = (int) (lambertMax.north() -lambertMin.north())/c;
+            int[] dx = {+1, 0, -1, 0};
+            int[] dy = {0, -1, 0, +1};
+            int currDir = -1, lDir = 1, i = 1, j = 0, k = -1;
+            if (x == 1)
+                currDir = 0;
+            dividedBbox.add(new EastNorthBound(mid, new EastNorth(mid.east()+c, mid.north()+c)));
+            while (i < (x*y)) {
+                i++;
+                j++;
+                if (j >= lDir) {
+                    k++;
+                    if (k > 1) {
+                        lDir++;
+                        k = 0;
+                    }
+                    j = 0;
+                    currDir = (currDir+1) % 4;
+                } else if (currDir >= 0 && j >= (currDir == 0 || currDir == 2 ? (x-1) : (y-1))) {
+                    // the overall is a rectangle, not a square. Jump to the other side to grab next square.
+                    k++;
+                    if (k > 1) {
+                        lDir++;
+                        k = 0;
+                    }
+                    j = lDir-1;
+                    currDir = (currDir+1) % 4;
+                    mid = new EastNorth(mid.east() + dx[currDir]*c*(lDir-1), mid.north() + dy[currDir]*c*(lDir-1));
+                }
+                mid = new EastNorth(mid.east() + dx[currDir]*c, mid.north() + dy[currDir]*c);
+                dividedBbox.add(new EastNorthBound(mid, new EastNorth(mid.east()+c, mid.north()+c)));
+            }
+        }
+    }
+
+    @Override
+    public Icon getIcon() {
+        return icon;
+    }
+
+    @Override
+    public String getToolTipText() {
+        String str = tr("WMS layer ({0}), {1} tile(s) loaded", getName(), images.size());
+        if (isRaster) {
+            str += "\n"+tr("Is not vectorized.");
+            str += "\n"+tr("Bounding box: {0}", communeBBox);
+            if (!images.isEmpty())
+                str += "\n"+tr("Image size (px): {0}/{1}", images.get(0).image.getWidth(), images.get(0).image.getHeight());
+        } else {
+            str += "\n"+tr("Is vectorized.");
+            str += "\n"+tr("Commune bbox: {0}", communeBBox);
+        }
+        return str;
+    }
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return false;
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {
+        // Do nothing
+    }
+
+    @Override
+    public void paint(Graphics2D g, final MapView mv, Bounds bounds) {
+        synchronized (this) {
+            Object savedInterpolation = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
+            if (savedInterpolation == null) savedInterpolation = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
+            String interpolation = Main.pref.get("cadastrewms.imageInterpolation", "standard");
+            if (interpolation.equals("bilinear"))
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+            else if (interpolation.equals("bicubic"))
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+            else
+                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+            imagesLock.lock();
+            for (GeorefImage img : images) {
+                img.paint(g, mv, CadastrePlugin.backgroundTransparent,
+                        CadastrePlugin.transparency, CadastrePlugin.drawBoundaries);
+            }
+            imagesLock.unlock();
+            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, savedInterpolation);
+        }
+        if (this.isRaster) {
+            paintCrosspieces(g, mv);
+        }
+        grabThread.paintBoxesToGrab(g, mv);
+        if (this.adjustModeEnabled) {
+            WMSAdjustAction.paintAdjustFrames(g, mv);
+        }
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {
+        for (GeorefImage img : images) {
+            v.visit(img.min);
+            v.visit(img.max);
+        }
+    }
+
+    @Override
+    public Object getInfoComponent() {
+        return getToolTipText();
+    }
+
+    @Override
+    public Action[] getMenuEntries() {
+        saveAsPng = new MenuActionSaveRasterAs(this);
+        saveAsPng.setEnabled(isRaster);
+        cancelGrab = new MenuActionCancelGrab(this);
+        cancelGrab.setEnabled(!isRaster && grabThread.getImagesToGrabSize() > 0);
+        refineGeoRef = new MenuActionRefineGeoRef(this);
+        refineGeoRef.setEnabled(isRaster && grabThread.getImagesToGrabSize() == 0);
+        Action resetOffset = new ResetOffsetActionMenu();
+        resetOffset.setEnabled(!isRaster && !images.isEmpty() && (deltaEast != 0.0 || deltaNorth != 0.0));
+        return new Action[] {
+                LayerListDialog.getInstance().createShowHideLayerAction(),
+                LayerListDialog.getInstance().createDeleteLayerAction(),
+                new MenuActionLoadFromCache(),
+                saveAsPng,
+                cancelGrab,
+                refineGeoRef,
+                resetOffset,
+                new LayerListPopup.InfoAction(this),
+        };
+    }
+
+    public GeorefImage findImage(EastNorth eastNorth) {
+        // Iterate in reverse, so we return the image which is painted last.
+        // (i.e. the topmost one)
+        for (int i = images.size() - 1; i >= 0; i--) {
+            if (images.get(i).contains(eastNorth)) {
+                return images.get(i);
+            }
+        }
+        return null;
+    }
+
+    public boolean isOverlapping(Bounds bounds) {
+        GeorefImage georefImage =
+            new GeorefImage(null,
+            Main.getProjection().latlon2eastNorth(bounds.getMin()),
+            Main.getProjection().latlon2eastNorth(bounds.getMax()), this);
+        for (GeorefImage img : images) {
+            if (img.overlap(georefImage))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Convert the eastNorth input coordinates to raster coordinates.
+     * The original raster size is [0,0,12286,8730] where 0,0 is the upper left corner and
+     * 12286,8730 is the approx. raster max size.
+     * @return the raster coordinates for the wms server request URL (minX,minY,maxX,maxY)
+     */
+    public String eastNorth2raster(EastNorth min, EastNorth max) {
+        double minX = (min.east() - rasterMin.east()) / rasterRatio;
+        double minY = (min.north() - rasterMin.north()) / rasterRatio;
+        double maxX = (max.east() - rasterMin.east()) / rasterRatio;
+        double maxY = (max.north() - rasterMin.north()) / rasterRatio;
+        return minX+","+minY+","+maxX+","+maxY;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+        setName(rebuildName());
+    }
+
+    public String getDepartement() {
+        return departement;
+    }
+
+    public void setDepartement(String departement) {
+        this.departement = departement;
+    }
+
+    public String getCodeCommune() {
+        return codeCommune;
+    }
+
+    public void setCodeCommune(String codeCommune) {
+        this.codeCommune = codeCommune;
+        setName(rebuildName());
+    }
+
+    public boolean isRaster() {
+        return isRaster;
+    }
+
+    public void setRaster(boolean isRaster) {
+        this.isRaster = isRaster;
+        if (saveAsPng != null)
+            saveAsPng.setEnabled(isRaster);
+    }
+
+    public boolean isAlreadyGeoreferenced() {
+        return isAlreadyGeoreferenced;
+    }
+
+    public void setAlreadyGeoreferenced(boolean isAlreadyGeoreferenced) {
+        this.isAlreadyGeoreferenced = isAlreadyGeoreferenced;
+    }
+
+    /**
+     * Set raster positions used for grabbing and georeferencing.
+     * rasterMin is the Eaast North of bottom left corner raster image on the screen when image is grabbed.
+     * The bounds width and height are the raster width and height. The image width matches the current view
+     * and the image height is adapted.
+     * Required: the communeBBox must be set (normally it is catched by CadastreInterface and saved by DownloadWMSPlanImage)
+     * @param bounds the current main map view boundaries
+     */
+    public void setRasterBounds(Bounds bounds) {
+        EastNorth rasterCenter = Main.getProjection().latlon2eastNorth(bounds.getCenter());
+        EastNorth eaMin = Main.getProjection().latlon2eastNorth(bounds.getMin());
+        EastNorth eaMax = Main.getProjection().latlon2eastNorth(bounds.getMax());
+        double rasterSizeX = communeBBox.max.getX() - communeBBox.min.getX();
+        double rasterSizeY = communeBBox.max.getY() - communeBBox.min.getY();
+        double ratio = rasterSizeY/rasterSizeX;
+        // keep same ratio on screen as WMS bbox (stored in communeBBox)
+        rasterMin = new EastNorth(eaMin.getX(), rasterCenter.getY()-(eaMax.getX()-eaMin.getX())*ratio/2);
+        rasterMax = new EastNorth(eaMax.getX(), rasterCenter.getY()+(eaMax.getX()-eaMin.getX())*ratio/2);
+        rasterRatio = (rasterMax.getX()-rasterMin.getX())/rasterSizeX;
+    }
+
+    /**
+     * Called by CacheControl when a new cache file is created on disk.
+     * Save only primitives to keep cache independent of software changes.
+     */
+    public void write(File associatedFile, ObjectOutputStream oos) throws IOException {
+        currentFormat = this.serializeFormatVersion;
+        setAssociatedFile(associatedFile);
+        oos.writeInt(this.serializeFormatVersion);
+        oos.writeObject(this.location);    // String
+        oos.writeObject(this.codeCommune); // String
+        oos.writeInt(this.lambertZone);
+        oos.writeBoolean(this.isRaster);
+        oos.writeBoolean(false); // previously buildingsOnly
+        if (this.isRaster) {
+            oos.writeDouble(this.rasterMin.getX());
+            oos.writeDouble(this.rasterMin.getY());
+            oos.writeDouble(this.rasterMax.getX());
+            oos.writeDouble(this.rasterMax.getY());
+            oos.writeDouble(this.rasterRatio);
+        }
+        oos.writeDouble(this.communeBBox.min.getX());
+        oos.writeDouble(this.communeBBox.min.getY());
+        oos.writeDouble(this.communeBBox.max.getX());
+        oos.writeDouble(this.communeBBox.max.getY());
+    }
+
+    /**
+     * Called by CacheControl when a cache file is read from disk.
+     * Cache uses only primitives to stay independent of software changes.
+     */
+    public boolean read(File associatedFile, ObjectInputStream ois, int currentLambertZone) throws IOException, ClassNotFoundException {
+        currentFormat = ois.readInt();;
+        if (currentFormat < 2) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Unsupported cache file version; found {0}, expected {1}\nCreate a new one.",
+                    currentFormat, this.serializeFormatVersion), tr("Cache Format Error"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+        this.setLocation((String) ois.readObject());
+        this.setCodeCommune((String) ois.readObject());
+        this.lambertZone = ois.readInt();
+        this.setRaster(ois.readBoolean());
+        setAssociatedFile(associatedFile);
+        if (currentFormat >= 4)
+            ois.readBoolean();
+        if (this.isRaster) {
+            double X = ois.readDouble();
+            double Y = ois.readDouble();
+            this.rasterMin = new EastNorth(X, Y);
+            X = ois.readDouble();
+            Y = ois.readDouble();
+            this.rasterMax = new EastNorth(X, Y);
+            this.rasterRatio = ois.readDouble();
+        }
+        double minX = ois.readDouble();
+        double minY = ois.readDouble();
+        double maxX = ois.readDouble();
+        double maxY = ois.readDouble();
+        this.communeBBox = new EastNorthBound(new EastNorth(minX, minY), new EastNorth(maxX, maxY));
+        if (this.lambertZone != currentLambertZone && currentLambertZone != -1) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Lambert zone {0} in cache "+
+                    "incompatible with current Lambert zone {1}",
+                    this.lambertZone+1, currentLambertZone), tr("Cache Lambert Zone Error"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+        synchronized (this) {
+            boolean EOF = false;
+            try {
+                while (!EOF) {
+                    GeorefImage newImage = (GeorefImage) ois.readObject();
+                    for (GeorefImage img : this.images) {
+                        if (CadastrePlugin.backgroundTransparent) {
+                            if (img.overlap(newImage))
+                                // mask overlapping zone in already grabbed image
+                                img.withdraw(newImage);
+                            else
+                                // mask overlapping zone in new image only when
+                                // new image covers completely the existing image
+                                newImage.withdraw(img);
+                        }
+                    }
+                    newImage.wmsLayer = this;
+                    this.images.add(newImage);
+                }
+            } catch (EOFException ex) {
+                // expected exception when all images are read
+                Main.trace(ex);
+            }
+        }
+        Main.info("Cache loaded for location "+location+" with "+images.size()+" images");
+        return true;
+    }
+
+    /**
+     * Join the grabbed images into one single.
+     */
+    public void joinBufferedImages() {
+        if (images.size() > 1) {
+            EastNorth min = images.get(0).min;
+            EastNorth max = images.get(images.size()-1).max;
+            int oldImgWidth = images.get(0).image.getWidth();
+            int oldImgHeight = images.get(0).image.getHeight();
+            HashSet<Double> lx = new HashSet<>();
+            HashSet<Double> ly = new HashSet<>();
+            for (GeorefImage img : images) {
+                lx.add(img.min.east());
+                ly.add(img.min.north());
+            }
+            int newWidth = oldImgWidth*lx.size();
+            int newHeight = oldImgHeight*ly.size();
+            BufferedImage newImg = new BufferedImage(newWidth, newHeight, images.get(0).image.getType()/*BufferedImage.TYPE_INT_ARGB*/);
+            Graphics g = newImg.getGraphics();
+            // Coordinate (0,0) is on top,left corner where images are grabbed from bottom left
+            int rasterDivider = (int) Math.sqrt(images.size());
+            for (int h = 0; h < lx.size(); h++) {
+                for (int v = 0; v < ly.size(); v++) {
+                    int newx = h*oldImgWidth;
+                    int newy = newHeight - oldImgHeight - (v*oldImgHeight);
+                    int j = h*rasterDivider + v;
+                    g.drawImage(images.get(j).image, newx, newy, this);
+                }
+            }
+            synchronized (this) {
+                images.clear();
+                images.add(new GeorefImage(newImg, min, max, this));
+            }
+        }
+    }
+
+    /**
+     * Image cropping based on two EN coordinates pointing to two corners in diagonal
+     * Because it's coming from user mouse clics, we have to sort de positions first.
+     * Works only for raster image layer (only one image in collection).
+     * Updates layer georeferences.
+     */
+    public void cropImage(EastNorth en1, EastNorth en2) {
+        // adj1 is corner bottom, left
+        EastNorth adj1 = new EastNorth(en1.east() <= en2.east() ? en1.east() : en2.east(),
+                en1.north() <= en2.north() ? en1.north() : en2.north());
+        // adj2 is corner top, right
+        EastNorth adj2 = new EastNorth(en1.east() > en2.east() ? en1.east() : en2.east(),
+                en1.north() > en2.north() ? en1.north() : en2.north());
+        images.get(0).crop(adj1, adj2);
+        // update the layer georefs
+        rasterMin = adj1;
+        rasterMax = adj2;
+        setCommuneBBox(new EastNorthBound(
+                new EastNorth(0, 0),
+                new EastNorth(images.get(0).image.getWidth()-1, images.get(0).image.getHeight()-1)));
+        rasterRatio = (rasterMax.getX()-rasterMin.getX())/(communeBBox.max.getX() - communeBBox.min.getX());
+    }
+
+    public EastNorthBound getCommuneBBox() {
+        return communeBBox;
+    }
+
+    public EastNorthBound getFirstViewFromCacheBBox() {
+        if (isRaster) {
+            return communeBBox;
+        }
+        double minX = Double.MAX_VALUE;
+        double maxX = Double.MIN_VALUE;
+        double minY = Double.MAX_VALUE;
+        double maxY = Double.MIN_VALUE;
+        for (GeorefImage image:images) {
+            minX = image.min.east() < minX ? image.min.east() : minX;
+            maxX = image.max.east() > maxX ? image.max.east() : maxX;
+            minY = image.min.north() < minY ? image.min.north() : minY;
+            maxY = image.max.north() > maxY ? image.max.north() : maxY;
+        }
+        return new EastNorthBound(new EastNorth(minX, minY), new EastNorth(maxX, maxY));
+    }
+
+    public void setCommuneBBox(EastNorthBound entireCommune) {
+        this.communeBBox = entireCommune;
+    }
+
+    /**
+     * Method required by ImageObserver when drawing an image
+     */
+    @Override
+    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
+        return false;
+    }
+
+    public int getLambertZone() {
+        return lambertZone;
+    }
+
+    public EastNorth getRasterCenter() {
+        return new EastNorth((images.get(0).max.east()+images.get(0).min.east())/2,
+                (images.get(0).max.north()+images.get(0).min.north())/2);
+    }
+
+    public void displace(double dx, double dy) {
+        if (isRaster) {
+            this.rasterMin = new EastNorth(rasterMin.east() + dx, rasterMin.north() + dy);
+            this.rasterMax = new EastNorth(rasterMax.east() + dx, rasterMax.north() + dy);
+            images.get(0).shear(dx, dy);
+        } else {
+            deltaEast += dx;
+            deltaNorth += dy;
+        }
+    }
+
+    public void resize(EastNorth rasterCenter, double proportion) {
+        this.rasterMin = rasterMin.interpolate(rasterCenter, proportion);
+        this.rasterMax = rasterMax.interpolate(rasterCenter, proportion);
+        images.get(0).scale(rasterCenter, proportion);
+    }
+
+    public void rotate(EastNorth rasterCenter, double angle) {
+        this.rasterMin = rasterMin.rotate(rasterCenter, angle);
+        this.rasterMax = rasterMax.rotate(rasterCenter, angle);
+        images.get(0).rotate(rasterCenter, angle);
+        this.angle += angle;
+    }
+
+    private void paintCrosspieces(Graphics g, MapView mv) {
+        String crosspieces = Main.pref.get("cadastrewms.crosspieces", "0");
+        if (!crosspieces.equals("0")) {
+            int modulo = 25;
+            if (crosspieces.equals("2")) modulo = 50;
+            if (crosspieces.equals("3")) modulo = 100;
+            EastNorthBound currentView = new EastNorthBound(mv.getEastNorth(0, mv.getHeight()),
+                    mv.getEastNorth(mv.getWidth(), 0));
+            int minX = ((int) currentView.min.east()/modulo+1)*modulo;
+            int minY = ((int) currentView.min.north()/modulo+1)*modulo;
+            int maxX = ((int) currentView.max.east()/modulo)*modulo;
+            int maxY = ((int) currentView.max.north()/modulo)*modulo;
+            int size = (maxX-minX)/modulo;
+            if (size < 20) {
+                int px = size > 10 ? 2 : Math.abs(12-size);
+                g.setColor(Color.green);
+                for (int x = minX; x <= maxX; x += modulo) {
+                    for (int y = minY; y <= maxY; y += modulo) {
+                        Point p = mv.getPoint(new EastNorth(x, y));
+                        g.drawLine(p.x-px, p.y, p.x+px, p.y);
+                        g.drawLine(p.x, p.y-px, p.x, p.y+px);
+                    }
+                }
+            }
+        }
+    }
+
+    public GeorefImage getImage(int index) {
+        imagesLock.lock();
+        GeorefImage img = null;
+        try {
+            img = this.images.get(index);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Main.error(e);
+        }
+        imagesLock.unlock();
+        return img;
+    }
+
+    public Vector<GeorefImage> getImages() {
+        return this.images;
+    }
+
+    public boolean hasImages() {
+        return images != null && !images.isEmpty();
+    }
+
+    public void addImage(GeorefImage img) {
+        imagesLock.lock();
+        this.images.add(img);
+        imagesLock.unlock();
+    }
+
+    public void setImages(Vector<GeorefImage> images) {
+        imagesLock.lock();
+        this.images = images;
+        imagesLock.unlock();
+    }
+
+    public void clearImages() {
+        imagesLock.lock();
+        this.images.clear();
+        imagesLock.unlock();
+    }
+}
