Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- #
- # Generates a gif image comparing Montreal metro map to its real geography.
- # Based on Reddit post "Berlin Subway Map compared to it's real geography"
- # https://www.reddit.com/r/dataisbeautiful/comments/6baefh/berlin_subway_map_compared_to_its_real_geography/
- #
- # Project at https://github.com/will8211/montreal
- #
- # Shared at https://www.reddit.com/r/dataisbeautiful/comments/6c8ukp/montreal_metro_map_distances_vs_geographic/
- # and http://imgur.com/a/TuRT8
- #
- # Depends on pycairo and imageio modules
- # Also depends on gifsicle
- # Font ('Falling Sky') available at http://www.fontspace.com/kineticplasma-fonts/falling-sky
- #
- # ~WM
- import glob
- import os
- import subprocess as sp
- import cairo
- import imageio as io
- FRAME_COUNT = 100
- FRAME_DURATION = 0.02
- # Station coordinates (start_x, start_y, node_type, end_x, end_y)
- # Node types: 0 -> Regular station (small dot)
- # 1 -> Transfer station (big dot)
- # 2 -> Bend in the tracks, no station
- # -1 -> Bend only in geographical map
- metro_a = ((200, 240, 1, 118, 293),
- (209, 249, -1, 136, 308),
- (219, 259, -1, 160, 312),
- (228, 268, 0, 166, 321),
- (237, 277, -1, 171, 330),
- (247, 287, -1, 182, 336),
- (256, 296, 0, 194, 334),
- (302, 296, 0, 230, 313),
- (346, 296, 0, 259, 285),
- (368, 296, -1, 271, 285),
- (391, 296, 0, 305, 273),
- (436, 296, 0, 339, 262),
- (474, 296, 0, 384, 254),
- (493, 315, 0, 412, 289),
- (504, 326, -1, 427, 296),
- (515, 337, 0, 438, 309),
- (536, 358, 0, 470, 313),
- (559, 381, 1, 485, 323),
- (582, 404, 0, 503, 362),
- (606, 428, 0, 513, 387),
- (634, 456, 1, 525, 412),
- (658, 480, 0, 550, 439),
- (684, 506, 0, 570, 462),
- (708, 530, 0, 589, 480),
- (732, 554, 0, 621, 510),
- (756, 578, 0, 648, 529),
- (768, 590, -1, 676, 532),
- (780, 602, 0, 700, 541),
- (804, 626, 0, 723, 556),
- (828, 650, 0, 741, 569),
- (852, 674, 0, 766, 587),
- (876, 694, 0, 787, 606),
- (900, 718, 0, 814, 630),
- (912, 730, -1, 831, 647),
- (924, 742, 1, 855, 658))
- metro_c = ((634, 456, 1, 525, 412),
- (681, 456, 0, 563, 402),
- (729, 456, 0, 603, 394),
- (777, 456, 0, 656, 400),
- (792, 456, -1, 679, 400),
- (809, 456, -1, 696, 388),
- (824, 456, 0, 710, 388),
- (871, 456, 0, 750, 408),
- (882, 467, -1, 763, 417),
- (893, 478, 0, 768, 435),
- (898, 483, -1, 777, 473),
- (904, 489, -1, 787, 484),
- (910, 495, -1, 804, 487),
- (915, 500, 0, 824, 494),
- (937, 522, 0, 869, 514),
- (944, 529, -1, 906, 519),
- (952, 537, -1, 922, 519),
- (959, 544, 0, 952, 532),
- (970, 555, -1, 967, 538),
- (981, 566, 0, 988, 538),
- (1003, 588, 0, 1023, 533),
- (1025, 610, 0, 1066, 535),
- (1047, 632, 0, 1110, 541),
- (1054, 639, -1, 1124, 542),
- (1062, 647, -1, 1155, 532),
- (1069, 654, 0, 1172, 530),
- (1091, 676, 0, 1200, 530),
- (1098, 683, -1, 1251, 540),
- (1106, 691, -1, 1275, 531),
- (1113, 698, 0, 1287, 527),
- (1135, 720, 0, 1327, 526),
- (1146, 731, -1, 1354, 523),
- (1157, 742, 1, 1375, 524))
- metro_b_one = ((577, 698, 1, 402, 747),
- (534, 698, 0, 345, 733),
- (512, 698, -1, 328, 725),
- (491, 698, 0, 317, 710),
- (478, 685, -1, 314, 692),
- (466, 673, 0, 327, 675),
- (466, 654, -1, 376, 633),
- (466, 636, 0, 380, 621),
- (466, 597, 0, 391, 584),
- (466, 556, 0, 411, 524),
- (466, 536, -1, 416, 502),
- (466, 517, 0, 409, 477),
- (466, 503, -1, 405, 460),
- (466, 489, -1, 414, 441),
- (466, 475, 0, 433, 423),
- (482, 459, -1, 442, 406),
- (498, 443, 0, 448, 376),
- (531, 410, 0, 460, 353),
- (559, 381, 1, 485, 323),
- (584, 357, 0, 509, 289),
- (597, 343, -1, 525, 279),
- (611, 330, 0, 543, 277),
- (628, 313, -1, 565, 255),
- (645, 296, 0, 575, 251), ## BIVIO
- (660, 296, -1, 595, 258),
- (675, 296, -1, 600, 265),
- (688, 296, 0, 625, 272),
- (699, 296, -1, 640, 258),
- (710, 296, -1, 650, 243),
- (732, 296, 0, 672, 236),
- (770, 296, 0, 702, 241),
- (784, 296, -1, 718, 246),
- (808, 296, 0, 736, 245),
- (820, 284, -1, 745, 238),
- (832, 272, 0, 753, 226),
- (859, 245, 0, 774, 191),
- (882, 222, 1, 795, 179))
- metro_b_two = ((645, 296, 0, 575, 251),
- (645, 274, -1, 579, 240),
- (645, 253, 0, 576, 221),
- (645, 231, -1, 556, 201),
- (645, 210, 0, 554, 188),
- (657, 198, -1, 554, 159),
- (669, 185, 0, 568, 142),
- (675, 179, -1, 595, 117),
- (681, 173, -1, 609, 80),
- (688, 167, -1, 603, 67),
- (694, 161, 1, 603, 55))
- metro_lines = ((metro_a, 0.921, 0.510, 0.122),
- (metro_c, 0.001, 0.529, 0.322),
- (metro_b_one, 0.001, 0.443, 0.737),
- (metro_b_two, 0.001, 0.443, 0.737))
- frames_list = []
- surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1500, 900)
- ctx = cairo.Context(surface)
- #############
- # Functions #
- #############
- def smoothstep(a, b, value):
- '''
- Based on javascript code found at <https://github.com/gre/smoothstep>
- Takes a start time (a), and end time (b), and the current time (value),
- and return what proportion (between 0 and 1.0) of the distance it should
- currently be at, using a smooth s-curve for displacement. Used by the
- transform() function.
- NOT USED: Swtiched to smootherstep()
- '''
- x = max(0, min(1, (value - a) / (b - a)))
- return x**2 * (3 - 2*x)
- def smootherstep(a, b, value):
- '''
- A smoother version of smoothstep()
- https://en.wikipedia.org/wiki/Smoothstep#Variations
- '''
- x = max(0, min(1, (value - a) / (b - a)))
- return x**3 * (x * (x*6 - 15) + 10)
- def transform(start, end, current_frame):
- '''
- Takes the starting and ending states of a value (an x or y coordinate or
- a size) and determines what that value should be at the current frame.
- '''
- #multiplier = current_frame / (FRAME_COUNT - 1) # Flat velocity
- multiplier = smootherstep(0, FRAME_COUNT, current_frame)
- return start + multiplier * (end - start)
- ###################################
- # Draw metro lines for each frame #
- ###################################
- for frame in range(FRAME_COUNT):
- # Background
- ctx.set_source_rgb(1.000, 1.000, 1.000) # white
- ctx.rectangle(0, 0, 1500, 900)
- ctx.fill()
- for metro_line, r, g, b in metro_lines:
- # Draw tracks
- ctx.set_line_cap(cairo.LINE_CAP_ROUND)
- ctx.set_line_join(cairo.LINE_JOIN_ROUND)
- ctx.set_source_rgb(r, g, b)
- line_width = transform(15, 4, frame)
- ctx.set_line_width(line_width)
- for i, coord in enumerate(metro_line):
- x = transform(coord[0], coord[3], frame)
- y = transform(coord[1], coord[4], frame)
- if i == 0:
- ctx.move_to(x, y)
- # for frame 0, skip actual stations, following only track bends
- # this makes the lines straighter
- if frame > 0 or coord[2] > 0:
- ctx.line_to(x, y)
- ctx.stroke()
- # Draw logos
- logoA = cairo.ImageSurface.create_from_png('LOGO/A.png')
- a = transform(944, 875, frame) ## X +20
- b = transform (722, 638, frame) ## Y -20
- ctx.set_source_surface(logoA, a, b)
- ctx.paint()
- logoB = cairo.ImageSurface.create_from_png('LOGO/B.png')
- a = transform(902, 815, frame)
- b = transform (202, 159, frame)
- ctx.set_source_surface(logoB, a, b)
- ctx.paint()
- logoC = cairo.ImageSurface.create_from_png('LOGO/C.png')
- a = transform(1177, 1395, frame)
- b = transform (722, 504, frame)
- ctx.set_source_surface(logoC, a, b)
- ctx.paint()
- # Draw stations
- ctx.set_source_rgb(1.000, 1.000, 1.000)
- for coord in metro_line:
- x = transform(coord[0], coord[3], frame)
- y = transform(coord[1], coord[4], frame)
- ctx.move_to(x, y)
- big_dot = transform(13, 3, frame)
- small_dot = transform(9, 3, frame)
- if coord[2] == 0:
- ctx.set_line_width(small_dot)
- ctx.line_to(x, y)
- elif coord[2] == 1:
- ctx.set_line_width(big_dot)
- ctx.line_to(x, y)
- ctx.stroke()
- # Write frame to file
- frame_name = 'frame_%d.png' % frame
- surface.write_to_png(frame_name)
- frames_list.append(frame_name)
- ################
- # Generate gif #
- ################
- images = []
- for filename in frames_list:
- images.append(io.imread(filename))
- io.imwrite('part_1.gif', images[0], duration = 1.00)
- io.mimwrite('part_2.gif', images, duration = FRAME_DURATION)
- io.imwrite('part_3.gif', images[-1], duration = 1.00)
- # To transition back to the start
- #io.mimwrite('part_4.gif', reversed(images), duration = FRAME_DURATION)
- for f in glob.glob('frame_*.png'):
- os.remove(f)
- result = sp.run(['gifsicle', '--colors', '256',
- 'part_1.gif', 'part_2.gif', 'part_3.gif'], # , 'part_4.gif'],
- stdout=sp.PIPE)
- with open('TEST.gif', 'wb') as f:
- f.write(result.stdout)
- for f in glob.glob('part_*.gif'):
- os.remove(f)
- sp.call(['xdg-open', 'TEST.gif'])
Add Comment
Please, Sign In to add comment