import os import argparse from wand.image import Image import lxml.etree as ET import copy import math from pyproj import CRS from pyproj.aoi import AreaOfInterest from pyproj.database import query_utm_crs_info from pyproj import Transformer arg_parser = argparse.ArgumentParser(description='Place drone FLIR-tiles into a SVG in order to edit them in Inkscape') arg_parser.add_argument('Input', metavar='input_directory', type=str, help='Path where the FLIR tiles are') arg_parser.add_argument( '--scale', action='store', default=15, help="Scaling (higher number leads to bigger canvas and less dense tiles) (defaults to 15)", type=int, dest='scale' ) arg_parser.add_argument( '--direction', action='store', default='both', help="left, right, both (both is default)", type=str, dest='direction' ) arg_parser.add_argument( '--base_rotation', action='store', default=115, help="Base orientation of drone in degrees (0-360) Defaults to 115", type=int, dest='base_rotation' ) arg_parser.add_argument( '--rotation_corr_right', action='store', default=0, help="rotation correction for tiles where drone flies into right direction", type=int, dest='rotation_corr_right' ) arg_parser.add_argument( '--rotation_corr_left', action='store', default=0, help="rotation correction for tiles where drone flies into left direction", type=int, dest='rotation_corr_left' ) arg_parser.add_argument( '--pos_corr_right', action='store', default="0x0", help="position correction for tiles where drone flies into right direction in pixels. eg. 10x80", type=str, dest='pos_corr_right' ) arg_parser.add_argument( '--pos_corr_left', action='store', default="0x0", help="position correction for tiles where drone flies into left direction in pixels. eg. 10x80", type=str, dest='pos_corr_left' ) args = arg_parser.parse_args() dirname = os.path.dirname(__file__) working_dir = args.Input OUTPUT_PATH = os.path.join(working_dir,'map.svg') filename = os.path.join(dirname, 'canvas.svg') tree = ET.parse(filename) root = tree.getroot() d = root.nsmap main_layer = root.xpath('//*[@id="tiles"]', namespaces={'n': "http://www.w3.org/2000/svg"})[0] tile_rows = root.xpath('//*[@id="tile_rows"]', namespaces={'n': "http://www.w3.org/2000/svg"})[0] def deg_coordinates_to_decimal(coordStr): coordArr = value.split(', ') calculatedCoordArray = [] for calculation in coordArr: calculationArr = calculation.split('/') calculatedCoordArray.append(int(calculationArr[0]) / int(calculationArr[1])) degrees = calculatedCoordArray[0] minutes = calculatedCoordArray[1] seconds = calculatedCoordArray[2] return (degrees + (minutes * 1/60) + (seconds * 1/60 * 1/60)) # finding the boundaries of the whole canvas latsArr = [] lonsArr = [] for root_path, directories, file in os.walk(os.path.join(dirname, working_dir)): for file in file: if(file.endswith(".jpg")): # print(os.path.join(root_path, file)) full_filepath = os.path.join(root_path, file) with Image(filename=full_filepath) as image: # print(image.width) # print(image.height) for key, value in image.metadata.items(): if key == 'exif:GPSLatitude': lat = deg_coordinates_to_decimal(value) # lat -> Y vertical latsArr.append(lat) # print("{}: {}".format(key, value)) # print('lat '+ str(lat)) if key == 'exif:GPSLongitude': lon = deg_coordinates_to_decimal(value) # lon -> X horizontal lonsArr.append(lon) # print("{}: {}".format(key, value)) # print('lon '+ str(lon)) minLat = min(latsArr) minLon = min(lonsArr) maxLat = max(latsArr) maxLon = max(lonsArr) midLon = (minLon + maxLon) /2 midLat = (minLat + maxLat) /2 # find CRS system utm_crs_list = query_utm_crs_info( datum_name="WGS 84", area_of_interest=AreaOfInterest( west_lon_degree=minLon, south_lat_degree=minLat, east_lon_degree=maxLon, north_lat_degree=maxLat, ), ) utm_crs = CRS.from_epsg(utm_crs_list[0].code) transformer = Transformer.from_crs("EPSG:4326", utm_crs, always_xy=True) min_transformed_lon, min_transformed_lat = transformer.transform(minLon, minLat) max_transformed_lon, max_transformed_lat = transformer.transform(maxLon, maxLat) width = max_transformed_lon - min_transformed_lon height = max_transformed_lat - min_transformed_lat # def latlngToGlobalXY(lat, lng): # earth_radius = 6371 # # Calculates x based on cos of average of the latitudes # x = earth_radius * lng * math.cos((minLat + maxLat)/2) # # Calculates y based on latitude # y = earth_radius * lat # return {x: x, y: y} # def latlngToScreenXY(lat, lng): # topLeft_corner = latlngToGlobalXY(minLat, minLon) # bottomRight_corner = latlngToGlobalXY(maxLat, maxLon) # # Calculate global X and Y for projection point # pos = latlngToGlobalXY(lat, lng) # # Calculate the percentage of Global X position in relation to total global width # pos.perX = ((pos.x - topLeft_corner.x) / (bottomRight_corner.x - topLeft_corner.x)) # # Calculate the percentage of Global Y position in relation to total global height # pos.perY = ((pos.y - topLeft_corner.y) / (bottomRight_corner.y - topLeft_corner.y)) # # Returns the screen position based on reference points # return { # x: p0.scrX + (p1.scrX - p0.scrX)*pos.perX, # y: p0.scrY + (p1.scrY - p0.scrY)*pos.perY # } # placing the images into the svg # image_rotation_left = rotation #32 # image_rotation_right = rotation + 180 #192 for root_path, directories, file in os.walk(os.path.join(dirname, working_dir)): for file in file: if(file.endswith(".jpg")): # print(os.path.join(root_path, file)) full_filepath = os.path.join(root_path, file) with Image(filename=full_filepath) as image: # print(image.width) # print(image.height) for key, value in image.metadata.items(): # print("{}: {}".format(key, value)) if key == 'exif:GPSLatitude': lat = deg_coordinates_to_decimal(value) lat_offset = lat - minLat if key == 'exif:GPSLongitude': lon = deg_coordinates_to_decimal(value) lon_offset = lon - minLon if key == 'exif:GPSImgDirection': direction = value.split('/') rotation = ( int(direction[0]) / int(direction[1]) ) / 2 + args.base_rotation # print('rotation',rotation) transformed_lon, transformed_lat = transformer.transform(lon, lat) lon_offset = transformed_lon - min_transformed_lon lat_offset = transformed_lat - min_transformed_lat # print(transformed_lon, min_transformed_lon, transformed_lat, min_transformed_lat) # print('lon_offset, lat_offset', lon_offset, lat_offset) g_pos_el_attributes = { 'transform': "translate({}, {})".format(format(lon_offset*args.scale, '.20f'), format(lat_offset*args.scale*-1, '.20f')), 'data-lat-offset': format(lat_offset, '.20f'), 'data-lon-offset': format(lon_offset, '.20f'), 'class': 'tile', 'id': 'tile_{}'.format(file.split('.')[0]), } g_pos_el = ET.SubElement(main_layer, 'g', attrib=g_pos_el_attributes) g_offset_corr_el_attributes = { 'data-offset-corr-x': "{}".format(-image.width/2), 'data-offset-corr-y': "{}".format(-image.height/2), 'transform': "translate({}, {})".format(-image.width/2, -image.height/2), 'class': 'tile-offset-corr', } g_offset_corr_el = ET.SubElement(g_pos_el, 'g', attrib=g_offset_corr_el_attributes) g_rot_el_attributes = { 'class': 'tile-rotate', 'data-image-rotation': str(rotation), 'data-image-dimensions': str(image.width) + ' ' + str(image.height), 'transform': 'rotate({} {} {})'.format(str(rotation), str(image.width/2), str(image.height/2)) # 'transform': 'rotate({} {} {})'.format(str(rotation), 0,0) } g_rot_el = ET.SubElement(g_offset_corr_el, 'g', attrib=g_rot_el_attributes) xlinkns ="http://www.w3.org/1999/xlink" image_el = ET.SubElement(g_rot_el, 'image', { "class": 'thermal_image', "{%s}href" % xlinkns: file, "width": str(image.width), "height": str(image.height), 'data-lat': format(lat, '.20f'), 'data-lon': format(lon, '.20f'), }) # sort elements def getkey(elem): # Used for sorting elements by @LIN. # returns a tuple of ints from the exploded @LIN value # '1.0' -> (1,0) # '1.0.1' -> (1,0,1) return float(elem.get('id').split('_')[2]) main_layer[:] = sorted(main_layer, key=getkey) # find rows # left/right is actually left/right or right/left last_direction = 'right' for index, el in enumerate(main_layer): if(el.getprevious() is not None): prev_lon_offset = el.getprevious().attrib['data-lon-offset'] lon_offset = el.attrib['data-lon-offset'] # determine direction direction = None # print(prev_lon_offset - lon_offset) if (prev_lon_offset == lon_offset): direction = last_direction # print('same lon!') elif (float(prev_lon_offset) > float(lon_offset)): direction = 'left' elif (float(prev_lon_offset) < float(lon_offset)): direction = 'right' if (direction == 'left'): # print('left {} | {} -> {}'.format(el.attrib['id'], prev_lon_offset, lon_offset)) el.attrib['data-direction'] = 'left' if (args.pos_corr_left): offset_corr_x, offset_corr_y = args.pos_corr_left.split('x') # print(offset_corr_x, offset_corr_y) pos_corr_el = el[0] corrected_pos_x = float(pos_corr_el.attrib['data-offset-corr-x']) + float(offset_corr_x) corrected_pos_y = float(pos_corr_el.attrib['data-offset-corr-y']) + float(offset_corr_y) pos_corr_el.attrib['transform'] = "translate({}px, {}px)".format(corrected_pos_x, corrected_pos_y) if (args.rotation_corr_left): rot_el = el[0][0] corrected_rotation = float(rot_el.attrib['data-image-rotation']) + args.rotation_corr_left rot_el.attrib['data-image-rotation'] = str(corrected_rotation) image = rot_el[0] rot_el.attrib['transform'] = 'rotate({} {} {})'.format(str(corrected_rotation), str(int(image.attrib['width'])/2), str(int(image.attrib['height'])/2)) if (direction == 'right'): # print('right {} | {} -> {}'.format(el.attrib['id'], prev_lon_offset, lon_offset)) el.attrib['data-direction'] = 'right' if (args.pos_corr_right): offset_corr_x, offset_corr_y = args.pos_corr_right.split('x') # print(offset_corr_x, offset_corr_y) pos_corr_el = el[0] corrected_pos_x = float(pos_corr_el.attrib['data-offset-corr-x']) + float(offset_corr_x) corrected_pos_y = float(pos_corr_el.attrib['data-offset-corr-y']) + float(offset_corr_y) pos_corr_el.attrib['transform'] = "translate({}px, {}px)".format(corrected_pos_x, corrected_pos_y) if (args.rotation_corr_right): rot_el = el[0][0] corrected_rotation = float(rot_el.attrib['data-image-rotation']) + args.rotation_corr_right rot_el.attrib['data-image-rotation'] = str(corrected_rotation) image = rot_el[0] rot_el.attrib['transform'] = 'rotate({} {} {})'.format(str(corrected_rotation), str(int(image.attrib['width'])/2), str(int(image.attrib['height'])/2)) # merge tiles into groups # Start new line if (args.direction == 'both' or args.direction == el.attrib['data-direction']): copyElem = copy.deepcopy(el) # if direction changes if (index == 1 or last_direction != el.attrib['data-direction']): # print('new row!') current_row = ET.SubElement(tile_rows, 'g', attrib={ 'class': 'tile-row' }) # print (el.attrib['id'], el.attrib['data-direction']) current_row.insert(0, copyElem) last_direction = el.attrib['data-direction'] # remove temporary group root.remove(main_layer) # # resize canvas to tiles and add some padding # print(width, height, args.scale) scaled_width = width * args.scale scaled_height = height * args.scale padding = 500 canvas_width = str(scaled_width + padding*2) canvas_height = str(scaled_height + padding*2) viewbox_x = str(padding * -1) viewbox_y = str((scaled_height + padding) * -1) viewbox_width = canvas_width viewbox_height = canvas_height root.attrib['width'] = canvas_width root.attrib['height'] = canvas_height root.attrib['viewBox'] = "{} {} {} {}".format(viewbox_x, viewbox_y, viewbox_width, viewbox_height) # Finally save the svg with open(OUTPUT_PATH, 'wb') as f: tree.write(f, encoding='utf-8') print('Saved SVG to {}'.format(OUTPUT_PATH))