1# Copyright (c) Twisted Matrix Laboratories. 2# See LICENSE for details. 3""" 4Test cases for positioning primitives. 5""" 6from zope.interface import verify 7 8from twisted.positioning import base 9from twisted.positioning.base import Angles, Directions 10from twisted.positioning.ipositioning import IPositioningBeacon 11from twisted.trial.unittest import TestCase 12 13 14class AngleTests(TestCase): 15 """ 16 Tests for the L{twisted.positioning.base.Angle} class. 17 """ 18 19 def test_empty(self): 20 """ 21 The repr of an empty angle says that is of unknown type and unknown 22 value. 23 """ 24 a = base.Angle() 25 self.assertEqual("<Angle of unknown type (unknown value)>", repr(a)) 26 27 def test_variation(self): 28 """ 29 The repr of an empty variation says that it is a variation of unknown 30 value. 31 """ 32 a = base.Angle(angleType=Angles.VARIATION) 33 self.assertEqual("<Variation (unknown value)>", repr(a)) 34 35 def test_unknownType(self): 36 """ 37 The repr of an angle of unknown type but a given value displays that 38 type and value in its repr. 39 """ 40 a = base.Angle(1.0) 41 self.assertEqual("<Angle of unknown type (1.0 degrees)>", repr(a)) 42 43 def test_bogusType(self): 44 """ 45 Trying to create an angle with a bogus type raises C{ValueError}. 46 """ 47 self.assertRaises(ValueError, base.Angle, angleType="BOGUS") 48 49 50class HeadingTests(TestCase): 51 """ 52 Tests for the L{twisted.positioning.base.Heading} class. 53 """ 54 55 def test_simple(self): 56 """ 57 Tests that a simple heading has a value in decimal degrees, which is 58 also its value when converted to a float. Its variation, and by 59 consequence its corrected heading, is L{None}. 60 """ 61 h = base.Heading(1.0) 62 self.assertEqual(h.inDecimalDegrees, 1.0) 63 self.assertEqual(float(h), 1.0) 64 self.assertIsNone(h.variation) 65 self.assertIsNone(h.correctedHeading) 66 67 def test_headingWithoutVariationRepr(self): 68 """ 69 A repr of a heading with no variation reports its value and that the 70 variation is unknown. 71 """ 72 heading = base.Heading(1.0) 73 expectedRepr = "<Heading (1.0 degrees, unknown variation)>" 74 self.assertEqual(repr(heading), expectedRepr) 75 76 def test_headingWithVariationRepr(self): 77 """ 78 A repr of a heading with known variation reports its value and the 79 value of that variation. 80 """ 81 angle, variation = 1.0, -10.0 82 heading = base.Heading.fromFloats(angle, variationValue=variation) 83 reprTemplate = "<Heading ({0} degrees, <Variation ({1} degrees)>)>" 84 self.assertEqual(repr(heading), reprTemplate.format(angle, variation)) 85 86 def test_valueEquality(self): 87 """ 88 Headings with the same values compare equal. 89 """ 90 self.assertEqual(base.Heading(1.0), base.Heading(1.0)) 91 92 def test_valueInequality(self): 93 """ 94 Headings with different values compare unequal. 95 """ 96 self.assertNotEqual(base.Heading(1.0), base.Heading(2.0)) 97 98 def test_zeroHeadingEdgeCase(self): 99 """ 100 Headings can be instantiated with a value of 0 and no variation. 101 """ 102 base.Heading(0) 103 104 def test_zeroHeading180DegreeVariationEdgeCase(self): 105 """ 106 Headings can be instantiated with a value of 0 and a variation of 180 107 degrees. 108 """ 109 base.Heading(0, 180) 110 111 def _badValueTest(self, **kw): 112 """ 113 Helper function for verifying that bad values raise C{ValueError}. 114 115 @param kw: The keyword arguments passed to L{base.Heading.fromFloats}. 116 """ 117 self.assertRaises(ValueError, base.Heading.fromFloats, **kw) 118 119 def test_badAngleValueEdgeCase(self): 120 """ 121 Headings can not be instantiated with a value of 360 degrees. 122 """ 123 self._badValueTest(angleValue=360.0) 124 125 def test_badVariationEdgeCase(self): 126 """ 127 Headings can not be instantiated with a variation of -180 degrees. 128 """ 129 self._badValueTest(variationValue=-180.0) 130 131 def test_negativeHeading(self): 132 """ 133 Negative heading values raise C{ValueError}. 134 """ 135 self._badValueTest(angleValue=-10.0) 136 137 def test_headingTooLarge(self): 138 """ 139 Heading values greater than C{360.0} raise C{ValueError}. 140 """ 141 self._badValueTest(angleValue=370.0) 142 143 def test_variationTooNegative(self): 144 """ 145 Variation values less than C{-180.0} raise C{ValueError}. 146 """ 147 self._badValueTest(variationValue=-190.0) 148 149 def test_variationTooPositive(self): 150 """ 151 Variation values greater than C{180.0} raise C{ValueError}. 152 """ 153 self._badValueTest(variationValue=190.0) 154 155 def test_correctedHeading(self): 156 """ 157 A heading with a value and a variation has a corrected heading. 158 """ 159 h = base.Heading.fromFloats(1.0, variationValue=-10.0) 160 self.assertEqual(h.correctedHeading, base.Angle(11.0, Angles.HEADING)) 161 162 def test_correctedHeadingOverflow(self): 163 """ 164 A heading with a value and a variation has the appropriate corrected 165 heading value, even when the variation puts it across the 360 degree 166 boundary. 167 """ 168 h = base.Heading.fromFloats(359.0, variationValue=-2.0) 169 self.assertEqual(h.correctedHeading, base.Angle(1.0, Angles.HEADING)) 170 171 def test_correctedHeadingOverflowEdgeCase(self): 172 """ 173 A heading with a value and a variation has the appropriate corrected 174 heading value, even when the variation puts it exactly at the 360 175 degree boundary. 176 """ 177 h = base.Heading.fromFloats(359.0, variationValue=-1.0) 178 self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING)) 179 180 def test_correctedHeadingUnderflow(self): 181 """ 182 A heading with a value and a variation has the appropriate corrected 183 heading value, even when the variation puts it under the 0 degree 184 boundary. 185 """ 186 h = base.Heading.fromFloats(1.0, variationValue=2.0) 187 self.assertEqual(h.correctedHeading, base.Angle(359.0, Angles.HEADING)) 188 189 def test_correctedHeadingUnderflowEdgeCase(self): 190 """ 191 A heading with a value and a variation has the appropriate corrected 192 heading value, even when the variation puts it exactly at the 0 193 degree boundary. 194 """ 195 h = base.Heading.fromFloats(1.0, variationValue=1.0) 196 self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING)) 197 198 def test_setVariationSign(self): 199 """ 200 Setting the sign of a heading changes the variation sign. 201 """ 202 h = base.Heading.fromFloats(1.0, variationValue=1.0) 203 h.setSign(1) 204 self.assertEqual(h.variation.inDecimalDegrees, 1.0) 205 h.setSign(-1) 206 self.assertEqual(h.variation.inDecimalDegrees, -1.0) 207 208 def test_setBadVariationSign(self): 209 """ 210 Setting the sign of a heading to values that aren't C{-1} or C{1} 211 raises C{ValueError} and does not affect the heading. 212 """ 213 h = base.Heading.fromFloats(1.0, variationValue=1.0) 214 self.assertRaises(ValueError, h.setSign, -50) 215 self.assertEqual(h.variation.inDecimalDegrees, 1.0) 216 217 self.assertRaises(ValueError, h.setSign, 0) 218 self.assertEqual(h.variation.inDecimalDegrees, 1.0) 219 220 self.assertRaises(ValueError, h.setSign, 50) 221 self.assertEqual(h.variation.inDecimalDegrees, 1.0) 222 223 def test_setUnknownVariationSign(self): 224 """ 225 Setting the sign on a heading with unknown variation raises 226 C{ValueError}. 227 """ 228 h = base.Heading.fromFloats(1.0) 229 self.assertIsNone(h.variation.inDecimalDegrees) 230 self.assertRaises(ValueError, h.setSign, 1) 231 232 233class CoordinateTests(TestCase): 234 def test_float(self): 235 """ 236 Coordinates can be converted to floats. 237 """ 238 coordinate = base.Coordinate(10.0) 239 self.assertEqual(float(coordinate), 10.0) 240 241 def test_repr(self): 242 """ 243 Coordinates that aren't explicitly latitudes or longitudes have an 244 appropriate repr. 245 """ 246 coordinate = base.Coordinate(10.0) 247 expectedRepr = f"<Angle of unknown type ({10.0} degrees)>" 248 self.assertEqual(repr(coordinate), expectedRepr) 249 250 def test_positiveLatitude(self): 251 """ 252 Positive latitudes have a repr that specifies their type and value. 253 """ 254 coordinate = base.Coordinate(10.0, Angles.LATITUDE) 255 expectedRepr = f"<Latitude ({10.0} degrees)>" 256 self.assertEqual(repr(coordinate), expectedRepr) 257 258 def test_negativeLatitude(self): 259 """ 260 Negative latitudes have a repr that specifies their type and value. 261 """ 262 coordinate = base.Coordinate(-50.0, Angles.LATITUDE) 263 expectedRepr = f"<Latitude ({-50.0} degrees)>" 264 self.assertEqual(repr(coordinate), expectedRepr) 265 266 def test_positiveLongitude(self): 267 """ 268 Positive longitudes have a repr that specifies their type and value. 269 """ 270 longitude = base.Coordinate(50.0, Angles.LONGITUDE) 271 expectedRepr = f"<Longitude ({50.0} degrees)>" 272 self.assertEqual(repr(longitude), expectedRepr) 273 274 def test_negativeLongitude(self): 275 """ 276 Negative longitudes have a repr that specifies their type and value. 277 """ 278 longitude = base.Coordinate(-50.0, Angles.LONGITUDE) 279 expectedRepr = f"<Longitude ({-50.0} degrees)>" 280 self.assertEqual(repr(longitude), expectedRepr) 281 282 def test_bogusCoordinateType(self): 283 """ 284 Creating coordinates with bogus types rasies C{ValueError}. 285 """ 286 self.assertRaises(ValueError, base.Coordinate, 150.0, "BOGUS") 287 288 def test_angleTypeNotCoordinate(self): 289 """ 290 Creating coordinates with angle types that aren't coordinates raises 291 C{ValueError}. 292 """ 293 self.assertRaises(ValueError, base.Coordinate, 150.0, Angles.HEADING) 294 295 def test_equality(self): 296 """ 297 Coordinates with the same value and type are equal. 298 """ 299 300 def makeCoordinate(): 301 return base.Coordinate(1.0, Angles.LONGITUDE) 302 303 self.assertEqual(makeCoordinate(), makeCoordinate()) 304 305 def test_differentAnglesInequality(self): 306 """ 307 Coordinates with different values aren't equal. 308 """ 309 c1 = base.Coordinate(1.0) 310 c2 = base.Coordinate(-1.0) 311 self.assertNotEqual(c1, c2) 312 313 def test_differentTypesInequality(self): 314 """ 315 Coordinates with the same values but different types aren't equal. 316 """ 317 c1 = base.Coordinate(1.0, Angles.LATITUDE) 318 c2 = base.Coordinate(1.0, Angles.LONGITUDE) 319 self.assertNotEqual(c1, c2) 320 321 def test_sign(self): 322 """ 323 Setting the sign on a coordinate sets the sign of the value of the 324 coordinate. 325 """ 326 c = base.Coordinate(50.0, Angles.LATITUDE) 327 c.setSign(1) 328 self.assertEqual(c.inDecimalDegrees, 50.0) 329 c.setSign(-1) 330 self.assertEqual(c.inDecimalDegrees, -50.0) 331 332 def test_badVariationSign(self): 333 """ 334 Setting a bogus sign value (not -1 or 1) on a coordinate raises 335 C{ValueError} and doesn't affect the coordinate. 336 """ 337 value = 50.0 338 c = base.Coordinate(value, Angles.LATITUDE) 339 340 self.assertRaises(ValueError, c.setSign, -50) 341 self.assertEqual(c.inDecimalDegrees, 50.0) 342 343 self.assertRaises(ValueError, c.setSign, 0) 344 self.assertEqual(c.inDecimalDegrees, 50.0) 345 346 self.assertRaises(ValueError, c.setSign, 50) 347 self.assertEqual(c.inDecimalDegrees, 50.0) 348 349 def test_northernHemisphere(self): 350 """ 351 Positive latitudes are in the northern hemisphere. 352 """ 353 coordinate = base.Coordinate(1.0, Angles.LATITUDE) 354 self.assertEqual(coordinate.hemisphere, Directions.NORTH) 355 356 def test_easternHemisphere(self): 357 """ 358 Positive longitudes are in the eastern hemisphere. 359 """ 360 coordinate = base.Coordinate(1.0, Angles.LONGITUDE) 361 self.assertEqual(coordinate.hemisphere, Directions.EAST) 362 363 def test_southernHemisphere(self): 364 """ 365 Negative latitudes are in the southern hemisphere. 366 """ 367 coordinate = base.Coordinate(-1.0, Angles.LATITUDE) 368 self.assertEqual(coordinate.hemisphere, Directions.SOUTH) 369 370 def test_westernHemisphere(self): 371 """ 372 Negative longitudes are in the western hemisphere. 373 """ 374 coordinate = base.Coordinate(-1.0, Angles.LONGITUDE) 375 self.assertEqual(coordinate.hemisphere, Directions.WEST) 376 377 def test_badHemisphere(self): 378 """ 379 Accessing the hemisphere for a coordinate that can't compute it 380 raises C{ValueError}. 381 """ 382 coordinate = base.Coordinate(1.0, None) 383 self.assertRaises(ValueError, lambda: coordinate.hemisphere) 384 385 def test_latitudeTooLarge(self): 386 """ 387 Creating a latitude with a value greater than or equal to 90 degrees 388 raises C{ValueError}. 389 """ 390 self.assertRaises(ValueError, _makeLatitude, 150.0) 391 self.assertRaises(ValueError, _makeLatitude, 90.0) 392 393 def test_latitudeTooSmall(self): 394 """ 395 Creating a latitude with a value less than or equal to -90 degrees 396 raises C{ValueError}. 397 """ 398 self.assertRaises(ValueError, _makeLatitude, -150.0) 399 self.assertRaises(ValueError, _makeLatitude, -90.0) 400 401 def test_longitudeTooLarge(self): 402 """ 403 Creating a longitude with a value greater than or equal to 180 degrees 404 raises C{ValueError}. 405 """ 406 self.assertRaises(ValueError, _makeLongitude, 250.0) 407 self.assertRaises(ValueError, _makeLongitude, 180.0) 408 409 def test_longitudeTooSmall(self): 410 """ 411 Creating a longitude with a value less than or equal to -180 degrees 412 raises C{ValueError}. 413 """ 414 self.assertRaises(ValueError, _makeLongitude, -250.0) 415 self.assertRaises(ValueError, _makeLongitude, -180.0) 416 417 def test_inDegreesMinutesSeconds(self): 418 """ 419 Coordinate values can be accessed in degrees, minutes, seconds. 420 """ 421 c = base.Coordinate(50.5, Angles.LATITUDE) 422 self.assertEqual(c.inDegreesMinutesSeconds, (50, 30, 0)) 423 424 c = base.Coordinate(50.213, Angles.LATITUDE) 425 self.assertEqual(c.inDegreesMinutesSeconds, (50, 12, 46)) 426 427 def test_unknownAngleInDegreesMinutesSeconds(self): 428 """ 429 If the vaue of a coordinate is L{None}, its values in degrees, 430 minutes, seconds is also L{None}. 431 """ 432 c = base.Coordinate(None, None) 433 self.assertIsNone(c.inDegreesMinutesSeconds) 434 435 436def _makeLatitude(value): 437 """ 438 Builds and returns a latitude of given value. 439 """ 440 return base.Coordinate(value, Angles.LATITUDE) 441 442 443def _makeLongitude(value): 444 """ 445 Builds and returns a longitude of given value. 446 """ 447 return base.Coordinate(value, Angles.LONGITUDE) 448 449 450class AltitudeTests(TestCase): 451 """ 452 Tests for the L{twisted.positioning.base.Altitude} class. 453 """ 454 455 def test_value(self): 456 """ 457 Altitudes can be instantiated and reports the correct value in 458 meters and feet, as well as when converted to float. 459 """ 460 altitude = base.Altitude(1.0) 461 self.assertEqual(float(altitude), 1.0) 462 self.assertEqual(altitude.inMeters, 1.0) 463 self.assertEqual(altitude.inFeet, 1.0 / base.METERS_PER_FOOT) 464 465 def test_repr(self): 466 """ 467 Altitudes report their type and value in their repr. 468 """ 469 altitude = base.Altitude(1.0) 470 self.assertEqual(repr(altitude), "<Altitude (1.0 m)>") 471 472 def test_equality(self): 473 """ 474 Altitudes with equal values compare equal. 475 """ 476 firstAltitude = base.Altitude(1.0) 477 secondAltitude = base.Altitude(1.0) 478 self.assertEqual(firstAltitude, secondAltitude) 479 480 def test_inequality(self): 481 """ 482 Altitudes with different values don't compare equal. 483 """ 484 firstAltitude = base.Altitude(1.0) 485 secondAltitude = base.Altitude(-1.0) 486 self.assertNotEqual(firstAltitude, secondAltitude) 487 488 489class SpeedTests(TestCase): 490 """ 491 Tests for the L{twisted.positioning.base.Speed} class. 492 """ 493 494 def test_value(self): 495 """ 496 Speeds can be instantiated, and report their value in meters 497 per second, and can be converted to floats. 498 """ 499 speed = base.Speed(50.0) 500 self.assertEqual(speed.inMetersPerSecond, 50.0) 501 self.assertEqual(float(speed), 50.0) 502 503 def test_repr(self): 504 """ 505 Speeds report their type and value in their repr. 506 """ 507 speed = base.Speed(50.0) 508 self.assertEqual(repr(speed), "<Speed (50.0 m/s)>") 509 510 def test_negativeSpeeds(self): 511 """ 512 Creating a negative speed raises C{ValueError}. 513 """ 514 self.assertRaises(ValueError, base.Speed, -1.0) 515 516 def test_inKnots(self): 517 """ 518 A speed can be converted into its value in knots. 519 """ 520 speed = base.Speed(1.0) 521 self.assertEqual(1 / base.MPS_PER_KNOT, speed.inKnots) 522 523 def test_asFloat(self): 524 """ 525 A speed can be converted into a C{float}. 526 """ 527 self.assertEqual(1.0, float(base.Speed(1.0))) 528 529 530class ClimbTests(TestCase): 531 """ 532 Tests for L{twisted.positioning.base.Climb}. 533 """ 534 535 def test_simple(self): 536 """ 537 Speeds can be instantiated, and report their value in meters 538 per second, and can be converted to floats. 539 """ 540 climb = base.Climb(42.0) 541 self.assertEqual(climb.inMetersPerSecond, 42.0) 542 self.assertEqual(float(climb), 42.0) 543 544 def test_repr(self): 545 """ 546 Climbs report their type and value in their repr. 547 """ 548 climb = base.Climb(42.0) 549 self.assertEqual(repr(climb), "<Climb (42.0 m/s)>") 550 551 def test_negativeClimbs(self): 552 """ 553 Climbs can have negative values, and still report that value 554 in meters per second and when converted to floats. 555 """ 556 climb = base.Climb(-42.0) 557 self.assertEqual(climb.inMetersPerSecond, -42.0) 558 self.assertEqual(float(climb), -42.0) 559 560 def test_speedInKnots(self): 561 """ 562 A climb can be converted into its value in knots. 563 """ 564 climb = base.Climb(1.0) 565 self.assertEqual(1 / base.MPS_PER_KNOT, climb.inKnots) 566 567 def test_asFloat(self): 568 """ 569 A climb can be converted into a C{float}. 570 """ 571 self.assertEqual(1.0, float(base.Climb(1.0))) 572 573 574class PositionErrorTests(TestCase): 575 """ 576 Tests for L{twisted.positioning.base.PositionError}. 577 """ 578 579 def test_allUnset(self): 580 """ 581 In an empty L{base.PositionError} with no invariant testing, all 582 dilutions of positions are L{None}. 583 """ 584 positionError = base.PositionError() 585 self.assertIsNone(positionError.pdop) 586 self.assertIsNone(positionError.hdop) 587 self.assertIsNone(positionError.vdop) 588 589 def test_allUnsetWithInvariant(self): 590 """ 591 In an empty L{base.PositionError} with invariant testing, all 592 dilutions of positions are L{None}. 593 """ 594 positionError = base.PositionError(testInvariant=True) 595 self.assertIsNone(positionError.pdop) 596 self.assertIsNone(positionError.hdop) 597 self.assertIsNone(positionError.vdop) 598 599 def test_withoutInvariant(self): 600 """ 601 L{base.PositionError}s can be instantiated with just a HDOP. 602 """ 603 positionError = base.PositionError(hdop=1.0) 604 self.assertEqual(positionError.hdop, 1.0) 605 606 def test_withInvariant(self): 607 """ 608 Creating a simple L{base.PositionError} with just a HDOP while 609 checking the invariant works. 610 """ 611 positionError = base.PositionError(hdop=1.0, testInvariant=True) 612 self.assertEqual(positionError.hdop, 1.0) 613 614 def test_invalidWithoutInvariant(self): 615 """ 616 Creating a L{base.PositionError} with values set to an impossible 617 combination works if the invariant is not checked. 618 """ 619 error = base.PositionError(pdop=1.0, vdop=1.0, hdop=1.0) 620 self.assertEqual(error.pdop, 1.0) 621 self.assertEqual(error.hdop, 1.0) 622 self.assertEqual(error.vdop, 1.0) 623 624 def test_invalidWithInvariant(self): 625 """ 626 Creating a L{base.PositionError} with values set to an impossible 627 combination raises C{ValueError} if the invariant is being tested. 628 """ 629 self.assertRaises( 630 ValueError, 631 base.PositionError, 632 pdop=1.0, 633 vdop=1.0, 634 hdop=1.0, 635 testInvariant=True, 636 ) 637 638 def test_setDOPWithoutInvariant(self): 639 """ 640 You can set the PDOP value to value inconsisted with HDOP and VDOP 641 when not checking the invariant. 642 """ 643 pe = base.PositionError(hdop=1.0, vdop=1.0) 644 pe.pdop = 100.0 645 self.assertEqual(pe.pdop, 100.0) 646 647 def test_setDOPWithInvariant(self): 648 """ 649 Attempting to set the PDOP value to value inconsisted with HDOP and 650 VDOP when checking the invariant raises C{ValueError}. 651 """ 652 pe = base.PositionError(hdop=1.0, vdop=1.0, testInvariant=True) 653 pdop = pe.pdop 654 655 def setPDOP(pe): 656 pe.pdop = 100.0 657 658 self.assertRaises(ValueError, setPDOP, pe) 659 self.assertEqual(pe.pdop, pdop) 660 661 REPR_TEMPLATE = "<PositionError (pdop: %s, hdop: %s, vdop: %s)>" 662 663 def _testDOP(self, pe, pdop, hdop, vdop): 664 """ 665 Tests the DOP values in a position error, and the repr of that 666 position error. 667 668 @param pe: The position error under test. 669 @type pe: C{PositionError} 670 @param pdop: The expected position dilution of precision. 671 @type pdop: C{float} or L{None} 672 @param hdop: The expected horizontal dilution of precision. 673 @type hdop: C{float} or L{None} 674 @param vdop: The expected vertical dilution of precision. 675 @type vdop: C{float} or L{None} 676 """ 677 self.assertEqual(pe.pdop, pdop) 678 self.assertEqual(pe.hdop, hdop) 679 self.assertEqual(pe.vdop, vdop) 680 self.assertEqual(repr(pe), self.REPR_TEMPLATE % (pdop, hdop, vdop)) 681 682 def test_positionAndHorizontalSet(self): 683 """ 684 The VDOP is correctly determined from PDOP and HDOP. 685 """ 686 pdop, hdop = 2.0, 1.0 687 vdop = (pdop ** 2 - hdop ** 2) ** 0.5 688 pe = base.PositionError(pdop=pdop, hdop=hdop) 689 self._testDOP(pe, pdop, hdop, vdop) 690 691 def test_positionAndVerticalSet(self): 692 """ 693 The HDOP is correctly determined from PDOP and VDOP. 694 """ 695 pdop, vdop = 2.0, 1.0 696 hdop = (pdop ** 2 - vdop ** 2) ** 0.5 697 pe = base.PositionError(pdop=pdop, vdop=vdop) 698 self._testDOP(pe, pdop, hdop, vdop) 699 700 def test_horizontalAndVerticalSet(self): 701 """ 702 The PDOP is correctly determined from HDOP and VDOP. 703 """ 704 hdop, vdop = 1.0, 1.0 705 pdop = (hdop ** 2 + vdop ** 2) ** 0.5 706 pe = base.PositionError(hdop=hdop, vdop=vdop) 707 self._testDOP(pe, pdop, hdop, vdop) 708 709 710class BeaconInformationTests(TestCase): 711 """ 712 Tests for L{twisted.positioning.base.BeaconInformation}. 713 """ 714 715 def test_minimal(self): 716 """ 717 For an empty beacon information object, the number of used 718 beacons is zero, the number of seen beacons is zero, and the 719 repr of the object reflects that. 720 """ 721 bi = base.BeaconInformation() 722 self.assertEqual(len(bi.usedBeacons), 0) 723 expectedRepr = ( 724 "<BeaconInformation (" "used beacons (0): [], " "unused beacons: [])>" 725 ) 726 self.assertEqual(repr(bi), expectedRepr) 727 728 satelliteKwargs = {"azimuth": 1, "elevation": 1, "signalToNoiseRatio": 1.0} 729 730 def test_simple(self): 731 """ 732 Tests a beacon information with a bunch of satellites, none of 733 which used in computing a fix. 734 """ 735 736 def _buildSatellite(**kw): 737 kwargs = dict(self.satelliteKwargs) 738 kwargs.update(kw) 739 return base.Satellite(**kwargs) 740 741 beacons = set() 742 for prn in range(1, 10): 743 beacons.add(_buildSatellite(identifier=prn)) 744 745 bi = base.BeaconInformation(beacons) 746 747 self.assertEqual(len(bi.seenBeacons), 9) 748 self.assertEqual(len(bi.usedBeacons), 0) 749 self.assertEqual( 750 repr(bi), 751 "<BeaconInformation (used beacons (0): [], " 752 "unused beacons: [" 753 "<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, " 754 "<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, " 755 "<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, " 756 "<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, " 757 "<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, " 758 "<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, " 759 "<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, " 760 "<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>, " 761 "<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>" 762 "])>", 763 ) 764 765 def test_someSatellitesUsed(self): 766 """ 767 Tests a beacon information with a bunch of satellites, some of 768 them used in computing a fix. 769 """ 770 bi = base.BeaconInformation() 771 772 for prn in range(1, 10): 773 satellite = base.Satellite(identifier=prn, **self.satelliteKwargs) 774 bi.seenBeacons.add(satellite) 775 if prn % 2: 776 bi.usedBeacons.add(satellite) 777 778 self.assertEqual(len(bi.seenBeacons), 9) 779 self.assertEqual(len(bi.usedBeacons), 5) 780 781 self.assertEqual( 782 repr(bi), 783 "<BeaconInformation (used beacons (5): [" 784 "<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, " 785 "<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, " 786 "<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, " 787 "<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, " 788 "<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>], " 789 "unused beacons: [" 790 "<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, " 791 "<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, " 792 "<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, " 793 "<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>])>", 794 ) 795 796 797class PositioningBeaconTests(TestCase): 798 """ 799 Tests for L{base.PositioningBeacon}. 800 """ 801 802 def test_interface(self): 803 """ 804 Tests that L{base.PositioningBeacon} implements L{IPositioningBeacon}. 805 """ 806 implements = IPositioningBeacon.implementedBy(base.PositioningBeacon) 807 self.assertTrue(implements) 808 verify.verifyObject(IPositioningBeacon, base.PositioningBeacon(1)) 809 810 def test_repr(self): 811 """ 812 Tests the repr of a positioning beacon. 813 """ 814 self.assertEqual(repr(base.PositioningBeacon("A")), "<Beacon (A)>") 815 816 817class SatelliteTests(TestCase): 818 """ 819 Tests for L{twisted.positioning.base.Satellite}. 820 """ 821 822 def test_minimal(self): 823 """ 824 Tests a minimal satellite that only has a known PRN. 825 826 Tests that the azimuth, elevation and signal to noise ratios 827 are L{None} and verifies the repr. 828 """ 829 s = base.Satellite(1) 830 self.assertEqual(s.identifier, 1) 831 self.assertIsNone(s.azimuth) 832 self.assertIsNone(s.elevation) 833 self.assertIsNone(s.signalToNoiseRatio) 834 self.assertEqual( 835 repr(s), "<Satellite (1), azimuth: None, " "elevation: None, snr: None>" 836 ) 837 838 def test_simple(self): 839 """ 840 Tests a minimal satellite that only has a known PRN. 841 842 Tests that the azimuth, elevation and signal to noise ratios 843 are correct and verifies the repr. 844 """ 845 s = base.Satellite( 846 identifier=1, azimuth=270.0, elevation=30.0, signalToNoiseRatio=25.0 847 ) 848 849 self.assertEqual(s.identifier, 1) 850 self.assertEqual(s.azimuth, 270.0) 851 self.assertEqual(s.elevation, 30.0) 852 self.assertEqual(s.signalToNoiseRatio, 25.0) 853 self.assertEqual( 854 repr(s), "<Satellite (1), azimuth: 270.0, " "elevation: 30.0, snr: 25.0>" 855 ) 856