Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

gis-svg-stitcher.py 9.3 KiB

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