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 if (minId.isPresent() && minId.getAsLong() < AbstractPrimitive.currentUniqueId()) { 330 AbstractPrimitive.advanceUniqueId(minId.getAsLong()); 331 } 332 progressMonitor.finishTask(); 333 progressMonitor.removeCancelListener(cancelListener); 334 } 335 } 336 337 protected final long getLong(String name, String value) throws IllegalDataException { 338 if (value == null) { 339 throw new IllegalDataException(tr("Missing required attribute ''{0}''.", name)); 340 } 341 try { 342 return Long.parseLong(value); 343 } catch (NumberFormatException e) { 344 throw new IllegalDataException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value), e); 345 } 346 } 347 348 protected final void parseVersion(String version) throws IllegalDataException { 349 validateVersion(version); 350 ds.setVersion(version); 351 } 352 353 private static void validateVersion(String version) throws IllegalDataException { 354 if (version == null) { 355 throw new IllegalDataException(tr("Missing mandatory attribute ''{0}''.", "version")); 356 } 357 if (!"0.6".equals(version)) { 358 throw new IllegalDataException(tr("Unsupported version: {0}", version)); 359 } 360 } 361 362 protected final void parseDownloadPolicy(String key, String downloadPolicy) throws IllegalDataException { 363 parsePolicy(key, downloadPolicy, policy -> ds.setDownloadPolicy(DownloadPolicy.of(policy))); 364 } 365 366 protected final void parseUploadPolicy(String key, String uploadPolicy) throws IllegalDataException { 367 parsePolicy(key, uploadPolicy, policy -> ds.setUploadPolicy(UploadPolicy.of(policy))); 368 } 369 370 private static void parsePolicy(String key, String policy, Consumer<String> consumer) throws IllegalDataException { 371 if (policy != null) { 372 try { 373 consumer.accept(policy); 374 } catch (IllegalArgumentException e) { 375 throw new IllegalDataException(MessageFormat.format( 376 "Illegal value for attribute ''{0}''. Got ''{1}''.", key, policy), e); 377 } 378 } 379 } 380 381 protected final void parseLocked(String locked) { 382 if ("true".equalsIgnoreCase(locked)) { 383 ds.lock(); 384 } 385 } 386 387 protected final void parseBounds(String generator, String minlon, String minlat, String maxlon, String maxlat, String origin) 388 throws IllegalDataException { 389 if (minlon != null && maxlon != null && minlat != null && maxlat != null) { 390 if (origin == null) { 391 origin = generator; 392 } 393 Bounds bounds = new Bounds( 394 Double.parseDouble(minlat), Double.parseDouble(minlon), 395 Double.parseDouble(maxlat), Double.parseDouble(maxlon)); 396 if (bounds.isOutOfTheWorld()) { 397 Bounds copy = new Bounds(bounds); 398 bounds.normalize(); 399 Logging.info("Bbox " + copy + " is out of the world, normalized to " + bounds); 400 } 401 ds.addDataSource(new DataSource(bounds, origin)); 402 } else { 403 throw new IllegalDataException(tr("Missing mandatory attributes on element ''bounds''. " + 404 "Got minlon=''{0}'',minlat=''{1}'',maxlon=''{2}'',maxlat=''{3}'', origin=''{4}''.", 405 minlon, minlat, maxlon, maxlat, origin 406 )); 407 } 408 } 409 410 protected final void parseId(PrimitiveData current, long id) throws IllegalDataException { 411 current.setId(id); 412 if (current.getUniqueId() == 0) { 413 throw new IllegalDataException(tr("Illegal object with ID=0.")); 414 } 415 } 416 417 protected final void parseTimestamp(PrimitiveData current, String time) { 418 if (time != null && !time.isEmpty()) { 419 current.setRawTimestamp((int) (DateUtils.tsFromString(time)/1000)); 420 } 421 } 422 423 private static User createUser(String uid, String name) throws IllegalDataException { 424 if (uid == null) { 425 if (name == null) 426 return null; 427 return User.createLocalUser(name); 428 } 429 try { 430 return User.createOsmUser(Long.parseLong(uid), name); 431 } catch (NumberFormatException e) { 432 throw new IllegalDataException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid), e); 433 } 434 } 435 436 protected final void parseUser(PrimitiveData current, String user, long uid) { 437 current.setUser(User.createOsmUser(uid, user)); 438 } 439 440 protected final void parseUser(PrimitiveData current, String user, String uid) throws IllegalDataException { 441 current.setUser(createUser(uid, user)); 442 } 443 444 protected final void parseVisible(PrimitiveData current, String visible) { 445 if (visible != null) { 446 current.setVisible(Boolean.parseBoolean(visible)); 447 } 448 } 449 450 protected final void parseVersion(PrimitiveData current, String versionString) throws IllegalDataException { 451 int version = 0; 452 if (versionString != null) { 453 try { 454 version = Integer.parseInt(versionString); 455 } catch (NumberFormatException e) { 456 throw new IllegalDataException( 457 tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 458 Long.toString(current.getUniqueId()), versionString), e); 459 } 460 parseVersion(current, version); 461 } else { 462 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6 463 if (!current.isNew() && ds.getVersion() != null && "0.6".equals(ds.getVersion())) { 464 throw new IllegalDataException( 465 tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId()))); 466 } 467 } 468 } 469 470 protected final void parseVersion(PrimitiveData current, int version) throws IllegalDataException { 471 switch (ds.getVersion()) { 472 case "0.6": 473 if (version <= 0 && !current.isNew()) { 474 throw new IllegalDataException( 475 tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 476 Long.toString(current.getUniqueId()), version)); 477 } else if (version < 0 && current.isNew()) { 478 Logging.warn(tr("Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", 479 current.getUniqueId(), version, 0, "0.6")); 480 version = 0; 481 } 482 break; 483 default: 484 // should not happen. API version has been checked before 485 throw new IllegalDataException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion())); 486 } 487 current.setVersion(version); 488 } 489 490 protected final void parseAction(PrimitiveData current, String action) { 491 if (action == null) { 492 // do nothing 493 } else if ("delete".equals(action)) { 494 current.setDeleted(true); 495 current.setModified(current.isVisible()); 496 } else if ("modify".equals(action)) { 497 current.setModified(true); 498 } 499 } 500 501 private static void handleIllegalChangeset(PrimitiveData current, IllegalArgumentException e, Object v) 502 throws IllegalDataException { 503 Logging.debug(e.getMessage()); 504 if (current.isNew()) { 505 // for a new primitive we just log a warning 506 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 507 v, current.getUniqueId())); 508 current.setChangesetId(0); 509 } else { 510 // for an existing primitive this is a problem 511 throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v), e); 512 } 513 } 514 515 protected final void parseChangeset(PrimitiveData current, String v) throws IllegalDataException { 516 if (v == null) { 517 current.setChangesetId(0); 518 } else { 519 try { 520 parseChangeset(current, Integer.parseInt(v)); 521 } catch (NumberFormatException e) { 522 handleIllegalChangeset(current, e, v); 523 } 524 } 525 } 526 527 protected final void parseChangeset(PrimitiveData current, int v) throws IllegalDataException { 528 try { 529 current.setChangesetId(v); 530 } catch (IllegalArgumentException e) { 531 handleIllegalChangeset(current, e, v); 532 } catch (IllegalStateException e) { 533 // thrown for positive changeset id on new primitives 534 Logging.debug(e); 535 Logging.info(e.getMessage()); 536 current.setChangesetId(0); 537 } 538 if (current.getChangesetId() <= 0) { 539 if (current.isNew()) { 540 // for a new primitive we just log a warning 541 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 542 v, current.getUniqueId())); 543 current.setChangesetId(0); 544 } else if (current.getChangesetId() < 0) { 545 // for an existing primitive this is a problem only for negative ids (GDPR extracts are set to 0) 546 throw new IllegalDataException(tr("Illegal value for attribute ''changeset''. Got {0}.", v)); 547 } 548 } 549 } 550 551 protected final void parseTag(Tagged t, String key, String value) throws IllegalDataException { 552 if (key == null || value == null) { 553 throw new IllegalDataException(tr("Missing key or value attribute in tag.")); 554 } else if (Utils.isStripEmpty(key) && t instanceof AbstractPrimitive) { 555 // #14199: Empty keys as ignored by AbstractPrimitive#put, but it causes problems to fix existing data 556 // Drop the tag on import, but flag the primitive as modified 557 ((AbstractPrimitive) t).setModified(true); 558 } else { 559 t.put(key.intern(), value.intern()); 560 } 561 } 562 563 @FunctionalInterface 564 protected interface CommonReader { 565 /** 566 * Reads the common primitive attributes and sets them in {@code pd} 567 * @param pd primitive data to update 568 * @throws IllegalDataException in case of invalid data 569 */ 570 void accept(PrimitiveData pd) throws IllegalDataException; 571 } 572 573 @FunctionalInterface 574 protected interface NodeReader { 575 /** 576 * Reads the node tags. 577 * @param n node 578 * @throws IllegalDataException in case of invalid data 579 */ 580 void accept(Node n) throws IllegalDataException; 581 } 582 583 @FunctionalInterface 584 protected interface WayReader { 585 /** 586 * Reads the way nodes and tags. 587 * @param w way 588 * @param nodeIds collection of resulting node ids 589 * @throws IllegalDataException in case of invalid data 590 */ 591 void accept(Way w, Collection<Long> nodeIds) throws IllegalDataException; 592 } 593 594 @FunctionalInterface 595 protected interface RelationReader { 596 /** 597 * Reads the relation members and tags. 598 * @param r relation 599 * @param members collection of resulting members 600 * @throws IllegalDataException in case of invalid data 601 */ 602 void accept(Relation r, Collection<RelationMemberData> members) throws IllegalDataException; 603 } 604 605 private static boolean areLatLonDefined(String lat, String lon) { 606 return lat != null && lon != null; 607 } 608 609 private static boolean areLatLonDefined(double lat, double lon) { 610 return !Double.isNaN(lat) && !Double.isNaN(lon); 611 } 612 613 protected OsmPrimitive buildPrimitive(PrimitiveData pd) { 614 OsmPrimitive p; 615 if (pd.getUniqueId() < AbstractPrimitive.currentUniqueId()) { 616 p = pd.getType().newInstance(pd.getUniqueId(), true); 617 AbstractPrimitive.advanceUniqueId(pd.getUniqueId()); 618 } else { 619 p = pd.getType().newVersionedInstance(pd.getId(), pd.getVersion()); 620 } 621 p.setVisible(pd.isVisible()); 622 p.load(pd); 623 externalIdMap.put(pd.getPrimitiveId(), p); 624 return p; 625 } 626 627 private Node addNode(NodeData nd, NodeReader nodeReader) throws IllegalDataException { 628 Node n = (Node) buildPrimitive(nd); 629 nodeReader.accept(n); 630 return n; 631 } 632 633 protected final Node parseNode(double lat, double lon, CommonReader commonReader, NodeReader nodeReader) 634 throws IllegalDataException { 635 NodeData nd = new NodeData(0); 636 LatLon ll = null; 637 if (areLatLonDefined(lat, lon)) { 638 try { 639 ll = new LatLon(lat, lon); 640 nd.setCoor(ll); 641 } catch (NumberFormatException e) { 642 Logging.trace(e); 643 } 644 } 645 commonReader.accept(nd); 646 if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) { 647 throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.", 648 Long.toString(nd.getId()), lat, lon)); 649 } 650 return addNode(nd, nodeReader); 651 } 652 653 protected final Node parseNode(String lat, String lon, CommonReader commonReader, NodeReader nodeReader) 654 throws IllegalDataException { 655 NodeData nd = new NodeData(); 656 LatLon ll = null; 657 if (areLatLonDefined(lat, lon)) { 658 try { 659 ll = new LatLon(Double.parseDouble(lat), Double.parseDouble(lon)); 660 nd.setCoor(ll); 661 } catch (NumberFormatException e) { 662 Logging.trace(e); 663 } 664 } 665 commonReader.accept(nd); 666 if (areLatLonDefined(lat, lon) && (ll == null || !ll.isValid())) { 667 throw new IllegalDataException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.", 668 Long.toString(nd.getId()), lat, lon)); 669 } 670 return addNode(nd, nodeReader); 671 } 672 673 protected final Way parseWay(CommonReader commonReader, WayReader wayReader) throws IllegalDataException { 674 WayData wd = new WayData(0); 675 commonReader.accept(wd); 676 Way w = (Way) buildPrimitive(wd); 677 678 Collection<Long> nodeIds = new ArrayList<>(); 679 wayReader.accept(w, nodeIds); 680 if (w.isDeleted() && !nodeIds.isEmpty()) { 681 Logging.info(tr("Deleted way {0} contains nodes", Long.toString(w.getUniqueId()))); 682 nodeIds = new ArrayList<>(); 683 } 684 ways.put(wd.getUniqueId(), nodeIds); 685 return w; 686 } 687 688 protected final Relation parseRelation(CommonReader commonReader, RelationReader relationReader) throws IllegalDataException { 689 RelationData rd = new RelationData(0); 690 commonReader.accept(rd); 691 Relation r = (Relation) buildPrimitive(rd); 692 693 Collection<RelationMemberData> members = new ArrayList<>(); 694 relationReader.accept(r, members); 695 if (r.isDeleted() && !members.isEmpty()) { 696 Logging.info(tr("Deleted relation {0} contains members", Long.toString(r.getUniqueId()))); 697 members = new ArrayList<>(); 698 } 699 relations.put(rd.getUniqueId(), members); 700 return r; 701 } 702 703 protected final RelationMemberData parseRelationMember(Relation r, String ref, String type, String role) throws IllegalDataException { 704 if (ref == null) { 705 throw new IllegalDataException(tr("Missing attribute ''ref'' on member in relation {0}.", 706 Long.toString(r.getUniqueId()))); 707 } 708 try { 709 return parseRelationMember(r, Long.parseLong(ref), type, role); 710 } catch (NumberFormatException e) { 711 throw new IllegalDataException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", 712 Long.toString(r.getUniqueId()), ref), e); 713 } 714 } 715 716 protected final RelationMemberData parseRelationMember(Relation r, long id, String type, String role) throws IllegalDataException { 717 if (id == 0) { 718 throw new IllegalDataException(tr("Incomplete <member> specification with ref=0")); 719 } 720 if (type == null) { 721 throw new IllegalDataException(tr("Missing attribute ''type'' on member {0} in relation {1}.", 722 Long.toString(id), Long.toString(r.getUniqueId()))); 723 } 724 try { 725 return new RelationMemberData(role, OsmPrimitiveType.fromApiTypeName(type), id); 726 } catch (IllegalArgumentException e) { 727 throw new IllegalDataException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", 728 Long.toString(id), Long.toString(r.getUniqueId()), type), e); 729 } 730 } 731}