I'm creating a tracker for my car, and one of the features I want to add is the vehicle's movement history. On the history screen, I retrieve the positions from the Firebase Realtime Database and I'm able to add markers on the map, but that's not what I need. What I want is to display a line connecting the positions.
I've searched a lot on Google, watched all sorts of tutorials on YouTube, but I couldn't adapt the solutions I found to work with my code (I'm a beginner with Kivy, and this is my first experience with it).
In my search for solutions to this problem, I came across some examples where markers are added and a line is drawn to connect them, but besides not being able to make it work in my code, it's not what I need. I would like only the initial and final markers to appear, and the rest of the route to be displayed as a line on the screen.
This is the best I could do so far:
After selecting the date you want to view the history, this is the screen that is displayed.
Can anyone tell me the best way to do this?
Below is the Python code and the KV code for you to take a look.
Thanks.
This is the Python code:
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy_garden.mapview import MapView, MapMarkerPopup, MapMarker
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.pickers import MDDatePicker
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.graphics import Color, Line
from firebase import FireBase
from mqtt import Mqtt
class NetBeast(MDApp):
def build(self):
self.theme_cls.primary_palette = "Green"
return Builder.load_file("main.kv")
class Login(Screen):
def __init__(self, **kwargs):
super(Login, self).__init__(**kwargs)
self.auth = FireBase()
self.dialog = None
self.mqtt = Mqtt()
def logar(self):
if self.ids.user.text == "" or self.ids.password.text == "":
self.alerta("Os campos de 'usuário' e 'senha' devem ser preenchidos!")
else:
if self.auth.login(self.ids.user.text, self.ids.password.text):
self.iniciar()
self.mqtt.conecta_mqtt()
else:
self.alerta("Usuário ou senha inválidos:")
@staticmethod
def iniciar():
NetBeast.get_running_app().root.current = 'inicial'
def clear(self):
self.ids.user.text = ""
self.ids.password.text = ""
def alerta(self, mensagem):
self.dialog = MDDialog(
text=mensagem,
buttons=[
MDFlatButton(
text="OK",
text_color=(0, 0, 0, 1),
md_bg_color=(1, 1, 1, 1),
on_release=self.fecha_alerta)])
self.dialog.open()
def fecha_alerta(self, obj):
self.dialog.dismiss()
self.dialog = None
def esqueci(self):
if self.ids.user.text == '':
self.alerta('Preencha o campo de e-mail para que a mensagem possa ser enviada.')
else:
self.auth.reset_pwd(self.ids.user.text)
self.alerta(
'Se o e-mail preenchido estiver correto, em breve você receberá '
'uma mensagem com o link para cadastrar uma nova senha.')
class Inicial(Screen):
def __init__(self, **kwargs):
super(Inicial, self).__init__(**kwargs)
Clock.schedule_interval(self.atualizar_botoes, 3)
self.alarme = False
self.roubado = False
self.reinicia = False
self.mqtt = Mqtt()
def atualizar_botoes(self, arg1 = None, arg2 = None):
if self.mqtt.alarme_est == 1:
self.ids.alarme_btn.md_bg_color = (1, 78/255, 78/255, 1)
self.alarme = True
else:
self.ids.alarme_btn.md_bg_color = (5/255, 92/255, 54/255, 1)
self.alarme = False
if self.mqtt.roubado_est == 1:
self.ids.roubado_btn.md_bg_color = (1, 78/255, 78/255, 1)
self.roubado = True
self.ids.alarme_btn.md_bg_color = (1, 78 / 255, 78 / 255, 1)
self.alarme = True
else:
self.ids.roubado_btn.md_bg_color = (5/255, 92/255, 54/255, 1)
self.roubado = False
if self.mqtt.reinicia_est == 1:
self.ids.reinicia_btn.md_bg_color = (1, 78/255, 78/255, 1)
self.reinicia = True
else:
self.ids.reinicia_btn.md_bg_color = (5/255, 92/255, 54/255, 1)
self.reinicia = False
def alarme_press(self):
if not self.roubado:
if self.alarme:
self.ids.alarme_btn.md_bg_color = (5 / 255, 92 / 255, 54 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/alarme', '0')
self.alarme = False
else:
self.ids.alarme_btn.md_bg_color = (1, 78 / 255, 78 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/alarme', '1')
self.alarme = True
def roubado_press(self):
if self.roubado:
self.ids.roubado_btn.md_bg_color = (5 / 255, 92 / 255, 54 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/roubado', '0')
self.roubado = False
else:
self.ids.roubado_btn.md_bg_color = (1, 78 / 255, 78 / 255, 0.4)
self.ids.alarme_btn.md_bg_color = (1, 78 / 255, 78 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/roubado', '1')
self.roubado = True
self.alarme = True
def reinicia_press(self):
if self.reinicia:
self.ids.reinicia_btn.md_bg_color = (5 / 255, 92 / 255, 54 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/reinicia', '0')
self.reinicia = False
else:
self.ids.reinicia_btn.md_bg_color = (1, 78 / 255, 78 / 255, 0.4)
self.mqtt.envia_dados('mymqtt/reinicia', '1')
self.reinicia = True
def fechar(self):
self.mqtt.desconectar()
quit()
class Mapa(Screen):
def __init__(self, **kwargs):
super(Mapa, self).__init__(**kwargs)
self.existe = False
Clock.schedule_interval(self.add_marker, 3)
self.mqtt = Mqtt()
self.marker = None
self.marker_old = None
self.inicio = True
def add_marker(self, arg1 = None, arg2 = None):
if self.mqtt.chegou:
if self.inicio:
self.ids.mapview.zoom = 15
self.inicio = False
gps = self.mqtt.get_gps
self.marker = MapMarkerPopup(lat=gps[0], lon=gps[1], source='Variant_MapP.png')
if self.existe:
self.ids.mapview.remove_widget(self.marker_old)
self.ids.mapview.add_widget(self.marker)
self.ids.mapview.center_on(float(gps[0]), float(gps[1]))
self.ids.velocidade.text = f'{gps[2]} Km/h'
self.marker_old = self.marker
self.existe = True
# This is the class responsible for the history.
class Historico(Screen):
def __init__(self, **kwargs):
super(Historico, self).__init__(**kwargs)
self.realtime = FireBase()
self.locais = None
#J ust for testing purposes, I'm using this variable in place of the actual data coming from Firebase.
self.pontos = [[-23.5609874, -46.6841326], [-23.5600753, -46.6844432], [-23.5608877, -46.6854705]]
self.dialog = None
def seleciona_data(self):
date_dialog = MDDatePicker()
date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
date_dialog.open()
def on_save(self, instance, value, date_range):
marcadores = []
lat_lon = []
for i in range(len(self.pontos)):
print(i)
lat, lon = self.pontos[i]
print(lat, lon)
marcadores.append(MapMarker(lat=lat, lon=lon))
self.ids.maphist.add_widget(marcadores[i])
self.ids.maphist.lat = self.pontos[0][0]
self.ids.maphist.lon = self.pontos[0][1]
self.ids.maphist.zoom = 17
for marcador in marcadores:
lat_lon.append(self.ids.maphist.get_window_xy_from(marcador.lon, marcador.lat, 17))
with self.ids.maphist.canvas:
Color(1, 0, 0)
Line(points=lat_lon, width=2)
def on_cancel(self, instance, value):
pass
def fecha_alerta(self, obj):
self.dialog.dismiss()
self.dialog = None
class Gerenciador(ScreenManager):
def __init__(self, **kwargs):
super(Gerenciador, self).__init__(**kwargs)
@staticmethod
def mudar():
ScreenManager.current = 'iniciar'
This is the KV code:
Gerenciador:
Login:
Inicial:
Mapa:
Historico:
<Login>:
id: tela_login
name: "login"
md_bg_color: 1, 1, 1, 1
MDBoxLayout:
orientation: "vertical"
md_bg_color: 1, 1, 1, 1
spacing: 40
padding: 15
MDLabel:
id: nblogin
text: "NetBeast Tracker"
font_size: 25
color: 5/255, 92/255, 54/255, 1
halign: 'center'
size_hint_y: None
height: self.texture_size[1]
padding_y: 15
MDTextField:
id: user
hint_text: 'usuário'
icon_right: 'account'
size_hint_x: None
width: 200
font_size: 18
text_color: 0, 0, 0, 1
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
MDTextField:
id: password
hint_text: 'senha'
icon_right: 'key-variant'
size_hint_x: None
width: 200
font_size: 18
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
password: True
MDGridLayout:
cols: 3
MDAnchorLayout:
anchor_x: 'right'
anchor_y: 'bottom'
MDRoundFlatButton:
text: 'Login'
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
font_size: 12
on_release: root.logar()
MDAnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Widget:
width: 20
MDAnchorLayout:
anchor_x: 'left'
anchor_y: 'bottom'
MDRoundFlatButton:
text: 'Limpar'
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
font_size: 12
on_release: root.clear()
MDRoundFlatButton:
pos_hint: {"center_x": 0.5, "center_y": 0.5}
text: 'Esqueci a senha'
font_size: 12
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
on_release: root.esqueci()
<Inicial>:
id: tela_inicial
name: "inicial"
MDBoxLayout:
orientation: "vertical"
md_bg_color: 1, 1, 1, 1
spacing: 80
padding: 30
MDRoundFlatButton:
id: alarme_btn
text: "Alarme"
font_size: 18
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
size_hint: 1, 0.1
pos_hint: {"center_x": 0.5}
on_release: root.alarme_press()
MDRoundFlatButton:
id: roubado_btn
text: "Seguro/Roubado"
font_size: 18
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
size_hint: 1, 0.1
pos_hint: {"center_x": 0.5}
on_release: root.roubado_press()
MDRoundFlatButton:
text: "Ver Mapa"
font_size: 18
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
size_hint: 1, 0.1
pos_hint: {"center_x": 0.5}
on_release:
app.root.current = "mapa"
root.manager.transition.direction = "left"
MDRoundFlatButton:
id: reinicia_btn
text: "Reiniciar Dispositivo"
font_size: 18
text_color: 1, 1, 1, 1
md_bg_color: 5/255, 92/255, 54/255, 1
size_hint: 1, 0.1
pos_hint: {"center_x": 0.5}
on_release: root.reinicia_press()
MDRoundFlatButton:
text: "Fechar App"
font_size: 18
font_name: "Roboto"
text_color: 1, 1, 1, 1
md_bg_color: 1, 78/255, 78/255, 1
size_hint: 0.7, 0.1
pos_hint: {"center_x": 0.5}
on_release: root.fechar()
<Mapa>:
id: tela_mapa
name: "mapa"
MDBoxLayout:
id: box_mapa
spacing: 10
padding: 5
md_bg_color: 1, 1, 1, 1
orientation: 'vertical'
MapView:
id: mapview
lat: -14.9074614
lon: -54.5827372
zoom: 3
MDGridLayout:
size_hint: 0.9, 0.06
cols: 3
MDAnchorLayout:
anchor_x: 'right'
anchor_y: 'bottom'
MDRoundFlatButton:
id: voltar_btn
text: "Voltar"
font_size: 14
#text_color: 0, 0, 1, 1
#md_bg_color: 158/255, 158/255, 158/255, 1
font_name: "Roboto"
size_hint: 0.3, 0.1
pos_hint: {"x": 0, "y": 0}
on_release:
app.root.current = "inicial"
root.manager.transition.direction = "right"
MDAnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
MDLabel:
id: velocidade
text: ""
font_name: "Roboto"
font_size: 14
halign: 'center'
size_hint_y: None
height: self.texture_size[1]
padding_y: 8
#Widget:
# width: 20
MDAnchorLayout:
anchor_x: 'left'
anchor_y: 'bottom'
MDRoundFlatButton:
id: historico_btn
text: "Historico"
font_size: 14
#text_color: 1, 1, 1, 1
#md_bg_color: 158/255, 158/255, 158/255, 1
font_name: "Roboto"
size_hint: 0.3, 0.1
pos_hint: {"x": 0, "y": 0}
on_release:
app.root.current = "historico"
root.manager.transition.direction = "left"
# Here is the part responsible for displaying the history.
<Historico>:
id: tela_historico
name: "historico"
MDBoxLayout:
spacing: 10
padding: 5
md_bg_color: 1, 1, 1, 1
orientation: 'vertical'
MapView:
id: maphist
lat: -14.9074614
lon: -54.5827372
zoom: 3
MDGridLayout:
size_hint: 0.8, 0.06
cols: 3
MDAnchorLayout:
anchor_x: 'right'
anchor_y: 'bottom'
MDRoundFlatButton:
id: voltar_hist_btn
text: "Voltar"
font_size: 14
#text_color: 1, 1, 1, 1
#md_bg_color: 158/255, 158/255, 158/255, 1
font_name: "Roboto"
size_hint: 0.3, 0.1
pos_hint: {"x": 0, "y": 1}
on_release:
app.root.current = "mapa"
root.manager.transition.direction = "right"
MDAnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Widget:
MDAnchorLayout:
anchor_x: 'left'
anchor_y: 'bottom'
MDRoundFlatButton:
id: data_btn
text: "Selecione a data"
font_size: 14
#text_color: 1, 1, 1, 1
#md_bg_color: 158/255, 158/255, 158/255, 1
font_name: "Roboto"
size_hint: 0.3, 0.06
pos_hint: {"x": 0, "y": 0}
on_release: root.seleciona_data()