models.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import subprocess
  4. import os
  5. from math import radians, degrees, sin, cos, asin, atan2, sqrt
  6. from django.db import models
  7. from django.conf import settings
  8. from django.core.validators import MinValueValidator, MaxValueValidator
  9. from django.utils.encoding import python_2_unicode_compatible
  10. EARTH_RADIUS = 6371009
  11. class Point(models.Model):
  12. latitude = models.FloatField(verbose_name="latitude", help_text="In degrees",
  13. validators=[MinValueValidator(-90),
  14. MaxValueValidator(90)])
  15. longitude = models.FloatField(verbose_name="longitude", help_text="In degrees",
  16. validators=[MinValueValidator(-180),
  17. MaxValueValidator(180)])
  18. altitude = models.FloatField(verbose_name="altitude", help_text="In meters",
  19. validators=[MinValueValidator(0.)])
  20. @property
  21. def latitude_rad(self):
  22. return radians(self.latitude)
  23. @property
  24. def longitude_rad(self):
  25. return radians(self.longitude)
  26. @property
  27. def altitude_abs(self):
  28. """Absolute distance to the center of Earth (in a spherical model)"""
  29. return EARTH_RADIUS + self.altitude
  30. def line_distance(self, other):
  31. """Distance of the straight line between two points on Earth, in meters.
  32. Note that this is only useful because we are considering
  33. line-of-sight links, where straight-line distance is the relevant
  34. distance. For arbitrary points on Earth, great-circle distance
  35. would most likely be preferred.
  36. """
  37. delta_lon = other.longitude_rad - self.longitude_rad
  38. # Cosine of the angle between the two points on their great circle.
  39. cos_angle = sin(self.latitude_rad) * sin(other.latitude_rad) \
  40. + cos(self.latitude_rad) * cos(other.latitude_rad) * cos(delta_lon)
  41. # Al-Kashi formula
  42. return sqrt(self.altitude_abs ** 2 \
  43. + other.altitude_abs ** 2 \
  44. - 2 * self.altitude_abs * other.altitude_abs * cos_angle)
  45. def bearing(self, other):
  46. """Bearing, in degrees, between this point and another point."""
  47. delta_lon = other.longitude_rad - self.longitude_rad
  48. y = sin(delta_lon) * cos(other.latitude_rad)
  49. x = cos(self.latitude_rad) * sin(other.latitude_rad) \
  50. - sin(self.latitude_rad) * cos(other.latitude_rad) * cos(delta_lon)
  51. return degrees(atan2(y, x))
  52. def elevation(self, other):
  53. """Elevation, in degrees, between this point and another point."""
  54. d = self.line_distance(other)
  55. sin_elev = (other.altitude_abs ** 2 - self.altitude_abs ** 2 - d ** 2) \
  56. / (2 * self.altitude_abs * d)
  57. return degrees(asin(sin_elev))
  58. class Meta:
  59. abstract = True
  60. @python_2_unicode_compatible
  61. class Panorama(Point):
  62. name = models.CharField(verbose_name="name", max_length=255,
  63. help_text="Name of the panorama")
  64. loop = models.BooleanField(default=False, verbose_name="360° panorama",
  65. help_text="Whether the panorama loops around the edges")
  66. image = models.ImageField(verbose_name="image", upload_to="pano")
  67. def tiles_dir(self):
  68. return os.path.join(settings.MEDIA_ROOT, settings.PANORAMA_TILES_DIR,
  69. str(self.pk))
  70. def tiles_url(self):
  71. return os.path.join(settings.MEDIA_URL, settings.PANORAMA_TILES_DIR,
  72. str(self.pk))
  73. def to_dict(self):
  74. """Useful to pass information to the javascript code as JSON"""
  75. return {"id": self.id,
  76. "name": self.name,
  77. "loop": self.loop,
  78. "latitude": self.latitude,
  79. "longitude": self.longitude,
  80. "altitude": self.altitude,
  81. "tiles_url": self.tiles_url()}
  82. def generate_tiles(self):
  83. # The trailing slash is necessary for the shell script.
  84. tiles_dir = self.tiles_dir() + "/"
  85. try:
  86. os.makedirs(tiles_dir)
  87. except OSError:
  88. pass
  89. script = os.path.join(settings.BASE_DIR, "panorama", "gen_tiles.sh")
  90. ret = subprocess.call([script, "-p", tiles_dir, self.image.path])
  91. return ret
  92. def __str__(self):
  93. return self.name
  94. @python_2_unicode_compatible
  95. class ReferencePoint(Point):
  96. name = models.CharField(verbose_name="name", max_length=255,
  97. help_text="Name of the reference point")
  98. def to_dict(self):
  99. """Useful to pass information to the javascript code as JSON"""
  100. return {"id": self.id,
  101. "name": self.name,
  102. "latitude": self.latitude,
  103. "longitude": self.longitude,
  104. "altitude": self.altitude}
  105. def to_dict_extended(self, point):
  106. """Same as above, but also includes information relative
  107. to the given point: bearing, azimuth, distance."""
  108. d = self.to_dict()
  109. d['distance'] = self.line_distance(point)
  110. return d
  111. def __str__(self):
  112. return self.name