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 OsmPrimitive primitive = null; 143 144 // lookup the member from the map of already created primitives 145 primitive = externalIdMap.get(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType())); 146 147 if (primitive == null) { 148 if (rm.getMemberId() <= 0) 149 // relation member refers to a primitive with a negative id which was not 150 // found in the data. This is always a data integrity problem and we abort 151 // with an exception 152 // 153 throw new IllegalDataException( 154 tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.", 155 externalRelationId, 156 rm.getMemberId())); 157 158 // member refers to OSM primitive which was not present in the parsed data 159 // -> create a new incomplete primitive and add it to the dataset 160 // 161 primitive = ds.getPrimitiveById(rm.getMemberId(), rm.getMemberType()); 162 if (primitive == null) { 163 switch (rm.getMemberType()) { 164 case NODE: 165 primitive = new Node(rm.getMemberId()); break; 166 case WAY: 167 primitive = new Way(rm.getMemberId()); break; 168 case RELATION: 169 primitive = new Relation(rm.getMemberId()); break; 170 default: throw new AssertionError(); // can't happen 171 } 172 173 ds.addPrimitive(primitive); 174 externalIdMap.put(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()), primitive); 175 } 176 } 177 if (primitive.isDeleted()) { 178 Main.info(tr("Deleted member {0} is used by relation {1}", primitive.getId(), relation.getId())); 179 } else { 180 relationMembers.add(new RelationMember(rm.getRole(), primitive)); 181 } 182 } 183 relation.setMembers(relationMembers); 184 } 185 } 186 187 protected void processChangesetAfterParsing() { 188 if (uploadChangeset != null) { 189 for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) { 190 ds.addChangeSetTag(e.getKey(), e.getValue()); 191 } 192 } 193 } 194 195 protected final void prepareDataSet() throws IllegalDataException { 196 try { 197 ds.beginUpdate(); 198 processNodesAfterParsing(); 199 processWaysAfterParsing(); 200 processRelationsAfterParsing(); 201 processChangesetAfterParsing(); 202 } finally { 203 ds.endUpdate(); 204 } 205 } 206}