Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

326 строки
11 KiB

  1. import os
  2. import argparse
  3. from wand.image import Image
  4. import lxml.etree as ET
  5. import copy
  6. import math
  7. from pyproj import CRS
  8. from pyproj.aoi import AreaOfInterest
  9. from pyproj.database import query_utm_crs_info
  10. from pyproj import Transformer
  11. arg_parser = argparse.ArgumentParser(description='Place drone FLIR-tiles into a SVG in order to edit them in Inkscape')
  12. arg_parser.add_argument('Input',
  13. metavar='input_directory',
  14. type=str,
  15. help='Path where the FLIR tiles are')
  16. arg_parser.add_argument(
  17. '--base_rotation', action='store', default=115,
  18. help="Base orientation of drone in degrees (0-360) Defaults to 115",
  19. type=int, dest='base_rotation'
  20. )
  21. arg_parser.add_argument(
  22. '--rotation_corr_right', action='store', default=0,
  23. help="correction for tiles where drone flies into right direction",
  24. type=int, dest='rotation_corr_right'
  25. )
  26. arg_parser.add_argument(
  27. '--rotation_corr_left', action='store', default=0,
  28. help="correction for tiles where drone flies into left direction",
  29. type=int, dest='rotation_corr_left'
  30. )
  31. arg_parser.add_argument(
  32. '--scale', action='store', default=15,
  33. help="Scaling (higher number leads to bigger canvas and less dense tiles) (defaults to 15)",
  34. type=int, dest='scale'
  35. )
  36. arg_parser.add_argument(
  37. '--direction', action='store', default='both',
  38. help="left, right, both (both is default)",
  39. type=str, dest='direction'
  40. )
  41. args = arg_parser.parse_args()
  42. dirname = os.path.dirname(__file__)
  43. working_dir = args.Input
  44. OUTPUT_PATH = os.path.join(working_dir,'map.svg')
  45. filename = os.path.join(dirname, 'canvas.svg')
  46. tree = ET.parse(filename)
  47. root = tree.getroot()
  48. d = root.nsmap
  49. main_layer = root.xpath('//*[@id="tiles"]', namespaces={'n': "http://www.w3.org/2000/svg"})[0]
  50. tile_rows = root.xpath('//*[@id="tile_rows"]', namespaces={'n': "http://www.w3.org/2000/svg"})[0]
  51. def deg_coordinates_to_decimal(coordStr):
  52. coordArr = value.split(', ')
  53. calculatedCoordArray = []
  54. for calculation in coordArr:
  55. calculationArr = calculation.split('/')
  56. calculatedCoordArray.append(int(calculationArr[0]) / int(calculationArr[1]))
  57. degrees = calculatedCoordArray[0]
  58. minutes = calculatedCoordArray[1]
  59. seconds = calculatedCoordArray[2]
  60. return (degrees + (minutes * 1/60) + (seconds * 1/60 * 1/60))
  61. # finding the boundaries of the whole canvas
  62. latsArr = []
  63. lonsArr = []
  64. for root_path, directories, file in os.walk(os.path.join(dirname, working_dir)):
  65. for file in file:
  66. if(file.endswith(".jpg")):
  67. # print(os.path.join(root_path, file))
  68. full_filepath = os.path.join(root_path, file)
  69. with Image(filename=full_filepath) as image:
  70. # print(image.width)
  71. # print(image.height)
  72. for key, value in image.metadata.items():
  73. if key == 'exif:GPSLatitude':
  74. lat = deg_coordinates_to_decimal(value) # lat -> Y vertical
  75. latsArr.append(lat)
  76. # print("{}: {}".format(key, value))
  77. # print('lat '+ str(lat))
  78. if key == 'exif:GPSLongitude':
  79. lon = deg_coordinates_to_decimal(value) # lon -> X horizontal
  80. lonsArr.append(lon)
  81. # print("{}: {}".format(key, value))
  82. # print('lon '+ str(lon))
  83. minLat = min(latsArr)
  84. minLon = min(lonsArr)
  85. maxLat = max(latsArr)
  86. maxLon = max(lonsArr)
  87. midLon = (minLon + maxLon) /2
  88. midLat = (minLat + maxLat) /2
  89. # find CRS system
  90. utm_crs_list = query_utm_crs_info(
  91. datum_name="WGS 84",
  92. area_of_interest=AreaOfInterest(
  93. west_lon_degree=minLon,
  94. south_lat_degree=minLat,
  95. east_lon_degree=maxLon,
  96. north_lat_degree=maxLat,
  97. ),
  98. )
  99. utm_crs = CRS.from_epsg(utm_crs_list[0].code)
  100. transformer = Transformer.from_crs("EPSG:4326", utm_crs, always_xy=True)
  101. min_transformed_lon, min_transformed_lat = transformer.transform(minLon, minLat)
  102. max_transformed_lon, max_transformed_lat = transformer.transform(maxLon, maxLat)
  103. width = max_transformed_lon - min_transformed_lon
  104. height = max_transformed_lat - min_transformed_lat
  105. # def latlngToGlobalXY(lat, lng):
  106. # earth_radius = 6371
  107. # # Calculates x based on cos of average of the latitudes
  108. # x = earth_radius * lng * math.cos((minLat + maxLat)/2)
  109. # # Calculates y based on latitude
  110. # y = earth_radius * lat
  111. # return {x: x, y: y}
  112. # def latlngToScreenXY(lat, lng):
  113. # topLeft_corner = latlngToGlobalXY(minLat, minLon)
  114. # bottomRight_corner = latlngToGlobalXY(maxLat, maxLon)
  115. # # Calculate global X and Y for projection point
  116. # pos = latlngToGlobalXY(lat, lng)
  117. # # Calculate the percentage of Global X position in relation to total global width
  118. # pos.perX = ((pos.x - topLeft_corner.x) / (bottomRight_corner.x - topLeft_corner.x))
  119. # # Calculate the percentage of Global Y position in relation to total global height
  120. # pos.perY = ((pos.y - topLeft_corner.y) / (bottomRight_corner.y - topLeft_corner.y))
  121. # # Returns the screen position based on reference points
  122. # return {
  123. # x: p0.scrX + (p1.scrX - p0.scrX)*pos.perX,
  124. # y: p0.scrY + (p1.scrY - p0.scrY)*pos.perY
  125. # }
  126. # placing the images into the svg
  127. # image_rotation_left = rotation #32
  128. # image_rotation_right = rotation + 180 #192
  129. for root_path, directories, file in os.walk(os.path.join(dirname, working_dir)):
  130. for file in file:
  131. if(file.endswith(".jpg")):
  132. # print(os.path.join(root_path, file))
  133. full_filepath = os.path.join(root_path, file)
  134. with Image(filename=full_filepath) as image:
  135. # print(image.width)
  136. # print(image.height)
  137. for key, value in image.metadata.items():
  138. # print("{}: {}".format(key, value))
  139. if key == 'exif:GPSLatitude':
  140. lat = deg_coordinates_to_decimal(value)
  141. lat_offset = lat - minLat
  142. if key == 'exif:GPSLongitude':
  143. lon = deg_coordinates_to_decimal(value)
  144. lon_offset = lon - minLon
  145. if key == 'exif:GPSImgDirection':
  146. direction = value.split('/')
  147. rotation = ( int(direction[0]) / int(direction[1]) ) / 2 + args.base_rotation
  148. # print('rotation',rotation)
  149. transformed_lon, transformed_lat = transformer.transform(lon, lat)
  150. lon_offset = transformed_lon - min_transformed_lon
  151. lat_offset = transformed_lat - min_transformed_lat
  152. # print(transformed_lon, min_transformed_lon, transformed_lat, min_transformed_lat)
  153. # print('lon_offset, lat_offset', lon_offset, lat_offset)
  154. g_pos_el_attributes = {
  155. 'transform': "translate({}, {})".format(format(lon_offset*args.scale, '.20f'), format(lat_offset*args.scale*-1, '.20f')),
  156. 'data-lat-offset': format(lat_offset, '.20f'),
  157. 'data-lon-offset': format(lon_offset, '.20f'),
  158. 'class': 'tile',
  159. 'id': 'tile_{}'.format(file.split('.')[0]),
  160. }
  161. g_pos_el = ET.SubElement(main_layer, 'g', attrib=g_pos_el_attributes)
  162. g_offset_corr_el_attributes = {
  163. 'transform': "translate({}, {})".format(-image.width/2, -image.height/2),
  164. 'class': 'tile-offset-corr',
  165. }
  166. g_offset_corr_el = ET.SubElement(g_pos_el, 'g', attrib=g_offset_corr_el_attributes)
  167. g_rot_el_attributes = {
  168. 'class': 'tile-rotate',
  169. 'data-image-rotation': str(rotation),
  170. 'data-image-dimensions': str(image.width) + ' ' + str(image.height),
  171. 'transform': 'rotate({} {} {})'.format(str(rotation), str(image.width/2), str(image.height/2))
  172. # 'transform': 'rotate({} {} {})'.format(str(rotation), 0,0)
  173. }
  174. g_rot_el = ET.SubElement(g_offset_corr_el, 'g', attrib=g_rot_el_attributes)
  175. xlinkns ="http://www.w3.org/1999/xlink"
  176. image_el = ET.SubElement(g_rot_el, 'image', {
  177. "class": 'thermal_image',
  178. "{%s}href" % xlinkns: file,
  179. "width": str(image.width),
  180. "height": str(image.height),
  181. 'data-lat': format(lat, '.20f'),
  182. 'data-lon': format(lon, '.20f'),
  183. })
  184. # sort elements
  185. def getkey(elem):
  186. # Used for sorting elements by @LIN.
  187. # returns a tuple of ints from the exploded @LIN value
  188. # '1.0' -> (1,0)
  189. # '1.0.1' -> (1,0,1)
  190. return float(elem.get('id').split('_')[2])
  191. main_layer[:] = sorted(main_layer, key=getkey)
  192. # find rows
  193. # left/right is actually left/right or right/left
  194. last_direction = 'right'
  195. for index, el in enumerate(main_layer):
  196. if(el.getprevious() is not None):
  197. prev_lon_offset = el.getprevious().attrib['data-lon-offset']
  198. lon_offset = el.attrib['data-lon-offset']
  199. if (prev_lon_offset > lon_offset or (prev_lon_offset == lon_offset and last_direction == 'left')):
  200. # print('left {} -> {}'.format(prev_lon_offset, lon_offset))
  201. el.attrib['data-direction'] = 'left'
  202. if (args.rotation_corr_left):
  203. rot_el = el[0][0]
  204. corrected_rotation = float(rot_el.attrib['data-image-rotation']) + args.rotation_corr_left
  205. rot_el.attrib['data-image-rotation'] = str(corrected_rotation)
  206. image = rot_el[0]
  207. rot_el.attrib['transform'] = 'rotate({} {} {})'.format(str(corrected_rotation), str(int(image.attrib['width'])/2), str(int(image.attrib['height'])/2))
  208. else:
  209. # print('right {} -> {}'.format(prev_lon_offset, lon_offset))
  210. el.attrib['data-direction'] = 'right'
  211. if (args.rotation_corr_right):
  212. rot_el = el[0][0]
  213. corrected_rotation = float(rot_el.attrib['data-image-rotation']) + args.rotation_corr_right
  214. rot_el.attrib['data-image-rotation'] = str(corrected_rotation)
  215. image = rot_el[0]
  216. rot_el.attrib['transform'] = 'rotate({} {} {})'.format(str(corrected_rotation), str(int(image.attrib['width'])/2), str(int(image.attrib['height'])/2))
  217. # merge tiles into groups
  218. # Start new line
  219. if (args.direction == 'both' or args.direction == el.attrib['data-direction']):
  220. if (index == 1 or last_direction is not el.attrib['data-direction']):
  221. current_row = ET.SubElement(tile_rows, 'g', attrib={ 'class': 'tile-row' })
  222. copyElem = copy.deepcopy(el)
  223. current_row.insert(0, copyElem)
  224. last_direction = el.attrib['data-direction']
  225. # remove temporary group
  226. root.remove(main_layer)
  227. #
  228. # resize canvas to tiles and add some padding
  229. # print(width, height, args.scale)
  230. scaled_width = width * args.scale
  231. scaled_height = height * args.scale
  232. padding = 500
  233. canvas_width = str(scaled_width + padding*2)
  234. canvas_height = str(scaled_height + padding*2)
  235. viewbox_x = str(padding * -1)
  236. viewbox_y = str((scaled_height + padding) * -1)
  237. viewbox_width = canvas_width
  238. viewbox_height = canvas_height
  239. root.attrib['width'] = canvas_width
  240. root.attrib['height'] = canvas_height
  241. root.attrib['viewBox'] = "{} {} {} {}".format(viewbox_x, viewbox_y, viewbox_width, viewbox_height)
  242. # Finally save the svg
  243. with open(OUTPUT_PATH, 'wb') as f:
  244. tree.write(f, encoding='utf-8')
  245. print('Saved SVG to {}'.format(OUTPUT_PATH))