def build(self): self.mapview = MapView(zoom=20, lat=45.47375, lon=9.17489, map_source="thunderforest-landscape") mml = MarkerMapLayer() self.mapview.add_layer(mml) self.player_marker = MapMarker(lat=45.47375, lon=9.17489) self.mapview.add_marker(self.player_marker) self.update_map() Clock.schedule_once(lambda *args: self.mapview.center_on(*self.location)) Clock.schedule_interval(self.update_map, 1) Clock.schedule_interval(lambda *a: mml.reposition(), 0.1) root = FloatLayout() root.add_widget(self.mapview) return root
class Editor(GridLayout): current_layer = None edit_last_move = None markers = [] mode = StringProperty("polygon") is_editing = BooleanProperty(False) map_source = ObjectProperty() geojson_fn = StringProperty() title = StringProperty() def __init__(self, **kwargs): super(Editor, self).__init__(**kwargs) self.marker_layer = MarkerMapLayer() self.current_layer = GeoJsonMapLayer() self.result_layer = GeoJsonMapLayer() self.result_layer.geojson = kwargs["geojson"] self.geojson_fn = kwargs["geojson_fn"] self.ids.mapview.add_widget(self.current_layer) self.ids.mapview.add_widget(self.result_layer) self.ids.mapview.add_widget(self.marker_layer) def on_touch_down(self, touch): if self.ids.mapview.collide_point(*touch.pos): if touch.is_double_tap and not self.is_editing: feature = self.select_feature(*touch.pos) if feature: self.edit_feature(feature) else: touch.grab(self) self.forward_to_object(touch) return True elif touch.is_double_tap and self.is_editing: if self.remove_marker_at(touch): return True touch.grab(self) return self.forward_to_object(touch) elif not self.is_editing: self.select_feature(*touch.pos) return self.ids.mapview.on_touch_down(touch) return super(Editor, self).on_touch_down(touch) def on_touch_move(self, touch): if touch.grab_current == self and self.is_editing: return self.forward_to_object(touch, move=True) return super(Editor, self).on_touch_move(touch) def switch_mode(self): if self.is_editing: self.finalize_object() return self.mode = "line" if self.mode == "polygon" else "polygon" def forward_to_object(self, touch, move=False): mapview = self.ids.mapview if not self.is_editing: self.is_editing = True self.clear_markers() coord = mapview.get_latlon_at(*touch.pos) if move: m = self.markers[-1] else: m = EditorMarker() m.mapview = mapview m.editor = self self.markers.append(m) self.marker_layer.add_widget(m) self.marker_layer.reposition() m.lat = coord.lat m.lon = coord.lon self._update_geojson() return True def clear_markers(self): while self.markers: self.remove_marker(self.markers.pop()) def remove_marker(self, m): m.mapview = m.editor = None self.marker_layer.remove_widget(m) if m in self.markers: self.markers.remove(m) self._update_geojson() def remove_marker_at(self, touch): pos = self.ids.mapview.to_local(*touch.pos) for marker in self.markers[:]: if marker.collide_point(*pos): self.remove_marker(marker) return True def finalize_object(self): if self.markers: geojson = self.current_layer.geojson if "properties" not in geojson: geojson["features"][0]["properties"] = {"title": self.title} else: geojson["features"][0]["properties"]["title"] = self.title self.result_layer.geojson["features"].extend(geojson["features"]) self.ids.mapview.trigger_update(True) self.clear_markers() self.is_editing = False self.title = "" self._update_geojson() def save_geojson(self): with open(self.geojson_fn, "wb") as fd: json.dump(self.result_layer.geojson, fd) def _update_geojson(self): features = [] if self.mode == "polygon": geotype = "Polygon" geocoords = lambda x: [x] else: geotype = "LineString" geocoords = lambda x: x # current commited path if self.markers: coordinates = [[c.lon, c.lat] for c in self.markers] features.append({ "type": "Feature", "properties": {}, "geometry": { "type": geotype, "coordinates": geocoords(coordinates) } }) geojson = {"type": "FeatureCollection", "features": features} self.current_layer.geojson = geojson self.ids.mapview.trigger_update(True) def point_inside_polygon(self, x, y, poly): n = len(poly) inside = False p1x, p1y = poly[0] for i in range(n + 1): p2x, p2y = poly[i % n] if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): if p1y != p2y: xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside p1x, p1y = p2x, p2y return inside def select_feature(self, x, y): coord = self.ids.mapview.get_latlon_at(x, y) for feature in self.result_layer.geojson["features"]: if feature["type"] != "Feature": continue geometry = feature["geometry"] if geometry["type"] == "Polygon": if self.point_inside_polygon(coord.lon, coord.lat, geometry["coordinates"][0]): return feature def edit_feature(self, feature): self.result_layer.geojson["features"].remove(feature) #self.current_layer.geojson = feature self.ids.mapview.trigger_update(True) self.title = feature.get("properties", {}).get("title", "") self.is_editing = True self.clear_markers() for c in feature["geometry"]["coordinates"][0]: m = EditorMarker(lon=c[0], lat=c[1]) m.mapview = self.ids.mapview m.editor = self self.marker_layer.add_widget(m) self.markers.append(m) self._update_geojson()
class ISSScreen(Screen): def __init__(self, **kwargs): super(ISSScreen, self).__init__(**kwargs) # Set the path for the folder self.path = os.path.dirname(os.path.abspath(__file__)) # Set the path for local images self.imagefolder = os.path.join(self.path, "images") # Ephem calculates the position using the Two Line Element data # We need to make sure we have up to date info tle = self.get_TLE() # Create an iss object from which we can get positional data self.iss = ephem.readtle(*tle) # Run the calculations self.iss.compute() # Get positon of iss and place a marker there lat, lon = self.get_loc() self.marker = MapMarker(lat=lat, lon=lon) # Create a value to check when we last drew ISS path self.last_path_update = 0 # Create the path icon self.path_icon = os.path.join(self.imagefolder, "dot.png") # Create the world map self.map = MapView(id="mpv", lat=0, lon=0, zoom=1, scale=1.5) x, y = self.map.get_window_xy_from(0, 0, 1) self.map.scale_at(1.2, x, y) # Add the ISS marker to the map and draw the map on the screen self.map.add_widget(self.marker) self.add_widget(self.map) # Add a new layer for the path self.mmlayer = MarkerMapLayer() self.draw_iss_path() self.timer = None def on_enter(self): self.timer = Clock.schedule_interval(self.update, 1) def on_leave(self): Clock.unschedule(self.timer) def utcnow(self): return (datetime.utcnow() - datetime(1970, 1, 1)).total_seconds() def draw_iss_path(self): # Path is drawn every 5 mins if self.utcnow() - self.last_path_update > 30: try: self.map.remove_layer(self.mmlayer) except: pass self.mmlayer = MarkerMapLayer() # Create markers showing the ISS's position every 5 mins for i in range(20): lat, lon = self.get_loc(datetime.now() + timedelta(0, i * 300)) self.mmlayer.add_widget( MapMarker(lat=lat, lon=lon, source=self.path_icon)) # Update the flag so we know when next update should be run self.last_path_update = self.utcnow() # Add the layer and call the reposition function otherwise the # markers don't show otherwise! self.map.add_layer(self.mmlayer) self.mmlayer.reposition() def get_TLE(self): # Set some flags need_update = False # Set our data source and the name of the object we're tracking source = "http://www.celestrak.com/NORAD/elements/stations.txt" ISS = "ISS (ZARYA)" # Get the current time utc_now = self.utcnow() # Set the name of our file to store data data = os.path.join(self.path, "iss_tle.json") # Try loading old data try: with open(data, "r") as savefile: saved = json.load(savefile) # If we can't create a dummy dict except IOError: saved = {"updated": 0} # If old data is more than an hour hold, let's check for an update if utc_now - saved["updated"] > 3600: need_update = True # If we don't have any TLE data then we need an update if not saved.get("tle"): need_update = True if need_update: # Load the TLE data raw = requests.get(source).text # Split the data into a neat list all_sats = [sat.strip() for sat in raw.split("\n")] # Find the ISS and grab the whole TLE (three lines) iss_index = all_sats.index(ISS) iss_tle = all_sats[iss_index:iss_index + 3] # Prepare a dict to save our data new_tle = {"updated": utc_now, "tle": iss_tle} # Save it with open(data, "w") as savefile: json.dump(new_tle, savefile, indent=4) # ephem needs strings not unicode return [str(x) for x in iss_tle] else: # return the saved data (as strings) return [str(x) for x in saved["tle"]] def update(self, *args): # Update the ISS with newest TLE self.iss = ephem.readtle(*self.get_TLE()) # Get the position and update marker lat, lon = self.get_loc() self.marker.lat = lat self.marker.lon = lon self.map.remove_widget(self.marker) self.map.add_widget(self.marker) # Check if the path needs redrawing self.draw_iss_path() def get_loc(self, dt=None): # We can get the location for a specific time as well if dt is None: self.iss.compute() else: self.iss.compute(dt) # Convert the ephem data into something that the map can use lat = float(self.iss.sublat / ephem.degree) lon = float(self.iss.sublong / ephem.degree) return lat, lon
class ISSScreen(Screen): def __init__(self, **kwargs): super(ISSScreen, self).__init__(**kwargs) # Set the path for the folder self.path = os.path.dirname(os.path.abspath(__file__)) # Set the path for local images self.imagefolder = os.path.join(self.path, "images") # Ephem calculates the position using the Two Line Element data # We need to make sure we have up to date info tle = self.get_TLE() # Create an iss object from which we can get positional data self.iss = ephem.readtle(*tle) # Run the calculations self.iss.compute() # Get positon of iss and place a marker there lat, lon = self.get_loc() self.marker = MapMarker(lat=lat, lon=lon) # Create a value to check when we last drew ISS path self.last_path_update = 0 # Create the path icon self.path_icon = os.path.join(self.imagefolder, "dot.png") # Create the world map self.map = MapView(id="mpv",lat=0, lon=0, zoom=1, scale=1.5) x, y = self.map.get_window_xy_from(0,0,1) self.map.scale_at(1.2, x, y) # Add the ISS marker to the map and draw the map on the screen self.map.add_widget(self.marker) self.add_widget(self.map) # Add a new layer for the path self.mmlayer = MarkerMapLayer() self.draw_iss_path() self.timer = None def on_enter(self): self.timer = Clock.schedule_interval(self.update, 1) def on_leave(self): Clock.unschedule(self.timer) def utcnow(self): return (datetime.utcnow() - datetime(1970,1,1)).total_seconds() def draw_iss_path(self): # Path is drawn every 5 mins if self.utcnow() - self.last_path_update > 30: try: self.map.remove_layer(self.mmlayer) except: pass self.mmlayer = MarkerMapLayer() # Create markers showing the ISS's position every 5 mins for i in range(20): lat, lon = self.get_loc(datetime.now() + timedelta(0, i * 300)) self.mmlayer.add_widget(MapMarker(lat=lat, lon=lon, source=self.path_icon)) # Update the flag so we know when next update should be run self.last_path_update = self.utcnow() # Add the layer and call the reposition function otherwise the # markers don't show otherwise! self.map.add_layer(self.mmlayer) self.mmlayer.reposition() def get_TLE(self): # Set some flags need_update = False # Set our data source and the name of the object we're tracking source = "http://www.celestrak.com/NORAD/elements/stations.txt" ISS = "ISS (ZARYA)" # Get the current time utc_now = self.utcnow() # Set the name of our file to store data data = os.path.join(self.path, "iss_tle.json") # Try loading old data try: with open(data, "r") as savefile: saved = json.load(savefile) # If we can't create a dummy dict except IOError: saved = {"updated": 0} # If old data is more than an hour hold, let's check for an update if utc_now - saved["updated"] > 3600: need_update = True # If we don't have any TLE data then we need an update if not saved.get("tle"): need_update = True if need_update: # Load the TLE data raw = requests.get(source).text # Split the data into a neat list all_sats = [sat.strip() for sat in raw.split("\n")] # Find the ISS and grab the whole TLE (three lines) iss_index = all_sats.index(ISS) iss_tle = all_sats[iss_index:iss_index + 3] # Prepare a dict to save our data new_tle = {"updated": utc_now, "tle": iss_tle} # Save it with open(data, "w") as savefile: json.dump(new_tle, savefile, indent=4) # ephem needs strings not unicode return [str(x) for x in iss_tle] else: # return the saved data (as strings) return [str(x) for x in saved["tle"]] def update(self, *args): # Update the ISS with newest TLE self.iss = ephem.readtle(*self.get_TLE()) # Get the position and update marker lat, lon = self.get_loc() self.marker.lat = lat self.marker.lon = lon self.map.remove_widget(self.marker) self.map.add_widget(self.marker) # Check if the path needs redrawing self.draw_iss_path() def get_loc(self, dt=None): # We can get the location for a specific time as well if dt is None: self.iss.compute() else: self.iss.compute(dt) # Convert the ephem data into something that the map can use lat = float(self.iss.sublat / ephem.degree) lon = float(self.iss.sublong / ephem.degree) return lat, lon
class RadarMapView(MapView): def __init__(self, lat=51.5, lon=0, zoom=8, url="http://localhost/dump1090/data/aircraft.json", interval=1): self.aircraft_layer = MarkerMapLayer() self.update_in_progress = False self.list_of_tracked_aircraft = [] self.aircraft_markers = [] self.url = url super().__init__(lat=lat, lon=lon, zoom=zoom) self.add_layer(self.aircraft_layer) Clock.schedule_interval(self.request_update, interval) def request_update(self, time): UrlRequest(self.url, on_success=self.update_aircraft) def update_aircraft(self, request, result): if self.update_in_progress: return self.update_in_progress = True list_of_aircraft = result["aircraft"] # Update tracked aircraft and mark stale ones as inactive for a_tracked_aircraft in self.list_of_tracked_aircraft: aircraft_active = False for an_aircraft in list_of_aircraft: if an_aircraft["hex"] == a_tracked_aircraft.hex: a_tracked_aircraft.data = an_aircraft aircraft_active = True break a_tracked_aircraft.active = aircraft_active # Add newly detected aircraft for an_aircraft in list_of_aircraft: aircraft_tracked = False for a_tracked_aircraft in self.list_of_tracked_aircraft: if a_tracked_aircraft.hex == an_aircraft["hex"]: aircraft_tracked = True break if not aircraft_tracked: a_tracked_aircraft = Aircraft() a_tracked_aircraft.hex = an_aircraft["hex"] a_tracked_aircraft.active = True a_tracked_aircraft.data = an_aircraft self.list_of_tracked_aircraft.append(a_tracked_aircraft) # Set up markers for a_tracked_aircraft in self.list_of_tracked_aircraft: active = a_tracked_aircraft.active data = a_tracked_aircraft.data position_known = "lat" in data and "lon" in data and "track" in data has_marker = hasattr(a_tracked_aircraft, "marker") if active and position_known: if has_marker: marker = a_tracked_aircraft.marker else: marker = AircraftMarker() marker.anchor_x = 0.5 marker.anchor_y = 0.5 a_tracked_aircraft.marker = marker marker.popup_size = (80, 25) a_tracked_aircraft.labelmode = 1 label = Label() bubble = Bubble() label.color = (1, 1, 0, 1) label.outline_color = (0, 0, 0, 1) bubble.add_widget(label) marker.add_widget(bubble) a_tracked_aircraft.label = label if hasattr(a_tracked_aircraft, "label"): label = a_tracked_aircraft.label if "flight" in data: label.text = data["flight"] else: label.text = "unknown" if marker.full_label: marker.popup_size = (80, 100) if "altitude" in data: label.text = label.text + \ "\nALT: {}".format(data["altitude"]) if "vert_rate" in data: label.text = label.text + \ "\nRoC: {}".format(data["vert_rate"]) if "track" in data: label.text = label.text + \ "\nTRK: {}".format(data["track"]) if "speed" in data: label.text = label.text + \ "\nSPD: {}".format(data["speed"]) else: marker.popup_size = (80, 25) marker.source = "icons/plane{}.png".format( round(a_tracked_aircraft.data["track"]/10)*10) marker.lat = a_tracked_aircraft.data["lat"] marker.lon = a_tracked_aircraft.data["lon"] if not has_marker: self.add_marker( a_tracked_aircraft.marker, layer=self.aircraft_layer) else: if has_marker: self.remove_marker(a_tracked_aircraft.marker) delattr(a_tracked_aircraft, "marker") if not active: self.list_of_tracked_aircraft.remove(a_tracked_aircraft) self.aircraft_layer.reposition() self.update_in_progress = False