148 lines
5.1 KiB
Python
148 lines
5.1 KiB
Python
|
|
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()
|