// License: GPL. See LICENSE file for details.
package org.openstreetmap.josm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.Changeset;
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.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.RelationMemberData;
import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
import org.openstreetmap.josm.data.osm.Way;
/**
* Abstract Reader, allowing other implementations than OsmReader (PbfReader in PBF plugin for example)
* @author Vincent
*
*/
public abstract class AbstractReader {
/**
* The dataset to add parsed objects to.
*/
protected DataSet ds = new DataSet();
protected Changeset uploadChangeset;
/** the map from external ids to read OsmPrimitives. External ids are
* longs too, but in contrast to internal ids negative values are used
* to identify primitives unknown to the OSM server
*/
protected final Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<>();
/**
* Data structure for the remaining way objects
*/
protected final Map<Long, Collection<Long>> ways = new HashMap<>();
/**
* Data structure for relation objects
*/
protected final Map<Long, Collection<RelationMemberData>> relations = new HashMap<>();
/**
* Replies the parsed data set
*
* @return the parsed data set
*/
public DataSet getDataSet() {
return ds;
}
/**
* Processes the parsed nodes after parsing. Just adds them to
* the dataset
*
*/
protected void processNodesAfterParsing() {
for (OsmPrimitive primitive: externalIdMap.values()) {
if (primitive instanceof Node) {
this.ds.addPrimitive(primitive);
}
}
}
/**
* Processes the ways after parsing. Rebuilds the list of nodes of each way and
* adds the way to the dataset
*
* @throws IllegalDataException thrown if a data integrity problem is detected
*/
protected void processWaysAfterParsing() throws IllegalDataException{
for (Long externalWayId: ways.keySet()) {
Way w = (Way)externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY));
List<Node> wayNodes = new ArrayList<>();
for (long id : ways.get(externalWayId)) {
Node n = (Node)externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
if (n == null) {
if (id <= 0)
throw new IllegalDataException (
tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.",
externalWayId,
id));
// create an incomplete node if necessary
//
n = (Node)ds.getPrimitiveById(id,OsmPrimitiveType.NODE);
if (n == null) {
n = new Node(id);
ds.addPrimitive(n);
}
}
if (n.isDeleted()) {
Main.info(tr("Deleted node {0} is part of way {1}", id, w.getId()));
} else {
wayNodes.add(n);
}
}
w.setNodes(wayNodes);
if (w.hasIncompleteNodes()) {
Main.info(tr("Way {0} with {1} nodes has incomplete nodes because at least one node was missing in the loaded data.",
externalWayId, w.getNodesCount()));
}
ds.addPrimitive(w);
}
}
/**
* Completes the parsed relations with its members.
*
* @throws IllegalDataException thrown if a data integrity problem is detected, i.e. if a
* relation member refers to a local primitive which wasn't available in the data
*
*/
protected void processRelationsAfterParsing() throws IllegalDataException {
// First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset
for (Long externalRelationId : relations.keySet()) {
Relation relation = (Relation) externalIdMap.get(
new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
);
ds.addPrimitive(relation);
}
for (Long externalRelationId : relations.keySet()) {
Relation relation = (Relation) externalIdMap.get(
new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
);
List<RelationMember> relationMembers = new ArrayList<>();
for (RelationMemberData rm : relations.get(externalRelationId)) {
OsmPrimitive primitive = null;
// lookup the member from the map of already created primitives
primitive = externalIdMap.get(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()));
if (primitive == null) {
if (rm.getMemberId() <= 0)
// relation member refers to a primitive with a negative id which was not
// found in the data. This is always a data integrity problem and we abort
// with an exception
//
throw new IllegalDataException(
tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.",
externalRelationId,
rm.getMemberId()));
// member refers to OSM primitive which was not present in the parsed data
// -> create a new incomplete primitive and add it to the dataset
//
primitive = ds.getPrimitiveById(rm.getMemberId(), rm.getMemberType());
if (primitive == null) {
switch (rm.getMemberType()) {
case NODE:
primitive = new Node(rm.getMemberId()); break;
case WAY:
primitive = new Way(rm.getMemberId()); break;
case RELATION:
primitive = new Relation(rm.getMemberId()); break;
default: throw new AssertionError(); // can't happen
}
ds.addPrimitive(primitive);
externalIdMap.put(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()), primitive);
}
}
if (primitive.isDeleted()) {
Main.info(tr("Deleted member {0} is used by relation {1}", primitive.getId(), relation.getId()));
} else {
relationMembers.add(new RelationMember(rm.getRole(), primitive));
}
}
relation.setMembers(relationMembers);
}
}
protected void processChangesetAfterParsing() {
if (uploadChangeset != null) {
for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) {
ds.addChangeSetTag(e.getKey(), e.getValue());
}
}
}
protected final void prepareDataSet() throws IllegalDataException {
try {
ds.beginUpdate();
processNodesAfterParsing();
processWaysAfterParsing();
processRelationsAfterParsing();
processChangesetAfterParsing();
} finally {
ds.endUpdate();
}
}
}