omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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

    Pythonista
    1
    1
    2088
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cvp
      cvp last edited by

      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()
      
      1 Reply Last reply Reply Quote 2
      • First post
        Last post
      Powered by NodeBB Forums | Contributors