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