# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test cases for positioning primitives.
"""
from __future__ import annotations
from zope.interface import verify
from twisted.positioning import base
from twisted.positioning.base import Angles, Directions
from twisted.positioning.ipositioning import IPositioningBeacon
from twisted.trial.unittest import TestCase
class AngleTests(TestCase):
"""
Tests for the L{twisted.positioning.base.Angle} class.
"""
def test_empty(self) -> None:
"""
The repr of an empty angle says that is of unknown type and unknown
value.
"""
a = base.Angle()
self.assertEqual("<Angle of unknown type (unknown value)>", repr(a))
def test_variation(self) -> None:
"""
The repr of an empty variation says that it is a variation of unknown
value.
"""
a = base.Angle(angleType=Angles.VARIATION)
self.assertEqual("<Variation (unknown value)>", repr(a))
def test_unknownType(self) -> None:
"""
The repr of an angle of unknown type but a given value displays that
type and value in its repr.
"""
a = base.Angle(1.0)
self.assertEqual("<Angle of unknown type (1.0 degrees)>", repr(a))
def test_bogusType(self) -> None:
"""
Trying to create an angle with a bogus type raises C{ValueError}.
"""
self.assertRaises(ValueError, base.Angle, angleType="BOGUS")
class HeadingTests(TestCase):
"""
Tests for the L{twisted.positioning.base.Heading} class.
"""
def test_simple(self) -> None:
"""
Tests that a simple heading has a value in decimal degrees, which is
also its value when converted to a float. Its variation, and by
consequence its corrected heading, is L{None}.
"""
h = base.Heading(1.0)
self.assertEqual(h.inDecimalDegrees, 1.0)
self.assertEqual(float(h), 1.0)
self.assertIsNone(h.variation)
self.assertIsNone(h.correctedHeading)
def test_headingWithoutVariationRepr(self) -> None:
"""
A repr of a heading with no variation reports its value and that the
variation is unknown.
"""
heading = base.Heading(1.0)
expectedRepr = "<Heading (1.0 degrees, unknown variation)>"
self.assertEqual(repr(heading), expectedRepr)
def test_headingWithVariationRepr(self) -> None:
"""
A repr of a heading with known variation reports its value and the
value of that variation.
"""
angle, variation = 1.0, -10.0
heading = base.Heading.fromFloats(angle, variationValue=variation)
reprTemplate = "<Heading ({0} degrees, <Variation ({1} degrees)>)>"
self.assertEqual(repr(heading), reprTemplate.format(angle, variation))
def test_valueEquality(self) -> None:
"""
Headings with the same values compare equal.
"""
self.assertEqual(base.Heading(1.0), base.Heading(1.0))
def test_valueInequality(self) -> None:
"""
Headings with different values compare unequal.
"""
self.assertNotEqual(base.Heading(1.0), base.Heading(2.0))
def test_zeroHeadingEdgeCase(self) -> None:
"""
Headings can be instantiated with a value of 0 and no variation.
"""
base.Heading(0)
def test_zeroHeading180DegreeVariationEdgeCase(self) -> None:
"""
Headings can be instantiated with a value of 0 and a variation of 180
degrees.
"""
base.Heading(0, 180)
def _badValueTest(self, **kw: float) -> None:
"""
Helper function for verifying that bad values raise C{ValueError}.
@param kw: The keyword arguments passed to L{base.Heading.fromFloats}.
"""
self.assertRaises(ValueError, base.Heading.fromFloats, **kw)
def test_badAngleValueEdgeCase(self) -> None:
"""
Headings can not be instantiated with a value of 360 degrees.
"""
self._badValueTest(angleValue=360.0)
def test_badVariationEdgeCase(self) -> None:
"""
Headings can not be instantiated with a variation of -180 degrees.
"""
self._badValueTest(variationValue=-180.0)
def test_negativeHeading(self) -> None:
"""
Negative heading values raise C{ValueError}.
"""
self._badValueTest(angleValue=-10.0)
def test_headingTooLarge(self) -> None:
"""
Heading values greater than C{360.0} raise C{ValueError}.
"""
self._badValueTest(angleValue=370.0)
def test_variationTooNegative(self) -> None:
"""
Variation values less than C{-180.0} raise C{ValueError}.
"""
self._badValueTest(variationValue=-190.0)
def test_variationTooPositive(self) -> None:
"""
Variation values greater than C{180.0} raise C{ValueError}.
"""
self._badValueTest(variationValue=190.0)
def test_correctedHeading(self) -> None:
"""
A heading with a value and a variation has a corrected heading.
"""
h = base.Heading.fromFloats(1.0, variationValue=-10.0)
self.assertEqual(h.correctedHeading, base.Angle(11.0, Angles.HEADING))
def test_correctedHeadingOverflow(self) -> None:
"""
A heading with a value and a variation has the appropriate corrected
heading value, even when the variation puts it across the 360 degree
boundary.
"""
h = base.Heading.fromFloats(359.0, variationValue=-2.0)
self.assertEqual(h.correctedHeading, base.Angle(1.0, Angles.HEADING))
def test_correctedHeadingOverflowEdgeCase(self) -> None:
"""
A heading with a value and a variation has the appropriate corrected
heading value, even when the variation puts it exactly at the 360
degree boundary.
"""
h = base.Heading.fromFloats(359.0, variationValue=-1.0)
self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING))
def test_correctedHeadingUnderflow(self) -> None:
"""
A heading with a value and a variation has the appropriate corrected
heading value, even when the variation puts it under the 0 degree
boundary.
"""
h = base.Heading.fromFloats(1.0, variationValue=2.0)
self.assertEqual(h.correctedHeading, base.Angle(359.0, Angles.HEADING))
def test_correctedHeadingUnderflowEdgeCase(self) -> None:
"""
A heading with a value and a variation has the appropriate corrected
heading value, even when the variation puts it exactly at the 0
degree boundary.
"""
h = base.Heading.fromFloats(1.0, variationValue=1.0)
self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING))
def test_setVariationSign(self) -> None:
"""
Setting the sign of a heading changes the variation sign.
"""
h = base.Heading.fromFloats(1.0, variationValue=1.0)
h.setSign(1)
self.assertEqual(h.variation.inDecimalDegrees, 1.0)
h.setSign(-1)
self.assertEqual(h.variation.inDecimalDegrees, -1.0)
def test_setBadVariationSign(self) -> None:
"""
Setting the sign of a heading to values that aren't C{-1} or C{1}
raises C{ValueError} and does not affect the heading.
"""
h = base.Heading.fromFloats(1.0, variationValue=1.0)
self.assertRaises(ValueError, h.setSign, -50)
self.assertEqual(h.variation.inDecimalDegrees, 1.0)
self.assertRaises(ValueError, h.setSign, 0)
self.assertEqual(h.variation.inDecimalDegrees, 1.0)
self.assertRaises(ValueError, h.setSign, 50)
self.assertEqual(h.variation.inDecimalDegrees, 1.0)
def test_setUnknownVariationSign(self) -> None:
"""
Setting the sign on a heading with unknown variation raises
C{ValueError}.
"""
h = base.Heading.fromFloats(1.0)
self.assertIsNone(h.variation.inDecimalDegrees)
self.assertRaises(ValueError, h.setSign, 1)
class CoordinateTests(TestCase):
def test_float(self) -> None:
"""
Coordinates can be converted to floats.
"""
coordinate = base.Coordinate(10.0)
self.assertEqual(float(coordinate), 10.0)
def test_repr(self) -> None:
"""
Coordinates that aren't explicitly latitudes or longitudes have an
appropriate repr.
"""
coordinate = base.Coordinate(10.0)
expectedRepr = f"<Angle of unknown type ({10.0} degrees)>"
self.assertEqual(repr(coordinate), expectedRepr)
def test_positiveLatitude(self) -> None:
"""
Positive latitudes have a repr that specifies their type and value.
"""
coordinate = base.Coordinate(10.0, Angles.LATITUDE)
expectedRepr = f"<Latitude ({10.0} degrees)>"
self.assertEqual(repr(coordinate), expectedRepr)
def test_negativeLatitude(self) -> None:
"""
Negative latitudes have a repr that specifies their type and value.
"""
coordinate = base.Coordinate(-50.0, Angles.LATITUDE)
expectedRepr = f"<Latitude ({-50.0} degrees)>"
self.assertEqual(repr(coordinate), expectedRepr)
def test_positiveLongitude(self) -> None:
"""
Positive longitudes have a repr that specifies their type and value.
"""
longitude = base.Coordinate(50.0, Angles.LONGITUDE)
expectedRepr = f"<Longitude ({50.0} degrees)>"
self.assertEqual(repr(longitude), expectedRepr)
def test_negativeLongitude(self) -> None:
"""
Negative longitudes have a repr that specifies their type and value.
"""
longitude = base.Coordinate(-50.0, Angles.LONGITUDE)
expectedRepr = f"<Longitude ({-50.0} degrees)>"
self.assertEqual(repr(longitude), expectedRepr)
def test_bogusCoordinateType(self) -> None:
"""
Creating coordinates with bogus types rasies C{ValueError}.
"""
self.assertRaises(ValueError, base.Coordinate, 150.0, "BOGUS")
def test_angleTypeNotCoordinate(self) -> None:
"""
Creating coordinates with angle types that aren't coordinates raises
C{ValueError}.
"""
self.assertRaises(ValueError, base.Coordinate, 150.0, Angles.HEADING)
def test_equality(self) -> None:
"""
Coordinates with the same value and type are equal.
"""
def makeCoordinate() -> base.Coordinate:
return base.Coordinate(1.0, Angles.LONGITUDE)
self.assertEqual(makeCoordinate(), makeCoordinate())
def test_differentAnglesInequality(self) -> None:
"""
Coordinates with different values aren't equal.
"""
c1 = base.Coordinate(1.0)
c2 = base.Coordinate(-1.0)
self.assertNotEqual(c1, c2)
def test_differentTypesInequality(self) -> None:
"""
Coordinates with the same values but different types aren't equal.
"""
c1 = base.Coordinate(1.0, Angles.LATITUDE)
c2 = base.Coordinate(1.0, Angles.LONGITUDE)
self.assertNotEqual(c1, c2)
def test_sign(self) -> None:
"""
Setting the sign on a coordinate sets the sign of the value of the
coordinate.
"""
c = base.Coordinate(50.0, Angles.LATITUDE)
c.setSign(1)
self.assertEqual(c.inDecimalDegrees, 50.0)
c.setSign(-1)
self.assertEqual(c.inDecimalDegrees, -50.0)
def test_badVariationSign(self) -> None:
"""
Setting a bogus sign value (not -1 or 1) on a coordinate raises
C{ValueError} and doesn't affect the coordinate.
"""
value = 50.0
c = base.Coordinate(value, Angles.LATITUDE)
self.assertRaises(ValueError, c.setSign, -50)
self.assertEqual(c.inDecimalDegrees, 50.0)
self.assertRaises(ValueError, c.setSign, 0)
self.assertEqual(c.inDecimalDegrees, 50.0)
self.assertRaises(ValueError, c.setSign, 50)
self.assertEqual(c.inDecimalDegrees, 50.0)
def test_northernHemisphere(self) -> None:
"""
Positive latitudes are in the northern hemisphere.
"""
coordinate = base.Coordinate(1.0, Angles.LATITUDE)
self.assertEqual(coordinate.hemisphere, Directions.NORTH)
def test_easternHemisphere(self) -> None:
"""
Positive longitudes are in the eastern hemisphere.
"""
coordinate = base.Coordinate(1.0, Angles.LONGITUDE)
self.assertEqual(coordinate.hemisphere, Directions.EAST)
def test_southernHemisphere(self) -> None:
"""
Negative latitudes are in the southern hemisphere.
"""
coordinate = base.Coordinate(-1.0, Angles.LATITUDE)
self.assertEqual(coordinate.hemisphere, Directions.SOUTH)
def test_westernHemisphere(self) -> None:
"""
Negative longitudes are in the western hemisphere.
"""
coordinate = base.Coordinate(-1.0, Angles.LONGITUDE)
self.assertEqual(coordinate.hemisphere, Directions.WEST)
def test_badHemisphere(self) -> None:
"""
Accessing the hemisphere for a coordinate that can't compute it
raises C{ValueError}.
"""
coordinate = base.Coordinate(1.0, None)
self.assertRaises(ValueError, lambda: coordinate.hemisphere)
def test_latitudeTooLarge(self) -> None:
"""
Creating a latitude with a value greater than or equal to 90 degrees
raises C{ValueError}.
"""
self.assertRaises(ValueError, _makeLatitude, 150.0)
self.assertRaises(ValueError, _makeLatitude, 90.0)
def test_latitudeTooSmall(self) -> None:
"""
Creating a latitude with a value less than or equal to -90 degrees
raises C{ValueError}.
"""
self.assertRaises(ValueError, _makeLatitude, -150.0)
self.assertRaises(ValueError, _makeLatitude, -90.0)
def test_longitudeTooLarge(self) -> None:
"""
Creating a longitude with a value greater than or equal to 180 degrees
raises C{ValueError}.
"""
self.assertRaises(ValueError, _makeLongitude, 250.0)
self.assertRaises(ValueError, _makeLongitude, 180.0)
def test_longitudeTooSmall(self) -> None:
"""
Creating a longitude with a value less than or equal to -180 degrees
raises C{ValueError}.
"""
self.assertRaises(ValueError, _makeLongitude, -250.0)
self.assertRaises(ValueError, _makeLongitude, -180.0)
def test_inDegreesMinutesSeconds(self) -> None:
"""
Coordinate values can be accessed in degrees, minutes, seconds.
"""
c = base.Coordinate(50.5, Angles.LATITUDE)
self.assertEqual(c.inDegreesMinutesSeconds, (50, 30, 0))
c = base.Coordinate(50.213, Angles.LATITUDE)
self.assertEqual(c.inDegreesMinutesSeconds, (50, 12, 46))
def test_unknownAngleInDegreesMinutesSeconds(self) -> None:
"""
If the vaue of a coordinate is L{None}, its values in degrees,
minutes, seconds is also L{None}.
"""
c = base.Coordinate(None, None)
self.assertIsNone(c.inDegreesMinutesSeconds)
def _makeLatitude(value: float) -> base.Coordinate:
"""
Builds and returns a latitude of given value.
"""
return base.Coordinate(value, Angles.LATITUDE)
def _makeLongitude(value: float) -> base.Coordinate:
"""
Builds and returns a longitude of given value.
"""
return base.Coordinate(value, Angles.LONGITUDE)
class AltitudeTests(TestCase):
"""
Tests for the L{twisted.positioning.base.Altitude} class.
"""
def test_value(self) -> None:
"""
Altitudes can be instantiated and reports the correct value in
meters and feet, as well as when converted to float.
"""
altitude = base.Altitude(1.0)
self.assertEqual(float(altitude), 1.0)
self.assertEqual(altitude.inMeters, 1.0)
self.assertEqual(altitude.inFeet, 1.0 / base.METERS_PER_FOOT)
def test_repr(self) -> None:
"""
Altitudes report their type and value in their repr.
"""
altitude = base.Altitude(1.0)
self.assertEqual(repr(altitude), "<Altitude (1.0 m)>")
def test_equality(self) -> None:
"""
Altitudes with equal values compare equal.
"""
firstAltitude = base.Altitude(1.0)
secondAltitude = base.Altitude(1.0)
self.assertEqual(firstAltitude, secondAltitude)
def test_inequality(self) -> None:
"""
Altitudes with different values don't compare equal.
"""
firstAltitude = base.Altitude(1.0)
secondAltitude = base.Altitude(-1.0)
self.assertNotEqual(firstAltitude, secondAltitude)
class SpeedTests(TestCase):
"""
Tests for the L{twisted.positioning.base.Speed} class.
"""
def test_value(self) -> None:
"""
Speeds can be instantiated, and report their value in meters
per second, and can be converted to floats.
"""
speed = base.Speed(50.0)
self.assertEqual(speed.inMetersPerSecond, 50.0)
self.assertEqual(float(speed), 50.0)
def test_repr(self) -> None:
"""
Speeds report their type and value in their repr.
"""
speed = base.Speed(50.0)
self.assertEqual(repr(speed), "<Speed (50.0 m/s)>")
def test_negativeSpeeds(self) -> None:
"""
Creating a negative speed raises C{ValueError}.
"""
self.assertRaises(ValueError, base.Speed, -1.0)
def test_inKnots(self) -> None:
"""
A speed can be converted into its value in knots.
"""
speed = base.Speed(1.0)
self.assertEqual(1 / base.MPS_PER_KNOT, speed.inKnots)
def test_asFloat(self) -> None:
"""
A speed can be converted into a C{float}.
"""
self.assertEqual(1.0, float(base.Speed(1.0)))
class ClimbTests(TestCase):
"""
Tests for L{twisted.positioning.base.Climb}.
"""
def test_simple(self) -> None:
"""
Speeds can be instantiated, and report their value in meters
per second, and can be converted to floats.
"""
climb = base.Climb(42.0)
self.assertEqual(climb.inMetersPerSecond, 42.0)
self.assertEqual(float(climb), 42.0)
def test_repr(self) -> None:
"""
Climbs report their type and value in their repr.
"""
climb = base.Climb(42.0)
self.assertEqual(repr(climb), "<Climb (42.0 m/s)>")
def test_negativeClimbs(self) -> None:
"""
Climbs can have negative values, and still report that value
in meters per second and when converted to floats.
"""
climb = base.Climb(-42.0)
self.assertEqual(climb.inMetersPerSecond, -42.0)
self.assertEqual(float(climb), -42.0)
def test_speedInKnots(self) -> None:
"""
A climb can be converted into its value in knots.
"""
climb = base.Climb(1.0)
self.assertEqual(1 / base.MPS_PER_KNOT, climb.inKnots)
def test_asFloat(self) -> None:
"""
A climb can be converted into a C{float}.
"""
self.assertEqual(1.0, float(base.Climb(1.0)))
class PositionErrorTests(TestCase):
"""
Tests for L{twisted.positioning.base.PositionError}.
"""
def test_allUnset(self) -> None:
"""
In an empty L{base.PositionError} with no invariant testing, all
dilutions of positions are L{None}.
"""
positionError = base.PositionError()
self.assertIsNone(positionError.pdop)
self.assertIsNone(positionError.hdop)
self.assertIsNone(positionError.vdop)
def test_allUnsetWithInvariant(self) -> None:
"""
In an empty L{base.PositionError} with invariant testing, all
dilutions of positions are L{None}.
"""
positionError = base.PositionError(testInvariant=True)
self.assertIsNone(positionError.pdop)
self.assertIsNone(positionError.hdop)
self.assertIsNone(positionError.vdop)
def test_withoutInvariant(self) -> None:
"""
L{base.PositionError}s can be instantiated with just a HDOP.
"""
positionError = base.PositionError(hdop=1.0)
self.assertEqual(positionError.hdop, 1.0)
def test_withInvariant(self) -> None:
"""
Creating a simple L{base.PositionError} with just a HDOP while
checking the invariant works.
"""
positionError = base.PositionError(hdop=1.0, testInvariant=True)
self.assertEqual(positionError.hdop, 1.0)
def test_invalidWithoutInvariant(self) -> None:
"""
Creating a L{base.PositionError} with values set to an impossible
combination works if the invariant is not checked.
"""
error = base.PositionError(pdop=1.0, vdop=1.0, hdop=1.0)
self.assertEqual(error.pdop, 1.0)
self.assertEqual(error.hdop, 1.0)
self.assertEqual(error.vdop, 1.0)
def test_invalidWithInvariant(self) -> None:
"""
Creating a L{base.PositionError} with values set to an impossible
combination raises C{ValueError} if the invariant is being tested.
"""
self.assertRaises(
ValueError,
base.PositionError,
pdop=1.0,
vdop=1.0,
hdop=1.0,
testInvariant=True,
)
def test_setDOPWithoutInvariant(self) -> None:
"""
You can set the PDOP value to value inconsisted with HDOP and VDOP
when not checking the invariant.
"""
pe = base.PositionError(hdop=1.0, vdop=1.0)
pe.pdop = 100.0
self.assertEqual(pe.pdop, 100.0)
def test_setDOPWithInvariant(self) -> None:
"""
Attempting to set the PDOP value to value inconsisted with HDOP and
VDOP when checking the invariant raises C{ValueError}.
"""
pe = base.PositionError(hdop=1.0, vdop=1.0, testInvariant=True)
pdop = pe.pdop
def setPDOP(pe: base.PositionError) -> None:
pe.pdop = 100.0
self.assertRaises(ValueError, setPDOP, pe)
self.assertEqual(pe.pdop, pdop)
REPR_TEMPLATE = "<PositionError (pdop: %s, hdop: %s, vdop: %s)>"
def _testDOP(
self,
pe: base.PositionError,
pdop: float | None,
hdop: float | None,
vdop: float | None,
) -> None:
"""
Tests the DOP values in a position error, and the repr of that
position error.
@param pe: The position error under test.
@type pe: C{PositionError}
@param pdop: The expected position dilution of precision.
@type pdop: C{float} or L{None}
@param hdop: The expected horizontal dilution of precision.
@type hdop: C{float} or L{None}
@param vdop: The expected vertical dilution of precision.
@type vdop: C{float} or L{None}
"""
self.assertEqual(pe.pdop, pdop)
self.assertEqual(pe.hdop, hdop)
self.assertEqual(pe.vdop, vdop)
self.assertEqual(repr(pe), self.REPR_TEMPLATE % (pdop, hdop, vdop))
def test_positionAndHorizontalSet(self) -> None:
"""
The VDOP is correctly determined from PDOP and HDOP.
"""
pdop, hdop = 2.0, 1.0
vdop = (pdop**2 - hdop**2) ** 0.5
pe = base.PositionError(pdop=pdop, hdop=hdop)
self._testDOP(pe, pdop, hdop, vdop)
def test_positionAndVerticalSet(self) -> None:
"""
The HDOP is correctly determined from PDOP and VDOP.
"""
pdop, vdop = 2.0, 1.0
hdop = (pdop**2 - vdop**2) ** 0.5
pe = base.PositionError(pdop=pdop, vdop=vdop)
self._testDOP(pe, pdop, hdop, vdop)
def test_horizontalAndVerticalSet(self) -> None:
"""
The PDOP is correctly determined from HDOP and VDOP.
"""
hdop, vdop = 1.0, 1.0
pdop = (hdop**2 + vdop**2) ** 0.5
pe = base.PositionError(hdop=hdop, vdop=vdop)
self._testDOP(pe, pdop, hdop, vdop)
class BeaconInformationTests(TestCase):
"""
Tests for L{twisted.positioning.base.BeaconInformation}.
"""
def test_minimal(self) -> None:
"""
For an empty beacon information object, the number of used
beacons is zero, the number of seen beacons is zero, and the
repr of the object reflects that.
"""
bi = base.BeaconInformation()
self.assertEqual(len(bi.usedBeacons), 0)
expectedRepr = (
"<BeaconInformation (" "used beacons (0): [], " "unused beacons: [])>"
)
self.assertEqual(repr(bi), expectedRepr)
satelliteKwargs = {"azimuth": 1, "elevation": 1, "signalToNoiseRatio": 1.0}
def test_simple(self) -> None:
"""
Tests a beacon information with a bunch of satellites, none of
which used in computing a fix.
"""
def _buildSatellite(**kw: float) -> base.Satellite:
kwargs = dict(self.satelliteKwargs)
kwargs.update(kw)
return base.Satellite(**kwargs)
beacons = set()
for prn in range(1, 10):
beacons.add(_buildSatellite(identifier=prn))
bi = base.BeaconInformation(beacons)
self.assertEqual(len(bi.seenBeacons), 9)
self.assertEqual(len(bi.usedBeacons), 0)
self.assertEqual(
repr(bi),
"<BeaconInformation (used beacons (0): [], "
"unused beacons: ["
"<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>"
"])>",
)
def test_someSatellitesUsed(self) -> None:
"""
Tests a beacon information with a bunch of satellites, some of
them used in computing a fix.
"""
bi = base.BeaconInformation()
for prn in range(1, 10):
satellite = base.Satellite(identifier=prn, **self.satelliteKwargs)
bi.seenBeacons.add(satellite)
if prn % 2:
bi.usedBeacons.add(satellite)
self.assertEqual(len(bi.seenBeacons), 9)
self.assertEqual(len(bi.usedBeacons), 5)
self.assertEqual(
repr(bi),
"<BeaconInformation (used beacons (5): ["
"<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>], "
"unused beacons: ["
"<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, "
"<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>])>",
)
class PositioningBeaconTests(TestCase):
"""
Tests for L{base.PositioningBeacon}.
"""
def test_interface(self) -> None:
"""
Tests that L{base.PositioningBeacon} implements L{IPositioningBeacon}.
"""
implements = IPositioningBeacon.implementedBy(base.PositioningBeacon)
self.assertTrue(implements)
verify.verifyObject(IPositioningBeacon, base.PositioningBeacon(1))
def test_repr(self) -> None:
"""
Tests the repr of a positioning beacon.
"""
self.assertEqual(repr(base.PositioningBeacon("A")), "<Beacon (A)>")
class SatelliteTests(TestCase):
"""
Tests for L{twisted.positioning.base.Satellite}.
"""
def test_minimal(self) -> None:
"""
Tests a minimal satellite that only has a known PRN.
Tests that the azimuth, elevation and signal to noise ratios
are L{None} and verifies the repr.
"""
s = base.Satellite(1)
self.assertEqual(s.identifier, 1)
self.assertIsNone(s.azimuth)
self.assertIsNone(s.elevation)
self.assertIsNone(s.signalToNoiseRatio)
self.assertEqual(
repr(s), "<Satellite (1), azimuth: None, " "elevation: None, snr: None>"
)
def test_simple(self) -> None:
"""
Tests a minimal satellite that only has a known PRN.
Tests that the azimuth, elevation and signal to noise ratios
are correct and verifies the repr.
"""
s = base.Satellite(
identifier=1, azimuth=270.0, elevation=30.0, signalToNoiseRatio=25.0
)
self.assertEqual(s.identifier, 1)
self.assertEqual(s.azimuth, 270.0)
self.assertEqual(s.elevation, 30.0)
self.assertEqual(s.signalToNoiseRatio, 25.0)
self.assertEqual(
repr(s), "<Satellite (1), azimuth: 270.0, " "elevation: 30.0, snr: 25.0>"
)
|