Index: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsConverter.java	(revision 36025)
@@ -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: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/GeotoolsHandler.java	(revision 36025)
@@ -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: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/DefaultGeoPackageHandler.java	(revision 36025)
@@ -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: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageHandler.java	(revision 36025)
@@ -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: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageImporter.java	(revision 36025)
@@ -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: /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java
===================================================================
--- /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java	(revision 36025)
+++ /applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/geopackage/GeoPackageReader.java	(revision 36025)
@@ -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;
+    }
+}
