001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011import java.util.Map.Entry; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.data.osm.Changeset; 015import org.openstreetmap.josm.data.osm.DataSet; 016import org.openstreetmap.josm.data.osm.Node; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 019import org.openstreetmap.josm.data.osm.PrimitiveId; 020import org.openstreetmap.josm.data.osm.Relation; 021import org.openstreetmap.josm.data.osm.RelationMember; 022import org.openstreetmap.josm.data.osm.RelationMemberData; 023import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 024import org.openstreetmap.josm.data.osm.Way; 025 026/** 027 * Abstract Reader, allowing other implementations than OsmReader (PbfReader in PBF plugin for example) 028 * @author Vincent 029 * 030 */ 031public abstract class AbstractReader { 032 033 /** 034 * The dataset to add parsed objects to. 035 */ 036 protected DataSet ds = new DataSet(); 037 038 protected Changeset uploadChangeset; 039 040 /** the map from external ids to read OsmPrimitives. External ids are 041 * longs too, but in contrast to internal ids negative values are used 042 * to identify primitives unknown to the OSM server 043 */ 044 protected final Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<>(); 045 046 /** 047 * Data structure for the remaining way objects 048 */ 049 protected final Map<Long, Collection<Long>> ways = new HashMap<>(); 050 051 /** 052 * Data structure for relation objects 053 */ 054 protected final Map<Long, Collection<RelationMemberData>> relations = new HashMap<>(); 055 056 /** 057 * Replies the parsed data set 058 * 059 * @return the parsed data set 060 */ 061 public DataSet getDataSet() { 062 return ds; 063 } 064 065 /** 066 * Processes the parsed nodes after parsing. Just adds them to 067 * the dataset 068 * 069 */ 070 protected void processNodesAfterParsing() { 071 for (OsmPrimitive primitive: externalIdMap.values()) { 072 if (primitive instanceof Node) { 073 this.ds.addPrimitive(primitive); 074 } 075 } 076 } 077 078 /** 079 * Processes the ways after parsing. Rebuilds the list of nodes of each way and 080 * adds the way to the dataset 081 * 082 * @throws IllegalDataException if a data integrity problem is detected 083 */ 084 protected void processWaysAfterParsing() throws IllegalDataException { 085 for (Entry<Long, Collection<Long>> entry : ways.entrySet()) { 086 Long externalWayId = entry.getKey(); 087 Way w = (Way) externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY)); 088 List<Node> wayNodes = new ArrayList<>(); 089 for (long id : entry.getValue()) { 090 Node n = (Node) externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE)); 091 if (n == null) { 092 if (id <= 0) 093 throw new IllegalDataException( 094 tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.", 095 externalWayId, 096 id)); 097 // create an incomplete node if necessary 098 n = (Node) ds.getPrimitiveById(id, OsmPrimitiveType.NODE); 099 if (n == null) { 100 n = new Node(id); 101 ds.addPrimitive(n); 102 } 103 } 104 if (n.isDeleted()) { 105 Main.info(tr("Deleted node {0} is part of way {1}", id, w.getId())); 106 } else { 107 wayNodes.add(n); 108 } 109 } 110 w.setNodes(wayNodes); 111 if (w.hasIncompleteNodes()) { 112 Main.info(tr("Way {0} with {1} nodes has incomplete nodes because at least one node was missing in the loaded data.", 113 externalWayId, w.getNodesCount())); 114 } 115 ds.addPrimitive(w); 116 } 117 } 118 119 /** 120 * Completes the parsed relations with its members. 121 * 122 * @throws IllegalDataException if a data integrity problem is detected, i.e. if a 123 * relation member refers to a local primitive which wasn't available in the data 124 */ 125 protected void processRelationsAfterParsing() throws IllegalDataException { 126 127 // First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset 128 for (Long externalRelationId : relations.keySet()) { 129 Relation relation = (Relation) externalIdMap.get( 130 new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION) 131 ); 132 ds.addPrimitive(relation); 133 } 134 135 for (Entry<Long, Collection<RelationMemberData>> entry : relations.entrySet()) { 136 Long externalRelationId = entry.getKey(); 137 Relation relation = (Relation) externalIdMap.get( 138 new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION) 139 ); 140 List<RelationMember> relationMembers = new ArrayList<>(); 141 for (RelationMemberData rm : entry.getValue()) { 142 // lookup the member from the map of already created primitives 143 OsmPrimitive primitive = externalIdMap.get(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType())); 144 145 if (primitive == null) { 146 if (rm.getMemberId() <= 0) 147 // relation member refers to a primitive with a negative id which was not 148 // found in the data. This is always a data integrity problem and we abort 149 // with an exception 150 // 151 throw new IllegalDataException( 152 tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.", 153 externalRelationId, 154 rm.getMemberId())); 155 156 // member refers to OSM primitive which was not present in the parsed data 157 // -> create a new incomplete primitive and add it to the dataset 158 // 159 primitive = ds.getPrimitiveById(rm.getMemberId(), rm.getMemberType()); 160 if (primitive == null) { 161 switch (rm.getMemberType()) { 162 case NODE: 163 primitive = new Node(rm.getMemberId()); break; 164 case WAY: 165 primitive = new Way(rm.getMemberId()); break; 166 case RELATION: 167 primitive = new Relation(rm.getMemberId()); break; 168 default: throw new AssertionError(); // can't happen 169 } 170 171 ds.addPrimitive(primitive); 172 externalIdMap.put(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()), primitive); 173 } 174 } 175 if (primitive.isDeleted()) { 176 Main.info(tr("Deleted member {0} is used by relation {1}", primitive.getId(), relation.getId())); 177 } else { 178 relationMembers.add(new RelationMember(rm.getRole(), primitive)); 179 } 180 } 181 relation.setMembers(relationMembers); 182 } 183 } 184 185 protected void processChangesetAfterParsing() { 186 if (uploadChangeset != null) { 187 for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) { 188 ds.addChangeSetTag(e.getKey(), e.getValue()); 189 } 190 } 191 } 192 193 protected final void prepareDataSet() throws IllegalDataException { 194 try { 195 ds.beginUpdate(); 196 processNodesAfterParsing(); 197 processWaysAfterParsing(); 198 processRelationsAfterParsing(); 199 processChangesetAfterParsing(); 200 } finally { 201 ds.endUpdate(); 202 } 203 } 204}