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 location of foto library for date range on a map

    Pythonista
    2
    25
    586
    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.
    • bodobolero
      bodobolero last edited by bodobolero

      I created a pythonista script that prompts you for a date range and then extracts the GPS longitude and latitude from all assets in your foto library for fotos created in the given date range and draws a hybrid satellite image with a path following the image creation dates.

      Thus you can create a path/map of your vacation / trip.

      Find the gist here ShowWhereIWas gist

      bodobolero 2 Replies Last reply Reply Quote 1
      • bodobolero
        bodobolero @bodobolero last edited by bodobolero

        @bodobolero Now the script was extended to

        • save the map as a png file in the current (scripts) folder
        • save the list of geo-locations as a track (.gpx) file that can be imported into apps like komoot or outdoor active (or tracking devices like Garmin)
        cvp 1 Reply Last reply Reply Quote 0
        • cvp
          cvp @bodobolero last edited by cvp

          @bodobolero Perhaps could you be interested by this script that displays all your (localized) photos (no selection actually ) as a route on an Apple Map (zoom possible) with each photo (minimized) shown at its location. Works in UI, not displayed in console

          Photos Route on Map.py

          Example

          alt text

          Tapping a photo displays its taken date and its order in the route

          alt text

          Zoom possible

          alt text

          bodobolero 1 Reply Last reply Reply Quote 0
          • bodobolero
            bodobolero @bodobolero last edited by

            @bodobolero Here is some more description/explanation https://www.bodobolero.com/2023/05/13/Pythonista-script-to-create-map-of-fotos-in-ios-foto-library/

            1 Reply Last reply Reply Quote 0
            • bodobolero
              bodobolero @cvp last edited by

              @cvp This looks interesting, so I tried it.
              It showed a map and a progress indicator for about 30 seconds then the app crashed.

              Note that I have about 10.000 images in my photo library.

              I think your app may not scale to that amount of photos.
              Also if you travel a lot you will basically display a world map to cover all locations which is not very helpful.

              Maybe we can combine our code - as a first step I suggest that you allow to specify the date range in your app to avoid the scalability issues and reduce the number of photos down to a smaller number

              cvp 1 Reply Last reply Reply Quote 0
              • cvp
                cvp @bodobolero last edited by

                @bodobolero understood, too many photos, I guess.
                Feel free to modify the script to use your dates range settings. Or do you want I do it?

                bodobolero 1 Reply Last reply Reply Quote 0
                • bodobolero
                  bodobolero @cvp last edited by

                  @cvp Don‘t have time now - if you have time you can give it a start and I can look into it the following days

                  cvp 1 Reply Last reply Reply Quote 0
                  • cvp
                    cvp @bodobolero last edited by cvp

                    @bodobolero ok, it is done, GitHub updated

                    bodobolero 1 Reply Last reply Reply Quote 0
                    • bodobolero
                      bodobolero @cvp last edited by

                      @cvp Thanks a lot - but somehow it still crashes for me without giving a useful error message - maybe I will find some time to debug in the following days.

                      cvp 1 Reply Last reply Reply Quote 0
                      • cvp
                        cvp @bodobolero last edited by

                        @bodobolero Ok, be sure that you don't select too much photos to test the script.

                        And could you also restart Pythonista (remove from tasks list and rerun).

                        And perhaps do you have in the root a file named _objc_exception.txt with the error reason.

                        bodobolero 1 Reply Last reply Reply Quote 0
                        • bodobolero
                          bodobolero @cvp last edited by

                          @cvp In your gist you had commented out the filtering of the images by date in line https://github.com/cvpe/Pythonista-scripts/blob/3d14d069fef70096e90776a98e6a5a78f7bdcd91/Gists/Photos Route on Map.py#L101

                          that is why it still was processing all pictures.

                          If I pick a date range with a time interval that has about 10 pictures it works, however if I select a date range of my latest trip with about 400 images it still crashes

                          cvp 2 Replies Last reply Reply Quote 0
                          • cvp
                            cvp @bodobolero last edited by cvp

                            @bodobolero ok, now we know it is a problem of memory. You could play with the thumb size for testing.

                            map_pin_size = 80
                            

                            Perhaps, my code of storing thumbs could be modified to gain memory

                            				# store reduced photo to win memory 
                            				img = p.get_ui_image()
                            				wh = img.size
                            				# normal photo 
                            				h = map_pin_size
                            				w = int(h * wh[0] / wh[1])
                            				with ui.ImageContext(w,h) as ctx:
                            					img.draw(0,0,w,h)
                            					img = ctx.get_image()				
                            
                            				# store latitude, longitude and taken date
                            				route_points.append((lat,lon,p_date, img))
                            
                            bodobolero 2 Replies Last reply Reply Quote 0
                            • cvp
                              cvp @bodobolero last edited by

                              @bodobolero said

                              In your gist you had commented out the filtering of the images by date in line

                              Sorry, forgotten during my tests

                              1 Reply Last reply Reply Quote 0
                              • bodobolero
                                bodobolero @cvp last edited by

                                @cvp Sorry, even with map_pin_size = 20 I could not get it to run for a reasonable number of photos (around 400) - it still crashed with out of memory.

                                At this point I think the interactive display of all images doesn‘t scale.

                                What the Apple photo library does in this case: it displays only one sample image for each location that has multiple images in a bounded area and on top it overlays a little number that shows how many images are taken at that location. Only when the user zooms into the area it shows more images.
                                Thus it manages the memory footprint and always stays within a max amount.

                                However this is rebuilding the photo library function and beyond the scope of what I intended to do.

                                Thanks for your help anyway!

                                1 Reply Last reply Reply Quote 0
                                • bodobolero
                                  bodobolero @cvp last edited by

                                  @cvp I have now solved the out of memory problem, the critical changed line (besides pin size) is

                                  img = p.get_ui_image(size=(map_pin_size,map_pin_size), crop=True)
                                  
                                  
                                  # 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 dialogs
                                  from objc_util import *
                                  import ctypes
                                  import ui
                                  from PIL import Image
                                  import photos			
                                  
                                  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')
                                  MKPointAnnotation = ObjCClass('MKPointAnnotation')
                                  MKAnnotationView = ObjCClass('MKAnnotationView')
                                  
                                  map_pin_size = 40
                                  
                                  def mapView_viewForAnnotation_(self,cmd,mk_mapview,mk_annotation):
                                  	global route_points
                                  	try:
                                  		# not specially called in the same sequence as pins created
                                  		# should have one MK(Pin)AnnotationView by type (ex: pin color)
                                  		annotation = ObjCInstance(mk_annotation)
                                  		mapView = ObjCInstance(mk_mapview)
                                  		if annotation.isKindOfClass_(MKPointAnnotation):
                                  			tit = str(annotation.title())	
                                  			subtit = str(annotation.subtitle())	
                                  			id = subtit
                                  			pinView = mapView.dequeueReusableAnnotationViewWithIdentifier_(id)		
                                  			if not pinView:
                                  				# Modify pin image: use MKAnnotationView
                                  				pinView = MKAnnotationView.alloc().initWithAnnotation_reuseIdentifier_(annotation,id)
                                  				ui_image = route_points[int(subtit)][3]
                                  				pinView.setImage_(ui_image.objc_instance)
                                  				pinView.canShowCallout = True
                                  			else:
                                  				pinView.annotation = annotation
                                  			return pinView.ptr
                                  		return None
                                  	except Exception as e:
                                  		print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e)
                                  
                                  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.blueColor().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_, mapView_viewForAnnotation_]
                                  protocols = ['MKMapViewDelegate']
                                  try:
                                  	MyMapViewDelegate = ObjCClass('MyMapViewDelegate')
                                  except:
                                  	MyMapViewDelegate = create_objc_class('MyMapViewDelegate', NSObject, methods=methods, protocols=protocols)
                                  	
                                  # ask the user for start date and end date
                                  def pickDateInterval():
                                  	startDate = dialogs.date_dialog(title='When did your trip start?',done_button_title='Select trip start date')
                                  	endDate = dialogs.date_dialog(title='When did you trip end?', done_button_title='Select trip end date')
                                  	print(f'startDate:{startDate} endDate:{endDate}')
                                  	return (startDate, endDate)
                                  	
                                  # go to the foto library and extract all images with location data in a given date range
                                  def getAssetsWithLocationInDateInterval(startDate, endDate):
                                  	all_assets = photos.get_assets(media_type='image', include_hidden=False)
                                  	print("Number of all assets:")
                                  	print(len(all_assets))
                                  	location_assets = [asset for asset in all_assets if asset.location != None]
                                  	print("Number of assets with location:")
                                  	print(len(location_assets))
                                  	timed_assets = [asset for asset in location_assets if ( asset.creation_date.date() >= startDate and asset.creation_date.date() <= endDate) ]
                                  	#timed_assets = location_assets
                                  	print("Number of assets with location in date interval:")
                                  	print(len(timed_assets))
                                  	return timed_assets
                                  
                                  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, subtitle):
                                  		global all_points
                                  		'''Add a pin annotation to the map'''
                                  		coord = CLLocationCoordinate2D(lat, lon)
                                  		all_points.append(coord)			# store all pin's for MKPolyline
                                  		annotation = MKPointAnnotation.alloc().init().autorelease()
                                  		annotation.setTitle_(title)
                                  		annotation.setSubtitle_(str(subtitle))
                                  		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 main():
                                  	global all_points, route_points
                                  	
                                  	#----- Main process -----
                                  	console.clear()
                                  	
                                  	# Hide script
                                  	back = MapView(frame=(0, 0, 540, 620))
                                  	back.background_color='white'
                                  	back.name = 'Display route of localized photos'
                                  	back.present('full_screen', hide_title_bar=False)
                                  	
                                  	# ask from and to date of range
                                  	startDate, endDate = pickDateInterval()
                                  	# get assets in dates range
                                  	assets = getAssetsWithLocationInDateInterval(startDate, endDate)
                                  
                                  	# Loop on all photos
                                  	route_points = []
                                  	min_date = min(assets[0].creation_date,assets[1].creation_date).date()
                                  	max_date = max(assets[0].creation_date,assets[1].creation_date).date()
                                  	for p in assets:
                                  		p_date = p.creation_date.date()
                                  		if (len(assets) > 2) or (len(assets) == 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 reduced photo to win memory 
                                  				img = p.get_ui_image(size=(map_pin_size,map_pin_size), crop=True)
                                  				if img !=None:
                                  					wh = img.size
                                  					# normal photo 
                                  					h = map_pin_size
                                  					w = int(h * wh[0] / wh[1])
                                  					with ui.ImageContext(w,h) as ctx:
                                  						img.draw(0,0,w,h)
                                  						img = ctx.get_image()				
                                  
                                  					# store latitude, longitude and taken date
                                  					route_points.append((lat,lon,p_date, img))
                                  				
                                  				
                                  	if len(route_points) < 2:
                                  		console.hud_alert('At least two localized photos neded','error')
                                  		back.close()
                                  		return
                                  	back.name = back.name + f' ({len(route_points)})'
                                  	
                                  	# 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 = []
                                  	idx = 0
                                  	for point in route_points:
                                  		back.add_pin(point[0],point[1],str(point[2]), str(idx))
                                  		idx += 1
                                  
                                  	# Display polygon line of sorted locations
                                  	back.addPolyLineToMap()
                                  			
                                  # Protect against import	
                                  if __name__ == '__main__':
                                  	main()
                                  
                                  
                                  cvp 2 Replies Last reply Reply Quote 0
                                  • cvp
                                    cvp @bodobolero last edited by

                                    @bodobolero I'll try it, I was just busy to change code so the images are o more memorized in memory but in a db local file. With max 400 photos and 80 pixels, it still works. Anyway, the photos in the map are still locally memorized.

                                    1 Reply Last reply Reply Quote 0
                                    • cvp
                                      cvp @bodobolero last edited by cvp

                                      @bodobolero with your modification and my new version with a local de, and a thumb size of 40, I can show 1400 photos on the same screen, before zooming

                                      alt text

                                      bodobolero 1 Reply Last reply Reply Quote 0
                                      • bodobolero
                                        bodobolero @cvp last edited by

                                        @cvp Great timework - I like it very much now!

                                        Did you update the gist?
                                        Here is my latest version https://gist.github.com/Bodobolero/f6dd134f3a349d69ce6f7eb68513d82d

                                        cvp 2 Replies Last reply Reply Quote 0
                                        • cvp
                                          cvp @bodobolero last edited by

                                          @bodobolero I'll update gist, I have tried same number of photos with old version and your "crop" modified, still crash

                                          1 Reply Last reply Reply Quote 0
                                          • cvp
                                            cvp @bodobolero last edited by

                                            @bodobolero with local db

                                            bodobolero 1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors