0001 /*
0002 * Java GPX Library (jpx-3.1.0).
0003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
0004 *
0005 * Licensed under the Apache License, Version 2.0 (the "License");
0006 * you may not use this file except in compliance with the License.
0007 * You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 *
0017 * Author:
0018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
0019 */
0020 package io.jenetics.jpx;
0021
0022 import static java.util.Objects.hash;
0023 import static java.util.Objects.requireNonNull;
0024 import static io.jenetics.jpx.Format.toDurationString;
0025 import static io.jenetics.jpx.Format.toIntString;
0026 import static io.jenetics.jpx.Length.Unit.METER;
0027 import static io.jenetics.jpx.Lists.copyOf;
0028 import static io.jenetics.jpx.Lists.copyTo;
0029 import static io.jenetics.jpx.Speed.Unit.METERS_PER_SECOND;
0030
0031 import java.io.DataInput;
0032 import java.io.DataOutput;
0033 import java.io.IOException;
0034 import java.io.InvalidObjectException;
0035 import java.io.ObjectInputStream;
0036 import java.io.Serial;
0037 import java.io.Serializable;
0038 import java.net.URI;
0039 import java.time.Duration;
0040 import java.time.Instant;
0041 import java.util.ArrayList;
0042 import java.util.List;
0043 import java.util.Objects;
0044 import java.util.Optional;
0045 import java.util.function.Function;
0046
0047 import org.w3c.dom.Document;
0048
0049 import io.jenetics.jpx.GPX.Version;
0050
0051 /**
0052 * A {@code WayPoint} represents a way-point, point of interest, or named
0053 * feature on a map.
0054 * <p>
0055 * Creating a {@code WayPoint}:
0056 * <pre>{@code
0057 * final WayPoint point = WayPoint.builder()
0058 * .lat(48.2081743).lon(16.3738189).ele(160)
0059 * .build();
0060 * }</pre>
0061 *
0062 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
0063 * @version 3.0
0064 * @since 1.0
0065 */
0066 public final class WayPoint implements Point, Serializable {
0067
0068 @Serial
0069 private static final long serialVersionUID = 2L;
0070
0071 private final Latitude _latitude;
0072 private final Longitude _longitude;
0073
0074 private final Length _elevation;
0075 private final Speed _speed;
0076 private final Instant _time;
0077 private final Degrees _magneticVariation;
0078 private final Length _geoidHeight;
0079 private final String _name;
0080 private final String _comment;
0081 private final String _description;
0082 private final String _source;
0083 private final List<Link> _links;
0084 private final String _symbol;
0085 private final String _type;
0086 private final Fix _fix;
0087 private final UInt _sat;
0088 private final Double _hdop;
0089 private final Double _vdop;
0090 private final Double _pdop;
0091 private final Duration _ageOfGPSData;
0092 private final DGPSStation _dgpsID;
0093 private final Degrees _course;
0094 private final Document _extensions;
0095
0096 /**
0097 * Create a new way-point with the given parameter.
0098 *
0099 * @param latitude the latitude of the point, WGS84 datum (mandatory)
0100 * @param longitude the longitude of the point, WGS84 datum (mandatory)
0101 * @param elevation the elevation (in meters) of the point (optional)
0102 * @param speed the current GPS speed (optional)
0103 * @param time creation/modification timestamp for element. Conforms to ISO
0104 * 8601 specification for date/time representation. Fractional seconds
0105 * are allowed for millisecond timing in tracklogs. (optional)
0106 * @param magneticVariation the magnetic variation at the point (optional)
0107 * @param geoidHeight height (in meters) of geoid (mean sea level) above
0108 * WGS84 earth ellipsoid. As defined in NMEA GGA message. (optional)
0109 * @param name the GPS name of the way-point. This field will be transferred
0110 * to and from the GPS. GPX does not place restrictions on the length
0111 * of this field or the characters contained in it. It is up to the
0112 * receiving application to validate the field before sending it to
0113 * the GPS. (optional)
0114 * @param comment GPS way-point comment. Sent to GPS as comment (optional)
0115 * @param description a text description of the element. Holds additional
0116 * information about the element intended for the user, not the GPS.
0117 * (optional)
0118 * @param source source of data. Included to give user some idea of
0119 * reliability and accuracy of data. "Garmin eTrex", "USGS quad
0120 * Boston North", e.g. (optional)
0121 * @param links links to additional information about the way-point. May be
0122 * empty, but not {@code null}.
0123 * @param symbol text of GPS symbol name. For interchange with other
0124 * programs, use the exact spelling of the symbol as displayed on the
0125 * GPS. If the GPS abbreviates words, spell them out. (optional)
0126 * @param type type (classification) of the way-point (optional)
0127 * @param fix type of GPX fix (optional)
0128 * @param sat number of satellites used to calculate the GPX fix (optional)
0129 * @param hdop horizontal dilution of precision (optional)
0130 * @param vdop vertical dilution of precision (optional)
0131 * @param pdop position dilution of precision. (optional)
0132 * @param ageOfGPSData number of seconds since last DGPS update (optional)
0133 * @param dgpsID ID of DGPS station used in differential correction (optional)
0134 * @param course the Instantaneous course at the point
0135 * @param extensions the XML extensions document
0136 * @throws NullPointerException if the {@code latitude} or {@code longitude}
0137 * is {@code null}
0138 */
0139 private WayPoint(
0140 final Latitude latitude,
0141 final Longitude longitude,
0142 final Length elevation,
0143 final Speed speed,
0144 final Instant time,
0145 final Degrees magneticVariation,
0146 final Length geoidHeight,
0147 final String name,
0148 final String comment,
0149 final String description,
0150 final String source,
0151 final List<Link> links,
0152 final String symbol,
0153 final String type,
0154 final Fix fix,
0155 final UInt sat,
0156 final Double hdop,
0157 final Double vdop,
0158 final Double pdop,
0159 final Duration ageOfGPSData,
0160 final DGPSStation dgpsID,
0161 final Degrees course,
0162 final Document extensions
0163 ) {
0164 _latitude = requireNonNull(latitude);
0165 _longitude = requireNonNull(longitude);
0166
0167 _elevation = elevation;
0168 _speed = speed;
0169 _time = time;
0170 _magneticVariation = magneticVariation;
0171 _geoidHeight = geoidHeight;
0172 _name = name;
0173 _comment = comment;
0174 _description = description;
0175 _source = source;
0176 _links = copyOf(links);
0177 _symbol = symbol;
0178 _type = type;
0179 _fix = fix;
0180 _sat = sat;
0181 _hdop = hdop;
0182 _vdop = vdop;
0183 _pdop = pdop;
0184 _ageOfGPSData = ageOfGPSData;
0185 _dgpsID = dgpsID;
0186 _course = course;
0187 _extensions = extensions;
0188 }
0189
0190 @Override
0191 public Latitude getLatitude() {
0192 return _latitude;
0193 }
0194
0195 @Override
0196 public Longitude getLongitude() {
0197 return _longitude;
0198 }
0199
0200 @Override
0201 public Optional<Length> getElevation() {
0202 return Optional.ofNullable(_elevation);
0203 }
0204
0205 /**
0206 * The current GPS speed.
0207 *
0208 * @return the current GPS speed
0209 */
0210 public Optional<Speed> getSpeed() {
0211 return Optional.ofNullable(_speed);
0212 }
0213
0214 @Override
0215 public Optional<Instant> getTime() {
0216 return Optional.ofNullable(_time);
0217 }
0218
0219 /**
0220 * The magnetic variation at the point.
0221 *
0222 * @return the magnetic variation at the point
0223 */
0224 public Optional<Degrees> getMagneticVariation() {
0225 return Optional.ofNullable(_magneticVariation);
0226 }
0227
0228 /**
0229 * The height (in meters) of geoid (mean sea level) above WGS84 earth
0230 * ellipsoid. As defined in NMEA GGA message.
0231 *
0232 * @return the height (in meters) of geoid (mean sea level) above WGS84
0233 * earth ellipsoid
0234 */
0235 public Optional<Length> getGeoidHeight() {
0236 return Optional.ofNullable(_geoidHeight);
0237 }
0238
0239 /**
0240 * The GPS name of the way-point. This field will be transferred to and from
0241 * the GPS. GPX does not place restrictions on the length of this field or
0242 * the characters contained in it. It is up to the receiving application to
0243 * validate the field before sending it to the GPS.
0244 *
0245 * @return the GPS name of the way-point
0246 */
0247 public Optional<String> getName() {
0248 return Optional.ofNullable(_name);
0249 }
0250
0251 /**
0252 * The GPS way-point comment.
0253 *
0254 * @return the GPS way-point comment
0255 */
0256 public Optional<String> getComment() {
0257 return Optional.ofNullable(_comment);
0258 }
0259
0260 /**
0261 * Return a text description of the element. Holds additional information
0262 * about the element intended for the user, not the GPS.
0263 *
0264 * @return a text description of the element
0265 */
0266 public Optional<String> getDescription() {
0267 return Optional.ofNullable(_description);
0268 }
0269
0270 /**
0271 * Return the source of data. Included to give user some idea of reliability
0272 * and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.
0273 *
0274 * @return the source of the data
0275 */
0276 public Optional<String> getSource() {
0277 return Optional.ofNullable(_source);
0278 }
0279
0280 /**
0281 * Return the links to additional information about the way-point.
0282 *
0283 * @return the links to additional information about the way-point
0284 */
0285 public List<Link> getLinks() {
0286 return _links;
0287 }
0288
0289 /**
0290 * Return the text of GPS symbol name. For interchange with other programs,
0291 * use the exact spelling of the symbol as displayed on the GPS. If the GPS
0292 * abbreviates words, spell them out.
0293 *
0294 * @return the text of GPS symbol name
0295 */
0296 public Optional<String> getSymbol() {
0297 return Optional.ofNullable(_symbol);
0298 }
0299
0300 /**
0301 * Return the type (classification) of the way-point.
0302 *
0303 * @return the type (classification) of the way-point
0304 */
0305 public Optional<String> getType() {
0306 return Optional.ofNullable(_type);
0307 }
0308
0309 /**
0310 * Return the type of GPX fix.
0311 *
0312 * @return the type of GPX fix
0313 */
0314 public Optional<Fix> getFix() {
0315 return Optional.ofNullable(_fix);
0316 }
0317
0318 /**
0319 * Return the number of satellites used to calculate the GPX fix.
0320 *
0321 * @return the number of satellites used to calculate the GPX fix
0322 */
0323 public Optional<UInt> getSat() {
0324 return Optional.ofNullable(_sat);
0325 }
0326
0327 /**
0328 * Return the horizontal dilution of precision.
0329 *
0330 * @return the horizontal dilution of precision
0331 */
0332 public Optional<Double> getHdop() {
0333 return Optional.ofNullable(_hdop);
0334 }
0335
0336 /**
0337 * Return the vertical dilution of precision.
0338 *
0339 * @return the vertical dilution of precision
0340 */
0341 public Optional<Double> getVdop() {
0342 return Optional.ofNullable(_vdop);
0343 }
0344
0345 /**
0346 * Return the position dilution of precision.
0347 *
0348 * @return the position dilution of precision
0349 */
0350 public Optional<Double> getPdop() {
0351 return Optional.ofNullable(_pdop);
0352 }
0353
0354 /**
0355 * Return the number of seconds since last DGPS update.
0356 *
0357 * @return number of seconds since last DGPS update
0358 */
0359 public Optional<Duration> getAgeOfGPSData() {
0360 return Optional.ofNullable(_ageOfGPSData);
0361 }
0362
0363 /**
0364 * Return the ID of DGPS station used in differential correction.
0365 *
0366 * @return the ID of DGPS station used in differential correction
0367 */
0368 public Optional<DGPSStation> getDGPSID() {
0369 return Optional.ofNullable(_dgpsID);
0370 }
0371
0372 /**
0373 * Return the instantaneous course at the point. This property is only
0374 * available when you read GPX files version 1.0. In version 1.1 this field
0375 * is always {@link Optional#empty()}.
0376 *
0377 * @since 1.3
0378 *
0379 * @return the instantaneous course at the point
0380 */
0381 public Optional<Degrees> getCourse() {
0382 return Optional.ofNullable(_course);
0383 }
0384
0385 /**
0386 * Return the (cloned) extensions document. The root element of the returned
0387 * document has the name {@code extensions}.
0388 * <pre>{@code
0389 * <extensions>
0390 * ...
0391 * </extensions>
0392 * }</pre>
0393 *
0394 * @since 1.5
0395 *
0396 * @return the extensions document
0397 * @throws org.w3c.dom.DOMException if the document could not be cloned,
0398 * because of an erroneous XML configuration
0399 */
0400 public Optional<Document> getExtensions() {
0401 return Optional.ofNullable(_extensions).map(XML::clone);
0402 }
0403
0404 /**
0405 * Convert the <em>immutable</em> way-point object into a <em>mutable</em>
0406 * builder initialized with the current way-point values.
0407 *
0408 * @since 1.1
0409 *
0410 * @return a new way-point builder initialized with the values of {@code this}
0411 * way-point
0412 */
0413 public Builder toBuilder() {
0414 return builder()
0415 .lat(_latitude)
0416 .lon(_longitude)
0417 .ele(_elevation)
0418 .speed(_speed)
0419 .time(_time)
0420 .magvar(_magneticVariation)
0421 .geoidheight(_geoidHeight)
0422 .name(_name)
0423 .cmt(_comment)
0424 .desc(_description)
0425 .src(_source)
0426 .links(_links)
0427 .sym(_symbol)
0428 .type(_type)
0429 .fix(_fix)
0430 .sat(_sat)
0431 .hdop(_hdop)
0432 .vdop(_vdop)
0433 .pdop(_pdop)
0434 .ageofdgpsdata(_ageOfGPSData)
0435 .dgpsid(_dgpsID)
0436 .course(_course)
0437 .extensions(_extensions);
0438 }
0439
0440 @Override
0441 public int hashCode() {
0442 return hash(
0443 _latitude,
0444 _longitude,
0445 _elevation,
0446 _speed,
0447 Objects.hashCode(_time),
0448 _magneticVariation,
0449 _geoidHeight,
0450 _name,
0451 _comment,
0452 _description,
0453 _source,
0454 Lists.hashCode(_links),
0455 _symbol,
0456 _type,
0457 _fix,
0458 _sat,
0459 _hdop,
0460 _vdop,
0461 _pdop,
0462 _ageOfGPSData,
0463 _dgpsID,
0464 _course
0465 );
0466 }
0467
0468 @Override
0469 public boolean equals(final Object obj) {
0470 return obj == this ||
0471 obj instanceof WayPoint wp &&
0472 Objects.equals(wp._latitude, _latitude) &&
0473 Objects.equals(wp._longitude, _longitude) &&
0474 Objects.equals(wp._elevation, _elevation) &&
0475 Objects.equals(wp._speed, _speed) &&
0476 Objects.equals(wp._time, _time) &&
0477 Objects.equals(wp._magneticVariation, _magneticVariation) &&
0478 Objects.equals(wp._geoidHeight, _geoidHeight) &&
0479 Objects.equals(wp._name, _name) &&
0480 Objects.equals(wp._comment, _comment) &&
0481 Objects.equals(wp._description, _description) &&
0482 Objects.equals(wp._source, _source) &&
0483 Lists.equals(wp._links, _links) &&
0484 Objects.equals(wp._symbol, _symbol) &&
0485 Objects.equals(wp._type, _type) &&
0486 Objects.equals(wp._fix, _fix) &&
0487 Objects.equals(wp._sat, _sat) &&
0488 Objects.equals(wp._hdop, _hdop) &&
0489 Objects.equals(wp._vdop, _vdop) &&
0490 Objects.equals(wp._pdop, _pdop) &&
0491 Objects.equals(wp._ageOfGPSData, _ageOfGPSData) &&
0492 Objects.equals(wp._dgpsID, _dgpsID) &&
0493 Objects.equals(wp._course, _course);
0494 }
0495
0496 @Override
0497 public String toString() {
0498 return _elevation != null
0499 ? String.format("[lat=%s, lon=%s, ele=%s]",
0500 _latitude, _longitude, _elevation)
0501 : String.format("[lat=%s, lon=%s]",
0502 _latitude, _longitude);
0503 }
0504
0505
0506 /**
0507 * Builder for creating a way-point with different parameters.
0508 * <p>
0509 * Creating a {@code WayPoint}:
0510 * <pre>{@code
0511 * final WayPoint point = WayPoint.builder()
0512 * .lat(48.2081743).lon(16.3738189).ele(160)
0513 * .build();
0514 * }</pre>
0515 *
0516 * @see #builder()
0517 */
0518 public static final class Builder {
0519 private Latitude _latitude;
0520 private Longitude _longitude;
0521
0522 private Length _elevation;
0523 private Speed _speed;
0524 private Instant _time;
0525 private Degrees _magneticVariation;
0526 private Length _geoidHeight;
0527 private String _name;
0528 private String _comment;
0529 private String _description;
0530 private String _source;
0531 private final List<Link> _links = new ArrayList<>();
0532 private String _symbol;
0533 private String _type;
0534 private Fix _fix;
0535 private UInt _sat;
0536 private Double _hdop;
0537 private Double _vdop;
0538 private Double _pdop;
0539 private Duration _ageOfDGPSData;
0540 private DGPSStation _dgpsID;
0541 private Degrees _course;
0542 private Document _extensions;
0543
0544 private Builder() {
0545 }
0546
0547 /**
0548 * Set the latitude value of the way-point.
0549 *
0550 * @param latitude the new latitude value
0551 * @return {@code this} {@code Builder} for method chaining
0552 * @throws NullPointerException if the given value is {@code null}
0553 */
0554 public Builder lat(final Latitude latitude) {
0555 _latitude = requireNonNull(latitude);
0556 return this;
0557 }
0558
0559 /**
0560 * Set the latitude value of the way-point.
0561 *
0562 * @param degrees the new latitude value
0563 * @return {@code this} {@code Builder} for method chaining
0564 * @throws IllegalArgumentException if the given value is not within the
0565 * range of {@code [-90..90]}
0566 */
0567 public Builder lat(final double degrees) {
0568 return lat(Latitude.ofDegrees(degrees));
0569 }
0570
0571 /**
0572 * Return the current latitude value.
0573 *
0574 * @since 1.1
0575 *
0576 * @return the current latitude value
0577 */
0578 public Latitude lat() {
0579 return _latitude;
0580 }
0581
0582 /**
0583 * Set the longitude value of the way-point.
0584 *
0585 * @param longitude the new longitude value
0586 * @return {@code this} {@code Builder} for method chaining
0587 * @throws NullPointerException if the given value is {@code null}
0588 */
0589 public Builder lon(final Longitude longitude) {
0590 _longitude = requireNonNull(longitude);
0591 return this;
0592 }
0593
0594 /**
0595 * Set the longitude value of the way-point.
0596 *
0597 * @param degrees the new longitude value
0598 * @return {@code this} {@code Builder} for method chaining
0599 * @throws IllegalArgumentException if the given value is not within the
0600 * range of {@code [-180..180]}
0601 */
0602 public Builder lon(final double degrees) {
0603 return lon(Longitude.ofDegrees(degrees));
0604 }
0605
0606 /**
0607 * Return the current longitude value.
0608 *
0609 * @since 1.1
0610 *
0611 * @return the current longitude value
0612 */
0613 public Longitude lon() {
0614 return _longitude;
0615 }
0616
0617 /**
0618 * Set the elevation of the point.
0619 *
0620 * @param elevation the elevation of the point
0621 * @return {@code this} {@code Builder} for method chaining
0622 */
0623 public Builder ele(final Length elevation) {
0624 _elevation = elevation;
0625 return this;
0626 }
0627
0628 /**
0629 * Set the elevation (in meters) of the point.
0630 *
0631 * @param meters the elevation of the point, in meters
0632 * @return {@code this} {@code Builder} for method chaining
0633 */
0634 public Builder ele(final double meters) {
0635 _elevation = Length.of(meters, METER);
0636 return this;
0637 }
0638
0639 /**
0640 * Set the elevation of the point.
0641 *
0642 * @param elevation the elevation of the point
0643 * @param unit the length unit
0644 * @return {@code this} {@code Builder} for method chaining
0645 */
0646 public Builder ele(final double elevation, final Length.Unit unit) {
0647 _elevation = Length.of(elevation, unit);
0648 return this;
0649 }
0650
0651 /**
0652 * Return the current elevation value.
0653 *
0654 * @since 1.1
0655 *
0656 * @return the current elevation value
0657 */
0658 public Optional<Length> ele() {
0659 return Optional.ofNullable(_elevation);
0660 }
0661
0662 /**
0663 * Set the current GPS speed.
0664 *
0665 * @param speed the current GPS speed
0666 * @return {@code this} {@code Builder} for method chaining
0667 */
0668 public Builder speed(final Speed speed) {
0669 _speed = speed;
0670 return this;
0671 }
0672
0673 /**
0674 * Set the current GPS speed
0675 *
0676 * @param speed the current speed value
0677 * @param unit the speed unit
0678 * @return {@code this} {@code Builder} for method chaining
0679 */
0680 public Builder speed(final double speed, final Speed.Unit unit) {
0681 return speed(Speed.of(speed, unit));
0682 }
0683
0684 /**
0685 * Set the current GPS speed.
0686 *
0687 * @param meterPerSecond the current GPS speed in m/s
0688 * @return {@code this} {@code Builder} for method chaining
0689 */
0690 public Builder speed(final double meterPerSecond) {
0691 _speed = Speed.of(meterPerSecond, METERS_PER_SECOND);
0692 return this;
0693 }
0694
0695 /**
0696 * Return the current speed value.
0697 *
0698 * @since 1.1
0699 *
0700 * @return the current speed value
0701 */
0702 public Optional<Speed> speed() {
0703 return Optional.ofNullable(_speed);
0704 }
0705
0706 /**
0707 * Set the creation/modification timestamp for the point.
0708 *
0709 * @param instant the instant of the way-point
0710 * @return {@code this} {@code Builder} for method chaining
0711 */
0712 public Builder time(final Instant instant) {
0713 _time = instant;
0714 return this;
0715 }
0716
0717 /**
0718 * Set the creation/modification timestamp for the point.
0719 *
0720 * @param millis the instant of the way-point
0721 * @return {@code this} {@code Builder} for method chaining
0722 */
0723 public Builder time(final long millis) {
0724 _time = Instant.ofEpochMilli(millis);
0725 return this;
0726 }
0727
0728 /**
0729 * Return the current time value.
0730 *
0731 * @return the current time value
0732 */
0733 public Optional<Instant> time() {
0734 return Optional.ofNullable(_time);
0735 }
0736
0737 /**
0738 * Set the magnetic variation at the point.
0739 *
0740 * @param variation the magnetic variation
0741 * @return {@code this} {@code Builder} for method chaining
0742 */
0743 public Builder magvar(final Degrees variation) {
0744 _magneticVariation = variation;
0745 return this;
0746 }
0747
0748 /**
0749 * Set the magnetic variation at the point.
0750 *
0751 * @param degree the magnetic variation
0752 * @return {@code this} {@code Builder} for method chaining
0753 * @throws IllegalArgumentException if the give value is not within the
0754 * range of {@code [0..360]}
0755 */
0756 public Builder magvar(final double degree) {
0757 _magneticVariation = Degrees.ofDegrees(degree);
0758 return this;
0759 }
0760
0761 /**
0762 * Return the current magnetic variation value.
0763 *
0764 * @since 1.1
0765 *
0766 * @return the current magnetic variation value
0767 */
0768 public Optional<Degrees> magvar() {
0769 return Optional.ofNullable(_magneticVariation);
0770 }
0771
0772 /**
0773 * Set the height (in meters) of geoid (mean sea level) above WGS84 earth
0774 * ellipsoid. As defined in NMEA GGA message.
0775 *
0776 * @param height the height (in meters) of geoid (mean sea level)
0777 * above WGS84 earth ellipsoid
0778 * @return {@code this} {@code Builder} for method chaining
0779 */
0780 public Builder geoidheight(final Length height) {
0781 _geoidHeight = height;
0782 return this;
0783 }
0784
0785 /**
0786 * Set the height (in meters) of geoid (mean sea level) above WGS84 earth
0787 * ellipsoid. As defined in NMEA GGA message.
0788 *
0789 * @param meter the height (in meters) of geoid (mean sea level)
0790 * above WGS84 earth ellipsoid
0791 * @return {@code this} {@code Builder} for method chaining
0792 */
0793 public Builder geoidheight(final double meter) {
0794 _geoidHeight = Length.of(meter, METER);
0795 return this;
0796 }
0797
0798 /**
0799 * Set the height of geoid (mean sea level) above WGS84 earth ellipsoid.
0800 * As defined in NMEA GGA message.
0801 *
0802 * @param length the height of geoid (mean sea level) above WGS84 earth
0803 * ellipsoid
0804 * @param unit the length unit
0805 * @return {@code this} {@code Builder} for method chaining
0806 */
0807 public Builder geoidheight(final double length, Length.Unit unit) {
0808 _geoidHeight = Length.of(length, unit);
0809 return this;
0810 }
0811
0812 /**
0813 * Return the current height of geoid value.
0814 *
0815 * @since 1.1
0816 *
0817 * @return the current height of geoid value
0818 */
0819 public Optional<Length> geoidheight() {
0820 return Optional.ofNullable(_geoidHeight);
0821 }
0822
0823 /**
0824 * Set the GPS name of the way-point. This field will be transferred to
0825 * and from the GPS. GPX does not place restrictions on the length of
0826 * this field or the characters contained in it. It is up to the
0827 * receiving application to validate the field before sending it to the
0828 * GPS.
0829 *
0830 * @param name the GPS name of the way-point
0831 * @return {@code this} {@code Builder} for method chaining
0832 */
0833 public Builder name(final String name) {
0834 _name = name;
0835 return this;
0836 }
0837
0838 /**
0839 * Return the current name value.
0840 *
0841 * @since 1.1
0842 *
0843 * @return the current name value
0844 */
0845 public Optional<String> name() {
0846 return Optional.ofNullable(_name);
0847 }
0848
0849 /**
0850 * Set the GPS way-point comment.
0851 *
0852 * @param comment the GPS way-point comment.
0853 * @return {@code this} {@code Builder} for method chaining
0854 */
0855 public Builder cmt(final String comment) {
0856 _comment = comment;
0857 return this;
0858 }
0859
0860 /**
0861 * Return the current comment value.
0862 *
0863 * @since 1.1
0864 *
0865 * @return the current comment value
0866 */
0867 public Optional<String> cmt() {
0868 return Optional.ofNullable(_comment);
0869 }
0870
0871 /**
0872 * Set the GPS way-point description.
0873 *
0874 * @param description the GPS way-point description.
0875 * @return {@code this} {@code Builder} for method chaining
0876 */
0877 public Builder desc(final String description) {
0878 _description = description;
0879 return this;
0880 }
0881
0882 /**
0883 * Return the current description value.
0884 *
0885 * @since 1.1
0886 *
0887 * @return the current description value
0888 */
0889 public Optional<String> desc() {
0890 return Optional.ofNullable(_description);
0891 }
0892
0893 /**
0894 * Set the GPS way-point source.
0895 *
0896 * @param source the GPS way-point source.
0897 * @return {@code this} {@code Builder} for method chaining
0898 */
0899 public Builder src(final String source) {
0900 _source = source;
0901 return this;
0902 }
0903
0904 /**
0905 * Return the current source value.
0906 *
0907 * @since 1.1
0908 *
0909 * @return the current source value
0910 */
0911 public Optional<String> src() {
0912 return Optional.ofNullable(_source);
0913 }
0914
0915 /**
0916 * Set the links to additional information about the way-point. The link
0917 * list may be {@code null}.
0918 *
0919 * @param links the links to additional information about the way-point
0920 * @return {@code this} {@code Builder} for method chaining
0921 * @throws NullPointerException if one of the links in the list is
0922 * {@code null}
0923 */
0924 public Builder links(final List<Link> links) {
0925 copyTo(links, _links);
0926 return this;
0927 }
0928
0929 /**
0930 * Set the links to external information about the way-point.
0931 *
0932 * @param link the links to external information about the way-point.
0933 * @return {@code this} {@code Builder} for method chaining
0934 */
0935 public Builder addLink(final Link link) {
0936 if (link != null) {
0937 _links.add(link);
0938 }
0939 return this;
0940 }
0941
0942 /**
0943 * Set the links to external information about the way-point.
0944 *
0945 * @param href the links to external information about the way-point.
0946 * @return {@code this} {@code Builder} for method chaining
0947 * @throws IllegalArgumentException if the given {@code href} is not a
0948 * valid URL
0949 */
0950 public Builder addLink(final String href) {
0951 if (href != null) {
0952 _links.add(Link.of(href));
0953 }
0954 return this;
0955 }
0956
0957 /**
0958 * Return the current links. The returned link list is mutable.
0959 *
0960 * @since 1.1
0961 *
0962 * @return the current links
0963 */
0964 public List<Link> links() {
0965 return new NonNullList<>(_links);
0966 }
0967
0968 /**
0969 * Set the text of GPS symbol name. For interchange with other programs,
0970 * use the exact spelling of the symbol as displayed on the GPS. If the
0971 * GPS abbreviates words, spell them out.
0972 *
0973 * @param symbol the text of GPS symbol name
0974 * @return {@code this} {@code Builder} for method chaining
0975 */
0976 public Builder sym(final String symbol) {
0977 _symbol = symbol;
0978 return this;
0979 }
0980
0981 /**
0982 * Return the current symbol value.
0983 *
0984 * @since 1.1
0985 *
0986 * @return the current symbol value
0987 */
0988 public Optional<String> sym() {
0989 return Optional.ofNullable(_symbol);
0990 }
0991
0992 /**
0993 * Set the type (classification) of the way-point.
0994 *
0995 * @param type the type (classification) of the way-point
0996 * @return {@code this} {@code Builder} for method chaining
0997 */
0998 public Builder type(final String type) {
0999 _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 }
|