Skip to content

Instantly share code, notes, and snippets.

@stewartadam
Created January 16, 2017 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stewartadam/afe7153a3e8228f75c5b6bf70a1f261e to your computer and use it in GitHub Desktop.
Save stewartadam/afe7153a3e8228f75c5b6bf70a1f261e to your computer and use it in GitHub Desktop.
contour-lines-dataviz
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Calculates the x,y co-ordinates of the contour lines given a 2D grid of land
heights, with the origin in the bottom-left corner.
"""
import math
# cheap scope hack
CONTOURS = {}
def add(height, x, y):
"""
Associates a known position on the coordinate grid to its land height
"""
if not height in CONTOURS:
CONTOURS[height] = []
CONTOURS[height].append((x,y))
def calculate(data, base_coord, rowWise=True):
"""
Interpolates the position of contour lines on our coordinate system where ever
the height is a whole integer, given known heights on the coordinate grid.
If rowWise=True, then we are operating on a fixed row (y-axis) and across the
column (x-axis) values, and vice-versa.
"""
print "%s %d" % ("Row" if rowWise else "Col", base_coord[1] if rowWise else base_coord[0])
for index in range(len(data)-1):
start_h = data[index]
end_h = data[index+1]
delta_h = end_h - start_h
if int(start_h) == start_h:
if rowWise:
add(start_h, (base_coord[0]+index)*SQUARE_SIZE, base_coord[1]*SQUARE_SIZE)
else:
add(start_h, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index)*SQUARE_SIZE)
if int(end_h) == end_h:
if rowWise:
add(end_h, (base_coord[0]+index+1)*SQUARE_SIZE, base_coord[1]*SQUARE_SIZE)
else:
add(end_h, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index+1)*SQUARE_SIZE)
print '\t%s (%s, %s) = Height change from %.1f -> %.1f' % ("Col" if rowWise else "Row", index, index+1, start_h, end_h),
if abs(delta_h) < 1.0 or (abs(delta_h) == 1.0 and int(start_h) == start_h and int(end_h) == end_h):
print 'N/A'
else:
if delta_h > 0:
next_increment = int(start_h + 1) if start_h % 1 == 0 else int(math.ceil(start_h))
last_increment = int(end_h - 1) if end_h % 1 == 0 else int(math.floor(end_h))
else:
next_increment = int(start_h - 1) if start_h % 1 == 0 else int(math.floor(start_h))
last_increment = int(end_h + 1) if end_h % 1 == 0 else int(math.ceil(end_h))
m_per_increment = SQUARE_SIZE / delta_h
output = "= %.2f (%.2f/increment) | "
params = [delta_h, m_per_increment]
bounds = [next_increment, last_increment]
bounds.sort()
bounds[-1] = bounds[-1] + 1
for point in range(*bounds):
output += "%s=%.1fm, "
distance = (point - start_h) * m_per_increment
params.append(point)
params.append(distance)
if rowWise:
add(point, (base_coord[0]+index)*SQUARE_SIZE+distance, base_coord[1]*SQUARE_SIZE)
else:
add(point, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index)*SQUARE_SIZE+distance)
print output % tuple(params)
HEIGHTS = [
[5.5, 6, 6, 6, 5.5, 5.5, 5, 5, 4.5, 4, 4, 4, 3.5, 3, 2],
[11, 13, 13, 12, 11.5, 10.5, 8.5, 9, 9, 8.5, 7, 7.5, 6.5, 5, 2.5],
[10.5, 14.5, 14, 11.5, 12.5, 12, 11, 10.5, 10.5, 9, 9, 9.5, 8.5, 7, 3.5],
[10.5, 14.5, 12.5, 11, 10, 10.5, 10, 9.5, 9.5, 9.5, 10, 11, 10, 8, 5],
[6.5, 7.5, 8, 6.5, 5.5, 4.5, 3.5, 5, 7, 8, 8.5, 9.5, 8.5, 6.5, 2.5],
[6, 8.5, 11, 10, 11, 8.5, 6.5, 2.5, 2, 3, 7, 8, 7, 5.5, 3],
[7, 11, 10, 9.5, 8.5, 8, 7.5, 5.5, 4, 2.5, 3, 4.5, 5.5, 6, 3.5],
[5.5, 8, 8.5, 8, 8, 8, 7.5, 7.5, 7.5, 6, 5.5, 2.5, 3, 5, 4],
[3.5, 6.5, 6.5, 6.5, 7.5, 7, 6.5, 6, 7.5, 7, 5.5, 4, 0.5, 1.5, 2.5],
[3, 7, 6.5, 5.5, 5, 5, 5.5, 6, 6.5, 6, 5, 4.5, 2.5, 0.5, 0.5],
[2.5, 3, 2.5, 2.5, 3.5, 3, 3.5, 2.5, 2, 2, 2, 2, 1.5, 0.5, 0],
]
SQUARE_SIZE = 10.0
CONTOURS = {}
for i in range(len(HEIGHTS)):
row = HEIGHTS[i]
calculate(row, (0, i))
for j in range(len(HEIGHTS[0])):
col = [row[j] for row in HEIGHTS]
calculate(col, (j, 0), rowWise=False)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Displays visualization of contour lines based on calc.py, interactively.
Left/Right = Switch between heights
Enter = Cycle between heights automatically
Up/Down = Control FPS limit (animation speed)
c = toggle color
"""
import pygame
import calc
NUM_ROWS = len(calc.HEIGHTS) - 1
NUM_COLS = len(calc.HEIGHTS[0]) - 1
SCALE = 10
TRANSFORM = 0.5
WIDTH = (len(calc.HEIGHTS[0]) - 1) * calc.SQUARE_SIZE
HEIGHT = (len(calc.HEIGHTS) - 1) * calc.SQUARE_SIZE
FPS = 10
RADIUS = 3
def scale(d):
return int(d*SCALE)
def convert_xy(x, y):
return (scale(x), scale(HEIGHT-y))
def heatmap_color(intensity):
if intensity <= 0.25:
color = (0, 0, 255*intensity*2)
elif intensity <= 0.5:
color = (0, 255*intensity, 255*intensity)
elif intensity <= 0.75:
color = (255*intensity, 255*intensity, 0)
else:
color = (255*intensity, 64*intensity, 0)
return color
clock = pygame.time.Clock()
pygame.init()
screen = pygame.display.set_mode((scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM)))
surface = pygame.surface.Surface((scale(WIDTH), scale(HEIGHT)))
current_index = calc.CONTOURS.keys()[0]
contour_cycle = False
contour_color = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
current_index = (current_index + 1) % len(calc.CONTOURS)
if event.key == pygame.K_LEFT:
current_index = (current_index - 1) % len(calc.CONTOURS)
if event.key == pygame.K_UP:
FPS = min(FPS+2, 60)
if event.key == pygame.K_DOWN:
FPS = max(FPS-2, 5)
if event.key == pygame.K_RETURN:
contour_cycle = not contour_cycle
if event.key == pygame.K_c:
contour_color = not contour_color
RADIUS += 3 if contour_color else -3
if contour_cycle:
current_index = (current_index + 1) % len(calc.CONTOURS)
surface.fill((0, 0, 0))
for row in range(NUM_ROWS+1):
y = row*calc.SQUARE_SIZE
pygame.draw.line(surface, (128, 128, 128), convert_xy(0, y), convert_xy(WIDTH, y))
for col in range(NUM_COLS+1):
x = col*calc.SQUARE_SIZE
pygame.draw.line(surface, (128, 128, 128), convert_xy(x, 0), convert_xy(x, HEIGHT))
pygame.display.set_caption("height=%d" % calc.CONTOURS.keys()[current_index])
keys = calc.CONTOURS.keys()
min_index = min(keys)
max_index = max(keys)
for pos in calc.CONTOURS[keys[current_index]]:
intensity = float(current_index - min_index) / (max_index - min_index)
x = pos[0]
y = pos[1]
color = heatmap_color(intensity) if contour_color else (255, 255, 255)
pygame.draw.circle(surface, color, convert_xy(x,y), RADIUS, 0)
scaled = pygame.transform.scale(surface, (scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM)))
screen.blit(scaled.convert(), (0,0))
pygame.display.update()
clock.tick(FPS)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Displays visualization of all contour lines at once with a color scale.
"""
import pygame
import calc
NUM_ROWS = len(calc.HEIGHTS) - 1
NUM_COLS = len(calc.HEIGHTS[0]) - 1
SCALE = 10
TRANSFORM = 0.75
WIDTH = (len(calc.HEIGHTS[0]) - 1) * calc.SQUARE_SIZE
HEIGHT = (len(calc.HEIGHTS) - 1) * calc.SQUARE_SIZE
FPS = 1
RADIUS = 4
def scale(d):
return int(d*SCALE)
def convert_xy(x, y):
return (scale(x), scale(HEIGHT-y))
def heatmap_color(intensity):
if intensity <= 0.25:
color = (0, 0, 255*intensity*2)
elif intensity <= 0.5:
color = (0, 255*intensity, 255*intensity)
elif intensity <= 0.75:
color = (255*intensity, 255*intensity, 0)
else:
color = (255*intensity, 64*intensity, 0)
return color
clock = pygame.time.Clock()
pygame.init()
screen = pygame.display.set_mode((scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM)))
surface = pygame.surface.Surface((scale(WIDTH), scale(HEIGHT)))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
surface.fill((0, 0, 0))
for row in range(NUM_ROWS+1):
y = row*calc.SQUARE_SIZE
pygame.draw.line(surface, (128, 128, 128), convert_xy(0, y), convert_xy(WIDTH, y))
for col in range(NUM_COLS+1):
x = col*calc.SQUARE_SIZE
pygame.draw.line(surface, (128, 128, 128), convert_xy(x, 0), convert_xy(x, HEIGHT))
keys = calc.CONTOURS.keys()
min_index = min(keys)
max_index = max(keys)
for current_index in keys:
intensity = float(current_index - min_index) / (max_index - min_index)
for pos in calc.CONTOURS[current_index]:
x = pos[0]
y = pos[1]
pygame.draw.circle(surface, heatmap_color(intensity), convert_xy(x,y), RADIUS, 0)
scaled = pygame.transform.scale(surface, (scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM)))
screen.blit(scaled.convert(), (0,0))
pygame.display.update()
clock.tick(FPS)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment