// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.osmrec.personalization;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeocentricCRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.io.OsmApi;
import org.openstreetmap.josm.plugins.osmrec.container.OSMNode;
import org.openstreetmap.josm.plugins.osmrec.container.OSMWay;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.Utils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Parses the history of an OSM user's changesets using OSM API.
*
* @author imis-nkarag
*/
public class HistoryParser {
private static final String OSM_API = OsmApi.getOsmApi().getBaseUrl();
private static final CoordinateReferenceSystem sourceCRS = DefaultGeographicCRS.WGS84;
private static final CoordinateReferenceSystem targetCRS = DefaultGeocentricCRS.CARTESIAN;
private static final GeometryFactory geometryFactory = new GeometryFactory();
private MathTransform transform;
private OSMNode nodeTmp;
private final List<OSMNode> nodeList;
private final Map<String, OSMNode> nodesWithIDs;
private final List<OSMWay> wayList;
private OSMWay wayTmp;
private final String username;
/**
* Constructs a new {@code HistoryParser}.
* @param username user name
*/
public HistoryParser(String username) {
this.username = username;
transform = null;
try {
transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
} catch (FactoryException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
nodeList = new ArrayList<>();
nodesWithIDs = new HashMap<>();
wayList = new ArrayList<>();
}
public void historyParse(String timeInterval) {
HashSet<String> changesetIDsList = new HashSet<>();
try {
String osmUrl = OSM_API + "changesets?display_name=" + username + "&time=" + timeInterval;
InputStream xml = HttpClient.create(new URL(osmUrl)).connect().getContent();
NodeList nodes = Utils.parseSafeDOM(xml).getElementsByTagName("changeset");
Main.debug("changeset size "+ nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
Main.debug("attributes of " + i + "th changeset");
String id = nodes.item(i).getAttributes().item(3).toString();
Main.debug("id:" + nodes.item(i).getAttributes().item(3));
id = stripQuotes(id);
changesetIDsList.add(id);
}
for (String id : changesetIDsList) {
getChangesetByID(id);
}
} catch (IOException | ParserConfigurationException | SAXException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void getChangesetByID(String id) {
try {
String changesetByIDURL = OSM_API+ "changeset/" + id + "/download";
InputStream xml = HttpClient.create(new URL(changesetByIDURL)).connect().getContent();
Node osmChange = Utils.parseSafeDOM(xml).getFirstChild();
//get all nodes first, in order to be able to call all nodes references and create the geometries
for (int i = 0; i < osmChange.getChildNodes().getLength(); i++) {
String changeType = osmChange.getChildNodes().item(i).getNodeName();
if (!(changeType.equals("#text") || changeType.equals("delete"))) {
NodeList changeChilds = osmChange.getChildNodes().item(i).getChildNodes();
Node osmObject = changeChilds.item(1);
if (osmObject.getNodeName().equals("node")) {
//node data
nodeTmp = new OSMNode();
nodeTmp.setID(osmObject.getAttributes().getNamedItem("id").getNodeValue());
//parse geometry
double longitude = Double.parseDouble(osmObject.getAttributes().getNamedItem("lon").getNodeValue());
double latitude = Double.parseDouble(osmObject.getAttributes().getNamedItem("lat").getNodeValue());
Coordinate targetGeometry = null;
Coordinate sourceCoordinate = new Coordinate(longitude, latitude);
try {
targetGeometry = JTS.transform(sourceCoordinate, null, transform);
} catch (MismatchedDimensionException | TransformException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
//create geometry object
Geometry geom = geometryFactory.createPoint(new Coordinate(targetGeometry));
nodeTmp.setGeometry(geom);
nodeList.add(nodeTmp);
nodesWithIDs.put(nodeTmp.getID(), nodeTmp);
}
}
}
for (int i = 0; i < osmChange.getChildNodes().getLength(); i++) {
String changeType = osmChange.getChildNodes().item(i).getNodeName();
if (!(changeType.equals("#text") || changeType.equals("delete"))) {
NodeList changeChilds = osmChange.getChildNodes().item(i).getChildNodes();
Node osmObject = changeChilds.item(1);
if (osmObject.getNodeName().equals("way")) {
//get way data
wayTmp = new OSMWay();
wayTmp.setID(osmObject.getAttributes().getNamedItem("id").getNodeValue());
// extract tags, then set tags to osm object
Main.debug("\n\nWAY: " + wayTmp.getID());
for (int l = 0; l < osmObject.getChildNodes().getLength(); l++) {
String wayChild = osmObject.getChildNodes().item(l).getNodeName();
if (wayChild.equals("tag")) {
String key = osmObject.getChildNodes().item(l).getAttributes().getNamedItem("k").getNodeValue();
String value = osmObject.getChildNodes().item(l).getAttributes().getNamedItem("v").getNodeValue();
System.out.println("key: " + key + " value: " + value);
wayTmp.setTagKeyValue(key, value);
} else if (wayChild.equals("nd")) {
wayTmp.addNodeReference(osmObject.getChildNodes().item(l).getAttributes().getNamedItem("ref").getNodeValue());
}
}
//construct the Way geometry from each node of the node references
List<String> references = wayTmp.getNodeReferences();
for (String entry: references) {
if (nodesWithIDs.containsKey(entry)) {
Geometry geometry = nodesWithIDs.get(entry).getGeometry(); //get the geometry of the node with ID=entry
wayTmp.addNodeGeometry(geometry); //add the node geometry in this way
} else {
Main.debug("nodes with ids, no entry " + entry);
getNodeFromAPI(entry);
}
}
Geometry geom = geometryFactory.buildGeometry(wayTmp.getNodeGeometries());
if ((wayTmp.getNodeGeometries().size() > 3) &&
wayTmp.getNodeGeometries().get(0).equals(wayTmp.getNodeGeometries()
.get(wayTmp.getNodeGeometries().size()-1))) {
//checks if the beginning and ending node are the same and the number of nodes are more than 3.
//the nodes must be more than 3, because jts does not allow a construction of a linear ring with less points.
if (!((wayTmp.getTagKeyValue().containsKey("barrier")) || wayTmp.getTagKeyValue().containsKey("highway"))) {
//this is not a barrier nor a road, so construct a polygon geometry
LinearRing linear = geometryFactory.createLinearRing(geom.getCoordinates());
Polygon poly = new Polygon(linear, null, geometryFactory);
wayTmp.setGeometry(poly);
} else {
//it is either a barrier or a road, so construct a linear ring geometry
LinearRing linear = geometryFactory.createLinearRing(geom.getCoordinates());
wayTmp.setGeometry(linear);
}
} else if (wayTmp.getNodeGeometries().size() > 1) {
//it is an open geometry with more than one nodes, make it linestring
LineString lineString = geometryFactory.createLineString(geom.getCoordinates());
wayTmp.setGeometry(lineString);
} else { //we assume all the rest geometries are points
//some ways happen to have only one point. Construct a Point.
Point point = geometryFactory.createPoint(geom.getCoordinate());
wayTmp.setGeometry(point);
}
wayList.add(wayTmp);
}
}
}
} catch (IOException | ParserConfigurationException | SAXException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
}
private String stripQuotes(String id) {
return id.substring(4, id.length()-1);
}
private void getNodeFromAPI(String nodeID) {
try {
String osmUrl = OSM_API + "node/" + nodeID;
InputStream xml = HttpClient.create(new URL(osmUrl)).connect().getContent();
NodeList nodes = Utils.parseSafeDOM(xml).getElementsByTagName("node");
String lat = nodes.item(0).getAttributes().getNamedItem("lat").getNodeValue();
String lon = nodes.item(0).getAttributes().getNamedItem("lon").getNodeValue();
nodeTmp = new OSMNode();
nodeTmp.setID(nodeID);
//parse geometry
double longitude = Double.parseDouble(lon);
double latitude = Double.parseDouble(lat);
Coordinate targetGeometry = null;
Coordinate sourceCoordinate = new Coordinate(longitude, latitude);
try {
targetGeometry = JTS.transform(sourceCoordinate, null, transform);
} catch (MismatchedDimensionException | TransformException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
//create geometry object
Geometry geom = geometryFactory.createPoint(new Coordinate(targetGeometry));
nodeTmp.setGeometry(geom);
nodeList.add(nodeTmp);
nodesWithIDs.put(nodeTmp.getID(), nodeTmp);
} catch (IOException | ParserConfigurationException | SAXException ex) {
Logger.getLogger(HistoryParser.class.getName()).log(Level.SEVERE, null, ex);
}
}
public List<OSMWay> getWayList() {
return wayList;
}
}