Revision: 5878
          http://sourceforge.net/p/jump-pilot/code/5878
Author:   michaudm
Date:     2018-06-17 12:10:51 +0000 (Sun, 17 Jun 2018)
Log Message:
-----------
Refactor Matching extension, use OJ aggregators, add code to svn

Added Paths:
-----------
    plug-ins/MatchingPlugIn/
    plug-ins/MatchingPlugIn/trunk/
    plug-ins/MatchingPlugIn/trunk/build.xml
    plug-ins/MatchingPlugIn/trunk/src/
    plug-ins/MatchingPlugIn/trunk/src/fr/
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/FeatureCollectionMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/I18NPlug.java
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Index.java
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Match.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchEditingPlugIn.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchMap.java
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Matcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatcherRegistry.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingExtension.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingPlugIn.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingUpdatePlugIn.java
    plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/AbstractMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/AttributeMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/CentroidDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/DamarauLevenshteinDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsExactGeom2dMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsExactGeom3dMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsNormalizedGeom2dMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsNormalizedGeom3dMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsTopologicalGeomMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/EqualsWithCoordinateToleranceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/GeometryMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/HausdorffDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/Intersects0DMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/Intersects1DMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/Intersects2DMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/IntersectsMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/IsWithinMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/LevenshteinDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/MatchAllAttributesMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/MatchAllMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/MatchAllStringsMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/MinimumDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/OverlappedByMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/OverlapsMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/SemiHausdorffDistanceMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/ShapeMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/StringEqualityIgnoreCaseAndAccentMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/StringEqualityIgnoreCaseMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/StringEqualityMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matcher/StringMatcher.java
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matching.properties
    
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/matching_fr.properties

Added: plug-ins/MatchingPlugIn/trunk/build.xml
===================================================================
--- plug-ins/MatchingPlugIn/trunk/build.xml                             (rev 0)
+++ plug-ins/MatchingPlugIn/trunk/build.xml     2018-06-17 12:10:51 UTC (rev 
5878)
@@ -0,0 +1,76 @@
+<project name="matching" default="compile" basedir=".">
+
+    
<!--*************************************************************************
+    
*****************************************************************************
+    **                                  PROPERTIES                             
**
+    
*****************************************************************************
+    
**************************************************************************-->
+
+    <!-- PROPERTIES : MAIN ARCHITECTURE -->
+    <property name="src"           value="src" />
+    <property name="bin"           value="bin" />
+    <property name="lib"           value="lib" />
+    <property name="build"         value="build" />
+    <property name="dist"          value="dist" />
+    <property name="doc"           value="doc" />
+    <property name="resources"     value="resources" />
+    <property name="javadoc"       value="javadoc" />
+
+    <property name="matching-version"        value="0.8.0" />
+    
+    <!-- =================================================================== 
-->
+    <!-- Defines the classpath used for compilation and test.                
-->
+    <!-- =================================================================== 
-->
+    <path id="classpath">
+        <fileset dir="${lib}">
+            <include name="**/*.jar"/>
+        </fileset>
+    </path>
+    
+    <target name="clean" id="clean">
+        <delete dir="build"/>
+        <delete dir="${javadoc}"/>
+    </target>
+   
+    <target name="compile" id="compile" depends="clean">
+        <tstamp/>
+        <mkdir dir="build"/>
+        <javac srcdir="${src}" destdir="build"
+               debug="on" deprecation="false" nowarn="true"
+               source="1.7" target="1.7">
+            <!--compilerarg value="-Xlint:unchecked"/-->
+             <classpath refid="classpath"/>
+        </javac>
+        <copy todir="build">
+            <fileset dir="${src}" includes="**/*.txt"/>
+            <fileset dir="${src}" includes="**/*.properties"/>
+            <fileset dir="${src}" includes="**/*.png"/>
+            <fileset dir="${src}" includes="**/*.gif"/>
+            <fileset dir="${src}" includes="**/*.jpg"/>
+        </copy>
+    </target>
+
+
+    <target name="matching-jar" id="matching-jar" depends="compile">
+        <mkdir dir="${dist}"/>
+        <jar jarfile="${dist}/matching-${matching-version}.jar">
+            <fileset dir="build">
+                <include name="fr/michaelm/jump/plugin/match/**/*.class"/>
+                <include name="fr/michaelm/jump/plugin/match/**/*.properties"/>
+            </fileset>
+        </jar>
+    </target>
+
+    <target name="matching-src" id="matching-src" depends="matching-jar">
+        <mkdir dir="${dist}"/>
+        <zip zipfile="${dist}/matching-src-${matching-version}.zip">
+            <fileset dir="${dist}">
+                <include name="matching-${matching-version}.jar"/>
+            </fileset>
+            <fileset dir=".">
+                <include 
name="${src}/fr/michaelm/jump/plugin/match/**/*.java"/>
+            </fileset>
+        </zip>
+    </target>
+
+</project>
\ No newline at end of file

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/FeatureCollectionMatcher.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/FeatureCollectionMatcher.java
                               (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/FeatureCollectionMatcher.java
       2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,375 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.index.strtree.STRtree;
+import com.vividsolutions.jts.operation.union.UnaryUnionOp;
+import com.vividsolutions.jump.feature.Feature;
+import com.vividsolutions.jump.task.TaskMonitor;
+
+import fr.michaelm.jump.plugin.match.matcher.*;
+import fr.michaelm.util.text.Rule;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Matcher iterating through two FeatureCollection to find matching features.
+ *
+ * @author Michaël Michaud
+ */
+public class FeatureCollectionMatcher {
+    
+    private Collection<Feature> source;
+    private Collection<Feature> target;
+    private GeometryMatcher geometryMatcher;
+    private StringMatcher attributeMatcher;
+    private MatchMap matchMap;
+    private TaskMonitor monitor;
+    public boolean interrupted = false;
+    
+    // set n_m = true to try to match source features to several target 
+    // features in one shot.
+    private boolean n_m = false;
+    private static final Matcher OVERLAP = OverlapsMatcher.instance();
+    
+    /**
+     * A high level matcher able to compare features from two feature 
+     * collections. It is able to compare pairs of features or to pre-process
+     * the feature collection in order to find N-M matches.
+     * @param source the source feature collection
+     * @param target the target feature collection
+     * @param geometryMatcher the Matcher to evaluate geometric similarity
+     * @param attributeMatcher the matcher to evaluate semantic similarity
+     */
+    public FeatureCollectionMatcher(Collection<Feature> source,
+                                    Collection<Feature> target,
+                                    GeometryMatcher geometryMatcher,
+                                    StringMatcher attributeMatcher,
+                                    TaskMonitor monitor) {
+        if (geometryMatcher == MatchAllMatcher.MATCH_ALL) {
+            geometryMatcher = null;
+        }
+        if (attributeMatcher == MatchAllStringsMatcher.MATCH_ALL) {
+            attributeMatcher = null;
+        }
+        assert geometryMatcher != null || attributeMatcher != null :
+           "A FeatureCollectionMatcher must have at least one Matcher";
+        this.source = source;
+        this.target = target;
+        this.geometryMatcher = geometryMatcher;
+        this.attributeMatcher = attributeMatcher;
+        this.monitor = monitor;
+        matchMap = new MatchMap();
+    }
+    
+    /**
+     * Main method trying to match all features from both input feature
+     * collections and returning the set of source features matching one or
+     * several target features.
+     * @param singleSource whether a target Feature can be matched by several
+     * source features or not.
+     * @param singleTarget whether a source feature can match several target 
+     * features or not.
+     */
+    public Collection<Feature> matchAll(boolean singleSource, 
+                                        boolean singleTarget) throws Exception 
{
+        long t0 = System.currentTimeMillis();
+        if (geometryMatcher != null) {
+            System.out.println("Geometry Matching");
+            monitor.report("Geometry matching");
+            matchMap = geometryMatching(singleSource, singleTarget);
+        }
+        if (attributeMatcher != null) {
+            System.out.println("Semantic Matching");
+            monitor.report("Attribute matching");
+            matchMap = attributeMatching(singleSource, singleTarget);
+        }
+        if (geometryMatcher == null && attributeMatcher == null) {
+            throw new Exception("Invalid params (both geometric and attribute 
matchers are null !)");
+        }
+        //System.out.println("MatchMap before filter : \n" + 
matchMap.toString().replaceAll(",","\n"));
+        monitor.report("Filtering results");
+        matchMap = matchMap.filter(singleSource, singleTarget);
+        //System.out.println("MatchMap after filter : \n" + 
matchMap.toString().replaceAll(",","\n"));
+        System.out.println("Match performed in " + 
(System.currentTimeMillis()-t0) + " ms");
+        return matchMap.getSourceFeatures();
+    }
+    
+    public MatchMap getMatchMap() {
+        return matchMap;
+    }
+    
+    public void clearMatchMap() {
+        matchMap.clear();
+    }
+    
+    
+    /**
+     * Returns a MatchMap representing all the match scores obtained by 
+     * comparing source feature geometries with target feature geometries with
+     * the GeometryMatcher.
+     * @param singleSource whether a target Feature can be matched by several
+     * source features or not.
+     * @param singleTarget whether a source feature can match several target 
+     * features or not.
+     */
+    public MatchMap geometryMatching(boolean singleSource, boolean 
singleTarget) throws Exception {
+        double maxDistance = geometryMatcher.getMaximumDistance();
+        if (Double.isNaN(maxDistance)) maxDistance = 0.0;
+        //System.out.println("Geometry Matching " + geometryMatcher + " " + 
maxDistance);
+        long t0 = System.currentTimeMillis();
+        double minOverlapping = geometryMatcher.getMinimumOverlapping();
+        //System.out.println("geometryMatcher.minOverlapping = " + 
minOverlapping);
+        monitor.report("Geometry matching : indexing features");
+        STRtree index = indexFeatureCollection(target);
+        // For each feature of the source collection
+        monitor.report("Geometry matching : matching feature geometries");
+        int countf1 = 0;
+        int total = source.size();
+        for (Feature f1 : source) {
+            //System.out.println("Feature " + f1.getID());
+            Geometry g1 = f1.getGeometry();
+            Envelope env = new Envelope(g1.getEnvelopeInternal());
+            env.expandBy(maxDistance);
+            List<Feature> candidates = index.query(env);
+            // if matching_layer = reference_layer don't try to match f1 with 
itself
+            candidates.remove(f1);
+            // This loop can select several target features for one source
+            // feature, a singleTarget filter must be applied afterwards
+            int countf2 = 0;
+            // if multiple targets are authorized, a oneOneMatches map is built
+            // during the one-to-one match phase in order to be used and 
optimize
+            // the phase where we try to match source with union of 
candidates. 
+            Map<Feature,Match> oneOneMatches = null;
+            if (!singleTarget) oneOneMatches = new HashMap<Feature,Match>();
+            for (Feature f2 : candidates) {
+                double score = geometryMatcher.match(f1, f2, null);
+                if (score > 0.0) {
+                    Match match = new Match(f1, f2, score);
+                    matchMap.add(match);
+                    if (!singleTarget) oneOneMatches.put(f2, match);
+                    countf2++;
+                }
+            }
+            
+            // If one source can match multiple target 
+            // and several target candidates are available
+            // and some candidates have not been individually matched
+            if (!singleTarget && candidates.size() > 1 && !(countf2 == 
candidates.size())) {
+                Geometry globalTarget = union(candidates);
+                // if g1 matches the union of candidates, we try to attribute 
+                // a score to each g1/candidate pair
+                if (geometryMatcher.match(g1, globalTarget, null) > 0) {
+                    Geometry g1Buffer = g1.buffer(maxDistance, 4);
+                    // if g1 matches union of g2, we put all g1/g2 matches 
+                    // in a temporary structure ordered by match scores
+                    Set<Match> partialMatches = new TreeSet<Match>();
+                    for (Feature f2 : candidates) {
+                        Geometry g2Buffer = 
f2.getGeometry().buffer(maxDistance, 4);
+                        Geometry intersection = 
g1Buffer.intersection(g2Buffer);
+                        if (intersection.isEmpty()) continue;
+                        double ratio1 = 
intersection.getArea()/g1Buffer.getArea();
+                        double ratio2 = 
intersection.getArea()/g2Buffer.getArea();
+                        if (ratio1 > 0.01) {
+                            // we set the ratio of the temporary match to the
+                            // max of ratio1 and ratio 2 (match is good if f1
+                            // buffer covers a lrage part of f2 or if f2 buffer
+                            // covers a large part of f1
+                            partialMatches.add(new Match(f1, f2, 
Math.max(ratio1, ratio2)));
+                        }
+                    }
+                    int countPartialMatches = 0;
+                    // Test temporary matches from the best score to the worst,
+                    // and add them to the final matchMap until f1 is 
completely 
+                    // covered by f2 buffers
+                    SortedSet<Match> previousMatches = 
matchMap.getMatchesForSourceFeature(f1);
+                    for (Match match : partialMatches) {
+                        Match oneOneMatch = 
oneOneMatches.get(match.getTarget());
+                        if (oneOneMatch != null) {
+                            if (oneOneMatch.getScore() > match.getScore()) {
+                                continue;
+                            }
+                        }
+                        // add at least one match
+                        if (0 == countPartialMatches) {
+                            if (oneOneMatch != null) 
matchMap.removeMatch(oneOneMatch);
+                            matchMap.add(match);                            
+                        }
+                        else {
+                            // substract candidate buffer from f1
+                            Geometry diff = homogeneousDifference(g1, 
match.getTarget().getGeometry().buffer(maxDistance, 4));
+                            // Add the match if the diff operation modified 
original geometry
+                            if (!diff.equals(g1)) {
+                                matchMap.add(match);
+                            }
+                            // break if f1 is completely covered by candidate 
buffers
+                            if (diff.isEmpty()) break;
+                            else g1 = diff;
+                        }
+                        countPartialMatches++;
+                    }
+                }
+            }
+            if (monitor.isCancelRequested()) {
+                interrupted = true;
+                return matchMap;
+            };
+            monitor.report(++countf1, total, "features");
+        }
+        System.out.println("Direct Geometry Matching done in " + 
(System.currentTimeMillis()-t0) + " ms");
+        return matchMap;
+    }
+    
+    private Geometry homogeneousDifference(Geometry g1, Geometry g2) {
+        Geometry g = g1.difference(g2);
+        if (g.isEmpty()) return g;
+        else if (g.getNumGeometries() == 1) return g;
+        else if (g.getDimension() < g1.getDimension()) {
+            if (g1.getDimension() == 0) return 
g1.getFactory().createPoint((Coordinate)null);
+            if (g1.getDimension() == 1) return 
g1.getFactory().createLineString(new Coordinate[0]);
+            if (g1.getDimension() == 2) return 
g1.getFactory().createPolygon(g1.getFactory().createLinearRing(new 
Coordinate[0]), null);
+        }
+        else {
+            List<Geometry> list = new ArrayList<Geometry>();
+            for (int i = 0 ; i < g.getNumGeometries() ; i++) {
+                if (g.getGeometryN(i).getDimension() == g1.getDimension()) {
+                    list.add(g.getGeometryN(i));
+                }
+            }
+            return g1.getFactory().buildGeometry(list);
+        }
+        return g;
+    }
+    
+    private STRtree indexFeatureCollection(Collection<Feature> collection) {
+        STRtree index = new STRtree();
+        for (Feature f : collection) {
+            index.insert(f.getGeometry().getEnvelopeInternal(), f);
+        }
+        return index;
+    }
+    
+    private SortedMap<String,Collection<Feature>> 
indexFeatureCollection(Collection<Feature> collection, String attribute) {
+        SortedMap<String,Collection<Feature>> map = new 
TreeMap<String,Collection<Feature>>();
+        for (Feature f : collection) {
+            String value = f.getString(attribute);
+            Collection coll = map.get(value);
+            if (coll == null) {
+                coll = new ArrayList<Feature>();
+                map.put(value, coll);
+            }
+            coll.add(f);
+        }
+        return map;
+    }
+    
+    private Geometry union(List<Feature> features) {
+        List geom = new ArrayList();
+        for (Feature f : features) geom.add(f.getGeometry());
+        return UnaryUnionOp.union(geom);
+    }
+    
+    private MatchMap attributeMatching(boolean singleSource, boolean 
singleTarget) throws Exception {
+        String sourceAttribute = attributeMatcher.getSourceAttribute();
+        String targetAttribute = attributeMatcher.getTargetAttribute();
+        Rule sourceRule = attributeMatcher.getSourceRule();
+        Rule targetRule = attributeMatcher.getTargetRule();
+        // If geometryMatcher is null, a simple join will be done.
+        if (geometryMatcher == null && attributeMatcher != null) {
+            monitor.report("Attribute matching : indexing features");
+            Index index = attributeMatcher.createIndex(target);
+            int count = 0;
+            int total = source.size();
+            monitor.report("Attribute matching : matching feature attributes");
+            for (Feature f1 : source) {
+                String sourceValue = 
sourceRule.transform(f1.getString(sourceAttribute));
+                //System.out.println("sourceValue : " + sourceValue);
+                Set<Feature> candidates = 
+                    index.query(sourceValue);
+                if (candidates == null || candidates.isEmpty()) {
+                    continue;
+                }
+                else if (Double.isNaN(attributeMatcher.getMaximumDistance())) {
+                    for (Feature f2 : candidates) {
+                        matchMap.add(new Match(f1, f2, 1.0));
+                    }
+                }
+                // In the case where a BKTree is used, there is room for 
+                // optimizition because distances are already computed by the
+                // BKTree query method
+                else {
+                    for (Feature f2 : candidates) {
+                        double d = attributeMatcher.match(f1, f2, null);
+                        matchMap.add(new Match(f1, f2, d));
+                    }
+                }
+                if (monitor.isCancelRequested()) {
+                    interrupted = true;
+                    return matchMap;
+                };
+                monitor.report(++count, total, "features");
+            }            
+            // index attribute data
+        }
+        // If a geometry matching has already been done, attribute matching
+        // use the resulting MatchMap from the geometry matching process 
+        else {
+            List<Match> new_matches = new ArrayList<Match>();
+            Set<Match> allMatches = matchMap.getAllMatches();
+            int count = 0;
+            int total = allMatches.size();
+            for (Match m : allMatches) {
+                String srcA = 
sourceRule.transform(m.getSource().getString(sourceAttribute));
+                String tgtA = 
targetRule.transform(m.getTarget().getString(targetAttribute));
+                double newScore = m.combineScore(attributeMatcher.match(srcA, 
tgtA, null));
+                if (newScore > 0.0) {
+                    new_matches.add(new Match(m.getSource(), m.getTarget(), 
newScore));
+                }
+                if (monitor.isCancelRequested()) {
+                    interrupted = true;
+                    return matchMap;
+                };
+                monitor.report(++count, total, "matches");
+            }
+            matchMap.clear();
+            for (Match match : new_matches) {
+                matchMap.add(match);
+            }
+
+        }
+        return matchMap;
+    }
+
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/I18NPlug.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/I18NPlug.java   
                            (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/I18NPlug.java   
    2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,76 @@
+/*
+ * (C) 2017 Michaël Michaud
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * 
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.log4j.Logger;
+
+import com.vividsolutions.jump.I18N;
+
+public class I18NPlug {
+    
+    private static final Logger LOG = Logger.getLogger(I18NPlug.class);
+    
+    // Use the same locale as the main program
+    private static final ResourceBundle I18N_RESOURCE =
+        ResourceBundle.getBundle("fr/michaelm/jump/plugin/match/matching", new 
Locale(I18N.getLocale()));
+        
+    public static String getI18N(String key) {
+        try { return I18N_RESOURCE.getString(key); }
+        catch (MissingResourceException ex) {
+            String[] labelpath = key.split("\\.");
+            ex.printStackTrace();
+            return labelpath[labelpath.length-1];
+        }
+        catch (Exception ex) {
+            ex.printStackTrace();
+            return "";
+        }
+    }
+    
+  /**
+   * Get a formatted message with argument insertion.
+   *
+   * @param label with argument insertion : {0}
+   * @param objects values to insert in the message
+   * @return i18n label
+   */
+  public static String getMessage(final String label, final Object[] objects) {
+    try {
+      final MessageFormat mformat = new 
MessageFormat(I18N_RESOURCE.getString(label));
+      return mformat.format(objects);
+    } catch (java.util.MissingResourceException e) {
+      final String[] labelpath = label.split("\\.");
+      LOG.warn(e.getMessage() + " no default value, the resource key is used: "
+        + labelpath[labelpath.length - 1]);
+      final MessageFormat mformat = new MessageFormat(
+        labelpath[labelpath.length - 1]);
+      return mformat.format(objects);
+    }
+  }
+    
+}
\ No newline at end of file

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Index.java
===================================================================
--- plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Index.java  
                        (rev 0)
+++ plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Index.java  
2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,38 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jump.feature.Feature;
+import java.util.Set;
+
+/**
+ * Index returns a Set of Feature candidates from an criteria Object.
+ * Criteria is tipically a Geometry, an Enveloppe or an attribute value.  
+ * 
+ * @author Michaël Michaud
+ */
+public interface Index {
+    
+    Set<Feature> query(Object o);
+    
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Match.java
===================================================================
--- plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Match.java  
                        (rev 0)
+++ plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Match.java  
2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,112 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jump.feature.Feature;
+
+/**
+ * A match between two features. A match is oriented from a source feature
+ * to a target feature.
+ * 
+ * @author Michaël Michaud
+ */
+public class Match implements Comparable<Match> {
+    
+    private Feature source;
+    private Feature target;
+    private double score;
+    //private double minDistance = Double.NaN;
+
+    /**
+     * Create a Match object.
+     * @param source the source Feature to match from
+     * @param target the target Feature to match to
+     * @param score the score of the match
+     */
+    public Match(Feature source, Feature target, double score) {
+        this.source = source;
+        this.target = target;
+        this.score = score;
+    }
+    
+    public Feature getSource() {
+        return source;
+    }
+    
+    public Feature getTarget() {
+        return target;
+    }
+    
+    public double getScore() {
+        return score;
+    }
+    
+    /**
+     * Combine score with another score so that 
+     * - one of to scores is 0 -> final score is 0
+     * - both score are 1 -> final score is 1
+     */
+    public double combineScore(double otherScore) {
+        return score * otherScore;
+        //return this;
+    }
+    
+    /**
+     * Compare two matches by comparing their matching score first, then, in
+     * case of matching score equality, their source feature ID, and in case
+     * of source feature ID equality, their target ID.
+     */
+     public int compareTo(Match m) {
+        if (getScore() > m.getScore()) return -1;
+        else if (getScore() < m.getScore()) return 1;
+        else {
+            if (getSource().getID() < m.getSource().getID()) return -1;
+            else if (getSource().getID() > m.getSource().getID()) return 1;
+            else {
+                if (getTarget().getID() < m.getTarget().getID()) return -1;
+                else if (getTarget().getID() > m.getTarget().getID()) return 1;
+                else return 0;
+            }
+        }
+    } 
+    
+    /**
+     * Two matches are equal iff their source feature ID, their target feature 
+     * ID AND their matching score are equal.
+     * It is important that equals is consistent with compareTo
+     */
+    public boolean equals(Object o) {
+        if (o instanceof Match) {
+            Match other = (Match)o;
+            return source.getID() == other.getSource().getID() && 
+                   target.getID() == other.getTarget().getID() && 
+                   score == other.getScore();
+        }
+        return false;
+    }
+    
+    public String toString() {
+        return "Match " + source.getID() + " and " + target.getID() + " with 
score " + score; 
+    }
+
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchEditingPlugIn.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchEditingPlugIn.java
                             (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchEditingPlugIn.java
     2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,257 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jump.feature.BasicFeature;
+import com.vividsolutions.jump.feature.Feature;
+import com.vividsolutions.jump.feature.FeatureCollection;
+import com.vividsolutions.jump.feature.FeatureSchema;
+import com.vividsolutions.jump.workbench.WorkbenchContext;
+import com.vividsolutions.jump.workbench.model.Layer;
+import com.vividsolutions.jump.workbench.model.LayerManager;
+import com.vividsolutions.jump.workbench.plugin.EnableCheck;
+import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
+import com.vividsolutions.jump.workbench.plugin.PlugInContext;
+import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
+import com.vividsolutions.jump.workbench.ui.GUIUtil;
+import com.vividsolutions.jump.workbench.ui.MenuNames;
+import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
+import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
+import com.vividsolutions.jump.workbench.ui.task.TaskMonitorManager;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Iterator;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+
+/**
+ * PlugIn to find features from a layer matching features of another layer. 
+ * @author Michaël Michaud
+ */
+public class MatchEditingPlugIn extends AbstractPlugIn implements 
ActionListener {
+    
+    private final String MATCH_EDITING = I18NPlug.getI18N("Match-editing");
+    private final String MATCHING      = I18NPlug.getI18N("Matching");
+    private final String LINK_LAYER    = I18NPlug.getI18N("Links");
+    private final String SOURCE_LAYER  = I18NPlug.getI18N("Source-layer");
+    private final String TARGET_LAYER  = I18NPlug.getI18N("Target-layer");
+    
+    private WorkbenchContext workbenchContext = null;
+    private Layer linkLayer   = null;
+    private Layer sourceLayer = null;
+    private Layer targetLayer = null;
+    
+   
+    
+    public MatchEditingPlugIn() {
+    }
+    
+    public String getName() {
+        return MATCH_EDITING;
+    }
+
+    public void initialize(PlugInContext context) throws Exception {
+        
+        context.getFeatureInstaller().addMainMenuPlugin(
+          this, new String[]{MenuNames.PLUGINS, MATCHING},
+          MATCH_EDITING + "...",
+          false, null, getEnableCheck(context));
+        workbenchContext = context.getWorkbenchContext();
+    }
+
+    /**
+     * Execute method initialize the plugin interface and get all the
+     * parameters from the user.
+     */
+    public boolean execute(PlugInContext context) throws Exception {
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CREATE MULTITAB INPUT DIALOG
+        
////////////////////////////////////////////////////////////////////////
+                
+        final MultiInputDialog dialog = new MultiInputDialog(
+            context.getWorkbenchFrame(), MATCH_EDITING, false);
+        
+        linkLayer = context.getLayerNamePanel().getSelectedLayers()[0];
+        FeatureCollection fc = linkLayer.getFeatureCollectionWrapper();
+        JTextField linkTextField = dialog.addTextField(LINK_LAYER, 
linkLayer.getName(), 24, null, null);
+        linkTextField.setEditable(false);
+        
+        sourceLayer = findTargetLayer(linkLayer, "SOURCE", 
context.getLayerManager());
+        if (sourceLayer != null) {
+            JTextField sourceTextField = dialog.addTextField(SOURCE_LAYER, 
sourceLayer.getName(), 24, null, null);
+            sourceTextField.setEditable(false);
+        } else {
+            JOptionPane.showMessageDialog(context.getWorkbenchFrame(), 
+                I18NPlug.getI18N("Missing-source-layer"),
+                I18NPlug.getI18N("Missing-layer"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+        
+        
+        targetLayer = findTargetLayer(linkLayer, "TARGET", 
context.getLayerManager());
+        if (targetLayer != null) {
+            JTextField targetTextField = dialog.addTextField(TARGET_LAYER, 
targetLayer.getName(), 24, null, null);
+            targetTextField.setEditable(false);
+        } else {
+            JOptionPane.showMessageDialog(context.getWorkbenchFrame(), 
+                I18NPlug.getI18N("Missing-target-layer"), 
+                I18NPlug.getI18N("Missing-layer"), JOptionPane.ERROR_MESSAGE);
+            return false;
+        }
+        
+        
+        JButton createLinksButton = 
dialog.addButton(I18NPlug.getI18N("Create-links-label"), 
I18NPlug.getI18N("Create-links"), "");
+        createLinksButton.addActionListener(this);
+        createLinksButton.setActionCommand("link");
+        
+        JButton updateMatchesButton = 
dialog.addButton(I18NPlug.getI18N("Update-matches"), 
I18NPlug.getI18N("Update-matches"), "");
+        updateMatchesButton.addActionListener(this);
+        updateMatchesButton.setActionCommand("update");
+        
+        
dialog.setSideBarDescription(I18NPlug.getI18N("Match-editing-description"));
+        
+        for (Object o : workbenchContext.getLayerManager().getLayers()) {
+            Layer layer = (Layer)o;
+            if (layer != linkLayer && layer != sourceLayer && layer != 
targetLayer) {
+                layer.setSelectable(false);
+                layer.setEditable(false);
+                layer.setVisible(false);
+            }
+        }
+        linkLayer.setVisible(true);
+        linkLayer.setEditable(true);
+        linkLayer.setSelectable(true);
+        
+        sourceLayer.setVisible(true);
+        sourceLayer.setSelectable(true);
+        sourceLayer.setEditable(false);
+        
+        targetLayer.setVisible(true);        
+        targetLayer.setSelectable(true);
+        targetLayer.setEditable(false);
+        
+        GUIUtil.centreOnWindow(dialog);
+        dialog.setVisible(true);
+ 
+        return true;
+        
+    }
+    
+
+    
+    
+    // Return the layer containing features identified by attribute column
+    // return null if layer is not found
+    private Layer findTargetLayer(Layer links, String attribute, LayerManager 
layerManager) {
+        int index = 
links.getFeatureCollectionWrapper().getFeatureSchema().getAttributeIndex(attribute);
+        if (index < 0) return null;
+        for (Iterator it = links.getFeatureCollectionWrapper().iterator() ; 
it.hasNext() ;) {
+            Feature f = (Feature)it.next();
+            int fid = f.getInteger(index);
+            for (Object o : layerManager.getLayers()) {
+                if (o == links) continue;
+                Layer lyr = (Layer)o;
+                for (Iterator it2 = 
lyr.getFeatureCollectionWrapper().iterator() ; it2.hasNext() ;) {
+                    Feature f2 = (Feature)it2.next();
+                    if (fid == f2.getID()) {
+                        return lyr;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+    
+    private EnableCheck getEnableCheck(final PlugInContext context) {
+        return new MultiEnableCheck()
+          .add(context.getCheckFactory().createTaskWindowMustBeActiveCheck())
+          
.add(context.getCheckFactory().createExactlyNLayersMustBeSelectedCheck(1))
+          .add(context.getCheckFactory().createAtLeastNLayersMustExistCheck(3))
+          
.add(context.getCheckFactory().createSelectedLayersMustBeEditableCheck())
+          .add(new EnableCheck(){
+              public String check(JComponent component) {
+                  Layer lyr = 
context.getWorkbenchContext().getLayerableNamePanel().getSelectedLayers()[0];
+                  FeatureSchema schema = 
lyr.getFeatureCollectionWrapper().getFeatureSchema();
+                  return schema.hasAttribute("SOURCE") &&
+                         schema.hasAttribute("TARGET") &&
+                         schema.hasAttribute("SCORE") ? 
+                      null : I18NPlug.getI18N("Invalid-link-layer");
+              }
+          });
+    }
+    
+    public void actionPerformed(ActionEvent e) {
+        if ("link".equals(e.getActionCommand())) {
+                Collection selectedSource = workbenchContext
+                        .getLayerViewPanel()
+                        .getSelectionManager()
+                        .getFeaturesWithSelectedItems(sourceLayer);
+                Collection selectedTarget = workbenchContext
+                        .getLayerViewPanel()
+                        .getSelectionManager()
+                        .getFeaturesWithSelectedItems(targetLayer);
+            for (Object o1 : selectedSource) {
+                Feature source = (Feature)o1;
+                for (Object o2 : selectedTarget) {
+                    Feature target = (Feature)o2;
+                    BasicFeature bf = new 
BasicFeature(linkLayer.getFeatureCollectionWrapper().getFeatureSchema());
+                    Coordinate cSource = 
source.getGeometry().getInteriorPoint().getCoordinate();
+                    Coordinate cTarget = 
target.getGeometry().getInteriorPoint().getCoordinate();
+                    if (cSource.equals(cTarget)) {
+                        bf.setGeometry(new 
GeometryFactory().createPoint(cSource));
+                    }
+                    else {
+                        bf.setGeometry(new GeometryFactory().createLineString(
+                            new Coordinate[]{cSource, cTarget}));
+                    }
+                    bf.setAttribute("SOURCE", source.getID());
+                    bf.setAttribute("TARGET", target.getID());
+                    bf.setAttribute("SCORE", 1.0);
+                    linkLayer.getFeatureCollectionWrapper().add(bf);
+                }
+            }
+        }
+        else if ("update".equals(e.getActionCommand())) {
+            try {
+                MatchingUpdatePlugIn mupi = new MatchingUpdatePlugIn();
+                mupi.setLinkLayerName(linkLayer.getName());
+                mupi.setSourceLayerName(sourceLayer.getName());
+                mupi.setTargetLayerName(targetLayer.getName());
+                PlugInContext context = workbenchContext.createPlugInContext();
+                if (mupi.execute(context)) {
+                    new TaskMonitorManager().execute(mupi, context); 
+                }
+            } catch(Exception ex) {
+                WorkbenchFrame.toMessage(ex);
+            }
+        }
+    }
+
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchMap.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchMap.java   
                            (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchMap.java   
    2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,230 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jump.feature.Feature;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * A Map accumulating information about matches between two sets of features.
+ *
+ * The MatchMap will store every single Match in a Tree Map ordering all 
+ * possible matches from the best score to the worst score. For matches
+ * returning the same score, ordering is determined by the 
+ * {@link Match#compareTo(Match other)} method.
+ *
+ * @author Michaël Michaud
+ */
+public class MatchMap {
+    
+    private final SortedSet<Match> EMPTY_SET = 
Collections.unmodifiableSortedSet(new TreeSet<Match>());
+        
+    private final Map<Feature,TreeSet<Match>> sourceMap = new 
HashMap<Feature,TreeSet<Match>>();
+    private final Map<Feature,TreeSet<Match>> targetMap = new 
HashMap<Feature,TreeSet<Match>>();
+    
+    //TODO : to improve performance, instead of maintaining 3 ordered map 
during 
+    // the feeding (at each add call), sort the map on demand, keeping track 
of 
+    // the ordering state (sorted after a get* or a filter call, unsorted 
after 
+    // a add call.
+    boolean sorted;
+
+    /**
+     * Construct a new MatchMap.
+     */
+    public MatchMap() {}
+    
+    /**
+     * Add a match to this MatchMap.
+     * In version 0.6.0+, we assume that feature 1 and feature 2 can have have 
+     * one match only (so that the test to keep the best match only is 
removed) 
+     */
+    public void add(Match m) {
+        TreeSet<Match> set = sourceMap.get(m.getSource());
+        if (set == null) {
+            set = new TreeSet<>();
+            sourceMap.put(m.getSource(), set);
+        }
+        set.add(m);
+        set = targetMap.get(m.getTarget());
+        if (set == null) {
+            set = new TreeSet<>();
+            targetMap.put(m.getTarget(), set);
+        }
+        set.add(m);
+    }
+    
+    /**
+     * Get the whole match Set.
+     */
+    public Set<Match> getAllMatches() {
+        Set<Match> matches = new HashSet<Match>();
+        for (Feature feature : sourceMap.keySet()) 
matches.addAll(sourceMap.get(feature));
+        return matches;
+    }
+    
+    /**
+     * Get the set of features matching one or more features.
+     */
+    public Set<Feature> getSourceFeatures() {
+        return sourceMap.keySet();
+    }
+    
+    /**
+     * Get the set of features being matched by one or more features.
+     */
+    public Set<Feature> getTargetFeatures() {
+        return targetMap.keySet();
+    }
+    
+    /**
+     * Get Matches recorded for this source Feature.
+     */
+    public SortedSet<Match> getMatchesForSourceFeature(Feature f) {
+        SortedSet<Match> matches = sourceMap.get(f);
+        return matches == null ? EMPTY_SET : matches;
+    }
+    
+    /**
+     * Get Matches recorded for this target Feature.
+     */
+    public SortedSet<Match> getMatchesForTargetFeature(Feature f) {
+        SortedSet<Match> matches = targetMap.get(f);
+        return matches == null ? EMPTY_SET : matches;
+    }
+    
+    /**
+     * Get Features matching source Feature f.
+     */
+    public List<Feature> getMatchedFeaturesFromSource(Feature f) {
+        TreeSet<Match> matchedFeatures = sourceMap.get(f);
+        List<Feature> list = new ArrayList<>();
+        if (matchedFeatures == null) return list;
+        for (Match m : matchedFeatures) {
+            list.add(m.getTarget());
+        }
+        return list;
+    }
+    
+    /**
+     * Get Features matching target Feature f.
+     */
+    public List<Feature> getMatchedFeaturesFromTarget(Feature f) {
+        TreeSet<Match> matchedFeatures = targetMap.get(f);
+        List<Feature> list = new ArrayList<>();
+        if (matchedFeatures == null) return list;
+        for (Match m : matchedFeatures) {
+            list.add(m.getSource());
+        }
+        return list;
+    }
+    
+    /**
+     * Return Match from source to target. Usually, the result contains 0
+     * or 1 Match, but nothing prevent insertion of several matches per couple
+     * of features.
+     */
+    public SortedSet<Match> getMatches(Feature source, Feature target) {
+        // Set of matches from f1
+        TreeSet<Match> set1 = sourceMap.get(source);
+        // Set of matches to f2
+        TreeSet<Match> set2 = targetMap.get(target);
+        // Intersection of both sets = Match:f1->f2
+        if (set1 != null && set2 != null) {
+            SortedSet<Match> set = (TreeSet<Match>)set1.clone();
+            set.retainAll(set2);
+            return set;
+        }
+        else return EMPTY_SET;
+    }
+    
+    private boolean removeMatchesForSourceFeature(Feature f) {
+        TreeSet<Match> set = sourceMap.remove(f);
+        return /*matches.removeAll(set)*/ true;
+    }
+    
+    private boolean removeMatchesForTargetFeature(Feature f) {
+        TreeSet<Match> set = targetMap.remove(f);
+        return /*matches.removeAll(set)*/ true;
+    }
+    
+    /**
+     * Remove a match from the map.
+     */
+    public void removeMatch(Match m, boolean singleSource, boolean 
singleTarget) {
+        if (singleTarget) removeMatchesForSourceFeature(m.getSource());
+        if (singleSource) removeMatchesForTargetFeature(m.getTarget());
+    }
+    
+    /**
+     * Remove a match from the map.
+     */
+    public void removeMatch(Match m) {
+        // remove match from sourceMap
+        sourceMap.get(m.getSource()).remove(m);
+        // if sourceMap has no more match for this source, remove source 
feature
+        if (sourceMap.get(m.getSource()).size() == 0) 
sourceMap.remove(m.getSource());
+        // remove match from targetMap
+        targetMap.get(m.getTarget()).remove(m);
+        // if targetMap has no more match for this target, remove target 
feature
+        if (targetMap.get(m.getTarget()).size() == 0) 
targetMap.remove(m.getTarget());
+    }
+    
+    /**
+     * Filter the matchMap so that each source feature has only one target 
match
+     * and/or each target feature has only one source match.
+     */
+    public MatchMap filter(boolean singleSource, boolean singleTarget) {
+        if (!singleSource && !singleTarget) return this;
+        //TreeSet<Match> filteredMatches = new TreeSet<Match>();
+        // new code
+        MatchMap matchMap = new MatchMap();
+        TreeSet<Match> matches = new TreeSet<Match>();
+        for (Feature feature : sourceMap.keySet()) 
matches.addAll(sourceMap.get(feature));
+        for (Match match : matches) {
+            Feature source = match.getSource();
+            Feature target = match.getTarget();
+            // Check if matchMap already has target features for this source
+            SortedSet<Match> matchesForSource = 
matchMap.getMatchesForSourceFeature(source);
+            // Check if matchMap already has source features for this target
+            SortedSet<Match> matchesForTarget = 
matchMap.getMatchesForTargetFeature(target);
+            if (singleTarget && matchesForSource.size() > 0) continue;
+            else if (singleSource && matchesForTarget.size() > 0) continue;
+            else matchMap.add(match);
+        }
+        return matchMap;
+    }
+    
+    public void clear() {
+        sourceMap.clear();
+        targetMap.clear();
+    }
+    
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Matcher.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Matcher.java    
                            (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/Matcher.java    
    2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,125 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jump.feature.Feature;
+
+/**
+ * Interface for all simple matchers able to evaluate if a feature f matches
+ * a reference feature ref and how well it matches.
+ * Matcher is not symmetric. For example, IncludeMatcher will return 1 for
+ * match(f, ref) if f is included in ref, and 0 for match(ref, f).
+ * On the other hand, minimum distance should be symmetric.
+ * @author Michaël Michaud
+ */
+public interface Matcher {
+    
+    //String MAXIMUM_DISTANCE = I18NPlug.getI18N("Maximum-distance");
+    
+    //String MINIMUM_OVERLAPPING = I18NPlug.getI18N("Minimum-overlapping");
+    
+    /**
+     * Returns a distance measuring the match quality of Feature f with a 
+     * reference Feature ref.
+     * The method returns 0 if f and ref do not match at all, and 1 if they
+     * match perfectly.
+     * If (f == ref), match should always return 1, but the return value of
+     * match(f, ref, context) if f.equals(ref) depends of the exact semantic
+     * of the matcher.
+     * It is not required that match(f, ref, context) = match(ref, f, 
context). 
+     *
+     * @param f Feature to match from
+     * @param ref reference Feature to match to
+     * @param context object containing useful information to check if
+     * Feature f effectively matches Feature ref
+     *
+     * @return a double in the range [0-1] representative of the match quality
+     * between f and ref. 
+     *
+     * @throws Exception if input data cannot be processed.
+     */
+    double match(Feature f, Feature ref, Object context) throws Exception;
+    
+    
+    /**
+     * Returns the maximum distance accepted between f1 and ref
+     * Exact meaning highly depends on what distance is measured, but whatever 
+     * the definition is (minimum distance, hausdorff distance, levenshtein
+     * distance...), if distance between f and ref is over the maximum
+     * distance, the value returned by match method will be 0.
+     * <ul>
+     * <li>
+     * If 0 is returned, match will always return 0 except for two
+     * identical features (identical meaning depends on matcher definition)
+     * </li>
+     * <li>
+     * Returning NaN means that using a tolerance has no meaning for this 
+     * matcher (example, getMaximumDistance of equals matchers returns NaN).
+     * </li>
+     * <li>
+     * If Double.POSITIVE_INFINITY is returned, matches between f and ref will
+     * alway return a non null value.
+     * </li>
+     * </ul>
+     */
+    double getMaximumDistance();
+    
+    
+    /**
+     * Returns the minimum overlapping between f and ref, where overlapping is
+     * generally expressed as a percentage, but not necessarily.
+     * Overlapping may have different meanings as the ratio between common area
+     * and f area or between the length of the longest common substring of two
+     * attributes values and the length of the full string.<br>
+     * Depending on the Matcher, overlapping between f and ref may be
+     * directional (ex. common area / f area) or symmetric (intersection area
+     * / union area).
+     * <ul>
+     * <li>
+     * If 0 is returned, any pair of f and ref which intersects will return a
+     * non null value.
+     * </li>
+     * <li>
+     * NaN means that this criteria has no meaning for this matcher.
+     * </li>
+     * <li>
+     * 100.0 generally means that f and ref must be equal, but the precise
+     * definition may bary from a matcher to another.
+     * </li>
+     * </ul>
+     */
+    double getMinimumOverlapping();
+    
+    /**
+     * Sets the maximum distance returning a non null value.
+     * @see #getMaximumDistance
+     */
+    void setMaximumDistance(double max_dist);
+    
+    /**
+     * Sets the minimum overlapping ratio returning a non null value.
+     * @see #getMinimumOverlapping
+     */
+    void setMinimumOverlapping(double min_overlap);
+
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatcherRegistry.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatcherRegistry.java
                                (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatcherRegistry.java
        2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,93 @@
+/*
+ * (C) 2017 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import fr.michaelm.jump.plugin.match.matcher.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * Matcher Registry
+ * @author Michaël Michaud
+ */
+public class MatcherRegistry<T extends Matcher> {
+    
+    public static MatcherRegistry<GeometryMatcher> GEOMETRY_MATCHERS = new 
MatcherRegistry<> (
+        MatchAllMatcher.instance(),
+        EqualsExactGeom3dMatcher.instance(),
+        EqualsNormalizedGeom3dMatcher.instance(),
+        EqualsExactGeom2dMatcher.instance(),
+        EqualsNormalizedGeom2dMatcher.instance(),
+        EqualsTopologicalGeomMatcher.instance(),
+        EqualsWithCoordinateToleranceMatcher.instance(),
+
+        IsWithinMatcher.instance(),
+        OverlapsMatcher.instance(),
+        OverlappedByMatcher.instance(),
+
+        IntersectsMatcher.instance(),
+        Intersects0DMatcher.instance(),
+        Intersects1DMatcher.instance(),
+        Intersects2DMatcher.instance(),
+
+        MinimumDistanceMatcher.instance(),
+        CentroidDistanceMatcher.instance(),
+        HausdorffDistanceMatcher.instance(),
+        SemiHausdorffDistanceMatcher.instance(),
+        ShapeMatcher.instance()
+    );
+    
+    public static MatcherRegistry<StringMatcher> STRING_MATCHERS = new 
MatcherRegistry<> (
+        MatchAllStringsMatcher.instance(),
+        StringEqualityMatcher.instance(),
+        StringEqualityIgnoreCaseMatcher.instance(),
+        StringEqualityIgnoreCaseAndAccentMatcher.instance(),
+        LevenshteinDistanceMatcher.instance(),
+        DamarauLevenshteinDistanceMatcher.instance()
+    );
+    
+    private Map<String,T> map = new LinkedHashMap<>();
+    
+    public void register(T matcher) {
+        //map.put(matcher.toString(), matcher);
+        map.put(matcher.getClass().getSimpleName(), matcher);
+    }
+    
+    public MatcherRegistry(T... matchers) {
+        for (T matcher : matchers) register(matcher);
+    }
+    
+    public T get(String name) {
+        return map.get(name);
+    }
+    
+    public Map<String,T> getMap() {
+        return map;
+    }
+    
+    public static Matcher getMatcher(MatcherRegistry<? extends Matcher> 
registry, String name) {
+        return registry.map.get(name);
+    }
+    
+}

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingExtension.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingExtension.java
                              (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingExtension.java
      2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,81 @@
+/*
+ * (C) 2017 Michaël Michaud
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * 
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jump.workbench.plugin.Extension;
+import com.vividsolutions.jump.workbench.plugin.PlugInContext;
+
+/**
+ * Extension containing matching processing also known as join.
+ * @author Michaël Michaud
+ * @version 0.8.0 (2018-06-15)
+ */ 
+// History
+// 0.8.0 (2018-06-15) : refactor to use add/getParameter
+// 0.7.5 (2017-03-26) : clean headers and remove dead code before inclusion in
+//                      OpenJUMP PLUS version
+// 0.7.4 (2017-03-13) : overlapping method could not handle linear geometries
+// 0.7.3 (2014-03-27) : layers created by the plugin are now true layers, not 
+//                      "views" on the source layer as views can cause severe
+//                      bugs if schema of the source layer is changed.
+//                      autorise le transfert d'attribut quand il n'y a pas 
+//                      d'attribut de type String
+// 0.7.2 (2013-07-30) : fix a bug which appears when GeometryMatcher and 
+//                      DamarauLevenshteinDistanceMatcher are used 
simultaneously
+// 0.7.1 (2013-04-21) : remember last attribute used if layers did not change
+// 0.7.0 (2013-04-07) : add MatchingUpdatePlugIn and MatchEditingPlugIn
+// 0.6.2 (2013-03-05) : correction d'une regression empechant tout appariment 
1:1
+// 0.6.1 (2013-03-02) : option to compute min distance of matched features
+//                      improve UI labels/I18N for source and target layers
+//                      matchingUpdatePlugIn (work-in-progress, not yet 
activated)                      
+// 0.6.0 (2013-01-29) : performance improvements
+//                      improve UI labels/I18N for singleSource and 
singleTarget options
+//                      the threaded process is now interruptable
+// 0.5.9 (2012-12-03) : UI labels and I18N
+// 0.5.8 (2012-10-07) : fix in 1:N matches wich were only partially found
+// 0.5.7 (2012-09-  ) : 
+// 0.5.6 (2012-06-28) : small fix in the UI (attribute aggregation)
+// 0.5.5 (2012-05-08) : fix a NPE when geometry+attribute+cardinality was used
+//                      changed the limit definition of damarau-levenshtein
+// 0.5.4 (2012-03-12) : add X_MIN_SCORE to reference dataset + fix link layer 
name 
+// 0.5.3 (2012-01-17) : recompile to be java 1.5 compatible
+// 0.5.2 (2012-01-03) : small fixes in i18n
+// 0.5.1 (2011-12-04) : small fix in i18n
+// 0.5   (2011-12-01) : initial version
+public class MatchingExtension extends Extension {
+
+    public String getName() {
+        return "Matching Extension (Michaël Michaud)";
+    }
+
+    public String getVersion() {
+        return "0.8.0 (2018-06-15)";
+    }
+
+    public void configure(PlugInContext context) throws Exception {
+        new MatchingPlugIn().initialize(context);
+        //new MatchingUpdatePlugIn().initialize(context);
+        new MatchEditingPlugIn().initialize(context);
+    }
+
+}
\ No newline at end of file

Added: 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingPlugIn.java
===================================================================
--- 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingPlugIn.java
                         (rev 0)
+++ 
plug-ins/MatchingPlugIn/trunk/src/fr/michaelm/jump/plugin/match/MatchingPlugIn.java
 2018-06-17 12:10:51 UTC (rev 5878)
@@ -0,0 +1,903 @@
+/*
+ * (C) 2018 Michaël Michaud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * For more information, contact:
+ *
+ * m.michael.mich...@orange.fr
+ */
+
+package fr.michaelm.jump.plugin.match;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.operation.distance.DistanceOp;
+import com.vividsolutions.jump.task.TaskMonitor;
+import com.vividsolutions.jump.feature.AttributeType;
+import com.vividsolutions.jump.feature.BasicFeature;
+import com.vividsolutions.jump.feature.Feature;
+import com.vividsolutions.jump.feature.FeatureCollection;
+import com.vividsolutions.jump.feature.FeatureDataset;
+import com.vividsolutions.jump.feature.FeatureSchema;
+import com.vividsolutions.jump.workbench.Logger;
+import com.vividsolutions.jump.workbench.model.Layer;
+import com.vividsolutions.jump.workbench.model.StandardCategoryNames;
+import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
+import com.vividsolutions.jump.workbench.plugin.PlugInContext;
+import com.vividsolutions.jump.workbench.plugin.ThreadedBasePlugIn;
+import com.vividsolutions.jump.workbench.ui.AttributeTypeFilter;
+import com.vividsolutions.jump.workbench.ui.GUIUtil;
+import com.vividsolutions.jump.workbench.ui.MenuNames;
+import com.vividsolutions.jump.workbench.ui.MultiTabInputDialog;
+import com.vividsolutions.jump.workbench.ui.renderer.style.BasicStyle;
+import com.vividsolutions.jump.workbench.ui.Viewport;
+import com.vividsolutions.jump.workbench.ui.renderer.style.RingVertexStyle;
+
+import fr.michaelm.jump.plugin.match.matcher.*;
+import fr.michaelm.util.text.RuleRegistry;
+import org.openjump.core.ui.plugin.tools.aggregate.Aggregator;
+import org.openjump.core.ui.plugin.tools.aggregate.Aggregators;
+
+import java.awt.*;
+import java.awt.geom.Point2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JTextField;
+
+/**
+ * PlugIn to find features of a source layer matching features of a target 
layer
+ * (or reference layer) and optionally transfer attributes between matching
+ * features.
+ * @author Michaël Michaud
+ */
+public class MatchingPlugIn extends ThreadedBasePlugIn {
+
+    private String P_SRC_LAYER                = "SourceLayer";
+    private String P_SINGLE_SRC               = "SingleSource";
+    private String P_TGT_LAYER                = "TargetLayer";
+    private String P_SINGLE_TGT               = "SingleTarget";
+    private String P_GEOMETRY_MATCHER         = "GeometryMatcher";
+    private String P_MAX_GEOM_DISTANCE        = "MaximumGeometriesDistance";
+    private String P_MIN_GEOM_OVERLAP         = "MinimumGeometriesOverlap";
+    private String P_COPY_MATCHING            = "CopyMatchingFeatures";
+    private String P_COPY_NOT_MATCHING        = "CopyNotMatchingFeatures";
+    private String P_DISPLAY_LINKS            = "DisplayLinks";
+
+    private String P_USE_ATTRIBUTES           = "UseAttributes";
+    private String P_SRC_ATTRIBUTE            = "SourceAttribute";
+    private String P_SRC_ATTRIBUTE_PREPROCESS = "SourceAttributePreprocess";
+    private String P_TGT_ATTRIBUTE            = "TargetAttribute";
+    private String P_TGT_ATTRIBUTE_PREPROCESS = "TargetAttributePreprocess";
+    private String P_ATTRIBUTE_MATCHER        = "AttributeMatcher";
+    private String P_MAX_STRING_DISTANCE      = "MaximumStringDistance";
+    private String P_HAS_MAX_STRING_DISTANCE  = "HasMaxStringDistance";
+    private String P_MIN_STRING_OVERLAP       = "MinStringOverlap";
+    private String P_HAS_MIN_STRING_OVERLAP   = "HasMinStringOverlap";
+
+
+    private String P_TRANSFER_ATTRIBUTES      = "TransferAttributes";
+    private String P_TRANSFER_BEST_MATCH_ONLY = "TransferBestMatchOnly";
+    private String P_STRING_AGGREGATOR        = "StringAggregator";
+    private String P_INTEGER_AGGREGATOR       = "IntegerAggregator";
+    private String P_DOUBLE_AGGREGATOR        = "DoubleAggregator";
+    private String P_DATE_AGGREGATOR          = "DateAggregator";
+
+    
+    private final String MATCHING                     = 
I18NPlug.getI18N("Matching");
+    private final String MATCHING_OPTIONS             = 
I18NPlug.getI18N("Matching-options");
+    
+    // Source layer
+    private final String SOURCE_LAYER                 = 
I18NPlug.getI18N("Source-layer");
+    private final String SOURCE_LAYER_TOOLTIP         = 
I18NPlug.getI18N("Source-layer-tooltip");
+    private final String SINGLE_SOURCE                = 
I18NPlug.getI18N("Single-source");
+    private final String SINGLE_SOURCE_TOOLTIP        = 
I18NPlug.getI18N("Single-source-tooltip");
+    
+    // Target layer
+    private final String TARGET_LAYER                 = 
I18NPlug.getI18N("Target-layer");
+    private final String TARGET_LAYER_TOOLTIP         = 
I18NPlug.getI18N("Target-layer-tooltip");
+    private final String SINGLE_TARGET                = 
I18NPlug.getI18N("Single-target");
+    private final String SINGLE_TARGET_TOOLTIP        = 
I18NPlug.getI18N("Single-target-tooltip");
+    
+    // Geometry matcher
+    private final String GEOMETRIC_OPTIONS            = 
I18NPlug.getI18N("Geometric-options");
+    private final String GEOMETRY_MATCHER             = 
I18NPlug.getI18N("Geometry-matcher");
+    private final String MAXIMUM_DISTANCE             = 
I18NPlug.getI18N("Maximum-distance");
+    private final String MINIMUM_OVERLAPPING          = 
I18NPlug.getI18N("Minimum-overlapping");
+    
+    // Output options
+    private final String OUTPUT_OPTIONS               = 
I18NPlug.getI18N("Output-options");
+    private final String COPY_MATCHING_FEATURES       = 
I18NPlug.getI18N("Copy-matching-features");
+    private final String COPY_NOT_MATCHING_FEATURES   = 
I18NPlug.getI18N("Copy-not-matching-features");
+    private final String DISPLAY_LINKS                = 
I18NPlug.getI18N("Display-links");
+    
+    // Attributes options
+    private final String ATTRIBUTE_OPTIONS            = 
I18NPlug.getI18N("Attribute-options");
+    private final String USE_ATTRIBUTES               = 
I18NPlug.getI18N("Use-attributes");
+    private final String SOURCE_LAYER_ATTRIBUTE       = 
I18NPlug.getI18N("Source-layer-attribute");
+    private final String SOURCE_ATT_PREPROCESSING     = 
I18NPlug.getI18N("Source-att-preprocessing");
+    private final String TARGET_LAYER_ATTRIBUTE       = 
I18NPlug.getI18N("Target-layer-attribute");
+    private final String TARGET_ATT_PREPROCESSING     = 
I18NPlug.getI18N("Target-att-preprocessing");
+    private final String ATTRIBUTE_MATCHER            = 
I18NPlug.getI18N("Attribute-matcher");
+    private final String MAXIMUM_STRING_DISTANCE      = 
I18NPlug.getI18N("Maximum-string-distance");
+    private final String MINIMUM_STRING_OVERLAPPING   = 
I18NPlug.getI18N("Minimum-string-overlapping");
+    
+    // Attribute transfer / aggregation
+    private final String TRANSFER_OPTIONS             = 
I18NPlug.getI18N("Transfer-options");
+    private final String TRANSFER_TO_REFERENCE_LAYER  = 
I18NPlug.getI18N("Transfer-to-reference-layer");
+    private final String TRANSFER_BEST_MATCH_ONLY     = 
I18NPlug.getI18N("Transfer-best-match-only");
+    
+    private final String STRING_AGGREGATION           = 
I18NPlug.getI18N("String-aggregation");
+    private final String INTEGER_AGGREGATION          = 
I18NPlug.getI18N("Integer-aggregation");
+    private final String DOUBLE_AGGREGATION           = 
I18NPlug.getI18N("Double-aggregation");
+    private final String DATE_AGGREGATION             = 
I18NPlug.getI18N("Date-aggregation");
+
+    // Processing and Error messages
+    private final String SEARCHING_MATCHES            = 
I18NPlug.getI18N("Searching-matches");
+    private final String MISSING_INPUT_LAYER          = 
I18NPlug.getI18N("Missing-input-layer");
+    private final String CHOOSE_MATCHER               = 
I18NPlug.getI18N("Choose-geometry-or-attribute-matcher");
+
+    // Parameters : source layer and cardinality
+    private String source_layer_name;
+    private boolean single_source = false;
+    // Parameters : target layer and cardinality
+    private String target_layer_name;
+    private boolean single_target = false;
+
+    // Parameters : geometry parameters
+    private GeometryMatcher geometry_matcher = 
CentroidDistanceMatcher.instance();
+    private double max_distance = geometry_matcher.getMaximumDistance();
+    private boolean set_max_distance = !Double.isNaN(max_distance);
+    private double min_overlapping = geometry_matcher.getMinimumOverlapping();
+    private boolean set_min_overlapping = !Double.isNaN(min_overlapping);
+
+    // Parameters : output options
+    private boolean copy_matching_features = true;
+    private boolean copy_not_matching_features;
+    private boolean display_links = false;
+
+    // Parameters : attribute parameters
+    private boolean use_attributes = false;
+    private String source_att_preprocessing = "";
+    private String source_layer_attribute;
+    private String target_att_preprocessing = "";
+    private String target_layer_attribute;
+    private StringMatcher attribute_matcher = 
+        StringEqualityIgnoreCaseAndAccentMatcher.instance();
+    private double max_string_distance = 
attribute_matcher.getMaximumDistance();
+    private boolean has_max_string_distance = 
!Double.isNaN(max_string_distance);
+    private double min_string_overlapping = 
attribute_matcher.getMinimumOverlapping();
+    private boolean has_min_string_overlapping = 
!Double.isNaN(min_string_overlapping);
+
+    // Parameters : transfer and aggregation
+    private boolean transfer = true;
+    private boolean transfer_best_match_only = false;
+    // Ignore null by default
+    //private boolean ignore_null = true;
+    private Aggregator string_aggregator =
+            Aggregators.getAggregator(new 
Aggregators.ConcatenateUnique(true).getName());
+    private Aggregator integer_aggregator =
+            Aggregators.getAggregator(new Aggregators.IntSum().getName());
+    private Aggregator double_aggregator =
+            Aggregators.getAggregator(new 
Aggregators.DoubleMean(true).getName());
+    private Aggregator date_aggregator =
+            Aggregators.getAggregator(new 
Aggregators.DateMean(true).getName());
+
+    // initialisation of parameters
+    {
+        addParameter(P_SRC_LAYER, source_layer_name);
+        addParameter(P_SINGLE_SRC, single_source);
+        addParameter(P_TGT_LAYER, target_layer_name);
+        addParameter(P_SINGLE_TGT, single_target);
+        addParameter(P_GEOMETRY_MATCHER, 
geometry_matcher.getClass().getSimpleName());
+        addParameter(P_MAX_GEOM_DISTANCE, max_distance);
+        addParameter(P_MIN_GEOM_OVERLAP, min_overlapping);
+        addParameter(P_COPY_MATCHING, copy_matching_features);
+        addParameter(P_COPY_NOT_MATCHING, copy_not_matching_features);
+        addParameter(P_DISPLAY_LINKS, display_links);
+
+        addParameter(P_USE_ATTRIBUTES, use_attributes);
+        addParameter(P_SRC_ATTRIBUTE, source_layer_attribute);
+        addParameter(P_SRC_ATTRIBUTE_PREPROCESS, source_att_preprocessing);
+        addParameter(P_TGT_ATTRIBUTE, target_layer_attribute);
+        addParameter(P_TGT_ATTRIBUTE_PREPROCESS, target_att_preprocessing);
+        addParameter(P_ATTRIBUTE_MATCHER, 
attribute_matcher.getClass().getSimpleName());
+        addParameter(P_MAX_STRING_DISTANCE, max_string_distance);
+        addParameter(P_HAS_MAX_STRING_DISTANCE, has_max_string_distance);
+        addParameter(P_MIN_STRING_OVERLAP, min_string_overlapping);
+        addParameter(P_HAS_MIN_STRING_OVERLAP, has_min_string_overlapping);
+
+        addParameter(P_TRANSFER_ATTRIBUTES, transfer);
+        addParameter(P_TRANSFER_BEST_MATCH_ONLY, transfer_best_match_only);
+        addParameter(P_STRING_AGGREGATOR, string_aggregator.getName());
+        addParameter(P_INTEGER_AGGREGATOR, integer_aggregator.getName());
+        addParameter(P_DOUBLE_AGGREGATOR, double_aggregator.getName());
+        addParameter(P_DATE_AGGREGATOR, date_aggregator.getName());
+    }
+
+    public MatchingPlugIn() {
+    }
+    
+    public String getName() {
+        return MATCHING;
+    }
+
+    public void initialize(PlugInContext context) throws Exception {
+        
+        context.getFeatureInstaller().addMainMenuPlugin(
+          this, new String[]{MenuNames.PLUGINS, MATCHING},
+          MATCHING + "...",
+          false, null, new MultiEnableCheck()
+          .add(context.getCheckFactory().createTaskWindowMustBeActiveCheck())
+          
.add(context.getCheckFactory().createAtLeastNLayersMustExistCheck(1)));
+    }
+
+    /**
+     * Execute method initialize the plugin interface and get all the
+     * parameters from the user.
+     */
+    public boolean execute(PlugInContext context) throws Exception {
+        
+        try {
+            RuleRegistry.loadRules(
+                
context.getWorkbenchContext().getWorkbench().getPlugInManager().getPlugInDirectory().getPath()
 + "\\Rules"
+            );
+        } catch (IllegalArgumentException iae) {
+            Logger.warn(iae.getMessage());
+            
context.getWorkbenchFrame().warnUser(I18NPlug.getMessage("Missing-directory",
+                new String[]{
+                    context.getWorkbenchContext().getWorkbench()
+                    .getPlugInManager().getPlugInDirectory().getName() + 
+                    "/Rules"
+                }
+            ));
+        }
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CREATE MULTITAB INPUT DIALOG
+        
////////////////////////////////////////////////////////////////////////
+                
+        final MultiTabInputDialog dialog = new MultiTabInputDialog(
+            context.getWorkbenchFrame(), MATCHING_OPTIONS, GEOMETRIC_OPTIONS, 
true);
+        initDialog(dialog, context);
+
+        GUIUtil.centreOnWindow(dialog);
+        dialog.setVisible(true);
+
+        if (dialog.wasOKPressed()) {
+
+            // Get source layer parameters
+            Layer source_layer = dialog.getLayer(SOURCE_LAYER);
+            source_layer_name  = source_layer.getName();
+            single_source      = dialog.getBoolean(SINGLE_SOURCE);
+
+            // Get target layer parameters
+            Layer target_layer = dialog.getLayer(TARGET_LAYER);
+            target_layer_name  = target_layer.getName();
+            single_target      = dialog.getBoolean(SINGLE_TARGET);
+
+            // Get geometry matcher and set its parameters
+            geometry_matcher   = 
(GeometryMatcher)dialog.getValue(GEOMETRY_MATCHER);
+            max_distance       = dialog.getDouble(MAXIMUM_DISTANCE);
+            min_overlapping    = dialog.getDouble(MINIMUM_OVERLAPPING);
+            geometry_matcher.setMaximumDistance(max_distance);
+            geometry_matcher.setMinimumOverlapping(min_overlapping);
+            
+            // Get output options
+            copy_matching_features       = 
dialog.getBoolean(COPY_MATCHING_FEATURES);
+            copy_not_matching_features   = 
dialog.getBoolean(COPY_NOT_MATCHING_FEATURES);
+            display_links                = dialog.getBoolean(DISPLAY_LINKS);
+            
+            // get attribute options
+            use_attributes               = dialog.getBoolean(USE_ATTRIBUTES);
+            source_layer_attribute       = 
dialog.getText(SOURCE_LAYER_ATTRIBUTE);
+            source_att_preprocessing     = 
dialog.getText(SOURCE_ATT_PREPROCESSING);
+            target_layer_attribute       = 
dialog.getText(TARGET_LAYER_ATTRIBUTE);
+            target_att_preprocessing     = 
dialog.getText(TARGET_ATT_PREPROCESSING);
+            attribute_matcher            = 
(StringMatcher)dialog.getValue(ATTRIBUTE_MATCHER);
+            max_string_distance          = 
dialog.getDouble(MAXIMUM_STRING_DISTANCE);
+            min_string_overlapping       = 
dialog.getDouble(MINIMUM_STRING_OVERLAPPING);
+            if (!use_attributes) attribute_matcher = 
MatchAllStringsMatcher.MATCH_ALL;
+            else attribute_matcher.setAttributes(source_layer_attribute, 
+                                                 target_layer_attribute);
+            attribute_matcher.setMaximumDistance(max_string_distance);
+            attribute_matcher.setMinimumOverlapping(min_string_overlapping);
+            
attribute_matcher.setSourceRule(RuleRegistry.getRule(source_att_preprocessing));
+            
attribute_matcher.setTargetRule(RuleRegistry.getRule(target_att_preprocessing));
+            
+            // get transfer options
+            transfer                 = 
dialog.getBoolean(TRANSFER_TO_REFERENCE_LAYER);
+            transfer_best_match_only = 
dialog.getBoolean(TRANSFER_BEST_MATCH_ONLY);
+            string_aggregator        = 
(Aggregator)dialog.getComboBox(STRING_AGGREGATION).getSelectedItem();
+            integer_aggregator       = 
(Aggregator)dialog.getComboBox(INTEGER_AGGREGATION).getSelectedItem();
+            double_aggregator        = 
(Aggregator)dialog.getComboBox(DOUBLE_AGGREGATION).getSelectedItem();
+            date_aggregator          = 
(Aggregator)dialog.getComboBox(DATE_AGGREGATION).getSelectedItem();
+
+            System.out.println("start adding parameters");
+
+            addParameter(P_SRC_LAYER, source_layer_name);
+            addParameter(P_SINGLE_SRC, single_source);
+            addParameter(P_TGT_LAYER, target_layer_name);
+            addParameter(P_SINGLE_TGT, single_target);
+            addParameter(P_GEOMETRY_MATCHER, 
geometry_matcher.getClass().getSimpleName());
+            addParameter(P_MAX_GEOM_DISTANCE, max_distance);
+            addParameter(P_MIN_GEOM_OVERLAP, min_overlapping);
+            addParameter(P_COPY_MATCHING, copy_matching_features);
+            addParameter(P_COPY_NOT_MATCHING, copy_not_matching_features);
+            addParameter(P_DISPLAY_LINKS, display_links);
+
+            addParameter(P_USE_ATTRIBUTES, use_attributes);
+            addParameter(P_SRC_ATTRIBUTE, source_layer_attribute);
+            addParameter(P_SRC_ATTRIBUTE_PREPROCESS, source_att_preprocessing);
+            addParameter(P_TGT_ATTRIBUTE, target_layer_attribute);
+            addParameter(P_TGT_ATTRIBUTE_PREPROCESS, target_att_preprocessing);
+            addParameter(P_ATTRIBUTE_MATCHER, 
attribute_matcher.getClass().getSimpleName());
+            addParameter(P_MAX_STRING_DISTANCE, max_string_distance);
+            addParameter(P_HAS_MAX_STRING_DISTANCE, has_max_string_distance);
+            addParameter(P_MIN_STRING_OVERLAP, min_string_overlapping);
+            addParameter(P_HAS_MIN_STRING_OVERLAP, has_min_string_overlapping);
+
+            addParameter(P_TRANSFER_ATTRIBUTES, transfer);
+            addParameter(P_TRANSFER_BEST_MATCH_ONLY, transfer_best_match_only);
+            addParameter(P_STRING_AGGREGATOR, string_aggregator.getName());
+            addParameter(P_INTEGER_AGGREGATOR, integer_aggregator.getName());
+            addParameter(P_DOUBLE_AGGREGATOR, double_aggregator.getName());
+            addParameter(P_DATE_AGGREGATOR, date_aggregator.getName());
+
+            if ((geometry_matcher instanceof MatchAllMatcher) && 
!use_attributes) {
+                context.getWorkbenchFrame().warnUser(CHOOSE_MATCHER);
+                return false;
+            }
+            return true;
+        }
+        else return false;
+        
+    }
+    
+    private void initDialog(final MultiTabInputDialog dialog, final 
PlugInContext context) {
+        
+        
////////////////////////////////////////////////////////////////////////
+        // UI : INITIALIZE LAYERS FROM LAST ONES OR FROM CONTEXT
+        
////////////////////////////////////////////////////////////////////////
+
+        Layer source_layer;
+        Layer target_layer;
+        source_layer = context.getLayerManager().getLayer(source_layer_name);
+        if (source_layer == null) source_layer = context.getCandidateLayer(0);
+        
+        target_layer = context.getLayerManager().getLayer(target_layer_name);
+        int layerNumber = context.getLayerManager().getLayers().size();
+        if (target_layer == null) target_layer = 
context.getCandidateLayer(layerNumber>1?1:0);
+        
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CHOOSE SOURCE LAYER AND SOURCE CARDINALITY
+        
////////////////////////////////////////////////////////////////////////
+
+        dialog.addLabel("<html><b>"+GEOMETRIC_OPTIONS+"</b></html>");
+        
+        final JComboBox jcb_layer = dialog.addLayerComboBox(SOURCE_LAYER, 
+            source_layer, SOURCE_LAYER_TOOLTIP, context.getLayerManager());
+        jcb_layer.setPreferredSize(new Dimension(220,20));
+        jcb_layer.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+        final JCheckBox singleSourceFeatureCheckBox = dialog.addCheckBox(
+            SINGLE_SOURCE, single_source, SINGLE_SOURCE_TOOLTIP);
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CHOOSE GEOMETRY MATCHER
+        
////////////////////////////////////////////////////////////////////////
+        Collection<GeometryMatcher> geomMatcherList =
+                MatcherRegistry.GEOMETRY_MATCHERS.getMap().values();
+        final JComboBox<GeometryMatcher> jcb_geom_operation =
+                dialog.addComboBox(GEOMETRY_MATCHER, geometry_matcher, 
geomMatcherList, null);
+
+        final JTextField jtf_dist = dialog.addDoubleField(MAXIMUM_DISTANCE, 
max_distance, 12, null);
+        jtf_dist.setEnabled(set_max_distance);
+        
+        final JTextField jtf_overlap = 
dialog.addDoubleField(MINIMUM_OVERLAPPING, min_overlapping, 12, null);
+        jtf_overlap.setEnabled(set_min_overlapping);
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CHOOSE TARGET LAYER AND SOURCE CARDINALITY
+        
////////////////////////////////////////////////////////////////////////
+        
+        final JComboBox jcb_layer_tgt = dialog.addLayerComboBox(TARGET_LAYER, 
+            target_layer, TARGET_LAYER_TOOLTIP, context.getLayerManager());
+        jcb_layer_tgt.setPreferredSize(new Dimension(220,20));
+        jcb_layer_tgt.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+        final JCheckBox singleTargetFeatureCheckBox = dialog.addCheckBox(
+            SINGLE_TARGET, single_target, SINGLE_TARGET_TOOLTIP);
+
+        jcb_geom_operation.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                updateDialog(dialog);
+                geometry_matcher = 
(GeometryMatcher)jcb_geom_operation.getSelectedItem();
+                jtf_dist.setText(""+geometry_matcher.getMaximumDistance());
+                
jtf_overlap.setText(""+geometry_matcher.getMinimumOverlapping());
+            }
+        });
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CHOOSE OUTPUT OPTIONS
+        
////////////////////////////////////////////////////////////////////////
+        dialog.addSeparator();
+        dialog.addLabel("<html><b>"+OUTPUT_OPTIONS+"</b></html>");
+
+        final JCheckBox jcb_new_layer_match = 
dialog.addCheckBox(COPY_MATCHING_FEATURES, copy_matching_features, null);
+        final JCheckBox jcb_new_layer_diff  = 
dialog.addCheckBox(COPY_NOT_MATCHING_FEATURES, copy_not_matching_features, 
null);
+        final JCheckBox jcb_display_links   = 
dialog.addCheckBox(DISPLAY_LINKS, display_links, null);
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : CHOOSE ATTRIBUTE OPTIONS
+        
////////////////////////////////////////////////////////////////////////
+        dialog.addPane(ATTRIBUTE_OPTIONS);
+
+        final JCheckBox jcb_use_attributes = 
dialog.addCheckBox(USE_ATTRIBUTES, use_attributes, null);
+        
+        final JComboBox jcb_src_att_preprocessing = 
dialog.addComboBox(SOURCE_ATT_PREPROCESSING,
+            source_att_preprocessing, Arrays.asList(RuleRegistry.getRules()), 
null);
+        final JComboBox jcb_src_attribute = dialog.addAttributeComboBox(
+            SOURCE_LAYER_ATTRIBUTE, SOURCE_LAYER, 
AttributeTypeFilter.STRING_FILTER, null);
+        jcb_src_attribute.setSelectedItem(source_layer_attribute);
+        jcb_src_attribute.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (jcb_src_attribute.getSelectedItem() != null) {
+                    source_layer_attribute = 
jcb_src_attribute.getSelectedItem().toString();
+                }
+            }
+        });
+        
+        
+        // Initialize string matching options
+        Collection<StringMatcher> stringMatcherList =
+                MatcherRegistry.STRING_MATCHERS.getMap().values();
+        final JComboBox<StringMatcher> jcb_attr_operation = dialog.addComboBox(
+            ATTRIBUTE_MATCHER, attribute_matcher, stringMatcherList, null);
+        
+        final JTextField jtf_string_dist = 
dialog.addDoubleField(MAXIMUM_STRING_DISTANCE, max_string_distance, 12, null);
+        jtf_string_dist.setEnabled(has_max_string_distance);
+        
+        final JTextField jtf_string_overlap = 
dialog.addDoubleField(MINIMUM_STRING_OVERLAPPING, min_string_overlapping, 12, 
null);
+        jtf_string_overlap.setEnabled(has_min_string_overlapping);
+        
+        final JComboBox jcb_tgt_att_preprocessing = 
dialog.addComboBox(TARGET_ATT_PREPROCESSING,
+            target_att_preprocessing, Arrays.asList(RuleRegistry.getRules()), 
null);
+        final JComboBox jcb_tgt_attribute = dialog.addAttributeComboBox(
+            TARGET_LAYER_ATTRIBUTE, TARGET_LAYER, 
AttributeTypeFilter.STRING_FILTER, null);
+        jcb_tgt_attribute.setSelectedItem(target_layer_attribute);
+        jcb_tgt_attribute.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (jcb_tgt_attribute.getSelectedItem() != null) {
+                    target_layer_attribute = 
jcb_tgt_attribute.getSelectedItem().toString();
+                }
+            }
+        });
+        
+        jcb_attr_operation.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+        jcb_use_attributes.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+
+        
////////////////////////////////////////////////////////////////////////
+        // UI : TRANSFER ATTRIBUTE / AGGREGATION
+        
////////////////////////////////////////////////////////////////////////
+        dialog.addSeparator();
+        dialog.addPane(TRANSFER_OPTIONS);
+        dialog.addSubTitle(TRANSFER_OPTIONS);
+        
+        final JCheckBox jcb_transfer = 
dialog.addCheckBox(TRANSFER_TO_REFERENCE_LAYER, transfer, null);
+        jcb_transfer.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+        final JCheckBox jcb_transfer_best_match_only = 
dialog.addCheckBox(TRANSFER_BEST_MATCH_ONLY, transfer_best_match_only, null);
+        jcb_transfer_best_match_only.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {updateDialog(dialog);}
+        });
+        
+        final JComboBox jcb_string_aggregator = dialog.addComboBox(
+                STRING_AGGREGATION, string_aggregator,
+                Aggregators.getAggregators(AttributeType.STRING).values(), null
+        );
+        final JComboBox jcb_integer_aggregator = dialog.addComboBox(
+                INTEGER_AGGREGATION, integer_aggregator,
+                Aggregators.getAggregators(AttributeType.INTEGER).values(), 
null
+        );
+        final JComboBox jcb_double_aggregator = dialog.addComboBox(
+                DOUBLE_AGGREGATION, double_aggregator,
+                Aggregators.getAggregators(AttributeType.DOUBLE).values(), null
+        );
+        final JComboBox jcb_date_aggregator = dialog.addComboBox(
+                DATE_AGGREGATION, date_aggregator,
+                Aggregators.getAggregators(AttributeType.DATE).values(), null
+        );
+        
+        updateDialog(dialog);
+    }
+    
+    
+    // update dialog is called by several component listeners to update the 
+    // dialog before the final validation
+    // 2012-06-29 : component values are stored in local variables as this is 
+    // not necessary to change the plugin parameters until final validation
+    private void updateDialog(MultiTabInputDialog dialog) {
+
+        // Updates related to a geometry_matcher change
+        geometry_matcher = (GeometryMatcher)dialog.getValue(GEOMETRY_MATCHER);
+        
geometry_matcher.setMaximumDistance(dialog.getDouble(MAXIMUM_DISTANCE));
+        
geometry_matcher.setMinimumOverlapping(dialog.getDouble(MINIMUM_OVERLAPPING));
+        dialog.setFieldEnabled(MAXIMUM_DISTANCE, 
!Double.isNaN(geometry_matcher.getMaximumDistance()));
+        dialog.setFieldEnabled(MINIMUM_OVERLAPPING, 
!Double.isNaN(geometry_matcher.getMinimumOverlapping()));
+        //String sMatcher = dialog.getText(GEOMETRY_MATCHER);
+        //Matcher _matcher = MatcherRegistry.GEOMETRY_MATCHERS.get(sMatcher);
+        //double dmax = _matcher.getMaximumDistance();
+        //double omin = _matcher.getMinimumOverlapping();
+        //boolean _set_max_distance = !Double.isNaN(dmax);
+        //boolean _set_min_overlapping = !Double.isNaN(omin);
+        //dialog.setFieldEnabled(MAXIMUM_DISTANCE, _set_max_distance);
+        //dialog.setFieldEnabled(MINIMUM_OVERLAPPING, _set_min_overlapping);
+
+
+        // Updates related to a layer change
+        Layer srcLayer      = dialog.getLayer(SOURCE_LAYER);
+        Layer tgtLayer      = dialog.getLayer(TARGET_LAYER);
+        boolean srcLayer_has_attributes = 
+            
srcLayer.getFeatureCollectionWrapper().getFeatureSchema().getAttributeCount() > 
1;
+        boolean srcLayer_has_string_attributes = 
+            
AttributeTypeFilter.STRING_FILTER.filter(srcLayer.getFeatureCollectionWrapper().getFeatureSchema()).size()
 > 0;
+        boolean tgtLayer_has_string_attributes = 
+            
AttributeTypeFilter.STRING_FILTER.filter(tgtLayer.getFeatureCollectionWrapper().getFeatureSchema()).size()
 > 0;
+        dialog.setFieldEnabled(USE_ATTRIBUTES, srcLayer_has_string_attributes 
&& 
+                                               tgtLayer_has_string_attributes);
+        dialog.setTabEnabled(ATTRIBUTE_OPTIONS, srcLayer_has_string_attributes 
&& 
+                                                
tgtLayer_has_string_attributes);
+        if (!srcLayer_has_string_attributes || 
!tgtLayer_has_string_attributes) {
+            attribute_matcher = MatchAllStringsMatcher.MATCH_ALL;
+            //dialog.getCheckBox(USE_ATTRIBUTES).setSelected(false);
+        } else {
+            attribute_matcher = 
(StringMatcher)dialog.getValue(ATTRIBUTE_MATCHER);
+        }
+        
//dialog.getComboBox(SOURCE_LAYER_ATTRIBUTE).setSelectedItem(source_layer_attribute);
+        
//dialog.getComboBox(TARGET_LAYER_ATTRIBUTE).setSelectedItem(target_layer_attribute);
+        
+        // Updates related to attribute transfer
+        dialog.setTabEnabled(TRANSFER_OPTIONS, srcLayer_has_attributes);
+
+        boolean _transfer = dialog.getBoolean(TRANSFER_TO_REFERENCE_LAYER);
+        dialog.setFieldEnabled(TRANSFER_BEST_MATCH_ONLY, _transfer);
+
+        boolean _transfer_best_match_only = 
dialog.getBoolean(TRANSFER_BEST_MATCH_ONLY);
+        dialog.setFieldEnabled(STRING_AGGREGATION, _transfer && 
!_transfer_best_match_only);
+        dialog.setFieldEnabled(INTEGER_AGGREGATION, _transfer && 
!_transfer_best_match_only);
+        dialog.setFieldEnabled(DOUBLE_AGGREGATION, _transfer && 
!_transfer_best_match_only);
+        dialog.setFieldEnabled(DATE_AGGREGATION, _transfer && 
!_transfer_best_match_only);
+        
+        // Updates related to attribute matching
+        boolean _use_attributes = dialog.getBoolean(USE_ATTRIBUTES);
+        dialog.setFieldEnabled(SOURCE_LAYER_ATTRIBUTE, _use_attributes);
+        dialog.setFieldEnabled(SOURCE_ATT_PREPROCESSING, _use_attributes);
+        dialog.setFieldEnabled(TARGET_LAYER_ATTRIBUTE, _use_attributes);
+        dialog.setFieldEnabled(TARGET_ATT_PREPROCESSING, _use_attributes);
+
+        dialog.setFieldEnabled(ATTRIBUTE_MATCHER, _use_attributes);
+        //attribute_matcher = 
(StringMatcher)dialog.getValue(ATTRIBUTE_MATCHER);
+        //String aMatcher = dialog.getText(ATTRIBUTE_MATCHER);
+        //StringMatcher _attribute_matcher = 
(StringMatcher)MatcherRegistry.STRING_MATCHERS.get(aMatcher);
+
+        dialog.setFieldEnabled(MAXIMUM_STRING_DISTANCE, _use_attributes && 
+            (attribute_matcher instanceof LevenshteinDistanceMatcher ||
+                    attribute_matcher instanceof 
DamarauLevenshteinDistanceMatcher));
+
+    }
+    
+    /**
+     * Run executes the main process, looping through matching layer, and
+     * looking for candidates in the reference layer.
+     */
+    public void run(TaskMonitor monitor, PlugInContext context) throws 
Exception {
+
+        // layers and cardinality constraints
+        source_layer_name          = getStringParam(P_SRC_LAYER);
+        single_source              = getBooleanParam(P_SINGLE_SRC);
+        target_layer_name          = getStringParam(P_TGT_LAYER);
+        single_target              = getBooleanParam(P_SINGLE_TGT);
+
+        // geometry matcher
+        geometry_matcher           = MatcherRegistry.GEOMETRY_MATCHERS
+                .get(getStringParam(P_GEOMETRY_MATCHER));
+        if (geometry_matcher == null) {
+            throw new Exception("GeometryMatcher '" + 
getStringParam(P_GEOMETRY_MATCHER) + "' has not been found");
+        }
+        max_distance               = getDoubleParam(P_MAX_GEOM_DISTANCE);
+        min_overlapping            = getDoubleParam(P_MIN_GEOM_OVERLAP);
+        copy_matching_features     = getBooleanParam(P_COPY_MATCHING);
+        copy_not_matching_features = getBooleanParam(P_COPY_NOT_MATCHING);
+        display_links              = getBooleanParam(P_DISPLAY_LINKS);
+        // derived
+        geometry_matcher.setMaximumDistance(max_distance);
+        geometry_matcher.setMinimumOverlapping(min_overlapping);
+
+        // attribute matcher
+        use_attributes           = getBooleanParam(P_USE_ATTRIBUTES);
+        source_layer_attribute   = getStringParam(P_SRC_ATTRIBUTE);
+        source_att_preprocessing = getStringParam(P_SRC_ATTRIBUTE_PREPROCESS);
+        target_layer_attribute   = getStringParam(P_TGT_ATTRIBUTE);
+        target_att_preprocessing = getStringParam(P_TGT_ATTRIBUTE_PREPROCESS);
+        attribute_matcher        = MatcherRegistry.STRING_MATCHERS
+                .get(getStringParam(P_ATTRIBUTE_MATCHER));
+        max_string_distance      = getDoubleParam(P_MAX_STRING_DISTANCE);
+        has_max_string_distance  = getBooleanParam(P_HAS_MAX_STRING_DISTANCE);
+        min_string_overlapping   = getDoubleParam(P_MIN_STRING_OVERLAP);
+        has_min_string_overlapping = getBooleanParam(P_HAS_MIN_STRING_OVERLAP);
+        // derived
+        if (!use_attributes) attribute_matcher = 
MatchAllStringsMatcher.MATCH_ALL;
+        else {
+            if (attribute_matcher == null) {
+                throw new Exception("Attribute Matcher '" + 
getStringParam(P_ATTRIBUTE_MATCHER) + "' has not been found");
+            }
+            attribute_matcher.setAttributes(source_layer_attribute, 
target_layer_attribute);
+            attribute_matcher.setMaximumDistance(max_string_distance);
+            attribute_matcher.setMinimumOverlapping(min_string_overlapping);
+        }
+
+        // transfer options
+        transfer                 = getBooleanParam(P_TRANSFER_ATTRIBUTES);
+        transfer_best_match_only = getBooleanParam(P_TRANSFER_BEST_MATCH_ONLY);
+        string_aggregator        = 
Aggregators.getAggregator(getStringParam(P_STRING_AGGREGATOR));
+        string_aggregator.setIgnoreNull(true);
+        integer_aggregator       = 
Aggregators.getAggregator(getStringParam(P_INTEGER_AGGREGATOR));
+        integer_aggregator.setIgnoreNull(true);
+        double_aggregator        = 
Aggregators.getAggregator(getStringParam(P_DOUBLE_AGGREGATOR));
+        double_aggregator.setIgnoreNull(true);
+        date_aggregator          = 
Aggregators.getAggregator(getStringParam(P_DATE_AGGREGATOR));
+        date_aggregator.setIgnoreNull(true);
+
+        
+        Layer source_layer = 
context.getLayerManager().getLayer(source_layer_name);

@@ Diff output truncated at 100000 characters. @@

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Jump-pilot-devel mailing list
Jump-pilot-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel

Reply via email to