from kivy_garden.mapview import MapLayer, MapMarker from kivy.graphics import Color, Line from kivy.graphics.context_instructions import Translate, Scale, PushMatrix, PopMatrix from kivy_garden.mapview.utils import clamp from kivy_garden.mapview.constants import (MIN_LONGITUDE, MAX_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE) from math import radians, log, tan, cos, pi class LineMapLayer(MapLayer): def __init__(self, coordinates=None, color=[0, 0, 1, 1], width=2, **kwargs): super().__init__(**kwargs) # if coordinates is None: # coordinates = [[0, 0], [0, 0]] self._coordinates = coordinates self.color = color self._line_points = None self._line_points_offset = (0, 0) self.zoom = 0 self.lon = 0 self.lat = 0 self.ms = 0 self._width = width @property def coordinates(self): return self._coordinates @coordinates.setter def coordinates(self, coordinates): self._coordinates = coordinates self.invalidate_line_points() self.clear_and_redraw() def add_point(self, point): if self._coordinates is None: #self._coordinates = [point] self._coordinates = [] self._coordinates.append(point) # self._coordinates = [self._coordinates[-1], point] self.invalidate_line_points() self.clear_and_redraw() @property def line_points(self): if self._line_points is None: self.calc_line_points() return self._line_points @property def line_points_offset(self): if self._line_points is None: self.calc_line_points() return self._line_points_offset def calc_line_points(self): # Offset all points by the coordinates of the first point, # to keep coordinates closer to zero. # (and therefore avoid some float precision issues when drawing lines) self._line_points_offset = (self.get_x(self.coordinates[0][1]), self.get_y(self.coordinates[0][0])) # Since lat is not a linear transform we must compute manually self._line_points = [(self.get_x(lon) - self._line_points_offset[0], self.get_y(lat) - self._line_points_offset[1]) for lat, lon in self.coordinates] def invalidate_line_points(self): self._line_points = None self._line_points_offset = (0, 0) def get_x(self, lon): """Get the x position on the map using this map source's projection (0, 0) is located at the top left. """ return clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE) * self.ms / 360.0 def get_y(self, lat): """Get the y position on the map using this map source's projection (0, 0) is located at the top left. """ lat = radians(clamp(-lat, MIN_LATITUDE, MAX_LATITUDE)) return (1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi) * self.ms / 2.0 # Function called when the MapView is moved def reposition(self): map_view = self.parent # Must redraw when the zoom changes # as the scatter transform resets for the new tiles if self.zoom != map_view.zoom or \ self.lon != round(map_view.lon, 7) or \ self.lat != round(map_view.lat, 7): map_source = map_view.map_source self.ms = pow(2.0, map_view.zoom) * map_source.dp_tile_size self.invalidate_line_points() self.clear_and_redraw() def clear_and_redraw(self, *args): with self.canvas: # Clear old line self.canvas.clear() self._draw_line() def _draw_line(self, *args): if self._coordinates is None: return map_view = self.parent self.zoom = map_view.zoom self.lon = map_view.lon self.lat = map_view.lat # When zooming we must undo the current scatter transform # or the animation distorts it scatter = map_view._scatter sx, sy, ss = scatter.x, scatter.y, scatter.scale # Account for map source tile size and map view zoom vx, vy, vs = map_view.viewport_pos[0], map_view.viewport_pos[1], map_view.scale with self.canvas: self.opacity = 0.5 # Save the current coordinate space context PushMatrix() # Offset by the MapView's position in the window (always 0,0 ?) Translate(*map_view.pos) # Undo the scatter animation transform Scale(1 / ss, 1 / ss, 1) Translate(-sx, -sy) # Apply the get window xy from transforms Scale(vs, vs, 1) Translate(-vx, -vy) # Apply what we can factor out of the mapsource long, lat to x, y conversion Translate(self.ms / 2, 0) # Translate by the offset of the line points # (this keeps the points closer to the origin) Translate(*self.line_points_offset) Color(*self.color) Line(points=self.line_points, width=self._width) # Retrieve the last saved coordinate space context PopMatrix()