WayPoint.java
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 outthrows IOException {
1800         int existing = 0;
1801         if (_elevation != nullexisting |= << 0;
1802         if (_speed != nullexisting |= << 1;
1803         if (_time != nullexisting |= << 2;
1804         if (_magneticVariation != nullexisting |= << 3;
1805         if (_geoidHeight != nullexisting |= << 4;
1806         if (_name != nullexisting |= << 5;
1807         if (_comment != nullexisting |= << 6;
1808         if (_description != nullexisting |= << 7;
1809         if (_source != nullexisting |= << 8;
1810         if (_links != null && !_links.isEmpty()) existing |= << 9;
1811         if (_symbol != nullexisting |= << 10;
1812         if (_type != nullexisting |= << 11;
1813         if (_fix != nullexisting |= << 12;
1814         if (_sat != nullexisting |= << 13;
1815         if (_hdop != nullexisting |= << 14;
1816         if (_vdop != nullexisting |= << 15;
1817         if (_pdop != nullexisting |= << 16;
1818         if (_ageOfGPSData != nullexisting |= << 17;
1819         if (_dgpsID != nullexisting |= << 18;
1820         if (_course != nullexisting |= << 19;
1821         if (_extensions != nullexisting |= << 20;
1822 
1823         out.writeInt(existing);
1824         out.writeDouble(_latitude.toDegrees());
1825         out.writeDouble(_longitude.toDegrees());
1826         if ((existing & (<<  0)) != 0) {
1827             assert _elevation != null;
1828             _elevation.write(out);
1829         }
1830         if ((existing & (<<  1)) != 0) {
1831             assert _speed != null;
1832             _speed.write(out);
1833         }
1834         if ((existing & (<<  2)) != 0) {
1835             assert _time != null;
1836             Instants.write(_time, out);
1837         }
1838         if ((existing & (<<  3)) != 0) {
1839             assert _magneticVariation != null;
1840             _magneticVariation.write(out);
1841         }
1842         if ((existing & (<<  4)) != 0) {
1843             assert _geoidHeight != null;
1844             _geoidHeight.write(out);
1845         }
1846         if ((existing & (<<  5)) != 0) {
1847             assert _name != null;
1848             IO.writeString(_name, out);
1849         }
1850         if ((existing & (<<  6)) != 0) {
1851             assert _comment != null;
1852             IO.writeString(_comment, out);
1853         }
1854         if ((existing & (<<  7)) != 0) {
1855             assert _description != null;
1856             IO.writeString(_description, out);
1857         }
1858         if ((existing & (<<  8)) != 0) {
1859             assert _source != null;
1860             IO.writeString(_source, out);
1861         }
1862         if ((existing & (<<  9)) != 0) {
1863             assert _links != null;
1864             IO.writes(_links, Link::write, out);
1865         }
1866         if ((existing & (<< 10)) != 0) {
1867             assert _symbol != null;
1868             IO.writeString(_symbol, out);
1869         }
1870         if ((existing & (<< 11)) != 0) {
1871             assert _type != null;
1872             IO.writeString(_type, out);
1873         }
1874         if ((existing & (<< 12)) != 0) {
1875             assert _fix != null;
1876             IO.writeString(_fix.name(), out);
1877         }
1878         if ((existing & (<< 13)) != 0) {
1879             assert _sat != null;
1880             _sat.write(out);
1881         }
1882         if ((existing & (<< 14)) != 0) {
1883             assert _hdop != null;
1884             out.writeDouble(_hdop);
1885         }
1886         if ((existing & (<< 15)) != 0) {
1887             assert _vdop != null;
1888             out.writeDouble(_vdop);
1889         }
1890         if ((existing & (<< 16)) != 0) {
1891             assert _pdop != null;
1892             out.writeDouble(_pdop);
1893         }
1894         if ((existing & (<< 17)) != 0) {
1895             assert _ageOfGPSData != null;
1896             out.writeLong(_ageOfGPSData.toMillis());
1897         }
1898         if ((existing & (<< 18)) != 0) {
1899             assert _dgpsID != null;
1900             _dgpsID.write(out);
1901         }
1902         if ((existing & (<< 19)) != 0) {
1903             assert _course != null;
1904             _course.write(out);
1905         }
1906         if ((existing & (<< 20)) != 0) {
1907             assert _extensions != null;
1908             IO.write(_extensions, out);
1909         }
1910     }
1911 
1912     static WayPoint read(final DataInput inthrows IOException {
1913         final int existing = in.readInt();
1914         return new WayPoint(
1915             Latitude.ofDegrees(in.readDouble()),
1916             Longitude.ofDegrees(in.readDouble()),
1917             ((existing & (<<  0)) != 0? Length.read(in: null,
1918             ((existing & (<<  1)) != 0? Speed.read(in: null,
1919             ((existing & (<<  2)) != 0? Instants.read(in: null,
1920             ((existing & (<<  3)) != 0? Degrees.read(in: null,
1921             ((existing & (<<  4)) != 0? Length.read(in: null,
1922             ((existing & (<<  5)) != 0? IO.readString(in: null,
1923             ((existing & (<<  6)) != 0? IO.readString(in: null,
1924             ((existing & (<<  7)) != 0? IO.readString(in: null,
1925             ((existing & (<<  8)) != 0? IO.readString(in: null,
1926             ((existing & (<<  9)) != 0? IO.reads(Link::read, in: null,
1927             ((existing & (<< 10)) != 0? IO.readString(in: null,
1928             ((existing & (<< 11)) != 0? IO.readString(in: null,
1929             ((existing & (<< 12)) != 0? Fix.valueOf(IO.readString(in)) : null,
1930             ((existing & (<< 13)) != 0? UInt.read(in: null,
1931             ((existing & (<< 14)) != 0? in.readDouble() : null,
1932             ((existing & (<< 15)) != 0? in.readDouble() : null,
1933             ((existing & (<< 16)) != 0? in.readDouble() : null,
1934             ((existing & (<< 17)) != 0? Duration.ofMillis(in.readLong()) : null,
1935             ((existing & (<< 18)) != 0? DGPSStation.read(in: null,
1936             ((existing & (<< 19)) != 0? Degrees.read(in: null,
1937             ((existing & (<< 20)) != 0? IO.readDoc(innull
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 }