Index: /applications/editors/josm/plugins/pdfimport/.checkstyle
===================================================================
--- /applications/editors/josm/plugins/pdfimport/.checkstyle	(revision 32542)
+++ /applications/editors/josm/plugins/pdfimport/.checkstyle	(revision 32542)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+  <local-check-config name="JOSM" location="/JOSM/tools/checkstyle/josm_checks.xml" type="project" description="">
+    <additional-data name="protect-config-file" value="false"/>
+  </local-check-config>
+  <fileset name="all" enabled="true" check-config-name="JOSM" local="true">
+    <file-match-pattern match-pattern="." include-pattern="true"/>
+  </fileset>
+  <filter name="DerivedFiles" enabled="true"/>
+  <filter name="FilesFromPackage" enabled="true">
+    <filter-data value="src/pdfimport/pdfbox/operators"/>
+    <filter-data value="data"/>
+    <filter-data value="images"/>
+    <filter-data value="styles"/>
+    <filter-data value="resources"/>
+    <filter-data value="scripts"/>
+  </filter>
+</fileset-config>
Index: /applications/editors/josm/plugins/pdfimport/.project
===================================================================
--- /applications/editors/josm/plugins/pdfimport/.project	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/.project	(revision 32542)
@@ -16,7 +16,13 @@
 			</arguments>
 		</buildCommand>
+		<buildCommand>
+			<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
 	</buildSpec>
 	<natures>
 		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
 	</natures>
 </projectDescription>
Index: /applications/editors/josm/plugins/pdfimport/.settings/org.eclipse.jdt.ui.prefs
===================================================================
--- /applications/editors/josm/plugins/pdfimport/.settings/org.eclipse.jdt.ui.prefs	(revision 32542)
+++ /applications/editors/josm/plugins/pdfimport/.settings/org.eclipse.jdt.ui.prefs	(revision 32542)
@@ -0,0 +1,60 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_type_arguments=false
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/DuplicateNodesFinder.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/DuplicateNodesFinder.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/DuplicateNodesFinder.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -11,71 +12,75 @@
 import java.util.TreeMap;
 
-public class DuplicateNodesFinder {
+public final class DuplicateNodesFinder {
 
-	/***
-	 * This method finds very close nodes and constructs a mapping from node to suggested representative node.
-	 * Works by performing a sweep and noting down similar nodes.
-	 * @param nodes the nodes to process
-	 * @return map from nodes that need replacement to a representative node.
-	 */
-	public static Map<Point2D, Point2D> findDuplicateNodes(Collection<Point2D> nodes, final double tolerance){
-		List<Point2D> points = new ArrayList<>(nodes);
-		Collections.sort(points, new Comparator<Point2D>(){
-			public int compare(Point2D o1, Point2D o2) {
-				double diff = o1.getY() - o2.getY();
-				return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
-			}
-		});
+    private DuplicateNodesFinder() {
+        // Hide default constructor for utilities classes
+    }
 
-		Map<Point2D, Point2D> result = new HashMap<>();
-		TreeMap<Point2D, Point2D> sweepLine = new TreeMap<>(new Comparator<Point2D>(){
-			public int compare(Point2D o1, Point2D o2) {
-				double diff = o1.getX() - o2.getX();
+    /***
+     * This method finds very close nodes and constructs a mapping from node to suggested representative node.
+     * Works by performing a sweep and noting down similar nodes.
+     * @param nodes the nodes to process
+     * @return map from nodes that need replacement to a representative node.
+     */
+    public static Map<Point2D, Point2D> findDuplicateNodes(Collection<Point2D> nodes, final double tolerance) {
+        List<Point2D> points = new ArrayList<>(nodes);
+        Collections.sort(points, new Comparator<Point2D>() {
+            @Override
+            public int compare(Point2D o1, Point2D o2) {
+                double diff = o1.getY() - o2.getY();
+                return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
+            }
+        });
 
-				if (Math.abs(diff) <= tolerance){
-					return 0;
-				}
+        Map<Point2D, Point2D> result = new HashMap<>();
+        TreeMap<Point2D, Point2D> sweepLine = new TreeMap<>(new Comparator<Point2D>() {
+            @Override
+            public int compare(Point2D o1, Point2D o2) {
+                double diff = o1.getX() - o2.getX();
 
-				return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
-			}
-		});
+                if (Math.abs(diff) <= tolerance) {
+                    return 0;
+                }
 
-		//sweep from top to bottom.
-		double prevY = Double.NEGATIVE_INFINITY;
+                return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
+            }
+        });
 
-		for(Point2D point: points) {
-			boolean mappedToOtherPoint = false;
+        //sweep from top to bottom.
+        double prevY = Double.NEGATIVE_INFINITY;
 
-			if (point.getY() - prevY > tolerance){
-				sweepLine.clear();
-				//big offset, clear old points
-			} else {
-				//small offset, test against existing points (there may be more than one)
+        for (Point2D point: points) {
+            boolean mappedToOtherPoint = false;
 
-				while (!mappedToOtherPoint && sweepLine.containsKey(point)) {
-					//a close point found
-					Point2D closePoint = sweepLine.get(point);
-					double dy = point.getY() - closePoint.getY();
-					if (dy <= tolerance) {
-						//mark them as close
-						result.put(point, closePoint);
-						mappedToOtherPoint = true;
-					}
-					else
-					{
-						sweepLine.remove(point);
+            if (point.getY() - prevY > tolerance) {
+                sweepLine.clear();
+                //big offset, clear old points
+            } else {
+                //small offset, test against existing points (there may be more than one)
 
-					}
-				}
-			}
+                while (!mappedToOtherPoint && sweepLine.containsKey(point)) {
+                    //a close point found
+                    Point2D closePoint = sweepLine.get(point);
+                    double dy = point.getY() - closePoint.getY();
+                    if (dy <= tolerance) {
+                        //mark them as close
+                        result.put(point, closePoint);
+                        mappedToOtherPoint = true;
+                    } else {
+                        sweepLine.remove(point);
 
-			if (!mappedToOtherPoint) {
-				sweepLine.put(point, point);
-			}
+                    }
+                }
+            }
 
-			prevY = point.getY();
-		}
+            if (!mappedToOtherPoint) {
+                sweepLine.put(point, point);
+            }
 
-		return result;
-	}
+            prevY = point.getY();
+        }
+
+        return result;
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/FilePlacement.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/FilePlacement.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/FilePlacement.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -13,208 +14,196 @@
 
 public class FilePlacement {
-	public Projection projection = null;
-	public double minX = 0;
-	public double maxX = 1;
-	public double minY = 0;
-	public double maxY = 1;
-
-	public double minEast = 0;
-	public double maxEast = 10000;
-	public double minNorth = 0;
-	public double maxNorth = 10000;
-
-	private AffineTransform transform;
-
-
-	public void setPdfBounds(double minX, double minY, double maxX, double maxY){
-		this.minX = minX;
-		this.minY = minY;
-		this.maxX = maxX;
-		this.maxY = maxY;
-	}
-
-	public void setEastNorthBounds(double minEast, double minNorth, double maxEast, double maxNorth) {
-		this.minEast = minEast;
-		this.maxEast = maxEast;
-		this.minNorth = minNorth;
-		this.maxNorth = maxNorth;
-	}
-
-	public Properties toProperties() {
-		Properties p = new Properties();
-		if (projection != null) {
-			p.setProperty("Projection", projection.toCode());
-		}
-
-		p.setProperty("minX", Double.toString(minX));
-		p.setProperty("maxX", Double.toString(maxX));
-		p.setProperty("minY", Double.toString(minY));
-		p.setProperty("maxY", Double.toString(maxY));
-		p.setProperty("minEast", Double.toString(minEast));
-		p.setProperty("maxEast", Double.toString(maxEast));
-		p.setProperty("minNorth", Double.toString(minNorth));
-		p.setProperty("maxNorth", Double.toString(maxNorth));
-
-		return p;
-	}
-
-	public void fromProperties(Properties p){
-		String projectionCode = p.getProperty("Projection", null);
-		if (projectionCode != null) {
-			projection = ProjectionInfo.getProjectionByCode(projectionCode);
-		} else {
-			projection = null;
-		}
-
-		minX = parseProperty(p, "minX", minX);
-		maxX = parseProperty(p, "maxX", maxX);
-		minY = parseProperty(p, "minY", minY);
-		maxY = parseProperty(p, "maxY", maxY);
-
-		minEast = parseProperty(p, "minEast", minEast);
-		maxEast = parseProperty(p, "maxEast", maxEast);
-		minNorth = parseProperty(p, "minNorth", minNorth);
-		maxNorth = parseProperty(p, "maxNorth", maxNorth);
-	}
-
-	private double parseProperty(Properties p, String name, double defaultValue){
-		if (!p.containsKey(name)) {
-			return defaultValue;
-		}
-
-		String value = p.getProperty(name);
-
-		try {
-			return Double.parseDouble(value);
-		} catch (Exception e) {
-			return defaultValue;
-		}
-
-	}
-
-
-	public String prepareTransform()
-	{
-		if (this.minX > this.maxX){
-			return tr("Transform error: Min X must be smaller than max");
-		}
-
-		if (this.minY > this.maxY){
-			return tr("Transform error: Min Y must be smaller than max");
-		}
-
-		if (Math.abs(this.minY - this.maxY) < 1 &&
-				Math.abs(this.minX - this.maxX) < 1)
-		{
-			return tr("Transform error: Points too close");
-		}
-		else if (Math.abs(this.minX - this.maxX) < 1){
-			//x axis equal, assume same scale in both dimensions
-			if (this.minEast == this.maxEast){
-				//no rotation
-				this.maxX = this.minX + this.maxY - this.minY;
-				this.maxEast = this.minEast + this.maxNorth - this.minNorth;
-			} else if (this.minNorth == this.maxNorth) {
-				//needs rotated 90 degrees clockwise, or counter
-				this.maxX = this.minX + this.maxY - this.minY;
-				this.maxNorth = this.minNorth - (this.maxEast - this.minEast);
-			} else {
-				return tr("Transform error: Unsupported variant.");
-			}
-		} else if (Math.abs(this.minY - this.maxY) < 1) {
-			//Y axis equal, assume same scale in both dimensions
-			if (this.minNorth == this.maxNorth){
-				//no rotation
-				this.maxY = this.minY + this.maxX - this.minX;
-				this.maxNorth = this.minNorth + this.maxEast - this.minEast;
-			} else if (this.minEast == this.maxEast){
-				//needs rotated 90 degrees clockwise, or counter
-				this.maxY = this.minY + this.maxX - this.minX;
-				this.maxEast = this.minEast - (this.maxNorth - this.minNorth);
-			} else {
-				return tr("Transform error: Unsupported variant.");
-			}
-		} else {
-			//all fine
-		}
-
-
-		if (this.minEast < this.maxEast && this.minNorth < this.maxNorth) {
-			//no rotation
-			this.transform = new AffineTransform();
-			this.transform.translate(this.minEast, this.minNorth);
-			this.transform.scale(
-					(this.maxEast - this.minEast) / (this.maxX - this.minX),
-					(this.maxNorth - this.minNorth) /  (this.maxY - this.minY));
-			this.transform.translate(-this.minX, -this.minY);
-		} else if (this.minEast > this.maxEast && this.minNorth < this.maxNorth) {
-			//need to rotate 90 degrees counterclockwise
-			this.transform = new AffineTransform();
-			//transform to 0..1, 0..1 range
-			this.transform.preConcatenate(AffineTransform.getTranslateInstance(-this.minX, -this.minY));
-			this.transform.preConcatenate(AffineTransform.getScaleInstance(1/(this.maxX - this.minX), 1/(this.maxY - this.minY)));
-
-			//rotate -90 degs around min
-			this.transform.preConcatenate(AffineTransform.getQuadrantRotateInstance(1,  0, 0));
-
-			//transform back to target range
-			this.transform.preConcatenate(AffineTransform.getScaleInstance(
-					(this.minEast - this.maxEast),
-					(this.maxNorth - this.minNorth)));
-			this.transform.preConcatenate(AffineTransform.getTranslateInstance(this.minEast, this.minNorth));
-		} else if (this.minEast < this.maxEast && this.minNorth > this.maxNorth) {
-			//need to rotate 90 degrees clockwise
-			this.transform = new AffineTransform();
-			//transform to 0..1, 0..1 range
-			this.transform.preConcatenate(AffineTransform.getTranslateInstance(-this.minX, -this.minY));
-			this.transform.preConcatenate(AffineTransform.getScaleInstance(1/(this.maxX - this.minX), 1/(this.maxY - this.minY)));
-
-			//rotate 90 degs around min
-			this.transform.preConcatenate(AffineTransform.getQuadrantRotateInstance(-1, 0, 0));
-
-			//transform back to target range
-			this.transform.preConcatenate(AffineTransform.getScaleInstance(
-					(this.maxEast - this.minEast),
-					(this.minNorth - this.maxNorth)));
-			this.transform.preConcatenate(AffineTransform.getTranslateInstance(this.minEast, this.minNorth));
-		}
-		else
-		{
-			return tr("Transform error: Unsupported orientation");
-		}
-
-		return null;
-
-	}
-
-	EastNorth en = new EastNorth(0, 0);
-	Point2D src = new Point2D.Double();
-
-
-	public Bounds getWorldBounds(PathOptimizer data) {
-		LatLon min = this.tranformCoords(new Point2D.Double(data.bounds.getMinX(), data.bounds.getMinY()));
-		LatLon max = this.tranformCoords(new Point2D.Double(data.bounds.getMaxX(), data.bounds.getMaxY()));
-		return new Bounds(min, max);
-	}
-
-	public LatLon tranformCoords(Point2D pt) {
-
-		if (this.projection == null) {
-			return new LatLon(pt.getY() / 1000, pt.getX() / 1000);
-		} else {
-		    Point2D dest = new Point2D.Double();
-			this.transform.transform(pt, dest);
-			en = new EastNorth(dest.getX(), dest.getY());
-			return this.projection.eastNorth2latlon(en);
-		}
-	}
-
-	public EastNorth reverseTransform(LatLon coor) {
-		if (this.projection == null) {
-			return new EastNorth(coor.lon() * 1000, coor.lat() * 1000);
-		} else {
-			return null;
-		}
-	}
+    public Projection projection = null;
+    public double minX = 0;
+    public double maxX = 1;
+    public double minY = 0;
+    public double maxY = 1;
+
+    public double minEast = 0;
+    public double maxEast = 10000;
+    public double minNorth = 0;
+    public double maxNorth = 10000;
+
+    private AffineTransform transform;
+
+    public void setPdfBounds(double minX, double minY, double maxX, double maxY) {
+        this.minX = minX;
+        this.minY = minY;
+        this.maxX = maxX;
+        this.maxY = maxY;
+    }
+
+    public void setEastNorthBounds(double minEast, double minNorth, double maxEast, double maxNorth) {
+        this.minEast = minEast;
+        this.maxEast = maxEast;
+        this.minNorth = minNorth;
+        this.maxNorth = maxNorth;
+    }
+
+    public Properties toProperties() {
+        Properties p = new Properties();
+        if (projection != null) {
+            p.setProperty("Projection", projection.toCode());
+        }
+
+        p.setProperty("minX", Double.toString(minX));
+        p.setProperty("maxX", Double.toString(maxX));
+        p.setProperty("minY", Double.toString(minY));
+        p.setProperty("maxY", Double.toString(maxY));
+        p.setProperty("minEast", Double.toString(minEast));
+        p.setProperty("maxEast", Double.toString(maxEast));
+        p.setProperty("minNorth", Double.toString(minNorth));
+        p.setProperty("maxNorth", Double.toString(maxNorth));
+
+        return p;
+    }
+
+    public void fromProperties(Properties p) {
+        String projectionCode = p.getProperty("Projection", null);
+        if (projectionCode != null) {
+            projection = ProjectionInfo.getProjectionByCode(projectionCode);
+        } else {
+            projection = null;
+        }
+
+        minX = parseProperty(p, "minX", minX);
+        maxX = parseProperty(p, "maxX", maxX);
+        minY = parseProperty(p, "minY", minY);
+        maxY = parseProperty(p, "maxY", maxY);
+
+        minEast = parseProperty(p, "minEast", minEast);
+        maxEast = parseProperty(p, "maxEast", maxEast);
+        minNorth = parseProperty(p, "minNorth", minNorth);
+        maxNorth = parseProperty(p, "maxNorth", maxNorth);
+    }
+
+    private double parseProperty(Properties p, String name, double defaultValue) {
+        if (!p.containsKey(name)) {
+            return defaultValue;
+        }
+
+        String value = p.getProperty(name);
+
+        try {
+            return Double.parseDouble(value);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    public String prepareTransform() {
+        if (this.minX > this.maxX) {
+            return tr("Transform error: Min X must be smaller than max");
+        }
+
+        if (this.minY > this.maxY) {
+            return tr("Transform error: Min Y must be smaller than max");
+        }
+
+        if (Math.abs(this.minY - this.maxY) < 1 &&
+                Math.abs(this.minX - this.maxX) < 1) {
+            return tr("Transform error: Points too close");
+        } else if (Math.abs(this.minX - this.maxX) < 1) {
+            //x axis equal, assume same scale in both dimensions
+            if (this.minEast == this.maxEast) {
+                //no rotation
+                this.maxX = this.minX + this.maxY - this.minY;
+                this.maxEast = this.minEast + this.maxNorth - this.minNorth;
+            } else if (this.minNorth == this.maxNorth) {
+                //needs rotated 90 degrees clockwise, or counter
+                this.maxX = this.minX + this.maxY - this.minY;
+                this.maxNorth = this.minNorth - (this.maxEast - this.minEast);
+            } else {
+                return tr("Transform error: Unsupported variant.");
+            }
+        } else if (Math.abs(this.minY - this.maxY) < 1) {
+            //Y axis equal, assume same scale in both dimensions
+            if (this.minNorth == this.maxNorth) {
+                //no rotation
+                this.maxY = this.minY + this.maxX - this.minX;
+                this.maxNorth = this.minNorth + this.maxEast - this.minEast;
+            } else if (this.minEast == this.maxEast) {
+                //needs rotated 90 degrees clockwise, or counter
+                this.maxY = this.minY + this.maxX - this.minX;
+                this.maxEast = this.minEast - (this.maxNorth - this.minNorth);
+            } else {
+                return tr("Transform error: Unsupported variant.");
+            }
+        }
+
+        if (this.minEast < this.maxEast && this.minNorth < this.maxNorth) {
+            //no rotation
+            this.transform = new AffineTransform();
+            this.transform.translate(this.minEast, this.minNorth);
+            this.transform.scale(
+                    (this.maxEast - this.minEast) / (this.maxX - this.minX),
+                    (this.maxNorth - this.minNorth) / (this.maxY - this.minY));
+            this.transform.translate(-this.minX, -this.minY);
+        } else if (this.minEast > this.maxEast && this.minNorth < this.maxNorth) {
+            //need to rotate 90 degrees counterclockwise
+            this.transform = new AffineTransform();
+            //transform to 0..1, 0..1 range
+            this.transform.preConcatenate(AffineTransform.getTranslateInstance(-this.minX, -this.minY));
+            this.transform.preConcatenate(AffineTransform.getScaleInstance(1/(this.maxX - this.minX), 1/(this.maxY - this.minY)));
+
+            //rotate -90 degs around min
+            this.transform.preConcatenate(AffineTransform.getQuadrantRotateInstance(1, 0, 0));
+
+            //transform back to target range
+            this.transform.preConcatenate(AffineTransform.getScaleInstance(
+                    (this.minEast - this.maxEast),
+                    (this.maxNorth - this.minNorth)));
+            this.transform.preConcatenate(AffineTransform.getTranslateInstance(this.minEast, this.minNorth));
+        } else if (this.minEast < this.maxEast && this.minNorth > this.maxNorth) {
+            //need to rotate 90 degrees clockwise
+            this.transform = new AffineTransform();
+            //transform to 0..1, 0..1 range
+            this.transform.preConcatenate(AffineTransform.getTranslateInstance(-this.minX, -this.minY));
+            this.transform.preConcatenate(AffineTransform.getScaleInstance(1/(this.maxX - this.minX), 1/(this.maxY - this.minY)));
+
+            //rotate 90 degs around min
+            this.transform.preConcatenate(AffineTransform.getQuadrantRotateInstance(-1, 0, 0));
+
+            //transform back to target range
+            this.transform.preConcatenate(AffineTransform.getScaleInstance(
+                    (this.maxEast - this.minEast),
+                    (this.minNorth - this.maxNorth)));
+            this.transform.preConcatenate(AffineTransform.getTranslateInstance(this.minEast, this.minNorth));
+        } else {
+            return tr("Transform error: Unsupported orientation");
+        }
+
+        return null;
+
+    }
+
+    EastNorth en = new EastNorth(0, 0);
+    Point2D src = new Point2D.Double();
+
+    public Bounds getWorldBounds(PathOptimizer data) {
+        LatLon min = this.tranformCoords(new Point2D.Double(data.bounds.getMinX(), data.bounds.getMinY()));
+        LatLon max = this.tranformCoords(new Point2D.Double(data.bounds.getMaxX(), data.bounds.getMaxY()));
+        return new Bounds(min, max);
+    }
+
+    public LatLon tranformCoords(Point2D pt) {
+
+        if (this.projection == null) {
+            return new LatLon(pt.getY() / 1000, pt.getX() / 1000);
+        } else {
+            Point2D dest = new Point2D.Double();
+            this.transform.transform(pt, dest);
+            en = new EastNorth(dest.getX(), dest.getY());
+            return this.projection.eastNorth2latlon(en);
+        }
+    }
+
+    public EastNorth reverseTransform(LatLon coor) {
+        if (this.projection == null) {
+            return new EastNorth(coor.lon() * 1000, coor.lat() * 1000);
+        } else {
+            return null;
+        }
+    }
 
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerContents.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerContents.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerContents.java	(revision 32542)
@@ -1,5 +1,3 @@
-/**
- *
- */
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -8,9 +6,9 @@
 import java.util.List;
 
-public class LayerContents{
+public class LayerContents {
 
-	List<Point2D> points = new ArrayList<>();
-	List<PdfPath> paths = new ArrayList<>();
-	List<PdfMultiPath> multiPaths = new ArrayList<>();
-	LayerInfo info;
+    List<Point2D> points = new ArrayList<>();
+    List<PdfPath> paths = new ArrayList<>();
+    List<PdfMultiPath> multiPaths = new ArrayList<>();
+    LayerInfo info;
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerInfo.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerInfo.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/LayerInfo.java	(revision 32542)
@@ -1,61 +1,60 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
 import java.awt.Color;
 
-public class LayerInfo{
-	public Color fill;
-	public Color stroke;
-	public int dash;
-	public double width;
-	public int divider;
-	public boolean isGroup;
+public class LayerInfo {
+    public Color fill;
+    public Color stroke;
+    public int dash;
+    public double width;
+    public int divider;
+    public boolean isGroup;
 
-	public int nr;
+    public int nr;
 
-	@Override
-	public int hashCode()
-	{
-		int code =  Double.toString(width).hashCode() ^ this.divider ^ this.dash;
+    @Override
+    public int hashCode() {
+        int code = Double.toString(width).hashCode() ^ this.divider ^ this.dash;
 
-		if (this.fill != null) {
-			code ^= this.fill.hashCode();
-		}
+        if (this.fill != null) {
+            code ^= this.fill.hashCode();
+        }
 
-		if (this.stroke != null) {
-			code ^= this.stroke.hashCode();
-		}
+        if (this.stroke != null) {
+            code ^= this.stroke.hashCode();
+        }
 
-		return code;
-	}
+        return code;
+    }
 
-	@Override
-	public boolean equals(Object o)
-	{
-		LayerInfo l = (LayerInfo) o;
-		boolean eq = this.width == l.width &&
-		this.divider == l.divider &&
-		this.dash == l.dash;
+    @Override
+    public boolean equals(Object o) {
+        LayerInfo l = (LayerInfo) o;
+        boolean eq = this.width == l.width &&
+        this.divider == l.divider &&
+        this.dash == l.dash;
 
 
-		if (this.fill != null){
-			eq &= this.fill.equals(l.fill);
-		}
+        if (this.fill != null) {
+            eq &= this.fill.equals(l.fill);
+        }
 
-		if (this.stroke != null) {
-			eq &= this.stroke.equals(l.stroke);
-		}
+        if (this.stroke != null) {
+            eq &= this.stroke.equals(l.stroke);
+        }
 
-		return eq;
-	}
+        return eq;
+    }
 
-	public LayerInfo copy() {
-		LayerInfo result = new LayerInfo();
-		result.fill = this.fill;
-		result.stroke = this.stroke;
-		result.dash = this.dash;
-		result.width = this.width;
-		result.divider = this.divider;
-		return result;
-	}
+    public LayerInfo copy() {
+        LayerInfo result = new LayerInfo();
+        result.fill = this.fill;
+        result.stroke = this.stroke;
+        result.dash = this.dash;
+        result.width = this.width;
+        result.divider = this.divider;
+        return result;
+    }
 
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/LoadPdfDialog.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/LoadPdfDialog.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/LoadPdfDialog.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -63,1035 +64,1012 @@
 import pdfimport.pdfbox.PdfBoxParser;
 
-public class LoadPdfDialog extends JFrame{
-
-    	class LoadProgressRenderer implements ProgressRenderer{
-		private final JProgressBar pBar;
-		private String title = "";
-
-		public LoadProgressRenderer(JProgressBar pb)
-		{
-			this.pBar =pb;
-			this.pBar.setMinimum(0);
-			this.pBar.setValue(0);
-			this.pBar.setMaximum(1);
-			this.pBar.setString("");
-			this.pBar.setStringPainted(true);
-
-		}
-
-		@Override
-		public void setCustomText(String message) {
-			this.pBar.setString(this.title + message);
-		}
-
-		@Override
-		public void setIndeterminate(boolean indeterminate) {
-			this.pBar.setIndeterminate(indeterminate);
-		}
-
-		@Override
-		public void setMaximum(int maximum) {
-			this.pBar.setMaximum(maximum);
-		}
-
-		@Override
-		public void setTaskTitle(String taskTitle) {
-			this.title = taskTitle;
-			this.pBar.setString(this.title);
-		}
-
-		@Override
-		public void setValue(int value) {
-			this.pBar.setValue(value);
-		}
-
-		public void finish() {
-			this.pBar.setString(tr("Finished"));
-			this.pBar.setValue(this.pBar.getMaximum());
-		}
-
-	}
-
-	private File fileName;
-	private PathOptimizer data;
-	private OsmDataLayer layer;
-
-	/**
-	 * Combobox with all projections available
-	 */
-	private JComboBox<ProjectionChoice> projectionCombo;
-	private JButton projectionPreferencesButton;
-	private JTextField minXField;
-	private JTextField minYField;
-	private JTextField minEastField;
-	private JTextField minNorthField;
-	private JButton getMinButton;
-	private JButton okButton;
-	private JButton cancelButton;
-	private JButton getMaxButton;
-	private JTextField maxNorthField;
-	private JTextField maxEastField;
-	private JTextField maxYField;
-	private JTextField maxXField;
-	private JButton loadFileButton;
-	private JButton showButton;
-	private JButton saveButton;
-	private JCheckBox debugModeCheck;
-	private JCheckBox mergeCloseNodesCheck;
-	private JTextField mergeCloseNodesTolerance;
-	private JCheckBox removeSmallObjectsCheck;
-	private JTextField removeSmallObjectsSize;
-	private JTextField colorFilterColor;
-	private JCheckBox colorFilterCheck;
-	private JCheckBox removeParallelSegmentsCheck;
-	private JTextField removeParallelSegmentsTolerance;
-	private JCheckBox removeLargeObjectsCheck;
-	private JTextField removeLargeObjectsSize;
-	private JProgressBar loadProgress;
-	protected OsmDataLayer newLayer;
-
-	private LoadProgressRenderer progressRenderer;
-	private JCheckBox limitPathCountCheck;
-	private JTextField limitPathCount;
-	private JCheckBox splitOnColorChangeCheck;
-	private JCheckBox splitOnShapeClosedCheck;
-	private JCheckBox splitOnSingleSegmentCheck;
-	private JCheckBox splitOnOrthogonalCheck;
-
-
-	public LoadPdfDialog() {
-		this.buildGUI();
-		FilePlacement pl = new FilePlacement();
-		this.setPlacement(pl);
-		this.addListeners();
-		this.removeLayer();
-	}
-        
- 	private void addListeners() {
-
-		this.projectionCombo.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				updateProjectionPrefButton();
-			}
-
-		});
-		this.projectionPreferencesButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				showProjectionPreferences();
-			}
-		});
-
-		this.loadFileButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				loadFilePressed();
-			}
-		});
-
-		this.okButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				okPressed();
-			}
-		});
-
-		this.saveButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				savePressed();
-			}
-		});
-
-		this.showButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				showPressed();
-			}
-		});
-
-		this.cancelButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				cancelPressed();
-			}
-		});
-
-		this.addWindowListener(new WindowAdapter()
-		{
-			@Override
-			public void windowClosing(WindowEvent e) {
-				cancelPressed();
-			}
-		});
-
-		this.getMinButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				getMinPressed();
-			}
-		});
-
-		this.getMaxButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				getMaxPressed();
-			}
-		});
-
-	}
-
-	private void buildGUI() {
-		GridBagConstraints c = new GridBagConstraints();
-		c.gridheight = 1;c.gridwidth = 1;c.weightx =1; c.weighty = 1; c.fill = GridBagConstraints.BOTH;
-
-		this.projectionCombo = new JComboBox<>();
-		for (ProjectionChoice p: ProjectionPreference.getProjectionChoices()) {
-			this.projectionCombo.addItem(p);
-		}
-
-		this.projectionPreferencesButton = new JButton(tr("Prefs"));
-		updateProjectionPrefButton();
-
-		this.loadFileButton = new JButton(tr("Load file..."));
-		this.okButton = new JButton(tr("Place"));
-		this.saveButton = new JButton(tr("Save"));
-		this.showButton = new JButton(tr("Show target"));
-		this.cancelButton = new JButton(tr("Discard"));
-		this.loadProgress = new JProgressBar();
-		this.progressRenderer = new LoadProgressRenderer(this.loadProgress);
-
-		this.minXField = new JTextField("");
-		this.minYField = new JTextField("");
-		this.minEastField = new JTextField("");
-		this.minNorthField = new JTextField("");
-		this.getMinButton = new JButton(tr("Take X and Y from selected node"));
-
-		this.maxXField = new JTextField("");
-		this.maxYField = new JTextField("");
-		this.maxEastField = new JTextField("");
-		this.maxNorthField = new JTextField("");
-		this.getMaxButton = new JButton(tr("Take X and Y from selected node"));
-
-		this.debugModeCheck = new JCheckBox(tr("Debug info"));
-		this.mergeCloseNodesCheck = new JCheckBox(tr("Merge close nodes"));
-		this.mergeCloseNodesTolerance = new JTextField("1e-3");
-
-		this.removeSmallObjectsCheck = new JCheckBox(tr("Remove objects smaller than"));
-		this.removeSmallObjectsSize = new JTextField("1");
-
-		this.removeLargeObjectsCheck = new JCheckBox(tr("Remove objects larger than"));
-		this.removeLargeObjectsSize = new JTextField("10");
-
-
-		this.colorFilterCheck = new JCheckBox(tr("Only this color"));
-		this.colorFilterColor = new JTextField("#000000");
-
-		this.removeParallelSegmentsCheck = new JCheckBox(tr("Remove parallel lines"));
-		this.removeParallelSegmentsTolerance = new JTextField("3");
-
-		this.limitPathCountCheck = new JCheckBox(tr("Take only first X paths"));
-		this.limitPathCount = new JTextField("10000");
-
-		this.splitOnColorChangeCheck = new JCheckBox(tr("Color/width change"));
-		this.splitOnShapeClosedCheck = new JCheckBox(tr("Shape closed"));
-		this.splitOnSingleSegmentCheck = new JCheckBox(tr("Single segments"));
-		this.splitOnOrthogonalCheck = new JCheckBox(tr("Orthogonal shapes"));
-
-		JPanel configPanel = new JPanel(new GridBagLayout());
-		configPanel.setBorder(BorderFactory.createTitledBorder(tr("Import settings")));
-		c.gridx = 0; c.gridy = 0; c.gridwidth = 1;
-		configPanel.add(this.mergeCloseNodesCheck, c);
-		c.gridx = 1; c.gridy = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
-		configPanel.add(new JLabel("Tolerance :"), c);
-		c.gridx = 2; c.gridy = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
-		configPanel.add(this.mergeCloseNodesTolerance, c);
-
-		c.gridx = 0; c.gridy = 1; c.gridwidth = 1;
-		configPanel.add(this.removeSmallObjectsCheck, c);
-		c.gridx = 1; c.gridy = 1; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
-		configPanel.add(new JLabel("Tolerance :"), c);
-		c.gridx = 2; c.gridy = 1; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
-		configPanel.add(this.removeSmallObjectsSize, c);
-
-		c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
-		configPanel.add(this.removeLargeObjectsCheck, c);
-		c.gridx = 1; c.gridy = 2; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
-		configPanel.add(new JLabel("Tolerance :"), c);
-		c.gridx = 2; c.gridy = 2; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
-		configPanel.add(this.removeLargeObjectsSize, c);
-
-		c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
-		configPanel.add(this.removeParallelSegmentsCheck, c);
-		c.gridx = 1; c.gridy = 3; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
-		configPanel.add(new JLabel("Max distance :"), c);
-		c.gridx = 2; c.gridy = 3; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
-		configPanel.add(this.removeParallelSegmentsTolerance, c);
-
-
-		c.gridx = 0; c.gridy = 4; c.gridwidth = 2;
-		configPanel.add(this.limitPathCountCheck, c);
-		c.gridx = 2; c.gridy = 4; c.gridwidth = 1;
-		configPanel.add(this.limitPathCount, c);
-
-		c.gridx = 0; c.gridy = 5; c.gridwidth = 1;
-		configPanel.add(this.colorFilterCheck, c);
-		c.gridx = 2; c.gridy = 5; c.gridwidth = 1;
-		configPanel.add(this.colorFilterColor, c);
-
-		c.gridx = 0; c.gridy = 6; c.gridwidth = 2;
-		configPanel.add(this.debugModeCheck, c);
-
-
-		c.gridx = 0; c.gridy = 7; c.gridwidth = 1;
-		configPanel.add(new JLabel(tr("Introduce separate layers for:")), c);
-		c.gridx = 1; c.gridy = 7; c.gridwidth = 1;
-		configPanel.add(this.splitOnShapeClosedCheck, c);
-		c.gridx = 2; c.gridy = 7; c.gridwidth = 1;
-		configPanel.add(this.splitOnSingleSegmentCheck, c);
-		c.gridx = 1; c.gridy = 8; c.gridwidth = 1;
-		configPanel.add(this.splitOnColorChangeCheck, c);
-		c.gridx = 2; c.gridy = 8; c.gridwidth = 1;
-		configPanel.add(this.splitOnOrthogonalCheck, c);
-
-
-		JPanel projectionPanel = new JPanel(new GridBagLayout());
-		projectionPanel.setBorder(BorderFactory.createTitledBorder(tr("Bind to coordinates")));
-
-		JPanel projectionSubPanel = new JPanel();
-		projectionSubPanel.setLayout(new BoxLayout(projectionSubPanel, BoxLayout.X_AXIS));
-
-		projectionSubPanel.add(new JLabel(tr("Projection:")));
-		projectionSubPanel.add(this.projectionCombo);
-		projectionSubPanel.add(this.projectionPreferencesButton);
-		c.gridx = 0; c.gridy = 0; c.gridwidth = 3;
-		projectionPanel.add(projectionSubPanel, c);
-
-		c.gridx = 0; c.gridy = 1; c.gridwidth = 2;
-		projectionPanel.add(new JLabel(tr("Bottom left (min) corner:")), c);
-		c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
-		projectionPanel.add(new JLabel(tr("PDF X and Y")), c);
-		c.gridx = 1; c.gridy = 2; c.gridwidth = 1;
-		projectionPanel.add(new JLabel(tr("East and North")), c);
-		c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
-		projectionPanel.add(this.minXField, c);
-		c.gridx = 0; c.gridy = 4; c.gridwidth = 1;
-		projectionPanel.add(this.minYField, c);
-		c.gridx = 1; c.gridy = 3; c.gridwidth = 1;
-		projectionPanel.add(this.minEastField, c);
-		c.gridx = 1; c.gridy = 4; c.gridwidth = 1;
-		projectionPanel.add(this.minNorthField, c);
-		c.gridx = 0; c.gridy = 5; c.gridwidth = 1;
-		projectionPanel.add(this.getMinButton, c);
-
-
-		c.gridx = 0; c.gridy = 6; c.gridwidth = 2;
-		projectionPanel.add(new JLabel(tr("Top right (max) corner:")), c);
-		c.gridx = 0; c.gridy = 7; c.gridwidth = 1;
-		projectionPanel.add(new JLabel(tr("PDF X and Y")), c);
-		c.gridx = 1; c.gridy = 7; c.gridwidth = 1;
-		projectionPanel.add(new JLabel(tr("East and North")), c);
-		c.gridx = 0; c.gridy = 8; c.gridwidth = 1;
-		projectionPanel.add(this.maxXField, c);
-		c.gridx = 0; c.gridy = 9; c.gridwidth = 1;
-		projectionPanel.add(this.maxYField, c);
-		c.gridx = 1; c.gridy = 8; c.gridwidth = 1;
-		projectionPanel.add(this.maxEastField, c);
-		c.gridx = 1; c.gridy = 9; c.gridwidth = 1;
-		projectionPanel.add(this.maxNorthField, c);
-		c.gridx = 0; c.gridy = 10; c.gridwidth = 1;
-		projectionPanel.add(this.getMaxButton, c);
-
-
-		JPanel okCancelPanel = new JPanel(new GridLayout(1,3));
-		okCancelPanel.add(this.cancelButton);
-		okCancelPanel.add(this.showButton);
-		okCancelPanel.add(this.okButton);
-		okCancelPanel.add(this.saveButton);
-
-
-		JPanel panel = new JPanel(new GridBagLayout());
-		c.gridx = 0; c.gridy = 0; c.gridwidth = 1;
-		panel.add(configPanel, c);
-		c.gridx = 0; c.gridy = 1; c.gridwidth = 1;
-		panel.add(loadFileButton, c);
-		c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
-		panel.add(projectionPanel, c);
-		c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
-		panel.add(okCancelPanel, c);
-		c.gridx = 0; c.gridy = 4; c.gridwidth = 1;
-		panel.add(this.loadProgress, c);
-
-
-		this.setSize(450, 550);
-		this.setContentPane(panel);
-	}
-
-	private class ProjectionSubPrefsDialog extends JDialog {
-		private final ProjectionChoice projPref;
-		private OKAction actOK;
-		private CancelAction actCancel;
-		private JPanel projPrefPanel;
-
-		public ProjectionSubPrefsDialog(Component parent, ProjectionChoice pr) {
-			super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
-
-			projPref = pr;
-
-			setTitle(tr("Projection Preferences"));
-			setDefaultCloseOperation(DISPOSE_ON_CLOSE);
-
-			build();
-		}
-
-		protected void makeButtonRespondToEnter(SideButton btn) {
-			btn.setFocusable(true);
-			btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter");
-			btn.getActionMap().put("enter", btn.getAction());
-		}
-
-		protected JPanel buildInputForm() {
-			return projPref.getPreferencePanel(null);
-		}
-
-		protected JPanel buildButtonRow() {
-			JPanel pnl = new JPanel(new FlowLayout());
-
-			actOK = new OKAction();
-			actCancel = new CancelAction();
-
-			SideButton btn;
-			pnl.add(btn = new SideButton(actOK));
-			makeButtonRespondToEnter(btn);
-			pnl.add(btn = new SideButton(actCancel));
-			makeButtonRespondToEnter(btn);
-			return pnl;
-		}
-
-		protected void build() {
-			projPrefPanel = buildInputForm();
-			getContentPane().setLayout(new BorderLayout());
-			getContentPane().add(projPrefPanel, BorderLayout.CENTER);
-			getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
-			pack();
-
-			// make dialog respond to ESCAPE
-			getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
-			getRootPane().getActionMap().put("escape", actCancel);
-		}
-
-		class OKAction extends AbstractAction {
-			public OKAction() {
-			putValue(NAME, tr("OK"));
-			putValue(SHORT_DESCRIPTION, tr("Close the dialog and apply projection preferences"));
-			putValue(SMALL_ICON, ImageProvider.get("ok"));
-			}
-
-			@Override
-			public void actionPerformed(ActionEvent e) {
-			projPref.setPreferences(projPref.getPreferences(projPrefPanel));
-			setVisible(false);
-			}
-		}
-
-		class CancelAction extends AbstractAction {
-			public CancelAction() {
-			putValue(NAME, tr("Cancel"));
-			putValue(SHORT_DESCRIPTION, tr("Close the dialog, discard projection preference changes"));
-			putValue(SMALL_ICON, ImageProvider.get("cancel"));
-			}
-
-			@Override
-			public void actionPerformed(ActionEvent e) {
-			setVisible(false);
-			}
-		}
-
-		@Override
-		public void setVisible(boolean visible) {
-			if (visible) {
-    			new WindowGeometry(
-    				getClass().getName() + ".geometry",
-    				WindowGeometry.centerOnScreen(new Dimension(400, 300))).applySafe(this);
-			} else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
-    			new WindowGeometry(this).remember(getClass().getName() + ".geometry");
-			}
-			super.setVisible(visible);
-		}
-	}
-
-	private void updateProjectionPrefButton() {
-//		ProjectionChoice proj = (ProjectionChoice) projectionCombo.getSelectedItem();
-
-		//TODO
-		// Enable/disable pref button
-//		if(!(proj instanceof ProjectionSubPrefs)) {
-//			projectionPreferencesButton.setEnabled(false);
-//		} else {
-			projectionPreferencesButton.setEnabled(true);
-//		}
-	}
-
-	private void showProjectionPreferences() {
-		ProjectionChoice proj = (ProjectionChoice) projectionCombo.getSelectedItem();
-
-		ProjectionSubPrefsDialog dlg = new ProjectionSubPrefsDialog(this, proj);
-		dlg.setVisible(true);
-
-	}
-
-	private void loadFilePressed() {
-		final File newFileName = this.chooseFile();
-
-		if (newFileName == null) {
-			return;
-		}
-
-		this.removeLayer();
-
-		this.loadFileButton.setEnabled(false);
-		this.loadFileButton.setText(tr("Loading..."));
-
-		this.runAsBackgroundTask(
-				new Runnable() {
-					@Override
-					public void run() {
-						//async part
-						SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
-						monitor.beginTask("Loading file", 1000);
-						data = loadPDF(newFileName, monitor.createSubTaskMonitor(500, false));
-						OsmBuilder.Mode mode = LoadPdfDialog.this.debugModeCheck.isSelected() ? OsmBuilder.Mode.Debug: OsmBuilder.Mode.Draft;
-
-						if (data!= null) {
-							LoadPdfDialog.this.newLayer = LoadPdfDialog.this.makeLayer(tr("PDF file preview"), new FilePlacement(), mode, monitor.createSubTaskMonitor(500, false));
-						}
-
-						monitor.finishTask();
-						progressRenderer.finish();
-					}
-				},
-				new ActionListener() {
-
-					@Override
-					public void actionPerformed(ActionEvent e) {
-						//sync part
-						if (data!= null) {
-							LoadPdfDialog.this.placeLayer(newLayer, new FilePlacement());
-							fileName = newFileName;
-							newLayer = null;
-							LoadPdfDialog.this.loadFileButton.setText(tr("Loaded"));
-							LoadPdfDialog.this.loadFileButton.setEnabled(true);
-							FilePlacement placement =  LoadPdfDialog.this.loadPlacement();
-							LoadPdfDialog.this.setPlacement(placement);
-						}
-					}
-				});
-	}
-
-
-	private FilePlacement preparePlacement()
-	{
-		FilePlacement placement = this.parsePlacement();
-		if (placement == null){
-			return null;
-		}
-
-		String transformError = placement.prepareTransform();
-		if (transformError != null){
-			JOptionPane.showMessageDialog(Main.parent, transformError);
-			return null;
-		}
-
-		this.savePlacement(placement);
-
-		return placement;
-	}
-
-	private void okPressed() {
-
-		final FilePlacement placement = preparePlacement();
-		if (placement == null) {
-			return;
-		}
-
-		this.removeLayer();
-
-		this.runAsBackgroundTask(
-				new Runnable() {
-					@Override
-					public void run() {
-						//async part
-						SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
-						LoadPdfDialog.this.newLayer = LoadPdfDialog.this.makeLayer(tr("Imported PDF: ") + fileName, placement, OsmBuilder.Mode.Final, monitor);
-						progressRenderer.finish();
-					}
-				},
-				new ActionListener() {
-
-					@Override
-					public void actionPerformed(ActionEvent e) {
-						//sync part
-						//rebuild layer with latest projection
-						LoadPdfDialog.this.placeLayer(newLayer, placement);
-						LoadPdfDialog.this.setVisible(false);
-					}
-				});
-	}
-
-	private void savePressed() {
-
-		final FilePlacement placement = preparePlacement();
-		if (placement == null) {
-			return;
-		}
-
-		final java.io.File file = this.chooseSaveFile();
-
-		if (file == null){
-			return;
-		}
-
-		this.removeLayer();
-
-		this.runAsBackgroundTask(
-				new Runnable() {
-					@Override
-					public void run() {
-						//async part
-						SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
-						LoadPdfDialog.this.saveLayer(file, placement, monitor);
-						progressRenderer.finish();
-					}
-				},
-				new ActionListener() {
-
-					@Override
-					public void actionPerformed(ActionEvent e) {
-						//sync part
-						LoadPdfDialog.this.setVisible(false);
-					}
-				});
-	}
-
-
-	private void showPressed() {
-
-		FilePlacement placement = preparePlacement();
-		if (placement == null) {
-			return;
-		}
-
-		//zoom to new location
-		Main.map.mapView.zoomTo(placement.getWorldBounds(this.data));
-		Main.map.repaint();
-	}
-
-	private void cancelPressed() {
-		this.removeLayer();
-		this.setVisible(false);
-	}
-
-
-	private void getMinPressed() {
-		EastNorth en = this.getSelectedCoor();
-
-		if (en != null) {
-			this.minXField.setText(Double.toString(en.east()));
-			this.minYField.setText(Double.toString(en.north()));
-		}
-	}
-
-	private void getMaxPressed() {
-		EastNorth en = this.getSelectedCoor();
-
-		if (en != null) {
-			this.maxXField.setText(Double.toString(en.east()));
-			this.maxYField.setText(Double.toString(en.north()));
-		}
-	}
-
-	// Implementation methods
-
-	private EastNorth getSelectedCoor() {
-		Collection<OsmPrimitive> selected = Main.getLayerManager().getEditDataSet().getSelected();
-
-		if (selected.size() != 1 || !(selected.iterator().next() instanceof Node)){
-			JOptionPane.showMessageDialog(Main.parent, tr("Please select exactly one node."));
-			return null;
-		}
-
-		LatLon ll = ((Node)selected.iterator().next()).getCoor();
-		FilePlacement pl = new FilePlacement();
-		return pl.reverseTransform(ll);
-	}
-
-
-	private java.io.File chooseFile() {
-		//get file name
-		JFileChooser fc = new JFileChooser();
-		fc.setAcceptAllFileFilterUsed(false);
-		fc.setMultiSelectionEnabled(false);
-		fc.setSelectedFile(this.fileName);
-		fc.setFileFilter(new FileFilter(){
-			@Override
-			public boolean accept(java.io.File pathname) {
-				return pathname.isDirectory() || pathname.getName().endsWith(".pdf");
-			}
-			@Override
-			public String getDescription() {
-				return tr("PDF files");
-			}
-		});
-		int result = fc.showOpenDialog(Main.parent);
-
-		if (result != JFileChooser.APPROVE_OPTION) {
-			return null;
-		}
-		else
-		{
-			return fc.getSelectedFile();
-		}
-	}
-
-	private java.io.File chooseSaveFile() {
-		//get file name
-		JFileChooser fc = new JFileChooser();
-		fc.setAcceptAllFileFilterUsed(true);
-		fc.setMultiSelectionEnabled(false);
-		fc.setFileFilter(new FileFilter(){
-			@Override
-			public boolean accept(java.io.File pathname) {
-				return pathname.isDirectory() || pathname.getName().endsWith(".osm");
-			}
-			@Override
-			public String getDescription() {
-				return tr("OSM files");
-			}
-		});
-		int result = fc.showOpenDialog(Main.parent);
-
-		if (result != JFileChooser.APPROVE_OPTION) {
-			return null;
-		}
-		else
-		{
-			return fc.getSelectedFile();
-		}
-	}
-
-	private void runAsBackgroundTask(final Runnable task, final ActionListener after) {
-		this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-		Thread t = new Thread(new Runnable()
-		{
-			@Override
-			public void run() {
-				task.run();
-
-				SwingUtilities.invokeLater(new Runnable(){
-					@Override
-					public void run() {
-						setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
-						after.actionPerformed(null);
-					}
-				});
-			}
-		});
-		t.start();
-	}
-
-	private PathOptimizer loadPDF(File fileName, ProgressMonitor monitor) {
-
-		monitor.beginTask("", 100);
-		monitor.setTicks(0);
-		monitor.setCustomText(tr("Preparing"));
-
-		double nodesTolerance = 0.0;
-		Color color = null;
-		int maxPaths = Integer.MAX_VALUE;
-
-		if (this.mergeCloseNodesCheck.isSelected()) {
-			try {
-				nodesTolerance = Double.parseDouble(this.mergeCloseNodesTolerance.getText());
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Tolerance is not a number"));
-				return null;
-			}
-		}
-
-		if (this.colorFilterCheck.isSelected()) {
-			try {
-				String colString = this.colorFilterColor.getText().replace("#", "");
-				color = new Color(Integer.parseInt(colString, 16));
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Could not parse color"));
-				return null;
-			}
-		}
-
-		if (this.limitPathCountCheck.isSelected()) {
-			try {
-				maxPaths = Integer.parseInt(this.limitPathCount.getText());
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Could not parse max path count"));
-				return null;
-			}
-		}
-
-
-		monitor.setTicks(10);
-		monitor.setCustomText(tr("Parsing file"));
-
-		PathOptimizer data = new PathOptimizer(nodesTolerance, color, this.splitOnColorChangeCheck.isSelected());
-
-		try {
-			PdfBoxParser parser = new PdfBoxParser(data);
-			parser.parse(fileName, maxPaths, monitor.createSubTaskMonitor(80, false));
-
-		} catch (FileNotFoundException e1) {
-			JOptionPane
-			.showMessageDialog(
-					Main.parent,
-					tr("File not found."));
-			return null;
-		} catch (Exception e) {
-			e.printStackTrace();
-			JOptionPane
-			.showMessageDialog(
-					Main.parent,
-					tr("Error while parsing: {0}", e.getMessage()));
-			return null;
-		}
-
-		monitor.setTicks(80);
-
-		if (this.removeParallelSegmentsCheck.isSelected()) {
-			try {
-				double tolerance = Double.parseDouble(this.removeParallelSegmentsTolerance.getText());
-				monitor.setCustomText(tr("Removing parallel segments"));
-				data.removeParallelLines(tolerance);
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Max distance is not a number"));
-				return null;
-			}
-		}
-
-		if (nodesTolerance > 0.0) {
-			monitor.setTicks(83);
-			monitor.setCustomText(tr("Joining nodes"));
-			data.mergeNodes();
-		}
-
-		monitor.setTicks(85);
-		monitor.setCustomText(tr("Joining adjacent segments"));
-		data.mergeSegments();
-
-		if (this.removeSmallObjectsCheck.isSelected()) {
-			try {
-				double tolerance = Double.parseDouble(this.removeSmallObjectsSize.getText());
-				monitor.setTicks(90);
-				monitor.setCustomText(tr("Removing small objects"));
-
-				data.removeSmallObjects(tolerance);
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Tolerance is not a number"));
-				return null;
-			}
-		}
-
-		if (this.removeLargeObjectsCheck.isSelected()) {
-			try {
-				double tolerance = Double.parseDouble(this.removeLargeObjectsSize.getText());
-				monitor.setTicks(90);
-				monitor.setCustomText(tr("Removing large objects"));
-				data.removeLargeObjects(tolerance);
-			}
-			catch (Exception e) {
-				JOptionPane
-				.showMessageDialog(
-						Main.parent,
-						tr("Tolerance is not a number"));
-				return null;
-			}
-		}
-
-		monitor.setTicks(95);
-		monitor.setCustomText(tr("Finalizing layers"));
-		data.splitLayersByPathKind(this.splitOnShapeClosedCheck.isSelected(), this.splitOnSingleSegmentCheck.isSelected(), this.splitOnOrthogonalCheck.isSelected());
-		data.finish();
-
-		monitor.finishTask();
-		return data;
-	}
-
-
-
-	private FilePlacement parsePlacement() {
-		ProjectionChoice selectedProjection = (ProjectionChoice) this.projectionCombo.getSelectedItem();
-
-		if (selectedProjection == null)
-		{
-			JOptionPane.showMessageDialog(Main.parent, tr("Please set a projection."));
-			return null;
-		}
-
-		FilePlacement placement = new FilePlacement();
-
-		placement.projection = selectedProjection.getProjection();
-
-		try
-		{
-			placement.setPdfBounds(
-					Double.parseDouble(this.minXField.getText()),
-					Double.parseDouble(this.minYField.getText()),
-					Double.parseDouble(this.maxXField.getText()),
-					Double.parseDouble(this.maxYField.getText()));
-			placement.setEastNorthBounds(
-					Double.parseDouble(this.minEastField.getText()),
-					Double.parseDouble(this.minNorthField.getText()),
-					Double.parseDouble(this.maxEastField.getText()),
-					Double.parseDouble(this.maxNorthField.getText()));
-		}
-		catch (Exception e) {
-			JOptionPane.showMessageDialog(Main.parent, tr("Could not parse numbers. Please check."));
-			return null;
-		}
-
-		return placement;
-	}
-
-	private void setPlacement(FilePlacement placement) {
-
-		if (placement == null) {
-			//use default values.
-			placement = new FilePlacement();
-		}
-
-		if (placement.projection != null) {
-			String projectionCode = placement.projection.toCode();
-			BIG_LOOP:
-			for (ProjectionChoice projectionChoice: ProjectionPreference.getProjectionChoices()) {
-				for (String code: projectionChoice.allCodes()) {
-					if (code.equals(projectionCode)) {
-						projectionChoice.getPreferencesFromCode(projectionCode);
-						this.projectionCombo.setSelectedItem(projectionChoice);
-						break BIG_LOOP;
-					}
-				}
-			}
-		}
-
-		this.minXField.setText(Double.toString(placement.minX));
-		this.maxXField.setText(Double.toString(placement.maxX));
-		this.minYField.setText(Double.toString(placement.minY));
-		this.maxYField.setText(Double.toString(placement.maxY));
-		this.minEastField.setText(Double.toString(placement.minEast));
-		this.maxEastField.setText(Double.toString(placement.maxEast));
-		this.minNorthField.setText(Double.toString(placement.minNorth));
-		this.maxNorthField.setText(Double.toString(placement.maxNorth));
-	}
-
-
-	private FilePlacement loadPlacement() {
-		FilePlacement pl = null;
-		//load saved transformation
-		File propertiesFile = new File(fileName.getAbsoluteFile()+ ".placement");
-		try {
-
-			if (propertiesFile.exists()){
-				pl = new FilePlacement();
-				Properties p = new Properties();
-				p.load(new FileInputStream(propertiesFile));
-				pl.fromProperties(p);
-			}
-		}catch (Exception e){
-			pl = null;
-			e.printStackTrace();
-		}
-
-		return pl;
-	}
-
-	private void savePlacement(FilePlacement pl){
-		//load saved transformation
-		File propertiesFile = new File(fileName.getAbsoluteFile()+ ".placement");
-		try {
-			Properties p = pl.toProperties();
-			p.store(new FileOutputStream(propertiesFile), "PDF file placement on OSM");
-		} catch (Exception e){
-			e.printStackTrace();
-		}
-	}
-
-
-	private OsmDataLayer makeLayer(String name, FilePlacement placement, OsmBuilder.Mode mode, ProgressMonitor monitor) {
-		monitor.beginTask(tr("Building JOSM layer"), 100);
-		OsmBuilder builder = new OsmBuilder(placement);
-		DataSet data = builder.build(this.data.getLayers(), mode, monitor.createSubTaskMonitor(50, false));
-		monitor.setTicks(50);
-		monitor.setCustomText(tr("Postprocessing layer"));
-		OsmDataLayer result = new OsmDataLayer(data, name, null);
-		result.onPostLoadFromFile();
-
-		monitor.finishTask();
-		return result;
-	}
-
-	private void placeLayer(OsmDataLayer _layer, FilePlacement placement) {
-		this.removeLayer();
-		this.layer = _layer;
-		Main.getLayerManager().addLayer(this.layer);
-		Main.map.mapView.zoomTo(placement.getWorldBounds(this.data));
-	}
-
-	private void removeLayer() {
-		if (this.layer != null) {
-			Main.getLayerManager().removeLayer(this.layer);
-			this.layer.data.clear(); //saves memory
-			this.layer = null;
-		}
-	}
-
-	private void saveLayer(java.io.File file, FilePlacement placement, ProgressMonitor monitor) {
-		monitor.beginTask(tr("Saving to file."), 1000);
-
-		OsmBuilder builder = new OsmBuilder(placement);
-		DataSet data = builder.build(this.data.getLayers(), OsmBuilder.Mode.Final, monitor.createSubTaskMonitor(500, false));
-		OsmDataLayer layer = new OsmDataLayer(data, file.getName(), file);
-
-		monitor.setCustomText(tr(" Writing to file"));
-		monitor.setTicks(500);
-
-		OsmExporter exporter = new OsmExporter();
-
-		try {
-			exporter.exportData(file, layer);
-		}
-		catch(IOException e){
-			//TODO:
-		}
-
-		monitor.finishTask();
-	}
+public class LoadPdfDialog extends JFrame {
+
+    class LoadProgressRenderer implements ProgressRenderer {
+        private final JProgressBar pBar;
+        private String title = "";
+
+        LoadProgressRenderer(JProgressBar pb) {
+            this.pBar = pb;
+            this.pBar.setMinimum(0);
+            this.pBar.setValue(0);
+            this.pBar.setMaximum(1);
+            this.pBar.setString("");
+            this.pBar.setStringPainted(true);
+
+        }
+
+        @Override
+        public void setCustomText(String message) {
+            this.pBar.setString(this.title + message);
+        }
+
+        @Override
+        public void setIndeterminate(boolean indeterminate) {
+            this.pBar.setIndeterminate(indeterminate);
+        }
+
+        @Override
+        public void setMaximum(int maximum) {
+            this.pBar.setMaximum(maximum);
+        }
+
+        @Override
+        public void setTaskTitle(String taskTitle) {
+            this.title = taskTitle;
+            this.pBar.setString(this.title);
+        }
+
+        @Override
+        public void setValue(int value) {
+            this.pBar.setValue(value);
+        }
+
+        public void finish() {
+            this.pBar.setString(tr("Finished"));
+            this.pBar.setValue(this.pBar.getMaximum());
+        }
+
+    }
+
+    private File fileName;
+    private PathOptimizer data;
+    private OsmDataLayer layer;
+
+    /**
+     * Combobox with all projections available
+     */
+    private JComboBox<ProjectionChoice> projectionCombo;
+    private JButton projectionPreferencesButton;
+    private JTextField minXField;
+    private JTextField minYField;
+    private JTextField minEastField;
+    private JTextField minNorthField;
+    private JButton getMinButton;
+    private JButton okButton;
+    private JButton cancelButton;
+    private JButton getMaxButton;
+    private JTextField maxNorthField;
+    private JTextField maxEastField;
+    private JTextField maxYField;
+    private JTextField maxXField;
+    private JButton loadFileButton;
+    private JButton showButton;
+    private JButton saveButton;
+    private JCheckBox debugModeCheck;
+    private JCheckBox mergeCloseNodesCheck;
+    private JTextField mergeCloseNodesTolerance;
+    private JCheckBox removeSmallObjectsCheck;
+    private JTextField removeSmallObjectsSize;
+    private JTextField colorFilterColor;
+    private JCheckBox colorFilterCheck;
+    private JCheckBox removeParallelSegmentsCheck;
+    private JTextField removeParallelSegmentsTolerance;
+    private JCheckBox removeLargeObjectsCheck;
+    private JTextField removeLargeObjectsSize;
+    private JProgressBar loadProgress;
+    protected OsmDataLayer newLayer;
+
+    private LoadProgressRenderer progressRenderer;
+    private JCheckBox limitPathCountCheck;
+    private JTextField limitPathCount;
+    private JCheckBox splitOnColorChangeCheck;
+    private JCheckBox splitOnShapeClosedCheck;
+    private JCheckBox splitOnSingleSegmentCheck;
+    private JCheckBox splitOnOrthogonalCheck;
+
+    public LoadPdfDialog() {
+        this.buildGUI();
+        FilePlacement pl = new FilePlacement();
+        this.setPlacement(pl);
+        this.addListeners();
+        this.removeLayer();
+    }
+
+     private void addListeners() {
+
+        this.projectionCombo.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                updateProjectionPrefButton();
+            }
+
+        });
+        this.projectionPreferencesButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                showProjectionPreferences();
+            }
+        });
+
+        this.loadFileButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                loadFilePressed();
+            }
+        });
+
+        this.okButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                okPressed();
+            }
+        });
+
+        this.saveButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                savePressed();
+            }
+        });
+
+        this.showButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                showPressed();
+            }
+        });
+
+        this.cancelButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                cancelPressed();
+            }
+        });
+
+        this.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                cancelPressed();
+            }
+        });
+
+        this.getMinButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                getMinPressed();
+            }
+        });
+
+        this.getMaxButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                getMaxPressed();
+            }
+        });
+    }
+
+    private void buildGUI() {
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridheight = 1; c.gridwidth = 1; c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH;
+
+        this.projectionCombo = new JComboBox<>();
+        for (ProjectionChoice p: ProjectionPreference.getProjectionChoices()) {
+            this.projectionCombo.addItem(p);
+        }
+
+        this.projectionPreferencesButton = new JButton(tr("Prefs"));
+        updateProjectionPrefButton();
+
+        this.loadFileButton = new JButton(tr("Load file..."));
+        this.okButton = new JButton(tr("Place"));
+        this.saveButton = new JButton(tr("Save"));
+        this.showButton = new JButton(tr("Show target"));
+        this.cancelButton = new JButton(tr("Discard"));
+        this.loadProgress = new JProgressBar();
+        this.progressRenderer = new LoadProgressRenderer(this.loadProgress);
+
+        this.minXField = new JTextField("");
+        this.minYField = new JTextField("");
+        this.minEastField = new JTextField("");
+        this.minNorthField = new JTextField("");
+        this.getMinButton = new JButton(tr("Take X and Y from selected node"));
+
+        this.maxXField = new JTextField("");
+        this.maxYField = new JTextField("");
+        this.maxEastField = new JTextField("");
+        this.maxNorthField = new JTextField("");
+        this.getMaxButton = new JButton(tr("Take X and Y from selected node"));
+
+        this.debugModeCheck = new JCheckBox(tr("Debug info"));
+        this.mergeCloseNodesCheck = new JCheckBox(tr("Merge close nodes"));
+        this.mergeCloseNodesTolerance = new JTextField("1e-3");
+
+        this.removeSmallObjectsCheck = new JCheckBox(tr("Remove objects smaller than"));
+        this.removeSmallObjectsSize = new JTextField("1");
+
+        this.removeLargeObjectsCheck = new JCheckBox(tr("Remove objects larger than"));
+        this.removeLargeObjectsSize = new JTextField("10");
+
+
+        this.colorFilterCheck = new JCheckBox(tr("Only this color"));
+        this.colorFilterColor = new JTextField("#000000");
+
+        this.removeParallelSegmentsCheck = new JCheckBox(tr("Remove parallel lines"));
+        this.removeParallelSegmentsTolerance = new JTextField("3");
+
+        this.limitPathCountCheck = new JCheckBox(tr("Take only first X paths"));
+        this.limitPathCount = new JTextField("10000");
+
+        this.splitOnColorChangeCheck = new JCheckBox(tr("Color/width change"));
+        this.splitOnShapeClosedCheck = new JCheckBox(tr("Shape closed"));
+        this.splitOnSingleSegmentCheck = new JCheckBox(tr("Single segments"));
+        this.splitOnOrthogonalCheck = new JCheckBox(tr("Orthogonal shapes"));
+
+        JPanel configPanel = new JPanel(new GridBagLayout());
+        configPanel.setBorder(BorderFactory.createTitledBorder(tr("Import settings")));
+        c.gridx = 0; c.gridy = 0; c.gridwidth = 1;
+        configPanel.add(this.mergeCloseNodesCheck, c);
+        c.gridx = 1; c.gridy = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
+        configPanel.add(new JLabel("Tolerance :"), c);
+        c.gridx = 2; c.gridy = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
+        configPanel.add(this.mergeCloseNodesTolerance, c);
+
+        c.gridx = 0; c.gridy = 1; c.gridwidth = 1;
+        configPanel.add(this.removeSmallObjectsCheck, c);
+        c.gridx = 1; c.gridy = 1; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
+        configPanel.add(new JLabel("Tolerance :"), c);
+        c.gridx = 2; c.gridy = 1; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
+        configPanel.add(this.removeSmallObjectsSize, c);
+
+        c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
+        configPanel.add(this.removeLargeObjectsCheck, c);
+        c.gridx = 1; c.gridy = 2; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
+        configPanel.add(new JLabel("Tolerance :"), c);
+        c.gridx = 2; c.gridy = 2; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
+        configPanel.add(this.removeLargeObjectsSize, c);
+
+        c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
+        configPanel.add(this.removeParallelSegmentsCheck, c);
+        c.gridx = 1; c.gridy = 3; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHEAST;
+        configPanel.add(new JLabel("Max distance :"), c);
+        c.gridx = 2; c.gridy = 3; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST;
+        configPanel.add(this.removeParallelSegmentsTolerance, c);
+
+
+        c.gridx = 0; c.gridy = 4; c.gridwidth = 2;
+        configPanel.add(this.limitPathCountCheck, c);
+        c.gridx = 2; c.gridy = 4; c.gridwidth = 1;
+        configPanel.add(this.limitPathCount, c);
+
+        c.gridx = 0; c.gridy = 5; c.gridwidth = 1;
+        configPanel.add(this.colorFilterCheck, c);
+        c.gridx = 2; c.gridy = 5; c.gridwidth = 1;
+        configPanel.add(this.colorFilterColor, c);
+
+        c.gridx = 0; c.gridy = 6; c.gridwidth = 2;
+        configPanel.add(this.debugModeCheck, c);
+
+
+        c.gridx = 0; c.gridy = 7; c.gridwidth = 1;
+        configPanel.add(new JLabel(tr("Introduce separate layers for:")), c);
+        c.gridx = 1; c.gridy = 7; c.gridwidth = 1;
+        configPanel.add(this.splitOnShapeClosedCheck, c);
+        c.gridx = 2; c.gridy = 7; c.gridwidth = 1;
+        configPanel.add(this.splitOnSingleSegmentCheck, c);
+        c.gridx = 1; c.gridy = 8; c.gridwidth = 1;
+        configPanel.add(this.splitOnColorChangeCheck, c);
+        c.gridx = 2; c.gridy = 8; c.gridwidth = 1;
+        configPanel.add(this.splitOnOrthogonalCheck, c);
+
+
+        JPanel projectionPanel = new JPanel(new GridBagLayout());
+        projectionPanel.setBorder(BorderFactory.createTitledBorder(tr("Bind to coordinates")));
+
+        JPanel projectionSubPanel = new JPanel();
+        projectionSubPanel.setLayout(new BoxLayout(projectionSubPanel, BoxLayout.X_AXIS));
+
+        projectionSubPanel.add(new JLabel(tr("Projection:")));
+        projectionSubPanel.add(this.projectionCombo);
+        projectionSubPanel.add(this.projectionPreferencesButton);
+        c.gridx = 0; c.gridy = 0; c.gridwidth = 3;
+        projectionPanel.add(projectionSubPanel, c);
+
+        c.gridx = 0; c.gridy = 1; c.gridwidth = 2;
+        projectionPanel.add(new JLabel(tr("Bottom left (min) corner:")), c);
+        c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
+        projectionPanel.add(new JLabel(tr("PDF X and Y")), c);
+        c.gridx = 1; c.gridy = 2; c.gridwidth = 1;
+        projectionPanel.add(new JLabel(tr("East and North")), c);
+        c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
+        projectionPanel.add(this.minXField, c);
+        c.gridx = 0; c.gridy = 4; c.gridwidth = 1;
+        projectionPanel.add(this.minYField, c);
+        c.gridx = 1; c.gridy = 3; c.gridwidth = 1;
+        projectionPanel.add(this.minEastField, c);
+        c.gridx = 1; c.gridy = 4; c.gridwidth = 1;
+        projectionPanel.add(this.minNorthField, c);
+        c.gridx = 0; c.gridy = 5; c.gridwidth = 1;
+        projectionPanel.add(this.getMinButton, c);
+
+
+        c.gridx = 0; c.gridy = 6; c.gridwidth = 2;
+        projectionPanel.add(new JLabel(tr("Top right (max) corner:")), c);
+        c.gridx = 0; c.gridy = 7; c.gridwidth = 1;
+        projectionPanel.add(new JLabel(tr("PDF X and Y")), c);
+        c.gridx = 1; c.gridy = 7; c.gridwidth = 1;
+        projectionPanel.add(new JLabel(tr("East and North")), c);
+        c.gridx = 0; c.gridy = 8; c.gridwidth = 1;
+        projectionPanel.add(this.maxXField, c);
+        c.gridx = 0; c.gridy = 9; c.gridwidth = 1;
+        projectionPanel.add(this.maxYField, c);
+        c.gridx = 1; c.gridy = 8; c.gridwidth = 1;
+        projectionPanel.add(this.maxEastField, c);
+        c.gridx = 1; c.gridy = 9; c.gridwidth = 1;
+        projectionPanel.add(this.maxNorthField, c);
+        c.gridx = 0; c.gridy = 10; c.gridwidth = 1;
+        projectionPanel.add(this.getMaxButton, c);
+
+        JPanel okCancelPanel = new JPanel(new GridLayout(1, 3));
+        okCancelPanel.add(this.cancelButton);
+        okCancelPanel.add(this.showButton);
+        okCancelPanel.add(this.okButton);
+        okCancelPanel.add(this.saveButton);
+
+        JPanel panel = new JPanel(new GridBagLayout());
+        c.gridx = 0; c.gridy = 0; c.gridwidth = 1;
+        panel.add(configPanel, c);
+        c.gridx = 0; c.gridy = 1; c.gridwidth = 1;
+        panel.add(loadFileButton, c);
+        c.gridx = 0; c.gridy = 2; c.gridwidth = 1;
+        panel.add(projectionPanel, c);
+        c.gridx = 0; c.gridy = 3; c.gridwidth = 1;
+        panel.add(okCancelPanel, c);
+        c.gridx = 0; c.gridy = 4; c.gridwidth = 1;
+        panel.add(this.loadProgress, c);
+
+        this.setSize(450, 550);
+        this.setContentPane(panel);
+    }
+
+    private class ProjectionSubPrefsDialog extends JDialog {
+        private final ProjectionChoice projPref;
+        private OKAction actOK;
+        private CancelAction actCancel;
+        private JPanel projPrefPanel;
+
+        ProjectionSubPrefsDialog(Component parent, ProjectionChoice pr) {
+            super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
+
+            projPref = pr;
+
+            setTitle(tr("Projection Preferences"));
+            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+            build();
+        }
+
+        protected void makeButtonRespondToEnter(SideButton btn) {
+            btn.setFocusable(true);
+            btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter");
+            btn.getActionMap().put("enter", btn.getAction());
+        }
+
+        protected JPanel buildInputForm() {
+            return projPref.getPreferencePanel(null);
+        }
+
+        protected JPanel buildButtonRow() {
+            JPanel pnl = new JPanel(new FlowLayout());
+
+            actOK = new OKAction();
+            actCancel = new CancelAction();
+
+            SideButton btn;
+            pnl.add(btn = new SideButton(actOK));
+            makeButtonRespondToEnter(btn);
+            pnl.add(btn = new SideButton(actCancel));
+            makeButtonRespondToEnter(btn);
+            return pnl;
+        }
+
+        protected void build() {
+            projPrefPanel = buildInputForm();
+            getContentPane().setLayout(new BorderLayout());
+            getContentPane().add(projPrefPanel, BorderLayout.CENTER);
+            getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
+            pack();
+
+            // make dialog respond to ESCAPE
+            getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
+                    KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
+            getRootPane().getActionMap().put("escape", actCancel);
+        }
+
+        class OKAction extends AbstractAction {
+            OKAction() {
+                putValue(NAME, tr("OK"));
+                putValue(SHORT_DESCRIPTION, tr("Close the dialog and apply projection preferences"));
+                putValue(SMALL_ICON, ImageProvider.get("ok"));
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                projPref.setPreferences(projPref.getPreferences(projPrefPanel));
+                setVisible(false);
+            }
+        }
+
+        class CancelAction extends AbstractAction {
+            CancelAction() {
+                putValue(NAME, tr("Cancel"));
+                putValue(SHORT_DESCRIPTION, tr("Close the dialog, discard projection preference changes"));
+                putValue(SMALL_ICON, ImageProvider.get("cancel"));
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                setVisible(false);
+            }
+        }
+
+        @Override
+        public void setVisible(boolean visible) {
+            if (visible) {
+                new WindowGeometry(
+                    getClass().getName() + ".geometry",
+                    WindowGeometry.centerOnScreen(new Dimension(400, 300))).applySafe(this);
+            } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
+                new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+            }
+            super.setVisible(visible);
+        }
+    }
+
+    private void updateProjectionPrefButton() {
+//        ProjectionChoice proj = (ProjectionChoice) projectionCombo.getSelectedItem();
+
+        //TODO
+        // Enable/disable pref button
+//        if(!(proj instanceof ProjectionSubPrefs)) {
+//            projectionPreferencesButton.setEnabled(false);
+//        } else {
+            projectionPreferencesButton.setEnabled(true);
+//        }
+    }
+
+    private void showProjectionPreferences() {
+        ProjectionChoice proj = (ProjectionChoice) projectionCombo.getSelectedItem();
+
+        ProjectionSubPrefsDialog dlg = new ProjectionSubPrefsDialog(this, proj);
+        dlg.setVisible(true);
+
+    }
+
+    private void loadFilePressed() {
+        final File newFileName = this.chooseFile();
+
+        if (newFileName == null) {
+            return;
+        }
+
+        this.removeLayer();
+
+        this.loadFileButton.setEnabled(false);
+        this.loadFileButton.setText(tr("Loading..."));
+
+        this.runAsBackgroundTask(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        //async part
+                        SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
+                        monitor.beginTask("Loading file", 1000);
+                        data = loadPDF(newFileName, monitor.createSubTaskMonitor(500, false));
+                        OsmBuilder.Mode mode = LoadPdfDialog.this.debugModeCheck.isSelected() ? OsmBuilder.Mode.Debug : OsmBuilder.Mode.Draft;
+
+                        if (data != null) {
+                            LoadPdfDialog.this.newLayer = LoadPdfDialog.this.makeLayer(
+                                    tr("PDF file preview"), new FilePlacement(), mode, monitor.createSubTaskMonitor(500, false));
+                        }
+
+                        monitor.finishTask();
+                        progressRenderer.finish();
+                    }
+                },
+                new ActionListener() {
+
+                    @Override
+                    public void actionPerformed(ActionEvent e) {
+                        //sync part
+                        if (data != null) {
+                            LoadPdfDialog.this.placeLayer(newLayer, new FilePlacement());
+                            fileName = newFileName;
+                            newLayer = null;
+                            LoadPdfDialog.this.loadFileButton.setText(tr("Loaded"));
+                            LoadPdfDialog.this.loadFileButton.setEnabled(true);
+                            FilePlacement placement = LoadPdfDialog.this.loadPlacement();
+                            LoadPdfDialog.this.setPlacement(placement);
+                        }
+                    }
+                });
+    }
+
+    private FilePlacement preparePlacement() {
+        FilePlacement placement = this.parsePlacement();
+        if (placement == null) {
+            return null;
+        }
+
+        String transformError = placement.prepareTransform();
+        if (transformError != null) {
+            JOptionPane.showMessageDialog(Main.parent, transformError);
+            return null;
+        }
+
+        this.savePlacement(placement);
+
+        return placement;
+    }
+
+    private void okPressed() {
+
+        final FilePlacement placement = preparePlacement();
+        if (placement == null) {
+            return;
+        }
+
+        this.removeLayer();
+
+        this.runAsBackgroundTask(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        //async part
+                        SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
+                        LoadPdfDialog.this.newLayer = LoadPdfDialog.this.makeLayer(
+                                tr("Imported PDF: ") + fileName, placement, OsmBuilder.Mode.Final, monitor);
+                        progressRenderer.finish();
+                    }
+                },
+                new ActionListener() {
+
+                    @Override
+                    public void actionPerformed(ActionEvent e) {
+                        //sync part
+                        //rebuild layer with latest projection
+                        LoadPdfDialog.this.placeLayer(newLayer, placement);
+                        LoadPdfDialog.this.setVisible(false);
+                    }
+                });
+    }
+
+    private void savePressed() {
+
+        final FilePlacement placement = preparePlacement();
+        if (placement == null) {
+            return;
+        }
+
+        final java.io.File file = this.chooseSaveFile();
+
+        if (file == null) {
+            return;
+        }
+
+        this.removeLayer();
+
+        this.runAsBackgroundTask(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        //async part
+                        SwingRenderingProgressMonitor monitor = new SwingRenderingProgressMonitor(progressRenderer);
+                        LoadPdfDialog.this.saveLayer(file, placement, monitor);
+                        progressRenderer.finish();
+                    }
+                },
+                new ActionListener() {
+
+                    @Override
+                    public void actionPerformed(ActionEvent e) {
+                        //sync part
+                        LoadPdfDialog.this.setVisible(false);
+                    }
+                });
+    }
+
+    private void showPressed() {
+
+        FilePlacement placement = preparePlacement();
+        if (placement == null) {
+            return;
+        }
+
+        //zoom to new location
+        Main.map.mapView.zoomTo(placement.getWorldBounds(this.data));
+        Main.map.repaint();
+    }
+
+    private void cancelPressed() {
+        this.removeLayer();
+        this.setVisible(false);
+    }
+
+    private void getMinPressed() {
+        EastNorth en = this.getSelectedCoor();
+
+        if (en != null) {
+            this.minXField.setText(Double.toString(en.east()));
+            this.minYField.setText(Double.toString(en.north()));
+        }
+    }
+
+    private void getMaxPressed() {
+        EastNorth en = this.getSelectedCoor();
+
+        if (en != null) {
+            this.maxXField.setText(Double.toString(en.east()));
+            this.maxYField.setText(Double.toString(en.north()));
+        }
+    }
+
+    // Implementation methods
+
+    private EastNorth getSelectedCoor() {
+        Collection<OsmPrimitive> selected = Main.getLayerManager().getEditDataSet().getSelected();
+
+        if (selected.size() != 1 || !(selected.iterator().next() instanceof Node)) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please select exactly one node."));
+            return null;
+        }
+
+        LatLon ll = ((Node) selected.iterator().next()).getCoor();
+        FilePlacement pl = new FilePlacement();
+        return pl.reverseTransform(ll);
+    }
+
+    private java.io.File chooseFile() {
+        //get file name
+        JFileChooser fc = new JFileChooser();
+        fc.setAcceptAllFileFilterUsed(false);
+        fc.setMultiSelectionEnabled(false);
+        fc.setSelectedFile(this.fileName);
+        fc.setFileFilter(new FileFilter() {
+            @Override
+            public boolean accept(java.io.File pathname) {
+                return pathname.isDirectory() || pathname.getName().endsWith(".pdf");
+            }
+
+            @Override
+            public String getDescription() {
+                return tr("PDF files");
+            }
+        });
+        int result = fc.showOpenDialog(Main.parent);
+
+        if (result != JFileChooser.APPROVE_OPTION) {
+            return null;
+        } else {
+            return fc.getSelectedFile();
+        }
+    }
+
+    private java.io.File chooseSaveFile() {
+        //get file name
+        JFileChooser fc = new JFileChooser();
+        fc.setAcceptAllFileFilterUsed(true);
+        fc.setMultiSelectionEnabled(false);
+        fc.setFileFilter(new FileFilter() {
+            @Override
+            public boolean accept(java.io.File pathname) {
+                return pathname.isDirectory() || pathname.getName().endsWith(".osm");
+            }
+
+            @Override
+            public String getDescription() {
+                return tr("OSM files");
+            }
+        });
+        int result = fc.showOpenDialog(Main.parent);
+
+        if (result != JFileChooser.APPROVE_OPTION) {
+            return null;
+        } else {
+            return fc.getSelectedFile();
+        }
+    }
+
+    private void runAsBackgroundTask(final Runnable task, final ActionListener after) {
+        this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        Thread t = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                task.run();
+
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                        after.actionPerformed(null);
+                    }
+                });
+            }
+        });
+        t.start();
+    }
+
+    private PathOptimizer loadPDF(File fileName, ProgressMonitor monitor) {
+
+        monitor.beginTask("", 100);
+        monitor.setTicks(0);
+        monitor.setCustomText(tr("Preparing"));
+
+        double nodesTolerance = 0.0;
+        Color color = null;
+        int maxPaths = Integer.MAX_VALUE;
+
+        if (this.mergeCloseNodesCheck.isSelected()) {
+            try {
+                nodesTolerance = Double.parseDouble(this.mergeCloseNodesTolerance.getText());
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Tolerance is not a number"));
+                return null;
+            }
+        }
+
+        if (this.colorFilterCheck.isSelected()) {
+            try {
+                String colString = this.colorFilterColor.getText().replace("#", "");
+                color = new Color(Integer.parseInt(colString, 16));
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Could not parse color"));
+                return null;
+            }
+        }
+
+        if (this.limitPathCountCheck.isSelected()) {
+            try {
+                maxPaths = Integer.parseInt(this.limitPathCount.getText());
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Could not parse max path count"));
+                return null;
+            }
+        }
+
+
+        monitor.setTicks(10);
+        monitor.setCustomText(tr("Parsing file"));
+
+        PathOptimizer data = new PathOptimizer(nodesTolerance, color, this.splitOnColorChangeCheck.isSelected());
+
+        try {
+            PdfBoxParser parser = new PdfBoxParser(data);
+            parser.parse(fileName, maxPaths, monitor.createSubTaskMonitor(80, false));
+
+        } catch (FileNotFoundException e1) {
+            JOptionPane
+            .showMessageDialog(
+                    Main.parent,
+                    tr("File not found."));
+            return null;
+        } catch (Exception e) {
+            e.printStackTrace();
+            JOptionPane
+            .showMessageDialog(
+                    Main.parent,
+                    tr("Error while parsing: {0}", e.getMessage()));
+            return null;
+        }
+
+        monitor.setTicks(80);
+
+        if (this.removeParallelSegmentsCheck.isSelected()) {
+            try {
+                double tolerance = Double.parseDouble(this.removeParallelSegmentsTolerance.getText());
+                monitor.setCustomText(tr("Removing parallel segments"));
+                data.removeParallelLines(tolerance);
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Max distance is not a number"));
+                return null;
+            }
+        }
+
+        if (nodesTolerance > 0.0) {
+            monitor.setTicks(83);
+            monitor.setCustomText(tr("Joining nodes"));
+            data.mergeNodes();
+        }
+
+        monitor.setTicks(85);
+        monitor.setCustomText(tr("Joining adjacent segments"));
+        data.mergeSegments();
+
+        if (this.removeSmallObjectsCheck.isSelected()) {
+            try {
+                double tolerance = Double.parseDouble(this.removeSmallObjectsSize.getText());
+                monitor.setTicks(90);
+                monitor.setCustomText(tr("Removing small objects"));
+
+                data.removeSmallObjects(tolerance);
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Tolerance is not a number"));
+                return null;
+            }
+        }
+
+        if (this.removeLargeObjectsCheck.isSelected()) {
+            try {
+                double tolerance = Double.parseDouble(this.removeLargeObjectsSize.getText());
+                monitor.setTicks(90);
+                monitor.setCustomText(tr("Removing large objects"));
+                data.removeLargeObjects(tolerance);
+            } catch (Exception e) {
+                JOptionPane
+                .showMessageDialog(
+                        Main.parent,
+                        tr("Tolerance is not a number"));
+                return null;
+            }
+        }
+
+        monitor.setTicks(95);
+        monitor.setCustomText(tr("Finalizing layers"));
+        data.splitLayersByPathKind(
+                this.splitOnShapeClosedCheck.isSelected(),
+                this.splitOnSingleSegmentCheck.isSelected(),
+                this.splitOnOrthogonalCheck.isSelected());
+        data.finish();
+
+        monitor.finishTask();
+        return data;
+    }
+
+    private FilePlacement parsePlacement() {
+        ProjectionChoice selectedProjection = (ProjectionChoice) this.projectionCombo.getSelectedItem();
+
+        if (selectedProjection == null) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please set a projection."));
+            return null;
+        }
+
+        FilePlacement placement = new FilePlacement();
+
+        placement.projection = selectedProjection.getProjection();
+
+        try {
+            placement.setPdfBounds(
+                    Double.parseDouble(this.minXField.getText()),
+                    Double.parseDouble(this.minYField.getText()),
+                    Double.parseDouble(this.maxXField.getText()),
+                    Double.parseDouble(this.maxYField.getText()));
+            placement.setEastNorthBounds(
+                    Double.parseDouble(this.minEastField.getText()),
+                    Double.parseDouble(this.minNorthField.getText()),
+                    Double.parseDouble(this.maxEastField.getText()),
+                    Double.parseDouble(this.maxNorthField.getText()));
+        } catch (Exception e) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Could not parse numbers. Please check."));
+            return null;
+        }
+
+        return placement;
+    }
+
+    private void setPlacement(FilePlacement placement) {
+
+        if (placement == null) {
+            //use default values.
+            placement = new FilePlacement();
+        }
+
+        if (placement.projection != null) {
+            String projectionCode = placement.projection.toCode();
+            BIG_LOOP:
+            for (ProjectionChoice projectionChoice: ProjectionPreference.getProjectionChoices()) {
+                for (String code: projectionChoice.allCodes()) {
+                    if (code.equals(projectionCode)) {
+                        projectionChoice.getPreferencesFromCode(projectionCode);
+                        this.projectionCombo.setSelectedItem(projectionChoice);
+                        break BIG_LOOP;
+                    }
+                }
+            }
+        }
+
+        this.minXField.setText(Double.toString(placement.minX));
+        this.maxXField.setText(Double.toString(placement.maxX));
+        this.minYField.setText(Double.toString(placement.minY));
+        this.maxYField.setText(Double.toString(placement.maxY));
+        this.minEastField.setText(Double.toString(placement.minEast));
+        this.maxEastField.setText(Double.toString(placement.maxEast));
+        this.minNorthField.setText(Double.toString(placement.minNorth));
+        this.maxNorthField.setText(Double.toString(placement.maxNorth));
+    }
+
+    private FilePlacement loadPlacement() {
+        FilePlacement pl = null;
+        //load saved transformation
+        File propertiesFile = new File(fileName.getAbsoluteFile()+ ".placement");
+        try {
+
+            if (propertiesFile.exists()) {
+                pl = new FilePlacement();
+                Properties p = new Properties();
+                p.load(new FileInputStream(propertiesFile));
+                pl.fromProperties(p);
+            }
+        } catch (Exception e) {
+            pl = null;
+            e.printStackTrace();
+        }
+
+        return pl;
+    }
+
+    private void savePlacement(FilePlacement pl) {
+        //load saved transformation
+        File propertiesFile = new File(fileName.getAbsoluteFile()+ ".placement");
+        try {
+            Properties p = pl.toProperties();
+            p.store(new FileOutputStream(propertiesFile), "PDF file placement on OSM");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private OsmDataLayer makeLayer(String name, FilePlacement placement, OsmBuilder.Mode mode, ProgressMonitor monitor) {
+        monitor.beginTask(tr("Building JOSM layer"), 100);
+        OsmBuilder builder = new OsmBuilder(placement);
+        DataSet data = builder.build(this.data.getLayers(), mode, monitor.createSubTaskMonitor(50, false));
+        monitor.setTicks(50);
+        monitor.setCustomText(tr("Postprocessing layer"));
+        OsmDataLayer result = new OsmDataLayer(data, name, null);
+        result.onPostLoadFromFile();
+
+        monitor.finishTask();
+        return result;
+    }
+
+    private void placeLayer(OsmDataLayer _layer, FilePlacement placement) {
+        this.removeLayer();
+        this.layer = _layer;
+        Main.getLayerManager().addLayer(this.layer);
+        Main.map.mapView.zoomTo(placement.getWorldBounds(this.data));
+    }
+
+    private void removeLayer() {
+        if (this.layer != null) {
+            Main.getLayerManager().removeLayer(this.layer);
+            this.layer.data.clear(); //saves memory
+            this.layer = null;
+        }
+    }
+
+    private void saveLayer(java.io.File file, FilePlacement placement, ProgressMonitor monitor) {
+        monitor.beginTask(tr("Saving to file."), 1000);
+
+        OsmBuilder builder = new OsmBuilder(placement);
+        DataSet data = builder.build(this.data.getLayers(), OsmBuilder.Mode.Final, monitor.createSubTaskMonitor(500, false));
+        OsmDataLayer layer = new OsmDataLayer(data, file.getName(), file);
+
+        monitor.setCustomText(tr(" Writing to file"));
+        monitor.setTicks(500);
+
+        OsmExporter exporter = new OsmExporter();
+
+        try {
+            exporter.exportData(file, layer);
+        } catch (IOException e) {
+            Main.error(e);
+        }
+
+        monitor.finishTask();
+    }
 
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/OrthogonalShapesFilter.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/OrthogonalShapesFilter.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/OrthogonalShapesFilter.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -4,42 +5,42 @@
 
 public class OrthogonalShapesFilter {
-	private double tolerance;
+    private double tolerance;
 
-	public OrthogonalShapesFilter(double toleranceDegrees) {
-		tolerance = Math.toRadians(toleranceDegrees);
-	}
+    public OrthogonalShapesFilter(double toleranceDegrees) {
+        tolerance = Math.toRadians(toleranceDegrees);
+    }
 
-	public boolean isOrthogonal(PdfPath path) {
+    public boolean isOrthogonal(PdfPath path) {
 
-		if (path.points.size() < 3)
-			return false;
+        if (path.points.size() < 3)
+            return false;
 
-		int targetPos = path.isClosed() ? path.points.size(): path.points.size() - 1;
+        int targetPos = path.isClosed() ? path.points.size() : path.points.size() - 1;
 
-		for(int pos = 1; pos < targetPos; pos++) {
-			Point2D p1 = path.points.get(pos -1);
-			Point2D p2 = path.points.get(pos);
-			Point2D p3 = pos+1 == path.points.size() ? path.points.get(1) : path.points.get(pos+1);
+        for (int pos = 1; pos < targetPos; pos++) {
+            Point2D p1 = path.points.get(pos -1);
+            Point2D p2 = path.points.get(pos);
+            Point2D p3 = pos+1 == path.points.size() ? path.points.get(1) : path.points.get(pos+1);
 
-			double angle1 = Math.atan2(p2.getY() - p1.getY(),p2.getX() - p1.getX());
-			double angle2 = Math.atan2(p3.getY() - p2.getY(),p3.getX() - p2.getX());
+            double angle1 = Math.atan2(p2.getY() - p1.getY(), p2.getX() - p1.getX());
+            double angle2 = Math.atan2(p3.getY() - p2.getY(), p3.getX() - p2.getX());
 
-			double angleDifference = angle1 - angle2;
-			while (angleDifference < 0) angleDifference += Math.PI;
+            double angleDifference = angle1 - angle2;
+            while (angleDifference < 0) angleDifference += Math.PI;
 
-			//test straight angles
-			boolean hasGoodVariant = false;
+            //test straight angles
+            boolean hasGoodVariant = false;
 
-			for (int quadrant = 0; quadrant <= 4; quadrant ++) {
-				double difference = angleDifference - Math.PI / 2 * quadrant;
-				if (Math.abs(difference) <= tolerance)
-					hasGoodVariant = true;
-			}
+            for (int quadrant = 0; quadrant <= 4; quadrant++) {
+                double difference = angleDifference - Math.PI / 2 * quadrant;
+                if (Math.abs(difference) <= tolerance)
+                    hasGoodVariant = true;
+            }
 
-			if (!hasGoodVariant)
-				return false;
-		}
+            if (!hasGoodVariant)
+                return false;
+        }
 
-		return true;
-	}
+        return true;
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/OsmBuilder.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/OsmBuilder.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/OsmBuilder.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -19,178 +20,173 @@
 public class OsmBuilder {
 
-	enum Mode {Draft, Final, Debug};
+    enum Mode { Draft, Final, Debug }
 
-	private final FilePlacement placement;
+    private final FilePlacement placement;
 
-	private String layerName;
-	private String fillName;
-	private String lineName;
-	private Mode mode;
+    private String layerName;
+    private String fillName;
+    private String lineName;
+    private Mode mode;
 
-	private ProgressMonitor monitor;
-	private int monitorPos;
-	private int monitorTotal;
+    private ProgressMonitor monitor;
+    private int monitorPos;
+    private int monitorTotal;
 
-	public OsmBuilder(FilePlacement placement)
-	{
-		this.placement = placement;
-	}
+    public OsmBuilder(FilePlacement placement) {
+        this.placement = placement;
+    }
 
-	public DataSet build(List<LayerContents> data, Mode mode, ProgressMonitor monitor) {
+    public DataSet build(List<LayerContents> data, Mode mode, ProgressMonitor monitor) {
 
-		this.monitor = monitor;
-		this.monitorPos = 0;
-		this.mode = mode;
-		DataSet result = new DataSet();
+        this.monitor = monitor;
+        this.monitorPos = 0;
+        this.mode = mode;
+        DataSet result = new DataSet();
 
-		//count total items for progress monitor.
-		this.monitorTotal = 0;
-		for (LayerContents layer: data) {
-			this.monitorTotal += layer.paths.size();
-			for(PdfMultiPath mp: layer.multiPaths){
-				this.monitorTotal += mp.paths.size();
-			}
-		}
+        //count total items for progress monitor.
+        this.monitorTotal = 0;
+        for (LayerContents layer: data) {
+            this.monitorTotal += layer.paths.size();
+            for (PdfMultiPath mp: layer.multiPaths) {
+                this.monitorTotal += mp.paths.size();
+            }
+        }
 
-		monitor.beginTask(tr("Building JOSM layer."), this.monitorTotal);
+        monitor.beginTask(tr("Building JOSM layer."), this.monitorTotal);
 
 
-		for (LayerContents layer: data) {
-			this.addLayer(result, layer);
-		}
+        for (LayerContents layer: data) {
+            this.addLayer(result, layer);
+        }
 
-		monitor.finishTask();
-		return result;
-	}
+        monitor.finishTask();
+        return result;
+    }
 
+    private void addLayer(DataSet target, LayerContents layer) {
+        Map<Point2D, Node> point2Node = new HashMap<>();
 
-	private void addLayer(DataSet target, LayerContents layer) {
-		Map<Point2D, Node> point2Node = new HashMap<>();
+        this.fillName = this.printColor(layer.info.fill);
+        this.lineName = this.printColor(layer.info.stroke);
+        this.layerName = "" + layer.info.nr;
 
-		this.fillName = this.printColor(layer.info.fill);
-		this.lineName = this.printColor(layer.info.stroke);
-		this.layerName = "" + layer.info.nr;
+        //insert nodes
+        for (Point2D pt: layer.points) {
+            Node node = new Node();
+            node.setCoor(this.placement.tranformCoords(pt));
 
-		//insert nodes
-		for(Point2D pt: layer.points) {
-			Node node = new Node();
-			node.setCoor(this.placement.tranformCoords(pt));
+            target.addPrimitive(node);
+            point2Node.put(pt, node);
+        }
 
-			target.addPrimitive(node);
-			point2Node.put(pt, node);
-		}
+        //insert ways
+        Map<PdfPath, Way> path2Way = new HashMap<>();
 
-		//insert ways
-		Map<PdfPath, Way> path2Way = new HashMap<>();
+        for (PdfPath path: layer.paths) {
+            Way w = this.insertWay(path, point2Node, -1, false);
+            target.addPrimitive(w);
+            path2Way.put(path, w);
+        }
 
-		for (PdfPath path: layer.paths){
-			Way w = this.insertWay(path, point2Node, -1, false);
-			target.addPrimitive(w);
-			path2Way.put(path, w);
-		}
+        int pathId = 0;
+        for (PdfMultiPath mpath: layer.multiPaths) {
+            for (PdfPath path: mpath.paths) {
+                Way w = this.insertWay(path, point2Node, pathId, true);
+                target.addPrimitive(w);
+                path2Way.put(path, w);
+            }
+            pathId++;
+        }
 
-		int pathId = 0;
-		for (PdfMultiPath mpath: layer.multiPaths) {
-			for (PdfPath path: mpath.paths){
-				Way w = this.insertWay(path, point2Node, pathId, true);
-				target.addPrimitive(w);
-				path2Way.put(path, w);
-			}
-			pathId ++;
-		}
+        if (this.mode != Mode.Draft) {
+            //insert relations
+            for (PdfMultiPath mpath: layer.multiPaths) {
+                Relation rel = new Relation();
 
-		if (this.mode != Mode.Draft) {
-			//insert relations
-			for (PdfMultiPath mpath: layer.multiPaths) {
-				Relation rel = new Relation();
+                Map<String, String> keys = new HashMap<>();
+                keys.put("type", "multipolygon");
+                keys.put("area", "yes");
+                rel.setKeys(keys);
 
-				Map<String, String> keys = new HashMap<>();
-				keys.put("type", "multipolygon");
-				keys.put("area", "yes");
-				rel.setKeys(keys);
+                for (PdfPath path: mpath.paths) {
+                    Way w = path2Way.get(path);
+                    rel.addMember(new RelationMember("", w));
+                }
 
-				for (PdfPath path: mpath.paths){
-					Way w = path2Way.get(path);
-					rel.addMember(new RelationMember("", w));
-				}
+                target.addPrimitive(rel);
+            }
+        }
+    }
 
-				target.addPrimitive(rel);
-			}
-		}
-	}
+    private Way insertWay(PdfPath path, Map<Point2D, Node> point2Node, int multipathId, boolean multipolygon) {
 
-	private Way insertWay(PdfPath path, Map<Point2D, Node> point2Node, int multipathId, boolean multipolygon) {
+        if (this.monitorPos % 100 == 0) {
+            monitor.setExtraText(tr(" "+this.monitorPos+"/"+this.monitorTotal));
+            monitor.setTicks(this.monitorPos);
+        }
+        this.monitorPos++;
 
-		if (this.monitorPos % 100 == 0) {
-			monitor.setExtraText(tr(" "+this.monitorPos+"/"+this.monitorTotal));
-			monitor.setTicks(this.monitorPos);
-		}
-		this.monitorPos ++;
+        List<Node> nodes = new ArrayList<>(path.points.size());
 
-		List<Node> nodes = new ArrayList<>(path.points.size());
+        for (Point2D point: path.points) {
+            Node node = point2Node.get(point);
+            if (node == null) {
+                throw new RuntimeException();
+            }
 
-		for (Point2D point: path.points) {
-			Node node = point2Node.get(point);
-			if (node == null) {
-				throw new RuntimeException();
-			}
+            nodes.add(node);
+        }
 
-			nodes.add(node);
-		}
+        Map<String, String> keys = new HashMap<>();
 
-		Map<String, String> keys = new HashMap<>();
+        if (this.mode != Mode.Draft) {
+            keys.put("PDF_nr", "" + path.nr);
+            keys.put("PDF_layer", this.layerName);
 
-		if (this.mode != Mode.Draft) {
-			keys.put("PDF_nr", "" + path.nr);
-			keys.put("PDF_layer", this.layerName);
+            if (path.isClosed()) {
+                keys.put("PDF_closed", "yes");
+            }
 
-			if (path.isClosed()){
-				keys.put("PDF_closed", "yes");
-			}
+            if (this.fillName != null) {
+                keys.put("PDF_fillColor", this.fillName);
+            }
 
-			if (this.fillName != null){
-				keys.put("PDF_fillColor", this.fillName);
-			}
+            if (this.lineName != null) {
+                keys.put("PDF_lineColor", this.lineName);
+            }
 
-			if (this.lineName != null) {
-				keys.put("PDF_lineColor", this.lineName);
-			}
+            if (multipathId != -1) {
+                keys.put("PDF_multipath", ""+ multipathId);
+            } else if (path.layer.info.fill != null && !multipolygon) {
+                keys.put("area", "yes");
+            }
+        }
 
-			if (multipathId != -1){
-				keys.put("PDF_multipath", ""+ multipathId);
-			}
-			else if (path.layer.info.fill != null && !multipolygon) {
-				keys.put("area", "yes");
-			}
-		}
+        if (this.mode == Mode.Debug) {
 
-		if (this.mode == Mode.Debug) {
+            keys.put("PDF_pathLayer", ""+path.layer.info.nr);
+            keys.put("PDF_lineWidth", ""+path.layer.info.width);
+            keys.put("PDF_lineDash", ""+path.layer.info.dash);
+            keys.put("PDF_layerHash", ""+path.layer.info.hashCode());
+            keys.put("PDF_layerDivider", ""+path.layer.info.divider);
+        }
 
-			keys.put("PDF_pathLayer", ""+path.layer.info.nr);
-			keys.put("PDF_lineWidth", ""+path.layer.info.width);
-			keys.put("PDF_lineDash", ""+path.layer.info.dash);
-			keys.put("PDF_layerHash", ""+path.layer.info.hashCode());
-			keys.put("PDF_layerDivider", ""+path.layer.info.divider);
-		}
+        Way newWay = new Way();
+        newWay.setNodes(nodes);
+        newWay.setKeys(keys);
+        return newWay;
+    }
 
-		Way newWay = new Way();
-		newWay.setNodes(nodes);
-		newWay.setKeys(keys);
-		return newWay;
-	}
+    private String printColor(Color col) {
+        if (col == null) {
+            return null;
+        }
 
+        String s = Integer.toHexString(col.getRGB() & 0xffffff);
+        while (s.length() < 6) {
+            s = "0" + s;
+        }
 
-
-	private String printColor(Color col){
-		if (col == null){
-			return null;
-		}
-
-		String s = Integer.toHexString(col.getRGB() & 0xffffff);
-		while (s.length() < 6) {
-			s = "0" + s;
-		}
-
-		return "#" + s;
-	}
+        return "#" + s;
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/ParallelSegmentsFinder.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/ParallelSegmentsFinder.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/ParallelSegmentsFinder.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -13,164 +14,156 @@
 
 public class ParallelSegmentsFinder {
-	public double angle;
-	public double angleSum;
-	public int refCount;
-	public List<PdfPath> paths = new ArrayList<>();
+    public double angle;
+    public double angleSum;
+    public int refCount;
+    public List<PdfPath> paths = new ArrayList<>();
 
-	public void addPath(PdfPath path, double angle2) {
-		angleSum += angle2;
-		paths.add(path);
-		angle = angleSum /paths.size();
-	}
+    public void addPath(PdfPath path, double angle2) {
+        angleSum += angle2;
+        paths.add(path);
+        angle = angleSum /paths.size();
+    }
 
-	public List<ParallelSegmentsFinder> splitByDistance(double maxDistance)
-	{
-		//sort perpendicular to angle
-		AffineTransform tr = new AffineTransform();
-		tr.rotate(-angle);
+    public List<ParallelSegmentsFinder> splitByDistance(double maxDistance) {
+        //sort perpendicular to angle
+        AffineTransform tr = new AffineTransform();
+        tr.rotate(-angle);
 
-		final Map<PdfPath, Point2D> positions = new HashMap<>();
-		Point2D src = new Point2D.Double();
+        final Map<PdfPath, Point2D> positions = new HashMap<>();
+        Point2D src = new Point2D.Double();
 
-		for(PdfPath path: paths)
-		{
-			src.setLocation((path.firstPoint().getX() + path.lastPoint().getX()) / 2, (path.firstPoint().getY() + path.lastPoint().getY()) / 2);
-			Point2D dest = new Point2D.Double();
-			Point2D destA = new Point2D.Double();
-			Point2D destB = new Point2D.Double();
-			tr.transform(src, dest);
-			tr.transform(path.firstPoint(), destA);
-			tr.transform(path.lastPoint(), destB);
-			positions.put(path, dest);
-		}
-		//point.y = Perpendicular lines, point.x = parallel lines
+        for (PdfPath path: paths) {
+            src.setLocation((path.firstPoint().getX() + path.lastPoint().getX()) / 2, (path.firstPoint().getY() + path.lastPoint().getY()) / 2);
+            Point2D dest = new Point2D.Double();
+            Point2D destA = new Point2D.Double();
+            Point2D destB = new Point2D.Double();
+            tr.transform(src, dest);
+            tr.transform(path.firstPoint(), destA);
+            tr.transform(path.lastPoint(), destB);
+            positions.put(path, dest);
+        }
+        //point.y = Perpendicular lines, point.x = parallel lines
 
-		Collections.sort(paths, new Comparator<PdfPath>() {
-			public int compare(PdfPath o1, PdfPath o2) {
-				double y1 = positions.get(o1).getY();
-				double y2 = positions.get(o2).getY();
-				return y1 > y2 ? 1: y1 < y2 ? -1 : 0;
-			}
-		});
+        Collections.sort(paths, new Comparator<PdfPath>() {
+            @Override
+            public int compare(PdfPath o1, PdfPath o2) {
+                double y1 = positions.get(o1).getY();
+                double y2 = positions.get(o2).getY();
+                return y1 > y2 ? 1 : y1 < y2 ? -1 : 0;
+            }
+        });
 
-		//process sweep
-		List<ParallelSegmentsFinder> result = new ArrayList<>();
+        //process sweep
+        List<ParallelSegmentsFinder> result = new ArrayList<>();
 
-		Map<PdfPath, ParallelSegmentsFinder> sweepLine = new HashMap<>();
+        Map<PdfPath, ParallelSegmentsFinder> sweepLine = new HashMap<>();
 
-		Set<ParallelSegmentsFinder> adjacentClustersSet = new HashSet<>();
-		List<ParallelSegmentsFinder> adjacentClusters = new ArrayList<>();
-		List<PdfPath> pathsToRemove = new ArrayList<>();
+        Set<ParallelSegmentsFinder> adjacentClustersSet = new HashSet<>();
+        List<ParallelSegmentsFinder> adjacentClusters = new ArrayList<>();
+        List<PdfPath> pathsToRemove = new ArrayList<>();
 
-		for (PdfPath path: paths){
-			adjacentClusters.clear();
-			adjacentClustersSet.clear();
-			pathsToRemove.clear();
+        for (PdfPath path: paths) {
+            adjacentClusters.clear();
+            adjacentClustersSet.clear();
+            pathsToRemove.clear();
 
-			for (PdfPath p: sweepLine.keySet()) {
-				Point2D pathPos = positions.get(path);
-				Point2D pPos = positions.get(p);
+            for (PdfPath p: sweepLine.keySet()) {
+                Point2D pathPos = positions.get(path);
+                Point2D pPos = positions.get(p);
 
-				if (pathPos.getY() - pPos.getY() > maxDistance) {
-					//path too far from sweep line
-					pathsToRemove.add(p);
-				} else {
+                if (pathPos.getY() - pPos.getY() > maxDistance) {
+                    //path too far from sweep line
+                    pathsToRemove.add(p);
+                } else {
 
-					double distance = distanceLineLine(path, p);
+                    double distance = distanceLineLine(path, p);
 
-					if (distance <= maxDistance) {
-						if (adjacentClustersSet.add(sweepLine.get(p)))
-						{
-							adjacentClusters.add(sweepLine.get(p));
-						}
-					}
-				}
-			}
+                    if (distance <= maxDistance) {
+                        if (adjacentClustersSet.add(sweepLine.get(p))) {
+                            adjacentClusters.add(sweepLine.get(p));
+                        }
+                    }
+                }
+            }
 
-			//remove segments too far apart
-			for(PdfPath p: pathsToRemove){
-				ParallelSegmentsFinder finder = sweepLine.remove(p);
-				finder.refCount --;
-				if (finder.refCount == 0){
-					result.add(finder);
-				}
-			}
+            //remove segments too far apart
+            for (PdfPath p: pathsToRemove) {
+                ParallelSegmentsFinder finder = sweepLine.remove(p);
+                finder.refCount--;
+                if (finder.refCount == 0) {
+                    result.add(finder);
+                }
+            }
 
-			//join together joinable parts
-			if (adjacentClusters.size() > 0){
-				ParallelSegmentsFinder finder = adjacentClusters.remove(0);
-				finder.paths.add(path);
-				sweepLine.put(path, finder);
-				finder.refCount ++;
+            //join together joinable parts
+            if (adjacentClusters.size() > 0) {
+                ParallelSegmentsFinder finder = adjacentClusters.remove(0);
+                finder.paths.add(path);
+                sweepLine.put(path, finder);
+                finder.refCount++;
 
-				for(ParallelSegmentsFinder finder1: adjacentClusters){
-					for(PdfPath path1: finder1.paths){
-						finder.paths.add(path1);
-						sweepLine.put(path1, finder);
-						finder.refCount ++;
-					}
-				}
-			}
-			else
-			{
-				ParallelSegmentsFinder finder = new ParallelSegmentsFinder();
-				finder.addPath(path, angle);
-				sweepLine.put(path, finder);
-				finder.refCount = 1;
-			}
-		}
+                for (ParallelSegmentsFinder finder1: adjacentClusters) {
+                    for (PdfPath path1: finder1.paths) {
+                        finder.paths.add(path1);
+                        sweepLine.put(path1, finder);
+                        finder.refCount++;
+                    }
+                }
+            } else {
+                ParallelSegmentsFinder finder = new ParallelSegmentsFinder();
+                finder.addPath(path, angle);
+                sweepLine.put(path, finder);
+                finder.refCount = 1;
+            }
+        }
 
-		//process remaining paths in sweep line
-		for (PdfPath p: sweepLine.keySet()) {
-			ParallelSegmentsFinder finder = sweepLine.get(p);
-			finder.refCount --;
-			if (finder.refCount == 0){
-				result.add(finder);
-			}
-		}
+        //process remaining paths in sweep line
+        for (PdfPath p: sweepLine.keySet()) {
+            ParallelSegmentsFinder finder = sweepLine.get(p);
+            finder.refCount--;
+            if (finder.refCount == 0) {
+                result.add(finder);
+            }
+        }
 
-		return result;
-	}
+        return result;
+    }
 
-	private double distanceLineLine(PdfPath p1, PdfPath p2) {
-		return distanceLineLine(p1.firstPoint(), p1.lastPoint(), p2.firstPoint(), p2.lastPoint());
-	}
+    private double distanceLineLine(PdfPath p1, PdfPath p2) {
+        return distanceLineLine(p1.firstPoint(), p1.lastPoint(), p2.firstPoint(), p2.lastPoint());
+    }
 
-	private double distanceLineLine(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
-		double dist1 = closestPointToSegment(p1, p2, p3).distance(p3);
-		double dist2 = closestPointToSegment(p1, p2, p4).distance(p4);
-		double dist3 = closestPointToSegment(p3, p4, p1).distance(p1);
-		double dist4 = closestPointToSegment(p3, p4, p2).distance(p2);
-		return Math.min(Math.min(dist1, dist2),Math.min(dist3, dist4));
-	}
+    private double distanceLineLine(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
+        double dist1 = closestPointToSegment(p1, p2, p3).distance(p3);
+        double dist2 = closestPointToSegment(p1, p2, p4).distance(p4);
+        double dist3 = closestPointToSegment(p3, p4, p1).distance(p1);
+        double dist4 = closestPointToSegment(p3, p4, p2).distance(p2);
+        return Math.min(Math.min(dist1, dist2), Math.min(dist3, dist4));
+    }
 
-	/**
-	 * Calculates closest point to a line segment.
-	 * @param segmentP1
-	 * @param segmentP2
-	 * @param point
-	 * @return segmentP1 if it is the closest point, segmentP2 if it is the closest point,
-	 * a new point if closest point is between segmentP1 and segmentP2.
-	 */
-	public static Point2D closestPointToSegment(Point2D segmentP1, Point2D segmentP2, Point2D point) {
+    /**
+     * Calculates closest point to a line segment.
+     * @return segmentP1 if it is the closest point, segmentP2 if it is the closest point,
+     * a new point if closest point is between segmentP1 and segmentP2.
+     */
+    public static Point2D closestPointToSegment(Point2D segmentP1, Point2D segmentP2, Point2D point) {
 
-		double ldx = segmentP2.getX() - segmentP1.getX();
-		double ldy = segmentP2.getY() - segmentP1.getY();
+        double ldx = segmentP2.getX() - segmentP1.getX();
+        double ldy = segmentP2.getY() - segmentP1.getY();
 
-		if (ldx == 0 && ldy == 0) //segment zero length
-			return segmentP1;
+        if (ldx == 0 && ldy == 0) //segment zero length
+            return segmentP1;
 
-		double pdx = point.getX() - segmentP1.getX();
-		double pdy = point.getY() - segmentP1.getY();
+        double pdx = point.getX() - segmentP1.getX();
+        double pdy = point.getY() - segmentP1.getY();
 
-		double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy);
+        double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy);
 
-		if (offset <= 0)
-			return segmentP1;
-		else if (offset >= 1)
-			return segmentP2;
-		else
-			return new Point2D.Double(segmentP1.getX() + ldx * offset, segmentP1.getY() + ldy * offset);
-
-	}
+        if (offset <= 0)
+            return segmentP1;
+        else if (offset >= 1)
+            return segmentP2;
+        else
+            return new Point2D.Double(segmentP1.getX() + ldx * offset, segmentP1.getY() + ldy * offset);
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/PathOptimizer.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/PathOptimizer.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/PathOptimizer.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -14,793 +15,744 @@
 public class PathOptimizer {
 
-	public List<Point2D> uniquePoints;
-	public Map<Point2D, Point2D> uniquePointMap;
-	private final Map<LayerInfo, LayerContents> layerMap;
-	private List<LayerContents> layers;
-	public Rectangle2D bounds;
-	private final double pointsTolerance;
-	private final Color color;
-	boolean splitOnColorChange;
-
-	LayerContents prevLayer = null;
-
-	public PathOptimizer(double _pointsTolerance, Color _color, boolean _splitOnColorChange)
-	{
-		uniquePointMap = new HashMap<>();
-		uniquePoints = new ArrayList<>();
-		layerMap = new HashMap<>();
-		layers = new ArrayList<>();
-		pointsTolerance = _pointsTolerance;
-		color = _color;
-		splitOnColorChange = _splitOnColorChange;
-	}
-
-	public Point2D getUniquePoint(Point2D point) {
-
-		if (uniquePointMap.containsKey(point)){
-			return uniquePointMap.get(point);
-		}
-		else {
-			uniquePointMap.put(point, point);
-			uniquePoints.add(point);
-			return point;
-		}
-	}
-
-	public void addPath(LayerInfo info, PdfPath path)
-	{
-		if (!isColorOK(info)){
-			return;
-		}
-
-		LayerContents layer = this.getLayer(info);
-		layer.paths.add(path);
-	}
-
-	public void addMultiPath(LayerInfo info, List<PdfPath> paths) {
-
-		if (!isColorOK(info)){
-			return;
-		}
-
-		LayerContents layer = this.getLayer(info);
-
-		//optimize the paths
-		Set<Point2D> points = new HashSet<>();
-		for(PdfPath path: paths) {
-			points.addAll(path.points);
-		}
-		LayerContents multipathLayer = new LayerContents();
-		multipathLayer.paths = paths;
-		Map<Point2D, Point2D> pointMap = DuplicateNodesFinder.findDuplicateNodes(points, pointsTolerance);
-		this.fixPoints(multipathLayer,pointMap);
-		this.concatenatePaths(multipathLayer);
-
-		paths = multipathLayer.paths;
-
-		boolean goodMultiPath = true;
-		for(PdfPath path: paths) {
-			goodMultiPath &= path.isClosed();
-		}
-
-		if (goodMultiPath) {
-			PdfMultiPath p = new PdfMultiPath(paths);
-			layer.multiPaths.add(p);
-		} else {
-			layer.paths.addAll(paths);
-		}
-	}
-
-	private boolean isColorOK(LayerInfo info) {
-
-		if (color == null) {
-			return true;
-		}
-
-		int rgb = color.getRGB() & 0xffffff;
-		boolean good = false;
-
-		if (info.fill != null && (info.fill.getRGB() & 0xffffff) == rgb) {
-			good = true;
-		}
-
-		if (info.stroke != null && (info.stroke.getRGB() & 0xffffff) == rgb) {
-			good = true;
-		}
-
-		return good;
-	}
-
-
-	public void removeParallelLines(double maxDistance){
-		for(LayerContents layer: this.layers) {
-			this.removeParallelLines(layer, maxDistance);
-		}
-	}
-
-	public void mergeNodes() {
-		Map<Point2D, Point2D> pointMap = DuplicateNodesFinder.findDuplicateNodes(uniquePoints, pointsTolerance);
-
-		for(LayerContents layer: this.layers) {
-			this.fixPoints(layer, pointMap);
-		}
-	}
-
-	public void mergeSegments() {
-		for(LayerContents layer: this.layers) {
-			this.concatenatePaths(layer);
-		}
-	}
-
-
-	public void removeSmallObjects(double tolerance) {
-		for(LayerContents layer: this.layers) {
-			this.removeSmallObjects(layer, tolerance, Double.POSITIVE_INFINITY);
-		}
-	}
-
-
-	public void removeLargeObjects(double tolerance) {
-		for(LayerContents layer: this.layers) {
-			this.removeSmallObjects(layer, 0.0, tolerance);
-		}
-	}
-
-	public void splitLayersBySimilarShapes(double tolerance) {
-		List<LayerContents> newLayers = new ArrayList<>();
-		for(LayerContents l: this.layers) {
-			List<LayerContents> splitResult = splitBySimilarGroups(l);
-
-			for(LayerContents ll: splitResult) {
-				newLayers.add(ll);
-			}
-		}
-		this.layers = newLayers;
-	}
-
-	public void splitLayersByPathKind(boolean closed, boolean single, boolean orthogonal) {
-		List<LayerContents> newLayers = new ArrayList<>();
-		for(LayerContents l: this.layers) {
-			List<LayerContents> splitResult = splitBySegmentKind(l, closed, single, orthogonal);
-
-			for(LayerContents ll: splitResult) {
-				newLayers.add(ll);
-			}
-		}
-
-		this.layers = newLayers;
-	}
-
-
-	public void finish() {
-		int nr = 0;
-		for(LayerContents layer: this.layers) {
-			layer.info.nr = nr;
-			nr++;
-			finalizeLayer(layer);
-		}
-	}
-
-
-	private LayerContents getLayer(LayerInfo info) {
-
-		LayerContents layer = null;
-
-		if (this.layerMap.containsKey(info))
-		{
-			layer = this.layerMap.get(info);
-
-			if (layer != this.prevLayer && this.splitOnColorChange) {
-				layer = null;
-			}
-		}
-
-		if (layer == null)
-		{
-			layer = new LayerContents();
-			layer.info = info.copy();
-			layer.info.nr = this.layers.size();
-			this.layerMap.put(layer.info, layer);
-			this.layers.add(layer);
-		}
-
-		this.prevLayer = layer;
-		return layer;
-	}
-
-
-	private void finalizeLayer(LayerContents layer){
-		Set<Point2D> points = new HashSet<>();
-		layer.points = new ArrayList<>();
-
-		for(PdfPath pp: layer.paths){
-			pp.layer = layer;
-
-			for(Point2D point: pp.points){
-				if (!points.contains(point)) {
-					layer.points.add(point);
-					points.add(point);
-				}
-			}
-		}
-
-		for (PdfMultiPath multipath: layer.multiPaths) {
-			multipath.layer = layer;
-			for(PdfPath pp: multipath.paths){
-				pp.layer = layer;
-
-				for(Point2D point: pp.points){
-					if (!points.contains(point)) {
-						layer.points.add(point);
-						points.add(point);
-					}
-				}
-			}
-		}
-	}
-
-	private void fixPoints(LayerContents layer, Map<Point2D, Point2D> pointMap) {
-
-		List<PdfPath> newPaths = new ArrayList<>(layer.paths.size());
-
-		for(PdfPath path: layer.paths) {
-			List<Point2D> points = fixPoints(path.points, pointMap);
-			path.points = points;
-			if (points.size() > 2 || (!path.isClosed() && points.size() > 1)){
-
-				newPaths.add(path);
-			}
-		}
-
-		layer.paths = newPaths;
-
-		for (PdfMultiPath mp: layer.multiPaths){
-			for(PdfPath path: mp.paths) {
-				path.points = fixPoints(path.points, pointMap);
-			}
-		}
-	}
-
-
-	private List<Point2D> fixPoints(List<Point2D> points, Map<Point2D, Point2D> pointMap) {
-
-		List<Point2D> newPoints = new ArrayList<>(points.size());
-		Point2D prevPoint = null;
-
-		for(Point2D p: points){
-			Point2D pp = p;
-
-			if (pointMap.containsKey(p)){
-				pp = pointMap.get(p);
-			}
-
-			if (prevPoint != pp){
-				newPoints.add(pp);
-			}
-
-			prevPoint = pp;
-		}
-
-		return newPoints;
-	}
-
-
-	private void removeSmallObjects(LayerContents layer, double min, double max) {
-		List<PdfPath> newPaths = new ArrayList<>(layer.paths.size());
-
-		for(PdfPath path: layer.paths) {
-			double size = getShapeSize(path);
-			boolean good = size >= min && size <= max;
-
-			if (good) {
-				newPaths.add(path);
-			}
-		}
-
-		layer.paths = newPaths;
-
-		List<PdfMultiPath> newMPaths = new ArrayList<>(layer.multiPaths.size());
-
-		for (PdfMultiPath mp: layer.multiPaths){
-			boolean good = true;
-			for(PdfPath path: mp.paths) {
-				double size = getShapeSize(path);
-				good &= size >= min && size <= max;
-			}
-
-			if (good) {
-				newMPaths.add(mp);
-			}
-		}
-
-		layer.multiPaths = newMPaths;
-	}
-
-
-	private double getShapeSize(PdfPath path) {
-
-		Rectangle2D bounds = new Rectangle2D.Double();
-		bounds.setRect(path.points.get(0).getX(), path.points.get(0).getY(), 0,0);
-
-		for(Point2D n: path.points) {
-			bounds.add(n);
-		}
-
-		return Math.max(bounds.getWidth(), bounds.getHeight());
-	}
-
-
-
-	/***
-	 * This method finds parralel lines with similar distance and removes them.
-	 * @param layer
-	 */
-	private void removeParallelLines(LayerContents layer, double maxDistance) {
-		double angleTolerance = 1.0 / 180.0 * Math.PI; // 1 degree
-		int minSegments = 10;
-
-		//filter paths by direction
-		List<ParallelSegmentsFinder> angles = new ArrayList<>();
-
-		for(PdfPath path: layer.paths) {
-			if (path.points.size() != 2){
-				continue;
-			}
-
-			Point2D p1 = path.firstPoint();
-			Point2D p2 = path.lastPoint();
-			double angle = Math.atan2(p2.getX() - p1.getX(), p2.getY() - p1.getY());
-			//normalize between 0 and 180 degrees
-			while (angle < 0) angle += Math.PI;
-			while (angle > Math.PI) angle -= Math.PI;
-			boolean added = false;
-
-			for(ParallelSegmentsFinder pa: angles) {
-				if (Math.abs(pa.angle - angle) < angleTolerance){
-					pa.addPath(path, angle);
-					added = true;
-					break;
-				}
-			}
-
-			if (!added) {
-				ParallelSegmentsFinder pa = new ParallelSegmentsFinder();
-				pa.addPath(path, angle);
-				angles.add(pa);
-			}
-		}
-
-		Set<PdfPath> pathsToRemove = new HashSet<>();
-
-		//process each direction
-		for (ParallelSegmentsFinder pa: angles){
-			if (pa.paths.size() < minSegments){
-				continue;
-			}
-
-			List<ParallelSegmentsFinder> parts = pa.splitByDistance(maxDistance);
-
-			for(ParallelSegmentsFinder part: parts) {
-				if (part.paths.size() >= minSegments){
-					pathsToRemove.addAll(part.paths);
-				}
-			}
-		}
-
-		//generate new path list
-		List<PdfPath> result = new ArrayList<>(layer.paths.size() - pathsToRemove.size());
-
-		for(PdfPath path: layer.paths) {
-			if (!pathsToRemove.contains(path)) {
-				result.add(path);
-			}
-		}
-
-		layer.paths = result;
-	}
-
-
-	/**
-	 * This method merges together paths with common end nodes.
-	 * @param layer the layer to process.
-	 */
-	private void concatenatePaths(LayerContents layer) {
-		Map<Point2D, List<PdfPath>> pathEndpoints = new HashMap<>();
-		Set<PdfPath> mergedPaths = new HashSet<>();
-		List<PdfPath> newPaths = new ArrayList<>();
-
-		//fill pathEndpoints map
-		for(PdfPath pp: layer.paths){
-			if (pp.isClosed()) {
-				newPaths.add(pp);
-				continue;
-			}
-
-			List<PdfPath> paths = pathEndpoints.get(pp.firstPoint());
-			if (paths == null){
-				paths = new ArrayList<>(2);
-				pathEndpoints.put(pp.firstPoint(), paths);
-			}
-			paths.add(pp);
-
-			paths = pathEndpoints.get(pp.lastPoint());
-			if (paths == null){
-				paths = new ArrayList<>(2);
-				pathEndpoints.put(pp.lastPoint(), paths);
-			}
-			paths.add(pp);
-		}
-
-		List<PdfPath> pathChain = new ArrayList<>(2);
-		Set<Point2D> pointsInPath = new HashSet<>();
-
-		//join the paths
-		for(PdfPath pp: layer.paths) {
-
-			if (pp.isClosed() || mergedPaths.contains(pp)) {
-				continue;
-			}
-
-			boolean changed = true;
-
-			PdfPath firstPath = pp;
-			PdfPath lastPath = pp;
-			Point2D firstPoint = pp.firstPoint();
-			Point2D lastPoint = pp.lastPoint();
-
-
-			pathChain.clear();
-			pathChain.add(pp);
-			pointsInPath.clear();
-			pointsInPath.add(firstPoint);
-			pointsInPath.add(lastPoint);
-
-			//process last point
-			while (changed && firstPoint != lastPoint) {
-				changed = false;
-
-				List<PdfPath> adjacentPaths = pathEndpoints.get(lastPoint);
-				PdfPath nextPath = findNextPath(adjacentPaths, lastPath);
-
-				if (nextPath != null) {
-					Point2D nextPoint = nextPath.getOtherEnd(lastPoint);
-
-					lastPoint = nextPoint;
-					lastPath = nextPath;
-					pathChain.add(lastPath);
-
-					if (!pointsInPath.contains(lastPoint)) {
-						pointsInPath.add(lastPoint);
-						changed = true;
-					}
-					else
-					{
-						//closed path found
-						//remove excess segments from start of chain
-						while (lastPoint != firstPoint) {
-							PdfPath pathToRemove = pathChain.remove(0);
-							firstPoint = pathToRemove.getOtherEnd(firstPoint);
-						}
-
-						changed = false;
-					}
-				}
-			}
-
-
-			//process first point
-			changed = true;
-			while (changed && firstPoint != lastPoint) {
-				changed = false;
-
-				List<PdfPath> adjacentPaths = pathEndpoints.get(firstPoint);
-				PdfPath nextPath = findNextPath(adjacentPaths, firstPath);
-
-				if (nextPath != null) {
-					Point2D nextPoint = nextPath.getOtherEnd(firstPoint);
-
-					firstPoint = nextPoint;
-					firstPath = nextPath;
-					pathChain.add(0, firstPath);
-
-					if (!pointsInPath.contains(firstPoint)) {
-						pointsInPath.add(firstPoint);
-						changed = true;
-					}
-					else
-					{
-						//closed path found
-						//remove excess segments from end of chain
-						while (lastPoint != firstPoint) {
-							PdfPath pathToRemove = pathChain.remove(pathChain.size() - 1);
-							lastPoint = pathToRemove.getOtherEnd(lastPoint);
-						}
-
-						changed = false;
-					}
-				}
-			}
-
-			//remove from map
-			for (PdfPath path: pathChain) {
-				pathEndpoints.get(path.firstPoint()).remove(path);
-				pathEndpoints.get(path.lastPoint()).remove(path);
-				mergedPaths.add(path);
-			}
-
-
-			//construct path
-			PdfPath path = pathChain.get(0);
-
-			for (int pos = 1; pos < pathChain.size(); pos ++) {
-				path.points = tryMergeNodeLists(path.points, pathChain.get(pos).points);
-
-				if (path.points == null) {
-					throw new RuntimeException();
-				}
-			}
-
-			newPaths.add(path);
-		}
-
-		layer.paths = newPaths;
-	}
-
-	private PdfPath findNextPath(List<PdfPath> adjacentPaths, PdfPath firstPath) {
-		for (int pos = 0; pos < adjacentPaths.size(); pos ++) {
-			PdfPath p = adjacentPaths.get(pos);
-			if (p != firstPath && !isSubpathOf(firstPath, p)){
-				return p;
-			}
-		}
-
-		return null;
-	}
-
-
-	/**
-	 * Tests if sub is subpath of main.
-	 * @param main
-	 * @param sub
-	 * @return
-	 */
-	private boolean isSubpathOf(PdfPath main, PdfPath sub) {
-
-		Set<Point2D> points = new HashSet<>(main.points);
-
-		for(Point2D point: sub.points) {
-			if (!points.contains(point)){
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	private List<LayerContents> splitBySegmentKind(LayerContents layer, boolean closed, boolean single, boolean orthogonal)
-	{
-		if (!closed && !single) {
-			return Collections.singletonList(layer);
-		}
-
-		OrthogonalShapesFilter of = new OrthogonalShapesFilter(10);
-
-		List<PdfPath> singleSegmentPaths = new ArrayList<>();
-		List<PdfPath> multiSegmentPaths = new ArrayList<>();
-		List<PdfPath> closedPaths = new ArrayList<>();
-		List<PdfPath> orthogonalPaths = new ArrayList<>();
-		List<PdfPath> orthogonalClosedPaths = new ArrayList<>();
-
-		for(PdfPath path: layer.paths) {
-			boolean pathOrthgonal = orthogonal && of.isOrthogonal(path);
-			boolean pathUnclosed = !path.isClosed() && closed;
-			boolean pathSingleSegment = path.points.size() <= 3 && single;
-
-			if (pathSingleSegment) {
-				singleSegmentPaths.add(path);
-			}
-			else if (pathUnclosed) {
-
-				if (pathOrthgonal) {
-					orthogonalPaths.add(path);
-				}
-				else {
-					multiSegmentPaths.add(path);
-				}
-			}
-			else {
-				if (pathOrthgonal) {
-					orthogonalClosedPaths.add(path);
-				}
-				else
-				{
-					closedPaths.add(path);
-				}
-
-			}
-		}
-
-		List<LayerContents> layers = new ArrayList<>();
-
-		if (multiSegmentPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = multiSegmentPaths;
-			l.info = layer.info.copy();
-
-			layers.add(l);
-		}
-
-		if (singleSegmentPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = singleSegmentPaths;
-			l.info = layer.info.copy();
-			layers.add(l);
-		}
-
-
-		if (orthogonalPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = orthogonalPaths;
-			l.info = layer.info.copy();
-			layers.add(l);
-		}
-
-		if (orthogonalClosedPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = orthogonalClosedPaths;
-			l.info = layer.info.copy();
-			layers.add(l);
-		}
-
-		if (closedPaths.size() > 0 || layer.multiPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = closedPaths;
-			l.info = layer.info.copy();
-			l.multiPaths = layer.multiPaths;
-			layers.add(l);
-		}
-
-		return layers;
-	}
-
-	private List<LayerContents> splitBySimilarGroups(LayerContents layer) {
-		List<List<PdfPath>> subparts = new ArrayList<>();
-
-		//split into similar parts
-		for (PdfPath path: layer.paths) {
-			List<PdfPath> sublayer = null;
-
-			for(List<PdfPath> ll: subparts){
-				if (this.pathsSimilar(ll.get(0).points, path.points))
-				{
-					sublayer = ll;
-					break;
-				}
-			}
-
-			if (sublayer == null) {
-				sublayer = new ArrayList<>();
-				subparts.add(sublayer);
-			}
-
-			sublayer.add(path);
-		}
-
-		//get groups
-		int minGroupTreshold = 10;
-
-		List<PdfPath> independantPaths = new ArrayList<>();
-		List<LayerContents> result = new ArrayList<>();
-
-		for(List<PdfPath> list: subparts){
-			if (list.size() >= minGroupTreshold) {
-				LayerContents l = new LayerContents();
-				l.paths = list;
-				l.info = layer.info.copy();
-				l.info.isGroup = true;
-				l.multiPaths = Collections.emptyList();
-				result.add(l);
-			}
-			else
-			{
-				independantPaths.addAll(list);
-			}
-		}
-
-		if (independantPaths.size() > 0 || layer.multiPaths.size() > 0) {
-			LayerContents l = new LayerContents();
-			l.paths = independantPaths;
-			l.info = layer.info.copy();
-			l.info.isGroup = false;
-			l.multiPaths = layer.multiPaths;
-			result.add(l);
-		}
-
-
-		return result;
-	}
-
-
-
-	private List<Point2D> tryMergeNodeLists(List<Point2D> nodes1, List<Point2D> nodes2) {
-
-		boolean nodes1Closed = (nodes1.get(0) == nodes1.get(nodes1.size() - 1));
-		boolean nodes2Closed = (nodes2.get(0) == nodes2.get(nodes2.size() - 1));
-
-		if (nodes1Closed || nodes2Closed) {
-			return null;
-		}
-
-		if (nodes1.get(nodes1.size() - 1) == nodes2.get(0)) {
-			nodes1.remove(nodes1.size() -1);
-			nodes1.addAll(nodes2);
-			return nodes1;
-		}
-		else if (nodes1.get(nodes1.size() - 1) == nodes2.get(nodes2.size() -1)) {
-			nodes1.remove(nodes1.size() -1);
-			for (int pos = nodes2.size() - 1; pos >= 0; pos --) {
-				nodes1.add(nodes2.get(pos));
-			}
-
-			return nodes1;
-		}
-		else if (nodes1.get(0) == nodes2.get(nodes2.size() - 1)) {
-			nodes1.remove(0);
-			nodes1.addAll(0, nodes2);
-			return nodes1;
-		}
-		else if (nodes1.get(0) == nodes2.get(0)) {
-			nodes1.remove(0);
-			for (int pos = 0; pos < nodes2.size(); pos ++) {
-				nodes1.add(0, nodes2.get(pos));
-			}
-
-			return nodes1;
-		} else {
-			return null;
-		}
-	}
-
-	public List<LayerContents> getLayers() {
-		return this.layers;
-	}
-
-	/**
-	 * Test if paths are different only by offset.
-	 * @return
-	 */
-	private boolean pathsSimilar(List<Point2D> path1, List<Point2D> path2) {
-		if (path1.size() != path2.size()) {
-			return false;
-		}
-
-		if (path1.size() < 3) {
-			return false;
-			//cannot judge so small paths
-		}
-
-		Point2D p1 = path1.get(0);
-		Point2D p2 = path2.get(0);
-
-		double offsetX = p1.getX() - p2.getX();
-		double offsetY = p1.getY() - p2.getY();
-		double tolerance = 1e-4;
-
-		for(int pos = 0; pos < path1.size(); pos ++) {
-			p1 = path1.get(pos);
-			p2 = path2.get(pos);
-
-			double errorX = p1.getX() - p2.getX() - offsetX;
-			double errorY = p1.getY() - p2.getY() - offsetY;
-
-			if (Math.abs(errorX) + Math.abs(errorY) > tolerance){
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-
-
+    public List<Point2D> uniquePoints;
+    public Map<Point2D, Point2D> uniquePointMap;
+    private final Map<LayerInfo, LayerContents> layerMap;
+    private List<LayerContents> layers;
+    public Rectangle2D bounds;
+    private final double pointsTolerance;
+    private final Color color;
+    boolean splitOnColorChange;
+
+    LayerContents prevLayer = null;
+
+    public PathOptimizer(double _pointsTolerance, Color _color, boolean _splitOnColorChange) {
+        uniquePointMap = new HashMap<>();
+        uniquePoints = new ArrayList<>();
+        layerMap = new HashMap<>();
+        layers = new ArrayList<>();
+        pointsTolerance = _pointsTolerance;
+        color = _color;
+        splitOnColorChange = _splitOnColorChange;
+    }
+
+    public Point2D getUniquePoint(Point2D point) {
+
+        if (uniquePointMap.containsKey(point)) {
+            return uniquePointMap.get(point);
+        } else {
+            uniquePointMap.put(point, point);
+            uniquePoints.add(point);
+            return point;
+        }
+    }
+
+    public void addPath(LayerInfo info, PdfPath path) {
+        if (!isColorOK(info)) {
+            return;
+        }
+
+        LayerContents layer = this.getLayer(info);
+        layer.paths.add(path);
+    }
+
+    public void addMultiPath(LayerInfo info, List<PdfPath> paths) {
+
+        if (!isColorOK(info)) {
+            return;
+        }
+
+        LayerContents layer = this.getLayer(info);
+
+        //optimize the paths
+        Set<Point2D> points = new HashSet<>();
+        for (PdfPath path: paths) {
+            points.addAll(path.points);
+        }
+        LayerContents multipathLayer = new LayerContents();
+        multipathLayer.paths = paths;
+        Map<Point2D, Point2D> pointMap = DuplicateNodesFinder.findDuplicateNodes(points, pointsTolerance);
+        this.fixPoints(multipathLayer, pointMap);
+        this.concatenatePaths(multipathLayer);
+
+        paths = multipathLayer.paths;
+
+        boolean goodMultiPath = true;
+        for (PdfPath path: paths) {
+            goodMultiPath &= path.isClosed();
+        }
+
+        if (goodMultiPath) {
+            PdfMultiPath p = new PdfMultiPath(paths);
+            layer.multiPaths.add(p);
+        } else {
+            layer.paths.addAll(paths);
+        }
+    }
+
+    private boolean isColorOK(LayerInfo info) {
+
+        if (color == null) {
+            return true;
+        }
+
+        int rgb = color.getRGB() & 0xffffff;
+        boolean good = false;
+
+        if (info.fill != null && (info.fill.getRGB() & 0xffffff) == rgb) {
+            good = true;
+        }
+
+        if (info.stroke != null && (info.stroke.getRGB() & 0xffffff) == rgb) {
+            good = true;
+        }
+
+        return good;
+    }
+
+    public void removeParallelLines(double maxDistance) {
+        for (LayerContents layer: this.layers) {
+            this.removeParallelLines(layer, maxDistance);
+        }
+    }
+
+    public void mergeNodes() {
+        Map<Point2D, Point2D> pointMap = DuplicateNodesFinder.findDuplicateNodes(uniquePoints, pointsTolerance);
+
+        for (LayerContents layer: this.layers) {
+            this.fixPoints(layer, pointMap);
+        }
+    }
+
+    public void mergeSegments() {
+        for (LayerContents layer: this.layers) {
+            this.concatenatePaths(layer);
+        }
+    }
+
+    public void removeSmallObjects(double tolerance) {
+        for (LayerContents layer: this.layers) {
+            this.removeSmallObjects(layer, tolerance, Double.POSITIVE_INFINITY);
+        }
+    }
+
+    public void removeLargeObjects(double tolerance) {
+        for (LayerContents layer: this.layers) {
+            this.removeSmallObjects(layer, 0.0, tolerance);
+        }
+    }
+
+    public void splitLayersBySimilarShapes(double tolerance) {
+        List<LayerContents> newLayers = new ArrayList<>();
+        for (LayerContents l: this.layers) {
+            List<LayerContents> splitResult = splitBySimilarGroups(l);
+
+            for (LayerContents ll: splitResult) {
+                newLayers.add(ll);
+            }
+        }
+        this.layers = newLayers;
+    }
+
+    public void splitLayersByPathKind(boolean closed, boolean single, boolean orthogonal) {
+        List<LayerContents> newLayers = new ArrayList<>();
+        for (LayerContents l: this.layers) {
+            List<LayerContents> splitResult = splitBySegmentKind(l, closed, single, orthogonal);
+
+            for (LayerContents ll: splitResult) {
+                newLayers.add(ll);
+            }
+        }
+
+        this.layers = newLayers;
+    }
+
+    public void finish() {
+        int nr = 0;
+        for (LayerContents layer: this.layers) {
+            layer.info.nr = nr;
+            nr++;
+            finalizeLayer(layer);
+        }
+    }
+
+    private LayerContents getLayer(LayerInfo info) {
+
+        LayerContents layer = null;
+
+        if (this.layerMap.containsKey(info)) {
+            layer = this.layerMap.get(info);
+
+            if (layer != this.prevLayer && this.splitOnColorChange) {
+                layer = null;
+            }
+        }
+
+        if (layer == null) {
+            layer = new LayerContents();
+            layer.info = info.copy();
+            layer.info.nr = this.layers.size();
+            this.layerMap.put(layer.info, layer);
+            this.layers.add(layer);
+        }
+
+        this.prevLayer = layer;
+        return layer;
+    }
+
+    private void finalizeLayer(LayerContents layer) {
+        Set<Point2D> points = new HashSet<>();
+        layer.points = new ArrayList<>();
+
+        for (PdfPath pp: layer.paths) {
+            pp.layer = layer;
+
+            for (Point2D point: pp.points) {
+                if (!points.contains(point)) {
+                    layer.points.add(point);
+                    points.add(point);
+                }
+            }
+        }
+
+        for (PdfMultiPath multipath: layer.multiPaths) {
+            multipath.layer = layer;
+            for (PdfPath pp: multipath.paths) {
+                pp.layer = layer;
+
+                for (Point2D point: pp.points) {
+                    if (!points.contains(point)) {
+                        layer.points.add(point);
+                        points.add(point);
+                    }
+                }
+            }
+        }
+    }
+
+    private void fixPoints(LayerContents layer, Map<Point2D, Point2D> pointMap) {
+
+        List<PdfPath> newPaths = new ArrayList<>(layer.paths.size());
+
+        for (PdfPath path: layer.paths) {
+            List<Point2D> points = fixPoints(path.points, pointMap);
+            path.points = points;
+            if (points.size() > 2 || (!path.isClosed() && points.size() > 1)) {
+
+                newPaths.add(path);
+            }
+        }
+
+        layer.paths = newPaths;
+
+        for (PdfMultiPath mp: layer.multiPaths) {
+            for (PdfPath path: mp.paths) {
+                path.points = fixPoints(path.points, pointMap);
+            }
+        }
+    }
+
+    private List<Point2D> fixPoints(List<Point2D> points, Map<Point2D, Point2D> pointMap) {
+
+        List<Point2D> newPoints = new ArrayList<>(points.size());
+        Point2D prevPoint = null;
+
+        for (Point2D p: points) {
+            Point2D pp = p;
+
+            if (pointMap.containsKey(p)) {
+                pp = pointMap.get(p);
+            }
+
+            if (prevPoint != pp) {
+                newPoints.add(pp);
+            }
+
+            prevPoint = pp;
+        }
+
+        return newPoints;
+    }
+
+    private void removeSmallObjects(LayerContents layer, double min, double max) {
+        List<PdfPath> newPaths = new ArrayList<>(layer.paths.size());
+
+        for (PdfPath path: layer.paths) {
+            double size = getShapeSize(path);
+            boolean good = size >= min && size <= max;
+
+            if (good) {
+                newPaths.add(path);
+            }
+        }
+
+        layer.paths = newPaths;
+
+        List<PdfMultiPath> newMPaths = new ArrayList<>(layer.multiPaths.size());
+
+        for (PdfMultiPath mp: layer.multiPaths) {
+            boolean good = true;
+            for (PdfPath path: mp.paths) {
+                double size = getShapeSize(path);
+                good &= size >= min && size <= max;
+            }
+
+            if (good) {
+                newMPaths.add(mp);
+            }
+        }
+
+        layer.multiPaths = newMPaths;
+    }
+
+    private double getShapeSize(PdfPath path) {
+
+        Rectangle2D bounds = new Rectangle2D.Double();
+        bounds.setRect(path.points.get(0).getX(), path.points.get(0).getY(), 0, 0);
+
+        for (Point2D n: path.points) {
+            bounds.add(n);
+        }
+
+        return Math.max(bounds.getWidth(), bounds.getHeight());
+    }
+
+    /***
+     * This method finds parralel lines with similar distance and removes them.
+     */
+    private void removeParallelLines(LayerContents layer, double maxDistance) {
+        double angleTolerance = 1.0 / 180.0 * Math.PI; // 1 degree
+        int minSegments = 10;
+
+        //filter paths by direction
+        List<ParallelSegmentsFinder> angles = new ArrayList<>();
+
+        for (PdfPath path: layer.paths) {
+            if (path.points.size() != 2) {
+                continue;
+            }
+
+            Point2D p1 = path.firstPoint();
+            Point2D p2 = path.lastPoint();
+            double angle = Math.atan2(p2.getX() - p1.getX(), p2.getY() - p1.getY());
+            //normalize between 0 and 180 degrees
+            while (angle < 0) angle += Math.PI;
+            while (angle > Math.PI) angle -= Math.PI;
+            boolean added = false;
+
+            for (ParallelSegmentsFinder pa: angles) {
+                if (Math.abs(pa.angle - angle) < angleTolerance) {
+                    pa.addPath(path, angle);
+                    added = true;
+                    break;
+                }
+            }
+
+            if (!added) {
+                ParallelSegmentsFinder pa = new ParallelSegmentsFinder();
+                pa.addPath(path, angle);
+                angles.add(pa);
+            }
+        }
+
+        Set<PdfPath> pathsToRemove = new HashSet<>();
+
+        //process each direction
+        for (ParallelSegmentsFinder pa: angles) {
+            if (pa.paths.size() < minSegments) {
+                continue;
+            }
+
+            List<ParallelSegmentsFinder> parts = pa.splitByDistance(maxDistance);
+
+            for (ParallelSegmentsFinder part: parts) {
+                if (part.paths.size() >= minSegments) {
+                    pathsToRemove.addAll(part.paths);
+                }
+            }
+        }
+
+        //generate new path list
+        List<PdfPath> result = new ArrayList<>(layer.paths.size() - pathsToRemove.size());
+
+        for (PdfPath path: layer.paths) {
+            if (!pathsToRemove.contains(path)) {
+                result.add(path);
+            }
+        }
+
+        layer.paths = result;
+    }
+
+
+    /**
+     * This method merges together paths with common end nodes.
+     * @param layer the layer to process.
+     */
+    private void concatenatePaths(LayerContents layer) {
+        Map<Point2D, List<PdfPath>> pathEndpoints = new HashMap<>();
+        Set<PdfPath> mergedPaths = new HashSet<>();
+        List<PdfPath> newPaths = new ArrayList<>();
+
+        //fill pathEndpoints map
+        for (PdfPath pp: layer.paths) {
+            if (pp.isClosed()) {
+                newPaths.add(pp);
+                continue;
+            }
+
+            List<PdfPath> paths = pathEndpoints.get(pp.firstPoint());
+            if (paths == null) {
+                paths = new ArrayList<>(2);
+                pathEndpoints.put(pp.firstPoint(), paths);
+            }
+            paths.add(pp);
+
+            paths = pathEndpoints.get(pp.lastPoint());
+            if (paths == null) {
+                paths = new ArrayList<>(2);
+                pathEndpoints.put(pp.lastPoint(), paths);
+            }
+            paths.add(pp);
+        }
+
+        List<PdfPath> pathChain = new ArrayList<>(2);
+        Set<Point2D> pointsInPath = new HashSet<>();
+
+        //join the paths
+        for (PdfPath pp: layer.paths) {
+
+            if (pp.isClosed() || mergedPaths.contains(pp)) {
+                continue;
+            }
+
+            boolean changed = true;
+
+            PdfPath firstPath = pp;
+            PdfPath lastPath = pp;
+            Point2D firstPoint = pp.firstPoint();
+            Point2D lastPoint = pp.lastPoint();
+
+
+            pathChain.clear();
+            pathChain.add(pp);
+            pointsInPath.clear();
+            pointsInPath.add(firstPoint);
+            pointsInPath.add(lastPoint);
+
+            //process last point
+            while (changed && firstPoint != lastPoint) {
+                changed = false;
+
+                List<PdfPath> adjacentPaths = pathEndpoints.get(lastPoint);
+                PdfPath nextPath = findNextPath(adjacentPaths, lastPath);
+
+                if (nextPath != null) {
+                    Point2D nextPoint = nextPath.getOtherEnd(lastPoint);
+
+                    lastPoint = nextPoint;
+                    lastPath = nextPath;
+                    pathChain.add(lastPath);
+
+                    if (!pointsInPath.contains(lastPoint)) {
+                        pointsInPath.add(lastPoint);
+                        changed = true;
+                    } else {
+                        //closed path found
+                        //remove excess segments from start of chain
+                        while (lastPoint != firstPoint) {
+                            PdfPath pathToRemove = pathChain.remove(0);
+                            firstPoint = pathToRemove.getOtherEnd(firstPoint);
+                        }
+
+                        changed = false;
+                    }
+                }
+            }
+
+            //process first point
+            changed = true;
+            while (changed && firstPoint != lastPoint) {
+                changed = false;
+
+                List<PdfPath> adjacentPaths = pathEndpoints.get(firstPoint);
+                PdfPath nextPath = findNextPath(adjacentPaths, firstPath);
+
+                if (nextPath != null) {
+                    Point2D nextPoint = nextPath.getOtherEnd(firstPoint);
+
+                    firstPoint = nextPoint;
+                    firstPath = nextPath;
+                    pathChain.add(0, firstPath);
+
+                    if (!pointsInPath.contains(firstPoint)) {
+                        pointsInPath.add(firstPoint);
+                        changed = true;
+                    } else {
+                        //closed path found
+                        //remove excess segments from end of chain
+                        while (lastPoint != firstPoint) {
+                            PdfPath pathToRemove = pathChain.remove(pathChain.size() - 1);
+                            lastPoint = pathToRemove.getOtherEnd(lastPoint);
+                        }
+
+                        changed = false;
+                    }
+                }
+            }
+
+            //remove from map
+            for (PdfPath path: pathChain) {
+                pathEndpoints.get(path.firstPoint()).remove(path);
+                pathEndpoints.get(path.lastPoint()).remove(path);
+                mergedPaths.add(path);
+            }
+
+            //construct path
+            PdfPath path = pathChain.get(0);
+
+            for (int pos = 1; pos < pathChain.size(); pos++) {
+                path.points = tryMergeNodeLists(path.points, pathChain.get(pos).points);
+
+                if (path.points == null) {
+                    throw new RuntimeException();
+                }
+            }
+
+            newPaths.add(path);
+        }
+
+        layer.paths = newPaths;
+    }
+
+    private PdfPath findNextPath(List<PdfPath> adjacentPaths, PdfPath firstPath) {
+        for (int pos = 0; pos < adjacentPaths.size(); pos++) {
+            PdfPath p = adjacentPaths.get(pos);
+            if (p != firstPath && !isSubpathOf(firstPath, p)) {
+                return p;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Tests if sub is subpath of main.
+     */
+    private boolean isSubpathOf(PdfPath main, PdfPath sub) {
+
+        Set<Point2D> points = new HashSet<>(main.points);
+
+        for (Point2D point: sub.points) {
+            if (!points.contains(point)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private List<LayerContents> splitBySegmentKind(LayerContents layer, boolean closed, boolean single, boolean orthogonal) {
+        if (!closed && !single) {
+            return Collections.singletonList(layer);
+        }
+
+        OrthogonalShapesFilter of = new OrthogonalShapesFilter(10);
+
+        List<PdfPath> singleSegmentPaths = new ArrayList<>();
+        List<PdfPath> multiSegmentPaths = new ArrayList<>();
+        List<PdfPath> closedPaths = new ArrayList<>();
+        List<PdfPath> orthogonalPaths = new ArrayList<>();
+        List<PdfPath> orthogonalClosedPaths = new ArrayList<>();
+
+        for (PdfPath path: layer.paths) {
+            boolean pathOrthgonal = orthogonal && of.isOrthogonal(path);
+            boolean pathUnclosed = !path.isClosed() && closed;
+            boolean pathSingleSegment = path.points.size() <= 3 && single;
+
+            if (pathSingleSegment) {
+                singleSegmentPaths.add(path);
+            } else if (pathUnclosed) {
+                if (pathOrthgonal) {
+                    orthogonalPaths.add(path);
+                } else {
+                    multiSegmentPaths.add(path);
+                }
+            } else {
+                if (pathOrthgonal) {
+                    orthogonalClosedPaths.add(path);
+                } else {
+                    closedPaths.add(path);
+                }
+            }
+        }
+
+        List<LayerContents> layers = new ArrayList<>();
+
+        if (multiSegmentPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = multiSegmentPaths;
+            l.info = layer.info.copy();
+
+            layers.add(l);
+        }
+
+        if (singleSegmentPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = singleSegmentPaths;
+            l.info = layer.info.copy();
+            layers.add(l);
+        }
+
+        if (orthogonalPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = orthogonalPaths;
+            l.info = layer.info.copy();
+            layers.add(l);
+        }
+
+        if (orthogonalClosedPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = orthogonalClosedPaths;
+            l.info = layer.info.copy();
+            layers.add(l);
+        }
+
+        if (closedPaths.size() > 0 || layer.multiPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = closedPaths;
+            l.info = layer.info.copy();
+            l.multiPaths = layer.multiPaths;
+            layers.add(l);
+        }
+
+        return layers;
+    }
+
+    private List<LayerContents> splitBySimilarGroups(LayerContents layer) {
+        List<List<PdfPath>> subparts = new ArrayList<>();
+
+        //split into similar parts
+        for (PdfPath path: layer.paths) {
+            List<PdfPath> sublayer = null;
+
+            for (List<PdfPath> ll: subparts) {
+                if (this.pathsSimilar(ll.get(0).points, path.points)) {
+                    sublayer = ll;
+                    break;
+                }
+            }
+
+            if (sublayer == null) {
+                sublayer = new ArrayList<>();
+                subparts.add(sublayer);
+            }
+
+            sublayer.add(path);
+        }
+
+        //get groups
+        int minGroupTreshold = 10;
+
+        List<PdfPath> independantPaths = new ArrayList<>();
+        List<LayerContents> result = new ArrayList<>();
+
+        for (List<PdfPath> list: subparts) {
+            if (list.size() >= minGroupTreshold) {
+                LayerContents l = new LayerContents();
+                l.paths = list;
+                l.info = layer.info.copy();
+                l.info.isGroup = true;
+                l.multiPaths = Collections.emptyList();
+                result.add(l);
+            } else {
+                independantPaths.addAll(list);
+            }
+        }
+
+        if (independantPaths.size() > 0 || layer.multiPaths.size() > 0) {
+            LayerContents l = new LayerContents();
+            l.paths = independantPaths;
+            l.info = layer.info.copy();
+            l.info.isGroup = false;
+            l.multiPaths = layer.multiPaths;
+            result.add(l);
+        }
+
+        return result;
+    }
+
+    private List<Point2D> tryMergeNodeLists(List<Point2D> nodes1, List<Point2D> nodes2) {
+
+        boolean nodes1Closed = (nodes1.get(0) == nodes1.get(nodes1.size() - 1));
+        boolean nodes2Closed = (nodes2.get(0) == nodes2.get(nodes2.size() - 1));
+
+        if (nodes1Closed || nodes2Closed) {
+            return null;
+        }
+
+        if (nodes1.get(nodes1.size() - 1) == nodes2.get(0)) {
+            nodes1.remove(nodes1.size() -1);
+            nodes1.addAll(nodes2);
+            return nodes1;
+        } else if (nodes1.get(nodes1.size() - 1) == nodes2.get(nodes2.size() -1)) {
+            nodes1.remove(nodes1.size() -1);
+            for (int pos = nodes2.size() - 1; pos >= 0; pos--) {
+                nodes1.add(nodes2.get(pos));
+            }
+
+            return nodes1;
+        } else if (nodes1.get(0) == nodes2.get(nodes2.size() - 1)) {
+            nodes1.remove(0);
+            nodes1.addAll(0, nodes2);
+            return nodes1;
+        } else if (nodes1.get(0) == nodes2.get(0)) {
+            nodes1.remove(0);
+            for (int pos = 0; pos < nodes2.size(); pos++) {
+                nodes1.add(0, nodes2.get(pos));
+            }
+
+            return nodes1;
+        } else {
+            return null;
+        }
+    }
+
+    public List<LayerContents> getLayers() {
+        return this.layers;
+    }
+
+    /**
+     * Test if paths are different only by offset.
+     */
+    private boolean pathsSimilar(List<Point2D> path1, List<Point2D> path2) {
+        if (path1.size() != path2.size()) {
+            return false;
+        }
+
+        if (path1.size() < 3) {
+            return false;
+            //cannot judge so small paths
+        }
+
+        Point2D p1 = path1.get(0);
+        Point2D p2 = path2.get(0);
+
+        double offsetX = p1.getX() - p2.getX();
+        double offsetY = p1.getY() - p2.getY();
+        double tolerance = 1e-4;
+
+        for (int pos = 0; pos < path1.size(); pos++) {
+            p1 = path1.get(pos);
+            p2 = path2.get(pos);
+
+            double errorX = p1.getX() - p2.getX() - offsetX;
+            double errorY = p1.getY() - p2.getY() - offsetY;
+
+            if (Math.abs(errorX) + Math.abs(errorY) > tolerance) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportAction.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportAction.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportAction.java	(revision 32542)
@@ -1,3 +1,3 @@
-// License: GPL.
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -16,25 +16,25 @@
 public class PdfImportAction extends JosmAction {
 
-	public PdfImportAction() {
-		super(tr("Import PDF file"), "pdf_import",
-		    tr("Import PDF file."), 
-		    Shortcut.registerShortcut("tools:pdfimport", tr("Tool: {0}",tr("Import PDF file")),
-		    KeyEvent.VK_F, Shortcut.ALT_CTRL_SHIFT)
-		    , true);
-	}
+    public PdfImportAction() {
+        super(tr("Import PDF file"), "pdf_import",
+            tr("Import PDF file."),
+            Shortcut.registerShortcut("tools:pdfimport", tr("Tool: {0}", tr("Import PDF file")),
+            KeyEvent.VK_F, Shortcut.ALT_CTRL_SHIFT), true);
+    }
 
-	/**
-	 * The action button has been clicked
-	 *
-	 * @param e
-	 *            Action Event
-	 */
-	public void actionPerformed(ActionEvent e) {
+    /**
+     * The action button has been clicked
+     *
+     * @param e
+     *            Action Event
+     */
+    @Override
+    public void actionPerformed(ActionEvent e) {
 
-		//show dialog asking to select coordinate axes and input coordinates and projection.
-		LoadPdfDialog dialog = new LoadPdfDialog();
-		dialog.setAlwaysOnTop(true);
-		dialog.setTitle(tr("Import PDF"));
-		dialog.setVisible(true);
-	}
+        //show dialog asking to select coordinate axes and input coordinates and projection.
+        LoadPdfDialog dialog = new LoadPdfDialog();
+        dialog.setAlwaysOnTop(true);
+        dialog.setTitle(tr("Import PDF"));
+        dialog.setVisible(true);
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportPlugin.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportPlugin.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfImportPlugin.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -13,10 +14,10 @@
 public class PdfImportPlugin extends Plugin {
 
-	protected String name;
+    protected String name;
 
-	public PdfImportPlugin(PluginInformation info) {
-		super(info);
-		name = tr("Import PDF file");
-		MainMenu.add(Main.main.menu.imagerySubMenu, new PdfImportAction());
-	}
+    public PdfImportPlugin(PluginInformation info) {
+        super(info);
+        name = tr("Import PDF file");
+        MainMenu.add(Main.main.menu.imagerySubMenu, new PdfImportAction());
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfMultiPath.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfMultiPath.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfMultiPath.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -4,10 +5,9 @@
 
 public class PdfMultiPath {
-	public List<PdfPath> paths;
-	public LayerContents layer;
+    public List<PdfPath> paths;
+    public LayerContents layer;
 
-	public PdfMultiPath(List<PdfPath> paths2) {
-		paths = paths2;
-	}
-
+    public PdfMultiPath(List<PdfPath> paths2) {
+        paths = paths2;
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfPath.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfPath.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/PdfPath.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport;
 
@@ -5,48 +6,47 @@
 
 public class PdfPath {
-	public List<Point2D> points;
-	public double length;
+    public List<Point2D> points;
+    public double length;
 
-	LayerContents layer;
-	public int nr;
+    LayerContents layer;
+    public int nr;
 
+    public PdfPath(List<Point2D> nodes) {
+        points = nodes;
+    }
 
-	public PdfPath(List<Point2D> nodes) {
-		points = nodes;
-	}
+    public boolean isClosed() {
+        return points.size() > 1 && points.get(0) == points.get(points.size() - 1);
+    }
 
-	public boolean isClosed() {
-		return points.size() > 1 && points.get(0) == points.get(points.size() - 1);
-	}
+    public Point2D firstPoint() {
+        return points.get(0);
+    }
 
-	public Point2D firstPoint() {
-		return points.get(0);
-	}
+    public Point2D lastPoint() {
+        return points.get(points.size() - 1);
+    }
 
-	public Point2D lastPoint() {
-		return points.get(points.size() - 1);
-	}
+    public void calculateLength() {
+        double len = 0;
 
-	public void calculateLength() {
-		double len = 0;
+        for (int pos = 1; pos < points.size(); pos++) {
+            len += points.get(pos).distance(points.get(pos -1));
+        }
 
-		for(int pos =1; pos < points.size(); pos ++) {
-			len += points.get(pos).distance(points.get(pos -1));
-		}
+        this.length = len;
+    }
 
-		this.length = len;
-	}
+    public Point2D getOtherEnd(Point2D endPoint) {
+        if (this.firstPoint() == endPoint) {
+            return this.lastPoint();
+        }
 
-	public Point2D getOtherEnd(Point2D endPoint) {
-		if (this.firstPoint() == endPoint) {
-			return this.lastPoint();
-		}
+        if (this.lastPoint() == endPoint) {
+            return this.firstPoint();
+        }
 
-		if (this.lastPoint() == endPoint) {
-			return this.firstPoint();
-		}
+        throw new RuntimeException("Unexpected point");
 
-		throw new RuntimeException("Unexpected point");
-
-	}
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/ProjectionInfo.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/ProjectionInfo.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/ProjectionInfo.java	(revision 32542)
@@ -10,5 +10,5 @@
 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
 
-public class ProjectionInfo {
+public final class ProjectionInfo {
     private static Map<String, ProjectionChoice> allCodesPC = new HashMap<>();
     private static Map<String, Projection> allCodes = new HashMap<>();
@@ -20,4 +20,8 @@
             }
         }
+    }
+
+    private ProjectionInfo() {
+        // Hide default constructor for utilities classes
     }
 
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/GraphicsProcessor.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/GraphicsProcessor.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/GraphicsProcessor.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport.pdfbox;
 
@@ -19,218 +20,196 @@
 import pdfimport.PdfPath;
 
-public class GraphicsProcessor{
-
-	public PathOptimizer target;
-	private Shape  clipShape;
-	private List<PdfPath> clipPath;
-	private final LayerInfo info = new LayerInfo();
-	int pathNo = 0;
-	private boolean complexClipShape;
-	private boolean clipAreaDrawn;
-
-	private final AffineTransform transform;
-	private final ProgressMonitor monitor;
-	private final int maxPaths;
-
-
-	public GraphicsProcessor(PathOptimizer target, int rotation, int maxPaths, ProgressMonitor monitor)
-	{
-		this.maxPaths = maxPaths;
-		this.target = target;
-		this.transform = new AffineTransform();
-		this.transform.rotate(-Math.toRadians(rotation));
-		this.info.stroke = Color.BLACK;
-		this.info.fill = Color.BLACK;
-		this.monitor = monitor;
-	}
-
-
-	private void addPath(Shape s, boolean closed) {
-		pathNo ++;
-
-		if (pathNo % 100 == 0) {
-			this.monitor.setCustomText(tr(" {0} objects so far", pathNo));
-		}
-
-		if (pathNo >= maxPaths) {
-			return;
-		}
-
-		List<PdfPath> paths = this.parsePath(s, closed);
-
-		for (PdfPath p: paths){
-			p.nr = pathNo;
-		}
-
-
-		if (paths.size() > 1) {
-			this.target.addMultiPath(this.info, paths);
-			this.parsePath(s, closed);
-		}
-		else if (paths.size() == 1) {
-			this.target.addPath(this.info, paths.get(0));
-		}
-
-	}
-
-
-	private List<PdfPath> parsePath(Shape s, boolean closed) {
-		List<PdfPath> result = new ArrayList<>(2);
-		List<Point2D> points = new ArrayList<>(2);
-
-		PathIterator iter = s.getPathIterator(null);
-		double[] coords = new double[6];
-
-		while (!iter.isDone()) {
-			int type = iter.currentSegment(coords);
-
-			if (type == PathIterator.SEG_CLOSE) {
-				//close polygon
-				this.addPoint(points, points.get(0));
-				if (points.size() > 1) {
-					result.add(new PdfPath(points));
-				}
-				points = new ArrayList<>(2);
-			} else if (type == PathIterator.SEG_CUBICTO) {
-				//cubic curve
-				this.addPoint(points, this.parsePoint(coords, 4));
-			}
-			else if (type == PathIterator.SEG_LINETO) {
-				this.addPoint(points, this.parsePoint(coords, 0));
-			}
-			else if (type == PathIterator.SEG_MOVETO) {
-				//new path
-				if (points.size() > 1){
-					result.add(new PdfPath(points));
-				}
-				points = new ArrayList<>(2);
-				this.addPoint(points, this.parsePoint(coords, 0));
-			}
-			else if (type == PathIterator.SEG_QUADTO) {
-				//quadratic curve
-				this.addPoint(points, this.parsePoint(coords, 2));
-			}
-			else if (type == PathIterator.WIND_EVEN_ODD) {
-				//fill even odd
-			}
-			else if (type == PathIterator.WIND_NON_ZERO) {
-				//fill all
-			}
-			else {
-				//Unexpected operation
-			}
-
-			iter.next();
-		}
-
-		if (points.size() > 1 )
-		{
-			if (closed) {
-				this.addPoint(points, points.get(0));
-			}
-
-			result.add(new PdfPath(points));
-		}
-
-		return result;
-	}
-
-	private void addPoint(List<Point2D> points, Point2D point) {
-		if (points.size() > 0) {
-			Point2D prevPoint = points.get(points.size() - 1);
-
-			if (prevPoint.getX() == point.getX() && prevPoint.getY() == point.getY()) {
-				return;
-			}
-		}
-
-		points.add(point);
-	}
-
-	private Point2D parsePoint(double[] buffer, int offset) {
-		//invert Y.
-		Point2D point = new Point2D.Double(buffer[offset], buffer[offset + 1]);
-		Point2D dest = new Point2D.Double();
-		this.transform.transform(point, dest);
-		return this.target.getUniquePoint(dest);
-	}
-
-	public void drawPath(Shape path, Color stroke, Color fill,
-			int windingRule) {
-
-		if (complexClipShape) {
-			if (!this.clipAreaDrawn) {
-				this.info.stroke = null;
-				this.info.fill = Color.CYAN;
-				this.addPath(this.clipShape, true);
-				this.clipAreaDrawn = true;
-			}
-		}
-
-		if (!complexClipShape || fill != null) {
-			this.info.stroke = stroke;
-			this.info.fill = fill;
-			this.addPath(path, fill != null);
-		}
-	}
-
-
-	public void drawImage() {
-
-		if (!this.clipAreaDrawn) {
-			this.info.stroke = null;
-			this.info.fill = Color.CYAN;
-			this.addPath(this.clipShape, true);
-			this.clipAreaDrawn = true;
-		}
-	}
-
-
-	public void setClip(Shape  clip) {
-		if (this.shapesEqual(this.clipShape,clip))
-			return;
-
-		this.clipPath = this.parsePath(clip, true);
-
-		boolean complexClipPath = false;
-
-		if (clipPath.size() > 1)
-		{
-			complexClipPath = true;
-		}
-		else if (clipPath.size() == 1 && clipPath.get(0).points.size() > 5)
-		{
-			//more than 4 points.
-			complexClipPath = true;
-		}
-
-		this.complexClipShape = complexClipPath;
-		this.clipAreaDrawn = false;
-		this.clipShape = clip;
-	}
-
-
-	private boolean shapesEqual(Shape shape1, Shape shape2) {
-
-		if (shape1== null || shape2 == null){
-			return false;
-		}
-
-		return shape1.getBounds2D().equals(shape2.getBounds2D());
-	}
-
-
-	public void setStroke(Stroke s) {
-		BasicStroke stroke = (BasicStroke) s;
-		this.info.width = stroke.getLineWidth();
-		this.info.dash = 0;
-
-		if (stroke.getDashArray() != null) {
-			this.info.dash = stroke.getDashArray().hashCode();
-		}
-	}
-
-	public void drawString(float x, float y, String character, Color color) {
-		// TODO Auto-generated method stub
-	}
+public class GraphicsProcessor {
+
+    public PathOptimizer target;
+    private Shape clipShape;
+    private List<PdfPath> clipPath;
+    private final LayerInfo info = new LayerInfo();
+    int pathNo = 0;
+    private boolean complexClipShape;
+    private boolean clipAreaDrawn;
+
+    private final AffineTransform transform;
+    private final ProgressMonitor monitor;
+    private final int maxPaths;
+
+    public GraphicsProcessor(PathOptimizer target, int rotation, int maxPaths, ProgressMonitor monitor) {
+        this.maxPaths = maxPaths;
+        this.target = target;
+        this.transform = new AffineTransform();
+        this.transform.rotate(-Math.toRadians(rotation));
+        this.info.stroke = Color.BLACK;
+        this.info.fill = Color.BLACK;
+        this.monitor = monitor;
+    }
+
+    private void addPath(Shape s, boolean closed) {
+        pathNo++;
+
+        if (pathNo % 100 == 0) {
+            this.monitor.setCustomText(tr(" {0} objects so far", pathNo));
+        }
+
+        if (pathNo >= maxPaths) {
+            return;
+        }
+
+        List<PdfPath> paths = this.parsePath(s, closed);
+
+        for (PdfPath p: paths) {
+            p.nr = pathNo;
+        }
+
+
+        if (paths.size() > 1) {
+            this.target.addMultiPath(this.info, paths);
+            this.parsePath(s, closed);
+        } else if (paths.size() == 1) {
+            this.target.addPath(this.info, paths.get(0));
+        }
+    }
+
+    private List<PdfPath> parsePath(Shape s, boolean closed) {
+        List<PdfPath> result = new ArrayList<>(2);
+        List<Point2D> points = new ArrayList<>(2);
+
+        PathIterator iter = s.getPathIterator(null);
+        double[] coords = new double[6];
+
+        while (!iter.isDone()) {
+            int type = iter.currentSegment(coords);
+
+            if (type == PathIterator.SEG_CLOSE) {
+                //close polygon
+                this.addPoint(points, points.get(0));
+                if (points.size() > 1) {
+                    result.add(new PdfPath(points));
+                }
+                points = new ArrayList<>(2);
+            } else if (type == PathIterator.SEG_CUBICTO) {
+                //cubic curve
+                this.addPoint(points, this.parsePoint(coords, 4));
+            } else if (type == PathIterator.SEG_LINETO) {
+                this.addPoint(points, this.parsePoint(coords, 0));
+            } else if (type == PathIterator.SEG_MOVETO) {
+                //new path
+                if (points.size() > 1) {
+                    result.add(new PdfPath(points));
+                }
+                points = new ArrayList<>(2);
+                this.addPoint(points, this.parsePoint(coords, 0));
+            } else if (type == PathIterator.SEG_QUADTO) {
+                //quadratic curve
+                this.addPoint(points, this.parsePoint(coords, 2));
+            } else if (type == PathIterator.WIND_EVEN_ODD) {
+                //fill even odd
+            } else if (type == PathIterator.WIND_NON_ZERO) {
+                //fill all
+            }
+
+            iter.next();
+        }
+
+        if (points.size() > 1) {
+            if (closed) {
+                this.addPoint(points, points.get(0));
+            }
+
+            result.add(new PdfPath(points));
+        }
+
+        return result;
+    }
+
+    private void addPoint(List<Point2D> points, Point2D point) {
+        if (points.size() > 0) {
+            Point2D prevPoint = points.get(points.size() - 1);
+
+            if (prevPoint.getX() == point.getX() && prevPoint.getY() == point.getY()) {
+                return;
+            }
+        }
+
+        points.add(point);
+    }
+
+    private Point2D parsePoint(double[] buffer, int offset) {
+        //invert Y.
+        Point2D point = new Point2D.Double(buffer[offset], buffer[offset + 1]);
+        Point2D dest = new Point2D.Double();
+        this.transform.transform(point, dest);
+        return this.target.getUniquePoint(dest);
+    }
+
+    public void drawPath(Shape path, Color stroke, Color fill,
+            int windingRule) {
+
+        if (complexClipShape) {
+            if (!this.clipAreaDrawn) {
+                this.info.stroke = null;
+                this.info.fill = Color.CYAN;
+                this.addPath(this.clipShape, true);
+                this.clipAreaDrawn = true;
+            }
+        }
+
+        if (!complexClipShape || fill != null) {
+            this.info.stroke = stroke;
+            this.info.fill = fill;
+            this.addPath(path, fill != null);
+        }
+    }
+
+    public void drawImage() {
+
+        if (!this.clipAreaDrawn) {
+            this.info.stroke = null;
+            this.info.fill = Color.CYAN;
+            this.addPath(this.clipShape, true);
+            this.clipAreaDrawn = true;
+        }
+    }
+
+    public void setClip(Shape clip) {
+        if (this.shapesEqual(this.clipShape, clip))
+            return;
+
+        this.clipPath = this.parsePath(clip, true);
+
+        boolean complexClipPath = false;
+
+        if (clipPath.size() > 1) {
+            complexClipPath = true;
+        } else if (clipPath.size() == 1 && clipPath.get(0).points.size() > 5) {
+            //more than 4 points.
+            complexClipPath = true;
+        }
+
+        this.complexClipShape = complexClipPath;
+        this.clipAreaDrawn = false;
+        this.clipShape = clip;
+    }
+
+    private boolean shapesEqual(Shape shape1, Shape shape2) {
+
+        if (shape1 == null || shape2 == null) {
+            return false;
+        }
+
+        return shape1.getBounds2D().equals(shape2.getBounds2D());
+    }
+
+    public void setStroke(Stroke s) {
+        BasicStroke stroke = (BasicStroke) s;
+        this.info.width = stroke.getLineWidth();
+        this.info.dash = 0;
+
+        if (stroke.getDashArray() != null) {
+            this.info.dash = stroke.getDashArray().hashCode();
+        }
+    }
+
+    public void drawString(float x, float y, String character, Color color) {
+        // TODO Auto-generated method stub
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PageDrawer.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PageDrawer.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PageDrawer.java	(revision 32542)
@@ -47,232 +47,204 @@
  * @version $Revision: 1.22 $
  */
-public class PageDrawer extends PDFStreamEngine
-{
-
-	private GraphicsProcessor graphics;
-	private BasicStroke stroke;
-	protected PDPage page;
-
-	private final GeneralPath linePath = new GeneralPath();
-
-	/**
-	 * Default constructor, loads properties from file.
-	 *
-	 * @throws IOException If there is an error loading properties from the file.
-	 */
-	public PageDrawer() throws IOException
-	{
-		super( ResourceLoader.loadProperties(
-				"resources/pdfimport/pdfbox/PageDrawer.properties", true ) );
-	}
-
-	/**
-	 * This will draw the page to the requested context.
-	 *
-	 * @param g The graphics context to draw onto.
-	 * @param p The page to draw.
-	 * @param pageDimension The size of the page to draw.
-	 *
-	 * @throws IOException If there is an IO error while drawing the page.
-	 */
-	public void drawPage( GraphicsProcessor g, PDPage p) throws IOException
-	{
-		graphics = g;
-		page = p;
-		// Only if there is some content, we have to process it.
-		// Otherwise we are done here and we will produce an empty page
-		if ( page.getContents() != null)
-		{
-			PDResources resources = page.findResources();
-			processStream( page, resources, page.getContents().getStream() );
-		}
-
-		List<?> annotations = page.getAnnotations();
-		for( int i=0; i<annotations.size(); i++ )
-		{
-			PDAnnotation annot = (PDAnnotation)annotations.get( i );
-			String appearanceName = annot.getAppearanceStream();
-			PDAppearanceDictionary appearDictionary = annot.getAppearance();
-			if( appearDictionary != null )
-			{
-				if( appearanceName == null )
-				{
-					appearanceName = "default";
-				}
-				Map<?, ?> appearanceMap = appearDictionary.getNormalAppearance();
-				if (appearanceMap != null) {
-					PDAppearanceStream appearance =
-						(PDAppearanceStream)appearanceMap.get( appearanceName );
-					if( appearance != null )
-					{
-						processSubStream( page, appearance.getResources(), appearance.getStream() );
-					}
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * You should override this method if you want to perform an action when a
-	 * text is being processed.
-	 *
-	 * @param text The text to process
-	 */
-	@Override
-	protected void processTextPosition( TextPosition text )
-	{
-
-		Color color = null;
-
-		try
-		{
-			switch(this.getGraphicsState().getTextState().getRenderingMode()) {
-			case PDTextState.RENDERING_MODE_FILL_TEXT:
-				color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
-				break;
-			case PDTextState.RENDERING_MODE_STROKE_TEXT:
-				color = this.getGraphicsState().getStrokingColor().getJavaColor();
-				break;
-			case PDTextState.RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT:
-				//basic support for text rendering mode "invisible"
-				Color nsc = this.getGraphicsState().getStrokingColor().getJavaColor();
-				float[] components = {Color.black.getRed(),Color.black.getGreen(),Color.black.getBlue()};
-				color =  new Color(nsc.getColorSpace(),components,0f);
-				break;
-			default:
-				color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
-			}
-
-			Matrix textPos = text.getTextPos().copy();
-			float x = textPos.getXPosition();
-			float y = textPos.getYPosition();
-			graphics.setClip(getGraphicsState().getCurrentClippingPath());
-			graphics.drawString(x,y,text.getCharacter(), color);
-		}
-		catch( IOException io )
-		{
-			io.printStackTrace();
-		}
-	}
-
-
-	/**
-	 * Get the page that is currently being drawn.
-	 *
-	 * @return The page that is being drawn.
-	 */
-	public PDPage getPage()
-	{
-		return page;
-	}
-
-
-	/**
-	 * Get the current line path to be drawn.
-	 *
-	 * @return The current line path to be drawn.
-	 */
-	public GeneralPath getLinePath()
-	{
-		return linePath;
-	}
-
-
-	/**
-	 * This will set the current stroke.
-	 *
-	 * @param newStroke The current stroke.
-	 *
-	 */
-	public void setStroke(BasicStroke newStroke)
-	{
-		this.stroke = newStroke;
-	}
-
-	public BasicStroke getStroke() {
-		return this.stroke;
-	}
-
-
-	public void drawPath(boolean stroke, boolean fill, int windingRule) throws IOException
-	{
-		graphics.setClip(getGraphicsState().getCurrentClippingPath());
-		GeneralPath path = getLinePath();
-
-		Color strokeColor = getGraphicsState().getStrokingColor().getJavaColor();
-		Color fillColor = getGraphicsState().getNonStrokingColor().getJavaColor();
-		graphics.drawPath(path, stroke ? strokeColor : null, fill ? fillColor : null, windingRule);
-
-		path.reset();
-	}
-
-
-	/**
-	 * Draw the AWT image. Called by Invoke.
-	 * Moved into PageDrawer so that Invoke doesn't have to reach in here for Graphics as that breaks extensibility.
-	 *
-	 * @param awtImage The image to draw.
-	 * @param at The transformation to use when drawing.
-	 *
-	 */
-	public void drawImage(){
-		graphics.setClip(getGraphicsState().getCurrentClippingPath());
-		graphics.drawImage( );
-	}
-
-	/**
-	 * Fill with Shading.  Called by SHFill operator.
-	 *
-	 * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
-	 *
-	 * @throws IOException If there is an IO error while shade-filling the path/clipping area.
-	 */
-	public void SHFill(COSName ShadingName) throws IOException
-	{
-		this.drawPath(false, true, Path2D.WIND_NON_ZERO);
-	}
-
-
-
-	//This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
-	/**
-	 * use the current transformation matrix to transform a single point.
-	 * @param x x-coordinate of the point to be transform
-	 * @param y y-coordinate of the point to be transform
-	 * @return the transformed coordinates as Point2D.Double
-	 */
-	public java.awt.geom.Point2D.Double transformedPoint(double x, double y)
-	{
-		double[] position = {x,y};
-		getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
-				position, 0, position, 0, 1);
-		return new Point2D.Double(position[0],position[1]);
-	}
-
-	/**
-	 * Set the clipping Path.
-	 *
-	 * @param windingRule The winding rule this path will use.
-	 *
-	 */
-	public void setClippingPath(int windingRule)
-	{
-		PDGraphicsState graphicsState = getGraphicsState();
-		GeneralPath clippingPath = (GeneralPath)getLinePath().clone();
-		clippingPath.setWindingRule(windingRule);
-		// If there is already set a clipping path, we have to intersect the new with the existing one
-		if (graphicsState.getCurrentClippingPath() != null)
-		{
-			Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
-			Area newArea = new Area(clippingPath);
-			currentArea.intersect(newArea);
-			graphicsState.setCurrentClippingPath(currentArea);
-		}
-		else
-		{
-			graphicsState.setCurrentClippingPath(clippingPath);
-		}
-		getLinePath().reset();
-	}
+public class PageDrawer extends PDFStreamEngine {
+
+    private GraphicsProcessor graphics;
+    private BasicStroke stroke;
+    protected PDPage page;
+
+    private final GeneralPath linePath = new GeneralPath();
+
+    /**
+     * Default constructor, loads properties from file.
+     *
+     * @throws IOException If there is an error loading properties from the file.
+     */
+    public PageDrawer() throws IOException {
+        super(ResourceLoader.loadProperties(
+                "resources/pdfimport/pdfbox/PageDrawer.properties", true));
+    }
+
+    /**
+     * This will draw the page to the requested context.
+     *
+     * @param g The graphics context to draw onto.
+     * @param p The page to draw.
+     * @param pageDimension The size of the page to draw.
+     *
+     * @throws IOException If there is an IO error while drawing the page.
+     */
+    public void drawPage(GraphicsProcessor g, PDPage p) throws IOException {
+        graphics = g;
+        page = p;
+        // Only if there is some content, we have to process it.
+        // Otherwise we are done here and we will produce an empty page
+        if (page.getContents() != null) {
+            PDResources resources = page.findResources();
+            processStream(page, resources, page.getContents().getStream());
+        }
+
+        List<?> annotations = page.getAnnotations();
+        for (int i = 0; i < annotations.size(); i++) {
+            PDAnnotation annot = (PDAnnotation) annotations.get(i);
+            String appearanceName = annot.getAppearanceStream();
+            PDAppearanceDictionary appearDictionary = annot.getAppearance();
+            if (appearDictionary != null) {
+                if (appearanceName == null) {
+                    appearanceName = "default";
+                }
+                Map<?, ?> appearanceMap = appearDictionary.getNormalAppearance();
+                if (appearanceMap != null) {
+                    PDAppearanceStream appearance =
+                        (PDAppearanceStream) appearanceMap.get(appearanceName);
+                    if (appearance != null) {
+                        processSubStream(page, appearance.getResources(), appearance.getStream());
+                    }
+                }
+            }
+        }
+
+    }
+
+    /**
+     * You should override this method if you want to perform an action when a
+     * text is being processed.
+     *
+     * @param text The text to process
+     */
+    @Override
+    protected void processTextPosition(TextPosition text) {
+
+        Color color = null;
+
+        try {
+            switch(this.getGraphicsState().getTextState().getRenderingMode()) {
+            case PDTextState.RENDERING_MODE_FILL_TEXT:
+                color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
+                break;
+            case PDTextState.RENDERING_MODE_STROKE_TEXT:
+                color = this.getGraphicsState().getStrokingColor().getJavaColor();
+                break;
+            case PDTextState.RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT:
+                //basic support for text rendering mode "invisible"
+                Color nsc = this.getGraphicsState().getStrokingColor().getJavaColor();
+                float[] components = {Color.black.getRed(), Color.black.getGreen(), Color.black.getBlue()};
+                color = new Color(nsc.getColorSpace(), components, 0f);
+                break;
+            default:
+                color = this.getGraphicsState().getNonStrokingColor().getJavaColor();
+            }
+
+            Matrix textPos = text.getTextPos().copy();
+            float x = textPos.getXPosition();
+            float y = textPos.getYPosition();
+            graphics.setClip(getGraphicsState().getCurrentClippingPath());
+            graphics.drawString(x, y, text.getCharacter(), color);
+        } catch (IOException io) {
+            io.printStackTrace();
+        }
+    }
+
+    /**
+     * Get the page that is currently being drawn.
+     *
+     * @return The page that is being drawn.
+     */
+    public PDPage getPage() {
+        return page;
+    }
+
+    /**
+     * Get the current line path to be drawn.
+     *
+     * @return The current line path to be drawn.
+     */
+    public GeneralPath getLinePath() {
+        return linePath;
+    }
+
+    /**
+     * This will set the current stroke.
+     *
+     * @param newStroke The current stroke.
+     *
+     */
+    public void setStroke(BasicStroke newStroke) {
+        this.stroke = newStroke;
+    }
+
+    public BasicStroke getStroke() {
+        return this.stroke;
+    }
+
+    public void drawPath(boolean stroke, boolean fill, int windingRule) throws IOException {
+        graphics.setClip(getGraphicsState().getCurrentClippingPath());
+        GeneralPath path = getLinePath();
+
+        Color strokeColor = getGraphicsState().getStrokingColor().getJavaColor();
+        Color fillColor = getGraphicsState().getNonStrokingColor().getJavaColor();
+        graphics.drawPath(path, stroke ? strokeColor : null, fill ? fillColor : null, windingRule);
+
+        path.reset();
+    }
+
+
+    /**
+     * Draw the AWT image. Called by Invoke.
+     * Moved into PageDrawer so that Invoke doesn't have to reach in here for Graphics as that breaks extensibility.
+     *
+     * @param awtImage The image to draw.
+     * @param at The transformation to use when drawing.
+     *
+     */
+    public void drawImage() {
+        graphics.setClip(getGraphicsState().getCurrentClippingPath());
+        graphics.drawImage();
+    }
+
+    /**
+     * Fill with Shading.  Called by SHFill operator.
+     *
+     * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    public void SHFill(COSName ShadingName) throws IOException {
+        this.drawPath(false, true, Path2D.WIND_NON_ZERO);
+    }
+
+    //This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
+    /**
+     * use the current transformation matrix to transform a single point.
+     * @param x x-coordinate of the point to be transform
+     * @param y y-coordinate of the point to be transform
+     * @return the transformed coordinates as Point2D.Double
+     */
+    public java.awt.geom.Point2D.Double transformedPoint(double x, double y) {
+        double[] position = {x, y};
+        getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
+                position, 0, position, 0, 1);
+        return new Point2D.Double(position[0], position[1]);
+    }
+
+    /**
+     * Set the clipping Path.
+     *
+     * @param windingRule The winding rule this path will use.
+     *
+     */
+    public void setClippingPath(int windingRule) {
+        PDGraphicsState graphicsState = getGraphicsState();
+        GeneralPath clippingPath = (GeneralPath) getLinePath().clone();
+        clippingPath.setWindingRule(windingRule);
+        // If there is already set a clipping path, we have to intersect the new with the existing one
+        if (graphicsState.getCurrentClippingPath() != null) {
+            Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
+            Area newArea = new Area(clippingPath);
+            currentArea.intersect(newArea);
+            graphicsState.setCurrentClippingPath(currentArea);
+        } else {
+            graphicsState.setCurrentClippingPath(clippingPath);
+        }
+        getLinePath().reset();
+    }
 }
Index: /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PdfBoxParser.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PdfBoxParser.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/src/pdfimport/pdfbox/PdfBoxParser.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport.pdfbox;
 
@@ -16,10 +17,10 @@
 import pdfimport.PathOptimizer;
 
-public class PdfBoxParser extends PDFStreamEngine{
-	private final PathOptimizer target;
+public class PdfBoxParser extends PDFStreamEngine {
+    private final PathOptimizer target;
 
-	public PdfBoxParser(PathOptimizer target){
-		this.target = target;
-	}
+    public PdfBoxParser(PathOptimizer target) {
+        this.target = target;
+    }
 
     public void parse(File file, int maxPaths, ProgressMonitor monitor) throws IOException {
@@ -37,5 +38,5 @@
                 throw new IllegalArgumentException(tr("The PDF file must have exactly one page."));
             }
-            
+
             PDPage page = (PDPage) allPages.get(0);
             PDRectangle pageSize = page.findMediaBox();
@@ -45,10 +46,10 @@
                 rotation = rotationVal.intValue();
             }
-    
+
             new PageDrawer().drawPage(new GraphicsProcessor(target, rotation, maxPaths, monitor), page);
             this.target.bounds = new Rectangle2D.Double(
                     pageSize.getLowerLeftX(),
-                    pageSize.getLowerLeftY(), 
-                    pageSize.getWidth(), 
+                    pageSize.getLowerLeftY(),
+                    pageSize.getWidth(),
                     pageSize.getHeight());
         }
Index: /applications/editors/josm/plugins/pdfimport/test/unit/pdfimport/pdfbox/PDFParserTest.java
===================================================================
--- /applications/editors/josm/plugins/pdfimport/test/unit/pdfimport/pdfbox/PDFParserTest.java	(revision 32541)
+++ /applications/editors/josm/plugins/pdfimport/test/unit/pdfimport/pdfbox/PDFParserTest.java	(revision 32542)
@@ -1,2 +1,3 @@
+// License: GPL. For details, see LICENSE file.
 package pdfimport.pdfbox;
 
