|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- 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(
- '--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="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="correction for tiles where drone flies into left direction",
- type=int, dest='rotation_corr_left'
- )
-
- 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'
- )
-
-
-
- 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 = {
- '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']
- if (prev_lon_offset > lon_offset or (prev_lon_offset == lon_offset and last_direction == 'left')):
- # print('left {} -> {}'.format(prev_lon_offset, lon_offset))
- el.attrib['data-direction'] = 'left'
- 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))
- else:
- # print('right {} -> {}'.format(prev_lon_offset, lon_offset))
- el.attrib['data-direction'] = 'right'
- 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']):
- if (index == 1 or last_direction is not el.attrib['data-direction']):
- current_row = ET.SubElement(tile_rows, 'g', attrib={ 'class': 'tile-row' })
- copyElem = copy.deepcopy(el)
- 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))
-
-
|