// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.io.geographic;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import org.geotools.factory.Hints;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.AbstractCRS;
import org.geotools.referencing.crs.AbstractDerivedCRS;
import org.geotools.referencing.crs.AbstractSingleCRS;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.referencing.operation.TransformException;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
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.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.AbstractReader;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
import org.openstreetmap.josm.plugins.opendata.core.gui.DialogPrompter;
import org.openstreetmap.josm.tools.ImageOverlay;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
import org.openstreetmap.josm.tools.UserCancelException;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
public abstract class GeographicReader extends AbstractReader {
protected static CoordinateReferenceSystem wgs84;
static {
try {
wgs84 = CRS.decode("EPSG:4326");
} catch (NoSuchAuthorityCodeException e) {
e.printStackTrace();
} catch (FactoryException e) {
e.printStackTrace();
}
}
private final GeographicHandler handler;
private final GeographicHandler[] defaultHandlers;
protected final Map<LatLon, Node> nodes;
protected CoordinateReferenceSystem crs;
protected MathTransform transform;
public GeographicReader(GeographicHandler handler, GeographicHandler[] defaultHandlers) {
this.nodes = new HashMap<>();
this.handler = handler;
this.defaultHandlers = defaultHandlers;
}
@Override
protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
return null;
}
protected Node getNode(Point p, LatLon key) {
Node n = nodes.get(key);
if (n == null && handler != null && handler.checkNodeProximity()) {
LatLon ll = new LatLon(p.getY(), p.getX());
for (Node node : nodes.values()) {
if (node.getCoor().equalsEpsilon(ll)) {
return node;
}
}
}
return n;
}
protected Node createOrGetNode(Point p) throws MismatchedDimensionException, TransformException {
return createOrGetNode(p, null);
}
protected Node createOrGetNode(Point p, String ele) throws MismatchedDimensionException, TransformException {
if (!p.isValid()) {
throw new IllegalArgumentException("Invalid point: " + p);
}
Point p2 = (Point) JTS.transform(p, transform);
LatLon key = new LatLon(p2.getY(), p2.getX());
Node n = getNode(p2, key);
if (n == null) {
n = new Node(key);
if (ele != null) {
n.put("ele", ele);
}
if (handler == null || handler.useNodeMap()) {
nodes.put(key, n);
}
ds.addPrimitive(n);
} else if (n.getDataSet() == null) {
// handler may have removed the node from DataSet (see Paris public light handler for example)
ds.addPrimitive(n);
}
return n;
}
protected Node createOrGetEmptyNode(Point p) throws MismatchedDimensionException, TransformException {
Point p2 = (Point) JTS.transform(p, transform);
LatLon key = new LatLon(p2.getY(), p2.getX());
Node n = getNode(p2, key);
if (n != null && n.hasKeys()) {
n = null;
}
if (n == null) {
n = new Node(key);
if (handler == null || handler.useNodeMap()) {
nodes.put(key, n);
}
ds.addPrimitive(n);
} else if (n.getDataSet() == null) {
// handler may have removed the node from DataSet (see Paris public light handler for example)
ds.addPrimitive(n);
}
return n;
}
protected <T extends OsmPrimitive> T addOsmPrimitive(T p) {
ds.addPrimitive(p);
return p;
}
protected final Way createWay() {
return addOsmPrimitive(new Way());
}
protected final Way createOrGetWay(LineString ls) {
Way w = null;
Way tempWay = new Way();
if (ls != null) {
// Build list of nodes
for (int i = 0; i < ls.getNumPoints(); i++) {
try {
tempWay.addNode(createOrGetNode(ls.getPointN(i)));
} catch (TransformException | IllegalArgumentException e) {
Main.error("Exception for " + ls + ": " + e.getClass().getName() + ": " + e.getMessage());
}
}
// Find possible duplicated ways
if (tempWay.getNodesCount() > 0) {
List<Way> candidates = OsmPrimitive.getFilteredList(tempWay.firstNode().getReferrers(), Way.class);
candidates.remove(tempWay);
List<LatLon> tempNodes = DuplicateWay.getOrderedNodes(tempWay);
for (Way candidate : candidates) {
List<LatLon> candNodesA = DuplicateWay.getOrderedNodes(candidate);
List<LatLon> candNodesB = new ArrayList<>(candNodesA);
Collections.reverse(candNodesB);
if (tempNodes.equals(candNodesA) || tempNodes.equals(candNodesB)) {
w = candidate;
break;
}
}
}
}
// If no duplicate way found, create new one
if (w == null) {
w = createWay();
w.setNodes(tempWay.getNodes());
}
return w;
}
protected final Relation createMultipolygon() {
Relation r = new Relation();
r.put("type", "multipolygon");
return addOsmPrimitive(r);
}
protected final void addWayToMp(Relation r, String role, Way w) {
r.addMember(new RelationMember(role, w));
}
/**
* returns true if the user wants to cancel, false if they
* want to continue
*/
protected static final boolean warnLenientMethod(final Component parent, final CoordinateReferenceSystem crs) {
return new DialogPrompter<ExtendedDialog>() {
@Override
protected ExtendedDialog buildDialog() {
final ExtendedDialog dlg = new ExtendedDialog(parent,
tr("Cannot transform to WGS84"),
new String[] {tr("Cancel"), tr("Continue")});
// CHECKSTYLE.OFF: LineLength
dlg.setContent("<html>" +
tr("JOSM was unable to find a strict mathematical transformation between ''{0}'' and WGS84.<br /><br />"+
"Do you want to try a <i>lenient</i> method, which will perform a non-precise transformation (<b>with location errors up to 1 km</b>) ?<br/><br/>"+
"If so, <b>do NOT upload</b> such data to OSM !", crs.getName())+
"</html>");
// CHECKSTYLE.ON: LineLength
dlg.setButtonIcons(new Icon[] {
new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).addOverlay(
new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()});
dlg.setToolTipTexts(new String[] {
tr("Cancel"),
tr("Try lenient method")});
dlg.setIcon(JOptionPane.WARNING_MESSAGE);
dlg.setCancelButton(1);
return dlg;
}
}.promptInEdt().getValue() != 2;
}
private static void compareDebug(CoordinateReferenceSystem crs1, CoordinateReferenceSystem crs2) {
Main.debug("-- COMPARING "+crs1.getName()+" WITH "+crs2.getName()+" --");
compareDebug("class", crs1.getClass(), crs2.getClass());
CoordinateSystem cs1 = crs1.getCoordinateSystem();
CoordinateSystem cs2 = crs2.getCoordinateSystem();
if (!compareDebug("cs", cs1, cs2)) {
Integer dim1 = cs1.getDimension();
Integer dim2 = cs2.getDimension();
if (compareDebug("cs.dim", dim1, dim2)) {
for (int i = 0; i < dim1; i++) {
compareDebug("cs.axis"+i, cs1.getAxis(i), cs1.getAxis(i));
}
}
}
if (crs1 instanceof AbstractSingleCRS) {
Datum datum1 = ((AbstractSingleCRS) crs1).getDatum();
Datum datum2 = ((AbstractSingleCRS) crs2).getDatum();
if (!compareDebug("datum", datum1, datum2)) {
AbstractIdentifiedObject adatum1 = (AbstractIdentifiedObject) datum1;
AbstractIdentifiedObject adatum2 = (AbstractIdentifiedObject) datum2;
compareDebug("datum.name1", adatum1.nameMatches(adatum2.getName().getCode()), adatum1.getName(), adatum2.getName());
compareDebug("datum.name2", adatum2.nameMatches(adatum1.getName().getCode()), adatum2.getName(), adatum1.getName());
}
if (crs1 instanceof AbstractDerivedCRS) {
AbstractDerivedCRS adcrs1 = (AbstractDerivedCRS) crs1;
AbstractDerivedCRS adcrs2 = (AbstractDerivedCRS) crs2;
compareDebug("baseCRS", adcrs1.getBaseCRS(), adcrs2.getBaseCRS());
compareDebug("conversionFromBase", adcrs1.getConversionFromBase(), adcrs2.getConversionFromBase());
}
}
Main.debug("-- COMPARING FINISHED --");
}
private static boolean compareDebug(String text, Object o1, Object o2) {
return compareDebug(text, o1.equals(o2), o1, o2);
}
private static boolean compareDebug(String text, IdentifiedObject o1, IdentifiedObject o2) {
return compareDebug(text, (AbstractIdentifiedObject) o1, (AbstractIdentifiedObject) o2);
}
private static boolean compareDebug(String text, AbstractIdentifiedObject o1, AbstractIdentifiedObject o2) {
return compareDebug(text, o1.equals(o2, false), o1, o2);
}
private static boolean compareDebug(String text, boolean result, Object o1, Object o2) {
Main.debug(text + ": " + result + "("+o1+", "+o2+")");
return result;
}
protected void findMathTransform(Component parent, boolean findSimiliarCrs)
throws FactoryException, UserCancelException, GeoMathTransformException {
try {
transform = CRS.findMathTransform(crs, wgs84);
} catch (OperationNotFoundException e) {
Main.info(crs.getName()+": "+e.getMessage()); // Bursa wolf parameters required.
if (findSimiliarCrs) {
List<CoordinateReferenceSystem> candidates = new ArrayList<>();
// Find matching CRS with Bursa Wolf parameters in EPSG database
for (String code : CRS.getAuthorityFactory(false).getAuthorityCodes(ProjectedCRS.class)) {
try {
CoordinateReferenceSystem candidate = CRS.decode(code);
if (candidate instanceof AbstractCRS && crs instanceof AbstractIdentifiedObject) {
Hints.putSystemDefault(Hints.COMPARISON_TOLERANCE, Main.pref.getDouble(
OdConstants.PREF_CRS_COMPARISON_TOLERANCE, OdConstants.DEFAULT_CRS_COMPARISON_TOLERANCE));
if (((AbstractCRS) candidate).equals((AbstractIdentifiedObject) crs, false)) {
Main.info("Found a potential CRS: "+candidate.getName());
candidates.add(candidate);
} else if (Main.pref.getBoolean(OdConstants.PREF_CRS_COMPARISON_DEBUG, false)) {
compareDebug(crs, candidate);
}
Hints.removeSystemDefault(Hints.COMPARISON_TOLERANCE);
}
} catch (FactoryException ex) {
Main.trace(ex);
}
}
if (candidates.size() > 1) {
Main.warn("Found several potential CRS: "+Arrays.toString(candidates.toArray()));
// TODO: ask user which one to use
}
if (candidates.size() > 0) {
CoordinateReferenceSystem newCRS = candidates.get(0);
try {
transform = CRS.findMathTransform(newCRS, wgs84, false);
} catch (OperationNotFoundException ex) {
Main.warn(newCRS.getName()+": "+e.getMessage());
}
}
}
if (transform == null) {
if (handler != null) {
// ask handler if it can provide a math transform
try {
transform = handler.findMathTransform(crs, wgs84, false);
} catch (OperationNotFoundException ex) {
Main.warn(crs.getName()+": "+ex.getMessage()); // Bursa wolf parameters required.
}
} else {
// ask default known handlers
for (GeographicHandler geoHandler : defaultHandlers) {
try {
if ((transform = geoHandler.findMathTransform(crs, wgs84, false)) != null) {
break;
}
} catch (OperationNotFoundException ex) {
Main.warn(crs.getName()+": "+ex.getMessage()); // Bursa wolf parameters required.
}
}
}
if (transform == null) {
// ask user before trying lenient method, unless in headless mode (unit tests)
if (!GraphicsEnvironment.isHeadless() && warnLenientMethod(parent, crs)) {
// User canceled
throw new UserCancelException();
}
Main.info("Searching for a lenient math transform.");
transform = CRS.findMathTransform(crs, wgs84, true);
}
}
}
if (transform == null) {
throw new GeoMathTransformException("Unable to find math transform !");
}
}
}