private static double GetDistanceBetweenWaypoints(IofXml30Waypoint w1, IofXml30Waypoint w2)
    {
        // use spherical coordinates: rho, phi, theta
        const double rho = 6378200; // earth radius in metres

        double sinPhi0   = Math.Sin(0.5 * Math.PI + w1.Latitude / 180.0 * Math.PI);
        double cosPhi0   = Math.Cos(0.5 * Math.PI + w1.Latitude / 180.0 * Math.PI);
        double sinTheta0 = Math.Sin(w1.Longitude / 180.0 * Math.PI);
        double cosTheta0 = Math.Cos(w1.Longitude / 180.0 * Math.PI);

        double sinPhi1   = Math.Sin(0.5 * Math.PI + w2.Latitude / 180.0 * Math.PI);
        double cosPhi1   = Math.Cos(0.5 * Math.PI + w2.Latitude / 180.0 * Math.PI);
        double sinTheta1 = Math.Sin(w2.Longitude / 180.0 * Math.PI);
        double cosTheta1 = Math.Cos(w2.Longitude / 180.0 * Math.PI);

        var x1 = rho * sinPhi0 * cosTheta0;
        var y1 = rho * sinPhi0 * sinTheta0;
        var z1 = rho * cosPhi0;

        var x2 = rho * sinPhi1 * cosTheta1;
        var y2 = rho * sinPhi1 * sinTheta1;
        var z2 = rho * cosPhi1;

        return(DistancePointToPoint(x1, y1, z1, x2, y2, z2));
    }
    /// <summary>
    /// Writes the route in IOF XML 3.0 binary format to the specified stream.
    /// </summary>
    /// <param name="stream">The stream to write to.</param>
    public void WriteToStream(Stream stream)
    {
        IofXml30Waypoint previousWaypoint = null;

        foreach (var waypoint in Waypoints)
        {
            waypoint.WriteToStream(stream, previousWaypoint);
            previousWaypoint = waypoint;
        }
    }
    /// <summary>
    /// Reads a route in IOF XML 3.0 binary format from a stream.
    /// </summary>
    /// <param name="stream">The stream to read from.</param>
    public static IofXml30Route FromStream(Stream stream)
    {
        var waypoints = new List <IofXml30Waypoint>();

        while (stream.Position < stream.Length)
        {
            waypoints.Add(IofXml30Waypoint.FromStream(stream, waypoints.LastOrDefault()));
        }
        return(new IofXml30Route()
        {
            Waypoints = waypoints
        });
    }
    /// <summary>
    /// Writes the waypoint in IOF XML 3.0 binary format to a stream.
    /// </summary>
    /// <param name="stream">The stream to write to.</param>
    /// <param name="previousWaypoint">The previous waypoint of the route, or null if this is the first waypoint.</param>
    public void WriteToStream(Stream stream, IofXml30Waypoint previousWaypoint)
    {
        var timeStorageMode = TimeStorageMode.Full;
        if (previousWaypoint != null)
        {
          if ((StorageTime - previousWaypoint.StorageTime) % 1000 == 0 && (StorageTime - previousWaypoint.StorageTime) / 1000 <= timeSecondsThreshold)
          {
        timeStorageMode = TimeStorageMode.Seconds;
          }
          else if (StorageTime - previousWaypoint.StorageTime <= timeMillisecondsThreshold)
          {
        timeStorageMode = TimeStorageMode.Milliseconds;
          }
        }

        var positionStorageMode = PositionStorageMode.Full;
        if (previousWaypoint != null &&
        (StorageAltitude == null || (previousWaypoint.StorageAltitude != null && StorageAltitude - previousWaypoint.StorageAltitude >= altitudeDeltaLowerThreshold && StorageAltitude - previousWaypoint.StorageAltitude <= altitudeDeltaUpperThreshold)))
        {
          if (StorageLatitude - previousWaypoint.StorageLatitude >= lanLngSmallDeltaLowerThreshold && StorageLatitude - previousWaypoint.StorageLatitude <= lanLngSmallDeltaUpperThreshold &&
          StorageLongitude - previousWaypoint.StorageLongitude >= lanLngSmallDeltaLowerThreshold && StorageLongitude - previousWaypoint.StorageLongitude <= lanLngSmallDeltaUpperThreshold)
          {
        positionStorageMode = PositionStorageMode.SmallDelta;
          }
          else if (StorageLatitude - previousWaypoint.StorageLatitude >= lanLngBigDeltaLowerThreshold && StorageLatitude - previousWaypoint.StorageLatitude <= lanLngBigDeltaUpperThreshold &&
               StorageLongitude - previousWaypoint.StorageLongitude >= lanLngBigDeltaLowerThreshold && StorageLongitude - previousWaypoint.StorageLongitude <= lanLngBigDeltaUpperThreshold)
          {
        positionStorageMode = PositionStorageMode.BigDelta;
          }
        }

        var headerByte = 0;

        if (Type == IofXml30WaypointType.Interruption) headerByte |= (1 << 7);
        if (timeStorageMode == TimeStorageMode.Milliseconds) headerByte |= (1 << 6);
        if (timeStorageMode == TimeStorageMode.Seconds) headerByte |= (1 << 5);
        if (positionStorageMode == PositionStorageMode.BigDelta) headerByte |= (1 << 4);
        if (positionStorageMode == PositionStorageMode.SmallDelta) headerByte |= (1 << 3);
        if (StorageAltitude != null) headerByte |= (1 << 2);

        // header byte
        stream.WriteByte((byte)headerByte);

        // time byte(s)
        switch (timeStorageMode)
        {
          case TimeStorageMode.Full: // 6 bytes
        stream.Write(BitConverter.GetBytes(StorageTime).Reverse().ToArray(), 2, 6);
        break;
          case TimeStorageMode.Milliseconds: // 2 bytes
        stream.Write(BitConverter.GetBytes((ushort)(StorageTime - previousWaypoint.StorageTime)).Reverse().ToArray(), 0, 2);
        break;
          case TimeStorageMode.Seconds: // 1 byte
        stream.WriteByte((byte)((StorageTime - previousWaypoint.StorageTime) / 1000));
        break;
        }

        // position bytes
        switch (positionStorageMode)
        {
          case PositionStorageMode.Full: // 4 + 4 + 3 bytes
        stream.Write(BitConverter.GetBytes(StorageLatitude).Reverse().ToArray(), 0, 4);
        stream.Write(BitConverter.GetBytes(StorageLongitude).Reverse().ToArray(), 0, 4);
        if (StorageAltitude != null) stream.Write(BitConverter.GetBytes(StorageAltitude.Value).Reverse().ToArray(), 1, 3);
        break;
          case PositionStorageMode.BigDelta: // 2 + 2 + 1 bytes
        stream.Write(BitConverter.GetBytes((short)(StorageLatitude - previousWaypoint.StorageLatitude)).Reverse().ToArray(), 0, 2);
        stream.Write(BitConverter.GetBytes((short)(StorageLongitude - previousWaypoint.StorageLongitude)).Reverse().ToArray(), 0, 2);
        if (StorageAltitude != null) stream.Write(BitConverter.GetBytes((sbyte)(StorageAltitude - previousWaypoint.StorageAltitude).Value), 0, 1);
        break;
          case PositionStorageMode.SmallDelta: // 1 + 1 + 1 bytes
        stream.Write(BitConverter.GetBytes((sbyte)(StorageLatitude - previousWaypoint.StorageLatitude)), 0, 1);
        stream.Write(BitConverter.GetBytes((sbyte)(StorageLongitude - previousWaypoint.StorageLongitude)), 0, 1);
        if (StorageAltitude != null) stream.Write(BitConverter.GetBytes((sbyte)(StorageAltitude - previousWaypoint.StorageAltitude).Value), 0, 1);
        break;
        }
    }
    /// <summary>
    /// Reads a waypoint in IOF XML 3.0 binary format from a stream.
    /// </summary>
    /// <param name="stream">The stream to read from.</param>
    /// <param name="previousWaypoint">The previous waypoint of the route, or null if this is the first waypoint.</param>
    /// <returns></returns>
    public static IofXml30Waypoint FromStream(Stream stream, IofXml30Waypoint previousWaypoint)
    {
        var waypoint = new IofXml30Waypoint();

        // header byte
        var headerByte = stream.ReadByte();
        waypoint.Type = (headerByte & (1 << 7)) == 0 ? IofXml30WaypointType.Normal : IofXml30WaypointType.Interruption;
        var timeStorageMode = TimeStorageMode.Full;
        if ((headerByte & (1 << 6)) > 0)
        {
          timeStorageMode = TimeStorageMode.Milliseconds;
        }
        else if ((headerByte & (1 << 5)) > 0)
        {
          timeStorageMode = TimeStorageMode.Seconds;
        }
        var positionStorageMode = PositionStorageMode.Full;
        if ((headerByte & (1 << 4)) > 0)
        {
          positionStorageMode = PositionStorageMode.BigDelta;
        }
        else if ((headerByte & (1 << 3)) > 0)
        {
          positionStorageMode = PositionStorageMode.SmallDelta;
        }
        var altitudePresent = (headerByte & (1 << 2)) > 0;

        byte[] bytes;
        int b;

        // time byte(s)
        switch (timeStorageMode)
        {
          case TimeStorageMode.Full: // 4 bytes
        bytes = new byte[8];
        stream.Read(bytes, 2, 6);
        waypoint.StorageTime = BitConverter.ToUInt64(bytes.Reverse().ToArray(), 0);
        break;
          case TimeStorageMode.Milliseconds: // 2 bytes
        bytes = new byte[2];
        stream.Read(bytes, 0, 2);
        waypoint.StorageTime = previousWaypoint.StorageTime + BitConverter.ToUInt16(bytes.Reverse().ToArray(), 0);
        break;
          case TimeStorageMode.Seconds: // 1 byte
        b = stream.ReadByte();
        waypoint.StorageTime = previousWaypoint.StorageTime + (ulong)b * 1000;
        break;
        }

        // position bytes
        switch (positionStorageMode)
        {
          case PositionStorageMode.Full: // 4 + 4 + 3 bytes
        bytes = new byte[4];
        stream.Read(bytes, 0, 4);
        waypoint.StorageLatitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
        bytes = new byte[4];
        stream.Read(bytes, 0, 4);
        waypoint.StorageLongitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
        if (altitudePresent)
        {
          bytes = new byte[4];
          stream.Read(bytes, 1, 3);
          waypoint.StorageAltitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
        }
        break;
          case PositionStorageMode.BigDelta: // 2 + 2 + 1 bytes
        bytes = new byte[2];
        stream.Read(bytes, 0, 2);
        waypoint.StorageLatitude = previousWaypoint.StorageLatitude + BitConverter.ToInt16(bytes.Reverse().ToArray(), 0);
        bytes = new byte[2];
        stream.Read(bytes, 0, 2);
        waypoint.StorageLongitude = previousWaypoint.StorageLongitude + BitConverter.ToInt16(bytes.Reverse().ToArray(), 0);
        if (altitudePresent)
        {
          b = stream.ReadByte();
          waypoint.StorageAltitude = previousWaypoint.StorageAltitude + (sbyte)b;
        }
        break;
          case PositionStorageMode.SmallDelta: // 1 + 1 + 1 bytes
        b = stream.ReadByte();
        waypoint.StorageLatitude = previousWaypoint.StorageLatitude + (sbyte)b;
        b = stream.ReadByte();
        waypoint.StorageLongitude = previousWaypoint.StorageLongitude + (sbyte)b;
        if (altitudePresent)
        {
          b = stream.ReadByte();
          waypoint.StorageAltitude = previousWaypoint.StorageAltitude + (sbyte)b;
        }
        break;
        }

        return waypoint;
    }
    private static double GetDistanceBetweenWaypoints(IofXml30Waypoint w1, IofXml30Waypoint w2)
    {
        // use spherical coordinates: rho, phi, theta
        const double rho = 6378200; // earth radius in metres

        double sinPhi0 = Math.Sin(0.5 * Math.PI + w1.Latitude / 180.0 * Math.PI);
        double cosPhi0 = Math.Cos(0.5 * Math.PI + w1.Latitude / 180.0 * Math.PI);
        double sinTheta0 = Math.Sin(w1.Longitude / 180.0 * Math.PI);
        double cosTheta0 = Math.Cos(w1.Longitude / 180.0 * Math.PI);

        double sinPhi1 = Math.Sin(0.5 * Math.PI + w2.Latitude / 180.0 * Math.PI);
        double cosPhi1 = Math.Cos(0.5 * Math.PI + w2.Latitude / 180.0 * Math.PI);
        double sinTheta1 = Math.Sin(w2.Longitude / 180.0 * Math.PI);
        double cosTheta1 = Math.Cos(w2.Longitude / 180.0 * Math.PI);

        var x1 = rho * sinPhi0 * cosTheta0;
        var y1 = rho * sinPhi0 * sinTheta0;
        var z1 = rho * cosPhi0;

        var x2 = rho * sinPhi1 * cosTheta1;
        var y2 = rho * sinPhi1 * sinTheta1;
        var z2 = rho * cosPhi1;

        return DistancePointToPoint(x1, y1, z1, x2, y2, z2);
    }
    /// <summary>
    /// Reads a waypoint in IOF XML 3.0 binary format from a stream.
    /// </summary>
    /// <param name="stream">The stream to read from.</param>
    /// <param name="previousWaypoint">The previous waypoint of the route, or null if this is the first waypoint.</param>
    /// <returns></returns>
    public static IofXml30Waypoint FromStream(Stream stream, IofXml30Waypoint previousWaypoint)
    {
        var waypoint = new IofXml30Waypoint();

        // header byte
        var headerByte = stream.ReadByte();

        waypoint.Type = (headerByte & (1 << 7)) == 0 ? IofXml30WaypointType.Normal : IofXml30WaypointType.Interruption;
        var timeStorageMode = TimeStorageMode.Full;

        if ((headerByte & (1 << 6)) > 0)
        {
            timeStorageMode = TimeStorageMode.Milliseconds;
        }
        else if ((headerByte & (1 << 5)) > 0)
        {
            timeStorageMode = TimeStorageMode.Seconds;
        }
        var positionStorageMode = PositionStorageMode.Full;

        if ((headerByte & (1 << 4)) > 0)
        {
            positionStorageMode = PositionStorageMode.BigDelta;
        }
        else if ((headerByte & (1 << 3)) > 0)
        {
            positionStorageMode = PositionStorageMode.SmallDelta;
        }
        var altitudePresent = (headerByte & (1 << 2)) > 0;

        byte[] bytes;
        int    b;

        // time byte(s)
        switch (timeStorageMode)
        {
        case TimeStorageMode.Full: // 4 bytes
            bytes = new byte[8];
            stream.Read(bytes, 2, 6);
            waypoint.StorageTime = BitConverter.ToUInt64(bytes.Reverse().ToArray(), 0);
            break;

        case TimeStorageMode.Milliseconds: // 2 bytes
            bytes = new byte[2];
            stream.Read(bytes, 0, 2);
            waypoint.StorageTime = previousWaypoint.StorageTime + BitConverter.ToUInt16(bytes.Reverse().ToArray(), 0);
            break;

        case TimeStorageMode.Seconds: // 1 byte
            b = stream.ReadByte();
            waypoint.StorageTime = previousWaypoint.StorageTime + (ulong)b * 1000;
            break;
        }

        // position bytes
        switch (positionStorageMode)
        {
        case PositionStorageMode.Full: // 4 + 4 + 3 bytes
            bytes = new byte[4];
            stream.Read(bytes, 0, 4);
            waypoint.StorageLatitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
            bytes = new byte[4];
            stream.Read(bytes, 0, 4);
            waypoint.StorageLongitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
            if (altitudePresent)
            {
                bytes = new byte[4];
                stream.Read(bytes, 1, 3);
                waypoint.StorageAltitude = BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);
            }
            break;

        case PositionStorageMode.BigDelta: // 2 + 2 + 1 bytes
            bytes = new byte[2];
            stream.Read(bytes, 0, 2);
            waypoint.StorageLatitude = previousWaypoint.StorageLatitude + BitConverter.ToInt16(bytes.Reverse().ToArray(), 0);
            bytes = new byte[2];
            stream.Read(bytes, 0, 2);
            waypoint.StorageLongitude = previousWaypoint.StorageLongitude + BitConverter.ToInt16(bytes.Reverse().ToArray(), 0);
            if (altitudePresent)
            {
                b = stream.ReadByte();
                waypoint.StorageAltitude = previousWaypoint.StorageAltitude + (sbyte)b;
            }
            break;

        case PositionStorageMode.SmallDelta: // 1 + 1 + 1 bytes
            b = stream.ReadByte();
            waypoint.StorageLatitude = previousWaypoint.StorageLatitude + (sbyte)b;
            b = stream.ReadByte();
            waypoint.StorageLongitude = previousWaypoint.StorageLongitude + (sbyte)b;
            if (altitudePresent)
            {
                b = stream.ReadByte();
                waypoint.StorageAltitude = previousWaypoint.StorageAltitude + (sbyte)b;
            }
            break;
        }

        return(waypoint);
    }
    /// <summary>
    /// Writes the waypoint in IOF XML 3.0 binary format to a stream.
    /// </summary>
    /// <param name="stream">The stream to write to.</param>
    /// <param name="previousWaypoint">The previous waypoint of the route, or null if this is the first waypoint.</param>
    public void WriteToStream(Stream stream, IofXml30Waypoint previousWaypoint)
    {
        var timeStorageMode = TimeStorageMode.Full;

        if (previousWaypoint != null)
        {
            if ((StorageTime - previousWaypoint.StorageTime) % 1000 == 0 && (StorageTime - previousWaypoint.StorageTime) / 1000 <= timeSecondsThreshold)
            {
                timeStorageMode = TimeStorageMode.Seconds;
            }
            else if (StorageTime - previousWaypoint.StorageTime <= timeMillisecondsThreshold)
            {
                timeStorageMode = TimeStorageMode.Milliseconds;
            }
        }

        var positionStorageMode = PositionStorageMode.Full;

        if (previousWaypoint != null &&
            (StorageAltitude == null || (previousWaypoint.StorageAltitude != null && StorageAltitude - previousWaypoint.StorageAltitude >= altitudeDeltaLowerThreshold && StorageAltitude - previousWaypoint.StorageAltitude <= altitudeDeltaUpperThreshold)))
        {
            if (StorageLatitude - previousWaypoint.StorageLatitude >= lanLngSmallDeltaLowerThreshold && StorageLatitude - previousWaypoint.StorageLatitude <= lanLngSmallDeltaUpperThreshold &&
                StorageLongitude - previousWaypoint.StorageLongitude >= lanLngSmallDeltaLowerThreshold && StorageLongitude - previousWaypoint.StorageLongitude <= lanLngSmallDeltaUpperThreshold)
            {
                positionStorageMode = PositionStorageMode.SmallDelta;
            }
            else if (StorageLatitude - previousWaypoint.StorageLatitude >= lanLngBigDeltaLowerThreshold && StorageLatitude - previousWaypoint.StorageLatitude <= lanLngBigDeltaUpperThreshold &&
                     StorageLongitude - previousWaypoint.StorageLongitude >= lanLngBigDeltaLowerThreshold && StorageLongitude - previousWaypoint.StorageLongitude <= lanLngBigDeltaUpperThreshold)
            {
                positionStorageMode = PositionStorageMode.BigDelta;
            }
        }

        var headerByte = 0;

        if (Type == IofXml30WaypointType.Interruption)
        {
            headerByte |= (1 << 7);
        }
        if (timeStorageMode == TimeStorageMode.Milliseconds)
        {
            headerByte |= (1 << 6);
        }
        if (timeStorageMode == TimeStorageMode.Seconds)
        {
            headerByte |= (1 << 5);
        }
        if (positionStorageMode == PositionStorageMode.BigDelta)
        {
            headerByte |= (1 << 4);
        }
        if (positionStorageMode == PositionStorageMode.SmallDelta)
        {
            headerByte |= (1 << 3);
        }
        if (StorageAltitude != null)
        {
            headerByte |= (1 << 2);
        }

        // header byte
        stream.WriteByte((byte)headerByte);

        // time byte(s)
        switch (timeStorageMode)
        {
        case TimeStorageMode.Full: // 6 bytes
            stream.Write(BitConverter.GetBytes(StorageTime).Reverse().ToArray(), 2, 6);
            break;

        case TimeStorageMode.Milliseconds: // 2 bytes
            stream.Write(BitConverter.GetBytes((ushort)(StorageTime - previousWaypoint.StorageTime)).Reverse().ToArray(), 0, 2);
            break;

        case TimeStorageMode.Seconds: // 1 byte
            stream.WriteByte((byte)((StorageTime - previousWaypoint.StorageTime) / 1000));
            break;
        }

        // position bytes
        switch (positionStorageMode)
        {
        case PositionStorageMode.Full: // 4 + 4 + 3 bytes
            stream.Write(BitConverter.GetBytes(StorageLatitude).Reverse().ToArray(), 0, 4);
            stream.Write(BitConverter.GetBytes(StorageLongitude).Reverse().ToArray(), 0, 4);
            if (StorageAltitude != null)
            {
                stream.Write(BitConverter.GetBytes(StorageAltitude.Value).Reverse().ToArray(), 1, 3);
            }
            break;

        case PositionStorageMode.BigDelta: // 2 + 2 + 1 bytes
            stream.Write(BitConverter.GetBytes((short)(StorageLatitude - previousWaypoint.StorageLatitude)).Reverse().ToArray(), 0, 2);
            stream.Write(BitConverter.GetBytes((short)(StorageLongitude - previousWaypoint.StorageLongitude)).Reverse().ToArray(), 0, 2);
            if (StorageAltitude != null)
            {
                stream.Write(BitConverter.GetBytes((sbyte)(StorageAltitude - previousWaypoint.StorageAltitude).Value), 0, 1);
            }
            break;

        case PositionStorageMode.SmallDelta: // 1 + 1 + 1 bytes
            stream.Write(BitConverter.GetBytes((sbyte)(StorageLatitude - previousWaypoint.StorageLatitude)), 0, 1);
            stream.Write(BitConverter.GetBytes((sbyte)(StorageLongitude - previousWaypoint.StorageLongitude)), 0, 1);
            if (StorageAltitude != null)
            {
                stream.Write(BitConverter.GetBytes((sbyte)(StorageAltitude - previousWaypoint.StorageAltitude).Value), 0, 1);
            }
            break;
        }
    }