[josm-plugins] 89/369: Imported Upstream version 0.0.svn18009

Bas Couwenberg sebastic at xs4all.nl
Sat Oct 18 12:03:29 UTC 2014


This is an automated email from the git hooks/post-receive script.

sebastic-guest pushed a commit to branch master
in repository josm-plugins.

commit 556f400c411d7d81e3c151e74e4e651752218172
Author: David Paleino <dapal at debian.org>
Date:   Mon Nov 9 17:34:10 2009 +0100

    Imported Upstream version 0.0.svn18009
---
 agpifoj/.classpath                                 |    7 +
 agpifoj/.project                                   |   17 +
 agpifoj/CHANGELOG                                  |    1 +
 agpifoj/LICENSE                                    |  341 ++++++
 agpifoj/README                                     |   57 +
 agpifoj/build.xml                                  |   64 ++
 agpifoj/images/agpifoj-open.png                    |  Bin 0 -> 938 bytes
 agpifoj/images/dialogs/agpifoj-mag-cursor.png      |  Bin 0 -> 394 bytes
 agpifoj/images/dialogs/agpifoj-marker-black.png    |  Bin 0 -> 239 bytes
 agpifoj/images/dialogs/agpifoj-marker-selected.png |  Bin 0 -> 231 bytes
 agpifoj/images/dialogs/agpifoj-marker.png          |  Bin 0 -> 233 bytes
 agpifoj/images/dialogs/agpifoj.png                 |  Bin 0 -> 500 bytes
 agpifoj/images/dialogs/gpx2img.png                 |  Bin 0 -> 663 bytes
 agpifoj/images/dialogs/gpx2imgManual.png           |  Bin 0 -> 924 bytes
 .../josm/plugins/agpifoj/AgpifojDialog.java        |  185 +++
 .../josm/plugins/agpifoj/AgpifojLayer.java         |  530 +++++++++
 .../josm/plugins/agpifoj/AgpifojPlugin.java        |  100 ++
 .../plugins/agpifoj/CorrelateGpxWithImages.java    | 1177 ++++++++++++++++++++
 .../josm/plugins/agpifoj/ImageDisplay.java         |  607 ++++++++++
 colorscheme/LICENSE                                |  339 ++++++
 colorscheme/build.xml                              |   57 +
 colorscheme/copyright.txt                          |    6 +
 .../josm/plugin/colorscheme/ColorSchemePlugin.java |   31 +
 .../plugin/colorscheme/ColorSchemePreference.java  |  190 ++++
 livegps/.classpath                                 |    7 +
 livegps/.project                                   |   17 +
 livegps/README                                     |   14 +
 livegps/build.xml                                  |   57 +
 livegps/images/autocentermenu.png                  |  Bin 0 -> 577 bytes
 livegps/images/capturemenu.png                     |  Bin 0 -> 743 bytes
 livegps/images/centermenu.png                      |  Bin 0 -> 745 bytes
 livegps/images/dialogs/livegps.png                 |  Bin 0 -> 2778 bytes
 livegps/src/livegps/LiveGpsAcquirer.java           |  226 ++++
 livegps/src/livegps/LiveGpsData.java               |  207 ++++
 livegps/src/livegps/LiveGpsDialog.java             |  108 ++
 livegps/src/livegps/LiveGpsLayer.java              |  157 +++
 livegps/src/livegps/LiveGpsLock.java               |   16 +
 livegps/src/livegps/LiveGpsPlugin.java             |  221 ++++
 livegps/src/livegps/LiveGpsStatus.java             |   49 +
 measurement/.classpath                             |    7 +
 measurement/.project                               |   17 +
 measurement/LICENSE                                |    7 +
 measurement/build.xml                              |   55 +
 measurement/images/dialogs/measure.png             |  Bin 0 -> 757 bytes
 measurement/images/mapmode/measurement.png         |  Bin 0 -> 664 bytes
 measurement/images/measurement.png                 |  Bin 0 -> 333 bytes
 .../plugins/measurement/MeasurementDialog.java     |  167 +++
 .../josm/plugins/measurement/MeasurementLayer.java |  333 ++++++
 .../josm/plugins/measurement/MeasurementMode.java  |   54 +
 .../plugins/measurement/MeasurementPlugin.java     |   51 +
 openvisible/.classpath                             |    7 +
 openvisible/.project                               |   17 +
 openvisible/LICENSE                                |  339 ++++++
 openvisible/build.xml                              |   57 +
 openvisible/copyright.txt                          |    6 +
 openvisible/images/openvisible.png                 |  Bin 0 -> 784 bytes
 .../josm/plugin/openvisible/OpenVisibleAction.java |  135 +++
 .../josm/plugin/openvisible/OpenVisiblePlugin.java |   21 +
 .../josm/plugin/openvisible/OsmGpxBounds.java      |  121 ++
 slippymap/.classpath                               |    7 +
 slippymap/.project                                 |   17 +
 slippymap/README                                   |   16 +
 slippymap/build.xml                                |   56 +
 slippymap/images/preferences/slippymap.png         |  Bin 0 -> 3748 bytes
 slippymap/images/slippymap.png                     |  Bin 0 -> 847 bytes
 .../josm/plugins/slippymap/SlippyMapKey.java       |   78 ++
 .../josm/plugins/slippymap/SlippyMapLayer.java     |  915 +++++++++++++++
 .../josm/plugins/slippymap/SlippyMapPlugin.java    |   40 +
 .../slippymap/SlippyMapPreferenceSetting.java      |  105 ++
 .../plugins/slippymap/SlippyMapPreferences.java    |  219 ++++
 .../josm/plugins/slippymap/SlippyMapTile.java      |  188 ++++
 surveyor/.classpath                                |    8 +
 surveyor/.project                                  |   17 +
 surveyor/LICENSE                                   |  339 ++++++
 surveyor/build.xml                                 |   69 ++
 surveyor/changelog.txt                             |    7 +
 surveyor/copyright.txt                             |    6 +
 surveyor/images/addrelation.png                    |  Bin 0 -> 1196 bytes
 surveyor/images/autosave.png                       |  Bin 0 -> 1058 bytes
 surveyor/images/surveyormenu.png                   |  Bin 0 -> 1305 bytes
 surveyor/resources/audio/KDE_Window_Iconify.wav    |  Bin 0 -> 26134 bytes
 surveyor/resources/surveyor.xml                    |  102 ++
 .../josm/plugin/surveyor/ActionConstants.java      |   23 +
 .../josm/plugin/surveyor/AutoSaveAction.java       |   72 ++
 .../surveyor/AutoSaveEditLayerTimerTask.java       |   75 ++
 .../plugin/surveyor/AutoSaveGpsLayerTimerTask.java |   90 ++
 .../josm/plugin/surveyor/ButtonDescription.java    |  254 +++++
 .../josm/plugin/surveyor/ButtonType.java           |   14 +
 .../josm/plugin/surveyor/GpsActionEvent.java       |   40 +
 .../josm/plugin/surveyor/GpsDataSource.java        |   20 +
 .../josm/plugin/surveyor/MetaAction.java           |  124 +++
 .../josm/plugin/surveyor/SurveyorAction.java       |   26 +
 .../plugin/surveyor/SurveyorActionDescription.java |  111 ++
 .../plugin/surveyor/SurveyorActionFactory.java     |   49 +
 .../josm/plugin/surveyor/SurveyorComponent.java    |  191 ++++
 .../josm/plugin/surveyor/SurveyorPlugin.java       |   73 ++
 .../josm/plugin/surveyor/SurveyorShowAction.java   |  183 +++
 .../surveyor/action/AbstractSurveyorAction.java    |   34 +
 .../josm/plugin/surveyor/action/BeepAction.java    |   52 +
 .../surveyor/action/ConsolePrinterAction.java      |   26 +
 .../plugin/surveyor/action/PlayAudioAction.java    |   92 ++
 .../josm/plugin/surveyor/action/SetNodeAction.java |   79 ++
 .../plugin/surveyor/action/SetWaypointAction.java  |  134 +++
 .../surveyor/action/SystemExecuteAction.java       |   63 ++
 .../surveyor/action/TaggingPresetAction.java       |   83 ++
 .../surveyor/action/gui/DialogClosingThread.java   |  142 +++
 .../plugin/surveyor/action/gui/WaypointDialog.java |  102 ++
 .../josm/plugin/surveyor/util/LayerUtil.java       |   35 +
 .../josm/plugin/surveyor/util/ResourceLoader.java  |   43 +
 .../src/org/dinopolis/util/collection/Tuple.java   |  133 +++
 surveyor/src/org/dinopolis/util/io/Tokenizer.java  |  942 ++++++++++++++++
 svn-info.xml                                       |   18 +
 utilsplugin/.classpath                             |    7 +
 utilsplugin/.project                               |   17 +
 utilsplugin/README                                 |   14 +
 utilsplugin/build.xml                              |   55 +
 utilsplugin/data/Join Areas Tests.osm              |  767 +++++++++++++
 utilsplugin/images/joinareas.png                   |  Bin 0 -> 328 bytes
 utilsplugin/images/joinnodeway.png                 |  Bin 0 -> 452 bytes
 utilsplugin/images/mergenodes.png                  |  Bin 0 -> 314 bytes
 utilsplugin/images/simplify.png                    |  Bin 0 -> 968 bytes
 utilsplugin/src/UtilsPlugin/JoinAreasAction.java   |  885 +++++++++++++++
 utilsplugin/src/UtilsPlugin/JumpToAction.java      |  179 +++
 utilsplugin/src/UtilsPlugin/SimplifyWayAction.java |  205 ++++
 utilsplugin/src/UtilsPlugin/UtilsPlugin.java       |   29 +
 validator/.classpath                               |    8 +
 validator/.project                                 |   17 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 validator/.settings/org.eclipse.jdt.core.prefs     |  258 +++++
 validator/.settings/org.eclipse.jdt.ui.prefs       |    4 +
 validator/CONTRIBUTION                             |    5 +
 validator/LICENSE                                  |  341 ++++++
 validator/README                                   |    2 +
 validator/build.xml                                |   56 +
 validator/ignoretags.cfg                           |  362 ++++++
 validator/images/data/error.gif                    |  Bin 0 -> 333 bytes
 validator/images/data/other.gif                    |  Bin 0 -> 121 bytes
 validator/images/data/warning.gif                  |  Bin 0 -> 324 bytes
 validator/images/dialogs/fix.png                   |  Bin 0 -> 30287 bytes
 validator/images/dialogs/validator.png             |  Bin 0 -> 890 bytes
 validator/images/layer/validator.png               |  Bin 0 -> 765 bytes
 validator/images/preferences/validator.png         |  Bin 0 -> 1388 bytes
 validator/images/validator.png                     |  Bin 0 -> 889 bytes
 validator/josm-validator.launch                    |   12 +
 .../josm/plugins/validator/ErrorLayer.java         |  142 +++
 .../josm/plugins/validator/ErrorTreePanel.java     |  327 ++++++
 .../josm/plugins/validator/ErrorTreeRenderer.java  |   47 +
 .../josm/plugins/validator/GridLayer.java          |  202 ++++
 .../josm/plugins/validator/OSMValidatorPlugin.java |  308 +++++
 .../josm/plugins/validator/PreferenceEditor.java   |  131 +++
 .../josm/plugins/validator/Severity.java           |   67 ++
 .../openstreetmap/josm/plugins/validator/Test.java |  219 ++++
 .../josm/plugins/validator/TestError.java          |  400 +++++++
 .../josm/plugins/validator/ValidateAction.java     |  187 ++++
 .../josm/plugins/validator/ValidateUploadHook.java |  127 +++
 .../josm/plugins/validator/ValidatorDialog.java    |  521 +++++++++
 .../josm/plugins/validator/ValidatorVisitor.java   |   10 +
 .../validator/tests/ChangePropertyKeyCommand.java  |   86 ++
 .../josm/plugins/validator/tests/Coastlines.java   |   90 ++
 .../josm/plugins/validator/tests/CrossingWays.java |  220 ++++
 .../plugins/validator/tests/DuplicateNode.java     |  155 +++
 .../josm/plugins/validator/tests/DuplicateWay.java |  153 +++
 .../validator/tests/DuplicatedWayNodes.java        |   70 ++
 .../plugins/validator/tests/NodesWithSameName.java |   56 +
 .../plugins/validator/tests/OverlappingWays.java   |  173 +++
 .../validator/tests/SelfIntersectingWay.java       |   41 +
 .../plugins/validator/tests/SimilarNamedWays.java  |  173 +++
 .../josm/plugins/validator/tests/TagChecker.java   | 1025 +++++++++++++++++
 .../josm/plugins/validator/tests/UnclosedWays.java |  127 +++
 .../plugins/validator/tests/UnconnectedWays.java   |  231 ++++
 .../josm/plugins/validator/tests/UntaggedNode.java |  109 ++
 .../josm/plugins/validator/tests/UntaggedWay.java  |  157 +++
 .../validator/tests/WronglyOrderedWays.java        |  113 ++
 .../validator/util/AgregatePrimitivesVisitor.java  |   72 ++
 .../josm/plugins/validator/util/Bag.java           |   93 ++
 .../josm/plugins/validator/util/Entities.java      |  410 +++++++
 .../validator/util/MultipleNameVisitor.java        |  105 ++
 .../josm/plugins/validator/util/NameVisitor.java   |   83 ++
 .../josm/plugins/validator/util/Util.java          |  175 +++
 validator/tagchecker.cfg                           |   79 ++
 wmsplugin/.classpath                               |    7 +
 wmsplugin/.project                                 |   17 +
 wmsplugin/Makefile                                 |   16 +
 wmsplugin/README                                   |   19 +
 wmsplugin/build.xml                                |  139 +++
 wmsplugin/gnome-web-photo-fixed                    |    3 +
 wmsplugin/images/OLmarker.png                      |  Bin 0 -> 549 bytes
 wmsplugin/images/blankmenu.png                     |  Bin 0 -> 1529 bytes
 wmsplugin/images/load.png                          |  Bin 0 -> 883 bytes
 wmsplugin/images/mapmode/adjustwms.png             |  Bin 0 -> 1300 bytes
 wmsplugin/images/preferences/wms.png               |  Bin 0 -> 5703 bytes
 wmsplugin/images/save.png                          |  Bin 0 -> 845 bytes
 wmsplugin/images/wms.png                           |  Bin 0 -> 9996 bytes
 wmsplugin/images/wms_small.png                     |  Bin 0 -> 826 bytes
 wmsplugin/images/wmsmenu.png                       |  Bin 0 -> 1628 bytes
 wmsplugin/sources.cfg                              |   39 +
 wmsplugin/src/wmsplugin/GeorefImage.java           |  161 +++
 wmsplugin/src/wmsplugin/Grabber.java               |   97 ++
 wmsplugin/src/wmsplugin/HTMLGrabber.java           |   51 +
 wmsplugin/src/wmsplugin/Help_WMSmenuAction.java    |   60 +
 .../src/wmsplugin/Map_Rectifier_WMSmenuAction.java |  246 ++++
 wmsplugin/src/wmsplugin/WMSAdjustAction.java       |  220 ++++
 wmsplugin/src/wmsplugin/WMSDownloadAction.java     |   33 +
 wmsplugin/src/wmsplugin/WMSGrabber.java            |  188 ++++
 wmsplugin/src/wmsplugin/WMSInfo.java               |   42 +
 wmsplugin/src/wmsplugin/WMSLayer.java              |  428 +++++++
 wmsplugin/src/wmsplugin/WMSPlugin.java             |  253 +++++
 wmsplugin/src/wmsplugin/WMSPreferenceEditor.java   |  273 +++++
 wmsplugin/src/wmsplugin/io/WMSLayerExporter.java   |   13 +
 wmsplugin/src/wmsplugin/io/WMSLayerImporter.java   |   13 +
 wmsplugin/webkit-image-gtk.c                       |   64 ++
 wmsplugin/webkit-image.cpp                         |  106 ++
 212 files changed, 24092 insertions(+)

diff --git a/agpifoj/.classpath b/agpifoj/.classpath
new file mode 100644
index 0000000..b5edc13
--- /dev/null
+++ b/agpifoj/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/agpifoj/.project b/agpifoj/.project
new file mode 100644
index 0000000..9fa7bb2
--- /dev/null
+++ b/agpifoj/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JOSM-agpifoj</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/agpifoj/CHANGELOG b/agpifoj/CHANGELOG
new file mode 100644
index 0000000..3e0fe52
--- /dev/null
+++ b/agpifoj/CHANGELOG
@@ -0,0 +1 @@
+01-15-2008 : Release of a first beta.
\ No newline at end of file
diff --git a/agpifoj/LICENSE b/agpifoj/LICENSE
new file mode 100644
index 0000000..fe31ef6
--- /dev/null
+++ b/agpifoj/LICENSE
@@ -0,0 +1,341 @@
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+

+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+

+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+

+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+

+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/agpifoj/README b/agpifoj/README
new file mode 100644
index 0000000..987c280
--- /dev/null
+++ b/agpifoj/README
@@ -0,0 +1,57 @@
+AgPiFoj stands for 'Another geotag plug-in for Josm'.
+
+FEATURES
+
+- Access by a new menu item in the 'File' menu. This menu loads pictures and
+  makes a new layer from them in the map view.
+- Displays the images in a ToggleDialog (so it appears as a panel on the right
+  of the screen and can be shown/hidden with a click on a button of the left
+  toolbar. It can be set in a separate window by clicking the sticky button)
+- Loads geotag data from exif or correlate pictures with GPS tracks.
+- Displays the pictures as a little camera icon in the map view (this improves
+  the speed of loading large sets of pictures). The selected picture appears in
+  red.
+- Easy zoom in/out of the image with the mouse wheel. Hability to move the
+  image by clicking and/or dragging on it with mouse left button, or to select
+  the part of the image to zoom in by dragging the right button.
+- Displays the altitude and speed of the photo when available from the GPS
+  track.
+- Hability to synchronize a same set of photos with many GPS tracks (choose
+  item 'Correlate to GPX' in the contextual menu of the layer). If a  picture
+  set and a GPS track were badly time-synchronized, just load again the same
+  GPX track on the layer, by specifying a different offset and/or timezone.
+- Adds a viewport to the left toolbar : with all these plug-ins that add buttons
+  to that toolbar, some of them became inaccessible. This adds some little
+  arrows on top and bottom of the toolbar.
+
+NOTE
+For the user who used to use the 'Import images' option on GPS layers, the
+timezone is the opposite : it is greater than 0 when going to the east of
+Greenwich Meridian.
+
+INSTALL
+
+To install, put the agpifoj.jar in the JOSM plugin directory. Then in JOSM,
+select the menu Edit / Preferences and the plugins tab. Check the agpifoj
+plugin check-box, and restart JOSM. You'll seee the AgPiFoj menu item in
+the 'File' menu.
+
+BUILD
+
+The source code is in the agpifoj.jar : unzip it.
+Edit the build.xml to set the path to your josm-latest.jar as property.
+Run ant.
+The plugin jar file is in the dist directory.
+
+Tested on the latest JOSM version (build 521).
+
+CONTRIBUTION
+
+I got inspiration and some code from the Geotagged plugin (by Rob Neild)
+and the core JOSM source code (by Immanuel Scholz and others). This plugin is
+delivered under the GPL licence terms. It also uses the jpeg metadata
+extraction code is from Drew Noakes (bundled with Josm).
+
+---
+
+Hope you'll find it useful.
diff --git a/agpifoj/build.xml b/agpifoj/build.xml
new file mode 100644
index 0000000..9008ceb
--- /dev/null
+++ b/agpifoj/build.xml
@@ -0,0 +1,64 @@
+<project name="AgPifoJ" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}">
+            <fileset dir=".">
+                <include name="CHANGELOG"/>
+                <include name="LICENSE"/>
+                <include name="README"/>
+            </fileset>
+        </copy>
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Christian Gallioz"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.agpifoj.AgpifojPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Another geotag plugin for JOSM. Correlates pictures with GPS tracks or import EXIF geotagged pictures."/>
+                <attribute name="Plugin-Early" value="false"/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/AgPifoJ"/>
+                <attribute name="Plugin-Mainversion" value="2166"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/agpifoj/images/agpifoj-open.png b/agpifoj/images/agpifoj-open.png
new file mode 100644
index 0000000..79ad9fe
Binary files /dev/null and b/agpifoj/images/agpifoj-open.png differ
diff --git a/agpifoj/images/dialogs/agpifoj-mag-cursor.png b/agpifoj/images/dialogs/agpifoj-mag-cursor.png
new file mode 100644
index 0000000..2d76611
Binary files /dev/null and b/agpifoj/images/dialogs/agpifoj-mag-cursor.png differ
diff --git a/agpifoj/images/dialogs/agpifoj-marker-black.png b/agpifoj/images/dialogs/agpifoj-marker-black.png
new file mode 100644
index 0000000..c15a737
Binary files /dev/null and b/agpifoj/images/dialogs/agpifoj-marker-black.png differ
diff --git a/agpifoj/images/dialogs/agpifoj-marker-selected.png b/agpifoj/images/dialogs/agpifoj-marker-selected.png
new file mode 100644
index 0000000..f365086
Binary files /dev/null and b/agpifoj/images/dialogs/agpifoj-marker-selected.png differ
diff --git a/agpifoj/images/dialogs/agpifoj-marker.png b/agpifoj/images/dialogs/agpifoj-marker.png
new file mode 100644
index 0000000..446f491
Binary files /dev/null and b/agpifoj/images/dialogs/agpifoj-marker.png differ
diff --git a/agpifoj/images/dialogs/agpifoj.png b/agpifoj/images/dialogs/agpifoj.png
new file mode 100644
index 0000000..6b683e8
Binary files /dev/null and b/agpifoj/images/dialogs/agpifoj.png differ
diff --git a/agpifoj/images/dialogs/gpx2img.png b/agpifoj/images/dialogs/gpx2img.png
new file mode 100644
index 0000000..dfb92bd
Binary files /dev/null and b/agpifoj/images/dialogs/gpx2img.png differ
diff --git a/agpifoj/images/dialogs/gpx2imgManual.png b/agpifoj/images/dialogs/gpx2imgManual.png
new file mode 100644
index 0000000..880b59e
Binary files /dev/null and b/agpifoj/images/dialogs/gpx2imgManual.png differ
diff --git a/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojDialog.java b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojDialog.java
new file mode 100644
index 0000000..b46037c
--- /dev/null
+++ b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojDialog.java
@@ -0,0 +1,185 @@
+// License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.plugins.agpifoj;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.plugins.agpifoj.AgpifojLayer.ImageEntry;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class AgpifojDialog extends ToggleDialog implements ActionListener {
+
+    private static final String COMMAND_ZOOM = "zoom";
+    private static final String COMMAND_CENTERVIEW = "centre";
+    private static final String COMMAND_NEXT = "next";
+    private static final String COMMAND_REMOVE = "remove";
+    private static final String COMMAND_PREVIOUS = "previous";
+
+    private ImageDisplay imgDisplay = new ImageDisplay();
+    private boolean centerView = false;
+
+    // Only one instance of that class
+    static private AgpifojDialog INSTANCE = null;
+
+    public static AgpifojDialog getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = new AgpifojDialog();
+        }
+        return INSTANCE;
+    }
+
+    private AgpifojDialog() {
+        super(tr("AgPifoJ - Geotagged pictures"), "agpifoj", tr("Display geotagged photos"), Shortcut.registerShortcut("tools:geotagged", tr("Tool: {0}", tr("Display geotagged photos")), KeyEvent.VK_Y, Shortcut.GROUP_EDIT), 200);
+
+        if (INSTANCE != null) {
+            throw new IllegalStateException("Agpifoj dialog should not be instanciated twice !");
+        }
+
+        INSTANCE = this;
+
+        JPanel content = new JPanel();
+        content.setLayout(new BorderLayout());
+
+        content.add(imgDisplay, BorderLayout.CENTER);
+
+        JPanel buttons = new JPanel();
+        buttons.setLayout(new FlowLayout());
+
+        JButton button;
+
+        Dimension buttonDim = new Dimension(26,26);
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "previous"));
+        button.setActionCommand(COMMAND_PREVIOUS);
+        button.setToolTipText(tr("Previous"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "delete"));
+        button.setActionCommand(COMMAND_REMOVE);
+        button.setToolTipText(tr("Remove photo from layer"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "next"));
+        button.setActionCommand(COMMAND_NEXT);
+        button.setToolTipText(tr("Next"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        JToggleButton tb = new JToggleButton();
+        tb.setIcon(ImageProvider.get("dialogs", "centreview"));
+        tb.setActionCommand(COMMAND_CENTERVIEW);
+        tb.setToolTipText(tr("Center view"));
+        tb.addActionListener(this);
+        tb.setPreferredSize(buttonDim);
+        buttons.add(tb);
+
+        button = new JButton();
+        button.setIcon(ImageProvider.get("dialogs", "zoom-best-fit"));
+        button.setActionCommand(COMMAND_ZOOM);
+        button.setToolTipText(tr("Zoom best fit and 1:1"));
+        button.addActionListener(this);
+        button.setPreferredSize(buttonDim);
+        buttons.add(button);
+
+        content.add(buttons, BorderLayout.SOUTH);
+
+        add(content, BorderLayout.CENTER);
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        if (COMMAND_NEXT.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+                currentLayer.showNextPhoto();
+            }
+        } else if (COMMAND_PREVIOUS.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+                currentLayer.showPreviousPhoto();
+            }
+
+        } else if (COMMAND_CENTERVIEW.equals(e.getActionCommand())) {
+            centerView = ((JToggleButton) e.getSource()).isSelected();
+            if (centerView && currentEntry != null && currentEntry.pos != null) {
+                Main.map.mapView.zoomTo(currentEntry.pos);
+            }
+
+        } else if (COMMAND_ZOOM.equals(e.getActionCommand())) {
+            imgDisplay.zoomBestFitOrOne();
+
+        } else if (COMMAND_REMOVE.equals(e.getActionCommand())) {
+            if (currentLayer != null) {
+               currentLayer.removeCurrentPhoto();
+            }
+        }
+
+    }
+
+    public static void showImage(AgpifojLayer layer, ImageEntry entry) {
+        getInstance().displayImage(layer, entry);
+    }
+
+    private AgpifojLayer currentLayer = null;
+    private ImageEntry currentEntry = null;
+
+    public void displayImage(AgpifojLayer layer, ImageEntry entry) {
+        synchronized(this) {
+            if (currentLayer == layer && currentEntry == entry) {
+                repaint();
+                return;
+            }
+
+            if (centerView && Main.map != null && entry != null && entry.pos != null) {
+                Main.map.mapView.zoomTo(entry.pos);
+            }
+
+            currentLayer = layer;
+            currentEntry = entry;
+        }
+
+        if (entry != null) {
+            imgDisplay.setImage(entry.file);
+            StringBuffer osd = new StringBuffer(entry.file != null ? entry.file.getName() : "");
+            if (entry.elevation != null) {
+                osd.append(tr("\nAltitude: {0} m", entry.elevation.longValue()));
+            }
+            if (entry.speed != null) {
+                osd.append(tr("\n{0} km/h", Math.round(entry.speed)));
+            }
+            imgDisplay.setOsdText(osd.toString());
+        } else {
+            imgDisplay.setImage(null);
+            imgDisplay.setOsdText("");
+        }
+    }
+    
+    /**
+     * Returns whether an image is currently displayed
+     * @return If image is currently displayed
+     */
+    public boolean hasImage() {
+        return currentEntry != null;
+    }
+}
diff --git a/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojLayer.java b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojLayer.java
new file mode 100644
index 0000000..b8010f8
--- /dev/null
+++ b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojLayer.java
@@ -0,0 +1,530 @@
+// License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.plugins.agpifoj;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.coor.CachedLatLon;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+import com.drew.imaging.jpeg.JpegMetadataReader;
+import com.drew.lang.Rational;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.exif.GpsDirectory;
+
+public class AgpifojLayer extends Layer {
+
+    List<ImageEntry> data;
+
+    private Icon icon = ImageProvider.get("dialogs/agpifoj-marker");
+    private Icon selectedIcon = ImageProvider.get("dialogs/agpifoj-marker-selected");
+
+    private int currentPhoto = -1;
+
+    // These are used by the auto-guess function to store the result,
+    // so when the dialig is re-opened the users modifications don't
+    // get overwritten
+    public boolean hasTimeoffset = false;
+    public long timeoffset = 0;
+
+    /*
+     * Stores info about each image
+     */
+
+    static final class ImageEntry implements Comparable<ImageEntry> {
+        File file;
+        Date time;
+        LatLon exifCoor;
+        CachedLatLon pos;
+        /** Speed in kilometer per second */
+        Double speed;
+        /** Elevation (altitude) in meters */
+        Double elevation;
+
+        public void setCoor(LatLon latlon)
+        {
+            pos = new CachedLatLon(latlon);
+        }
+        public int compareTo(ImageEntry image) {
+            if (time != null && image.time != null) {
+                return time.compareTo(image.time);
+            } else if (time == null && image.time == null) {
+                return 0;
+            } else if (time == null) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+    }
+
+    /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
+     * In facts, this object is instantiated with a list of files. These files may be JPEG files or
+     * directories. In case of directories, they are scanned to find all the images they contain.
+     * Then all the images that have be found are loaded as ImageEntry instances.
+     */
+    private static final class Loader extends PleaseWaitRunnable {
+
+        private boolean cancelled = false;
+        private AgpifojLayer layer;
+        private final File[] selection;
+        private HashSet<String> loadedDirectories = new HashSet<String>();
+        private String errorMessage = "";
+
+        public Loader(File[] selection) {
+            super(tr("Extracting GPS locations from EXIF"));
+            this.selection = selection;
+        }
+
+        @Override protected void realRun() throws IOException {
+
+            progressMonitor.subTask(tr("Starting directory scan"));
+            List<File> files = new ArrayList<File>();
+            try {
+                addRecursiveFiles(files, selection);
+            } catch(NullPointerException npe) {
+                errorMessage += tr("One of the selected files was null !!!");
+            }
+
+            if (cancelled) {
+                return;
+            }            
+            progressMonitor.subTask(tr("Read photos..."));
+            progressMonitor.setTicksCount(files.size());
+
+            progressMonitor.subTask(tr("Read photos..."));
+            progressMonitor.setTicksCount(files.size());
+
+            // read the image files
+            ArrayList<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
+
+            for (File f : files) {
+
+                if (cancelled) {
+                    break;
+                }
+
+                progressMonitor.subTask(tr("Reading {0}...", f.getName()));
+                progressMonitor.worked(1);
+
+                ImageEntry e = new ImageEntry();
+
+                // Changed to silently cope with no time info in exif. One case
+                // of person having time that couldn't be parsed, but valid GPS info
+
+                try {
+                    e.time = ExifReader.readTime(f);
+                } catch (ParseException e1) {
+                    e.time = null;
+                }
+                e.file = f;
+                extractExif(e);
+                data.add(e);
+            }
+            layer = new AgpifojLayer(data);
+            files.clear();
+            if (errorMessage != null && ! errorMessage.trim().equals("")) {
+            	progressMonitor.setErrorMessage(errorMessage);
+            }
+        }
+
+        private void addRecursiveFiles(List<File> files, File[] sel) {
+            boolean nullFile = false;
+
+            for (File f : sel) {
+
+                if(cancelled) {
+                    break;
+                }
+
+                if (f == null) {
+                    nullFile = true;
+
+                } else if (f.isDirectory()) {
+                    String canonical = null;
+                    try {
+                        canonical = f.getCanonicalPath();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                        errorMessage += tr("Unable to get canonical path for directory {0}\n",
+                                           f.getAbsolutePath());
+                    }
+
+                    if (canonical == null || loadedDirectories.contains(canonical)) {
+                        continue;
+                    } else {
+                        loadedDirectories.add(canonical);
+                    }
+
+                    File[] children = f.listFiles(AgpifojPlugin.JPEG_FILE_FILTER);
+                    if (children != null) {
+                        progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
+                        try {
+                            addRecursiveFiles(files, children);
+                        } catch(NullPointerException npe) {
+                            npe.printStackTrace();
+                            errorMessage += tr("Found null file in directory {0}\n", f.getPath());
+                        }
+                    } else {
+                    	errorMessage += tr("Error while getting files from directory {0}\n", f.getPath());
+                    }
+
+                } else {
+                      files.add(f);
+                }
+            }
+
+            if (nullFile) {
+                throw new NullPointerException();
+            }
+        }
+
+        @Override protected void finish() {
+            if (layer != null) {
+                Main.main.addLayer(layer);
+                layer.hook_up_mouse_events(); // Main.map.mapView should exist
+                                              // now. Can add mouse listener
+
+                if (! cancelled && layer.data.size() > 0) {
+                    boolean noGeotagFound = true;
+                    for (ImageEntry e : layer.data) {
+                        if (e.pos != null) {
+                            noGeotagFound = false;
+                        }
+                    }
+                    if (noGeotagFound) {
+                        new CorrelateGpxWithImages(layer).actionPerformed(null);
+                    }
+                }
+            }
+        }
+
+        @Override protected void cancel() {
+            cancelled = true;
+        }
+    }
+
+    public static void create(File[] files) {
+        Loader loader = new Loader(files);
+        Main.worker.execute(loader);
+    }
+
+    private AgpifojLayer(final List<ImageEntry> data) {
+
+        super(tr("Geotagged Images"));
+
+        Collections.sort(data);
+        this.data = data;
+    }
+
+    @Override
+    public Icon getIcon() {
+        return ImageProvider.get("dialogs/agpifoj");
+    }
+
+    @Override
+    public Object getInfoComponent() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Component[] getMenuEntries() {
+
+        JMenuItem correlateItem = new JMenuItem(tr("Correlate to GPX"), ImageProvider.get("dialogs/gpx2img"));
+        correlateItem.addActionListener(new CorrelateGpxWithImages(this));
+
+        return new Component[] {
+                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
+                new JMenuItem(new RenameLayerAction(null, this)),
+                new JSeparator(),
+                correlateItem
+                };
+    }
+
+    @Override
+    public String getToolTipText() {
+        int i = 0;
+        for (ImageEntry e : data)
+            if (e.pos != null)
+                i++;
+        return data.size() + " " + trn("image", "images", data.size())
+                + " loaded. " + tr("{0} were found to be gps tagged.", i);
+    }
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return other instanceof AgpifojLayer;
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {
+        AgpifojLayer l = (AgpifojLayer) from;
+
+        ImageEntry selected = null;
+        if (l.currentPhoto >= 0) {
+            selected = l.data.get(l.currentPhoto);
+        }
+
+        data.addAll(l.data);
+        Collections.sort(data);
+
+        // Supress the double photos.
+        if (data.size() > 1) {
+            ImageEntry cur;
+            ImageEntry prev = data.get(data.size() - 1);
+            for (int i = data.size() - 2; i >= 0; i--) {
+                cur = data.get(i);
+                if (cur.file.equals(prev.file)) {
+                    data.remove(i);
+                } else {
+                    prev = cur;
+                }
+            }
+        }
+
+        if (selected != null) {
+            for (int i = 0; i < data.size() ; i++) {
+                if (data.get(i) == selected) {
+                    currentPhoto = i;
+                    AgpifojDialog.showImage(AgpifojLayer.this, data.get(i));
+                    break;
+                }
+            }
+        }
+
+        setName(l.getName());
+
+    }
+
+    @Override
+    public void paint(Graphics g, MapView mv) {
+
+        int iconWidth = icon.getIconWidth() / 2;
+        int iconHeight = icon.getIconHeight() / 2;
+
+        for (ImageEntry e : data) {
+            if (e.pos != null) {
+                Point p = mv.getPoint(e.pos);
+
+                Rectangle r = new Rectangle(p.x - iconWidth,
+                                            p.y - iconHeight,
+                                            icon.getIconWidth(),
+                                            icon.getIconHeight());
+                icon.paintIcon(mv, g, r.x, r.y);
+            }
+        }
+
+        // Draw the selection on top of the other pictures.
+        if (currentPhoto >= 0 && currentPhoto < data.size()) {
+            ImageEntry e = data.get(currentPhoto);
+
+            if (e.pos != null) {
+                Point p = mv.getPoint(e.pos);
+
+                Rectangle r = new Rectangle(p.x - selectedIcon.getIconWidth() / 2,
+                                            p.y - selectedIcon.getIconHeight() / 2,
+                                            selectedIcon.getIconWidth(),
+                                            selectedIcon.getIconHeight());
+                selectedIcon.paintIcon(mv, g, r.x, r.y);
+            }
+        }
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {
+        for (ImageEntry e : data)
+            v.visit(e.pos);
+    }
+
+    /*
+     * Extract gps from image exif
+     *
+     * If successful, fills in the LatLon and EastNorth attributes of passed in
+     * image;
+     */
+
+    private static void extractExif(ImageEntry e) {
+
+        try {
+            int deg;
+            float min, sec;
+            double lon, lat;
+
+            Metadata metadata = JpegMetadataReader.readMetadata(e.file);
+            Directory dir = metadata.getDirectory(GpsDirectory.class);
+
+            // longitude
+
+            Rational[] components = dir
+                    .getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
+
+            deg = components[0].intValue();
+            min = components[1].floatValue();
+            sec = components[2].floatValue();
+
+            lon = (deg + (min / 60) + (sec / 3600));
+
+            if (dir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W')
+                lon = -lon;
+
+            // latitude
+
+            components = dir.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
+
+            deg = components[0].intValue();
+            min = components[1].floatValue();
+            sec = components[2].floatValue();
+
+            lat = (deg + (min / 60) + (sec / 3600));
+
+            if (dir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S')
+                lat = -lat;
+
+            // Store values
+
+            e.setCoor(new LatLon(lat, lon));
+            e.exifCoor = e.pos;
+
+        } catch (Exception p) {
+            e.pos = null;
+        }
+    }
+
+    public void showNextPhoto() {
+        if (data != null && data.size() > 0) {
+            currentPhoto++;
+            if (currentPhoto >= data.size()) {
+                currentPhoto = data.size() - 1;
+            }
+            AgpifojDialog.showImage(this, data.get(currentPhoto));
+        } else {
+            currentPhoto = -1;
+        }
+        Main.main.map.repaint();
+    }
+
+    public void showPreviousPhoto() {
+        if (data != null && data.size() > 0) {
+            currentPhoto--;
+            if (currentPhoto < 0) {
+                currentPhoto = 0;
+            }
+            AgpifojDialog.showImage(this, data.get(currentPhoto));
+        } else {
+            currentPhoto = -1;
+        }
+        Main.main.map.repaint();
+    }
+
+    public void removeCurrentPhoto() {
+        if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
+            data.remove(currentPhoto);
+            if (currentPhoto >= data.size()) {
+                currentPhoto = data.size() - 1;
+            }
+            if (currentPhoto >= 0) {
+                AgpifojDialog.showImage(this, data.get(currentPhoto));
+            } else {
+                AgpifojDialog.showImage(this, null);
+            }
+        }
+        Main.main.map.repaint();
+    }
+
+    private MouseAdapter mouseAdapter = null;
+
+    private void hook_up_mouse_events() {
+        mouseAdapter = new MouseAdapter() {
+            @Override public void mousePressed(MouseEvent e) {
+
+                if (e.getButton() != MouseEvent.BUTTON1) {
+                    return;
+                }
+                if (isVisible())
+                    Main.map.mapView.repaint();
+            }
+
+            @Override public void mouseReleased(MouseEvent ev) {
+                if (ev.getButton() != MouseEvent.BUTTON1) {
+                    return;
+                }
+                if (!isVisible()) {
+                    return;
+                }
+                for (int i = data.size() - 1; i >= 0; --i) {
+                    ImageEntry e = data.get(i);
+                    if (e.pos == null)
+                        continue;
+                    Point p = Main.map.mapView.getPoint(e.pos);
+                    Rectangle r = new Rectangle(p.x - icon.getIconWidth() / 2,
+                                                p.y - icon.getIconHeight() / 2,
+                                                icon.getIconWidth(),
+                                                icon.getIconHeight());
+                    if (r.contains(ev.getPoint())) {
+                        currentPhoto = i;
+                        AgpifojDialog.showImage(AgpifojLayer.this, e);
+                        Main.main.map.repaint();
+                        break;
+                    }
+                }
+                Main.map.mapView.repaint();
+            }
+        };
+        Main.map.mapView.addMouseListener(mouseAdapter);
+        Layer.listeners.add(new LayerChangeListener() {
+            public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+                if (newLayer == AgpifojLayer.this && currentPhoto >= 0) {
+                    Main.main.map.repaint();
+                    AgpifojDialog.showImage(AgpifojLayer.this, data.get(currentPhoto));
+                }
+            }
+
+            public void layerAdded(Layer newLayer) {
+            }
+
+            public void layerRemoved(Layer oldLayer) {
+                if (oldLayer == AgpifojLayer.this) {
+                    Main.map.mapView.removeMouseListener(mouseAdapter);
+                    currentPhoto = -1;
+                    data.clear();
+                    data = null;
+                }
+            }
+        });
+    }
+
+}
diff --git a/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojPlugin.java b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojPlugin.java
new file mode 100644
index 0000000..9d17ac1
--- /dev/null
+++ b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojPlugin.java
@@ -0,0 +1,100 @@
+// License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.plugins.agpifoj;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+
+import javax.swing.JFileChooser;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.IconToggleButton;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.plugins.Plugin;
+
+public class AgpifojPlugin extends Plugin {
+
+    static class JpegFileFilter extends javax.swing.filechooser.FileFilter
+                                        implements java.io.FileFilter {
+
+        @Override public boolean accept(File f) {
+            if (f.isDirectory()) {
+                return true;
+            } else {
+                String name = f.getName().toLowerCase();
+                return name.endsWith(".jpg") || name.endsWith(".jpeg");
+            }
+        }
+
+        @Override public String getDescription() {
+            return tr("JPEG images (*.jpg)");
+        }
+    };
+
+    static final JpegFileFilter JPEG_FILE_FILTER = new JpegFileFilter();
+
+    private class Action extends JosmAction {
+
+        public Action() {
+            super(tr("Open images with AgPifoJ..."),
+                  "agpifoj-open",
+                  tr("Load set of images as a new layer."),
+                  null, false);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+
+            JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
+            fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+            fc.setMultiSelectionEnabled(true);
+            fc.setAcceptAllFileFilterUsed(false);
+            fc.setFileFilter(JPEG_FILE_FILTER);
+
+            int result = fc.showOpenDialog(Main.parent);
+
+            File[] sel = fc.getSelectedFiles();
+            if (sel == null || sel.length == 0 || result != JFileChooser.APPROVE_OPTION) {
+                return;
+            }
+
+            Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
+
+            AgpifojLayer.create(sel);
+        }
+    }
+
+    public AgpifojPlugin() {
+        MainMenu.add(Main.main.menu.fileMenu, new Action());
+    }
+
+    /**
+     * Called after Main.mapFrame is initialized. (After the first data is loaded).
+     * You can use this callback to tweak the newFrame to your needs, as example install
+     * an alternative Painter.
+     */
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (newFrame != null) {
+            AgpifojDialog dialog = AgpifojDialog.getInstance();
+            IconToggleButton b = newFrame.addToggleDialog(dialog);
+
+            boolean found = false;
+            for (Layer layer : newFrame.mapView.getAllLayers()) {
+                if (layer instanceof AgpifojLayer) {
+                    found = true;
+                    break;
+                }
+            }
+            b.setSelected(found);
+        } else {
+            AgpifojDialog.getInstance().displayImage(null, null);
+        }
+    }
+}
diff --git a/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/CorrelateGpxWithImages.java b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/CorrelateGpxWithImages.java
new file mode 100644
index 0000000..6d46151
--- /dev/null
+++ b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/CorrelateGpxWithImages.java
@@ -0,0 +1,1177 @@
+// License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
+// Parts of code from Geotagged plugin (by Rob Neild)
+// and the core JOSM source code (by Immanuel Scholz and others)
+
+package org.openstreetmap.josm.plugins.agpifoj;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+
+import javax.swing.AbstractListModel;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.io.GpxReader;
+import org.openstreetmap.josm.plugins.agpifoj.AgpifojLayer.ImageEntry;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.PrimaryDateParser;
+import org.xml.sax.SAXException;
+
+
+/** This class displays the window to select the GPX file and the offset (timezone + delta).
+ * Then it correlates the images of the layer with that GPX file.
+ */
+public class CorrelateGpxWithImages implements ActionListener {
+
+    private static List<GpxData> loadedGpxData = new ArrayList<GpxData>();
+
+    public static class CorrelateParameters {
+        GpxData gpxData;
+        float timezone;
+        long offset;
+    }
+
+    AgpifojLayer yLayer = null;
+
+    private static class GpxDataWrapper {
+        String name;
+        GpxData data;
+        File file;
+
+        public GpxDataWrapper(String name, GpxData data, File file) {
+            this.name = name;
+            this.data = data;
+            this.file = file;
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    Vector gpxLst = new Vector();
+    JPanel panel = null;
+    JComboBox cbGpx = null;
+    JTextField tfTimezone = null;
+    JTextField tfOffset = null;
+    JRadioButton rbAllImg = null;
+    JRadioButton rbUntaggedImg = null;
+    JRadioButton rbNoExifImg = null;
+
+    /** This class is called when the user doesn't find the GPX file he needs in the files that have
+     * been loaded yet. It displays a FileChooser dialog to select the GPX file to be loaded.
+     */
+    private class LoadGpxDataActionListener implements ActionListener {
+
+        public void actionPerformed(ActionEvent arg0) {
+            JFileChooser fc = new JFileChooser(Main.pref.get("lastDirectory"));
+            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+            fc.setAcceptAllFileFilterUsed(false);
+            fc.setMultiSelectionEnabled(false);
+            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+            fc.setFileFilter(new FileFilter(){
+                @Override public boolean accept(File f) {
+                    return (f.isDirectory()
+                            || f .getName().toLowerCase().endsWith(".gpx")
+                            || f.getName().toLowerCase().endsWith(".gpx.gz"));
+                }
+                @Override public String getDescription() {
+                    return tr("GPX Files (*.gpx *.gpx.gz)");
+                }
+            });
+            fc.showOpenDialog(Main.parent);
+            File sel = fc.getSelectedFile();
+            if (sel == null)
+                return;
+
+            try {
+                panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+                Main.pref.put("lastDirectory", sel.getPath());
+
+                for (int i = gpxLst.size() - 1 ; i >= 0 ; i--) {
+                    if (gpxLst.get(i) instanceof GpxDataWrapper) {
+                        GpxDataWrapper wrapper = (GpxDataWrapper) gpxLst.get(i);
+                        if (sel.equals(wrapper.file)) {
+                            cbGpx.setSelectedIndex(i);
+                            if (!sel.getName().equals(wrapper.name)) {
+                                JOptionPane.showMessageDialog(
+                                		Main.parent,
+                                        tr("File {0} is loaded yet under the name \"{1}\"", sel.getName(), wrapper.name),
+                                        tr("Error"),
+                                        JOptionPane.ERROR_MESSAGE
+                                        );
+                            }
+                            return;
+                        }
+                    }
+                }
+                GpxData data = null;
+                try {
+                    InputStream iStream;
+                    if (sel.getName().toLowerCase().endsWith(".gpx.gz")) {
+                        iStream = new GZIPInputStream(new FileInputStream(sel));
+                    } else {
+                        iStream = new FileInputStream(sel);
+                    }
+                    data = new GpxReader(iStream, sel).data;
+                    data.storageFile = sel;
+
+                } catch (SAXException x) {
+                    x.printStackTrace();
+                    JOptionPane.showMessageDialog(
+                    		Main.parent, 
+                    		tr("Error while parsing {0}",sel.getName())+": "+x.getMessage(),
+                    		tr("Error"),
+                    		JOptionPane.ERROR_MESSAGE
+                    		);
+                    return;
+                } catch (IOException x) {
+                    x.printStackTrace();
+                    JOptionPane.showMessageDialog(
+                    		Main.parent, 
+                    		tr("Could not read \"{0}\"",sel.getName())+"\n"+x.getMessage(),
+                    		tr("Error"),
+                    		JOptionPane.ERROR_MESSAGE
+                    		);
+                    return;
+                }
+
+                loadedGpxData.add(data);
+                if (gpxLst.get(0) instanceof String) {
+                    gpxLst.remove(0);
+                }
+                gpxLst.add(new GpxDataWrapper(sel.getName(), data, sel));
+                cbGpx.setSelectedIndex(cbGpx.getItemCount() - 1);
+            } finally {
+                panel.setCursor(Cursor.getDefaultCursor());
+            }
+        }
+    }
+
+    /** This action listener is called when the user has a photo of the time of his GPS receiver. It
+     * displays the list of photos of the layer, and upon selection displays the selected photo.
+     * From that photo, the user can key in the time of the GPS.
+     * Then values of timezone and delta are set.
+     * @author chris
+     *
+     */
+    private class SetOffsetActionListener implements ActionListener {
+        JPanel panel;
+        JLabel lbExifTime;
+        JTextField tfGpsTime;
+        JComboBox cbTimezones;
+        ImageDisplay imgDisp;
+        JList imgList;
+
+        public void actionPerformed(ActionEvent arg0) {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+
+            panel = new JPanel();
+            panel.setLayout(new BorderLayout());
+            panel.add(new JLabel(tr("<html>Take a photo of your GPS receiver while it displays the time.<br>"
+                                    + "Display that photo here.<br>"
+                                    + "And then, simply capture the time you read on the photo and select a timezone<hr></html>")),
+                                    BorderLayout.NORTH);
+
+            imgDisp = new ImageDisplay();
+            imgDisp.setPreferredSize(new Dimension(300, 225));
+            panel.add(imgDisp, BorderLayout.CENTER);
+
+            JPanel panelTf = new JPanel();
+            panelTf.setLayout(new GridBagLayout());
+
+            GridBagConstraints gc = new GridBagConstraints();
+            gc.gridx = gc.gridy = 0;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("Photo time (from exif):")), gc);
+
+            lbExifTime = new JLabel();
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            gc.gridwidth = 2;
+            panelTf.add(lbExifTime, gc);
+
+            gc.gridx = 0;
+            gc.gridy = 1;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("Gps time (read from the above photo): ")), gc);
+
+            tfGpsTime = new JTextField();
+            tfGpsTime.setEnabled(false);
+            tfGpsTime.setMinimumSize(new Dimension(150, tfGpsTime.getMinimumSize().height));
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            panelTf.add(tfGpsTime, gc);
+
+            gc.gridx = 2;
+            gc.weightx = 0.2;
+            panelTf.add(new JLabel(tr(" [dd/mm/yyyy hh:mm:ss]")), gc);
+
+            gc.gridx = 0;
+            gc.gridy = 2;
+            gc.gridwidth = gc.gridheight = 1;
+            gc.weightx = gc.weighty = 0.0;
+            gc.fill = GridBagConstraints.NONE;
+            gc.anchor = GridBagConstraints.WEST;
+            panelTf.add(new JLabel(tr("I'm in the timezone of: ")), gc);
+
+            Vector vtTimezones = new Vector<String>();
+            String[] tmp = TimeZone.getAvailableIDs();
+
+            for (String tzStr : tmp) {
+                TimeZone tz = TimeZone.getTimeZone(tzStr);
+
+                String tzDesc = new StringBuffer(tzStr).append(" (")
+                                        .append(formatTimezone(tz.getRawOffset() / 3600000.0))
+                                        .append(')').toString();
+                vtTimezones.add(tzDesc);
+            }
+
+            Collections.sort(vtTimezones);
+
+            cbTimezones = new JComboBox(vtTimezones);
+
+            String tzId = Main.pref.get("tagimages.timezoneid", "");
+            TimeZone defaultTz;
+            if (tzId.length() == 0) {
+                defaultTz = TimeZone.getDefault();
+            } else {
+                defaultTz = TimeZone.getTimeZone(tzId);
+            }
+
+            cbTimezones.setSelectedItem(new StringBuffer(defaultTz.getID()).append(" (")
+                    .append(formatTimezone(defaultTz.getRawOffset() / 3600000.0))
+                    .append(')').toString());
+
+            gc.gridx = 1;
+            gc.weightx = 1.0;
+            gc.gridwidth = 2;
+            gc.fill = GridBagConstraints.HORIZONTAL;
+            panelTf.add(cbTimezones, gc);
+
+            panel.add(panelTf, BorderLayout.SOUTH);
+
+            JPanel panelLst = new JPanel();
+            panelLst.setLayout(new BorderLayout());
+
+            imgList = new JList(new AbstractListModel() {
+                public Object getElementAt(int i) {
+                    return yLayer.data.get(i).file.getName();
+                }
+
+                public int getSize() {
+                    return yLayer.data.size();
+                }
+            });
+            imgList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+            imgList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+                public void valueChanged(ListSelectionEvent arg0) {
+                    int index = imgList.getSelectedIndex();
+                    imgDisp.setImage(yLayer.data.get(index).file);
+                    Date date = yLayer.data.get(index).time;
+                    if (date != null) {
+                        lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
+                        tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));
+                        tfGpsTime.setCaretPosition(tfGpsTime.getText().length());
+                        tfGpsTime.setEnabled(true);
+                    } else {
+                        lbExifTime.setText(tr("No date"));
+                        tfGpsTime.setText("");
+                        tfGpsTime.setEnabled(false);
+                    }
+                }
+
+            });
+            panelLst.add(new JScrollPane(imgList), BorderLayout.CENTER);
+
+            JButton openButton = new JButton(tr("Open an other photo"));
+            openButton.addActionListener(new ActionListener() {
+
+                public void actionPerformed(ActionEvent arg0) {
+                    JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
+                    fc.setAcceptAllFileFilterUsed(false);
+                    fc.setMultiSelectionEnabled(false);
+                    fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                    fc.setFileFilter(AgpifojPlugin.JPEG_FILE_FILTER);
+                    fc.showOpenDialog(Main.parent);
+                    File sel = fc.getSelectedFile();
+                    if (sel == null) {
+                        return;
+                    }
+
+                    imgDisp.setImage(sel);
+
+                    Date date = null;
+                    try {
+                        date = ExifReader.readTime(sel);
+                    } catch (Exception e) {
+                    }
+                    if (date != null) {
+                        lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
+                        tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));
+                        tfGpsTime.setEnabled(true);
+                    } else {
+                        lbExifTime.setText(tr("No date"));
+                        tfGpsTime.setText("");
+                        tfGpsTime.setEnabled(false);
+                    }
+                }
+            });
+            panelLst.add(openButton, BorderLayout.PAGE_END);
+
+            panel.add(panelLst, BorderLayout.LINE_START);
+
+            boolean isOk = false;
+            while (! isOk) {
+                int answer = JOptionPane.showConfirmDialog(
+                		Main.parent, panel, 
+                		tr("Synchronize time from a photo of the GPS receiver"), 
+                		JOptionPane.OK_CANCEL_OPTION,
+                		JOptionPane.QUESTION_MESSAGE
+                		);
+                if (answer == JOptionPane.CANCEL_OPTION) {
+                    return;
+                }
+
+                long delta;
+
+                try {
+                    delta = dateFormat.parse(lbExifTime.getText()).getTime()
+                            - dateFormat.parse(tfGpsTime.getText()).getTime();
+                } catch(ParseException e) {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing the date.\n"
+                            + "Please use the requested format"),
+                            tr("Invalid date"), JOptionPane.ERROR_MESSAGE );
+                    continue;
+                }
+
+                String selectedTz = (String) cbTimezones.getSelectedItem();
+                int pos = selectedTz.lastIndexOf('(');
+                tzId = selectedTz.substring(0, pos - 1);
+                String tzValue = selectedTz.substring(pos + 1, selectedTz.length() - 1);
+
+                Main.pref.put("tagimages.timezoneid", tzId);
+                tfOffset.setText(Long.toString(delta / 1000));
+                tfTimezone.setText(tzValue);
+
+                isOk = true;
+
+            }
+
+        }
+    }
+
+    public CorrelateGpxWithImages(AgpifojLayer layer) {
+        this.yLayer = layer;
+    }
+
+    public void actionPerformed(ActionEvent arg0) {
+        // Construct the list of loaded GPX tracks
+        Collection<Layer> layerLst = Main.main.map.mapView.getAllLayers();
+        Iterator<Layer> iterLayer = layerLst.iterator();
+        while (iterLayer.hasNext()) {
+            Layer cur = iterLayer.next();
+            if (cur instanceof GpxLayer) {
+                gpxLst.add(new GpxDataWrapper(((GpxLayer) cur).getName(),
+                                              ((GpxLayer) cur).data,
+                                              ((GpxLayer) cur).data.storageFile));
+            }
+        }
+        for (GpxData data : loadedGpxData) {
+            gpxLst.add(new GpxDataWrapper(data.storageFile.getName(),
+                                          data,
+                                          data.storageFile));
+        }
+
+        if (gpxLst.size() == 0) {
+            gpxLst.add(tr("<No GPX track loaded yet>"));
+        }
+
+        JPanel panelCb = new JPanel();
+        panelCb.setLayout(new FlowLayout());
+
+        panelCb.add(new JLabel(tr("GPX track: ")));
+
+        cbGpx = new JComboBox(gpxLst);
+        panelCb.add(cbGpx);
+
+        JButton buttonOpen = new JButton(tr("Open another GPX trace"));
+        buttonOpen.setIcon(ImageProvider.get("agpifoj-open"));
+        buttonOpen.addActionListener(new LoadGpxDataActionListener());
+
+        panelCb.add(buttonOpen);
+
+        JPanel panelTf = new JPanel();
+        panelTf.setLayout(new GridBagLayout());
+
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.anchor = GridBagConstraints.WEST;
+
+        gc.gridx = gc.gridy = 0;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Timezone: ")), gc);
+
+        float gpstimezone = Float.parseFloat(Main.pref.get("tagimages.doublegpstimezone", "0.0"));
+        if (gpstimezone == 0.0) {
+            gpstimezone = - Long.parseLong(Main.pref.get("tagimages.gpstimezone", "0"));
+        }
+        tfTimezone = new JTextField();
+        tfTimezone.setText(formatTimezone(gpstimezone));
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        panelTf.add(tfTimezone, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Offset:")), gc);
+
+        long delta = Long.parseLong(Main.pref.get("tagimages.delta", "0")) / 1000;
+        tfOffset = new JTextField();
+        tfOffset.setText(Long.toString(delta));
+        gc.gridx = gc.gridy = 1;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        panelTf.add(tfOffset, gc);
+
+        JButton buttonViewGpsPhoto = new JButton(tr("<html>I can take a picture of my GPS receiver.<br>"
+                                                    + "Can this help?</html>"));
+        buttonViewGpsPhoto.addActionListener(new SetOffsetActionListener());
+        gc.gridx = 2;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 2;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 0.5;
+        gc.weighty = 1.0;
+        panelTf.add(buttonViewGpsPhoto, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        gc.gridwidth = gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.weightx = gc.weighty = 0.0;
+        panelTf.add(new JLabel(tr("Update position for: ")), gc);
+
+        gc.gridx = 1;
+        gc.gridy = 2;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbAllImg = new JRadioButton(tr("All images"));
+        panelTf.add(rbAllImg, gc);
+
+        gc.gridx = 1;
+        gc.gridy = 3;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbNoExifImg = new JRadioButton(tr("Images with no exif position"));
+        panelTf.add(rbNoExifImg, gc);
+
+        gc.gridx = 1;
+        gc.gridy = 4;
+        gc.gridwidth = 2;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        gc.weighty = 0.0;
+        rbUntaggedImg = new JRadioButton(tr("Not yet tagged images"));
+        panelTf.add(rbUntaggedImg, gc);
+
+        ButtonGroup group = new ButtonGroup();
+        group.add(rbAllImg);
+        group.add(rbNoExifImg);
+        group.add(rbUntaggedImg);
+
+        rbUntaggedImg.setSelected(true);
+
+        panel = new JPanel();
+        panel.setLayout(new BorderLayout());
+
+        panel.add(panelCb, BorderLayout.PAGE_START);
+        panel.add(panelTf, BorderLayout.CENTER);
+
+        boolean isOk = false;
+        GpxDataWrapper selectedGpx = null;
+        while (! isOk) {
+        	ExtendedDialog dialog = new ExtendedDialog(
+        			Main.parent,
+                tr("Correlate images with GPX track"),
+                new String[] { tr("Correlate"), tr("Auto-Guess"), tr("Cancel") }
+        			);
+
+        	dialog.setContent(panel);
+        	dialog.setButtonIcons(new String[] { "ok.png", "dialogs/gpx2imgManual.png", "cancel.png" });
+        	dialog.showDialog();
+        	int answer = dialog.getValue();
+            if(answer != 1 && answer != 2)
+                return;
+
+            // Check the selected values
+            Object item = cbGpx.getSelectedItem();
+
+            if (item == null || ! (item instanceof GpxDataWrapper)) {
+                JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"),
+                                              tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE );
+                continue;
+            }
+            selectedGpx = ((GpxDataWrapper) item);
+
+            if (answer == 2) {
+                autoGuess(selectedGpx.data);
+                return;
+            }
+
+            Float timezoneValue = parseTimezone(tfTimezone.getText().trim());
+            if (timezoneValue == null) {
+                JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM"),
+                        tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE);
+                continue;
+            }
+            gpstimezone = timezoneValue.floatValue();
+
+            String deltaText = tfOffset.getText().trim();
+            if (deltaText.length() > 0) {
+                try {
+                    if(deltaText.startsWith("+"))
+                        deltaText = deltaText.substring(1);
+                    delta = Long.parseLong(deltaText);
+                } catch(NumberFormatException nfe) {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing offset.\nExpected format: {0}", "number"),
+                            tr("Invalid offset"), JOptionPane.ERROR_MESSAGE);
+                    continue;
+                }
+            } else {
+                delta = 0;
+            }
+
+            Main.pref.put("tagimages.doublegpstimezone", Double.toString(gpstimezone));
+            Main.pref.put("tagimages.gpstimezone", Long.toString(- ((long) gpstimezone)));
+            Main.pref.put("tagimages.delta", Long.toString(delta * 1000));
+
+            isOk = true;
+        }
+
+        // Construct a list of images that have a date, and sort them on the date.
+        ArrayList<ImageEntry> dateImgLst = getSortedImgList(rbAllImg.isSelected(), rbNoExifImg.isSelected());
+
+        int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600) + delta);
+
+        // Search whether an other layer has yet defined some bounding box.
+        // If none, we'll zoom to the bounding box of the layer with the photos.
+        Collection<Layer> layerCol = Main.map.mapView.getAllLayers();
+        Iterator<Layer> layerIter = layerCol.iterator();
+        boolean boundingBoxedLayerFound = false;
+        while (layerIter.hasNext()) {
+            Layer l = layerIter.next();
+            if (l != yLayer) {
+                BoundingXYVisitor bbox = new BoundingXYVisitor();
+                l.visitBoundingBox(bbox);
+                if (bbox.getBounds() != null) {
+                    boundingBoxedLayerFound = true;
+                    break;
+                }
+            }
+        }
+        if (! boundingBoxedLayerFound) {
+            BoundingXYVisitor bbox = new BoundingXYVisitor();
+            yLayer.visitBoundingBox(bbox);
+            Main.map.mapView.recalculateCenterScale(bbox);
+        }
+
+        Main.main.map.repaint();
+
+        JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matches of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name),
+                tr("GPX Track loaded"),
+                ((dateImgLst.size() > 0 && matched == 0) ? JOptionPane.WARNING_MESSAGE
+                                                         : JOptionPane.INFORMATION_MESSAGE));
+
+    }
+
+    // These variables all belong to "auto guess" but need to be accessible
+    // from the slider change listener
+    private int dayOffset;
+    private JLabel lblMatches;
+    private JLabel lblOffset;
+    private JLabel lblTimezone;
+    private JLabel lblMinutes;
+    private JLabel lblSeconds;
+    private JSlider sldTimezone;
+    private JSlider sldMinutes;
+    private JSlider sldSeconds;
+    private GpxData autoGpx;
+    private ArrayList<ImageEntry> autoImgs;
+    private long firstGPXDate = -1;
+    private long firstExifDate = -1;
+
+    /**
+     * Tries to automatically match opened photos to a given GPX track. Changes are applied
+     * immediately. Presents dialog with sliders for manual adjust.
+     * @param GpxData The GPX track to match against
+     */
+    private void autoGuess(GpxData gpx) {
+        autoGpx = gpx;
+        autoImgs = getSortedImgList(true, false);
+        PrimaryDateParser dateParser = new PrimaryDateParser();
+
+        // no images found, exit
+        if(autoImgs.size() <= 0) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("The selected photos don't contain time information."),
+                tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        AgpifojDialog dialog = AgpifojDialog.getInstance();
+        dialog.showDialog();
+        // Will show first photo if none is selected yet
+        if(!dialog.hasImage())
+            yLayer.showNextPhoto();
+        // FIXME: If the dialog is minimized it will not be maximized. ToggleDialog is
+        // in need of a complete re-write to allow this in a reasonable way.
+
+        // Init variables
+        firstExifDate = autoImgs.get(0).time.getTime()/1000;
+
+
+        // Finds first GPX point
+        outer: for (GpxTrack trk : gpx.tracks) {
+            for (Collection<WayPoint> segment : trk.trackSegs) {
+                for (WayPoint curWp : segment) {
+                    String curDateWpStr = (String) curWp.attr.get("time");
+                    if (curDateWpStr == null) continue;
+
+                    try {
+                        firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000;
+                        break outer;
+                    } catch(Exception e) {}
+                }
+            }
+        }
+
+        // No GPX timestamps found, exit
+        if(firstGPXDate < 0) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("The selected GPX track doesn't contain timestamps. Please select another one."),
+                tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        // seconds
+        long diff = (yLayer.hasTimeoffset)
+            ? yLayer.timeoffset
+            : firstExifDate - firstGPXDate;
+        yLayer.timeoffset = diff;
+        yLayer.hasTimeoffset = true;
+
+        double diffInH = (double)diff/(60*60);    // hours
+
+        // Find day difference
+        dayOffset = (int)Math.round(diffInH / 24); // days
+        double timezone = diff - dayOffset*24*60*60;  // seconds
+
+        // In hours, rounded to two decimal places
+        timezone = (double)Math.round(timezone*100/(60*60)) / 100;
+
+        // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
+        // -2 minutes offset. This determines the real timezone and finds offset.
+        double fixTimezone = (double)Math.round(timezone * 2)/2; // hours, rounded to one decimal place
+        int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds
+
+        /*System.out.println("phto " + firstExifDate);
+        System.out.println("gpx  " + firstGPXDate);
+        System.out.println("diff " + diff);
+        System.out.println("difh " + diffInH);
+        System.out.println("days " + dayOffset);
+        System.out.println("time " + timezone);
+        System.out.println("fix  " + fixTimezone);
+        System.out.println("offt " + offset);*/
+
+        // This is called whenever one of the sliders is moved.
+        // It updates the labels and also calls the "match photos" code
+        class sliderListener implements ChangeListener {
+            public void stateChanged(ChangeEvent e) {
+                // parse slider position into real timezone
+                double tz = Math.abs(sldTimezone.getValue());
+                String zone = tz % 2 == 0
+                    ? (int)Math.floor(tz/2) + ":00"
+                    : (int)Math.floor(tz/2) + ":30";
+                if(sldTimezone.getValue() < 0) zone = "-" + zone;
+
+                lblTimezone.setText(tr("Timezone: {0}", zone));
+                lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
+                lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue()));
+
+                float gpstimezone = parseTimezone(zone).floatValue();
+
+                // Reset previous position
+                for(ImageEntry x : autoImgs) {
+                    x.pos = null;
+                }
+
+                long timediff = (long) (gpstimezone * 3600)
+                        + dayOffset*24*60*60
+                        + sldMinutes.getValue()*60
+                        + sldSeconds.getValue();
+
+                int matched = matchGpxTrack(autoImgs, autoGpx, timediff);
+
+                lblMatches.setText(
+                    tr("Matched {0} of {1} photos to GPX track.", matched, autoImgs.size())
+                    + ((Math.abs(dayOffset) == 0)
+                        ? ""
+                        : " " + tr("(Time difference of {0} days)", Math.abs(dayOffset))
+                      )
+                );
+
+                int offset = (int)(firstGPXDate+timediff-firstExifDate);
+                int o = Math.abs(offset);
+                lblOffset.setText(
+                    tr("Offset between track and photos: {0}m {1}s",
+                          (offset < 0 ? "-" : "") + Long.toString(Math.round(o/60)),
+                          Long.toString(Math.round(o%60))
+                    )
+                );
+
+                yLayer.timeoffset = timediff;
+                Main.main.map.repaint();
+            }
+        }
+
+        // Info Labels
+        lblMatches = new JLabel();
+        lblOffset = new JLabel();
+
+        // Timezone Slider
+        // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes
+        // steps. Therefore the range is -24 to 24.
+        lblTimezone = new JLabel();
+        sldTimezone = new JSlider(-24, 24, 0);
+        sldTimezone.setPaintLabels(true);
+        Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>();
+        labelTable.put(-24, new JLabel("-12:00"));
+        labelTable.put(-12, new JLabel( "-6:00"));
+        labelTable.put(  0, new JLabel(  "0:00"));
+        labelTable.put( 12, new JLabel(  "6:00"));
+        labelTable.put( 24, new JLabel( "12:00"));
+        sldTimezone.setLabelTable(labelTable);
+
+        // Minutes Slider
+        lblMinutes = new JLabel();
+        sldMinutes = new JSlider(-15, 15, 0);
+        sldMinutes.setPaintLabels(true);
+        sldMinutes.setMajorTickSpacing(5);
+
+        // Seconds slider
+        lblSeconds = new JLabel();
+        sldSeconds = new JSlider(-60, 60, 0);
+        sldSeconds.setPaintLabels(true);
+        sldSeconds.setMajorTickSpacing(30);
+
+        // Put everything together
+        JPanel p = new JPanel(new GridBagLayout());
+        p.setPreferredSize(new Dimension(400, 230));
+        p.add(lblMatches, GBC.eol().fill());
+        p.add(lblOffset, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblTimezone, GBC.eol().fill());
+        p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblMinutes, GBC.eol().fill());
+        p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10));
+        p.add(lblSeconds, GBC.eol().fill());
+        p.add(sldSeconds, GBC.eol().fill());
+
+        // If there's an error in the calculation the found values
+        // will be off range for the sliders. Catch this error
+        // and inform the user about it.
+        try {
+            sldTimezone.setValue((int)(fixTimezone*2));
+            sldMinutes.setValue(offset/60);
+            sldSeconds.setValue(offset%60);
+        } catch(Exception e) {
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("An error occurred while trying to match the photos to the GPX track."
+                    +" You can adjust the sliders to manually match the photos."),
+                tr("Matching photos to track failed"),
+                JOptionPane.WARNING_MESSAGE);
+        }
+
+        // Call the sliderListener once manually so labels get adjusted
+        new sliderListener().stateChanged(null);
+        // Listeners added here, otherwise it tries to match three times
+        // (when setting the default values)
+        sldTimezone.addChangeListener(new sliderListener());
+        sldMinutes.addChangeListener(new sliderListener());
+        sldSeconds.addChangeListener(new sliderListener());
+
+        // There is no way to cancel this dialog, all changes get applied
+        // immediately. Therefore "Close" is marked with an "OK" icon.
+        // Settings are only saved temporarily to the layer.
+        ExtendedDialog d = new ExtendedDialog(Main.parent,
+            tr("Adjust timezone and offset"),
+            new String[] { tr("Close"),  tr("Default Values") }           
+        );
+
+        d.setContent(p);
+        d.setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"});
+        d.showDialog();
+        int answer = d.getValue();
+        // User wants default values; discard old result and re-open dialog
+        if(answer == 2) {
+            yLayer.hasTimeoffset = false;
+            autoGuess(gpx);
+        }
+    }
+
+    /**
+     * Returns a list of images that fulfill the given criteria.
+     * Default setting is to return untagged images, but may be overwritten.
+     * @param boolean all -- returns all available images
+     * @param boolean noexif -- returns untagged images without EXIF-GPS coords
+     * @return ArrayList<ImageEntry> matching images
+     */
+    private ArrayList<ImageEntry> getSortedImgList(boolean all, boolean noexif) {
+        ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size());
+        if (all) {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null) {
+                    // Reset previous position
+                    e.pos = null;
+                    dateImgLst.add(e);
+                }
+            }
+
+        } else if (noexif) {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null && e.exifCoor == null) {
+                    dateImgLst.add(e);
+                }
+            }
+
+        } else {
+            for (ImageEntry e : yLayer.data) {
+                if (e.time != null && e.pos == null) {
+                    dateImgLst.add(e);
+                }
+            }
+        }
+
+        Collections.sort(dateImgLst, new Comparator<ImageEntry>() {
+            public int compare(ImageEntry arg0, ImageEntry arg1) {
+                return arg0.time.compareTo(arg1.time);
+            }
+        });
+
+        return dateImgLst;
+    }
+
+    private int matchGpxTrack(ArrayList<ImageEntry> dateImgLst, GpxData selectedGpx, long offset) {
+        int ret = 0;
+
+        PrimaryDateParser dateParser = new PrimaryDateParser();
+
+        for (GpxTrack trk : selectedGpx.tracks) {
+            for (Collection<WayPoint> segment : trk.trackSegs) {
+
+                long prevDateWp = 0;
+                WayPoint prevWp = null;
+
+                for (WayPoint curWp : segment) {
+
+                    String curDateWpStr = (String) curWp.attr.get("time");
+                    if (curDateWpStr != null) {
+
+                        try {
+                            long curDateWp = dateParser.parse(curDateWpStr).getTime()/1000 + offset;
+                            ret += matchPoints(dateImgLst, prevWp, prevDateWp, curWp, curDateWp);
+
+                            prevWp = curWp;
+                            prevDateWp = curDateWp;
+
+                        } catch(ParseException e) {
+                            System.err.println("Error while parsing date \"" + curDateWpStr + '"');
+                            e.printStackTrace();
+                            prevWp = null;
+                            prevDateWp = 0;
+                        }
+                    } else {
+                        prevWp = null;
+                        prevDateWp = 0;
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
+    private int matchPoints(ArrayList<ImageEntry> dateImgLst, WayPoint prevWp, long prevDateWp,
+                                                                   WayPoint curWp, long curDateWp) {
+        // Time between the track point and the previous one, 5 sec if first point, i.e. photos take
+        // 5 sec before the first track point can be assumed to be take at the starting position
+        long interval = prevDateWp > 0 ? ((int)Math.abs(curDateWp - prevDateWp)) : 5;
+        int ret = 0;
+
+        // i is the index of the timewise last photo that has the same or earlier EXIF time
+        int i = getLastIndexOfListBefore(dateImgLst, curDateWp);
+
+        // no photos match
+        if (i < 0)
+            return 0;
+
+        Double speed = null;
+        Double prevElevation = null;
+        Double curElevation = null;
+
+        if (prevWp != null) {
+            double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
+            // This is in km/h, 3.6 * m/s
+            if (curDateWp > prevDateWp)
+                speed = 3.6 * distance / (curDateWp - prevDateWp);
+            try {
+                prevElevation = new Double((String) prevWp.attr.get("ele"));
+            } catch(Exception e) {}
+        }
+
+        try {
+            curElevation = new Double((String) curWp.attr.get("ele"));
+        } catch (Exception e) {}
+
+        // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds
+        // before the first point will be geotagged with the starting point
+        if(prevDateWp == 0 || curDateWp <= prevDateWp) {
+            while(i >= 0 && (dateImgLst.get(i).time.getTime()/1000) <= curDateWp
+                        && (dateImgLst.get(i).time.getTime()/1000) >= (curDateWp - interval)) {
+                if(dateImgLst.get(i).pos == null) {
+                    dateImgLst.get(i).setCoor(curWp.getCoor());
+                    dateImgLst.get(i).speed = speed;
+                    dateImgLst.get(i).elevation = curElevation;
+                    ret++;
+                }
+                i--;
+            }
+            return ret;
+        }
+
+        // This code gives a simple linear interpolation of the coordinates between current and
+        // previous track point assuming a constant speed in between
+        long imgDate;
+        while(i >= 0 && (imgDate = dateImgLst.get(i).time.getTime()/1000) >= prevDateWp) {
+
+            if(dateImgLst.get(i).pos == null) {
+                // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless
+                // variable
+                double timeDiff = (double)(imgDate - prevDateWp) / interval;
+                dateImgLst.get(i).setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
+                dateImgLst.get(i).speed = speed;
+
+                if (curElevation != null && prevElevation != null)
+                    dateImgLst.get(i).elevation = prevElevation + (curElevation - prevElevation) * timeDiff;
+
+                ret++;
+            }
+            i--;
+        }
+        return ret;
+    }
+
+    private int getLastIndexOfListBefore(ArrayList<ImageEntry> dateImgLst, long searchedDate) {
+        int lstSize= dateImgLst.size();
+
+        // No photos or the first photo taken is later than the search period
+        if(lstSize == 0 || searchedDate < dateImgLst.get(0).time.getTime()/1000)
+            return -1;
+
+        // The search period is later than the last photo
+        if (searchedDate > dateImgLst.get(lstSize - 1).time.getTime() / 1000)
+            return lstSize-1;
+
+        // The searched index is somewhere in the middle, do a binary search from the beginning
+        int curIndex= 0;
+        int startIndex= 0;
+        int endIndex= lstSize-1;
+        while (endIndex - startIndex > 1) {
+            curIndex= (int) Math.round((double)(endIndex + startIndex)/2);
+            if (searchedDate > dateImgLst.get(curIndex).time.getTime()/1000)
+                startIndex= curIndex;
+            else
+                endIndex= curIndex;
+        }
+        if (searchedDate < dateImgLst.get(endIndex).time.getTime()/1000)
+            return startIndex;
+
+        // This final loop is to check if photos with the exact same EXIF time follows
+        while ((endIndex < (lstSize-1)) && (dateImgLst.get(endIndex).time.getTime()
+                                                == dateImgLst.get(endIndex + 1).time.getTime()))
+            endIndex++;
+        return endIndex;
+    }
+
+
+    private String formatTimezone(double timezone) {
+        StringBuffer ret = new StringBuffer();
+
+        if (timezone < 0) {
+            ret.append('-');
+            timezone = -timezone;
+        } else {
+            ret.append('+');
+        }
+        ret.append((long) timezone).append(':');
+        int minutes = (int) ((timezone % 1) * 60);
+        if (minutes < 10) {
+            ret.append('0');
+        }
+        ret.append(minutes);
+
+        return ret.toString();
+    }
+
+    private Float parseTimezone(String timezone) {
+        if (timezone.length() == 0) {
+            return new Float(0);
+        }
+
+        char sgnTimezone = '+';
+        StringBuffer hTimezone = new StringBuffer();
+        StringBuffer mTimezone = new StringBuffer();
+        int state = 1; // 1=start/sign, 2=hours, 3=minutes.
+        for (int i = 0; i < timezone.length(); i++) {
+            char c = timezone.charAt(i);
+            switch (c) {
+            case ' ' :
+                if (state != 2 || hTimezone.length() != 0) {
+                    return null;
+                }
+                break;
+            case '+' :
+            case '-' :
+                if (state == 1) {
+                    sgnTimezone = c;
+                    state = 2;
+                } else {
+                    return null;
+                }
+                break;
+            case ':' :
+            case '.' :
+                if (state == 2) {
+                    state = 3;
+                } else {
+                    return null;
+                }
+                break;
+            case '0' : case '1' : case '2' : case '3' : case '4' :
+            case '5' : case '6' : case '7' : case '8' : case '9' :
+                switch(state) {
+                case 1 :
+                case 2 :
+                    state = 2;
+                    hTimezone.append(c);
+                    break;
+                case 3 :
+                    mTimezone.append(c);
+                    break;
+                default :
+                    return null;
+                }
+                break;
+            default :
+                return null;
+            }
+        }
+
+        int h = 0;
+        int m = 0;
+        try {
+            h = Integer.parseInt(hTimezone.toString());
+            if (mTimezone.length() > 0) {
+                m = Integer.parseInt(mTimezone.toString());
+            }
+        } catch (NumberFormatException nfe) {
+            // Invalid timezone
+            return null;
+        }
+
+        if (h > 12 || m > 59 ) {
+            return null;
+        } else {
+            return new Float((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
+        }
+    }
+}
diff --git a/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/ImageDisplay.java b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/ImageDisplay.java
new file mode 100644
index 0000000..0bb96f0
--- /dev/null
+++ b/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/ImageDisplay.java
@@ -0,0 +1,607 @@
+// License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
+
+package org.openstreetmap.josm.plugins.agpifoj;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+
+import javax.swing.JComponent;
+
+public class ImageDisplay extends JComponent {
+
+    /** The file that is currently displayed */
+    private File file = null;
+
+    /** The image currently displayed */
+    private Image image = null;
+
+    /** The image currently displayed */
+    private boolean errorLoading = false;
+
+    /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
+     * each time the zoom is modified */
+    private Rectangle visibleRect = null;
+
+    /** When a selection is done, the rectangle of the selection (in image coordinates) */
+    private Rectangle selectedRect = null;
+
+    /** The tracker to load the images */
+    private MediaTracker tracker = new MediaTracker(this);
+
+    private String osdText = null;
+
+    /** The thread that reads the images. */
+    private class LoadImageRunnable implements Runnable {
+
+        File file = null;
+
+        public LoadImageRunnable(File file) {
+            this.file = file;
+        }
+
+        public void run() {
+            Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
+            tracker.addImage(img, 1);
+
+            // Wait for the end of loading
+            while (! tracker.checkID(1, true)) {
+                if (this.file != ImageDisplay.this.file) {
+                    // The file has changed
+                    tracker.removeImage(img);
+                    return;
+                }
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {
+                }
+            }
+
+            boolean error = tracker.isErrorID(1);
+            if (img != null && (img.getWidth(null) == 0 || img.getHeight(null) == 0)) {
+                error = true;
+            }
+
+            synchronized(ImageDisplay.this) {
+                if (this.file != ImageDisplay.this.file) {
+                    // The file has changed
+                    tracker.removeImage(img);
+                    return;
+                }
+                ImageDisplay.this.image = img;
+                visibleRect = new Rectangle(0, 0, img.getWidth(null), img.getHeight(null));
+                selectedRect = null;
+                errorLoading = error;
+            }
+            tracker.removeImage(img);
+            ImageDisplay.this.repaint();
+        }
+    }
+
+    private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
+
+        boolean mouseIsDragging = false;
+        long lastTimeForMousePoint = 0l;
+        Point mousePointInImg = null;
+
+        /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
+         * at the same place */
+        public void mouseWheelMoved(MouseWheelEvent e) {
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            mouseIsDragging = false;
+            selectedRect = null;
+
+            if (image == null) {
+                return;
+            }
+
+            // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
+            // on that mouse position. 
+            // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated 
+            // again if there was less than 1.5seconds since the last event.
+            System.out.println(e);
+            if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
+            	lastTimeForMousePoint = e.getWhen();
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+            }
+
+            // Applicate the zoom to the visible rectangle in image coordinates
+            if (e.getWheelRotation() > 0) {
+                visibleRect.width = visibleRect.width * 3 / 2;
+                visibleRect.height = visibleRect.height * 3 / 2;
+            } else {
+                visibleRect.width = visibleRect.width * 2 / 3;
+                visibleRect.height = visibleRect.height * 2 / 3;
+            }
+
+            // Check that the zoom doesn't exceed 2:1
+            if (visibleRect.width < getSize().width / 2) {
+                visibleRect.width = getSize().width / 2;
+            }
+            if (visibleRect.height < getSize().height / 2) {
+                visibleRect.height = getSize().height / 2;
+            }
+
+            // Set the same ratio for the visible rectangle and the display area
+            int hFact = visibleRect.height * getSize().width;
+            int wFact = visibleRect.width * getSize().height;
+            if (hFact > wFact) {
+                visibleRect.width = hFact / getSize().height;
+            } else {
+                visibleRect.height = wFact / getSize().width;
+            }
+
+            // The size of the visible rectangle is limited by the image size.
+            checkVisibleRectSize(image, visibleRect);
+
+            // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
+            Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+            visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
+            visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
+
+            // The position is also limited by the image size
+            checkVisibleRectPos(image, visibleRect);
+
+            synchronized(ImageDisplay.this) {
+                if (ImageDisplay.this.file == file) {
+                    ImageDisplay.this.visibleRect = visibleRect;
+                }
+            }
+            ImageDisplay.this.repaint();
+        }
+
+        /** Center the display on the point that has been clicked */
+        public void mouseClicked(MouseEvent e) {
+            // Move the center to the clicked point.
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                return;
+            }
+
+            if (e.getButton() != 1) {
+                return;
+            }
+
+            // Calculate the translation to set the clicked point the center of the view.
+            Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
+            Point center = getCenterImgCoord(visibleRect);
+
+            visibleRect.x += click.x - center.x;
+            visibleRect.y += click.y - center.y;
+
+            checkVisibleRectPos(image, visibleRect);
+
+            synchronized(ImageDisplay.this) {
+                if (ImageDisplay.this.file == file) {
+                    ImageDisplay.this.visibleRect = visibleRect;
+                }
+            }
+            ImageDisplay.this.repaint();
+        }
+
+        /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
+         * a picture part) */
+        public void mousePressed(MouseEvent e) {
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                return;
+            }
+
+            if (e.getButton() == 1) {
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                mouseIsDragging = true;
+                selectedRect = null;
+            } else if (e.getButton() == 3) {
+                mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                checkPointInVisibleRect(mousePointInImg, visibleRect);
+                mouseIsDragging = false;
+                selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
+                ImageDisplay.this.repaint();
+            } else {
+                mouseIsDragging = false;
+                selectedRect = null;
+            }
+        }
+
+        public void mouseDragged(MouseEvent e) {
+            if (! mouseIsDragging && selectedRect == null) {
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            if (mouseIsDragging) {
+                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                visibleRect.x += mousePointInImg.x - p.x;
+                visibleRect.y += mousePointInImg.y - p.y;
+                checkVisibleRectPos(image, visibleRect);
+                synchronized(ImageDisplay.this) {
+                    if (ImageDisplay.this.file == file) {
+                        ImageDisplay.this.visibleRect = visibleRect;
+                    }
+                }
+                ImageDisplay.this.repaint();
+
+            } else if (selectedRect != null) {
+                Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
+                checkPointInVisibleRect(p, visibleRect);
+                Rectangle rect = new Rectangle(
+                        (p.x < mousePointInImg.x ? p.x : mousePointInImg.x),
+                        (p.y < mousePointInImg.y ? p.y : mousePointInImg.y),
+                        (p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x),
+                        (p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y));
+                checkVisibleRectSize(image, rect);
+                checkVisibleRectPos(image, rect);
+                ImageDisplay.this.selectedRect = rect;
+                ImageDisplay.this.repaint();
+            }
+
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            if (! mouseIsDragging && selectedRect == null) {
+                return;
+            }
+
+            File file;
+            Image image;
+            Rectangle visibleRect;
+
+            synchronized (ImageDisplay.this) {
+                file = ImageDisplay.this.file;
+                image = ImageDisplay.this.image;
+                visibleRect = ImageDisplay.this.visibleRect;
+            }
+
+            if (image == null) {
+                mouseIsDragging = false;
+                selectedRect = null;
+                return;
+            }
+
+            if (mouseIsDragging) {
+                mouseIsDragging = false;
+
+            } else if (selectedRect != null) {
+                int oldWidth = selectedRect.width;
+                int oldHeight = selectedRect.height;
+
+                // Check that the zoom doesn't exceed 2:1
+                if (selectedRect.width < getSize().width / 2) {
+                    selectedRect.width = getSize().width / 2;
+                }
+                if (selectedRect.height < getSize().height / 2) {
+                    selectedRect.height = getSize().height / 2;
+                }
+
+                // Set the same ratio for the visible rectangle and the display area
+                int hFact = selectedRect.height * getSize().width;
+                int wFact = selectedRect.width * getSize().height;
+                if (hFact > wFact) {
+                    selectedRect.width = hFact / getSize().height;
+                } else {
+                    selectedRect.height = wFact / getSize().width;
+                }
+
+                // Keep the center of the selection
+                if (selectedRect.width != oldWidth) {
+                    selectedRect.x -= (selectedRect.width - oldWidth) / 2;
+                }
+                if (selectedRect.height != oldHeight) {
+                    selectedRect.y -= (selectedRect.height - oldHeight) / 2;
+                }
+
+                checkVisibleRectSize(image, selectedRect);
+                checkVisibleRectPos(image, selectedRect);
+
+                synchronized (ImageDisplay.this) {
+                    if (file == ImageDisplay.this.file) {
+                        ImageDisplay.this.visibleRect = selectedRect;
+                    }
+                }
+                selectedRect = null;
+                ImageDisplay.this.repaint();
+            }
+        }
+
+        public void mouseEntered(MouseEvent e) {
+        }
+
+        public void mouseExited(MouseEvent e) {
+        }
+
+        public void mouseMoved(MouseEvent e) {
+        }
+
+        private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
+            if (p.x < visibleRect.x) {
+                p.x = visibleRect.x;
+            }
+            if (p.x > visibleRect.x + visibleRect.width) {
+                p.x = visibleRect.x + visibleRect.width;
+            }
+            if (p.y < visibleRect.y) {
+                p.y = visibleRect.y;
+            }
+            if (p.y > visibleRect.y + visibleRect.height) {
+                p.y = visibleRect.y + visibleRect.height;
+            }
+        }
+    }
+
+    public ImageDisplay() {
+        ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
+        addMouseListener(mouseListener);
+        addMouseWheelListener(mouseListener);
+        addMouseMotionListener(mouseListener);
+    }
+
+    public void setImage(File file) {
+        synchronized(this) {
+            this.file = file;
+            image = null;
+            selectedRect = null;
+            errorLoading = false;
+        }
+        repaint();
+        if (file != null) {
+            new Thread(new LoadImageRunnable(file)).start();
+        }
+    }
+
+    public void setOsdText(String text) {
+        this.osdText = text;
+    }
+
+    public void paintComponent(Graphics g) {
+        Image image;
+        File file;
+        Rectangle visibleRect;
+        boolean errorLoading;
+
+        synchronized(this) {
+            image = this.image;
+            file = this.file;
+            visibleRect = this.visibleRect;
+            errorLoading = this.errorLoading;
+        }
+
+        if (file == null) {
+            g.setColor(Color.black);
+            String noImageStr = tr("No image");
+            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
+            Dimension size = getSize();
+            g.drawString(noImageStr,
+                         (int) ((size.width - noImageSize.getWidth()) / 2),
+                         (int) ((size.height - noImageSize.getHeight()) / 2));
+        } else if (image == null) {
+            g.setColor(Color.black);
+            String loadingStr;
+            if (! errorLoading) {;
+                loadingStr = tr("Loading {0}", file.getName());
+            } else {
+                loadingStr = tr("Error on file {0}", file.getName());
+            }
+            Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
+            Dimension size = getSize();
+            g.drawString(loadingStr,
+                         (int) ((size.width - noImageSize.getWidth()) / 2),
+                         (int) ((size.height - noImageSize.getHeight()) / 2));
+        } else {
+            Rectangle target = calculateDrawImageRectangle(visibleRect);
+            g.drawImage(image,
+                        target.x, target.y, target.x + target.width, target.y + target.height,
+                        visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
+                        null);
+            if (selectedRect != null) {
+                Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
+                Point bottomRight = img2compCoord(visibleRect,
+                                                  selectedRect.x + selectedRect.width,
+                                                  selectedRect.y + selectedRect.height);
+                g.setColor(new Color(128, 128, 128, 180));
+                g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
+                g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
+                g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
+                g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
+                g.setColor(Color.black);
+                g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
+            }
+            if (errorLoading) {
+                String loadingStr = tr("Error on file {0}", file.getName());
+                Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
+                Dimension size = getSize();
+                g.drawString(loadingStr,
+                             (int) ((size.width - noImageSize.getWidth()) / 2),
+                             (int) ((size.height - noImageSize.getHeight()) / 2));
+            }
+            if (osdText != null) {
+                FontMetrics metrics = g.getFontMetrics(g.getFont());
+                int ascent = metrics.getAscent();
+                Color bkground = new Color(255, 255, 255, 128);
+                int lastPos = 0;
+                int pos = osdText.indexOf("\n");
+                int x = 3;
+                int y = 3;
+                String line;
+                while (pos > 0) {
+                    line = osdText.substring(lastPos, pos);
+                    Rectangle2D lineSize = metrics.getStringBounds(line, g);
+                    g.setColor(bkground);
+                    g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
+                    g.setColor(Color.black);
+                    g.drawString(line, x, y + ascent);
+                    y += (int) lineSize.getHeight();
+                    lastPos = pos + 1;
+                    pos = osdText.indexOf("\n", lastPos);
+                }
+
+                line = osdText.substring(lastPos);
+                Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
+                g.setColor(bkground);
+                g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
+                g.setColor(Color.black);
+                g.drawString(line, x, y + ascent);
+            }
+        }
+    }
+
+    private final Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
+        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+        return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
+                         drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
+    }
+
+    private final Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
+        Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
+        return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
+                         visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
+    }
+
+   private final Point getCenterImgCoord(Rectangle visibleRect) {
+        return new Point(visibleRect.x + visibleRect.width / 2,
+                                 visibleRect.y + visibleRect.height / 2);
+    }
+
+    private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
+        Dimension size = getSize();
+        int x, y, w, h;
+        x = 0;
+        y = 0;
+        w = size.width;
+        h = size.height;
+
+        int wFact = w * visibleRect.height;
+        int hFact = h * visibleRect.width;
+        if (wFact != hFact) {
+            if (wFact > hFact) {
+                w = hFact / visibleRect.height;
+                x = (size.width - w) / 2;
+            } else {
+                h = wFact / visibleRect.width;
+                y = (size.height - h) / 2;
+            }
+        }
+        return new Rectangle(x, y, w, h);
+    }
+
+    public void zoomBestFitOrOne() {
+        File file;
+        Image image;
+        Rectangle visibleRect;
+
+        synchronized (this) {
+            file = ImageDisplay.this.file;
+            image = ImageDisplay.this.image;
+            visibleRect = ImageDisplay.this.visibleRect;
+        }
+
+        if (image == null) {
+            return;
+        }
+
+        if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
+            // The display is not at best fit. => Zoom to best fit
+            visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
+
+        } else {
+            // The display is at best fit => zoom to 1:1
+            Point center = getCenterImgCoord(visibleRect);
+            visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
+                                        getWidth(), getHeight());
+            checkVisibleRectPos(image, visibleRect);
+        }
+
+        synchronized(this) {
+            if (file == this.file) {
+                this.visibleRect = visibleRect;
+            }
+        }
+        repaint();
+    }
+
+    private final void checkVisibleRectPos(Image image, Rectangle visibleRect) {
+        if (visibleRect.x < 0) {
+            visibleRect.x = 0;
+        }
+        if (visibleRect.y < 0) {
+            visibleRect.y = 0;
+        }
+        if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
+            visibleRect.x = image.getWidth(null) - visibleRect.width;
+        }
+        if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
+            visibleRect.y = image.getHeight(null) - visibleRect.height;
+        }
+    }
+
+    private void checkVisibleRectSize(Image image, Rectangle visibleRect) {
+        if (visibleRect.width > image.getWidth(null)) {
+            visibleRect.width = image.getWidth(null);
+        }
+        if (visibleRect.height > image.getHeight(null)) {
+            visibleRect.height = image.getHeight(null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/colorscheme/LICENSE b/colorscheme/LICENSE
new file mode 100644
index 0000000..37b7a4d
--- /dev/null
+++ b/colorscheme/LICENSE
@@ -0,0 +1,339 @@
+      GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+      How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/colorscheme/build.xml b/colorscheme/build.xml
new file mode 100644
index 0000000..3cde739
--- /dev/null
+++ b/colorscheme/build.xml
@@ -0,0 +1,57 @@
+<project name="colorscheme" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}">
+            <fileset dir="src">
+                <include name="*.xml"/>
+            </fileset>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Christof Dallermassl"/>
+                <attribute name="Plugin-Class" value="at.dallermassl.josm.plugin.colorscheme.ColorSchemePlugin" />
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Allows the user to create different color schemes and to switch between them. Just change the colors and create a new scheme. Used to switch to a white background with matching colors for better visibility in bright sunlight. See dialog in JOSM's preferences and 'Map Settings' (strange but true :-)" />
+                <attribute name="Plugin-Mainversion" value="1742"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/colorscheme/copyright.txt b/colorscheme/copyright.txt
new file mode 100644
index 0000000..871ae08
--- /dev/null
+++ b/colorscheme/copyright.txt
@@ -0,0 +1,6 @@
+Plugin colorscheme 
+
+This plugin is copyrighted 2008-2009 
+by Christof Dallermassl <christof at dallermassl.at>.
+
+It is distributed under GPL-v2 license (see file  LICENSE in this directory).
diff --git a/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePlugin.java b/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePlugin.java
new file mode 100644
index 0000000..6bd746b
--- /dev/null
+++ b/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePlugin.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.colorscheme;
+
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+
+/**
+ * ColorScheme Plugin for JOSM.
+ * @author cdaller
+ *
+ */
+public class ColorSchemePlugin extends Plugin {
+
+    /**
+     * Default Constructor
+     */
+    public ColorSchemePlugin() {
+
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new ColorSchemePreference();
+    }
+
+
+
+}
diff --git a/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePreference.java b/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePreference.java
new file mode 100644
index 0000000..75a65c7
--- /dev/null
+++ b/colorscheme/src/at/dallermassl/josm/plugin/colorscheme/ColorSchemePreference.java
@@ -0,0 +1,190 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.colorscheme;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.swing.Box;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.ColorPreference;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.tools.GBC;
+
+public class ColorSchemePreference implements PreferenceSetting {
+    private static final String PREF_KEY_SCHEMES_PREFIX = "colorschemes.";
+    private static final String PREF_KEY_SCHEMES_NAMES = PREF_KEY_SCHEMES_PREFIX + "names";
+    public static final String PREF_KEY_COLOR_PREFIX = "color.";
+    private JList schemesList;
+    private DefaultListModel listModel;
+    private List<String>colorKeys;
+    private ColorPreference colorPreference;
+
+    /**
+     * Default Constructor
+     */
+    public ColorSchemePreference() {
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.josm.gui.preferences.PreferenceSetting#addGui(org.openstreetmap.josm.gui.preferences.PreferenceDialog)
+     */
+    public void addGui(final PreferenceDialog gui) {
+        Map<String, String> colorMap = Main.pref.getAllPrefix(PREF_KEY_COLOR_PREFIX);
+        colorKeys = new ArrayList<String>(colorMap.keySet());
+        Collections.sort(colorKeys);
+        listModel = new DefaultListModel();
+        schemesList = new JList(listModel);
+        String schemes = Main.pref.get(PREF_KEY_SCHEMES_NAMES);
+        StringTokenizer st = new StringTokenizer(schemes, ";");
+        String schemeName;
+        while (st.hasMoreTokens()) {
+            schemeName = st.nextToken();
+            listModel.addElement(schemeName);
+        }
+
+        JButton useScheme = new JButton(tr("Use"));
+        useScheme.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (schemesList.getSelectedIndex() == -1)
+                    JOptionPane.showMessageDialog(Main.parent, tr("Please select a scheme to use."));
+                else {
+                    String schemeName = (String) listModel.get(schemesList.getSelectedIndex());
+                    getColorPreference(gui).setColorModel(getColorMap(schemeName));
+                }
+            }
+        });
+        JButton addScheme = new JButton(tr("Add"));
+        addScheme.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                String schemeName = JOptionPane.showInputDialog(Main.parent, tr("Color Scheme"));
+                if (schemeName == null)
+                    return;
+                schemeName = schemeName.replaceAll("\\.", "_");
+                setColorScheme(schemeName, getColorPreference(gui).getColorModel());
+                listModel.addElement(schemeName);
+                saveSchemeNamesToPref();
+            }
+        });
+
+        JButton deleteScheme = new JButton(tr("Delete"));
+        deleteScheme.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (schemesList.getSelectedIndex() == -1)
+                    JOptionPane.showMessageDialog(Main.parent, tr("Please select the scheme to delete."));
+                else {
+                    String schemeName = (String) listModel.get(schemesList.getSelectedIndex());
+                    removeColorSchemeFromPreferences(schemeName);
+                    listModel.remove(schemesList.getSelectedIndex());
+                    saveSchemeNamesToPref();
+                }
+            }
+        });
+        schemesList.setVisibleRowCount(3);
+
+        //schemesList.setToolTipText(tr("The sources (url or filename) of annotation preset definition files. See http://josm.eigenheimstrasse.de/wiki/AnnotationPresets for help."));
+        useScheme.setToolTipText(tr("Use the selected scheme from the list."));
+        addScheme.setToolTipText(tr("Use the current colors as a new color scheme."));
+        deleteScheme.setToolTipText(tr("Delete the selected scheme from the list."));
+
+        gui.map.add(new JLabel(tr("Color Schemes")), GBC.eol().insets(0,5,0,0));
+        gui.map.add(new JScrollPane(schemesList), GBC.eol().fill(GBC.BOTH));
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        gui.map.add(buttonPanel, GBC.eol().fill(GBC.HORIZONTAL));
+        buttonPanel.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
+        buttonPanel.add(useScheme, GBC.std().insets(0,5,5,0));
+        buttonPanel.add(addScheme, GBC.std().insets(0,5,5,0));
+        buttonPanel.add(deleteScheme, GBC.std().insets(0,5,5,0));
+    }
+
+    /**
+     * Saves the names of the schemes to the preferences.
+     */
+    public void saveSchemeNamesToPref() {
+        if (schemesList.getModel().getSize() > 0) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < schemesList.getModel().getSize(); ++i)
+                sb.append(";"+schemesList.getModel().getElementAt(i));
+            Main.pref.put(PREF_KEY_SCHEMES_NAMES, sb.toString().substring(1));
+        } else
+            Main.pref.put(PREF_KEY_SCHEMES_NAMES, null);
+    }
+
+    public boolean ok() {
+        return false;// nothing to do
+    }
+
+    /**
+     * Remove all color entries for the given scheme from the preferences.
+     * @param schemeName the name of the scheme.
+     */
+    public void removeColorSchemeFromPreferences(String schemeName) {
+        // delete color entries for scheme in preferences:
+        Map<String, String> colors = Main.pref.getAllPrefix(PREF_KEY_SCHEMES_PREFIX + schemeName + ".");
+        for(String key : colors.keySet()) {
+            Main.pref.put(key, null);
+        }
+    }
+
+    /**
+     * Copy all color entries from the given map to entries in preferences with the scheme name.
+     * @param schemeName the name of the scheme.
+     * @param the map containing the color key (without prefix) and the html color values.
+     */
+    public void setColorScheme(String schemeName, Map<String, String> colorMap) {
+        String key;
+        for(String colorKey : colorMap.keySet()) {
+            key = PREF_KEY_SCHEMES_PREFIX + schemeName + "." + PREF_KEY_COLOR_PREFIX + colorKey;
+            Main.pref.put(key, colorMap.get(colorKey));
+        }
+    }
+
+    /**
+     * Reads all colors for a scheme and returns them in a map (key = color key without prefix,
+     * value = html color code).
+     * @param schemeName the name of the scheme.
+     */
+    public Map<String, String> getColorMap(String schemeName) {
+        String colorKey;
+        String prefix = PREF_KEY_SCHEMES_PREFIX + schemeName + "." + PREF_KEY_COLOR_PREFIX;
+        Map<String, String>colorMap = new HashMap<String, String>();
+        for(String schemeColorKey : Main.pref.getAllPrefix(prefix).keySet()) {
+            colorKey = schemeColorKey.substring(prefix.length());
+            colorMap.put(colorKey, Main.pref.get(schemeColorKey));
+        }
+        return colorMap;
+    }
+
+    public ColorPreference getColorPreference(PreferenceDialog gui) {
+        if(colorPreference == null) {
+            for(PreferenceSetting setting : gui.getSettings()) {
+                if(setting instanceof ColorPreference) {
+                    colorPreference = (ColorPreference) setting;
+                    break;
+                }
+            }
+        }
+        return colorPreference;
+    }
+}
diff --git a/livegps/.classpath b/livegps/.classpath
new file mode 100644
index 0000000..81a65e8
--- /dev/null
+++ b/livegps/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sun-jdk-1.5.0.15"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/livegps/.project b/livegps/.project
new file mode 100644
index 0000000..29f9c6a
--- /dev/null
+++ b/livegps/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>livegps</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/livegps/README b/livegps/README
new file mode 100644
index 0000000..373df65
--- /dev/null
+++ b/livegps/README
@@ -0,0 +1,14 @@
+liveGPS plugin for josm
+-----------------------
+
+Author: Frederik Ramm <frederik at remote.org>
+Public Domain.
+
+* Install like any other plugin
+* start the unix program "gpsd" on any machine in the network
+  (http://gpsd.berlios.de/)
+* run josm
+* activate track acquisition in "LiveGPS" menu
+
+Some problems exist!
+
diff --git a/livegps/build.xml b/livegps/build.xml
new file mode 100644
index 0000000..1f7acd1
--- /dev/null
+++ b/livegps/build.xml
@@ -0,0 +1,57 @@
+<project name="livegps" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Frederik Ramm"/>
+                <attribute name="Plugin-Class" value="livegps.LiveGpsPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Support live GPS input (moving dot) through a connection to gpsd server."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/LiveGPS"/>
+                <attribute name="Plugin-Mainversion" value="1890"/>
+                <attribute name="Plugin-Stage" value="50"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/livegps/images/autocentermenu.png b/livegps/images/autocentermenu.png
new file mode 100644
index 0000000..2b83bcf
Binary files /dev/null and b/livegps/images/autocentermenu.png differ
diff --git a/livegps/images/capturemenu.png b/livegps/images/capturemenu.png
new file mode 100644
index 0000000..232dbcd
Binary files /dev/null and b/livegps/images/capturemenu.png differ
diff --git a/livegps/images/centermenu.png b/livegps/images/centermenu.png
new file mode 100644
index 0000000..37a8c65
Binary files /dev/null and b/livegps/images/centermenu.png differ
diff --git a/livegps/images/dialogs/livegps.png b/livegps/images/dialogs/livegps.png
new file mode 100644
index 0000000..c2f281b
Binary files /dev/null and b/livegps/images/dialogs/livegps.png differ
diff --git a/livegps/src/livegps/LiveGpsAcquirer.java b/livegps/src/livegps/LiveGpsAcquirer.java
new file mode 100644
index 0000000..9d33d4d
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsAcquirer.java
@@ -0,0 +1,226 @@
+package livegps;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.Main;
+
+public class LiveGpsAcquirer implements Runnable {
+    Socket gpsdSocket;
+    BufferedReader gpsdReader;
+    boolean connected = false;
+    String gpsdHost = Main.pref.get("livegps.gpsd.host","localhost");
+    int gpsdPort = Main.pref.getInteger("livegps.gpsd.port", 2947);
+    boolean shutdownFlag = false;
+    private List<PropertyChangeListener> propertyChangeListener = new ArrayList<PropertyChangeListener>();
+    private PropertyChangeEvent lastStatusEvent;
+    private PropertyChangeEvent lastDataEvent;
+
+    /**
+     * Adds a property change listener to the acquirer.
+     * @param listener the new listener
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        if(!propertyChangeListener.contains(listener)) {
+            propertyChangeListener.add(listener);
+        }
+    }
+
+    /**
+     * Remove a property change listener from the acquirer.
+     * @param listener the new listener
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        if(propertyChangeListener.contains(listener)) {
+            propertyChangeListener.remove(listener);
+        }
+    }
+
+    /**
+     * Fire a gps status change event. Fires events with key "gpsstatus" and a {@link LiveGpsStatus}
+     * object as value.
+     * @param status the status.
+     * @param statusMessage the status message.
+     */
+    public void fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus status, String statusMessage) {
+        PropertyChangeEvent event = new PropertyChangeEvent(this, "gpsstatus", null, new LiveGpsStatus(status, statusMessage));
+        if(!event.equals(lastStatusEvent)) {
+            firePropertyChangeEvent(event);
+            lastStatusEvent = event;
+        }
+    }
+
+    /**
+     * Fire a gps data change event to all listeners. Fires events with key "gpsdata" and a
+     * {@link LiveGpsData} object as values.
+     * @param oldData the old gps data.
+     * @param newData the new gps data.
+     */
+    public void fireGpsDataChangeEvent(LiveGpsData oldData, LiveGpsData newData) {
+        PropertyChangeEvent event = new PropertyChangeEvent(this, "gpsdata", oldData, newData);
+        if(!event.equals(lastDataEvent)) {
+            firePropertyChangeEvent(event);
+            lastDataEvent = event;
+        }
+    }
+
+    /**
+     * Fires the given event to all listeners.
+     * @param event the event to fire.
+     */
+    protected void firePropertyChangeEvent(PropertyChangeEvent event) {
+        for (PropertyChangeListener listener : propertyChangeListener) {
+            listener.propertyChange(event);
+        }
+    }
+
+    public void run() {
+        LiveGpsData oldGpsData = null;
+        LiveGpsData gpsData = null;
+        shutdownFlag = false;
+        while(!shutdownFlag) {
+            double lat = 0;
+            double lon = 0;
+            float speed = 0;
+            float course = 0;
+            boolean haveFix = false;
+
+            try
+            {
+                if (!connected)
+                {
+                    System.out.println("LiveGps tries to connect to gpsd");
+                    fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.CONNECTING, tr("Connecting"));
+                    InetAddress[] addrs = InetAddress.getAllByName(gpsdHost);
+                    for (int i=0; i < addrs.length && gpsdSocket == null; i++) {
+                        try {
+                            gpsdSocket = new Socket(addrs[i], gpsdPort);
+                            break;
+                        } catch (Exception e) {
+                            System.out.println("LiveGps: Could not open connection to gpsd: " + e);
+                            gpsdSocket = null;
+                        }
+                    }
+
+                    if (gpsdSocket != null)
+                    {
+                        gpsdReader = new BufferedReader(new InputStreamReader(gpsdSocket.getInputStream()));
+                        gpsdSocket.getOutputStream().write(new byte[] { 'w', 13, 10 });
+                        fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.CONNECTING, tr("Connecting"));
+                        connected = true;
+                    System.out.println("LiveGps: Connected to gpsd");
+                    }
+                }
+
+
+                if(connected) {
+                    // <FIXXME date="23.06.2007" author="cdaller">
+                    // TODO this read is blocking if gps is connected but has no fix, so gpsd does not send positions
+                    String line = gpsdReader.readLine();
+                    // </FIXXME>
+                    if (line == null) break;
+                    String words[] = line.split(",");
+
+                    if ((words.length == 0) || (!words[0].equals("GPSD"))) {
+                        // unexpected response.
+                        continue;
+                    }
+
+                    for (int i = 1; i < words.length; i++) {
+
+                        if ((words[i].length() < 2) || (words[i].charAt(1) != '=')) {
+                            // unexpected response.
+                            continue;
+                        }
+
+                        char what = words[i].charAt(0);
+                        String value = words[i].substring(2);
+                        oldGpsData = gpsData;
+                        gpsData = new LiveGpsData();
+                        switch(what) {
+                        case 'O':
+                            // full report, tab delimited.
+                            String[] status = value.split("\\s+");
+                            if (status.length >= 5) {
+                                lat = Double.parseDouble(status[3]);
+                                lon = Double.parseDouble(status[4]);
+                                try {
+                                    speed = Float.parseFloat(status[9]);
+                                    course = Float.parseFloat(status[8]);
+                                    //view.setSpeed(speed);
+                                    //view.setCourse(course);
+                                } catch (NumberFormatException nex) {}
+                                haveFix = true;
+                            }
+                            break;
+                        case 'P':
+                            // position report, tab delimited.
+                            String[] pos = value.split("\\s+");
+                            if (pos.length >= 2) {
+                                lat = Double.parseDouble(pos[0]);
+                                lon = Double.parseDouble(pos[1]);
+                                speed = Float.NaN;
+                                course = Float.NaN;
+                                haveFix = true;
+                            }
+                        default:
+                            // not interested
+                        }
+                        fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.CONNECTED, tr("Connected"));
+                        gpsData.setFix(haveFix);
+                        if (haveFix) {
+                            //view.setCurrentPosition(lat, lon);
+                            gpsData.setLatLon(new LatLon(lat, lon));
+                            gpsData.setSpeed(speed);
+                            gpsData.setCourse(course);
+                            fireGpsDataChangeEvent(oldGpsData, gpsData);
+                        }
+                    }
+                } else {
+                    // not connected:
+                    fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.DISCONNECTED, tr("Not connected"));
+                    try { Thread.sleep(1000); } catch (InterruptedException ignore) {};
+                }
+            } catch(IOException iox) {
+                connected = false;
+                if(gpsData != null) {
+                    gpsData.setFix(false);
+                    fireGpsDataChangeEvent(oldGpsData, gpsData);
+                }
+                fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.CONNECTION_FAILED, tr("Connection Failed"));
+                try { Thread.sleep(1000); } catch (InterruptedException ignore) {};
+                // send warning to layer
+
+            }
+        }
+
+        fireGpsStatusChangeEvent(LiveGpsStatus.GpsStatus.DISCONNECTED, tr("Not connected"));
+        if (gpsdSocket != null) {
+            try {
+              gpsdSocket.close();
+              gpsdSocket = null;
+                  System.out.println("LiveGps: Disconnected from gpsd");
+            }
+            catch (Exception e) {
+              System.out.println("LiveGps: Unable to close socket; reconnection may not be possible");
+            }
+        }
+    }
+
+    public void shutdown()
+    {
+        shutdownFlag = true;
+    }
+}
diff --git a/livegps/src/livegps/LiveGpsData.java b/livegps/src/livegps/LiveGpsData.java
new file mode 100644
index 0000000..c547713
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsData.java
@@ -0,0 +1,207 @@
+/**
+ *
+ */
+package livegps;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Point;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Way;
+
+/**
+ * @author cdaller
+ *
+ */
+public class LiveGpsData {
+    private LatLon latLon;
+    private float course;
+    private float speed;
+    private boolean fix;
+    private String wayString;
+    private Way way;
+
+    /**
+     * @param latitude
+     * @param longitude
+     * @param course
+     * @param speed
+     * @param haveFix
+     */
+    public LiveGpsData(double latitude, double longitude, float course, float speed, boolean haveFix) {
+        super();
+        this.latLon = new LatLon(latitude, longitude);
+        this.course = course;
+        this.speed = speed;
+        this.fix = haveFix;
+    }
+    /**
+     *
+     */
+    public LiveGpsData() {
+        // TODO Auto-generated constructor stub
+    }
+    /**
+     * @return the course
+     */
+    public float getCourse() {
+        return this.course;
+    }
+    /**
+     * @param course the course to set
+     */
+    public void setCourse(float course) {
+        this.course = course;
+    }
+    /**
+     * @return the haveFix
+     */
+    public boolean isFix() {
+        return this.fix;
+    }
+    /**
+     * @param haveFix the haveFix to set
+     */
+    public void setFix(boolean haveFix) {
+        this.fix = haveFix;
+    }
+    /**
+     * @return the latitude
+     */
+    public double getLatitude() {
+        return this.latLon.lat();
+    }
+    /**
+     * @return the longitude
+     */
+    public double getLongitude() {
+        return this.latLon.lon();
+    }
+    /**
+     * @return the speed in metres per second!
+     */
+    public float getSpeed() {
+        return this.speed;
+    }
+    /**
+     * @param speed the speed to set
+     */
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * @return the latlon
+     */
+    public LatLon getLatLon() {
+        return this.latLon;
+    }
+
+    /**
+     * @param latLon
+     */
+    public void setLatLon(LatLon latLon) {
+        this.latLon = latLon;
+    }
+
+    public String toString() {
+        return getClass().getSimpleName() + "[fix=" + fix + ", lat=" + latLon.lat()
+        + ", long=" + latLon.lon() + ", speed=" + speed + ", course=" + course + "]";
+    }
+
+    /**
+     * Returns the name of the way that is closest to the current coordinates or an
+     * empty string if no way is around.
+     *
+     * @return the name of the way that is closest to the current coordinates.
+     */
+    public String getWayInfo() {
+        if(wayString == null) {
+            Way way = getWay();
+            if(way != null) {
+                StringBuilder builder = new StringBuilder();
+                String tmp = way.get("name");
+                if(tmp != null) {
+                    builder.append(tmp);
+                } else {
+                    builder.append(tr("no name"));
+                }
+                tmp = way.get("ref");
+                if(tmp != null) {
+                    builder.append(" (").append(tmp).append(")");
+                }
+                tmp = way.get("highway");
+                if(tmp != null) {
+                    builder.append(" {").append(tmp).append("}");
+                }
+                String type = "";
+                tmp = way.get("tunnel");
+                if(tmp != null) {
+                    type = type + "T";
+                }
+                tmp = way.get("bridge");
+                if(tmp != null) {
+                    type = type + "B";
+                }
+                if(type.length() > 0) {
+                    builder.append(" [").append(type).append("]");
+                }
+                wayString = builder.toString();
+            } else {
+                wayString = "";
+            }
+        }
+        return wayString;
+    }
+
+    /**
+     * Returns the closest way to this position.
+     * @return the closest way to this position.
+     */
+    public Way getWay() {
+        if(way == null && Main.map != null && Main.map.mapView != null) {
+            Point xy = Main.map.mapView.getPoint(getLatLon());
+            way = Main.map.mapView.getNearestWay(xy);
+        }
+        return way;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Float.floatToIntBits(this.course);
+        result = prime * result + ((this.latLon == null) ? 0 : this.latLon.hashCode());
+        result = prime * result + Float.floatToIntBits(this.speed);
+        return result;
+    }
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final LiveGpsData other = (LiveGpsData) obj;
+        if (Float.floatToIntBits(this.course) != Float.floatToIntBits(other.course))
+            return false;
+        if (this.latLon == null) {
+            if (other.latLon != null)
+                return false;
+        } else if (!this.latLon.equals(other.latLon))
+            return false;
+        if (Float.floatToIntBits(this.speed) != Float.floatToIntBits(other.speed))
+            return false;
+        return true;
+    }
+}
diff --git a/livegps/src/livegps/LiveGpsDialog.java b/livegps/src/livegps/LiveGpsDialog.java
new file mode 100644
index 0000000..dd42d0e
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsDialog.java
@@ -0,0 +1,108 @@
+/**
+ *
+ */
+package livegps;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.GridLayout;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * @author cdaller
+ *
+ */
+public class LiveGpsDialog extends ToggleDialog implements PropertyChangeListener {
+    private static final long serialVersionUID = 6183400754671501117L;
+    private JLabel statusLabel;
+    private JLabel wayLabel;
+    private JLabel latLabel;
+    private JLabel longLabel;
+    private JLabel courseLabel;
+    private JLabel speedLabel;
+    private JPanel panel;
+
+    /**
+     * @param name
+     * @param iconName
+     * @param tooltip
+     * @param shortcut
+     * @param preferredHeight
+     */
+    public LiveGpsDialog(final MapFrame mapFrame) {
+        super(tr("Live GPS"), "livegps", tr("Show GPS data."),
+        Shortcut.registerShortcut("subwindow:livegps", tr("Toggle: {0}", tr("Live GPS")),
+        KeyEvent.VK_G, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 100);
+        panel = new JPanel();
+        panel.setLayout(new GridLayout(6,2));
+        panel.add(new JLabel(tr("Status")));
+        panel.add(statusLabel = new JLabel());
+        panel.add(new JLabel(tr("Way Info")));
+        panel.add(wayLabel = new JLabel());
+        panel.add(new JLabel(tr("Latitude")));
+        panel.add(latLabel = new JLabel());
+        panel.add(new JLabel(tr("Longitude")));
+        panel.add(longLabel = new JLabel());
+        panel.add(new JLabel(tr("Speed")));
+        panel.add(speedLabel = new JLabel());
+        panel.add(new JLabel(tr("Course")));
+        panel.add(courseLabel = new JLabel());
+        add(new JScrollPane(panel), BorderLayout.CENTER);
+    }
+
+    /* (non-Javadoc)
+     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+     */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (!isVisible())
+            return;
+        if("gpsdata".equals(evt.getPropertyName())) {
+            LiveGpsData data = (LiveGpsData) evt.getNewValue();
+            if(data.isFix()) {
+//                fixLabel.setText("fix");
+                panel.setBackground(Color.WHITE);
+                latLabel.setText(data.getLatitude() + "deg");
+                longLabel.setText(data.getLongitude() + "deg");
+                double mySpeed = data.getSpeed() * 3.6f;
+                speedLabel.setText((Math.round(mySpeed*100)/100) + "km/h"); // m(s to km/h
+                courseLabel.setText(data.getCourse() + "deg");
+
+                String wayString = data.getWayInfo();
+                if(wayString.length() > 0) {
+                    wayLabel.setText(wayString);
+                } else {
+                    wayLabel.setText(tr("unknown"));
+                }
+
+            } else {
+//                fixLabel.setText("no fix");
+                latLabel.setText("");
+                longLabel.setText("");
+                speedLabel.setText("");
+                courseLabel.setText("");
+                panel.setBackground(Color.RED);
+            }
+        } else if ("gpsstatus".equals(evt.getPropertyName())) {
+            LiveGpsStatus status = (LiveGpsStatus) evt.getNewValue();
+            statusLabel.setText(status.getStatusMessage());
+            if(status.getStatus() != LiveGpsStatus.GpsStatus.CONNECTED) {
+                panel.setBackground(Color.RED);
+            } else {
+                panel.setBackground(Color.WHITE);
+            }
+        }
+
+    }
+}
diff --git a/livegps/src/livegps/LiveGpsLayer.java b/livegps/src/livegps/LiveGpsLayer.java
new file mode 100644
index 0000000..a38c003
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsLayer.java
@@ -0,0 +1,157 @@
+package livegps;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.tools.ColorHelper;
+
+public class LiveGpsLayer extends GpxLayer implements PropertyChangeListener {
+    public static final String LAYER_NAME = tr("LiveGPS layer");
+    public static final String KEY_LIVEGPS_COLOR ="color.livegps.position";
+    LatLon lastPos;
+    WayPoint lastPoint;
+    GpxTrack trackBeingWritten;
+    Collection<WayPoint> trackSegment;
+    float speed;
+    float course;
+    String status;
+    //JLabel lbl;
+    boolean autocenter;
+    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+    public LiveGpsLayer(GpxData data)
+    {
+        super (data, LAYER_NAME);
+        trackBeingWritten = new GpxTrack();
+        trackBeingWritten.attr.put("desc", "josm live gps");
+        trackSegment = new ArrayList<WayPoint>();
+        trackBeingWritten.trackSegs.add(trackSegment);
+        data.tracks.add(trackBeingWritten);
+    }
+
+    void setCurrentPosition(double lat, double lon)
+    {
+        //System.out.println("adding pos " + lat + "," + lon);
+        LatLon thisPos = new LatLon(lat, lon);
+        if ((lastPos != null) && (thisPos.equalsEpsilon(lastPos))) {
+            // no change in position
+            // maybe show a "paused" cursor or some such
+            return;
+        }
+
+        lastPos = thisPos;
+        lastPoint = new WayPoint(thisPos);
+        lastPoint.attr.put("time", dateFormat.format(new Date()));
+        // synchronize when adding data, as otherwise the autosave action
+        // needs concurrent access and this results in an exception!
+        synchronized (LiveGpsLock.class) {
+            trackSegment.add(lastPoint);
+        }
+        if (autocenter) {
+            center();
+        }
+
+        //Main.map.repaint();
+    }
+
+    public void center()
+    {
+        if (lastPoint != null)
+            Main.map.mapView.zoomTo(lastPoint.getCoor());
+    }
+
+//  void setStatus(String status)
+//  {
+//      this.status = status;
+//      Main.map.repaint();
+//        System.out.println("LiveGps status: " + status);
+//  }
+
+    void setSpeed(float metresPerSecond)
+    {
+        speed = metresPerSecond;
+        //Main.map.repaint();
+    }
+
+    void setCourse(float degrees)
+    {
+        course = degrees;
+        //Main.map.repaint();
+    }
+
+    public void setAutoCenter(boolean ac)
+    {
+        autocenter = ac;
+    }
+
+    @Override public void paint(Graphics g, MapView mv)
+    {
+        //System.out.println("in paint");
+        synchronized (LiveGpsLock.class) {
+            //System.out.println("in synced paint");
+            super.paint(g, mv);
+//          int statusHeight = 50;
+//          Rectangle mvs = mv.getBounds();
+//          mvs.y = mvs.y + mvs.height - statusHeight;
+//          mvs.height = statusHeight;
+//          g.setColor(new Color(1.0f, 1.0f, 1.0f, 0.8f));
+//          g.fillRect(mvs.x, mvs.y, mvs.width, mvs.height);
+
+            if (lastPoint != null)
+            {
+                Point screen = mv.getPoint(lastPoint.getCoor());
+                g.setColor(Main.pref.getColor(KEY_LIVEGPS_COLOR, Color.RED));
+                g.drawOval(screen.x-10, screen.y-10,20,20);
+                g.drawOval(screen.x-9, screen.y-9,18,18);
+            }
+
+//          lbl.setText("gpsd: "+status+" Speed: " + speed + " Course: "+course);
+//          lbl.setBounds(0, 0, mvs.width-10, mvs.height-10);
+//          Graphics sub = g.create(mvs.x+5, mvs.y+5, mvs.width-10, mvs.height-10);
+//          lbl.paint(sub);
+
+//          if(status != null) {
+//          g.setColor(Color.WHITE);
+//          g.drawString("gpsd: " + status, 5, mv.getBounds().height - 15); // lower left corner
+//          }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+     */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if(!isVisible()) {
+            return;
+        }
+        if("gpsdata".equals(evt.getPropertyName())) {
+            LiveGpsData data = (LiveGpsData) evt.getNewValue();
+            if(data.isFix()) {
+                setCurrentPosition(data.getLatitude(), data.getLongitude());
+                if(!Float.isNaN(data.getSpeed())) {
+                    setSpeed(data.getSpeed());
+                }
+                if(!Float.isNaN(data.getCourse())) {
+                    setCourse(data.getCourse());
+                }
+                Main.map.repaint();
+            }
+        }
+    }
+}
diff --git a/livegps/src/livegps/LiveGpsLock.java b/livegps/src/livegps/LiveGpsLock.java
new file mode 100644
index 0000000..948674e
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsLock.java
@@ -0,0 +1,16 @@
+/**
+ *
+ */
+package livegps;
+
+/**
+ * This class is only used to prevent concurrent object modification. So all classes that
+ * read or write live gps data must synchronize to this class. Especially the save action
+ * takes quite long, so concurrency problems occur.
+ *
+ * @author cdaller
+ *
+ */
+public class LiveGpsLock {
+
+}
diff --git a/livegps/src/livegps/LiveGpsPlugin.java b/livegps/src/livegps/LiveGpsPlugin.java
new file mode 100644
index 0000000..5b29083
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsPlugin.java
@@ -0,0 +1,221 @@
+package livegps;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class LiveGpsPlugin extends Plugin implements LayerChangeListener
+{
+    private LiveGpsAcquirer acquirer = null;
+    private Thread acquirerThread = null;
+    private JMenu lgpsmenu;
+    private JCheckBoxMenuItem lgpscapture;
+    private JMenuItem lgpscenter;
+    private JCheckBoxMenuItem lgpsautocenter;
+    private LiveGpsDialog lgpsdialog;
+    List<PropertyChangeListener>listenerQueue;
+
+    private GpxData data = new GpxData();
+    private LiveGpsLayer lgpslayer = null;
+
+    public class CaptureAction extends JosmAction {
+        public CaptureAction() {
+            super(tr("Capture GPS Track"), "capturemenu", tr("Connect to gpsd server and show current position in LiveGPS layer."),
+                Shortcut.registerShortcut("menu:livegps:capture", tr("Menu: {0}", tr("Capture GPS Track")),
+                KeyEvent.VK_R, Shortcut.GROUP_MENU), true);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            enableTracking(lgpscapture.isSelected());
+        }
+    }
+
+    public class CenterAction extends JosmAction {
+        public CenterAction() {
+            super(tr("Center Once"), "centermenu", tr("Center the LiveGPS layer to current position."),
+            Shortcut.registerShortcut("edit:centergps", tr("Edit: {0}", tr("Center Once")),
+            KeyEvent.VK_HOME, Shortcut.GROUP_EDIT), true);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if(lgpslayer != null) {
+                lgpslayer.center();
+            }
+        }
+    }
+
+    public class AutoCenterAction extends JosmAction {
+        public AutoCenterAction() {
+            super(tr("Auto-Center"), "autocentermenu", tr("Continuously center the LiveGPS layer to current position."),
+            Shortcut.registerShortcut("menu:livegps:autocenter", tr("Menu: {0}", tr("Capture GPS Track")),
+            KeyEvent.VK_HOME, Shortcut.GROUP_MENU), true);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if(lgpslayer != null) {
+                setAutoCenter(lgpsautocenter.isSelected());
+            }
+        }
+    }
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+    }
+
+    public void layerAdded(Layer newLayer) {
+    }
+
+    public void layerRemoved(Layer oldLayer) {
+        if(oldLayer == lgpslayer)
+        {
+            enableTracking(false);
+            lgpscapture.setSelected(false);
+            removePropertyChangeListener(lgpslayer);
+            Layer.listeners.remove(this);
+            lgpslayer = null;
+        }
+    }
+
+    public LiveGpsPlugin()
+    {
+        MainMenu menu = Main.main.menu;
+        lgpsmenu = menu.addMenu(marktr("LiveGPS"), KeyEvent.VK_G, menu.defaultMenuPos);
+
+        JosmAction captureAction = new CaptureAction();
+        lgpscapture = new JCheckBoxMenuItem(captureAction);
+        lgpsmenu.add(lgpscapture);
+        lgpscapture.setAccelerator(captureAction.getShortcut().getKeyStroke());
+
+        JosmAction centerAction = new CenterAction();
+        JMenuItem centerMenu = new JMenuItem(centerAction);
+        lgpsmenu.add(centerMenu);
+        centerMenu.setAccelerator(centerAction.getShortcut().getKeyStroke());
+
+        JosmAction autoCenterAction = new AutoCenterAction();
+        lgpsautocenter = new JCheckBoxMenuItem(autoCenterAction);
+        lgpsmenu.add(lgpsautocenter);
+        lgpsautocenter.setAccelerator(autoCenterAction.getShortcut().getKeyStroke());
+    }
+
+    /**
+     * Set to <code>true</code> if the current position should always be in the center of the map.
+     * @param autoCenter if <code>true</code> the map is always centered.
+     */
+    public void setAutoCenter(boolean autoCenter) {
+        lgpsautocenter.setSelected(autoCenter); // just in case this method was not called from the menu
+        if(lgpslayer != null) {
+            lgpslayer.setAutoCenter(autoCenter);
+            if (autoCenter) lgpslayer.center();
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if autocenter is selected.
+     * @return <code>true</code> if autocenter is selected.
+     */
+    public boolean isAutoCenter() {
+        return lgpsautocenter.isSelected();
+    }
+
+    /**
+     * Enable or disable gps tracking
+     * @param enable if <code>true</code> tracking is started.
+     */
+    public void enableTracking(boolean enable) {
+        if ((acquirer != null) && (!enable))
+        {
+            acquirer.shutdown();
+            acquirerThread = null;
+        }
+        else if(enable)
+        {
+            if (acquirer == null) {
+                acquirer = new LiveGpsAcquirer();
+                if (lgpslayer == null) {
+                    lgpslayer = new LiveGpsLayer(data);
+                    Main.main.addLayer(lgpslayer);
+                    Layer.listeners.add(this);
+                    lgpslayer.setAutoCenter(isAutoCenter());
+                }
+                // connect layer with acquirer:
+                addPropertyChangeListener(lgpslayer);
+                // add all listeners that were added before the acquirer existed:
+                if(listenerQueue != null) {
+                    for(PropertyChangeListener listener : listenerQueue) {
+                        addPropertyChangeListener(listener);
+                    }
+                    listenerQueue.clear();
+                }
+            }
+            if(acquirerThread == null) {
+                acquirerThread = new Thread(acquirer);
+                acquirerThread.start();
+            }
+        }
+    }
+
+
+    /**
+     * Add a listener for gps events.
+     * @param listener the listener.
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        if(acquirer != null) {
+            acquirer.addPropertyChangeListener(listener);
+        } else {
+            if(listenerQueue == null) {
+                listenerQueue = new ArrayList<PropertyChangeListener>();
+            }
+            listenerQueue.add(listener);
+        }
+    }
+
+    /**
+     * Remove a listener for gps events.
+     * @param listener the listener.
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        if(acquirer != null)
+            acquirer.removePropertyChangeListener(listener);
+        else if(listenerQueue != null && listenerQueue.contains(listener))
+            listenerQueue.remove(listener);
+    }
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.josm.plugins.Plugin#mapFrameInitialized(org.openstreetmap.josm.gui.MapFrame, org.openstreetmap.josm.gui.MapFrame)
+     */
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if(newFrame != null) {
+            // add dialog
+            newFrame.addToggleDialog(lgpsdialog = new LiveGpsDialog(newFrame));
+            // connect listeners with acquirer:
+            addPropertyChangeListener(lgpsdialog);
+        }
+    }
+
+    /**
+     * @return the lgpsmenu
+     */
+    public JMenu getLgpsMenu() {
+        return this.lgpsmenu;
+    }
+}
diff --git a/livegps/src/livegps/LiveGpsStatus.java b/livegps/src/livegps/LiveGpsStatus.java
new file mode 100644
index 0000000..260298e
--- /dev/null
+++ b/livegps/src/livegps/LiveGpsStatus.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package livegps;
+
+/**
+ * @author cdaller
+ *
+ */
+public class LiveGpsStatus {
+    public enum GpsStatus {CONNECTING, CONNECTED, DISCONNECTED, CONNECTION_FAILED};
+    private String statusMessage;
+    private GpsStatus status;
+
+    /**
+     * @param status
+     * @param statusMessage
+     */
+    public LiveGpsStatus(GpsStatus status, String statusMessage) {
+        super();
+        this.status = status;
+        this.statusMessage = statusMessage;
+    }
+/**
+     * @return the status
+     */
+    public GpsStatus getStatus() {
+        return this.status;
+    }
+    /**
+     * @param status the status to set
+     */
+    public void setStatus(GpsStatus status) {
+        this.status = status;
+    }
+    /**
+     * @return the statusMessage
+     */
+    public String getStatusMessage() {
+        return this.statusMessage;
+    }
+    /**
+     * @param statusMessage the statusMessage to set
+     */
+    public void setStatusMessage(String statusMessage) {
+        this.statusMessage = statusMessage;
+    }
+
+}
diff --git a/measurement/.classpath b/measurement/.classpath
new file mode 100644
index 0000000..05929d7
--- /dev/null
+++ b/measurement/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sun-jdk-1.5.0"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/measurement/.project b/measurement/.project
new file mode 100644
index 0000000..51abf61
--- /dev/null
+++ b/measurement/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JOSM-Measurement</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/measurement/LICENSE b/measurement/LICENSE
new file mode 100644
index 0000000..bb78e55
--- /dev/null
+++ b/measurement/LICENSE
@@ -0,0 +1,7 @@
+
+Plugin measurement
+
+This plugins is copyrighted 2007-2008 by Raphael Mack <ramack at raphael-mack.de>.
+
+It is distributed under a GPL-3 license or later.
+
diff --git a/measurement/build.xml b/measurement/build.xml
new file mode 100644
index 0000000..8833eb3
--- /dev/null
+++ b/measurement/build.xml
@@ -0,0 +1,55 @@
+<project name="measurement" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Raphael Mack, Reza Mohammadi"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.measurement.MeasurementPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Provide a measurement dialog and a layer to measure length and angle of segments, area surrounded by a (simple) closed way and create measurement paths (which also can be imported from a gps layer)."/>
+                <attribute name="Plugin-Mainversion" value="2012"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/measurement/images/dialogs/measure.png b/measurement/images/dialogs/measure.png
new file mode 100644
index 0000000..ca9c1d1
Binary files /dev/null and b/measurement/images/dialogs/measure.png differ
diff --git a/measurement/images/mapmode/measurement.png b/measurement/images/mapmode/measurement.png
new file mode 100644
index 0000000..561e5e0
Binary files /dev/null and b/measurement/images/mapmode/measurement.png differ
diff --git a/measurement/images/measurement.png b/measurement/images/measurement.png
new file mode 100644
index 0000000..3390444
Binary files /dev/null and b/measurement/images/measurement.png differ
diff --git a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementDialog.java b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementDialog.java
new file mode 100644
index 0000000..1669f3a
--- /dev/null
+++ b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementDialog.java
@@ -0,0 +1,167 @@
+package org.openstreetmap.josm.plugins.measurement;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.text.DecimalFormat;
+import java.util.Collection;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * A small tool dialog for displaying the current measurement data.
+ *
+ * @author ramack
+ */
+public class MeasurementDialog extends ToggleDialog implements ActionListener
+{
+    private static final long serialVersionUID = 4708541586297950021L;
+
+    /**
+     * The reset button
+     */
+    private SideButton resetButton;
+
+    /**
+     * The measurement label for the path length
+     */
+    protected JLabel pathLengthLabel;
+
+    /**
+     * The measurement label for the currently selected segments
+     */
+    protected JLabel selectLengthLabel;
+
+    /**
+     * The measurement label for area of the currently selected loop
+     */
+    protected JLabel selectAreaLabel;
+
+    /**
+     * The measurement label for the segment angle, actually updated, if 2 nodes are selected
+     */
+    protected JLabel segAngleLabel;
+
+    /**
+     * Constructor
+     */
+    public MeasurementDialog()
+    {
+        super(tr("Measured values"), "measure", tr("Open the measurement window."),
+        Shortcut.registerShortcut("subwindow:measurement", tr("Toggle: {0}", tr("Measured values")),
+        KeyEvent.VK_M, Shortcut.GROUP_LAYER), 150);
+
+        JPanel buttonPanel = new JPanel(new GridLayout(1,2));
+
+        resetButton = new SideButton(marktr("Reset"), "select", "Measurement",
+                tr("Reset current measurement results and delete measurement path."), this);
+        buttonPanel.add(resetButton);
+        add(buttonPanel, BorderLayout.SOUTH);
+
+        JPanel valuePanel = new JPanel(new GridLayout(0,2));
+
+        valuePanel.add(new JLabel(tr("Path Length")));
+
+        pathLengthLabel = new JLabel("0 m");
+        valuePanel.add(pathLengthLabel);
+
+        valuePanel.add(new JLabel(tr("Selection Length")));
+
+        selectLengthLabel = new JLabel("0 m");
+        valuePanel.add(selectLengthLabel);
+
+        valuePanel.add(new JLabel(tr("Selection Area")));
+
+        selectAreaLabel = new JLabel("0 m\u00b2");
+        valuePanel.add(selectAreaLabel);
+
+        JLabel angle = new JLabel(tr("Angle"));
+        angle.setToolTipText(tr("Angle between two selected Nodes"));
+        valuePanel.add(angle);
+
+        segAngleLabel = new JLabel("- \u00b0");
+        valuePanel.add(segAngleLabel);
+
+        add(valuePanel, BorderLayout.CENTER);
+
+        this.setPreferredSize(new Dimension(0, 92));
+        final MeasurementDialog dlg = this;
+       //TODO: is this enough?
+
+        DataSet.selListeners.add(new SelectionChangedListener(){
+
+            public void selectionChanged(Collection<? extends OsmPrimitive> arg0) {
+                double length = 0.0;
+                double segAngle = 0.0;
+                                double area = 0.0;
+                                Node lastNode = null;
+                for(OsmPrimitive p:arg0){
+                                    // ignore incomplete nodes
+                                    if(p instanceof Node && !((Node)p).incomplete){
+                                        Node n =(Node)p;
+                                        if(lastNode == null){
+                                            lastNode = n;
+                                        }else{
+                                            length += MeasurementLayer.calcDistance(lastNode.getCoor(), n.getCoor());
+                                            segAngle = MeasurementLayer.angleBetween(lastNode.getCoor(), n.getCoor());
+                                            lastNode = n;
+                                        }
+                                    } else if(p instanceof Way){
+                                        Way w = (Way)p;
+                                        Node lastN = null;
+                                        for(Node n: w.getNodes()){
+                                            if(lastN != null){
+                                                length += MeasurementLayer.calcDistance(lastN.getCoor(), n.getCoor());
+                                                //http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
+                                                area += (MeasurementLayer.calcX(n.getCoor()) * MeasurementLayer.calcY(lastN.getCoor()))
+                              - (MeasurementLayer.calcY(n.getCoor()) * MeasurementLayer.calcX(lastN.getCoor()));
+                                            }
+                                            lastN = n;
+                                        }
+                                        if (lastN != null && lastN == w.getNodes().iterator().next()){
+                                            area = Math.abs(area / 2);
+                                        }else{
+                                            area = 0;
+                                        }
+                                    }
+                }
+                dlg.selectLengthLabel.setText(new DecimalFormat("#0.00").format(length) + " m");
+
+                dlg.segAngleLabel.setText(new DecimalFormat("#0.0").format(segAngle) + " \u00b0");
+                dlg.selectAreaLabel.setText(new DecimalFormat("#0.00").format(area) + " m\u00b2");
+            }
+        });
+    }
+
+    public void actionPerformed(ActionEvent e)
+    {
+        String actionCommand = e.getActionCommand();
+        if( actionCommand.equals("Reset")){
+            resetValues();
+        }
+    }
+
+    /**
+     * Cleans the active Meausurement Layer
+     */
+    public void resetValues(){
+        MeasurementPlugin.getCurrentLayer().reset();
+    }
+
+}
diff --git a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java
new file mode 100644
index 0000000..34e8066
--- /dev/null
+++ b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementLayer.java
@@ -0,0 +1,333 @@
+package org.openstreetmap.josm.plugins.measurement;
+/// @author Raphael Mack <ramack at raphael-mack.de>
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseEvent;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.swing.AbstractAction;
+import javax.swing.Box;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxTrack;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+
+/**
+ * This is a layer that draws a grid
+ */
+public class MeasurementLayer extends Layer {
+
+    public MeasurementLayer(String arg0) {
+        super(arg0);
+    }
+
+    private static Icon icon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(MeasurementPlugin.class.getResource("/images/measurement.png")));
+    private Collection<WayPoint> points = new ArrayList<WayPoint>(32);
+
+    @Override public Icon getIcon() {
+        return icon;
+    }
+
+    @Override public String getToolTipText() {
+        return tr("Layer to make measurements");
+    }
+
+    @Override public boolean isMergable(Layer other) {
+        //return other instanceof MeasurementLayer;
+        return false;
+    }
+
+    @Override public void mergeFrom(Layer from) {
+        // TODO: nyi - doubts about how this should be done are around. Ideas?
+
+    }
+
+    @Override public void paint(Graphics g, final MapView mv) {
+        g.setColor(Color.green);
+        Point l = null;
+        for(WayPoint p:points){
+            Point pnt = Main.map.mapView.getPoint(p.getCoor());
+            if (l != null){
+                g.drawLine(l.x, l.y, pnt.x, pnt.y);
+            }
+            g.drawOval(pnt.x - 2, pnt.y - 2, 4, 4);
+            l = pnt;
+        }
+    }
+
+    @Override public void visitBoundingBox(BoundingXYVisitor v) {
+        // nothing to do here
+    }
+
+    @Override public Object getInfoComponent() {
+        return getToolTipText();
+    }
+
+    @Override public Component[] getMenuEntries() {
+        return new Component[]{
+            new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+            // TODO: implement new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
+            new JSeparator(),
+            new JMenuItem(new GPXLayerImportAction(this)),
+            new JSeparator(),
+            new JMenuItem(new LayerListPopup.InfoAction(this))};
+    }
+
+    public void removeLastPoint(){
+        WayPoint l = null;
+        for(WayPoint p:points) l = p;
+        if(l != null) points.remove(l);
+        recalculate();
+        Main.map.repaint();
+    }
+
+    public void mouseClicked(MouseEvent e){
+        if (e.getButton() != MouseEvent.BUTTON1) return;
+
+        LatLon coor = Main.map.mapView.getLatLon(e.getX(), e.getY());
+        points.add(new WayPoint(coor));
+
+        Main.map.repaint();
+        recalculate();
+    }
+
+    public void reset(){
+        points.clear();
+        recalculate();
+        Main.map.repaint();
+    }
+
+    private void recalculate(){
+        double pathLength = 0.0, segLength = 0.0; // in meters
+        WayPoint last = null;
+
+        pathLength = 0.0;
+        for(WayPoint p : points){
+            if(last != null){
+                segLength = calcDistance(last, p);
+                pathLength += segLength;
+            }
+            last = p;
+        }
+        DecimalFormat nf = new DecimalFormat("#0.00");
+        DecimalFormat nf2 = new DecimalFormat("#0.0");
+        MeasurementPlugin.measurementDialog.pathLengthLabel.setText(pathLength < 800?nf2.format(pathLength) + " m":nf.format(pathLength/1000) + " km");
+    }
+
+    public static double calcDistance(LatLon p1, LatLon p2){
+        double lat1, lon1, lat2, lon2;
+        double dlon, dlat;
+
+        lat1 = p1.lat() * Math.PI / 180.0;
+        lon1 = p1.lon() * Math.PI / 180.0;
+        lat2 = p2.lat() * Math.PI / 180.0;
+        lon2 = p2.lon() * Math.PI / 180.0;
+
+        dlon = lon2 - lon1;
+        dlat = lat2 - lat1;
+
+        double a = (Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2));
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+        return 6367000 * c;
+    }
+
+    public static double calcX(LatLon p1){
+        double lat1, lon1, lat2, lon2;
+        double dlon, dlat;
+
+        lat1 = p1.lat() * Math.PI / 180.0;
+        lon1 = p1.lon() * Math.PI / 180.0;
+        lat2 = lat1;
+        lon2 = 0;
+
+        dlon = lon2 - lon1;
+        dlat = lat2 - lat1;
+
+        double a = (Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2));
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+        return 6367000 * c;
+    }
+
+    public static double calcY(LatLon p1){
+        double lat1, lon1, lat2, lon2;
+        double dlon, dlat;
+
+        lat1 = p1.lat() * Math.PI / 180.0;
+        lon1 = p1.lon() * Math.PI / 180.0;
+        lat2 = 0;
+        lon2 = lon1;
+
+        dlon = lon2 - lon1;
+        dlat = lat2 - lat1;
+
+        double a = (Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2));
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+        return 6367000 * c;
+    }
+
+    public static double calcDistance(WayPoint p1, WayPoint p2){
+        return calcDistance(p1.getCoor(), p2.getCoor());
+    }
+
+    public static double angleBetween(WayPoint p1, WayPoint p2){
+        return angleBetween(p1.getCoor(), p2.getCoor());
+    }
+
+    public static double angleBetween(LatLon p1, LatLon p2){
+        double lat1, lon1, lat2, lon2;
+        double dlon;
+
+        lat1 = p1.lat() * Math.PI / 180.0;
+        lon1 = p1.lon() * Math.PI / 180.0;
+        lat2 = p2.lat() * Math.PI / 180.0;
+        lon2 = p2.lon() * Math.PI / 180.0;
+
+        dlon = lon2 - lon1;
+        double coslat2 = Math.cos(lat2);
+
+        return (180 * Math.atan2(coslat2 * Math.sin(dlon),
+                          (Math.cos(lat1) * Math.sin(lat2)
+                                    -
+                           Math.sin(lat1) * coslat2 * Math.cos(dlon)))) / Math.PI;
+    }
+
+    public static double OldangleBetween(LatLon p1, LatLon p2){
+        double lat1, lon1, lat2, lon2;
+        double dlon, dlat;
+        double heading;
+
+        lat1 = p1.lat() * Math.PI / 180.0;
+        lon1 = p1.lon() * Math.PI / 180.0;
+        lat2 = p2.lat() * Math.PI / 180.0;
+        lon2 = p2.lon() * Math.PI / 180.0;
+
+        dlon = lon2 - lon1;
+        dlat = lat2 - lat1;
+
+        double a = (Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2));
+        double d = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+        heading = Math.acos((Math.sin(lat2) - Math.sin(lat1) * Math.cos(d))
+                            / (Math.sin(d) * Math.cos(lat1)));
+        if (Math.sin(lon2 - lon1) < 0) {
+            heading = 2 * Math.PI - heading;
+        }
+
+        return heading * 180 / Math.PI;
+    }
+
+
+    private class GPXLayerImportAction extends AbstractAction {
+
+    /**
+     * The data model for the list component.
+     */
+    private DefaultListModel model = new DefaultListModel();
+
+    /**
+     * @param layer the targeting measurement layer
+     */
+    public GPXLayerImportAction(MeasurementLayer layer) {
+        super(tr("Import path from GPX layer"), ImageProvider.get("dialogs", "edit")); // TODO: find better image
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Box panel = Box.createVerticalBox();
+        final JList layerList = new JList(model);
+        Collection<Layer> data = Main.map.mapView.getAllLayers();
+        Layer lastLayer = null;
+        int layerCnt = 0;
+
+        for (Layer l : data){
+                if(l instanceof GpxLayer){
+                    model.addElement(l);
+                    lastLayer = l;
+                    layerCnt++;
+                }
+        }
+        if(layerCnt == 1){
+                layerList.setSelectedValue(lastLayer, true);
+            }
+            if(layerCnt > 0){
+
+                layerList.setCellRenderer(new DefaultListCellRenderer(){
+                        @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                            Layer layer = (Layer)value;
+                            JLabel label = (JLabel)super.getListCellRendererComponent(list,
+                                                                                      layer.getName(), index, isSelected, cellHasFocus);
+                            Icon icon = layer.getIcon();
+                            label.setIcon(icon);
+                            label.setToolTipText(layer.getToolTipText());
+                            return label;
+                        }
+                    });
+
+                JCheckBox dropFirst = new JCheckBox(tr("Drop existing path"));
+
+                panel.add(layerList);
+                panel.add(dropFirst);
+
+                final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
+                        @Override public void selectInitialValue() {
+                            layerList.requestFocusInWindow();
+                        }
+                    };
+                final JDialog dlg = optionPane.createDialog(Main.parent, tr("Import path from GPX layer"));
+                dlg.setVisible(true);
+
+                Object answer = optionPane.getValue();
+                if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE ||
+                    (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) {
+                    return;
+                }
+
+                GpxLayer gpx = (GpxLayer)layerList.getSelectedValue();
+                if(dropFirst.isSelected()){
+                    points = new ArrayList<WayPoint>(32);
+                }
+
+                for (GpxTrack trk : gpx.data.tracks) {
+                    for (Collection<WayPoint> trkseg : trk.trackSegs) {
+                        for(WayPoint p: trkseg){
+                            points.add(p);
+                        }
+                    }
+            }
+                recalculate();
+                Main.parent.repaint();
+            }else{
+                // TODO: register a listener and show menu entry only if gps layers are available
+                // no gps layer
+                JOptionPane.showMessageDialog(Main.parent,tr("No GPX data layer found."));
+            }
+        }
+    }
+
+}
diff --git a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementMode.java b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementMode.java
new file mode 100644
index 0000000..12df1f3
--- /dev/null
+++ b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementMode.java
@@ -0,0 +1,54 @@
+package org.openstreetmap.josm.plugins.measurement;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.MapFrame;
+
+public class MeasurementMode extends MapMode {
+
+    private static final long serialVersionUID = 3853830673475744263L;
+
+    public MeasurementMode(MapFrame mapFrame, String name, String desc) {
+        super(name, "measurement.png", desc, mapFrame, Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+    }
+
+    @Override public void enterMode() {
+        super.enterMode();
+        Main.map.mapView.addMouseListener(this);
+    }
+
+    @Override public void exitMode() {
+        super.exitMode();
+        Main.map.mapView.removeMouseListener(this);
+    }
+
+    /**
+     * If user clicked with the left button, add a node at the current mouse
+     * position.
+     *
+     * If in nodesegment mode, add the node to the line segment by splitting the
+     * segment. The new created segment will be inserted in every way the segment
+     * was part of.
+     */
+    @Override public void mouseClicked(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON3){
+            MeasurementPlugin.getCurrentLayer().removeLastPoint();
+        }else if (e.getButton() == MouseEvent.BUTTON1){
+            LatLon coor = Main.map.mapView.getLatLon(e.getX(), e.getY());
+            if (coor.isOutSideWorld()) {
+                JOptionPane.showMessageDialog(Main.parent,tr("Can not draw outside of the world."));
+                return;
+            }
+            MeasurementPlugin.getCurrentLayer().mouseClicked(e);
+        }
+    }
+
+}
diff --git a/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementPlugin.java b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementPlugin.java
new file mode 100644
index 0000000..85220c3
--- /dev/null
+++ b/measurement/src/org/openstreetmap/josm/plugins/measurement/MeasurementPlugin.java
@@ -0,0 +1,51 @@
+package org.openstreetmap.josm.plugins.measurement;
+/// @author Raphael Mack <osm at raphael-mack.de>
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.IconToggleButton;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
+import org.openstreetmap.josm.plugins.Plugin;
+
+public class MeasurementPlugin extends Plugin {
+
+    private IconToggleButton btn;
+    private MeasurementMode mode;
+    protected static MeasurementDialog measurementDialog;
+    protected static MeasurementLayer currentLayer;
+
+    public MeasurementPlugin() {
+        mode = new MeasurementMode(Main.map, "measurement", tr("measurement mode"));
+        btn = new IconToggleButton(mode);
+        btn.setVisible(true);
+        measurementDialog = new MeasurementDialog();
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if(newFrame != null)
+            newFrame.addToggleDialog(measurementDialog);
+        if(Main.map != null)
+            Main.map.addMapMode(btn);
+    }
+
+    public static MeasurementLayer getCurrentLayer(){
+        if(currentLayer == null){
+            currentLayer = new MeasurementLayer(tr("Measurements"));
+            Main.main.addLayer(currentLayer);
+            Layer.listeners.add(new LayerChangeListener(){
+                public void activeLayerChange(final Layer oldLayer, final Layer newLayer) {
+                    if(newLayer instanceof MeasurementLayer)
+                        MeasurementPlugin.currentLayer = (MeasurementLayer)newLayer;
+                }
+                public void layerAdded(final Layer newLayer) {
+                }
+                public void layerRemoved(final Layer oldLayer) {
+                }
+            });
+        }
+        return currentLayer;
+    }
+}
diff --git a/openvisible/.classpath b/openvisible/.classpath
new file mode 100644
index 0000000..b5edc13
--- /dev/null
+++ b/openvisible/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/openvisible/.project b/openvisible/.project
new file mode 100644
index 0000000..148bd4e
--- /dev/null
+++ b/openvisible/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JOSM-openvisible</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/openvisible/LICENSE b/openvisible/LICENSE
new file mode 100644
index 0000000..37b7a4d
--- /dev/null
+++ b/openvisible/LICENSE
@@ -0,0 +1,339 @@
+      GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+      How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/openvisible/build.xml b/openvisible/build.xml
new file mode 100644
index 0000000..2cd93bd
--- /dev/null
+++ b/openvisible/build.xml
@@ -0,0 +1,57 @@
+<project name="openvisible" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Christof Dallermassl"/>
+                <attribute name="Plugin-Class" value="at.dallermassl.josm.plugin.openvisible.OpenVisiblePlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Dependencies" value="jgrapht-jdk1.5"/>
+                <attribute name="Plugin-Description" value="Allows opening gpx/osm files that intersect the currently visible screen area"/>
+                <attribute name="Plugin-Mainversion" value="2082"/>
+                <attribute name="Plugin-Stage" value="50"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/openvisible/copyright.txt b/openvisible/copyright.txt
new file mode 100644
index 0000000..1bd98c0
--- /dev/null
+++ b/openvisible/copyright.txt
@@ -0,0 +1,6 @@
+Plugin openvisible
+
+This plugin is copyrighted 2008-2009 
+by Christof Dallermassl <christof at dallermassl.at>.
+
+It is distributed under GPL-v2 license (see file  LICENSE in this directory).
diff --git a/openvisible/images/openvisible.png b/openvisible/images/openvisible.png
new file mode 100644
index 0000000..9653b2a
Binary files /dev/null and b/openvisible/images/openvisible.png differ
diff --git a/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisibleAction.java b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisibleAction.java
new file mode 100644
index 0000000..e7a6a1e
--- /dev/null
+++ b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisibleAction.java
@@ -0,0 +1,135 @@
+/**
+ *
+ */
+package at.dallermassl.josm.plugin.openvisible;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.io.GpxImporter;
+import org.openstreetmap.josm.io.GpxReader;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.OsmImporter;
+import org.openstreetmap.josm.io.OsmReader;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
+
+import at.dallermassl.josm.plugin.openvisible.OsmGpxBounds;
+
+/**
+ * @author cdaller
+ *
+ */
+public class OpenVisibleAction extends JosmAction {
+    private File lastDirectory;
+
+    public OpenVisibleAction() {
+        super(tr("Open Visible..."), "openvisible",
+        tr("Open only files that are visible in current view."),
+        Shortcut.registerShortcut("tools:openvisible", tr("Menu: {0}", tr("Open Visible...")),
+        KeyEvent.VK_I, Shortcut.GROUP_MENU, Shortcut.SHIFT_DEFAULT), true);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+     */
+    public void actionPerformed(ActionEvent e) {
+        if(Main.map == null || Main.map.mapView == null) {
+            JOptionPane.showMessageDialog(Main.parent, tr("No view open - cannot determine boundaries!"));
+            return;
+        }
+        MapView view = Main.map.mapView;
+        Rectangle bounds = view.getBounds();
+        LatLon bottomLeft = view.getLatLon(bounds.x, bounds.y + bounds.height);
+        LatLon topRight = view.getLatLon(bounds.x + bounds.width, bounds.y);
+
+        System.err.println("FileFind Bounds: " + bottomLeft + " to " + topRight);
+
+        JFileChooser fileChooser;
+        if(lastDirectory != null) {
+            fileChooser = new JFileChooser(lastDirectory);
+        } else {
+            fileChooser = new JFileChooser();
+        }
+        fileChooser.setMultiSelectionEnabled(true);
+        fileChooser.showOpenDialog(Main.parent);
+        File[] files = fileChooser.getSelectedFiles();
+        lastDirectory = fileChooser.getCurrentDirectory();
+
+        for(File file : files) {
+            try {
+                OsmGpxBounds parser = new OsmGpxBounds();
+                parser.parse(new BufferedInputStream(new FileInputStream(file)));
+                if(parser.intersects(bottomLeft.lat(), topRight.lat(), bottomLeft.lon(), topRight.lon())) {
+                    System.out.println(file.getAbsolutePath()); // + "," + parser.minLat + "," + parser.maxLat + "," + parser.minLon + "," + parser.maxLon);
+                    if(file.getName().endsWith("osm")) {
+                        openAsData(file);
+                    } else if(file.getName().endsWith("gpx")) {
+                        openFileAsGpx(file);
+                    }
+
+                }
+            } catch (FileNotFoundException e1) {
+                e1.printStackTrace();
+            } catch (IOException e1) {
+                e1.printStackTrace();
+            } catch (SAXException e1) {
+                e1.printStackTrace();
+            } catch(IllegalDataException e1) {
+            	e1.printStackTrace();
+            }
+        }
+
+    }
+
+    private void openAsData(File file) throws SAXException, IOException, FileNotFoundException, IllegalDataException {
+        String fn = file.getName();
+        if (new OsmImporter().acceptFile(file)) {
+            DataSet dataSet = OsmReader.parseDataSet(new FileInputStream(file), NullProgressMonitor.INSTANCE);
+            OsmDataLayer layer = new OsmDataLayer(dataSet, fn, file);
+            Main.main.addLayer(layer);
+        }
+        else
+            JOptionPane.showMessageDialog(Main.parent, fn+": "+tr("Unknown file extension: {0}", fn.substring(fn.lastIndexOf('.')+1)));
+    }
+
+    private void openFileAsGpx(File file) throws SAXException, IOException, FileNotFoundException {
+        String fn = file.getName();
+        if (new GpxImporter().acceptFile(file)) {
+            GpxReader r = null;
+            if (file.getName().endsWith(".gpx.gz")) {
+                r = new GpxReader(new GZIPInputStream(new FileInputStream(file)), file.getAbsoluteFile().getParentFile());
+            } else{
+                r = new GpxReader(new FileInputStream(file), file.getAbsoluteFile().getParentFile());
+            }
+            r.data.storageFile = file;
+            GpxLayer gpxLayer = new GpxLayer(r.data, fn);
+            Main.main.addLayer(gpxLayer);
+            Main.main.addLayer(new MarkerLayer(r.data, tr("Markers from {0}", fn), file, gpxLayer));
+
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+}
diff --git a/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisiblePlugin.java b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisiblePlugin.java
new file mode 100644
index 0000000..91276f2
--- /dev/null
+++ b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OpenVisiblePlugin.java
@@ -0,0 +1,21 @@
+/**
+ *
+ */
+package at.dallermassl.josm.plugin.openvisible;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.plugins.Plugin;
+
+/**
+ * @author cdaller
+ *
+ */
+public class OpenVisiblePlugin extends Plugin {
+
+    public OpenVisiblePlugin() {
+        super();
+        MainMenu.add(Main.main.menu.fileMenu, new OpenVisibleAction());
+    }
+
+}
diff --git a/openvisible/src/at/dallermassl/josm/plugin/openvisible/OsmGpxBounds.java b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OsmGpxBounds.java
new file mode 100644
index 0000000..b93cf73
--- /dev/null
+++ b/openvisible/src/at/dallermassl/josm/plugin/openvisible/OsmGpxBounds.java
@@ -0,0 +1,121 @@
+/**
+ *
+ */
+package at.dallermassl.josm.plugin.openvisible;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * @author cdaller
+ *
+ */
+public class OsmGpxBounds extends DefaultHandler {
+    private double minLat = 180.0;
+    private double maxLat = -180.0;
+    private double minLon = 90.0;
+    private double maxLon = -90.0;
+
+    public OsmGpxBounds() {
+
+    }
+
+    /**
+     * Parses the given input stream (gpx or osm file).
+     * @param in the stream to parse.
+     * @throws IOException if the file cannot be read.
+     * @throws SAXException if the file could not be parsed.
+     */
+    public void parse(InputStream in) throws IOException, SAXException {
+        try {
+            SAXParserFactory.newInstance().newSAXParser().parse(in, this);
+        } catch (ParserConfigurationException e1) {
+            e1.printStackTrace(); // broken SAXException chaining
+            throw new SAXException(e1);
+        }
+    }
+
+    @Override
+    public void startElement(String ns, String lname, String qname, Attributes a) {
+        if (qname.equals("node") || qname.equals("trkpt")) {
+            double lat = Double.parseDouble(a.getValue("lat"));
+            double lon = Double.parseDouble(a.getValue("lon"));
+            minLat = Math.min(minLat, lat);
+            minLon = Math.min(minLon, lon);
+            maxLat = Math.max(maxLat, lat);
+            maxLon = Math.max(maxLon, lon);
+        }
+    }
+
+    /**
+     * Returns <code>true</code>, if the given coordinates intersect with the
+     * parsed min/max latitude longitude.
+     * @param minLat the minimum latitude.
+     * @param maxLat the maximum latitude.
+     * @param minLon the minimum longitude.
+     * @param maxLon the maximum longitude.
+     * @return <code>true</code> if the given rectangle intersects with the parsed min/max.
+     */
+    public boolean intersects(double minLat, double maxLat, double minLon, double maxLon) {
+        double lat1 = Math.max(this.minLat, minLat);
+        double lon1 = Math.max(this.minLon, minLon);
+        double lat2 = Math.min(this.maxLat, maxLat);
+        double lon2 = Math.min(this.maxLon, maxLon);
+        return ((lat2-lat1) > 0) && ((lon2-lon1) > 0);
+    }
+
+    public static void main(String[] args) {
+        if(args.length < 5) {
+            printHelp();
+            return;
+        }
+        double minLat = Double.parseDouble(args[0]);
+        double maxLat = Double.parseDouble(args[1]);
+        double minLon = Double.parseDouble(args[2]);
+        double maxLon = Double.parseDouble(args[3]);
+        String[] files = new String[args.length - 4];
+        System.arraycopy(args, 4, files, 0, args.length - 4);
+
+        try {
+            File file;
+            for(String fileName : files) {
+                file = new File(fileName);
+                if(!file.isDirectory()
+                  && (file.getName().endsWith("gpx") || file.getName().endsWith("osm"))) {
+                    OsmGpxBounds parser = new OsmGpxBounds();
+                    parser.parse(new BufferedInputStream(new FileInputStream(file)));
+                    if(parser.intersects(minLat, maxLat, minLon, maxLon)) {
+                        System.out.println(file.getAbsolutePath()); // + "," + parser.minLat + "," + parser.maxLat + "," + parser.minLon + "," + parser.maxLon);
+                    }
+//                    System.out.println(parser.intersects(47.0555, 47.09, 15.406, 15.4737));
+                }
+            }
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (SAXException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     */
+    private static void printHelp() {
+        System.out.println(OsmGpxBounds.class.getName() + " <minLat> <maxLat> <minLon> <maxLon> <files+>");
+
+    }
+
+}
diff --git a/slippymap/.classpath b/slippymap/.classpath
new file mode 100644
index 0000000..32eb8e6
--- /dev/null
+++ b/slippymap/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/josm"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/slippymap/.project b/slippymap/.project
new file mode 100644
index 0000000..6e95ff2
--- /dev/null
+++ b/slippymap/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>slippymap</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/slippymap/README b/slippymap/README
new file mode 100644
index 0000000..118147e
--- /dev/null
+++ b/slippymap/README
@@ -0,0 +1,16 @@
+A plugin for displaying a slippy map grid, with various server interaction
+options (download tiles, request tile updates etc.)
+
+Author: Frederik Ramm <frederik at remote.org>
+        Lubomir Varga <lubomir.varga at freemap.sk> or <luvar at plaintext.sk>
+Public Domain.
+
+Software with a little bit of customisation, fade background feature, autozoom, autoload tiles e.t.c. Just a begining of the best plugin for josm ;-)
+
+Publishing new plugin binary:
+http://lists.openstreetmap.org/pipermail/josm-dev/2008-August/001462.html
+
+1. commit your work
+2. update to latest revision (your local copy revision number is old and you want it set to new)
+3. build josm plugin by build.xml ant script
+4. commit plugin binary to svn ("cd ../../dist;svn commit slippymap.jar;" if you dont have in your local copy that folder, you should make some temp directory and download there dist directory, copy there your fresh build binary and comit it back "mkdir someDir;cd someDir;svn co http://svn.openstreetmap.org/applications/editors/josm/dist .;cp myActualBinary.jar slippymap.jar;svn commit slippymap.jar")
diff --git a/slippymap/build.xml b/slippymap/build.xml
new file mode 100644
index 0000000..a0eed67
--- /dev/null
+++ b/slippymap/build.xml
@@ -0,0 +1,56 @@
+<project name="slippymap" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Frederik Ramm"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.slippymap.SlippyMapPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Displays a slippy map grid in JOSM. Can load tiles from slippy map as background and request updates."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/SlippyMap"/>
+                <attribute name="Plugin-Mainversion" value="2196"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/slippymap/images/preferences/slippymap.png b/slippymap/images/preferences/slippymap.png
new file mode 100644
index 0000000..9b9ab8a
Binary files /dev/null and b/slippymap/images/preferences/slippymap.png differ
diff --git a/slippymap/images/slippymap.png b/slippymap/images/slippymap.png
new file mode 100644
index 0000000..11cdb64
Binary files /dev/null and b/slippymap/images/slippymap.png differ
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java
new file mode 100644
index 0000000..8f6263f
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java
@@ -0,0 +1,78 @@
+/**
+ * 
+ */
+package org.openstreetmap.josm.plugins.slippymap;
+
+/**
+ * <p>
+ * Key for map tile. Key have just X and Y value. It have overriden {@link #hashCode()},
+ * {@link #equals(Object)} and also {@link #toString()}.
+ * </p>
+ * 
+ * @author LuVar <lubomir.varga at freemap.sk>
+ * @author Dave Hansen <dave at sr71.net>
+ *
+ */
+public class SlippyMapKey {
+	private final int x;
+	private final int y;
+	private final int level;
+	
+	/**
+	 * <p>
+	 * Constructs key for hashmaps for some tile describedy by X and Y position. X and Y are tiles
+	 * positions on discrete map.
+	 * </p>
+	 * 
+	 * @param x	x position in tiles table
+	 * @param y	y position in tiles table
+	 */
+	public final boolean valid;
+	public SlippyMapKey(int x, int y, int level) {
+		this.x = x;
+		this.y = y;
+		this.level = level;
+		if (level <= 0 || x < 0 || y < 0) {
+			this.valid = false;
+			System.err.println("invalid SlippyMapKey("+level+", "+x+", "+y+")");
+		} else {
+			this.valid = true;
+		}
+	}
+	
+	/**
+	 * <p>
+	 * Returns true ONLY if x and y are equals.
+	 * </p>
+	 * 
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof SlippyMapKey) {
+			SlippyMapKey smk = (SlippyMapKey) obj;
+			if((smk.x == this.x) && (smk.y == this.y) && (smk.level == this.level)) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * @return	return new Integer(this.x + this.y * 10000).hashCode();
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		return new Integer(this.x + this.y * 10000 + this.level * 100000).hashCode();
+	}
+	
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString() {
+		return "SlippyMapKey(x=" + this.x + ",y=" + this.y + ",level=" + level + ")";
+	}
+	
+}
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
new file mode 100644
index 0000000..e33a6d5
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
@@ -0,0 +1,915 @@
+package org.openstreetmap.josm.plugins.slippymap;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.ImageObserver;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.TreeSet;
+
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Class that displays a slippy map layer.
+ *
+ * @author Frederik Ramm <frederik at remote.org>
+ * @author LuVar <lubomir.varga at freemap.sk>
+ * @author Dave Hansen <dave at sr71.net>
+ *
+ */
+public class SlippyMapLayer extends Layer implements ImageObserver,
+    PreferenceChangedListener {
+    /**
+     * Actual zoom lvl. Initial zoom lvl is set to
+     * {@link SlippyMapPreferences#getMinZoomLvl()}.
+     */
+    public int currentZoomLevel = SlippyMapPreferences.getMinZoomLvl();
+    private Hashtable<SlippyMapKey, SlippyMapTile> tileStorage = null;
+
+    Point[][] pixelpos = new Point[21][21];
+    LatLon lastTopLeft;
+    LatLon lastBotRight;
+    private Image bufferImage;
+    private SlippyMapTile clickedTile;
+    private boolean needRedraw;
+    private JPopupMenu tileOptionMenu;
+
+    static void debug(String msg)
+    {
+
+    }
+    @SuppressWarnings("serial")
+    public SlippyMapLayer() {
+        super(tr("Slippy Map"));
+        setBackgroundLayer(true);
+
+        clearTileStorage();
+
+        tileOptionMenu = new JPopupMenu();
+        tileOptionMenu.add(new JMenuItem(new AbstractAction(tr("Load Tile")) {
+            public void actionPerformed(ActionEvent ae) {
+                if (clickedTile != null) {
+                    loadSingleTile(clickedTile);
+                    needRedraw = true;
+                    Main.map.repaint();
+                }
+            }
+        }));
+
+        tileOptionMenu.add(new JMenuItem(new AbstractAction(
+                tr("Show Tile Status")) {
+            public void actionPerformed(ActionEvent ae) {
+                if (clickedTile != null) {
+                    clickedTile.loadMetadata();
+                    needRedraw = true;
+                    Main.map.repaint();
+                }
+            }
+        }));
+
+        tileOptionMenu.add(new JMenuItem(new AbstractAction(
+                tr("Request Update")) {
+            public void actionPerformed(ActionEvent ae) {
+                if (clickedTile != null) {
+                    clickedTile.requestUpdate();
+                    needRedraw = true;
+                    Main.map.repaint();
+                }
+            }
+        }));
+
+        tileOptionMenu.add(new JMenuItem(new AbstractAction(
+                tr("Load All Tiles")) {
+            public void actionPerformed(ActionEvent ae) {
+                loadAllTiles();
+                needRedraw = true;
+                Main.map.repaint();
+            }
+        }));
+
+        // increase and decrease commands
+        tileOptionMenu.add(new JMenuItem(
+                new AbstractAction(tr("Increase zoom")) {
+                    public void actionPerformed(ActionEvent ae) {
+                        increaseZoomLevel();
+                        needRedraw = true;
+                        Main.map.repaint();
+                    }
+                }));
+
+        tileOptionMenu.add(new JMenuItem(
+                new AbstractAction(tr("Decrease zoom")) {
+                    public void actionPerformed(ActionEvent ae) {
+                        decreaseZoomLevel();
+                        Main.map.repaint();
+                    }
+                }));
+
+        tileOptionMenu.add(new JMenuItem(
+                new AbstractAction(tr("Flush Tile Cache")) {
+                    public void actionPerformed(ActionEvent ae) {
+                        System.out.print("flushing all tiles...");
+                        for (SlippyMapTile t : tileStorage.values()) {
+                            t.dropImage();
+                        }
+                        System.out.println("done");
+                        shrinkTileStorage(0);
+                    }
+                }));
+        // end of adding menu commands
+
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                Main.map.mapView.addMouseListener(new MouseAdapter() {
+                    @Override
+                    public void mouseClicked(MouseEvent e) {
+                        if (e.getButton() != MouseEvent.BUTTON3)
+                            return;
+                        clickedTile = getTileForPixelpos(e.getX(), e.getY());
+                        tileOptionMenu.show(e.getComponent(), e.getX(), e
+                                .getY());
+                    }
+                });
+
+                listeners.add(new LayerChangeListener() {
+                    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+                    }
+
+                    public void layerAdded(Layer newLayer) {
+                    }
+
+                    public void layerRemoved(Layer oldLayer) {
+                        Main.pref.listener.remove(SlippyMapLayer.this);
+                    }
+                });
+            }
+        });
+
+        Main.pref.listener.add(this);
+    }
+
+    /**
+     * Zoom in, go closer to map.
+     *
+     * @return    true, if zoom increasing was successfull, false othervise
+     */
+    public boolean zoomIncreaseAllowed()
+    {
+        boolean zia = currentZoomLevel < SlippyMapPreferences.getMaxZoomLvl();
+        this.debug("zoomIncreaseAllowed(): " + zia + " " + currentZoomLevel + " vs. " + SlippyMapPreferences.getMaxZoomLvl() );
+        return zia;
+    }
+    public boolean increaseZoomLevel()
+    {
+        lastImageScale = null;
+        if (zoomIncreaseAllowed()) {
+            currentZoomLevel++;
+            this.debug("increasing zoom level to: " + currentZoomLevel);
+            needRedraw = true;
+        } else {
+            System.err.println("current zoom lvl ("+currentZoomLevel+") couldnt be increased. "+
+                             "MaxZoomLvl ("+SlippyMapPreferences.getMaxZoomLvl()+") reached.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Zoom out from map.
+     *
+     * @return    true, if zoom increasing was successfull, false othervise
+     */
+    public boolean zoomDecreaseAllowed()
+    {
+        return currentZoomLevel > SlippyMapPreferences.getMinZoomLvl();
+    }
+    public boolean decreaseZoomLevel() {
+        int minZoom = SlippyMapPreferences.getMinZoomLvl();
+        lastImageScale = null;
+        if (zoomDecreaseAllowed()) {
+            this.debug("decreasing zoom level to: " + currentZoomLevel);
+            currentZoomLevel--;
+            needRedraw = true;
+        } else {
+            System.err.println("current zoom lvl couldnt be decreased. MinZoomLvl("+minZoom+") reached.");
+            return false;
+        }
+        return true;
+    }
+
+    public void clearTileStorage() {
+        // when max zoom lvl is begin saved, this method is called and probably
+        // the setting isnt saved yet.
+        int maxZoom = SlippyMapPreferences.getMaxZoomLvl();
+        tileStorage = new Hashtable<SlippyMapKey, SlippyMapTile>();
+        checkTileStorage();
+    }
+
+    class TileTimeComp implements Comparator<SlippyMapTile> {
+            public int compare(SlippyMapTile s1, SlippyMapTile s2) {
+                    long t1 = s1.access_time();
+                    long t2 = s2.access_time();
+                    if (s1 == s2)
+                            return 0;
+                    if (t1 == t2) {
+                            t1 = s1.hashCode();
+                            t2 = s2.hashCode();
+                    }
+                    if (t1 < t2)
+                            return -1;
+                    return 1;
+            }
+    }
+
+    /**
+     * <p>
+     * Check if tiles.size() is not more than maxNrTiles.
+     * If yes, oldest tiles by timestamp are flushed from
+     * the cache.
+     * </p>
+     */
+    public void shrinkTileStorage(int maxNrTiles)
+    {
+        TreeSet<SlippyMapTile> tiles = new TreeSet<SlippyMapTile>(new TileTimeComp());
+        tiles.addAll(tileStorage.values());
+        int nr_to_drop = tiles.size() - maxNrTiles;
+        if (nr_to_drop <= 0) {
+            this.debug("total of " + tiles.size() + " loaded tiles, size OK (< " + maxNrTiles + ")");
+            return;
+        }
+        this.debug("total of " + tiles.size() + " tiles, need to flush " + nr_to_drop + " tiles");
+        for (SlippyMapTile t : tiles) {
+            if (nr_to_drop <= 0)
+                break;
+            t.dropImage();
+            nr_to_drop--;
+            //tileStorage.remove(t.getKey());
+        }
+    }
+    long lastCheck = 0;
+    public void checkTileStorage() {
+        long now = System.currentTimeMillis();
+        if (now - lastCheck < 1000)
+            return;
+        lastCheck = now;
+        shrinkTileStorage(200);
+    }
+
+    LinkedList<SlippyMapTile> downloadQueue = new LinkedList<SlippyMapTile>();
+    LinkedList<Image> downloadList = new LinkedList<Image>();
+    int simultaneousTileDownloads = 5;
+    int maxQueueSize = 50;
+    synchronized void markDone(Image i)
+    {
+        boolean inList = downloadList.remove(i);
+        if (!inList) {
+            Main.debug("ERROR: downloaded image was not queued");
+            return;
+        }
+        //System.out.print("currently downloading: " + downloadList.size() +
+        //                   " queue size: " + downloadQueue.size() +"    \r");
+        if (downloadQueue.size() > 0)
+            loadSingleTile(downloadQueue.getLast());
+    }
+    synchronized void loadSingleTile(SlippyMapTile tile)
+    {
+        // this moves the tile to the front of the line
+        if (downloadQueue.contains(tile))
+            downloadQueue.remove(tile);
+        downloadQueue.addLast(tile);
+        // We assume that a queue larger than this is
+        // too big and the downloads at the end of it
+        // too old to be relevant.
+        while (downloadQueue.size() > maxQueueSize)
+            downloadQueue.removeFirst();
+        if (downloadList.size() > simultaneousTileDownloads)
+            return;
+        //System.out.print("currently downloading: " + downloadList.size() +
+        //                   " queue size: " + downloadQueue.size() + "    \r");
+        while (!downloadQueue.isEmpty()) {
+            // This is a FIFO queue.  The reasoning is
+            // that we want to draw the most recently
+            // requested images now.  We may have panned
+            // or zoomed away
+            tile = downloadQueue.removeLast();
+            Image img = tile.loadImage();
+            if (imageLoaded(img))
+                continue;
+            Toolkit.getDefaultToolkit().prepareImage(img, -1, -1, this);
+            downloadList.add(img);
+            break;
+        }
+    }
+
+    synchronized SlippyMapTile getTile(int x, int y, int zoom) {
+        SlippyMapKey key = new SlippyMapKey(x, y, zoom);
+        if (!key.valid) {
+            return null;
+        }
+        return tileStorage.get(key);
+    }
+
+    synchronized SlippyMapTile putTile(SlippyMapTile tile, int x, int y, int zoom) {
+        SlippyMapKey key = new SlippyMapKey(x, y, zoom);
+        if (!key.valid) {
+            return null;
+        }
+        return tileStorage.put(key, tile);
+    }
+
+    synchronized SlippyMapTile getOrCreateTile(int x, int y, int zoom) {
+        SlippyMapTile tile = getTile(x, y, zoom);
+        if (tile != null) {
+            return tile;
+        }
+        tile = new SlippyMapTile(x, y, zoom);
+        putTile(tile, x, y, zoom);
+        return tile;
+    }
+
+    void loadAllTiles() {
+        MapView mv = Main.map.mapView;
+        LatLon topLeft = mv.getLatLon(0, 0);
+        LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
+
+        TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);
+
+        // if there is more than 18 tiles on screen in any direction, do not
+        // load all tiles!
+        if (ts.tilesSpanned() > (18*18)) {
+            System.out.println("Not downloading all tiles because there is more than 18 tiles on an axis!");
+            return;
+        }
+
+        for (Tile t : ts.allTiles()) {
+            SlippyMapTile tile = getOrCreateTile(t.x, t.y, currentZoomLevel);
+            if (tile.getImage() == null) {
+                this.loadSingleTile(tile);
+            }
+        }//end of for Tile t
+    }
+
+    /*
+     * Attempt to approximate how much the image is being scaled. For instance,
+     * a 100x100 image being scaled to 50x50 would return 0.25.
+     */
+    Image lastScaledImage = null;
+
+    double getImageScaling(Image img, Point p0, Point p1) {
+        int realWidth = -1;
+        int realHeight = -1;
+           if (img != null) {
+            realWidth = img.getHeight(this);
+               realWidth = img.getWidth(this);
+        }
+        if (realWidth == -1 || realHeight == -1) {
+            /*
+             * We need a good image against which to work. If
+             * the current one isn't loaded, then try the last one.
+             * Should be good enough. If we've never seen one, then
+             * guess.
+             */
+            if (lastScaledImage != null) {
+                return getImageScaling(lastScaledImage, p0, p1);
+            }
+            realWidth = 256;
+            realHeight = 256;
+        } else {
+            lastScaledImage = img;
+        }
+        /*
+         * If the zoom scale gets really, really off, these can get into
+         * the millions, so make this a double to prevent integer
+         * overflows.
+         */
+        double drawWidth = p1.x - p0.x;
+        double drawHeight = p1.x - p0.x;
+        // stem.out.println("drawWidth: " + drawWidth + " drawHeight: " +
+        // drawHeight);
+
+        double drawArea = drawWidth * drawHeight;
+        double realArea = realWidth * realHeight;
+
+        return drawArea / realArea;
+    }
+
+    boolean imageLoaded(Image i) {
+        if (i == null)
+            return false;
+
+        int status = Toolkit.getDefaultToolkit().checkImage(i, -1, -1, this);
+        if ((status & ALLBITS) != 0)
+            return true;
+        return false;
+    }
+
+    Double lastImageScale = null;
+    int paintTileImages(Graphics g, TileSet ts, int zoom) {
+        int paintedTiles = 0;
+        boolean imageScaleRecorded = false;
+        Image img = null;
+        for (Tile t : ts.allTiles()) {
+            SlippyMapTile tile = getTile(t.x, t.y, zoom);
+            if (tile == null) {
+                // Don't trigger tile loading if this isn't
+                // the exact zoom level we're looking for
+                if (zoom != currentZoomLevel)
+                    continue;
+                tile = getOrCreateTile(t.x, t.y, zoom);
+                if (SlippyMapPreferences.getAutoloadTiles())
+                    loadSingleTile(tile);
+            }
+            img = tile.getImage();
+            if (img == null)
+                continue;
+            if ((zoom != currentZoomLevel) && !tile.isDownloaded())
+                continue;
+            Point p = t.pixelPos(zoom);
+            /*
+             * We need to get a box in which to draw, so advance by one tile in
+             * each direction to find the other corner of the box
+             */
+            Tile t2 = new Tile(t.x + 1, t.y + 1);
+            Point p2 = t2.pixelPos(zoom);
+            if (imageLoaded(img)) {
+                g.drawImage(img, p.x, p.y, p2.x - p.x, p2.y - p.y, this);
+                paintedTiles++;
+            }
+            if (img == null)
+                continue;
+            if (!imageScaleRecorded && zoom == currentZoomLevel) {
+                lastImageScale = new Double(getImageScaling(img, p, p2));
+                imageScaleRecorded = true;
+            }
+            float fadeBackground = SlippyMapPreferences.getFadeBackground();
+            if (fadeBackground != 0f) {
+                // dimm by painting opaque rect...
+                g.setColor(new Color(1f, 1f, 1f, fadeBackground));
+                g.fillRect(p.x, p.y, p2.x - p.x, p2.y - p.y);
+            }
+        }// end of for
+        return paintedTiles;
+    }
+
+    void paintTileText(TileSet ts, Graphics g, MapView mv, int zoom, Tile t) {
+        int fontHeight = g.getFontMetrics().getHeight();
+
+        SlippyMapTile tile = getTile(t.x, t.y, zoom);
+        if (tile == null) {
+            return;
+        }
+        if (tile.getImage() == null) {
+            loadSingleTile(tile);
+        }
+        Point p = t.pixelPos(zoom);
+        int texty = p.y + 2 + fontHeight;
+
+        if (SlippyMapPreferences.getDrawDebug()) {
+            g.drawString("x=" + t.x + " y=" + t.y + " z=" + zoom + "", p.x + 2, texty);
+            texty += 1 + fontHeight;
+            if ((t.x % 32 == 0) && (t.y % 32 == 0)) {
+                g.drawString("x=" + t.x / 32 + " y=" + t.y / 32 + " z=7", p.x + 2, texty);
+                texty += 1 + fontHeight;
+            }
+        }// end of if draw debug
+
+        String md = tile.getMetadata();
+        if (md != null) {
+            g.drawString(md, p.x + 2, texty);
+            texty += 1 + fontHeight;
+        }
+
+        String tileStatus = tile.getStatus();
+        Image tileImage = tile.getImage();
+        if (!imageLoaded(tileImage)) {
+            g.drawString(tr("image " + tileStatus), p.x + 2, texty);
+            texty += 1 + fontHeight;
+        }
+        /*
+        We already do repaint when the images load
+        we don't need to poll like this
+        */
+        if (!imageLoaded(tileImage)) {
+            needRedraw = true;
+            Main.map.repaint(100);
+        }
+
+        if (SlippyMapPreferences.getDrawDebug()) {
+            if (ts.leftTile(t)) {
+                if (t.y % 32 == 31) {
+                    g.fillRect(0, p.y - 1, mv.getWidth(), 3);
+                } else {
+                    g.drawLine(0, p.y, mv.getWidth(), p.y);
+                }
+            }
+        }// /end of if draw debug
+    }
+
+    private class Tile {
+        public int x;
+        public int y;
+
+        Tile(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        public Point pixelPos(int zoom) {
+            double lon = tileXToLon(this.x, zoom);
+            LatLon tmpLL = new LatLon(tileYToLat(this.y, zoom), lon);
+            MapView mv = Main.map.mapView;
+            return mv.getPoint(tmpLL);
+        }
+    }
+    private class TileSet {
+        int z12x0, z12x1, z12y0, z12y1;
+        int zoom;
+        int tileMax = -1;
+
+        TileSet(LatLon topLeft, LatLon botRight, int zoom) {
+            this.zoom = zoom;
+            z12x0 = lonToTileX(topLeft.lon(), zoom) - 1;
+            z12y0 = latToTileY(topLeft.lat(),  zoom) - 1;
+            z12x1 = lonToTileX(botRight.lon(), zoom) + 1;
+            z12y1 = latToTileY(botRight.lat(), zoom) + 1;
+            if (z12x0 > z12x1) {
+                int tmp = z12x0;
+                z12x0 = z12x1;
+                z12x1 = tmp;
+            }
+            if (z12y0 > z12y1) {
+                int tmp = z12y0;
+                z12y0 = z12y1;
+                z12y1 = tmp;
+            }
+            tileMax = (int)Math.pow(2.0, zoom);
+            if (z12x0 < 0) z12x0 = 0;
+            if (z12y0 < 0) z12y0 = 0;
+            if (z12x1 > tileMax) z12x1 = tileMax;
+            if (z12y1 > tileMax) z12y1 = tileMax;
+        }
+        double tilesSpanned() {
+            int x_span = z12x1 - z12x0;
+            int y_span = z12y1 - z12y0;
+            return Math.sqrt(1.0 * x_span * y_span);
+        }
+
+        /*
+         * This is pretty silly. Should probably just be implemented as an
+         * iterator to keep from having to instantiate all these tiles.
+         */
+        List<Tile> allTiles()
+        {
+            List<Tile> ret = new ArrayList<Tile>();
+            for (int x = z12x0; x <= z12x1; x++) {
+                for (int y = z12y0; y <= z12y1; y++) {
+                    Tile t = new Tile(x % tileMax, y % tileMax);
+                    ret.add(t);
+                }
+            }
+            return ret;
+        }
+
+        boolean topTile(Tile t) {
+            if (t.y == z12y0 )
+                return true;
+            return false;
+        }
+
+        boolean leftTile(Tile t) {
+            if (t.x == z12x0 )
+                return true;
+            return false;
+        }
+    }
+
+    /**
+     */
+    @Override
+    public void paint(Graphics g, MapView mv) {
+        long start = System.currentTimeMillis();
+        LatLon topLeft = mv.getLatLon(0, 0);
+        LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
+        Graphics oldg = g;
+
+        if (botRight.lon() == 0.0 || botRight.lat() == 0) {
+            Main.debug("still initializing??");
+            // probably still initializing
+            return;
+        }
+        if (lastTopLeft != null && lastBotRight != null && topLeft.equalsEpsilon(lastTopLeft)
+                && botRight.equalsEpsilon(lastBotRight) && bufferImage != null
+                && mv.getWidth() == bufferImage.getWidth(null) && mv.getHeight() == bufferImage.getHeight(null)
+                && !needRedraw) {
+
+            this.debug("drawing buffered image");
+            g.drawImage(bufferImage, 0, 0, null);
+            return;
+        }
+
+        needRedraw = false;
+        lastTopLeft = topLeft;
+        lastBotRight = botRight;
+        bufferImage = mv.createImage(mv.getWidth(), mv.getHeight());
+        g = bufferImage.getGraphics();
+
+        TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);
+        int zoom = currentZoomLevel;
+
+        if (zoomDecreaseAllowed() && (ts.tilesSpanned() > 18)) {
+            this.debug("too many tiles, decreasing zoom from " + currentZoomLevel);
+            if (decreaseZoomLevel())
+                this.paint(oldg, mv);
+            return;
+        }
+        if (zoomIncreaseAllowed() && (ts.tilesSpanned() < 1.0)) {
+            this.debug("doesn't even cover one tile (" + ts.tilesSpanned()
+                       + "), increasing zoom from " + currentZoomLevel);
+            if (increaseZoomLevel())
+                 this.paint(oldg, mv);
+            return;
+        }
+
+        if (ts.tilesSpanned() <= 0) {
+            System.out.println("doesn't even cover one tile, increasing zoom from " + currentZoomLevel);
+            if (increaseZoomLevel()) {
+                this.paint(oldg, mv);
+            }
+            return;
+        }
+
+        int fontHeight = g.getFontMetrics().getHeight();
+
+        g.setColor(Color.DARK_GRAY);
+
+        /*
+         * Go looking for tiles in zoom levels *other* than the current
+         * one. Even if they might look bad, they look better than a
+         * blank tile.
+         */
+        int otherZooms[] = {-5, -4, -3, 2, -2, 1, -1};
+        for (int zoomOff : otherZooms) {
+            int zoom2 = currentZoomLevel + zoomOff;
+            if ((zoom2 < SlippyMapPreferences.getMinZoomLvl()) ||
+                (zoom2 > SlippyMapPreferences.getMaxZoomLvl())) {
+                continue;
+            }
+            TileSet ts2 = new TileSet(topLeft, botRight, zoom2);
+            int painted = this.paintTileImages(g, ts2, zoom2);
+            //if (painted > 0)
+            //    System.out.println("painted " + painted + " tiles from zoom: " + zoom2);
+        }
+        /*
+         * Save this for last since it will get painted over all the others
+         */
+        this.paintTileImages(g, ts, currentZoomLevel);
+        g.setColor(Color.red);
+
+        for (Tile t : ts.allTiles()) {
+            // This draws the vertical lines for the entire
+            // column. Only draw them for the top tile in
+            // the column.
+            if (ts.topTile(t)) {
+                Point p = t.pixelPos(currentZoomLevel);
+                if (SlippyMapPreferences.getDrawDebug()) {
+                    if (t.x % 32 == 0) {
+                        // level 7 tile boundary
+                        g.fillRect(p.x - 1, 0, 3, mv.getHeight());
+                    } else {
+                        g.drawLine(p.x, 0, p.x, mv.getHeight());
+                    }
+                }
+            }
+            this.paintTileText(ts, g, mv, currentZoomLevel, t);
+        }
+        float fadeBackground = SlippyMapPreferences.getFadeBackground();
+        oldg.drawImage(bufferImage, 0, 0, null);
+
+        if (lastImageScale != null) {
+            // If each source image pixel is being stretched into > 3
+            // drawn pixels, zoom in... getting too pixelated
+            if (lastImageScale > 3 && zoomIncreaseAllowed()) {
+                if (SlippyMapPreferences.getAutozoom()) {
+                    this.debug("autozoom increase: scale: " + lastImageScale);
+                    increaseZoomLevel();
+                }
+                this.paint(oldg, mv);
+            // If each source image pixel is being squished into > 0.32
+            // of a drawn pixels, zoom out.
+            } else if ((lastImageScale < 0.45) && (lastImageScale > 0) && zoomDecreaseAllowed()) {
+                if (SlippyMapPreferences.getAutozoom()) {
+                    this.debug("autozoom decrease: scale: " + lastImageScale);
+                    decreaseZoomLevel();
+                }
+                this.paint(oldg, mv);
+            }
+        }
+        g.setColor(Color.black);
+        g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);
+    }// end of paint method
+
+    /**
+     * This isn't very efficient, but it is only used when the
+     * user right-clicks on the map.
+     */
+    SlippyMapTile getTileForPixelpos(int px, int py) {
+        MapView mv = Main.map.mapView;
+        Point clicked = new Point(px, py);
+        LatLon topLeft = mv.getLatLon(0, 0);
+        LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
+        TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);
+        int z = currentZoomLevel;
+
+        Tile clickedTile = null;
+        Point p1 = null, p2 = null;
+        for (Tile t1 : ts.allTiles()) {
+            Tile t2 = new Tile(t1.x+1, t1.y+1);
+            p1 = t1.pixelPos(z);
+            p2 = t2.pixelPos(z);
+            Rectangle r = new Rectangle(p1,new Dimension(p2.x, p2.y));
+            if (!r.contains(clicked))
+                continue;
+            clickedTile  = t1;
+            break;
+        }
+        if (clickedTile == null)
+             return null;
+        System.out.println("clicked on tile: " + clickedTile.x + " " + clickedTile.y +
+                           " scale: " + lastImageScale + " currentZoomLevel: " + currentZoomLevel);
+        SlippyMapTile tile = getOrCreateTile(clickedTile.x, clickedTile.y, currentZoomLevel);
+        checkTileStorage();
+        return tile;
+    }
+
+    @Override
+    public Icon getIcon() {
+        return ImageProvider.get("slippymap");
+    }
+
+    @Override
+    public Object getInfoComponent() {
+        return null;
+    }
+
+    @Override
+    public Component[] getMenuEntries() {
+        return new Component[] {
+                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
+                new JSeparator(),
+                // color,
+                new JMenuItem(new RenameLayerAction(this.getAssociatedFile(), this)),
+                new JSeparator(),
+                new JMenuItem(new LayerListPopup.InfoAction(this)) };
+    }
+
+    @Override
+    public String getToolTipText() {
+        return null;
+    }
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return false;
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {
+    }
+
+    private int latToTileY(double lat, int zoom) {
+        double l = lat / 180 * Math.PI;
+        double pf = Math.log(Math.tan(l) + (1 / Math.cos(l)));
+        return (int) (Math.pow(2.0, zoom - 1) * (Math.PI - pf) / Math.PI);
+    }
+
+    private int lonToTileX(double lon, int zoom) {
+        return (int) (Math.pow(2.0, zoom - 3) * (lon + 180.0) / 45.0);
+    }
+
+    private double tileYToLat(int y, int zoom) {
+        return Math.atan(Math.sinh(Math.PI
+                - (Math.PI * y / Math.pow(2.0, zoom - 1))))
+                * 180 / Math.PI;
+    }
+
+    private double tileXToLon(int x, int zoom) {
+        return x * 45.0 / Math.pow(2.0, zoom - 3) - 180.0;
+    }
+
+    private SlippyMapTile imgToTile(Image img) {
+        // we use the enumeration to avoid ConcurrentUpdateExceptions
+        // with other users of the tileStorage
+        Enumeration<SlippyMapTile> e = tileStorage.elements();
+        while (e.hasMoreElements()) {
+            SlippyMapTile t = e.nextElement();
+            if (t.getImageNoTimestamp() != img) {
+                continue;
+            }
+            return t;
+        }
+        return null;
+    }
+
+    private static int nr_loaded = 0;
+    private static int at_zoom = -1;
+
+    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
+        boolean error = (infoflags & ERROR) != 0;
+        boolean done = ((infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0);
+        boolean success = ((infoflags & (ALLBITS)) != 0);
+        SlippyMapTile imageTile = imgToTile(img);
+        if (success || error) {
+            this.debug("tile done loading: " + imageTile);
+            markDone(img);
+        }
+        if (imageTile == null) {
+            System.err.println("no tile for image");
+            return false;
+        }
+
+        if ((infoflags & ERROR) != 0) {
+            String url; // = "unknown";
+            url = imageTile.getImageURL().toString();
+            System.err.println("imageUpdate(" + img + ") error " + url + ")");
+        }
+        if (((infoflags & ALLBITS) != 0)) {
+            int z = imageTile.getZoom();
+            if (z == at_zoom) {
+                nr_loaded++;
+            } else {
+                //System.out.println("downloaded " + nr_loaded + " at: " + at_zoom + " now going to " + z +
+                //                " class: " + img.getClass());
+                nr_loaded = 0;
+                at_zoom = z;
+            }
+        }
+        if ((infoflags & SOMEBITS) != 0) {
+            // if (y%100 == 0)
+            //System.out.println("imageUpdate("+img+") SOMEBITS ("+x+","+y+")");
+        }
+        // Repaint immediately if we are done, otherwise batch up
+        // repaint requests every 100 milliseconds
+        needRedraw = true;
+        Main.map.repaint(done ? 0 : 100);
+        return !done;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @seeorg.openstreetmap.josm.data.Preferences.PreferenceChangedListener#
+     * preferenceChanged(java.lang.String, java.lang.String)
+     */
+    public void preferenceChanged(String key, String newValue) {
+        if (key.startsWith(SlippyMapPreferences.PREFERENCE_PREFIX)) {
+            // System.err.println(this + ".preferenceChanged('" + key + "', '"
+            // + newValue + "') called");
+            // when fade background changed, no need to clear tile storage
+            // TODO move this code to SlippyMapPreferences class.
+            if (!key.equals(SlippyMapPreferences.PREFERENCE_FADE_BACKGROUND)) {
+                clearTileStorage();
+            }
+        }
+    }
+
+    @Override
+    public void destroy() {
+        Main.pref.listener.remove(SlippyMapLayer.this);
+    }
+}
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java
new file mode 100644
index 0000000..ffe6718
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPlugin.java
@@ -0,0 +1,40 @@
+package org.openstreetmap.josm.plugins.slippymap;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+
+/**
+ * Main class for the slippy map plugin.
+ *
+ * @author Frederik Ramm <frederik at remote.org>
+ *
+ */
+public class SlippyMapPlugin extends Plugin
+{
+    public SlippyMapPlugin()
+    {
+    }
+
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame)
+    {
+        if (newFrame != null){
+            SlippyMapLayer smlayer;
+            smlayer = new SlippyMapLayer();
+            Main.main.addLayer(smlayer);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.openstreetmap.josm.plugins.Plugin#getPreferenceSetting()
+     */
+    @Override
+    public PreferenceSetting getPreferenceSetting()
+    {
+        return new SlippyMapPreferenceSetting();
+    }
+
+}
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java
new file mode 100644
index 0000000..3ea6ea8
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferenceSetting.java
@@ -0,0 +1,105 @@
+package org.openstreetmap.josm.plugins.slippymap;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JSpinner;
+
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Preference Dialog for Slippy Map Tiles
+ *
+ * @author Hakan Tandogan <hakan at gurkensalat.com>
+ *
+ */
+public class SlippyMapPreferenceSetting implements PreferenceSetting {
+    /**
+     * ComboBox with all known tile sources.
+     */
+    private JComboBox tileSourceCombo;
+    
+    private JCheckBox autozoomActive = new JCheckBox(tr("autozoom"));
+    private JCheckBox autoloadTiles = new JCheckBox(tr("autoload tiles"));
+    private JSpinner maxZoomLvl = new JSpinner();
+    private JSlider fadeBackground = new JSlider(0, 100);
+    
+    public void addGui(PreferenceDialog gui)
+    {
+        //String description = tr("A plugin that adds to JOSM new layer. This layer could render external tiles.");
+        JPanel slippymapTab = gui.createPreferenceTab("slippymap.png", tr("SlippyMap"), tr("Settings for the SlippyMap plugin."));
+        String[] allMapUrls = SlippyMapPreferences.getAllMapUrls();
+        tileSourceCombo = new JComboBox(allMapUrls);
+        tileSourceCombo.setEditable(true);
+        String source = SlippyMapPreferences.getMapUrl();
+        tileSourceCombo.setSelectedItem(source);
+        slippymapTab.add(new JLabel(tr("Tile Sources")), GBC.std());
+        slippymapTab.add(GBC.glue(5, 0), GBC.std());
+        slippymapTab.add(tileSourceCombo, GBC.eol().fill(GBC.HORIZONTAL));
+        
+        slippymapTab.add(new JLabel(tr("Auto zoom: ")), GBC.std());
+        slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
+        slippymapTab.add(autozoomActive, GBC.eol().fill(GBC.HORIZONTAL));
+        
+        slippymapTab.add(new JLabel(tr("Autoload Tiles: ")), GBC.std());
+        slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
+        slippymapTab.add(autoloadTiles, GBC.eol().fill(GBC.HORIZONTAL));
+        
+        slippymapTab.add(new JLabel(tr("Max zoom lvl: ")), GBC.std());
+        slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
+        slippymapTab.add(this.maxZoomLvl, GBC.eol().fill(GBC.HORIZONTAL));
+        
+        slippymapTab.add(new JLabel(tr("Fade background: ")), GBC.std());
+        slippymapTab.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
+        slippymapTab.add(this.fadeBackground, GBC.eol().fill(GBC.HORIZONTAL));
+        
+        slippymapTab.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
+
+        this.loadSettings();
+    }
+
+
+    /**
+     * <p>
+     * Load settings from {@link SlippyMapPreferences} class. Loaded preferences are stored to local GUI components.
+     * Actualy this method loads and sets this params:<br>
+     * <ul>
+     * 	<li>autozoom - {@link #autozoomActive} - {@link SlippyMapPreferences#getAutozoom()}</li>
+     * 	<li>autoload - {@link #autoloadTiles} - {@link SlippyMapPreferences#getAutoloadTiles()}</li>
+     * 	<li>maxZoomLvl - {@link #maxZoomLvl} - {@link SlippyMapPreferences#getMaxZoomLvl()}</li>
+     * </ul>
+     * </p>
+     */
+    private void loadSettings() {
+        this.autozoomActive.setSelected(SlippyMapPreferences.getAutozoom());
+        this.autoloadTiles.setSelected(SlippyMapPreferences.getAutoloadTiles());
+        this.maxZoomLvl.setValue(SlippyMapPreferences.getMaxZoomLvl());
+        this.fadeBackground.setValue(Math.round(SlippyMapPreferences.getFadeBackground()*100f));
+    }
+    
+    /**
+     * <p>
+     * Someone pressed the "ok" button
+     * </p>
+     * <p>
+     * This method saves actual state from GUI objects to actual preferences.
+     * </p>
+     */
+    public boolean ok()
+    {
+        SlippyMapPreferences.setMapUrl(this.tileSourceCombo.getSelectedItem().toString());
+        SlippyMapPreferences.setAutozoom(this.autozoomActive.isSelected());
+        SlippyMapPreferences.setAutoloadTiles(this.autoloadTiles.isSelected());
+        SlippyMapPreferences.setMaxZoomLvl((Integer)this.maxZoomLvl.getValue());
+        SlippyMapPreferences.setFadeBackground(this.fadeBackground.getValue()/100f);
+        //restart isn't required
+        return false;
+    }
+}
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java
new file mode 100644
index 0000000..ebae7ce
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapPreferences.java
@@ -0,0 +1,219 @@
+package org.openstreetmap.josm.plugins.slippymap;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * Preferences for Slippy Map Tiles
+ * 
+ * @author Hakan Tandogan <hakan at gurkensalat.com>
+ * @author LuVar <lubomir.varga at freemap.sk>
+ * 
+ */
+public class SlippyMapPreferences
+{
+    public static final String PREFERENCE_PREFIX   = "slippymap";
+
+    public static final String PREFERENCE_TILE_URL = PREFERENCE_PREFIX + ".tile_url";
+    public static final String PREFERENCE_AUTOZOOM = PREFERENCE_PREFIX + ".autozoom";
+    public static final String PREFERENCE_AUTOLOADTILES = PREFERENCE_PREFIX + ".autoload_tiles";
+    public static final String PREFERENCE_MIN_ZOOM_LVL = PREFERENCE_PREFIX + ".min_zoom_lvl";
+    public static final String PREFERENCE_MAX_ZOOM_LVL = PREFERENCE_PREFIX + ".max_zoom_lvl";
+    public static final String PREFERENCE_FADE_BACKGROUND = PREFERENCE_PREFIX + ".fade_background";
+    public static final String PREFERENCE_DRAW_DEBUG = PREFERENCE_PREFIX + ".draw_debug";
+    
+    public static String getMapUrl()
+    {
+        String url = Main.pref.get(PREFERENCE_TILE_URL);
+
+        if (url == null || "".equals(url))
+        {
+            url = "http://tah.openstreetmap.org/Tiles/tile"; // t at h
+            Main.pref.put(PREFERENCE_TILE_URL, url);
+        }
+
+        return url;
+    }
+    
+    public static void setMapUrl(String mapUrl) {
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_TILE_URL, mapUrl);
+    }
+    
+    public static boolean getAutozoom()
+    {
+        String autozoom = Main.pref.get(PREFERENCE_AUTOZOOM);
+
+        if (autozoom == null || "".equals(autozoom))
+        {
+        	autozoom = "true";
+            Main.pref.put(PREFERENCE_AUTOZOOM, autozoom);
+        }
+
+        return Boolean.parseBoolean(autozoom);
+    }
+    
+    public static void setAutozoom(boolean autozoom) {
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_AUTOZOOM, autozoom);
+    }
+    
+    public static void setDrawDebug(boolean drawDebug) {
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_DRAW_DEBUG, drawDebug);
+    }
+    
+    public static boolean getDrawDebug()
+    {
+        String drawDebug = Main.pref.get(PREFERENCE_DRAW_DEBUG);
+
+        if (drawDebug == null || "".equals(drawDebug))
+        {
+        	drawDebug = "false";
+            Main.pref.put(PREFERENCE_DRAW_DEBUG, drawDebug);
+        }
+
+        return Boolean.parseBoolean(drawDebug);
+    }
+    
+    public static boolean getAutoloadTiles()
+    {
+        String autoloadTiles = Main.pref.get(PREFERENCE_AUTOLOADTILES);
+
+        if (autoloadTiles == null || "".equals(autoloadTiles))
+        {
+        	autoloadTiles = "true";
+            Main.pref.put(PREFERENCE_AUTOLOADTILES, autoloadTiles);
+        }
+
+        return Boolean.parseBoolean(autoloadTiles);
+    }
+    
+    public static void setFadeBackground(float fadeBackground) {
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_FADE_BACKGROUND, fadeBackground + "");
+    }
+
+    /**
+     * 
+     * @return	number between 0 and 1, inclusive
+     */
+    public static float getFadeBackground() {
+        String fadeBackground = Main.pref.get(PREFERENCE_FADE_BACKGROUND);
+
+        if (fadeBackground == null || "".equals(fadeBackground))
+        {
+        	fadeBackground = "0.0";
+            Main.pref.put(PREFERENCE_FADE_BACKGROUND, fadeBackground);
+        }
+        
+        float parsed;
+        try {
+        	parsed = Float.parseFloat(fadeBackground);
+        } catch (Exception ex) {
+        	setFadeBackground(0.1f);
+        	System.out.println("Error while parsing setting fade background to float! returning 0.1, because of error:");
+        	ex.printStackTrace(System.out);
+        	return 0.1f;
+        }
+        if(parsed < 0f) {
+        	parsed = 0f;
+        } else {
+        	if(parsed > 1f) {
+            	parsed = 1f;
+            }
+        }
+        return parsed;
+    }
+    
+    public static void setAutoloadTiles(boolean autoloadTiles) {
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_AUTOLOADTILES, autoloadTiles);
+    }
+    
+    public static int getMaxZoomLvl()
+    {
+        String maxZoomLvl = Main.pref.get(PREFERENCE_MAX_ZOOM_LVL);
+
+        if (maxZoomLvl == null || "".equals(maxZoomLvl))
+        {
+        	maxZoomLvl = "17";
+            Main.pref.put(PREFERENCE_MAX_ZOOM_LVL, maxZoomLvl);
+        }
+
+        int navrat;
+        try {
+        	navrat = Integer.parseInt(maxZoomLvl);
+        } catch (Exception ex) {
+        	throw new RuntimeException("Problem while converting string to int. Converting value of prefetrences " + PREFERENCE_MAX_ZOOM_LVL + ". Value=\"" + maxZoomLvl + "\". Should be an integer. Error: " + ex.getMessage(), ex);
+        }
+        if(navrat > 30) {
+    		System.err.println("MaxZoomLvl shouldnt be more than 30! Setting to 30.");
+    		navrat = 30;
+    	}
+        //if(navrat < SlippyMapPreferences.getMinZoomLvl()) {
+    	//	System.err.println("maxZoomLvl shouldnt be more than minZoomLvl! Setting to minZoomLvl.");
+    	//	navrat = SlippyMapPreferences.getMinZoomLvl();
+    	//}
+        return navrat;
+    }
+    
+    public static void setMaxZoomLvl(int maxZoomLvl) {
+    	if(maxZoomLvl > 30) {
+    		System.err.println("MaxZoomLvl shouldnt be more than 30! Setting to 30.");
+    		maxZoomLvl = 30;
+    	}
+    	if(maxZoomLvl < SlippyMapPreferences.getMinZoomLvl()) {
+    		System.err.println("maxZoomLvl shouldnt be more than minZoomLvl! Setting to minZoomLvl.");
+    		maxZoomLvl = SlippyMapPreferences.getMinZoomLvl();
+    	}
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_MAX_ZOOM_LVL, "" + maxZoomLvl);
+    }
+    
+    public static int getMinZoomLvl()
+    {
+        String minZoomLvl = Main.pref.get(PREFERENCE_MIN_ZOOM_LVL);
+
+        if (minZoomLvl == null || "".equals(minZoomLvl))
+        {
+        	minZoomLvl = "" + (SlippyMapPreferences.getMaxZoomLvl() - 4);
+            Main.pref.put(PREFERENCE_MIN_ZOOM_LVL, minZoomLvl);
+        }
+
+        int navrat;
+        try {
+        	navrat = Integer.parseInt(minZoomLvl);
+        } catch (Exception ex) {
+        	throw new RuntimeException("Problem while converting string to int. Converting value of prefetrences " + PREFERENCE_MIN_ZOOM_LVL + ". Value=\"" + minZoomLvl + "\". Should be an integer. Error: " + ex.getMessage(), ex);
+        }
+        if(navrat < 2) {
+    		System.err.println("minZoomLvl shouldnt be lees than 2! Setting to 2.");
+    		navrat = 2;
+    	}
+        //if(navrat > SlippyMapPreferences.getMaxZoomLvl()) {
+    	//	System.err.println("minZoomLvl shouldnt be more than maxZoomLvl! Setting to maxZoomLvl.");
+    	//	navrat = SlippyMapPreferences.getMaxZoomLvl();
+    	//}
+        return navrat;
+    }
+    
+    public static void setMinZoomLvl(int minZoomLvl) {
+    	if(minZoomLvl < 2) {
+    		System.err.println("minZoomLvl shouldnt be lees than 2! Setting to 2.");
+    		minZoomLvl = 2;
+    	}
+    	if(minZoomLvl > SlippyMapPreferences.getMaxZoomLvl()) {
+    		System.err.println("minZoomLvl shouldnt be more than maxZoomLvl! Setting to maxZoomLvl.");
+    		minZoomLvl = SlippyMapPreferences.getMaxZoomLvl();
+    	}
+    	Main.pref.put(SlippyMapPreferences.PREFERENCE_MIN_ZOOM_LVL, "" + minZoomLvl);
+    }
+    
+    public static String[] getAllMapUrls()
+    {
+        String[] defaultTileSources = new String[]
+        {
+                "http://tah.openstreetmap.org/Tiles/tile", // t at h
+                "http://tah.openstreetmap.org/Tiles/maplint", // maplint
+                "http://tile.openstreetmap.org", // mapnik
+                "http://hypercube.telascience.org/tiles/1.0.0/coastline", // coastline
+                "http://www.freemap.sk/layers/allinone/?", //freemapy.sk
+                "http://www.freemap.sk/layers/tiles/?", //freemapy.sk pokus 2
+        };
+        return defaultTileSources;
+    }
+}
diff --git a/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java
new file mode 100644
index 0000000..7e100ef
--- /dev/null
+++ b/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java
@@ -0,0 +1,188 @@
+package org.openstreetmap.josm.plugins.slippymap;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Class that contains information about one single slippy map tile.
+ * 
+ * @author Frederik Ramm <frederik at remote.org>
+ * @author LuVar <lubomir.varga at freemap.sk>
+ * @author Dave Hansen <dave at sr71.net>
+ * 
+ */
+public class SlippyMapTile implements ImageObserver
+{
+    private Image  tileImage;
+	private long timestamp;
+
+	private int x;
+	private int y;
+	private int z;
+	// Setting this to pending is a bit of a hack
+	// as it requires knowledge that SlippyMapLayer
+	// will put this tile in a queue before it calls
+	// loadImage().  But, this gives the best message
+	// to the user.
+	private String status = "pending download";
+    
+    private boolean imageDownloaded = false;
+
+    private String metadata;
+
+    public SlippyMapTile(int x, int y, int z)
+    {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+		timestamp = System.currentTimeMillis();
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+    public String getMetadata()
+    {
+        return metadata;
+    }
+
+    public URL getImageURL()
+    {
+        try
+        {
+            return new URL(SlippyMapPreferences.getMapUrl() + "/" + z + "/" + x + "/" + y + ".png");
+        }
+        catch (MalformedURLException mfu)
+        {
+            mfu.printStackTrace();
+        }
+            return null;
+        }
+
+    public Image loadImage()
+    {
+		// We do not update the timestamp in this function
+		// The download code prioritizes the most recent
+		// downloads and will download the oldest tiles last.
+        URL imageURL = this.getImageURL();
+        tileImage = Toolkit.getDefaultToolkit().createImage(imageURL);
+        Toolkit.getDefaultToolkit().prepareImage(tileImage, -1, -1, this);
+		Toolkit.getDefaultToolkit().sync();
+		status = "being downloaded";
+		return tileImage;
+    }
+	public String toString()
+	{
+			return "SlippyMapTile{zoom=" + z + " (" + x + "," + y + ") '" + status + "'}";
+	}
+	synchronized public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
+	{
+		if ((infoflags & ALLBITS) != 0) {
+    	    imageDownloaded = true;
+	        status = "downloaded";
+			if (tileImage == null) {
+                System.out.println("completed null'd image: " + this.toString());
+			}
+			tileImage = img;
+			return false;
+        }
+		return true;
+	}
+
+    public Image getImageNoTimestamp() {
+    	return tileImage;
+    }
+    
+    public Image getImage()
+    {
+        timestamp = System.currentTimeMillis();
+        return tileImage;
+    }
+
+    public int getZoom() {
+    	return z;
+    }
+    
+    synchronized public void dropImage()
+    {
+		if(tileImage != null) {
+			tileImage.flush();
+		    status = "dropped";
+		}
+		tileImage = null;
+		//  This should work in theory but doesn't seem to actually
+		//  reduce the X server memory usage
+		//tileImage.flush();
+	    imageDownloaded = false;
+    }
+    
+    public boolean isDownloaded() {
+    	return imageDownloaded;
+    }
+    
+    public void loadMetadata()
+    {
+        try
+        {
+            URL dev = new URL(
+                    "http://tah.openstreetmap.org/Tiles/info_short.php?x=" + x
+                            + "&y=" + y + "&z=" + z + "/layer=tile");
+            URLConnection devc = dev.openConnection();
+            BufferedReader in = new BufferedReader(new InputStreamReader(devc
+                    .getInputStream()));
+            metadata = tr(in.readLine());
+        }
+        catch (Exception ex)
+        {
+            metadata = tr("error loading metadata" + ex.toString());
+        }
+
+    }
+
+    public void requestUpdate()
+    {
+		if (z != 12) {
+            metadata = tr("error requesting update: not zoom-level 12");
+		}
+        try
+        {
+            URL dev = new URL("http://tah.openstreetmap.org/Request/create/?x=" + x
+                    + "&y=" + y + "&priority=1&src=slippymap_plugin");
+            URLConnection devc = dev.openConnection();
+            BufferedReader in = new BufferedReader(new InputStreamReader(devc
+                    .getInputStream()));
+			timestamp = System.currentTimeMillis();
+            metadata = tr("requested: {0}", tr(in.readLine()));
+        }
+        catch (Exception ex)
+        {
+            metadata = tr("error requesting update");
+        }
+    }
+
+    public long access_time()
+    {
+        return timestamp;
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof SlippyMapTile))
+            return false;
+        SlippyMapTile other = (SlippyMapTile) o;
+        return (this.x == other.x && this.y == other.y && this.z == other.z);
+    }
+    SlippyMapKey getKey()
+    {
+	    return new SlippyMapKey(x, y, z);
+    }
+}
diff --git a/surveyor/.classpath b/surveyor/.classpath
new file mode 100644
index 0000000..1148006
--- /dev/null
+++ b/surveyor/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/livegps"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/surveyor/.project b/surveyor/.project
new file mode 100644
index 0000000..3e801a1
--- /dev/null
+++ b/surveyor/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>josm-plugin-surveyor</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/surveyor/LICENSE b/surveyor/LICENSE
new file mode 100644
index 0000000..37b7a4d
--- /dev/null
+++ b/surveyor/LICENSE
@@ -0,0 +1,339 @@
+      GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+      How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/surveyor/build.xml b/surveyor/build.xml
new file mode 100644
index 0000000..18a9d5e
--- /dev/null
+++ b/surveyor/build.xml
@@ -0,0 +1,69 @@
+<project name="surveyor" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="livegpsplugin.jar"      value="${plugin.dist.dir}/livegps.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+            <classpath>
+                <pathelement location="${josm}"/>
+                <pathelement location="${livegpsplugin.jar}"/>
+            </classpath>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/">
+            <fileset dir="resources">
+                <include name="*.xml"/>
+                <include name="audio/*"/>
+            </fileset>
+        </copy>
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Christof Dallermassl"/>
+                <attribute name="Plugin-Class" value="at.dallermassl.josm.plugin.surveyor.SurveyorPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Allow adding markers/nodes on current gps positions."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/Surveyor"/>
+                <attribute name="Plugin-Mainversion" value="2012"/>
+                <attribute name="Plugin-Requires" value="livegps"/>
+                <attribute name="Plugin-Stage" value="60"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/surveyor/changelog.txt b/surveyor/changelog.txt
new file mode 100644
index 0000000..448a1f6
--- /dev/null
+++ b/surveyor/changelog.txt
@@ -0,0 +1,7 @@
+2007-08-05: added BeepAction. Parameter is number of beeps
+
+2007-08-02: PlayAudioAction is able to read wav files from classpath and from urls
+
+            It also plays the audio file in the background, so the PlayAudioAction can
+            be set as the first action without delaying the setting of the waypoint/nodes
+            by a following SetWaypointAction.
\ No newline at end of file
diff --git a/surveyor/copyright.txt b/surveyor/copyright.txt
new file mode 100644
index 0000000..4965647
--- /dev/null
+++ b/surveyor/copyright.txt
@@ -0,0 +1,6 @@
+Plugin surveyor
+
+This plugin is copyrighted 2008-2009 
+by Christof Dallermassl <christof at dallermassl.at>.
+
+It is distributed under GPL-v2 license (see file  LICENSE in this directory).
diff --git a/surveyor/images/addrelation.png b/surveyor/images/addrelation.png
new file mode 100644
index 0000000..c1879f1
Binary files /dev/null and b/surveyor/images/addrelation.png differ
diff --git a/surveyor/images/autosave.png b/surveyor/images/autosave.png
new file mode 100644
index 0000000..22685b5
Binary files /dev/null and b/surveyor/images/autosave.png differ
diff --git a/surveyor/images/surveyormenu.png b/surveyor/images/surveyormenu.png
new file mode 100644
index 0000000..9da1a04
Binary files /dev/null and b/surveyor/images/surveyormenu.png differ
diff --git a/surveyor/resources/audio/KDE_Window_Iconify.wav b/surveyor/resources/audio/KDE_Window_Iconify.wav
new file mode 100644
index 0000000..55b4e45
Binary files /dev/null and b/surveyor/resources/audio/KDE_Window_Iconify.wav differ
diff --git a/surveyor/resources/surveyor.xml b/surveyor/resources/surveyor.xml
new file mode 100644
index 0000000..1ecd455
--- /dev/null
+++ b/surveyor/resources/surveyor.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<surveyor columns="4" width="1300" height="0">
+  <!-- icons can either be absolute paths or relative paths to the .josm directory -->
+  <!-- action class: either fully qualified classnames or if not found,
+       package at.dallermassl.josm.plugin.surveyor.action is assumed -->
+  <button label="Tunnel Start" hotkey="T" icon="styles/standard/vehicle/tunnel.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="tunnel start"/>
+    <!--action class="ConsolePrinterAction" params="tunnel start,tunnel"/-->
+    <!--action class="SystemExecuteAction" params="mplayer,-quiet,/usr/share/apps/klettres/de/alpha/x.ogg"/-->
+  </button>
+  <button label="Bridge" hotkey="B" type="toggle">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="bridge"/>
+  </button>
+  <button label="Village/City" hotkey="V" icon="styles/standard/place.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="village"/>
+    <!--
+    <action class="SetNodeAction" params=""/>
+    <action class="AnnotationPresetAction" params="Places"/>
+    -->
+  </button>
+  <button label="Parking" hotkey="P" icon="styles/standard/vehicle/parking.png">
+    <!--action class="SetNodeAction" params="amenity=parking"/-->
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Parking"/>
+  </button>
+  <button label="One Way" hotkey="O" icon="presets/oneway.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="oneway=yes"/>
+  </button>
+  <button label="Church" hotkey="C" icon="styles/standard/religion/church.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetNodeAction" params="amenity=place_of_worship,denomination=christian"/>
+    <action class="SetWaypointAction" params="Church"/>
+  </button>
+  <button label="Fuel Station" hotkey="F" icon="styles/standard/vehicle/fuel.png">
+    <!--action class="SetNodeAction" params="amenity=fuel"/-->
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Fuel"/>
+  </button>
+  <button label="Hotel" hotkey="H" icon="styles/standard/accommodation/hotel.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Hotel"/>
+  </button>
+  <button label="Restaurant" hotkey="R" icon="styles/standard/food/restaurant.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetNodeAction" params="amenity=restaurant"/>
+    <action class="SetWaypointAction" params="Restaurant"/>
+  </button>
+  <button label="Shopping" hotkey="S" icon="styles/standard/shop.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Info"/>
+  </button>
+  <button label="WC" hotkey="W" icon="styles/standard/vehicle/parking/restarea_toilets.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Info"/>
+  </button>
+  <button label="Camping" hotkey="Z" icon="styles/standard/accommodation/camping/caravan.png">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Camping"/>
+  </button>
+  <button label="Info" hotkey="I">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Info"/>
+  </button>
+  <button label="Exit" hotkey="E">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Exit"/>
+  </button>
+  <button label="Motorway" hotkey="1">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Motorway"/>
+  </button>
+  <button label="Primary" hotkey="2">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Primary"/>
+  </button>
+  <button label="Secondary" hotkey="3">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Secondary"/>
+  </button>
+  <button label="Unclassified" hotkey="4">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Unclassified"/>
+  </button>
+  <button label="Residential" hotkey="5">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Residential"/>
+  </button>
+  <!--
+  <button label="Test" hotkey="shift X">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Test X"/>
+  </button>
+  <button label="Test" hotkey="F12">
+    <action class="PlayAudioAction" params="resource://audio/KDE_Window_Iconify.wav"/>
+    <action class="SetWaypointAction" params="Test"/>
+  </button>
+   -->
+</surveyor>
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/ActionConstants.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ActionConstants.java
new file mode 100644
index 0000000..609a5a5
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ActionConstants.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import javax.swing.Action;
+
+/**
+ * Definition of constants for actions (as {@link Action#SELECTED_KEY} only exists since java 1.6
+ * Followed tutorial at http://www.javalobby.org/java/forums/t53484.html
+ * @author cdaller
+ *
+ */
+public final class ActionConstants {
+
+    /**
+     *
+     */
+    private ActionConstants() { }
+
+    public static final String SELECTED_KEY = "actionConstants.selected";
+}
\ No newline at end of file
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveAction.java
new file mode 100644
index 0000000..ead8636
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveAction.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import java.awt.event.KeyEvent;
+
+import at.dallermassl.josm.plugin.surveyor.action.SetWaypointAction;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.tools.Shortcut;
+
+import livegps.LiveGpsLayer;
+
+/**
+ * @author cdaller
+ *
+ */
+public class AutoSaveAction extends JosmAction {
+    private static final long serialVersionUID = -8608679323231116043L;
+    private static final long AUTO_SAVE_PERIOD_SEC = 60; // once a minute
+    public static final String GPS_FILE_NAME_PATTERN = "surveyor-{0,date,yyyyMMdd-HHmmss}.gpx";
+    public static final String OSM_FILE_NAME_PATTERN = "surveyor-{0,date,yyyyMMdd-HHmmss}.osm";
+    private boolean autoSave = false;
+    private Timer gpsDataTimer;
+
+    public AutoSaveAction() {
+        super(tr("AutoSave LiveData"), "autosave.png", tr("Save captured data to file every minute."),
+        Shortcut.registerShortcut("surveyor:autosave", tr("Tool: {0}", tr("AutoSave LiveData")),
+        KeyEvent.VK_S, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+     */
+    public void actionPerformed(ActionEvent e) {
+        if (e.getSource() instanceof AbstractButton) {
+            autoSave = ((AbstractButton)e.getSource()).isSelected();
+        }
+
+        if(autoSave) {
+            if(gpsDataTimer == null) {
+                gpsDataTimer = new Timer();
+            }
+            TimerTask task;
+
+            String gpxFilename = MessageFormat.format(GPS_FILE_NAME_PATTERN, new Date());
+            task = new AutoSaveGpsLayerTimerTask(gpxFilename, LiveGpsLayer.LAYER_NAME);
+            gpsDataTimer.schedule(task, 1000, AUTO_SAVE_PERIOD_SEC * 1000);
+
+            String osmFilename = MessageFormat.format(OSM_FILE_NAME_PATTERN, new Date());
+            task = new AutoSaveEditLayerTimerTask(osmFilename);
+            gpsDataTimer.schedule(task, 5000, AUTO_SAVE_PERIOD_SEC * 1000);
+        } else {
+            if(gpsDataTimer != null) {
+                gpsDataTimer.cancel();
+            }
+        }
+
+
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveEditLayerTimerTask.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveEditLayerTimerTask.java
new file mode 100644
index 0000000..b8b580a
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveEditLayerTimerTask.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.TimerTask;
+
+import javax.swing.JOptionPane;
+
+import livegps.LiveGpsLock;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.OsmWriter;
+import org.openstreetmap.josm.io.XmlWriter;
+
+/**
+ * @author cdaller
+ *
+ */
+public class AutoSaveEditLayerTimerTask extends TimerTask {
+    private File file;
+
+    public AutoSaveEditLayerTimerTask(String filename) {
+        file = new File(filename);
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.TimerTask#run()
+     */
+    @Override
+    public void run() {
+        if(Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) {
+            return;
+        }
+        OsmDataLayer layer = Main.map.mapView.getEditLayer();
+        try {
+            DataSet dataset = layer.data;
+
+//            File outFile = layer.associatedFile;
+//            if(outFile == null) {
+//                outFile = file;
+//            }
+
+            // write to temporary file, on success, rename tmp file to target file:
+            File tmpFile = new File(file.getAbsoluteFile()+".tmp");
+            System.out.println("AutoSaving osm data to file " + file.getAbsolutePath());
+            synchronized(LiveGpsLock.class) {
+                OsmWriter w = new OsmWriter(new PrintWriter(new FileOutputStream(tmpFile)), false, dataset.version);
+                w.header();
+                w.writeDataSources(dataset);
+                w.writeContent(dataset);
+                w.footer();
+            }
+            tmpFile.renameTo(file);
+            System.out.println("AutoSaving finished");
+        } catch (IOException x) {
+            x.printStackTrace();
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("Error while exporting {0}: {1}", file.getAbsoluteFile(), x.getMessage()),
+                tr("Error"),
+                JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveGpsLayerTimerTask.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveGpsLayerTimerTask.java
new file mode 100644
index 0000000..b83c7b7
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/AutoSaveGpsLayerTimerTask.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.TimerTask;
+
+import javax.swing.JOptionPane;
+
+import livegps.LiveGpsLock;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.io.GpxWriter;
+
+import at.dallermassl.josm.plugin.surveyor.util.LayerUtil;
+
+/**
+ * TimerTask that writes the data of a {@link GpxLayer} to a gpx file.
+ * Every time the task is run, the layer is retrieved from the map view so it even works
+ * if the layer does not exist at the start of the timer task. If the layer does not exist,
+ * the file is not written.
+ *
+ * @author cdaller
+ *
+ */
+public class AutoSaveGpsLayerTimerTask extends TimerTask {
+    private String gpsLayerName;
+    private File file;
+
+    /**
+     * Constructor using the file to write to and the name of the layer.
+     * @param filename the file to write to.
+     * @param gpsLayername the name of the layer holding the gps data.
+     */
+    public AutoSaveGpsLayerTimerTask(String filename, String layerName) {
+        super();
+        this.gpsLayerName = layerName;
+        this.file = new File(filename);
+    }
+
+    /**
+     * @return the gpsLayerName
+     */
+    public String getGpsLayerName() {
+        return this.gpsLayerName;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.TimerTask#run()
+     */
+    @Override
+    public void run() {
+
+        try {
+
+            GpxLayer gpsLayer = LayerUtil.findGpsLayer(gpsLayerName, GpxLayer.class);
+            if(gpsLayer == null) {
+                return;
+            }
+            // write to temporary file, on success, rename tmp file to target file:
+            File tmpFile = new File(file.getAbsoluteFile()+".tmp");
+            System.out.println("AutoSaving data to file " + file.getAbsolutePath());
+            // synchronize on layer to prevent concurrent adding of data to the layer
+            // quite a hack, but no other object to sync available :-(
+            // @see LiveGpsLayer
+            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(tmpFile)));
+            GpxWriter gpxWriter = new GpxWriter(out);
+            synchronized(LiveGpsLock.class) {
+                gpxWriter.write(gpsLayer.data);
+            }
+            tmpFile.renameTo(file);
+        } catch (IOException ioExc) {
+            ioExc.printStackTrace();
+            JOptionPane.showMessageDialog(Main.parent,
+                tr("Error while exporting {0}: {1}", file.getAbsoluteFile(), ioExc.getMessage()),
+                tr("Error"),
+                JOptionPane.ERROR_MESSAGE);
+        }
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonDescription.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonDescription.java
new file mode 100644
index 0000000..82054c2
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonDescription.java
@@ -0,0 +1,254 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.ComponentInputMap;
+import javax.swing.Icon;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JToggleButton;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.ActionMapUIResource;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * this class represents a button as it is described in the xml file.
+ * @author cdaller
+ *
+ */
+public class ButtonDescription {
+    private String label;
+    private String hotkey;
+    private String iconName;
+    private ButtonType type;
+    private List<SurveyorActionDescription> actions;
+    private GpsDataSource gpsDataSource;
+
+    /**
+     * Default Constructor
+     */
+    public ButtonDescription() {
+        super();
+    }
+    /**
+     * @param hotkey
+     * @param actions a list of actions to be performed.
+     * @param type if <code>null</code> {@link ButtonType#SINGLE} is used.
+     */
+    public ButtonDescription(String label, String hotkey, String iconName, String buttonAction, ButtonType type) {
+        this(label, hotkey, iconName, createFromOneElement(new SurveyorActionDescription(buttonAction)), type);
+    }
+
+    /**
+     * @param hotkey
+     * @param actions a list of actions to be performed.
+     * @param type if <code>null</code> {@link ButtonType#SINGLE} is used.
+     */
+    public ButtonDescription(String label, String hotkey, String iconName, SurveyorActionDescription actionDescription, ButtonType type) {
+        this(label, hotkey, iconName, createFromOneElement(actionDescription), type);
+    }
+
+    /**
+     * Helper method to create a list from one element.
+     * @param buttonActionClassName the action's class name.
+     * @return a list holding one ButtonActionDescription element.
+     */
+    private static List<SurveyorActionDescription> createFromOneElement(SurveyorActionDescription actionDescription) {
+        List<SurveyorActionDescription> list = new ArrayList<SurveyorActionDescription>();
+        list.add(actionDescription);
+        return list;
+    }
+
+    /**
+     * @param hotkey
+     * @param actions a list of actions to be performed.
+     * @param type if <code>null</code> {@link ButtonType#SINGLE} is used.
+     */
+    public ButtonDescription(String label, String hotkey, String iconName, List<SurveyorActionDescription> actions, ButtonType type) {
+        super();
+        this.label = label;
+        this.hotkey = hotkey;
+        this.iconName = iconName;
+        if(type == null) {
+            this.type = ButtonType.SINGLE;
+        } else {
+            this.type = type;
+        }
+        this.actions = actions;
+    }
+
+    /**
+     * @return the actions
+     */
+    public List<SurveyorActionDescription> getActions() {
+        return this.actions;
+    }
+    /**
+     * @param actions the actions to set
+     */
+    public void setActions(List<SurveyorActionDescription> actions) {
+        this.actions = actions;
+    }
+    /**
+     * @return the hotkey
+     */
+    public String getHotkey() {
+        return this.hotkey;
+    }
+    /**
+     * @param hotkey the hotkey to set
+     */
+    public void setHotkey(String hotkey) {
+        this.hotkey = hotkey;
+    }
+    /**
+     * @return the label
+     */
+    public String getLabel() {
+        return this.label;
+    }
+
+    /**
+     * @param label the label to set
+     */
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    /**
+     * @return the type
+     */
+    public ButtonType getButtonType() {
+        return this.type;
+    }
+
+    /**
+     * Set the button type as a string.
+     * @param type the type of the button
+     * @see ButtonType
+     */
+    public void setType(String type) {
+        try {
+        this.type = ButtonType.valueOf(type.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            System.err.println("Unkown button type '" + type + "' given. Allowed values are " + Arrays.toString(ButtonType.values()));
+        }
+    }
+    /**
+     * @param type the type to set
+     */
+    public void setButtonType(ButtonType type) {
+        this.type = type;
+    }
+
+    /**
+     * Sets the name of the icon.
+     * @param icon
+     */
+    public void setIcon(String icon) {
+        this.iconName = icon;
+    }
+
+    /**
+     * Creates the button.
+     * @return the button.
+     */
+    public JComponent createComponent() {
+
+        String actionName = tr(getLabel()) + " (" + hotkey + ")";
+
+        Icon icon = ImageProvider.getIfAvailable(null,iconName);
+        if (icon == null)
+            icon = ImageProvider.getIfAvailable("markers",iconName);
+        if (icon == null)
+            icon = ImageProvider.getIfAvailable("symbols",iconName);
+        if (icon == null)
+            icon = ImageProvider.getIfAvailable("nodes",iconName);
+
+        MetaAction action = new MetaAction(actionName, icon);
+        action.setActions(actions);
+        action.setGpsDataSource(gpsDataSource);
+
+        AbstractButton button;
+        if(type == ButtonType.TOGGLE) {
+            button = new JToggleButton(action);
+            connectActionAndButton(action, button);
+        } else {
+            button = new JButton(action);
+        }
+        button.setActionCommand(label);
+
+        // connect component keyboard map with buttons:
+        ActionMap actionMap = new ActionMapUIResource();
+        actionMap.put(actionName, action);
+
+        InputMap keyMap = new ComponentInputMap(button);
+        keyMap.put(KeyStroke.getKeyStroke(hotkey), actionName);
+
+        SwingUtilities.replaceUIActionMap(button, actionMap);
+        SwingUtilities.replaceUIInputMap(button, JComponent.WHEN_IN_FOCUSED_WINDOW, keyMap);
+        return button;
+    }
+
+    private static void connectActionAndButton(Action action, AbstractButton button) {
+        SelectionStateAdapter adapter = new SelectionStateAdapter(action, button);
+        adapter.configure();
+    }
+
+    /**
+     * Class that connects the selection state of the action
+     * to the selection state of the button.
+     *
+     * @author R.J. Lorimer
+     */
+    private static class SelectionStateAdapter implements PropertyChangeListener, ItemListener {
+        private Action action;
+        private AbstractButton button;
+        public SelectionStateAdapter(Action theAction, AbstractButton theButton) {
+            action = theAction;
+            button = theButton;
+        }
+        protected void configure() {
+            action.addPropertyChangeListener(this);
+            button.addItemListener(this);
+        }
+        public void itemStateChanged(ItemEvent e) {
+            boolean value = e.getStateChange() == ItemEvent.SELECTED;
+            Boolean valueObj = Boolean.valueOf(value);
+            action.putValue(ActionConstants.SELECTED_KEY, valueObj);
+        }
+
+        public void propertyChange(PropertyChangeEvent evt) {
+            if(evt.getPropertyName().equals(ActionConstants.SELECTED_KEY)) {
+                Boolean newSelectedState = (Boolean)evt.getNewValue();
+                button.setSelected(newSelectedState.booleanValue());
+            }
+        }
+    }
+
+    /**
+     * Set the source of gps data.
+     * @param gpsDataSource the source from where gps data can be obtained.
+     */
+    public void setGpsDataSource(GpsDataSource gpsDataSource) {
+        this.gpsDataSource = gpsDataSource;
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonType.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonType.java
new file mode 100644
index 0000000..1357e45
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/ButtonType.java
@@ -0,0 +1,14 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+/**
+ * @author cdaller
+ *
+ */
+public enum ButtonType {
+    SINGLE,
+    TOGGLE
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsActionEvent.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsActionEvent.java
new file mode 100644
index 0000000..01c8e28
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsActionEvent.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+
+/**
+ * @author cdaller
+ *
+ */
+public class GpsActionEvent extends ActionEvent {
+    private static final long serialVersionUID = 2674961758007055637L;
+    private LatLon coordinates;
+
+
+    /**
+     * @param e
+     * @param latitude
+     * @param longitude
+     */
+    public GpsActionEvent(ActionEvent e, double latitude, double longitude) {
+        super(e.getSource(), e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers());
+        coordinates = new LatLon(latitude, longitude);
+    }
+
+
+    /**
+     * @return the coordinates
+     */
+    public LatLon getCoordinates() {
+        return this.coordinates;
+    }
+
+
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsDataSource.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsDataSource.java
new file mode 100644
index 0000000..03cef66
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/GpsDataSource.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import livegps.LiveGpsData;
+
+/**
+ * @author cdaller
+ *
+ */
+public interface GpsDataSource {
+    /**
+     * Returns gps data.
+     * @return gps data.
+     */
+    public LiveGpsData getGpsData();
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/MetaAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/MetaAction.java
new file mode 100644
index 0000000..82c9127
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/MetaAction.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.JFrame;
+
+import org.openstreetmap.josm.Main;
+
+import livegps.LiveGpsData;
+
+/**
+ * Action that fires a {@link SurveyorAction} to the registered actions.
+ *
+ * @author cdaller
+ *
+ */
+public class MetaAction extends AbstractAction {
+    private static final long serialVersionUID = -1523524381092575809L;
+    private List<SurveyorActionDescription> actions;
+    private GpsDataSource gpsDataSource;
+    private long lastActionCall = 0;
+    public static final long MIN_TIME_DIFF = 500; // 500ms
+
+    /**
+     *
+     */
+    public MetaAction() {
+        // TODO Auto-generated constructor stub
+    }
+
+    /**
+     * @param name
+     */
+    public MetaAction(String name) {
+        super(name);
+        // TODO Auto-generated constructor stub
+    }
+
+    /**
+     * @param name
+     * @param icon
+     */
+    public MetaAction(String name, Icon icon) {
+        super(name, icon);
+        // TODO Auto-generated constructor stub
+    }
+
+    /**
+     * @return the actions
+     */
+    public List<SurveyorActionDescription> getActions() {
+        return this.actions;
+    }
+
+    /**
+     * @param actions
+     *            the actions to set
+     */
+    public void setActions(List<SurveyorActionDescription> actions) {
+        this.actions = actions;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+     */
+    public void actionPerformed(ActionEvent e) {
+        // check if action was called by repeating key presses too long pressed):
+        long time = System.currentTimeMillis();
+        if ((time - lastActionCall) < MIN_TIME_DIFF) {
+            lastActionCall = time;
+// System.out.println("repeating key detected");
+            return;
+        }
+        lastActionCall = time;
+        // System.out.println("meta action '" + super.toString() + "' called");
+
+        // toggle on/off
+        Boolean selected = (Boolean) getValue(ActionConstants.SELECTED_KEY);
+        if (selected == null || selected == Boolean.FALSE) {
+            selected = Boolean.TRUE;
+        } else {
+            selected = Boolean.FALSE;
+        }
+        putValue(ActionConstants.SELECTED_KEY, selected);
+
+        LiveGpsData gpsData = gpsDataSource.getGpsData();
+        if (gpsData != null && gpsData.isFix()) {
+            double latitude = gpsData.getLatitude();
+            double longitude = gpsData.getLongitude();
+            GpsActionEvent gpsEvent = new GpsActionEvent(e, latitude, longitude);
+            for (SurveyorActionDescription action : actions) {
+                action.actionPerformed(gpsEvent);
+            }
+        } else {
+            System.out.println("Surveyor: no gps data available!");
+            // TEST for offline test only:
+            if(Main.pref.getBoolean("surveyor.debug")) {
+                for (SurveyorActionDescription action : actions) {
+                    action.actionPerformed(new GpsActionEvent(e, 0, 0));
+                }
+            }
+        }
+        JFrame frame = SurveyorPlugin.getSurveyorFrame();
+        if(frame != null && frame.isVisible()) {
+            frame.toFront();
+        }
+    }
+
+    /**
+     * @param gpsDataSource
+     */
+    public void setGpsDataSource(GpsDataSource gpsDataSource) {
+        this.gpsDataSource = gpsDataSource;
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorAction.java
new file mode 100644
index 0000000..b79b109
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorAction.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import java.util.List;
+
+/**
+ * @author cdaller
+ *
+ */
+public interface SurveyorAction {
+
+    /**
+     * Action callback indicating that the action should do something.
+     * @param event the event.
+     */
+    public void actionPerformed(GpsActionEvent event);
+
+    /**
+     * Sets the parameters for the action execution.
+     * @param parameters the parameters.
+     */
+    public void setParameters(List<String> parameters);
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionDescription.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionDescription.java
new file mode 100644
index 0000000..87872f2
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionDescription.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dinopolis.util.io.Tokenizer;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SurveyorActionDescription {
+    private String actionClass;
+    private List<String> params;
+    private SurveyorAction action;
+
+
+    /**
+     * Default Constructor
+     */
+    public SurveyorActionDescription() {
+        super();
+    }
+    /**
+     * @param actionClass
+     * @param params
+     */
+    public SurveyorActionDescription(String actionClass) {
+        super();
+        this.actionClass = actionClass;
+    }
+    /**
+     * @param actionClass
+     * @param params
+     */
+    public SurveyorActionDescription(String actionClass, List<String> params) {
+        super();
+        this.actionClass = actionClass;
+        this.params = params;
+    }
+    /**
+     * @param actionClass
+     * @param params
+     */
+    public SurveyorActionDescription(String actionClass, String[] params) {
+        super();
+        this.actionClass = actionClass;
+        this.params = new ArrayList<String>();
+        for (int index = 0; index < params.length; index++) {
+            this.params.add(params[index]);
+        }
+    }
+    /**
+     * @return the actionClass
+     */
+    public String getActionClass() {
+        return this.actionClass;
+    }
+    /**
+     * @param actionClass the actionClass to set
+     */
+    public void setActionClass(String actionClass) {
+        this.actionClass = actionClass;
+    }
+    /**
+     * @return the params
+     */
+    public List<String> getParameterList() {
+        return this.params;
+    }
+    /**
+     * @param params the params to set
+     */
+    public void setParameterList(List<String> params) {
+        this.params = params;
+    }
+
+    public void actionPerformed(GpsActionEvent e) {
+        if(action == null) {
+            action = SurveyorActionFactory.getInstance(actionClass);
+            action.setParameters(getParameterList());
+        }
+        action.actionPerformed(e);
+    }
+
+    /**
+     * Sets the classname of the action to use. Callback method of xml parser.
+     * @param claszName the name of the action class.
+     */
+    public void setClass_(String claszName) {
+        setActionClass(claszName);
+    }
+
+    /**
+     * Set the params as a comma separated string.
+     * @param paramString the comma separated string for the parameters.
+     */
+    public void setParams(String paramString) {
+        Tokenizer tokenizer = new Tokenizer(paramString, ",");
+        try {
+            params = tokenizer.nextLine();
+        } catch (IOException ignore) {
+        }
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionFactory.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionFactory.java
new file mode 100644
index 0000000..82db7c1
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorActionFactory.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple factory that creates a class instance from a classname. It caches the instances, so
+ * the action instances are used as singletons!
+ * A package name of "at.dallermassl.josm.plugin.surveyor.action" is assumed, if the class could
+ * not be found.
+ *
+ * @author cdaller
+ *
+ */
+public class SurveyorActionFactory {
+    private static Map<String, SurveyorAction>actionCache = new HashMap<String, SurveyorAction>();
+    public static final String DEFAULT_PACKAGE = SurveyorActionFactory.class.getPackage().getName() + ".action";
+
+    /**
+     * @param actionClass
+     * @return
+     */
+    public static SurveyorAction getInstance(String actionClass) {
+        try {
+            SurveyorAction action = actionCache.get(actionClass);
+            if(action == null) {
+                try {
+                    action = (SurveyorAction)Class.forName(actionClass).newInstance();
+                } catch (ClassNotFoundException e) {
+                    actionClass = DEFAULT_PACKAGE + "." + actionClass;
+                    action = (SurveyorAction)Class.forName(actionClass).newInstance();
+                }
+                actionCache.put(actionClass, action);
+            }
+            return action;
+        } catch (InstantiationException e) {
+            throw new RuntimeException("Could not create action class '" + actionClass + "'", e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Could not create action class '" + actionClass + "'", e);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("Could not create action class '" + actionClass + "'", e);
+        }
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorComponent.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorComponent.java
new file mode 100644
index 0000000..6cda7f5
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorComponent.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import livegps.LiveGpsData;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.XmlObjectParser;
+import org.xml.sax.SAXException;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SurveyorComponent extends JComponent implements PropertyChangeListener, GpsDataSource {
+    private static final long serialVersionUID = 4539838472057529042L;
+    private LiveGpsData gpsData;
+    private int rows = 0;
+    private int columns = 0;
+    private int width = 0;
+    private int height = 0;
+    private JLabel streetLabel;
+    private JPanel buttonPanel;
+    private Set<String>hotKeys;
+
+    public SurveyorComponent() {
+        super();
+        hotKeys = new HashSet<String>();
+        setLayout(new BorderLayout());
+        streetLabel = new JLabel(tr("Way: "));
+        float fontSize = Float.parseFloat(Main.pref.get(SurveyorPlugin.PREF_KEY_STREET_NAME_FONT_SIZE, "35"));
+        Main.pref.put(SurveyorPlugin.PREF_KEY_STREET_NAME_FONT_SIZE, String.valueOf(fontSize));
+        streetLabel.setFont(streetLabel.getFont().deriveFont(35f));
+        add(streetLabel, BorderLayout.NORTH);
+        buttonPanel = new JPanel();
+        add(buttonPanel, BorderLayout.CENTER);
+    }
+
+    /**
+     * Set the number of rows as a string (callback method from xml parser).
+     * @param rowsString the row string.
+     */
+    public void setRows(String rowsString) {
+        rows = Integer.parseInt(rowsString);
+        buttonPanel.setLayout(new GridLayout(rows, columns));
+    }
+
+    /**
+     * Set the number of columns as a string (callback method from xml parser).
+     * @param columnsString the column string.
+     */
+    public void setColumns(String columnsString) {
+        System.out.println("setting columns to " +columnsString);
+        columns = Integer.parseInt(columnsString);
+        buttonPanel.setLayout(new GridLayout(rows, columns));
+    }
+
+    /**
+     * Set the width as a string.
+     * @param widthString the width of the component.
+     */
+    public void setWidth(String widthString) {
+        width = Integer.parseInt(widthString);
+        if(width > 0 && height > 0) {
+            super.setPreferredSize(new Dimension(width, height));
+        }
+    }
+
+    /**
+     * Set the width as a string.
+     * @param widthString the width of the component.
+     */
+    public void setHeight(String heightString) {
+        height = Integer.parseInt(heightString);
+        if(width > 0 && height > 0) {
+            super.setPreferredSize(new Dimension(width, height));
+        }
+    }
+
+    public void setGridSize(int rows, int cols) {
+        setLayout(new GridLayout(rows, cols));
+    }
+
+    public void addButton(ButtonDescription description) {
+        if(description.getHotkey() != "" &&  hotKeys.contains(description.getHotkey())) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Duplicate hotkey for button '{0}' - button will be ignored!",description.getLabel()));
+        } else {
+            if(rows == 0 && columns == 0) {
+                setColumns("4");
+            }
+            description.setGpsDataSource(this);
+            buttonPanel.add(description.createComponent());
+            hotKeys.add(description.getHotkey());
+        }
+    }
+
+
+
+    public static void main(String[] args) {
+
+
+        // parse xml file and create component from it:
+        Reader in = new InputStreamReader(SurveyorComponent.class.getClassLoader().getResourceAsStream("surveyor.xml"));
+        XmlObjectParser parser = new XmlObjectParser();
+        parser.mapOnStart("surveyor", SurveyorComponent.class);
+        parser.map("button", ButtonDescription.class);
+        parser.map("action", SurveyorActionDescription.class);
+
+        SurveyorComponent surveyorComponent = null;
+        try {
+            parser.start(in);
+            List<SurveyorActionDescription> actions = new ArrayList<SurveyorActionDescription>();
+            while(parser.hasNext()) {
+                Object object = parser.next();
+                if (object instanceof SurveyorComponent) {
+                    System.out.println("SurveyorComponent " + object);
+                    surveyorComponent = (SurveyorComponent) object;
+                } else if (object instanceof ButtonDescription) {
+                    System.out.println("ButtonDescription " + object);
+                    ((ButtonDescription)object).setActions(actions);
+                    surveyorComponent.addButton(((ButtonDescription)object));
+                    actions.clear();
+                } else if (object instanceof SurveyorActionDescription) {
+                    System.out.println("SurveyorActionDescription " + object);
+                    actions.add((SurveyorActionDescription)object);
+                } else {
+                    System.err.println("unknown " + object);
+                }
+            }
+        } catch (SAXException e) {
+            e.printStackTrace();
+        }
+
+//        SurveyorComponent surveyorComponent = new SurveyorComponent();
+//        surveyorComponent.setGridSize(3,3);
+//        surveyorComponent.addButton(new ButtonDescription("Tunnel", "T", "images/symbols/tunnel.png", "ConsolePrinterAction", ButtonType.SINGLE));
+//        surveyorComponent.addButton(new ButtonDescription("Bridge", "B", null, "ConsolePrinterAction", ButtonType.TOGGLE));
+//        surveyorComponent.addButton(new ButtonDescription("Motorway", "M", null, "ConsolePrinterAction", null));
+//        surveyorComponent.addButton(new ButtonDescription("Primary", "P", null, "ConsolePrinterAction", null));
+//        surveyorComponent.addButton(new ButtonDescription("Secondary", "S", null, "ConsolePrinterAction", null));
+//        surveyorComponent.addButton(new ButtonDescription("Unclassified", "U", null, "ConsolePrinterAction", null));
+//        surveyorComponent.addButton(new ButtonDescription("Residential", "R", null, "ConsolePrinterAction", null));
+//        surveyorComponent.addButton(new ButtonDescription("Parking", "P", "images/symbols/parking.png", "ConsolePrinterAction", null));
+
+        JFrame frame = new JFrame();
+        frame.add(surveyorComponent);
+        frame.pack();
+        frame.setVisible(true);
+        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+    }
+
+    /* (non-Javadoc)
+     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+     */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if("gpsdata".equals(evt.getPropertyName())) {
+            gpsData = (LiveGpsData) evt.getNewValue();
+            streetLabel.setText(tr("Way: ") + gpsData.getWayInfo());
+        }
+
+    }
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.GpsDataSource#getGpsData()
+     */
+    public LiveGpsData getGpsData() {
+        return gpsData;
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorPlugin.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorPlugin.java
new file mode 100644
index 0000000..e30e295
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorPlugin.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+
+import livegps.LiveGpsPlugin;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.plugins.PluginHandler;
+
+/**
+ * Plugin that uses live gps data and a button panel to add nodes/waypoints etc at the current
+ * position.
+ *
+ * TODO: auto save marker layer and data layer?
+ * TODO: in action retrieve buttontype state to set on/off values
+ * @author cdaller
+ *
+ */
+public class SurveyorPlugin {
+
+    private static JFrame surveyorFrame;
+    public static final String PREF_KEY_STREET_NAME_FONT_SIZE = "surveyor.way.fontsize";
+
+    /**
+     *
+     */
+    public SurveyorPlugin() {
+        super();
+
+        LiveGpsPlugin gpsPlugin = (LiveGpsPlugin) PluginHandler.getPlugin("livegps");
+        if(gpsPlugin == null)
+            throw new IllegalStateException(tr("SurveyorPlugin needs LiveGpsPlugin, but could not find it!"));
+
+        JMenu m = gpsPlugin.getLgpsMenu();
+        m.addSeparator();
+        MainMenu.add(m, new SurveyorShowAction(gpsPlugin));
+
+        AutoSaveAction autoSaveAction = new AutoSaveAction();
+        JCheckBoxMenuItem autoSaveMenu = new JCheckBoxMenuItem(autoSaveAction);
+        m.add(autoSaveMenu);
+        autoSaveMenu.setAccelerator(autoSaveAction.getShortcut().getKeyStroke());
+    }
+
+    /**
+     * @return the surveyorFrame
+     */
+    public static JFrame getSurveyorFrame() {
+        return surveyorFrame;
+    }
+
+    /**
+     * @param surveyorFrame the surveyorFrame to set
+     */
+    public static void setSurveyorFrame(JFrame surveyorFrame) {
+        SurveyorPlugin.surveyorFrame = surveyorFrame;
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorShowAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorShowAction.java
new file mode 100644
index 0000000..80f723b
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/SurveyorShowAction.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+
+import livegps.LiveGpsPlugin;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.XmlObjectParser;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.xml.sax.SAXException;
+
+import at.dallermassl.josm.plugin.surveyor.util.ResourceLoader;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SurveyorShowAction extends JosmAction {
+    private static final long serialVersionUID = 2184570223633094734L;
+    private static final String DEFAULT_SOURCE = "resource://surveyor.xml";
+    private JFrame surveyorFrame;
+    private LiveGpsPlugin gpsPlugin;
+
+    public SurveyorShowAction(LiveGpsPlugin gpsPlugin) {
+        super(tr("Surveyor..."), "surveyormenu.png", tr("Open surveyor tool."),
+        Shortcut.registerShortcut("surveyor:open", tr("Tool: {0}", tr("Surveyor...")),
+        KeyEvent.VK_R, Shortcut.GROUP_MENU, Shortcut.SHIFT_DEFAULT), true);
+        this.gpsPlugin = gpsPlugin;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+     */
+    public void actionPerformed(ActionEvent e) {
+        if(surveyorFrame == null) {
+            surveyorFrame = new JFrame();
+
+            SurveyorComponent comp = createComponent();
+//          comp.setGridSize(3,3);
+//          comp.addButton(new ButtonDescription("Tunnel", "T", "images/symbols/tunnel.png", "ConsolePrinterAction", ButtonType.SINGLE));
+//          comp.addButton(new ButtonDescription("Bridge", "B", null, "ConsolePrinterAction", ButtonType.TOGGLE));
+//          comp.addButton(new ButtonDescription("Motorway", "M", null, "ConsolePrinterAction", null));
+//          comp.addButton(new ButtonDescription("Primary", "I", null, "ConsolePrinterAction", null));
+//          comp.addButton(new ButtonDescription("Secondary", "S", null, "ConsolePrinterAction", null));
+//          comp.addButton(new ButtonDescription("Unclassified", "U", null, "ConsolePrinterAction", null));
+//          comp.addButton(new ButtonDescription("Residential", "R", null,
+//          new SurveyorActionDescription("SetWaypointAction", new String[] {"residential", "images/reorder.png"}), null));
+//          comp.addButton(new ButtonDescription("Parking", "P", "images/symbols/parking.png",
+//          new SurveyorActionDescription("SetNodeAction", new String[] {"amenity", "parking", "createdby", "surveyor"}), null));
+
+            // add component as gps event listener:
+            gpsPlugin.addPropertyChangeListener(comp);
+
+            // add some hotkeys to the component:
+            ActionMap actionMap = comp.getActionMap();
+            InputMap inputMap = comp.getInputMap();
+            // zoomout:
+            actionMap.put("zoomout", new AbstractAction() {
+                public void actionPerformed(ActionEvent e) {
+                    if(Main.map != null && Main.map.mapView != null) {
+                        Main.map.mapView.zoomToFactor(2);
+                    }
+                }
+            });
+            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "zoomout");
+            // zoomin:
+            actionMap.put("zoomin", new AbstractAction() {
+                public void actionPerformed(ActionEvent e) {
+                    if(Main.map != null && Main.map.mapView != null) {
+                        Main.map.mapView.zoomToFactor(1/2);
+                    }
+                }
+            });
+            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "zoomin");
+            // autocenter:
+            actionMap.put("autocenter", new AbstractAction() {
+                public void actionPerformed(ActionEvent e) {
+                    // toggle autocenter
+                    gpsPlugin.setAutoCenter(!gpsPlugin.isAutoCenter());
+                }
+            });
+            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), "autocenter");
+
+            surveyorFrame.add(comp);
+            surveyorFrame.pack();
+            surveyorFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+            //surveyorFrame.setTitle((String)getValue(AbstractAction.NAME));
+            surveyorFrame.setTitle(tr("Surveyor"));
+            // <FIXXME date="28.04.2007" author="cdaller">
+            // TODO get old pos of frame from properties
+            // </FIXXME>
+            SurveyorPlugin.setSurveyorFrame(surveyorFrame);
+        }
+        surveyorFrame.setAlwaysOnTop(true);
+        surveyorFrame.setVisible(true);
+
+    }
+
+    public SurveyorComponent createComponent() {
+        InputStream in = null;
+        String source = Main.pref.get("surveyor.source");
+        if(source == null || source.length() == 0) {
+            source = DEFAULT_SOURCE;
+            Main.pref.put("surveyor.source", DEFAULT_SOURCE);
+            // <FIXXME date="04.05.2007" author="cdaller">
+            // TODO copy xml file to .josm directory if it does not exist!
+            // </FIXXME>
+        }
+        SurveyorComponent component= null;
+        try {
+            in = ResourceLoader.getInputStream(source);
+            component = createComponent(in);
+            in.close();
+            return component;
+        } catch (IOException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(Main.parent, tr("Could not read surveyor definition: {0}",source));
+        } catch (SAXException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(Main.parent, tr("Error parsing {0}: {1}", source, e.getMessage()));
+        }
+        return component;
+
+    }
+
+    /**
+     * Parse an xml file containing the definitions for the surveyor component.
+     * @param in the inputstream to read the xml from.
+     * @return the component.
+     * @throws SAXException if the xml could not be read.
+     */
+    public SurveyorComponent createComponent(InputStream in) throws SAXException {
+        XmlObjectParser parser = new XmlObjectParser();
+        parser.mapOnStart("surveyor", SurveyorComponent.class);
+        parser.map("button", ButtonDescription.class);
+        parser.map("action", SurveyorActionDescription.class);
+
+        SurveyorComponent surveyorComponent = null;
+        parser.start(new BufferedReader(new InputStreamReader(in)));
+        List<SurveyorActionDescription> actions = new ArrayList<SurveyorActionDescription>();
+        while(parser.hasNext()) {
+            Object object = parser.next();
+            if (object instanceof SurveyorComponent) {
+                //System.out.println("SurveyorComponent " + object);
+                surveyorComponent = (SurveyorComponent) object;
+            } else if (object instanceof ButtonDescription) {
+                //System.out.println("ButtonDescription " + object);
+                ((ButtonDescription)object).setActions(actions);
+                surveyorComponent.addButton(((ButtonDescription)object));
+                actions = new ArrayList<SurveyorActionDescription>();
+            } else if (object instanceof SurveyorActionDescription) {
+                //System.out.println("SurveyorActionDescription " + object);
+                actions.add((SurveyorActionDescription)object);
+            } else {
+                System.err.println("surveyor: unknown xml element: " + object);
+            }
+        }
+        return surveyorComponent;
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/AbstractSurveyorAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/AbstractSurveyorAction.java
new file mode 100644
index 0000000..5592923
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/AbstractSurveyorAction.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.util.List;
+
+import at.dallermassl.josm.plugin.surveyor.SurveyorAction;
+
+/**
+ * @author cdaller
+ *
+ */
+public abstract class AbstractSurveyorAction implements SurveyorAction {
+    private List<String> parameters;
+
+    /**
+     * Returns the parameters.
+     * @return the parameters
+     */
+    public List<String> getParameters() {
+        return parameters;
+    }
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#setParameters(java.util.List)
+     */
+    //@Override
+    public void setParameters(List<String> parameters) {
+        this.parameters = parameters;
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/BeepAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/BeepAction.java
new file mode 100644
index 0000000..2232631
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/BeepAction.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.awt.Toolkit;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+import at.dallermassl.josm.plugin.surveyor.SurveyorAction;
+
+/**
+ * @author cdaller
+ *
+ */
+public class BeepAction implements SurveyorAction {
+    int beepNumber = 1;
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent)
+     */
+    public void actionPerformed(GpsActionEvent event) {
+     // run as a separate thread
+        Main.worker.execute(new Runnable() {
+            public void run() {
+                for(int index = 0; index < beepNumber; ++index) {
+                    Toolkit.getDefaultToolkit().beep();
+                    try {
+                        Thread.sleep(200);
+                    } catch (InterruptedException ignore) {
+                    }
+                }
+            }
+        });
+    }
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#setParameters(java.util.List)
+     */
+    public void setParameters(List<String> parameters) {
+        try {
+            beepNumber = Integer.parseInt(parameters.get(0));
+        } catch(NumberFormatException e) {
+            // print but recover
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/ConsolePrinterAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/ConsolePrinterAction.java
new file mode 100644
index 0000000..381dead
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/ConsolePrinterAction.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+
+/**
+ * @author cdaller
+ *
+ */
+public class ConsolePrinterAction extends AbstractSurveyorAction {
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.ButtonAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent, java.util.List)
+     */
+    public void actionPerformed(GpsActionEvent event) {
+        LatLon coordinates = event.getCoordinates();
+        System.out.println(getClass().getSimpleName() + " KOORD: " + coordinates.lat() + ", "
+            + coordinates.lon() + " params: " + getParameters());
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/PlayAudioAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/PlayAudioAction.java
new file mode 100644
index 0000000..0a74464
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/PlayAudioAction.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import org.openstreetmap.josm.Main;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+import at.dallermassl.josm.plugin.surveyor.util.ResourceLoader;
+
+/**
+ * Action that plays an audio file.
+ *
+ * @author cdaller
+ *
+ */
+public class PlayAudioAction extends AbstractSurveyorAction {
+    private String audioSource = null;
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent)
+     */
+    //@Override
+    public void actionPerformed(GpsActionEvent event) {
+        // run as a separate thread
+        Main.worker.execute(new Runnable() {
+            public void run() {
+                try {
+                    if(audioSource == null) {
+                        audioSource = getParameters().get(0);
+                        System.out.println("reading audio from " + audioSource);
+                    }
+                    InputStream in = new BufferedInputStream(ResourceLoader.getInputStream(audioSource));
+                    AudioInputStream stream = AudioSystem.getAudioInputStream(in);
+
+                    // From URL
+//                  stream = AudioSystem.getAudioInputStream(new URL("http://hostname/audiofile"));
+
+                    // At present, ALAW and ULAW encodings must be converted
+                    // to PCM_SIGNED before it can be played
+                    AudioFormat format = stream.getFormat();
+                    if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
+                        format = new AudioFormat(
+                            AudioFormat.Encoding.PCM_SIGNED,
+                            format.getSampleRate(),
+                            format.getSampleSizeInBits()*2,
+                            format.getChannels(),
+                            format.getFrameSize()*2,
+                            format.getFrameRate(),
+                            true);        // big endian
+                        stream = AudioSystem.getAudioInputStream(format, stream);
+                    }
+
+                    // Create the clip
+                    DataLine.Info info = new DataLine.Info(
+                        Clip.class, stream.getFormat(), ((int)stream.getFrameLength()*format.getFrameSize()));
+                    Clip clip = (Clip) AudioSystem.getLine(info);
+
+                    // This method does not return until the audio file is completely loaded
+                    clip.open(stream);
+
+                    // Start playing
+                    clip.start();
+                } catch (MalformedURLException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } catch (LineUnavailableException e) {
+                    e.printStackTrace();
+                } catch (UnsupportedAudioFileException e) {
+                    e.printStackTrace();
+                }
+            }
+
+        });
+
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetNodeAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetNodeAction.java
new file mode 100644
index 0000000..fc5995a
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetNodeAction.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+
+import livegps.LiveGpsLock;
+
+import org.dinopolis.util.collection.Tuple;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+import at.dallermassl.josm.plugin.surveyor.SurveyorAction;
+
+/**
+ * Action that sets a node into the data layer. The first parameter is used as a property key
+ * the second as the property value (e.g. amenity=parking). If there are more than two parameters
+ * they are used as key/value pairs.
+ * @author cdaller
+ *
+ */
+public class SetNodeAction implements SurveyorAction {
+    private Collection<Tuple<String, String>> keyValues;
+
+    /**
+     * Default Constructor
+     */
+    public SetNodeAction() {
+
+    }
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#setParameters(java.util.List)
+     */
+    //@Override
+    public void setParameters(List<String> parameters) {
+        keyValues = new ArrayList<Tuple<String, String>>();
+        int pos;
+        String key;
+        String value;
+        for (String keyValuePair : parameters) {
+            pos = keyValuePair.indexOf('=');
+            if(pos > 0) {
+                key = keyValuePair.substring(0, pos);
+                value = keyValuePair.substring(pos + 1);
+                keyValues.add(new Tuple<String, String>(key, value));
+            } else {
+                System.err.println("SetNodeAction: ignoring invalid key value pair: " + keyValuePair);
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.ButtonAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent)
+     */
+    public void actionPerformed(GpsActionEvent event) {
+        LatLon coordinates = event.getCoordinates();
+        System.out.println(getClass().getSimpleName() + " KOORD: " + coordinates.lat() + ", " + coordinates.lon() + " params: " + keyValues);
+        Node node = new Node(coordinates);
+        for(Entry<String, String> entry : keyValues) {
+            node.put(entry.getKey(), entry.getValue());
+        }
+        node.put("created_by", "JOSM-surveyor-plugin");
+        synchronized(LiveGpsLock.class) {
+            Main.map.mapView.getEditLayer().data.nodes.add(node);
+            Main.main.getCurrentDataSet().setSelected(node);
+        }
+        Main.map.repaint();
+    }
+
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetWaypointAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetWaypointAction.java
new file mode 100644
index 0000000..dc727a6
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SetWaypointAction.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+
+import javax.swing.JToggleButton;
+
+import livegps.LiveGpsLayer;
+import livegps.LiveGpsLock;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
+import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+import at.dallermassl.josm.plugin.surveyor.SurveyorPlugin;
+import at.dallermassl.josm.plugin.surveyor.action.gui.WaypointDialog;
+import at.dallermassl.josm.plugin.surveyor.util.LayerUtil;
+
+/**
+ * Action that sets a marker into a marker layer. The first parameter of the action
+ * is used as the text of the marker, the second (if it exists) as an icon.
+ *
+ * @author cdaller
+ *
+ */
+public class SetWaypointAction extends AbstractSurveyorAction {
+    private LiveGpsLayer liveGpsLayer;
+    private MarkerLayer markerLayer;
+    public static final String MARKER_LAYER_NAME = "surveyorwaypointlayer";
+    private WaypointDialog dialog;
+
+    /**
+     * Default Constructor.
+     */
+    public SetWaypointAction() {
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.ButtonAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent, java.util.List)
+     */
+    public void actionPerformed(GpsActionEvent event) {
+        LatLon coordinates = event.getCoordinates();
+        System.out.println(getClass().getSimpleName() + " KOORD: " + coordinates.lat() + ", " + coordinates.lon());
+        String markerTitle = getParameters().get(0);
+        Object source = event.getSource();
+        if(source instanceof JToggleButton) {
+            if(((JToggleButton)source).isSelected()) {
+                markerTitle = tr("{0} start", markerTitle);
+            } else {
+                markerTitle = tr("{0} end", markerTitle);
+            }
+        }
+
+        if(dialog == null) {
+            dialog = new WaypointDialog();
+        }
+
+        String markerText = markerTitle;
+        String inputText = dialog.openDialog(SurveyorPlugin.getSurveyorFrame(), "Waypoint Description");
+        if(inputText != null && inputText.length() > 0) {
+            inputText = inputText.replaceAll("<", "_"); // otherwise the gpx file is ruined
+            markerText = markerText + " " + inputText;
+        }
+
+        String iconName = getParameters().size() > 1 ? getParameters().get(1) : null;
+
+        // add the waypoint to the marker layer AND to the gpx layer
+        // (easy export of data + waypoints):
+        MarkerLayer layer = getMarkerLayer();
+        GpxLayer gpsLayer = getGpxLayer();
+        WayPoint waypoint = new WayPoint(event.getCoordinates());
+        waypoint.attr.put("name", markerText);
+        if(iconName != null)
+            waypoint.attr.put("sym", iconName);
+        synchronized(LiveGpsLock.class) {
+            //layer.data.add(new Marker(event.getCoordinates(), markerText, iconName));
+            layer.data.add(new Marker(event.getCoordinates(), markerText, iconName, null, -1.0, 0.0));
+            if(gpsLayer != null) {
+                gpsLayer.data.waypoints.add(waypoint);
+            }
+        }
+
+        Main.map.repaint();
+    }
+
+    /**
+     * Returns the marker layer with the name {@link #MARKER_LAYER_NAME}.
+     * @return the marker layer with the name {@link #MARKER_LAYER_NAME}.
+     */
+    public MarkerLayer getMarkerLayer() {
+        if(markerLayer == null) {
+            markerLayer = LayerUtil.findGpsLayer(MARKER_LAYER_NAME, MarkerLayer.class);
+
+            if(markerLayer == null) {
+                // not found, add a new one
+                //markerLayer = new MarkerLayer(new GpxData(), MARKER_LAYER_NAME, null);
+                markerLayer = new MarkerLayer(new GpxData(), MARKER_LAYER_NAME, null, null);
+                Main.main.addLayer(markerLayer);
+            }
+        }
+        return markerLayer;
+    }
+
+    /**
+     * Returns the gpx layer that is filled by the live gps data.
+     * @return the gpx layer that is filled by the live gps data.
+     */
+    public GpxLayer getGpxLayer() {
+        if(liveGpsLayer == null) {
+            Collection<Layer> layers = Main.map.mapView.getAllLayers();
+            for (Layer layer : layers) {
+                if(layer instanceof LiveGpsLayer) {
+                    liveGpsLayer = (LiveGpsLayer) layer;
+                    break;
+                }
+            }
+        }
+        return liveGpsLayer;
+    }
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SystemExecuteAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SystemExecuteAction.java
new file mode 100644
index 0000000..d8750e5
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/SystemExecuteAction.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SystemExecuteAction extends AbstractSurveyorAction {
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent, java.util.List)
+     */
+    //@Override
+    public void actionPerformed(GpsActionEvent event) {
+        final ProcessBuilder builder = new ProcessBuilder(getParameters());
+        //Map<String, String> environ = builder.environment();
+        builder.directory(new File(System.getProperty("user.home")));
+
+        System.out.println("Directory : " + builder.directory());
+        Thread executionThread = new Thread() {
+
+            /* (non-Javadoc)
+             * @see java.lang.Thread#run()
+             */
+            @Override
+            public void run() {
+                try {
+                    final Process process = builder.start();
+                    InputStream is = process.getInputStream();
+                    InputStreamReader isr = new InputStreamReader(is);
+                    BufferedReader br = new BufferedReader(isr);
+                    String line;
+
+                    while ((line = br.readLine()) != null) {
+                        System.out.println(getClass().getSimpleName() + ": " +  line);
+                    }
+
+                    System.out.println(getClass().getSimpleName() + "Program terminated!");
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                }
+            }
+
+        };
+        executionThread.start();
+//        try {
+//            System.in.read();
+//        } catch (IOException e) {
+//            // TODO Auto-generated catch block
+//            e.printStackTrace();
+//        }
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/TaggingPresetAction.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/TaggingPresetAction.java
new file mode 100644
index 0000000..91acb5c
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/TaggingPresetAction.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action;
+
+import java.util.List;
+
+import javax.swing.Action;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
+import org.openstreetmap.josm.gui.tagging.TaggingPreset;
+
+import at.dallermassl.josm.plugin.surveyor.GpsActionEvent;
+import at.dallermassl.josm.plugin.surveyor.SurveyorAction;
+
+/**
+ * @author cdaller
+ *
+ */
+public class TaggingPresetAction implements SurveyorAction {
+    private String presetName;
+    private TaggingPreset preset;
+
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#actionPerformed(at.dallermassl.josm.plugin.surveyor.GpsActionEvent)
+     */
+    //@Override
+    public void actionPerformed(GpsActionEvent event) {
+        if(preset == null) {
+            return;
+        }
+        LatLon coordinates = event.getCoordinates();
+        System.out.println(getClass().getSimpleName() + " KOORD: " + coordinates.lat() + ", "
+            + coordinates.lon() + ", preset=" + presetName);
+//        Node node = new Node(coordinates);
+//        node.put("created_by", "JOSM-surveyor-plugin");
+//        synchronized(LiveGpsLock.class) {
+//            Main.main.editLayer().data.nodes.add(node);
+//            Main.ds.setSelected(node);
+//        }
+//        Main.map.repaint();
+
+        // call an annotationpreset to add additional properties...
+        preset.actionPerformed(null);
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see at.dallermassl.josm.plugin.surveyor.SurveyorAction#setParameters(java.util.List)
+     */
+    //@Override
+    public void setParameters(List<String> parameters) {
+        if(parameters.size() == 0) {
+            throw new IllegalArgumentException("No annotation preset name given!");
+        }
+        presetName = parameters.get(0);
+        preset = getAnnotationPreset(presetName);
+        if(preset == null) {
+            System.err.println("No valid preset '" + parameters.get(0) + "' found - disable action!");
+            return;
+        }
+    }
+
+    /**
+     * Returns the preset with the given name or <code>null</code>.
+     * @param name the name of the annotation preset.
+     * @return  the preset with the given name.
+     */
+    protected TaggingPreset getAnnotationPreset(String name) {
+        for(TaggingPreset preset : TaggingPresetPreference.taggingPresets) {
+            if(name.equals(preset.getValue(Action.NAME))) {
+                return preset;
+            }
+        }
+        return null;
+    }
+
+
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/DialogClosingThread.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/DialogClosingThread.java
new file mode 100644
index 0000000..bdfeff3
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/DialogClosingThread.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action.gui;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import javax.swing.JDialog;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+/**
+ * @author cdaller
+ *
+ */
+public class DialogClosingThread extends Thread implements KeyListener, DocumentListener {
+    private static long DEFAULT_TIMEOUT = 5000;
+    private JDialog dialog;
+    private long timeout;
+    private long loopCount;
+
+    /**
+     * Using the given dialog and the default timeout.
+     * @param dialog
+     */
+    public DialogClosingThread(JDialog dialog) {
+        this(dialog, DEFAULT_TIMEOUT);
+    }
+
+    /**
+     * @param dialog
+     * @param timeout
+     */
+    public DialogClosingThread(JDialog dialog, long timeout) {
+        super();
+        this.dialog = dialog;
+        this.timeout = timeout;
+        this.loopCount = timeout / 1000;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Thread#run()
+     */
+    @Override
+    public void run() {
+        String title = dialog.getTitle();
+        while(loopCount > 0) {
+            dialog.setTitle(title + " (" + loopCount + "sec)");
+            --loopCount;
+            try {
+                sleep(1000);
+            } catch(InterruptedException ignore) {}
+        }
+
+        dialog.setVisible(false);
+        dialog.dispose();
+    }
+
+    public void reset() {
+        this.loopCount = timeout / 1000;
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
+     */
+    //@Override
+    public void keyPressed(KeyEvent e) {
+        reset();
+        System.out.println("keypressed: " + e.getKeyCode());
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
+     */
+    //@Override
+    public void keyReleased(KeyEvent e) {
+        reset();
+        System.out.println("keyreleased: " + e.getKeyCode());
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
+     */
+    //@Override
+    public void keyTyped(KeyEvent e) {
+        reset();
+        System.out.println("keytyped: " + e.getKeyCode());
+    }
+
+    /**
+     * @param optionPane
+     */
+    public void observe(Container container) {
+        for(Component component : container.getComponents()) {
+            if(component instanceof JTextField) {
+                observe((JTextField)component);
+            } else {
+                observe(component);
+            }
+        }
+    }
+
+    public void observe(Component component) {
+        component.addKeyListener(this);
+    }
+
+    public void observe(JTextField textfield) {
+        textfield.getDocument().addDocumentListener(this);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
+     */
+    //@Override
+    public void changedUpdate(DocumentEvent e) {
+        reset();
+        System.out.println("changedUpdate: " + e);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
+     */
+    //@Override
+    public void insertUpdate(DocumentEvent e) {
+        reset();
+        System.out.println("insertUpdate: " + e);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
+     */
+    //@Override
+    public void removeUpdate(DocumentEvent e) {
+        reset();
+        System.out.println("removeUpdate: " + e);
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/WaypointDialog.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/WaypointDialog.java
new file mode 100644
index 0000000..eba62bb
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/action/gui/WaypointDialog.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.action.gui;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+
+
+/**
+ * @author cdaller
+ *
+ */
+public class WaypointDialog {
+
+    public String openDialog(JFrame frame, String message) {
+
+        JTextField textField = new JTextField(10);
+
+        //Create an array of the text and components to be displayed.
+        Object[] array = {message, textField};
+
+        //Create an array specifying the number of dialog buttons
+        //and their text.
+        Object[] options = {"OK"};
+
+        //Create the JOptionPane.
+        final JOptionPane optionPane = new JOptionPane(array,
+                                    JOptionPane.QUESTION_MESSAGE,
+                                    JOptionPane.OK_OPTION,
+                                    null,
+                                    options,
+                                    options[0]);
+
+//        final JOptionPane optionPane = new JOptionPane("The only way to close this dialog is by\n"
+//                        + "pressing one of the following buttons.\n" + "Do you understand?",
+//            JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION);
+
+
+        final JDialog dialog = new JDialog(frame, "Enter Description", true);
+        DialogClosingThread closer = new DialogClosingThread(dialog);
+        closer.observe(textField);
+        dialog.setContentPane(optionPane);
+        optionPane.addPropertyChangeListener(new PropertyChangeListener() {
+            public void propertyChange(PropertyChangeEvent e) {
+                String prop = e.getPropertyName();
+
+                if (dialog.isVisible() && (e.getSource() == optionPane)
+                                && (prop.equals(JOptionPane.VALUE_PROPERTY))) {
+                    // If you were going to check something
+                    // before closing the window, you'd do
+                    // it here.
+                    dialog.setVisible(false);
+                }
+            }
+        });
+        closer.start();
+        dialog.pack();
+        dialog.setVisible(true);
+
+
+        System.out.println("value: " + optionPane.getValue());
+        return textField.getText();
+
+//        int value = ((Integer) optionPane.getValue()).intValue();
+//        if (value == JOptionPane.YES_OPTION) {
+//            System.out.println("yes");
+//        } else if (value == JOptionPane.NO_OPTION) {
+//            System.out.println("no");
+//        }
+
+    }
+
+    public static void main(String[] args) {
+        //1. Create the frame.
+          JFrame frame = new JFrame("FrameDemo");
+
+          //2. Optional: What happens when the frame closes?
+          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+          //3. Create components and put them in the frame.
+          //...create emptyLabel...
+          frame.getContentPane().add(new JLabel("test"), BorderLayout.CENTER);
+
+          //4. Size the frame.
+          frame.pack();
+          frame.setSize(600,400);
+          frame.setLocation(0,0);
+
+          //5. Show it.
+          frame.setVisible(true);
+          new WaypointDialog().openDialog(frame, "test");
+      }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/LayerUtil.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/LayerUtil.java
new file mode 100644
index 0000000..3277b97
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/LayerUtil.java
@@ -0,0 +1,35 @@
+/**
+ *
+ */
+package at.dallermassl.josm.plugin.surveyor.util;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.layer.Layer;
+
+/**
+ * @author cdaller
+ *
+ */
+public class LayerUtil {
+
+    /**
+     * Returns the layer with the given name and type from the map view or <code>null</code>.
+     * @param <LayerType> the type of the layer.
+     * @param layerName the name of the layer.
+     * @param layerType the type of the layer.
+     * @return the layer or <code>null</code>.
+     */
+    @SuppressWarnings("unchecked")
+    public static <LayerType extends Layer> LayerType findGpsLayer(String layerName, Class<LayerType> layerType) {
+        Layer result = null;
+        if(Main.map != null && Main.map.mapView != null) {
+            for(Layer layer : Main.map.mapView.getAllLayers()) {
+                if(layerName.equals(layer.getName()) && layerType.isAssignableFrom(layer.getClass())) {
+                    result = layer;
+                    break;
+                }
+            }
+        }
+        return (LayerType) result;
+    }
+}
diff --git a/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/ResourceLoader.java b/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/ResourceLoader.java
new file mode 100644
index 0000000..2357f78
--- /dev/null
+++ b/surveyor/src/at/dallermassl/josm/plugin/surveyor/util/ResourceLoader.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package at.dallermassl.josm.plugin.surveyor.util;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * @author cdaller
+ *
+ */
+public class ResourceLoader {
+
+    private ResourceLoader() {
+
+    }
+
+    /**
+     * Returns an inputstream from urls, files and classloaders, depending on the name.
+     * @param source the source: if starting with "http://", "ftp://" or
+     * "file://" source is interpreted as an URL. If starting with "resource://"
+     * the classloader is used. All other sources are interpreted as filenames.
+     * @return the inputstream.
+     * @throws IOException if an error occurs on opening the url, or if the file is not found.
+     */
+    public static InputStream getInputStream(String source) throws IOException {
+        InputStream in = null;
+        if (source.startsWith("http://") || source.startsWith("ftp://") || source.startsWith("file:")) {
+            in = new URL(source).openStream();
+        } else if (source.startsWith("resource://")) {
+            in = ResourceLoader.class.getResourceAsStream(source.substring("resource:/".length()));
+        } else {
+            in = new FileInputStream(source);
+        }
+        System.out.println("stream for resource is " + in);
+        return in;
+    }
+
+}
diff --git a/surveyor/src/org/dinopolis/util/collection/Tuple.java b/surveyor/src/org/dinopolis/util/collection/Tuple.java
new file mode 100644
index 0000000..706d7c7
--- /dev/null
+++ b/surveyor/src/org/dinopolis/util/collection/Tuple.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright by Christof Dallermassl
+ * This program is free software and licensed under GPL.
+ */
+package org.dinopolis.util.collection;
+
+import java.util.Map;
+
+/**
+ * Simple implementation of a tuple (two objects).
+ *
+ * @author cdaller
+ *
+ */
+public class Tuple<T1 extends Object, T2 extends Object> implements Map.Entry<T1, T2>{
+    T1 first;
+    T2 second;
+
+    /**
+     * Default Constructor
+     */
+    public Tuple() {
+    }
+
+    /**
+     * Constructor filling the values.
+     * @param first the first value.
+     * @param second the second value.
+     */
+    public Tuple(T1 one, T2 two) {
+        this.first = one;
+        this.second = two;
+    }
+
+    /**
+     * @return the first
+     */
+    public T1 getFirst() {
+        return this.first;
+    }
+
+    /**
+     * @param first the first to set
+     */
+    public void setFirst(T1 first) {
+        this.first = first;
+    }
+
+    /**
+     * @return the second
+     */
+    public T2 getSecond() {
+        return this.second;
+    }
+
+    /**
+     * @param second the second to set
+     */
+    public T2 setSecond(T2 second) {
+        T2 oldValue = this.second;
+        this.second = second;
+        return oldValue;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Map.Entry#getKey()
+     */
+    //@Override
+    public T1 getKey() {
+        return getFirst();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Map.Entry#getValue()
+     */
+    //@Override
+    public T2 getValue() {
+        return getSecond();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Map.Entry#setValue(java.lang.Object)
+     */
+    //@Override
+    public T2 setValue(T2 value) {
+        return setSecond(value);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((this.first == null) ? 0 : this.first.hashCode());
+        result = prime * result + ((this.second == null) ? 0 : this.second.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final Tuple other = (Tuple) obj;
+        if (this.first == null) {
+            if (other.first != null)
+                return false;
+        } else if (!this.first.equals(other.first))
+            return false;
+        if (this.second == null) {
+            if (other.second != null)
+                return false;
+        } else if (!this.second.equals(other.second))
+            return false;
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "[" + first + "=" + second + "]";
+    }
+}
diff --git a/surveyor/src/org/dinopolis/util/io/Tokenizer.java b/surveyor/src/org/dinopolis/util/io/Tokenizer.java
new file mode 100644
index 0000000..e8f5009
--- /dev/null
+++ b/surveyor/src/org/dinopolis/util/io/Tokenizer.java
@@ -0,0 +1,942 @@
+/***********************************************************************
+ * @(#)$RCSfile: Tokenizer.java,v $   $Revision: 1.6 $$Date: 2006/04/21 14:14:56 $
+ *
+ * Copyright (c) Christof Dallermassl
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License (LGPL)
+ * as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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
+ ***********************************************************************/
+
+package org.dinopolis.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+//----------------------------------------------------------------------
+/**
+
+ * This tokenizer merges the benefits of the java.lang.StringTokenizer
+ * class and the java.io.StreamTokenizer class. It provides a low
+ * level and a high level interface to the tokenizer. The low level
+ * interface consists of the method pair nextToken() and getWord(),
+ * where the first returns the type of token in the parsing process,
+ * and the latter returns the String element itself.
+ * <p>
+ * The high level interface consists of the methods hasNextLine() and
+ * nextLine(). They use the low level interface to parse the data line
+ * by line and create a list of strings from it.
+ * <p>
+ * It is unsure, if it is wise to mix the usage of the high and
+ * the low level interface. For normal usage, the high level interface
+ * should be more comfortable to use and does not provide any
+ * drawbacks.
+ * <p>
+
+ * An example for the high level interface:
+ * <pre>
+ *    try
+ *    {
+ *          // simple example, tokenizing string, no escape, but quoted
+ *          // works:
+ *      System.out.println("example 1");
+ *      Tokenizer tokenizer = new Tokenizer("text,,,\"another,text\"");
+ *      List tokens;
+ *      while(tokenizer.hasNextLine())
+ *      {
+ *        tokens = tokenizer.nextLine();
+ *        System.out.println(tokens.get(0)); // prints 'text'
+ *        System.out.println(tokens.get(1)); // prints ''
+ *        System.out.println(tokens.get(2)); // prints ''
+ *        System.out.println(tokens.get(3)); // prints 'another,text'
+ *      }
+ *
+ *      System.out.println("example 2");
+ *          // simple example, tokenizing string, using escape char and
+ *          // quoted strings:
+ *      tokenizer = new Tokenizer("text,text with\\,comma,,\"another,text\"");
+ *      tokenizer.respectEscapedCharacters(true);
+ *      while(tokenizer.hasNextLine())
+ *      {
+ *        tokens = tokenizer.nextLine();
+ *        System.out.println(tokens.get(0)); // prints 'text'
+ *        System.out.println(tokens.get(1)); // prints 'text with, comma'
+ *        System.out.println(tokens.get(2)); // prints ''
+ *        System.out.println(tokens.get(3)); // prints 'another,text'
+ *      }
+ *    }
+ *    catch(Exception ioe)
+ *    {
+ *      ioe.printStackTrace();
+ *    }
+ * </pre>
+ * <p>
+ * The advantages compared to the StreamTokenizer class are: Unlike
+ * the StreamTokenizer, this Tokenizer class returns the delimiters as
+ * tokens and therefore may be used to tokenize e.g. comma separated
+ * files with empty fields (the StreamTokenizer handles multiple
+ * delimiters in a row like one delimiter).
+ * <p>
+ * The tokenizer respect quoted words, so the delimiter is ignored if
+ * inside quotes. And it may handle escaped characters (like an
+ * escaped quote character, or an escaped new line). So the line
+ * <code>eric,"he said, \"great!\""</code> returns <code>eric</code>
+ * and <code>he said, "great!"</code> as words.
+ * <p>
+ * Low level interface: The design of the Tokenizer allows to get
+ * empty columns as well as treat multiple delimiters in a row as one
+ * delimiter. For the first approach trigger the values on every
+ * DELIMITER and EOF token whereas for the second, trigger only on
+ * WORD tokens.
+ * <p>
+ * If one wants to be informed about empty words as well, use the
+ * Tokenizer like in the following code fragment:
+ *  <pre>
+ *   Tokenizer tokenizer = new Tokenizer("text,,,another text");
+ *   String word = "";
+ *   int token;
+ *   while((token = tokenizer.nextToken()) != Tokenizer.EOF)
+ *   {
+ *     switch(token)
+ *     {
+ *     case Tokenizer.EOL:
+ *       System.out.println("word: "+word);
+ *       word = "";
+ *       System.out.println("-------------");
+ *       break;
+ *     case Tokenizer.WORD:
+ *       word = tokenizer.getWord();
+ *       break;
+ *     case Tokenizer.QUOTED_WORD:
+ *       word = tokenizer.getWord() + " (quoted)";
+ *       break;
+ *     case Tokenizer.DELIMITER:
+ *       System.out.println("word: "+word);
+ *       word = "";
+ *       break;
+ *     default:
+ *       System.err.println("Unknown Token: "+token);
+ *     }
+ *   }
+ * </pre>
+ * In this example, if the delimiter is set to a comma, a line like
+ * <code>column1,,,"column4,partofcolumn4"</code> would be treated correctly.
+ * <p>
+ * This tokenizer uses the LF character as end of line characters. It
+ * ignores any CR characters, so it can be used in windows
+ * environments as well.
+ *
+ * @author Christof Dallermassl
+ * @version $Revision: 1.6 $
+ */
+
+public class Tokenizer
+{
+  /** the reader to read from */
+  protected PushbackReader reader_;
+  /** the buffer to create the tokens */
+  protected StringBuffer buffer_;
+  /** all characters in this string are used as delimiters */
+  protected String delimiters_ = ",";
+  /** the escape character */
+  protected int escapeChar_ = '\\';
+  /** the quote character */
+  protected int quoteChar_ = '"';
+
+  /** if true, characters are treated as escaped */
+  protected boolean escapeMode_ = false;
+
+  /** if true, end of line is respected */
+  protected boolean eolIsSignificant_ = true;
+  /** if true, escape characters are respected */
+  protected boolean respectEscapedChars_ = false;
+  /** if true, quoted words are respected */
+  protected boolean respectQuotedWords_ = true;
+
+  /** line count */
+  protected int lineCount_ = 1;
+
+  /** end of file marker */
+  protected boolean eofReached_ = false;
+
+  /** the last token that was found */
+  protected int lastToken_ = NOT_STARTED;
+
+  /** end of file token */
+  public static final int EOF = -1;
+  /** end of line token */
+  public static final int EOL = 0;
+  /** word token */
+  public static final int WORD = 1;
+  /** quoted word token */
+  public static final int QUOTED_WORD = 2;
+  /** delimiter token */
+  public static final int DELIMITER = 3;
+  /** error token */
+  public static final int ERROR = 4;
+  /** not started token */
+  public static final int NOT_STARTED = 5;
+
+
+//----------------------------------------------------------------------
+/**
+ * Creates a tokenizer that reads from the given string. It uses the
+ * comma as delimiter, does not respect escape characters but respects
+ * quoted words.
+ *
+ * @param string the string to read from.
+ */
+  public Tokenizer(String string)
+  {
+    this(new StringReader(string));
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Creates a tokenizer that reads from the given string. All
+ * characters in the given delimiters string are used as
+ * delimiter. The tokenizer does not respect escape characters but
+ * respects quoted words.
+ *
+ * @param string the string to read from.
+ * @param delimiters the delimiters to use.
+ */
+  public Tokenizer(String string, String delimiters)
+  {
+    this(new StringReader(string));
+    setDelimiters(delimiters);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Creates a tokenizer that reads from the given string. It uses the
+ * comma as delimiter, does not respect escape characters but respects
+ * quoted words.
+ *
+ * @param inStream the stream to read from.
+ */
+  public Tokenizer(InputStream inStream)
+  {
+    this(new InputStreamReader(inStream));
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Creates a tokenizer that reads from the given reader. It uses the
+ * comma as delimiter, does not respect escape characters but respects
+ * quoted words.
+ *
+ * @param reader the reader to read from.
+ */
+  public Tokenizer(Reader reader)
+  {
+    reader_ = new PushbackReader(reader,2);
+    buffer_ = new StringBuffer();
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Set the delimiter character. The default is the comma.
+ *
+ * @param delimiterChar the delimiter character.
+ */
+  public void setDelimiter(int delimiterChar)
+  {
+    delimiters_ = new String(new char[]{(char)delimiterChar});
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Get the first delimiter character.
+ *
+ * @return the delimiter character.
+ * @deprecated use the getDelimiters() method now
+ */
+  public int getDelimiter()
+  {
+    return(delimiters_.charAt(0));
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Set the delimiter characters. All characters in the delimiters are
+ * used as delimiter.
+ *
+ * @param delimiters the delimiter characters.
+ */
+  public void setDelimiters(String delimiters)
+  {
+    delimiters_ = delimiters;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Get the delimiter character.
+ *
+ * @return the delimiter character.
+ */
+  public String getDelimiters()
+  {
+    return(delimiters_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Set the escape character. The default is the backslash.
+ *
+ * @param escapeChar the escape character.
+ */
+  public void setEscapeChar(int escapeChar)
+  {
+    escapeChar_ = escapeChar;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Get the escape character.
+ *
+ * @return the escape character.
+ */
+  public int getEscapeChar()
+  {
+    return(escapeChar_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * If escape characters should be respected, set the param to
+ * <code>true</code>. The default is to ignore escape characters.
+ *
+ * @param respectEscaped If escape characters should be respected,
+ * set the param to <code>true</code>.
+ */
+  public void respectEscapedCharacters(boolean respectEscaped)
+  {
+    respectEscapedChars_ = respectEscaped;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns <code>true</code>, if escape character is respected.
+ *
+ * @return <code>true</code>, if escape character is respected.
+ */
+  public boolean respectEscapedCharacters()
+  {
+    return(respectEscapedChars_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Get the quote character.
+ *
+ * @return the quote character.
+ */
+  public int getQuoteChar()
+  {
+    return (quoteChar_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Set the quote character. The default is the double quote.
+ *
+ * @param quoteChar the quote character.
+ */
+  public void setQuoteChar(int quoteChar)
+  {
+    quoteChar_ = quoteChar;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * If quoted words should be respected, set the param to
+ * <code>true</code>. The default is to respect quoted words.
+ *
+ * @param respectQuotes If quoted words should be respected,
+ * set the param to <code>true</code>.
+ */
+  public void respectQuotedWords(boolean respectQuotes)
+  {
+    respectQuotedWords_ = respectQuotes;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns <code>true</code>, if quoted words are respected.
+ *
+ * @return <code>true</code>, if quoted words are respected.
+ */
+  public boolean respectQuotedWords()
+  {
+    return(respectQuotedWords_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * If set to <code>true</code> the end of line is signaled by the EOL
+ * token.  If set to <code>false</code> end of line is treated as a
+ * normal delimiter. The default value is true;
+ *
+ * @param significant if the end of line is treated as a special token
+ * or as a delimiter.
+ */
+  public void eolIsSignificant(boolean significant)
+  {
+    eolIsSignificant_ = significant;
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns <code>true</code>, if in case of an end of line detected,
+ * an EOL token is returned. If <code>false</code>, the end of line is
+ * treated as a normal delimiter.
+ *
+ * @return <code>true</code>, if in case of an end of line detected,
+ * an EOL token is returned. If <code>false</code>, the end of line is
+ * treated as a normal delimiter.
+ */
+  public boolean isEolSignificant()
+  {
+    return(eolIsSignificant_);
+  }
+
+
+//----------------------------------------------------------------------
+/**
+ * Returns the current line number of the reader.
+ *
+ * @return the current line number of the reader.
+ */
+  public int getLineNumber()
+  {
+    return(lineCount_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns the value of the token. If the token was of the type WORD,
+ * the word is returned.
+ *
+ * @return the value of the token.
+ */
+  public String getWord()
+  {
+    return(buffer_.toString());
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns the last token that was returned from the nextToken() method.
+ *
+ * @return the last token.
+ */
+  public int getLastToken()
+  {
+    return(lastToken_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns true, if the given character is seen as a delimiter. This
+ * method respects escape_mode, so if the escape character was found
+ * before, it has to act accordingly (usually, return false, even if
+ * the character is a delimiter).
+ *
+ * @param character the character to check for delimiter
+ * @return true, if the given character is seen as a delimiter.
+ */
+  protected boolean isDelimiter(int character)
+  {
+        // check for escape mode:
+    if(escapeMode_)
+      return(false);
+
+    return(delimiters_.indexOf(character) >= 0);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns true, if the given character is seen as a quote
+ * character. This method respects escape_mode, so if the escape
+ * character was found before, it has to act accordingly (usually,
+ * return false, even if the character is a quote character).
+ *
+ * @param character the character to check for quote.
+ * @return true, if the given character is seen as a quote character.
+ */
+  protected boolean isQuoteChar(int character)
+  {
+    if(!respectQuotedWords_)
+      return(false);
+
+        // check for escape mode:
+    if(escapeMode_)
+      return(false);
+
+    return(character == quoteChar_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns true, if the given character is seen as a escape
+ * character. This method respects escape_mode, so if the escape
+ * character was found before, it has to act accordingly (usually,
+ * return false, even if the character is a escape character).
+ * @param character the character to check for escape character.
+ * @return true, if the given character is seen as a escape character.
+ */
+  protected boolean isEscapeChar(int character)
+  {
+    if(!respectEscapedChars_)
+      return(false);
+
+        // check for escape mode:
+    if(escapeMode_)
+      return(false);
+
+    return(character == escapeChar_);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns true, if the given character is seen as a end of line
+ * character. This method respects end of line_mode, so if the end of
+ * line character was found before, it has to act accordingly
+ * (usually, return false, even if the character is a end of line
+ * character).
+ * @param character the character to check for end of line.
+ * @return true, if the given character is seen as a end of line
+ * character.
+ */
+  protected boolean isEndOfLine(int character)
+  {
+        // check for escape mode:
+    if(escapeMode_)
+    {
+      if(character == '\n')   // add line count, even if in escape mode!
+        lineCount_++;
+      return(false);
+    }
+    if(character == -1)
+      eofReached_ = true;
+
+    return((character=='\n') || (character=='\r') || (character == -1));
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Closes the tokenizer (and the reader is uses internally).
+ *
+ * @exception IOException if an error occured.
+ */
+  public void close()
+    throws IOException
+  {
+    reader_.close();
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Reads and returns the next character from the reader and checks for
+ * the escape character. If an escape character is read, a flag is set
+ * and the next character is read. A newline following the escape
+ * character is ignored.
+ *
+ * @return the next character.
+ * @exception IOException if an error occured.
+ */
+  protected int readNextChar()
+    throws IOException
+  {
+    int next_char = reader_.read();
+    if(escapeMode_)
+    {
+      escapeMode_ = false;
+    }
+    else
+    {
+      if(isEscapeChar(next_char))
+      {
+            // ignore escape char itself:
+        next_char = reader_.read();
+
+            // check for newline and ignore it:
+        if(isEndOfLine(next_char))
+        {
+          lineCount_++;
+          next_char = reader_.read();
+              // ignore CR:
+          if(next_char == '\r')
+          {
+            next_char = readNextChar();
+          }
+        }
+        escapeMode_ = true;
+      }
+    }
+        // ignore CR:
+    if(next_char == '\r')
+    {
+      next_char = readNextChar();
+    }
+    return(next_char);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns the next token from the reader. The token's value may be
+ * WORD, QUOTED_WORD, EOF, EOL, or DELIMITER. In the case or WORD or
+ * QUOTED_WORD the actual word can be obtained by the use of the
+ * getWord method.
+ *
+ * @return the next token.
+ * @exception IOException if an error occured.
+ */
+  public int nextToken()
+    throws IOException
+  {
+    buffer_.setLength(0);
+
+    int next_char;
+    next_char = readNextChar();
+
+        // handle EOF:
+    if(eofReached_)
+    {
+      lastToken_ = EOF;
+      return(EOF);
+    }
+
+        // handle EOL:
+    if(isEndOfLine(next_char))
+    {
+      lineCount_++;
+      if(eolIsSignificant_)
+      {
+        lastToken_ = EOL;
+        return(EOL);
+      }
+      else
+      {
+        lastToken_ = DELIMITER;
+        return(DELIMITER);
+      }
+    }
+
+        // handle DELIMITER
+    if(isDelimiter(next_char))
+    {
+      lastToken_ = DELIMITER;
+      return(DELIMITER);
+    }
+
+        // handle quoted words:
+    if(isQuoteChar(next_char))
+    {
+      while(true)
+      {
+        next_char = readNextChar();
+        if(isEndOfLine(next_char))
+        {
+          lastToken_ = ERROR;
+          return(ERROR);
+        }
+        else
+        {
+          if(isQuoteChar(next_char))
+          {
+            lastToken_ = QUOTED_WORD;
+            return(QUOTED_WORD);
+          }
+
+              // no special char, then append to buffer:
+          buffer_.append((char)next_char);
+        }
+      }
+    }
+
+        // handle 'normal' words:
+    while(true)
+    {
+      buffer_.append((char)next_char);
+      next_char = readNextChar();
+      if(isDelimiter(next_char) || isEndOfLine(next_char))
+      {
+        reader_.unread(next_char);
+        lastToken_ = WORD;
+        return(WORD);
+      }
+    }
+  }
+
+//----------------------------------------------------------------------
+/**
+ * Returns true, if the tokenizer can return another line.
+ *
+ * @return true, if the tokenizer can return another line.
+ * @exception IOException if an error occured.
+ */
+  public boolean hasNextLine()
+    throws IOException
+  {
+    if(lastToken_ == EOF)
+      return(false);
+
+    if((lastToken_ == EOL) || (lastToken_ == NOT_STARTED))
+    {
+      int next_char = readNextChar();
+      if(next_char == -1)
+        return(false);
+
+      reader_.unread(next_char);
+    }
+    return(true);
+  }
+
+
+//----------------------------------------------------------------------
+/**
+ * Returns a list of elements (Strings) from the next line of the
+ * tokenizer. If there are multiple delimiters without any values in
+ * between, empty (zero length) strings are added to the list. They
+ * may be removed by the use of the {@link
+ * #removeZeroLengthElements(List)} method.
+ *
+ * @return a list of elements (Strings) from the next line of the
+ * tokenizer.
+ * @exception IOException if an error occured.
+ */
+  public List<String> nextLine()
+    throws IOException
+  {
+    int token = nextToken();
+    List<String> list = new ArrayList<String>();
+    String word = "";
+//    while(token != Tokenizer.EOF)
+    while(true)
+    {
+      switch(token)
+      {
+        case Tokenizer.WORD:
+          word = getWord();
+          break;
+        case Tokenizer.QUOTED_WORD:
+          word = getWord();
+          break;
+        case Tokenizer.DELIMITER:
+          list.add(word);
+          word = "";
+          break;
+        case Tokenizer.EOL:
+        case Tokenizer.EOF:
+          list.add(word);
+          return(list);
+        default:
+          System.err.println("Unknown Token: "+token);
+      }
+      token = nextToken();
+    }
+//    return(list);
+  }
+
+//----------------------------------------------------------------------
+/**
+ * This helper method removes all zero length elements from the given
+ * list and returns it. The given list is not changed!
+ *
+ * @param list the list of String objects to remove the zero elements from.
+ * @return a copy of the given list where all zero length elements are removed.
+ */
+  public static List<String> removeZeroLengthElements(List<String> list)
+  {
+    return removeZeroLengthElements(list, false);
+  }
+
+//----------------------------------------------------------------------
+  /**
+   * This helper method trims all elements and removes all zero length
+   * (length is taken after trimming leading and trailing spaces) elements from the given
+   * list and returns it. This method copies the (trimmed and) non-zero elements to a
+   * new list.
+   *
+   * @param list the list of String objects to remove the zero elements from.
+   * @param trim if set to <code>true</code>, all leading and trailing spaces are removed from
+   * the elements. This is done, before the length is compared to zero (and the element
+   * may be removed if the length is zero). If set to <code>true</code>, elements
+   * that only consist of spaces are removed as well!
+   * @return the list where all zero length elements are remove.
+   */
+    public static List<String> removeZeroLengthElements(List<String> list, boolean trim)
+    {
+      Iterator<String> iterator = list.iterator();
+      String value;
+      List<String> new_list = new ArrayList<String>();
+      while(iterator.hasNext())
+      {
+        value = iterator.next();
+        if (trim)
+          value = value.trim();
+        if(value.length() != 0)
+          new_list.add(value);
+      }
+      return(new_list);
+    }
+
+//  /**
+//   * Demonstrates the low level interface.
+//   * @param args command line arguments.
+//   */
+//  protected static void testLowLevel(String[] args)
+//  {
+//    try
+//    {
+//      String filename;
+//      if(args.length > 0)
+//        filename = args[0];
+//      else
+//        filename = "/filer/cdaller/tmp/test.csv";
+//
+//      Tokenizer tokenizer = new Tokenizer(new BufferedReader(new FileReader(filename)));
+////      Tokenizer tokenizer = new Tokenizer("column1,\"quoted column2\",column3\\, with quoted comma");
+//      tokenizer.setDelimiter(',');
+////      tokenizer.eolIsSignificant(false);
+//      tokenizer.respectEscapedCharacters(true);
+//      tokenizer.respectQuotedWords(true);
+//
+//      int token;
+//      while((token = tokenizer.nextToken()) != Tokenizer.EOF)
+//      {
+//        switch(token)
+//        {
+//        case Tokenizer.EOL:
+//          System.out.println("------------- ");
+//          break;
+//        case Tokenizer.WORD:
+//          System.out.println("line" +tokenizer.getLineNumber() +" word: "+tokenizer.getWord());
+//          break;
+//        case Tokenizer.QUOTED_WORD:
+//          System.out.println("line" +tokenizer.getLineNumber() +" quoted word: "+tokenizer.getWord());
+//          break;
+//        case Tokenizer.DELIMITER:
+//          System.out.println("delimiter");
+//          break;
+//        default:
+//          System.err.println("Unknown Token: "+token);
+//        }
+//      }
+//      tokenizer.close();
+//    }
+//    catch(Exception ioe)
+//    {
+//      ioe.printStackTrace();
+//    }
+//  }
+//
+//
+//  /**
+//   * Demonstration of the high level interface.
+//   * @param args command line arguments.
+//   */
+//  protected static void testHighLevel(String[] args)
+//  {
+//    try
+//    {
+//      String filename;
+//      if(args.length > 0)
+//        filename = args[0];
+//      else
+//        filename = "/filer/cdaller/tmp/test.csv";
+//
+//      Tokenizer tokenizer = new Tokenizer(new BufferedReader(new FileReader(filename)));
+////      Tokenizer tokenizer = new Tokenizer("column1,\"quoted column2\",column3\\, with quoted comma");
+//      tokenizer.setDelimiter(',');
+////      tokenizer.eolIsSignificant(false);
+//      tokenizer.respectEscapedCharacters(true);
+//      tokenizer.respectQuotedWords(true);
+//
+//      List list;
+//      while(tokenizer.hasNextLine())
+//      {
+//        list = tokenizer.nextLine();
+//        System.out.println("List: "+list);
+//        System.out.println("List w/o zero length elements: "+removeZeroLengthElements(list));
+//        System.out.println("--");
+//      }
+//
+//    }
+//    catch(Exception ioe)
+//    {
+//      ioe.printStackTrace();
+//    }
+//  }
+//
+//   /**
+//   *  Demo code for the high level interface.
+//   */
+//  protected static void testHighLevelExample()
+//  {
+//    try
+//    {
+//          // simple example, tokenizing string, no escape, but quoted
+//          // works:
+//      System.out.println("example 1");
+//      Tokenizer tokenizer = new Tokenizer("text,,,\"another,text\"");
+//      List tokens;
+//      while(tokenizer.hasNextLine())
+//      {
+//        tokens = tokenizer.nextLine();
+//        System.out.println(tokens.get(0)); // prints 'text'
+//        System.out.println(tokens.get(1)); // prints ''
+//        System.out.println(tokens.get(2)); // prints ''
+//        System.out.println(tokens.get(3)); // prints 'another,text'
+//      }
+//
+//      System.out.println("example 2");
+//          // simple example, tokenizing string, using escape char and
+//          // quoted strings:
+//      tokenizer = new Tokenizer("text,text with\\,comma,,\"another,text\"");
+//      tokenizer.respectEscapedCharacters(true);
+//      while(tokenizer.hasNextLine())
+//      {
+//        tokens = tokenizer.nextLine();
+//        System.out.println(tokens.get(0)); // prints 'text'
+//        System.out.println(tokens.get(1)); // prints 'text with, comma'
+//        System.out.println(tokens.get(2)); // prints ''
+//        System.out.println(tokens.get(3)); // prints 'another,text'
+//      }
+//    }
+//    catch(Exception ioe)
+//    {
+//      ioe.printStackTrace();
+//    }
+//  }
+//
+//  public static void main(String[] args)
+//  {
+////    testLowLevel(args);
+////    testHighLevel(args);
+////    testGeonetUTF8(args);
+//    testHighLevelExample();
+//  }
+}
+
+
diff --git a/svn-info.xml b/svn-info.xml
new file mode 100644
index 0000000..2d681c9
--- /dev/null
+++ b/svn-info.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<info>
+<entry
+   kind="dir"
+   path="plugins"
+   revision="18465">
+<url>http://svn.openstreetmap.org/applications/editors/josm/plugins</url>
+<repository>
+<root>http://svn.openstreetmap.org</root>
+<uuid>b9d5c4c9-76e1-0310-9c85-f3177eceb1e4</uuid>
+</repository>
+<commit
+   revision="18453">
+<author>daeron</author>
+<date>2009-11-04T14:17:43.184975Z</date>
+</commit>
+</entry>
+</info>
diff --git a/utilsplugin/.classpath b/utilsplugin/.classpath
new file mode 100644
index 0000000..17b8e6a
--- /dev/null
+++ b/utilsplugin/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 5"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/utilsplugin/.project b/utilsplugin/.project
new file mode 100644
index 0000000..2e7e4d2
--- /dev/null
+++ b/utilsplugin/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JOSM-utilsplugin</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/utilsplugin/README b/utilsplugin/README
new file mode 100644
index 0000000..ccabb5c
--- /dev/null
+++ b/utilsplugin/README
@@ -0,0 +1,14 @@
+The utils plugin has been created by Martijn van Oosterhout <kleptog at svana.org>
+and has been mostly merged into josm.
+
+Some of the remaining parts are written by
+Frederik Ramm <frederik at remote.org>.
+
+The "simplify way" action has been rewritten by
+Gabriel Ebner <ge at gabrielebner.at>.
+
+License: GPL v2 or later.
+
+The complete text of the GNU General Public License can be found
+in the LICENSE file in the OSM subversion root directory.
+
diff --git a/utilsplugin/build.xml b/utilsplugin/build.xml
new file mode 100644
index 0000000..3f9871e
--- /dev/null
+++ b/utilsplugin/build.xml
@@ -0,0 +1,55 @@
+<project name="utilsplugin" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Martijn van Oosterhout"/>
+                <attribute name="Plugin-Class" value="UtilsPlugin.UtilsPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Several utilities that make your life easier: e.g. simplify way, join areas, jump to position."/>
+                <attribute name="Plugin-Mainversion" value="2166"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/utilsplugin/data/Join Areas Tests.osm b/utilsplugin/data/Join Areas Tests.osm
new file mode 100644
index 0000000..b99a326
--- /dev/null
+++ b/utilsplugin/data/Join Areas Tests.osm	
@@ -0,0 +1,767 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version='0.5' generator='JOSM'>
+  <bounds minlat='49.6358455937926' minlon='8.97891998291015' maxlat='49.6659612524238' maxlon='9.01926040649414' origin='JOSM' />
+  <bounds minlat='51.0727784461145' minlon='13.7410855293274' maxlat='51.07443677101' maxlon='13.743531703949' origin='OpenStreetMap server' />
+  <node id='-1' visible='true' lat='49.665557350679464' lon='8.980018030697323'>
+    <tag k='name' v='Multipolygon' />
+  </node>
+  <node id='-2' visible='true' lat='49.66293444373006' lon='8.979904268383734' />
+  <node id='-3' visible='true' lat='49.66309431357888' lon='8.98771163350794' />
+  <node id='-4' visible='true' lat='49.66550322539429' lon='8.987481709734286' />
+  <node id='-5' visible='true' lat='49.661140812390876' lon='8.980754551545285' />
+  <node id='-6' visible='true' lat='49.66414787714609' lon='8.98167662069808' />
+  <node id='-7' visible='true' lat='49.66193599760082' lon='8.983668051096597' />
+  <node id='-8' visible='true' lat='49.66423062929877' lon='8.985896954118042' />
+  <node id='-9' visible='true' lat='49.66122094281029' lon='8.987593944961972' />
+  <node id='-10' visible='true' lat='49.665246272951265' lon='8.996673744036638'>
+    <tag k='name' v='No Additional Inner Nodes' />
+  </node>
+  <node id='-11' visible='true' lat='49.66260897027721' lon='8.996995205661822' />
+  <node id='-12' visible='true' lat='49.66247822670222' lon='9.006405902938653' />
+  <node id='-13' visible='true' lat='49.66509579743186' lon='9.006492938770437' />
+  <node id='-14' visible='true' lat='49.663673852556485' lon='9.001872836660935' />
+  <node id='-15' visible='true' lat='49.66356462957665' lon='9.003786018319271' />
+  <node id='-16' visible='true' lat='49.659381328509475' lon='9.002998050852923' />
+  <node id='-17' visible='true' lat='49.658143795735675' lon='9.001321365825214' />
+  <node id='-18' visible='true' lat='49.66102960777179' lon='9.004102187558955' />
+  <node id='-19' visible='true' lat='49.65966007592634' lon='8.995756927212474'>
+    <tag k='name' v='With Inner Nodes, resolve tag conflicts beforehand' />
+  </node>
+  <node id='-20' visible='true' lat='49.65470614469758' lon='9.000967096568774' />
+  <node id='-21' visible='true' lat='49.658215448844345' lon='8.998101909487305' />
+  <node id='-22' visible='true' lat='49.65482452933729' lon='8.99800181533644' />
+  <node id='-23' visible='true' lat='49.65622420968986' lon='8.999630518959972' />
+  <node id='-24' visible='true' lat='49.656334599426415' lon='8.99596685442522' />
+  <node id='-25' visible='true' lat='49.664767306664714' lon='9.00358600531296' />
+  <node id='-26' visible='true' lat='49.65595437351232' lon='9.002670090930014' />
+  <node id='-27' visible='true' lat='49.66298227683501' lon='9.007574342263663' />
+  <node id='-28' visible='true' lat='49.652897713964435' lon='8.995398194580462'>
+    <tag k='name' v='Overlapping Itself' />
+  </node>
+  <node id='-29' visible='true' lat='49.64759565941927' lon='8.995601060044594' />
+  <node id='-30' visible='true' lat='49.657294551336726' lon='9.005523880219158' />
+  <node id='-31' visible='true' lat='49.65131105391522' lon='9.00298658642922' />
+  <node id='-32' visible='true' lat='49.659323888301465' lon='9.00052935158495' />
+  <node id='-33' visible='true' lat='49.65151745640557' lon='8.997992057795011' />
+  <node id='-34' visible='true' lat='49.648042897844014' lon='9.003942985529385' />
+  <node id='-35' visible='true' lat='49.64965980248303' lon='9.003039719712563' />
+  <node id='-36' visible='true' lat='49.647526853143034' lon='8.99921412331189' />
+  <node id='-37' visible='true' lat='49.64862774189198' lon='8.99756699152827' />
+  <node id='-38' visible='true' lat='49.650123242376026' lon='8.984223634986959' />
+  <node id='-39' visible='true' lat='49.6534782364111' lon='9.003730452396017' />
+  <node id='-40' visible='true' lat='49.64965980248303' lon='8.997301325111557' />
+  <node id='-41' visible='true' lat='49.648702988205066' lon='8.99108732452821' />
+  <node id='-42' visible='true' lat='49.6498411096278' lon='8.989584718400737' />
+  <node id='-43' visible='true' lat='49.64837612802103' lon='8.982669694368326' />
+  <node id='-44' visible='true' lat='49.65267519581664' lon='8.982531354736969'>
+    <tag k='name' v='Joining an two areas with self-overlap' />
+  </node>
+  <node id='-45' visible='true' lat='49.64821655131596' lon='8.986618619738293' />
+  <node id='-46' visible='true' lat='49.65263419499493' lon='8.992015874465995' />
+  <node id='-47' visible='true' lat='49.65180728142213' lon='8.98635078159476' />
+  <node id='-48' visible='true' lat='49.65154365880048' lon='8.989894759403137' />
+  <node id='-49' visible='true' lat='49.664258008517386' lon='8.994834196254615' />
+  <node id='-50' visible='true' lat='49.66111564855195' lon='8.994837077867599' />
+  <node id='-51' visible='true' lat='49.661575265881794' lon='9.002417415068946' />
+  <node id='-52' visible='true' lat='49.64910487989931' lon='8.98434746261959' />
+  <node id='-53' visible='true' lat='49.66439890567706' lon='9.000253362850133' />
+  <node id='-54' visible='true' lat='49.66336086872286' lon='9.000172520579218' />
+  <node id='-55' visible='true' lat='49.66163205844883' lon='8.996182583343169' />
+  <node id='-56' visible='true' lat='49.66327697326473' lon='8.996037451903568' />
+  <node id='-57' visible='true' lat='49.65366266054767' lon='8.983765181752341' />
+  <node id='-58' visible='true' lat='49.65093842995153' lon='8.98362536207706' />
+  <node id='-59' visible='true' lat='49.65083240952062' lon='8.981009751040055' />
+  <node id='-60' visible='true' lat='49.653656418352305' lon='8.980925219001294' />
+  <node id='-61' visible='true' lat='49.65918199608578' lon='8.97945358407826'>
+    <tag k='name' v='Intersection already have nodes added' />
+  </node>
+  <node id='-62' visible='true' lat='49.65687579780412' lon='8.979558364399258' />
+  <node id='-63' visible='true' lat='49.65684188230803' lon='8.984902160770126' />
+  <node id='-64' visible='true' lat='49.65918199608578' lon='8.984744990288629' />
+  <node id='-65' visible='true' lat='49.65687579780412' lon='8.980815728251226' />
+  <node id='-66' visible='true' lat='49.65809673992071' lon='8.980815728251226' />
+  <node id='-67' visible='true' lat='49.65792716645855' lon='8.983382846115664' />
+  <node id='-68' visible='true' lat='49.65701145955204' lon='8.98306850515267' />
+  <node id='-69' visible='true' lat='49.65545132661812' lon='8.981025288893223' />
+  <node id='-70' visible='true' lat='49.65558501959522' lon='8.988437026602341' />
+  <node id='-71' visible='true' lat='49.65931567881406' lon='8.986865321787379'>
+    <tag k='name' v='SOME Intersection already have nodes added' />
+  </node>
+  <node id='-72' visible='true' lat='49.65700948686801' lon='8.986970102108378' />
+  <node id='-73' visible='true' lat='49.65823042563044' lon='8.988227465960346' />
+  <node id='-74' visible='true' lat='49.65931567881406' lon='8.992156727997747' />
+  <node id='-75' visible='true' lat='49.65697557146509' lon='8.992313898479246' />
+  <node id='-76' visible='true' lat='49.65806085263415' lon='8.990794583824783' />
+  <node id='-77' visible='true' lat='49.65700948686801' lon='8.988227465960346' />
+  <node id='-78' visible='true' lat='49.660221488976575' lon='8.977137289660517' />
+  <node id='-79' visible='true' lat='49.66019710027813' lon='9.021519330497165' />
+  <node id='-80' visible='true' lat='49.66824309940781' lon='8.99379608098765' />
+  <node id='-81' visible='true' lat='49.665325869211465' lon='9.010629016616129'>
+    <tag k='name' v='Member of some (i.e. not multigon) relation' />
+  </node>
+  <node id='-82' visible='true' lat='49.65418064466472' lon='9.02166564057246' />
+  <node id='-83' visible='true' lat='49.66084008723247' lon='9.01744447037755' />
+  <node id='-84' visible='true' lat='49.66089950426598' lon='9.010651964271892' />
+  <node id='-85' visible='true' lat='49.66770174614116' lon='9.008788568211484' />
+  <node id='-86' action='modify' visible='true' lat='49.6353930124394' lon='8.99395938766279' />
+  <node id='-87' visible='true' lat='49.654142227092336' lon='8.97793106762297' />
+  <node id='-88' action='modify' visible='true' lat='49.63560427014124' lon='9.009633761966247' />
+  <node id='-89' visible='true' lat='49.66149367061061' lon='9.016824883671967' />
+  <node id='-90' visible='true' lat='49.66156794089354' lon='9.014644856374543' />
+  <node id='-91' visible='true' lat='49.6657566013461' lon='9.017559208656362' />
+  <node id='-92' visible='true' lat='49.665266457583265' lon='9.016733093048918' />
+  <node id='-93' visible='true' lat='49.662236368338334' lon='9.011707556436962' />
+  <node id='-94' visible='true' lat='49.66210268358402' lon='9.017513313344837' />
+  <node id='-95' visible='true' lat='49.663929676767346' lon='9.01471369934183' />
+  <node id='-96' visible='true' lat='49.663989090028004' lon='9.01179934706001' />
+  <node id='-97' visible='true' lat='49.656561909834345' lon='9.011712617586559' />
+  <node id='-98' visible='true' lat='49.65825541574074' lon='9.014718760491427' />
+  <node id='-99' visible='true' lat='49.65507724377221' lon='9.016274166792183' />
+  <node id='-100' visible='true' lat='49.65831483593041' lon='9.011804408209608' />
+  <node id='-101' visible='true' lat='49.65954150350348' lon='9.010225255249217'>
+    <tag k='name' v='One Area already part of multipolygon (as outer)' />
+  </node>
+  <node id='-102' visible='true' lat='49.65948208481168' lon='9.016329331682007' />
+  <node id='-103' visible='true' lat='49.66572689580407' lon='9.018683643788718' />
+  <node id='-104' visible='true' lat='49.66467233731082' lon='9.018729539100242' />
+  <node id='-105' visible='true' lat='49.650467504431084' lon='9.01556653387284' />
+  <node id='-106' visible='true' lat='49.650467504431084' lon='9.012653673442712' />
+  <node id='-107' visible='true' lat='49.6577783879124' lon='9.01859068072843' />
+  <node id='-108' visible='true' lat='49.659950690584154' lon='9.018522543642344' />
+  <node id='-109' visible='true' lat='49.659983770585875' lon='9.015660786026782' />
+  <node id='-110' visible='true' lat='49.65777838791238' lon='9.0156778202983' />
+  <node id='-111' visible='true' lat='49.655114612313845' lon='9.01024820290498' />
+  <node id='-112' visible='true' lat='49.65649630973553' lon='9.01456121513066' />
+  <node id='-113' visible='true' lat='49.649790825127035' lon='9.010994229724082' />
+  <node id='-114' visible='true' lat='49.64907390166997' lon='9.010241166221228' />
+  <node id='-115' visible='true' lat='49.64972521590133' lon='9.013842827268181' />
+  <node id='-116' visible='true' lat='49.652640133446276' lon='9.015498396786755' />
+  <node id='-117' visible='true' lat='49.65267321841768' lon='9.012636639171191' />
+  <node id='-118' visible='true' lat='49.653441916259965' lon='9.016322294998254' />
+  <node id='-119' visible='true' lat='49.651484566652904' lon='9.01400037262895' />
+  <node id='-120' visible='true' lat='49.649036528490285' lon='9.01626713010843' />
+  <node id='-121' visible='true' lat='49.64474487637503' lon='9.013313148784425' />
+  <node id='-122' visible='true' lat='49.64329477394559' lon='9.00982115694612' />
+  <node id='-123' visible='true' lat='49.64633804682556' lon='9.016044421132413' />
+  <node id='-124' visible='true' lat='49.6440995060445' lon='9.015089670434026' />
+  <node id='-125' visible='true' lat='49.64698953060391' lon='8.97763933322747' />
+  <node id='-126' visible='true' lat='49.64702795382207' lon='9.021373906176958' />
+  <node id='-127' visible='true' lat='49.651543995109876' lon='9.011086020347133' />
+  <node id='-128' visible='true' lat='49.65350134232707' lon='9.010218218565463'>
+    <tag k='name' v='One Area already part of multipolygon (as inner)' />
+  </node>
+  <node id='-129' visible='true' lat='49.64442618533402' lon='9.018507236795525' />
+  <node id='-130' visible='true' lat='49.64634787643868' lon='9.01859251804108' />
+  <node id='-131' visible='true' lat='49.646377327053195' lon='9.017591884759854' />
+  <node id='-132' visible='true' lat='49.64446300005248' lon='9.017591884759854' />
+  <node id='-133' action='modify' visible='true' lat='49.644279396738646' lon='8.99701844322583' />
+  <node id='-134' action='modify' visible='true' lat='49.645108516691536' lon='9.00501200093294' />
+  <node id='-135' action='modify' visible='true' lat='49.64579713321828' lon='9.001725329495672' />
+  <node id='-136' action='modify' visible='true' lat='49.643570600890634' lon='9.005424239693767' />
+  <node id='-137' visible='true' lat='49.645694403782294' lon='9.012131348650524' />
+  <node id='-138' visible='true' lat='49.646452701397266' lon='9.00974135512665'>
+    <tag k='name' v='Both part of other relations' />
+  </node>
+  <node id='-139' visible='true' lat='49.64566131406722' lon='9.01503858959705' />
+  <node id='-140' visible='true' lat='49.64477854294109' lon='9.010865000644996' />
+  <node id='-141' visible='true' lat='49.6436516438452' lon='9.013337536747516' />
+  <node id='-142' visible='true' lat='49.64333102750582' lon='9.015932402078883' />
+  <node id='-143' visible='true' lat='49.64364363066422' lon='9.010830064185653' />
+  <node id='-144' visible='true' lat='49.6440995060445' lon='9.012176810003897' />
+  <node id='-145' action='modify' visible='true' lat='49.644215448513066' lon='8.987601210318644' />
+  <node id='-146' action='modify' visible='true' lat='49.64509437228782' lon='8.985632145182455' />
+  <node id='-147' action='modify' visible='true' lat='49.64512070290696' lon='8.990191229749867' />
+  <node id='-148' action='modify' visible='true' lat='49.64400653203681' lon='8.985527741087623' />
+  <node id='-149' action='modify' visible='true' lat='49.64362715512701' lon='8.983795590458412' />
+  <node id='-150' action='modify' visible='true' lat='49.6438359138228' lon='8.988873075900425' />
+  <node id='-151' action='modify' visible='true' lat='49.64624752120218' lon='8.990209078516749' />
+  <node id='-152' action='modify' visible='true' lat='49.64561767902291' lon='8.987111582364214' />
+  <node id='-153' action='modify' visible='true' lat='49.645870282835396' lon='9.000573288940334' />
+  <node id='-154' action='modify' visible='true' lat='49.644533441390884' lon='9.001738472218983' />
+  <node id='-155' action='modify' visible='true' lat='49.64620102922634' lon='8.995285052653605' />
+  <node id='-156' action='modify' visible='true' lat='49.645549394972946' lon='8.997512960693886' />
+  <node id='-157' action='modify' visible='true' lat='49.64390917555046' lon='9.00236900726505' />
+  <node id='-158' action='modify' visible='true' lat='49.643583272941804' lon='8.996426078862356' />
+  <node id='-159' action='modify' visible='true' lat='49.645064815871' lon='8.999528632035787' />
+  <node id='-160' action='modify' visible='true' lat='49.64660348405886' lon='9.007804150387257' />
+  <node id='-161' visible='true' lat='49.67180165588925' lon='9.019276197911166'>
+    <tag k='name' v='out of bounds' />
+  </node>
+  <node id='-162' visible='true' lat='49.66726589111355' lon='9.018808975183893' />
+  <node id='-163' action='modify' visible='true' lat='49.64294039575888' lon='8.98264180269116' />
+  <node id='-164' action='modify' visible='true' lat='49.64654691824636' lon='8.982418392157395' />
+  <node id='-165' action='modify' visible='true' lat='49.6465677851024' lon='8.992596999330347' />
+  <node id='-166' action='modify' visible='true' lat='49.64305580574678' lon='8.992869020494213' />
+  <node id='-167' visible='true' lat='49.66722269132057' lon='9.026351284924155' />
+  <node id='-168' visible='true' lat='49.671931242953896' lon='9.026017554404675' />
+  <node id='-169' visible='true' lat='49.66903704959538' lon='9.022880487521558' />
+  <node id='-170' visible='true' lat='49.664846643627826' lon='9.02334771024883' />
+  <node id='-171' action='modify' visible='true' lat='49.64619224514668' lon='8.984061673983168' />
+  <node id='-172' action='modify' visible='true' lat='49.64560080131112' lon='8.988407502515859' />
+  <node id='-173' visible='true' lat='49.66476023970688' lon='9.029688590118962' />
+  <node id='-174' visible='true' lat='49.6687778596993' lon='9.028553906352727' />
+  <node id='-175' action='modify' visible='true' lat='49.64226790264086' lon='9.020479682913138' />
+  <node id='-176' action='modify' visible='true' lat='49.64222947566583' lon='8.976745109963648' />
+  <node id='-177' visible='true' lat='49.63766059241029' lon='8.985426371428245' />
+  <node id='-178' action='modify' visible='true' lat='49.64113713627012' lon='8.99764014460778' />
+  <node id='-179' visible='true' lat='49.63620969614912' lon='8.984267584902272' />
+  <node id='-180' visible='true' lat='49.63831098015055' lon='8.986005764691233' />
+  <node id='-181' action='modify' visible='true' lat='49.63833608786159' lon='9.001965476258553' />
+  <node id='-182' action='modify' visible='true' lat='49.63866179944343' lon='9.001060174285136' />
+  <node id='-183' action='modify' visible='true' lat='49.636707497273726' lon='8.996902491147958' />
+  <node id='-184' action='modify' visible='true' lat='49.63672921217317' lon='9.001496060420484' />
+  <node id='-185' visible='true' lat='49.64166284075567' lon='8.990370527272402' />
+  <node id='-186' visible='true' lat='49.64038715981111' lon='8.984846978165258' />
+  <node id='-187' visible='true' lat='49.63635979087037' lon='8.9920314546263' />
+  <node id='-188' visible='true' lat='49.63665997892509' lon='8.979825569886037' />
+  <node id='-189' visible='true' lat='49.64158780162527' lon='8.984460715989934' />
+  <node id='-190' visible='true' lat='49.63671001008765' lon='8.985696754950974' />
+  <node id='-191' visible='true' lat='49.639336573937996' lon='8.985619502515908' />
+  <node id='-192' visible='true' lat='49.64153777547409' lon='8.98052084180162' />
+  <node id='-193' action='modify' visible='true' lat='49.63919649619353' lon='9.000021094269913' />
+  <node id='-194' visible='true' lat='49.63655427055324' lon='9.003685105162774' />
+  <node id='-195' visible='true' lat='49.638889008438305' lon='9.003170088929009' />
+  <node id='-196' visible='true' lat='49.63766439678692' lon='9.008322826347838' />
+  <node id='-197' visible='true' lat='49.636803870494575' lon='9.005634441607576' />
+  <node id='-198' visible='true' lat='49.64135304304343' lon='9.008042625328157' />
+  <node id='-199' action='modify' visible='true' lat='49.641397690721426' lon='9.002032535663991' />
+  <node id='-200' visible='true' lat='49.639702839791006' lon='9.002946110514843' />
+  <node id='-201' visible='true' lat='49.641505032635514' lon='9.005561427326938' />
+  <node id='-202' action='modify' visible='true' lat='49.640355464550595' lon='9.000557228744347' />
+  <node id='-203' action='modify' visible='true' lat='49.63983876223289' lon='8.999763400318008' />
+  <node id='-204' action='modify' visible='true' lat='49.640963432528' lon='9.002300773285747' />
+  <node id='-205' action='modify' visible='true' lat='49.640355464550595' lon='9.00163017923136' />
+  <node id='-206' action='modify' visible='true' lat='49.641650361020126' lon='9.011375542032631' />
+  <node id='-207' action='modify' visible='true' lat='49.63994510769946' lon='9.011537754906723' />
+  <node id='-208' action='modify' visible='true' lat='49.63989778320692' lon='9.01301762121565' />
+  <node id='-209' action='modify' timestamp='2008-03-17T23:16:02Z' visible='true' lat='49.63851559945754' lon='9.011923928883068'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-210' action='modify' timestamp='2008-03-17T23:16:00Z' visible='true' lat='49.63931004998166' lon='9.014994879645652'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-211' action='modify' timestamp='2008-03-17T23:16:02Z' visible='true' lat='49.63656290879177' lon='9.01184224653937'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-212' action='modify' timestamp='2008-03-19T00:23:09Z' visible='true' lat='49.638495992538296' lon='9.013041516659012'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-213' action='modify' timestamp='2008-03-17T23:16:00Z' visible='true' lat='49.639297465489946' lon='9.013075043737933'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-214' action='modify' timestamp='2008-03-17T23:16:01Z' visible='true' lat='49.63932787364639' lon='9.011341760369241'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-215' action='modify' timestamp='2008-03-17T23:16:00Z' visible='true' lat='49.6362341674196' lon='9.011212347946481' />
+  <node id='-216' action='modify' timestamp='2008-06-18T15:47:24Z' visible='true' lat='49.63846974351432' lon='9.014959728094702' />
+  <node id='-217' action='modify' timestamp='2008-03-17T23:16:01Z' visible='true' lat='49.639285639926086' lon='9.016386267202325'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-218' action='modify' timestamp='2008-03-17T23:16:01Z' visible='true' lat='49.63614569649766' lon='9.016254920813461'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <node id='-219' action='modify' timestamp='2008-03-17T23:16:02Z' visible='true' lat='49.636497278618506' lon='9.01558299248487' />
+  <node id='-220' action='modify' visible='true' lat='49.64173715249402' lon='9.01288893804428' />
+  <node id='-221' action='modify' visible='true' lat='49.63997057825359' lon='9.017121223877993' />
+  <node id='-222' action='modify' visible='true' lat='49.6418769973781' lon='9.016690773382148' />
+  <node id='-223' action='modify' timestamp='2008-03-17T23:16:02Z' visible='true' lat='49.638457370514' lon='9.015664984322639'>
+    <tag k='created_by' v='Merkaartor 0.10' />
+  </node>
+  <way id='-224' visible='true'>
+    <nd ref='-171' />
+    <nd ref='-151' />
+    <nd ref='-147' />
+    <nd ref='-146' />
+    <nd ref='-148' />
+    <nd ref='-145' />
+    <nd ref='-152' />
+    <nd ref='-172' />
+    <nd ref='-150' />
+    <nd ref='-149' />
+    <nd ref='-171' />
+    <tag k='name' v='Thing in the Middle' />
+  </way>
+  <way id='-225' visible='true'>
+    <nd ref='-164' />
+    <nd ref='-163' />
+    <nd ref='-166' />
+    <nd ref='-165' />
+    <nd ref='-164' />
+    <tag k='parking' v='surface' />
+    <tag k='name' v='Outer blob' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-226' visible='true'>
+    <nd ref='-117' />
+    <nd ref='-106' />
+    <nd ref='-105' />
+    <nd ref='-116' />
+    <nd ref='-117' />
+  </way>
+  <way id='-227' visible='true'>
+    <nd ref='-28' />
+    <nd ref='-39' />
+    <nd ref='-34' />
+    <nd ref='-37' />
+    <nd ref='-40' />
+    <nd ref='-35' />
+    <nd ref='-31' />
+    <nd ref='-33' />
+    <nd ref='-36' />
+    <nd ref='-29' />
+    <nd ref='-28' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-228' visible='true'>
+    <nd ref='-158' />
+    <nd ref='-155' />
+    <nd ref='-160' />
+    <nd ref='-136' />
+    <nd ref='-158' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-229' visible='true'>
+    <nd ref='-109' />
+    <nd ref='-110' />
+    <nd ref='-107' />
+    <nd ref='-108' />
+    <nd ref='-109' />
+  </way>
+  <way id='-230' visible='true'>
+    <nd ref='-70' />
+    <nd ref='-77' />
+    <nd ref='-73' />
+    <nd ref='-76' />
+    <nd ref='-70' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-231' visible='true'>
+    <nd ref='-10' />
+    <nd ref='-11' />
+    <nd ref='-12' />
+    <nd ref='-13' />
+    <nd ref='-10' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-232' visible='true'>
+    <nd ref='-99' />
+    <nd ref='-102' />
+    <nd ref='-101' />
+    <nd ref='-111' />
+    <nd ref='-99' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-233' action='modify' visible='true'>
+    <nd ref='-198' />
+    <nd ref='-201' />
+    <nd ref='-199' />
+    <nd ref='-204' />
+    <nd ref='-205' />
+    <nd ref='-200' />
+    <nd ref='-195' />
+    <nd ref='-181' />
+    <nd ref='-184' />
+    <nd ref='-194' />
+    <nd ref='-197' />
+    <nd ref='-196' />
+    <nd ref='-198' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-234' visible='true'>
+    <nd ref='-61' />
+    <nd ref='-62' />
+    <nd ref='-65' />
+    <nd ref='-68' />
+    <nd ref='-63' />
+    <nd ref='-64' />
+    <nd ref='-61' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-235' visible='true'>
+    <nd ref='-81' />
+    <nd ref='-84' />
+    <nd ref='-83' />
+    <nd ref='-94' />
+    <nd ref='-93' />
+    <nd ref='-96' />
+    <nd ref='-95' />
+    <nd ref='-90' />
+    <nd ref='-89' />
+    <nd ref='-92' />
+    <nd ref='-81' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-236' visible='true'>
+    <nd ref='-44' />
+    <nd ref='-46' />
+    <nd ref='-41' />
+    <nd ref='-52' />
+    <nd ref='-38' />
+    <nd ref='-42' />
+    <nd ref='-48' />
+    <nd ref='-47' />
+    <nd ref='-45' />
+    <nd ref='-43' />
+    <nd ref='-44' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-237' visible='true'>
+    <nd ref='-112' />
+    <nd ref='-97' />
+    <nd ref='-100' />
+    <nd ref='-98' />
+    <nd ref='-112' />
+  </way>
+  <way id='-238' visible='true'>
+    <nd ref='-120' />
+    <nd ref='-118' />
+    <nd ref='-128' />
+    <nd ref='-114' />
+    <nd ref='-120' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-239' visible='true'>
+    <nd ref='-78' />
+    <nd ref='-79' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-240' visible='true'>
+    <nd ref='-25' />
+    <nd ref='-15' />
+    <nd ref='-27' />
+    <tag k='highway' v='footway' />
+  </way>
+  <way id='-241' visible='true'>
+    <nd ref='-125' />
+    <nd ref='-126' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-242' action='modify' visible='true'>
+    <nd ref='-192' />
+    <nd ref='-189' />
+    <nd ref='-186' />
+    <nd ref='-191' />
+    <nd ref='-180' />
+    <nd ref='-177' />
+    <nd ref='-190' />
+    <nd ref='-179' />
+    <nd ref='-188' />
+    <nd ref='-192' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-243' visible='true'>
+    <nd ref='-60' />
+    <nd ref='-59' />
+    <nd ref='-58' />
+    <nd ref='-57' />
+    <nd ref='-60' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-244' visible='true'>
+    <nd ref='-91' />
+    <nd ref='-103' />
+    <nd ref='-104' />
+  </way>
+  <way id='-245' action='modify' visible='true'>
+    <nd ref='-208' />
+    <nd ref='-221' />
+    <nd ref='-222' />
+    <nd ref='-220' />
+    <nd ref='-208' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-246' visible='true'>
+    <nd ref='-22' />
+    <nd ref='-21' />
+    <nd ref='-17' />
+    <nd ref='-20' />
+    <nd ref='-22' />
+    <tag k='landuse' v='basin' />
+  </way>
+  <way id='-247' action='modify' visible='true'>
+    <nd ref='-185' />
+    <nd ref='-189' />
+    <nd ref='-186' />
+    <nd ref='-191' />
+    <nd ref='-180' />
+    <nd ref='-177' />
+    <nd ref='-190' />
+    <nd ref='-179' />
+    <nd ref='-187' />
+    <nd ref='-185' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-248' visible='true'>
+    <nd ref='-5' />
+    <nd ref='-6' />
+    <nd ref='-7' />
+    <nd ref='-8' />
+    <nd ref='-9' />
+    <nd ref='-5' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-249' timestamp='2008-06-18T15:47:24Z' visible='true'>
+    <nd ref='-216' />
+    <nd ref='-210' />
+    <nd ref='-213' />
+    <nd ref='-212' />
+    <nd ref='-216' />
+    <tag k='created_by' v='Potlatch 0.9c' />
+    <tag k='building' v='yes' />
+  </way>
+  <way id='-250' action='modify' visible='true'>
+    <nd ref='-178' />
+    <nd ref='-183' />
+    <nd ref='-184' />
+    <nd ref='-181' />
+    <nd ref='-182' />
+    <nd ref='-193' />
+    <nd ref='-203' />
+    <nd ref='-202' />
+    <nd ref='-205' />
+    <nd ref='-204' />
+    <nd ref='-199' />
+    <nd ref='-178' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-251' visible='true'>
+    <nd ref='-87' />
+    <nd ref='-82' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-252' visible='true'>
+    <nd ref='-153' />
+    <nd ref='-154' />
+    <nd ref='-133' />
+    <nd ref='-156' />
+    <nd ref='-153' />
+  </way>
+  <way id='-253' visible='true'>
+    <nd ref='-130' />
+    <nd ref='-129' />
+    <nd ref='-132' />
+    <nd ref='-131' />
+    <nd ref='-130' />
+  </way>
+  <way id='-254' visible='true'>
+    <nd ref='-115' />
+    <nd ref='-113' />
+    <nd ref='-127' />
+    <nd ref='-119' />
+    <nd ref='-115' />
+  </way>
+  <way id='-255' visible='true'>
+    <nd ref='-137' />
+    <nd ref='-144' />
+    <nd ref='-124' />
+    <nd ref='-139' />
+    <nd ref='-137' />
+  </way>
+  <way id='-256' visible='true'>
+    <nd ref='-32' />
+    <nd ref='-17' />
+    <nd ref='-30' />
+    <tag k='highway' v='footway' />
+  </way>
+  <way id='-257' timestamp='2008-06-17T03:52:47Z' visible='true'>
+    <nd ref='-210' />
+    <nd ref='-217' />
+    <nd ref='-218' />
+    <nd ref='-215' />
+    <nd ref='-214' />
+    <nd ref='-213' />
+    <nd ref='-212' />
+    <nd ref='-209' />
+    <nd ref='-211' />
+    <nd ref='-219' />
+    <nd ref='-223' />
+    <nd ref='-216' />
+    <nd ref='-210' />
+    <tag k='created_by' v='Merkaartor 0.10' />
+    <tag k='building' v='yes' />
+  </way>
+  <way id='-258' visible='true'>
+    <nd ref='-161' />
+    <nd ref='-162' />
+    <nd ref='-167' />
+    <nd ref='-168' />
+    <nd ref='-161' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-259' action='modify' visible='true'>
+    <nd ref='-206' />
+    <nd ref='-207' />
+    <nd ref='-208' />
+    <nd ref='-220' />
+    <nd ref='-206' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-260' visible='true'>
+    <nd ref='-169' />
+    <nd ref='-170' />
+    <nd ref='-173' />
+    <nd ref='-174' />
+    <nd ref='-169' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-261' visible='true'>
+    <nd ref='-159' />
+    <nd ref='-135' />
+    <nd ref='-134' />
+    <nd ref='-157' />
+    <nd ref='-159' />
+  </way>
+  <way id='-262' visible='true'>
+    <nd ref='-141' />
+    <nd ref='-143' />
+    <nd ref='-140' />
+    <nd ref='-121' />
+    <nd ref='-141' />
+  </way>
+  <way id='-263' visible='true'>
+    <nd ref='-18' />
+    <nd ref='-50' />
+    <nd ref='-49' />
+    <nd ref='-53' />
+    <nd ref='-54' />
+    <nd ref='-56' />
+    <nd ref='-55' />
+    <nd ref='-51' />
+    <nd ref='-14' />
+    <nd ref='-15' />
+    <nd ref='-18' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-264' visible='true'>
+    <nd ref='-176' />
+    <nd ref='-175' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-265' visible='true'>
+    <nd ref='-26' />
+    <nd ref='-16' />
+    <nd ref='-19' />
+    <nd ref='-24' />
+    <nd ref='-23' />
+    <nd ref='-26' />
+    <tag k='landuse' v='retail' />
+  </way>
+  <way id='-266' visible='true'>
+    <nd ref='-71' />
+    <nd ref='-72' />
+    <nd ref='-77' />
+    <nd ref='-75' />
+    <nd ref='-74' />
+    <nd ref='-71' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-267' visible='true'>
+    <nd ref='-85' />
+    <nd ref='-88' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-268' visible='true'>
+    <nd ref='-80' />
+    <nd ref='-86' />
+    <tag k='highway' v='motorway' />
+    <tag k='lanes' v='5' />
+  </way>
+  <way id='-269' visible='true'>
+    <nd ref='-142' />
+    <nd ref='-123' />
+    <nd ref='-138' />
+    <nd ref='-122' />
+    <nd ref='-142' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-270' visible='true'>
+    <nd ref='-69' />
+    <nd ref='-65' />
+    <nd ref='-66' />
+    <nd ref='-67' />
+    <nd ref='-68' />
+    <nd ref='-69' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <way id='-271' visible='true'>
+    <nd ref='-1' />
+    <nd ref='-2' />
+    <nd ref='-3' />
+    <nd ref='-4' />
+    <nd ref='-1' />
+    <tag k='parking' v='surface' />
+    <tag k='amenity' v='parking' />
+  </way>
+  <relation id='-272' visible='true'>
+    <member type='way' ref='-224' role='inner' />
+    <member type='way' ref='-225' role='outer' />
+    <tag k='name' v='6.' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-273' visible='true'>
+    <member type='way' ref='-252' role='inner' />
+    <member type='way' ref='-261' role='inner' />
+    <member type='way' ref='-228' role='outer' />
+    <tag k='name' v='5.' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-274' visible='true'>
+    <member type='way' ref='-262' role='inner' />
+    <member type='way' ref='-269' role='outer' />
+    <tag k='name' v='4. a' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-275' visible='true'>
+    <member type='way' ref='-237' role='inner' />
+    <member type='way' ref='-232' role='outer' />
+    <tag k='name' v='2.' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-276' visible='true'>
+  </relation>
+  <relation id='-277' visible='true'>
+    <member type='way' ref='-253' role='' />
+    <member type='way' ref='-255' role='' />
+    <tag k='name ' v='4. b' />
+  </relation>
+  <relation id='-278' visible='true'>
+    <member type='way' ref='-254' role='inner' />
+    <member type='way' ref='-238' role='outer' />
+    <tag k='name' v='3.' />
+    <tag k='type' v='multipolygon' />
+  </relation>
+  <relation id='-279' visible='true'>
+    <member type='way' ref='-235' role='area' />
+    <member type='node' ref='-93' role='node in area' />
+    <member type='node' ref='-95' role='node in area' />
+    <member type='way' ref='-244' role='some other way' />
+    <member type='node' ref='-90' role='to be deleted node in area' />
+    <tag k='name' v='1.' />
+  </relation>
+</osm>
diff --git a/utilsplugin/images/joinareas.png b/utilsplugin/images/joinareas.png
new file mode 100644
index 0000000..98267f8
Binary files /dev/null and b/utilsplugin/images/joinareas.png differ
diff --git a/utilsplugin/images/joinnodeway.png b/utilsplugin/images/joinnodeway.png
new file mode 100644
index 0000000..ea176d9
Binary files /dev/null and b/utilsplugin/images/joinnodeway.png differ
diff --git a/utilsplugin/images/mergenodes.png b/utilsplugin/images/mergenodes.png
new file mode 100644
index 0000000..fc41fb3
Binary files /dev/null and b/utilsplugin/images/mergenodes.png differ
diff --git a/utilsplugin/images/simplify.png b/utilsplugin/images/simplify.png
new file mode 100644
index 0000000..191ab68
Binary files /dev/null and b/utilsplugin/images/simplify.png differ
diff --git a/utilsplugin/src/UtilsPlugin/JoinAreasAction.java b/utilsplugin/src/UtilsPlugin/JoinAreasAction.java
new file mode 100644
index 0000000..bcae225
--- /dev/null
+++ b/utilsplugin/src/UtilsPlugin/JoinAreasAction.java
@@ -0,0 +1,885 @@
+package UtilsPlugin;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.Polygon;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.geom.Line2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Map.Entry;
+
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.CombineWayAction;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.ReverseWayAction;
+import org.openstreetmap.josm.actions.SplitWayAction;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.UndoRedoHandler;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSource;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.TigerUtils;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class JoinAreasAction extends JosmAction {
+    // This will be used to commit commands and unite them into one large command sequence at the end
+    private LinkedList<Command> cmds = new LinkedList<Command>();
+    private int cmdsCount = 0;
+
+    // HelperClass
+    // Saves a node and two positions where to insert the node into the ways
+    private class NodeToSegs implements Comparable<NodeToSegs> {
+        public int pos;
+        public Node n;
+        public double dis;
+        public NodeToSegs(int pos, Node n, LatLon dis) {
+            this.pos = pos;
+            this.n = n;
+            this.dis = n.getCoor().greatCircleDistance(dis);
+        }
+
+        public int compareTo(NodeToSegs o) {
+            if(this.pos == o.pos)
+                return (this.dis - o.dis) > 0 ? 1 : -1;
+            return this.pos - o.pos;
+        }
+    };
+
+    // HelperClass
+    // Saves a relation and a role an OsmPrimitve was part of until it was stripped from all relations
+    private class RelationRole {
+        public Relation rel;
+        public String role;
+        public RelationRole(Relation rel, String role) {
+            this.rel = rel;
+            this.role = role;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RelationRole)) return false;
+            RelationRole otherMember = (RelationRole) other;
+            return otherMember.role.equals(role) && otherMember.rel.equals(rel);
+        }
+    }
+
+    // Adds the menu entry, Shortcuts, etc.
+    public JoinAreasAction() {
+        super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), Shortcut.registerShortcut("tools:joinareas", tr("Tool: {0}", tr("Join overlapping Areas")),
+        KeyEvent.VK_J, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+    }
+
+    /**
+     * Gets called whenever the shortcut is pressed or the menu entry is selected
+     * Checks whether the selected objects are suitable to join and joins them if so
+     */
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = Main.main.getCurrentDataSet().getSelectedWays();
+
+        int ways = 0;
+        Way[] selWays = new Way[2];
+
+        LinkedList<Bounds> bounds = new LinkedList<Bounds>();
+        OsmDataLayer dataLayer = Main.map.mapView.getEditLayer();
+        for (DataSource ds : dataLayer.data.dataSources) {
+            if (ds.bounds != null)
+                bounds.add(ds.bounds);
+        }
+
+        boolean askedAlready = false;
+        for (OsmPrimitive prim : selection) {
+            Way way = (Way) prim;
+
+            // Too many ways
+            if(ways == 2) {
+                JOptionPane.showMessageDialog(Main.parent, tr("Only up to two areas can be joined at the moment."));
+                return;
+            }
+
+            if(!way.isClosed()) {
+                JOptionPane.showMessageDialog(Main.parent, tr("\"{0}\" is not closed and therefore can't be joined.", way.getName()));
+                return;
+            }
+
+            // This is copied from SimplifyAction and should be probably ported to tools
+            for (Node node : way.getNodes()) {
+                if(askedAlready) break;
+                boolean isInsideOneBoundingBox = false;
+                for (Bounds b : bounds) {
+                    if (b.contains(node.getCoor())) {
+                        isInsideOneBoundingBox = true;
+                        break;
+                    }
+                }
+
+                if (!isInsideOneBoundingBox) {
+                    int option = JOptionPane.showConfirmDialog(Main.parent,
+                            tr("The selected way(s) have nodes outside of the downloaded data region.\n"
+                                    + "This can lead to nodes being deleted accidentally.\n"
+                                    + "Are you really sure to continue?"),
+                            tr("Please abort if you are not sure"), JOptionPane.YES_NO_OPTION,
+                            JOptionPane.WARNING_MESSAGE);
+
+                    if (option != JOptionPane.YES_OPTION) return;
+                    askedAlready = true;
+                    break;
+                }
+            }
+
+            selWays[ways] = way;
+            ways++;
+        }
+
+        if (ways < 1) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please select at least one closed way the should be joined."));
+            return;
+        }
+
+        if(joinAreas(selWays[0], selWays[ways == 2 ? 1 : 0])) {
+            Main.map.mapView.repaint();
+            DataSet.fireSelectionChanged(Main.main.getCurrentDataSet().getSelected());
+        } else
+            JOptionPane.showMessageDialog(Main.parent, tr("No intersection found. Nothing was changed."));
+    }
+
+
+    /**
+     * Will join two overlapping areas
+     * @param Way First way/area
+     * @param Way Second way/area
+     * @return boolean Whether to display the "no operation" message
+     */
+    private boolean joinAreas(Way a, Way b) {
+        // Fix self-overlapping first or other errors
+        boolean same = a.equals(b);
+        boolean hadChanges = false;
+        if(!same) {
+            if(checkForTagConflicts(a, b)) return true; // User aborted, so don't warn again
+            hadChanges = joinAreas(a, a);
+            hadChanges = joinAreas(b, b) || hadChanges;
+        }
+
+        ArrayList<OsmPrimitive> nodes = addIntersections(a, b);
+        if(nodes.size() == 0) return hadChanges;
+        commitCommands(marktr("Added node on all intersections"));
+
+        // Remove ways from all relations so ways can be combined/split quietly
+        ArrayList<RelationRole> relations = removeFromRelations(a);
+        if(!same) relations.addAll(removeFromRelations(b));
+
+        // Don't warn now, because it will really look corrupted
+        boolean warnAboutRelations = relations.size() > 0;
+
+        Collection<Way> allWays = splitWaysOnNodes(a, b, nodes);
+
+        // Find all nodes and inner ways save them to a list
+        Collection<Node> allNodes = getNodesFromWays(allWays);
+        Collection<Way> innerWays = findInnerWays(allWays, allNodes);
+
+        // Join outer ways
+        Way outerWay = joinOuterWays(allWays, innerWays);
+
+        // Fix Multipolygons if there are any
+        Collection<Way> newInnerWays = fixMultigons(innerWays, outerWay);
+
+        // Delete the remaining inner ways
+        if(innerWays != null && innerWays.size() > 0)
+            cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), innerWays, true));
+        commitCommands(marktr("Delete Ways that are not part of an inner multipolygon"));
+
+        // We can attach our new multipolygon relation and pretend it has always been there
+        addOwnMultigonRelation(newInnerWays, outerWay, relations);
+        fixRelations(relations, outerWay);
+        commitCommands(marktr("Fix relations"));
+
+        stripTags(newInnerWays);
+        makeCommitsOneAction(
+            same
+                ? marktr("Joined self-overlapping area")
+                : marktr("Joined overlapping areas")
+        );
+
+        if(warnAboutRelations)
+            JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced."));
+
+        return true;
+    }
+
+    /**
+     * Checks if tags of two given ways differ, and presents the user a dialog to solve conflicts
+     * @param Way First way to check
+     * @param Way Second Way to check
+     * @return boolean True if not all conflicts could be resolved, False if everything's fine
+     */
+    private boolean checkForTagConflicts(Way a, Way b) {
+        ArrayList<Way> ways = new ArrayList<Way>();
+        ways.add(a);
+        ways.add(b);
+
+        // This is mostly copied and pasted from CombineWayAction.java and one day should be moved into tools
+        Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
+        for (Way w : ways) {
+            for (Entry<String,String> e : w.entrySet()) {
+                if (!props.containsKey(e.getKey()))
+                    props.put(e.getKey(), new TreeSet<String>());
+                props.get(e.getKey()).add(e.getValue());
+            }
+        }
+
+        Way ax = new Way(a);
+        Way bx = new Way(b);
+
+        Map<String, JComboBox> components = new HashMap<String, JComboBox>();
+        JPanel p = new JPanel(new GridBagLayout());
+        for (Entry<String, Set<String>> e : props.entrySet()) {
+            if (TigerUtils.isTigerTag(e.getKey())) {
+                String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
+                ax.put(e.getKey(), combined);
+                bx.put(e.getKey(), combined);
+            } else if (e.getValue().size() > 1) {
+                if("created_by".equals(e.getKey()))
+                {
+                    ax.put("created_by", "JOSM");
+                    bx.put("created_by", "JOSM");
+                } else {
+                    JComboBox c = new JComboBox(e.getValue().toArray());
+                    c.setEditable(true);
+                    p.add(new JLabel(e.getKey()), GBC.std());
+                    p.add(Box.createHorizontalStrut(10), GBC.std());
+                    p.add(c, GBC.eol());
+                    components.put(e.getKey(), c);
+                }
+            } else {
+                String val = e.getValue().iterator().next();
+                ax.put(e.getKey(), val);
+                bx.put(e.getKey(), val);
+            }
+        }
+
+        if (components.isEmpty())
+            return false; // No conflicts found
+
+        ExtendedDialog ed = new ExtendedDialog(Main.parent,
+        		tr("Enter values for all conflicts."),
+        		new String[] {tr("Solve Conflicts"), tr("Cancel")});
+        ed.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
+        ed.setContent(p);
+        ed.showDialog();
+
+        if (ed.getValue() != 1) return true; // user cancel, unresolvable conflicts
+
+        for (Entry<String, JComboBox> e : components.entrySet()) {
+            String val = e.getValue().getEditor().getItem().toString();
+            ax.put(e.getKey(), val);
+            bx.put(e.getKey(), val);
+        }
+
+        cmds.add(new ChangeCommand(a, ax));
+        cmds.add(new ChangeCommand(b, bx));
+        commitCommands(marktr("Fix tag conflicts"));
+        return false;
+    }
+
+    /**
+     * Will find all intersection and add nodes there for two given ways
+     * @param Way First way
+     * @param Way Second way
+     * @return ArrayList<OsmPrimitive> List of new nodes
+     */
+    private ArrayList<OsmPrimitive> addIntersections(Way a, Way b) {
+        boolean same = a.equals(b);
+        int nodesSizeA = a.getNodesCount();
+        int nodesSizeB = b.getNodesCount();
+
+        // We use OsmPrimitive here instead of Node because we later need to split a way at these nodes.
+        // With OsmPrimitve we can simply add the way and don't have to loop over the nodes
+        ArrayList<OsmPrimitive> nodes = new ArrayList<OsmPrimitive>();
+        ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>();
+        ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>();
+
+        for (int i = (same ? 1 : 0); i < nodesSizeA - 1; i++) {
+            for (int j = (same ? i + 2 : 0); j < nodesSizeB - 1; j++) {
+                // Avoid re-adding nodes that already exist on (some) intersections
+                if(a.getNode(i).equals(b.getNode(j)) || a.getNode(i+1).equals(b.getNode(j)))   {
+                    nodes.add(b.getNode(j));
+                    continue;
+                } else
+                if(a.getNode(i).equals(b.getNode(j+1)) || a.getNode(i+1).equals(b.getNode(j+1))) {
+                    nodes.add(b.getNode(j+1));
+                    continue;
+                }
+                LatLon intersection = getLineLineIntersection(
+                        a.getNode(i)  .getEastNorth().east(), a.getNode(i)  .getEastNorth().north(),
+                        a.getNode(i+1).getEastNorth().east(), a.getNode(i+1).getEastNorth().north(),
+                        b.getNode(j)  .getEastNorth().east(), b.getNode(j)  .getEastNorth().north(),
+                        b.getNode(j+1).getEastNorth().east(), b.getNode(j+1).getEastNorth().north());
+                if(intersection == null) continue;
+
+                // Create the node. Adding them to the ways must be delayed because we still loop over them
+                Node n = new Node(intersection);
+                cmds.add(new AddCommand(n));
+                nodes.add(n);
+                // The distance is needed to sort and add the nodes in direction of the way
+                nodesA.add(new NodeToSegs(i,  n, a.getNode(i).getCoor()));
+                if(same)
+                    nodesA.add(new NodeToSegs(j,  n, a.getNode(j).getCoor()));
+                else
+                    nodesB.add(new NodeToSegs(j,  n, b.getNode(j).getCoor()));
+            }
+        }
+
+        addNodesToWay(a, nodesA);
+        if(!same) addNodesToWay(b, nodesB);
+
+        return nodes;
+    }
+
+    /**
+     * Finds the intersection of two lines
+     * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise
+     */
+    static private LatLon getLineLineIntersection(
+                double x1, double y1, double x2, double y2,
+                double x3, double y3, double x4, double y4) {
+
+        if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;
+
+        // Convert line from (point, point) form to ax+by=c
+        double a1 = y2 - y1;
+        double b1 = x1 - x2;
+        double c1 = x2*y1 - x1*y2;
+
+        double a2 = y4 - y3;
+        double b2 = x3 - x4;
+        double c2 = x4*y3 - x3*y4;
+
+        // Solve the equations
+        double det = a1*b2 - a2*b1;
+        if(det == 0) return null; // Lines are parallel
+
+        return Main.proj.eastNorth2latlon(new EastNorth(
+            (b1*c2 - b2*c1)/det,
+            (a2*c1 -a1*c2)/det
+        ));
+    }
+
+    /**
+     * Inserts given nodes with positions into the given ways
+     * @param Way The way to insert the nodes into
+     * @param Collection<NodeToSegs> The list of nodes with positions to insert
+     */
+    private void addNodesToWay(Way a, ArrayList<NodeToSegs> nodes) {
+        Way ax=new Way(a);
+        Collections.sort(nodes);
+
+        int numOfAdds = 1;
+        for(NodeToSegs n : nodes) {
+            ax.addNode(n.pos + numOfAdds, n.n);
+            numOfAdds++;
+        }
+
+        cmds.add(new ChangeCommand(a, ax));
+    }
+
+    /**
+     * Commits the command list with a description
+     * @param String The description of what the commands do
+     */
+    private void commitCommands(String description) {
+        switch(cmds.size()) {
+            case 0:
+                return;
+            case 1:
+                Main.main.undoRedo.add(cmds.getFirst());
+                break;
+            default:
+                Command c = new SequenceCommand(tr(description), cmds);
+                Main.main.undoRedo.add(c);
+                break;
+        }
+
+        cmds.clear();
+        cmdsCount++;
+    }
+
+    /**
+     * Removes a given OsmPrimitive from all relations
+     * @param OsmPrimitive Element to remove from all relations
+     * @return ArrayList<RelationRole> List of relations with roles the primitives was part of
+     */
+    private ArrayList<RelationRole> removeFromRelations(OsmPrimitive osm) {
+        ArrayList<RelationRole> result = new ArrayList<RelationRole>();
+        for (Relation r : Main.main.getCurrentDataSet().relations) {
+            if (r.isDeleted() || r.incomplete) continue;
+            for (RelationMember rm : r.getMembers()) {
+                if (rm.getMember() != osm) continue;
+
+                Relation newRel = new Relation(r);
+                List<RelationMember> members = newRel.getMembers();
+                members.remove(rm);
+                newRel.setMembers(members);
+
+                cmds.add(new ChangeCommand(r, newRel));
+                RelationRole saverel =  new RelationRole(r, rm.getRole());
+                if(!result.contains(saverel)) result.add(saverel);
+                break;
+            }
+        }
+
+        commitCommands(marktr("Removed Element from Relations"));
+        return result;
+    }
+
+    /**
+     * This is a hacky implementation to make use of the splitWayAction code and
+     * should be improved. SplitWayAction needs to expose its splitWay function though.
+     */
+    private Collection<Way> splitWaysOnNodes(Way a, Way b, Collection<OsmPrimitive> nodes) {
+        ArrayList<Way> ways = new ArrayList<Way>();
+        ways.add(a);
+        if(!a.equals(b)) ways.add(b);
+
+        List<OsmPrimitive> affected = new ArrayList<OsmPrimitive>();
+        for (Way way : ways) {
+            nodes.add(way);
+            Main.main.getCurrentDataSet().setSelected(nodes);
+            nodes.remove(way);
+            new SplitWayAction().actionPerformed(null);
+            cmdsCount++;
+            affected.addAll(Main.main.getCurrentDataSet().getSelectedWays());
+        }
+        return osmprim2way(affected);
+    }
+
+    /**
+     * Converts a list of OsmPrimitives to a list of Ways
+     * @param Collection<OsmPrimitive> The OsmPrimitives list that's needed as a list of Ways
+     * @return Collection<Way> The list as list of Ways
+     */
+    static private Collection<Way> osmprim2way(Collection<OsmPrimitive> ways) {
+        Collection<Way> result = new ArrayList<Way>();
+        for(OsmPrimitive w: ways) {
+            if(w instanceof Way) result.add((Way) w);
+        }
+        return result;
+    }
+
+    /**
+     * Returns all nodes for given ways
+     * @param Collection<Way> The list of ways which nodes are to be returned
+     * @return Collection<Node> The list of nodes the ways contain
+     */
+    private Collection<Node> getNodesFromWays(Collection<Way> ways) {
+        Collection<Node> allNodes = new ArrayList<Node>();
+        for(Way w: ways) allNodes.addAll(w.getNodes());
+        return allNodes;
+    }
+
+    /**
+     * Finds all inner ways for a given list of Ways and Nodes from a multigon by constructing a polygon
+     * for each way, looking for inner nodes that are not part of this way. If a node is found, all ways
+     * containing this node are added to the list
+     * @param Collection<Way> A list of (splitted) ways that form a multigon
+     * @param Collection<Node> A list of nodes that belong to the multigon
+     * @return Collection<Way> A list of ways that are positioned inside the outer borders of the multigon
+     */
+    private Collection<Way> findInnerWays(Collection<Way> multigonWays, Collection<Node> multigonNodes) {
+        Collection<Way> innerWays = new ArrayList<Way>();
+        for(Way w: multigonWays) {
+            Polygon poly = new Polygon();
+            for(Node n: (w).getNodes()) poly.addPoint(latlonToXY(n.getCoor().lat()), latlonToXY(n.getCoor().lon()));
+
+            for(Node n: multigonNodes) {
+                if(!(w).containsNode(n) && poly.contains(latlonToXY(n.getCoor().lat()), latlonToXY(n.getCoor().lon()))) {
+                    getWaysByNode(innerWays, multigonWays, n);
+                }
+            }
+        }
+
+        return innerWays;
+    }
+
+    // Polygon only supports int coordinates, so convert them
+    private int latlonToXY(double val) {
+        return (int)Math.round(val*1000000);
+    }
+
+    /**
+     * Finds all ways that contain the given node.
+     * @param Collection<Way> A list to which matching ways will be added
+     * @param Collection<Way> A list of ways to check
+     * @param Node The node the ways should be checked against
+     */
+    private void getWaysByNode(Collection<Way> innerWays, Collection<Way> w, Node n) {
+        for(Way way : w) {
+            if(!(way).containsNode(n)) continue;
+            if(!innerWays.contains(way)) innerWays.add(way); // Will need this later for multigons
+        }
+    }
+
+    /**
+     * Joins the two outer ways and deletes all short ways that can't be part of a multipolygon anyway
+     * @param Collection<OsmPrimitive> The list of all ways that belong to that multigon
+     * @param Collection<Way> The list of inner ways that belong to that multigon
+     * @return Way The newly created outer way
+     */
+    private Way joinOuterWays(Collection<Way> multigonWays, Collection<Way> innerWays) {
+        ArrayList<Way> join = new ArrayList<Way>();
+        for(Way w: multigonWays) {
+            // Skip inner ways
+            if(innerWays.contains(w)) continue;
+
+            if(w.getNodesCount() <= 2)
+                cmds.add(new DeleteCommand(w));
+            else
+                join.add(w);
+        }
+
+        commitCommands(marktr("Join Areas: Remove Short Ways"));
+        return closeWay(joinWays(join));
+    }
+
+    /**
+     * Ensures a way is closed. If it isn't, last and first node are connected.
+     * @param Way the way to ensure it's closed
+     * @return Way The joined way.
+     */
+    private Way closeWay(Way w) {
+        if(w.isClosed())
+            return w;
+        Main.main.getCurrentDataSet().setSelected(w);
+        Way wnew = new Way(w);
+        wnew.addNode(wnew.firstNode());
+        cmds.add(new ChangeCommand(w, wnew));
+        commitCommands(marktr("Closed Way"));
+        return (Way)(Main.main.getCurrentDataSet().getSelectedWays().toArray())[0];
+    }
+
+    /**
+     * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former)
+     * @param ArrayList<Way> The list of ways to join
+     * @return Way The newly created way
+     */
+    private Way joinWays(ArrayList<Way> ways) {
+        if(ways.size() < 2) return ways.get(0);
+
+        // This will turn ways so all of them point in the same direction and CombineAction won't bug
+        // the user about this.
+        Way a = null;
+        for(Way b : ways) {
+            if(a == null) {
+                a = b;
+                continue;
+            }
+            if(a.getNode(0).equals(b.getNode(0)) ||
+               a.getNode(a.getNodesCount()-1).equals(b.getNode(b.getNodesCount()-1))) {
+                Main.main.getCurrentDataSet().setSelected(b);
+                new ReverseWayAction().actionPerformed(null);
+                cmdsCount++;
+            }
+            a = b;
+        }
+        Main.main.getCurrentDataSet().setSelected(ways);
+        // TODO: It might be possible that a confirmation dialog is presented even after reversing (for
+        // "strange" ways). If the user cancels this, makeCommitsOneAction will wrongly consume a previous
+        // action. Make CombineWayAction either silent or expose its combining capabilities.
+        new CombineWayAction().actionPerformed(null);
+        cmdsCount++;
+        return (Way)(Main.main.getCurrentDataSet().getSelectedWays().toArray())[0];
+    }
+
+    /**
+     * Finds all ways that may be part of a multipolygon relation and removes them from the given list.
+     * It will automatically combine "good" ways
+     * @param Collection<Way> The list of inner ways to check
+     * @param Way The newly created outer way
+     * @return ArrayList<Way> The List of newly created inner ways
+     */
+    private ArrayList<Way> fixMultigons(Collection<Way> uninterestingWays, Way outerWay) {
+        Collection<Node> innerNodes = getNodesFromWays(uninterestingWays);
+        Collection<Node> outerNodes = outerWay.getNodes();
+
+        // The newly created inner ways. uninterestingWays is passed by reference and therefore modified in-place
+        ArrayList<Way> newInnerWays = new ArrayList<Way>();
+
+        // Now we need to find all inner ways that contain a remaining node, but no outer nodes
+        // Remaining nodes are those that contain to more than one way. All nodes that belong to an
+        // inner multigon part will have at least two ways, so we can use this to find which ways do
+        // belong to the multigon.
+        ArrayList<Way> possibleWays = new ArrayList<Way>();
+        wayIterator: for(Way w : uninterestingWays) {
+            boolean hasInnerNodes = false;
+            for(Node n : w.getNodes()) {
+                if(outerNodes.contains(n)) continue wayIterator;
+                if(!hasInnerNodes && innerNodes.contains(n)) hasInnerNodes = true;
+            }
+            if(!hasInnerNodes || w.getNodesCount() < 2) continue;
+            possibleWays.add(w);
+        }
+
+        // This removes unnecessary ways that might have been added.
+        removeAlmostAlikeWays(possibleWays);
+        removePartlyUnconnectedWays(possibleWays);
+
+        // Join all ways that have one start/ending node in common
+        Way joined = null;
+        outerIterator: do {
+            joined = null;
+            for(Way w1 : possibleWays) {
+                if(w1.isClosed()) {
+                    if(!wayIsCollapsed(w1)) {
+                        uninterestingWays.remove(w1);
+                        newInnerWays.add(w1);
+                    }
+                    joined = w1;
+                    possibleWays.remove(w1);
+                    continue outerIterator;
+                }
+                for(Way w2 : possibleWays) {
+                    // w2 cannot be closed, otherwise it would have been removed above
+                    if(!waysCanBeCombined(w1, w2)) continue;
+
+                    ArrayList<Way> joinThem = new ArrayList<Way>();
+                    joinThem.add(w1);
+                    joinThem.add(w2);
+                    uninterestingWays.removeAll(joinThem);
+                    possibleWays.removeAll(joinThem);
+
+                    // Although we joined the ways, we cannot simply assume that they are closed
+                    joined = joinWays(joinThem);
+                    uninterestingWays.add(joined);
+                    possibleWays.add(joined);
+                    continue outerIterator;
+                }
+            }
+        } while(joined != null);
+        return newInnerWays;
+    }
+
+    /**
+     * Removes almost alike ways (= ways that are on top of each other for all nodes)
+     * @param ArrayList<Way> the ways to remove almost-duplicates from
+     */
+    private void removeAlmostAlikeWays(ArrayList<Way> ways) {
+        Collection<Way> removables = new ArrayList<Way>();
+        outer: for(int i=0; i < ways.size(); i++) {
+            Way a = ways.get(i);
+            for(int j=i+1; j < ways.size(); j++) {
+                Way b = ways.get(j);
+                List<Node> revNodes = new ArrayList<Node>(b.getNodes());
+                Collections.reverse(revNodes);
+                if(a.getNodes().equals(b.getNodes()) || a.getNodes().equals(revNodes)) {
+                    removables.add(a);
+                    continue outer;
+                }
+            }
+        }
+        ways.removeAll(removables);
+    }
+
+    /**
+     * Removes ways from the given list whose starting or ending node doesn't
+     * connect to other ways from the same list (it's like removing spikes).
+     * @param ArrayList<Way> The list of ways to remove "spikes" from
+     */
+    private void removePartlyUnconnectedWays(ArrayList<Way> ways) {
+        List<Way> removables = new ArrayList<Way>();
+        for(Way a : ways) {
+            if(a.isClosed()) continue;
+            boolean connectedStart = false;
+            boolean connectedEnd = false;
+            for(Way b : ways) {
+                if(a.equals(b))
+                    continue;
+                if(b.isFirstLastNode(a.firstNode()))
+                    connectedStart = true;
+                if(b.isFirstLastNode(a.lastNode()))
+                    connectedEnd = true;
+            }
+            if(!connectedStart || !connectedEnd)
+                removables.add(a);
+        }
+        ways.removeAll(removables);
+    }
+
+    /**
+     * Checks if a way is collapsed (i.e. looks like <---->)
+     * @param Way A *closed* way to check if it is collapsed
+     * @return boolean If the closed way is collapsed or not
+     */
+    private boolean wayIsCollapsed(Way w) {
+        if(w.getNodesCount() <= 3) return true;
+
+        // If a way contains more than one node twice, it must be collapsed (only start/end node may be the same)
+        Way x = new Way(w);
+        int count = 0;
+        for(Node n : w.getNodes()) {
+            x.removeNode(n);
+            if(x.containsNode(n)) count++;
+            if(count == 2) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks if two ways share one starting/ending node
+     * @param Way first way
+     * @param Way second way
+     * @return boolean Wheter the ways share a starting/ending node or not
+     */
+    private boolean waysCanBeCombined(Way w1, Way w2) {
+        if(w1.equals(w2)) return false;
+
+        if(w1.getNode(0).equals(w2.getNode(0))) return true;
+        if(w1.getNode(0).equals(w2.getNode(w2.getNodesCount()-1))) return true;
+
+        if(w1.getNode(w1.getNodesCount()-1).equals(w2.getNode(0))) return true;
+        if(w1.getNode(w1.getNodesCount()-1).equals(w2.getNode(w2.getNodesCount()-1))) return true;
+
+        return false;
+    }
+
+    /**
+     * Will add own multipolygon relation to the "previously existing" relations. Fixup is done by fixRelations
+     * @param Collection<Way> List of already closed inner ways
+     * @param Way The outer way
+     * @param ArrayList<RelationRole> The list of relation with roles to add own relation to
+     */
+    private void addOwnMultigonRelation(Collection<Way> inner, Way outer, ArrayList<RelationRole> rels) {
+        if(inner.size() == 0) return;
+        // Create new multipolygon relation and add all inner ways to it
+        Relation newRel = new Relation();
+        newRel.put("type", "multipolygon");
+        for(Way w : inner)
+            newRel.addMember(new RelationMember("inner", w));
+        cmds.add(new AddCommand(newRel));
+
+        // We don't add outer to the relation because it will be handed to fixRelations()
+        // which will then do the remaining work. Collections are passed by reference, so no
+        // need to return it
+        rels.add(new RelationRole(newRel, "outer"));
+        //return rels;
+    }
+
+    /**
+     * Adds the previously removed relations again to the outer way. If there are multiple multipolygon
+     * relations where the joined areas were in "outer" role a new relation is created instead with all
+     * members of both. This function depends on multigon relations to be valid already, it won't fix them.
+     * @param ArrayList<RelationRole> List of relations with roles the (original) ways were part of
+     * @param Way The newly created outer area/way
+     */
+    private void fixRelations(ArrayList<RelationRole> rels, Way outer) {
+        ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>();
+        for(RelationRole r : rels) {
+            if( r.rel.get("type") != null &&
+                r.rel.get("type").equalsIgnoreCase("multipolygon") &&
+                r.role.equalsIgnoreCase("outer")
+              ) {
+                multiouters.add(r);
+                continue;
+            }
+            // Add it back!
+            Relation newRel = new Relation(r.rel);
+            newRel.addMember(new RelationMember(r.role, outer));
+            cmds.add(new ChangeCommand(r.rel, newRel));
+        }
+
+        Relation newRel = null;
+        switch(multiouters.size()) {
+            case 0:
+                return;
+            case 1:
+                // Found only one to be part of a multipolygon relation, so just add it back as well
+                newRel = new Relation(multiouters.get(0).rel);
+                newRel.addMember(new RelationMember(multiouters.get(0).role, outer));
+                cmds.add(new ChangeCommand(multiouters.get(0).rel, newRel));
+                return;
+            default:
+                // Create a new relation with all previous members and (Way)outer as outer.
+                newRel = new Relation();
+                for(RelationRole r : multiouters) {
+                    // Add members
+                    for(RelationMember rm : r.rel.getMembers())
+                        if(!newRel.getMembers().contains(rm)) newRel.addMember(rm);
+                    // Add tags
+                    for (String key : r.rel.keySet()) {
+                        newRel.put(key, r.rel.get(key));
+                    }
+                    // Delete old relation
+                    cmds.add(new DeleteCommand(r.rel));
+                }
+                newRel.addMember(new RelationMember("outer", outer));
+                cmds.add(new AddCommand(newRel));
+        }
+    }
+
+    /**
+     * @param Collection<Way> The List of Ways to remove all tags from
+     */
+    private void stripTags(Collection<Way> ways) {
+        for(Way w: ways) stripTags(w);
+        commitCommands(marktr("Remove tags from inner ways"));
+    }
+
+    /**
+     * @param Way The Way to remove all tags from
+     */
+    private void stripTags(Way x) {
+        if(x.getKeys() == null) return;
+        Way y = new Way(x);
+        for (String key : x.keySet())
+            y.remove(key);
+        cmds.add(new ChangeCommand(x, y));
+    }
+
+    /**
+     * Takes the last cmdsCount actions back and combines them into a single action
+     * (for when the user wants to undo the join action)
+     * @param String The commit message to display
+     */
+    private void makeCommitsOneAction(String message) {
+        UndoRedoHandler ur = Main.main.undoRedo;
+        cmds.clear();
+        int i = Math.max(ur.commands.size() - cmdsCount, 0);
+        for(; i < ur.commands.size(); i++)
+            cmds.add(ur.commands.get(i));
+
+        for(i = 0; i < cmds.size(); i++)
+            ur.undo();
+
+        commitCommands(message == null ? marktr("Join Areas Function") : message);
+        cmdsCount = 0;
+    }
+}
diff --git a/utilsplugin/src/UtilsPlugin/JumpToAction.java b/utilsplugin/src/UtilsPlugin/JumpToAction.java
new file mode 100644
index 0000000..b85711a
--- /dev/null
+++ b/utilsplugin/src/UtilsPlugin/JumpToAction.java
@@ -0,0 +1,179 @@
+package UtilsPlugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.OsmUrlToBounds;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class JumpToAction extends JosmAction implements MouseListener {
+    public JumpToAction() {
+        super(tr("Jump To Position"), null, tr("Opens a dialog that allows to jump to a specific location"), Shortcut.registerShortcut("tools:jumpto", tr("Tool: {0}", tr("Jump To Position")),
+        KeyEvent.VK_G, Shortcut.GROUP_HOTKEY), true);
+    }
+
+    private JTextField url = new JTextField();
+    private JTextField lat = new JTextField();
+    private JTextField lon = new JTextField();
+    private JTextField zm = new JTextField();
+
+    private double zoomFactor = 0;
+    public void showJumpToDialog() {
+        MapView mv = Main.map.mapView;
+        LatLon curPos=mv.getProjection().eastNorth2latlon(mv.getCenter());
+        lat.setText(java.lang.Double.toString(curPos.lat()));
+        lon.setText(java.lang.Double.toString(curPos.lon()));
+
+        double dist = mv.getDist100Pixel();
+        zoomFactor = 1/dist;
+
+        zm.setText(java.lang.Long.toString(Math.round(dist*100)/100));
+        updateUrl(true);
+
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(new JLabel("<html>"
+                              + tr("Enter Lat/Lon to jump to position.")
+                              + "<br>"
+                              + tr("You can also paste an URL from www.openstreetmap.org")
+                              + "<br>"
+                              + "</html>"),
+                  BorderLayout.NORTH);
+
+        class osmURLListener implements DocumentListener {
+            public void changedUpdate(DocumentEvent e) { parseURL(); }
+            public void insertUpdate(DocumentEvent e) { parseURL(); }
+            public void removeUpdate(DocumentEvent e) { parseURL(); }
+        }
+
+        class osmLonLatListener implements DocumentListener {
+            public void changedUpdate(DocumentEvent e) { updateUrl(false); }
+            public void insertUpdate(DocumentEvent e) { updateUrl(false); }
+            public void removeUpdate(DocumentEvent e) { updateUrl(false); }
+        }
+
+        osmLonLatListener x=new osmLonLatListener();
+        lat.getDocument().addDocumentListener(x);
+        lon.getDocument().addDocumentListener(x);
+        zm.getDocument().addDocumentListener(x);
+        url.getDocument().addDocumentListener(new osmURLListener());
+
+        JPanel p = new JPanel(new GridBagLayout());
+        panel.add(p, BorderLayout.NORTH);
+
+        p.add(new JLabel(tr("Latitude")), GBC.eol());
+        p.add(lat, GBC.eol().fill(GBC.HORIZONTAL));
+
+        p.add(new JLabel(tr("Longitude")), GBC.eol());
+        p.add(lon, GBC.eol().fill(GBC.HORIZONTAL));
+
+        p.add(new JLabel(tr("Zoom (in metres)")), GBC.eol());
+        p.add(zm, GBC.eol().fill(GBC.HORIZONTAL));
+
+        p.add(new JLabel(tr("URL")), GBC.eol());
+        p.add(url, GBC.eol().fill(GBC.HORIZONTAL));
+
+        Object[] buttons = { tr("Jump there"), tr("Cancel") };
+        LatLon ll = null;
+        double zoomLvl = 100;
+        while(ll == null) {
+            int option = JOptionPane.showOptionDialog(
+                            Main.parent,
+                            panel,
+                            tr("Jump to Position"),
+                            JOptionPane.OK_CANCEL_OPTION,
+                            JOptionPane.PLAIN_MESSAGE,
+                            null,
+                            buttons,
+                            buttons[0]);
+
+            if (option != JOptionPane.OK_OPTION) return;
+            try {
+                zoomLvl = Double.parseDouble(zm.getText());
+                ll = new LatLon(Double.parseDouble(lat.getText()), Double.parseDouble(lon.getText()));
+            } catch (Exception ex) {
+                JOptionPane.showMessageDialog(Main.parent, tr("Could not parse Latitude, Longitude or Zoom. Please check."), tr("Unable to parse Lon/Lat"), JOptionPane.ERROR_MESSAGE);
+            }
+        }
+
+        mv.zoomToFactor(mv.getProjection().latlon2eastNorth(ll), zoomFactor * zoomLvl);
+    }
+
+    private void parseURL() {
+        if(!url.hasFocus()) return;
+        Bounds b = OsmUrlToBounds.parse(url.getText());
+        if (b != null) {
+            lat.setText(Double.toString((b.min.lat() + b.max.lat())/2));
+            lon.setText(Double.toString((b.min.lon() + b.max.lon())/2));
+
+            int zoomLvl = 16;
+            String[] args = url.getText().substring(url.getText().indexOf('?')+1).split("&");
+            for (String arg : args) {
+                int eq = arg.indexOf('=');
+                if (eq == -1 || !arg.substring(0, eq).equalsIgnoreCase("zoom")) continue;
+
+                zoomLvl = Integer.parseInt(arg.substring(eq + 1));
+                break;
+            }
+
+            // 10 000 000 = 10 000 * 1000 = World * (km -> m)
+            zm.setText(Double.toString(Math.round(10000000 * Math.pow(2, (-1) * zoomLvl))));
+        }
+    }
+
+    private void updateUrl(boolean force) {
+        if(!lat.hasFocus() && !lon.hasFocus() && !zm.hasFocus() && !force) return;
+        try {
+            double dlat = Double.parseDouble(lat.getText());
+            double dlon = Double.parseDouble(lon.getText());
+            double m = Double.parseDouble(zm.getText());
+            // Inverse function to the one above. 18 is the current maximum zoom
+            // available on standard renderers, so choose this is in case m
+            // should be zero
+            int zoomLvl = 18;
+            if(m > 0)
+            	zoomLvl = (int)Math.round((-1) * Math.log(m/10000000)/Math.log(2));
+            
+            int decimals = (int) Math.pow(10, (zoomLvl / 3));
+            dlat = Math.round(dlat * decimals);
+            dlat /= decimals;
+            dlon = Math.round(dlon * decimals);
+            dlon /= decimals;
+            url.setText("http://www.openstreetmap.org/?lat="+dlat+"&lon="+dlon+"&zoom="+zoomLvl);
+        } catch (NumberFormatException x) {}
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        showJumpToDialog();
+    }
+
+    public void mousePressed(MouseEvent e) {}
+
+    public void mouseReleased(MouseEvent e) {}
+
+    public void mouseEntered(MouseEvent e) {}
+
+    public void mouseExited(MouseEvent e) {}
+
+    public void mouseClicked(MouseEvent e) {
+        showJumpToDialog();
+    }
+}
diff --git a/utilsplugin/src/UtilsPlugin/SimplifyWayAction.java b/utilsplugin/src/UtilsPlugin/SimplifyWayAction.java
new file mode 100644
index 0000000..d821577
--- /dev/null
+++ b/utilsplugin/src/UtilsPlugin/SimplifyWayAction.java
@@ -0,0 +1,205 @@
+package UtilsPlugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.osm.DataSource;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class SimplifyWayAction extends JosmAction {
+    public SimplifyWayAction() {
+        super(tr("Simplify Way"), "simplify", tr("Delete unnecessary nodes from a way."), Shortcut.registerShortcut("tools:simplify", tr("Tool: {0}", tr("Simplify Way")),
+        KeyEvent.VK_Y, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = Main.main.getCurrentDataSet().getSelected();
+
+        int ways = 0;
+        LinkedList<Bounds> bounds = new LinkedList<Bounds>();
+        OsmDataLayer dataLayer = Main.map.mapView.getEditLayer();
+        for (DataSource ds : dataLayer.data.dataSources) {
+            if (ds.bounds != null)
+                bounds.add(ds.bounds);
+        }
+        for (OsmPrimitive prim : selection) {
+            if (prim instanceof Way) {
+                if (bounds.size() > 0) {
+                    Way way = (Way) prim;
+                    // We check if each node of each way is at least in one download
+                    // bounding box. Otherwise nodes may get deleted that are necessary by
+                    // unloaded ways (see Ticket #1594)
+                    for (Node node : way.getNodes()) {
+                        boolean isInsideOneBoundingBox = false;
+                        for (Bounds b : bounds) {
+                            if (b.contains(node.getCoor())) {
+                                isInsideOneBoundingBox = true;
+                                break;
+                            }
+                        }
+                        if (!isInsideOneBoundingBox) {
+                            int option = JOptionPane.showConfirmDialog(Main.parent,
+                                    tr("The selected way(s) have nodes outside of the downloaded data region.\n"
+                                            + "This can lead to nodes being deleted accidentally.\n"
+                                            + "Are you really sure to continue?"),
+                                    tr("Please abort if you are not sure"), JOptionPane.YES_NO_CANCEL_OPTION,
+                                    JOptionPane.WARNING_MESSAGE);
+
+                            if (option != JOptionPane.YES_OPTION)
+                                return;
+                            break;
+                        }
+                    }
+                }
+
+                ways++;
+            }
+        }
+
+        if (ways == 0) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please select at least one way to simplify."));
+            return;
+        } else if (ways > 10) {
+            //TRANSLATION: Although for English the use of trn is needless it is important for other languages
+            int option = JOptionPane.showConfirmDialog(Main.parent, trn(
+                    "The selection contains {0} way. Are you sure you want to simplify it?",
+                    "The selection contains {0} ways. Are you sure you want to simplify them all?",
+                    ways,ways),
+                    tr("Are you sure?"), JOptionPane.YES_NO_OPTION);
+            if (option != JOptionPane.YES_OPTION)
+                return;
+        }
+
+        for (OsmPrimitive prim : selection) {
+            if (prim instanceof Way) {
+                simplifyWay((Way) prim);
+            }
+        }
+    }
+
+    public void simplifyWay(Way w) {
+        double threshold = Double.parseDouble(Main.pref.get("simplify-way.max-error", "3"));
+
+        Way wnew = new Way(w);
+
+        int toI = wnew.getNodesCount() - 1;
+        for (int i = wnew.getNodesCount() - 1; i >= 0; i--) {
+            CollectBackReferencesVisitor backRefsV = new CollectBackReferencesVisitor(Main.main.getCurrentDataSet(), false);
+            backRefsV.visit(wnew.getNode(i));
+            boolean used = false;
+            if (backRefsV.getData().size() == 1) {
+                used = Collections.frequency(w.getNodes(), wnew.getNode(i)) > 1;
+            } else {
+                backRefsV.getData().remove(w);
+                used = !backRefsV.getData().isEmpty();
+            }
+            if (!used)
+                used = wnew.getNode(i).isTagged();
+
+            if (used) {
+                simplifyWayRange(wnew, i, toI, threshold);
+                toI = i;
+            }
+        }
+        simplifyWayRange(wnew, 0, toI, threshold);
+
+        HashSet<Node> delNodes = new HashSet<Node>();
+        delNodes.addAll(w.getNodes());
+        delNodes.removeAll(wnew.getNodes());
+
+        if (wnew.getNodesCount() != w.getNodesCount()) {
+            Collection<Command> cmds = new LinkedList<Command>();
+            cmds.add(new ChangeCommand(w, wnew));
+            cmds.add(new DeleteCommand(delNodes));
+            Main.main.undoRedo.add(new SequenceCommand(trn("Simplify Way (remove {0} node)", "Simplify Way (remove {0} nodes)", delNodes.size(), delNodes.size()), cmds));
+            Main.map.repaint();
+        }
+    }
+
+    public void simplifyWayRange(Way wnew, int from, int to, double thr) {
+        if (to - from >= 2) {
+            ArrayList<Node> ns = new ArrayList<Node>();
+            simplifyWayRange(wnew, from, to, ns, thr);
+            List<Node> nodes = wnew.getNodes();
+            for (int j = to - 1; j > from; j--) {            	
+            	nodes.remove(j);            	
+            }            
+            nodes.addAll(from + 1, ns);
+            wnew.setNodes(nodes);
+        }
+    }
+
+    /*
+     * Takes an interval [from,to] and adds nodes from (from,to) to ns.
+     * (from and to are indices of wnew.nodes.)
+     */
+    public void simplifyWayRange(Way wnew, int from, int to, ArrayList<Node> ns, double thr) {
+        Node fromN = wnew.getNode(from), toN = wnew.getNode(to);
+
+        int imax = -1;
+        double xtemax = 0;
+        for (int i = from + 1; i < to; i++) {
+            Node n = wnew.getNode(i);
+            double xte = Math.abs(EARTH_RAD
+                    * xtd(fromN.getCoor().lat() * Math.PI / 180, fromN.getCoor().lon() * Math.PI / 180, toN.getCoor().lat() * Math.PI
+                            / 180, toN.getCoor().lon() * Math.PI / 180, n.getCoor().lat() * Math.PI / 180, n.getCoor().lon() * Math.PI
+                            / 180));
+            if (xte > xtemax) {
+                xtemax = xte;
+                imax = i;
+            }
+        }
+
+        if (imax != -1 && xtemax >= thr) {
+            simplifyWayRange(wnew, from, imax, ns, thr);
+            ns.add(wnew.getNode(imax));
+            simplifyWayRange(wnew, imax, to, ns, thr);
+        }
+    }
+
+    public static double EARTH_RAD = 6378137.0;
+
+    /* From Aviaton Formulary v1.3
+     * http://williams.best.vwh.net/avform.htm
+     */
+    public static double dist(double lat1, double lon1, double lat2, double lon2) {
+        return 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) + Math.cos(lat1) * Math.cos(lat2)
+                * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
+    }
+
+    public static double course(double lat1, double lon1, double lat2, double lon2) {
+        return Math.atan2(Math.sin(lon1 - lon2) * Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
+                * Math.cos(lat2) * Math.cos(lon1 - lon2))
+                % (2 * Math.PI);
+    }
+
+    public static double xtd(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3) {
+        double dist_AD = dist(lat1, lon1, lat3, lon3);
+        double crs_AD = course(lat1, lon1, lat3, lon3);
+        double crs_AB = course(lat1, lon1, lat2, lon2);
+        return Math.asin(Math.sin(dist_AD) * Math.sin(crs_AD - crs_AB));
+    }
+}
diff --git a/utilsplugin/src/UtilsPlugin/UtilsPlugin.java b/utilsplugin/src/UtilsPlugin/UtilsPlugin.java
new file mode 100644
index 0000000..2456ff6
--- /dev/null
+++ b/utilsplugin/src/UtilsPlugin/UtilsPlugin.java
@@ -0,0 +1,29 @@
+package UtilsPlugin;
+
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.plugins.Plugin;
+
+public class UtilsPlugin extends Plugin {
+    JMenuItem SimplifyWay;
+    JMenuItem JoinAreas;
+    JumpToAction JumpToAct = new JumpToAction();
+
+    public UtilsPlugin() {
+        SimplifyWay = MainMenu.add(Main.main.menu.toolsMenu, new SimplifyWayAction());
+        JoinAreas = MainMenu.add(Main.main.menu.toolsMenu, new JoinAreasAction());
+        SimplifyWay.setEnabled(false);
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (oldFrame == null && newFrame != null) {
+            SimplifyWay.setEnabled(true);
+            JoinAreas.setEnabled(true);
+            newFrame.statusLine.addMouseListener(JumpToAct);
+        }
+    }
+}
diff --git a/validator/.classpath b/validator/.classpath
new file mode 100644
index 0000000..875df1c
--- /dev/null
+++ b/validator/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry including="images/" kind="src" path=""/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 5"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/validator/.project b/validator/.project
new file mode 100644
index 0000000..f56e39f
--- /dev/null
+++ b/validator/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>validator</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/validator/.settings/org.eclipse.core.resources.prefs b/validator/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..2ffd7fc
--- /dev/null
+++ b/validator/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Sun Sep 07 16:25:44 CEST 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/validator/.settings/org.eclipse.jdt.core.prefs b/validator/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..19195a5
--- /dev/null
+++ b/validator/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,258 @@
+#Fri Jun 26 21:35:48 CEST 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=false
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=false
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
+org.eclipse.jdt.core.formatter.comment.format_line_comments=false
+org.eclipse.jdt.core.formatter.comment.format_source_code=false
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=100
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/validator/.settings/org.eclipse.jdt.ui.prefs b/validator/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..dc5d284
--- /dev/null
+++ b/validator/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,4 @@
+#Sun Sep 07 16:57:55 CEST 2008
+eclipse.preferences.version=1
+formatter_profile=_JOSM
+formatter_settings_version=11
diff --git a/validator/CONTRIBUTION b/validator/CONTRIBUTION
new file mode 100644
index 0000000..68a744b
--- /dev/null
+++ b/validator/CONTRIBUTION
@@ -0,0 +1,5 @@
+The validator plugin was originally designed and coded by Francisco R. Santos 
+<frsantos at gmail.com>, with many modifications by Gabriel Ebner 
+<ge at gabrielebner.at>, Joerg Ostertag <openstreetmap at ostertag.name> and others.
+Now it is maintained by the OpenStreetMap community.
+
diff --git a/validator/LICENSE b/validator/LICENSE
new file mode 100644
index 0000000..fe31ef6
--- /dev/null
+++ b/validator/LICENSE
@@ -0,0 +1,341 @@
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+

+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+

+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+

+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+

+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/validator/README b/validator/README
new file mode 100644
index 0000000..a3dc612
--- /dev/null
+++ b/validator/README
@@ -0,0 +1,2 @@
+A OSM data validator that checks for common errors.
+
diff --git a/validator/build.xml b/validator/build.xml
new file mode 100644
index 0000000..aa74d50
--- /dev/null
+++ b/validator/build.xml
@@ -0,0 +1,56 @@
+<project name="validator" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Francisco R. Santos"/>
+                <attribute name="Plugin-Class" value="org.openstreetmap.josm.plugins.validator.OSMValidatorPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="An OSM data validator. It checks for problems in data, and provides fixes for the common ones. Spellcheck integrated for tag names."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/index.php/JOSM/Plugins/Validator"/>
+                <attribute name="Plugin-Mainversion" value="2168"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+</project>
diff --git a/validator/ignoretags.cfg b/validator/ignoretags.cfg
new file mode 100644
index 0000000..028cec5
--- /dev/null
+++ b/validator/ignoretags.cfg
@@ -0,0 +1,362 @@
+# JOSM IgnoreTags
+;
+; Ignore valid and semi-valid keys that start with...
+;
+S:opengeodb
+S:openGeoDB
+S:name:
+S:note:
+S:tiger:
+S:gnis:
+S:census:
+S:au.gov.abs:
+S:qroti:
+S:is_in
+S:wikipedia
+S:source_ref:
+;
+; Ignore valid and semi-valid keys that equal...
+;
+E:loc_name
+E:attribution
+E:admin_level
+E:old_name
+E:usage
+E:construction
+E:collection
+E:addr:state
+E:import_uuid
+E:image
+E:url
+E:website
+E:postal_code
+E:source:boundary
+E:hour_on
+E:hour_off
+E:tower:type
+E:rcn_ref
+E:place_name
+E:cycleway
+E:abutters
+E:survey_date
+E:right:state
+E:left:state
+;
+; Ignore valid and semi-valid keys that end with...
+;
+F::nswgnb
+F::forward
+F::backward
+F::left
+F::right
+;
+; Misc Tags
+;
+K:type=is_in
+K:bridge=viaduct
+K:bridge=aqueduct
+K:bridge=swing
+;
+; Highway Key/Value Pairs
+;
+K:highway=motorway_link
+K:highway=trunk_link
+K:highway=primary_link
+K:highway=secondary_link
+K:oneway=-1
+;
+; traffic_calming Tags
+;
+K:traffic_calming=yes
+K:traffic_calming=bump
+K:traffic_calming=chicane
+K:traffic_calming=cushion
+K:traffic_calming=hump
+K:traffic_calming=rumble_strip
+K:traffic_calming=table
+K:traffic_calming=choker
+;
+; Aeroway Key/Value Pairs
+;
+K:aeroway=apron
+K:aeroway=hanger
+K:aeroway=helipad
+K:aeroway=runway
+K:aeroway=taxiway
+K:aeroway=terminal
+T:aeroway=aerodrome|type=public
+;
+; Amenity Key/Value Pairs
+;
+K:amenity=arts_centre
+K:amenity=atm
+K:amenity=baby_hatch
+K:amenity=bank
+K:amenity=bbq
+K:amenity=bench
+K:amenity=biergarten
+K:amenity=bicycle_parking
+K:amenity=bicycle_rental
+K:amenity=bureau_de_change
+K:amenity=bus_station
+K:amenity=brothel
+K:amenity=cafe
+K:amenity=car_rental
+K:amenity=car_sharing
+K:amenity=cinema
+K:amenity=college
+K:amenity=courthouse
+K:amenity=crematorium
+K:amenity=dentist
+K:amenity=doctors
+K:amenity=drinking_water
+K:amenity=embassy
+K:amenity=emergency_phone
+K:amenity=fast_food
+K:amenity=ferry_terminal
+K:amenity=fire_station
+K:amenity=food_court
+K:amenity=fountain
+K:amenity=fuel
+K:amenity=gallery
+K:amenity=grave_yard
+K:amenity=grit_bin
+K:amenity=gym
+K:amenity=hospital
+K:amenity=hunting_stand
+K:amenity=kindergarten
+K:amenity=library
+K:amenity=marketplace
+K:amenity=nightclub
+K:amenity=parking
+K:amenity=pharmacy
+K:amenity=place_of_worship
+K:amenity=police
+K:amenity=post_box
+K:amenity=post_office
+K:amenity=prison
+K:amenity=pub
+K:amenity=public_building
+K:amenity=recycling
+K:amenity=restaurant
+K:amenity=school
+K:amenity=shelter
+K:amenity=signpost
+K:amenity=studio
+K:amenity=taxi
+K:amenity=telephone
+K:amenity=theatre
+K:amenity=toilets
+K:amenity=townhall
+K:amenity=university
+K:amenity=vending_machine
+K:amenity=veterinary
+K:amenity=waste_basket
+K:amenity=waste_disposal
+;
+; Cuisine Tags
+;
+K:cuisine=coffee_shop
+K:cuisine=fish_and_chips
+K:cuisine=pie
+;
+; Cycleway Tags
+;
+K:cycleway=lane
+K:cycleway=track
+K:cycleway=opposite_lane
+K:cycleway=opposite
+K:cycleway=opposite_track
+;
+; Historic Tags
+;
+K:historic=castle
+K:historic=monument
+K:historic=memorial
+K:historic=archaeological_site
+K:historic=ruins
+K:historic=battlefield
+K:historic=wreck
+K:historic=yes
+;
+; Man_made Tags
+;
+T:man_made=pipeline|type=water
+T:man_made=pipeline|type=oil
+T:man_made=pipeline|type=gas
+T:man_made=pipeline|type=sewage
+T:man_made=pipeline|location=underground
+T:man_made=pipeline|location=underwater
+T:man_made=pipeline|location=overground
+;
+; Military Tags
+;
+K:military=airfield
+K:military=bunker
+K:military=barracks
+K:military=danger_area
+K:military=range
+K:military=naval_base
+;
+; Natural Tags
+;
+K:natural=bay
+K:natural=beach
+K:natural=cave_entrance
+K:natural=cliff
+K:natural=fell
+K:natural=glacier
+K:natural=heath
+K:natural=marsh
+K:natural=mud
+K:natural=peak
+K:natural=scree
+K:natural=scrub
+K:natural=spring
+K:natural=tree
+K:natural=volcano
+K:natural=wetland
+;
+; Surface Key/Value Pairs
+;
+K:surface=dirt
+K:surface=wood
+;
+; Relation Tags
+;
+K:relation=to
+K:relation=from
+;
+; Religious Key/Value Pairs
+;
+T:religion=christian|denomination=anglican
+T:religion=muslim|denomination=alaouite
+T:religion=jewish|denomination=alternative
+T:religion=christian|denomination=apostolic
+T:religion=jewish|denomination=ashkenazi
+T:religion=christian|denomination=baptist
+T:religion=christian|denomination=catholic
+T:religion=christian|denomination=christian_community
+T:religion=christian|denomination=christian_scientist
+T:religion=jewish|denomination=conservative
+T:religion=christian|denomination=coptic_orthodox
+T:religion=christian|denomination=czechoslovak_hussite
+T:religion=muslim|denomination=druze
+T:religion=christian|denomination=dutch_reformed
+T:religion=christian|denomination=evangelical
+T:religion=pastafarian|denomination=EVKdFSMiD
+T:religion=christian|denomination=foursquare
+T:religion=christian|denomination=greek_orthodox
+T:religion=jewish|denomination=hasidic
+T:religion=jewish|denomination=humanistic  
+T:religion=muslim|denomination=ibadi
+T:religion=muslim|denomination=ismaili
+T:religion=christian|denomination=jehovahs_witness
+T:religion=christian|denomination=kabbalah
+T:religion=christian|denomination=karaite  
+T:religion=jewish|denomination=liberal  
+T:religion=christian|denomination=living_waters_church
+T:religion=christian|denomination=lutheran
+T:religion=christian|denomination=maronite
+T:religion=other|denomination=masonic
+T:religion=christian|denomination=mennonite
+T:religion=christian|denomination=methodist
+T:religion=jewish|denomination=modern_orthodox
+T:religion=christian|denomination=mormon
+T:religion=jewish|denomination=neo_orthodox
+T:religion=christian|denomination=new_apostolic
+T:religion=christian|denomination=nondenominational
+T:religion=jewish|denomination=nondenominational
+T:religion=muslim|denomination=nondenominational
+T:religion=christian|denomination=old_catholic
+T:religion=christian|denomination=orthodox 
+T:religion=jewish|denomination=orthodox 
+T:religion=christian|denomination=pentecostal
+T:religion=christian|denomination=presbyterian
+T:religion=jewish|denomination=progressive 
+T:religion=christian|denomination=protestant
+T:religion=christian|denomination=quaker
+T:religion=jewish|denomination=reconstructionist
+T:religion=jewish|denomination=reform
+T:religion=jewish|denomination=renewal   
+T:religion=christian|denomination=roman_catholic
+T:religion=christian|denomination=russian_orthodox
+T:religion=christian|denomination=salvation_army
+T:religion=jewish|denomination=samaritan
+T:religion=christian|denomination=seventh_day_adventist
+T:religion=muslim|denomination=shia
+T:religion=muslim|denomination=sunni  
+T:religion=jewish|denomination=ultra_orthodox
+T:religion=christian|denomination=united
+T:religion=christian|denomination=united_reformed
+T:religion=christian|denomination=uniting
+;
+; Shop Key/Value Pairs
+;
+K:shop=alcohol
+K:shop=bakery
+K:shop=beverages
+K:shop=bicycle
+K:shop=books
+K:shop=butcher
+K:shop=car
+K:shop=car_repair
+K:shop=chemist
+K:shop=clothes
+K:shop=computer
+K:shop=confectionery
+K:shop=convenience
+K:shop=department_store
+K:shop=dry_cleaning
+K:shop=doityourself
+K:shop=electronics
+K:shop=florist
+K:shop=furniture
+K:shop=garden_centre
+K:shop=greengrocer
+K:shop=hairdresser
+K:shop=hardware
+K:shop=hifi
+K:shop=kiosk
+K:shop=laundry
+K:shop=mall
+K:shop=motorcycle
+K:shop=optician
+K:shop=organic
+K:shop=outdoor
+K:shop=sports
+K:shop=stationery
+K:shop=supermarket
+K:shop=shoes
+K:shop=toys
+K:shop=travel_agency
+K:shop=video
+;
+; Sports Tags
+;
+K:sport=boxing
+K:sport=netball
+;
+; Tourism Tags
+;
+K:tourism=alpine_hut
+K:tourism=attraction
+K:tourism=artwork
+K:tourism=camp_site
+K:tourism=caravan_site
+K:tourism=chalet
+K:tourism=guest_house
+K:tourism=hostel
+K:tourism=hotel
+K:tourism=information
+K:tourism=motel
+K:tourism=museum
+K:tourism=picnic_site
+K:tourism=theme_park
+K:tourism=viewpoint
+K:tourism=zoo
+K:tourism=yes
+;
+; Type Key/Value Pairs
+;
+K:type=collection
diff --git a/validator/images/data/error.gif b/validator/images/data/error.gif
new file mode 100644
index 0000000..486faff
Binary files /dev/null and b/validator/images/data/error.gif differ
diff --git a/validator/images/data/other.gif b/validator/images/data/other.gif
new file mode 100644
index 0000000..2da001e
Binary files /dev/null and b/validator/images/data/other.gif differ
diff --git a/validator/images/data/warning.gif b/validator/images/data/warning.gif
new file mode 100644
index 0000000..2b2e50f
Binary files /dev/null and b/validator/images/data/warning.gif differ
diff --git a/validator/images/dialogs/fix.png b/validator/images/dialogs/fix.png
new file mode 100644
index 0000000..9af6a52
Binary files /dev/null and b/validator/images/dialogs/fix.png differ
diff --git a/validator/images/dialogs/validator.png b/validator/images/dialogs/validator.png
new file mode 100644
index 0000000..2a51812
Binary files /dev/null and b/validator/images/dialogs/validator.png differ
diff --git a/validator/images/layer/validator.png b/validator/images/layer/validator.png
new file mode 100644
index 0000000..517c4d1
Binary files /dev/null and b/validator/images/layer/validator.png differ
diff --git a/validator/images/preferences/validator.png b/validator/images/preferences/validator.png
new file mode 100644
index 0000000..7fcbc1d
Binary files /dev/null and b/validator/images/preferences/validator.png differ
diff --git a/validator/images/validator.png b/validator/images/validator.png
new file mode 100644
index 0000000..8f52cf6
Binary files /dev/null and b/validator/images/validator.png differ
diff --git a/validator/josm-validator.launch b/validator/josm-validator.launch
new file mode 100644
index 0000000..fd6b2ff
--- /dev/null
+++ b/validator/josm-validator.launch
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/JOSM/src/org/openstreetmap/josm/gui/MainApplication.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 6"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.openstreetmap.josm.gui.MainApplication"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="validator"/>
+</launchConfiguration>
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ErrorLayer.java b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorLayer.java
new file mode 100644
index 0000000..58ac364
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorLayer.java
@@ -0,0 +1,142 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JSeparator;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A layer showing error messages.
+ * 
+ * @author frsantos
+ */
+public class ErrorLayer extends Layer implements LayerChangeListener {
+    private OSMValidatorPlugin plugin;
+
+    public ErrorLayer(OSMValidatorPlugin plugin) {
+        super(tr("Validation errors"));
+        this.plugin = plugin;
+        Layer.listeners.add(this);
+    }
+
+    /**
+     * Return a static icon.
+     */
+    @Override
+    public Icon getIcon() {
+        return ImageProvider.get("layer", "validator");
+    }
+
+    /**
+     * Draw all primitives in this layer but do not draw modified ones (they
+     * are drawn by the edit layer).
+     * Draw nodes last to overlap the ways they belong to.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void paint(final Graphics g, final MapView mv) {
+        DefaultMutableTreeNode root = plugin.validationDialog.tree.getRoot();
+        if (root == null || root.getChildCount() == 0)
+            return;
+
+        DefaultMutableTreeNode severity = (DefaultMutableTreeNode) root.getLastChild();
+        while (severity != null) {
+            Enumeration<DefaultMutableTreeNode> errorMessages = severity.breadthFirstEnumeration();
+            while (errorMessages.hasMoreElements()) {
+                Object tn = errorMessages.nextElement().getUserObject();
+                if (tn instanceof TestError)
+                    ((TestError) tn).paint(g, mv);
+            }
+
+            // Severities in inverse order
+            severity = severity.getPreviousSibling();
+        }
+    }
+
+    @Override
+    public String getToolTipText() {
+        Bag<Severity, TestError> errorTree = new Bag<Severity, TestError>();
+        List<TestError> errors = plugin.validationDialog.tree.getErrors();
+        for (TestError e : errors) {
+            errorTree.add(e.getSeverity(), e);
+        }
+
+        StringBuilder b = new StringBuilder();
+        for (Severity s : Severity.values()) {
+            if (errorTree.containsKey(s))
+                b.append(tr(s.toString())).append(": ").append(errorTree.get(s).size()).append("<br>");
+        }
+
+        if (b.length() == 0)
+            return "<html>" + tr("No validation errors") + "</html>";
+        else
+            return "<html>" + tr("Validation errors") + ":<br>" + b + "</html>";
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {
+    }
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return false;
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {
+    }
+
+    @Override
+    public Object getInfoComponent() {
+        return getToolTipText();
+    }
+
+    @Override
+    public Component[] getMenuEntries() {
+        return new Component[] { new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)), 
+                new JSeparator(),
+                new JMenuItem(new RenameLayerAction(null, this)), new JSeparator(),
+                new JMenuItem(new LayerListPopup.InfoAction(this)) };
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+    }
+
+    public void layerAdded(Layer newLayer) {
+    }
+
+    /**
+     * If layer is the OSM Data layer, remove all errors
+     */
+    public void layerRemoved(Layer oldLayer) {
+        if (oldLayer instanceof OsmDataLayer &&  Main.map.mapView.getEditLayer() == null) {
+            Main.map.mapView.removeLayer(this);
+        } else if (oldLayer == this) {
+            OSMValidatorPlugin.errorLayer = null;
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreePanel.java b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreePanel.java
new file mode 100644
index 0000000..e079c6e
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreePanel.java
@@ -0,0 +1,327 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.swing.JTree;
+import javax.swing.ToolTipManager;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.plugins.validator.util.MultipleNameVisitor;
+
+/**
+ * A panel that displays the error tree. The selection manager
+ * respects clicks into the selection list. Ctrl-click will remove entries from
+ * the list while single click will make the clicked entry the only selection.
+ *
+ * @author frsantos
+ */
+
+public class ErrorTreePanel extends JTree {
+    /** Serializable ID */
+    private static final long serialVersionUID = 2952292777351992696L;
+
+    /**
+     * The validation data.
+     */
+    protected DefaultTreeModel treeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
+
+    /** The list of errors shown in the tree */
+    private List<TestError> errors;
+
+    /**
+     * If {@link #filter} is not <code>null</code> only errors are displayed 
+     * that refer to one of the primitives in the filter.  
+     */
+    private Set<OsmPrimitive> filter = null;
+
+    /**
+     * Constructor
+     * @param errors The list of errors
+     */
+    public ErrorTreePanel(List<TestError> errors) {
+        ToolTipManager.sharedInstance().registerComponent(this);
+        this.setModel(treeModel);
+        this.setRootVisible(false);
+        this.setShowsRootHandles(true);
+        this.expandRow(0);
+        this.setVisibleRowCount(8);
+        this.setCellRenderer(new ErrorTreeRenderer());
+        this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+        setErrorList(errors);
+    }
+
+    public String getToolTipText(MouseEvent e) {
+        String res = null;
+        TreePath path = getPathForLocation(e.getX(), e.getY());
+        if (path != null) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            Object nodeInfo = node.getUserObject();
+
+            if (nodeInfo instanceof TestError) {
+                TestError error = (TestError) nodeInfo;
+                MultipleNameVisitor v = new MultipleNameVisitor();
+                v.visit(error.getPrimitives());
+                res = "<html>" + v.getText() + "<br>" + error.getMessage();
+                String d = error.getDescription();
+                if (d != null)
+                    res += "<br>" + d;
+                res += "</html>";
+            } else
+                res = node.toString();
+        }
+        return res;
+    }
+
+    /** Constructor */
+    public ErrorTreePanel() {
+        this(null);
+    }
+
+    @Override
+    public void setVisible(boolean v) {
+        if (v)
+            buildTree();
+        else
+            treeModel.setRoot(new DefaultMutableTreeNode());
+        super.setVisible(v);
+    }
+
+    /**
+     * Builds the errors tree
+     */
+    public void buildTree() {
+        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
+
+        if (errors == null || errors.isEmpty()) {
+            treeModel.setRoot(rootNode);
+            return;
+        }
+
+        // Remember the currently expanded rows
+        Set<Object> oldSelectedRows = new HashSet<Object>();
+        Enumeration<TreePath> expanded = getExpandedDescendants(new TreePath(getRoot()));
+        if (expanded != null) {
+            while (expanded.hasMoreElements()) {
+                TreePath path = expanded.nextElement();
+                DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+                Object userObject = node.getUserObject();
+                if (userObject instanceof Severity)
+                    oldSelectedRows.add(userObject);
+                else if (userObject instanceof String) {
+                    String msg = (String) userObject;
+                    msg = msg.substring(0, msg.lastIndexOf(" ("));
+                    oldSelectedRows.add(msg);
+                }
+            }
+        }
+
+        Map<Severity, Bag<String, TestError>> errorTree = new HashMap<Severity, Bag<String, TestError>>();
+        Map<Severity, HashMap<String, Bag<String, TestError>>> errorTreeDeep = new HashMap<Severity, HashMap<String, Bag<String, TestError>>>();
+        for (Severity s : Severity.values()) {
+            errorTree.put(s, new Bag<String, TestError>(20));
+            errorTreeDeep.put(s, new HashMap<String, Bag<String, TestError>>());
+        }
+
+        for (TestError e : errors) {
+            if (e.getIgnored())
+                continue;
+            Severity s = e.getSeverity();
+            String d = e.getDescription();
+            String m = e.getMessage();
+            if (filter != null) {
+                boolean found = false;
+                for (OsmPrimitive p : e.getPrimitives()) {
+                    if (filter.contains(p)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found)
+                    continue;
+            }
+            if (d != null) {
+                Bag<String, TestError> b = errorTreeDeep.get(s).get(m);
+                if (b == null) {
+                    b = new Bag<String, TestError>(20);
+                    errorTreeDeep.get(s).put(m, b);
+                }
+                b.add(d, e);
+            } else
+                errorTree.get(s).add(m, e);
+        }
+
+        List<TreePath> expandedPaths = new ArrayList<TreePath>();
+        for (Severity s : Severity.values()) {
+            Bag<String, TestError> severityErrors = errorTree.get(s);
+            Map<String, Bag<String, TestError>> severityErrorsDeep = errorTreeDeep.get(s);
+            if (severityErrors.isEmpty() && severityErrorsDeep.isEmpty())
+                continue;
+
+            // Severity node
+            DefaultMutableTreeNode severityNode = new DefaultMutableTreeNode(s);
+            rootNode.add(severityNode);
+
+            if (oldSelectedRows.contains(s))
+                expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode }));
+
+            for (Entry<String, List<TestError>> msgErrors : severityErrors.entrySet()) {
+                // Message node
+                List<TestError> errors = msgErrors.getValue();
+                String msg = msgErrors.getKey() + " (" + errors.size() + ")";
+                DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
+                severityNode.add(messageNode);
+
+                if (oldSelectedRows.contains(msgErrors.getKey())) {
+                    expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
+                }
+
+                for (TestError error : errors) {
+                    // Error node
+                    DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
+                    messageNode.add(errorNode);
+                }
+            }
+            for (Entry<String, Bag<String, TestError>> bag : severityErrorsDeep.entrySet()) {
+                // Group node
+                Bag<String, TestError> errorlist = bag.getValue();
+                DefaultMutableTreeNode groupNode = null;
+                if (errorlist.size() > 1) {
+                    String nmsg = bag.getKey() + " (" + errorlist.size() + ")";
+                    groupNode = new DefaultMutableTreeNode(nmsg);
+                    severityNode.add(groupNode);
+                    if (oldSelectedRows.contains(bag.getKey())) {
+                        expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode }));
+                    }
+                }
+
+                for (Entry<String, List<TestError>> msgErrors : errorlist.entrySet()) {
+                    // Message node
+                    List<TestError> errors = msgErrors.getValue();
+                    String msg;
+                    if (groupNode != null)
+                        msg = msgErrors.getKey() + " (" + errors.size() + ")";
+                    else
+                        msg = msgErrors.getKey() + " - " + bag.getKey() + " (" + errors.size() + ")";
+                    DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
+                    if (groupNode != null)
+                        groupNode.add(messageNode);
+                    else
+                        severityNode.add(messageNode);
+
+                    if (oldSelectedRows.contains(msgErrors.getKey())) {
+                        if (groupNode != null) {
+                            expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode,
+                                    messageNode }));
+                        } else {
+                            expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
+                        }
+                    }
+
+                    for (TestError error : errors) {
+                        // Error node
+                        DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
+                        messageNode.add(errorNode);
+                    }
+                }
+            }
+        }
+
+        treeModel.setRoot(rootNode);
+        for (TreePath path : expandedPaths) {
+            this.expandPath(path);
+        }
+    }
+
+    /**
+     * Sets the errors list used by a data layer
+     * @param errors The error list that is used by a data layer
+     */
+    public void setErrorList(List<TestError> errors) {
+        this.errors = errors;
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Clears the current error list and adds these errors to it
+     * @param errors The validation errors
+     */
+    public void setErrors(List<TestError> newerrors) {
+        if (errors == null)
+            return;
+        errors.clear();
+        for (TestError error : newerrors) {
+            if (!error.getIgnored())
+                errors.add(error);
+        }
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Returns the errors of the tree
+     * @return  the errors of the tree
+     */
+    public List<TestError> getErrors() {
+        return errors != null ? errors : Collections.<TestError> emptyList();
+    }
+
+    public Set<OsmPrimitive> getFilter() {
+        return filter;
+    }
+
+    public void setFilter(Set<OsmPrimitive> filter) {
+        if (filter != null && filter.size() == 0)
+            this.filter = null;
+        else
+            this.filter = filter;
+        if (isVisible())
+            buildTree();
+    }
+
+    /**
+     * Updates the current errors list
+     * @param errors The validation errors
+     */
+    public void resetErrors() {
+        List<TestError> e = new ArrayList<TestError>(errors);
+        setErrors(e);
+    }
+
+    /**
+     * Expands all tree
+     */
+    @SuppressWarnings("unchecked")
+    public void expandAll() {
+        DefaultMutableTreeNode root = getRoot();
+
+        int row = 0;
+        Enumeration<DefaultMutableTreeNode> children = root.breadthFirstEnumeration();
+        while (children.hasMoreElements()) {
+            children.nextElement();
+            expandRow(row++);
+        }
+    }
+
+    /**
+     * Returns the root node model.
+     * @return The root node model
+     */
+    public DefaultMutableTreeNode getRoot() {
+        return (DefaultMutableTreeNode) treeModel.getRoot();
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreeRenderer.java b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreeRenderer.java
new file mode 100644
index 0000000..53f1de3
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ErrorTreeRenderer.java
@@ -0,0 +1,47 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import org.openstreetmap.josm.plugins.validator.util.MultipleNameVisitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Tree renderer for displaying errors
+ * @author frsantos
+ */
+public class ErrorTreeRenderer extends DefaultTreeCellRenderer
+{
+    /** Serializable ID */
+    private static final long serialVersionUID = 5567632718124640198L;
+
+    @Override
+    public Component getTreeCellRendererComponent(JTree tree, Object value,
+            boolean selected, boolean expanded, boolean leaf, int row,
+            boolean hasFocus)
+    {
+        super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
+        Object nodeInfo = node.getUserObject();
+
+        if (nodeInfo instanceof Severity)
+        {
+            Severity s = (Severity)nodeInfo;
+            setIcon(ImageProvider.get("data", s.getIcon()));
+        }
+        else if (nodeInfo instanceof TestError)
+        {
+            TestError error = (TestError)nodeInfo;
+            MultipleNameVisitor v = new MultipleNameVisitor();
+            v.visit(error.getPrimitives());
+            setText(v.getText());
+            setIcon(v.getIcon());
+        }
+
+        return this;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/GridLayer.java b/validator/src/org/openstreetmap/josm/plugins/validator/GridLayer.java
new file mode 100644
index 0000000..d4a0326
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/GridLayer.java
@@ -0,0 +1,202 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.geom.Point2D;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A debug layer for testing the grid cells a way crosses.
+ *
+ * @author frsantos
+ */
+public class GridLayer extends Layer
+{
+    /**
+     * Constructor
+     * @param name
+     */
+    public GridLayer(String name)
+    {
+        super(name);
+    }
+
+    /**
+     * Return a static icon.
+     */
+    @Override public Icon getIcon() {
+        return ImageProvider.get("layer", "validator");
+    }
+
+    /**
+     * Draw the grid and highlight all cells acuppied by any selected primitive.
+     */
+    @Override
+    public void paint(final Graphics g, final MapView mv)
+    {
+        if( !Main.pref.hasKey(PreferenceEditor.PREF_DEBUG + ".grid") )
+            return;
+
+        int gridWidth = Integer.parseInt(Main.pref.get(PreferenceEditor.PREF_DEBUG + ".grid") );
+        int width = mv.getWidth();
+        int height = mv.getHeight();
+
+        EastNorth origin = mv.getEastNorth(0, 0);
+        EastNorth border = mv.getEastNorth(width, height);
+
+        if( border.east() * gridWidth > 50 )
+            return;
+
+        g.setColor(Color.RED.darker().darker());
+        HighlightCellVisitor visitor = new HighlightCellVisitor(g, mv, gridWidth);
+        for(OsmPrimitive p : Main.main.getCurrentDataSet().getSelected() )
+            p.visit(visitor);
+
+        long x0 = (long)Math.floor(origin.east()  * gridWidth);
+        long x1 = (long)Math.floor(border.east()  * gridWidth);
+        long y0 = (long)Math.floor(origin.north() * gridWidth) + 1;
+        long y1 = (long)Math.floor(border.north() * gridWidth) + 1;
+        long aux;
+        if( x0 > x1 ) { aux = x0; x0 = x1; x1 = aux; }
+        if( y0 > y1 ) { aux = y0; y0 = y1; y1 = aux; }
+
+        g.setColor(Color.RED.brighter().brighter());
+        for( double x = x0; x <= x1; x++)
+        {
+            Point point = mv.getPoint( new EastNorth(x/gridWidth, 0));
+            g.drawLine(point.x, 0, point.x, height);
+        }
+
+        for( double y = y0; y <= y1; y++)
+        {
+            Point point = mv.getPoint( new EastNorth(0, y/gridWidth));
+            g.drawLine(0, point.y, width, point.y);
+        }
+    }
+
+    @Override
+    public String getToolTipText()
+    {
+        return null;
+    }
+
+    @Override
+    public void mergeFrom(Layer from) {}
+
+    @Override
+    public boolean isMergable(Layer other) {
+        return false;
+    }
+
+    @Override
+    public void visitBoundingBox(BoundingXYVisitor v) {}
+
+    @Override
+    public Object getInfoComponent()
+    {
+        return getToolTipText();
+    }
+
+    @Override
+    public Component[] getMenuEntries()
+    {
+        return new Component[]{
+                new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+                new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
+                new JSeparator(),
+                new JMenuItem(new RenameLayerAction(null, this)),
+                new JSeparator(),
+                new JMenuItem(new LayerListPopup.InfoAction(this))};
+    }
+
+    @Override public void destroy() { }
+
+    /**
+     * Visitor that highlights all cells the selected primitives go through
+     */
+    class HighlightCellVisitor extends AbstractVisitor
+    {
+        /** The MapView */
+        private final MapView mv;
+        /** The graphics */
+        private final Graphics g;
+        /** The grid width */
+        private final int gridDetail;
+        /** The width of a cell */
+        private int cellWidth;
+
+        /**
+         * Constructor
+         * @param g the graphics
+         * @param mv The MapView
+         * @param gridDetail The grid detail
+         */
+        public HighlightCellVisitor(final Graphics g, final MapView mv, int gridDetail)
+        {
+            this.g = g;
+            this.mv = mv;
+            this.gridDetail = gridDetail;
+
+            Point p = mv.getPoint( new EastNorth(0, 0) );
+            Point p2 = mv.getPoint( new EastNorth(1d/gridDetail, 1d/gridDetail) );
+            cellWidth = Math.abs(p2.x - p.x);
+        }
+
+        public void visit(Node n)
+        {
+            double x = n.getEastNorth().east() * gridDetail;
+            double y = n.getEastNorth().north()* gridDetail + 1;
+
+            drawCell( Math.floor(x), Math.floor(y) );
+        }
+
+        public void visit(Way w)
+        {
+            Node lastN = null;
+            for (Node n : w.getNodes()) {
+                if (lastN == null) {
+                    lastN = n;
+                    continue;
+                }
+                for (Point2D p : Util.getSegmentCells(lastN, n, gridDetail)) {
+                    drawCell( p.getX(), p.getY() );
+                }
+                lastN = n;
+            }
+        }
+
+        public void visit(Relation r) {}
+
+        /**
+         * Draws a solid cell at the (x,y) location
+         * @param x
+         * @param y
+         */
+        protected void drawCell(double x, double y)
+        {
+            Point p = mv.getPoint( new EastNorth(x/gridDetail, y/gridDetail) );
+            g.fillRect(p.x, p.y, cellWidth, cellWidth);
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java b/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java
new file mode 100644
index 0000000..b09371b
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/OSMValidatorPlugin.java
@@ -0,0 +1,308 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.UploadAction;
+import org.openstreetmap.josm.data.projection.Epsg4326;
+import org.openstreetmap.josm.data.projection.Lambert;
+import org.openstreetmap.josm.data.projection.Mercator;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.validator.tests.Coastlines;
+import org.openstreetmap.josm.plugins.validator.tests.CrossingWays;
+import org.openstreetmap.josm.plugins.validator.tests.DuplicateNode;
+import org.openstreetmap.josm.plugins.validator.tests.DuplicateWay;
+import org.openstreetmap.josm.plugins.validator.tests.DuplicatedWayNodes;
+import org.openstreetmap.josm.plugins.validator.tests.NodesWithSameName;
+import org.openstreetmap.josm.plugins.validator.tests.OverlappingWays;
+import org.openstreetmap.josm.plugins.validator.tests.SelfIntersectingWay;
+import org.openstreetmap.josm.plugins.validator.tests.SimilarNamedWays;
+import org.openstreetmap.josm.plugins.validator.tests.TagChecker;
+import org.openstreetmap.josm.plugins.validator.tests.UnclosedWays;
+import org.openstreetmap.josm.plugins.validator.tests.UnconnectedWays;
+import org.openstreetmap.josm.plugins.validator.tests.UntaggedNode;
+import org.openstreetmap.josm.plugins.validator.tests.UntaggedWay;
+import org.openstreetmap.josm.plugins.validator.tests.WronglyOrderedWays;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+
+/**
+ *
+ * A OSM data validator
+ *
+ * @author Francisco R. Santos <frsantos at gmail.com>
+ */
+public class OSMValidatorPlugin extends Plugin implements LayerChangeListener {
+
+    protected static OSMValidatorPlugin plugin;
+
+    protected static ErrorLayer errorLayer = null;
+
+    /** The validate action */
+    ValidateAction validateAction = new ValidateAction(this);
+
+    /** The validation dialog */
+    ValidatorDialog validationDialog;
+
+    /** The list of errors per layer*/
+    Map<Layer, List<TestError>> layerErrors = new HashMap<Layer, List<TestError>>();
+
+    /** Grid detail, multiplier of east,north values for valuable cell sizing */
+    public static double griddetail;
+
+    public Collection<String> ignoredErrors = new TreeSet<String>();
+
+    /**
+     * All available tests
+     * TODO: is there any way to find out automagically all available tests?
+     */
+    @SuppressWarnings("unchecked")
+    public static Class<Test>[] allAvailableTests = new Class[] { DuplicateNode.class, // ID    1 ..   99
+            OverlappingWays.class, // ID  101 ..  199
+            UntaggedNode.class, // ID  201 ..  299
+            UntaggedWay.class, // ID  301 ..  399
+            SelfIntersectingWay.class, // ID  401 ..  499
+            DuplicatedWayNodes.class, // ID  501 ..  599
+            CrossingWays.class, // ID  601 ..  699
+            SimilarNamedWays.class, // ID  701 ..  799
+            NodesWithSameName.class, // ID  801 ..  899
+            Coastlines.class, // ID  901 ..  999
+            WronglyOrderedWays.class, // ID 1001 .. 1099
+            UnclosedWays.class, // ID 1101 .. 1199
+            TagChecker.class, // ID 1201 .. 1299
+            UnconnectedWays.class, // ID 1301 .. 1399
+            DuplicateWay.class, // ID 1401 .. 1499
+    };
+
+    /**
+     * Creates the plugin
+     */
+    public OSMValidatorPlugin() {
+        plugin = this;
+        checkPluginDir();
+        initializeGridDetail();
+        initializeTests(getTests());
+        loadIgnoredErrors();
+    }
+
+    /**
+     * Check if plugin directory exists (store ignored errors file)
+     */
+    private void checkPluginDir() {
+        try {
+        File pathDir = new File(Util.getPluginDir());
+        if (!pathDir.exists())
+            pathDir.mkdirs();
+        } catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    private void loadIgnoredErrors() {
+        ignoredErrors.clear();
+        if (Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true)) {
+            try {
+                final BufferedReader in = new BufferedReader(new FileReader(Util.getPluginDir() + "ignorederrors"));
+                for (String line = in.readLine(); line != null; line = in.readLine()) {
+                    ignoredErrors.add(line);
+                }
+            } catch (final FileNotFoundException e) {
+            } catch (final IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void saveIgnoredErrors() {
+        try {
+            final PrintWriter out = new PrintWriter(new FileWriter(Util.getPluginDir() + "ignorederrors"), false);
+            for (String e : ignoredErrors)
+                out.println(e);
+            out.close();
+        } catch (final IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new PreferenceEditor(this);
+    }
+
+    private ValidateUploadHook uploadHook;
+    
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (newFrame != null) {
+            validationDialog = new ValidatorDialog(this);
+            newFrame.addToggleDialog(validationDialog);
+            initializeErrorLayer();
+            if (Main.pref.hasKey(PreferenceEditor.PREF_DEBUG + ".grid"))
+                Main.main.addLayer(new GridLayer(tr("Grid")));
+            Layer.listeners.add(this);
+        } else
+            Layer.listeners.remove(this);
+
+        if (newFrame != null) {
+        	UploadAction.registerUploadHook(uploadHook = new ValidateUploadHook(this));
+        } else {
+        	UploadAction.unregisterUploadHook(uploadHook);
+        	uploadHook = null;
+        }
+    }
+
+    public void initializeErrorLayer() {
+        if (!Main.pref.getBoolean(PreferenceEditor.PREF_LAYER, true))
+            return;
+        if (errorLayer == null) {
+            errorLayer = new ErrorLayer(this);
+            Main.main.addLayer(errorLayer);
+        }
+    }
+
+    /** Gets a map from simple names to all tests. */
+    public static Map<String, Test> getAllTestsMap() {
+        Map<String, Test> tests = new HashMap<String, Test>();
+        for (Class<Test> testClass : getAllAvailableTests()) {
+            try {
+                Test test = testClass.newInstance();
+                tests.put(testClass.getSimpleName(), test);
+            } catch (Exception e) {
+                e.printStackTrace();
+                continue;
+            }
+        }
+        applyPrefs(tests, false);
+        applyPrefs(tests, true);
+        return tests;
+    }
+
+    private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) {
+        Pattern regexp = Pattern.compile("(\\w+)=(true|false),?");
+        Matcher m = regexp.matcher(Main.pref.get(beforeUpload ? PreferenceEditor.PREF_TESTS_BEFORE_UPLOAD
+                : PreferenceEditor.PREF_TESTS));
+        int pos = 0;
+        while (m.find(pos)) {
+            String testName = m.group(1);
+            Test test = tests.get(testName);
+            if (test != null) {
+                boolean enabled = Boolean.valueOf(m.group(2));
+                if (beforeUpload) {
+                    test.testBeforeUpload = enabled;
+                } else {
+                    test.enabled = enabled;
+                }
+            }
+            pos = m.end();
+        }
+    }
+
+    public static Collection<Test> getTests() {
+        return getAllTestsMap().values();
+    }
+
+    public static Collection<Test> getEnabledTests(boolean beforeUpload) {
+        Collection<Test> enabledTests = getTests();
+        for (Test t : new ArrayList<Test>(enabledTests)) {
+            if (beforeUpload ? t.testBeforeUpload : t.enabled)
+                continue;
+            enabledTests.remove(t);
+        }
+        return enabledTests;
+    }
+
+    /**
+     * Gets the list of all available test classes
+     *
+     * @return An array of the test classes
+     */
+    public static Class<Test>[] getAllAvailableTests() {
+        return allAvailableTests;
+    }
+
+    /**
+     * Initialize grid details based on current projection system. Values based on
+     * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&error
+     * until most bugs were discovered while keeping the processing time reasonable)
+     */
+    public void initializeGridDetail() {
+        if (Main.proj.toString().equals(new Epsg4326().toString()))
+            OSMValidatorPlugin.griddetail = 10000;
+        else if (Main.proj.toString().equals(new Mercator().toString()))
+            OSMValidatorPlugin.griddetail = 100000;
+        else if (Main.proj.toString().equals(new Lambert().toString()))
+            OSMValidatorPlugin.griddetail = 0.1;
+    }
+
+    /**
+     * Initializes all tests
+     * @param allTests The tests to initialize
+     */
+    public void initializeTests(Collection<Test> allTests) {
+        for (Test test : allTests) {
+            try {
+                if (test.enabled) {
+                    test.getClass().getMethod("initialize", new Class[] { OSMValidatorPlugin.class }).invoke(null,
+                            new Object[] { this });
+                }
+            } catch (InvocationTargetException ite) {
+                ite.getCause().printStackTrace();
+                JOptionPane.showMessageDialog(Main.parent, 
+                		tr("Error initializing test {0}:\n {1}", test.getClass()
+                        .getSimpleName(), ite.getCause().getMessage()),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE);
+            } catch (Exception e) {
+                e.printStackTrace();
+                JOptionPane.showMessageDialog(Main.parent, 
+                		tr("Error initializing test {0}:\n {1}", test.getClass()
+                        .getSimpleName(), e),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }
+    }
+
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
+        if (newLayer instanceof OsmDataLayer) {
+            List<TestError> errors = layerErrors.get(newLayer);
+            validationDialog.tree.setErrorList(errors);
+            Main.map.repaint();
+        }
+    }
+
+    public void layerAdded(Layer newLayer) {
+        if (newLayer instanceof OsmDataLayer) {
+            layerErrors.put(newLayer, new ArrayList<TestError>());
+        }
+    }
+
+    public void layerRemoved(Layer oldLayer) {
+        layerErrors.remove(oldLayer);
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/PreferenceEditor.java b/validator/src/org/openstreetmap/josm/plugins/validator/PreferenceEditor.java
new file mode 100644
index 0000000..45fcf87
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/PreferenceEditor.java
@@ -0,0 +1,131 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.Collection;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Preference settings for the validator plugin
+ * 
+ * @author frsantos
+ */
+public class PreferenceEditor implements PreferenceSetting
+{
+    private OSMValidatorPlugin plugin;
+
+    /** The preferences prefix */
+    public static final String PREFIX = "validator";
+
+    /** The preferences key for debug preferences */
+    public static final String PREF_DEBUG = PREFIX + ".debug";
+
+    /** The preferences key for debug preferences */
+    public static final String PREF_LAYER = PREFIX + ".layer";
+
+    /** The preferences key for enabled tests */
+    public static final String PREF_TESTS = PREFIX + ".tests";
+
+    /** The preferences key for enabled tests */
+    public static final String PREF_USE_IGNORE = PREFIX + ".ignore";
+
+    /** The preferences key for enabled tests before upload*/
+    public static final String PREF_TESTS_BEFORE_UPLOAD = PREFIX + ".testsBeforeUpload";
+
+    /** The preferences key for ignored severity other on upload */
+    public static final String PREF_OTHER_UPLOAD = PREFIX + ".otherUpload";
+
+    /**
+     * The preferences key for enabling the permanent filtering
+     * of the displayed errors in the tree regarding the current selection 
+     */
+    public static final String PREF_FILTER_BY_SELECTION = PREFIX + ".selectionFilter";
+
+    private JCheckBox prefUseIgnore;
+    private JCheckBox prefUseLayer;
+    private JCheckBox prefOtherUpload;
+
+    /** The list of all tests */
+    private Collection<Test> allTests;
+
+    public PreferenceEditor(OSMValidatorPlugin plugin) {
+        this.plugin = plugin;
+    }
+
+    public void addGui(PreferenceDialog gui)
+    {
+        JPanel testPanel = new JPanel(new GridBagLayout());
+        testPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+        prefUseIgnore = new JCheckBox(tr("Use ignore list."), Main.pref.getBoolean(PREF_USE_IGNORE, true));
+        prefUseIgnore.setToolTipText(tr("Use the ignore list to suppress warnings."));
+        testPanel.add(prefUseIgnore, GBC.eol());
+
+        prefUseLayer = new JCheckBox(tr("Use error layer."), Main.pref.getBoolean(PREF_LAYER, true));
+        prefUseLayer.setToolTipText(tr("Use the error layer to display problematic elements."));
+        testPanel.add(prefUseLayer, GBC.eol());
+
+        prefOtherUpload = new JCheckBox(tr("Show informational level on upload."), Main.pref.getBoolean(PREF_OTHER_UPLOAD, false));
+        prefOtherUpload.setToolTipText(tr("Show the informational tests in the upload check windows."));
+        testPanel.add(prefOtherUpload, GBC.eol());
+
+        GBC a = GBC.eol().insets(-5,0,0,0);
+        a.anchor = GBC.EAST;
+        testPanel.add( new JLabel(tr("On demand")), GBC.std() );
+        testPanel.add( new JLabel(tr("On upload")), a );
+
+        allTests = OSMValidatorPlugin.getTests();
+        for(Test test: allTests)
+        {
+            test.addGui(testPanel);
+        }
+
+        JScrollPane testPane = new JScrollPane(testPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        testPane.setBorder(null);
+
+        String description = tr("An OSM data validator that checks for common errors made by users and editor programs.");
+        JPanel tab = gui.createPreferenceTab("validator", tr("Data validator"), description);
+        tab.add(testPane, GBC.eol().fill(GBC.BOTH));
+        tab.add(GBC.glue(0,10), a);
+    }
+
+    public boolean ok()
+    {
+        StringBuilder tests = new StringBuilder();
+        StringBuilder testsBeforeUpload = new StringBuilder();
+        Boolean res = false;
+
+        for (Test test : allTests)
+        {
+            if(test.ok())
+                res = false;
+            String name = test.getClass().getSimpleName();
+            tests.append( ',' ).append( name ).append( '=' ).append( test.enabled );
+            testsBeforeUpload.append( ',' ).append( name ).append( '=' ).append( test.testBeforeUpload );
+        }
+
+        if (tests.length() > 0 ) tests = tests.deleteCharAt(0);
+        if (testsBeforeUpload.length() > 0 ) testsBeforeUpload = testsBeforeUpload.deleteCharAt(0);
+
+        plugin.initializeTests( allTests );
+
+        Main.pref.put( PREF_TESTS, tests.toString());
+        Main.pref.put( PREF_TESTS_BEFORE_UPLOAD, testsBeforeUpload.toString());
+        Main.pref.put( PREF_USE_IGNORE, prefUseIgnore.isSelected());
+        Main.pref.put( PREF_OTHER_UPLOAD, prefOtherUpload.isSelected());
+        Main.pref.put( PREF_LAYER, prefUseLayer.isSelected());
+        return false;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/Severity.java b/validator/src/org/openstreetmap/josm/plugins/validator/Severity.java
new file mode 100644
index 0000000..253b3c4
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/Severity.java
@@ -0,0 +1,67 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+
+import org.openstreetmap.josm.Main;
+
+/** The error severity */
+public enum Severity {
+    /** Error messages */
+    ERROR(tr("Errors"), "error.gif",       Main.pref.getColor(marktr("validation error"), Color.RED)),
+    /** Warning messages */
+    WARNING(tr("Warnings"), "warning.gif", Main.pref.getColor(marktr("validation warning"), Color.YELLOW)),
+    /** Other messages */
+    OTHER(tr("Other"), "other.gif",        Main.pref.getColor(marktr("validation other"), Color.CYAN));
+
+    /** Description of the severity code */
+    private final String message;
+
+    /** Associated icon */
+    private final String icon;
+
+    /** Associated color */
+    private final Color color;
+
+    /**
+     * Constructor
+     *
+     * @param message Description
+     * @param icon Associated icon
+     * @param color The color of this severity
+     */
+    Severity(String message, String icon, Color color)
+    {
+        this.message = message;
+        this.icon = icon;
+        this.color = color;
+    }
+
+    @Override
+    public String toString()
+    {
+        return message;
+    }
+
+    /**
+     * Gets the associated icon
+     * @return the associated icon
+     */
+    public String getIcon()
+    {
+        return icon;
+    }
+
+    /**
+     * Gets the associated color
+     * @return The associated color
+     */
+    public Color getColor()
+    {
+        return color;
+    }
+
+
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/Test.java b/validator/src/org/openstreetmap/josm/plugins/validator/Test.java
new file mode 100644
index 0000000..4ef3790
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/Test.java
@@ -0,0 +1,219 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.GBC;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Parent class for all validation tests.
+ * <p>
+ * A test is a primitive visitor, so that it can access to all data to be
+ * validated. These primitives are always visited in the same order: nodes
+ * first, then ways.
+ *
+ * @author frsantos
+ */
+public class Test extends AbstractVisitor
+{
+    /** Name of the test */
+    protected String name;
+
+    /** Description of the test */
+    protected String description;
+
+    /** Whether this test is enabled. Enabled by default */
+    protected boolean enabled = true;
+
+    /** The preferences check for validation */
+    protected JCheckBox checkEnabled;
+
+    /** The preferences check for validation on upload */
+    protected JCheckBox checkBeforeUpload;
+
+    /** Whether this test must check before upload. Enabled by default */
+    protected boolean testBeforeUpload = true;
+
+    /** Whether this test is performing just before an upload */
+    protected boolean isBeforeUpload;
+
+    /** The list of errors */
+    protected List<TestError> errors = new ArrayList<TestError>(30);
+
+    /** Whether the test is run on a partial selection data */
+    protected boolean partialSelection;
+    
+    /** the progress monitor to use */
+    protected ProgressMonitor progressMonitor;
+
+    /**
+     * Constructor
+     * @param name Name of the test
+     * @param description Description of the test
+     */
+    public Test(String name, String description)
+    {
+        this.name = name;
+        this.description = description;
+    }
+
+    /**
+     * Constructor
+     * @param name Name of the test
+     */
+    public Test(String name)
+    {
+        this.name = name;
+    }
+
+    /**
+     * Initializes any global data used this tester.
+     * @param plugin The plugin
+     * @throws Exception When cannot initialize the test
+     */
+    public static void initialize(OSMValidatorPlugin plugin) throws Exception {}
+
+    /**
+     * Start the test using a given progress monitor 
+     * 
+     * @param progressMonitor  the progress monitor 
+     */
+    public void startTest(ProgressMonitor progressMonitor) {
+    	if (progressMonitor == null) {
+    		this.progressMonitor = NullProgressMonitor.INSTANCE;
+    	} else {
+    		this.progressMonitor = progressMonitor;
+    	}
+    	this.progressMonitor.beginTask(tr("Running test {0}", name));
+    	errors = new ArrayList<TestError>(30);
+    }
+
+    /**
+     * Flag notifying that this test is run over a partial data selection
+     * @param partialSelection Whether the test is on a partial selection data
+     */
+    public void setPartialSelection(boolean partialSelection)
+    {
+        this.partialSelection = partialSelection;
+    }
+
+    /**
+     * Gets the validation errors accumulated until this moment.
+     * @return The list of errors
+     */
+    public List<TestError> getErrors()
+    {
+        return errors;
+    }
+
+    /**
+     * Notification of the end of the test. The tester may perform additional
+     * actions and destroy the used structures
+     */
+    public void endTest() {
+    	progressMonitor.finishTask();
+    	progressMonitor = null;
+    }
+
+    /**
+     * Visits all primitives to be tested. These primitives are always visited
+     * in the same order: nodes first, then ways.
+     *
+     * @param selection The primitives to be tested
+     */
+    public void visit(Collection<OsmPrimitive> selection)
+    {
+    	progressMonitor.setTicksCount(selection.size());
+        for (OsmPrimitive p : selection) {
+            if( p.isUsable() )
+                p.visit(this);
+            progressMonitor.worked(1);
+        }
+    }
+
+    public void visit(Node n) {}
+
+    public void visit(Way w) {}
+
+    public void visit(Relation r) {}
+
+    /**
+     * Allow the tester to manage its own preferences
+     * @param testPanel The panel to add any preferences component
+     */
+    public void addGui(JPanel testPanel)
+    {
+        checkEnabled = new JCheckBox(name, enabled);
+        checkEnabled.setToolTipText(description);
+        testPanel.add(checkEnabled, GBC.std());
+
+        GBC a = GBC.eol();
+        a.anchor = GBC.EAST;
+        checkBeforeUpload = new JCheckBox();
+        checkBeforeUpload.setSelected(testBeforeUpload);
+        testPanel.add(checkBeforeUpload, a);
+    }
+
+    /**
+     * Called when the used submits the preferences
+     */
+    public boolean ok()
+    {
+        enabled = checkEnabled.isSelected();
+        testBeforeUpload = checkBeforeUpload.isSelected();
+        return false;
+    }
+
+    /**
+     * Fixes the error with the appropiate command
+     *
+     * @param testError
+     * @return The command to fix the error
+     */
+    public Command fixError(TestError testError)
+    {
+        return null;
+    }
+
+    /**
+     * Returns true if the given error can be fixed automatically
+     *
+     * @param testError The error to check if can be fixed
+     * @return true if the error can be fixed
+     */
+    public boolean isFixable(TestError testError)
+    {
+        return false;
+    }
+
+    /**
+     * Returns true if this plugin must check the uploaded data before uploading
+     * @return true if this plugin must check the uploaded data before uploading
+     */
+    public boolean testBeforeUpload()
+    {
+        return testBeforeUpload;
+    }
+
+    /**
+     * Sets the flag that marks an upload check
+     * @param isUpload if true, the test is before upload
+     */
+    public void setBeforeUpload(boolean isUpload)
+    {
+        this.isBeforeUpload = isUpload;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/TestError.java b/validator/src/org/openstreetmap/josm/plugins/validator/TestError.java
new file mode 100644
index 0000000..cd85619
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/TestError.java
@@ -0,0 +1,400 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.gui.MapView;
+
+/**
+ * Validation error
+ * @author frsantos
+ */
+public class TestError {
+    /** is this error on the ignore list */
+    private Boolean ignored = false;
+    /** Severity */
+    private Severity severity;
+    /** The error message */
+    private String message;
+    /** Deeper error description */
+    private String description;
+    private String description_en;
+    /** The affected primitives */
+    private List<? extends OsmPrimitive> primitives;
+    /** The primitives to be highlighted */
+    private List<?> highlighted;
+    /** The tester that raised this error */
+    private Test tester;
+    /** Internal code used by testers to classify errors */
+    private int code;
+    /** If this error is selected */
+    private boolean selected;
+
+    /**
+     * Constructors
+     * @param tester The tester
+     * @param severity The severity of this error
+     * @param message The error message
+     * @param primitive The affected primitive
+     * @param primitives The affected primitives
+     * @param code The test error reference code
+     */
+    public TestError(Test tester, Severity severity, String message, String description, String description_en,
+            int code, List<? extends OsmPrimitive> primitives, List<?> highlighted) {
+        this.tester = tester;
+        this.severity = severity;
+        this.message = message;
+        this.description = description;
+        this.description_en = description_en;
+        this.primitives = primitives;
+        this.highlighted = highlighted;
+        this.code = code;
+    }
+
+    public TestError(Test tester, Severity severity, String message, int code, List<? extends OsmPrimitive> primitives,
+            List<?> highlighted) {
+        this(tester, severity, message, null, null, code, primitives, highlighted);
+    }
+
+    public TestError(Test tester, Severity severity, String message, String description, String description_en,
+            int code, List<? extends OsmPrimitive> primitives) {
+        this(tester, severity, message, description, description_en, code, primitives, primitives);
+    }
+
+    public TestError(Test tester, Severity severity, String message, int code, List<? extends OsmPrimitive> primitives) {
+        this(tester, severity, message, null, null, code, primitives, primitives);
+    }
+
+    public TestError(Test tester, Severity severity, String message, int code, OsmPrimitive primitive) {
+        this(tester, severity, message, null, null, code, Collections.singletonList(primitive), Collections
+                .singletonList(primitive));
+    }
+
+    public TestError(Test tester, Severity severity, String message, String description, String description_en,
+            int code, OsmPrimitive primitive) {
+        this(tester, severity, message, description, description_en, code, Collections.singletonList(primitive));
+    }
+
+    /**
+     * Gets the error message
+     * @return the error message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Gets the error message
+     * @return the error description
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Sets the error message
+     * @param message The error message
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Gets the list of primitives affected by this error
+     * @return the list of primitives affected by this error
+     */
+    public List<? extends OsmPrimitive> getPrimitives() {
+        return primitives;
+    }
+
+    /**
+     * Sets the list of primitives affected by this error
+     * @param primitives the list of primitives affected by this error
+     */
+
+    public void setPrimitives(List<OsmPrimitive> primitives) {
+        this.primitives = primitives;
+    }
+
+    /**
+     * Gets the severity of this error
+     * @return the severity of this error
+     */
+    public Severity getSeverity() {
+        return severity;
+    }
+
+    /**
+     * Sets the severity of this error
+     * @param severity the severity of this error
+     */
+    public void setSeverity(Severity severity) {
+        this.severity = severity;
+    }
+
+    /**
+     * Sets the ignore state for this error
+     */
+    public String getIgnoreState() {
+        Collection<String> strings = new TreeSet<String>();
+        String ignorestring = getIgnoreSubGroup();
+        for (OsmPrimitive o : primitives) {
+            // ignore data not yet uploaded
+            if (o.getId() == 0)
+                return null;
+            String type = "u";
+            if (o instanceof Way)
+                type = "w";
+            else if (o instanceof Relation)
+                type = "r";
+            else if (o instanceof Node)
+                type = "n";
+            strings.add(type + "_" + o.getId());
+        }
+        for (String o : strings) {
+            ignorestring += ":" + o;
+        }
+        return ignorestring;
+    }
+
+    public String getIgnoreSubGroup() {
+        String ignorestring = getIgnoreGroup();
+        if (description_en != null)
+            ignorestring += "_" + description_en;
+        return ignorestring;
+    }
+
+    public String getIgnoreGroup() {
+        return Integer.toString(code);
+    }
+
+    public void setIgnored(boolean state) {
+        ignored = state;
+    }
+
+    public Boolean getIgnored() {
+        return ignored;
+    }
+
+    /**
+     * Gets the tester that raised this error
+     * @return the tester that raised this error
+     */
+    public Test getTester() {
+        return tester;
+    }
+
+    /**
+     * Gets the code
+     * @return the code
+     */
+    public int getCode() {
+        return code;
+    }
+
+    /**
+     * Returns true if the error can be fixed automatically
+     *
+     * @return true if the error can be fixed
+     */
+    public boolean isFixable() {
+        return tester != null && tester.isFixable(this);
+    }
+
+    /**
+     * Fixes the error with the appropiate command
+     *
+     * @return The command to fix the error
+     */
+    public Command getFix() {
+        if (tester == null)
+            return null;
+
+        return tester.fixError(this);
+    }
+
+    /**
+     * Paints the error on affected primitives
+     *
+     * @param g The graphics
+     * @param mv The MapView
+     */
+    public void paint(Graphics g, MapView mv) {
+        if (!ignored) {
+            PaintVisitor v = new PaintVisitor(g, mv);
+            visitHighlighted(v);
+        }
+    }
+
+    public void visitHighlighted(ValidatorVisitor v) {
+        for (Object o : highlighted) {
+            if (o instanceof OsmPrimitive)
+                v.visit((OsmPrimitive) o);
+            else if (o instanceof WaySegment)
+                v.visit((WaySegment) o);
+        }
+    }
+
+    /**
+     * Visitor that highlights the primitives affected by this error
+     * @author frsantos
+     */
+    class PaintVisitor extends AbstractVisitor implements ValidatorVisitor {
+        /** The graphics */
+        private final Graphics g;
+        /** The MapView */
+        private final MapView mv;
+
+        /**
+         * Constructor
+         * @param g The graphics
+         * @param mv The Mapview
+         */
+        public PaintVisitor(Graphics g, MapView mv) {
+            this.g = g;
+            this.mv = mv;
+        }
+
+        public void visit(OsmPrimitive p) {
+            if (p.isUsable()) {
+                p.visit(this);
+            }
+        }
+
+        /**
+         * Draws a circle around the node
+         * @param n The node
+         * @param color The circle color
+         */
+        public void drawNode(Node n, Color color) {
+            Point p = mv.getPoint(n);
+            g.setColor(color);
+            if (selected) {
+                g.fillOval(p.x - 5, p.y - 5, 10, 10);
+            } else
+                g.drawOval(p.x - 5, p.y - 5, 10, 10);
+        }
+
+        /**
+         * Draws a line around the segment
+         *
+         * @param s The segment
+         * @param color The color
+         */
+        public void drawSegment(Node n1, Node n2, Color color) {
+            Point p1 = mv.getPoint(n1);
+            Point p2 = mv.getPoint(n2);
+            g.setColor(color);
+
+            double t = Math.atan2(p2.x - p1.x, p2.y - p1.y);
+            double cosT = Math.cos(t);
+            double sinT = Math.sin(t);
+            int deg = (int) Math.toDegrees(t);
+            if (selected) {
+                int[] x = new int[] { (int) (p1.x + 5 * cosT), (int) (p2.x + 5 * cosT), (int) (p2.x - 5 * cosT),
+                        (int) (p1.x - 5 * cosT) };
+                int[] y = new int[] { (int) (p1.y - 5 * sinT), (int) (p2.y - 5 * sinT), (int) (p2.y + 5 * sinT),
+                        (int) (p1.y + 5 * sinT) };
+                g.fillPolygon(x, y, 4);
+                g.fillArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180);
+                g.fillArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180);
+            } else {
+                g.drawLine((int) (p1.x + 5 * cosT), (int) (p1.y - 5 * sinT), (int) (p2.x + 5 * cosT),
+                        (int) (p2.y - 5 * sinT));
+                g.drawLine((int) (p1.x - 5 * cosT), (int) (p1.y + 5 * sinT), (int) (p2.x - 5 * cosT),
+                        (int) (p2.y + 5 * sinT));
+                g.drawArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180);
+                g.drawArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180);
+            }
+        }
+
+        /**
+         * Draw a small rectangle.
+         * White if selected (as always) or red otherwise.
+         *
+         * @param n The node to draw.
+         */
+        public void visit(Node n) {
+            if (isNodeVisible(n))
+                drawNode(n, severity.getColor());
+        }
+
+        public void visit(Way w) {
+            Node lastN = null;
+            for (Node n : w.getNodes()) {
+                if (lastN == null) {
+                    lastN = n;
+                    continue;
+                }
+                if (isSegmentVisible(lastN, n)) {
+                    drawSegment(lastN, n, severity.getColor());
+                }
+                lastN = n;
+            }
+        }
+
+        public void visit(WaySegment ws) {
+            if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount())
+                return;
+            Node a = ws.way.getNodes().get(ws.lowerIndex), b = ws.way.getNodes().get(ws.lowerIndex + 1);
+            if (isSegmentVisible(a, b)) {
+                drawSegment(a, b, severity.getColor());
+            }
+        }
+
+        public void visit(Relation r) {
+            /* No idea how to draw a relation. */
+        }
+
+        /**
+         * Checks if the given node is in the visible area.
+         * @param n The node to check for visibility
+         * @return true if the node is visible
+         */
+        protected boolean isNodeVisible(Node n) {
+            Point p = mv.getPoint(n);
+            return !((p.x < 0) || (p.y < 0) || (p.x > mv.getWidth()) || (p.y > mv.getHeight()));
+        }
+
+        /**
+         * Checks if the given segment is in the visible area.
+         * NOTE: This will return true for a small number of non-visible
+         *       segments.
+         * @param ls The segment to check
+         * @return true if the segment is visible
+         */
+        protected boolean isSegmentVisible(Node n1, Node n2) {
+            Point p1 = mv.getPoint(n1);
+            Point p2 = mv.getPoint(n2);
+            if ((p1.x < 0) && (p2.x < 0))
+                return false;
+            if ((p1.y < 0) && (p2.y < 0))
+                return false;
+            if ((p1.x > mv.getWidth()) && (p2.x > mv.getWidth()))
+                return false;
+            if ((p1.y > mv.getHeight()) && (p2.y > mv.getHeight()))
+                return false;
+            return true;
+        }
+    }
+
+    /**
+     * Sets the selection flag of this error
+     * @param selected if this error is selected
+     */
+    public void setSelected(boolean selected) {
+        this.selected = selected;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java b/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java
new file mode 100644
index 0000000..341ce90
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ValidateAction.java
@@ -0,0 +1,187 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.plugins.validator.util.AgregatePrimitivesVisitor;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
+
+/**
+ * The action that does the validate thing.
+ * <p>
+ * This action iterates through all active tests and give them the data, so that
+ * each one can test it.
+ *
+ * @author frsantos
+ */
+public class ValidateAction extends JosmAction {
+    private OSMValidatorPlugin plugin;
+
+    /** Serializable ID */
+    private static final long serialVersionUID = -2304521273582574603L;
+
+    /** Last selection used to validate */
+    private Collection<OsmPrimitive> lastSelection;
+
+    /**
+     * Constructor
+     */
+    public ValidateAction(OSMValidatorPlugin plugin) {
+        super(tr("Validation"), "validator", tr("Performs the data validation"),
+        Shortcut.registerShortcut("tools:validate", tr("Tool: {0}", tr("Validation")), KeyEvent.VK_V, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
+        this.plugin = plugin;
+    }
+
+    public void actionPerformed(ActionEvent ev) {
+        doValidate(ev, true);
+    }
+
+    /**
+     * Does the validation.
+     * <p>
+     * If getSelectedItems is true, the selected items (or all items, if no one
+     * is selected) are validated. If it is false, last selected items are
+     * revalidated
+     *
+     * @param ev The event
+     * @param getSelectedItems If selected or last selected items must be validated
+     */
+    public void doValidate(ActionEvent ev, boolean getSelectedItems) {
+        if (plugin.validateAction == null || Main.map == null || !Main.map.isVisible())
+            return;
+
+        OSMValidatorPlugin.plugin.initializeErrorLayer();
+
+        Collection<Test> tests = OSMValidatorPlugin.getEnabledTests(false);
+        if (tests.isEmpty())
+            return;
+
+        Collection<OsmPrimitive> selection;
+        if (getSelectedItems) {
+            selection = Main.main.getCurrentDataSet().getSelected();
+            if (selection.isEmpty()) {
+                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
+                lastSelection = null;
+            } else {
+                AgregatePrimitivesVisitor v = new AgregatePrimitivesVisitor();
+                selection = v.visit(selection);
+                lastSelection = selection;
+            }
+        } else {
+            if (lastSelection == null)
+                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
+            else
+                selection = lastSelection;
+        }
+
+        ValidationTask task = new ValidationTask(tests, selection, lastSelection);
+        Main.worker.submit(task);        
+    }
+
+    @Override
+    public void updateEnabledState() {
+        setEnabled(getEditLayer() != null);
+    }
+    
+    /**
+     * Asynchronous task for running a collection of tests against a collection
+     * of primitives 
+     *
+     */
+    
+    class ValidationTask extends PleaseWaitRunnable {
+    	private Collection<Test> tests;
+    	private Collection<OsmPrimitive> validatedPrimitmives;
+    	private Collection<OsmPrimitive> formerValidatedPrimitives;
+    	private boolean canceled;
+        private List<TestError> errors;
+        
+        /**
+         * 
+         * @param tests  the tests to run 
+         * @param validatedPrimitives the collection of primitives to validate. 
+         * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
+         */
+    	public ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives, Collection<OsmPrimitive> formerValidatedPrimitives) {
+    		super(tr("Validating"), false /*don't ignore exceptions */);
+    		this.validatedPrimitmives  = validatedPrimitives;
+    		this.formerValidatedPrimitives = formerValidatedPrimitives;
+    		this.tests = tests;
+    	}
+    	
+		@Override
+		protected void cancel() {
+			this.canceled = true; 			
+		}
+
+		@Override
+		protected void finish() {
+			if (canceled) return;
+			
+			// update GUI on Swing EDT
+			//
+			Runnable r = new Runnable()  {
+				public void run() {
+			        plugin.validationDialog.tree.setErrors(errors);
+			        plugin.validationDialog.setVisible(true);
+			        DataSet.fireSelectionChanged(Main.main.getCurrentDataSet().getSelected());
+				}				
+			};
+			if (SwingUtilities.isEventDispatchThread()) {
+				r.run();				
+			} else {
+				SwingUtilities.invokeLater(r);
+			}
+		}
+
+		@Override
+		protected void realRun() throws SAXException, IOException,
+				OsmTransferException {
+			if (tests == null || tests.isEmpty()) return;		
+	        errors = new ArrayList<TestError>(200);
+	        getProgressMonitor().setTicksCount(tests.size() * validatedPrimitmives.size());
+	        int testCounter = 0;
+			for (Test test : tests) {				
+				if (canceled) return;
+				testCounter++;
+				getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(),test.name));
+	            test.setPartialSelection(formerValidatedPrimitives != null);
+	            test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitmives.size(), false));
+	            test.visit(validatedPrimitmives);
+	            test.endTest();
+	            errors.addAll(test.getErrors());
+	        }
+			tests = null;
+	        if (Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true)) {
+				getProgressMonitor().subTask(tr("Updating ignored errors ..."));
+	            for (TestError error : errors) {
+	            	if (canceled) return;
+	                List<String> s = new ArrayList<String>();
+	                s.add(error.getIgnoreState());
+	                s.add(error.getIgnoreGroup());
+	                s.add(error.getIgnoreSubGroup());
+	                for (String state : s) {
+	                    if (state != null && plugin.ignoredErrors.contains(state)) {
+	                        error.setIgnored(true);
+	                    }
+	                }
+	            }
+	        }
+		}
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ValidateUploadHook.java b/validator/src/org/openstreetmap/josm/plugins/validator/ValidateUploadHook.java
new file mode 100644
index 0000000..5736c2f
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ValidateUploadHook.java
@@ -0,0 +1,127 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.upload.UploadHook;
+import org.openstreetmap.josm.data.APIDataSet;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.validator.util.AgregatePrimitivesVisitor;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * The action that does the validate thing.
+ * <p>
+ * This action iterates through all active tests and give them the data, so that
+ * each one can test it.
+ *
+ * @author frsantos
+ */
+public class ValidateUploadHook implements UploadHook
+{
+    /** Serializable ID */
+    private static final long serialVersionUID = -2304521273582574603L;
+
+    private OSMValidatorPlugin plugin;
+
+    public ValidateUploadHook(OSMValidatorPlugin plugin)
+    {
+        this.plugin = plugin;
+    }
+
+    /**
+     * Validate the modified data before uploading
+     */
+    public boolean checkUpload(APIDataSet apiDataSet)
+    {
+        Collection<Test> tests = OSMValidatorPlugin.getEnabledTests(true);
+        if( tests.isEmpty() )
+            return true;
+
+        AgregatePrimitivesVisitor v = new AgregatePrimitivesVisitor();
+        v.visit(apiDataSet.getPrimitivesToAdd());
+        Collection<OsmPrimitive> selection = v.visit(apiDataSet.getPrimitivesToUpdate());
+
+        List<TestError> errors = new ArrayList<TestError>(30);
+        for(Test test : tests)
+        {
+            test.setBeforeUpload(true);
+            test.setPartialSelection(true);
+            test.startTest(null);
+            test.visit(selection);
+            test.endTest();
+            if(Main.pref.getBoolean(PreferenceEditor.PREF_OTHER_UPLOAD, false))
+                errors.addAll( test.getErrors() );
+            else
+            {
+                for(TestError e : test.getErrors())
+                {
+                    if(e.getSeverity() != Severity.OTHER)
+                        errors.add(e);
+                }
+            }
+        }
+        tests = null;
+        if(errors == null || errors.isEmpty())
+            return true;
+
+        if(Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true))
+        {
+            int nume = 0;
+            for(TestError error : errors)
+            {
+                List<String> s = new ArrayList<String>();
+                s.add(error.getIgnoreState());
+                s.add(error.getIgnoreGroup());
+                s.add(error.getIgnoreSubGroup());
+                for(String state : s)
+                {
+                    if(state != null && plugin.ignoredErrors.contains(state))
+                    {
+                        error.setIgnored(true);
+                    }
+                }
+                if(!error.getIgnored())
+                    ++nume;
+            }
+            if(nume == 0)
+                return true;
+        }
+        return displayErrorScreen(errors);
+    }
+
+    /**
+     * Displays a screen where the actions that would be taken are displayed and
+     * give the user the possibility to cancel the upload.
+     * @param errors The errors displayed in the screen
+     * @return <code>true</code>, if the upload should continue. <code>false</code>
+     *          if the user requested cancel.
+     */
+    private boolean displayErrorScreen(List<TestError> errors)
+    {
+        JPanel p = new JPanel(new GridBagLayout());
+        ErrorTreePanel errorPanel = new ErrorTreePanel(errors);
+        errorPanel.expandAll();
+        p.add(new JScrollPane(errorPanel), GBC.eol());
+
+        int res  = JOptionPane.showConfirmDialog(Main.parent, p,
+        tr("Data with errors. Upload anyway?"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+        if(res == JOptionPane.NO_OPTION)
+        {
+            plugin.validationDialog.tree.setErrors(errors);
+            plugin.validationDialog.setVisible(true);
+            DataSet.fireSelectionChanged(Main.main.getCurrentDataSet().getSelected());
+        }
+        return res == JOptionPane.YES_OPTION;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorDialog.java b/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorDialog.java
new file mode 100644
index 0000000..e5efd64
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorDialog.java
@@ -0,0 +1,521 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.plugins.validator.tests.DuplicateNode;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.xml.sax.SAXException;
+
+/**
+ * A small tool dialog for displaying the current errors. The selection manager
+ * respects clicks into the selection list. Ctrl-click will remove entries from
+ * the list while single click will make the clicked entry the only selection.
+ *
+ * @author frsantos
+ */
+public class ValidatorDialog extends ToggleDialog implements ActionListener, SelectionChangedListener {
+    private OSMValidatorPlugin plugin;
+
+    /** Serializable ID */
+    private static final long serialVersionUID = 2952292777351992696L;
+
+    /** The display tree */
+    protected ErrorTreePanel tree;
+
+    private SideButton fixButton;
+    /** The fix button */
+    private SideButton ignoreButton;
+    /** The ignore button */
+    private SideButton selectButton;
+    /** The select button */
+
+    private JPopupMenu popupMenu;
+    private TestError popupMenuError = null;
+
+    /** Last selected element */
+    private DefaultMutableTreeNode lastSelectedNode = null;
+
+    /**
+     * Constructor
+     */
+    public ValidatorDialog(OSMValidatorPlugin plugin) {
+        super(tr("Validation errors"), "validator", tr("Open the validation window."),
+        Shortcut.registerShortcut("subwindow:validator", tr("Toggle: {0}", tr("Validation errors")),
+        KeyEvent.VK_V, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
+
+        this.plugin = plugin;
+        popupMenu = new JPopupMenu();
+
+        JMenuItem zoomTo = new JMenuItem(tr("Zoom to problem"));
+        zoomTo.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                zoomToProblem();
+            }
+        });
+        popupMenu.add(zoomTo);
+
+        tree = new ErrorTreePanel();
+        tree.addMouseListener(new ClickWatch());
+        tree.addTreeSelectionListener(new SelectionWatch());
+
+        add(new JScrollPane(tree), BorderLayout.CENTER);
+
+        JPanel buttonPanel = new JPanel(new GridLayout(1, 3));
+
+        selectButton = new SideButton(marktr("Select"), "select", "Validator",
+                tr("Set the selected elements on the map to the selected items in the list above."), this);
+        selectButton.setEnabled(false);
+        buttonPanel.add(selectButton);
+        buttonPanel.add(new SideButton(plugin.validateAction), "refresh");
+        fixButton = new SideButton(marktr("Fix"), "fix", "Validator", tr("Fix the selected errors."), this);
+        fixButton.setEnabled(false);
+        buttonPanel.add(fixButton);
+        if (Main.pref.getBoolean(PreferenceEditor.PREF_USE_IGNORE, true)) {
+            ignoreButton = new SideButton(marktr("Ignore"), "delete", "Validator",
+                    tr("Ignore the selected errors next time."), this);
+            ignoreButton.setEnabled(false);
+            buttonPanel.add(ignoreButton);
+        } else {
+            ignoreButton = null;
+        }
+        add(buttonPanel, BorderLayout.SOUTH);
+        DataSet.selListeners.add(this);
+    }
+
+    @Override
+    public void setVisible(boolean v) {
+        if (tree != null)
+            tree.setVisible(v);
+        super.setVisible(v);
+        Main.map.repaint();
+    }
+
+    /**
+     * Fix selected errors
+     *
+     * @param e
+     */
+    @SuppressWarnings("unchecked")
+    private void fixErrors(ActionEvent e) {
+        TreePath[] selectionPaths = tree.getSelectionPaths();
+        if (selectionPaths == null)
+            return;
+
+        Set<DefaultMutableTreeNode> processedNodes = new HashSet<DefaultMutableTreeNode>();
+               
+        DuplicateNode.clearBackreferences();
+        LinkedList<TestError> errorsToFix = new LinkedList<TestError>();
+        for (TreePath path : selectionPaths) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            if (node == null)
+                continue;
+
+            Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                if (processedNodes.contains(childNode))
+                    continue;
+
+                processedNodes.add(childNode);
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof TestError) {
+                	errorsToFix.add((TestError)nodeInfo);
+                }
+            }
+        }
+        
+        // run fix task asynchronously
+        //
+        FixTask fixTask = new FixTask(errorsToFix);
+        Main.worker.submit(fixTask);
+    }
+
+    /**
+     * Set selected errors to ignore state
+     *
+     * @param e
+     */
+    @SuppressWarnings("unchecked")
+    private void ignoreErrors(ActionEvent e) {
+        int asked = JOptionPane.DEFAULT_OPTION;
+        boolean changed = false;
+        TreePath[] selectionPaths = tree.getSelectionPaths();
+        if (selectionPaths == null)
+            return;
+
+        Set<DefaultMutableTreeNode> processedNodes = new HashSet<DefaultMutableTreeNode>();
+        for (TreePath path : selectionPaths) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            if (node == null)
+                continue;
+
+            Object mainNodeInfo = node.getUserObject();
+            if (!(mainNodeInfo instanceof TestError)) {
+                Set<String> state = new HashSet<String>();
+                // ask if the whole set should be ignored
+                if (asked == JOptionPane.DEFAULT_OPTION) {
+                    String[] a = new String[] { tr("Whole group"), tr("Single elements"), tr("Nothing") };
+                    asked = JOptionPane.showOptionDialog(Main.parent, tr("Ignore whole group or individual elements?"),
+                            tr("Ignoring elements"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
+                            a, a[1]);
+                }
+                if (asked == JOptionPane.YES_NO_OPTION) {
+                    Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+                    while (children.hasMoreElements()) {
+                        DefaultMutableTreeNode childNode = children.nextElement();
+                        if (processedNodes.contains(childNode))
+                            continue;
+
+                        processedNodes.add(childNode);
+                        Object nodeInfo = childNode.getUserObject();
+                        if (nodeInfo instanceof TestError) {
+                            TestError err = (TestError) nodeInfo;
+                            err.setIgnored(true);
+                            changed = true;
+                            state.add(node.getDepth() == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup());
+                        }
+                    }
+                    for (String s : state)
+                        plugin.ignoredErrors.add(s);
+                    continue;
+                } else if (asked == JOptionPane.CANCEL_OPTION)
+                    continue;
+            }
+
+            Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                if (processedNodes.contains(childNode))
+                    continue;
+
+                processedNodes.add(childNode);
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof TestError) {
+                    TestError error = (TestError) nodeInfo;
+                    String state = error.getIgnoreState();
+                    if (state != null)
+                        plugin.ignoredErrors.add(state);
+                    changed = true;
+                    error.setIgnored(true);
+                }
+            }
+        }
+        if (changed) {
+            tree.resetErrors();
+            plugin.saveIgnoredErrors();
+            Main.map.repaint();
+        }
+    }
+
+    private void showPopupMenu(MouseEvent e) {
+        if (!e.isPopupTrigger())
+            return;
+        popupMenuError = null;
+        TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
+        if (selPath == null)
+            return;
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getPathComponent(selPath.getPathCount() - 1);
+        if (!(node.getUserObject() instanceof TestError))
+            return;
+        popupMenuError = (TestError) node.getUserObject();
+        popupMenu.show(e.getComponent(), e.getX(), e.getY());
+    }
+
+    private void zoomToProblem() {
+        if (popupMenuError == null)
+            return;
+        ValidatorBoundingXYVisitor bbox = new ValidatorBoundingXYVisitor();
+        popupMenuError.visitHighlighted(bbox);
+        if (bbox.getBounds() == null)
+            return;
+        bbox.enlargeBoundingBox();
+        Main.map.mapView.recalculateCenterScale(bbox);
+    }
+
+    /**
+     * Sets the selection of the map to the current selected items.
+     */
+    @SuppressWarnings("unchecked")
+    private void setSelectedItems() {
+        if (tree == null)
+            return;
+
+        Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(40);
+
+        TreePath[] selectedPaths = tree.getSelectionPaths();
+        if (selectedPaths == null)
+            return;
+
+        for (TreePath path : selectedPaths) {
+            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
+            Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof TestError) {
+                    TestError error = (TestError) nodeInfo;
+                    sel.addAll(error.getPrimitives());
+                }
+            }
+        }
+
+        Main.main.getCurrentDataSet().setSelected(sel);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String actionCommand = e.getActionCommand();
+        if (actionCommand.equals("Select"))
+            setSelectedItems();
+        else if (actionCommand.equals("Fix"))
+            fixErrors(e);
+        else if (actionCommand.equals("Ignore"))
+            ignoreErrors(e);
+    }
+
+    /**
+     * Checks for fixes in selected element and, if needed, adds to the sel
+     * parameter all selected elements
+     *
+     * @param sel
+     *            The collection where to add all selected elements
+     * @param addSelected
+     *            if true, add all selected elements to collection
+     * @return whether the selected elements has any fix
+     */
+    @SuppressWarnings("unchecked")
+    private boolean setSelection(Collection<OsmPrimitive> sel, boolean addSelected) {
+        boolean hasFixes = false;
+
+        DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
+        if (lastSelectedNode != null && !lastSelectedNode.equals(node)) {
+            Enumeration<DefaultMutableTreeNode> children = lastSelectedNode.breadthFirstEnumeration();
+            while (children.hasMoreElements()) {
+                DefaultMutableTreeNode childNode = children.nextElement();
+                Object nodeInfo = childNode.getUserObject();
+                if (nodeInfo instanceof TestError) {
+                    TestError error = (TestError) nodeInfo;
+                    error.setSelected(false);
+                }
+            }
+        }
+
+        lastSelectedNode = node;
+        if (node == null)
+            return hasFixes;
+
+        Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
+        while (children.hasMoreElements()) {
+            DefaultMutableTreeNode childNode = children.nextElement();
+            Object nodeInfo = childNode.getUserObject();
+            if (nodeInfo instanceof TestError) {
+                TestError error = (TestError) nodeInfo;
+                error.setSelected(true);
+
+                hasFixes = hasFixes || error.isFixable();
+                if (addSelected) {
+                    sel.addAll(error.getPrimitives());
+                }
+            }
+        }
+        selectButton.setEnabled(true);
+        if (ignoreButton != null)
+            ignoreButton.setEnabled(true);
+
+        return hasFixes;
+    }
+
+    /**
+     * Watches for clicks.
+     */
+    public class ClickWatch extends MouseAdapter {
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            fixButton.setEnabled(false);
+            if (ignoreButton != null)
+                ignoreButton.setEnabled(false);
+            selectButton.setEnabled(false);
+
+            boolean isDblClick = e.getClickCount() > 1;
+
+            Collection<OsmPrimitive> sel = isDblClick ? new HashSet<OsmPrimitive>(40) : null;
+
+            boolean hasFixes = setSelection(sel, isDblClick);
+            fixButton.setEnabled(hasFixes);
+
+            if (isDblClick) {
+                Main.main.getCurrentDataSet().setSelected(sel);
+            }
+        }
+
+        @Override
+        public void mousePressed(MouseEvent e) {
+            showPopupMenu(e);
+        }
+
+        @Override
+        public void mouseReleased(MouseEvent e) {
+            showPopupMenu(e);
+        }
+
+    }
+
+    /**
+     * Watches for tree selection.
+     */
+    public class SelectionWatch implements TreeSelectionListener {
+        public void valueChanged(TreeSelectionEvent e) {
+            fixButton.setEnabled(false);
+            if (ignoreButton != null)
+                ignoreButton.setEnabled(false);
+            selectButton.setEnabled(false);
+
+            if (e.getSource() instanceof JScrollPane) {
+                System.out.println(e.getSource());
+                return;
+            }
+
+            boolean hasFixes = setSelection(null, false);
+            fixButton.setEnabled(hasFixes);
+            Main.map.repaint();
+        }
+    }
+
+    public static class ValidatorBoundingXYVisitor extends BoundingXYVisitor implements ValidatorVisitor {
+
+        public void visit(OsmPrimitive p) {
+            if (p.isUsable()) {
+                p.visit(this);
+            }
+        }
+
+        public void visit(WaySegment ws) {
+            if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount())
+                return;
+            visit(ws.way.getNodes().get(ws.lowerIndex));
+            visit(ws.way.getNodes().get(ws.lowerIndex + 1));
+        }
+    }
+
+    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+        if (!Main.pref.getBoolean(PreferenceEditor.PREF_FILTER_BY_SELECTION, false))
+            return;
+        if (newSelection == null || newSelection.size() == 0)
+            tree.setFilter(null);
+        HashSet<OsmPrimitive> filter = new HashSet<OsmPrimitive>(newSelection);
+        tree.setFilter(filter);
+    }
+    
+    /**
+     * Task for fixing a collection of {@see TestError}s. Can be run asynchronously. 
+     * 
+     *
+     */
+    class FixTask extends PleaseWaitRunnable {
+    	private Collection<TestError> testErrors;
+    	private boolean canceled;
+    	private LinkedList<Command> fixCommands;
+    
+    	
+    	public FixTask(Collection<TestError> testErrors) {
+    		super(tr("Fixing errors ..."), false /* don't ignore exceptions */);
+    		this.testErrors = testErrors == null ? new ArrayList<TestError> (): testErrors;
+    		fixCommands = new LinkedList<Command>();
+    	}
+
+		@Override
+		protected void cancel() {
+			this.canceled = true; 			
+		}
+
+		@Override
+		protected void finish() {
+			// do nothing
+		}
+ 		
+		@Override
+		protected void realRun() throws SAXException, IOException,
+				OsmTransferException {
+			ProgressMonitor monitor = getProgressMonitor();
+			try {				
+				monitor.setTicksCount(testErrors.size());
+				int i=0;
+				for (TestError error: testErrors) {
+					i++;
+					monitor.subTask(tr("Fixing ({0}/{1}): ''{2}''", i, testErrors.size(),error.getMessage()));
+					if (this.canceled) 
+						return;
+					final Command fixCommand = error.getFix();
+                    if (fixCommand != null) {
+                    	fixCommands.add(fixCommand);
+        				SwingUtilities.invokeAndWait(
+        						new Runnable() {
+        							public void run() {
+        								Main.main.undoRedo.addNoRedraw(fixCommand);
+        							}
+        						}
+        				);
+                        error.setIgnored(true);
+                    }
+                    monitor.worked(1);
+				}		
+				monitor.subTask(tr("Updating map ..."));
+				SwingUtilities.invokeAndWait(new Runnable() {
+					public void run() {
+						Main.main.undoRedo.afterAdd();
+						Main.map.repaint();
+						tree.resetErrors();
+						DataSet.fireSelectionChanged(Main.main.getCurrentDataSet().getSelected());
+					}
+				});
+			} catch(InterruptedException e) { 
+				// FIXME: signature of realRun should have a generic checked exception we
+				// could throw here
+				throw new RuntimeException(e);
+			} catch(InvocationTargetException e) {
+				throw new RuntimeException(e);
+			} finally {
+				monitor.finishTask();
+			}			
+		}
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorVisitor.java b/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorVisitor.java
new file mode 100644
index 0000000..4ca73fa
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/ValidatorVisitor.java
@@ -0,0 +1,10 @@
+package org.openstreetmap.josm.plugins.validator;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.WaySegment;
+
+public interface ValidatorVisitor {
+    void visit(OsmPrimitive p);
+
+    void visit(WaySegment ws);
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/ChangePropertyKeyCommand.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/ChangePropertyKeyCommand.java
new file mode 100644
index 0000000..4d5284f
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/ChangePropertyKeyCommand.java
@@ -0,0 +1,86 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.plugins.validator.util.NameVisitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Command that replaces the key of several objects
+ *
+ */
+public class ChangePropertyKeyCommand extends Command {
+    /**
+     * All primitives, that are affected with this command.
+     */
+    private final List<OsmPrimitive> objects;
+    /**
+     * The key that is subject to change.
+     */
+    private final String key;
+    /**
+     * The mew key.
+     */
+    private final String newKey;
+
+    /**
+     * Constructor
+     *
+     * @param objects all objects subject to change replacement
+     * @param key The key to replace
+     * @param newKey the new value of the key
+     */
+    public ChangePropertyKeyCommand(Collection<? extends OsmPrimitive> objects, String key, String newKey) {
+        this.objects = new LinkedList<OsmPrimitive>(objects);
+        this.key = key;
+        this.newKey = newKey;
+    }
+
+    @Override public boolean executeCommand() {
+        if (!super.executeCommand()) return false; // save old
+        for (OsmPrimitive osm : objects) {
+            if(osm.hasKeys())
+            {
+                osm.setModified(true);
+                String oldValue = osm.get(key);
+                osm.put(newKey, oldValue);
+                osm.remove(key);
+            }
+        }
+        return true;
+    }
+
+    @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        modified.addAll(objects);
+    }
+
+    @Override public MutableTreeNode description() {
+        String text = tr( "Replace \"{0}\" by \"{1}\" for", key, newKey);
+        if (objects.size() == 1) {
+            NameVisitor v = new NameVisitor();
+            objects.iterator().next().visit(v);
+            text += " "+tr(v.className)+" "+v.name;
+        } else
+            text += " "+objects.size()+" "+trn("object","objects",objects.size());
+        DefaultMutableTreeNode root = new DefaultMutableTreeNode(new JLabel(text, ImageProvider.get("data", "key"), JLabel.HORIZONTAL));
+        if (objects.size() == 1)
+            return root;
+        NameVisitor v = new NameVisitor();
+        for (OsmPrimitive osm : objects) {
+            osm.visit(v);
+            root.add(new DefaultMutableTreeNode(v.toLabel()));
+        }
+        return root;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/Coastlines.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/Coastlines.java
new file mode 100644
index 0000000..3f248b3
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/Coastlines.java
@@ -0,0 +1,90 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Point2D;
+import java.util.*;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+
+/**
+ * Check coastlines for errors
+ *
+ * @author frsantos
+ */
+public class Coastlines extends Test
+{
+    protected static int UNORDERED_COASTLINES = 901;
+
+    /** All ways, grouped by cells */
+    Map<Point2D,List<Way>> _cellWays;
+    /** The already detected errors */
+    Bag<Way, Way> _errorWays;
+
+    /**
+     * Constructor
+     */
+    public Coastlines()
+    {
+        super(tr("Coastlines."),
+              tr("This test checks that coastlines are correct."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        _cellWays = new HashMap<Point2D,List<Way>>(1000);
+        _errorWays = new Bag<Way, Way>();
+    }
+
+    @Override
+    public void endTest()
+    {
+    	super.endTest();
+        _cellWays = null;
+        _errorWays = null;
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if( !w.isUsable() )
+            return;
+
+        String natural = w.get("natural");
+        if( natural == null || !natural.equals("coastline") )
+            return;
+
+        List<List<Way>> cellWays = Util.getWaysInCell(w, _cellWays);
+        for( List<Way> ways : cellWays)
+        {
+            for( Way w2 : ways)
+            {
+                if( _errorWays.contains(w, w2) || _errorWays.contains(w2, w) )
+                    continue;
+
+                String natural2 = w.get("natural");
+                if( natural2 == null || !natural2.equals("coastline") )
+                    continue;
+
+                if( w.getNodes().get(0).equals(w2.getNodes().get(0)) || w.getNodes().get(w.getNodesCount() - 1).equals(w2.getNodes().get(w2.getNodesCount() - 1)))
+                {
+                    List<OsmPrimitive> primitives = new ArrayList<OsmPrimitive>();
+                    primitives.add(w);
+                    primitives.add(w2);
+                    errors.add( new TestError(this, Severity.ERROR, tr("Unordered coastline"), UNORDERED_COASTLINES, primitives) );
+                    _errorWays.add(w, w2);
+                }
+            }
+            ways.add(w);
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/CrossingWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/CrossingWays.java
new file mode 100644
index 0000000..db5b090
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/CrossingWays.java
@@ -0,0 +1,220 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.OSMValidatorPlugin;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+
+/**
+ * Tests if there are segments that crosses in the same layer
+ *
+ * @author frsantos
+ */
+public class CrossingWays extends Test
+{
+    protected static int CROSSING_WAYS = 601;
+
+    /** All way segments, grouped by cells */
+    Map<Point2D,List<ExtendedSegment>> cellSegments;
+    /** The already detected errors */
+    HashSet<WaySegment> errorSegments;
+    /** The already detected ways in error */
+    Map<List<Way>, List<WaySegment>> ways_seen;
+
+
+    /**
+     * Constructor
+     */
+    public CrossingWays()
+    {
+        super(tr("Crossing ways."),
+              tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, but are not connected by a node."));
+    }
+
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        cellSegments = new HashMap<Point2D,List<ExtendedSegment>>(1000);
+        errorSegments = new HashSet<WaySegment>();
+        ways_seen = new HashMap<List<Way>, List<WaySegment>>(50);
+    }
+
+    @Override
+    public void endTest()
+    {
+    	super.endTest();
+        cellSegments = null;
+        errorSegments = null;
+        ways_seen = null;
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if( !w.isUsable() )
+            return;
+
+        String coastline1 = w.get("natural");
+        boolean isCoastline1 = coastline1 != null && (coastline1.equals("water") || coastline1.equals("coastline"));
+        String railway1 = w.get("railway");
+        boolean isSubway1 = railway1 != null && railway1.equals("subway");
+        boolean isBuilding = false;
+        if(w.get("building") != null)
+            isBuilding = true;
+        
+        if( w.get("highway") == null && w.get("waterway") == null && (railway1 == null || isSubway1)  && !isCoastline1 && !isBuilding)
+            return;
+
+        String layer1 = w.get("layer");
+
+        int nodesSize = w.getNodesCount();
+        for (int i = 0; i < nodesSize - 1; i++) {
+            WaySegment ws = new WaySegment(w, i);
+            ExtendedSegment es1 = new ExtendedSegment(ws, layer1, railway1, coastline1);
+            List<List<ExtendedSegment>> cellSegments = getSegments(es1.n1, es1.n2);
+            for( List<ExtendedSegment> segments : cellSegments)
+            {
+                for( ExtendedSegment es2 : segments)
+                {
+                    List<Way> prims;
+                    List<WaySegment> highlight;
+
+                    if (errorSegments.contains(ws) && errorSegments.contains(es2.ws))
+                        continue;
+
+                    String layer2 = es2.layer;
+                    String railway2 = es2.railway;
+                    String coastline2 = es2.coastline;
+                    if (layer1 == null ? layer2 != null : !layer1.equals(layer2))
+                        continue;
+
+                    if( !es1.intersects(es2) ) continue;
+                    if( isSubway1 && "subway".equals(railway2)) continue;
+
+                    boolean isCoastline2 = coastline2 != null && (coastline2.equals("water") || coastline2.equals("coastline"));
+                    if( isCoastline1 != isCoastline2 ) continue;
+
+                    if((es1.railway != null && es1.railway.equals("abandoned")) || (railway2 != null && railway2.equals("abandoned"))) continue;
+
+                    prims = Arrays.asList(es1.ws.way, es2.ws.way);
+                    if ((highlight = ways_seen.get(prims)) == null)
+                    {
+                        highlight = new ArrayList<WaySegment>();
+                        highlight.add(es1.ws);
+                        highlight.add(es2.ws);
+
+                        errors.add(new TestError(this, Severity.WARNING,
+                        isBuilding ? tr("Crossing buildings") : tr("Crossing ways"), CROSSING_WAYS, prims, highlight));
+                        ways_seen.put(prims, highlight);
+                    }
+                    else
+                    {
+                        highlight.add(es1.ws);
+                        highlight.add(es2.ws);
+                    }
+                }
+                segments.add(es1);
+            }
+        }
+    }
+
+    /**
+    * Returns all the cells this segment crosses.  Each cell contains the list
+    * of segments already processed
+    *
+    * @param n1 The first node
+    * @param n2 The second node
+    * @return A list with all the cells the segment crosses
+    */
+    public List<List<ExtendedSegment>> getSegments(Node n1, Node n2)
+    {
+        List<List<ExtendedSegment>> cells = new ArrayList<List<ExtendedSegment>>();
+        for( Point2D cell : Util.getSegmentCells(n1, n2, OSMValidatorPlugin.griddetail) )
+        {
+            List<ExtendedSegment> segments = cellSegments.get( cell );
+            if( segments == null )
+            {
+                segments = new ArrayList<ExtendedSegment>();
+                cellSegments.put(cell, segments);
+            }
+            cells.add(segments);
+        }
+
+        return cells;
+    }
+
+    /**
+     * A way segment with some additional information
+     * @author frsantos
+     */
+    private class ExtendedSegment
+    {
+        public Node n1, n2;
+
+        public WaySegment ws;
+
+        /** The layer */
+        public String layer;
+
+        /** The railway type */
+        public String railway;
+
+        /** The coastline type */
+        public String coastline;
+
+        /**
+         * Constructor
+         * @param ws The way segment
+         * @param layer The layer of the way this segment is in
+         * @param railway The railway type of the way this segment is in
+         * @param coastline The coastlyne typo of the way the segment is in
+         */
+        public ExtendedSegment(WaySegment ws, String layer, String railway, String coastline)
+        {
+            this.ws = ws;
+            this.n1 = ws.way.getNodes().get(ws.lowerIndex);
+            this.n2 = ws.way.getNodes().get(ws.lowerIndex + 1);
+            this.layer = layer;
+            this.railway = railway;
+            this.coastline = coastline;
+        }
+
+        /**
+         * Checks whether this segment crosses other segment
+         * @param s2 The other segment
+         * @return true if both segements crosses
+         */
+        public boolean intersects(ExtendedSegment s2)
+        {
+            if( n1.equals(s2.n1) || n2.equals(s2.n2) ||
+                n1.equals(s2.n2)   || n2.equals(s2.n1) )
+            {
+                return false;
+            }
+
+            return Line2D.linesIntersect(
+                n1.getEastNorth().east(), n1.getEastNorth().north(),
+                n2.getEastNorth().east(), n2.getEastNorth().north(),
+                s2.n1.getEastNorth().east(), s2.n1.getEastNorth().north(),
+                s2.n2.getEastNorth().east(), s2.n2.getEastNorth().north());
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateNode.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateNode.java
new file mode 100644
index 0000000..9a63fc6
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateNode.java
@@ -0,0 +1,155 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Area;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.MergeNodesAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+/**
+ * Tests if there are duplicate nodes
+ *
+ * @author frsantos
+ */
+public class DuplicateNode extends Test{
+	
+	private static BackreferencedDataSet backreferences;
+	
+	public static BackreferencedDataSet getBackreferenceDataSet() {
+		if (backreferences == null) {
+			backreferences = new BackreferencedDataSet(Main.main.getEditLayer().data);
+			backreferences.build();
+		}
+		return backreferences;
+	}
+	
+	public static void clearBackreferences() {
+		backreferences = null;
+	}
+	
+    protected static int DUPLICATE_NODE = 1;
+
+    /** Bag of all nodes */
+    Bag<LatLon, OsmPrimitive> nodes;
+
+    /**
+     * Constructor
+     */
+    public DuplicateNode()
+    {
+        super(tr("Duplicated nodes")+".",
+              tr("This test checks that there are no nodes at the very same location."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor) {
+    	super.startTest(monitor);
+        nodes = new Bag<LatLon, OsmPrimitive>(1000);
+    }
+
+	@Override
+	public void endTest() {
+		for (List<OsmPrimitive> duplicated : nodes.values()) {
+			if (duplicated.size() > 1) {
+				boolean sameTags = true;
+				Map<String, String> keys0 = duplicated.get(0).getKeys();
+				keys0.remove("created_by");
+				for (int i = 0; i < duplicated.size(); i++) {
+					Map<String, String> keysI = duplicated.get(i).getKeys();
+					keysI.remove("created_by");
+					if (!keys0.equals(keysI))
+						sameTags = false;
+				}
+				if (!sameTags) {
+					TestError testError = new TestError(this, Severity.WARNING,
+							tr("Nodes at same position"), DUPLICATE_NODE,
+							duplicated);
+					errors.add(testError);
+				} else {
+					TestError testError = new TestError(this, Severity.ERROR,
+							tr("Duplicated nodes"), DUPLICATE_NODE, duplicated);
+					errors.add(testError);
+				}
+			}
+		}
+		super.endTest();
+		nodes = null;
+	}
+
+    @Override
+    public void visit(Node n)
+    {
+        if(n.isUsable())
+            nodes.add(n.getCoor(), n);
+    }
+
+    /**
+     * Merge the nodes into one.
+     * Copied from UtilsPlugin.MergePointsAction
+     */
+    @Override
+    public Command fixError(TestError testError)
+    {
+        Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>(testError.getPrimitives());
+        LinkedList<Node> nodes = new LinkedList<Node>(OsmPrimitive.getFilteredList(sel, Node.class));
+        Node target = MergeNodesAction.selectTargetNode(nodes);
+        if(checkAndConfirmOutlyingDeletes(nodes))
+            return MergeNodesAction.mergeNodes(Main.main.getEditLayer(),getBackreferenceDataSet(), nodes, target);
+
+        return null;// undoRedo handling done in mergeNodes
+    }
+
+    @Override
+    public boolean isFixable(TestError testError)
+    {
+        return (testError.getTester() instanceof DuplicateNode);
+    }
+
+    /**
+     * Check whether user is about to delete data outside of the download area.
+     * Request confirmation if he is.
+     */
+    private static boolean checkAndConfirmOutlyingDeletes(LinkedList<Node> del) {
+        Area a = Main.main.getCurrentDataSet().getDataSourceArea();
+        if (a != null) {
+            for (OsmPrimitive osm : del) {
+                if (osm instanceof Node && osm.getId() != 0) {
+                    Node n = (Node) osm;
+                    if (!a.contains(n.getCoor())) {
+                        return ConditionalOptionPaneUtil.showConfirmationDialog(
+                            "delete_outside_nodes",
+                            Main.parent,
+                            tr("You are about to delete nodes outside of the area you have downloaded." +
+                                                "<br>" +
+                                                "This can cause problems because other objects (that you don't see) might use them." +
+                                                "<br>" +
+                                        "Do you really want to delete?") + "</html>",
+                            tr("Confirmation"),
+                            JOptionPane.YES_NO_OPTION,
+                            JOptionPane.QUESTION_MESSAGE,
+                            JOptionPane.YES_OPTION);
+                    }
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateWay.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateWay.java
new file mode 100644
index 0000000..98a0cc6
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicateWay.java
@@ -0,0 +1,153 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Vector;
+import java.util.Map;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+/**
+ * Tests if there are duplicate ways
+ */
+public class DuplicateWay extends Test
+{
+
+    private class WayPair {
+        public List<LatLon> coor;
+        public Map<String, String> keys;
+        public WayPair(List<LatLon> _coor,Map<String, String> _keys) {
+            coor=_coor;
+            keys=_keys;
+        }
+        @Override
+        public int hashCode() {
+            return coor.hashCode()+keys.hashCode();
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof WayPair)) return false;
+            WayPair wp = (WayPair) obj;
+            return wp.coor.equals(coor) && wp.keys.equals(keys);
+        }
+    }
+
+    protected static int DUPLICATE_WAY = 1401;
+
+    /** Bag of all ways */
+    Bag<WayPair, OsmPrimitive> ways;
+
+    /**
+     * Constructor
+     */
+    public DuplicateWay()
+    {
+        super(tr("Duplicated ways")+".",
+              tr("This test checks that there are no ways with same tags and same node coordinates."));
+    }
+
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        ways = new Bag<WayPair, OsmPrimitive>(1000);
+    }
+
+    @Override
+    public void endTest()
+    {
+    	super.endTest();
+        for(List<OsmPrimitive> duplicated : ways.values() )
+        {
+            if( duplicated.size() > 1)
+            {
+                TestError testError = new TestError(this, Severity.ERROR, tr("Duplicated ways"), DUPLICATE_WAY, duplicated);
+                errors.add( testError );
+            }
+        }
+        ways = null;
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if( !w.isUsable() )
+            return;
+        List<Node> wNodes=w.getNodes();
+        Vector<LatLon> wLat=new Vector<LatLon>(wNodes.size());
+        for(int i=0;i<wNodes.size();i++) {
+                 wLat.add(wNodes.get(i).getCoor());
+        }
+        Map<String, String> wkeys=w.getKeys();
+        wkeys.remove("created_by");
+        WayPair wKey=new WayPair(wLat,wkeys);
+        ways.add(wKey, w);
+    }
+
+    /**
+     * Fix the error by removing all but one instance of duplicate ways
+     */
+    @Override
+    public Command fixError(TestError testError)
+    {
+        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
+        LinkedList<Way> ways = new LinkedList<Way>();
+
+        for (OsmPrimitive osm : sel)
+            if (osm instanceof Way)
+                ways.add((Way)osm);
+
+        if( ways.size() < 2 )
+            return null;
+
+        long idToKeep = 0;
+        // Only one way will be kept - the one with lowest positive ID, if such exist
+        // or one "at random" if no such exists. Rest of the ways will be deleted
+        for (Way w: ways) {
+            if (w.getId() > 0) {
+                if (idToKeep == 0 || w.getId() < idToKeep) idToKeep = w.getId();
+            }
+        }
+
+        if (idToKeep > 0) {
+            //Remove chosen way from the list, rest of ways in the list will be deleted
+            for (Way w: ways) {
+                if (w.getId() == idToKeep) {
+                        ways.remove(w);
+                    break;
+                }
+            }
+        } else {
+            //Remove first way from the list, delete the rest
+            ways.remove(0);
+        }
+
+        //Delete all ways in the list
+        //Note: nodes are not deleted, these can be detected and deleted at next pass
+        Collection<Command> commands = new LinkedList<Command>();
+        commands.add(new DeleteCommand(ways));
+        Main.main.undoRedo.add(new SequenceCommand(tr("Delete duplicate ways"), commands));
+        return null;
+    }
+
+    @Override
+    public boolean isFixable(TestError testError)
+    {
+        return (testError.getTester() instanceof DuplicateWay);
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicatedWayNodes.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicatedWayNodes.java
new file mode 100644
index 0000000..b8578e1
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/DuplicatedWayNodes.java
@@ -0,0 +1,70 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+public class DuplicatedWayNodes extends Test {
+    protected static int DUPLICATE_WAY_NODE = 501;
+
+    public DuplicatedWayNodes() {
+        super(tr("Duplicated way nodes."),
+            tr("Checks for ways with identical consecutive nodes."));
+    }
+
+    @Override public void visit(Way w) {
+        if (!w.isUsable()) return;
+
+        Node lastN = null;
+        for (Node n : w.getNodes()) {
+            if (lastN == null) {
+                lastN = n;
+                continue;
+            }
+            if (lastN == n) {
+                errors.add(new TestError(this, Severity.ERROR, tr("Duplicated way nodes"), DUPLICATE_WAY_NODE,
+                    Arrays.asList(w), Arrays.asList(n)));
+                break;
+            }
+            lastN = n;
+        }
+    }
+
+    @Override public Command fixError(TestError testError) {
+        Way w = (Way) testError.getPrimitives().iterator().next();
+        Way wnew = new Way(w);
+        wnew.setNodes(null);
+        Node lastN = null;
+        for (Node n : w.getNodes()) {
+            if (lastN == null) {
+            	wnew.addNode(n);
+            } else if (n == lastN) {
+                // Skip this node
+            } else {
+            	wnew.addNode(n);
+            }
+            lastN = n;
+        }
+        if (wnew.getNodesCount() < 2) {
+            // Empty way, delete
+            return DeleteCommand.delete(Main.map.mapView.getEditLayer(), Collections.singleton(w));
+        } else {
+            return new ChangeCommand(w, wnew);
+        }
+    }
+
+    @Override public boolean isFixable(TestError testError) {
+        return testError.getTester() instanceof DuplicatedWayNodes;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/NodesWithSameName.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/NodesWithSameName.java
new file mode 100644
index 0000000..6e2c103
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/NodesWithSameName.java
@@ -0,0 +1,56 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Map;
+import java.util.List;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+public class NodesWithSameName extends Test {
+    protected static int SAME_NAME = 801;
+
+    private Map<String, List<Node>> namesToNodes;
+
+    public NodesWithSameName() {
+        super(tr("Nodes with same name"),
+            tr("This test finds nodes that have the same name (might be duplicates)."));
+    }
+
+    @Override public void startTest(ProgressMonitor monitor) {
+    	super.startTest(monitor);
+        namesToNodes = new HashMap<String, List<Node>>();
+    }
+
+    @Override public void visit(Node n) {
+        if (!n.isUsable()) return;
+
+        String name = n.get("name");
+        String sign = n.get("traffic_sign");
+        if (name == null || (sign != null && sign.equals("city_limit"))) return;
+
+        List<Node> nodes = namesToNodes.get(name);
+        if (nodes == null)
+            namesToNodes.put(name, nodes = new ArrayList<Node>());
+
+        nodes.add(n);
+    }
+
+    @Override public void endTest() {
+        for (List<Node> nodes : namesToNodes.values()) {
+            if (nodes.size() > 1) {
+                errors.add(new TestError(this, Severity.OTHER,
+                    tr("Nodes with same name"), SAME_NAME, nodes));
+            }
+        }
+        super.endTest();
+        namesToNodes = null;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/OverlappingWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/OverlappingWays.java
new file mode 100644
index 0000000..1c3e21f
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/OverlappingWays.java
@@ -0,0 +1,173 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * Tests if there are overlapping ways
+ *
+ * @author frsantos
+ */
+public class OverlappingWays extends Test
+{
+    /** Bag of all way segments */
+    Bag<Pair<Node,Node>, WaySegment> nodePairs;
+
+    protected static int OVERLAPPING_HIGHWAY = 101;
+    protected static int OVERLAPPING_RAILWAY = 102;
+    protected static int OVERLAPPING_WAY = 103;
+    protected static int OVERLAPPING_HIGHWAY_AREA = 111;
+    protected static int OVERLAPPING_RAILWAY_AREA = 112;
+    protected static int OVERLAPPING_WAY_AREA = 113;
+    protected static int OVERLAPPING_AREA = 120;
+
+    /** Constructor */
+    public OverlappingWays()
+    {
+        super(tr("Overlapping ways."),
+              tr("This test checks that a connection between two nodes "
+                + "is not used by more than one way."));
+
+    }
+
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        nodePairs = new Bag<Pair<Node,Node>, WaySegment>(1000);
+    }
+
+    @Override
+    public void endTest()
+    {
+        Map<List<Way>, List<WaySegment>> ways_seen = new HashMap<List<Way>, List<WaySegment>>(500);
+
+        for (List<WaySegment> duplicated : nodePairs.values())
+        {
+            int ways = duplicated.size();
+
+            if (ways > 1)
+            {
+                List<OsmPrimitive> prims = new ArrayList<OsmPrimitive>();
+                List<Way> current_ways = new ArrayList<Way>();
+                List<WaySegment> highlight;
+                int highway = 0;
+                int railway = 0;
+                int area = 0;
+
+                for (WaySegment ws : duplicated)
+                {
+                    if (ws.way.get("highway") != null)
+                        highway++;
+                    else if (ws.way.get("railway") != null)
+                        railway++;
+                    Boolean ar = OsmUtils.getOsmBoolean(ws.way.get("area"));
+                    if (ar != null && ar)
+                        area++;
+                    if (ws.way.get("landuse") != null || ws.way.get("natural") != null
+                    || ws.way.get("amenity") != null || ws.way.get("leisure") != null
+                    || ws.way.get("building") != null)
+                    {
+                        area++; ways--;
+                    }
+
+                    prims.add(ws.way);
+                    current_ways.add(ws.way);
+                }
+                /* These ways not seen before
+                 * If two or more of the overlapping ways are
+                 * highways or railways mark a seperate error
+                 */
+                if ((highlight = ways_seen.get(current_ways)) == null)
+                {
+                    String errortype;
+                    int type;
+
+                    if(area > 0)
+                    {
+                        if (ways == 0 || duplicated.size() == area)
+                        {
+                            errortype = tr("Overlapping areas");
+                            type = OVERLAPPING_AREA;
+                        }
+                        else if (highway == ways)
+                        {
+                            errortype = tr("Overlapping highways (with area)");
+                            type = OVERLAPPING_HIGHWAY_AREA;
+                        }
+                        else if (railway == ways)
+                        {
+                            errortype = tr("Overlapping railways (with area)");
+                            type = OVERLAPPING_RAILWAY_AREA;
+                        }
+                        else
+                        {
+                            errortype = tr("Overlapping ways (with area)");
+                            type = OVERLAPPING_WAY_AREA;
+                        }
+                    }
+                    else if (highway == ways)
+                    {
+                        errortype = tr("Overlapping highways");
+                        type = OVERLAPPING_HIGHWAY;
+                    }
+                    else if (railway == ways)
+                    {
+                        errortype = tr("Overlapping railways");
+                        type = OVERLAPPING_RAILWAY;
+                    }
+                    else
+                    {
+                        errortype = tr("Overlapping ways");
+                        type = OVERLAPPING_WAY;
+                    }
+
+                    errors.add(new TestError(this, type < OVERLAPPING_HIGHWAY_AREA
+                    ? Severity.WARNING : Severity.OTHER, tr(errortype), type, prims, duplicated));
+                    ways_seen.put(current_ways, duplicated);
+                }
+                else    /* way seen, mark highlight layer only */
+                {
+                    for (WaySegment ws : duplicated)
+                        highlight.add(ws);
+                }
+            }
+        }
+        super.endTest();
+        nodePairs = null;
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        Node lastN = null;
+        int i = -2;
+        for (Node n : w.getNodes()) {
+            i++;
+            if (lastN == null) {
+                lastN = n;
+                continue;
+            }
+            nodePairs.add(Pair.sort(new Pair<Node,Node>(lastN, n)),
+                new WaySegment(w, i));
+            lastN = n;
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/SelfIntersectingWay.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/SelfIntersectingWay.java
new file mode 100644
index 0000000..250add8
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/SelfIntersectingWay.java
@@ -0,0 +1,41 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashSet;
+import java.util.Arrays;
+
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+/**
+ * Checks for self-intersecting ways.
+ */
+public class SelfIntersectingWay extends Test {
+    protected static int SELF_INTERSECT = 401;
+
+    public SelfIntersectingWay() {
+        super(tr("Self-intersecting ways"),
+              tr("This test checks for ways " +
+                "that contain some of their nodes more than once."));
+    }
+
+    @Override public void visit(Way w) {
+        HashSet<Node> nodes = new HashSet<Node>();
+
+        for (int i = 1; i < w.getNodesCount() - 1; i++) {
+            Node n = w.getNode(i);
+            if (nodes.contains(n)) {
+                errors.add(new TestError(this,
+                    Severity.WARNING, tr("Self-intersecting ways"), SELF_INTERSECT,
+                    Arrays.asList(w), Arrays.asList(n)));
+                break;
+            } else {
+                nodes.add(n);
+            }
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/SimilarNamedWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/SimilarNamedWays.java
new file mode 100644
index 0000000..7d7b3a8
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/SimilarNamedWays.java
@@ -0,0 +1,173 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Point2D;
+import java.util.*;
+
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+
+/**
+ * Checks for similar named ways, symptom of a possible typo. It uses the
+ * Levenshtein distance to check for similarity
+ *
+ * @author frsantos
+ */
+public class SimilarNamedWays extends Test
+{
+    protected static int SIMILAR_NAMED = 701;
+
+    /** All ways, grouped by cells */
+    Map<Point2D,List<Way>> cellWays;
+    /** The already detected errors */
+    Bag<Way, Way> errorWays;
+
+    /**
+     * Constructor
+     */
+    public SimilarNamedWays()
+    {
+        super(tr("Similarly named ways")+".",
+              tr("This test checks for ways with similar names that may have been misspelled."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        cellWays = new HashMap<Point2D,List<Way>>(1000);
+        errorWays = new Bag<Way, Way>();
+    }
+
+    @Override
+    public void endTest()
+    {
+        cellWays = null;
+        errorWays = null;
+        super.endTest();
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if( !w.isUsable() )
+            return;
+
+        String name = w.get("name");
+        if( name == null || name.length() < 6 )
+            return;
+
+        List<List<Way>> theCellWays = Util.getWaysInCell(w, cellWays);
+        for( List<Way> ways : theCellWays)
+        {
+            for( Way w2 : ways)
+            {
+                if( errorWays.contains(w, w2) || errorWays.contains(w2, w) )
+                    continue;
+
+                String name2 = w2.get("name");
+                if( name2 == null || name2.length() < 6 )
+                    continue;
+
+                int levenshteinDistance = getLevenshteinDistance(name, name2);
+                if( 0 < levenshteinDistance && levenshteinDistance <= 2 )
+                {
+                    List<OsmPrimitive> primitives = new ArrayList<OsmPrimitive>();
+                    primitives.add(w);
+                    primitives.add(w2);
+                    errors.add( new TestError(this, Severity.WARNING, tr("Similarly named ways"), SIMILAR_NAMED, primitives) );
+                    errorWays.add(w, w2);
+                }
+            }
+            ways.add(w);
+        }
+    }
+
+    /**
+     * Compute Levenshtein distance
+     *
+     * @param s First word
+     * @param t Second word
+     * @return The distance between words
+     */
+    public int getLevenshteinDistance(String s, String t)
+    {
+        int d[][]; // matrix
+        int n; // length of s
+        int m; // length of t
+        int i; // iterates through s
+        int j; // iterates through t
+        char s_i; // ith character of s
+        char t_j; // jth character of t
+        int cost; // cost
+
+        // Step 1
+
+        n = s.length();
+        m = t.length();
+        if (n == 0) return m;
+        if (m == 0) return n;
+        d = new int[n + 1][m + 1];
+
+        // Step 2
+        for (i = 0; i <= n; i++) d[i][0] = i;
+        for (j = 0; j <= m; j++) d[0][j] = j;
+
+        // Step 3
+        for (i = 1; i <= n; i++)
+        {
+            s_i = s.charAt(i - 1);
+
+            // Step 4
+            for (j = 1; j <= m; j++)
+            {
+                t_j = t.charAt(j - 1);
+
+                // Step 5
+                if (s_i == t_j)
+                {
+                    cost = 0;
+                }
+                else
+                {
+                    cost = 1;
+                }
+
+                // Step 6
+                d[i][j] = Minimum(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
+            }
+        }
+
+        // Step 7
+        return d[n][m];
+    }
+
+    /**
+     * Get minimum of three values
+     * @param a First value
+     * @param b Second value
+     * @param c Third value
+     * @return The minimum of the tre values
+     */
+    private static int Minimum(int a, int b, int c)
+    {
+        int mi = a;
+        if (b < mi)
+        {
+            mi = b;
+        }
+        if (c < mi)
+        {
+            mi = c;
+        }
+        return mi;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/TagChecker.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/TagChecker.java
new file mode 100644
index 0000000..c5ecc16
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/TagChecker.java
@@ -0,0 +1,1025 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.tagging.TaggingPreset;
+import org.openstreetmap.josm.io.MirroredInputStream;
+import org.openstreetmap.josm.plugins.validator.OSMValidatorPlugin;
+import org.openstreetmap.josm.plugins.validator.PreferenceEditor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+import org.openstreetmap.josm.plugins.validator.util.Entities;
+import org.openstreetmap.josm.plugins.validator.util.Util;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Check for mispelled or wrong properties
+ *
+ * @author frsantos
+ */
+public class TagChecker extends Test
+{
+    /** The default data files */
+    public static final String DATA_FILE = "http://svn.openstreetmap.org/applications/editors/josm/plugins/validator/tagchecker.cfg";
+    public static final String IGNORE_FILE = "http://svn.openstreetmap.org/applications/editors/josm/plugins/validator/ignoretags.cfg";
+    public static final String SPELL_FILE = "http://svn.openstreetmap.org/applications/utils/planet.osm/java/speller/words.cfg";
+
+    /** The spell check key substitutions: the key should be substituted by the value */
+    protected static Map<String, String> spellCheckKeyData;
+    /** The spell check preset values */
+    protected static Bag<String, String> presetsValueData;
+    /** The TagChecker data */
+    protected static List<CheckerData> checkerData = new ArrayList<CheckerData>();
+    protected static ArrayList<String> ignoreDataStartsWith = new ArrayList<String>();
+    protected static ArrayList<String> ignoreDataEquals = new ArrayList<String>();
+    protected static ArrayList<String> ignoreDataEndsWith = new ArrayList<String>();
+    protected static ArrayList<IgnoreKeyPair> ignoreDataKeyPair = new ArrayList<IgnoreKeyPair>();
+    protected static ArrayList<IgnoreTwoKeyPair> ignoreDataTwoKeyPair = new ArrayList<IgnoreTwoKeyPair>();
+
+    /** The preferences prefix */
+    protected static final String PREFIX = PreferenceEditor.PREFIX + "." + TagChecker.class.getSimpleName();
+
+    public static final String PREF_CHECK_VALUES = PREFIX + ".checkValues";
+    public static final String PREF_CHECK_KEYS = PREFIX + ".checkKeys";
+    public static final String PREF_CHECK_COMPLEX = PREFIX + ".checkComplex";
+    public static final String PREF_CHECK_FIXMES = PREFIX + ".checkFixmes";
+    public static final String PREF_CHECK_PAINT = PREFIX + ".paint";
+
+    public static final String PREF_SOURCES = PREFIX + ".sources";
+    public static final String PREF_USE_DATA_FILE = PREFIX + ".usedatafile";
+    public static final String PREF_USE_IGNORE_FILE = PREFIX + ".useignorefile";
+    public static final String PREF_USE_SPELL_FILE = PREFIX + ".usespellfile";
+
+    public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + "BeforeUpload";
+    public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + "BeforeUpload";
+    public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + "BeforeUpload";
+    public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + "BeforeUpload";
+    public static final String PREF_CHECK_PAINT_BEFORE_UPLOAD = PREF_CHECK_PAINT + "BeforeUpload";
+
+    protected boolean checkKeys = false;
+    protected boolean checkValues = false;
+    protected boolean checkComplex = false;
+    protected boolean checkFixmes = false;
+    protected boolean checkPaint = false;
+
+    protected JCheckBox prefCheckKeys;
+    protected JCheckBox prefCheckValues;
+    protected JCheckBox prefCheckComplex;
+    protected JCheckBox prefCheckFixmes;
+    protected JCheckBox prefCheckPaint;
+
+    protected JCheckBox prefCheckKeysBeforeUpload;
+    protected JCheckBox prefCheckValuesBeforeUpload;
+    protected JCheckBox prefCheckComplexBeforeUpload;
+    protected JCheckBox prefCheckFixmesBeforeUpload;
+    protected JCheckBox prefCheckPaintBeforeUpload;
+
+    protected JCheckBox prefUseDataFile;
+    protected JCheckBox prefUseIgnoreFile;
+    protected JCheckBox prefUseSpellFile;
+
+    protected JButton addSrcButton;
+    protected JButton editSrcButton;
+    protected JButton deleteSrcButton;
+
+    protected static int EMPTY_VALUES      = 1200;
+    protected static int INVALID_KEY       = 1201;
+    protected static int INVALID_VALUE     = 1202;
+    protected static int FIXME             = 1203;
+    protected static int INVALID_SPACE     = 1204;
+    protected static int INVALID_KEY_SPACE = 1205;
+    protected static int INVALID_HTML      = 1206;
+    protected static int PAINT             = 1207;
+    /** 1250 and up is used by tagcheck */
+
+    /** List of sources for spellcheck data */
+    protected JList Sources;
+
+
+    protected static Entities entities = new Entities();
+    /**
+     * Constructor
+     */
+    public TagChecker()
+    {
+        super(tr("Properties checker :"),
+              tr("This plugin checks for errors in property keys and values."));
+    }
+
+    public static void initialize(OSMValidatorPlugin plugin) throws Exception
+    {
+        initializeData();
+        initializePresets();
+    }
+
+    /**
+     * Reads the spellcheck file into a HashMap.
+     * The data file is a list of words, beginning with +/-. If it starts with +,
+     * the word is valid, but if it starts with -, the word should be replaced
+     * by the nearest + word before this.
+     *
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    private static void initializeData() throws IOException
+    {
+        spellCheckKeyData = new HashMap<String, String>();
+        String sources = Main.pref.get( PREF_SOURCES, "");
+        if(Main.pref.getBoolean(PREF_USE_DATA_FILE, true))
+        {
+            if( sources == null || sources.length() == 0)
+                sources = DATA_FILE;
+            else
+                sources = DATA_FILE + ";" + sources;
+        }
+        if(Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true))
+        {
+            if( sources == null || sources.length() == 0)
+                sources = IGNORE_FILE;
+            else
+                sources = IGNORE_FILE + ";" + sources;
+        }
+        if(Main.pref.getBoolean(PREF_USE_SPELL_FILE, true))
+        {
+            if( sources == null || sources.length() == 0)
+                sources = SPELL_FILE;
+            else
+                sources = SPELL_FILE + ";" + sources;
+        }
+
+        String errorSources = "";
+        if(sources.length() == 0)
+            return;
+        for(String source: sources.split(";"))
+        {
+            try
+            {
+                MirroredInputStream s = new MirroredInputStream(source, Util.getPluginDir(), -1);
+                InputStreamReader r;
+                try
+                {
+                    r = new InputStreamReader(s, "UTF-8");
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    r = new InputStreamReader(s);
+                }
+                BufferedReader reader = new BufferedReader(r);
+
+                String okValue = null;
+                Boolean tagcheckerfile = false;
+                Boolean ignorefile = false;
+                String line;
+                while((line = reader.readLine()) != null && (tagcheckerfile || line.length() != 0))
+                {
+                    if(line.startsWith("#"))
+                    {
+                        if(line.startsWith("# JOSM TagChecker"))
+                            tagcheckerfile = true;
+                        if(line.startsWith("# JOSM IgnoreTags"))
+                            ignorefile = true;
+                        continue;
+                    }
+                    else if(ignorefile)
+                    {
+                        line = line.trim();
+                        if(line.length() < 4)
+                            continue;
+
+                        String key = line.substring(0, 2);
+                        line = line.substring(2);
+
+                        if(key.equals("S:"))
+                        {
+                            ignoreDataStartsWith.add(line);
+                        }
+                        else if(key.equals("E:"))
+                        {
+                            ignoreDataEquals.add(line);
+                        }
+                        else if(key.equals("F:"))
+                        {
+                            ignoreDataEndsWith.add(line);
+                        }
+                        else if(key.equals("K:"))
+                        {
+                            IgnoreKeyPair tmp = new IgnoreKeyPair();
+                            int mid = line.indexOf("=");
+                            tmp.key = line.substring(0, mid);
+                            tmp.value = line.substring(mid+1);
+                            ignoreDataKeyPair.add(tmp);
+                        }
+                        else if(key.equals("T:"))
+                        {
+                            IgnoreTwoKeyPair tmp = new IgnoreTwoKeyPair();
+                            int mid = line.indexOf("=");
+                            int split = line.indexOf("|");
+                            tmp.key1 = line.substring(0, mid);
+                            tmp.value1 = line.substring(mid+1, split);
+                            line = line.substring(split+1);
+                            mid = line.indexOf("=");
+                            tmp.key2 = line.substring(0, mid);
+                            tmp.value2 = line.substring(mid+1);
+                            ignoreDataTwoKeyPair.add(tmp);
+                        }
+                        continue;
+                    }
+                    else if(tagcheckerfile)
+                    {
+                        if(line.length() > 0)
+                        {
+                            CheckerData d = new CheckerData();
+                            String err = d.getData(line);
+
+                            if(err == null)
+                                checkerData.add(d);
+                            else
+                                System.err.println(tr("Invalid tagchecker line - {0}: {1}", err, line));
+                        }
+                    }
+                    else if(line.charAt(0) == '+')
+                    {
+                        okValue = line.substring(1);
+                    }
+                    else if(line.charAt(0) == '-' && okValue != null)
+                    {
+                        spellCheckKeyData.put(line.substring(1), okValue);
+                    }
+                    else
+                    {
+                        System.err.println(tr("Invalid spellcheck line: {0}", line));
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                errorSources += source + "\n";
+            }
+        }
+
+        if( errorSources.length() > 0 )
+            throw new IOException( tr("Could not access data file(s):\n{0}", errorSources) );
+    }
+
+    /**
+     * Reads the presets data.
+     *
+     * @throws Exception
+     */
+    public static void initializePresets() throws Exception
+    {
+        if( !Main.pref.getBoolean(PREF_CHECK_VALUES, true) )
+            return;
+
+        Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
+        if(presets != null)
+        {
+            presetsValueData = new Bag<String, String>();
+            for(String a : OsmPrimitive.getUninterestingKeys())
+                presetsValueData.add(a);
+            for(String a : OsmPrimitive.getDirectionKeys())
+                presetsValueData.add(a);
+            for(String a : Main.pref.getCollection(PreferenceEditor.PREFIX + ".knownkeys",
+            Arrays.asList(new String[]{"is_in", "int_ref", "fixme", "population"})))
+                presetsValueData.add(a);
+            for(TaggingPreset p : presets)
+            {
+                for(TaggingPreset.Item i : p.data)
+                {
+                    if(i instanceof TaggingPreset.Combo)
+                    {
+                        TaggingPreset.Combo combo = (TaggingPreset.Combo) i;
+                        for(String value : combo.values.split(","))
+                            presetsValueData.add(combo.key, value);
+                    }
+                    else if(i instanceof TaggingPreset.Key)
+                    {
+                        TaggingPreset.Key k = (TaggingPreset.Key) i;
+                        presetsValueData.add(k.key, k.value);
+                    }
+                    else if(i instanceof TaggingPreset.Text)
+                    {
+                        TaggingPreset.Text k = (TaggingPreset.Text) i;
+                        presetsValueData.add(k.key);
+                    }
+                    else if(i instanceof TaggingPreset.Check)
+                    {
+                        TaggingPreset.Check k = (TaggingPreset.Check) i;
+                        presetsValueData.add(k.key, "yes");
+                        presetsValueData.add(k.key, "no");
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visit(Node n)
+    {
+        checkPrimitive(n);
+    }
+
+
+    @Override
+    public void visit(Relation n)
+    {
+        checkPrimitive(n);
+    }
+
+
+    @Override
+    public void visit(Way w)
+    {
+        checkPrimitive(w);
+    }
+
+    /**
+     * Checks the primitive properties
+     * @param p The primitive to check
+     */
+    private void checkPrimitive(OsmPrimitive p)
+    {
+        // Just a collection to know if a primitive has been already marked with error
+        Bag<OsmPrimitive, String> withErrors = new Bag<OsmPrimitive, String>();
+
+        if(checkComplex)
+        {
+            Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
+            for(Entry<String, String> prop: props.entrySet() )
+            {
+                boolean ignore = true;
+                String key1 = prop.getKey();
+                String value1 = prop.getValue();
+
+                for(IgnoreTwoKeyPair a : ignoreDataTwoKeyPair)
+                {
+                    if(key1.equals(a.key1) && value1.equals(a.value1))
+                    {
+                        ignore = false;
+                        for(Entry<String, String> prop2: props.entrySet() )
+                        {
+                            String key2 = prop2.getKey();
+                            String value2 = prop2.getValue();
+                            for(IgnoreTwoKeyPair b : ignoreDataTwoKeyPair)
+                            {
+                                if(key2.equals(b.key2) && value2.equals(b.value2))
+                                {
+                                    ignore = true;
+                                    break;
+                                }
+                            }
+                            if(ignore)
+                                break;
+                        }
+                    }
+                    if(ignore)
+                        break;
+                }
+
+                if(!ignore)
+                {
+                    errors.add( new TestError(this, Severity.ERROR, tr("Illegal tag/value combinations"),
+                    tr("Illegal tag/value combinations"), tr("Illegal tag/value combinations"), 1272, p) );
+                    withErrors.add(p, "TC");
+                }
+            }
+
+            for(CheckerData d : checkerData)
+            {
+                if(d.match(p))
+                {
+                    errors.add( new TestError(this, d.getSeverity(), tr("Illegal tag/value combinations"),
+                    d.getDescription(), d.getDescriptionOrig(), d.getCode(), p) );
+                    withErrors.add(p, "TC");
+                }
+            }
+        }
+        if(checkPaint && p.errors != null)
+        {
+            for(String s: p.errors)
+            {
+                /* passing translated text also to original string, as we already
+                translated the stuff before. Makes the ignore file language dependend. */
+                errors.add( new TestError(this, Severity.WARNING, tr("Painting problem"),
+                s, s, PAINT, p) );
+                withErrors.add(p, "P");
+            }
+        }
+
+        Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
+        for(Entry<String, String> prop: props.entrySet() )
+        {
+            String s = marktr("Key ''{0}'' invalid.");
+            String key = prop.getKey();
+            String value = prop.getValue();
+            if( checkValues && (value==null || value.trim().length() == 0) && !withErrors.contains(p, "EV"))
+            {
+                errors.add( new TestError(this, Severity.WARNING, tr("Tags with empty values"),
+                tr(s, key), MessageFormat.format(s, key), EMPTY_VALUES, p) );
+                withErrors.add(p, "EV");
+            }
+            if( checkKeys && spellCheckKeyData.containsKey(key) && !withErrors.contains(p, "IPK"))
+            {
+                errors.add( new TestError(this, Severity.WARNING, tr("Invalid property key"),
+                tr(s, key), MessageFormat.format(s, key), INVALID_KEY, p) );
+                withErrors.add(p, "IPK");
+            }
+            if( checkKeys && key.indexOf(" ") >= 0 && !withErrors.contains(p, "IPK"))
+            {
+                errors.add( new TestError(this, Severity.WARNING, tr("Invalid white space in property key"),
+                tr(s, key), MessageFormat.format(s, key), INVALID_KEY_SPACE, p) );
+                withErrors.add(p, "IPK");
+            }
+            if( checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE"))
+            {
+                errors.add( new TestError(this, Severity.OTHER, tr("Property values start or end with white space"),
+                tr(s, key), MessageFormat.format(s, key), INVALID_SPACE, p) );
+                withErrors.add(p, "SPACE");
+            }
+            if( checkValues && value != null && !value.equals(entities.unescape(value)) && !withErrors.contains(p, "HTML"))
+            {
+                errors.add( new TestError(this, Severity.OTHER, tr("Property values contain HTML entity"),
+                tr(s, key), MessageFormat.format(s, key), INVALID_HTML, p) );
+                withErrors.add(p, "HTML");
+            }
+            if( checkValues && value != null && value.length() > 0 && presetsValueData != null)
+            {
+                List<String> values = presetsValueData.get(key);
+                if(values == null)
+                {
+                    Boolean ignore = false;
+                    for(String a : ignoreDataStartsWith)
+                    {
+                        if(key.startsWith(a))
+                            ignore = true;
+                    }
+                    for(String a : ignoreDataEquals)
+                    {
+                        if(key.equals(a))
+                            ignore = true;
+                    }
+                    for(String a : ignoreDataEndsWith)
+                    {
+                        if(key.endsWith(a))
+                            ignore = true;
+                    }
+                    if(!ignore)
+                    {
+                        String i = marktr("Key ''{0}'' not in presets.");
+                        errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property key"),
+                        tr(i, key), MessageFormat.format(i, key), INVALID_VALUE, p) );
+                        withErrors.add(p, "UPK");
+                    }
+                }
+                else if(values.size() > 0 && !values.contains(prop.getValue()))
+                {
+                    boolean ignore = false;
+                    for(IgnoreKeyPair a : ignoreDataKeyPair)
+                    {
+                        if(key.equals(a.key) && value.equals(a.value))
+                            ignore = true;
+                    }
+
+                    for(IgnoreTwoKeyPair a : ignoreDataTwoKeyPair)
+                    {
+                        if(key.equals(a.key2) && value.equals(a.value2))
+                            ignore = true;
+                    }
+
+                    if(!ignore)
+                    {
+                        String i = marktr("Value ''{0}'' for key ''{1}'' not in presets.");
+                        errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property value"),
+                        tr(i, prop.getValue(), key), MessageFormat.format(i, prop.getValue(), key), INVALID_VALUE, p) );
+                        withErrors.add(p, "UPV");
+                    }
+                }
+            }
+            if( checkFixmes && value != null && value.length() > 0 )
+            {
+                if( (value.contains("FIXME") || value.contains("check and delete") || key.contains("todo") || key.contains("fixme"))
+                && !withErrors.contains(p, "FIXME"))
+                {
+                    errors.add( new TestError(this, Severity.OTHER, tr("FIXMES"), FIXME, p) );
+                    withErrors.add(p, "FIXME");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        checkKeys = Main.pref.getBoolean(PREF_CHECK_KEYS, true);
+        if( isBeforeUpload )
+            checkKeys = checkKeys && Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true);
+
+        checkValues = Main.pref.getBoolean(PREF_CHECK_VALUES, true);
+        if( isBeforeUpload )
+            checkValues = checkValues && Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true);
+
+        checkComplex = Main.pref.getBoolean(PREF_CHECK_COMPLEX, true);
+        if( isBeforeUpload )
+            checkComplex = checkValues && Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true);
+
+        checkFixmes = Main.pref.getBoolean(PREF_CHECK_FIXMES, true);
+        if( isBeforeUpload )
+            checkFixmes = checkFixmes && Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true);
+
+        checkPaint = Main.pref.getBoolean(PREF_CHECK_PAINT, true);
+        if( isBeforeUpload )
+            checkPaint = checkPaint && Main.pref.getBoolean(PREF_CHECK_PAINT_BEFORE_UPLOAD, true);
+    }
+
+    @Override
+    public void visit(Collection<OsmPrimitive> selection)
+    {
+        if( checkKeys || checkValues || checkComplex)
+            super.visit(selection);
+    }
+
+    @Override
+    public void addGui(JPanel testPanel)
+    {
+        GBC a = GBC.eol();
+        a.anchor = GBC.EAST;
+
+        testPanel.add( new JLabel(name), GBC.eol().insets(3,0,0,0) );
+
+        prefCheckKeys = new JCheckBox(tr("Check property keys."), Main.pref.getBoolean(PREF_CHECK_KEYS, true));
+        prefCheckKeys.setToolTipText(tr("Validate that property keys are valid checking against list of words."));
+        testPanel.add(prefCheckKeys, GBC.std().insets(20,0,0,0));
+
+        prefCheckKeysBeforeUpload = new JCheckBox();
+        prefCheckKeysBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckKeysBeforeUpload, a);
+
+        prefCheckComplex = new JCheckBox(tr("Use complex property checker."), Main.pref.getBoolean(PREF_CHECK_COMPLEX, true));
+        prefCheckComplex.setToolTipText(tr("Validate property values and tags using complex rules."));
+        testPanel.add(prefCheckComplex, GBC.std().insets(20,0,0,0));
+
+        prefCheckComplexBeforeUpload = new JCheckBox();
+        prefCheckComplexBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckComplexBeforeUpload, a);
+
+        Sources = new JList(new DefaultListModel());
+
+        String sources = Main.pref.get( PREF_SOURCES );
+        if(sources != null && sources.length() > 0)
+        {
+            for(String source : sources.split(";"))
+                ((DefaultListModel)Sources.getModel()).addElement(source);
+        }
+
+        addSrcButton = new JButton(tr("Add"));
+        addSrcButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                String source = JOptionPane.showInputDialog(
+                		Main.parent, 
+                		tr("TagChecker source"),
+                		tr("TagChecker source"),
+                		JOptionPane.QUESTION_MESSAGE
+                		);
+                if (source != null)
+                    ((DefaultListModel)Sources.getModel()).addElement(source);
+                Sources.clearSelection();
+            }
+        });
+
+        editSrcButton = new JButton(tr("Edit"));
+        editSrcButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                int row = Sources.getSelectedIndex();
+                if(row == -1 && Sources.getModel().getSize() == 1)
+                {
+                    Sources.setSelectedIndex(0);
+                    row = 0;
+                }
+                if (row == -1)
+                {
+                    if(Sources.getModel().getSize() == 0)
+                    {
+                        String source = JOptionPane.showInputDialog(Main.parent, tr("TagChecker source"), tr("TagChecker source"), JOptionPane.QUESTION_MESSAGE);
+                        if (source != null)
+                            ((DefaultListModel)Sources.getModel()).addElement(source);
+                    }
+                    else
+                    {
+                        JOptionPane.showMessageDialog(
+                        		Main.parent, 
+                        		tr("Please select the row to edit."),
+                        		tr("Information"),
+                        		JOptionPane.INFORMATION_MESSAGE
+                        		);
+                    }
+                }
+                else {
+                	String source = (String)JOptionPane.showInputDialog(Main.parent,
+                			tr("TagChecker source"),
+                			tr("TagChecker source"),
+                			JOptionPane.QUESTION_MESSAGE, null, null,
+                			Sources.getSelectedValue());
+                    if (source != null)
+                        ((DefaultListModel)Sources.getModel()).setElementAt(source, row);
+                }
+                Sources.clearSelection();
+            }
+        });
+
+        deleteSrcButton = new JButton(tr("Delete"));
+        deleteSrcButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (Sources.getSelectedIndex() == -1)
+                    JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to delete."), tr("Information"), JOptionPane.QUESTION_MESSAGE);
+                else {
+                    ((DefaultListModel)Sources.getModel()).remove(Sources.getSelectedIndex());
+                }
+            }
+        });
+        Sources.setMinimumSize(new Dimension(300,50));
+        Sources.setVisibleRowCount(3);
+
+        Sources.setToolTipText(tr("The sources (URL or filename) of spell check (see http://wiki.openstreetmap.org/index.php/User:JLS/speller) or tag checking data files."));
+        addSrcButton.setToolTipText(tr("Add a new source to the list."));
+        editSrcButton.setToolTipText(tr("Edit the selected source."));
+        deleteSrcButton.setToolTipText(tr("Delete the selected source from the list."));
+
+        testPanel.add(new JLabel(tr("Data sources")), GBC.eol().insets(23,0,0,0));
+        testPanel.add(new JScrollPane(Sources), GBC.eol().insets(23,0,0,0).fill(GBC.HORIZONTAL));
+        final JPanel buttonPanel = new JPanel(new GridBagLayout());
+        testPanel.add(buttonPanel, GBC.eol().fill(GBC.HORIZONTAL));
+        buttonPanel.add(addSrcButton, GBC.std().insets(0,5,0,0));
+        buttonPanel.add(editSrcButton, GBC.std().insets(5,5,5,0));
+        buttonPanel.add(deleteSrcButton, GBC.std().insets(0,5,0,0));
+
+        ActionListener disableCheckActionListener = new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                handlePrefEnable();
+            }
+        };
+        prefCheckKeys.addActionListener(disableCheckActionListener);
+        prefCheckKeysBeforeUpload.addActionListener(disableCheckActionListener);
+        prefCheckComplex.addActionListener(disableCheckActionListener);
+        prefCheckComplexBeforeUpload.addActionListener(disableCheckActionListener);
+
+        handlePrefEnable();
+
+        prefCheckValues = new JCheckBox(tr("Check property values."), Main.pref.getBoolean(PREF_CHECK_VALUES, true));
+        prefCheckValues.setToolTipText(tr("Validate that property values are valid checking against presets."));
+        testPanel.add(prefCheckValues, GBC.std().insets(20,0,0,0));
+
+        prefCheckValuesBeforeUpload = new JCheckBox();
+        prefCheckValuesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckValuesBeforeUpload, a);
+
+        prefCheckFixmes = new JCheckBox(tr("Check for FIXMES."), Main.pref.getBoolean(PREF_CHECK_FIXMES, true));
+        prefCheckFixmes.setToolTipText(tr("Looks for nodes or ways with FIXME in any property value."));
+        testPanel.add(prefCheckFixmes, GBC.std().insets(20,0,0,0));
+
+        prefCheckFixmesBeforeUpload = new JCheckBox();
+        prefCheckFixmesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckFixmesBeforeUpload, a);
+
+        prefCheckPaint = new JCheckBox(tr("Check for paint notes."), Main.pref.getBoolean(PREF_CHECK_PAINT, true));
+        prefCheckPaint.setToolTipText(tr("Check if map painting found data errors."));
+        testPanel.add(prefCheckPaint, GBC.std().insets(20,0,0,0));
+
+        prefCheckPaintBeforeUpload = new JCheckBox();
+        prefCheckPaintBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_PAINT_BEFORE_UPLOAD, true));
+        testPanel.add(prefCheckPaintBeforeUpload, a);
+
+        prefUseDataFile = new JCheckBox(tr("Use default data file."), Main.pref.getBoolean(PREF_USE_DATA_FILE, true));
+        prefUseDataFile.setToolTipText(tr("Use the default data file (recommended)."));
+        testPanel.add(prefUseDataFile, GBC.eol().insets(20,0,0,0));
+
+        prefUseIgnoreFile = new JCheckBox(tr("Use default tag ignore file."), Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true));
+        prefUseIgnoreFile.setToolTipText(tr("Use the default tag ignore file (recommended)."));
+        testPanel.add(prefUseIgnoreFile, GBC.eol().insets(20,0,0,0));
+
+        prefUseSpellFile = new JCheckBox(tr("Use default spellcheck file."), Main.pref.getBoolean(PREF_USE_SPELL_FILE, true));
+        prefUseSpellFile.setToolTipText(tr("Use the default spellcheck file (recommended)."));
+        testPanel.add(prefUseSpellFile, GBC.eol().insets(20,0,0,0));
+    }
+
+    public void handlePrefEnable()
+    {
+        boolean selected = prefCheckKeys.isSelected() || prefCheckKeysBeforeUpload.isSelected()
+        || prefCheckComplex.isSelected() || prefCheckComplexBeforeUpload.isSelected();
+        Sources.setEnabled( selected );
+        addSrcButton.setEnabled(selected);
+        editSrcButton.setEnabled(selected);
+        deleteSrcButton.setEnabled(selected);
+    }
+
+    @Override
+    public boolean ok()
+    {
+        enabled = prefCheckKeys.isSelected() || prefCheckValues.isSelected() || prefCheckComplex.isSelected() || prefCheckFixmes.isSelected();
+        testBeforeUpload = prefCheckKeysBeforeUpload.isSelected() || prefCheckValuesBeforeUpload.isSelected()
+        || prefCheckFixmesBeforeUpload.isSelected() || prefCheckComplexBeforeUpload.isSelected();
+
+        Main.pref.put(PREF_CHECK_VALUES, prefCheckValues.isSelected());
+        Main.pref.put(PREF_CHECK_COMPLEX, prefCheckComplex.isSelected());
+        Main.pref.put(PREF_CHECK_KEYS, prefCheckKeys.isSelected());
+        Main.pref.put(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected());
+        Main.pref.put(PREF_CHECK_PAINT, prefCheckPaint.isSelected());
+        Main.pref.put(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected());
+        Main.pref.put(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected());
+        Main.pref.put(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected());
+        Main.pref.put(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected());
+        Main.pref.put(PREF_CHECK_PAINT_BEFORE_UPLOAD, prefCheckPaintBeforeUpload.isSelected());
+        Main.pref.put(PREF_USE_DATA_FILE, prefUseDataFile.isSelected());
+        Main.pref.put(PREF_USE_IGNORE_FILE, prefUseIgnoreFile.isSelected());
+        Main.pref.put(PREF_USE_SPELL_FILE, prefUseSpellFile.isSelected());
+        String sources = "";
+        if( Sources.getModel().getSize() > 0 )
+        {
+            String sb = "";
+            for (int i = 0; i < Sources.getModel().getSize(); ++i)
+                sb += ";"+Sources.getModel().getElementAt(i);
+            sources = sb.substring(1);
+        }
+        if(sources.length() == 0)
+            sources = null;
+        return Main.pref.put(PREF_SOURCES, sources);
+    }
+
+    @Override
+    public Command fixError(TestError testError)
+    {
+        List<Command> commands = new ArrayList<Command>(50);
+
+        int i = -1;
+        List<? extends OsmPrimitive> primitives = testError.getPrimitives();
+        for(OsmPrimitive p : primitives )
+        {
+            i++;
+            Map<String, String> tags = p.getKeys();
+            if( tags == null || tags.size() == 0 )
+                continue;
+
+            for(Entry<String, String> prop: tags.entrySet() )
+            {
+                String key = prop.getKey();
+                String value = prop.getValue();
+                if( value == null || value.trim().length() == 0 )
+                    commands.add( new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, null) );
+                else if(value.startsWith(" ") || value.endsWith(" "))
+                    commands.add( new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, value.trim()) );
+                else if(key.startsWith(" ") || key.endsWith(" "))
+                    commands.add( new ChangePropertyKeyCommand(Collections.singleton(primitives.get(i)), key, key.trim()) );
+                else
+                {
+                    String evalue = entities.unescape(value);
+                    if(!evalue.equals(value))
+                        commands.add( new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, evalue) );
+                    else
+                    {
+                        String replacementKey = spellCheckKeyData.get(key);
+                        if( replacementKey != null )
+                        {
+                            commands.add( new ChangePropertyKeyCommand(Collections.singleton(primitives.get(i)),
+                            key, replacementKey) );
+                        }
+                    }
+                }
+            }
+        }
+
+        if( commands.size() == 0 )
+            return null;
+        else if( commands.size() == 1 )
+            return commands.get(0);
+        else
+            return new SequenceCommand(tr("Fix properties"), commands);
+    }
+
+    @Override
+    public boolean isFixable(TestError testError)
+    {
+        if( testError.getTester() instanceof TagChecker)
+        {
+            int code = testError.getCode();
+            return code == INVALID_KEY || code == EMPTY_VALUES || code == INVALID_SPACE || code == INVALID_KEY_SPACE || code == INVALID_HTML;
+        }
+
+        return false;
+    }
+
+    private static class IgnoreTwoKeyPair {
+        public String key1;
+        public String value1;
+        public String key2;
+        public String value2;
+    }
+
+    private static class IgnoreKeyPair {
+        public String key;
+        public String value;
+    }
+
+    private static class CheckerData {
+        private String description;
+        private List<CheckerElement> data = new ArrayList<CheckerElement>();
+        private Integer type = 0;
+        private Integer code;
+        protected Severity severity;
+        protected static int NODE = 1;
+        protected static int WAY = 2;
+        protected static int RELATION = 3;
+        protected static int ALL = 4;
+        protected static int TAG_CHECK_ERROR  = 1250;
+        protected static int TAG_CHECK_WARN   = 1260;
+        protected static int TAG_CHECK_INFO   = 1270;
+
+        private class CheckerElement {
+            public Object tag;
+            public Object value;
+            public Boolean noMatch;
+            public Boolean tagAll = false;
+            public Boolean valueAll = false;
+            public Boolean valueBool = false;
+            private Pattern getPattern(String str) throws IllegalStateException, PatternSyntaxException
+            {
+                if(str.endsWith("/i"))
+                    return Pattern.compile(str.substring(1,str.length()-2), Pattern.CASE_INSENSITIVE);
+                else if(str.endsWith("/"))
+                    return Pattern.compile(str.substring(1,str.length()-1));
+                throw new IllegalStateException();
+            }
+            public CheckerElement(String exp) throws IllegalStateException, PatternSyntaxException
+            {
+                Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp);
+                m.matches();
+
+                String n = m.group(1).trim();
+                if(n.equals("*"))
+                    tagAll = true;
+                else
+                    tag = n.startsWith("/") ? getPattern(n) : n;
+                noMatch = m.group(2).equals("!=");
+                n = m.group(3).trim();
+                if(n.equals("*"))
+                    valueAll = true;
+                else if(n.equals("BOOLEAN_TRUE"))
+                {
+                    valueBool = true;
+                    value = OsmUtils.trueval;
+                }
+                else if(n.equals("BOOLEAN_FALSE"))
+                {
+                    valueBool = true;
+                    value = OsmUtils.falseval;
+                }
+                else
+                    value = n.startsWith("/") ? getPattern(n) : n;
+            }
+            public Boolean match(OsmPrimitive osm)
+            {
+                for(Entry<String, String> prop: osm.getKeys().entrySet())
+                {
+                    String key = prop.getKey();
+                    String val = valueBool ? OsmUtils.getNamedOsmBoolean(prop.getValue()) : prop.getValue();
+                    if((tagAll || (tag instanceof Pattern ? ((Pattern)tag).matcher(key).matches() : key.equals(tag)))
+                    && (valueAll || (value instanceof Pattern ? ((Pattern)value).matcher(val).matches() : val.equals(value))))
+                        return !noMatch;
+                }
+                return noMatch;
+            }
+        };
+
+        public String getData(String str)
+        {
+            Matcher m = Pattern.compile(" *# *([^#]+) *$").matcher(str);
+            str = m.replaceFirst("").trim();
+            try
+            {
+                description = m.group(1);
+                if(description != null && description.length() == 0)
+                    description = null;
+            }
+            catch (IllegalStateException e)
+            {
+                description = null;
+            }
+            String[] n = str.split(" *: *", 3);
+            if(n[0].equals("way"))
+                type = WAY;
+            else if(n[0].equals("node"))
+                type = NODE;
+            else if(n[0].equals("relation"))
+                type = RELATION;
+            else if(n[0].equals("*"))
+                type = ALL;
+            if(type == 0 || n.length != 3)
+                return tr("Could not find element type");
+            if(n[1].equals("W"))
+            {
+                severity = Severity.WARNING;
+                code = TAG_CHECK_WARN;
+            }
+            else if(n[1].equals("E"))
+            {
+                severity = Severity.ERROR;
+                code = TAG_CHECK_ERROR;
+            }
+            else if(n[1].equals("I"))
+            {
+                severity = Severity.OTHER;
+                code = TAG_CHECK_INFO;
+            }
+            else
+                return tr("Could not find warning level");
+            for(String exp: n[2].split(" *&& *"))
+            {
+                try
+                {
+                    data.add(new CheckerElement(exp));
+                }
+                catch(IllegalStateException e)
+                {
+                    return tr("Illegal expression ''{0}''", exp);
+                }
+                catch(PatternSyntaxException e)
+                {
+                    return tr("Illegal regular expression ''{0}''", exp);
+                }
+            }
+            return null;
+        }
+        public Boolean match(OsmPrimitive osm)
+        {
+            if(osm.getKeys() == null || (type == NODE && !(osm instanceof Node))
+            || (type == RELATION && !(osm instanceof Relation)) || (type == WAY && !(osm instanceof Way)))
+                return false;
+            for(CheckerElement ce : data)
+            {
+                if(!ce.match(osm))
+                    return false;
+            }
+            return true;
+        }
+        public String getDescription()
+        {
+            return tr(description);
+        }
+        public String getDescriptionOrig()
+        {
+            return description;
+        }
+        public Severity getSeverity()
+        {
+            return severity;
+        }
+        public int getCode()
+        {
+            return code + type;
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnclosedWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnclosedWays.java
new file mode 100644
index 0000000..506e55c
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnclosedWays.java
@@ -0,0 +1,127 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+
+/**
+ * Check area type ways for errors
+ *
+ * @author stoecker
+ */
+public class UnclosedWays extends Test {
+    /** The already detected errors */
+    Bag<Way, Way> _errorWays;
+
+    /**
+     * Constructor
+     */
+    public UnclosedWays() {
+        super(tr("Unclosed Ways."), tr("This tests if ways which should be circular are closed."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor) {
+    	super.startTest(monitor);
+        _errorWays = new Bag<Way, Way>();
+    }
+
+    @Override
+    public void endTest() {
+        _errorWays = null;
+        super.endTest();
+    }
+
+    private String type;
+    private String etype;
+    private int mode;
+    private boolean force;
+
+    public void set(boolean f, int m, String text, String desc) {
+        etype = MessageFormat.format(text, desc);
+        type = tr(text, tr(desc));
+        mode = m;
+        force = f;
+    }
+
+    public void set(boolean f, int m, String text) {
+        etype = text;
+        type = tr(text);
+        mode = m;
+        force = f;
+    }
+
+    @Override
+    public void visit(Way w) {
+        String test;
+        force = false; /* force even if end-to-end distance is long */
+        type = etype = null;
+        mode = 0;
+
+        if (!w.isUsable())
+            return;
+
+        test = w.get("natural");
+        if (test != null)
+            set(!"coastline".equals(test), 1101, marktr("natural type {0}"), test);
+        test = w.get("landuse");
+        if (test != null)
+            set(true, 1102, marktr("landuse type {0}"), test);
+        test = w.get("amenities");
+        if (test != null)
+            set(true, 1103, marktr("amenities type {0}"), test);
+        test = w.get("sport");
+        if (test != null && !test.equals("water_slide"))
+            set(true, 1104, marktr("sport type {0}"), test);
+        test = w.get("tourism");
+        if (test != null)
+            set(true, 1105, marktr("tourism type {0}"), test);
+        test = w.get("shop");
+        if (test != null)
+            set(true, 1106, marktr("shop type {0}"), test);
+        test = w.get("leisure");
+        if (test != null)
+            set(true, 1107, marktr("leisure type {0}"), test);
+        test = w.get("waterway");
+        if (test != null && test.equals("riverbank"))
+            set(true, 1108, marktr("waterway type {0}"), test);
+        Boolean btest = OsmUtils.getOsmBoolean(w.get("building"));
+        if (btest != null && btest)
+            set(true, 1120, marktr("building"));
+        btest = OsmUtils.getOsmBoolean(w.get("area"));
+        if (btest != null && btest)
+            set(true, 1130, marktr("area"));
+
+        if (type != null && !w.isClosed())
+        {
+            Node f = w.getNode(0);
+            Node l = w.getNode(w.getNodesCount() - 1);
+            if(force || f.getCoor().greatCircleDistance(l.getCoor()) < 10000)
+            {
+                List<OsmPrimitive> primitives = new ArrayList<OsmPrimitive>();
+                List<OsmPrimitive> highlight = new ArrayList<OsmPrimitive>();
+                primitives.add(w);
+                // The important parts of an unclosed way are the first and
+                // the last node which should be connected, therefore we highlight them
+                highlight.add(f);
+                highlight.add(l);
+                errors.add(new TestError(this, Severity.WARNING, tr("Unclosed way"), type, etype, mode, primitives,
+                        highlight));
+                _errorWays.add(w, w);
+            }
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnconnectedWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnconnectedWays.java
new file mode 100644
index 0000000..6666394
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UnconnectedWays.java
@@ -0,0 +1,231 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.PreferenceEditor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+/**
+ * Tests if there are segments that crosses in the same layer
+ *
+ * @author frsantos
+ */
+public class UnconnectedWays extends Test
+{
+    protected static int UNCONNECTED_WAYS = 1301;
+    protected static final String PREFIX = PreferenceEditor.PREFIX + "." + UnconnectedWays.class.getSimpleName();
+
+    Set<MyWaySegment> ways;
+    Set<Node> endnodes; // nodes at end of way
+    Set<Node> endnodes_highway; // nodes at end of way
+    Set<Node> middlenodes; // nodes in middle of way
+    Set<Node> othernodes; // nodes appearing at least twice
+
+    double mindist;
+    double minmiddledist;
+    /**
+     * Constructor
+     */
+    public UnconnectedWays()
+    {
+        super(tr("Unconnected ways."),
+              tr("This test checks if a way has an endpoint very near to another way."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        ways = new HashSet<MyWaySegment>();
+        endnodes = new HashSet<Node>();
+        endnodes_highway = new HashSet<Node>();
+        middlenodes = new HashSet<Node>();
+        othernodes = new HashSet<Node>();
+        mindist = Main.pref.getDouble(PREFIX + ".node_way_distance", 10.0)/6378135.0;
+        minmiddledist = Main.pref.getDouble(PREFIX + ".way_way_distance", 0.0)/6378135.0;
+    }
+
+    @Override
+    public void endTest()
+    {
+        Area a = Main.main.getCurrentDataSet().getDataSourceArea();
+        Map<Node, Way> map = new HashMap<Node, Way>();
+        for(Node en : endnodes_highway)
+        {
+            Boolean isexit = OsmUtils.getOsmBoolean(en.get("noexit"));
+            if("turning_circle".equals(en.get("highway")) ||
+            (isexit != null && isexit) || en.get("barrier") != null)
+                continue;
+            for(MyWaySegment s : ways)
+            {
+                if(!s.isBoundary && !s.isAbandoned && s.highway && s.nearby(en, mindist) && (a == null || a.contains(en.getCoor())))
+                    map.put(en, s.w);
+            }
+        }
+        if(map.size() > 0)
+        {
+            for(Map.Entry<Node, Way> error : map.entrySet())
+            {
+                errors.add(new TestError(this, Severity.WARNING,
+                tr("Way end node near other highway"), UNCONNECTED_WAYS,
+                Arrays.asList(error.getKey(), error.getValue())));
+            }
+        }
+        map.clear();
+        for(Node en : endnodes_highway)
+        {
+            for(MyWaySegment s : ways)
+            {
+                if(!s.isBoundary && !s.isAbandoned && !s.highway && s.nearby(en, mindist) && !s.isArea() && (a == null || a.contains(en.getCoor())))
+                    map.put(en, s.w);
+            }
+        }
+        for(Node en : endnodes)
+        {
+            for(MyWaySegment s : ways)
+            {
+                if(!s.isBoundary && !s.isAbandoned && s.nearby(en, mindist) && !s.isArea() && (a == null || a.contains(en.getCoor())))
+                    map.put(en, s.w);
+            }
+        }
+        if(map.size() > 0)
+        {
+            for(Map.Entry<Node, Way> error : map.entrySet())
+            {
+                errors.add(new TestError(this, Severity.WARNING,
+                tr("Way end node near other way"), UNCONNECTED_WAYS,
+                Arrays.asList(error.getKey(), error.getValue())));
+            }
+        }
+        /* the following two use a shorter distance */
+        if(minmiddledist > 0.0)
+        {
+            map.clear();
+            for(Node en : middlenodes)
+            {
+                for(MyWaySegment s : ways)
+                {
+                    if(!s.isBoundary && !s.isAbandoned && s.nearby(en, minmiddledist) && (a == null || a.contains(en.getCoor())))
+                        map.put(en, s.w);
+                }
+            }
+            if(map.size() > 0)
+            {
+                for(Map.Entry<Node, Way> error : map.entrySet())
+                {
+                    errors.add(new TestError(this, Severity.OTHER,
+                    tr("Way node near other way"), UNCONNECTED_WAYS,
+                    Arrays.asList(error.getKey(), error.getValue())));
+                }
+            }
+            map.clear();
+            for(Node en : othernodes)
+            {
+                for(MyWaySegment s : ways)
+                {
+                    if(!s.isBoundary && !s.isAbandoned && s.nearby(en, minmiddledist) && (a == null || a.contains(en.getCoor())))
+                        map.put(en, s.w);
+                }
+            }
+            if(map.size() > 0)
+            {
+                for(Map.Entry<Node, Way> error : map.entrySet())
+                {
+                    errors.add(new TestError(this, Severity.OTHER,
+                    tr("Connected way end node near other way"), UNCONNECTED_WAYS,
+                    Arrays.asList(error.getKey(), error.getValue())));
+                }
+            }
+        }
+        ways = null;
+        endnodes = null;
+        super.endTest();
+    }
+
+    private class MyWaySegment
+    {
+        private Line2D line;
+        public Way w;
+        public Boolean isAbandoned = false;
+        public Boolean isBoundary = false;
+        public Boolean highway;
+
+        public MyWaySegment(Way w, Node n1, Node n2)
+        {
+            this.w = w;
+            String railway = w.get("railway");
+            this.isAbandoned = railway != null && railway.equals("abandoned");
+            this.highway = w.get("highway") != null || (railway != null && !isAbandoned);
+            this.isBoundary = w.get("boundary") != null && w.get("boundary").equals("administrative") && !this.highway;
+            line = new Line2D.Double(n1.getEastNorth().east(), n1.getEastNorth().north(),
+            n2.getEastNorth().east(), n2.getEastNorth().north());
+        }
+
+        public boolean nearby(Node n, double dist)
+        {
+            return !w.containsNode(n)
+            && line.ptSegDist(n.getEastNorth().east(), n.getEastNorth().north()) < dist;
+        }
+
+        public boolean isArea() {
+            return w.get("landuse") != null
+                || w.get("leisure") != null
+                || w.get("building") != null;
+        }
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if( !w.isUsable() )
+            return;
+        int size = w.getNodesCount();
+        if(size < 2)
+            return;
+        for(int i = 1; i < size; ++i)
+        {
+            if(i < size-1)
+                addNode(w.getNode(i), middlenodes);
+            ways.add(new MyWaySegment(w, w.getNode(i-1), w.getNode(i)));
+        }
+        Set<Node> set = endnodes;
+        if(w.get("highway") != null || w.get("railway") != null)
+            set = endnodes_highway;
+        addNode(w.getNode(0), set);
+        addNode(w.getNode(size-1), set);
+    }
+    private void addNode(Node n, Set<Node> s)
+    {
+        Boolean m = middlenodes.contains(n);
+        Boolean e = endnodes.contains(n);
+        Boolean eh = endnodes_highway.contains(n);
+        Boolean o = othernodes.contains(n);
+        if(!m && !e && !o && !eh)
+            s.add(n);
+        else if(!o)
+        {
+            othernodes.add(n);
+            if(e)
+                endnodes.remove(n);
+            else if(eh)
+                endnodes_highway.remove(n);
+            else
+                middlenodes.remove(n);
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedNode.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedNode.java
new file mode 100644
index 0000000..b9fb80e
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedNode.java
@@ -0,0 +1,109 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+/**
+ * Checks for untagged nodes that are in no way
+ *
+ * @author frsantos
+ */
+public class UntaggedNode extends Test
+{
+    protected static int UNTAGGED_NODE = 201;
+
+    /** Bag of all nodes */
+    Set<Node> emptyNodes;
+
+    /**
+     * Constructor
+     */
+    public UntaggedNode()
+    {
+        super(tr("Untagged and unconnected nodes")+".",
+              tr("This test checks for untagged nodes that are not part of any way."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        emptyNodes = new HashSet<Node>(100);
+    }
+
+    @Override
+    public void visit(Collection<OsmPrimitive> selection)
+    {
+        // If there is a partial selection, it may be false positives if a
+        // node is selected, but not the container way. So, in this
+        // case, we must visit all ways, selected or not.
+        if (partialSelection) {
+            for (OsmPrimitive p : selection) {
+                if (p.isUsable() && p instanceof Node) {
+                    p.visit(this);
+                }
+            }
+            for (Way w : Main.main.getCurrentDataSet().ways) {
+                visit(w);
+            }
+        } else {
+            for (OsmPrimitive p : selection) {
+                if (p.isUsable()) {
+                    p.visit(this);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visit(Node n)
+    {
+        if(n.isUsable() && !n.isTagged())
+            emptyNodes.add(n);
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        for (Node n : w.getNodes()) {
+            emptyNodes.remove(n);
+        }
+    }
+
+    @Override
+    public void endTest()
+    {
+        for(Node node : emptyNodes)
+        {
+            errors.add( new TestError(this, Severity.OTHER, tr("Untagged and unconnected nodes"), UNTAGGED_NODE, node) );
+        }
+        emptyNodes = null;
+        super.endTest();
+    }
+
+    @Override
+    public Command fixError(TestError testError)
+    {
+        return DeleteCommand.delete(Main.map.mapView.getEditLayer(), testError.getPrimitives());
+    }
+
+    @Override
+    public boolean isFixable(TestError testError)
+    {
+        return (testError.getTester() instanceof UntaggedNode);
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedWay.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedWay.java
new file mode 100644
index 0000000..29183eb
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/UntaggedWay.java
@@ -0,0 +1,157 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+
+/**
+ * Checks for untagged ways
+ *
+ * @author frsantos
+ */
+public class UntaggedWay extends Test
+{
+    /** Empty way error */
+    protected static final int EMPTY_WAY    = 301;
+    /** Untagged way error */
+    protected static final int UNTAGGED_WAY = 302;
+    /** Unnamed way error */
+    protected static final int UNNAMED_WAY  = 303;
+    /** One node way error */
+    protected static final int ONE_NODE_WAY = 304;
+    /** Unnamed junction error */
+    protected static final int UNNAMED_JUNCTION  = 305;
+
+    private LinkedList<Way> multipolygonways;
+
+    /** Ways that must have a name */
+    public static final Set<String> NAMED_WAYS = new HashSet<String>();
+    static
+    {
+        NAMED_WAYS.add( "motorway" );
+        NAMED_WAYS.add( "trunk" );
+        NAMED_WAYS.add( "primary" );
+        NAMED_WAYS.add( "secondary" );
+        NAMED_WAYS.add( "tertiary" );
+        NAMED_WAYS.add( "residential" );
+        NAMED_WAYS.add( "pedestrian" ); ;
+    }
+
+    /**
+     * Constructor
+     */
+    public UntaggedWay()
+    {
+        super(tr("Untagged, empty and one node ways."),
+              tr("This test checks for untagged, empty and one node ways."));
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        if (!w.isUsable()) return;
+
+        Map<String, String> tags = w.getKeys();
+        if( tags.size() != 0 )
+        {
+            String highway = tags.get("highway");
+            if(highway != null && NAMED_WAYS.contains(highway))
+            {
+                if( !tags.containsKey("name") && !tags.containsKey("ref") )
+                {
+                    boolean isRoundabout = false;
+                    boolean hasName = false;
+                    for( String key : w.keySet())
+                    {
+                        hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
+                        if( hasName )
+                            break;
+                        if(key.equals("junction"))
+                        {
+                            isRoundabout = w.get("junction").equals("roundabout");
+                            break;
+                        }
+                    }
+
+                    if( !hasName && !isRoundabout)
+                        errors.add( new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w) );
+		    else if(isRoundabout)
+                        errors.add( new TestError(this, Severity.WARNING, tr("Unnamed junction"), UNNAMED_JUNCTION, w) );
+                }
+            }
+        }
+
+        if(!w.isTagged() && !multipolygonways.contains(w))
+        {
+            errors.add( new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w) );
+        }
+
+        if( w.getNodesCount() == 0 )
+        {
+            errors.add( new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w) );
+        }
+        else if( w.getNodesCount() == 1 )
+        {
+            errors.add( new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w) );
+        }
+
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        multipolygonways = new LinkedList<Way>();
+        for (final Relation r : Main.main.getCurrentDataSet().relations)
+        {
+            if(r.isUsable() && "multipolygon".equals(r.get("type")))
+            {
+                for (RelationMember m : r.getMembers())
+                {
+                    if(m.getMember() != null && m.getMember() instanceof Way &&
+                    m.getMember().isUsable() && !m.getMember().isTagged())
+                        multipolygonways.add((Way)m.getMember());
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endTest()
+    {
+        multipolygonways = null;
+        super.endTest();
+    }
+
+    @Override
+    public boolean isFixable(TestError testError)
+    {
+        if( testError.getTester() instanceof UntaggedWay )
+        {
+            return testError.getCode() == EMPTY_WAY
+                || testError.getCode() == ONE_NODE_WAY;
+        }
+
+        return false;
+    }
+
+    @Override
+    public Command fixError(TestError testError)
+    {
+        return DeleteCommand.delete(Main.map.mapView.getEditLayer(), testError.getPrimitives());
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/tests/WronglyOrderedWays.java b/validator/src/org/openstreetmap/josm/plugins/validator/tests/WronglyOrderedWays.java
new file mode 100644
index 0000000..1ab0996
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/tests/WronglyOrderedWays.java
@@ -0,0 +1,113 @@
+package org.openstreetmap.josm.plugins.validator.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.validator.Severity;
+import org.openstreetmap.josm.plugins.validator.Test;
+import org.openstreetmap.josm.plugins.validator.TestError;
+import org.openstreetmap.josm.plugins.validator.util.Bag;
+
+/**
+ * Check cyclic ways for errors
+ *
+ * @author jrreid
+ */
+public class WronglyOrderedWays extends Test  {
+    protected static int WRONGLY_ORDERED_COAST = 1001;
+    protected static int WRONGLY_ORDERED_WATER = 1002;
+    protected static int WRONGLY_ORDERED_LAND  = 1003;
+
+    /** The already detected errors */
+    Bag<Way, Way> _errorWays;
+
+    /**
+     * Constructor
+     */
+    public WronglyOrderedWays()
+    {
+        super(tr("Wrongly Ordered Ways."),
+              tr("This test checks the direction of water, land and coastline ways."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor)
+    {
+    	super.startTest(monitor);
+        _errorWays = new Bag<Way, Way>();
+    }
+
+    @Override
+    public void endTest()
+    {
+        _errorWays = null;
+        super.endTest();
+    }
+
+    @Override
+    public void visit(Way w)
+    {
+        String errortype = "";
+        int type;
+
+        if( !w.isUsable() )
+            return;
+
+        String natural = w.get("natural");
+        if( natural == null)
+            return;
+
+        if( natural.equals("coastline") )
+        {
+            errortype = tr("Reversed coastline: land not on left side");
+            type= WRONGLY_ORDERED_COAST;
+        }
+        else if(natural.equals("water") )
+        {
+            errortype = tr("Reversed water: land not on left side");
+            type= WRONGLY_ORDERED_WATER;
+        }
+        else if( natural.equals("land") )
+        {
+            errortype = tr("Reversed land: land not on left side");
+            type= WRONGLY_ORDERED_LAND;
+        }
+        else
+            return;
+
+
+        /**
+         * Test the directionality of the way
+         *
+         * Assuming a closed non-looping way, compute twice the area
+         * of the polygon using the formula 2*a = sum (Xn * Yn+1 - Xn+1 * Yn)
+         * If the area is negative the way is ordered in a clockwise direction
+         *
+         */
+
+        if(w.getNode(0) == w.getNode(w.getNodesCount()-1))
+        {
+            double area2 = 0;
+
+            for (int node = 1; node < w.getNodesCount(); node++)
+            {
+                area2 += (w.getNode(node-1).getCoor().lon() * w.getNode(node).getCoor().lat()
+                - w.getNode(node).getCoor().lon() * w.getNode(node-1).getCoor().lat());
+            }
+
+            if(((natural.equals("coastline") || natural.equals("land")) && area2 < 0.)
+            || (natural.equals("water") && area2 > 0.))
+            {
+                List<OsmPrimitive> primitives = new ArrayList<OsmPrimitive>();
+                primitives.add(w);
+                errors.add( new TestError(this, Severity.OTHER, errortype, type, primitives) );
+                _errorWays.add(w,w);
+            }
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/AgregatePrimitivesVisitor.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/AgregatePrimitivesVisitor.java
new file mode 100644
index 0000000..2965691
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/AgregatePrimitivesVisitor.java
@@ -0,0 +1,72 @@
+package org.openstreetmap.josm.plugins.validator.util;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+
+/**
+ * A visitor that aggregates all primitives it visits.
+ * <p>
+ * The primitives are sorted according to their type: first nodes, then ways.
+ *
+ * @author frsantos
+ */
+public class AgregatePrimitivesVisitor extends AbstractVisitor
+{
+    /** Aggregated data */
+    Collection<OsmPrimitive> aggregatedData;
+
+    /**
+     * Constructor
+     */
+    public AgregatePrimitivesVisitor()
+    {
+        aggregatedData = new LinkedList<OsmPrimitive>();
+    }
+
+    /**
+     * Visits a collection of primitives
+     * @param data The collection of primitives
+     * @return The aggregated primitives
+     */
+    public Collection<OsmPrimitive> visit(Collection<OsmPrimitive> data)
+    {
+        for (OsmPrimitive osm : data)
+        {
+            osm.visit(this);
+        }
+
+        return aggregatedData;
+    }
+
+    public void visit(Node n)
+    {
+        if(!aggregatedData.contains(n))
+            aggregatedData.add(n);
+    }
+
+    public void visit(Way w)
+    {
+        if(!aggregatedData.contains(w))
+        {
+            aggregatedData.add(w);
+            for (Node n : w.getNodes())
+                visit(n);
+        }
+    }
+
+    public void visit(Relation r) {
+        if (!aggregatedData.contains(r)) {
+            aggregatedData.add(r);
+            for (RelationMember m : r.getMembers()) {
+                m.getMember().visit(this);
+            }
+        }
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/Bag.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/Bag.java
new file mode 100644
index 0000000..cb64b20
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/Bag.java
@@ -0,0 +1,93 @@
+package org.openstreetmap.josm.plugins.validator.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ *
+ * A very simple bag to store multiple occurences of a same key.
+ * <p>
+ * The bag will keep, for each key, a list of values.
+ *
+ * @author frsantos
+ *
+ * @param <K> The key class
+ * @param <V> The value class
+ */
+public class Bag<K,V> extends HashMap<K, List<V>>
+{
+    /** Serializable ID */
+    private static final long serialVersionUID = 5374049172859211610L;
+
+    /**
+     * Returns the list of elements with the same key
+     * @param key The key to obtain the elements
+     * @return the list of elements with the same key
+     */
+    public List<V> get(K key)
+    {
+        return super.get(key);
+    }
+
+    /**
+     * Adds an element to the bag
+     * @param key The key of the element
+     * @param value The element to add
+     */
+    public void add(K key, V value)
+    {
+        List<V> values = get(key);
+        if( values == null )
+        {
+            values = new ArrayList<V>();
+            put(key, values);
+        }
+        values.add(value);
+    }
+
+    /**
+     * Adds an element to the bag
+     * @param key The key of the element
+     * @param value The element to add
+     */
+    public void add(K key)
+    {
+        List<V> values = get(key);
+        if( values == null )
+        {
+            values = new ArrayList<V>();
+            put(key, values);
+        }
+    }
+
+    /**
+     * Constructor
+     */
+    public Bag()
+    {
+        super();
+    }
+
+    /**
+     * Constructor
+     *
+     * @param initialCapacity The initial capacity
+     */
+    public Bag(int initialCapacity)
+    {
+        super(initialCapacity);
+    }
+
+    /**
+     * Returns true if the bag contains a value for a key
+     * @param key The key
+     * @param value The value
+     * @return true if the key contains the value
+     */
+    public boolean contains(K key, V value)
+    {
+        List<V> values = get(key);
+        return (values == null) ? false : values.contains(value);
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/Entities.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/Entities.java
new file mode 100644
index 0000000..b07906e
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/Entities.java
@@ -0,0 +1,410 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Taken from: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/apache/commons/lang/Entities.java?revision=636641 */
+package org.openstreetmap.josm.plugins.validator.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>
+ * Provides HTML and XML entity utilities.
+ * </p>
+ * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
+ * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
+ * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
+ * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
+ * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
+ */
+public class Entities {
+    private static final String[][] ARRAY = {
+        /* BASIC */
+        {"quot", "34"}, // " - double-quote
+        {"amp", "38"}, // & - ampersand
+        {"lt", "60"}, // < - less-than
+        {"gt", "62"}, // > - greater-than
+        /* XML */
+        {"apos", "39"}, // XML apostrophe
+        /* ISO8859_1 */
+        {"nbsp", "160"}, // non-breaking space
+        {"iexcl", "161"}, // inverted exclamation mark
+        {"cent", "162"}, // cent sign
+        {"pound", "163"}, // pound sign
+        {"curren", "164"}, // currency sign
+        {"yen", "165"}, // yen sign = yuan sign
+        {"brvbar", "166"}, // broken bar = broken vertical bar
+        {"sect", "167"}, // section sign
+        {"uml", "168"}, // diaeresis = spacing diaeresis
+        {"copy", "169"}, // © - copyright sign
+        {"ordf", "170"}, // feminine ordinal indicator
+        {"laquo", "171"}, // left-pointing double angle quotation mark = left pointing guillemet
+        {"not", "172"}, // not sign
+        {"shy", "173"}, // soft hyphen = discretionary hyphen
+        {"reg", "174"}, // ® - registered trademark sign
+        {"macr", "175"}, // macron = spacing macron = overline = APL overbar
+        {"deg", "176"}, // degree sign
+        {"plusmn", "177"}, // plus-minus sign = plus-or-minus sign
+        {"sup2", "178"}, // superscript two = superscript digit two = squared
+        {"sup3", "179"}, // superscript three = superscript digit three = cubed
+        {"acute", "180"}, // acute accent = spacing acute
+        {"micro", "181"}, // micro sign
+        {"para", "182"}, // pilcrow sign = paragraph sign
+        {"middot", "183"}, // middle dot = Georgian comma = Greek middle dot
+        {"cedil", "184"}, // cedilla = spacing cedilla
+        {"sup1", "185"}, // superscript one = superscript digit one
+        {"ordm", "186"}, // masculine ordinal indicator
+        {"raquo", "187"}, // right-pointing double angle quotation mark = right pointing guillemet
+        {"frac14", "188"}, // vulgar fraction one quarter = fraction one quarter
+        {"frac12", "189"}, // vulgar fraction one half = fraction one half
+        {"frac34", "190"}, // vulgar fraction three quarters = fraction three quarters
+        {"iquest", "191"}, // inverted question mark = turned question mark
+        {"Agrave", "192"}, // À - uppercase A, grave accent
+        {"Aacute", "193"}, // Á - uppercase A, acute accent
+        {"Acirc", "194"}, // Â - uppercase A, circumflex accent
+        {"Atilde", "195"}, // Ã - uppercase A, tilde
+        {"Auml", "196"}, // Ä - uppercase A, umlaut
+        {"Aring", "197"}, // Å - uppercase A, ring
+        {"AElig", "198"}, // Æ - uppercase AE
+        {"Ccedil", "199"}, // Ç - uppercase C, cedilla
+        {"Egrave", "200"}, // È - uppercase E, grave accent
+        {"Eacute", "201"}, // É - uppercase E, acute accent
+        {"Ecirc", "202"}, // Ê - uppercase E, circumflex accent
+        {"Euml", "203"}, // Ë - uppercase E, umlaut
+        {"Igrave", "204"}, // Ì - uppercase I, grave accent
+        {"Iacute", "205"}, // Í - uppercase I, acute accent
+        {"Icirc", "206"}, // Î - uppercase I, circumflex accent
+        {"Iuml", "207"}, // Ï - uppercase I, umlaut
+        {"ETH", "208"}, // Ð - uppercase Eth, Icelandic
+        {"Ntilde", "209"}, // Ñ - uppercase N, tilde
+        {"Ograve", "210"}, // Ò - uppercase O, grave accent
+        {"Oacute", "211"}, // Ó - uppercase O, acute accent
+        {"Ocirc", "212"}, // Ô - uppercase O, circumflex accent
+        {"Otilde", "213"}, // Õ - uppercase O, tilde
+        {"Ouml", "214"}, // Ö - uppercase O, umlaut
+        {"times", "215"}, // multiplication sign
+        {"Oslash", "216"}, // Ø - uppercase O, slash
+        {"Ugrave", "217"}, // Ù - uppercase U, grave accent
+        {"Uacute", "218"}, // Ú - uppercase U, acute accent
+        {"Ucirc", "219"}, // Û - uppercase U, circumflex accent
+        {"Uuml", "220"}, // Ü - uppercase U, umlaut
+        {"Yacute", "221"}, // Ý - uppercase Y, acute accent
+        {"THORN", "222"}, // Þ - uppercase THORN, Icelandic
+        {"szlig", "223"}, // ß - lowercase sharps, German
+        {"agrave", "224"}, // à - lowercase a, grave accent
+        {"aacute", "225"}, // á - lowercase a, acute accent
+        {"acirc", "226"}, // â - lowercase a, circumflex accent
+        {"atilde", "227"}, // ã - lowercase a, tilde
+        {"auml", "228"}, // ä - lowercase a, umlaut
+        {"aring", "229"}, // å - lowercase a, ring
+        {"aelig", "230"}, // æ - lowercase ae
+        {"ccedil", "231"}, // ç - lowercase c, cedilla
+        {"egrave", "232"}, // è - lowercase e, grave accent
+        {"eacute", "233"}, // é - lowercase e, acute accent
+        {"ecirc", "234"}, // ê - lowercase e, circumflex accent
+        {"euml", "235"}, // ë - lowercase e, umlaut
+        {"igrave", "236"}, // ì - lowercase i, grave accent
+        {"iacute", "237"}, // í - lowercase i, acute accent
+        {"icirc", "238"}, // î - lowercase i, circumflex accent
+        {"iuml", "239"}, // ï - lowercase i, umlaut
+        {"eth", "240"}, // ð - lowercase eth, Icelandic
+        {"ntilde", "241"}, // ñ - lowercase n, tilde
+        {"ograve", "242"}, // ò - lowercase o, grave accent
+        {"oacute", "243"}, // ó - lowercase o, acute accent
+        {"ocirc", "244"}, // ô - lowercase o, circumflex accent
+        {"otilde", "245"}, // õ - lowercase o, tilde
+        {"ouml", "246"}, // ö - lowercase o, umlaut
+        {"divide", "247"}, // division sign
+        {"oslash", "248"}, // ø - lowercase o, slash
+        {"ugrave", "249"}, // ù - lowercase u, grave accent
+        {"uacute", "250"}, // ú - lowercase u, acute accent
+        {"ucirc", "251"}, // û - lowercase u, circumflex accent
+        {"uuml", "252"}, // ü - lowercase u, umlaut
+        {"yacute", "253"}, // ý - lowercase y, acute accent
+        {"thorn", "254"}, // þ - lowercase thorn, Icelandic
+        {"yuml", "255"}, // ÿ - lowercase y, umlaut
+        /* HTML 40 */
+        // <!-- Latin Extended-B -->
+        {"fnof", "402"}, // latin small f with hook = function= florin, U+0192 ISOtech -->
+        // <!-- Greek -->
+        {"Alpha", "913"}, // greek capital letter alpha, U+0391 -->
+        {"Beta", "914"}, // greek capital letter beta, U+0392 -->
+        {"Gamma", "915"}, // greek capital letter gamma,U+0393 ISOgrk3 -->
+        {"Delta", "916"}, // greek capital letter delta,U+0394 ISOgrk3 -->
+        {"Epsilon", "917"}, // greek capital letter epsilon, U+0395 -->
+        {"Zeta", "918"}, // greek capital letter zeta, U+0396 -->
+        {"Eta", "919"}, // greek capital letter eta, U+0397 -->
+        {"Theta", "920"}, // greek capital letter theta,U+0398 ISOgrk3 -->
+        {"Iota", "921"}, // greek capital letter iota, U+0399 -->
+        {"Kappa", "922"}, // greek capital letter kappa, U+039A -->
+        {"Lambda", "923"}, // greek capital letter lambda,U+039B ISOgrk3 -->
+        {"Mu", "924"}, // greek capital letter mu, U+039C -->
+        {"Nu", "925"}, // greek capital letter nu, U+039D -->
+        {"Xi", "926"}, // greek capital letter xi, U+039E ISOgrk3 -->
+        {"Omicron", "927"}, // greek capital letter omicron, U+039F -->
+        {"Pi", "928"}, // greek capital letter pi, U+03A0 ISOgrk3 -->
+        {"Rho", "929"}, // greek capital letter rho, U+03A1 -->
+        // <!-- there is no Sigmaf, and no U+03A2 character either -->
+        {"Sigma", "931"}, // greek capital letter sigma,U+03A3 ISOgrk3 -->
+        {"Tau", "932"}, // greek capital letter tau, U+03A4 -->
+        {"Upsilon", "933"}, // greek capital letter upsilon,U+03A5 ISOgrk3 -->
+        {"Phi", "934"}, // greek capital letter phi,U+03A6 ISOgrk3 -->
+        {"Chi", "935"}, // greek capital letter chi, U+03A7 -->
+        {"Psi", "936"}, // greek capital letter psi,U+03A8 ISOgrk3 -->
+        {"Omega", "937"}, // greek capital letter omega,U+03A9 ISOgrk3 -->
+        {"alpha", "945"}, // greek small letter alpha,U+03B1 ISOgrk3 -->
+        {"beta", "946"}, // greek small letter beta, U+03B2 ISOgrk3 -->
+        {"gamma", "947"}, // greek small letter gamma,U+03B3 ISOgrk3 -->
+        {"delta", "948"}, // greek small letter delta,U+03B4 ISOgrk3 -->
+        {"epsilon", "949"}, // greek small letter epsilon,U+03B5 ISOgrk3 -->
+        {"zeta", "950"}, // greek small letter zeta, U+03B6 ISOgrk3 -->
+        {"eta", "951"}, // greek small letter eta, U+03B7 ISOgrk3 -->
+        {"theta", "952"}, // greek small letter theta,U+03B8 ISOgrk3 -->
+        {"iota", "953"}, // greek small letter iota, U+03B9 ISOgrk3 -->
+        {"kappa", "954"}, // greek small letter kappa,U+03BA ISOgrk3 -->
+        {"lambda", "955"}, // greek small letter lambda,U+03BB ISOgrk3 -->
+        {"mu", "956"}, // greek small letter mu, U+03BC ISOgrk3 -->
+        {"nu", "957"}, // greek small letter nu, U+03BD ISOgrk3 -->
+        {"xi", "958"}, // greek small letter xi, U+03BE ISOgrk3 -->
+        {"omicron", "959"}, // greek small letter omicron, U+03BF NEW -->
+        {"pi", "960"}, // greek small letter pi, U+03C0 ISOgrk3 -->
+        {"rho", "961"}, // greek small letter rho, U+03C1 ISOgrk3 -->
+        {"sigmaf", "962"}, // greek small letter final sigma,U+03C2 ISOgrk3 -->
+        {"sigma", "963"}, // greek small letter sigma,U+03C3 ISOgrk3 -->
+        {"tau", "964"}, // greek small letter tau, U+03C4 ISOgrk3 -->
+        {"upsilon", "965"}, // greek small letter upsilon,U+03C5 ISOgrk3 -->
+        {"phi", "966"}, // greek small letter phi, U+03C6 ISOgrk3 -->
+        {"chi", "967"}, // greek small letter chi, U+03C7 ISOgrk3 -->
+        {"psi", "968"}, // greek small letter psi, U+03C8 ISOgrk3 -->
+        {"omega", "969"}, // greek small letter omega,U+03C9 ISOgrk3 -->
+        {"thetasym", "977"}, // greek small letter theta symbol,U+03D1 NEW -->
+        {"upsih", "978"}, // greek upsilon with hook symbol,U+03D2 NEW -->
+        {"piv", "982"}, // greek pi symbol, U+03D6 ISOgrk3 -->
+        // <!-- General Punctuation -->
+        {"bull", "8226"}, // bullet = black small circle,U+2022 ISOpub -->
+        // <!-- bullet is NOT the same as bullet operator, U+2219 -->
+        {"hellip", "8230"}, // horizontal ellipsis = three dot leader,U+2026 ISOpub -->
+        {"prime", "8242"}, // prime = minutes = feet, U+2032 ISOtech -->
+        {"Prime", "8243"}, // double prime = seconds = inches,U+2033 ISOtech -->
+        {"oline", "8254"}, // overline = spacing overscore,U+203E NEW -->
+        {"frasl", "8260"}, // fraction slash, U+2044 NEW -->
+        // <!-- Letterlike Symbols -->
+        {"weierp", "8472"}, // script capital P = power set= Weierstrass p, U+2118 ISOamso -->
+        {"image", "8465"}, // blackletter capital I = imaginary part,U+2111 ISOamso -->
+        {"real", "8476"}, // blackletter capital R = real part symbol,U+211C ISOamso -->
+        {"trade", "8482"}, // trade mark sign, U+2122 ISOnum -->
+        {"alefsym", "8501"}, // alef symbol = first transfinite cardinal,U+2135 NEW -->
+        // <!-- alef symbol is NOT the same as hebrew letter alef,U+05D0 although the
+        // same glyph could be used to depict both characters -->
+        // <!-- Arrows -->
+        {"larr", "8592"}, // leftwards arrow, U+2190 ISOnum -->
+        {"uarr", "8593"}, // upwards arrow, U+2191 ISOnum-->
+        {"rarr", "8594"}, // rightwards arrow, U+2192 ISOnum -->
+        {"darr", "8595"}, // downwards arrow, U+2193 ISOnum -->
+        {"harr", "8596"}, // left right arrow, U+2194 ISOamsa -->
+        {"crarr", "8629"}, // downwards arrow with corner leftwards= carriage return, U+21B5 NEW -->
+        {"lArr", "8656"}, // leftwards double arrow, U+21D0 ISOtech -->
+        // <!-- ISO 10646 does not say that lArr is the same as the 'is implied by'
+        // arrow but also does not have any other character for that function.
+        // So ? lArr canbe used for 'is implied by' as ISOtech suggests -->
+        {"uArr", "8657"}, // upwards double arrow, U+21D1 ISOamsa -->
+        {"rArr", "8658"}, // rightwards double arrow,U+21D2 ISOtech -->
+        // <!-- ISO 10646 does not say this is the 'implies' character but does not
+        // have another character with this function so ?rArr can be used for
+        // 'implies' as ISOtech suggests -->
+        {"dArr", "8659"}, // downwards double arrow, U+21D3 ISOamsa -->
+        {"hArr", "8660"}, // left right double arrow,U+21D4 ISOamsa -->
+        // <!-- Mathematical Operators -->
+        {"forall", "8704"}, // for all, U+2200 ISOtech -->
+        {"part", "8706"}, // partial differential, U+2202 ISOtech -->
+        {"exist", "8707"}, // there exists, U+2203 ISOtech -->
+        {"empty", "8709"}, // empty set = null set = diameter,U+2205 ISOamso -->
+        {"nabla", "8711"}, // nabla = backward difference,U+2207 ISOtech -->
+        {"isin", "8712"}, // element of, U+2208 ISOtech -->
+        {"notin", "8713"}, // not an element of, U+2209 ISOtech -->
+        {"ni", "8715"}, // contains as member, U+220B ISOtech -->
+        // <!-- should there be a more memorable name than 'ni'? -->
+        {"prod", "8719"}, // n-ary product = product sign,U+220F ISOamsb -->
+        // <!-- prod is NOT the same character as U+03A0 'greek capital letter pi'
+        // though the same glyph might be used for both -->
+        {"sum", "8721"}, // n-ary summation, U+2211 ISOamsb -->
+        // <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+        // though the same glyph might be used for both -->
+        {"minus", "8722"}, // minus sign, U+2212 ISOtech -->
+        {"lowast", "8727"}, // asterisk operator, U+2217 ISOtech -->
+        {"radic", "8730"}, // square root = radical sign,U+221A ISOtech -->
+        {"prop", "8733"}, // proportional to, U+221D ISOtech -->
+        {"infin", "8734"}, // infinity, U+221E ISOtech -->
+        {"ang", "8736"}, // angle, U+2220 ISOamso -->
+        {"and", "8743"}, // logical and = wedge, U+2227 ISOtech -->
+        {"or", "8744"}, // logical or = vee, U+2228 ISOtech -->
+        {"cap", "8745"}, // intersection = cap, U+2229 ISOtech -->
+        {"cup", "8746"}, // union = cup, U+222A ISOtech -->
+        {"int", "8747"}, // integral, U+222B ISOtech -->
+        {"there4", "8756"}, // therefore, U+2234 ISOtech -->
+        {"sim", "8764"}, // tilde operator = varies with = similar to,U+223C ISOtech -->
+        // <!-- tilde operator is NOT the same character as the tilde, U+007E,although
+        // the same glyph might be used to represent both -->
+        {"cong", "8773"}, // approximately equal to, U+2245 ISOtech -->
+        {"asymp", "8776"}, // almost equal to = asymptotic to,U+2248 ISOamsr -->
+        {"ne", "8800"}, // not equal to, U+2260 ISOtech -->
+        {"equiv", "8801"}, // identical to, U+2261 ISOtech -->
+        {"le", "8804"}, // less-than or equal to, U+2264 ISOtech -->
+        {"ge", "8805"}, // greater-than or equal to,U+2265 ISOtech -->
+        {"sub", "8834"}, // subset of, U+2282 ISOtech -->
+        {"sup", "8835"}, // superset of, U+2283 ISOtech -->
+        // <!-- note that nsup, 'not a superset of, U+2283' is not covered by the
+        // Symbol font encoding and is not included. Should it be, for symmetry?
+        // It is in ISOamsn --> <!ENTITY nsub", "8836"},
+        // not a subset of, U+2284 ISOamsn -->
+        {"sube", "8838"}, // subset of or equal to, U+2286 ISOtech -->
+        {"supe", "8839"}, // superset of or equal to,U+2287 ISOtech -->
+        {"oplus", "8853"}, // circled plus = direct sum,U+2295 ISOamsb -->
+        {"otimes", "8855"}, // circled times = vector product,U+2297 ISOamsb -->
+        {"perp", "8869"}, // up tack = orthogonal to = perpendicular,U+22A5 ISOtech -->
+        {"sdot", "8901"}, // dot operator, U+22C5 ISOamsb -->
+        // <!-- dot operator is NOT the same character as U+00B7 middle dot -->
+        // <!-- Miscellaneous Technical -->
+        {"lceil", "8968"}, // left ceiling = apl upstile,U+2308 ISOamsc -->
+        {"rceil", "8969"}, // right ceiling, U+2309 ISOamsc -->
+        {"lfloor", "8970"}, // left floor = apl downstile,U+230A ISOamsc -->
+        {"rfloor", "8971"}, // right floor, U+230B ISOamsc -->
+        {"lang", "9001"}, // left-pointing angle bracket = bra,U+2329 ISOtech -->
+        // <!-- lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation
+        // mark' -->
+        {"rang", "9002"}, // right-pointing angle bracket = ket,U+232A ISOtech -->
+        // <!-- rang is NOT the same character as U+003E 'greater than' or U+203A
+        // 'single right-pointing angle quotation mark' -->
+        // <!-- Geometric Shapes -->
+        {"loz", "9674"}, // lozenge, U+25CA ISOpub -->
+        // <!-- Miscellaneous Symbols -->
+        {"spades", "9824"}, // black spade suit, U+2660 ISOpub -->
+        // <!-- black here seems to mean filled as opposed to hollow -->
+        {"clubs", "9827"}, // black club suit = shamrock,U+2663 ISOpub -->
+        {"hearts", "9829"}, // black heart suit = valentine,U+2665 ISOpub -->
+        {"diams", "9830"}, // black diamond suit, U+2666 ISOpub -->
+
+        // <!-- Latin Extended-A -->
+        {"OElig", "338"}, // -- latin capital ligature OE,U+0152 ISOlat2 -->
+        {"oelig", "339"}, // -- latin small ligature oe, U+0153 ISOlat2 -->
+        // <!-- ligature is a misnomer, this is a separate character in some languages -->
+        {"Scaron", "352"}, // -- latin capital letter S with caron,U+0160 ISOlat2 -->
+        {"scaron", "353"}, // -- latin small letter s with caron,U+0161 ISOlat2 -->
+        {"Yuml", "376"}, // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 -->
+        // <!-- Spacing Modifier Letters -->
+        {"circ", "710"}, // -- modifier letter circumflex accent,U+02C6 ISOpub -->
+        {"tilde", "732"}, // small tilde, U+02DC ISOdia -->
+        // <!-- General Punctuation -->
+        {"ensp", "8194"}, // en space, U+2002 ISOpub -->
+        {"emsp", "8195"}, // em space, U+2003 ISOpub -->
+        {"thinsp", "8201"}, // thin space, U+2009 ISOpub -->
+        {"zwnj", "8204"}, // zero width non-joiner,U+200C NEW RFC 2070 -->
+        {"zwj", "8205"}, // zero width joiner, U+200D NEW RFC 2070 -->
+        {"lrm", "8206"}, // left-to-right mark, U+200E NEW RFC 2070 -->
+        {"rlm", "8207"}, // right-to-left mark, U+200F NEW RFC 2070 -->
+        {"ndash", "8211"}, // en dash, U+2013 ISOpub -->
+        {"mdash", "8212"}, // em dash, U+2014 ISOpub -->
+        {"lsquo", "8216"}, // left single quotation mark,U+2018 ISOnum -->
+        {"rsquo", "8217"}, // right single quotation mark,U+2019 ISOnum -->
+        {"sbquo", "8218"}, // single low-9 quotation mark, U+201A NEW -->
+        {"ldquo", "8220"}, // left double quotation mark,U+201C ISOnum -->
+        {"rdquo", "8221"}, // right double quotation mark,U+201D ISOnum -->
+        {"bdquo", "8222"}, // double low-9 quotation mark, U+201E NEW -->
+        {"dagger", "8224"}, // dagger, U+2020 ISOpub -->
+        {"Dagger", "8225"}, // double dagger, U+2021 ISOpub -->
+        {"permil", "8240"}, // per mille sign, U+2030 ISOtech -->
+        {"lsaquo", "8249"}, // single left-pointing angle quotation mark,U+2039 ISO proposed -->
+        // <!-- lsaquo is proposed but not yet ISO standardized -->
+        {"rsaquo", "8250"}, // single right-pointing angle quotation mark,U+203A ISO proposed -->
+        // <!-- rsaquo is proposed but not yet ISO standardized -->
+        {"euro", "8364"}, // -- euro sign, U+20AC NEW -->
+    };
+
+    private static Map<String, String> mapNameToValue = null;
+
+    public String unescape(String str) {
+        int firstAmp = str.indexOf('&');
+        if (firstAmp < 0)
+            return str;
+        String res = new String(str.substring(0, firstAmp));
+        int len = str.length();
+        for (int i = firstAmp; i < len; i++) {
+            char c = str.charAt(i);
+            if (c == '&') {
+                int nextIdx = i + 1;
+                int semiColonIdx = str.indexOf(';', nextIdx);
+                if (semiColonIdx == -1) {
+                    res += c;
+                    continue;
+                }
+                int amphersandIdx = str.indexOf('&', i + 1);
+                if (amphersandIdx != -1 && amphersandIdx < semiColonIdx) {
+                    // Then the text looks like &...&...;
+                    res += c;
+                    continue;
+                }
+                String entityContent = str.substring(nextIdx, semiColonIdx);
+                int entityValue = -1;
+                int entityContentLen = entityContent.length();
+                if (entityContentLen > 0) {
+                    if (entityContent.charAt(0) == '#') { // escaped value content is an integer (decimal or
+                        // hexidecimal)
+                        if (entityContentLen > 1) {
+                            char isHexChar = entityContent.charAt(1);
+                            try {
+                                switch (isHexChar) {
+                                    case 'X' :
+                                    case 'x' : {
+                                        entityValue = Integer.parseInt(entityContent.substring(2), 16);
+                                        break;
+                                    }
+                                    default : {
+                                        entityValue = Integer.parseInt(entityContent.substring(1), 10);
+                                    }
+                                }
+                                if (entityValue > 0xFFFF) {
+                                    entityValue = -1;
+                                }
+                            } catch (NumberFormatException e) {
+                                entityValue = -1;
+                            }
+                        }
+                    } else { // escaped value content is an entity name
+                        if(mapNameToValue == null)
+                        {
+                            mapNameToValue = new HashMap<String, String>();
+                            for (int in = 0; in < ARRAY.length; ++in)
+                                mapNameToValue.put(ARRAY[in][0], ARRAY[in][1]);
+                        }
+                        String value = mapNameToValue.get(entityContent);
+                        entityValue = (value == null ? -1 : Integer.parseInt(value));
+                    }
+                }
+
+                if (entityValue == -1) {
+                    res += '&' + entityContent + ';';
+                } else {
+                    res += (char) entityValue;
+                }
+                i = semiColonIdx; // move index up to the semi-colon
+            } else {
+                res += c;
+            }
+        }
+        return res;
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/MultipleNameVisitor.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/MultipleNameVisitor.java
new file mode 100644
index 0000000..fdb7d07
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/MultipleNameVisitor.java
@@ -0,0 +1,105 @@
+package org.openstreetmap.josm.plugins.validator.util;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.util.Collection;
+
+import javax.swing.Icon;
+import javax.swing.JLabel;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Able to create a name and an icon for a collection of elements.
+ *
+ * @author frsantos
+ */
+public class MultipleNameVisitor extends NameVisitor
+{
+    /** The class name of the combined primitives */
+    String multipleClassname;
+    /* name to be displayed */
+    String displayName;
+    /** Size of the collection */
+    int size;
+
+    /**
+     * Visits a collection of primitives
+     * @param data The collection of primitives
+     */
+    public void visit(Collection<? extends OsmPrimitive> data)
+    {
+        String multipleName = null;
+        String multiplePluralClassname = null;
+        String firstName = null;
+        boolean initializedname = false;
+        size = data.size();
+
+        multipleClassname = null;
+        for (OsmPrimitive osm : data)
+        {
+            String name = osm.get("name");
+            if(name == null) name = osm.get("ref");
+            if(!initializedname)
+            {
+                multipleName = name; initializedname = true;
+            }
+            else if(multipleName != null && (name == null  || !name.equals(multipleName)))
+            {
+                multipleName = null;
+            }
+
+            if(firstName == null && name != null)
+                firstName = name;
+            osm.visit(this);
+            if (multipleClassname == null)
+            {
+                multipleClassname = className;
+                multiplePluralClassname = classNamePlural;
+            }
+            else if (!multipleClassname.equals(className))
+            {
+                multipleClassname = "object";
+                multiplePluralClassname = trn("object", "objects", 2);
+            }
+        }
+
+        if( size == 1 )
+            displayName = name;
+        else if(multipleName != null)
+            displayName = size + " " + trn(multipleClassname, multiplePluralClassname, size) + ": " + multipleName;
+        else if(firstName != null)
+            displayName = size + " " + trn(multipleClassname, multiplePluralClassname, size) + ": " + tr("{0}, ...", firstName);
+        else
+            displayName = size + " " + trn(multipleClassname, multiplePluralClassname, size);
+    }
+
+    @Override
+    public JLabel toLabel()
+    {
+        return new JLabel(getText(), getIcon(), JLabel.HORIZONTAL);
+    }
+
+    /**
+     * Gets the name of the items
+     * @return the name of the items
+     */
+    public String getText()
+    {
+        return displayName;
+    }
+
+    /**
+     * Gets the icon of the items
+     * @return the icon of the items
+     */
+    public Icon getIcon()
+    {
+        if( size == 1 )
+            return icon;
+        else
+            return ImageProvider.get("data", multipleClassname);
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/NameVisitor.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/NameVisitor.java
new file mode 100644
index 0000000..435644e
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/NameVisitor.java
@@ -0,0 +1,83 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.plugins.validator.util;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import javax.swing.Icon;
+import javax.swing.JLabel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Able to create a name and an icon for each data element.
+ *
+ * @author imi
+ */
+//TODO This class used to be in JOSM but it was removed. MultipleNameVisitor depends on it so I copied it here, but MultipleNameVisitor should be refactored instead of using this class
+public class NameVisitor extends AbstractVisitor {
+
+    /**
+     * The name of the item class
+     */
+    public String className;
+    public String classNamePlural;
+    /**
+     * The name of this item.
+     */
+    public String name;
+    /**
+     * The icon of this item.
+     */
+    public Icon icon;
+
+    /**
+     * If the node has a name-key or id-key, this is displayed. If not, (lat,lon)
+     * is displayed.
+     */
+    public void visit(Node n) {
+        name = n.getName();
+        addId(n);
+        icon = ImageProvider.get("data", "node");
+        className = "node";
+        classNamePlural = trn("node", "nodes", 2);
+    }
+
+    /**
+     * If the way has a name-key or id-key, this is displayed. If not, (x nodes)
+     * is displayed with x being the number of nodes in the way.
+     */
+    public void visit(Way w) {
+        name = w.getName();
+        addId(w);
+        icon = ImageProvider.get("data", "way");
+        className = "way";
+        classNamePlural = trn("way", "ways", 2);
+    }
+
+    /**
+     */
+    public void visit(Relation e) {
+        name = e.getName();
+        addId(e);
+        icon = ImageProvider.get("data", "relation");
+        className = "relation";
+        classNamePlural = trn("relation", "relations", 2);
+    }
+
+    public JLabel toLabel() {
+        return new JLabel(name, icon, JLabel.HORIZONTAL);
+    }
+
+
+    private void addId(OsmPrimitive osm) {
+        if (Main.pref.getBoolean("osm-primitives.showid"))
+            name += tr(" [id: {0}]", osm.getId());
+    }
+}
diff --git a/validator/src/org/openstreetmap/josm/plugins/validator/util/Util.java b/validator/src/org/openstreetmap/josm/plugins/validator/util/Util.java
new file mode 100644
index 0000000..c4dbef0
--- /dev/null
+++ b/validator/src/org/openstreetmap/josm/plugins/validator/util/Util.java
@@ -0,0 +1,175 @@
+package org.openstreetmap.josm.plugins.validator.util;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.plugins.validator.OSMValidatorPlugin;
+
+/**
+ * Utility class
+ *
+ * @author frsantos
+ */
+public class Util
+{
+    /**
+     * Returns the plugin's directory of the plugin
+     *
+     * @return The directory of the plugin
+     */
+    public static String getPluginDir()
+    {
+        return Main.pref.getPreferencesDir() + "plugins/validator/";
+    }
+
+    /**
+     * Returns the start and end cells of a way.
+     * @param w The way
+     * @param cellWays The map with all cells
+     * @return A list with all the cells the way starts or ends
+     */
+    public static List<List<Way>> getWaysInCell(Way w, Map<Point2D,List<Way>> cellWays)
+    {
+        if (w.getNodesCount() == 0)
+            return Collections.emptyList();
+
+        Node n1 = w.getNode(0);
+        Node n2 = w.getNode(w.getNodesCount() - 1);
+
+        List<List<Way>> cells = new ArrayList<List<Way>>(2);
+        Set<Point2D> cellNodes = new HashSet<Point2D>();
+        Point2D cell;
+
+        // First, round coordinates
+        long x0 = Math.round(n1.getEastNorth().east()  * OSMValidatorPlugin.griddetail);
+        long y0 = Math.round(n1.getEastNorth().north() * OSMValidatorPlugin.griddetail);
+        long x1 = Math.round(n2.getEastNorth().east()  * OSMValidatorPlugin.griddetail);
+        long y1 = Math.round(n2.getEastNorth().north() * OSMValidatorPlugin.griddetail);
+
+        // Start of the way
+        cell = new Point2D.Double(x0, y0);
+        cellNodes.add(cell);
+        List<Way> ways = cellWays.get( cell );
+        if( ways == null )
+        {
+            ways = new ArrayList<Way>();
+            cellWays.put(cell, ways);
+        }
+        cells.add(ways);
+
+        // End of the way
+        cell = new Point2D.Double(x1, y1);
+        if( !cellNodes.contains(cell) )
+        {
+            cellNodes.add(cell);
+            ways = cellWays.get( cell );
+            if( ways == null )
+            {
+                ways = new ArrayList<Way>();
+                cellWays.put(cell, ways);
+            }
+            cells.add(ways);
+        }
+
+        // Then floor coordinates, in case the way is in the border of the cell.
+        x0 = (long)Math.floor(n1.getEastNorth().east()  * OSMValidatorPlugin.griddetail);
+        y0 = (long)Math.floor(n1.getEastNorth().north() * OSMValidatorPlugin.griddetail);
+        x1 = (long)Math.floor(n2.getEastNorth().east()  * OSMValidatorPlugin.griddetail);
+        y1 = (long)Math.floor(n2.getEastNorth().north() * OSMValidatorPlugin.griddetail);
+
+        // Start of the way
+        cell = new Point2D.Double(x0, y0);
+        if( !cellNodes.contains(cell) )
+        {
+            cellNodes.add(cell);
+            ways = cellWays.get( cell );
+            if( ways == null )
+            {
+                ways = new ArrayList<Way>();
+                cellWays.put(cell, ways);
+            }
+            cells.add(ways);
+        }
+
+        // End of the way
+        cell = new Point2D.Double(x1, y1);
+        if( !cellNodes.contains(cell) )
+        {
+            cellNodes.add(cell);
+            ways = cellWays.get( cell );
+            if( ways == null )
+            {
+                ways = new ArrayList<Way>();
+                cellWays.put(cell, ways);
+            }
+            cells.add(ways);
+        }
+
+        return cells;
+    }
+
+    /**
+     * Returns the coordinates of all cells in a grid that a line between 2
+     * nodes intersects with.
+     *
+     * @param n1 The first node.
+     * @param n2 The second node.
+     * @param gridDetail The detail of the grid. Bigger values give smaller
+     * cells, but a bigger number of them.
+     * @return A list with the coordinates of all cells
+     */
+    public static List<Point2D> getSegmentCells(Node n1, Node n2, double gridDetail)
+    {
+        List<Point2D> cells = new ArrayList<Point2D>();
+        double x0 = n1.getEastNorth().east() * gridDetail;
+        double x1 = n2.getEastNorth().east() * gridDetail;
+        double y0 = n1.getEastNorth().north() * gridDetail + 1;
+        double y1 = n2.getEastNorth().north() * gridDetail + 1;
+
+        if( x0 > x1 )
+        {
+            // Move to 1st-4th cuadrants
+            double aux;
+            aux = x0; x0 = x1; x1 = aux;
+            aux = y0; y0 = y1; y1 = aux;
+        }
+
+        double dx  = x1 - x0;
+        double dy  = y1 - y0;
+        long stepY = y0 <= y1 ? 1 : -1;
+        long gridX0 = (long)Math.floor(x0);
+        long gridX1 = (long)Math.floor(x1);
+        long gridY0 = (long)Math.floor(y0);
+        long gridY1 = (long)Math.floor(y1);
+
+        long maxSteps = (gridX1 - gridX0) + Math.abs(gridY1 - gridY0) + 1;
+        while( (gridX0 <= gridX1 && (gridY0 - gridY1)*stepY <= 0) && maxSteps-- > 0)
+        {
+            cells.add( new Point2D.Double(gridX0, gridY0) );
+
+            // Is the cross between the segment and next vertical line nearer than the cross with next horizontal line?
+            // Note: segment line formula: y=dy/dx(x-x1)+y1
+            // Note: if dy < 0, must use *bottom* line. If dy > 0, must use upper line
+            double scanY = dy/dx * (gridX0 + 1 - x1) + y1 + (dy < 0 ? -1 : 0);
+            double scanX = dx/dy * (gridY0 + (dy < 0 ? 0 : 1)*stepY - y1) + x1;
+
+            double distX = Math.pow(gridX0 + 1 - x0, 2) + Math.pow(scanY - y0, 2);
+            double distY = Math.pow(scanX - x0, 2) + Math.pow(gridY0 + stepY - y0, 2);
+
+            if( distX < distY)
+                gridX0 += 1;
+            else
+                gridY0 += stepY;
+        }
+
+        return cells;
+    }
+}
diff --git a/validator/tagchecker.cfg b/validator/tagchecker.cfg
new file mode 100644
index 0000000..1438909
--- /dev/null
+++ b/validator/tagchecker.cfg
@@ -0,0 +1,79 @@
+# JOSM TagChecker validator file
+
+# Format:
+# Each line specifies a certain error to be reported
+# <data type> : messagetype : <key><expression><value>
+#
+# Data type can be:
+#  node        - a node point
+#  way         - a way
+#  relation    - a relation
+#  *           - all data types
+#
+# Message type can be:
+# E            - an error
+# W            - a warning
+# I            - an low priority informational warning
+#
+# Key and value are expressions describing certain keys and values of these keys.
+# Regulator expressions are supported. In this case the expressions starts and
+# ends with // signs. If an 'i' is appended, the regular expression is
+# case insensitive.
+#
+# The * sign indicates any string.
+# The texts BOOLEAN_TRUE and BOOLEAN_FALSE in the value part indicate a special
+# handling for boolean values (yes, true, 0, false, no, ...).
+#
+# Expression can be:
+#  !=          - the key/value combination does not match
+#  ==          - the key/value combination does match
+#
+# To have more complicated expressions, multiple elements can be grouped together
+# with an logical and (&&).
+#
+# The comment at the end of a rule is displayed in validator description
+#
+# Empty lines and space signs are ignored
+
+way  : W : highway == * && name == /.* (Ave|Blvd|Cct|Cir|Cl|Cr|Crct|Cres|Crt|Ct|Dr|Drv|Esp|Espl|Hwy|Ln|Mw|Mwy|Pl|Rd|Qy|Qys|Sq|St|Str|Ter|Tce|Tr|Wy)\.?$/i               # abbreviated street name
+
+node : W : oneway == *                                         # oneway tag on a node
+node : W : bridge == BOOLEAN_TRUE                              # bridge tag on a node
+node : W : highway == tertiary                                 # wrong highway tag on a node
+node : W : highway == secondary                                # wrong highway tag on a node
+node : W : highway == residential                              # wrong highway tag on a node
+node : W : highway == unclassified                             # wrong highway tag on a node
+node : W : highway == track                                    # wrong highway tag on a node
+way  : W : highway == unclassified && name != *                # Unnamed unclassified highway
+way  : I : highway == secondary && ref != *                    # highway without a reference
+way  : I : highway == tertiary && ref != *                     # highway without a reference
+way  : I : highway == motorway && nat_ref != *                 # highway without a reference
+*    : W : highway == road                                     # temporary highway type
+*    : W : / *name */i == * && name != *                       # misspelled key name
+
+# The following could replace unnamed way check. Still at the moment we keep it as it is
+#way  : W : junction == roundabout && highway == /motorway|trunk|primary|secondary|tertiary|residential|pedestrian/ && /name|ref|(name:.*)|(.*_name)|(.*_ref)/ != * # Unnamed junction
+#way  : W : highway == /motorway|trunk|primary|secondary|tertiary|residential|pedestrian/ && /name|ref|(name:.*)|(.*_name)|(.*_ref)/ != * # Unnamed 
+
+way  : W : highway == cycleway && bicycle == BOOLEAN_FALSE     # cycleway with tag bicycle
+way  : W : highway == footway && foot == BOOLEAN_FALSE         # footway with tag foot
+#way  : I : highway == cycleway && bicycle == *                 # cycleway with tag bicycle
+#way  : I : highway == footway && foot == *                     # footway with tag foot
+way  : W : highway == cycleway && cycleway == lane             # separate cycleway as lane on a cycleway
+way  : W : highway == * && barrier == *                        # barrier used on a way
+
+#way  : I : waterway == * && layer != *                         # waterway without layer tag
+way  : I : highway == footway && maxspeed == *                 # maxspeed used for footway
+way  : I : highway == steps && maxspeed == *                   # maxspeed used for footway
+
+*    : W : layer == /\+.*/                                     # layer tag with + sign
+
+*    : I : name == /.*Strasse.*/i                              # street name contains ss
+
+relation : E : type != *                                       # relation without type
+
+node : I : amenity == /restaurant|cafe|fast_food/ && name != * # restaurant without name
+#way  : I : highway != * && railway != * && waterway != * && name == * # unusual named way type
+*    : W : natural == water && waterway == *                   # unusual tag combination
+*    : W : highway == * && waterway == *                       # unusual tag combination
+*    : W : highway == * && natural == *                        # unusual tag combination
diff --git a/wmsplugin/.classpath b/wmsplugin/.classpath
new file mode 100644
index 0000000..2f5994b
--- /dev/null
+++ b/wmsplugin/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="lib" path="C:/prj.ht/josm-snapshot-467.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/wmsplugin/.project b/wmsplugin/.project
new file mode 100644
index 0000000..a269867
--- /dev/null
+++ b/wmsplugin/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>wmsplugin</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/wmsplugin/Makefile b/wmsplugin/Makefile
new file mode 100644
index 0000000..302fb1a
--- /dev/null
+++ b/wmsplugin/Makefile
@@ -0,0 +1,16 @@
+#!/usr/bin/make
+
+MOC=moc
+#MOC=/usr/share/qt4/bin/moc
+CFLAGS =
+LDFLAGS =
+LDLIBS = `pkg-config --libs --cflags QtCore QtGui QtWebKit`
+
+webkit-image: webkit-image.cpp webkit-image.h
+	g++ -W -o $@ -O2 $(CFLAGS) $(LDFLAGS) webkit-image.cpp $(LDLIBS)
+
+webkit-image.h: webkit-image.cpp
+	$(MOC) webkit-image.cpp >$@
+
+clean:
+	rm -f *.o webkit-image webkit-image.h
diff --git a/wmsplugin/README b/wmsplugin/README
new file mode 100644
index 0000000..b91122a
--- /dev/null
+++ b/wmsplugin/README
@@ -0,0 +1,19 @@
+A plugin for JOSM that can query any WMS server for background images.
+Also supports Metacarta's map rectifier.
+Supersedes the "Landsat" plugin, as the WMS plugin can do everything the
+landsat plugin could plus more.
+
+See also utils/ywms for a way to get Yahoo satellite images with this
+plugin (Linux required for ywms).
+
+AUTHORS and LICENSE:
+
+This plugin has been created by tim <chippy2005 at gmail.com>
+and has received major contributions from Frederik Ramm
+<frederik at remote.org>. It is based on the "Landsat" plugin
+by Nick Whitelegg <Nick.Whitelegg at solent.ac.uk> and includes
+some code from Jonathan Stott <jonathan at jstott.me.uk>, Gabriel Ebner
+<ge at gabrielebner.at> and Ulf Lamping <ulf.lamping at web.de>.
+The automatic tiles downloading and Yahoo downloader made by Petr Dlouhý <petr.dlouhy at email.cz>
+
+This plugin is licensed under the GNU GPL v2 or later.
diff --git a/wmsplugin/build.xml b/wmsplugin/build.xml
new file mode 100644
index 0000000..099377d
--- /dev/null
+++ b/wmsplugin/build.xml
@@ -0,0 +1,139 @@
+<!--
+** This is a template build file for a JOSM  plugin.
+**
+** Maintaining versions
+** ====================
+** see README.template
+**
+** Usage
+** =====
+** To build it run
+**
+**    > ant  dist
+**
+** To install the generated plugin locally (in your default plugin directory) run
+**
+**    > ant  install
+**
+** To build against the core in ../../core, create a correct manifest and deploy to
+** SVN, run
+**    - set the property commit.message 
+**    - set the property josm.reference.release to lowest JOSM release number this
+**      plugin build is compatible with
+**    > ant  deploy
+**
+**
+-->
+<project name="wmsplugin" default="dist" basedir=".">
+    <property name="josm"                   location="../../core/dist/josm-custom.jar"/>
+    <property name="plugin.dist.dir"        value="../../dist"/>
+    <property name="plugin.build.dir"       value="build"/>
+    <property name="plugin.jar"             value="${plugin.dist.dir}/${ant.project.name}.jar"/>
+    <property name="ant.build.javac.target" value="1.5"/>
+	<property name="commit.message"         value="fixing JOSM issue #3186" />
+	<property name="josm.reference.release" value="2196" />
+	
+    <target name="init">
+        <mkdir dir="${plugin.build.dir}"/>
+    </target>
+    <target name="compile" depends="init">
+        <echo message="creating ${plugin.jar}"/>
+        <javac srcdir="src" classpath="${josm}" debug="true" destdir="${plugin.build.dir}">
+            <compilerarg value="-Xlint:deprecation"/>
+            <compilerarg value="-Xlint:unchecked"/>
+        </javac>
+    </target>
+    <target name="dist" depends="compile,revision">
+    	<echo message="building ${plugin.jar} with version ${version.entry.commit.revision} for JOSM version ${josm.reference.release} "/>
+        <copy todir="${plugin.build.dir}/images">
+            <fileset dir="images"/>
+        </copy>
+        <jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
+            <manifest>
+                <attribute name="Author" value="Tim Waters, Petr Dlouhý"/>
+                <attribute name="Plugin-Class" value="wmsplugin.WMSPlugin"/>
+                <attribute name="Plugin-Date" value="${version.entry.commit.date}"/>
+                <attribute name="Plugin-Description" value="Display georeferenced images as background in JOSM (WMS servers, Yahoo, ...)."/>
+                <attribute name="Plugin-Link" value="http://wiki.openstreetmap.org/wiki/JOSM/Plugins/WMSPlugin"/>
+                <attribute name="Plugin-Mainversion" value="${josm.reference.release}"/>
+                <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
+                <attribute name="de_Plugin-Link" value="http://wiki.openstreetmap.org/wiki/DE:JOSM/Plugins/WMSPlugin"/>
+            </manifest>
+        </jar>
+    </target>
+    <target name="revision">
+        <exec append="false" output="REVISION" executable="svn" failifexecutionfails="false">
+            <env key="LANG" value="C"/>
+            <arg value="info"/>
+            <arg value="--xml"/>
+            <arg value="."/>
+        </exec>
+        <xmlproperty file="REVISION" prefix="version" keepRoot="false" collapseAttributes="true"/>
+        <delete file="REVISION"/>
+    </target>
+    <target name="clean">
+        <delete dir="${plugin.build.dir}"/>
+        <delete file="${plugin.jar}"/>
+    </target>
+    <target name="install" depends="dist">
+        <property environment="env"/>
+        <condition property="josm.plugins.dir" value="${env.APPDATA}/JOSM/plugins" else="${user.home}/.josm/plugins">
+            <and>
+                <os family="windows"/>
+            </and>
+        </condition>
+        <copy file="${plugin.jar}" todir="${josm.plugins.dir}"/>
+    </target>
+	
+	<target name="core-info">
+	        <exec append="false" output="core.info.xml" executable="svn" failifexecutionfails="false">
+	                    <env key="LANG" value="C"/>
+	                    <arg value="info"/>
+	                    <arg value="--xml"/>
+	                    <arg value="../../core"/>
+	        </exec>
+	        <xmlproperty file="core.info.xml" prefix="coreversion" keepRoot="true" collapseAttributes="true"/>
+			<echo>Building against core revision ${coreversion.info.entry.revision} ...</echo>
+			<delete file="core.info.xml" />
+		</target>
+
+		
+		<target name="commit-current">
+			<echo>Commiting the plugin source ...</echo>
+		    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+		                    <env key="LANG" value="C"/>
+		                    <arg value="commit"/>
+		                    <arg value="-m "${commit.message}""/>
+		                    <arg value="."/>
+		    </exec>	    
+		</target>
+
+		
+		<target name="update-current">
+			<echo>Updating basedir ...</echo>
+		    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+		                    <env key="LANG" value="C"/>
+		                    <arg value="up"/>
+		                    <arg value="."/>
+		    </exec>	    
+			<echo>Updating ${plugin.jar} ...</echo>
+		    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+		                    <env key="LANG" value="C"/>
+		                    <arg value="up"/>
+		                    <arg value="${plugin.jar}"/>
+		    </exec>	    
+		</target>
+		
+		<target name="commit-dist">
+				<echo>Commiting ${plugin.jar} ...</echo>
+			    <exec append="true" output="svn.log" executable="svn" failifexecutionfails="false">
+			                    <env key="LANG" value="C"/>
+			                    <arg value="commit"/>
+	                			<arg value="-m "${commit.message}""/>
+			                    <arg value="${plugin.jar}"/>
+			    </exec>	    
+	   	</target>
+		
+		<target name="deploy" depends="core-info,commit-current,update-current,clean,dist,commit-dist">
+		</target>
+</project>
diff --git a/wmsplugin/gnome-web-photo-fixed b/wmsplugin/gnome-web-photo-fixed
new file mode 100755
index 0000000..62c7f68
--- /dev/null
+++ b/wmsplugin/gnome-web-photo-fixed
@@ -0,0 +1,3 @@
+#! /bin/bash
+
+gnome-web-photo --mode=photo --format=ppm "$1" /dev/stdout |pnmcrop -white |pnmtopng
diff --git a/wmsplugin/images/OLmarker.png b/wmsplugin/images/OLmarker.png
new file mode 100644
index 0000000..b1917f2
Binary files /dev/null and b/wmsplugin/images/OLmarker.png differ
diff --git a/wmsplugin/images/blankmenu.png b/wmsplugin/images/blankmenu.png
new file mode 100644
index 0000000..640fbde
Binary files /dev/null and b/wmsplugin/images/blankmenu.png differ
diff --git a/wmsplugin/images/load.png b/wmsplugin/images/load.png
new file mode 100644
index 0000000..2019dc8
Binary files /dev/null and b/wmsplugin/images/load.png differ
diff --git a/wmsplugin/images/mapmode/adjustwms.png b/wmsplugin/images/mapmode/adjustwms.png
new file mode 100644
index 0000000..2fa1964
Binary files /dev/null and b/wmsplugin/images/mapmode/adjustwms.png differ
diff --git a/wmsplugin/images/preferences/wms.png b/wmsplugin/images/preferences/wms.png
new file mode 100644
index 0000000..2ddb7d9
Binary files /dev/null and b/wmsplugin/images/preferences/wms.png differ
diff --git a/wmsplugin/images/save.png b/wmsplugin/images/save.png
new file mode 100644
index 0000000..2949b3f
Binary files /dev/null and b/wmsplugin/images/save.png differ
diff --git a/wmsplugin/images/wms.png b/wmsplugin/images/wms.png
new file mode 100644
index 0000000..c0c1f21
Binary files /dev/null and b/wmsplugin/images/wms.png differ
diff --git a/wmsplugin/images/wms_small.png b/wmsplugin/images/wms_small.png
new file mode 100644
index 0000000..de0d0b4
Binary files /dev/null and b/wmsplugin/images/wms_small.png differ
diff --git a/wmsplugin/images/wmsmenu.png b/wmsplugin/images/wmsmenu.png
new file mode 100644
index 0000000..77ee3b9
Binary files /dev/null and b/wmsplugin/images/wmsmenu.png differ
diff --git a/wmsplugin/sources.cfg b/wmsplugin/sources.cfg
new file mode 100644
index 0000000..5dd428b
--- /dev/null
+++ b/wmsplugin/sources.cfg
@@ -0,0 +1,39 @@
+# FORMAT
+# default(true or false);Name;URL
+# NOTE: default items should be common and worldwide
+#
+true;Landsat;http://onearth.jpl.nasa.gov/wms.cgi?request=GetMap&layers=global_mosaic&styles=&format=image/jpeg&
+true;Open Aerial Map;http://openaerialmap.org/wms/?VERSION=1.0&request=GetMap&layers=world&styles=&format=image/jpeg&
+# fails with division by zero error
+false;NPE Maps;http://nick.dev.openstreetmap.org/openpaths/freemap.php?layers=npe&
+false;NPE Maps (Tim);http://dev.openstreetmap.org/~timsc/wms2/map.php?
+#
+# different forms for web access
+# must be html:<url>
+true;Yahoo Sat;html:http://josm.openstreetmap.de/wmsplugin/YahooDirect.html?
+false;OpenStreetMap;html:http://josm.openstreetmap.de/wmsplugin/OpenStreetMap.html?
+false;OpenCycleMap;html:http://josm.openstreetmap.de/wmsplugin/OpenCycleMap.html?
+false;TilesAtHome;html:http://josm.openstreetmap.de/wmsplugin/TilesAtHome.html?
+#
+#
+# only for Germany
+false;Streets NRW Geofabrik.de;http://tools.geofabrik.de/osmi/view/strassennrw/josmwms?
+# Terraserver USCG - High resolution maps
+false;Terraserver Topo;http://terraservice.net/ogcmap.ashx?version=1.1.1&request=GetMap&Layers=drg&styles=&format=image/jpeg&
+false;Terraserver Urban;http://terraservice.net/ogcmap.ashx?version=1.1.1&request=GetMap&Layers=urbanarea&styles=&format=image/jpeg&
+# only for Czech Republic
+false;Czech CUZK:KM;http://wms.cuzk.cz/wms.asp?service=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=kn,def_budovy,prehledky&FORMAT=image/png&TRANSPARENT=TRUE&
+false;Czech UHUL:ORTOFOTO;http://geoportal2.uhul.cz/cgi-bin/oprl.asp?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG:4326&LAYERS=Ortofoto_cb&STYLES=default&FORMAT=image/jpeg&TRANSPARENT=TRUE&
+#
+#
+# URLS must be designed to append arguments directly behind. So the URLS should either end with '?' or '&'
+# Following arguments are added: width, height, bbox, srs (projection method)
+# srs is only added when no srs is given already (In this case the projection is checked
+# and an error is issued when they mismatch).
+#
+# If more specific URL design is needed, then patterns are supported as well. If
+# patterns are found no other arguments are added:
+# {proj} is replaced by projection
+# {bbox} is replaced by bounding box using projected coordinates
+# {width} is requested display width
+# {height} is requested display height
diff --git a/wmsplugin/src/wmsplugin/GeorefImage.java b/wmsplugin/src/wmsplugin/GeorefImage.java
new file mode 100644
index 0000000..2e6f87b
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/GeorefImage.java
@@ -0,0 +1,161 @@
+package wmsplugin;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+
+public class GeorefImage implements Serializable {
+    public BufferedImage image = null;
+    private Image reImg = null;
+    private Dimension reImgHash = new Dimension(0, 0);
+    public EastNorth min, max;
+    public boolean downloadingStarted;
+    public boolean failed = false;
+
+    public GeorefImage(boolean downloadingStarted) {
+        this.downloadingStarted = downloadingStarted;
+    }
+
+    public boolean contains(EastNorth en, double dx, double dy) {
+        return min.east()+dx <= en.east() && en.east() <= max.east()+dx
+            && min.north()+dy <= en.north() && en.north() <= max.north()+dy;
+    }
+
+    public boolean isVisible(NavigatableComponent nc, double dx, double dy) {
+        EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
+        EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
+        Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
+        Graphics g = nc.getGraphics();
+
+        return (g.hitClip(minPt.x, maxPt.y,
+                maxPt.x - minPt.x, minPt.y - maxPt.y));
+    }
+
+    public boolean paint(Graphics g, NavigatableComponent nc, double dx, double dy) {
+        if (image == null || min == null || max == null) return false;
+
+        EastNorth mi = new EastNorth(min.east()+dx, min.north()+dy);
+        EastNorth ma = new EastNorth(max.east()+dx, max.north()+dy);
+        Point minPt = nc.getPoint(mi), maxPt = nc.getPoint(ma);
+
+        if(!isVisible(nc, dx, dy)){
+            return false;
+        }
+
+        // Width and height flicker about 2 pixels due to rounding errors, typically only 1
+        int width = Math.abs(maxPt.x-minPt.x);
+        int height = Math.abs(minPt.y-maxPt.y);
+        int diffx, diffy;
+        try {
+            diffx = reImgHash.width - width;
+            diffy = reImgHash.height - height;
+        } catch(Exception e) {
+            reImgHash = new Dimension(0, 0);
+            diffx = 99;
+            diffy = 99;
+        }
+        // This happens if you zoom outside the world
+        if(width == 0 || height == 0)
+            return false;
+
+        // We still need to re-render if the requested size is larger (otherwise we'll have black lines)
+        // If it's only up to two pixels smaller, just draw the old image, the errors are minimal
+        // but the performance improvements when moving are huge
+        // Zooming is still slow because the images need to be resized
+        if(diffx >= 0 && diffx <= 2 && diffy >= 0 && diffy <= 2 && reImg != null) {
+            /*g.setColor(Color.RED);
+              g.drawRect(minPt.x, minPt.y-height, width, height);*/
+            g.drawImage(reImg, minPt.x, maxPt.y, null);
+            return true;
+        }
+
+        boolean alphaChannel = Main.pref.getBoolean("wmsplugin.alpha_channel");
+
+        try {
+            if(reImg != null) reImg.flush();
+            long freeMem = Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory();
+            //System.out.println("Free Memory:           "+ (freeMem/1024/1024) +" MB");
+            // Notice that this value can get negative due to integer overflows
+            //System.out.println("Img Size:              "+ (width*height*3/1024/1024) +" MB");
+
+            int multipl = alphaChannel ? 4 : 3;
+            // This happens when requesting images while zoomed out and then zooming in
+            // Storing images this large in memory will certainly hang up JOSM. Luckily
+            // traditional rendering is as fast at these zoom levels, so it's no loss.
+            // Also prevent caching if we're out of memory soon
+            if(width > 2000 || height > 2000 || width*height*multipl > freeMem) {
+                fallbackDraw(g, image, minPt, maxPt);
+            } else {
+                // We haven't got a saved resized copy, so resize and cache it
+                reImg = new BufferedImage(width, height,
+                    alphaChannel
+                        ? BufferedImage.TYPE_INT_ARGB
+                        : BufferedImage.TYPE_3BYTE_BGR  // This removes alpha
+                    );
+                reImg.getGraphics().drawImage(image,
+                    0, 0, width, height, // dest
+                    0, 0, image.getWidth(null), image.getHeight(null), // src
+                    null);
+                reImg.getGraphics().dispose();
+
+                reImgHash.setSize(width, height);
+                /*g.setColor(Color.RED);
+                  g.drawRect(minPt.x, minPt.y-height, width, height);*/
+                g.drawImage(reImg, minPt.x, maxPt.y, null);
+            }
+        } catch(Exception e) {
+            fallbackDraw(g, image, minPt, maxPt);
+        }
+        return true;
+    }
+
+    private void fallbackDraw(Graphics g, Image img, Point min, Point max) {
+        if(reImg != null) reImg.flush();
+        reImg = null;
+        g.drawImage(img,
+            min.x, max.y, max.x, min.y, // dest
+            0, 0, img.getWidth(null), img.getHeight(null), // src
+            null);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        max = (EastNorth) in.readObject();
+        min = (EastNorth) in.readObject();
+        boolean hasImage = in.readBoolean();
+        if (hasImage)
+        	image = (BufferedImage) ImageIO.read(ImageIO.createImageInputStream(in));
+        else {
+        	in.readObject(); // read null from input stream
+        	image = null;
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeObject(max);
+        out.writeObject(min);
+        if(image == null) {
+        	out.writeBoolean(false);
+            out.writeObject(null);
+        } else {
+        	out.writeBoolean(true);
+            ImageIO.write(image, "png", ImageIO.createImageOutputStream(out));
+        }
+    }
+
+    public void flushedResizedCachedInstance() {
+        reImg = null;
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/Grabber.java b/wmsplugin/src/wmsplugin/Grabber.java
new file mode 100644
index 0000000..66f21fe
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/Grabber.java
@@ -0,0 +1,97 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.io.CacheFiles;
+
+abstract public class Grabber extends Thread {
+    protected ProjectionBounds b;
+    protected Projection proj;
+    protected double pixelPerDegree;
+    protected MapView mv;
+    protected WMSLayer layer;
+    protected GeorefImage image;
+    protected CacheFiles cache;
+
+    Grabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache)
+    {
+        if (b.min != null && b.max != null && WMSPlugin.doOverlap) { 
+            double eastSize =  b.max.east() - b.min.east(); 
+            double northSize =  b.max.north() - b.min.north(); 
+    
+            double eastCoef = WMSPlugin.overlapEast / 100.0; 
+            double northCoef = WMSPlugin.overlapNorth / 100.0; 
+             
+            this.b = new ProjectionBounds( new EastNorth(b.min.east(), 
+                                            b.min.north()), 
+                                 new EastNorth(b.max.east() + eastCoef * eastSize, 
+                                            b.max.north() + northCoef * northSize));             
+        } else 
+           this.b = b;
+
+        this.proj = Main.proj;
+        this.pixelPerDegree = layer.pixelPerDegree;
+        this.image = image;
+        this.mv = mv;
+        this.layer = layer;
+        this.cache = cache;
+
+    }
+
+    abstract void fetch() throws Exception; // the image fetch code
+
+    int width(){
+        return (int) ((b.max.north() - b.min.north()) * pixelPerDegree);
+    }
+    int height(){
+        return (int) ((b.max.east() - b.min.east()) * pixelPerDegree);
+    }
+
+    protected void grabError(Exception e){ // report error when grabing image
+        e.printStackTrace();
+
+        BufferedImage img = new BufferedImage(width(), height(), BufferedImage.TYPE_INT_ARGB);
+        Graphics g = img.getGraphics();
+        g.setColor(Color.RED);
+        g.fillRect(0, 0, width(), height());
+        Font font = g.getFont();
+        Font tempFont = font.deriveFont(Font.PLAIN).deriveFont(36.0f);
+        g.setFont(tempFont);
+        g.setColor(Color.BLACK);
+        g.drawString(tr("Exception occurred"), 10, height()/2);
+        image.image = img;
+        image.flushedResizedCachedInstance();
+        image.failed = true;
+        g.setFont(font);
+    }
+
+    protected void attempt(){ // try to fetch the image
+        int maxTries = 5; // n tries for every image
+        for (int i = 1; i <= maxTries; i++) {
+            try {
+                fetch();
+                break; // break out of the retry loop
+            } catch (Exception e) {
+                try { // sleep some time and then ask the server again
+                    Thread.sleep(random(1000, 2000));
+                } catch (InterruptedException e1) {}
+
+                if(i == maxTries) grabError(e);
+            }
+        }
+    }
+
+    public static int random(int min, int max) {
+        return (int)(Math.random() * ((max+1)-min) ) + min;
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/HTMLGrabber.java b/wmsplugin/src/wmsplugin/HTMLGrabber.java
new file mode 100644
index 0000000..e201506
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/HTMLGrabber.java
@@ -0,0 +1,51 @@
+package wmsplugin;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.io.CacheFiles;
+
+public class HTMLGrabber extends WMSGrabber {
+    HTMLGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
+        super(b, image, mv, layer, cache);
+        this.baseURL = layer.baseURL.replaceFirst("html:", "");
+    }
+
+    @Override
+    protected BufferedImage grab(URL url) throws IOException {
+        String urlstring = url.toExternalForm();
+
+        System.out.println("Grabbing HTML " + url);
+
+        BufferedImage cached = cache.getImg(urlstring);
+        if(cached != null) return cached;
+
+        ArrayList<String> cmdParams = new ArrayList<String>();
+        StringTokenizer st = new StringTokenizer(MessageFormat.format(
+        Main.pref.get("wmsplugin.browser", "webkit-image {0}"), urlstring));
+        while( st.hasMoreTokens() )
+            cmdParams.add(st.nextToken());
+
+        ProcessBuilder builder = new ProcessBuilder( cmdParams);
+
+        Process browser;
+        try {
+            browser = builder.start();
+        } catch(IOException ioe) {
+            throw new IOException( "Could not start browser. Please check that the executable path is correct.\n" + ioe.getMessage() );
+        }
+
+        BufferedImage img = ImageIO.read(browser.getInputStream());
+        cache.saveImg(urlstring, img);
+        return img;
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/Help_WMSmenuAction.java b/wmsplugin/src/wmsplugin/Help_WMSmenuAction.java
new file mode 100644
index 0000000..f02f4fc
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/Help_WMSmenuAction.java
@@ -0,0 +1,60 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import javax.swing.*;
+import org.openstreetmap.josm.actions.JosmAction;
+
+
+
+public class Help_WMSmenuAction extends JosmAction {
+
+    /**
+     *
+     */
+
+
+    public Help_WMSmenuAction() {
+        //super("Help / About");
+        super(tr("help"), "help", tr("Help / About"), null, false);
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        //todo - put this into a txt file?
+          String helptext =
+            tr("You can add, edit and delete WMS entries in the WMSplugin Preference Tab - "  +
+            "these will then show up in the WMS menu.\n\n"+
+
+            "You can also do this manually in the Advanced Preferences, using the following schema:\n"+
+            "wmsplugin.url.1.name=Landsat\n"+
+            "wmsplugin.url.1.url=http://onearth.jpl.nasa.gov....\n"+
+            "wmsplugin.url.2.name=NPE Maps... etc\n\n"+
+
+            "Full WMS URL input format example (landsat)\n"+
+            "http://onearth.jpl.nasa.gov/wms.cgi?request=GetMap&\n"+
+            "layers=global_mosaic&styles=&srs=EPSG:4326&format=image/jpeg\n\n"+
+
+            "For Metacarta's Map Rectifier http://labs.metacarta.com/rectifier/ , you only need to input the relevant 'id'.\n" +
+            "To add a Metacarta Map Rectifier menu item, manually create the URL like in this example, " +
+            "replacing 73 with your image id:\n" +
+            "http://labs.metacarta.com/rectifier/wms.cgi?id=73\n" +
+            "&srs=EPSG:4326&Service=WMS&Version=1.1.0&Request=GetMap&format=image/png\n\n" +
+
+            "Note: Make sure the image is suitable, copyright-wise, if in doubt, don't use.");
+
+        JTextPane tp = new JTextPane();
+          JScrollPane js = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                  JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+          js.getViewport().add(tp);
+          JFrame jf = new JFrame(tr("WMS Plugin Help"));
+          jf.getContentPane().add(js);
+          jf.pack();
+          jf.setSize(400,500);
+          jf.setVisible(true);
+          tp.setText(helptext);
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java b/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java
new file mode 100644
index 0000000..b588ac9
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/Map_Rectifier_WMSmenuAction.java
@@ -0,0 +1,246 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.UrlLabel;
+
+public class Map_Rectifier_WMSmenuAction extends JosmAction {
+    /**
+     * Class that bundles all required information of a rectifier service
+     */
+    public class rectifierService {
+        private final String name;
+        private final String url;
+        private final String wmsUrl;
+        private final Pattern urlRegEx;
+        private final Pattern idValidator;
+        public JRadioButton btn;
+        /**
+         * @param name: Name of the rectifing service
+         * @param url: URL to the service where users can register, upload, etc.
+         * @param wmsUrl: URL to the WMS server where JOSM will grab the images. Insert __s__ where the ID should be placed
+         * @param urlRegEx: a regular expression that determines if a given URL is one of the service and returns the WMS id if so
+         * @param idValidator: regular expression that checks if a given ID is syntactically valid
+         */
+        public rectifierService(String name, String url, String wmsUrl, String urlRegEx, String idValidator) {
+            this.name = name;
+            this.url = url;
+            this.wmsUrl = wmsUrl;
+            this.urlRegEx = Pattern.compile(urlRegEx);
+            this.idValidator = Pattern.compile(idValidator);
+        }
+
+        public boolean isSelected() {
+            return btn.isSelected();
+        }
+    }
+
+    /**
+     * List of available rectifier services. May be extended from the outside
+     */
+    public ArrayList<rectifierService> services = new ArrayList<rectifierService>();
+
+    public Map_Rectifier_WMSmenuAction() {
+        super(tr("Rectified Image..."),
+                "OLmarker",
+                tr("Download Rectified Images From Various Services"),
+                Shortcut.registerShortcut("wms:rectimg",
+                        tr("WMS: {0}", tr("Rectified Image...")),
+                        KeyEvent.VK_R,
+                        Shortcut.GROUP_NONE),
+                        true
+        );
+
+        // Add default services
+        services.add(
+            new rectifierService("Metacarta Map Rectifier",
+                "http://labs.metacarta.com/rectifier/",
+                "http://labs.metacarta.com/rectifier/wms.cgi?id=__s__&srs=EPSG:4326"
+                + "&Service=WMS&Version=1.1.0&Request=GetMap&format=image/png&",
+                // This matches more than the "classic" WMS link, so users can pretty much
+                // copy any link as long as it includes the ID
+                "labs\\.metacarta\\.com/(?:.*?)(?:/|=)([0-9]+)(?:\\?|/|\\.|$)",
+                "^[0-9]+$")
+        );
+        services.add(
+            // TODO: Change all links to mapwarper.net once the project has moved.
+            // The RegEx already matches the new URL and old URLs will be forwarded
+            // to make the transition as smooth as possible for the users
+            new rectifierService("Geothings Map Warper",
+                "http://warper.geothings.net/",
+                "http://warper.geothings.net/maps/wms/__s__?request=GetMap&version=1.1.1"
+                + "&styles=&format=image/png&srs=epsg:4326&exceptions=application/vnd.ogc.se_inimage&",
+                // This matches more than the "classic" WMS link, so users can pretty much
+                // copy any link as long as it includes the ID
+                "(?:mapwarper\\.net|warper\\.geothings\\.net)/(?:.*?)/([0-9]+)(?:\\?|/|\\.|$)",
+                "^[0-9]+$")
+        );
+
+        // This service serves the purpose of "just this once" without forcing the user
+        // to commit the link to the preferences
+
+        // Clipboard content gets trimmed, so matching whitespace only ensures that this
+        // service will never be selected automatically.
+        services.add(new rectifierService(tr("Custom WMS Link"), "", "", "^\\s+$", ""));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.add(new JLabel(tr("Supported Rectifier Services:")), GBC.eol());
+
+        JTextField tfWmsUrl = new JTextField(30);
+
+        String clip = getClipboardContents();
+        ButtonGroup group = new ButtonGroup();
+
+        JRadioButton firstBtn = null;
+        for(rectifierService s : services) {
+            JRadioButton serviceBtn = new JRadioButton(s.name);
+            if(firstBtn == null)
+                firstBtn = serviceBtn;
+            // Checks clipboard contents against current service if no match has been found yet.
+            // If the contents match, they will be inserted into the text field and the corresponding
+            // service will be pre-selected.
+            if(!clip.equals("") && tfWmsUrl.getText().equals("")
+                    && (s.urlRegEx.matcher(clip).find() || s.idValidator.matcher(clip).matches())) {
+                serviceBtn.setSelected(true);
+                tfWmsUrl.setText(clip);
+            }
+            s.btn = serviceBtn;
+            group.add(serviceBtn);
+            if(!s.url.equals("")) {
+                panel.add(serviceBtn, GBC.std());
+                panel.add(new UrlLabel(s.url, tr("Visit Homepage")), GBC.eol().anchor(GBC.EAST));
+            } else
+                panel.add(serviceBtn, GBC.eol().anchor(GBC.WEST));
+        }
+
+        // Fallback in case no match was found
+        if(tfWmsUrl.getText().equals("") && firstBtn != null)
+            firstBtn.setSelected(true);
+
+        panel.add(new JLabel(tr("WMS URL or Image ID:")), GBC.eol());
+        panel.add(tfWmsUrl, GBC.eol().fill(GBC.HORIZONTAL));
+
+        ExtendedDialog diag = new ExtendedDialog(Main.parent,
+                tr("Add Rectified Image"),
+
+                new String[] {tr("Add Rectified Image"), tr("Cancel")});
+        diag.setContent(panel);
+        diag.setButtonIcons(new String[] {"OLmarker.png", "cancel.png"});
+
+        // This repeatedly shows the dialog in case there has been an error.
+        // The loop is break;-ed if the users cancels
+        outer: while(true) {
+        	diag.showDialog();
+            int answer = diag.getValue();
+            // Break loop when the user cancels
+            if(answer != 1)
+                break;
+
+            String text = tfWmsUrl.getText().trim();
+            // Loop all services until we find the selected one
+            for(rectifierService s : services) {
+                if(!s.isSelected())
+                    continue;
+
+                // We've reached the custom WMS URL service
+                // Just set the URL and hope everything works out
+                if(s.wmsUrl.equals("")) {
+                    addWMSLayer(s.name + " (" + text + ")", text);
+                    break outer;
+                }
+
+                // First try to match if the entered string as an URL
+                Matcher m = s.urlRegEx.matcher(text);
+                if(m.find()) {
+                    String id = m.group(1);
+                    String newURL = s.wmsUrl.replaceAll("__s__", id);
+                    String title = s.name + " (" + id + ")";
+                    addWMSLayer(title, newURL);
+                    break outer;
+                }
+                // If not, look if it's a valid ID for the selected service
+                if(s.idValidator.matcher(text).matches()) {
+                    String newURL = s.wmsUrl.replaceAll("__s__", text);
+                    String title = s.name + " (" + text + ")";
+                    addWMSLayer(title, newURL);
+                    break outer;
+                }
+
+                // We've found the selected service, but the entered string isn't suitable for
+                // it. So quit checking the other radio buttons
+                break;
+            }
+
+            // and display an error message. The while(true) ensures that the dialog pops up again
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Couldn't match the entered link or id to the selected service. Please try again."),
+                    tr("No valid WMS URL or id"),
+                    JOptionPane.ERROR_MESSAGE);
+            diag.setVisible(true);
+        }
+    }
+
+    /**
+     * Adds a WMS Layer with given title and URL
+     * @param title: Name of the layer as it will shop up in the layer manager
+     * @param url: URL to the WMS server
+     * @param cookies: Cookies to send with each image request (Format: josm=is; very=cool)
+     */
+    private void addWMSLayer(String title, String url, String cookies) {
+        WMSLayer wmsLayer = new WMSLayer(title, url, cookies);
+        Main.main.addLayer(wmsLayer);
+    }
+
+    /**
+     * Adds a WMS Layer with given title and URL
+     * @param title: Name of the layer as it will shop up in the layer manager
+     * @param url: URL to the WMS server
+     */
+    private void addWMSLayer(String title, String url) {
+        addWMSLayer(title, url, "");
+    }
+
+    /**
+     * Helper function that extracts a String from the Clipboard if available.
+     * Returns an empty String otherwise
+     * @return String Clipboard contents if available
+     */
+    private String getClipboardContents() {
+        String result = "";
+        Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
+
+        if(contents == null || !contents.isDataFlavorSupported(DataFlavor.stringFlavor))
+            return "";
+
+        try {
+            result = (String)contents.getTransferData(DataFlavor.stringFlavor);
+        } catch(Exception ex) {
+            return "";
+        }
+        return result.trim();
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/WMSAdjustAction.java b/wmsplugin/src/wmsplugin/WMSAdjustAction.java
new file mode 100644
index 0000000..03b2a10
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSAdjustAction.java
@@ -0,0 +1,220 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.GridBagLayout;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.List;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+
+public class WMSAdjustAction extends MapMode implements
+    MouseListener, MouseMotionListener{
+
+    GeorefImage selectedImage;
+    boolean mouseDown;
+    EastNorth prevEastNorth;
+    private WMSLayer adjustingLayer;
+
+    public WMSAdjustAction(MapFrame mapFrame) {
+        super(tr("Adjust WMS"), "adjustwms",
+                        tr("Adjust the position of the selected WMS layer"), mapFrame,
+                        ImageProvider.getCursor("normal", "move"));
+    }
+
+    
+    
+    @Override public void enterMode() {
+        super.enterMode();       
+        if (!hasWMSLayersToAdjust()) {
+        	warnNoWMSLayers();
+        	return;
+        }
+        List<WMSLayer> wmsLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
+        if (wmsLayers.size() == 1) {
+        	adjustingLayer = wmsLayers.get(0);
+        } else {
+        	adjustingLayer = (WMSLayer)askAdjustLayer(Main.map.mapView.getLayersOfType(WMSLayer.class));
+        }
+        if (adjustingLayer == null)
+        	return;
+        if (!adjustingLayer.isVisible()) {
+        	adjustingLayer.setVisible(true);
+        }
+        Main.map.mapView.addMouseListener(this);
+        Main.map.mapView.addMouseMotionListener(this);
+    }
+
+    @Override public void exitMode() {
+        super.exitMode();
+        Main.map.mapView.removeMouseListener(this);
+        Main.map.mapView.removeMouseMotionListener(this);
+        adjustingLayer = null;
+    }
+
+    @Override public void mousePressed(MouseEvent e) {
+        if (e.getButton() != MouseEvent.BUTTON1)
+            return;
+
+        if (adjustingLayer.isVisible()) {
+            prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY());
+            selectedImage = adjustingLayer.findImage(prevEastNorth);
+            if(selectedImage!=null) {
+                Main.map.mapView.setCursor
+                    (Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            }
+        }
+    }
+
+    @Override public void mouseDragged(MouseEvent e) {
+        if(selectedImage!=null) {
+            EastNorth eastNorth=
+                    Main.map.mapView.getEastNorth(e.getX(),e.getY());
+            adjustingLayer.displace(
+                eastNorth.east()-prevEastNorth.east(),
+                eastNorth.north()-prevEastNorth.north()
+            );
+            prevEastNorth = eastNorth;
+            Main.map.mapView.repaint();
+        }
+    }
+
+    @Override public void mouseReleased(MouseEvent e) {
+        Main.map.mapView.repaint();
+        Main.map.mapView.setCursor(Cursor.getDefaultCursor());
+        selectedImage = null;
+        prevEastNorth = null;
+    }
+    
+    @Override
+    public void mouseEntered(MouseEvent e) {
+    }
+    
+    @Override
+    public void mouseExited(MouseEvent e) {
+    }
+    
+    @Override
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    @Override public void mouseClicked(MouseEvent e) {
+    }
+
+    // This only makes the buttons look disabled, but since no keyboard shortcut is
+    // provided there aren't any other means to activate this tool
+    @Override public boolean layerIsSupported(Layer l) {
+        return (l instanceof WMSLayer) && l.isVisible();
+    }
+    
+    /**
+    * the list cell renderer used to render layer list entries
+    * 
+    */
+   static public class LayerListCellRenderer extends DefaultListCellRenderer {
+
+       protected boolean isActiveLayer(Layer layer) {
+           if (Main.map == null)
+               return false;
+           if (Main.map.mapView == null)
+               return false;
+           return Main.map.mapView.getActiveLayer() == layer;
+       }
+
+       @Override
+       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+               boolean cellHasFocus) {
+           Layer layer = (Layer) value;
+           JLabel label = (JLabel) super.getListCellRendererComponent(list, layer.getName(), index, isSelected,
+                   cellHasFocus);
+           Icon icon = layer.getIcon();
+           label.setIcon(icon);
+           label.setToolTipText(layer.getToolTipText());
+           return label;
+       }
+   }
+
+   /**
+    * Prompts the user with a list of WMS layers which can be adjusted  
+    * 
+    * @param adjustableLayers the list of adjustable layers 
+    * @return  the selected layer; null, if no layer was selected 
+    */
+   protected Layer askAdjustLayer(List<? extends Layer> adjustableLayers) {
+       JComboBox layerList = new JComboBox();
+       layerList.setRenderer(new LayerListCellRenderer());
+       layerList.setModel(new DefaultComboBoxModel(adjustableLayers.toArray()));
+       layerList.setSelectedIndex(0);
+   
+       JPanel pnl = new JPanel();
+       pnl.setLayout(new GridBagLayout());
+       pnl.add(new JLabel(tr("Please select the WMS layer to adjust.")), GBC.eol());
+       pnl.add(layerList, GBC.eol());
+   
+       ExtendedDialog diag = new ExtendedDialog(
+    		   Main.parent, 
+    		   tr("Select WMS layer"), 
+    		   new String[] { tr("Start adjusting"),tr("Cancel") }
+    		   );
+       diag.setContent(pnl);
+       diag.setButtonIcons(new String[] { "mapmode/adjustwms", "cancel" });
+       diag.showDialog();
+       int decision = diag.getValue();
+       if (decision != 1)
+           return null;
+       Layer adjustLayer = (Layer) layerList.getSelectedItem();
+       return adjustLayer;
+   }
+
+   /**
+    * Displays a warning message if there are no WMS layers to adjust 
+    * 
+    */
+   protected void warnNoWMSLayers() {
+       JOptionPane.showMessageDialog(
+    		   Main.parent,
+               tr("There are currently no WMS layer to adjust."),
+               tr("No layers to adjust"), 
+               JOptionPane.WARNING_MESSAGE
+       );
+   }
+   
+   /**
+    * Replies true if there is at least one WMS layer 
+    * 
+    * @return true if there is at least one WMS layer
+    */
+   protected boolean hasWMSLayersToAdjust() {
+	   if (Main.map == null) return false;
+	   if (Main.map.mapView == null) return false;
+	   return ! Main.map.mapView.getLayersOfType(WMSLayer.class).isEmpty();
+   }
+
+
+
+	@Override
+	protected void updateEnabledState() {
+		setEnabled(hasWMSLayersToAdjust());
+	}   
+}
diff --git a/wmsplugin/src/wmsplugin/WMSDownloadAction.java b/wmsplugin/src/wmsplugin/WMSDownloadAction.java
new file mode 100644
index 0000000..f7eff50
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSDownloadAction.java
@@ -0,0 +1,33 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+
+public class WMSDownloadAction extends JosmAction {
+
+    private final WMSInfo info;
+
+    public WMSDownloadAction(WMSInfo info) {
+        super(info.name, "wmsmenu", tr("Download WMS tile from {0}",info.name), null, false);
+        putValue("toolbar", "wms_" + info.name);
+        this.info = info;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        //System.out.println(info.url);
+
+        WMSLayer wmsLayer = new WMSLayer(info.name, info.url, info.cookies);
+        Main.main.addLayer(wmsLayer);
+    }
+
+    public static WMSLayer getLayer(WMSInfo info) {
+        // FIXME: move this to WMSPlugin/WMSInfo/preferences.
+        WMSLayer wmsLayer = new WMSLayer(info.name, info.url, info.cookies);
+        Main.main.addLayer(wmsLayer);
+        return wmsLayer;
+    }
+};
diff --git a/wmsplugin/src/wmsplugin/WMSGrabber.java b/wmsplugin/src/wmsplugin/WMSGrabber.java
new file mode 100644
index 0000000..eecb3c9
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSGrabber.java
@@ -0,0 +1,188 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.imageio.ImageIO;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.Mercator;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.io.CacheFiles;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.io.ProgressInputStream;
+
+
+public class WMSGrabber extends Grabber {
+	public static boolean isUrlWithPatterns(String url) {
+		return  url != null && url.contains("{") && url.contains("}");
+	}
+	
+    protected String baseURL;
+    private final boolean urlWithPatterns;
+
+    WMSGrabber(ProjectionBounds b, GeorefImage image, MapView mv, WMSLayer layer, CacheFiles cache) {
+        super(b, image, mv, layer, cache);
+        this.baseURL = layer.baseURL;
+        /* URL containing placeholders? */
+        urlWithPatterns = isUrlWithPatterns(baseURL);
+    }
+
+    public void run() {
+        attempt();
+        mv.repaint();
+    }
+
+    @Override
+    void fetch() throws Exception{
+        URL url = null;
+        try {
+            url = getURL(
+                b.min.east(), b.min.north(),
+                b.max.east(), b.max.north(),
+                width(), height());
+
+            image.min = b.min;
+            image.max = b.max;
+
+            if(image.isVisible(mv, layer.getDx(), layer.getDy())) { //don't download, if the image isn't visible already
+                image.image = grab(url);
+                image.flushedResizedCachedInstance();
+            }
+            image.downloadingStarted = false;
+        } catch(Exception e) {
+        	e.printStackTrace();
+            throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
+        }
+    }
+
+    public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
+            new DecimalFormatSymbols(Locale.US));
+
+    protected URL getURL(double w, double s,double e,double n,
+            int wi, int ht) throws MalformedURLException {
+        String myProj = Main.proj.toCode();
+        if(Main.proj instanceof Mercator) // don't use mercator code directly
+        {
+            LatLon sw = Main.proj.eastNorth2latlon(new EastNorth(w, s));
+            LatLon ne = Main.proj.eastNorth2latlon(new EastNorth(e, n));
+            myProj = "EPSG:4326";
+            s = sw.lat();
+            w = sw.lon();
+            n = ne.lat();
+            e = ne.lon();
+        }
+
+        String str = baseURL;
+        String bbox = latLonFormat.format(w) + ","
+                           + latLonFormat.format(s) + ","
+                           + latLonFormat.format(e) + ","
+                           + latLonFormat.format(n);
+
+        if (urlWithPatterns) {
+            str = str.replaceAll("\\{proj\\}", myProj)
+            .replaceAll("\\{bbox\\}", bbox)
+            .replaceAll("\\{width\\}", String.valueOf(wi))
+            .replaceAll("\\{height\\}", String.valueOf(ht));
+        } else {
+            str += "bbox=" + bbox
+                + getProjection(baseURL, false)
+                + "&width=" + wi + "&height=" + ht;
+        	if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
+        		System.out.println(tr("Warning: The base URL ''{0}'' for a WMS service doesn't have a trailing '&' or a trailing '?'.", baseURL));
+        		System.out.println(tr("Warning: Fetching WMS tiles is likely to fail. Please check you preference settings."));
+        		System.out.println(tr("Warning: The complete URL is ''{0}''.", str));
+        	}
+        }
+        return new URL(str.replace(" ", "%20"));
+    }
+
+    static public String getProjection(String baseURL, Boolean warn)
+    {
+        String projname = Main.proj.toCode();
+        if(Main.proj instanceof Mercator) // don't use mercator code
+            projname = "EPSG:4326";
+        String res = "";
+        try
+        {
+            Matcher m = Pattern.compile(".*srs=([a-z0-9:]+).*").matcher(baseURL.toLowerCase());
+            if(m.matches())
+            {
+                projname = projname.toLowerCase();
+                if(!projname.equals(m.group(1)) && warn)
+                {
+                    JOptionPane.showMessageDialog(Main.parent,
+                    tr("The projection ''{0}'' in URL and current projection ''{1}'' mismatch.\n"
+                    + "This may lead to wrong coordinates.",
+                    m.group(1), projname),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+                }
+            }
+            else
+                res ="&srs="+projname;
+        }
+        catch(Exception e)
+        {
+        }
+        return res;
+    }
+
+    protected BufferedImage grab(URL url) throws IOException, OsmTransferException {
+        BufferedImage cached = cache.getImg(url.toString());
+        if(cached != null) return cached;
+
+        System.out.println("Grabbing WMS " + url);
+
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        if(layer.cookies != null && !layer.cookies.equals(""))
+            conn.setRequestProperty("Cookie", layer.cookies);
+        conn.setConnectTimeout(Main.pref.getInteger("wmsplugin.timeout.connect", 30) * 1000);
+        conn.setReadTimeout(Main.pref.getInteger("wmsplugin.timeout.read", 30) * 1000);
+
+        String contentType = conn.getHeaderField("Content-Type");
+        if( conn.getResponseCode() != 200
+                || contentType != null && !contentType.startsWith("image") ) {
+            throw new IOException(readException(conn));
+        }
+
+        InputStream is = new ProgressInputStream(conn, null);
+        BufferedImage img = ImageIO.read(is);
+        is.close();
+
+        cache.saveImg(url.toString(), img);
+        return img;
+    }
+
+    protected String readException(URLConnection conn) throws IOException {
+        StringBuilder exception = new StringBuilder();
+        InputStream in = conn.getInputStream();
+        BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+        String line = null;
+        while( (line = br.readLine()) != null) {
+            exception.append(line);
+            exception.append('\n');
+        }
+        return exception.toString();
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/WMSInfo.java b/wmsplugin/src/wmsplugin/WMSInfo.java
new file mode 100644
index 0000000..0469c64
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSInfo.java
@@ -0,0 +1,42 @@
+package wmsplugin;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * Class that stores info about a WMS server.
+ *
+ * @author Frederik Ramm <frederik at remote.org>
+ */
+public class WMSInfo implements Comparable<WMSInfo> {
+
+    String name;
+    String url;
+    String cookies;
+    int prefid;
+
+    public WMSInfo(String name, String url, int prefid) {
+        this(name, url, null, prefid);
+    }
+
+    public WMSInfo(String name, String url, String cookies, int prefid) {
+        this.name=name;
+        this.url=url;
+        this.cookies=cookies;
+        this.prefid=prefid;
+    }
+
+    public void save() {
+        Main.pref.put("wmsplugin.url." + prefid + ".name", name);
+        Main.pref.put("wmsplugin.url." + prefid + ".url", url);
+    }
+
+    public int compareTo(WMSInfo in)
+    {
+        Integer i = name.compareTo(in.name);
+        if(i == 0)
+            i = url.compareTo(in.url);
+        if(i == 0)
+            i = prefid-in.prefid;
+        return i;
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/WMSLayer.java b/wmsplugin/src/wmsplugin/WMSLayer.java
new file mode 100644
index 0000000..f514def
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSLayer.java
@@ -0,0 +1,428 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFileChooser;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JSeparator;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.DiskAccessAction;
+import org.openstreetmap.josm.actions.SaveActionBase;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.io.CacheFiles;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This is a layer that grabs the current screen from an WMS server. The data
+ * fetched this way is tiled and managerd to the disc to reduce server load.
+ */
+public class WMSLayer extends Layer {
+	protected static final Icon icon =
+		new ImageIcon(Toolkit.getDefaultToolkit().createImage(WMSPlugin.class.getResource("/images/wms_small.png")));
+
+	public int messageNum = 5; //limit for messages per layer
+	protected MapView mv;
+	protected String resolution;
+	protected boolean stopAfterPaint = false;
+	protected int ImageSize = 500;
+	protected int dax = 10;
+	protected int day = 10;
+	protected int minZoom = 3;
+	protected double dx = 0.0;
+	protected double dy = 0.0;
+	protected double pixelPerDegree;
+	protected GeorefImage[][] images = new GeorefImage[dax][day];
+	JCheckBoxMenuItem startstop = new JCheckBoxMenuItem(tr("Automatic downloading"), true);
+	protected JCheckBoxMenuItem alphaChannel = new JCheckBoxMenuItem(new ToggleAlphaAction());
+	protected String baseURL;
+	protected String cookies;
+	protected final int serializeFormatVersion = 5;
+
+	private ExecutorService executor = null;
+
+	/** set to true if this layer uses an invalid base url */
+	private boolean usesInvalidUrl = false;
+	/** set to true if the user confirmed to use an potentially invalid WMS base url */
+	private boolean isInvalidUrlConfirmed = false;
+	
+	public WMSLayer() {
+		this(tr("Blank Layer"), null, null);
+		initializeImages();
+		mv = Main.map.mapView;
+	}
+
+	public WMSLayer(String name, String baseURL, String cookies) {
+		super(name);
+		alphaChannel.setSelected(Main.pref.getBoolean("wmsplugin.alpha_channel"));
+		setBackgroundLayer(true); /* set global background variable */ 
+		initializeImages();
+		this.baseURL = baseURL;
+		this.cookies = cookies;
+		WMSGrabber.getProjection(baseURL, true);
+		mv = Main.map.mapView;
+		resolution = mv.getDist100PixelText();
+		pixelPerDegree = getPPD();
+
+		executor = Executors.newFixedThreadPool(3);
+		if (!baseURL.startsWith("html:") && !WMSGrabber.isUrlWithPatterns(baseURL)) {
+			if (!(baseURL.endsWith("&") || baseURL.endsWith("?"))) {
+				if (!confirmMalformedUrl(baseURL)) {
+					System.out.println(tr("Warning: WMS layer deactivated because of malformed base url ''{0}''", baseURL));
+					usesInvalidUrl = true;
+					setName(getName() + tr("(deactivated)"));
+					return;
+				} else {
+					isInvalidUrlConfirmed = true;
+				}
+			}
+		}
+	}
+
+	public double getDx(){
+		return dx;
+	}
+
+	public double getDy(){
+		return dy;
+	}
+
+	@Override
+	public void destroy() {	
+		try {
+			executor.shutdownNow();
+			// Might not be initalized, so catch NullPointer as well
+		} catch(Exception x) {
+			x.printStackTrace();
+		}
+	}
+
+	public double getPPD(){
+		ProjectionBounds bounds = mv.getProjectionBounds();
+		return mv.getWidth() / (bounds.max.east() - bounds.min.east());
+	}
+
+	public void initializeImages() {
+		images = new GeorefImage[dax][day];
+		for(int x = 0; x<dax; ++x) {
+			for(int y = 0; y<day; ++y) {
+				images[x][y]= new GeorefImage(false);
+			}
+		}
+	}
+
+	@Override public Icon getIcon() {
+		return icon;
+	}
+
+	@Override public String getToolTipText() {
+		if(startstop.isSelected())
+			return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolution);
+		else
+			return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolution);
+	}
+
+	@Override public boolean isMergable(Layer other) {
+		return false;
+	}
+
+	@Override public void mergeFrom(Layer from) {
+	}
+
+	private ProjectionBounds XYtoBounds (int x, int y) {
+		return new ProjectionBounds(
+				new EastNorth(      x * ImageSize / pixelPerDegree,       y * ImageSize / pixelPerDegree),
+				new EastNorth((x + 1) * ImageSize / pixelPerDegree, (y + 1) * ImageSize / pixelPerDegree));
+	}
+
+	private int modulo (int a, int b) {
+		return a % b >= 0 ? a%b : a%b+b;
+	}
+
+	@Override public void paint(Graphics g, final MapView mv) {
+		if(baseURL == null) return;
+		if (usesInvalidUrl && !isInvalidUrlConfirmed) return;
+
+		if( !startstop.isSelected() || (pixelPerDegree / getPPD() > minZoom) ){ //don't download when it's too outzoomed
+			for(int x = 0; x<dax; ++x) {
+				for(int y = 0; y<day; ++y) {
+					images[modulo(x,dax)][modulo(y,day)].paint(g, mv, dx, dy);
+				}
+			}
+		} else {
+			downloadAndPaintVisible(g, mv);
+		}
+	}
+
+	public void displace(double dx, double dy) {
+		this.dx += dx;
+		this.dy += dy;
+	}
+
+	protected boolean confirmMalformedUrl(String url) {
+		if (isInvalidUrlConfirmed)
+			return true;
+		String msg  = tr("<html>The base URL<br>"
+				        + "''{0}''<br>"
+				        + "for this WMS layer does neither end with a ''&'' nor with a ''?''.<br>"
+				        + "This is likely to lead to invalid WMS request. You should check your<br>"
+				        + "preference settings.<br>"
+				        + "Do you want to fetch WMS tiles anyway?",				        
+				        url);
+		String [] options = new String[] {
+			tr("Yes, fetch images"),
+			tr("No, abort")
+		};
+		int ret = JOptionPane.showOptionDialog(
+				Main.parent, 
+				msg,
+				tr("Invalid URL?"),
+				JOptionPane.YES_NO_OPTION, 
+				JOptionPane.WARNING_MESSAGE, 
+				null, 
+				options, options[1]
+		);
+		switch(ret) {
+		case JOptionPane.YES_OPTION: return true;
+		default: return false;
+		}
+	}
+	protected void downloadAndPaintVisible(Graphics g, final MapView mv){
+		if (usesInvalidUrl)
+			return;
+		ProjectionBounds bounds = mv.getProjectionBounds();
+		int bminx= (int)Math.floor (((bounds.min.east() - dx) * pixelPerDegree) / ImageSize );
+		int bminy= (int)Math.floor (((bounds.min.north() - dy) * pixelPerDegree) / ImageSize );
+		int bmaxx= (int)Math.ceil  (((bounds.max.east() - dx) * pixelPerDegree) / ImageSize );
+		int bmaxy= (int)Math.ceil  (((bounds.max.north() - dy) * pixelPerDegree) / ImageSize );
+
+		if((bmaxx - bminx > dax) || (bmaxy - bminy > day)){
+			JOptionPane.showMessageDialog(
+					Main.parent,
+					tr("The requested area is too big. Please zoom in a little, or change resolution"),
+					tr("Error"),
+					JOptionPane.ERROR_MESSAGE
+			);
+			return;
+		}		
+		
+		for(int x = bminx; x<bmaxx; ++x) {
+			for(int y = bminy; y<bmaxy; ++y){
+				GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
+				g.drawRect(x, y, dax, bminy);
+				if(!img.paint(g, mv, dx, dy) && !img.downloadingStarted){
+					img.downloadingStarted = true;
+					img.image = null;
+					img.flushedResizedCachedInstance();
+					Grabber gr = WMSPlugin.getGrabber(XYtoBounds(x,y), img, mv, this);
+					gr.setPriority(1);
+					executor.submit(gr);
+				}
+			}
+		}
+	}
+
+	@Override public void visitBoundingBox(BoundingXYVisitor v) {
+		for(int x = 0; x<dax; ++x) {
+			for(int y = 0; y<day; ++y)
+				if(images[x][y].image!=null){
+					v.visit(images[x][y].min);
+					v.visit(images[x][y].max);
+				}
+		}
+	}
+
+	@Override public Object getInfoComponent() {
+		return getToolTipText();
+	}
+
+	@Override public Component[] getMenuEntries() {
+		return new Component[]{
+				new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
+				new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
+				new JSeparator(),
+				new JMenuItem(new LoadWmsAction()),
+				new JMenuItem(new SaveWmsAction()),
+				new JSeparator(),
+				startstop,
+				alphaChannel,
+				new JMenuItem(new ChangeResolutionAction()),
+				new JMenuItem(new ReloadErrorTilesAction()),
+				new JMenuItem(new DownloadAction()),
+				new JSeparator(),
+				new JMenuItem(new LayerListPopup.InfoAction(this))
+		};
+	}
+
+	public GeorefImage findImage(EastNorth eastNorth) {
+		for(int x = 0; x<dax; ++x) {
+			for(int y = 0; y<day; ++y)
+				if(images[x][y].image!=null && images[x][y].min!=null && images[x][y].max!=null)
+					if(images[x][y].contains(eastNorth, dx, dy))
+						return images[x][y];
+		}
+		return null;
+	}
+
+	public class DownloadAction extends AbstractAction {
+		public DownloadAction() {
+			super(tr("Download visible tiles"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			downloadAndPaintVisible(mv.getGraphics(), mv);
+		}
+	}
+
+	public class ChangeResolutionAction extends AbstractAction {
+		public ChangeResolutionAction() {
+			super(tr("Change resolution"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			initializeImages();
+			resolution = mv.getDist100PixelText();
+			pixelPerDegree = getPPD();
+			mv.repaint();
+		}
+	}
+
+	public class ReloadErrorTilesAction extends AbstractAction {
+		public ReloadErrorTilesAction() {
+			super(tr("Reload erroneous tiles"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			// Delete small files, because they're probably blank tiles.
+			// See https://josm.openstreetmap.de/ticket/2307
+			WMSPlugin.cache.customCleanUp(CacheFiles.CLEAN_SMALL_FILES, 2048);
+
+			for (int x = 0; x < dax; ++x) {
+				for (int y = 0; y < day; ++y) {
+					GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
+					if(img.failed){
+						img.image = null;
+						img.flushedResizedCachedInstance();
+						img.downloadingStarted = false;
+						img.failed = false;
+						mv.repaint();
+					}
+				}
+			}
+		}
+	}
+
+	public class ToggleAlphaAction extends AbstractAction {
+		public ToggleAlphaAction() {
+			super(tr("Alpha channel"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
+			boolean alphaChannel = checkbox.isSelected();
+			Main.pref.put("wmsplugin.alpha_channel", alphaChannel);
+
+			// clear all resized cached instances and repaint the layer
+			for (int x = 0; x < dax; ++x) {
+				for (int y = 0; y < day; ++y) {
+					GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
+					img.flushedResizedCachedInstance();
+				}
+			}
+			mv.repaint();
+		}
+	}
+	
+	public class SaveWmsAction extends AbstractAction {
+		public SaveWmsAction() {
+			super(tr("Save WMS layer to file"), ImageProvider.get("save"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			File f = SaveActionBase.createAndOpenSaveFileChooser(
+					tr("Save WMS layer"), ".wms");
+			try {
+				if (f != null) {
+					ObjectOutputStream oos = new ObjectOutputStream(
+							new FileOutputStream(f)
+					);
+					oos.writeInt(serializeFormatVersion);
+					oos.writeInt(dax);
+					oos.writeInt(day);
+					oos.writeInt(ImageSize);
+					oos.writeDouble(pixelPerDegree);
+					oos.writeObject(getName());
+					oos.writeObject(baseURL);
+					oos.writeObject(images);
+					oos.close();
+				}
+			} catch (Exception ex) {
+				ex.printStackTrace(System.out);
+			}
+		}
+	}
+
+	public class LoadWmsAction extends AbstractAction {
+		public LoadWmsAction() {
+			super(tr("Load WMS layer from file"), ImageProvider.get("load"));
+		}
+		public void actionPerformed(ActionEvent ev) {
+			JFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true,
+					false, tr("Load WMS layer"), "wms");
+			if(fc == null) return;
+			File f = fc.getSelectedFile();
+			if (f == null) return;
+			try
+			{
+				FileInputStream fis = new FileInputStream(f);
+				ObjectInputStream ois = new ObjectInputStream(fis);
+				int sfv = ois.readInt();
+				if (sfv != serializeFormatVersion) {
+					JOptionPane.showMessageDialog(Main.parent,
+							tr("Unsupported WMS file version; found {0}, expected {1}", sfv, serializeFormatVersion),
+							tr("File Format Error"),
+							JOptionPane.ERROR_MESSAGE);
+					return;
+				}
+				startstop.setSelected(false);
+				dax = ois.readInt();
+				day = ois.readInt();
+				ImageSize = ois.readInt();
+				pixelPerDegree = ois.readDouble();
+				setName((String)ois.readObject());
+				baseURL = (String) ois.readObject();
+				images = (GeorefImage[][])ois.readObject();
+				ois.close();
+				fis.close();
+				mv.repaint();
+			}
+			catch (Exception ex) {
+				// FIXME be more specific
+				ex.printStackTrace(System.out);
+				JOptionPane.showMessageDialog(Main.parent,
+						tr("Error loading file"),
+						tr("Error"),
+						JOptionPane.ERROR_MESSAGE);
+				return;
+			}
+		}
+	}
+}
diff --git a/wmsplugin/src/wmsplugin/WMSPlugin.java b/wmsplugin/src/wmsplugin/WMSPlugin.java
new file mode 100644
index 0000000..313676e
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSPlugin.java
@@ -0,0 +1,253 @@
+package wmsplugin;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.gui.IconToggleButton;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.io.CacheFiles;
+import org.openstreetmap.josm.io.MirroredInputStream;
+import org.openstreetmap.josm.plugins.Plugin;
+
+import wmsplugin.io.WMSLayerExporter;
+import wmsplugin.io.WMSLayerImporter;
+
+public class WMSPlugin extends Plugin {
+    static CacheFiles cache = new CacheFiles("wmsplugin");
+
+    WMSLayer wmsLayer;
+    static JMenu wmsJMenu;
+
+    static ArrayList<WMSInfo> wmsList = new ArrayList<WMSInfo>();
+    static TreeMap<String,String> wmsListDefault = new TreeMap<String,String>();
+
+    static boolean doOverlap = false;
+    static int overlapEast = 14;
+    static int overlapNorth = 4;
+    
+    // remember state of menu item to restore on changed preferences
+    static private boolean menuEnabled = false;
+    
+    protected void initExporterAndImporter() {
+    	ExtensionFileFilter.exporters.add(new WMSLayerExporter());
+    	ExtensionFileFilter.importers.add(new WMSLayerImporter());
+    }
+
+    public WMSPlugin() {
+        refreshMenu();
+        cache.setExpire(CacheFiles.EXPIRE_MONTHLY, false);
+        cache.setMaxSize(70, false);
+        initExporterAndImporter();
+    }
+
+    // this parses the preferences settings. preferences for the wms plugin have to
+    // look like this:
+    // wmsplugin.1.name=Landsat
+    // wmsplugin.1.url=http://and.so.on/
+
+    @Override
+    public void copy(String from, String to) throws FileNotFoundException, IOException
+    {
+        File pluginDir = new File(getPrefsPath());
+        if (!pluginDir.exists())
+            pluginDir.mkdirs();
+        FileOutputStream out = new FileOutputStream(getPrefsPath() + to);
+        InputStream in = WMSPlugin.class.getResourceAsStream(from);
+        byte[] buffer = new byte[8192];
+        for(int len = in.read(buffer); len > 0; len = in.read(buffer))
+            out.write(buffer, 0, len);
+        in.close();
+        out.close();
+    }
+
+
+    public static void refreshMenu() {
+        wmsList.clear();
+        Map<String,String> prefs = Main.pref.getAllPrefix("wmsplugin.url.");
+
+        TreeSet<String> keys = new TreeSet<String>(prefs.keySet());
+
+        // Here we load the settings for "overlap" checkbox and spinboxes. 
+         
+        try { 
+            doOverlap = Boolean.valueOf(prefs.get("wmsplugin.url.overlap"));             
+        } catch (Exception e) {} // If sth fails, we drop to default settings. 
+ 
+        try { 
+            overlapEast = Integer.valueOf(prefs.get("wmsplugin.url.overlapEast"));             
+        } catch (Exception e) {} // If sth fails, we drop to default settings. 
+ 
+        try { 
+            overlapNorth = Integer.valueOf(prefs.get("wmsplugin.url.overlapNorth"));             
+        } catch (Exception e) {} // If sth fails, we drop to default settings. 
+
+        // And then the names+urls of WMS servers
+        int prefid = 0;
+        String name = null;
+        String url = null;
+        String cookies = "";
+        int lastid = -1;
+        for (String key : keys) {
+            String[] elements = key.split("\\.");
+            if (elements.length != 4) continue;
+            try {
+                prefid = Integer.parseInt(elements[2]);
+            } catch(NumberFormatException e) {
+                continue;
+            }
+            if (prefid != lastid) {
+                name = url = null; lastid = prefid;
+            }
+            if (elements[3].equals("name"))
+                name = prefs.get(key);
+            else if (elements[3].equals("url"))
+            {
+                /* FIXME: Remove the if clause after some time */
+                if(!prefs.get(key).startsWith("yahoo:")) /* legacy stuff */
+                    url = prefs.get(key);
+            }
+            else if (elements[3].equals("cookies"))
+                cookies = prefs.get(key);
+            if (name != null && url != null)
+                wmsList.add(new WMSInfo(name, url, cookies, prefid));
+        }
+        String source = "http://svn.openstreetmap.org/applications/editors/josm/plugins/wmsplugin/sources.cfg";
+        try
+        {
+            MirroredInputStream s = new MirroredInputStream(source,
+                    Main.pref.getPreferencesDir() + "plugins/wmsplugin/", -1);
+            InputStreamReader r;
+            try
+            {
+                r = new InputStreamReader(s, "UTF-8");
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                r = new InputStreamReader(s);
+            }
+            BufferedReader reader = new BufferedReader(r);
+            String line;
+            while((line = reader.readLine()) != null)
+            {
+                String val[] = line.split(";");
+                if(!line.startsWith("#") && val.length == 3)
+                    setDefault("true".equals(val[0]), tr(val[1]), val[2]);
+            }
+        }
+        catch (IOException e)
+        {
+        }
+
+        Collections.sort(wmsList);
+        MainMenu menu = Main.main.menu;
+
+        if (wmsJMenu == null)
+            wmsJMenu = menu.addMenu(marktr("WMS"), KeyEvent.VK_W, menu.defaultMenuPos);
+        else
+            wmsJMenu.removeAll();
+
+        // for each configured WMSInfo, add a menu entry.
+        for (final WMSInfo u : wmsList) {
+            wmsJMenu.add(new JMenuItem(new WMSDownloadAction(u)));
+        }
+        wmsJMenu.addSeparator();
+        wmsJMenu.add(new JMenuItem(new Map_Rectifier_WMSmenuAction()));
+
+        wmsJMenu.addSeparator();
+        wmsJMenu.add(new JMenuItem(new
+                JosmAction(tr("Blank Layer"), "blankmenu", tr("Open a blank WMS layer to load data from a file"), null, false) {
+            public void actionPerformed(ActionEvent ev) {
+                Main.main.addLayer(new WMSLayer());
+            }
+        }));
+        wmsJMenu.addSeparator();
+        wmsJMenu.add(new JMenuItem(new Help_WMSmenuAction()));
+        setEnabledAll(menuEnabled);
+    }
+
+    /* add a default entry in case the URL does not yet exist */
+    private static void setDefault(Boolean force, String name, String url)
+    {
+        String testurl = url.replaceAll("=", "_");
+        wmsListDefault.put(name, url);
+
+        if(force && !Main.pref.getBoolean("wmsplugin.default."+testurl))
+        {
+            Main.pref.put("wmsplugin.default."+testurl, true);
+            int id = -1;
+            for(WMSInfo i : wmsList)
+            {
+                if(url.equals(i.url))
+                    return;
+                if(i.prefid > id)
+                    id = i.prefid;
+            }
+            WMSInfo newinfo = new WMSInfo(name, url, id+1);
+            newinfo.save();
+            wmsList.add(newinfo);
+        }
+    }
+
+    public static Grabber getGrabber(ProjectionBounds bounds, GeorefImage img, MapView mv, WMSLayer layer){
+        if(layer.baseURL.startsWith("html:"))
+            return new HTMLGrabber(bounds, img, mv, layer, cache);
+        else
+            return new WMSGrabber(bounds, img, mv, layer, cache);
+    }
+
+    private static void setEnabledAll(boolean isEnabled) {
+        for(int i=0; i < wmsJMenu.getItemCount(); i++) {
+            JMenuItem item = wmsJMenu.getItem(i);
+
+            if(item != null) item.setEnabled(isEnabled);
+        }
+        menuEnabled = isEnabled;
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (oldFrame==null && newFrame!=null) {
+            setEnabledAll(true);
+            Main.map.addMapMode(new IconToggleButton
+                    (new WMSAdjustAction(Main.map)));
+        } else if (oldFrame!=null && newFrame==null ) {
+            setEnabledAll(false);
+        }
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new WMSPreferenceEditor();
+    }
+
+    static public String getPrefsPath()
+    {
+        return Main.pref.getPluginsDirFile().getPath() + "/wmsplugin/";
+    }
+}
diff --git a/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java b/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
new file mode 100644
index 0000000..bdd155b
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/WMSPreferenceEditor.java
@@ -0,0 +1,273 @@
+package wmsplugin;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import org.openstreetmap.josm.Main;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.tools.GBC;
+
+public class WMSPreferenceEditor implements PreferenceSetting {
+    private DefaultTableModel model;
+    private JComboBox browser;
+    private HashMap<Integer, WMSInfo> oldValues = new HashMap<Integer, WMSInfo>();
+
+    JCheckBox overlapCheckBox;
+    JSpinner spinEast;
+    JSpinner spinNorth;
+    
+    public void addGui(final PreferenceDialog gui) {
+        JPanel p = gui.createPreferenceTab("wms", tr("WMS Plugin Preferences"), tr("Modify list of WMS servers displayed in the WMS plugin menu"));
+
+        model = new DefaultTableModel(new String[]{tr("Menu Name"), tr("WMS URL")}, 0);
+        final JTable list = new JTable(model);
+        JScrollPane scroll = new JScrollPane(list);
+        p.add(scroll, GBC.eol().fill(GBC.BOTH));
+        scroll.setPreferredSize(new Dimension(200,200));
+
+        for (WMSInfo i : WMSPlugin.wmsList) {
+            oldValues.put(i.prefid, i);
+            model.addRow(new String[]{i.name, i.url});
+        }
+
+        final DefaultTableModel modeldef = new DefaultTableModel(
+        new String[]{tr("Menu Name (Default)"), tr("WMS URL (Default)")}, 0);
+        final JTable listdef = new JTable(modeldef){
+        	@Override
+            public boolean isCellEditable(int row,int column){return false;}
+        };
+        JScrollPane scrolldef = new JScrollPane(listdef);
+        // scrolldef is added after the buttons so it's clearer the buttons
+        // control the top list and not the default one
+        scrolldef.setPreferredSize(new Dimension(200,200));
+
+        for (Map.Entry<String,String> i : WMSPlugin.wmsListDefault.entrySet()) {
+            modeldef.addRow(new String[]{i.getKey(), i.getValue()});
+        }
+
+        JPanel buttonPanel = new JPanel(new FlowLayout());
+
+        JButton add = new JButton(tr("Add"));
+        buttonPanel.add(add, GBC.std().insets(0,5,0,0));
+        add.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                JPanel p = new JPanel(new GridBagLayout());
+                p.add(new JLabel(tr("Menu Name")), GBC.std().insets(0,0,5,0));
+                JTextField key = new JTextField(40);
+                JTextField value = new JTextField(40);
+                p.add(key, GBC.eop().insets(5,0,0,0).fill(GBC.HORIZONTAL));
+                p.add(new JLabel(tr("WMS URL")), GBC.std().insets(0,0,5,0));
+                p.add(value, GBC.eol().insets(5,0,0,0).fill(GBC.HORIZONTAL));
+                int answer = JOptionPane.showConfirmDialog(
+                		gui, p, 
+                		tr("Enter a menu name and WMS URL"), 
+                		JOptionPane.OK_CANCEL_OPTION,
+                		JOptionPane.QUESTION_MESSAGE);
+                if (answer == JOptionPane.OK_OPTION) {
+                    model.addRow(new String[]{key.getText(), value.getText()});
+                }
+            }
+        });
+
+        JButton delete = new JButton(tr("Delete"));
+        buttonPanel.add(delete, GBC.std().insets(0,5,0,0));
+        delete.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (list.getSelectedRow() == -1)
+                    JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
+                else
+                {
+                    Integer i;
+                    while ((i = list.getSelectedRow()) != -1)
+                        model.removeRow(i);
+                }
+            }
+        });
+
+        JButton copy = new JButton(tr("Copy Selected Default(s)"));
+        buttonPanel.add(copy, GBC.std().insets(0,5,0,0));
+        copy.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                int[] lines = listdef.getSelectedRows();
+                if (lines.length == 0) {
+                    JOptionPane.showMessageDialog(
+                    		gui, 
+                    		tr("Please select at least one row to copy."),
+                    		tr("Information"),
+                    		JOptionPane.INFORMATION_MESSAGE
+                    		);
+                    return;
+                }
+                
+                outer: for(int i = 0; i < lines.length; i++) {
+                	String c1 = modeldef.getValueAt(lines[i], 0).toString();
+                	String c2 = modeldef.getValueAt(lines[i], 1).toString();
+                	
+                	// Check if an entry with exactly the same values already
+                	// exists
+                	for(int j = 0; j < model.getRowCount(); j++) {
+                		if(c1.equals(model.getValueAt(j, 0).toString()) 
+                				&& c2.equals(model.getValueAt(j, 1).toString())) {
+                			// Select the already existing row so the user has
+                			// some feedback in case an entry exists
+                			list.getSelectionModel().setSelectionInterval(j, j);
+                			list.scrollRectToVisible(list.getCellRect(j, 0, true));
+                			continue outer;
+                		}
+                	}
+                	
+	                model.addRow(new String[] {c1, c2});
+	                int lastLine = model.getRowCount() - 1;
+	                list.getSelectionModel().setSelectionInterval(lastLine, lastLine);
+	                list.scrollRectToVisible(list.getCellRect(lastLine, 0, true));
+                }
+            }
+        });
+
+        p.add(buttonPanel);
+        p.add(Box.createHorizontalGlue(), GBC.eol().fill(GBC.HORIZONTAL));
+        // Add default item list
+        p.add(scrolldef, GBC.eol().insets(0,5,0,0).fill(GBC.BOTH));       
+        
+        browser = new JComboBox(new String[]{
+        "webkit-image {0}",
+        "gnome-web-photo --mode=photo --format=png {0} /dev/stdout",
+        "gnome-web-photo-fixed {0}",
+        "webkit-image-gtk {0}"});
+        browser.setEditable(true);
+        browser.setSelectedItem(Main.pref.get("wmsplugin.browser", "webkit-image {0}"));
+        p.add(new JLabel(tr("Downloader:")), GBC.eol().fill(GBC.HORIZONTAL));
+        p.add(browser);
+
+
+        //Overlap
+        p.add(Box.createHorizontalGlue(), GBC.eol().fill(GBC.HORIZONTAL)); 
+         
+        overlapCheckBox = new JCheckBox(tr("Overlap tiles"), WMSPlugin.doOverlap ); 
+        JLabel labelEast = new JLabel(tr("% of east:")); 
+        JLabel labelNorth = new JLabel(tr("% of north:")); 
+        spinEast = new JSpinner(new SpinnerNumberModel(WMSPlugin.overlapEast, 1, 50, 1)); 
+        spinNorth = new JSpinner(new SpinnerNumberModel(WMSPlugin.overlapNorth, 1, 50, 1)); 
+         
+        JPanel overlapPanel = new JPanel(new FlowLayout()); 
+        overlapPanel.add(overlapCheckBox); 
+        overlapPanel.add(labelEast); 
+        overlapPanel.add(spinEast); 
+        overlapPanel.add(labelNorth); 
+        overlapPanel.add(spinNorth); 
+         
+        p.add(overlapPanel);	
+    }
+
+    public boolean ok() {
+        boolean change = false;
+        for (int i = 0; i < model.getRowCount(); ++i) {
+            String name = model.getValueAt(i,0).toString();
+            String url = model.getValueAt(i,1).toString();
+
+            WMSInfo origValue = oldValues.get(i);
+            if (origValue == null)
+            {
+                new WMSInfo(name, url, i).save();
+                change = true;
+            }
+            else
+            {
+                if (!origValue.name.equals(name) || !origValue.url.equals(url))
+                {
+                    origValue.name = name;
+                    origValue.url = url;
+                    origValue.save();
+                    change = true;
+                }
+                oldValues.remove(i);
+            }
+        }
+
+        // using null values instead of empty string really deletes
+        // the preferences entry
+        for (WMSInfo i : oldValues.values())
+        {
+            i.url = null;
+            i.name = null;
+            i.save();
+            change = true;
+        }
+
+        if (change) WMSPlugin.refreshMenu();
+
+        WMSPlugin.doOverlap = overlapCheckBox.getModel().isSelected(); 
+        WMSPlugin.overlapEast = (Integer) spinEast.getModel().getValue(); 
+        WMSPlugin.overlapNorth = (Integer) spinNorth.getModel().getValue(); 
+         
+        Main.pref.put("wmsplugin.url.overlap",    String.valueOf(WMSPlugin.doOverlap)); 
+        Main.pref.put("wmsplugin.url.overlapEast", String.valueOf(WMSPlugin.overlapEast)); 
+        Main.pref.put("wmsplugin.url.overlapNorth", String.valueOf(WMSPlugin.overlapNorth)); 
+
+        Main.pref.put("wmsplugin.browser", browser.getEditor().getItem().toString());
+        return false;
+    }
+
+    /**
+     * Updates a server URL in the preferences dialog. Used by other plugins.
+     *
+     * @param server The server name
+     * @param url The server URL
+     */
+    public void setServerUrl(String server, String url)
+    {
+        for (int i = 0; i < model.getRowCount(); i++)
+        {
+            if( server.equals(model.getValueAt(i,0).toString()) )
+            {
+                model.setValueAt(url, i, 1);
+                return;
+            }
+        }
+        model.addRow(new String[]{server, url});
+    }
+
+    /**
+     * Gets a server URL in the preferences dialog. Used by other plugins.
+     *
+     * @param server The server name
+     * @return The server URL
+     */
+    public String getServerUrl(String server)
+    {
+        for (int i = 0; i < model.getRowCount(); i++)
+        {
+            if( server.equals(model.getValueAt(i,0).toString()) )
+            {
+                return model.getValueAt(i,1).toString();
+            }
+        }
+        return null;
+    }
+}
+
diff --git a/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java b/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java
new file mode 100644
index 0000000..3030c04
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/io/WMSLayerExporter.java
@@ -0,0 +1,13 @@
+package wmsplugin.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.io.FileExporter;
+
+public class WMSLayerExporter extends FileExporter{
+	
+	public WMSLayerExporter() {
+		super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
+	}
+}
\ No newline at end of file
diff --git a/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java b/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java
new file mode 100644
index 0000000..8de34c4
--- /dev/null
+++ b/wmsplugin/src/wmsplugin/io/WMSLayerImporter.java
@@ -0,0 +1,13 @@
+package wmsplugin.io;
+
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.io.FileImporter;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class WMSLayerImporter extends FileImporter{
+
+	public WMSLayerImporter() {
+		super(new ExtensionFileFilter("wms", "wms", tr("WMS Files (*.wms)")));
+	}
+	
+}
diff --git a/wmsplugin/webkit-image-gtk.c b/wmsplugin/webkit-image-gtk.c
new file mode 100644
index 0000000..af81e5d
--- /dev/null
+++ b/wmsplugin/webkit-image-gtk.c
@@ -0,0 +1,64 @@
+/* There is no licence for this, I don't care what you do with it */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <webkit/webkit.h>
+
+#define WIDTH 2000
+
+/* compile with:
+gcc -o webkit-image-gtk webkit-image-gtk.c `pkg-config --cflags --libs webkit-1.0`
+*/
+
+static void
+on_finished (WebKitWebView *view, WebKitWebFrame *frame)
+{
+	GdkPixmap *pixmap;
+	GdkColormap *cmap;
+	GdkPixbuf *pixbuf;
+	gchar *buffer;
+	gsize size;
+
+	pixmap = gtk_widget_get_snapshot (GTK_WIDGET (view), NULL);
+	cmap = gdk_colormap_get_system ();
+	pixbuf = gdk_pixbuf_get_from_drawable (NULL, GDK_DRAWABLE (pixmap), cmap,
+					       0, 0, 0, 0, WIDTH, WIDTH);
+
+	gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &size, "png", NULL, NULL);
+
+	fwrite (buffer, 1, size, stdout);
+
+	exit (1);
+}
+
+int main (int argc, char **argv)
+{
+	GtkWidget *window;
+	GtkWidget *view;
+
+	if (argc != 2)
+		exit (20);
+
+	gtk_init (&argc, &argv);
+
+	window = gtk_window_new (GTK_WINDOW_POPUP);
+
+	/* Check if compositing window manager is running, needs one for now */
+	if (gtk_widget_is_composited (window))
+		gtk_window_set_opacity (GTK_WINDOW (window), 0.0);
+	else
+		g_error ("This requires a compositing window manager for now");
+
+	view = webkit_web_view_new ();
+	webkit_web_view_open (WEBKIT_WEB_VIEW (view), argv[1]);
+	gtk_widget_set_size_request (view, WIDTH, WIDTH);
+	gtk_container_add (GTK_CONTAINER (window), view);
+
+	gtk_widget_show_all (window);
+
+	g_signal_connect (G_OBJECT (view), "load-finished",
+			  G_CALLBACK (on_finished), NULL);
+
+	gtk_main ();
+	return 0;
+}
diff --git a/wmsplugin/webkit-image.cpp b/wmsplugin/webkit-image.cpp
new file mode 100644
index 0000000..ae905b4
--- /dev/null
+++ b/wmsplugin/webkit-image.cpp
@@ -0,0 +1,106 @@
+/* compile with
+moc webkit-image.cpp >webkit-image.h
+g++ webkit-image.cpp -o webkit-image -lQtCore -lQtWebKit -lQtGui -s -O2
+or under Windows:
+g++ webkit-image.cpp -o webkit-image -lQtCore4 -lQtWebKit4 -lQtGui4 -s O2
+adding the correct directories with -L or -I:
+-I C:\Progra~1\Qt\include -L C:\Progra~1\Qt\lib
+*/
+#include <QtGui/QApplication>
+#include <QtGui/QPainter>
+#include <QtCore/QFile>
+#include <QtCore/QString>
+#include <QtWebKit/QWebPage>
+#include <QtWebKit/QWebFrame>
+#include <QtNetwork/QNetworkProxy>
+#include <QtCore/QProcess>
+
+/* using mingw to set binary mode */
+#ifdef WIN32
+#include <io.h>
+#include <fcntl.h>
+#define BINARYSTDOUT setmode(fileno(stdout), O_BINARY);
+#else
+#define BINARYSTDOUT
+#endif
+
+class Save : public QObject
+{
+Q_OBJECT
+public:
+  Save(QWebPage *p) : page(p) {};
+
+public slots:
+  void loaded(bool ok)
+  {
+    if(ok)
+    {
+      page->setViewportSize(page->mainFrame()->contentsSize());
+      QImage im(page->viewportSize(), QImage::Format_ARGB32);
+      QPainter painter(&im);
+      page->mainFrame()->render(&painter);
+
+      QFile f;
+      BINARYSTDOUT
+      if(f.open(stdout, QIODevice::WriteOnly|QIODevice::Unbuffered))
+      {
+        if(!im.save(&f, "JPEG"))
+        {
+          im.save(&f, "PNG");
+        }
+      }
+    }
+    emit finish();
+  }
+signals:
+  void finish(void);
+
+private:
+  QWebPage * page;
+};
+
+#include "webkit-image.h"
+
+int main(int argc, char **argv)
+{
+  if(argc != 2)
+    return 20;
+  QString url = QString(argv[1]);
+
+  QApplication a(argc, argv);
+  QWebPage * page = new QWebPage();
+  Save * s = new Save(page);
+
+  QStringList environment = QProcess::systemEnvironment();
+  int idx = environment.indexOf(QRegExp("http_proxy=.*"));
+  if(idx != -1)
+  {
+     QString proxyHost;
+     int proxyPort = 8080;
+     QStringList tmpList = environment.at(idx).split("=");
+     QStringList host_port = tmpList.at(1).split(":");
+     proxyHost = host_port.at(0);
+     if(host_port.size() == 2)
+     {
+       bool ok;
+       int port = host_port.at(1).toInt(&ok);
+       if(ok)
+         proxyPort = port;
+     }
+
+     QNetworkProxy proxy;
+     proxy.setType(QNetworkProxy::HttpCachingProxy);
+     proxy.setHostName(proxyHost);
+     proxy.setPort(proxyPort);
+     page->networkAccessManager()->setProxy(proxy);
+  }
+
+  QObject::connect(page, SIGNAL(loadFinished(bool)), s, SLOT(loaded(bool)));
+  QObject::connect(s, SIGNAL(finish(void)), &a, SLOT(quit()));
+  /* set some useful defaults for a webpage */
+//  page->setViewportSize(QSize(1280,1024));
+//  page->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
+//  page->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
+  page->mainFrame()->load (QUrl(url));
+  return a.exec();
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/josm-plugins.git



More information about the Pkg-grass-devel mailing list