from plyer import gps from kivy.app import App from kivy.clock import Clock from kivy.core.window import Window from kivy.properties import DictProperty, NumericProperty, StringProperty from kivy.clock import mainthread from kivy.utils import platform from kivy_garden.mapview import MapView from kivy.uix.screenmanager import ScreenManager, Screen, RiseInTransition from kivy.uix.behaviors import ButtonBehavior from kivy.uix.image import Image from kivy.vector import Vector from kivy.animation import Animation from math import atan2, sin, cos, degrees, floor from geopy.distance import geodesic class IconButton(ButtonBehavior, Image): pass class MapScreen(Screen): pass class CompassScreen(Screen): def __init__(self, **kwargs): super(CompassScreen, self).__init__(**kwargs) Window.bind(on_keyboard=self.android_back_click) def android_back_click(self, window, key, *largs): if key == 27 and self.parent is not None: self.parent.current='map' return True class SettingsScreen(Screen): def __init__(self, **kwargs): super(SettingsScreen, self).__init__(**kwargs) Window.bind(on_keyboard=self.android_back_click) def android_back_click(self, window, key, *largs): if key == 27 and self.parent is not None: self.parent.current='map' return True class FlockompassApp(App): gps_data = DictProperty() session_data = DictProperty() needle_angle = NumericProperty(0) fbearing = NumericProperty(0) dest_distance = StringProperty("") dashboard_dest = StringProperty("") dashboard_flock = StringProperty("") def dump(self, dt): print(dt, self.gps_data, self.session_data) def set_destination(self): self.compass_enable() self.session_data['dest_lat'] = self.ms.ids.centermark.lat self.session_data['dest_lon'] = self.ms.ids.centermark.lon self.ms.ids.mapview.center_on(self.session_data['dest_lat'], self.session_data['dest_lon']) def request_android_permissions(self): from android.permissions import request_permissions, Permission def callback(permissions, results): if all([res for res in results]): print("aguas: callback. All permissions granted.") else: print("aguas: callback. Some permissions refused.", results) request_permissions([Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION], callback) def gps_start(self, minTime, minDistance): gps.start(minTime, minDistance) def gps_stop(self): gps.stop() @mainthread def on_location(self, **kwargs): self.gps_data = kwargs self.dump(1) if ('dest_lat' not in self.session_data and 'dest_lon' not in self.session_data): self.session_data['dest_lat'] = self.gps_data['lat'] self.session_data['dest_lon'] = self.gps_data['lon'] self.ms.ids.mapview.center_on(self.session_data['dest_lat'], self.session_data['dest_lon']) else: dest_distance = geodesic( (self.gps_data['lat'], self.gps_data['lon']), (self.session_data['dest_lat'], self.session_data['dest_lon'])).kilometers if dest_distance < 1: dest_distance *= 1000 dest_distance = "%i metros" % dest_distance else: dest_distance = "%0.1f km" % dest_distance self.dest_distance = "Destino: %s\nVel: %0.1f km/h" % (dest_distance, self.gps_data['speed']) def center_map_on_gps(self): self.ms.ids.mapview.center_on(self.gps_data['lat'], self.gps_data['lon']) def get_field(self, dt): """ get magnetic field for compass """ needle_angle = 0 if self.cs.facade.field != (None, None, None): x, y, z = self.cs.facade.field smoothingFactor = 0.676 angle = atan2(y, x) self.last_angle = smoothingFactor * self.last_angle + (1 - smoothingFactor) * angle needle_angle = degrees(self.last_angle) - 90 # fix animation transition around the unit circle if (self.needle_angle % 360) - needle_angle > 180: needle_angle += 360 elif (self.needle_angle % 360) - needle_angle < -180: needle_angle -= 360 # add the number of revolutions to the result needle_angle += 360 * floor(self.needle_angle / 360.) # animate the needle if self.cs._anim: self.cs._anim.stop(self) self.cs._anim = Animation(needle_angle=needle_angle, d=0.1, t='out_quad') lat1 = self.gps_data['lat'] lon1 = self.gps_data['lon'] lat2 = self.session_data['dest_lat'] lon2 = self.session_data['dest_lon'] fbearing = atan2(sin(lon2 - lon1) * cos(lat2), cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2-lon1)) fbearing = self.needle_angle - degrees(fbearing) #fbearing = (fbearing + 360) % 360 self.cs._anim &= Animation(fbearing=fbearing, d=0.1, t='out_quad') self.cs._anim.start(self) def compass_enable(self): self.cs.facade.enable() Clock.schedule_interval(self.get_field, 1 / 33.3) def compass_disable(self): self.cs.facade.disable() Clock.unschedule(self.get_field) def on_pause(self): self.compass_disable() self.gps_stop() return True def on_resume(self): self.compass_enable() self.gps_start(1000, 0) def build(self): # start GPS try: gps.configure(on_location=self.on_location) except NotImplementedError: import traceback traceback.print_exc() print('GPS is not implemented for your platform') if platform == "android": print("gps.py: Android detected. Requesting permissions") self.request_android_permissions() self.gps_start(1000, 0) # setup app screens screen_manager = ScreenManager(transition=RiseInTransition()) self.ms = MapScreen(name='map') screen_manager.add_widget(self.ms) self.cs = CompassScreen(name='compass') self.cs._anim = None self.cs._anim1 = None screen_manager.add_widget(self.cs) self.ss = SettingsScreen(name='settings') screen_manager.add_widget(self.ss) # smoothing vars for compass self.last_angle = 0 #Clock.schedule_interval(self.dump, 2.0) return screen_manager if __name__ == '__main__': app = FlockompassApp() app.run()