dashboard.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. from geoplotlib.layers import BaseLayer
  2. from geoplotlib.core import BatchPainter
  3. import geoplotlib
  4. from geoplotlib.colors import create_set_cmap
  5. from geoplotlib.utils import BoundingBox, epoch_to_str
  6. import time as ptime
  7. import pandas as pd
  8. import numpy as np
  9. from de2.routing import get_graph, euler_distance, pathfinder, find_percentage_point_in_path as fpp
  10. from twin.elements import Vessel
  11. class PoABLayer(BaseLayer):
  12. """
  13. Renders the map.
  14. """
  15. __colors__ = {
  16. "highlight": [150, 0, 250, 200], # yellow
  17. "nodes": [150, 0, 250, 200], # purple
  18. "ecological": [0, 255, 0, 200] # green
  19. }
  20. FPS = 30
  21. def __init__(self, client):
  22. self.client = client
  23. self.berths = pd.read_csv("berths.csv")
  24. self.painter = BatchPainter()
  25. self.ticks = 0
  26. self.time = 0
  27. self.initial_time = 0
  28. self.last_rt_time = ptime.time()
  29. self.time_factor = 1.0
  30. self.vessels = []
  31. self.graph = get_graph()
  32. def update_vessels(self):
  33. self.client.spin()
  34. if len(self.client.received["initial_time"]) > 0:
  35. self.initial_time, self.time_factor = eval(self.client.received["initial_time"].pop(0))
  36. while len(self.client.received["vessels"]) > 0:
  37. vessel = eval(self.client.received["vessels"].pop(0))
  38. # self.time_factor = (vessel[0] - self.time) / (ptime.time() - self.last_rt_time)
  39. self.time = vessel[0]
  40. self.ticks = 0
  41. vdict = {
  42. "mmsi": vessel[1],
  43. "name": vessel[2],
  44. "task": vessel[3],
  45. "velocity": vessel[4],
  46. "source": vessel[5],
  47. "target": vessel[6],
  48. "start": vessel[7],
  49. "ETA": vessel[8],
  50. }
  51. self.vessels.append(vdict)
  52. self.last_rt_time = ptime.time()
  53. def draw_shape(self, x, y):
  54. """
  55. Draws the shape on the layer, at a specific location.
  56. Args:
  57. x (numeric): The x-position to draw this shape at.
  58. y (numeric): The y-position to draw this shape at.
  59. """
  60. self.painter.points(x, y, 5)
  61. def draw_path(self, path, proj):
  62. """
  63. Draws a WSG84 path on the layer.
  64. Args:
  65. path: The path to draw as (x, y) tuples.
  66. proj: The projector.
  67. """
  68. x = []
  69. y = []
  70. for ix in range(len(path) - 1):
  71. point1 = path[ix]
  72. point2 = path[ix + 1]
  73. x.append(point1[0])
  74. x.append(point2[0])
  75. y.append(point1[1])
  76. y.append(point2[1])
  77. px, py = proj.lonlat_to_screen(np.asarray(x), np.asarray(y))
  78. vx1 = [e for ei, e in enumerate(px) if ei % 2 == 0]
  79. vx2 = [e for ei, e in enumerate(px) if ei % 2 == 1]
  80. vy1 = [e for ei, e in enumerate(py) if ei % 2 == 0]
  81. vy2 = [e for ei, e in enumerate(py) if ei % 2 == 1]
  82. self.painter.lines(vx1, vy1, vx2, vy2, width=1.5)
  83. def draw(self, proj, mouse_x, mouse_y, ui_manager):
  84. self.ticks += 1
  85. self.update_vessels()
  86. if len(self.client.received["error"]) > 0:
  87. print('\n'.join(self.client.received["error"]))
  88. ui_manager.status('\n'.join(self.client.received["error"]))
  89. self.client.received["error"].clear()
  90. self.painter = BatchPainter()
  91. tooltips = []
  92. # Show the simulation time in top right
  93. ts = self.time + (self.ticks / self.FPS) * self.time_factor
  94. ui_manager.info("TIME: %s (x %.1f)" % (epoch_to_str(self.initial_time + ts), self.time_factor))
  95. # Get all sailing vessels
  96. rem = []
  97. for vix, vessel in enumerate(self.vessels):
  98. col = self.__colors__["ecological"]
  99. eco = Vessel.ecological()
  100. dist = 0
  101. if vessel["velocity"] < eco[0]:
  102. dist = eco[0] - vessel["velocity"]
  103. elif vessel["velocity"] > eco[1]:
  104. dist = vessel["velocity"] - eco[1]
  105. col[0] = min(255, int(dist) * 50)
  106. col[1] = 255 - col[0]
  107. self.painter.set_color(col)
  108. # Obtain (and draw) vessel trajectory
  109. if "trajectory" not in vessel:
  110. vessel["trajectory"] = pathfinder(self.graph, vessel["source"], vessel["target"])
  111. path, dists = vessel["trajectory"]
  112. # Compute distance traveled and draw a vessel there
  113. # TODO: use distance_left when it should be updated
  114. tot_distance = sum(dists)
  115. traveled = (ts - vessel["start"]) * vessel["velocity"]
  116. distance_left = tot_distance - traveled
  117. if tot_distance == 0:
  118. p1 = p2 = path[0]
  119. percentage = 1.
  120. else:
  121. percentage = traveled / tot_distance
  122. p1, p2, percentage = fpp(path, dists, percentage)
  123. if percentage < 1:
  124. self.draw_path(path, proj)
  125. sx, sy = proj.lonlat_to_screen(np.asarray([p1[0]]), np.asarray([p1[1]]))
  126. tx, ty = proj.lonlat_to_screen(np.asarray([p2[0]]), np.asarray([p2[1]]))
  127. dx = (tx - sx) * percentage
  128. dy = (ty - sy) * percentage
  129. if euler_distance(sx + dx, sy + dy, mouse_x, mouse_y) < 10:
  130. self.painter.set_color(self.__colors__["highlight"])
  131. tooltips.append("%s (MMSI: %s)" % (str(vessel["name"]), str(vessel["mmsi"])))
  132. tooltips.append("task: %s | ETA: %s" % (vessel["task"], epoch_to_str(vessel["ETA"])))
  133. tooltips.append("velocity: %.3f m/s" % vessel["velocity"])
  134. tooltips.append("dist left/total: %.3f/%.3f m" % (distance_left, tot_distance))
  135. # self.draw_path(path, proj)
  136. self.draw_shape(sx + dx, sy + dy)
  137. else:
  138. rem.append(vix)
  139. for vix in reversed(rem):
  140. self.vessels.pop(vix)
  141. ui_manager.tooltip("\n".join(tooltips))
  142. self.painter.batch_draw()
  143. def bbox(self):
  144. box = [max(self.berths["center_lat"]), max(self.berths["center_lon"]), min(self.berths["center_lat"]), min(self.berths["center_lon"])]
  145. return BoundingBox(north=box[0], east=box[1], south=box[2], west=box[3])
  146. if __name__ == '__main__':
  147. from networking.mqtt import MQTTClient
  148. client = MQTTClient("Dashboard")
  149. client.subscribe("initial_time")
  150. client.subscribe("vessels")
  151. client.subscribe("error")
  152. layer = PoABLayer(client)
  153. geoplotlib.set_window_size(640, 760)
  154. geoplotlib.tiles_provider({
  155. 'url': lambda zoom, xtile, ytile: 'https://tile.openstreetmap.org/%d/%d/%d.png' % (zoom, xtile, ytile),
  156. 'tiles_dir': 'mytiles',
  157. 'attribution': 'Map tiles by OpenStreetMap Carto, under CC BY 3.0. Data @ OpenStreetMap contributors.'
  158. })
  159. geoplotlib.add_layer(layer)
  160. geoplotlib.show()