models.py 5.2 KB

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