Index: plugins/geotools/ivy.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/geotools/ivy.xml b/plugins/geotools/ivy.xml
--- a/plugins/geotools/ivy.xml	(revision 36023)
+++ b/plugins/geotools/ivy.xml	(date 1664219610943)
@@ -13,6 +13,7 @@
         <dependency org="org.geotools" name="gt-opengis" rev="${gt.version}" conf="default->default"/>
         <dependency org="org.geotools" name="gt-referencing" rev="${gt.version}" conf="default->default"/>
         <dependency org="org.geotools" name="gt-shapefile" rev="${gt.version}" conf="default->default"/>
+        <dependency org="org.geotools" name="gt-geopkg" rev="${gt.version}" conf="default->default"/>
         <!-- Dependencies that were not needed in 22.0 (according to lib in svn) -->
         <exclude org="org.geotools" module="gt-imagemosaic"/>
         <exclude org="net.sourceforge.hatbox" module="hatbox"/>
Index: plugins/geotools/ivy_settings.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/geotools/ivy_settings.xml b/plugins/geotools/ivy_settings.xml
--- a/plugins/geotools/ivy_settings.xml	(revision 36023)
+++ b/plugins/geotools/ivy_settings.xml	(date 1664219600638)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ivysettings>
   <!-- When geotools is updated, run `ant merge-geotools-services` -->
-  <property name="gt.version" value="27.0"/>
+  <property name="gt.version" value="27.1"/>
   <settings defaultResolver="ordered-resolvers"/>
   <resolvers>
     <chain name="ordered-resolvers">
Index: plugins/geotools/README
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/geotools/README b/plugins/geotools/README
--- a/plugins/geotools/README	(revision 36023)
+++ b/plugins/geotools/README	(date 1665065360601)
@@ -3,5 +3,5 @@
 This plugin provides parts of the GeoTools library for JOSM plugins.
     * Licensed under GPL v3 (see LICENSE)
 
-The current embedded version of GeoTools is 26.2.
+The current embedded version of GeoTools is 27.1.
 
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/fr/FrenchShpHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/fr/FrenchShpHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/fr/FrenchShpHandler.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/fr/FrenchShpHandler.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/fr/FrenchShpHandler.java	(date 1665008535929)
@@ -5,20 +5,23 @@
 import org.geotools.referencing.operation.projection.LambertConformal2SP;
 import org.geotools.referencing.operation.projection.MapProjection.AbstractProvider;
 import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.operation.MathTransform;
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.DefaultShpHandler;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeotoolsHandler;
 
+/**
+ * A handler for french-specific shapefiles
+ */
 public class FrenchShpHandler extends DefaultShpHandler {
 
     @Override
-    public CoordinateReferenceSystem getCrsFor(String crsName) throws NoSuchAuthorityCodeException, FactoryException {
-        if (crsName.equalsIgnoreCase("RGM04")) {
+    public CoordinateReferenceSystem getCrsFor(String crsName) throws FactoryException {
+        if ("RGM04".equalsIgnoreCase(crsName)) {
             return CRS.decode("EPSG:4471");
-        } else if (crsName.equalsIgnoreCase("RGFG95_UTM_Zone_22N")) {
+        } else if ("RGFG95_UTM_Zone_22N".equalsIgnoreCase(crsName)) {
             return CRS.decode("EPSG:2972");
         } else {
             return super.getCrsFor(crsName);
@@ -28,20 +31,18 @@
     @Override
     public MathTransform findMathTransform(CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS, boolean lenient)
             throws FactoryException {
-        if (sourceCRS.getName().getCode().equalsIgnoreCase("Lambert I Nord")) {
-            if (sourceCRS instanceof ProjectedCRS) {
-                GeodeticDatum datum = ((ProjectedCRS) sourceCRS).getDatum();
-                if (datum.getPrimeMeridian().getGreenwichLongitude() > 0.0
-                        && ((ProjectedCRS) sourceCRS).getConversionFromBase().getMathTransform() instanceof LambertConformal2SP) {
-                    LambertConformal2SP lambert = (LambertConformal2SP) ((ProjectedCRS) sourceCRS).getConversionFromBase().getMathTransform();
-                    Double falseNorthing = get(lambert.getParameterValues(), AbstractProvider.FALSE_NORTHING);
-                    Double centralmeridian = get(lambert.getParameterValues(), AbstractProvider.CENTRAL_MERIDIAN);
-                    if (centralmeridian.equals(0.0)) {
-                        if (falseNorthing.equals(200000.0)) {
-                            return CRS.findMathTransform(CRS.decode("EPSG:27561"), targetCRS, lenient);
-                        } else if (falseNorthing.equals(1200000.0)) {
-                            return CRS.findMathTransform(CRS.decode("EPSG:27571"), targetCRS, lenient);
-                        }
+        if ("Lambert I Nord".equalsIgnoreCase(sourceCRS.getName().getCode()) && sourceCRS instanceof ProjectedCRS) {
+            GeodeticDatum datum = ((ProjectedCRS) sourceCRS).getDatum();
+            if (datum.getPrimeMeridian().getGreenwichLongitude() > 0.0
+                    && ((ProjectedCRS) sourceCRS).getConversionFromBase().getMathTransform() instanceof LambertConformal2SP) {
+                LambertConformal2SP lambert = (LambertConformal2SP) ((ProjectedCRS) sourceCRS).getConversionFromBase().getMathTransform();
+                Double falseNorthing = GeotoolsHandler.get(lambert.getParameterValues(), AbstractProvider.FALSE_NORTHING);
+                Double centralmeridian = GeotoolsHandler.get(lambert.getParameterValues(), AbstractProvider.CENTRAL_MERIDIAN);
+                if (centralmeridian.equals(0.0)) {
+                    if (falseNorthing.equals(200000.0)) {
+                        return CRS.findMathTransform(CRS.decode("EPSG:27561"), targetCRS, lenient);
+                    } else if (falseNorthing.equals(1200000.0)) {
+                        return CRS.findMathTransform(CRS.decode("EPSG:27571"), targetCRS, lenient);
                     }
                 }
             }
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/AbstractDataSetHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/AbstractDataSetHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/AbstractDataSetHandler.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/AbstractDataSetHandler.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/AbstractDataSetHandler.java	(date 1664218067996)
@@ -28,6 +28,8 @@
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GmlHandler;
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.MifHandler;
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.ShpHandler;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage.DefaultGeoPackageHandler;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage.GeoPackageHandler;
 import org.openstreetmap.josm.plugins.opendata.core.io.tabular.CsvHandler;
 import org.openstreetmap.josm.plugins.opendata.core.io.tabular.DefaultCsvHandler;
 import org.openstreetmap.josm.plugins.opendata.core.io.tabular.SpreadSheetHandler;
@@ -82,6 +84,7 @@
         setArchiveHandler(new DefaultArchiveHandler());
         setCsvHandler(new DefaultCsvHandler());
         setGmlHandler(new DefaultGmlHandler());
+        setGeoPackageHandler(new DefaultGeoPackageHandler());
     }
 
     private boolean acceptsFilename(String filename, String[] expected, String ... extensions) {
@@ -162,6 +165,10 @@
         return acceptsFilename(filename, expected, OdConstants.CSV_EXT, OdConstants.XLS_EXT);
     }
 
+    protected final boolean acceptsGpkgFilename(String filename, String... expected) {
+        return acceptsFilename(filename, expected, OdConstants.GEOPACKAGE_EXT);
+    }
+
     // -------------------- License --------------------
 
     private License license;
@@ -479,6 +486,18 @@
         return gmlHandler;
     }
 
+    // ------------ GeoPackage handling ------------
+
+    private GeoPackageHandler geoPackageHandler;
+
+    public final void setGeoPackageHandler(GeoPackageHandler handler) {
+        this.geoPackageHandler = handler;
+    }
+
+    public final GeoPackageHandler getGeoPackageHandler() {
+        return this.geoPackageHandler;
+    }
+
     // ------------ Archive handling ------------
 
     private ArchiveHandler archiveHandler;
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/DefaultShpHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/DefaultShpHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/DefaultShpHandler.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/DefaultShpHandler.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/DefaultShpHandler.java	(date 1665007984633)
@@ -2,131 +2,21 @@
 package org.openstreetmap.josm.plugins.opendata.core.io.geographic;
 
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
-import org.geotools.referencing.CRS;
-import org.geotools.referencing.crs.AbstractDerivedCRS;
-import org.geotools.referencing.datum.DefaultEllipsoid;
-import org.geotools.referencing.operation.projection.LambertConformal;
-import org.geotools.referencing.operation.projection.LambertConformal1SP;
-import org.geotools.referencing.operation.projection.LambertConformal2SP;
-import org.geotools.referencing.operation.projection.MapProjection.AbstractProvider;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.GeodeticDatum;
-import org.opengis.referencing.operation.MathTransform;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.projection.AbstractProjection;
-import org.openstreetmap.josm.data.projection.Ellipsoid;
-import org.openstreetmap.josm.data.projection.Projection;
-import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
-import org.openstreetmap.josm.data.projection.proj.LambertConformalConic.Parameters;
-import org.openstreetmap.josm.data.projection.proj.LambertConformalConic.Parameters1SP;
-import org.openstreetmap.josm.data.projection.proj.LambertConformalConic.Parameters2SP;
-import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice;
-import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
-import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Pair;
-
-public class DefaultShpHandler extends DefaultGeographicHandler implements ShpHandler {
 
-    private static final List<Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid>>
-    ellipsoids = new ArrayList<>();
-    static {
-        ellipsoids.add(new Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid>(DefaultEllipsoid.GRS80, Ellipsoid.GRS80));
-        ellipsoids.add(new Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid>(DefaultEllipsoid.WGS84, Ellipsoid.WGS84));
-    }
+/**
+ * The default shapefile handler
+ */
+public class DefaultShpHandler extends DefaultGeographicHandler implements ShpHandler, GeotoolsHandler {
 
-    protected static final Double get(ParameterValueGroup values, ParameterDescriptor<?> desc) {
-        return (Double) values.parameter(desc.getName().getCode()).getValue();
-    }
-
-    private static boolean equals(Double a, Double b) {
-        boolean res = Math.abs(a - b) <= Config.getPref().getDouble(
-                OdConstants.PREF_CRS_COMPARISON_TOLERANCE, OdConstants.DEFAULT_CRS_COMPARISON_TOLERANCE);
-        if (Config.getPref().getBoolean(OdConstants.PREF_CRS_COMPARISON_DEBUG, false)) {
-            Logging.debug("Comparing "+a+" and "+b+" -> "+res);
-        }
-        return res;
-    }
-
-    private Charset dbfCharset = null;
-
-    @Override
-    public MathTransform findMathTransform(CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS, boolean lenient)
-            throws FactoryException {
-        if (getCrsFor(sourceCRS.getName().getCode()) != null) {
-            return CRS.findMathTransform(getCrsFor(sourceCRS.getName().getCode()), targetCRS, lenient);
-        } else if (sourceCRS instanceof AbstractDerivedCRS && sourceCRS.getName().getCode().equalsIgnoreCase("Lambert_Conformal_Conic")) {
-            List<MathTransform> result = new ArrayList<>();
-            AbstractDerivedCRS crs = (AbstractDerivedCRS) sourceCRS;
-            MathTransform transform = crs.getConversionFromBase().getMathTransform();
-            if (transform instanceof LambertConformal && crs.getDatum() instanceof GeodeticDatum) {
-                LambertConformal lambert = (LambertConformal) transform;
-                GeodeticDatum geo = (GeodeticDatum) crs.getDatum();
-                for (ProjectionChoice choice : ProjectionPreference.getProjectionChoices()) {
-                    Projection p = choice.getProjection();
-                    if (p instanceof AbstractProjection) {
-                        AbstractProjection ap = (AbstractProjection) p;
-                        if (ap.getProj() instanceof LambertConformalConic) {
-                            for (Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid> pair : ellipsoids) {
-                                if (pair.a.equals(geo.getEllipsoid()) && pair.b.equals(ap.getEllipsoid())) {
-                                    boolean ok = true;
-                                    ParameterValueGroup values = lambert.getParameterValues();
-                                    Parameters params = ((LambertConformalConic) ap.getProj()).getParameters();
-
-                                    ok = ok ? equals(get(values, AbstractProvider.LATITUDE_OF_ORIGIN), params.latitudeOrigin) : ok;
-                                    ok = ok ? equals(get(values, AbstractProvider.CENTRAL_MERIDIAN), ap.getCentralMeridian()) : ok;
-                                    ok = ok ? equals(get(values, AbstractProvider.SCALE_FACTOR), ap.getScaleFactor()) : ok;
-                                    ok = ok ? equals(get(values, AbstractProvider.FALSE_EASTING), ap.getFalseEasting()) : ok;
-                                    ok = ok ? equals(get(values, AbstractProvider.FALSE_NORTHING), ap.getFalseNorthing()) : ok;
-
-                                    if (lambert instanceof LambertConformal2SP && params instanceof Parameters2SP) {
-                                        Parameters2SP param = (Parameters2SP) params;
-                                        ok = ok ? equals(Math.min(get(values, AbstractProvider.STANDARD_PARALLEL_1),
-                                                get(values, AbstractProvider.STANDARD_PARALLEL_2)),
-                                                Math.min(param.standardParallel1, param.standardParallel2)) : ok;
-                                        ok = ok ? equals(Math.max(get(values, AbstractProvider.STANDARD_PARALLEL_1),
-                                                get(values, AbstractProvider.STANDARD_PARALLEL_2)),
-                                                Math.max(param.standardParallel1, param.standardParallel2)) : ok;
-
-                                    } else if (!(lambert instanceof LambertConformal1SP && params instanceof Parameters1SP)) {
-                                        ok = false;
-                                    }
-
-                                    if (ok) {
-                                        try {
-                                            result.add(CRS.findMathTransform(CRS.decode(p.toCode()), targetCRS, lenient));
-                                        } catch (FactoryException e) {
-                                            Logging.error(e.getMessage());
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            if (!result.isEmpty()) {
-                if (result.size() > 1) {
-                    Logging.warn("Found multiple projections !"); // TODO: something
-                }
-                return result.get(0);
-            }
-        }
-        return null;
-    }
+    private Charset dbfCharset;
 
     @Override
     public void notifyFeatureParsed(Object feature, DataSet result, Set<OsmPrimitive> featurePrimitives) {
-        // To be overriden by modules handlers
+        // To be overridden by modules handlers
     }
 
     @Override
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeographicReader.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeographicReader.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeographicReader.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeographicReader.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeographicReader.java	(date 1665065251662)
@@ -71,7 +71,7 @@
 import org.openstreetmap.josm.tools.Utils;
 
 /**
- * Superclass of geographic format readers (currently GML and SHP).
+ * Superclass of geographic format readers (currently GML, GPKG, and SHP).
  */
 public abstract class GeographicReader extends AbstractReader {
 
@@ -107,6 +107,10 @@
         return null;
     }
 
+    public GeographicHandler getHandler() {
+        return this.handler;
+    }
+
     protected Node getNode(Point p, LatLon key) {
         Node n = nodes.get(key);
         if (n == null && handler != null && handler.checkNodeProximity()) {
@@ -343,6 +347,14 @@
         return esriWkid.get(wkid);
     }
 
+    /**
+     * Find the math transform for the CRS used by this reader
+     * @param parent The parent component, used for showing dialogs
+     * @param findSimiliarCrs {@code true} if we don't need to find the exact CRS
+     * @throws FactoryException See {@link CRS#findMathTransform}, {@link org.opengis.referencing.AuthorityFactory#getAuthorityCodes}
+     * @throws UserCancelException If the user cancelled in one of the message dialogs
+     * @throws GeoMathTransformException If no transform could be found
+     */
     protected void findMathTransform(Component parent, boolean findSimiliarCrs)
             throws FactoryException, UserCancelException, GeoMathTransformException {
         try {
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java
new file mode 100644
--- /dev/null	(date 1664226378379)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java	(date 1664226378379)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage;
+
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.DefaultGeographicHandler;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeotoolsHandler;
+
+/**
+ * The default handler for GeoPackages
+ * @author Taylor Smock
+ */
+public class DefaultGeoPackageHandler extends DefaultGeographicHandler implements GeoPackageHandler, GeotoolsHandler {
+    private boolean preferMultipolygonToSimpleWay;
+    private boolean checkNodeProximity;
+    private boolean useNodeMap;
+    @Override
+    public void setPreferMultipolygonToSimpleWay(boolean prefer) {
+        this.preferMultipolygonToSimpleWay = prefer;
+    }
+
+    @Override
+    public boolean preferMultipolygonToSimpleWay() {
+        return this.preferMultipolygonToSimpleWay;
+    }
+
+    @Override
+    public void setCheckNodeProximity(boolean check) {
+        this.checkNodeProximity = check;
+    }
+
+    @Override
+    public boolean checkNodeProximity() {
+        return this.checkNodeProximity;
+    }
+
+    @Override
+    public void setUseNodeMap(boolean use) {
+        this.useNodeMap = use;
+    }
+
+    @Override
+    public boolean useNodeMap() {
+        return this.useNodeMap;
+    }
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java
new file mode 100644
--- /dev/null	(date 1664218068011)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java	(date 1664218068011)
@@ -0,0 +1,7 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage;
+
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeographicHandler;
+
+public interface GeoPackageHandler extends GeographicHandler {
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java
new file mode 100644
--- /dev/null	(date 1664225354764)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java	(date 1664225354764)
@@ -0,0 +1,31 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
+import org.openstreetmap.josm.plugins.opendata.core.io.AbstractImporter;
+
+public class GeoPackageImporter extends AbstractImporter {
+    public static final ExtensionFileFilter GEOPACKAGE_FILE_FILTER = new ExtensionFileFilter(
+            OdConstants.GEOPACKAGE_EXT, OdConstants.GEOPACKAGE_EXT, tr("Shapefiles") + " (*."+ OdConstants.GEOPACKAGE_EXT+")");
+    public GeoPackageImporter() {
+        super(GEOPACKAGE_FILE_FILTER);
+    }
+
+    @Override
+    protected DataSet parseDataSet(InputStream in, ProgressMonitor progressMonitor) throws IllegalDataException {
+        try {
+            return GeoPackageReader.parseDataSet(in, file, handler, progressMonitor);
+        } catch (IOException e) {
+            throw new IllegalDataException(e);
+        }
+    }
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java
new file mode 100644
--- /dev/null	(date 1665009539191)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java	(date 1665009539191)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.geotools.data.DataStore;
+import org.geotools.data.DataStoreFinder;
+import org.geotools.geopkg.GeoPkgDataStoreFactory;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.opendata.core.datasets.AbstractDataSetHandler;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeoCrsException;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeoMathTransformException;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeographicReader;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeotoolsConverter;
+
+/**
+ * Read a geopackage file
+ * @author Taylor Smock
+ */
+public final class GeoPackageReader extends GeographicReader {
+    /**
+     * Parse the dataset
+     * @param in The {@link InputStream} to read (we actually close it and use the file instead)
+     * @param file The originating file
+     * @param handler The handler to use, may be {@code null}
+     * @param instance The {@link ProgressMonitor} instance to update
+     * @return The parsed dataset
+     * @throws IOException If something prevented us from parsing the dataset
+     */
+    public static DataSet parseDataSet(InputStream in, File file,
+                                       AbstractDataSetHandler handler, ProgressMonitor instance) throws IOException {
+        if (in != null) {
+            in.close();
+        }
+        return new GeoPackageReader(handler != null ? handler.getGeoPackageHandler() : null).parse(file, instance);
+    }
+
+    private GeoPackageReader(GeoPackageHandler handler) {
+        super(handler, new GeoPackageHandler[0]);
+    }
+
+    private DataSet parse(File file, ProgressMonitor instance) throws IOException {
+        if (file != null) {
+            Map<String, Serializable> params = new HashMap<>();
+            params.put(GeoPkgDataStoreFactory.DATABASE.key, file);
+            params.put(GeoPkgDataStoreFactory.READ_ONLY.key, true);
+            params.put(GeoPkgDataStoreFactory.DBTYPE.key, (String) GeoPkgDataStoreFactory.DBTYPE.sample);
+            DataStore dataStore = DataStoreFinder.getDataStore(params);
+            try {
+                new GeotoolsConverter(this, dataStore).convert(instance);
+            } catch (FactoryException | GeoCrsException | GeoMathTransformException | TransformException e) {
+                throw new IOException(e);
+            }
+        }
+        return ds;
+    }
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java
new file mode 100644
--- /dev/null	(date 1665065892963)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java	(date 1665065892963)
@@ -0,0 +1,248 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic;
+
+import static org.openstreetmap.josm.plugins.opendata.core.io.geographic.GeographicReader.wgs84;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.geotools.data.DataStore;
+import org.geotools.data.FeatureSource;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.feature.FeatureIterator;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.opengis.feature.Feature;
+import org.opengis.feature.GeometryAttribute;
+import org.opengis.feature.Property;
+import org.opengis.feature.type.GeometryDescriptor;
+import org.opengis.feature.type.Name;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.UserCancelException;
+
+/**
+ * Convert a {@link DataStore} to a {@link DataSet}
+ */
+public class GeotoolsConverter {
+    private final DataStore dataStore;
+    private final GeographicReader reader;
+    private final Set<OsmPrimitive> featurePrimitives = new HashSet<>();
+
+    /**
+     * Create a new converter
+     * @param reader The reader which should have a dataset
+     * @param original The original data store
+     */
+    public GeotoolsConverter(GeographicReader reader, DataStore original) {
+        this.dataStore = original;
+        this.reader = reader;
+    }
+
+    /**
+     * Run the actual conversion process
+     * @param progressMonitor The monitor to show progress on
+     * @throws IOException If something could not be read
+     * @throws FactoryException See {@link GeographicReader#findMathTransform(Component, boolean)}
+     * @throws GeoMathTransformException See {@link GeographicReader#findMathTransform(Component, boolean)}
+     * @throws TransformException See {@link GeographicReader#createOrGetNode(Point)}
+     * @throws GeoCrsException If the CRS cannot be detected
+     */
+    public void convert(ProgressMonitor progressMonitor)
+            throws IOException, FactoryException, GeoMathTransformException, TransformException, GeoCrsException {
+        String[] typeNames = dataStore.getTypeNames();
+        String typeName = typeNames[0];
+
+        FeatureSource<?, ?> featureSource = dataStore.getFeatureSource(typeName);
+        FeatureCollection<?, ?> collection = featureSource.getFeatures();
+
+        if (progressMonitor != null) {
+            progressMonitor.beginTask(tr("Loading shapefile ({0} features)", collection.size()), collection.size());
+        }
+
+        int n = 0;
+
+        Component parent = progressMonitor != null ? progressMonitor.getWindowParent() : MainApplication.getMainFrame();
+
+        int size = collection.size();
+        this.reader.getDataSet().beginUpdate();
+        try (FeatureIterator<?> iterator = collection.features()) {
+            while (iterator.hasNext()) {
+                n++;
+                try {
+                    Feature feature = iterator.next();
+                    parseFeature(feature, parent);
+                    if (reader.getHandler() instanceof ShpHandler) {
+                        ((ShpHandler) reader.getHandler()).notifyFeatureParsed(feature, reader.getDataSet(), featurePrimitives);
+                    }
+                } catch (UserCancelException e) {
+                    Logging.error(e);
+                    return;
+                }
+                if (progressMonitor != null) {
+                    progressMonitor.worked(1);
+                    progressMonitor.setCustomText(n+"/"+size);
+                    if (progressMonitor.isCanceled()) {
+                        return;
+                    }
+                }
+            }
+        } finally {
+            reader.nodes.clear();
+            this.reader.getDataSet().endUpdate();
+            if (progressMonitor != null) {
+                progressMonitor.setCustomText(null);
+            }
+        }
+    }
+
+    private void parseFeature(Feature feature, final Component parent) throws UserCancelException, GeoMathTransformException,
+            FactoryException, GeoCrsException, MismatchedDimensionException, TransformException {
+        featurePrimitives.clear();
+        GeometryAttribute geometry = feature.getDefaultGeometryProperty();
+        if (geometry != null) {
+
+            GeometryDescriptor desc = geometry.getDescriptor();
+            if (reader.crs == null) {
+                if (desc != null && desc.getCoordinateReferenceSystem() != null) {
+                    reader.crs = desc.getCoordinateReferenceSystem();
+                } else if (!GraphicsEnvironment.isHeadless()) {
+                    GuiHelper.runInEDTAndWait(() -> {
+                        if (0 == JOptionPane.showConfirmDialog(
+                                parent,
+                                tr("Unable to detect Coordinate Reference System.\nWould you like to fallback to ESPG:4326 (WGS 84) ?"),
+                                tr("Warning: CRS not found"),
+                                JOptionPane.YES_NO_CANCEL_OPTION
+                        )) {
+                            reader.crs = wgs84;
+                        }
+                    });
+                } else {
+                    // Always use WGS84 in headless mode (used for unit tests only)
+                    reader.crs = wgs84;
+                }
+                if (reader.crs != null) {
+                    reader.findMathTransform(parent, true);
+                } else {
+                    throw new GeoCrsException(tr("Unable to detect CRS !"));
+                }
+            }
+
+            Object geomObject = geometry.getValue();
+            if (geomObject instanceof Point) {  // TODO: Support LineString and Polygon.
+                // Sure you could have a Set of 1 object and join these 2 branches of
+                // code, but I feel there would be a performance hit.
+                OsmPrimitive primitive = reader.createOrGetEmptyNode((Point) geomObject);
+                readNonGeometricAttributes(feature, primitive);
+            } else if (geomObject instanceof GeometryCollection) { // Deals with both MultiLineString and MultiPolygon
+                Set<OsmPrimitive> primitives = processGeometryCollection((GeometryCollection) geomObject);
+                for (OsmPrimitive prim : primitives) {
+                    readNonGeometricAttributes(feature, prim);
+                }
+            } else {
+                // Debug unknown geometry
+                Logging.debug("\ttype: "+geometry.getType());
+                Logging.debug("\tbounds: "+geometry.getBounds());
+                Logging.debug("\tdescriptor: "+desc);
+                Logging.debug("\tname: "+geometry.getName());
+                Logging.debug("\tvalue: "+geomObject);
+                Logging.debug("\tid: "+geometry.getIdentifier());
+                Logging.debug("-------------------------------------------------------------");
+            }
+        }
+    }
+
+    protected Set<OsmPrimitive> processGeometryCollection(GeometryCollection gc) throws TransformException {
+        // A feture may be a collection.  This set holds the items of the collection.
+        Set<OsmPrimitive> primitives = new HashSet<>();
+        int nGeometries = gc.getNumGeometries();
+        if (nGeometries < 1) {
+            Logging.error("empty geometry collection found");
+        } else {
+            // Create the primitive "op" and add it to the set of primitives.
+            for (int i = 0; i < nGeometries; i++) {
+                OsmPrimitive op = null;
+                Geometry g = gc.getGeometryN(i);
+                if (g instanceof Polygon) {
+                    // TODO: Split this section between Polygon and MultiPolygon.
+                    Relation r = (Relation) op;
+                    Polygon p = (Polygon) g;
+                    // Do not create relation if there's only one polygon without interior ring
+                    // except if handler prefers it
+                    if (r == null && (nGeometries > 1 || p.getNumInteriorRing() > 0 ||
+                            (reader.getHandler() != null && reader.getHandler().preferMultipolygonToSimpleWay()))) {
+                        r = reader.createMultipolygon();
+                    }
+                    Way w = reader.createOrGetWay(p.getExteriorRing());
+                    if (r != null) {
+                        reader.addWayToMp(r, "outer", w);
+                        for (int j = 0; j < p.getNumInteriorRing(); j++) {
+                            reader.addWayToMp(r, "inner", reader.createOrGetWay(p.getInteriorRingN(j)));
+                        }
+                    }
+                    op = r != null ? r : w;
+                } else if (g instanceof LineString) {
+                    op = reader.createOrGetWay((LineString) g);
+                } else if (g instanceof Point) {
+                    op = reader.createOrGetNode((Point) g);
+                } else {
+                    Logging.error("unsupported geometry : "+g);
+                }
+                if (op != null) {
+                    primitives.add(op);
+                }
+            }
+        }
+        return primitives;
+    }
+
+    private static void readNonGeometricAttributes(Feature feature, OsmPrimitive primitive) {
+        try {
+            Collection<Property> properties = feature.getProperties();
+            Map<String, String> tagMap = new LinkedHashMap<>(properties.size());
+            for (Property prop : properties) {
+                if (!(prop instanceof GeometryAttribute)) {
+                    Name name = prop.getName();
+                    Object value = prop.getValue();
+                    if (name != null && value != null) {
+                        String sName = name.toString();
+                        String sValue = value.toString();
+                        if (value instanceof Date) {
+                            sValue = new SimpleDateFormat("yyyy-MM-dd").format(value);
+                        }
+                        if (!sName.isEmpty() && !sValue.isEmpty()) {
+                            tagMap.put(sName, sValue);
+                            //primitive.put(sName, sValue);
+                        }
+                    }
+                }
+            }
+            primitive.putAll(tagMap);
+        } catch (Exception e) {
+            Logging.error(e);
+        }
+    }
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java
new file mode 100644
--- /dev/null	(date 1664230482205)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java	(date 1664230482205)
@@ -0,0 +1,137 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.opendata.core.io.geographic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.AbstractDerivedCRS;
+import org.geotools.referencing.datum.DefaultEllipsoid;
+import org.geotools.referencing.operation.projection.LambertConformal;
+import org.geotools.referencing.operation.projection.LambertConformal1SP;
+import org.geotools.referencing.operation.projection.LambertConformal2SP;
+import org.geotools.referencing.operation.projection.MapProjection.AbstractProvider;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.operation.MathTransform;
+import org.openstreetmap.josm.data.projection.AbstractProjection;
+import org.openstreetmap.josm.data.projection.Ellipsoid;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
+import org.openstreetmap.josm.data.projection.proj.LambertConformalConic.Parameters1SP;
+import org.openstreetmap.josm.data.projection.proj.LambertConformalConic.Parameters2SP;
+import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice;
+import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
+import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * A common handler for geotools
+ */
+public interface GeotoolsHandler extends GeographicHandler {
+    /**
+     * A mapping of GeoTools ellipsoid to JOSM ellipsoids. Don't use outside the {@link GeotoolsHandler} class.
+     */
+    List<Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid>>
+            ellipsoids = Collections.unmodifiableList(Arrays.asList(
+            new Pair<>(DefaultEllipsoid.GRS80, Ellipsoid.GRS80),
+            new Pair<>(DefaultEllipsoid.WGS84, Ellipsoid.WGS84)
+    ));
+
+    @Override
+    default MathTransform findMathTransform(CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS, boolean lenient)
+            throws FactoryException {
+        if (getCrsFor(sourceCRS.getName().getCode()) != null) {
+            return CRS.findMathTransform(getCrsFor(sourceCRS.getName().getCode()), targetCRS, lenient);
+        } else if (sourceCRS instanceof AbstractDerivedCRS && sourceCRS.getName().getCode().equalsIgnoreCase("Lambert_Conformal_Conic")) {
+            List<MathTransform> result = new ArrayList<>();
+            AbstractDerivedCRS crs = (AbstractDerivedCRS) sourceCRS;
+            MathTransform transform = crs.getConversionFromBase().getMathTransform();
+            if (transform instanceof LambertConformal && crs.getDatum() instanceof GeodeticDatum) {
+                LambertConformal lambert = (LambertConformal) transform;
+                GeodeticDatum geo = (GeodeticDatum) crs.getDatum();
+                for (ProjectionChoice choice : ProjectionPreference.getProjectionChoices()) {
+                    Projection p = choice.getProjection();
+                    if (p instanceof AbstractProjection) {
+                        AbstractProjection ap = (AbstractProjection) p;
+                        if (ap.getProj() instanceof LambertConformalConic) {
+                            for (Pair<org.opengis.referencing.datum.Ellipsoid, Ellipsoid> pair : ellipsoids) {
+                                if (pair.a.equals(geo.getEllipsoid()) && pair.b.equals(ap.getEllipsoid())) {
+                                    boolean ok = true;
+                                    ParameterValueGroup values = lambert.getParameterValues();
+                                    LambertConformalConic.Parameters params = ((LambertConformalConic) ap.getProj()).getParameters();
+
+                                    ok = ok ? equals(get(values, AbstractProvider.LATITUDE_OF_ORIGIN), params.latitudeOrigin) : ok;
+                                    ok = ok ? equals(get(values, AbstractProvider.CENTRAL_MERIDIAN), ap.getCentralMeridian()) : ok;
+                                    ok = ok ? equals(get(values, AbstractProvider.SCALE_FACTOR), ap.getScaleFactor()) : ok;
+                                    ok = ok ? equals(get(values, AbstractProvider.FALSE_EASTING), ap.getFalseEasting()) : ok;
+                                    ok = ok ? equals(get(values, AbstractProvider.FALSE_NORTHING), ap.getFalseNorthing()) : ok;
+
+                                    if (lambert instanceof LambertConformal2SP && params instanceof Parameters2SP) {
+                                        Parameters2SP param = (Parameters2SP) params;
+                                        ok = ok ? equals(Math.min(get(values, AbstractProvider.STANDARD_PARALLEL_1),
+                                                        get(values, AbstractProvider.STANDARD_PARALLEL_2)),
+                                                Math.min(param.standardParallel1, param.standardParallel2)) : ok;
+                                        ok = ok ? equals(Math.max(get(values, AbstractProvider.STANDARD_PARALLEL_1),
+                                                        get(values, AbstractProvider.STANDARD_PARALLEL_2)),
+                                                Math.max(param.standardParallel1, param.standardParallel2)) : ok;
+
+                                    } else if (!(lambert instanceof LambertConformal1SP && params instanceof Parameters1SP)) {
+                                        ok = false;
+                                    }
+
+                                    if (ok) {
+                                        try {
+                                            result.add(CRS.findMathTransform(CRS.decode(p.toCode()), targetCRS, lenient));
+                                        } catch (FactoryException e) {
+                                            Logging.error(e.getMessage());
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (!result.isEmpty()) {
+                if (result.size() > 1) {
+                    Logging.warn("Found multiple projections !"); // TODO: something
+                }
+                return result.get(0);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get a value from a group of values
+     * @param values The values to look at
+     * @param desc The description of the value to get
+     * @return The value
+     */
+    static Double get(ParameterValueGroup values, ParameterDescriptor<?> desc) {
+        return (Double) values.parameter(desc.getName().getCode()).getValue();
+    }
+
+    /**
+     * Check if two doubles are equal to within the {@link OdConstants#PREF_CRS_COMPARISON_TOLERANCE}
+     * @param a The first double
+     * @param b The second double
+     * @return {@code true} if the difference is less than the tolerance
+     */
+    static boolean equals(Double a, Double b) {
+        boolean res = Math.abs(a - b) <= Config.getPref().getDouble(
+                OdConstants.PREF_CRS_COMPARISON_TOLERANCE, OdConstants.DEFAULT_CRS_COMPARISON_TOLERANCE);
+        if (Config.getPref().getBoolean(OdConstants.PREF_CRS_COMPARISON_DEBUG, false)) {
+            Logging.debug("Comparing "+a+" and "+b+" -> "+res);
+        }
+        return res;
+    }
+}
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/ShpReader.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/ShpReader.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/ShpReader.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/ShpReader.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/ShpReader.java	(date 1665065566288)
@@ -3,8 +3,6 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.Component;
-import java.awt.GraphicsEnvironment;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -16,46 +14,24 @@
 import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.text.SimpleDateFormat;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import javax.swing.JOptionPane;
-
 import org.geotools.data.DataStore;
-import org.geotools.data.FeatureSource;
 import org.geotools.data.shapefile.ShapefileDataStoreFactory;
-import org.geotools.feature.FeatureCollection;
-import org.geotools.feature.FeatureIterator;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryCollection;
-import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.Feature;
-import org.opengis.feature.GeometryAttribute;
-import org.opengis.feature.Property;
-import org.opengis.feature.type.GeometryDescriptor;
-import org.opengis.feature.type.Name;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.plugins.opendata.core.datasets.AbstractDataSetHandler;
 import org.openstreetmap.josm.plugins.opendata.core.datasets.NationalHandlers;
 import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.UserCancelException;
 
 /**
  * Reader of SHP (Shapefile) files.
@@ -84,107 +60,6 @@
         }
     }
 
-    private void parseFeature(Feature feature, final Component parent) throws UserCancelException, GeoMathTransformException,
-    FactoryException, GeoCrsException, MismatchedDimensionException, TransformException {
-        featurePrimitives.clear();
-        GeometryAttribute geometry = feature.getDefaultGeometryProperty();
-        if (geometry != null) {
-
-            GeometryDescriptor desc = geometry.getDescriptor();
-
-            if (crs == null) {
-                if (desc != null && desc.getCoordinateReferenceSystem() != null) {
-                    crs = desc.getCoordinateReferenceSystem();
-                } else if (!GraphicsEnvironment.isHeadless()) {
-                    GuiHelper.runInEDTAndWait(() -> {
-                        if (0 == JOptionPane.showConfirmDialog(
-                                parent,
-                                tr("Unable to detect Coordinate Reference System.\nWould you like to fallback to ESPG:4326 (WGS 84) ?"),
-                                tr("Warning: CRS not found"),
-                                JOptionPane.YES_NO_CANCEL_OPTION
-                                )) {
-                            crs = wgs84;
-                        }
-                    });
-                } else {
-                    // Always use WGS84 in headless mode (used for unit tests only)
-                    crs = wgs84;
-                }
-                if (crs != null) {
-                    findMathTransform(parent, true);
-                } else {
-                    throw new GeoCrsException(tr("Unable to detect CRS !"));
-                }
-            }
-
-            Object geomObject = geometry.getValue();
-            if (geomObject instanceof Point) {  // TODO: Support LineString and Polygon.
-                // Sure you could have a Set of 1 object and join these 2 branches of
-                // code, but I feel there would be a performance hit.
-                OsmPrimitive primitive = createOrGetEmptyNode((Point) geomObject);
-                readNonGeometricAttributes(feature, primitive);
-            } else if (geomObject instanceof GeometryCollection) { // Deals with both MultiLineString and MultiPolygon
-                Set<OsmPrimitive> primitives = processGeometryCollection((GeometryCollection) geomObject);
-                for (OsmPrimitive prim : primitives) {
-                    readNonGeometricAttributes(feature, prim);
-                }
-            } else {
-                // Debug unknown geometry
-                Logging.debug("\ttype: "+geometry.getType());
-                Logging.debug("\tbounds: "+geometry.getBounds());
-                Logging.debug("\tdescriptor: "+desc);
-                Logging.debug("\tname: "+geometry.getName());
-                Logging.debug("\tvalue: "+geomObject);
-                Logging.debug("\tid: "+geometry.getIdentifier());
-                Logging.debug("-------------------------------------------------------------");
-            }
-        }
-    }
-
-    protected Set<OsmPrimitive> processGeometryCollection(GeometryCollection gc) throws TransformException {
-        // A feture may be a collection.  This set holds the items of the collection.
-        Set<OsmPrimitive> primitives = new HashSet<>();
-        int nGeometries = gc.getNumGeometries();
-        if (nGeometries < 1) {
-            Logging.error("empty geometry collection found");
-        } else {
-            // Create the primitive "op" and add it to the set of primitives.
-            for (int i = 0; i < nGeometries; i++) {
-                OsmPrimitive op = null;
-                Geometry g = gc.getGeometryN(i);
-                if (g instanceof Polygon) {
-                    // TODO: Split this section between Polygon and MultiPolygon.
-                    Relation r = (Relation) op;
-                    Polygon p = (Polygon) g;
-                    // Do not create relation if there's only one polygon without interior ring
-                    // except if handler prefers it
-                    if (r == null && (nGeometries > 1 || p.getNumInteriorRing() > 0 ||
-                            (handler != null && handler.preferMultipolygonToSimpleWay()))) {
-                        r = createMultipolygon();
-                    }
-                    Way w = createOrGetWay(p.getExteriorRing());
-                    if (r != null) {
-                        addWayToMp(r, "outer", w);
-                        for (int j = 0; j < p.getNumInteriorRing(); j++) {
-                            addWayToMp(r, "inner", createOrGetWay(p.getInteriorRingN(j)));
-                        }
-                    }
-                    op = r != null ? r : w;
-                } else if (g instanceof LineString) {
-                    op = createOrGetWay((LineString) g);
-                } else if (g instanceof Point) {
-                    op = createOrGetNode((Point) g);
-                } else {
-                    Logging.error("unsupported geometry : "+g);
-                }
-                if (op != null) {
-                    primitives.add(op);
-                }
-            }
-        }
-        return primitives;
-    }
-
     public DataSet parse(File file, ProgressMonitor instance) throws IOException {
         crs = null;
         transform = null;
@@ -231,45 +106,7 @@
                 if (dataStore == null) {
                     throw new IOException(tr("Unable to find a data store for file {0}", file.getName()));
                 }
-
-                String[] typeNames = dataStore.getTypeNames();
-                String typeName = typeNames[0];
-
-                FeatureSource<?, ?> featureSource = dataStore.getFeatureSource(typeName);
-                FeatureCollection<?, ?> collection = featureSource.getFeatures();
-
-                if (instance != null) {
-                    instance.beginTask(tr("Loading shapefile ({0} features)", collection.size()), collection.size());
-                }
-
-                int n = 0;
-
-                Component parent = instance != null ? instance.getWindowParent() : MainApplication.getMainFrame();
-
-                try (FeatureIterator<?> iterator = collection.features()) {
-                    while (iterator.hasNext()) {
-                        n++;
-                        try {
-                            Feature feature = iterator.next();
-                            parseFeature(feature, parent);
-                            if (handler != null) {
-                                handler.notifyFeatureParsed(feature, ds, featurePrimitives);
-                            }
-                        } catch (UserCancelException e) {
-                            e.printStackTrace();
-                            return ds;
-                        }
-                        if (instance != null) {
-                            instance.worked(1);
-                            instance.setCustomText(n+"/"+collection.size());
-                        }
-                    }
-                } finally {
-                    nodes.clear();
-                    if (instance != null) {
-                        instance.setCustomText(null);
-                    }
-                }
+                new GeotoolsConverter(this, dataStore).convert(instance);
             }
         } catch (IOException e) {
             Logging.error(e);
@@ -281,29 +118,6 @@
         return ds;
     }
 
-    private static void readNonGeometricAttributes(Feature feature, OsmPrimitive primitive) {
-        try {
-            for (Property prop : feature.getProperties()) {
-                if (!(prop instanceof GeometryAttribute)) {
-                    Name name = prop.getName();
-                    Object value = prop.getValue();
-                    if (name != null && value != null) {
-                        String sName = name.toString();
-                        String sValue = value.toString();
-                        if (value instanceof Date) {
-                            sValue = new SimpleDateFormat("yyyy-MM-dd").format(value);
-                        }
-                        if (!sName.isEmpty() && !sValue.isEmpty()) {
-                            primitive.put(sName, sValue);
-                        }
-                    }
-                }
-            }
-        } catch (Exception e) {
-            Logging.error(e);
-        }
-    }
-
     @Override
     protected Node createOrGetNode(Point p) throws MismatchedDimensionException, TransformException {
         Node n = super.createOrGetNode(p);
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/OdConstants.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/OdConstants.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/OdConstants.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/OdConstants.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/OdConstants.java	(date 1664217481812)
@@ -91,6 +91,7 @@
     public static final String XML_EXT = "xml";
     public static final String JSON_EXT = "json";
     public static final String GEOJSON_EXT = "geojson";
+    public static final String GEOPACKAGE_EXT = "gpkg";
 
     /**
      * Protocols
Index: plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/OdPlugin.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/OdPlugin.java b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/OdPlugin.java
--- a/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/OdPlugin.java	(revision 36023)
+++ b/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/OdPlugin.java	(date 1665008847830)
@@ -40,6 +40,7 @@
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.KmlKmzImporter;
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.MifTabImporter;
 import org.openstreetmap.josm.plugins.opendata.core.io.geographic.ShpImporter;
+import org.openstreetmap.josm.plugins.opendata.core.io.geographic.geopackage.GeoPackageImporter;
 import org.openstreetmap.josm.plugins.opendata.core.io.session.OpenDataSessionExporter;
 import org.openstreetmap.josm.plugins.opendata.core.io.session.OpenDataSessionImporter;
 import org.openstreetmap.josm.plugins.opendata.core.io.tabular.CsvImporter;
@@ -66,13 +67,21 @@
 
     private OdDialog dialog;
 
-    public final List<AbstractImporter> importers = Arrays.asList(new AbstractImporter[] {
-            new CsvImporter(), new OdsImporter(), new XlsImporter(), // Tabular file formats
-            new KmlKmzImporter(), new ShpImporter(), new MifTabImporter(), new GmlImporter(), // Geographic file formats
-            new ZipImporter(), // Zip archive containing any of the others
-            new SevenZipImporter(), // 7Zip archive containing any of the others
-            xmlImporter // Generic importer for XML files (currently used for Neptune files)
-    });
+    /**
+     * The importers that will get registered for use by file import
+     */
+    public final List<AbstractImporter> importers = Arrays.asList(
+            // Tabular file formats
+            new CsvImporter(), new OdsImporter(), new XlsImporter(),
+            // Geographic file formats
+            new KmlKmzImporter(), new ShpImporter(), new MifTabImporter(), new GeoPackageImporter(), new GmlImporter(),
+            // Zip archive containing any of the others
+            new ZipImporter(),
+            // 7Zip archive containing any of the others
+            new SevenZipImporter(),
+            // Generic importer for XML files (currently used for Neptune files)
+            xmlImporter
+    );
 
     public OdPlugin(PluginInformation info) {
         super(info);
