Guest User

rome.py

a guest
Dec 2nd, 2018
1,027
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.90 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. #
  3. # Generates a gif image comparing Montreal metro map to its real geography.
  4. # Based on Reddit post "Berlin Subway Map compared to it's real geography"
  5. # https://www.reddit.com/r/dataisbeautiful/comments/6baefh/berlin_subway_map_compared_to_its_real_geography/
  6. #
  7. # Project at https://github.com/will8211/montreal
  8. #
  9. # Shared at https://www.reddit.com/r/dataisbeautiful/comments/6c8ukp/montreal_metro_map_distances_vs_geographic/
  10. # and http://imgur.com/a/TuRT8
  11. #
  12. # Depends on pycairo and imageio modules
  13. # Also depends on gifsicle
  14. # Font ('Falling Sky') available at http://www.fontspace.com/kineticplasma-fonts/falling-sky
  15. #
  16. # ~WM
  17.  
  18. import glob
  19. import os
  20. import subprocess as sp
  21.  
  22. import cairo
  23. import imageio as io
  24.  
  25. FRAME_COUNT = 100
  26. FRAME_DURATION = 0.02
  27.  
  28. # Station coordinates (start_x, start_y, node_type, end_x, end_y)
  29. # Node types: 0 -> Regular station (small dot)
  30. #             1 -> Transfer station (big dot)
  31. #             2 -> Bend in the tracks, no station
  32. #            -1 -> Bend only in geographical map
  33.  
  34. metro_a = ((200, 240, 1, 118, 293),
  35. (209, 249, -1, 136, 308),
  36. (219, 259, -1, 160, 312),
  37. (228, 268, 0, 166, 321),
  38. (237, 277, -1, 171, 330),
  39. (247, 287, -1, 182, 336),
  40. (256, 296, 0, 194, 334),
  41. (302, 296, 0, 230, 313),
  42. (346, 296, 0, 259, 285),
  43. (368, 296, -1, 271, 285),
  44. (391, 296, 0, 305, 273),
  45. (436, 296, 0, 339, 262),
  46. (474, 296, 0, 384, 254),
  47. (493, 315, 0, 412, 289),
  48. (504, 326, -1, 427, 296),
  49. (515, 337, 0, 438, 309),
  50. (536, 358, 0, 470, 313),
  51. (559, 381, 1, 485, 323),
  52. (582, 404, 0, 503, 362),
  53. (606, 428, 0, 513, 387),
  54. (634, 456, 1, 525, 412),
  55. (658, 480, 0, 550, 439),
  56. (684, 506, 0, 570, 462),
  57. (708, 530, 0, 589, 480),
  58. (732, 554, 0, 621, 510),
  59. (756, 578, 0, 648, 529),
  60. (768, 590, -1, 676, 532),
  61. (780, 602, 0, 700, 541),
  62. (804, 626, 0, 723, 556),
  63. (828, 650, 0, 741, 569),
  64. (852, 674, 0, 766, 587),
  65. (876, 694, 0, 787, 606),
  66. (900, 718, 0, 814, 630),
  67. (912, 730, -1, 831, 647),
  68. (924, 742, 1, 855, 658))
  69.  
  70. metro_c = ((634, 456, 1, 525, 412),
  71. (681, 456, 0, 563, 402),
  72. (729, 456, 0, 603, 394),
  73. (777, 456, 0, 656, 400),
  74. (792, 456, -1, 679, 400),
  75. (809, 456, -1, 696, 388),
  76. (824, 456, 0, 710, 388),
  77. (871, 456, 0, 750, 408),
  78. (882, 467, -1, 763, 417),
  79. (893, 478, 0, 768, 435),
  80. (898, 483, -1, 777, 473),
  81. (904, 489, -1, 787, 484),
  82. (910, 495, -1, 804, 487),
  83. (915, 500, 0, 824, 494),
  84. (937, 522, 0, 869, 514),
  85. (944, 529, -1, 906, 519),
  86. (952, 537, -1, 922, 519),
  87. (959, 544, 0, 952, 532),
  88. (970, 555, -1, 967, 538),
  89. (981, 566, 0, 988, 538),
  90. (1003, 588, 0, 1023, 533),
  91. (1025, 610, 0, 1066, 535),
  92. (1047, 632, 0, 1110, 541),
  93. (1054, 639, -1, 1124, 542),
  94. (1062, 647, -1, 1155, 532),
  95. (1069, 654, 0, 1172, 530),
  96. (1091, 676, 0, 1200, 530),
  97. (1098, 683, -1, 1251, 540),
  98. (1106, 691, -1, 1275, 531),
  99. (1113, 698, 0, 1287, 527),
  100. (1135, 720, 0, 1327, 526),
  101. (1146, 731, -1, 1354, 523),
  102. (1157, 742, 1, 1375, 524))
  103.  
  104. metro_b_one = ((577, 698, 1, 402, 747),
  105. (534, 698, 0, 345, 733),
  106. (512, 698, -1, 328, 725),
  107. (491, 698, 0, 317, 710),
  108. (478, 685, -1, 314, 692),
  109. (466, 673, 0, 327, 675),
  110. (466, 654, -1, 376, 633),
  111. (466, 636, 0, 380, 621),
  112. (466, 597, 0, 391, 584),
  113. (466, 556, 0, 411, 524),
  114. (466, 536, -1, 416, 502),
  115. (466, 517, 0, 409, 477),
  116. (466, 503, -1, 405, 460),
  117. (466, 489, -1, 414, 441),
  118. (466, 475, 0, 433, 423),
  119. (482, 459, -1, 442, 406),
  120. (498, 443, 0, 448, 376),
  121. (531, 410, 0, 460, 353),
  122. (559, 381, 1, 485, 323),
  123. (584, 357, 0, 509, 289),
  124. (597, 343, -1, 525, 279),
  125. (611, 330, 0, 543, 277),
  126. (628, 313, -1, 565, 255),
  127. (645, 296, 0, 575, 251),  ## BIVIO
  128. (660, 296, -1, 595, 258),
  129. (675, 296, -1, 600, 265),
  130. (688, 296, 0, 625, 272),
  131. (699, 296, -1, 640, 258),
  132. (710, 296, -1, 650, 243),
  133. (732, 296, 0, 672, 236),
  134. (770, 296, 0, 702, 241),
  135. (784, 296, -1, 718, 246),
  136. (808, 296, 0, 736, 245),
  137. (820, 284, -1, 745, 238),
  138. (832, 272, 0, 753, 226),
  139. (859, 245, 0, 774, 191),
  140. (882, 222, 1, 795, 179))
  141.  
  142. metro_b_two = ((645, 296, 0, 575, 251),
  143. (645, 274, -1, 579, 240),
  144. (645, 253, 0, 576, 221),
  145. (645, 231, -1, 556, 201),
  146. (645, 210, 0, 554, 188),
  147. (657, 198, -1, 554, 159),
  148. (669, 185, 0, 568, 142),
  149. (675, 179, -1, 595, 117),
  150. (681, 173, -1, 609, 80),
  151. (688, 167, -1, 603, 67),
  152. (694, 161, 1, 603, 55))
  153.  
  154.                
  155. metro_lines = ((metro_a, 0.921, 0.510, 0.122),
  156.                (metro_c, 0.001, 0.529, 0.322),
  157.                (metro_b_one, 0.001, 0.443, 0.737),
  158.                (metro_b_two, 0.001, 0.443, 0.737))
  159.  
  160. frames_list = []
  161. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1500, 900)
  162. ctx = cairo.Context(surface)
  163.  
  164. #############
  165. # Functions #
  166. #############
  167.  
  168. def smoothstep(a, b, value):
  169.     '''
  170.    Based on javascript code found at <https://github.com/gre/smoothstep>
  171.    Takes a start time (a), and end time (b), and the current time (value),
  172.    and return what proportion (between 0 and 1.0) of the distance it should
  173.    currently be at, using a smooth s-curve for displacement. Used by the
  174.    transform() function.
  175.    
  176.    NOT USED: Swtiched to smootherstep()
  177.    '''
  178.     x = max(0, min(1, (value - a) / (b - a)))
  179.     return x**2 * (3 - 2*x)
  180.  
  181. def smootherstep(a, b, value):
  182.     '''
  183.    A smoother version of smoothstep()
  184.    https://en.wikipedia.org/wiki/Smoothstep#Variations
  185.    '''
  186.     x = max(0, min(1, (value - a) / (b - a)))
  187.     return x**3 * (x * (x*6 - 15) + 10)
  188.  
  189. def transform(start, end, current_frame):
  190.     '''
  191.    Takes the starting and ending states of a value (an x or y coordinate or
  192.    a size) and determines what that value should be at the current frame.
  193.    '''
  194.     #multiplier = current_frame / (FRAME_COUNT - 1) # Flat velocity
  195.     multiplier = smootherstep(0, FRAME_COUNT, current_frame)
  196.     return start + multiplier * (end - start)
  197.  
  198.  
  199. ###################################
  200. # Draw metro lines for each frame #
  201. ###################################
  202.  
  203. for frame in range(FRAME_COUNT):
  204.  
  205.     # Background
  206.  
  207.     ctx.set_source_rgb(1.000, 1.000, 1.000) # white
  208.     ctx.rectangle(0, 0, 1500, 900)
  209.     ctx.fill()
  210.  
  211.     for metro_line, r, g, b in metro_lines:
  212.  
  213.         # Draw tracks
  214.  
  215.         ctx.set_line_cap(cairo.LINE_CAP_ROUND)
  216.         ctx.set_line_join(cairo.LINE_JOIN_ROUND)
  217.         ctx.set_source_rgb(r, g, b)
  218.        
  219.         line_width = transform(15, 4, frame)
  220.         ctx.set_line_width(line_width)
  221.  
  222.         for i, coord in enumerate(metro_line):
  223.  
  224.             x = transform(coord[0], coord[3], frame)
  225.             y = transform(coord[1], coord[4], frame)
  226.            
  227.             if i == 0:
  228.                 ctx.move_to(x, y)
  229.            
  230.             # for frame 0, skip actual stations, following only track bends
  231.             # this makes the lines straighter
  232.             if frame > 0 or coord[2] > 0:
  233.                 ctx.line_to(x, y)
  234.  
  235.         ctx.stroke()
  236.  
  237.         # Draw logos
  238.  
  239.         logoA = cairo.ImageSurface.create_from_png('LOGO/A.png')
  240.  
  241.         a = transform(944, 875, frame)  ## X +20
  242.         b = transform (722, 638, frame) ## Y -20
  243.            
  244.         ctx.set_source_surface(logoA, a, b)
  245.  
  246.         ctx.paint()
  247.  
  248.         logoB = cairo.ImageSurface.create_from_png('LOGO/B.png')
  249.  
  250.         a = transform(902, 815, frame)
  251.         b = transform (202, 159, frame)
  252.            
  253.         ctx.set_source_surface(logoB, a, b)
  254.  
  255.         ctx.paint()
  256.  
  257.         logoC = cairo.ImageSurface.create_from_png('LOGO/C.png')
  258.  
  259.         a = transform(1177, 1395, frame)
  260.         b = transform (722, 504, frame)
  261.            
  262.         ctx.set_source_surface(logoC, a, b)
  263.  
  264.         ctx.paint()
  265.  
  266.         # Draw stations
  267.  
  268.         ctx.set_source_rgb(1.000, 1.000, 1.000)
  269.        
  270.         for coord in metro_line:
  271.  
  272.             x = transform(coord[0], coord[3], frame)
  273.             y = transform(coord[1], coord[4], frame)
  274.  
  275.             ctx.move_to(x, y)
  276.  
  277.             big_dot = transform(13, 3, frame)
  278.             small_dot = transform(9, 3, frame)
  279.        
  280.             if coord[2] == 0:
  281.                 ctx.set_line_width(small_dot)
  282.                 ctx.line_to(x, y)
  283.  
  284.             elif coord[2] == 1:
  285.                 ctx.set_line_width(big_dot)
  286.                 ctx.line_to(x, y)
  287.  
  288.             ctx.stroke()
  289.  
  290.     # Write frame to file
  291.  
  292.     frame_name = 'frame_%d.png' % frame
  293.     surface.write_to_png(frame_name)
  294.     frames_list.append(frame_name)
  295.  
  296.  
  297. ################
  298. # Generate gif #
  299. ################
  300.  
  301. images = []
  302. for filename in frames_list:
  303.     images.append(io.imread(filename))
  304.  
  305. io.imwrite('part_1.gif', images[0], duration = 1.00)
  306. io.mimwrite('part_2.gif', images, duration = FRAME_DURATION)
  307. io.imwrite('part_3.gif', images[-1], duration = 1.00)
  308.  
  309. # To transition back to the start
  310. #io.mimwrite('part_4.gif', reversed(images), duration = FRAME_DURATION)
  311.  
  312. for f in glob.glob('frame_*.png'):
  313.     os.remove(f)
  314.  
  315. result = sp.run(['gifsicle', '--colors', '256',
  316.                  'part_1.gif', 'part_2.gif', 'part_3.gif'], # , 'part_4.gif'],
  317.                  stdout=sp.PIPE)
  318.  
  319. with open('TEST.gif', 'wb') as f:
  320.     f.write(result.stdout)
  321.  
  322. for f in glob.glob('part_*.gif'):
  323.     os.remove(f)
  324.  
  325. sp.call(['xdg-open', 'TEST.gif'])
Add Comment
Please, Sign In to add comment