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.io.IOException; 007import java.io.InputStream; 008import java.io.InputStreamReader; 009import java.text.MessageFormat; 010import java.time.DateTimeException; 011import java.util.ArrayList; 012import java.util.Collection; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.Map.Entry; 017import java.util.OptionalLong; 018import java.util.function.Consumer; 019 020import org.openstreetmap.josm.data.Bounds; 021import org.openstreetmap.josm.data.DataSource; 022import org.openstreetmap.josm.data.coor.LatLon; 023import org.openstreetmap.josm.data.osm.AbstractPrimitive; 024import org.openstreetmap.josm.data.osm.Changeset; 025import org.openstreetmap.josm.data.osm.DataSet; 026import org.openstreetmap.josm.data.osm.DownloadPolicy; 027import org.openstreetmap.josm.data.osm.Node; 028import org.openstreetmap.josm.data.osm.NodeData; 029import org.openstreetmap.josm.data.osm.OsmPrimitive; 030import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 031import org.openstreetmap.josm.data.osm.PrimitiveData; 032import org.openstreetmap.josm.data.osm.PrimitiveId; 033import org.openstreetmap.josm.data.osm.Relation; 034import org.openstreetmap.josm.data.osm.RelationData; 035import org.openstreetmap.josm.data.osm.RelationMember; 036import org.openstreetmap.josm.data.osm.RelationMemberData; 037import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 038import org.openstreetmap.josm.data.osm.Tagged; 039import org.openstreetmap.josm.data.osm.UploadPolicy; 040import org.openstreetmap.josm.data.osm.User; 041import org.openstreetmap.josm.data.osm.Way; 042import org.openstreetmap.josm.data.osm.WayData; 043import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 044import org.openstreetmap.josm.gui.progress.ProgressMonitor; 045import org.openstreetmap.josm.gui.util.LruCache; 046import org.openstreetmap.josm.tools.CheckParameterUtil; 047import org.openstreetmap.josm.tools.Logging; 048import org.openstreetmap.josm.tools.UncheckedParseException; 049import org.openstreetmap.josm.tools.Utils; 050import org.openstreetmap.josm.tools.date.DateUtils; 051 052/** 053 * Abstract Reader, allowing other implementations than OsmReader (PbfReader in PBF plugin for example) 054 * @author Vincent 055 * @since 4490 056 */ 057public abstract class AbstractReader { 058 059 /** Used by plugins to register themselves as data postprocessors. */ 060 private static volatile List<OsmServerReadPostprocessor> postprocessors; 061 062 protected boolean cancel; 063 064 /** 065 * Register a new postprocessor. 066 * @param pp postprocessor 067 * @see #deregisterPostprocessor 068 * @since 14119 (moved from OsmReader) 069 */ 070 public static void registerPostprocessor(OsmServerReadPostprocessor pp) { 071 if (postprocessors == null) { 072 postprocessors = new ArrayList<>(); 073 } 074 postprocessors.add(pp); 075 } 076 077 /** 078 * Deregister a postprocessor previously registered with {@link #registerPostprocessor}. 079 * @param pp postprocessor 080 * @see #registerPostprocessor 081 * @since 14119 (moved from OsmReader) 082 */ 083 public static void deregisterPostprocessor(OsmServerReadPostprocessor pp) { 084 if (postprocessors != null) { 085 postprocessors.remove(pp); 086 } 087 } 088 089 /** 090 * The dataset to add parsed objects to. 091 */ 092 protected DataSet ds = new DataSet(); 093 094 protected Changeset uploadChangeset; 095 096 /** the map from external ids to read OsmPrimitives. External ids are 097 * longs too, but in contrast to internal ids negative values are used 098 * to identify primitives unknown to the OSM server 099 */ 100 protected final Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<>(); 101 102 /** 103 * Data structure for the remaining way objects 104 */ 105 protected final Map<Long, Collection<Long>> ways = new HashMap<>(); 106 107 /** 108 * Data structure for relation objects 109 */ 110 protected final Map<Long, Collection<RelationMemberData>> relations = new HashMap<>(); 111 112 /** 113 * Replies the parsed data set 114 * 115 * @return the parsed data set 116 */ 117 public DataSet getDataSet() { 118 return ds; 119 } 120 121 /** 122 * Iterate over registered postprocessors and give them each a chance to modify the dataset we have just loaded. 123 * @param progressMonitor Progress monitor 124 */ 125 protected void callPostProcessors(ProgressMonitor progressMonitor) { 126 if (postprocessors != null) { 127 for (OsmServerReadPostprocessor pp : postprocessors) { 128 pp.postprocessDataSet(getDataSet(), progressMonitor); 129 } 130 } 131 } 132 133 /** 134 * Processes the parsed nodes after parsing. Just adds them to 135 * the dataset 136 * 137 */ 138 protected void processNodesAfterParsing() { 139 for (OsmPrimitive primitive: externalIdMap.values()) { 140 if (primitive instanceof Node) { 141 this.ds.addPrimitive(primitive); 142 } 143 } 144 } 145 146 /** 147 * Processes the ways after parsing. Rebuilds the list of nodes of each way and 148 * adds the way to the dataset 149 * 150 * @throws IllegalDataException if a data integrity problem is detected 151 */ 152 protected void processWaysAfterParsing() throws IllegalDataException { 153 for (Entry<Long, Collection<Long>> entry : ways.entrySet()) { 154 Long externalWayId = entry.getKey(); 155 Way w = (Way) externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY)); 156 List<Node> wayNodes = new ArrayList<>(); 157 for (long id : entry.getValue()) { 158 Node n = (Node) externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE)); 159 if (n == null) { 160 if (id <= 0) 161 throw new IllegalDataException( 162 tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.", 163 Long.toString(externalWayId), 164 Long.toString(id))); 165 // create an incomplete node if necessary 166 n = (Node) ds.getPrimitiveById(id, OsmPrimitiveType.NODE); 167 if (n == null) { 168 n = new Node(id); 169 ds.addPrimitive(n); 170 } 171 } 172 if (n.isDeleted()) { 173 Logging.info(tr("Deleted node {0} is part of way {1}", Long.toString(id), Long.toString(w.getId()))); 174 } else { 175 wayNodes.add(n); 176 } 177 } 178 w.setNodes(wayNodes); 179 if (w.hasIncompleteNodes()) { 180 Logging.info(tr("Way {0} with {1} nodes is incomplete because at least one node was missing in the loaded data.", 181 Long.toString(externalWayId), w.getNodesCount())); 182 } 183 ds.addPrimitive(w); 184 } 185 } 186 187 /** 188 * Completes the parsed relations with its members. 189 * 190 * @throws IllegalDataException if a data integrity problem is detected, i.e. if a 191 * relation member refers to a local primitive which wasn't available in the data 192 */ 193 protected void processRelationsAfterParsing() throws IllegalDataException { 194 195 // First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset 196 for (Long externalRelationId : relations.keySet()) { 197 Relation relation = (Relation) externalIdMap.get( 198 new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION) 199 ); 200 ds.addPrimitive(relation); 201 } 202 203 for (Entry<Long, Collection<RelationMemberData>> entry : relations.entrySet()) { 204 Long externalRelationId = entry.getKey(); 205 Relation relation = (Relation) externalIdMap.get( 206 new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION) 207 ); 208 List<RelationMember> relationMembers = new ArrayList<>(); 209 for (RelationMemberData rm : entry.getValue()) { 210 // lookup the member from the map of already created primitives 211 OsmPrimitive primitive = externalIdMap.get(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType())); 212 213 if (primitive == null) { 214 if (rm.getMemberId() <= 0) 215 // relation member refers to a primitive with a negative id which was not 216 // found in the data. This is always a data integrity problem and we abort 217 // with an exception 218 // 219 throw new IllegalDataException( 220 tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.", 221 Long.toString(externalRelationId), 222 Long.toString(rm.getMemberId()))); 223 224 // member refers to OSM primitive which was not present in the parsed data 225 // -> create a new incomplete primitive and add it to the dataset 226 // 227 primitive = ds.getPrimitiveById(rm.getMemberId(), rm.getMemberType()); 228 if (primitive == null) { 229 switch (rm.getMemberType()) { 230 case NODE: 231 primitive = new Node(rm.getMemberId()); break; 232 case WAY: 233 primitive = new Way(rm.getMemberId()); break; 234 case RELATION: 235 primitive = new Relation(rm.getMemberId()); break; 236 default: throw new AssertionError(); // can't happen 237 } 238 239 ds.addPrimitive(primitive); 240 externalIdMap.put(new SimplePrimitiveId(rm.getMemberId(), rm.getMemberType()), primitive); 241 } 242 } 243 if (primitive.isDeleted()) { 244 Logging.info(tr("Deleted member {0} is used by relation {1}", 245 Long.toString(primitive.getId()), Long.toString(relation.getId()))); 246 } else { 247 relationMembers.add(new RelationMember(rm.getRole(), primitive)); 248 } 249 } 250 relation.setMembers(relationMembers); 251 } 252 } 253 254 protected void processChangesetAfterParsing() { 255 if (uploadChangeset != null) { 256 for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) { 257 ds.addChangeSetTag(e.getKey(), e.getValue()); 258 } 259 } 260 } 261 262 protected final void prepareDataSet() throws IllegalDataException { 263 ds.beginUpdate(); 264 try { 265 processNodesAfterParsing(); 266 processWaysAfterParsing(); 267 processRelationsAfterParsing(); 268 processChangesetAfterParsing(); 269 } finally { 270 ds.endUpdate(); 271 } 272 } 273 274 protected abstract DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException; 275 276 @FunctionalInterface 277 protected interface ParserWorker { 278 /** 279 * Effectively parses the file, depending on the format (XML, JSON, etc.) 280 * @param ir input stream reader 281 * @throws IllegalDataException in case of invalid data 282 * @throws IOException in case of I/O error 283 */ 284 void accept(InputStreamReader ir) throws IllegalDataException, IOException; 285 } 286 287 protected final DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor, ParserWorker parserWorker) 288 throws IllegalDataException { 289 if (progressMonitor == null) { 290 progressMonitor = NullProgressMonitor.INSTANCE; 291 } 292 ProgressMonitor.CancelListener cancelListener = () -> cancel = true; 293 progressMonitor.addCancelListener(cancelListener); 294 CheckParameterUtil.ensureParameterNotNull(source, "source"); 295 try { 296 progressMonitor.beginTask(tr("Prepare OSM data..."), 4); // read, prepare, post-process, render 297 progressMonitor.indeterminateSubTask(tr("Parsing OSM data...")); 298 299 try (InputStreamReader ir = UTFInputStreamReader.create(source)) { 300 parserWorker.accept(ir); 301 } 302 progressMonitor.worked(1); 303 304 boolean readOnly = getDataSet().isLocked(); 305 306 progressMonitor.indeterminateSubTask(tr("Preparing data set...")); 307 if (readOnly) { 308 getDataSet().unlock(); 309 } 310 prepareDataSet(); 311 if (readOnly) { 312 getDataSet().lock(); 313 } 314 progressMonitor.worked(1); 315 progressMonitor.indeterminateSubTask(tr("Post-processing data set...")); 316 // iterate over registered postprocessors and give them each a chance 317 // to modify the dataset we have just loaded. 318 callPostProcessors(progressMonitor); 319 progressMonitor.worked(1); 320 progressMonitor.indeterminateSubTask(tr("Rendering data set...")); 321 // Make sure postprocessors did not change the read-only state 322 if (readOnly && !getDataSet().isLocked()) { 323 getDataSet().lock(); 324 } 325 return getDataSet(); 326 } catch (IllegalDataException e) { 327 throw e; 328 } catch (IOException e) { 329 throw new IllegalDataException(e); 330 } finally { 331 for (OsmPrimitiveType dataType : OsmPrimitiveType.dataValues()) { 332 OptionalLong minId = externalIdMap.entrySet().parallelStream() 333 .filter(e -> e.getKey().getType() == dataType) 334 .mapToLong(e -> e.getValue().getUniqueId()).min(); 335 synchronized (dataType.getDataClass()) { 336 if (minId.isPresent() && minId.getAsLong() < dataType.getIdGenerator().currentUniqueId()) { 337 dataType.getIdGenerator().advanceUniqueId(minId.getAsLong()); 338 } 339 } 340 } 341 progressMonitor.finishTask(); 342 progressMonitor.removeCancelListener(cancelListener); 343 } 344 } 345 346 protected final long getLong(String name, String value) throws IllegalDataException { 347 if (value == null) { 348 throw new IllegalDataException(tr("Missing required attribute ''{0}''.", name)); 349 } 350 try { 351 return Long.parseLong(value); 352 } catch (NumberFormatException e) { 353 throw new IllegalDataException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value), e); 354 } 355 } 356 357 protected final void parseVersion(String version) throws IllegalDataException { 358 validateVersion(version); 359 ds.setVersion(version); 360 } 361 362 private static void validateVersion(String version) throws IllegalDataException { 363 if (version == null) { 364 throw new IllegalDataException(tr("Missing mandatory attribute ''{0}''.", "version")); 365 } 366 if (!"0.6".equals(version)) { 367 throw new IllegalDataException(tr("Unsupported version: {0}", version)); 368 } 369 } 370 371 protected final void parseDownloadPolicy(String key, String downloadPolicy) throws IllegalDataException { 372 parsePolicy(key, downloadPolicy, policy -> ds.setDownloadPolicy(DownloadPolicy.of(policy))); 373 } 374 375 protected final void parseUploadPolicy(String key, String uploadPolicy) throws IllegalDataException { 376 parsePolicy(key, uploadPolicy, policy -> ds.setUploadPolicy(UploadPolicy.of(policy))); 377 } 378 379 private static void parsePolicy(String key, String policy, Consumer<String> consumer) throws IllegalDataException { 380 if (policy != null) { 381 try { 382 consumer.accept(policy); 383 } catch (IllegalArgumentException e) { 384 throw new IllegalDataException(MessageFormat.format( 385 "Illegal value for attribute ''{0}''. Got ''{1}''.", key, policy), e); 386 } 387 } 388 } 389 390 protected final void parseLocked(String locked) { 391 if ("true".equalsIgnoreCase(locked)) { 392 ds.lock(); 393 } 394 } 395 396 protected final void parseBounds(String generator, String minlon, String minlat, String maxlon, String maxlat, String origin) 397 throws IllegalDataException { 398 if (minlon != null && maxlon != null && minlat != null && maxlat != null) { 399 if (origin == null) { 400 origin = generator; 401 } 402 Bounds bounds = new Bounds( 403 Double.parseDouble(minlat), Double.parseDouble(minlon), 404 Double.parseDouble(maxlat), Double.parseDouble(maxlon)); 405 if (bounds.isOutOfTheWorld()) { 406 Bounds copy = new Bounds(bounds); 407 bounds.normalize(); 408 Logging.info("Bbox " + copy + " is out of the world, normalized to " + bounds); 409 } 410 ds.addDataSource(new DataSource(bounds, origin)); 411 } else { 412 throw new IllegalDataException(tr("Missing mandatory attributes on element ''bounds''. " + 413 "Got minlon=''{0}'',minlat=''{1}'',maxlon=''{2}'',maxlat=''{3}'', origin=''{4}''.", 414 minlon, minlat, maxlon, maxlat, origin 415 )); 416 } 417 } 418 419 protected final void parseId(PrimitiveData current, long id) throws IllegalDataException { 420 current.setId(id); 421 if (current.getUniqueId() == 0) { 422 throw new IllegalDataException(tr("Illegal object with ID=0.")); 423 } 424 } 425 426 private final Map<String, Integer> timestampCache = new LruCache<>(30); 427 428 protected final void parseTimestamp(PrimitiveData current, String time) { 429 if (Utils.isEmpty(time)) { 430 return; 431 } 432 try { 433 int timestamp = timestampCache.computeIfAbsent(time, t -> (int) DateUtils.parseInstant(t).getEpochSecond()); 434 current.setRawTimestamp(timestamp); 435 } catch (UncheckedParseException | DateTimeException e) { 436 Logging.error(e); 437 } 438 } 439 440 private static User createUser(String uid, String name) throws IllegalDataException { 441 if (uid == null) { 442 if (name == null) 443 return null; 444 return User.createLocalUser(name); 445 } 446 try { 447 return User.createOsmUser(Long.parseLong(uid), name); 448 } catch (NumberFormatException e) { 449 throw new IllegalDataException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid), e); 450 } 451 } 452 453 protected final void parseUser(PrimitiveData current, String user, long uid) { 454 current.setUser(User.createOsmUser(uid, user)); 455 } 456 457 protected final void parseUser(PrimitiveData current, String user, String uid) throws IllegalDataException { 458 current.setUser(createUser(uid, user)); 459 } 460 461 protected final void parseVisible(PrimitiveData current, String visible) { 462 if (visible != null) { 463 current.setVisible(Boolean.parseBoolean(visible)); 464 } 465 } 466 467 protected final void parseVersion(PrimitiveData current, String versionString) throws IllegalDataException { 468 int version = 0; 469 if (versionString != null) { 470 try { 471 version = Integer.parseInt(versionString); 472 } catch (NumberFormatException e) { 473 throw new IllegalDataException( 474 tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 475 Long.toString(current.getUniqueId()), versionString), e); 476 } 477 parseVersion(current, version); 478 } else { 479 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6 480 if (!current.isNew() && ds.getVersion() != null && "0.6".equals(ds.getVersion())) { 481 throw new IllegalDataException( 482 tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId()))); 483 } 484 } 485 } 486 487 protected final void parseVersion(PrimitiveData current, int version) throws IllegalDataException { 488 switch (ds.getVersion()) { 489 case "0.6": 490 if (version <= 0 && !current.isNew()) { 491 throw new IllegalDataException( 492 tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 493 Long.toString(current.getUniqueId()), version)); 494 } else if (version < 0 && current.isNew()) { 495 Logging.warn(tr("Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", 496 current.getUniqueId(), version, 0, "0.6")); 497 version = 0; 498 } 499 break; 500 default: 501 // should not happen. API version has been checked before 502 throw new IllegalDataException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion())); 503 } 504 current.setVersion(version); 505 } 506 507 protected final void parseAction(PrimitiveData current, String action) { 508 if (action == null) { 509 // do nothing 510 } else if ("delete".equals(action)) { 511 current.setDeleted(true); 512 current.setModified(current.isVisible()); 513 } else if ("modify".equals(action)) { 514 current.setModified(true); 515 } 516 } 517 518 private static void handleIllegalChangeset(PrimitiveData current, IllegalArgumentException e, Object v) 519 throws IllegalDataException { 520 Logging.debug(e.getMessage()); 521 if (current.isNew()) { 522 // for a new primitive we just log a warning 523 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 524 v, current.getUniqueId())); 525 current.setChangesetId(0); 526 } else { 527 // for an existing primitive this is a problem 528 throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v), e); 529 } 530 } 531 532 protected final void parseChangeset(PrimitiveData current, String v) throws IllegalDataException { 533 if (v == null) { 534 current.setChangesetId(0); 535 } else { 536 try { 537 parseChangeset(current, Integer.parseInt(v)); 538 } catch (NumberFormatException e) { 539 handleIllegalChangeset(current, e, v); 540 } 541 } 542 } 543 544 protected final void parseChangeset(PrimitiveData current, int v) throws IllegalDataException { 545 try { 546 current.setChangesetId(v); 547 } catch (IllegalArgumentException e) { 548 handleIllegalChangeset(current, e, v); 549 } catch (IllegalStateException e) { 550 // thrown for positive changeset id on new primitives 551 Logging.debug(e); 552 Logging.info(e.getMessage()); 553 current.setChangesetId(0); 554 } 555 if (current.getChangesetId() <= 0) { 556 if (current.isNew()) { 557 // for a new primitive we just log a warning 558 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 559 v, current.getUniqueId())); 560 current.setChangesetId(0); 561 } else if (current.getChangesetId() < 0) { 562 // for an existing primitive this is a problem only for negative ids (GDPR extracts are set to 0) 563 throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v)); 564 } 565 } 566 } 567 568 protected final void parseTag(Tagged t, String key, String value) throws IllegalDataException { 569 if (key == null || value == null) { 570 throw new IllegalDataException(tr("Missing key or value attribute in tag.")); 571 } else if (Utils.isStripEmpty(key) && t instanceof AbstractPrimitive) { 572 // #14199: Empty keys as ignored by AbstractPrimitive#put, but it causes problems to fix existing data 573 // Drop the tag on import, but flag the primitive as modified 574 ((AbstractPrimitive) t).setModified(true); 575 } else { 576 t.put(key.intern(), value.intern()); 577 } 578 } 579 580 @FunctionalInterface 581 protected interface CommonReader { 582 /** 583 * Reads the common primitive attributes and sets them in {@code pd} 584 * @param pd primitive data to update 585 * @throws IllegalDataException in case of invalid data 586 */ 587 void accept(PrimitiveData pd) throws IllegalDataException; 588 } 589 590 @FunctionalInterface 591 protected interface NodeReader { 592 /** 593 * Reads the node tags. 594 * @param n node 595 * @throws IllegalDataException in case of invalid data 596 */ 597 void accept(NodeData n) throws IllegalDataException; 598 } 599 600 @FunctionalInterface 601 protected interface WayReader { 602 /** 603 * Reads the way nodes and tags. 604 * @param w way 605 * @param nodeIds collection of resulting node ids 606 * @throws IllegalDataException in case of invalid data 607 */ 608 void accept(WayData w, Collection<Long> nodeIds) throws IllegalDataException; 609 } 610 611 @FunctionalInterface 612 protected interface RelationReader { 613 /** 614 * Reads the relation members and tags. 615 * @param r relation 616 * @param members collection of resulting members 617 * @throws IllegalDataException in case of invalid data 618 */ 619 void accept(RelationData r, Collection<RelationMemberData> members) throws IllegalDataException; 620 } 621 622 private static boolean areLatLonDefined(String lat, String lon) { 623 return lat != null && lon != null; 624 } 625 626 private static boolean areLatLonDefined(double lat, double lon) { 627 return !Double.isNaN(lat) && !Double.isNaN(lon); 628 } 629 630 protected OsmPrimitive buildPrimitive(PrimitiveData pd) { 631 OsmPrimitive p; 632 if (pd.getUniqueId() < pd.getIdGenerator().currentUniqueId()) { 633 p = pd.getType().newInstance(pd.getUniqueId(), true); 634 pd.getIdGenerator().advanceUniqueId(pd.getUniqueId()); 635 } else { 636 p = pd.getType().newVersionedInstance(pd.getId(), pd.getVersion()); 637 } 638 p.setVisible(pd.isVisible()); 639 p.load(pd); 640 externalIdMap.put(pd.getPrimitiveId(), p); 641 return p; 642 } 643 644 private Node addNode(NodeData nd, NodeReader nodeReader) throws IllegalDataException { 645 nodeReader.accept(nd); 646 return (Node) buildPrimitive(nd); 647 } 648 649 protected final Node parseNode(double lat, double lon, CommonReader commonReader, NodeReader nodeReader) 650 throws IllegalDataException { 651 NodeData nd = new NodeData(0); 652 LatLon ll = null; 653 if (areLatLonDefined(lat, lon)) { 654 try { 655 ll = new LatLon(lat, lon); 656 nd.setCoor(ll); 657 } catch (NumberFormatException e) { 658 Logging.trace(e); 659 } 660 } 661 commonReader.accept(nd); 662 if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) { 663 throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.", 664 Long.toString(nd.getId()), lat, lon)); 665 } 666 return addNode(nd, nodeReader); 667 } 668 669 protected final Node parseNode(String lat, String lon, CommonReader commonReader, NodeReader nodeReader) 670 throws IllegalDataException { 671 NodeData nd = new NodeData(0); 672 LatLon ll = null; 673 if (areLatLonDefined(lat, lon)) { 674 try { 675 ll = new LatLon(Double.parseDouble(lat), Double.parseDouble(lon)); 676 nd.setCoor(ll); 677 } catch (NumberFormatException e) { 678 Logging.trace(e); 679 } 680 } 681 commonReader.accept(nd); 682 if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) { 683 throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.", 684 Long.toString(nd.getId()), lat, lon)); 685 } 686 return addNode(nd, nodeReader); 687 } 688 689 protected final Way parseWay(CommonReader commonReader, WayReader wayReader) throws IllegalDataException { 690 WayData wd = new WayData(0); 691 commonReader.accept(wd); 692 693 Collection<Long> nodeIds = new ArrayList<>(); 694 wayReader.accept(wd, nodeIds); 695 if (wd.isDeleted() && !nodeIds.isEmpty()) { 696 Logging.info(tr("Deleted way {0} contains nodes", Long.toString(wd.getUniqueId()))); 697 nodeIds = new ArrayList<>(); 698 } 699 ways.put(wd.getUniqueId(), nodeIds); 700 return (Way) buildPrimitive(wd); 701 } 702 703 protected final Relation parseRelation(CommonReader commonReader, RelationReader relationReader) throws IllegalDataException { 704 RelationData rd = new RelationData(0); 705 commonReader.accept(rd); 706 707 Collection<RelationMemberData> members = new ArrayList<>(); 708 relationReader.accept(rd, members); 709 if (rd.isDeleted() && !members.isEmpty()) { 710 Logging.info(tr("Deleted relation {0} contains members", Long.toString(rd.getUniqueId()))); 711 members = new ArrayList<>(); 712 } 713 relations.put(rd.getUniqueId(), members); 714 return (Relation) buildPrimitive(rd); 715 } 716 717 protected final RelationMemberData parseRelationMember(RelationData r, String ref, String type, String role) throws IllegalDataException { 718 if (ref == null) { 719 throw new IllegalDataException(tr("Missing attribute ''ref'' on member in relation {0}.", 720 Long.toString(r.getUniqueId()))); 721 } 722 try { 723 return parseRelationMember(r, Long.parseLong(ref), type, role); 724 } catch (NumberFormatException e) { 725 throw new IllegalDataException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", 726 Long.toString(r.getUniqueId()), ref), e); 727 } 728 } 729 730 protected final RelationMemberData parseRelationMember(RelationData r, long id, String type, String role) throws IllegalDataException { 731 if (id == 0) { 732 throw new IllegalDataException(tr("Incomplete <member> specification with ref=0")); 733 } 734 if (type == null) { 735 throw new IllegalDataException(tr("Missing attribute ''type'' on member {0} in relation {1}.", 736 Long.toString(id), Long.toString(r.getUniqueId()))); 737 } 738 try { 739 return new RelationMemberData(role, OsmPrimitiveType.fromApiTypeName(type), id); 740 } catch (IllegalArgumentException e) { 741 throw new IllegalDataException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", 742 Long.toString(id), Long.toString(r.getUniqueId()), type), e); 743 } 744 } 745}