kivy-garden / mapview

Mapview is a Kivy widget for displaying interactive maps.
https://kivy-garden.github.io/mapview/
MIT License
87 stars 29 forks source link

Draw lines by geocoordinates. #4

Open kilbee opened 8 years ago

kilbee commented 8 years ago

Hi! I'd like to draw .gpx routes on mapview, is there generic class for drawing lines (simple line between two geocoordinates) or how would I go about it?

Actually worked out temporary solution with canvas lines drawn from MapMarkers, however I think marker positioning is off when map is zoomed.

Checked with examples from this repo and it seems marks are actually off when map is zoomed (kivy 1.9.2 dev)

frmdstryr commented 8 years ago

I implemented this here. https://github.com/kivy-garden/garden.mapview/compare/master...frmdstryr:master . Works with zooming, moving, etc...

kilbee commented 8 years ago

@frmdstryr I've came up with similar implementation, but what I'd really like is mode="scatter" on my layer where I draw lines, because recalculating too many points on every MapView transform takes too long. And while for simple translation "scatter" works ok, it doesn't work for zooming. Unless I'm doing something wrong... here's example code I'm using: line from Dover (England) to Calais (France).

frmdstryr commented 8 years ago

Yeah I have the same problem on mobile... tried cythonizing and still is slow. Will see if I can figure something out...

frmdstryr commented 7 years ago

I discovered that commenting out this line https://github.com/kivy-garden/garden.mapview/blob/master/mapview/view.py#L647 fixes the scaling issue. However doing this prevents the map from loading the new tiles when zooming. Still looking for an actual fix...

frmdstryr commented 7 years ago

Got it working with a scatter now. The map resets the scatter transform the zoom level changes, thus the scatter layer must be redrawn in this case. All other movements and scaling within the same zoom level will be taken care of by the scatter.

from kivy.graphics.context_instructions import Translate, Scale

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self.zoom = 0

    def reposition(self):
        mapview = self.parent

        #: Must redraw when the zoom changes 
        #: as the scatter transform resets for the new tiles
        if (self.zoom != mapview.zoom):
            self.draw_line()

    def draw_line(self, *args):
        mapview = self.parent
        self.zoom = mapview.zoom
        geo_dover   = [51.126251, 1.327067]
        geo_calais  = [50.959086, 1.827652]
        screen_dover  = mapview.get_window_xy_from(geo_dover[0], geo_dover[1], mapview.zoom)
        screen_calais = mapview.get_window_xy_from(geo_calais[0], geo_calais[1], mapview.zoom)

        # When zooming we must undo the current scatter transform
        # or the animation makes the line misplaced
        scatter = mapview._scatter
        x,y,s = scatter.x, scatter.y, scatter.scale
        point_list    = [ screen_dover[0], screen_dover[1], 
                                            screen_calais[0], screen_calais[1] ]

        with self.canvas:
            self.canvas.clear()
            Scale(1/s,1/s,1)
            Translate(-x,-y)
            Color(0, 0, 0, .6)
            Line(points=point_list, width=3, joint="bevel")
kilbee commented 7 years ago

Looks great, very needed addition. I will test it when i pick up my project with mapview. One more thought: do you think it would be faster if instead get_window_xy_from (coordinates..) we could translate geopoints based on transform on zoom level change? I actually had working scatter too, but without resetting it (I scaled line width instead - very dirty solution), it was super fast, but adding new points on zoom/transformation other than default was way over my head (I'd need to reverse track all transformations etc. - it kinda worked, but only on zooming in, on zooming out it was buggy and I couldn't narrow it down - probably applied transformations in wrong order or something and since it was very dirty anyway I gave up on it).

frmdstryr commented 7 years ago

Actually I didn't even think about it but yes you should be able to just apply the same transformations that get_window_xy_from does. Will try it.

frmdstryr commented 7 years ago

I was able to get about a 2x speedup by doing this, however it is breaking the line width drawing algorithm.


import os
import random
from math import *
from mapview.utils import clamp
import time

from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout

from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Line
from kivy.graphics.transformation import Matrix
from kivy.graphics.context_instructions import Translate, Scale
from mapview import MapView, MapLayer, MIN_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, MAX_LONGITUDE

class MapViewApp(App):
    mapview = None

    def __init__(self, **kwargs):
        super(MapViewApp, self).__init__(**kwargs)
        Clock.schedule_once(self.post, 0)

    def build(self):
        layout = BoxLayout(orientation='vertical')
        return layout

    def post(self, *args):
        layout = FloatLayout()
        self.mapview = MapView(zoom=9, lat=51.046284, lon=1.541179)
        line = LineMapLayer()
        self.mapview.add_layer(line, mode="scatter")  # window scatter
        layout.add_widget(self.mapview)

        self.root.add_widget(layout)
        b = BoxLayout(orientation='horizontal',height='32dp',size_hint_y=None)
        b.add_widget(Button(text="Zoom in",on_press=lambda a: setattr(self.mapview,'zoom',self.mapview.zoom+1)))
        b.add_widget(Button(text="Zoom out",on_press=lambda a: setattr(self.mapview,'zoom',self.mapview.zoom-1)))
        b.add_widget(Button(text="AddPoint",on_press=lambda a: line.add_point()))
        self.root.add_widget(b)

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self.zoom = 0

        geo_dover   = [51.126251, 1.327067]
        geo_calais  = [50.959086, 1.827652]

        # NOTE: Points must be valid as they're no longer clamped
        self.coordinates = [geo_dover, geo_calais]
        for i in range(25000-2):
            self.coordinates.append(self.gen_point())

    def reposition(self):
        mapview = self.parent

        #: Must redraw when the zoom changes 
        #: as the scatter transform resets for the new tiles
        if (self.zoom != mapview.zoom):
            self.draw_line()

    def gen_point(self):
        n = len(self.coordinates)
        dx,dy = random.randint(-100,100)/10000.0,random.randint(0,100)/10000.0
        c = (self.coordinates[-1][0]+dx,
             self.coordinates[-1][1]+dy)

        return c

    def add_point(self):
        #: Add a random point close to the previous one
        for i in range(len(self.coordinates)):
            self.coordinates.append(self.gen_point())
        self.draw_line()

    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)

    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 = clamp(-lat, MIN_LATITUDE, MAX_LATITUDE)
        lat = lat * pi / 180.
        return ((1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi))

    def draw_line(self, *args):
        mapview = self.parent
        self.zoom = mapview.zoom

        # When zooming we must undo the current scatter transform
        # or the animation distorts it
        scatter = mapview._scatter
        map_source = mapview.map_source
        sx,sy,ss = scatter.x, scatter.y, scatter.scale
        vx,vy,vs = mapview.viewport_pos[0], mapview.viewport_pos[1], mapview.scale

        # Account for map source tile size and mapview zoom
        ms = pow(2.0,mapview.zoom) * map_source.dp_tile_size

        #: Since lat is not a linear transform we must compute manually 
        line_points = []
        for lat,lon in self.coordinates:
            line_points.extend((self.get_x(lon),self.get_y(lat)))
            #line_points.extend(mapview.get_window_xy_from(lat,lon,mapview.zoom))

        with self.canvas:
            # Clear old line
            self.canvas.clear()

            # 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 the what we can factor out
            # of the mapsource long,lat to x,y conversion
            Scale(ms/360.0,ms/2.0,1)
            Translate(180,0)

            # Draw new
            Color(0, 0, 0, .6)
            Line(points=line_points, width=1)#4/ms)#, joint="round",joint_precision=100)

MapViewApp().run()

Edit: This doesn't eliminate redrawing on zoom

whitelynx commented 6 years ago

I'm also interested in this, since I'm implementing in-dash navigation for my car using Kivy. I'm planning on using the Google Maps Directions API to get directions, and display them on the map as vector lines and markers.

Is there anything I can do to help move this feature along aside from testing what's been pasted above?

whitelynx commented 6 years ago

It seems that Kivy's Line() graphics instruction doesn't do line width very well; it only expands in the y direction, not in x. 2018-04-09-151318_553x250_scrot (the darker line is with width=1, the lighter one is width=0.00002, joint='round', close=False, cap='none')

whitelynx commented 6 years ago

I was able to get the line width working much better by multiplying the result of get_y() by 180, and changing the last Scale() call to:

Scale(ms/360.0, ms/360.0, 1)
whitelynx commented 6 years ago

I was able to get things working faster by precalculating line_points whenever coordinates changes, instead of recalculating it every time we draw.

Also, line width works better scaling so we do ms/2.0, ms/2.0 instead of ms/360.0, ms/360.0

import random
from math import *

from kivy.graphics import Color, Line, SmoothLine
from kivy.graphics.context_instructions import Translate, Scale
from kivy.garden.mapview.mapview.utils import clamp
from kivy.garden.mapview.mapview import MapLayer, MIN_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, MAX_LONGITUDE

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self._coordinates = []
        self.zoom = 0

        geo_dover = [51.126251, 1.327067]
        geo_calais = [50.959086, 1.827652]

        # NOTE: Points must be valid as they're no longer clamped
        coordinates = [geo_dover, geo_calais]
        for i in range(500 - 2):
            coordinates.append(self.gen_point(coordinates[-1]))

        self.coordinates = coordinates

    @property
    def coordinates(self):
        return self._coordinates

    @coordinates.setter
    def coordinates(self, coordinates):
        self._coordinates = coordinates

        #: Since lat is not a linear transform we must compute manually
        self.line_points = [(self.get_x(lon), self.get_y(lat)) for lat, lon in coordinates]
        #self.line_points = [mapview.get_window_xy_from(lat, lon, mapview.zoom) for lat, lon in coordinates]

    def reposition(self):
        mapview = self.parent

        #: Must redraw when the zoom changes
        #: as the scatter transform resets for the new tiles
        if (self.zoom != mapview.zoom):
            self.draw_line()

    def gen_point(self, lastPoint):
        dx, dy = random.randint(-100, 100) / 20000.0, random.randint(0, 100) / 20000.0
        c = (lastPoint[0] + dx, lastPoint[1] + dy)

        return c

    def add_point(self):
        #: Add a random point close to the previous one
        coordinates = self.coordinates
        for i in range(len(self.coordinates)):
            coordinates.append(self.gen_point(coordinates[-1]))
        self.coordinates = coordinates
        self.draw_line()

    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) / 180.

    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 = clamp(-lat, MIN_LATITUDE, MAX_LATITUDE)
        lat = lat * pi / 180.
        return ((1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi))

    def draw_line(self, *args):
        mapview = self.parent
        self.zoom = mapview.zoom

        # When zooming we must undo the current scatter transform
        # or the animation distorts it
        scatter = mapview._scatter
        map_source = mapview.map_source
        sx, sy, ss = scatter.x, scatter.y, scatter.scale
        vx, vy, vs = mapview.viewport_pos[0], mapview.viewport_pos[1], mapview.scale

        # Account for map source tile size and mapview zoom
        ms = pow(2.0, mapview.zoom) * map_source.dp_tile_size

        with self.canvas:
            # Clear old line
            self.canvas.clear()

            # 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 the what we can factor out
            # of the mapsource long, lat to x, y conversion
            Scale(ms / 2.0, ms / 2.0, 1)
            Translate(1, 0)

            # Draw new
            Color(0, 0.2, 0.7, 0.25)
            Line(points=self.line_points, width=6.5 / ms)
            Color(0, 0.2, 0.7, 1)
            Line(points=self.line_points, width=6 / ms)
            Color(0, 0.3, 1, 1)
            Line(points=self.line_points, width=4 / ms)
            #Line(points=self.line_points, width=1)#4 / ms)#, joint='round', joint_precision=100)
            #Line(points=self.line_points, width=4 / ms, joint='round', close=False, cap='none')
            #Line(points=self.line_points, width=1)
whitelynx commented 6 years ago

I'm running into some precision issues with the current code:

~Changing the scale of the map back to Scale(ms/360.0, ms/360.0, 1) instead of Scale(ms / 2.0, ms / 2.0, 1) helps a bit with the glitches when zoomed in, but they still appear. (it seems like we'll need an even bigger scale in order to fix those)~ It seems that the imprecision issues show up regardless of what scale I use for the numbers here, which led me to believe it's due to including ms in the Scale() call; however, I experimented with pre-scaling line_points each time the zoom changes, but it's still not giving me good results. I'm not sure why the imprecision issue constantly shows up at the highest zooms.

Not sure what to do about the offset... still trying to diagnose that.

whitelynx commented 6 years ago

The offset seems to be because the MapView is not positioned at (0, 0) in the window; I have it in a PageLayout. So, the drawing routine for the lines should apparently take the MapView's position into account when rendering.

whitelynx commented 6 years ago

This is working much better:

from math import *

from kivy.graphics import Color, Line, SmoothLine, MatrixInstruction
from kivy.graphics.context_instructions import Translate, Scale
from kivy.clock import Clock

from kivy.garden.mapview.mapview.utils import clamp
from kivy.garden.mapview.mapview import MapLayer, MIN_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, MAX_LONGITUDE

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self._coordinates = []
        self._line_points = None
        self._line_points_offset = (0, 0)
        self.zoom = 0

    @property
    def coordinates(self):
        return self._coordinates

    @coordinates.setter
    def coordinates(self, coordinates):
        self._coordinates = coordinates
        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

    def reposition(self):
        mapview = self.parent

        # Must redraw when the zoom changes
        # as the scatter transform resets for the new tiles
        if (self.zoom != mapview.zoom):
            map_source = mapview.map_source
            self.ms = pow(2.0, mapview.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()

        # FIXME: Why is 0.05 a good value here? Why does 0 leave us with weird offsets?
        Clock.schedule_once(self._draw_line, 0.05)

    def _draw_line(self, *args):
        mapview = self.parent
        self.zoom = mapview.zoom

        # When zooming we must undo the current scatter transform
        # or the animation distorts it
        scatter = mapview._scatter
        sx, sy, ss = scatter.x, scatter.y, scatter.scale

        # Account for map source tile size and mapview zoom
        vx, vy, vs = mapview.viewport_pos[0], mapview.viewport_pos[1], mapview.scale

        with self.canvas:
            # Clear old line
            self.canvas.clear()

            # Offset by the MapView's position in the window
            Translate(*mapview.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 the 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)

            # Draw line
            Color(0, 0.2, 0.7, 0.25)
            Line(points=self.line_points, width=6.5 / 2)
            Color(0, 0.2, 0.7, 1)
            Line(points=self.line_points, width=6 / 2)
            Color(0, 0.3, 1, 1)
            Line(points=self.line_points, width=4 / 2)

This makes several changes from the last version:

This performs rather well (although there's some visible lag when zooming in or out before it redraws, but I think that's unavoidable as long as there's a Scatter animation present) and it looks equally good zoomed in or out.

There's only one big problem here:

JRoehrig commented 6 years ago

sx, sy, ss = scatter.x - mapview.x, scatter.y - mapview.y, scatter.scale instead of Translate(*mapview.pos) also works

amnonn122 commented 5 years ago

This is working much better:

from math import *

from kivy.graphics import Color, Line, SmoothLine, MatrixInstruction
from kivy.graphics.context_instructions import Translate, Scale
from kivy.clock import Clock

from kivy.garden.mapview.mapview.utils import clamp
from kivy.garden.mapview.mapview import MapLayer, MIN_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, MAX_LONGITUDE

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self._coordinates = []
        self._line_points = None
        self._line_points_offset = (0, 0)
        self.zoom = 0

    @property
    def coordinates(self):
        return self._coordinates

    @coordinates.setter
    def coordinates(self, coordinates):
        self._coordinates = coordinates
        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

    def reposition(self):
        mapview = self.parent

        # Must redraw when the zoom changes
        # as the scatter transform resets for the new tiles
        if (self.zoom != mapview.zoom):
            map_source = mapview.map_source
            self.ms = pow(2.0, mapview.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()

        # FIXME: Why is 0.05 a good value here? Why does 0 leave us with weird offsets?
        Clock.schedule_once(self._draw_line, 0.05)

    def _draw_line(self, *args):
        mapview = self.parent
        self.zoom = mapview.zoom

        # When zooming we must undo the current scatter transform
        # or the animation distorts it
        scatter = mapview._scatter
        sx, sy, ss = scatter.x, scatter.y, scatter.scale

        # Account for map source tile size and mapview zoom
        vx, vy, vs = mapview.viewport_pos[0], mapview.viewport_pos[1], mapview.scale

        with self.canvas:
            # Clear old line
            self.canvas.clear()

            # Offset by the MapView's position in the window
            Translate(*mapview.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 the 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)

            # Draw line
            Color(0, 0.2, 0.7, 0.25)
            Line(points=self.line_points, width=6.5 / 2)
            Color(0, 0.2, 0.7, 1)
            Line(points=self.line_points, width=6 / 2)
            Color(0, 0.3, 1, 1)
            Line(points=self.line_points, width=4 / 2)

This makes several changes from the last version:

  • I removed the test points, since now you can set line.coordinates to any array of coordinates you want.
  • All coordinates are offset to be relative to the first coordinate, and the first coordinate is used in a Translate() while drawing. This prevents float imprecision issues while rendering. (provided your line isn't too large; it might be better to offset relative to the center of the viewport, but that would require re-calculating line_points every frame, or having some sort of distance threshold which triggers a re-calculation)
  • We use mapview.pos to translate the drawing, so it actually lines up with the map most of the time.
  • We delay drawing by 0.05 seconds. Apparently this gives the other transforms a chance to settle so the line doesn't end up being weirdly offset. (still don't understand the mechanics here, though)

This performs rather well (although there's some visible lag when zooming in or out before it redraws, but I think that's unavoidable as long as there's a Scatter animation present) and it looks equally good zoomed in or out.

There's only one big problem here:

  • After zooming too far in or too far out, the line becomes permanently offset to the east. I'm not sure, but I think it might have something to do with how MapView handles its Scatter.

Can you please attach MapViewApp Class that run the map?

lucifermorn commented 3 years ago

Great work! It helped me to draw a route on MapView. The only part where I had to make a change is that a redraw is also wanted when the position of MapView changes, not only when the zoom changes.

from math import *

from kivy.graphics import Color, Line
from kivy.graphics.context_instructions import Translate, Scale
from kivy.clock import Clock

from kivy.garden.mapview.mapview.utils import clamp
from kivy.garden.mapview.mapview import MapLayer, MIN_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, MAX_LONGITUDE

class LineMapLayer(MapLayer):
    def __init__(self, **kwargs):
        super(LineMapLayer, self).__init__(**kwargs)
        self._coordinates = list()
        self._line_points = None
        self._line_points_offset = (0, 0)
        self.zoom = 0
        self.lon = 0
        self.lat = 0
        self.ms = 0

    @property
    def coordinates(self):
        return self._coordinates

    @coordinates.setter
    def coordinates(self, coordinates):
        self._coordinates = coordinates
        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

    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()

        # FIXME: Why is 0.05 a good value here? Why does 0 leave us with weird offsets?
        Clock.schedule_once(self._draw_line, 0.05)

    def _draw_line(self, *args):
        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:
            # Clear old line
            self.canvas.clear()

            # Offset by the MapView's position in the window
            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 the 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(0, 0.3, 1, 1)
            Line(points=self.line_points, width=2)
tshirtman commented 3 years ago

Sounds like someone should make a PR out of that code :) Nice collaboration here!

Chipmax64 commented 3 years ago

Hello. I have a problem. I'm working on a project and would like to use class LineMapLayer in it. However, the drawing of the track does not work correctly for me. When you shift the mapview and zoom in, the track is drawn in the wrong place. But when you zoom out or resize the program window, the track is drawn perfectly. I use Kivy 2.0.0, Kivymd - master branch, mapview-1.0.5. Tell me where my error is. Program Listing:

import sys

from kivy.base import runTouchApp from kivy.lang import Builder from linemaplayer import *

if name == 'main' and package is None: from os import path

sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

root = Builder.load_string( """

:import sys sys

:import MapSource kivy_garden.mapview.MapSource

MapView: lat: 51.543 lon: 46.059 zoom: 12 map_source: MapSource(sys.argv[1], attribution="") if len(sys.argv) > 1 else "osm"

LineMapLayer:
    _coordinates: [[51.51203, 45.95524], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.51207, 45.95524], [51.512066, 45.955235], [51.51208, 45.95522], [51.51208, 45.9552], [51.512085, 45.9552], [51.512085, 45.955196], [51.51209, 45.955196], [51.51209, 45.955196], [51.512085, 45.955196], [51.51209, 45.955196], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.95519], [51.512173, 45.95524], [51.51217, 45.955242], [51.51214, 45.955265], [51.51211, 45.955288], [51.512096, 45.955307], [51.512043, 45.955364], [51.512024, 45.95538], [51.51187, 45.95544], [51.51182, 45.955437], [51.511784, 45.955444], [51.5117, 45.95546], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51175, 45.95542], [51.51174, 45.955425], [51.51174, 45.955425], [51.51174, 45.955425], [51.51174, 45.955425], [51.511745, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.511745, 45.955425], [51.511745, 45.955425], [51.51175, 45.955425], [51.511753, 45.955425], [51.511753, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.51174, 45.95543], [51.51174, 45.95543], [51.511726, 45.95545], [51.511696, 45.955494], [51.511684, 45.9555], [51.511658, 45.95552], [51.511642, 45.955532], [51.511597, 45.955536], [51.511585, 45.955544], [51.511566, 45.95558], [51.511505, 45.955566], [51.5115, 45.955532], [51.511555, 45.955585], [51.511574, 45.95561], [51.511467, 45.955616], [51.511574, 45.955616], [51.511524, 45.955566], [51.511677, 45.955517], [51.51164, 45.95561], [51.511593, 45.95557], [51.51158, 45.955574], [51.511585, 45.95567], [51.51164, 45.955658], [51.51159, 45.955624], [51.511566, 45.955624], [51.511562, 45.95559], [51.51149, 45.955627], [51.511444, 45.95569], [51.511517, 45.95567], [51.511497, 45.95567], [51.51153, 45.955704], [51.511597, 45.95569], [51.511513, 45.95571], [51.511505, 45.955692], [51.511482, 45.955757], [51.51154, 45.955692], [51.511505, 45.955612], [51.51148, 45.955593], [51.511536, 45.955563], [51.511646, 45.95563], [51.5117, 45.955723], [51.511547, 45.955658], [51.511566, 45.95558], [51.51142, 45.955593], [51.51153, 45.95558], [51.511543, 45.95556], [51.511486, 45.955597], [51.511597, 45.955563], [51.51159, 45.955574], [51.511578, 45.955666], [51.511562, 45.955654], [51.511635, 45.95567], [51.511623, 45.95561], [51.511604, 45.955593], [51.511543, 45.95562], [51.511456, 45.95571], [51.51143, 45.955723], [51.511234, 45.955776], [51.511196, 45.955757], [51.511173, 45.955753], [51.511093, 45.955616], [51.510975, 45.955235], [51.51095, 45.954994], [51.51093, 45.954926], [51.510906, 45.95479], [51.510895, 45.954784], [51.510883, 45.954773], [51.510876, 45.95475], [51.51092, 45.954685], [51.51095, 45.95465], [51.510933, 45.954597], [51.510902, 45.9546], [51.51087, 45.9546], [51.510815, 45.95462], [51.510326, 45.954826], [51.5103, 45.954838], [51.51029, 45.95487], [51.51003, 45.95508], [51.50994, 45.95515], [51.50946, 45.955414], [51.509445, 45.95542], [51.50942, 45.955395], [51.509422, 45.9554], [51.509277, 45.95543], [51.509014, 45.954926], [51.508488, 45.953297], [51.507915, 45.951374], [51.507874, 45.95123], [51.507866, 45.951233], [51.507736, 45.95083], [51.507317, 45.949284], [51.507065, 45.948357], [51.507053, 45.948273], [51.50705, 45.948193], [51.507057, 45.948124], [51.507076, 45.948063], [51.50711, 45.94802], [51.507156, 45.948], [51.5072, 45.947998], [51.507244, 45.948013], [51.50736, 45.94806], [51.507793, 45.94836], [51.507847, 45.948414], [51.507893, 45.948463], [51.50794, 45.948517], [51.507954, 45.948574], [51.507954, 45.94863], [51.507935, 45.94868], [51.507877, 45.948765], [51.507725, 45.94887], [51.507526, 45.948982], [51.50625, 45.9499], [51.50601, 45.950085], [51.50592, 45.950138], [51.505825, 45.95027], [51.5058, 45.950302], [51.505756, 45.950314], [51.50571, 45.9503], [51.505554, 45.95021], [51.505505, 45.950207], [51.505444, 45.95022], [51.5054, 45.950237], [51.505306, 45.950268], [51.505287, 45.95027], [51.505257, 45.95031], [51.50522, 45.950363], [51.505203, 45.95043], [51.505177, 45.950607], [51.50518, 45.950817], [51.505188, 45.952324], [51.505177, 45.9532], [51.505096, 45.953903], [51.50502, 45.954056], [51.50466, 45.955162], [51.50445, 45.95554], [51.504356, 45.955643], [51.504173, 45.95584], [51.5038, 45.95617], [51.50289, 45.956696], [51.502804, 45.956776], [51.502773, 45.956783], [51.502747, 45.95679], [51.502743, 45.956814], [51.50273, 45.95679], [51.50268, 45.956696], [51.502514, 45.956917], [51.502476, 45.95695]]

""" )

runTouchApp(root)

dark-matter08 commented 3 years ago

is it possible to use google maps as a provider for kivy garden mapview? i used the google maps directions api to get the distance, time, and a bunch of other information including polylines points but i'm stuck. i don't know the possibility of plotting the polyline points on my kivygarden mapview

Chipmax64 commented 3 years ago

Hello. I have a problem. I'm working on a project and would like to use class LineMapLayer in it. However, the drawing of the track does not work correctly for me. When you shift the mapview and zoom in, the track is drawn in the wrong place. But when you zoom out or resize the program window, the track is drawn perfectly. I use Kivy 2.0.0, Kivymd - master branch, mapview-1.0.5. Tell me where my error is. Program Listing:

import sys

from kivy.base import runTouchApp from kivy.lang import Builder from linemaplayer import *

if name == 'main' and package is None: from os import path

sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

root = Builder.load_string( """

:import sys sys

:import MapSource kivy_garden.mapview.MapSource

MapView: lat: 51.543 lon: 46.059 zoom: 12 map_source: MapSource(sys.argv[1], attribution="") if len(sys.argv) > 1 else "osm"

LineMapLayer:
    _coordinates: [[51.51203, 45.95524], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.512066, 45.955235], [51.51207, 45.95524], [51.512066, 45.955235], [51.51208, 45.95522], [51.51208, 45.9552], [51.512085, 45.9552], [51.512085, 45.955196], [51.51209, 45.955196], [51.51209, 45.955196], [51.512085, 45.955196], [51.51209, 45.955196], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.955193], [51.51209, 45.95519], [51.512173, 45.95524], [51.51217, 45.955242], [51.51214, 45.955265], [51.51211, 45.955288], [51.512096, 45.955307], [51.512043, 45.955364], [51.512024, 45.95538], [51.51187, 45.95544], [51.51182, 45.955437], [51.511784, 45.955444], [51.5117, 45.95546], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51172, 45.955437], [51.51175, 45.95542], [51.51174, 45.955425], [51.51174, 45.955425], [51.51174, 45.955425], [51.51174, 45.955425], [51.511745, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.511745, 45.955425], [51.511745, 45.955425], [51.51175, 45.955425], [51.511753, 45.955425], [51.511753, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.51175, 45.955425], [51.51174, 45.95543], [51.51174, 45.95543], [51.511726, 45.95545], [51.511696, 45.955494], [51.511684, 45.9555], [51.511658, 45.95552], [51.511642, 45.955532], [51.511597, 45.955536], [51.511585, 45.955544], [51.511566, 45.95558], [51.511505, 45.955566], [51.5115, 45.955532], [51.511555, 45.955585], [51.511574, 45.95561], [51.511467, 45.955616], [51.511574, 45.955616], [51.511524, 45.955566], [51.511677, 45.955517], [51.51164, 45.95561], [51.511593, 45.95557], [51.51158, 45.955574], [51.511585, 45.95567], [51.51164, 45.955658], [51.51159, 45.955624], [51.511566, 45.955624], [51.511562, 45.95559], [51.51149, 45.955627], [51.511444, 45.95569], [51.511517, 45.95567], [51.511497, 45.95567], [51.51153, 45.955704], [51.511597, 45.95569], [51.511513, 45.95571], [51.511505, 45.955692], [51.511482, 45.955757], [51.51154, 45.955692], [51.511505, 45.955612], [51.51148, 45.955593], [51.511536, 45.955563], [51.511646, 45.95563], [51.5117, 45.955723], [51.511547, 45.955658], [51.511566, 45.95558], [51.51142, 45.955593], [51.51153, 45.95558], [51.511543, 45.95556], [51.511486, 45.955597], [51.511597, 45.955563], [51.51159, 45.955574], [51.511578, 45.955666], [51.511562, 45.955654], [51.511635, 45.95567], [51.511623, 45.95561], [51.511604, 45.955593], [51.511543, 45.95562], [51.511456, 45.95571], [51.51143, 45.955723], [51.511234, 45.955776], [51.511196, 45.955757], [51.511173, 45.955753], [51.511093, 45.955616], [51.510975, 45.955235], [51.51095, 45.954994], [51.51093, 45.954926], [51.510906, 45.95479], [51.510895, 45.954784], [51.510883, 45.954773], [51.510876, 45.95475], [51.51092, 45.954685], [51.51095, 45.95465], [51.510933, 45.954597], [51.510902, 45.9546], [51.51087, 45.9546], [51.510815, 45.95462], [51.510326, 45.954826], [51.5103, 45.954838], [51.51029, 45.95487], [51.51003, 45.95508], [51.50994, 45.95515], [51.50946, 45.955414], [51.509445, 45.95542], [51.50942, 45.955395], [51.509422, 45.9554], [51.509277, 45.95543], [51.509014, 45.954926], [51.508488, 45.953297], [51.507915, 45.951374], [51.507874, 45.95123], [51.507866, 45.951233], [51.507736, 45.95083], [51.507317, 45.949284], [51.507065, 45.948357], [51.507053, 45.948273], [51.50705, 45.948193], [51.507057, 45.948124], [51.507076, 45.948063], [51.50711, 45.94802], [51.507156, 45.948], [51.5072, 45.947998], [51.507244, 45.948013], [51.50736, 45.94806], [51.507793, 45.94836], [51.507847, 45.948414], [51.507893, 45.948463], [51.50794, 45.948517], [51.507954, 45.948574], [51.507954, 45.94863], [51.507935, 45.94868], [51.507877, 45.948765], [51.507725, 45.94887], [51.507526, 45.948982], [51.50625, 45.9499], [51.50601, 45.950085], [51.50592, 45.950138], [51.505825, 45.95027], [51.5058, 45.950302], [51.505756, 45.950314], [51.50571, 45.9503], [51.505554, 45.95021], [51.505505, 45.950207], [51.505444, 45.95022], [51.5054, 45.950237], [51.505306, 45.950268], [51.505287, 45.95027], [51.505257, 45.95031], [51.50522, 45.950363], [51.505203, 45.95043], [51.505177, 45.950607], [51.50518, 45.950817], [51.505188, 45.952324], [51.505177, 45.9532], [51.505096, 45.953903], [51.50502, 45.954056], [51.50466, 45.955162], [51.50445, 45.95554], [51.504356, 45.955643], [51.504173, 45.95584], [51.5038, 45.95617], [51.50289, 45.956696], [51.502804, 45.956776], [51.502773, 45.956783], [51.502747, 45.95679], [51.502743, 45.956814], [51.50273, 45.95679], [51.50268, 45.956696], [51.502514, 45.956917], [51.502476, 45.95695]]

""" )

runTouchApp(root)

I found a solution to this problem. Everything turned out to be quite simple. When adding a maplayer, you must specify mode = "scatter". add_layer (mylayer, mode = "scatter")

harrison-denton commented 3 years ago

Has anyone managed to use this to draw multiple lines on the map? I have tried calling multiple instances of the LineMapLayer to no avail

krrobert commented 2 years ago

Now calling multiple instances of the LineMapLayer works!

main.py:

from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen
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
import random

class LineMapLayer(MapLayer):
    def __init__(self, coordinates=[[0, 0], [0, 0]], color=[0, 0, 1, 1], **kwargs):
        super().__init__(**kwargs)
        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

    @property
    def coordinates(self):
        return self._coordinates

    @coordinates.setter
    def coordinates(self, coordinates):
        self._coordinates = coordinates
        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):
        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:

            # 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=2)

            # Retrieve the last saved coordinate space context
            PopMatrix()

class MapLayout(Screen):
    pass

class MapViewApp(MDApp):
    def on_start(self):
        mapview = self.root.mapview

        mapview.lat = 51.046284
        mapview.lon = 1.541179
        mapview.zoom = 7             # zoom values: 0 - 19

        # You can import JSON data here or:
        my_coordinates = [[51.505807, -0.128513], [51.126251, 1.327067],
                          [50.959086, 1.827652], [48.85519, 2.35021]]

        # Add routes
        lml1 = LineMapLayer(coordinates=my_coordinates, color=[1, 0, 0, 1])
        mapview.add_layer(lml1, mode="scatter")

        my_coordinates = [my_coordinates[-1]]
        for i in range(4600):
            my_coordinates.append(gen_rand_point(my_coordinates[-1]))
        lml2 = LineMapLayer(coordinates=my_coordinates, color=[0.5, 0, 1, 1])
        mapview.add_layer(lml2, mode="scatter")

        my_coordinates = [[51.505807, -0.128513], [48.85519, 2.35021]]
        lml3 = LineMapLayer(coordinates=my_coordinates, color=[0, 0, 1, 1])
        mapview.add_layer(lml3, mode="scatter")

        # Add markers
        marker = MapMarker(lat=51.126251, lon=1.327067, source='images/marker.png')
        mapview.add_marker(marker)

        marker = MapMarker(lat=50.959086, lon=1.827652, source='images/marker.png')
        mapview.add_marker(marker)

    def build(self):
        return MapLayout()

def gen_rand_point(last_coordinate):
    dx, dy = random.randint(-100, 100) / 10000.0, random.randint(0, 100) / 10000.0
    c = (last_coordinate[0] + dx,
         last_coordinate[1] + dy)
    return c

if __name__ == '__main__':
    MapViewApp().run()

mapview.kv:

<MapLayout>:
    mapview: _mapview

    MapView:
        id: _mapview

        MapMarker:                            # London
            source: 'images/marker.png'       # png image 32x32
            lat: 51.505807
            lon: -0.128513

        MapMarker:                            # Paris
            source: 'images/marker.png'       # png image 32x32
            lat: 48.85519
            lon: 2.35021

    MDFillRoundFlatButton:
        text: 'Home'
        pos_hint: {'right': 0.95, 'y': 0.05}
        on_release:
            root.mapview.center_on(51.505807, -0.128513)   # London

Note: Do not forget to add 'images/marker.png' to your project.

The result of the above code: image

eliasdopontiac commented 2 years ago

Hi, I'm Elias. I'm Brazilian and I'm sorry for my bad English. I'm developing an application in kivy, about bus routes. I wanted help in adding bus routes to my map. my code is like this: if you can help me i would appreciate it imagem_2022-09-28_114103482

from kivy.base import runTouchApp from kivy.lang import Builder

if name == 'main' and package is None: from os import sys, path sys.path.append(path.dirname(path.dirname(path.abspath(file))))

root = Builder.load_string("""

:import MapSource mapview.MapSource

Toolbar@BoxLayout: size_hint_y: None height: '48dp' padding: '4dp' spacing: '4dp'

canvas:
    Color:
        rgba: .5, .2, .2, .6
    Rectangle:
        pos: self.pos
        size: self.size

ShadedLabel@Label: size: self.texture_size canvas.before: Color: rgba: .2, .2, .2, .6 Rectangle: pos: self.pos size: self.size

RelativeLayout:

MapView:
    id: mapview
    lat: -3.065254715651993
    lon: -60.017043914794925
    zoom: 15
    #size_hint: .5, .5
    #pos_hint: {"x": .25, "y": .25}

    #on_map_relocated: mapview2.sync_to(self)
    #on_map_relocated: mapview3.sync_to(self)
Toolbar:
    top: root.top
    Button:
        text: "ir para o centro"
        on_release: mapview.center_on(-3.127648227145917, -60.0188892759717)
    Button:
        text: "ir para cidade nova"
        on_release: mapview.center_on(-3.0295139313693595, -59.9921953048975)
    Spinner:
        text: "onibus"
        values: '842','850'
        on_text: 

Toolbar:
    Label:
        text: "Longitude: {}".format(mapview.lon)
    Label:
        text: "Latitude: {}".format(mapview.lat)
""")

runTouchApp(root)

harrison-denton commented 2 years ago

Thanks for your email,

I am currently on annual leave until Monday 3rd October 2022. This inbox will not be monitored in my absence; for urgent communications, please contact @.***

Kind regards, Harrison

eliasdopontiac commented 2 years ago

hi, if you have time, you can see my code and help me. please.