001/* 002 * Java GPX Library (jpx-3.1.0). 003 * Copyright (c) 2016-2023 Franz Wilhelmstötter 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 * Author: 018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) 019 */ 020package io.jenetics.jpx; 021 022import static java.util.Objects.hash; 023import static java.util.Objects.requireNonNull; 024import static io.jenetics.jpx.Format.toDurationString; 025import static io.jenetics.jpx.Format.toIntString; 026import static io.jenetics.jpx.Length.Unit.METER; 027import static io.jenetics.jpx.Lists.copyOf; 028import static io.jenetics.jpx.Lists.copyTo; 029import static io.jenetics.jpx.Speed.Unit.METERS_PER_SECOND; 030 031import java.io.DataInput; 032import java.io.DataOutput; 033import java.io.IOException; 034import java.io.InvalidObjectException; 035import java.io.ObjectInputStream; 036import java.io.Serial; 037import java.io.Serializable; 038import java.net.URI; 039import java.time.Duration; 040import java.time.Instant; 041import java.util.ArrayList; 042import java.util.List; 043import java.util.Objects; 044import java.util.Optional; 045import java.util.function.Function; 046 047import org.w3c.dom.Document; 048 049import io.jenetics.jpx.GPX.Version; 050 051/** 052 * A {@code WayPoint} represents a way-point, point of interest, or named 053 * feature on a map. 054 * <p> 055 * Creating a {@code WayPoint}: 056 * <pre>{@code 057 * final WayPoint point = WayPoint.builder() 058 * .lat(48.2081743).lon(16.3738189).ele(160) 059 * .build(); 060 * }</pre> 061 * 062 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 063 * @version 3.0 064 * @since 1.0 065 */ 066public final class WayPoint implements Point, Serializable { 067 068 @Serial 069 private static final long serialVersionUID = 2L; 070 071 private final Latitude _latitude; 072 private final Longitude _longitude; 073 074 private final Length _elevation; 075 private final Speed _speed; 076 private final Instant _time; 077 private final Degrees _magneticVariation; 078 private final Length _geoidHeight; 079 private final String _name; 080 private final String _comment; 081 private final String _description; 082 private final String _source; 083 private final List<Link> _links; 084 private final String _symbol; 085 private final String _type; 086 private final Fix _fix; 087 private final UInt _sat; 088 private final Double _hdop; 089 private final Double _vdop; 090 private final Double _pdop; 091 private final Duration _ageOfGPSData; 092 private final DGPSStation _dgpsID; 093 private final Degrees _course; 094 private final Document _extensions; 095 096 /** 097 * Create a new way-point with the given parameter. 098 * 099 * @param latitude the latitude of the point, WGS84 datum (mandatory) 100 * @param longitude the longitude of the point, WGS84 datum (mandatory) 101 * @param elevation the elevation (in meters) of the point (optional) 102 * @param speed the current GPS speed (optional) 103 * @param time creation/modification timestamp for element. Conforms to ISO 104 * 8601 specification for date/time representation. Fractional seconds 105 * are allowed for millisecond timing in tracklogs. (optional) 106 * @param magneticVariation the magnetic variation at the point (optional) 107 * @param geoidHeight height (in meters) of geoid (mean sea level) above 108 * WGS84 earth ellipsoid. As defined in NMEA GGA message. (optional) 109 * @param name the GPS name of the way-point. This field will be transferred 110 * to and from the GPS. GPX does not place restrictions on the length 111 * of this field or the characters contained in it. It is up to the 112 * receiving application to validate the field before sending it to 113 * the GPS. (optional) 114 * @param comment GPS way-point comment. Sent to GPS as comment (optional) 115 * @param description a text description of the element. Holds additional 116 * information about the element intended for the user, not the GPS. 117 * (optional) 118 * @param source source of data. Included to give user some idea of 119 * reliability and accuracy of data. "Garmin eTrex", "USGS quad 120 * Boston North", e.g. (optional) 121 * @param links links to additional information about the way-point. May be 122 * empty, but not {@code null}. 123 * @param symbol text of GPS symbol name. For interchange with other 124 * programs, use the exact spelling of the symbol as displayed on the 125 * GPS. If the GPS abbreviates words, spell them out. (optional) 126 * @param type type (classification) of the way-point (optional) 127 * @param fix type of GPX fix (optional) 128 * @param sat number of satellites used to calculate the GPX fix (optional) 129 * @param hdop horizontal dilution of precision (optional) 130 * @param vdop vertical dilution of precision (optional) 131 * @param pdop position dilution of precision. (optional) 132 * @param ageOfGPSData number of seconds since last DGPS update (optional) 133 * @param dgpsID ID of DGPS station used in differential correction (optional) 134 * @param course the Instantaneous course at the point 135 * @param extensions the XML extensions document 136 * @throws NullPointerException if the {@code latitude} or {@code longitude} 137 * is {@code null} 138 */ 139 private WayPoint( 140 final Latitude latitude, 141 final Longitude longitude, 142 final Length elevation, 143 final Speed speed, 144 final Instant time, 145 final Degrees magneticVariation, 146 final Length geoidHeight, 147 final String name, 148 final String comment, 149 final String description, 150 final String source, 151 final List<Link> links, 152 final String symbol, 153 final String type, 154 final Fix fix, 155 final UInt sat, 156 final Double hdop, 157 final Double vdop, 158 final Double pdop, 159 final Duration ageOfGPSData, 160 final DGPSStation dgpsID, 161 final Degrees course, 162 final Document extensions 163 ) { 164 _latitude = requireNonNull(latitude); 165 _longitude = requireNonNull(longitude); 166 167 _elevation = elevation; 168 _speed = speed; 169 _time = time; 170 _magneticVariation = magneticVariation; 171 _geoidHeight = geoidHeight; 172 _name = name; 173 _comment = comment; 174 _description = description; 175 _source = source; 176 _links = copyOf(links); 177 _symbol = symbol; 178 _type = type; 179 _fix = fix; 180 _sat = sat; 181 _hdop = hdop; 182 _vdop = vdop; 183 _pdop = pdop; 184 _ageOfGPSData = ageOfGPSData; 185 _dgpsID = dgpsID; 186 _course = course; 187 _extensions = extensions; 188 } 189 190 @Override 191 public Latitude getLatitude() { 192 return _latitude; 193 } 194 195 @Override 196 public Longitude getLongitude() { 197 return _longitude; 198 } 199 200 @Override 201 public Optional<Length> getElevation() { 202 return Optional.ofNullable(_elevation); 203 } 204 205 /** 206 * The current GPS speed. 207 * 208 * @return the current GPS speed 209 */ 210 public Optional<Speed> getSpeed() { 211 return Optional.ofNullable(_speed); 212 } 213 214 @Override 215 public Optional<Instant> getTime() { 216 return Optional.ofNullable(_time); 217 } 218 219 /** 220 * The magnetic variation at the point. 221 * 222 * @return the magnetic variation at the point 223 */ 224 public Optional<Degrees> getMagneticVariation() { 225 return Optional.ofNullable(_magneticVariation); 226 } 227 228 /** 229 * The height (in meters) of geoid (mean sea level) above WGS84 earth 230 * ellipsoid. As defined in NMEA GGA message. 231 * 232 * @return the height (in meters) of geoid (mean sea level) above WGS84 233 * earth ellipsoid 234 */ 235 public Optional<Length> getGeoidHeight() { 236 return Optional.ofNullable(_geoidHeight); 237 } 238 239 /** 240 * The GPS name of the way-point. This field will be transferred to and from 241 * the GPS. GPX does not place restrictions on the length of this field or 242 * the characters contained in it. It is up to the receiving application to 243 * validate the field before sending it to the GPS. 244 * 245 * @return the GPS name of the way-point 246 */ 247 public Optional<String> getName() { 248 return Optional.ofNullable(_name); 249 } 250 251 /** 252 * The GPS way-point comment. 253 * 254 * @return the GPS way-point comment 255 */ 256 public Optional<String> getComment() { 257 return Optional.ofNullable(_comment); 258 } 259 260 /** 261 * Return a text description of the element. Holds additional information 262 * about the element intended for the user, not the GPS. 263 * 264 * @return a text description of the element 265 */ 266 public Optional<String> getDescription() { 267 return Optional.ofNullable(_description); 268 } 269 270 /** 271 * Return the source of data. Included to give user some idea of reliability 272 * and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g. 273 * 274 * @return the source of the data 275 */ 276 public Optional<String> getSource() { 277 return Optional.ofNullable(_source); 278 } 279 280 /** 281 * Return the links to additional information about the way-point. 282 * 283 * @return the links to additional information about the way-point 284 */ 285 public List<Link> getLinks() { 286 return _links; 287 } 288 289 /** 290 * Return the text of GPS symbol name. For interchange with other programs, 291 * use the exact spelling of the symbol as displayed on the GPS. If the GPS 292 * abbreviates words, spell them out. 293 * 294 * @return the text of GPS symbol name 295 */ 296 public Optional<String> getSymbol() { 297 return Optional.ofNullable(_symbol); 298 } 299 300 /** 301 * Return the type (classification) of the way-point. 302 * 303 * @return the type (classification) of the way-point 304 */ 305 public Optional<String> getType() { 306 return Optional.ofNullable(_type); 307 } 308 309 /** 310 * Return the type of GPX fix. 311 * 312 * @return the type of GPX fix 313 */ 314 public Optional<Fix> getFix() { 315 return Optional.ofNullable(_fix); 316 } 317 318 /** 319 * Return the number of satellites used to calculate the GPX fix. 320 * 321 * @return the number of satellites used to calculate the GPX fix 322 */ 323 public Optional<UInt> getSat() { 324 return Optional.ofNullable(_sat); 325 } 326 327 /** 328 * Return the horizontal dilution of precision. 329 * 330 * @return the horizontal dilution of precision 331 */ 332 public Optional<Double> getHdop() { 333 return Optional.ofNullable(_hdop); 334 } 335 336 /** 337 * Return the vertical dilution of precision. 338 * 339 * @return the vertical dilution of precision 340 */ 341 public Optional<Double> getVdop() { 342 return Optional.ofNullable(_vdop); 343 } 344 345 /** 346 * Return the position dilution of precision. 347 * 348 * @return the position dilution of precision 349 */ 350 public Optional<Double> getPdop() { 351 return Optional.ofNullable(_pdop); 352 } 353 354 /** 355 * Return the number of seconds since last DGPS update. 356 * 357 * @return number of seconds since last DGPS update 358 */ 359 public Optional<Duration> getAgeOfGPSData() { 360 return Optional.ofNullable(_ageOfGPSData); 361 } 362 363 /** 364 * Return the ID of DGPS station used in differential correction. 365 * 366 * @return the ID of DGPS station used in differential correction 367 */ 368 public Optional<DGPSStation> getDGPSID() { 369 return Optional.ofNullable(_dgpsID); 370 } 371 372 /** 373 * Return the instantaneous course at the point. This property is only 374 * available when you read GPX files version 1.0. In version 1.1 this field 375 * is always {@link Optional#empty()}. 376 * 377 * @since 1.3 378 * 379 * @return the instantaneous course at the point 380 */ 381 public Optional<Degrees> getCourse() { 382 return Optional.ofNullable(_course); 383 } 384 385 /** 386 * Return the (cloned) extensions document. The root element of the returned 387 * document has the name {@code extensions}. 388 * <pre>{@code 389 * <extensions> 390 * ... 391 * </extensions> 392 * }</pre> 393 * 394 * @since 1.5 395 * 396 * @return the extensions document 397 * @throws org.w3c.dom.DOMException if the document could not be cloned, 398 * because of an erroneous XML configuration 399 */ 400 public Optional<Document> getExtensions() { 401 return Optional.ofNullable(_extensions).map(XML::clone); 402 } 403 404 /** 405 * Convert the <em>immutable</em> way-point object into a <em>mutable</em> 406 * builder initialized with the current way-point values. 407 * 408 * @since 1.1 409 * 410 * @return a new way-point builder initialized with the values of {@code this} 411 * way-point 412 */ 413 public Builder toBuilder() { 414 return builder() 415 .lat(_latitude) 416 .lon(_longitude) 417 .ele(_elevation) 418 .speed(_speed) 419 .time(_time) 420 .magvar(_magneticVariation) 421 .geoidheight(_geoidHeight) 422 .name(_name) 423 .cmt(_comment) 424 .desc(_description) 425 .src(_source) 426 .links(_links) 427 .sym(_symbol) 428 .type(_type) 429 .fix(_fix) 430 .sat(_sat) 431 .hdop(_hdop) 432 .vdop(_vdop) 433 .pdop(_pdop) 434 .ageofdgpsdata(_ageOfGPSData) 435 .dgpsid(_dgpsID) 436 .course(_course) 437 .extensions(_extensions); 438 } 439 440 @Override 441 public int hashCode() { 442 return hash( 443 _latitude, 444 _longitude, 445 _elevation, 446 _speed, 447 Objects.hashCode(_time), 448 _magneticVariation, 449 _geoidHeight, 450 _name, 451 _comment, 452 _description, 453 _source, 454 Lists.hashCode(_links), 455 _symbol, 456 _type, 457 _fix, 458 _sat, 459 _hdop, 460 _vdop, 461 _pdop, 462 _ageOfGPSData, 463 _dgpsID, 464 _course 465 ); 466 } 467 468 @Override 469 public boolean equals(final Object obj) { 470 return obj == this || 471 obj instanceof WayPoint wp && 472 Objects.equals(wp._latitude, _latitude) && 473 Objects.equals(wp._longitude, _longitude) && 474 Objects.equals(wp._elevation, _elevation) && 475 Objects.equals(wp._speed, _speed) && 476 Objects.equals(wp._time, _time) && 477 Objects.equals(wp._magneticVariation, _magneticVariation) && 478 Objects.equals(wp._geoidHeight, _geoidHeight) && 479 Objects.equals(wp._name, _name) && 480 Objects.equals(wp._comment, _comment) && 481 Objects.equals(wp._description, _description) && 482 Objects.equals(wp._source, _source) && 483 Lists.equals(wp._links, _links) && 484 Objects.equals(wp._symbol, _symbol) && 485 Objects.equals(wp._type, _type) && 486 Objects.equals(wp._fix, _fix) && 487 Objects.equals(wp._sat, _sat) && 488 Objects.equals(wp._hdop, _hdop) && 489 Objects.equals(wp._vdop, _vdop) && 490 Objects.equals(wp._pdop, _pdop) && 491 Objects.equals(wp._ageOfGPSData, _ageOfGPSData) && 492 Objects.equals(wp._dgpsID, _dgpsID) && 493 Objects.equals(wp._course, _course); 494 } 495 496 @Override 497 public String toString() { 498 return _elevation != null 499 ? String.format("[lat=%s, lon=%s, ele=%s]", 500 _latitude, _longitude, _elevation) 501 : String.format("[lat=%s, lon=%s]", 502 _latitude, _longitude); 503 } 504 505 506 /** 507 * Builder for creating a way-point with different parameters. 508 * <p> 509 * Creating a {@code WayPoint}: 510 * <pre>{@code 511 * final WayPoint point = WayPoint.builder() 512 * .lat(48.2081743).lon(16.3738189).ele(160) 513 * .build(); 514 * }</pre> 515 * 516 * @see #builder() 517 */ 518 public static final class Builder { 519 private Latitude _latitude; 520 private Longitude _longitude; 521 522 private Length _elevation; 523 private Speed _speed; 524 private Instant _time; 525 private Degrees _magneticVariation; 526 private Length _geoidHeight; 527 private String _name; 528 private String _comment; 529 private String _description; 530 private String _source; 531 private final List<Link> _links = new ArrayList<>(); 532 private String _symbol; 533 private String _type; 534 private Fix _fix; 535 private UInt _sat; 536 private Double _hdop; 537 private Double _vdop; 538 private Double _pdop; 539 private Duration _ageOfDGPSData; 540 private DGPSStation _dgpsID; 541 private Degrees _course; 542 private Document _extensions; 543 544 private Builder() { 545 } 546 547 /** 548 * Set the latitude value of the way-point. 549 * 550 * @param latitude the new latitude value 551 * @return {@code this} {@code Builder} for method chaining 552 * @throws NullPointerException if the given value is {@code null} 553 */ 554 public Builder lat(final Latitude latitude) { 555 _latitude = requireNonNull(latitude); 556 return this; 557 } 558 559 /** 560 * Set the latitude value of the way-point. 561 * 562 * @param degrees the new latitude value 563 * @return {@code this} {@code Builder} for method chaining 564 * @throws IllegalArgumentException if the given value is not within the 565 * range of {@code [-90..90]} 566 */ 567 public Builder lat(final double degrees) { 568 return lat(Latitude.ofDegrees(degrees)); 569 } 570 571 /** 572 * Return the current latitude value. 573 * 574 * @since 1.1 575 * 576 * @return the current latitude value 577 */ 578 public Latitude lat() { 579 return _latitude; 580 } 581 582 /** 583 * Set the longitude value of the way-point. 584 * 585 * @param longitude the new longitude value 586 * @return {@code this} {@code Builder} for method chaining 587 * @throws NullPointerException if the given value is {@code null} 588 */ 589 public Builder lon(final Longitude longitude) { 590 _longitude = requireNonNull(longitude); 591 return this; 592 } 593 594 /** 595 * Set the longitude value of the way-point. 596 * 597 * @param degrees the new longitude value 598 * @return {@code this} {@code Builder} for method chaining 599 * @throws IllegalArgumentException if the given value is not within the 600 * range of {@code [-180..180]} 601 */ 602 public Builder lon(final double degrees) { 603 return lon(Longitude.ofDegrees(degrees)); 604 } 605 606 /** 607 * Return the current longitude value. 608 * 609 * @since 1.1 610 * 611 * @return the current longitude value 612 */ 613 public Longitude lon() { 614 return _longitude; 615 } 616 617 /** 618 * Set the elevation of the point. 619 * 620 * @param elevation the elevation of the point 621 * @return {@code this} {@code Builder} for method chaining 622 */ 623 public Builder ele(final Length elevation) { 624 _elevation = elevation; 625 return this; 626 } 627 628 /** 629 * Set the elevation (in meters) of the point. 630 * 631 * @param meters the elevation of the point, in meters 632 * @return {@code this} {@code Builder} for method chaining 633 */ 634 public Builder ele(final double meters) { 635 _elevation = Length.of(meters, METER); 636 return this; 637 } 638 639 /** 640 * Set the elevation of the point. 641 * 642 * @param elevation the elevation of the point 643 * @param unit the length unit 644 * @return {@code this} {@code Builder} for method chaining 645 */ 646 public Builder ele(final double elevation, final Length.Unit unit) { 647 _elevation = Length.of(elevation, unit); 648 return this; 649 } 650 651 /** 652 * Return the current elevation value. 653 * 654 * @since 1.1 655 * 656 * @return the current elevation value 657 */ 658 public Optional<Length> ele() { 659 return Optional.ofNullable(_elevation); 660 } 661 662 /** 663 * Set the current GPS speed. 664 * 665 * @param speed the current GPS speed 666 * @return {@code this} {@code Builder} for method chaining 667 */ 668 public Builder speed(final Speed speed) { 669 _speed = speed; 670 return this; 671 } 672 673 /** 674 * Set the current GPS speed 675 * 676 * @param speed the current speed value 677 * @param unit the speed unit 678 * @return {@code this} {@code Builder} for method chaining 679 */ 680 public Builder speed(final double speed, final Speed.Unit unit) { 681 return speed(Speed.of(speed, unit)); 682 } 683 684 /** 685 * Set the current GPS speed. 686 * 687 * @param meterPerSecond the current GPS speed in m/s 688 * @return {@code this} {@code Builder} for method chaining 689 */ 690 public Builder speed(final double meterPerSecond) { 691 _speed = Speed.of(meterPerSecond, METERS_PER_SECOND); 692 return this; 693 } 694 695 /** 696 * Return the current speed value. 697 * 698 * @since 1.1 699 * 700 * @return the current speed value 701 */ 702 public Optional<Speed> speed() { 703 return Optional.ofNullable(_speed); 704 } 705 706 /** 707 * Set the creation/modification timestamp for the point. 708 * 709 * @param instant the instant of the way-point 710 * @return {@code this} {@code Builder} for method chaining 711 */ 712 public Builder time(final Instant instant) { 713 _time = instant; 714 return this; 715 } 716 717 /** 718 * Set the creation/modification timestamp for the point. 719 * 720 * @param millis the instant of the way-point 721 * @return {@code this} {@code Builder} for method chaining 722 */ 723 public Builder time(final long millis) { 724 _time = Instant.ofEpochMilli(millis); 725 return this; 726 } 727 728 /** 729 * Return the current time value. 730 * 731 * @return the current time value 732 */ 733 public Optional<Instant> time() { 734 return Optional.ofNullable(_time); 735 } 736 737 /** 738 * Set the magnetic variation at the point. 739 * 740 * @param variation the magnetic variation 741 * @return {@code this} {@code Builder} for method chaining 742 */ 743 public Builder magvar(final Degrees variation) { 744 _magneticVariation = variation; 745 return this; 746 } 747 748 /** 749 * Set the magnetic variation at the point. 750 * 751 * @param degree the magnetic variation 752 * @return {@code this} {@code Builder} for method chaining 753 * @throws IllegalArgumentException if the give value is not within the 754 * range of {@code [0..360]} 755 */ 756 public Builder magvar(final double degree) { 757 _magneticVariation = Degrees.ofDegrees(degree); 758 return this; 759 } 760 761 /** 762 * Return the current magnetic variation value. 763 * 764 * @since 1.1 765 * 766 * @return the current magnetic variation value 767 */ 768 public Optional<Degrees> magvar() { 769 return Optional.ofNullable(_magneticVariation); 770 } 771 772 /** 773 * Set the height (in meters) of geoid (mean sea level) above WGS84 earth 774 * ellipsoid. As defined in NMEA GGA message. 775 * 776 * @param height the height (in meters) of geoid (mean sea level) 777 * above WGS84 earth ellipsoid 778 * @return {@code this} {@code Builder} for method chaining 779 */ 780 public Builder geoidheight(final Length height) { 781 _geoidHeight = height; 782 return this; 783 } 784 785 /** 786 * Set the height (in meters) of geoid (mean sea level) above WGS84 earth 787 * ellipsoid. As defined in NMEA GGA message. 788 * 789 * @param meter the height (in meters) of geoid (mean sea level) 790 * above WGS84 earth ellipsoid 791 * @return {@code this} {@code Builder} for method chaining 792 */ 793 public Builder geoidheight(final double meter) { 794 _geoidHeight = Length.of(meter, METER); 795 return this; 796 } 797 798 /** 799 * Set the height of geoid (mean sea level) above WGS84 earth ellipsoid. 800 * As defined in NMEA GGA message. 801 * 802 * @param length the height of geoid (mean sea level) above WGS84 earth 803 * ellipsoid 804 * @param unit the length unit 805 * @return {@code this} {@code Builder} for method chaining 806 */ 807 public Builder geoidheight(final double length, Length.Unit unit) { 808 _geoidHeight = Length.of(length, unit); 809 return this; 810 } 811 812 /** 813 * Return the current height of geoid value. 814 * 815 * @since 1.1 816 * 817 * @return the current height of geoid value 818 */ 819 public Optional<Length> geoidheight() { 820 return Optional.ofNullable(_geoidHeight); 821 } 822 823 /** 824 * Set the GPS name of the way-point. This field will be transferred to 825 * and from the GPS. GPX does not place restrictions on the length of 826 * this field or the characters contained in it. It is up to the 827 * receiving application to validate the field before sending it to the 828 * GPS. 829 * 830 * @param name the GPS name of the way-point 831 * @return {@code this} {@code Builder} for method chaining 832 */ 833 public Builder name(final String name) { 834 _name = name; 835 return this; 836 } 837 838 /** 839 * Return the current name value. 840 * 841 * @since 1.1 842 * 843 * @return the current name value 844 */ 845 public Optional<String> name() { 846 return Optional.ofNullable(_name); 847 } 848 849 /** 850 * Set the GPS way-point comment. 851 * 852 * @param comment the GPS way-point comment. 853 * @return {@code this} {@code Builder} for method chaining 854 */ 855 public Builder cmt(final String comment) { 856 _comment = comment; 857 return this; 858 } 859 860 /** 861 * Return the current comment value. 862 * 863 * @since 1.1 864 * 865 * @return the current comment value 866 */ 867 public Optional<String> cmt() { 868 return Optional.ofNullable(_comment); 869 } 870 871 /** 872 * Set the GPS way-point description. 873 * 874 * @param description the GPS way-point description. 875 * @return {@code this} {@code Builder} for method chaining 876 */ 877 public Builder desc(final String description) { 878 _description = description; 879 return this; 880 } 881 882 /** 883 * Return the current description value. 884 * 885 * @since 1.1 886 * 887 * @return the current description value 888 */ 889 public Optional<String> desc() { 890 return Optional.ofNullable(_description); 891 } 892 893 /** 894 * Set the GPS way-point source. 895 * 896 * @param source the GPS way-point source. 897 * @return {@code this} {@code Builder} for method chaining 898 */ 899 public Builder src(final String source) { 900 _source = source; 901 return this; 902 } 903 904 /** 905 * Return the current source value. 906 * 907 * @since 1.1 908 * 909 * @return the current source value 910 */ 911 public Optional<String> src() { 912 return Optional.ofNullable(_source); 913 } 914 915 /** 916 * Set the links to additional information about the way-point. The link 917 * list may be {@code null}. 918 * 919 * @param links the links to additional information about the way-point 920 * @return {@code this} {@code Builder} for method chaining 921 * @throws NullPointerException if one of the links in the list is 922 * {@code null} 923 */ 924 public Builder links(final List<Link> links) { 925 copyTo(links, _links); 926 return this; 927 } 928 929 /** 930 * Set the links to external information about the way-point. 931 * 932 * @param link the links to external information about the way-point. 933 * @return {@code this} {@code Builder} for method chaining 934 */ 935 public Builder addLink(final Link link) { 936 if (link != null) { 937 _links.add(link); 938 } 939 return this; 940 } 941 942 /** 943 * Set the links to external information about the way-point. 944 * 945 * @param href the links to external information about the way-point. 946 * @return {@code this} {@code Builder} for method chaining 947 * @throws IllegalArgumentException if the given {@code href} is not a 948 * valid URL 949 */ 950 public Builder addLink(final String href) { 951 if (href != null) { 952 _links.add(Link.of(href)); 953 } 954 return this; 955 } 956 957 /** 958 * Return the current links. The returned link list is mutable. 959 * 960 * @since 1.1 961 * 962 * @return the current links 963 */ 964 public List<Link> links() { 965 return new NonNullList<>(_links); 966 } 967 968 /** 969 * Set the text of GPS symbol name. For interchange with other programs, 970 * use the exact spelling of the symbol as displayed on the GPS. If the 971 * GPS abbreviates words, spell them out. 972 * 973 * @param symbol the text of GPS symbol name 974 * @return {@code this} {@code Builder} for method chaining 975 */ 976 public Builder sym(final String symbol) { 977 _symbol = symbol; 978 return this; 979 } 980 981 /** 982 * Return the current symbol value. 983 * 984 * @since 1.1 985 * 986 * @return the current symbol value 987 */ 988 public Optional<String> sym() { 989 return Optional.ofNullable(_symbol); 990 } 991 992 /** 993 * Set the type (classification) of the way-point. 994 * 995 * @param type the type (classification) of the way-point 996 * @return {@code this} {@code Builder} for method chaining 997 */ 998 public Builder type(final String type) { 999 _type = type; 1000 return this; 1001 } 1002 1003 /** 1004 * Return the current type value. 1005 * 1006 * @since 1.1 1007 * 1008 * @return the current type value 1009 */ 1010 public Optional<String> type() { 1011 return Optional.ofNullable(_type); 1012 } 1013 1014 /** 1015 * Set the type of GPX fix. 1016 * 1017 * @param fix the type of GPX fix 1018 * @return {@code this} {@code Builder} for method chaining 1019 */ 1020 public Builder fix(final Fix fix) { 1021 _fix = fix; 1022 return this; 1023 } 1024 1025 /** 1026 * Set the type of GPX fix. 1027 * 1028 * @param fix the type of GPX fix 1029 * @return {@code this} {@code Builder} for method chaining 1030 * @throws IllegalArgumentException if the fix value is not one of the 1031 * following values: [none, 2d, 3d, dgps, pps] 1032 */ 1033 public Builder fix(final String fix) { 1034 _fix = Fix.parse(fix); 1035 return this; 1036 } 1037 1038 /** 1039 * Return the current GPX fix value. 1040 * 1041 * @since 1.1 1042 * 1043 * @return the current GPX fix value 1044 */ 1045 public Optional<Fix> fix() { 1046 return Optional.ofNullable(_fix); 1047 } 1048 1049 /** 1050 * Set the number of satellites used to calculate the GPX fix. 1051 * 1052 * @param sat the number of satellites used to calculate the GPX fix 1053 * @return {@code this} {@code Builder} for method chaining 1054 */ 1055 public Builder sat(final UInt sat) { 1056 _sat = sat; 1057 return this; 1058 } 1059 1060 /** 1061 * Set the number of satellites used to calculate the GPX fix. 1062 * 1063 * @param sat the number of satellites used to calculate the GPX fix 1064 * @return {@code this} {@code Builder} for method chaining 1065 * @throws IllegalArgumentException if the given {@code value} is smaller 1066 * than zero 1067 */ 1068 public Builder sat(final int sat) { 1069 _sat = UInt.of(sat); 1070 return this; 1071 } 1072 1073 /** 1074 * Return the current number of satelites. 1075 * 1076 * @since 1.1 1077 * 1078 * @return the current number of satelites 1079 */ 1080 public Optional<UInt> sat() { 1081 return Optional.ofNullable(_sat); 1082 } 1083 1084 /** 1085 * Set the horizontal dilution of precision. 1086 * 1087 * @param hdop the horizontal dilution of precision 1088 * @return {@code this} {@code Builder} for method chaining 1089 */ 1090 public Builder hdop(final Double hdop) { 1091 _hdop = hdop; 1092 return this; 1093 } 1094 1095 /** 1096 * Return the current horizontal dilution. 1097 * 1098 * @since 1.1 1099 * 1100 * @return the current horizontal dilution 1101 */ 1102 public Optional<Double> hdop() { 1103 return Optional.ofNullable(_hdop); 1104 } 1105 1106 /** 1107 * Set the vertical dilution of precision. 1108 * 1109 * @param vdop the vertical dilution of precision 1110 * @return {@code this} {@code Builder} for method chaining 1111 */ 1112 public Builder vdop(final Double vdop) { 1113 _vdop = vdop; 1114 return this; 1115 } 1116 1117 /** 1118 * Return the current vertical dilution. 1119 * 1120 * @since 1.1 1121 * 1122 * @return the current vertical dilution 1123 */ 1124 public Optional<Double> vdop() { 1125 return Optional.ofNullable(_vdop); 1126 } 1127 1128 /** 1129 * Set the position dilution of precision. 1130 * 1131 * @param pdop the position dilution of precision 1132 * @return {@code this} {@code Builder} for method chaining 1133 */ 1134 public Builder pdop(final Double pdop) { 1135 _pdop = pdop; 1136 return this; 1137 } 1138 1139 /** 1140 * Return the current position dilution. 1141 * 1142 * @since 1.1 1143 * 1144 * @return the current position dilution 1145 */ 1146 public Optional<Double> pdop() { 1147 return Optional.ofNullable(_pdop); 1148 } 1149 1150 /** 1151 * Set the age since last DGPS update. 1152 * 1153 * @param age the age since last DGPS update 1154 * @return {@code this} {@code Builder} for method chaining 1155 */ 1156 public Builder ageofdgpsdata(final Duration age) { 1157 _ageOfDGPSData = age; 1158 return this; 1159 } 1160 1161 /** 1162 * Set the number of seconds since last DGPS update. 1163 * 1164 * @param seconds the age since last DGPS update 1165 * @return {@code this} {@code Builder} for method chaining 1166 */ 1167 public Builder ageofdgpsdata(final double seconds) { 1168 _ageOfDGPSData = Duration.ofMillis((long)(seconds*1000)); 1169 return this; 1170 } 1171 1172 /** 1173 * Return the current age since last DGPS update. 1174 * 1175 * @since 1.1 1176 * 1177 * @return the current age since last DGPS update 1178 */ 1179 public Optional<Duration> ageofdgpsdata() { 1180 return Optional.ofNullable(_ageOfDGPSData); 1181 } 1182 1183 /** 1184 * Set the ID of DGPS station used in differential correction. 1185 * 1186 * @param station the ID of DGPS station used in differential correction 1187 * @return {@code this} {@code Builder} for method chaining 1188 */ 1189 public Builder dgpsid(final DGPSStation station) { 1190 _dgpsID = station; 1191 return this; 1192 } 1193 1194 /** 1195 * Set the ID of DGPS station used in differential correction. 1196 * 1197 * @param station the ID of DGPS station used in differential correction 1198 * @return {@code this} {@code Builder} for method chaining 1199 * @throws IllegalArgumentException if the given station number is not in the 1200 * range of {@code [0..1023]} 1201 */ 1202 public Builder dgpsid(final int station) { 1203 _dgpsID = DGPSStation.of(station); 1204 return this; 1205 } 1206 1207 /** 1208 * Return the current the ID of DGPS station used in differential 1209 * correction. 1210 * 1211 * @since 1.1 1212 * 1213 * @return the current the ID of DGPS station used in differential 1214 * correction 1215 */ 1216 public Optional<DGPSStation> dgpsid() { 1217 return Optional.ofNullable(_dgpsID); 1218 } 1219 1220 /** 1221 * Set the the instantaneous course at the point. 1222 * 1223 * @since 1.3 1224 * 1225 * @param course the the instantaneous course at the point 1226 * @return {@code this} {@code Builder} for method chaining 1227 */ 1228 public Builder course(final Degrees course) { 1229 _course = course; 1230 return this; 1231 } 1232 1233 /** 1234 * Set the the instantaneous course at the point. 1235 * 1236 * @since 1.3 1237 * 1238 * @param courseDegrees the the instantaneous course at the point 1239 * @return {@code this} {@code Builder} for method chaining 1240 * @throws IllegalArgumentException if the give value is not within the 1241 * range of {@code [0..360]} 1242 */ 1243 public Builder course(final double courseDegrees) { 1244 _course = Degrees.ofDegrees(courseDegrees); 1245 return this; 1246 } 1247 1248 /** 1249 * Return the instantaneous course at the point. 1250 * 1251 * @since 1.3 1252 * 1253 * @return the instantaneous course at the point. 1254 */ 1255 public Optional<Degrees> course() { 1256 return Optional.ofNullable(_course); 1257 } 1258 1259 /** 1260 * Sets the extensions object, which may be {@code null}. The root 1261 * element of the extensions document must be {@code extensions}. 1262 * <pre>{@code 1263 * <extensions> 1264 * ... 1265 * </extensions> 1266 * }</pre> 1267 * 1268 * @since 1.5 1269 * 1270 * @param extensions the document 1271 * @return {@code this} {@code Builder} for method chaining 1272 * @throws IllegalArgumentException if the root element is not the 1273 * an {@code extensions} node 1274 */ 1275 public Builder extensions(final Document extensions) { 1276 _extensions = XML.checkExtensions(extensions); 1277 return this; 1278 } 1279 1280 /** 1281 * Return the current extensions 1282 * 1283 * @since 1.5 1284 * 1285 * @return the extensions document 1286 */ 1287 public Optional<Document> extensions() { 1288 return Optional.ofNullable(_extensions); 1289 } 1290 1291 /** 1292 * Create a new way-point with the given latitude and longitude value. 1293 * 1294 * @param latitude the latitude of the way-point 1295 * @param longitude the longitude of the way-point 1296 * @return a newly created way-point 1297 */ 1298 public WayPoint build(final Latitude latitude, final Longitude longitude) { 1299 lat(latitude); 1300 lon(longitude); 1301 return build(); 1302 } 1303 1304 /** 1305 * Create a new way-point with the given latitude and longitude value. 1306 * 1307 * @param latitude the latitude of the way-point 1308 * @param longitude the longitude of the way-point 1309 * @return a newly created way-point 1310 */ 1311 public WayPoint build(final double latitude, final double longitude) { 1312 return build(Latitude.ofDegrees(latitude), Longitude.ofDegrees(longitude)); 1313 } 1314 1315 /** 1316 * Build a new way-point from the current builder state. 1317 * 1318 * @return a new way-point from the current builder state 1319 * @throws IllegalStateException if the {@link WayPoint#getLatitude()} 1320 * or {@link WayPoint#getLongitude()} is {@code null} or has 1321 * not been set, respectively. 1322 */ 1323 public WayPoint build() { 1324 if (_latitude == null || _longitude == null) { 1325 throw new IllegalStateException( 1326 "Latitude and longitude value must be set " + 1327 "for creating a new 'WayPoint'." 1328 ); 1329 } 1330 1331 return new WayPoint( 1332 _latitude, 1333 _longitude, 1334 _elevation, 1335 _speed, 1336 _time, 1337 _magneticVariation, 1338 _geoidHeight, 1339 _name, 1340 _comment, 1341 _description, 1342 _source, 1343 _links, 1344 _symbol, 1345 _type, 1346 _fix, 1347 _sat, 1348 _hdop, 1349 _vdop, 1350 _pdop, 1351 _ageOfDGPSData, 1352 _dgpsID, 1353 _course, 1354 _extensions 1355 ); 1356 } 1357 1358 } 1359 1360 /** 1361 * Return a new {@code WayPoint} builder. 1362 * 1363 * @return a new {@code WayPoint} builder 1364 */ 1365 public static Builder builder() { 1366 return new Builder(); 1367 } 1368 1369 1370 /* ************************************************************************* 1371 * Static object creation methods 1372 * ************************************************************************/ 1373 1374 /** 1375 * Create a new way-point with the given parameter. 1376 * 1377 * @since 1.5 1378 * 1379 * @param latitude the latitude of the point, WGS84 datum (mandatory) 1380 * @param longitude the longitude of the point, WGS84 datum (mandatory) 1381 * @param elevation the elevation (in meters) of the point (optional) 1382 * @param speed the current GPS speed (optional) 1383 * @param time creation/modification timestamp for element. Conforms to ISO 1384 * 8601 specification for date/time representation. Fractional seconds 1385 * are allowed for millisecond timing in tracklogs. (optional) 1386 * @param magneticVariation the magnetic variation at the point (optional) 1387 * @param geoidHeight height (in meters) of geoid (mean sea level) above 1388 * WGS84 earth ellipsoid. As defined in NMEA GGA message. (optional) 1389 * @param name the GPS name of the way-point. This field will be transferred 1390 * to and from the GPS. GPX does not place restrictions on the length 1391 * of this field or the characters contained in it. It is up to the 1392 * receiving application to validate the field before sending it to 1393 * the GPS. (optional) 1394 * @param comment GPS way-point comment. Sent to GPS as comment (optional) 1395 * @param description a text description of the element. Holds additional 1396 * information about the element intended for the user, not the GPS. 1397 * (optional) 1398 * @param source source of data. Included to give user some idea of 1399 * reliability and accuracy of data. "Garmin eTrex", "USGS quad 1400 * Boston North", e.g. (optional) 1401 * @param links links to additional information about the way-point. May be 1402 * empty, but not {@code null}. 1403 * @param symbol text of GPS symbol name. For interchange with other 1404 * programs, use the exact spelling of the symbol as displayed on the 1405 * GPS. If the GPS abbreviates words, spell them out. (optional) 1406 * @param type type (classification) of the way-point (optional) 1407 * @param fix type of GPX fix (optional) 1408 * @param sat number of satellites used to calculate the GPX fix (optional) 1409 * @param hdop horizontal dilution of precision (optional) 1410 * @param vdop vertical dilution of precision (optional) 1411 * @param pdop position dilution of precision. (optional) 1412 * @param ageOfGPSData number of seconds since last DGPS update (optional) 1413 * @param dgpsID ID of DGPS station used in differential correction (optional) 1414 * @param course the Instantaneous course at the point 1415 * @param extensions the extensions document 1416 * @throws NullPointerException if the {@code latitude} or {@code longitude} 1417 * is {@code null} 1418 * @return a new {@code WayPoint} 1419 */ 1420 public static WayPoint of( 1421 final Latitude latitude, 1422 final Longitude longitude, 1423 final Length elevation, 1424 final Speed speed, 1425 final Instant time, 1426 final Degrees magneticVariation, 1427 final Length geoidHeight, 1428 final String name, 1429 final String comment, 1430 final String description, 1431 final String source, 1432 final List<Link> links, 1433 final String symbol, 1434 final String type, 1435 final Fix fix, 1436 final UInt sat, 1437 final Double hdop, 1438 final Double vdop, 1439 final Double pdop, 1440 final Duration ageOfGPSData, 1441 final DGPSStation dgpsID, 1442 final Degrees course, 1443 final Document extensions 1444 ) { 1445 return new WayPoint( 1446 latitude, 1447 longitude, 1448 elevation, 1449 speed, 1450 time, 1451 magneticVariation, 1452 geoidHeight, 1453 name, 1454 comment, 1455 description, 1456 source, 1457 links, 1458 symbol, 1459 type, 1460 fix, 1461 sat, 1462 hdop, 1463 vdop, 1464 pdop, 1465 ageOfGPSData, 1466 dgpsID, 1467 course, 1468 XML.extensions(XML.clone(extensions)) 1469 ); 1470 } 1471 1472 /** 1473 * Create a new {@code WayPoint} with the given {@code latitude} and 1474 * {@code longitude} value. 1475 * 1476 * @param latitude the latitude of the point 1477 * @param longitude the longitude of the point 1478 * @return a new {@code WayPoint} 1479 * @throws NullPointerException if one of the given arguments is {@code null} 1480 */ 1481 public static WayPoint of( 1482 final Latitude latitude, 1483 final Longitude longitude 1484 ) { 1485 return new WayPoint( 1486 latitude, 1487 longitude, 1488 null, 1489 null, 1490 null, 1491 null, 1492 null, 1493 null, 1494 null, 1495 null, 1496 null, 1497 null, 1498 null, 1499 null, 1500 null, 1501 null, 1502 null, 1503 null, 1504 null, 1505 null, 1506 null, 1507 null, 1508 null 1509 ); 1510 } 1511 1512 /** 1513 * Create a new {@code WayPoint} with the given {@code latitude} and 1514 * {@code longitude} value. 1515 * 1516 * @param latitudeDegree the latitude of the point 1517 * @param longitudeDegree the longitude of the point 1518 * @return a new {@code WayPoint} 1519 * @throws IllegalArgumentException if the given latitude or longitude is not 1520 * in the valid range. 1521 */ 1522 public static WayPoint of( 1523 final double latitudeDegree, 1524 final double longitudeDegree 1525 ) { 1526 return of( 1527 Latitude.ofDegrees(latitudeDegree), 1528 Longitude.ofDegrees(longitudeDegree) 1529 ); 1530 } 1531 1532 /** 1533 * Create a new {@code WayPoint} with the given parameters. 1534 * 1535 * @param latitude the latitude of the point 1536 * @param longitude the longitude of the point 1537 * @param time the timestamp of the way-point 1538 * @return a new {@code WayPoint} 1539 * @throws NullPointerException if one of the given arguments is {@code null} 1540 */ 1541 public static WayPoint of( 1542 final Latitude latitude, 1543 final Longitude longitude, 1544 final Instant time 1545 ) { 1546 return new WayPoint( 1547 latitude, 1548 longitude, 1549 null, 1550 null, 1551 time, 1552 null, 1553 null, 1554 null, 1555 null, 1556 null, 1557 null, 1558 null, 1559 null, 1560 null, 1561 null, 1562 null, 1563 null, 1564 null, 1565 null, 1566 null, 1567 null, 1568 null, 1569 null 1570 ); 1571 } 1572 1573 /** 1574 * Create a new {@code WayPoint} with the given parameters. 1575 * 1576 * @param latitudeDegree the latitude of the point 1577 * @param longitudeDegree the longitude of the point 1578 * @param timeEpochMilli the timestamp of the way-point 1579 * @return a new {@code WayPoint} 1580 * @throws IllegalArgumentException if one of the given arguments is invalid 1581 */ 1582 public static WayPoint of( 1583 final double latitudeDegree, 1584 final double longitudeDegree, 1585 final long timeEpochMilli 1586 ) { 1587 return of( 1588 Latitude.ofDegrees(latitudeDegree), 1589 Longitude.ofDegrees(longitudeDegree), 1590 Instant.ofEpochMilli(timeEpochMilli) 1591 ); 1592 } 1593 1594 /** 1595 * Create a new {@code WayPoint} with the given parameters. 1596 * 1597 * @param latitude the latitude of the point 1598 * @param longitude the longitude of the point 1599 * @param elevation the elevation of the point 1600 * @param time the timestamp of the way-point 1601 * @return a new {@code WayPoint} 1602 * @throws NullPointerException if one of the given arguments is {@code null} 1603 */ 1604 public static WayPoint of( 1605 final Latitude latitude, 1606 final Longitude longitude, 1607 final Length elevation, 1608 final Instant time 1609 ) { 1610 return new WayPoint( 1611 latitude, 1612 longitude, 1613 elevation, 1614 null, 1615 time, 1616 null, 1617 null, 1618 null, 1619 null, 1620 null, 1621 null, 1622 null, 1623 null, 1624 null, 1625 null, 1626 null, 1627 null, 1628 null, 1629 null, 1630 null, 1631 null, 1632 null, 1633 null 1634 ); 1635 } 1636 1637 /** 1638 * Create a new {@code WayPoint} with the given parameters. 1639 * 1640 * @param latitudeDegree the latitude of the point 1641 * @param longitudeDegree the longitude of the point 1642 * @param elevationMeter the elevation of the point 1643 * @param timeEpochMilli the timestamp of the way-point 1644 * @return a new {@code WayPoint} 1645 * @throws IllegalArgumentException if one of the given arguments is invalid 1646 */ 1647 public static WayPoint of( 1648 final double latitudeDegree, 1649 final double longitudeDegree, 1650 final double elevationMeter, 1651 final long timeEpochMilli 1652 ) { 1653 return of( 1654 Latitude.ofDegrees(latitudeDegree), 1655 Longitude.ofDegrees(longitudeDegree), 1656 Length.of(elevationMeter, METER), 1657 Instant.ofEpochMilli(timeEpochMilli) 1658 ); 1659 } 1660 1661 1662 1663 /** 1664 * Create a new way-point with the given parameter. 1665 * 1666 * @since 1.3 1667 * 1668 * @param latitude the latitude of the point, WGS84 datum (mandatory) 1669 * @param longitude the longitude of the point, WGS84 datum (mandatory) 1670 * @param elevation the elevation (in meters) of the point (optional) 1671 * @param speed the current GPS speed (optional) 1672 * @param time creation/modification timestamp for element. Conforms to ISO 1673 * 8601 specification for date/time representation. Fractional seconds 1674 * are allowed for millisecond timing in tracklogs. (optional) 1675 * @param magneticVariation the magnetic variation at the point (optional) 1676 * @param geoidHeight height (in meters) of geoid (mean sea level) above 1677 * WGS84 earth ellipsoid. As defined in NMEA GGA message. (optional) 1678 * @param name the GPS name of the way-point. This field will be transferred 1679 * to and from the GPS. GPX does not place restrictions on the length 1680 * of this field or the characters contained in it. It is up to the 1681 * receiving application to validate the field before sending it to 1682 * the GPS. (optional) 1683 * @param comment GPS way-point comment. Sent to GPS as comment (optional) 1684 * @param description a text description of the element. Holds additional 1685 * information about the element intended for the user, not the GPS. 1686 * (optional) 1687 * @param source source of data. Included to give user some idea of 1688 * reliability and accuracy of data. "Garmin eTrex", "USGS quad 1689 * Boston North", e.g. (optional) 1690 * @param links links to additional information about the way-point. May be 1691 * empty, but not {@code null}. 1692 * @param symbol text of GPS symbol name. For interchange with other 1693 * programs, use the exact spelling of the symbol as displayed on the 1694 * GPS. If the GPS abbreviates words, spell them out. (optional) 1695 * @param type type (classification) of the way-point (optional) 1696 * @param fix type of GPX fix (optional) 1697 * @param sat number of satellites used to calculate the GPX fix (optional) 1698 * @param hdop horizontal dilution of precision (optional) 1699 * @param vdop vertical dilution of precision (optional) 1700 * @param pdop position dilution of precision. (optional) 1701 * @param ageOfGPSData number of seconds since last DGPS update (optional) 1702 * @param dgpsID ID of DGPS station used in differential correction (optional) 1703 * @param course the Instantaneous course at the point 1704 * @throws NullPointerException if the {@code latitude} or {@code longitude} 1705 * is {@code null} 1706 * @return a new {@code WayPoint} 1707 */ 1708 public static WayPoint of( 1709 final Latitude latitude, 1710 final Longitude longitude, 1711 final Length elevation, 1712 final Speed speed, 1713 final Instant time, 1714 final Degrees magneticVariation, 1715 final Length geoidHeight, 1716 final String name, 1717 final String comment, 1718 final String description, 1719 final String source, 1720 final List<Link> links, 1721 final String symbol, 1722 final String type, 1723 final Fix fix, 1724 final UInt sat, 1725 final Double hdop, 1726 final Double vdop, 1727 final Double pdop, 1728 final Duration ageOfGPSData, 1729 final DGPSStation dgpsID, 1730 final Degrees course 1731 ) { 1732 return new WayPoint( 1733 latitude, 1734 longitude, 1735 elevation, 1736 speed, 1737 time, 1738 magneticVariation, 1739 geoidHeight, 1740 name, 1741 comment, 1742 description, 1743 source, 1744 links, 1745 symbol, 1746 type, 1747 fix, 1748 sat, 1749 hdop, 1750 vdop, 1751 pdop, 1752 ageOfGPSData, 1753 dgpsID, 1754 course, 1755 null 1756 ); 1757 } 1758 1759 /** 1760 * Return a (new) WayPoint from the given input {@code point}. If the given 1761 * {@code point} is already a {@code WayPoint} instance, the input is casted 1762 * to a {@code WayPoint} and returned. 1763 * 1764 * @since 1.4 1765 * 1766 * @param point the input {@code point} to create the {@code WayPoint} from 1767 * @return a newly created {@code WayPoint} instance, or the input 1768 * {@code point} if it is already a {@code WayPoint} instance 1769 * @throws NullPointerException if the given {@code point} is {@code null} 1770 */ 1771 public static WayPoint of(final Point point) { 1772 requireNonNull(point); 1773 1774 return point instanceof WayPoint 1775 ? (WayPoint)point 1776 : of( 1777 point.getLatitude(), 1778 point.getLongitude(), 1779 point.getElevation().orElse(null), 1780 point.getTime().orElse(null)); 1781 } 1782 1783 /* ************************************************************************* 1784 * Java object serialization 1785 * ************************************************************************/ 1786 1787 @Serial 1788 private Object writeReplace() throws IOException { 1789 return new SerialProxy(SerialProxy.WAY_POINT, this); 1790 } 1791 1792 @Serial 1793 private void readObject(final ObjectInputStream stream) 1794 throws InvalidObjectException 1795 { 1796 throw new InvalidObjectException("Serialization proxy required."); 1797 } 1798 1799 void write(final DataOutput out) throws IOException { 1800 int existing = 0; 1801 if (_elevation != null) existing |= 1 << 0; 1802 if (_speed != null) existing |= 1 << 1; 1803 if (_time != null) existing |= 1 << 2; 1804 if (_magneticVariation != null) existing |= 1 << 3; 1805 if (_geoidHeight != null) existing |= 1 << 4; 1806 if (_name != null) existing |= 1 << 5; 1807 if (_comment != null) existing |= 1 << 6; 1808 if (_description != null) existing |= 1 << 7; 1809 if (_source != null) existing |= 1 << 8; 1810 if (_links != null && !_links.isEmpty()) existing |= 1 << 9; 1811 if (_symbol != null) existing |= 1 << 10; 1812 if (_type != null) existing |= 1 << 11; 1813 if (_fix != null) existing |= 1 << 12; 1814 if (_sat != null) existing |= 1 << 13; 1815 if (_hdop != null) existing |= 1 << 14; 1816 if (_vdop != null) existing |= 1 << 15; 1817 if (_pdop != null) existing |= 1 << 16; 1818 if (_ageOfGPSData != null) existing |= 1 << 17; 1819 if (_dgpsID != null) existing |= 1 << 18; 1820 if (_course != null) existing |= 1 << 19; 1821 if (_extensions != null) existing |= 1 << 20; 1822 1823 out.writeInt(existing); 1824 out.writeDouble(_latitude.toDegrees()); 1825 out.writeDouble(_longitude.toDegrees()); 1826 if ((existing & (1 << 0)) != 0) { 1827 assert _elevation != null; 1828 _elevation.write(out); 1829 } 1830 if ((existing & (1 << 1)) != 0) { 1831 assert _speed != null; 1832 _speed.write(out); 1833 } 1834 if ((existing & (1 << 2)) != 0) { 1835 assert _time != null; 1836 Instants.write(_time, out); 1837 } 1838 if ((existing & (1 << 3)) != 0) { 1839 assert _magneticVariation != null; 1840 _magneticVariation.write(out); 1841 } 1842 if ((existing & (1 << 4)) != 0) { 1843 assert _geoidHeight != null; 1844 _geoidHeight.write(out); 1845 } 1846 if ((existing & (1 << 5)) != 0) { 1847 assert _name != null; 1848 IO.writeString(_name, out); 1849 } 1850 if ((existing & (1 << 6)) != 0) { 1851 assert _comment != null; 1852 IO.writeString(_comment, out); 1853 } 1854 if ((existing & (1 << 7)) != 0) { 1855 assert _description != null; 1856 IO.writeString(_description, out); 1857 } 1858 if ((existing & (1 << 8)) != 0) { 1859 assert _source != null; 1860 IO.writeString(_source, out); 1861 } 1862 if ((existing & (1 << 9)) != 0) { 1863 assert _links != null; 1864 IO.writes(_links, Link::write, out); 1865 } 1866 if ((existing & (1 << 10)) != 0) { 1867 assert _symbol != null; 1868 IO.writeString(_symbol, out); 1869 } 1870 if ((existing & (1 << 11)) != 0) { 1871 assert _type != null; 1872 IO.writeString(_type, out); 1873 } 1874 if ((existing & (1 << 12)) != 0) { 1875 assert _fix != null; 1876 IO.writeString(_fix.name(), out); 1877 } 1878 if ((existing & (1 << 13)) != 0) { 1879 assert _sat != null; 1880 _sat.write(out); 1881 } 1882 if ((existing & (1 << 14)) != 0) { 1883 assert _hdop != null; 1884 out.writeDouble(_hdop); 1885 } 1886 if ((existing & (1 << 15)) != 0) { 1887 assert _vdop != null; 1888 out.writeDouble(_vdop); 1889 } 1890 if ((existing & (1 << 16)) != 0) { 1891 assert _pdop != null; 1892 out.writeDouble(_pdop); 1893 } 1894 if ((existing & (1 << 17)) != 0) { 1895 assert _ageOfGPSData != null; 1896 out.writeLong(_ageOfGPSData.toMillis()); 1897 } 1898 if ((existing & (1 << 18)) != 0) { 1899 assert _dgpsID != null; 1900 _dgpsID.write(out); 1901 } 1902 if ((existing & (1 << 19)) != 0) { 1903 assert _course != null; 1904 _course.write(out); 1905 } 1906 if ((existing & (1 << 20)) != 0) { 1907 assert _extensions != null; 1908 IO.write(_extensions, out); 1909 } 1910 } 1911 1912 static WayPoint read(final DataInput in) throws IOException { 1913 final int existing = in.readInt(); 1914 return new WayPoint( 1915 Latitude.ofDegrees(in.readDouble()), 1916 Longitude.ofDegrees(in.readDouble()), 1917 ((existing & (1 << 0)) != 0) ? Length.read(in) : null, 1918 ((existing & (1 << 1)) != 0) ? Speed.read(in) : null, 1919 ((existing & (1 << 2)) != 0) ? Instants.read(in) : null, 1920 ((existing & (1 << 3)) != 0) ? Degrees.read(in) : null, 1921 ((existing & (1 << 4)) != 0) ? Length.read(in) : null, 1922 ((existing & (1 << 5)) != 0) ? IO.readString(in) : null, 1923 ((existing & (1 << 6)) != 0) ? IO.readString(in) : null, 1924 ((existing & (1 << 7)) != 0) ? IO.readString(in) : null, 1925 ((existing & (1 << 8)) != 0) ? IO.readString(in) : null, 1926 ((existing & (1 << 9)) != 0) ? IO.reads(Link::read, in) : null, 1927 ((existing & (1 << 10)) != 0) ? IO.readString(in) : null, 1928 ((existing & (1 << 11)) != 0) ? IO.readString(in) : null, 1929 ((existing & (1 << 12)) != 0) ? Fix.valueOf(IO.readString(in)) : null, 1930 ((existing & (1 << 13)) != 0) ? UInt.read(in) : null, 1931 ((existing & (1 << 14)) != 0) ? in.readDouble() : null, 1932 ((existing & (1 << 15)) != 0) ? in.readDouble() : null, 1933 ((existing & (1 << 16)) != 0) ? in.readDouble() : null, 1934 ((existing & (1 << 17)) != 0) ? Duration.ofMillis(in.readLong()) : null, 1935 ((existing & (1 << 18)) != 0) ? DGPSStation.read(in) : null, 1936 ((existing & (1 << 19)) != 0) ? Degrees.read(in) : null, 1937 ((existing & (1 << 20)) != 0) ? IO.readDoc(in) : null 1938 ); 1939 } 1940 1941 1942 /* ************************************************************************* 1943 * XML stream object serialization 1944 * ************************************************************************/ 1945 1946 private static String url(final WayPoint point) { 1947 return point.getLinks().isEmpty() 1948 ? null 1949 : point.getLinks().get(0).getHref().toString(); 1950 } 1951 1952 private static String urlname(final WayPoint point) { 1953 return point.getLinks().isEmpty() 1954 ? null 1955 : point.getLinks().get(0).getText().orElse(null); 1956 } 1957 1958 // Define the needed writers for the different versions. 1959 private static XMLWriters<WayPoint> 1960 writers(final Function<? super Number, String> formatter) { 1961 return new XMLWriters<WayPoint>() 1962 .v00(XMLWriter.attr("lat").map(wp -> formatter.apply(wp._latitude))) 1963 .v00(XMLWriter.attr("lon").map(wp -> formatter.apply(wp._longitude))) 1964 .v00(XMLWriter.elem("ele").map(wp -> formatter.apply(wp._elevation))) 1965 .v00(XMLWriter.elem("speed").map(wp -> formatter.apply(wp._speed))) 1966 .v00(XMLWriter.elem("time").map(wp -> TimeFormat.format(wp._time))) 1967 .v00(XMLWriter.elem("magvar").map(wp -> formatter.apply(wp._magneticVariation))) 1968 .v00(XMLWriter.elem("geoidheight").map(wp -> formatter.apply(wp._geoidHeight))) 1969 .v00(XMLWriter.elem("name").map(wp -> wp._name)) 1970 .v00(XMLWriter.elem("cmt").map(wp -> wp._comment)) 1971 .v00(XMLWriter.elem("desc").map(wp -> wp._description)) 1972 .v00(XMLWriter.elem("src").map(wp -> wp._source)) 1973 .v11(XMLWriter.elems(Link.WRITER).map(wp -> wp._links)) 1974 .v10(XMLWriter.elem("url").map(WayPoint::url)) 1975 .v10(XMLWriter.elem("urlname").map(WayPoint::urlname)) 1976 .v00(XMLWriter.elem("sym").map(wp -> wp._symbol)) 1977 .v00(XMLWriter.elem("type").map(wp -> wp._type)) 1978 .v00(XMLWriter.elem("fix").map(wp -> Fix.format(wp._fix))) 1979 .v00(XMLWriter.elem("sat").map(wp -> toIntString(wp._sat))) 1980 .v00(XMLWriter.elem("hdop").map(wp -> formatter.apply(wp._hdop))) 1981 .v00(XMLWriter.elem("vdop").map(wp -> formatter.apply(wp._vdop))) 1982 .v00(XMLWriter.elem("pdop").map(wp -> formatter.apply(wp._pdop))) 1983 .v00(XMLWriter.elem("ageofdgpsdata").map(wp -> toDurationString(wp._ageOfGPSData))) 1984 .v00(XMLWriter.elem("dgpsid").map(wp -> toIntString(wp._dgpsID))) 1985 .v10(XMLWriter.elem("course").map(wp -> formatter.apply(wp._course))) 1986 .v00(XMLWriter.doc("extensions").map(gpx -> gpx._extensions)); 1987 } 1988 1989 // Define the needed readers for the different versions. 1990 private static XMLReaders 1991 readers(final Function<? super String, Length> lengthParser) { 1992 return new XMLReaders() 1993 .v00(XMLReader.attr("lat").map(Latitude::parse)) 1994 .v00(XMLReader.attr("lon").map(Longitude::parse)) 1995 .v00(XMLReader.elem("ele").map(lengthParser)) 1996 .v00(XMLReader.elem("speed").map(Speed::parse)) 1997 .v00(XMLReader.elem("time").map(TimeFormat::parse)) 1998 .v00(XMLReader.elem("magvar").map(Degrees::parse)) 1999 .v00(XMLReader.elem("geoidheight").map(lengthParser)) 2000 .v00(XMLReader.elem("name")) 2001 .v00(XMLReader.elem("cmt")) 2002 .v00(XMLReader.elem("desc")) 2003 .v00(XMLReader.elem("src")) 2004 .v11(XMLReader.elems(Link.READER)) 2005 .v10(XMLReader.elem("url").map(Format::parseURI)) 2006 .v10(XMLReader.elem("urlname")) 2007 .v00(XMLReader.elem("sym")) 2008 .v00(XMLReader.elem("type")) 2009 .v00(XMLReader.elem("fix").map(Fix::parse)) 2010 .v00(XMLReader.elem("sat").map(UInt::parse)) 2011 .v00(XMLReader.elem("hdop").map(Format::parseDouble)) 2012 .v00(XMLReader.elem("vdop").map(Format::parseDouble)) 2013 .v00(XMLReader.elem("pdop").map(Format::parseDouble)) 2014 .v00(XMLReader.elem("ageofdgpsdata").map(Format::parseDuration)) 2015 .v00(XMLReader.elem("dgpsid").map(DGPSStation::parse)) 2016 .v10(XMLReader.elem("course").map(Degrees::parse)) 2017 .v00(XMLReader.doc("extensions")); 2018 } 2019 2020 static XMLWriter<WayPoint> xmlWriter( 2021 final Version version, 2022 final String name, 2023 final Function<? super Number, String> formatter 2024 ) { 2025 return XMLWriter.elem(name, writers(formatter).writers(version)); 2026 } 2027 2028 @SuppressWarnings("unchecked") 2029 static XMLReader<WayPoint> xmlReader( 2030 final Version version, 2031 final String name, 2032 final Function<? super String, Length> lengthParser 2033 ) { 2034 return XMLReader.elem( 2035 version == Version.V10 2036 ? WayPoint::toWayPointV10 2037 : WayPoint::toWayPointV11, 2038 name, 2039 readers(lengthParser).readers(version) 2040 ); 2041 } 2042 2043 @SuppressWarnings("unchecked") 2044 private static WayPoint toWayPointV11(final Object[] v) { 2045 return new WayPoint( 2046 (Latitude)v[0], 2047 (Longitude)v[1], 2048 (Length)v[2], 2049 (Speed)v[3], 2050 (Instant)v[4], 2051 (Degrees)v[5], 2052 (Length)v[6], 2053 (String)v[7], 2054 (String)v[8], 2055 (String)v[9], 2056 (String)v[10], 2057 (List<Link>)v[11], 2058 (String)v[12], 2059 (String)v[13], 2060 (Fix)v[14], 2061 (UInt)v[15], 2062 (Double)v[16], 2063 (Double)v[17], 2064 (Double)v[18], 2065 (Duration)v[19], 2066 (DGPSStation)v[20], 2067 null, 2068 XML.extensions((Document)v[21]) 2069 ); 2070 } 2071 2072 private static WayPoint toWayPointV10(final Object[] v) { 2073 return new WayPoint( 2074 (Latitude)v[0], 2075 (Longitude)v[1], 2076 (Length)v[2], 2077 (Speed)v[3], 2078 (Instant)v[4], 2079 (Degrees)v[5], 2080 (Length)v[6], 2081 (String)v[7], 2082 (String)v[8], 2083 (String)v[9], 2084 (String)v[10], 2085 v[11] != null 2086 ? List.of(Link.of((URI)v[11], (String)v[12], null)) 2087 : null, 2088 (String)v[13], 2089 (String)v[14], 2090 (Fix)v[15], 2091 (UInt)v[16], 2092 (Double)v[17], 2093 (Double)v[18], 2094 (Double)v[19], 2095 (Duration)v[20], 2096 (DGPSStation)v[21], 2097 (Degrees)v[22], 2098 XML.extensions((Document)v[23]) 2099 ); 2100 } 2101 2102}