Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Show on a map the chronological route of your photos
-
The script
asks to pick 2 or more photos
get their "taken date"
selects either the picked photos (if more than 2) or all photos (if 2) with a "taken date" between the 2 photos dates
gets their localizations
displays a map containg all photos
displays pin's of their localizations
sorts all photos on their ascending "taken date"
displays a polygonal line showing the route of the photos
This last part has been possible with the help of two guru's, @JonB and @dgelessus
see# todo # - settîngs pin's visible or not # - settings route color/width # coding: utf-8 # # MKMapView part initially copied from OMZ MapView demo # https://gist.github.com/omz/451a6685fddcf8ccdfc5 # then "cleaned" to keep the easiest code as possible # # For use of objc_util calls and crashes trace, more than help received from # @dgelssus and @JonB in Pythonista forum # https://forum.omz-software.com/topic/3507/need-help-for-calling-an-objective_c-function # # Display MKPolyline in Mapkit from Robert Kerr # http://blog.robkerr.com/adding-a-mkpolyline-overlay-using-swift-to-an-ios-mapkit-map/ # import console import clipboard from objc_util import * import ctypes import ui from PIL import Image from PIL.ExifTags import TAGS,GPSTAGS import appex import photos # used to test in non-appex mode import webbrowser # if the launcher app is installed class CLLocationCoordinate2D (Structure): _fields_ = [('latitude', c_double), ('longitude', c_double)] class MKCoordinateSpan (Structure): _fields_ = [('d_lat', c_double), ('d_lon', c_double)] class MKCoordinateRegion (Structure): _fields_ = [('center', CLLocationCoordinate2D), ('span', MKCoordinateSpan)] MKPolyline = ObjCClass('MKPolyline') MKPolylineRenderer = ObjCClass('MKPolylineRenderer') def mapView_rendererForOverlay_(self,cmd,mk_mapview,mk_overlay): try: overlay = ObjCInstance(mk_overlay) mapView = ObjCInstance(mk_mapview) if overlay.isKindOfClass_(MKPolyline): pr = MKPolylineRenderer.alloc().initWithPolyline(overlay); pr.strokeColor = UIColor.redColor().colorWithAlphaComponent(0.5); pr.lineWidth = 2; return pr.ptr pass return None except Exception as e: print('exception: ',e) # Build method of MKMapView Delegate methods = [mapView_rendererForOverlay_] protocols = ['MKMapViewDelegate'] try: MyMapViewDelegate = ObjCClass('MyMapViewDelegate') except: MyMapViewDelegate = create_objc_class('MyMapViewDelegate', NSObject, methods=methods, protocols=protocols) class MapView(ui.View): @on_main_thread def __init__(self, *args, **kwargs): ui.View.__init__(self, *args, **kwargs) MKMapView = ObjCClass('MKMapView') frame = CGRect(CGPoint(0, 0), CGSize(self.width, self.height)) self.mk_map_view = MKMapView.alloc().initWithFrame_(frame) flex_width, flex_height = (1<<1), (1<<4) self.mk_map_view.setAutoresizingMask_(flex_width|flex_height) self_objc = ObjCInstance(self) self_objc.addSubview_(self.mk_map_view) self.mk_map_view.release() # Set Delegate of mk_map_view self.map_delegate = MyMapViewDelegate.alloc().init().autorelease() self.mk_map_view.setDelegate_(self.map_delegate) @on_main_thread def add_pin(self, lat, lon, title): global all_points '''Add a pin annotation to the map''' MKPointAnnotation = ObjCClass('MKPointAnnotation') coord = CLLocationCoordinate2D(lat, lon) all_points.append(coord) # store all pin's for MKPolyline annotation = MKPointAnnotation.alloc().init().autorelease() annotation.setTitle_(title) annotation.setCoordinate_(coord, restype=None, argtypes=[CLLocationCoordinate2D]) self.mk_map_view.addAnnotation_(annotation) @on_main_thread def set_region(self, lat, lon, d_lat, d_lon, animated=False): '''Set latitude/longitude of the view's center and the zoom level (specified implicitly as a latitude/longitude delta)''' region = MKCoordinateRegion(CLLocationCoordinate2D(lat, lon), MKCoordinateSpan(d_lat, d_lon)) self.mk_map_view.setRegion_animated_(region, animated, restype=None, argtypes=[MKCoordinateRegion, c_bool]) @on_main_thread def addPolyLineToMap(self): global all_points global all_points_array all_points_array = (CLLocationCoordinate2D * len(all_points))(*all_points) polyline = ObjCInstance(MKPolyline.polylineWithCoordinates_count_( all_points_array, len(all_points), restype=c_void_p, argtypes=[POINTER(CLLocationCoordinate2D), c_ulong], )) self.mk_map_view.addOverlay_(polyline) def will_close(self): # Back to home screen return # temporary during tests webbrowser.open('launcher://crash') def main(): global all_points #----- Main process ----- console.clear() # Hide script back = MapView(frame=(0, 0, 540, 620)) back.background_color='white' back.name = 'Display route of selected localized photos' back.present('full_screen', hide_title_bar=False) # Get a list of all photos c = photos.get_assets(media_type='image') # Pick at least two photos from all photos ps = photos.pick_asset(assets=c, title='Pick begin/end or all photos of the route', multi=True) if ps == None or len(ps) < 2: # Pick has been canceled console.hud_alert('At least two photos are needed','error') back.close() return # Loop on all photos route_points = [] if len(ps) > 2: # more than 2 picked photos scan_ph = ps # use picked photos only else: # 2 photos picked scan_ph = c # scan all photos min_date = min(ps[0].creation_date,ps[1].creation_date).date() max_date = max(ps[0].creation_date,ps[1].creation_date).date() for p in scan_ph: p_date = p.creation_date.date() if (len(ps) > 2) or (len(ps) == 2 and p_date >= min_date and p_date <= max_date): # Photo belongs to the route period if p.location: # Photo has GPS tags lat = p.location['latitude'] lon = p.location['longitude'] # store latitude, longitude and taken date route_points.append((lat,lon,p_date)) if len(route_points) < 2: console.hud_alert('At least two localized photos neded','error') back.close() return # Sort points by ascending taken date route_points = sorted(route_points,key = lambda x: x[2]) # Compute min and max of latitude and longitude min_lat = min(route_points,key = lambda x:x[0])[0] max_lat = max(route_points,key = lambda x:x[0])[0] min_lon = min(route_points,key = lambda x:x[1])[1] max_lon = max(route_points,key = lambda x:x[1])[1] # Display map, center and zoom so all points are visible back.set_region((min_lat+max_lat)/2,(min_lon+max_lon)/2, 1.2*(max_lat-min_lat), 1.2*(max_lon-min_lon), animated=True) # Display pin's all_points = [] for point in route_points: back.add_pin(point[0],point[1],str(point[2])) # Display polygon line of sorted locations back.addPolyLineToMap() # Protect against import if __name__ == '__main__': main()