/// <summary>
        /// Initializes a new instance of the <see cref="GpxWaypoint"/> class.
        /// </summary>
        /// <param name="longitude">
        /// The value for <see cref="Longitude"/>.
        /// </param>
        /// <param name="latitude">
        /// The value for <see cref="Latitude"/>.
        /// </param>
        /// <param name="elevationInMeters">
        /// The value for <see cref="ElevationInMeters"/>.
        /// </param>
        /// <param name="timestampUtc">
        /// The value for <see cref="TimestampUtc"/>.
        /// </param>
        /// <param name="magneticVariation">
        /// The value for <see cref="MagneticVariation"/>.
        /// </param>
        /// <param name="geoidHeight">
        /// The value for <see cref="GeoidHeight"/>.
        /// </param>
        /// <param name="name">
        /// The value for <see cref="Name"/>.
        /// </param>
        /// <param name="comment">
        /// The value for <see cref="Comment"/>.
        /// </param>
        /// <param name="description">
        /// The value for <see cref="Description"/>.
        /// </param>
        /// <param name="source">
        /// The value for <see cref="Source"/>.
        /// </param>
        /// <param name="links">
        /// The value for <see cref="Links"/>.
        /// </param>
        /// <param name="symbolText">
        /// The value for <see cref="SymbolText"/>.
        /// </param>
        /// <param name="classification">
        /// The value for <see cref="Classification"/>.
        /// </param>
        /// <param name="fixKind">
        /// The value for <see cref="FixKind"/>.
        /// </param>
        /// <param name="numberOfSatellites">
        /// The value for <see cref="NumberOfSatellites"/>.
        /// </param>
        /// <param name="horizontalDilutionOfPrecision">
        /// The value for <see cref="HorizontalDilutionOfPrecision"/>.
        /// </param>
        /// <param name="verticalDilutionOfPrecision">
        /// The value for <see cref="VerticalDilutionOfPrecision"/>.
        /// </param>
        /// <param name="positionDilutionOfPrecision">
        /// The value for <see cref="PositionDilutionOfPrecision"/>.
        /// </param>
        /// <param name="secondsSinceLastDgpsUpdate">
        /// The value for <see cref="SecondsSinceLastDgpsUpdate"/>.
        /// </param>
        /// <param name="dgpsStationId">
        /// The value for <see cref="DgpsStationId"/>.
        /// </param>
        /// <param name="extensions">
        /// The value for <see cref="Extensions"/>.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown when:
        /// <list type="bullet">
        /// <item><description><paramref name="elevationInMeters"/> is <see cref="Nullable{T}.HasValue">non-null</see> and not a finite number, or</description></item>
        /// <item><description><paramref name="timestampUtc"/> is <see cref="Nullable{T}.HasValue">non-null</see> and its <see cref="DateTime.Kind"/> is not <see cref="DateTimeKind.Utc"/>.</description></item>
        /// </list>
        /// </exception>
        public GpxWaypoint(GpxLongitude longitude, GpxLatitude latitude, double?elevationInMeters, DateTime?timestampUtc, GpxDegrees?magneticVariation, double?geoidHeight, string name, string comment, string description, string source, ImmutableArray <GpxWebLink> links, string symbolText, string classification, GpxFixKind?fixKind, uint?numberOfSatellites, double?horizontalDilutionOfPrecision, double?verticalDilutionOfPrecision, double?positionDilutionOfPrecision, double?secondsSinceLastDgpsUpdate, GpxDgpsStationId?dgpsStationId, object extensions)
        {
            Longitude = longitude;
            Latitude  = latitude;
            if (!(elevationInMeters is null))
            {
                _elevationInMeters = elevationInMeters.GetValueOrDefault();
                if (!_elevationInMeters.IsFinite())
                {
                    throw new ArgumentOutOfRangeException(nameof(elevationInMeters), elevationInMeters, "Must be a finite number");
                }
            }

            if (!(timestampUtc is null))
            {
                _timestampUtc = timestampUtc.GetValueOrDefault();
                if (_timestampUtc.Kind != DateTimeKind.Utc)
                {
                    throw new ArgumentOutOfRangeException(nameof(timestampUtc), timestampUtc, "Must be UTC");
                }
            }

            Name        = name;
            Description = description;
            SymbolText  = symbolText;

            // only allocate space for these less commonly used properties if they're used.
            if (magneticVariation is null && geoidHeight is null && comment is null && source is null && links.IsDefaultOrEmpty && classification is null && fixKind is null && numberOfSatellites is null && horizontalDilutionOfPrecision is null && verticalDilutionOfPrecision is null && positionDilutionOfPrecision is null && secondsSinceLastDgpsUpdate is null && dgpsStationId is null && extensions is null)
            {
                return;
            }

            _uncommonProperties = new UncommonProperties(magneticVariation, geoidHeight, comment, source, links, classification, fixKind, numberOfSatellites, horizontalDilutionOfPrecision, verticalDilutionOfPrecision, positionDilutionOfPrecision, secondsSinceLastDgpsUpdate, dgpsStationId, extensions);
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="GpxBoundingBox"/> class.
 /// </summary>
 /// <param name="minLongitude">
 /// The value for <see cref="MinLongitude"/>.
 /// </param>
 /// <param name="minLatitude">
 /// The value for <see cref="MinLatitude"/>.
 /// </param>
 /// <param name="maxLongitude">
 /// The value for <see cref="MaxLongitude"/>.
 /// </param>
 /// <param name="maxLatitude">
 /// The value for <see cref="MaxLatitude"/>.
 /// </param>
 public GpxBoundingBox(GpxLongitude minLongitude, GpxLatitude minLatitude, GpxLongitude maxLongitude, GpxLatitude maxLatitude)
 {
     this.MinLongitude = minLongitude;
     this.MinLatitude  = minLatitude;
     this.MaxLongitude = maxLongitude;
     this.MaxLatitude  = maxLatitude;
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="GpxBoundingBox"/> class.
 /// </summary>
 /// <param name="minLongitude">
 /// The value for <see cref="MinLongitude"/>.
 /// </param>
 /// <param name="minLatitude">
 /// The value for <see cref="MinLatitude"/>.
 /// </param>
 /// <param name="maxLongitude">
 /// The value for <see cref="MaxLongitude"/>.
 /// </param>
 /// <param name="maxLatitude">
 /// The value for <see cref="MaxLatitude"/>.
 /// </param>
 public GpxBoundingBox(GpxLongitude minLongitude, GpxLatitude minLatitude, GpxLongitude maxLongitude, GpxLatitude maxLatitude)
 {
     MinLongitude = minLongitude;
     MinLatitude  = minLatitude;
     MaxLongitude = maxLongitude;
     MaxLatitude  = maxLatitude;
 }
        /// <summary>
        /// Initializes a new instance of the <see cref="GpxWaypoint"/> class.
        /// </summary>
        /// <param name="coordinate">
        /// A <see cref="Coordinate"/> to use to initialize <see cref="Longitude"/> and
        /// <see cref="Latitude"/>.
        /// </param>
        /// <remarks>
        /// <see cref="Coordinate.X"/> and <see cref="Coordinate.Y"/> are assumed to be WGS-84
        /// degrees, and <see cref="Coordinate.Z"/> is assumed to be an elevation in meters when it
        /// isn't equal to <see cref="Coordinate.NullOrdinate"/>.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// Thrown when <paramref name="coordinate"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// Thrown when:
        /// <list type="bullet">
        /// <item><description><paramref name="coordinate"/>'s <see cref="Coordinate.X"/> value is not within the range of valid values for <see cref="GpxLongitude"/>,</description></item>
        /// <item><description><paramref name="coordinate"/>'s <see cref="Coordinate.Y"/> value is not within the range of valid values for <see cref="GpxLatitude"/>, or</description></item>
        /// <item><description><paramref name="coordinate"/>'s <see cref="Coordinate.Z"/> value <see cref="double.IsInfinity">is infinite</see></description></item>
        /// </list>
        /// </exception>
        public GpxWaypoint(Coordinate coordinate)
            : this(default, default)
        {
            if (coordinate is null)
            {
                throw new ArgumentNullException(nameof(coordinate));
            }

            if (!(Math.Abs(coordinate.X) <= 180))
            {
                throw new ArgumentException("X must be a valid WGS-84 longitude value, in degrees", nameof(coordinate));
            }

            if (!(Math.Abs(coordinate.Y) <= 90))
            {
                throw new ArgumentException("Y must be a valid WGS-84 latitude value, in degrees", nameof(coordinate));
            }

            if (double.IsInfinity(coordinate.Z))
            {
                throw new ArgumentException("Z must be either a finite value, in meters, or Coordinate.NullOrdinate if the coordinate is two-dimensional", nameof(coordinate));
            }

            double longitudeValue = coordinate.X;

            // +180 and -180 represent the same longitude value, so the GPX schema only allows -180.
            if (longitudeValue == 180)
            {
                longitudeValue = -180;
            }

            Longitude = new GpxLongitude(longitudeValue);
            Latitude  = new GpxLatitude(coordinate.Y);
            if (!double.IsNaN(coordinate.Z))
            {
                _elevationInMeters = coordinate.Z;
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="GpxWaypoint"/> class.
 /// </summary>
 /// <param name="longitude">
 /// The value for <see cref="Longitude"/>.
 /// </param>
 /// <param name="latitude">
 /// The value for <see cref="Latitude"/>.
 /// </param>
 public GpxWaypoint(GpxLongitude longitude, GpxLatitude latitude)
     : this(longitude, latitude, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default)
 {
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="GpxWaypoint"/> class.
 /// </summary>
 /// <param name="longitude">
 /// The value for <see cref="Longitude"/>.
 /// </param>
 /// <param name="latitude">
 /// The value for <see cref="Latitude"/>.
 /// </param>
 /// <param name="elevationInMeters">
 /// The value for <see cref="ElevationInMeters"/>.
 /// </param>
 /// <exception cref="ArgumentOutOfRangeException">
 /// Thrown when <paramref name="elevationInMeters"/> is <see cref="Nullable{T}.HasValue">non-null</see> and not a finite number.
 /// </exception>
 public GpxWaypoint(GpxLongitude longitude, GpxLatitude latitude, double?elevationInMeters)
     : this(longitude, latitude, elevationInMeters, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default)
 {
 }