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