Last active
May 16, 2023 08:44
-
-
Save Bodobolero/a05319734d683d18f5cf22f0c027a818 to your computer and use it in GitHub Desktop.
Pyhonista is an iOS App to develop python scripts. This is a pythonista script that: creates a map from the Exif location metadata in your foto library for a time range. The map is saved as .png file in your scripts folder. In addition a .gpx file is created with the track points from the EXIF locations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import photos | |
import dialogs | |
import location | |
import canvas | |
import clipboard | |
from math import * | |
### If you like this script you can pay me a coffee here | |
### https://www.paypal.com/donate/?hosted_button_id=JBFJMV35LX5AY | |
# 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) ] | |
print("Number of assets with location in date interval:") | |
print(len(timed_assets)) | |
return timed_assets | |
# return an array of (lat,long) points for pictures' metadata (assets is a list of metadata) | |
def getCoordinates(assets): | |
return [(asset.location['latitude'],asset.location['longitude']) for asset in assets] | |
# return quadrupel min lat, max lat, min long, max long | |
def getBounds(coordinates): | |
latitudes = [x[0] for x in coordinates] | |
longitudes= [x[1] for x in coordinates] | |
return (min(latitudes), max(latitudes), min(longitudes), max(longitudes)) | |
# distance in km | |
def get_distance(point1, point2): | |
R = 6370 | |
lat1 = radians(point1[0]) #insert value | |
lon1 = radians(point1[1]) | |
lat2 = radians(point2[0]) | |
lon2 = radians(point2[1]) | |
dlon = lon2 - lon1 | |
dlat = lat2- lat1 | |
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 | |
c = 2 * atan2(sqrt(a), sqrt(1-a)) | |
distance = R * c | |
return distance | |
# width and height in meters | |
def computeWidthAndHeight(bounds): | |
height = get_distance([bounds[0],bounds[2]], [bounds[1], bounds[2]]) * 1000.0 | |
width = get_distance([bounds[0], bounds[2]], [bounds[0], bounds[3]]) * 1000.0 | |
return (width,height) | |
# compute x,y in canvas based on (latitude, longitude) of a point on the map | |
def translatePoint(img_width, img_height, width, height, lat_bottom_left, long_bottom_left, lat, long): | |
y_dist_in_m = get_distance([lat_bottom_left, long_bottom_left], [lat, long_bottom_left]) *1000.0 | |
x_dist_in_m = get_distance([lat_bottom_left, long_bottom_left],[lat_bottom_left, long] ) *1000.0 | |
print(f'tP x:{x_dist_in_m} y:{y_dist_in_m}') | |
maxdist= max(width,height) | |
if (width > height): | |
xcorr = 0.0 | |
ycorr = (width-height)/2.0 | |
else: | |
ycorr = 0.0 | |
xcorr = (height - width)/2.0 | |
return ((x_dist_in_m+xcorr) * img_width / maxdist, (y_dist_in_m+ycorr) * img_height / maxdist) | |
def createGPXFile(filename, title, assets): | |
xml = "<?xml version='1.0' encoding='UTF-8'?>" | |
header = '''\ | |
<gpx version="1.1" creator="https://www.bodobolero.com" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> | |
<metadata> | |
<name>{title}</name> | |
<author> | |
<link href="https://www.bodobolero.com"> | |
<text>Bodo Bolero</text> | |
<type>text/html</type> | |
</link> | |
</author> | |
</metadata> | |
<trk> | |
<name>{title}</name> | |
<trkseg>\ | |
'''.format(title=title, ordinal='second') | |
pointlist = [xml,header] | |
for asset in assets: | |
latitude = asset.location['latitude'] | |
longitude = asset.location['longitude'] | |
altitude = asset.location['altitude'] | |
if altitude == None: | |
altitude = 0.0 | |
# time format: 2023-05-09T14:10:42.542Z | |
formattedtime = asset.creation_date.isoformat() | |
point = '''\ | |
<trkpt lat="{lat}" lon="{lon}"><ele>{ele}</ele> | |
<time>{time}</time> | |
</trkpt>\ | |
'''.format(lat=latitude, lon=longitude, time=formattedtime, ele=altitude) | |
pointlist.append(point) | |
footer='''\ | |
</trkseg> | |
</trk> | |
</gpx>\ | |
''' | |
pointlist.append(footer) | |
text = ''.join(pointlist) | |
with open(filename, "w+") as text_file: | |
text_file.write(text) | |
def main(): | |
startDate, endDate = pickDateInterval() | |
assets = getAssetsWithLocationInDateInterval(startDate, endDate) | |
coordinates = getCoordinates(assets) | |
bounds = getBounds(coordinates) | |
print(bounds) | |
# compute center of image | |
latitude = (bounds[0]+bounds[1])/2 | |
longitude = (bounds[2]+bounds[3])/2 | |
# compute width and height of image | |
width, height = computeWidthAndHeight(bounds) | |
print(f'width: {width}m, height: {height}m') | |
maxdim = max(width,height) | |
img_width = 1024 | |
img_height = 1024 | |
print(f'Location: lat:{latitude} long: {longitude}') | |
print(f'Image size in pixels x: {img_width} y:{img_height}') | |
img = location.render_map_snapshot(latitude, longitude, map_type='hybrid', width=width, height=height, img_width=img_width, img_height=img_height, img_scale=1.0) | |
xw,yw = img.size | |
scale = img.scale | |
print(f'Image size x:{xw} y:{yw} scale:{scale}') | |
clipboard.set_image(img) | |
canvas.clear() | |
canvas.set_size(img_width,img_height) | |
canvas.draw_clipboard(0,0,img_width,img_height) | |
x,y = translatePoint(img_width, img_height, width, height, bounds[0], bounds[2], coordinates[0][0], coordinates[0][1]) | |
print(f'first Point x:{x} y:{y}') | |
canvas.set_stroke_color(255,255,0) | |
canvas.move_to(x,y) | |
for i in range(len(coordinates)-1): | |
index = i+1 | |
x,y = translatePoint(img_width, img_height, width, height, bounds[0], bounds[2], coordinates[index][0], coordinates[index][1]) | |
print(f'{index} Point x:{x} y:{y}') | |
canvas.add_line(x,y) | |
canvas.close_path() | |
canvas.set_line_width(3) | |
canvas.draw_path() | |
filename = f'trip_from_{startDate}_to_{endDate}' | |
canvas.save_png(f'{filename}.png') | |
createGPXFile(f'{filename}.gpx', f'Trip from {startDate} to {endDate}', assets) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you like this script you can pay me a coffee here
https://www.paypal.com/donate/?hosted_button_id=JBFJMV35LX5AY